'Intervenants', 'labels' => [ 'name' => 'Intervenants', 'singular_name' => 'Intervenant', 'add_new' => 'Ajouter un intervenant', 'add_new_item' => 'Ajouter un nouvel intervenant', 'edit_item' => 'Modifier l\'intervenant', 'new_item' => 'Nouvel intervenant', 'view_item' => 'Voir l\'intervenant', 'search_items' => 'Rechercher un intervenant', 'not_found' => 'Aucun intervenant trouvé', 'not_found_in_trash' => 'Aucun intervenant dans la corbeille', ], 'public' => true, 'show_in_menu' => true, 'menu_position' => 22, 'menu_icon' => 'dashicons-businessman', 'supports' => ['title'], 'has_archive' => false, 'show_in_rest' => true, ]); self::register_taxonomy(); \register_taxonomy_for_object_type('departement', 'intervenant'); \register_taxonomy_for_object_type('type_intervention', 'intervenant'); } public static function register_taxonomy() { \register_taxonomy('departement', 'intervenant', [ 'label' => 'Départements', 'labels' => [ 'name' => 'Départements', 'singular_name' => 'Département', ], 'show_in_rest' => true, 'hierarchical' => true, 'rewrite' => ['slug' => 'departement'], 'show_admin_column' => true, 'query_var' => true, 'public' => true, 'show_in_menu' => true, ]); \register_taxonomy('type_intervention', 'intervenant', [ 'label' => 'Types d\'intervention', 'labels' => [ 'name' => 'Types d\'intervention', 'singular_name' => 'Type d\'intervention', ], 'show_in_rest' => true, 'hierarchical' => true, 'rewrite' => ['slug' => 'type-intervention'], 'show_admin_column' => true, 'query_var' => true, 'public' => true, 'show_in_menu' => true, ]); } /** * Retourne les intervenants disponibles à une date donnée. * @param string $date au format Y-m-d * @param string|null $departement (optionnel) * @param string|null $specialisation (optionnel) * @return array Liste des intervenants disponibles (WP_Post) */ public static function filtrer_disponibles($date, $departement = null, $specialisation = null) { if (empty($date)) { return []; } $timestamp = strtotime($date); $jour = strtolower(date('l', $timestamp)); // ex: 'monday' $jours_fr = [ 'monday' => 'lundi', 'tuesday' => 'mardi', 'wednesday' => 'mercredi', 'thursday' => 'jeudi', 'friday' => 'vendredi', 'saturday' => 'samedi', 'sunday' => 'dimanche', ]; $jour_semaine = $jours_fr[$jour] ?? ''; if (!$jour_semaine) return []; // 1. Récupérer les intervenants (filtrage taxonomie si besoin) $tax_query = []; if ($departement) { $tax_query[] = [ 'taxonomy' => 'departement', 'field' => 'slug', 'terms' => $departement, ]; } if ($specialisation) { $tax_query[] = [ 'taxonomy' => 'type_intervention', 'field' => 'slug', 'terms' => $specialisation, ]; } $args = [ 'post_type' => 'intervenant', 'numberposts' => -1, ]; if (!empty($tax_query)) { $args['tax_query'] = $tax_query; } $intervenants = get_posts($args); $disponibles = []; foreach ($intervenants as $intervenant) { // Vérifier jours de disponibilité $jours = get_field('jours_de_disponibilite', $intervenant->ID); if (!is_array($jours) || !in_array($jour_semaine, $jours, true)) { continue; } // Vérifier absences ponctuelles $absences = get_field('indisponibilitee_ponctuelle', $intervenant->ID); $est_absent = false; if (is_array($absences)) { foreach ($absences as $absence) { $debut = isset($absence['debut']) ? strtotime($absence['debut']) : null; $fin = isset($absence['fin']) ? strtotime($absence['fin']) : null; if ($debut && $fin && $timestamp >= $debut && $timestamp <= $fin) { $est_absent = true; break; } } } if ($est_absent) continue; $disponibles[] = $intervenant; } return $disponibles; } public static function register_routes() { register_rest_route('crvi/v1', '/intervenants', [ [ 'methods' => 'GET', 'callback' => [self::class, 'get_items'], 'permission_callback' => '__return_true', ], [ 'methods' => 'POST', 'callback' => [self::class, 'create_item'], 'permission_callback' => [self::class, 'can_edit'], ], ]); register_rest_route('crvi/v1', '/intervenants/import', [ [ 'methods' => 'POST', 'callback' => [self::class, 'import_csv'], 'permission_callback' => [self::class, 'can_edit'], 'args' => [ 'file' => [ 'required' => true, 'description' => 'Fichier CSV à importer', 'type' => 'file', ], ], ], ]); register_rest_route('crvi/v1', '/intervenants/(?P\\d+)', [ [ 'methods' => 'GET', 'callback' => [self::class, 'get_item'], 'permission_callback' => '__return_true', ], [ 'methods' => 'PUT,PATCH', 'callback' => [self::class, 'update_item'], 'permission_callback' => [self::class, 'can_edit'], ], [ 'methods' => 'DELETE', 'callback' => [self::class, 'delete_item'], 'permission_callback' => [self::class, 'can_delete'], ], ]); } public static function get_items($request) { $items = CRVI_Intervenant_Model::all(); return Api_Helper::json_success($items); } public static function get_item($request) { $id = (int) $request['id']; $item = CRVI_Intervenant_Model::load($id); if (!$item) { return Api_Helper::json_error('Intervenant introuvable', 404); } return Api_Helper::json_success($item); } public static function create_item($request) { $data = $request->get_json_params(); $validation = self::validate_intervenant_data($data); if (is_wp_error($validation)) { return Api_Helper::json_error($validation->get_error_message(), 400); } $model = new CRVI_Intervenant_Model(); $result = $model->create($data); if (is_wp_error($result)) { return $result; } return Api_Helper::json_success([ 'id' => $result, 'nom' => $data['nom'], 'prenom' => $data['prenom'] ?? '', 'message' => 'Intervenant créé avec succès' ]); } public static function update_item($request) { $id = (int) $request['id']; $model = new CRVI_Intervenant_Model(); $result = $model->update($id, $request->get_json_params()); if (is_wp_error($result)) { return $result; } return Api_Helper::json_success(['id' => $id]); } public static function delete_item($request) { $id = (int) $request['id']; $model = new CRVI_Intervenant_Model(); $result = $model->delete($id); if (is_wp_error($result)) { return $result; } return Api_Helper::json_success(['id' => $id, 'deleted' => true]); } /** * Import CSV d'intervenants via REST (aligné sur Traducteur_Controller) * @param WP_REST_Request $request * @return array */ public static function import_csv($request) { if (!current_user_can('edit_posts')) { return Api_Helper::json_error('Non autorisé', 403); } $file = $request->get_file_params()['file'] ?? null; if (!$file || !is_uploaded_file($file['tmp_name'])) { return Api_Helper::json_error('Fichier CSV manquant ou invalide', 400); } $handle = fopen($file['tmp_name'], 'r'); if (!$handle) { return Api_Helper::json_error('Impossible d\'ouvrir le fichier', 400); } $header = fgetcsv($handle, 0, ','); $created = $updated = $errors = 0; while (($row = fgetcsv($handle, 0, ',')) !== false) { $data = array_combine($header, $row); $data = array_combine( array_map(function($k) { return sanitize_title($k); }, array_keys($data)), array_map('trim', array_values($data)) ); // Conversion titres -> IDs pour departements_ids et types_intervention_ids foreach ([ 'departements_ids' => 'departement', 'types_intervention_ids' => 'type_intervention', ] as $csv_key => $cpt) { if (!empty($data[$csv_key])) { $titles = explode('|', $data[$csv_key]); $ids = []; foreach ($titles as $title) { $title = trim($title); if (!$title) continue; $slug = sanitize_title($title); $posts = get_posts([ 'post_type' => $cpt, 'name' => $slug, 'posts_per_page' => 1, 'post_status' => 'publish', 'fields' => 'ids', ]); if (!empty($posts)) { $ids[] = $posts[0]; } } // Adapter la clé pour le modèle (departements, specialisations) if ($csv_key === 'departements_ids') { $data['departements'] = $ids; } elseif ($csv_key === 'types_intervention_ids') { $data['specialisations'] = $ids; } } } $result = CRVI_Intervenant_Model::create($data, false); if (is_numeric($result)) $created++; else $errors++; } fclose($handle); $msg = "Créés: $created, Modifiés: $updated, Erreurs: $errors"; return Api_Helper::json_success(['message' => $msg]); } // Permissions personnalisées public static function can_edit() { $user = wp_get_current_user(); // Intervenant : lecture seule (GET autorisé via __return_true) if (in_array('intervenant', (array)$user->roles, true)) { return false; } // Admins ou autres rôles ayant edit_posts : accès autorisé return current_user_can('edit_posts'); } public static function can_delete() { return current_user_can('delete_posts'); } // Handler pour l'import CSV via formulaire admin public static function import_csv_admin() { if (!current_user_can('edit_users')) { wp_die('Non autorisé'); } check_admin_referer('crvi_import_intervenant'); if (empty($_FILES['import_csv']['tmp_name'])) { wp_redirect(admin_url('admin.php?page=crvi_agenda&import=error&msg=Fichier manquant')); exit; } $file = $_FILES['import_csv']['tmp_name']; $handle = fopen($file, 'r'); if (!$handle) { wp_redirect(admin_url('admin.php?page=crvi_agenda&import=error&msg=Impossible d\'ouvrir le fichier')); exit; } $header = fgetcsv($handle, 0, ','); $created = $updated = $errors = 0; while (($row = fgetcsv($handle, 0, ',')) !== false) { $data = array_combine($header, $row); $data = array_combine( array_map(function($k) { return sanitize_title($k); }, array_keys($data)), array_map('trim', array_values($data)) ); // Conversion titres -> IDs pour departements_ids et types_intervention_ids foreach ([ 'departements_ids' => 'departement', 'types_intervention_ids' => 'type_intervention', ] as $csv_key => $cpt) { if (!empty($data[$csv_key])) { $titles = explode('|', $data[$csv_key]); $ids = []; foreach ($titles as $title) { $title = trim($title); if (!$title) continue; $slug = sanitize_title($title); $posts = get_posts([ 'post_type' => $cpt, 'name' => $slug, 'posts_per_page' => 1, 'post_status' => 'publish', 'fields' => 'ids', ]); if (!empty($posts)) { $ids[] = $posts[0]; } else { // Fallback LIKE sur le slug global $wpdb; $like = '%' . $wpdb->esc_like($slug) . '%'; $sql = $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_type = %s AND post_status = 'publish' AND post_name LIKE %s LIMIT 1", $cpt, $like ); $like_id = $wpdb->get_var($sql); if ($like_id) { $ids[] = (int)$like_id; } } } if ($csv_key === 'departements_ids') { $data['departements'] = $ids; } elseif ($csv_key === 'types_intervention_ids') { $data['specialisations'] = $ids; } } } $result = CRVI_Intervenant_Model::create($data, false); if (is_numeric($result)) $created++; else $errors++; } fclose($handle); $msg = "Créés: $created, Modifiés: $updated, Erreurs: $errors"; wp_redirect(admin_url('admin.php?page=crvi_agenda&import=success&msg=' . urlencode($msg))); exit; } /** * Parse un champ répéteur au format pipe/point-virgule/deux-points. * @param string $value * @return array */ function parse_repeater_field(string $value): array { if (empty($value)) return []; $items = explode('|', $value); $result = []; foreach ($items as $item) { $fields = explode(';', $item); $assoc = []; foreach ($fields as $field) { [$key, $val] = array_pad(explode(':', $field, 2), 2, null); if ($key !== null) { $assoc[trim($key)] = trim($val ?? ''); } } if ($assoc) $result[] = $assoc; } return $result; } /** * Permet à un intervenant de modifier uniquement ses propres événements (liés à son intervenant_id). * @param int|null $event_intervenant_id L'ID de l'intervenant dans l'événement * @return bool */ public static function can_edit_own_event($event_intervenant_id = null) { $user = wp_get_current_user(); // Admins ou rôles ayant edit_posts : accès total if (current_user_can('edit_posts')) { return true; } // Intervenant : peut modifier uniquement ses propres événements if (in_array('intervenant', (array)$user->roles, true)) { // L'ID de l'intervenant dans l'événement doit correspondre à l'ID de l'utilisateur connecté if ($event_intervenant_id && (int)$event_intervenant_id === (int)$user->ID) { return true; } return false; } return false; } public static function validate_intervenant_data($data) { if (empty($data['nom'])) { return new \WP_Error('missing_nom', 'Le nom est obligatoire'); } if (!empty($data['email']) && !is_email($data['email'])) { return new \WP_Error('invalid_email', 'L\'email n\'est pas valide'); } return true; } }