'Français', 'de' => 'Allemand', 'it' => 'Italien', 'es' => 'Espagnol', 'pt' => 'Portugais', 'nl' => 'Néerlandais', 'en' => 'Anglais', 'ru' => 'Russe', 'pl' => 'Polonais', 'ro' => 'Roumain', 'el' => 'Grec', 'tr' => 'Turc', 'ar' => 'Arabe', 'kab' => 'Kabyle', 'ber' => 'Berbère', 'tzm' => 'Tamazight', 'da' => 'Danois', 'sv' => 'Suédois', 'no' => 'Norvégien', 'fi' => 'Finnois', 'cs' => 'Tchèque', 'sk' => 'Slovaque', 'hu' => 'Hongrois', 'bg' => 'Bulgare', 'hr' => 'Croate', 'sr' => 'Serbe', 'bs' => 'Bosnien', 'sq' => 'Albanais', 'mk' => 'Macédonien', 'sl' => 'Slovène', 'he' => 'Hébreu', 'lt' => 'Lituanien', 'lv' => 'Letton', 'et' => 'Estonien', ]; /** * Schéma des champs ACF : nom => type (ex : group, repeater, taxonomy, text...) */ public static $acf_schema = [ 'nom' => 'text', 'prenom' => 'text', 'email' => 'email', 'organisme' => 'taxonomy', 'langues_parlees' => 'taxonomy', 'jours_de_disponibilite' => 'checkbox', 'indisponibilitee_ponctuelle' => 'repeater', 'coordonnees' => 'group', 'commentaires' => 'textarea', 'type_de_fiche' => 'select', ]; public function __construct($data = []) { foreach ($data as $key => $value) { if (property_exists($this, $key)) { $this->$key = $value; } } } public static function get_traducteurs($filters = [],$simple_list = false) { $posts = get_posts([ 'post_type' => 'traducteur', 'numberposts' => -1, 'meta_query' => $filters, ]); if ($simple_list) { $posts = array_map(function($post) { return [ 'id' => $post->ID, 'nom' => $post->post_title, ]; }, $posts); } return $posts; } public static function load($id, $fields = []) { // Charger depuis CPT/meta $traducteur = get_post($id); if (!$traducteur) { return null; } // Si des champs spécifiques sont demandés, ne charger que ceux-ci if (!empty($fields)) { $data = []; foreach ($fields as $field) { if ($field === 'id') { $data['id'] = $traducteur->ID; } elseif (property_exists(self::class, $field)) { $data[$field] = get_field($field, $traducteur->ID); } } return new self($data); } // Sinon, charger tous les champs par défaut return new self([ 'id' => $traducteur->ID, 'nom' => get_field('nom', $traducteur->ID), 'prenom' => get_field('prenom', $traducteur->ID), 'email' => get_field('email', $traducteur->ID), 'langues_parlees' => get_field('langues_parlees', $traducteur->ID), 'jours_de_disponibilite' => get_field('jours_de_disponibilite', $traducteur->ID), 'indisponibilitee_ponctuelle' => get_field('indisponibilitee_ponctuelle', $traducteur->ID), 'organisme' => get_field('organisme', $traducteur->ID), 'commentaires' => get_field('commentaires', $traducteur->ID), 'type_de_fiche' => get_field('type_de_fiche', $traducteur->ID), ]); } public function save() { // À implémenter : sauvegarder dans CPT/meta return true; } /** * Retourne les traducteurs disponibles à une date donnée et pour une langue donnée. * @param string $date au format Y-m-d * @param string $langue_slug (slug de la langue) * @param int|null $event_id ID de l'événement en cours d'édition (pour inclure le traducteur actuel) * @return array Liste des traducteurs disponibles (CRVI_Traducteur_Model) */ public static function filtrer_disponibles($date, $langue_slug, $event_id = null) { if (empty($date)) { return []; } // Construire la requête de base $args = [ 'post_type' => 'traducteur', 'numberposts' => -1, ]; // Ajouter le filtre de langue seulement si spécifié if (!empty($langue_slug)) { $args['tax_query'] = [ [ 'taxonomy' => 'langue', 'field' => 'slug', 'terms' => $langue_slug, ], ]; } $traducteurs = get_posts($args); $disponibles = []; foreach ($traducteurs as $traducteur_post) { $traducteur = new self([ 'id' => $traducteur_post->ID, 'nom' => get_field('nom', $traducteur_post->ID), 'prenom' => get_field('prenom', $traducteur_post->ID), 'email' => get_field('email', $traducteur_post->ID), 'langues_parlees' => get_field('langues_parlees', $traducteur_post->ID), 'jours_de_disponibilite' => get_field('jours_de_disponibilite', $traducteur_post->ID), 'indisponibilitee_ponctuelle' => get_field('indisponibilitee_ponctuelle', $traducteur_post->ID), 'organisme' => get_field('organisme', $traducteur_post->ID), 'commentaires' => get_field('commentaires', $traducteur_post->ID), 'type_de_fiche' => get_field('type_de_fiche', $traducteur_post->ID), ]); // Vérifier si le traducteur est disponible $is_disponible = $traducteur->is_disponible($traducteur->id, $date, $langue_slug); // Vérifier les conflits d'événements existants if ($is_disponible) { $conflits = self::verifier_conflits_traducteur($traducteur->id, $date, $event_id); if (!empty($conflits)) { $is_disponible = false; } } // Si c'est l'événement en cours d'édition, inclure le traducteur même s'il est "pris" if ($event_id && self::is_traducteur_of_event($traducteur->id, $event_id)) { $is_disponible = true; } if ($is_disponible) { $disponibles[] = $traducteur; } } return $disponibles; } /** * Vérifie si un traducteur est associé à un événement donné * @param int $traducteur_id * @param int $event_id * @return bool */ private static function is_traducteur_of_event($traducteur_id, $event_id) { global $wpdb; $table_name = $wpdb->prefix . 'crvi_agenda'; $result = $wpdb->get_var($wpdb->prepare( "SELECT id FROM $table_name WHERE id = %d AND id_traducteur = %d", $event_id, $traducteur_id )); return !empty($result); } /** * Vérifie si ce traducteur est disponible à une date donnée et pour une langue donnée. * @param string $date au format Y-m-d * @param string|null $langue_slug * @return bool */ public function is_disponible($traducteur_id, $date, $langue_slug = null) { // Vérifier que la date n'est pas null ou vide if (empty($date)) { return false; } $timestamp = strtotime($date); if ($timestamp === false) { return false; } $jour = strtolower(date('l', $timestamp)); $jours_disponibles = get_field('jours_de_disponibilite', 'user_' . $traducteur_id); if (!is_array($jours_disponibles) || !in_array($jour, $jours_disponibles, true)) { return false; } $indisponibilites = get_field('indisponibilitee_ponctuelle', 'user_' . $traducteur_id); if (is_array($indisponibilites)) { foreach ($indisponibilites 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) { return false; } } } // Optionnel : vérifier la langue si besoin if ($langue_slug && is_array($this->langues_parlees) && !in_array($langue_slug, $this->langues_parlees)) { return false; } return true; } public function get_relations() { // À implémenter : récupérer les rendez-vous associés, etc. return []; } /** * Créer un traducteur. * @param array $data * @return int|WP_Error */ public static function create(array $data, bool $as_rest = false) { if (empty($data['nom']) || empty($data['prenom']) || empty($data['email'])) { if ($as_rest) { return [ 'success' => false, 'code' => 400, 'message' => 'Champs obligatoires manquants', 'data' => null ]; } return false; } // Chercher un traducteur existant par email $existing = get_posts([ 'post_type' => 'traducteur', 'meta_key' => 'email', 'meta_value' => $data['email'], 'post_status'=> 'any', 'numberposts'=> 1, ]); if ($existing) { // Mise à jour $post_id = $existing[0]->ID; // Mettre à jour le titre si nom/prenom fournis $post_title = ($data['nom'] ?? get_post_meta($post_id, 'nom', true)) . ' ' . ($data['prenom'] ?? get_post_meta($post_id, 'prenom', true)); wp_update_post([ 'ID' => $post_id, 'post_title' => $post_title, ]); foreach ($data as $key => $value) { if ($key === 'organisme') { if (!taxonomy_exists('organisme')) { register_taxonomy( 'organisme', 'traducteur', [ 'label' => 'Organismes', 'public' => false, 'hierarchical' => false, 'show_ui' => true, 'show_in_rest' => false, ] ); } $org_ids = []; $organismes = is_array($value) ? $value : explode('|', $value); foreach ($organismes as $org_nom) { $org_nom = trim($org_nom); if (empty($org_nom)) continue; $slug = sanitize_title($org_nom); $term = get_term_by('slug', $slug, 'organisme'); if (!$term) { $result = wp_insert_term($org_nom, 'organisme', ['slug' => $slug]); if (!is_wp_error($result) && isset($result['term_id'])) { $org_ids[] = $result['term_id']; } } else { $org_ids[] = $term->term_id; } } if (!empty($org_ids)) { update_field('organisme', $org_ids, $post_id); } } else if ($key === 'langues_parlees') { if (!taxonomy_exists('langue')) { register_taxonomy( 'langue', 'traducteur', [ 'label' => 'Langues', 'public' => false, 'hierarchical' => false, 'show_ui' => true, 'show_in_rest' => false, ] ); } $langue_ids = []; $langues = is_array($value) ? $value : explode('|', $value); foreach ($langues as $slug) { $slug = trim($slug); if (empty($slug)) continue; $term = get_term_by('slug', $slug, 'langue'); if (!$term) { $nom = self::$lg_map[$slug] ?? $slug; $result = wp_insert_term($nom, 'langue', ['slug' => $slug]); if (!is_wp_error($result) && isset($result['term_id'])) { $langue_ids[] = $result['term_id']; } } else { $langue_ids[] = $term->term_id; } } if (!empty($langue_ids)) { update_field('langue', $langue_ids, $post_id); } } else if (isset(self::$acf_schema[$key])) { self::set_acf_field($post_id, $key, $value, self::$acf_schema); } else { update_post_meta($post_id, $key, $value); } } return $post_id; } // Création classique $post_id = wp_insert_post([ 'post_type' => 'traducteur', 'post_title' => $data['nom'] . ' ' . $data['prenom'], 'post_status' => 'publish', ]); if (is_wp_error($post_id)) { if ($as_rest) { return [ 'success' => false, 'code' => 500, 'message' => 'Erreur lors de la création', 'data' => json_encode($post_id) ]; } return $post_id; } foreach ($data as $key => $value) { if ($key === 'organisme') { if (!taxonomy_exists('organisme')) { register_taxonomy( 'organisme', 'traducteur', [ 'label' => 'Organismes', 'public' => false, 'hierarchical' => false, 'show_ui' => true, 'show_in_rest' => false, ] ); } $org_ids = []; $organismes = is_array($value) ? $value : explode('|', $value); foreach ($organismes as $org_nom) { $org_nom = trim($org_nom); if (empty($org_nom)) continue; $slug = sanitize_title($org_nom); $term = get_term_by('slug', $slug, 'organisme'); if (!$term) { $result = wp_insert_term($org_nom, 'organisme', ['slug' => $slug]); if (!is_wp_error($result) && isset($result['term_id'])) { $org_ids[] = $result['term_id']; } } else { $org_ids[] = $term->term_id; } } if (!empty($org_ids)) { update_field('organisme', $org_ids, $post_id); } } else if ($key === 'langues_parlees') { if (!taxonomy_exists('langue')) { register_taxonomy( 'langue', 'traducteur', [ 'label' => 'Langues', 'public' => false, 'hierarchical' => false, 'show_ui' => true, 'show_in_rest' => false, ] ); } $langue_ids = []; $langues = is_array($value) ? $value : explode('|', $value); foreach ($langues as $slug) { $slug = trim($slug); if (empty($slug)) continue; $term = get_term_by('slug', $slug, 'langue'); if (!$term) { $nom = self::$lg_map[$slug] ?? $slug; $result = wp_insert_term($nom, 'langue', ['slug' => $slug]); if (!is_wp_error($result) && isset($result['term_id'])) { $langue_ids[] = $result['term_id']; } } else { $langue_ids[] = $term->term_id; } } if (!empty($langue_ids)) { update_field('langue', $langue_ids, $post_id); } } else if (isset(self::$acf_schema[$key])) { self::set_acf_field($post_id, $key, $value, self::$acf_schema); } else { update_post_meta($post_id, $key, $value); } } return $post_id; } /** * Mettre à jour un traducteur. * @param int $id * @param array $data * @return bool|WP_Error */ public function update(int $id, array $data) { if (!is_user_logged_in() || !get_current_user_id()) { return Api_Helper::json_error('Authentification requise', 401); } if (!current_user_can('edit_posts')) { return Api_Helper::json_error('Non autorisé', 403); } $post = get_post($id); if (!$post || $post->post_type !== 'traducteur') { return Api_Helper::json_error('Traducteur introuvable', 404); } // Vérifier unicité de l'email si modifié if (!empty($data['email'])) { $existing = get_posts([ 'post_type' => 'traducteur', 'meta_key' => 'email', 'meta_value' => $data['email'], 'post_status'=> 'any', 'exclude' => [$id], 'numberposts'=> 1, ]); if ($existing) { return Api_Helper::json_error('Email déjà utilisé', 409); } } $result = wp_update_post([ 'ID' => $id, 'post_title' => ($data['nom'] ?? get_post_meta($id, 'nom', true)) . ' ' . ($data['prenom'] ?? get_post_meta($id, 'prenom', true)), ], true); if (is_wp_error($result)) { return $result; } foreach ($data as $key => $value) { update_post_meta($id, $key, $value); } return true; } /** * Supprimer un traducteur (corbeille WordPress) * @param int $id * @return bool|WP_Error */ public function delete(int $id) { if (!is_user_logged_in() || !get_current_user_id()) { return Api_Helper::json_error('Authentification requise', 401); } if (!current_user_can('delete_posts')) { return Api_Helper::json_error('Non autorisé', 403); } $post = get_post($id); if (!$post || $post->post_type !== 'traducteur') { return Api_Helper::json_error('Traducteur introuvable', 404); } $result = wp_trash_post($id); if (!$result) { return Api_Helper::json_error('Erreur lors de la suppression', 500); } return true; } /** * Vérifie s'il y a des conflits d'événements pour un traducteur donné * @param int $traducteur_id * @param string $date * @param int|null $event_id - ID de l'événement à exclure (pour l'édition) * @return array */ private static function verifier_conflits_traducteur($traducteur_id, $date, $event_id = null) { global $wpdb; $table_events = $wpdb->prefix . 'crvi_agenda'; $where_conditions = ['id_traducteur = %d']; $where_values = [$traducteur_id]; // Exclure l'événement en cours d'édition si spécifié if ($event_id) { $where_conditions[] = 'id != %d'; $where_values[] = $event_id; } // Ajouter la condition de date $where_conditions[] = 'date_rdv = %s'; $where_values[] = $date; $where_clause = implode(' AND ', $where_conditions); $query = $wpdb->prepare( "SELECT * FROM {$table_events} WHERE {$where_clause}", $where_values ); return $wpdb->get_results($query, ARRAY_A); } }