XXX_Controller.php if (strpos($relative_class, 'controllers\\CRVI_') === 0) { $class_name = substr($relative_class, strlen('controllers\\CRVI_')); $possible_files[] = plugin_dir_path(__FILE__) . $class_name . '.php'; } // Pattern 2: controllers\XXX_Controller -> XXX_Controller.php (sans CRVI_) if (strpos($relative_class, 'controllers\\') === 0) { $class_name = substr($relative_class, strlen('controllers\\')); $possible_files[] = plugin_dir_path(__FILE__) . $class_name . '.php'; } // Essayer de charger le premier fichier qui existe foreach ($possible_files as $file) { if (file_exists($file)) { try { require_once $file; $file_loaded = true; break; } catch (\Throwable $e) { error_log("Erreur lors du chargement du fichier {$file}: " . $e->getMessage()); } } } // Si le fichier n'a pas pu être chargé, logger les détails if (!$file_loaded && !class_exists($controller)) { $debug_info = [ 'controller' => $controller, 'relative_class' => $relative_class, 'files_tried' => $possible_files, 'files_exist' => array_map('file_exists', $possible_files) ]; error_log("Classe {$controller} non trouvée lors de l'enregistrement des routes. Détails: " . print_r($debug_info, true)); continue; // Passer au contrôleur suivant } } if (class_exists($controller)) { try { // Vérifier que la méthode register_routes existe avant de l'appeler if (method_exists($controller, 'register_routes')) { $controller::register_routes(); } else { error_log("La méthode register_routes() n'existe pas pour la classe {$controller}"); } } catch (\Throwable $e) { // Capturer toutes les exceptions et erreurs (Exception, Error, etc.) $error_details = [ 'controller' => $controller, 'message' => $e->getMessage(), 'file' => $e->getFile(), 'line' => $e->getLine(), 'trace' => $e->getTraceAsString() ]; error_log("Erreur lors de l'enregistrement des routes pour {$controller}: " . print_r($error_details, true)); } } else { // Cette ligne ne devrait plus être atteinte si le chargement a réussi // Mais on la garde pour sécurité error_log("Classe {$controller} non trouvée après tentative de chargement manuel"); } } } public function load_actions() { add_action('init', [self::class, 'register_cpt']); add_action('rest_api_init', [self::class, 'register_routes']); // Enregistrer les routes REST API add_action('admin_menu', [self::class, 'add_admin_menu']); // Initialiser le contrôleur de notifications pour enregistrer les hooks AJAX new \ESI_CRVI_AGENDA\controllers\CRVI_Notifications_Controller(); add_action('admin_post_crvi_import_beneficiaire', [\ESI_CRVI_AGENDA\controllers\CRVI_Beneficiaire_Controller::class, 'import_csv_admin']); add_action('admin_post_crvi_import_local', [\ESI_CRVI_AGENDA\controllers\CRVI_Local_Controller::class, 'import_csv_admin']); add_action('admin_post_crvi_import_traducteur', [\ESI_CRVI_AGENDA\controllers\CRVI_Traducteur_Controller::class, 'import_csv_admin']); add_action('admin_post_crvi_import_intervenant', [\ESI_CRVI_AGENDA\controllers\CRVI_Intervenant_Controller::class, 'import_csv_admin']); add_action('admin_post_crvi_import_departement', [\ESI_CRVI_AGENDA\controllers\CRVI_Departement_Controller::class, 'import_csv_admin']); add_action('admin_post_crvi_import_type_intervention', [\ESI_CRVI_AGENDA\controllers\CRVI_Type_Intervention_Controller::class, 'import_csv_admin']); // Enqueue assets conditionnellement add_action('admin_enqueue_scripts', [self::class, 'enqueue_admin_assets']); add_action('wp_enqueue_scripts', [self::class, 'enqueue_front_assets']); // Localisation des scripts après que toutes les fonctions WordPress soient disponibles add_action('wp_loaded', [self::class, 'localize_scripts']); // Ajouter type="module" aux scripts ES6 add_filter('script_loader_tag', [self::class, 'add_module_type_to_scripts'], 10, 3); // Hook pour vérifier les indisponibilités lors de la sauvegarde d'un intervenant add_action('acf/save_post', [self::class, 'check_intervenant_availability_on_save'], 20); // Hook pour afficher les messages de conflit d'indisponibilités add_action('admin_notices', [self::class, 'display_intervenant_conflicts_notice']); // Hook pour vider le cache via paramètre URL add_action('admin_init', [self::class, 'handle_clear_cache_request']); // Redirection des utilisateurs non connectés depuis les pages intervenant add_action('template_redirect', [self::class, 'redirect_non_logged_from_intervenant_pages']); } /** * Ajoute la page d'admin Agenda CRVI avec sous-menus Hub, Stats, Agenda */ public static function add_admin_menu() { // Menu principal : Agenda CRVI (affiche le Hub) add_menu_page( __('Agenda CRVI', 'esi_crvi_agenda'), __('Agenda CRVI', 'esi_crvi_agenda'), 'edit_posts', 'crvi_agenda', [CRVI_Main_View::class, 'render_hub_admin_page'], 'dashicons-calendar-alt', 6 ); // Sous-menu : Hub (même callback que le menu principal) add_submenu_page( 'crvi_agenda', __('Hub', 'esi_crvi_agenda'), __('Hub', 'esi_crvi_agenda'), 'edit_posts', 'crvi_agenda', [CRVI_Main_View::class, 'render_hub_admin_page'] ); // Sous-menu : Stats add_submenu_page( 'crvi_agenda', __('Stats', 'esi_crvi_agenda'), __('Stats', 'esi_crvi_agenda'), 'edit_posts', 'crvi_agenda_stats', [self::class, 'render_stats_page'] ); // Sous-menu : Agenda add_submenu_page( 'crvi_agenda', __('Agenda', 'esi_crvi_agenda'), __('Agenda', 'esi_crvi_agenda'), 'edit_posts', 'crvi_agenda_hub', [CRVI_Agenda_View::class, 'render_agenda_page'] ); // Sous-menu : Gestion des permanences add_submenu_page( 'crvi_agenda', __('Permanences', 'esi_crvi_agenda'), __('Permanences', 'esi_crvi_agenda'), 'edit_posts', 'crvi_agenda_permanences', [self::class, 'render_permanences_page'] ); // Menu : Capacités de traduction CRVI_TraductionLangue_Controller::register_admin_page(); } /** * Affiche la page d'admin des stats */ public static function render_stats_page() { // Charger les mêmes données que pour la page agenda $locals = \ESI_CRVI_AGENDA\models\CRVI_Local_Model::get_locals([], true); $intervenants = \ESI_CRVI_AGENDA\models\CRVI_Intervenant_Model::get_intervenants([], true); $departements = \ESI_CRVI_AGENDA\models\CRVI_Departement_Model::all(true); $types_intervention = \ESI_CRVI_AGENDA\models\CRVI_Type_Intervention_Model::all(true); $traducteurs = \ESI_CRVI_AGENDA\models\CRVI_Traducteur_Model::get_traducteurs([], true); $langues_beneficiaire = \ESI_CRVI_AGENDA\helpers\Api_Helper::get_languages(true); // Récupérer tous les bénéficiaires $beneficiaire_model = new \ESI_CRVI_AGENDA\models\CRVI_Beneficiaire_Model(); $beneficiaires_objects = $beneficiaire_model->get_all_beneficiaires(); $beneficiaires = []; foreach ($beneficiaires_objects as $beneficiaire) { $beneficiaires[] = [ 'id' => $beneficiaire->id, 'nom' => $beneficiaire->nom . ' ' . $beneficiaire->prenom, ]; } $template = plugin_dir_path(__FILE__) . '../../templates/admin/agenda-stats-form.php'; if (file_exists($template)) { include $template; } else { echo '

Template agenda-stats-form.php introuvable.

'; } } /** * Affiche la page d'admin de gestion des permanences */ public static function render_permanences_page() { echo '

' . esc_html__('Gestion des Permanences', 'esi_crvi_agenda') . '

'; $template = plugin_dir_path(__FILE__) . '../../templates/admin/permanences-admin.php'; if (file_exists($template)) { include $template; } else { echo '

Template permanences-admin.php introuvable.

'; } echo '
'; } public static function load_filters() { /* add_filter('rest_endpoints', [self::class, 'register_routes']); */ add_filter('wp_script_attributes', [self::class, 'custom_script_tag'], 10, 2); // Redirection des intervenants après connexion vers leur espace add_filter('login_redirect', [self::class, 'redirect_intervenant_after_login'], 10, 3); } /** * Redirige les intervenants vers leur espace après connexion * * @param string $redirect_to URL de redirection par défaut * @param string $requested_redirect_to URL de redirection demandée * @param WP_User|WP_Error $user Utilisateur connecté ou erreur * @return string URL de redirection */ public static function redirect_intervenant_after_login($redirect_to, $requested_redirect_to, $user) { // Vérifier si l'utilisateur est valide et n'est pas une erreur if (is_wp_error($user) || !is_a($user, 'WP_User')) { return $redirect_to; } // Si une redirection spécifique a été demandée, la respecter if (!empty($requested_redirect_to)) { return $requested_redirect_to; } // Vérifier si l'utilisateur a le rôle 'intervenant' if (in_array('intervenant', $user->roles, true)) { // Rediriger vers l'espace intervenant return home_url('/espace-intervenant'); } // Pour les autres utilisateurs, utiliser la redirection par défaut return $redirect_to; } /** * Redirige les utilisateurs non connectés depuis les pages intervenant vers la home * Vérifie la page 'espace-intervenant' (ID 3307) et ses pages enfants */ public static function redirect_non_logged_from_intervenant_pages() { // Vérifier si l'utilisateur est connecté if (is_user_logged_in()) { return; } global $post; // ID de la page espace-intervenant $intervenant_page_id = 3307; // Vérifier si on est sur la page espace-intervenant ou une de ses pages enfants $is_intervenant_page = false; if ($post && $post->post_type === 'page') { // Vérifier si c'est la page espace-intervenant elle-même if ($post->ID == $intervenant_page_id) { $is_intervenant_page = true; } // Vérifier si c'est une page enfant de espace-intervenant if (!$is_intervenant_page && $post->post_parent) { $current_post = $post; // Remonter la hiérarchie pour vérifier si un parent est la page 3307 while ($current_post->post_parent) { $current_post = get_post($current_post->post_parent); if (!$current_post) { break; } if ($current_post->ID == $intervenant_page_id) { $is_intervenant_page = true; break; } } } } // Si on est sur une page intervenant et que l'utilisateur n'est pas connecté, rediriger vers la home if ($is_intervenant_page) { wp_redirect(home_url('/')); exit; } } public function load_shortcodes() { /* add_shortcode('crvi_agenda', [self::class, 'crvi_agenda_shortcode']); */ // Shortcodes pour l'espace intervenant add_shortcode('crvi_intervenant_hub', [\ESI_CRVI_AGENDA\controllers\Intervenant_Space_Controller::class, 'shortcode_hub']); add_shortcode('crvi_intervenant_agenda', [\ESI_CRVI_AGENDA\controllers\Intervenant_Space_Controller::class, 'shortcode_agenda']); add_shortcode('crvi_intervenant_profil', [\ESI_CRVI_AGENDA\controllers\Intervenant_Space_Controller::class, 'shortcode_profil']); add_shortcode('crvi_intervenant_permanences', [\ESI_CRVI_AGENDA\controllers\Intervenant_Space_Controller::class, 'shortcode_permanences']); } public function load_frontend_assets() { self::enqueue_admin_assets(); self::enqueue_front_assets(); } public function load_admin_assets() { } public function add_pages() { //hub admin } /** * Charge les assets uniquement sur les pages admin CRVI */ public static function enqueue_admin_assets() { // Les slugs des pages admin CRVI $crvi_pages = [ 'crvi_agenda', 'crvi_agenda_stats', 'crvi_agenda_hub', 'crvi_agenda_permanences', ]; $page = $_GET['page'] ?? ''; if (in_array($page, $crvi_pages, true)) { // Charger Select2 via WordPress wp_enqueue_style('select2', 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/css/select2.min.css', [], '4.0.13'); wp_enqueue_script('select2', 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.full.min.js', ['jquery'], '4.0.13', true); wp_enqueue_script('crvi_fa', 'https://kit.fontawesome.com/08d831faac.js', [], '6.0.0', true); // CSS des librairies (Bootstrap, Toastr, Select2) - compilé par Vite wp_enqueue_style('crvi_libraries_css', ESI_CRVI_AGENDA_URL . 'assets/js/dist/crvi_libraries.min.css', [], '1.0.0'); // CSS principal du plugin - compilé par Vite (inclut déjà agenda-visual-filters.css) wp_enqueue_style('crvi_main_css', ESI_CRVI_AGENDA_URL . 'assets/js/dist/crvi_main.min.css', ['crvi_libraries_css'], '1.0.0'); // Librairies externes (Bootstrap, Toastr, FullCalendar) - chargées en premier wp_enqueue_script('crvi_libraries', ESI_CRVI_AGENDA_URL . 'assets/js/dist/crvi_libraries.min.js', ['jquery', 'select2'], '1.0.0', true); // Script principal du plugin (modules locaux, inclut déjà agenda-visual-filters.js) - dépend des librairies wp_enqueue_script('crvi_main_agenda', ESI_CRVI_AGENDA_URL . 'assets/js/dist/crvi_main.min.js', ['crvi_libraries'], '1.0.0', true); self::localize_acf_data('crvi_main_agenda'); } } /** * Charge les assets uniquement sur la page publique crvi_agenda_interne * et sur les pages contenant les shortcodes de l'espace intervenant */ public static function enqueue_front_assets() { global $post; // Vérifier si on est sur la page crvi_agenda_interne $is_agenda_page = is_page('crvi_agenda_interne'); // Vérifier si on est sur une page de l'espace intervenant (par slug) $is_intervenant_page = false; $intervenant_page_slugs = [ 'espace-intervenant', 'espace-intervenant-profil', 'espace-intervenant-agenda', 'encodage-permanences' ]; // Vérifier si la page actuelle correspond à un slug intervenant if ($post && $post->post_type === 'page') { $page_slug = $post->post_name; // Vérifier directement le slug de la page if (in_array($page_slug, $intervenant_page_slugs, true)) { $is_intervenant_page = true; } // Vérifier si c'est une page enfant d'espace-intervenant if (!$is_intervenant_page && $post->post_parent) { $parent = get_post($post->post_parent); if ($parent && $parent->post_name === 'espace-intervenant') { $is_intervenant_page = true; } } // Vérifier aussi avec is_page() pour plus de robustesse if (!$is_intervenant_page) { foreach ($intervenant_page_slugs as $slug) { if (is_page($slug)) { $is_intervenant_page = true; break; } } } } // Vérifier aussi par le chemin de l'URL (pour les pages avec permalink hiérarchique) if (!$is_intervenant_page) { $current_url = $_SERVER['REQUEST_URI'] ?? ''; $current_path = parse_url($current_url, PHP_URL_PATH); if ($current_path && ( strpos($current_path, '/espace-intervenant') !== false || strpos($current_path, '/encodage-permanences') !== false )) { $is_intervenant_page = true; } } // Vérifier si la page contient un shortcode intervenant $has_intervenant_shortcode = false; if ($post && isset($post->post_content)) { $shortcodes = [ 'crvi_intervenant_hub', 'crvi_intervenant_agenda', 'crvi_intervenant_profil', 'crvi_intervenant_permanences' ]; foreach ($shortcodes as $shortcode) { if (has_shortcode($post->post_content, $shortcode)) { $has_intervenant_shortcode = true; break; } } } if ($is_agenda_page || $is_intervenant_page || $has_intervenant_shortcode) { wp_enqueue_script('crvi_fa', 'https://kit.fontawesome.com/08d831faac.js', [], '6.0.0', true); // CSS des librairies (Bootstrap, Toastr, Select2) - compilé par Vite wp_enqueue_style('crvi_libraries_css', ESI_CRVI_AGENDA_URL . 'assets/js/dist/crvi_libraries.min.css', [], '1.0.0'); // CSS principal du plugin - compilé par Vite wp_enqueue_style('crvi_main_css', ESI_CRVI_AGENDA_URL . 'assets/js/dist/crvi_main.min.css', ['crvi_libraries_css'], '1.0.0'); // CSS spécifique pour la page de profil intervenant (compilé par Vite) if ($is_intervenant_page && $post && $post->post_name === 'espace-intervenant-profil') { wp_enqueue_style('crvi_intervenant_profile_css', ESI_CRVI_AGENDA_URL . 'assets/js/dist/intervenant-profile.min.css', ['crvi_main_css'], '1.0.0'); } // Select2 JS via CDN (utilisé par crvi_libraries) wp_enqueue_script('select2', 'https://cdnjs.cloudflare.com/ajax/libs/select2/4.0.13/js/select2.full.min.js', ['jquery'], '4.0.13', true); // Librairies externes (Bootstrap, Toastr, Select2) - compilées par Vite wp_enqueue_script('crvi_libraries', ESI_CRVI_AGENDA_URL . 'assets/js/dist/crvi_libraries.min.js', ['jquery', 'select2'], '1.0.0', true); // Script principal du plugin (modules locaux) - dépend des librairies wp_enqueue_script('crvi_main_agenda', ESI_CRVI_AGENDA_URL . 'assets/js/dist/crvi_main.min.js', ['crvi_libraries'], '1.0.0', true); // Localiser les données si c'est une page intervenant if ($is_intervenant_page || $has_intervenant_shortcode) { self::localize_acf_data('crvi_main_agenda'); } } } /** * Récupère toutes les données ACF pour les couleurs et icônes * et les passe au JavaScript via wp_localize_script */ public static function localize_acf_data($script_handle) { // Vérifier que ACF est actif if (!function_exists('get_field')) { return; } $acf_data = [ 'departements' => self::get_departements_acf_data(), 'statuts' => self::get_statuts_acf_data(), 'types_local' => self::get_types_local_acf_data(), 'types_intervention' => self::get_types_intervention_acf_data(), 'intervenants' => self::get_intervenants_acf_data(), 'permissions' => self::get_user_permissions(), 'couleurs_rdv' => self::get_couleurs_rdv_acf_data(), 'couleurs_permanence' => self::get_couleurs_permanence_acf_data(), 'indisponibilites_intervenants' => self::get_intervenants_disponibilites(), 'beneficiaires' => self::get_beneficiaires_acf_data(), 'traductions_capacites' => self::get_traductions_capacites_data() ]; wp_localize_script($script_handle, 'crviACFData', $acf_data); } /** * Récupère les données des départements (icône et couleur) */ private static function get_departements_acf_data() { $departements = []; // Récupérer tous les départements $departements_posts = get_posts([ 'post_type' => 'departement', 'numberposts' => -1, 'post_status' => 'publish' ]); foreach ($departements_posts as $post) { $nom = get_field('nom', $post->ID); $icone = get_field('icone_departement', $post->ID); $couleur = get_field('couleur_departement', $post->ID); if ($nom) { $departements[strtolower($nom)] = [ 'id' => $post->ID, 'nom' => $nom, 'icone' => $icone ?: '📋', 'couleur' => $couleur ?: '#6c757d' ]; } } return $departements; } /** * Récupère les données des statuts de RDV */ private static function get_statuts_acf_data() { $statuts = []; // Récupérer les statuts depuis les options ACF $statuts_rdv = get_field('statut_rdv', 'option'); if (is_array($statuts_rdv)) { foreach ($statuts_rdv as $statut) { $nom = $statut['statut'] ?? ''; $couleur = $statut['couleur'] ?? ''; if ($nom) { $statuts[sanitize_title($nom)] = [ 'nom' => $nom, 'couleur' => $couleur ?: '#3788d8' ]; } } } // Valeurs par défaut si aucun statut configuré if (empty($statuts)) { $statuts = [ 'prevu' => ['nom' => 'Prévu', 'couleur' => '#28a745'], 'annule' => ['nom' => 'Annulé', 'couleur' => '#dc3545'], 'non_tenu' => ['nom' => 'Non tenu', 'couleur' => '#ffc107'], 'cloture' => ['nom' => 'Clôturé', 'couleur' => '#6c757d'], 'absence' => ['nom' => 'Absence', 'couleur' => '#fd7e14'] ]; } return $statuts; } /** * Récupère les données des types de local */ private static function get_types_local_acf_data() { $types_local = []; // Récupérer les types de local depuis les options ACF $types_local_config = get_field('type_de_local', 'option'); if (is_array($types_local_config)) { foreach ($types_local_config as $type) { $nom = $type['type'] ?? ''; $couleur = $type['couleur'] ?? ''; $icone = $type['icone'] ?? ''; if ($nom) { $types_local[strtolower($nom)] = [ 'nom' => $nom, 'couleur' => $couleur ?: '#6c757d', 'icone' => $icone ?: '🏢' ]; } } } return $types_local; } /** * Récupère les données des types d'intervention */ private static function get_types_intervention_acf_data() { $types_intervention = []; // Récupérer tous les types d'intervention (CPT) $types_intervention_posts = get_posts([ 'post_type' => 'type_intervention', 'numberposts' => -1, 'post_status' => 'publish', ]); foreach ($types_intervention_posts as $post) { $couleur = get_field('couleur', $post->ID); $icone = get_field('icone', $post->ID); $types_intervention[$post->ID] = [ 'id' => $post->ID, 'nom' => $post->post_title, 'couleur' => $couleur ?: '#6c757d', 'icone' => $icone ?: '📋' ]; } return $types_intervention; } /** * Récupère les données des intervennats (user role intervenant) */ private static function get_intervenants_acf_data() { $intervenants_data = []; $intervenants = get_users([ 'role' => 'intervenant' ]); //champs acf 'couleur' foreach ($intervenants as $intervenant) { $couleur = get_field('couleur', 'user_'.$intervenant->ID); $intervenants_data[$intervenant->ID] = [ 'id' => $intervenant->ID, 'nom' => $intervenant->display_name, 'couleur' => $couleur ?: '#6c757d' ]; } return $intervenants_data; } /** * Récupère les permissions de l'utilisateur actuel */ private static function get_user_permissions() { $user = wp_get_current_user(); return [ 'can_view' => current_user_can('read'), 'can_create' => current_user_can('edit_posts'), 'can_edit' => current_user_can('edit_posts'), 'can_delete' => current_user_can('delete_posts'), 'user_id' => $user->ID, 'user_roles' => $user->roles ]; } /** * Récupère les couleurs des RDV selon le type de local depuis les options ACF */ private static function get_couleurs_rdv_acf_data() { return [ 'bureau' => get_field('couleur_rdv_bureau', 'option') ?: '#6c757d', 'salle' => get_field('couleur_rdv_salle', 'option') ?: '#6c757d' ]; } /** * Récupère les couleurs des permanences depuis les options ACF */ private static function get_couleurs_permanence_acf_data() { return [ 'permanence' => get_field('couleur_permanence', 'option') ?: '#9e9e9e', 'permanence_non_attribuee' => get_field('couleur_permanence_non_attribuee', 'option') ?: '#9e9e9e', 'permanence_non_disponible' => get_field('couleur_permanence_non_disponible', 'option') ?: '#9e9e9e' ]; } /** * Localise les scripts après que toutes les fonctions WordPress soient disponibles */ public static function localize_scripts() { // Vérifier si le script crvi_main_agenda est enregistré if (!wp_script_is('crvi_main_agenda', 'registered')) { return; } // Ajouter le nonce pour l'authentification API wp_localize_script('crvi_main_agenda', 'wpApiSettings', [ 'nonce' => \wp_create_nonce('wp_rest'), 'root' => \esc_url_raw(\rest_url()) ]); // Transmettre les permissions utilisateur au JavaScript if (is_user_logged_in()) { $permissions = \ESI_CRVI_AGENDA\helpers\Api_Helper::get_user_permissions(); wp_localize_script('crvi_main_agenda', 'crviPermissions', $permissions); } else { // Permissions par défaut si l'utilisateur n'est pas connecté wp_localize_script('crvi_main_agenda', 'crviPermissions', [ 'can_create' => false, 'can_edit' => false, 'can_delete' => false, 'can_close' => false, 'can_view' => false, 'user_roles' => [], 'user_id' => 0, ]); } // Fournir l'URL AJAX et un nonce pour les appels admin-ajax (ex: debug SMS) // + données statiques avec cache (couleurs, permanences, disponibilités intervenants) wp_localize_script('crvi_main_agenda', 'crviAjax', [ 'url' => \admin_url('admin-ajax.php'), 'nonce' => \wp_create_nonce('crvi_sms'), 'couleurs_types_intervention' => self::get_couleurs_types_intervention(), 'couleur_permanence_non_attribuee' => self::get_couleur_permanence_non_attribuee(), 'couleur_permanence_non_disponible' => self::get_couleur_permanence_non_disponible(), 'intervenants_disponibilites' => self::get_intervenants_disponibilites() ]); } /** * Récupère les couleurs de tous les types d'intervention (id => couleur) * Utilise un transient de 12h pour le cache */ private static function get_couleurs_types_intervention() { $transient_key = 'crvi_couleurs_types_intervention'; $couleurs = get_transient($transient_key); if (false === $couleurs) { $couleurs = []; // Vérifier que ACF est actif if (!function_exists('get_field')) { return $couleurs; } // Récupérer tous les types d'intervention (CPT) $types_intervention_posts = get_posts([ 'post_type' => 'type_intervention', 'numberposts' => -1, 'post_status' => 'publish', ]); foreach ($types_intervention_posts as $post) { $couleur = get_field('couleur', $post->ID); $couleurs[$post->ID] = $couleur ?: '#6c757d'; } // Mettre en cache pour 12 heures (43200 secondes) set_transient($transient_key, $couleurs, 12 * HOUR_IN_SECONDS); } return $couleurs; } /** * Récupère la couleur de permanence non attribuée depuis les options ACF * Utilise un transient de 12h pour le cache */ private static function get_couleur_permanence_non_attribuee() { $transient_key = 'crvi_permanence_non_attribuee'; $couleur = get_transient($transient_key); if (false === $couleur) { // Vérifier que ACF est actif if (!function_exists('get_field')) { return '#9e9e9e'; } $couleur = get_field('couleur_permanence_non_attribuee', 'option') ?: '#9e9e9e'; // Mettre en cache pour 12 heures set_transient($transient_key, $couleur, 12 * HOUR_IN_SECONDS); } return $couleur; } /** * Récupère la couleur de permanence non disponible depuis les options ACF * Utilise un transient de 12h pour le cache */ private static function get_couleur_permanence_non_disponible() { $transient_key = 'crvi_permanence_non_disponible'; $couleur = get_transient($transient_key); if (false === $couleur) { // Vérifier que ACF est actif if (!function_exists('get_field')) { return '#9e9e9e'; } $couleur = get_field('couleur_permanence_non_disponible', 'option') ?: '#9e9e9e'; // Mettre en cache pour 12 heures set_transient($transient_key, $couleur, 12 * HOUR_IN_SECONDS); } return $couleur; } /** * Récupère les disponibilités de tous les intervenants * Structure: array de tous les intervenants avec jours_dispo => array, conges => array * Utilise un transient de 12h pour le cache */ private static function get_intervenants_disponibilites() { $transient_key = 'crvi_intervenants_disponibilites'; $intervenants_data = get_transient($transient_key); if (false === $intervenants_data) { $intervenants_data = []; // Vérifier que ACF est actif if (!function_exists('get_field')) { return $intervenants_data; } // Récupérer tous les intervenants (users avec role 'intervenant') $intervenants = get_users([ 'role' => 'intervenant' ]); foreach ($intervenants as $intervenant) { // Récupérer les jours de disponibilité (checkbox) $jours_dispo = get_field('jours_de_disponibilite', 'user_' . $intervenant->ID); if (!is_array($jours_dispo)) { $jours_dispo = []; } // Récupérer les indisponibilités ponctuelles (repeater) $indisponibilites = get_field('indisponibilitee_ponctuelle', 'user_' . $intervenant->ID); $conges = []; if (is_array($indisponibilites)) { foreach ($indisponibilites as $indispo) { $conges[] = [ 'debut' => $indispo['debut'] ?? '', 'fin' => $indispo['fin'] ?? '', 'type' => $indispo['type'] ?? '', 'commentaire' => $indispo['commentaire'] ?? '' ]; } } $intervenants_data[$intervenant->ID] = [ 'id' => $intervenant->ID, 'jours_dispo' => $jours_dispo, 'conges' => $conges ]; } // Mettre en cache pour 12 heures set_transient($transient_key, $intervenants_data, 3 * HOUR_IN_SECONDS); } return $intervenants_data; } /** * Récupère les données des bénéficiaires (notamment leur statut liste rouge) */ private static function get_beneficiaires_acf_data() { $transient_key = 'crvi_beneficiaires_liste_rouge'; $beneficiaires_data = get_transient($transient_key); if (false === $beneficiaires_data) { $beneficiaires_data = []; // Vérifier que ACF est actif if (!function_exists('get_field')) { return $beneficiaires_data; } // Récupérer tous les bénéficiaires $beneficiaires = get_posts([ 'post_type' => 'beneficiaire', 'numberposts' => -1, 'post_status' => 'publish' ]); foreach ($beneficiaires as $beneficiaire) { // Récupérer le champ ACF personne_en_liste_rouge $personne_en_liste_rouge = get_field('field_69495826ac495', $beneficiaire->ID); $beneficiaires_data[$beneficiaire->ID] = [ 'id' => $beneficiaire->ID, 'personne_en_liste_rouge' => (bool) $personne_en_liste_rouge ]; } // Mettre en cache pour 3 heures set_transient($transient_key, $beneficiaires_data, 3 * HOUR_IN_SECONDS); } return $beneficiaires_data; } /** * Récupère les capacités de traduction par langue avec ventilation par période et par jour * Format : langue_slug => {id, name, slug, total, remaining, by_periode: {matin/apres_midi: {total, remaining}}, by_jour: {lundi->dimanche: {total, remaining}}} * * @return array Capacités de traduction par langue */ private static function get_traductions_capacites_data() { // Vérifier que la classe du modèle existe if (!class_exists('\ESI_CRVI_AGENDA\models\CRVI_TraductionLangue_Model')) { return []; } // Calculer le début et la fin du mois courant $date_actuelle = current_time('Y-m-d'); $date_obj = new \DateTime($date_actuelle); $date_debut = $date_obj->format('Y-m-01'); // Premier jour du mois $date_fin = $date_obj->format('Y-m-t'); // Dernier jour du mois // Utiliser la méthode du modèle qui gère le cache avec la plage de dates $capacites = \ESI_CRVI_AGENDA\models\CRVI_TraductionLangue_Model::getAllLanguesCapacitesForACF($date_debut, $date_fin, true); error_log('🔍 Capacités de traduction récupérées pour le mois de ' . $date_debut . ' à ' . $date_fin . ': ' . print_r($capacites, true)); return $capacites; } /** * crvi+script loader */ public static function custom_script_tag(array $attr, $handle = null) { if (!isset($attr['id']) || 'crvi_main_agenda-js' !== $attr['id']) { return $attr; } // Le script est maintenant compilé avec toutes les dépendances, pas besoin de type module // $attr['type'] = 'module'; return $attr; } public static function write_log($message, $level = 'INFO') { $message = self::format_message_for_log($message); $log_entry = sprintf("[%s] [%s] %s\n", date('Y-m-d H:i:s'), $level, $message); error_log($log_entry); } /** * Écrit un message dans un fichier debug.txt sous le dossier uploads * Chemin: wp-content/uploads/crvi-debug.txt */ public static function write_debug_file($message, $level = 'INFO') { $message = self::format_message_for_log($message); $log_entry = sprintf("[%s] [%s] %s\n", date('Y-m-d H:i:s'), $level, $message); $upload_dir = wp_upload_dir(); $file_path = trailingslashit($upload_dir['basedir']) . 'crvi-debug.txt'; // Tente d'écrire le log. En cas d'échec, fallback sur error_log @file_put_contents($file_path, $log_entry, FILE_APPEND); // Écrire également dans le log PHP (wp-content/debug.log si WP_DEBUG_LOG actif, sinon error_log du serveur) error_log($log_entry); } private static function format_message_for_log($message) { if (is_array($message)) { return print_r($message, true); } elseif (is_object($message)) { return print_r($message, true); } elseif (is_bool($message)) { return $message ? 'TRUE' : 'FALSE'; } elseif (is_numeric($message)) { return (string) $message; } elseif (is_null($message)) { return 'NULL'; } else { return var_export($message, true); } } /** * Ajoute type="module" aux scripts ES6 pour permettre les imports/exports */ public static function add_module_type_to_scripts($tag, $handle, $src) { // Scripts qui utilisent ES modules $module_scripts = ['crvi_libraries', 'crvi_main_agenda']; if (in_array($handle, $module_scripts)) { // Remplacer type='text/javascript' par type='module' $tag = str_replace("type='text/javascript'", "type='module'", $tag); $tag = str_replace('type="text/javascript"', 'type="module"', $tag); // Si pas de type existant, l'ajouter if (strpos($tag, "type=") === false) { $tag = str_replace('