$code]); } /** * Retourne une réponse API standardisée pour succès. * @param mixed $data * @return array */ public static function json_success($data): array { return [ 'success' => true, 'data' => $data, ]; } /** * Vérifie si l'utilisateur est prêt à être vérifié (WordPress initialisé + utilisateur connecté). * @return bool */ public static function is_user_ready(): bool { return did_action('init') && is_user_logged_in(); } /** * Vérifie si l'utilisateur courant a un rôle donné. * @param string|array $roles * @return bool */ public static function check_role($roles): bool { // S'assurer que WordPress est complètement chargé if (!did_action('init')) { return false; } // Vérifier si l'utilisateur est connecté if (!is_user_logged_in()) { return false; } // Récupérer l'utilisateur courant $user = wp_get_current_user(); // Vérifier que l'utilisateur est valide if (!$user || !$user->exists()) { return false; } // Vérifier que les rôles sont bien définis if (empty($user->roles) || !is_array($user->roles)) { return false; } if (is_array($roles)) { foreach ($roles as $role) { if (in_array($role, $user->roles, true)) { return true; } } return false; } else { return in_array($roles, $user->roles, true); } } /** * Récupère toutes les langues de la taxonomie 'langue_beneficiaire'. * @param bool $simple_list Si true, retourne un tableau simple [id => nom] * @return array */ public static function get_languages($simple_list = false): array { $terms = get_terms([ 'taxonomy' => 'langue_beneficiaire', 'hide_empty' => false, 'orderby' => 'name', 'order' => 'ASC', ]); if (is_wp_error($terms) || empty($terms)) { return []; } if ($simple_list) { $languages = []; foreach ($terms as $term) { $languages[] = [ 'id' => $term->term_id, 'nom' => $term->name, 'slug' => $term->slug, ]; } return $languages; } return $terms; } /** * Récupère une langue spécifique par son slug. * @param string $slug * @return array|null */ public static function get_language_by_slug($slug): ?array { $term = get_term_by('slug', $slug, 'langue_beneficiaire'); if (!$term || is_wp_error($term)) { return null; } return [ 'id' => $term->term_id, 'nom' => $term->name, 'slug' => $term->slug, ]; } /** * Vérifie si l'utilisateur peut créer des événements. * @return bool */ public static function can_create_events(): bool { // Les admins ont tous les droits if (self::check_role(['administrator', 'editor', 'crvi_manager'])) { return true; } // Les intervenants peuvent créer des événements pour eux-mêmes if (self::check_role(['intervenant'])) { return true; } return false; } /** * Vérifie si l'utilisateur peut modifier des événements. * @param int|null $event_id ID de l'événement (optionnel) * @return bool */ public static function can_edit_events($event_id = null): bool { // Les admins ont tous les droits if (self::check_role(['administrator', 'editor', 'crvi_manager'])) { return true; } // Les intervenants peuvent éditer leurs propres événements if (self::check_role(['intervenant'])) { // Si un event_id est fourni, vérifier que l'événement appartient à l'intervenant if ($event_id) { $model = new \ESI_CRVI_AGENDA\models\CRVI_Event_Model(); $event = $model->get_details($event_id); $current_user_id = get_current_user_id(); // Vérifier si l'utilisateur est l'intervenant assigné à cet événement if ($event && isset($event->id_intervenant) && $event->id_intervenant == $current_user_id) { return true; } return false; } // Sans event_id spécifique, autoriser (le filtre se fera au niveau de l'événement) return true; } return false; } /** * Vérifie si l'utilisateur peut supprimer des événements. * @param int|null $event_id ID de l'événement (optionnel) * @return bool */ public static function can_delete_events($event_id = null): bool { return self::check_role(['administrator', 'editor']); } /** * Vérifie si l'utilisateur peut clôturer des événements. * @param int|null $event_id ID de l'événement (optionnel) * @return bool */ public static function can_close_events($event_id = null): bool { return self::check_role(['administrator', 'editor', 'crvi_manager']); } /** * Vérifie si l'utilisateur peut voir les événements. * @return bool */ public static function can_view_events(): bool { // Les admins et viewers ont accès if (self::check_role(['administrator', 'editor', 'crvi_manager', 'crvi_viewer'])) { return true; } // Les intervenants peuvent voir leurs événements et ceux de leurs collègues if (self::check_role(['intervenant'])) { return true; } return false; } /** * Retourne toutes les permissions de l'utilisateur courant pour l'agenda. * @return array */ public static function get_user_permissions(): array { // Vérifier si l'utilisateur est connecté et chargé if (!is_user_logged_in()) { return [ 'can_create' => false, 'can_edit' => false, 'can_delete' => false, 'can_close' => false, 'can_view' => false, 'user_roles' => [], 'user_id' => 0, ]; } $user = wp_get_current_user(); $user_roles = $user && $user->exists() ? $user->roles : []; $user_id = get_current_user_id(); return [ 'can_create' => self::can_create_events(), 'can_edit' => self::can_edit_events(), 'can_delete' => self::can_delete_events(), 'can_close' => self::can_close_events(), 'can_view' => self::can_view_events(), 'user_roles' => $user_roles, 'user_id' => $user_id, ]; } /** * Valide les données d'un événement. * @param array $data Données de l'événement * @param string $action 'create' ou 'update' * @return array ['valid' => bool, 'errors' => array] */ public static function validate_event_data($data, $action = 'create'): array { $errors = []; $required_fields = [ 'date_rdv' => 'Date de rendez-vous', 'heure_rdv' => 'Heure de rendez-vous', 'id_beneficiaire' => 'Bénéficiaire', 'id_intervenant' => 'Intervenant', 'id_local' => 'Local', ]; // Vérifier les champs requis foreach ($required_fields as $field => $label) { if (empty($data[$field])) { $errors[] = "Le champ '$label' est requis."; } } // Validation spécifique pour la création if ($action === 'create') { // Vérifier que la date n'est pas dans le passé if (!empty($data['date_rdv'])) { $date_rdv = strtotime($data['date_rdv']); $today = strtotime('today'); if ($date_rdv < $today) { $errors[] = "La date de rendez-vous ne peut pas être dans le passé."; } } } // Validation de l'heure if (!empty($data['heure_rdv'])) { if (!preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $data['heure_rdv'])) { $errors[] = "Le format de l'heure n'est pas valide (HH:MM)."; } } // Validation de la date if (!empty($data['date_rdv'])) { $date = \DateTime::createFromFormat('Y-m-d', $data['date_rdv']); if (!$date || $date->format('Y-m-d') !== $data['date_rdv']) { $errors[] = "Le format de la date n'est pas valide (YYYY-MM-DD)."; } } // Validation des IDs (doivent être des entiers positifs) $id_fields = ['id_beneficiaire', 'id_intervenant', 'id_local', 'id_traducteur']; foreach ($id_fields as $field) { if (!empty($data[$field]) && (!is_numeric($data[$field]) || (int)$data[$field] <= 0)) { $errors[] = "L'ID du champ '$field' doit être un nombre entier positif."; } } // Validation de cohérence des dates/heures if (!empty($data['date_fin']) && !empty($data['date_rdv'])) { if ($data['date_fin'] < $data['date_rdv']) { $errors[] = "La date de fin doit être après ou égale à la date de début"; } elseif ($data['date_fin'] === $data['date_rdv'] && !empty($data['heure_fin']) && !empty($data['heure_rdv'])) { // Si même jour, vérifier que l'heure de fin est après l'heure de début if ($data['heure_fin'] <= $data['heure_rdv']) { $errors[] = "L'heure de fin doit être après l'heure de début pour un événement sur la même journée"; } } } return [ 'valid' => empty($errors), 'errors' => $errors, ]; } /** * Valide les données de disponibilité. * @param array $data Données de disponibilité * @return array ['valid' => bool, 'errors' => array] */ public static function validate_availability_data($data): array { $errors = []; // Vérifier que au moins une date est fournie if (empty($data['date']) && empty($data['date_rdv'])) { $errors[] = "Une date doit être fournie pour vérifier les disponibilités."; } // Validation de la date si fournie $date = $data['date'] ?? $data['date_rdv'] ?? null; if ($date) { $date_obj = \DateTime::createFromFormat('Y-m-d', $date); if (!$date_obj || $date_obj->format('Y-m-d') !== $date) { $errors[] = "Le format de la date n'est pas valide (YYYY-MM-DD)."; } } // Validation de l'heure si fournie $heure = $data['heure'] ?? $data['heure_rdv'] ?? null; if ($heure && !preg_match('/^([01]?[0-9]|2[0-3]):[0-5][0-9]$/', $heure)) { $errors[] = "Le format de l'heure n'est pas valide (HH:MM)."; } return [ 'valid' => empty($errors), 'errors' => $errors, ]; } /** * Vérifie les conflits de disponibilité. * @param string $date Date au format Y-m-d * @param string $heure Heure au format H:i * @param int $intervenant_id ID de l'intervenant * @param int $traducteur_id ID du traducteur * @param int $local_id ID du local * @param int|null $exclude_event_id ID de l'événement à exclure (pour l'édition) * @return array ['has_conflicts' => bool, 'conflicts' => array] */ public static function check_availability_conflicts($date, $heure, $intervenant_id, $traducteur_id, $local_id, $exclude_event_id = null): array { global $wpdb; $conflicts = []; // Vérifier les conflits pour l'intervenant if ($intervenant_id) { $intervenant_conflicts = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}crvi_agenda WHERE date_rdv = %s AND heure_rdv = %s AND id_intervenant = %d AND id != %d", $date, $heure, $intervenant_id, $exclude_event_id ?? 0 )); if (!empty($intervenant_conflicts)) { $conflicts['intervenant'] = $intervenant_conflicts; } } // Vérifier les conflits pour le traducteur if ($traducteur_id) { $traducteur_conflicts = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}crvi_agenda WHERE date_rdv = %s AND heure_rdv = %s AND id_traducteur = %d AND id != %d", $date, $heure, $traducteur_id, $exclude_event_id ?? 0 )); if (!empty($traducteur_conflicts)) { $conflicts['traducteur'] = $traducteur_conflicts; } } // Vérifier les conflits pour le local if ($local_id) { $local_conflicts = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$wpdb->prefix}crvi_agenda WHERE date_rdv = %s AND heure_rdv = %s AND id_local = %d AND id != %d", $date, $heure, $local_id, $exclude_event_id ?? 0 )); if (!empty($local_conflicts)) { $conflicts['local'] = $local_conflicts; } } return [ 'has_conflicts' => !empty($conflicts), 'conflicts' => $conflicts, ]; } /** * Récupère les options d'un champ ACF pour créer des éléments HTML dynamiques. * Basé sur get_field_object() d'Advanced Custom Fields. * * @param string $field_name Nom ou clé du champ ACF * @param int|false $post_id ID du post (optionnel, défaut: post courant) * @param bool $format_value Si true, applique le formatage (défaut: true) * @param bool $load_value Si true, charge la valeur du champ (défaut: true) * @param bool $escape_html Si true, échappe le HTML (défaut: false) * @return array|null Retourne un tableau avec les options formatées ou null si erreur */ public static function get_acf_field_options($field_name, $post_id = false, $format_value = true, $load_value = true, $escape_html = false): ?array { // Vérifier que ACF est disponible if (!function_exists('get_field_object')) { return null; } // Récupérer l'objet champ ACF $field_object = get_field_object($field_name, $post_id, $format_value, $load_value, $escape_html); if (!$field_object || is_wp_error($field_object)) { return null; } $result = [ 'field_info' => [ 'name' => $field_object['name'] ?? '', 'label' => $field_object['label'] ?? '', 'type' => $field_object['type'] ?? '', 'key' => $field_object['key'] ?? '', 'required' => (bool)($field_object['required'] ?? false), 'instructions' => $field_object['instructions'] ?? '', ], 'options' => [], 'current_value' => $field_object['value'] ?? null, 'html_attributes' => [ 'id' => $field_object['id'] ?? '', 'class' => $field_object['class'] ?? '', ] ]; // Traiter selon le type de champ switch ($field_object['type']) { case 'select': case 'checkbox': case 'radio': if (isset($field_object['choices']) && is_array($field_object['choices'])) { $result['options'] = []; foreach ($field_object['choices'] as $value => $label) { $result['options'][] = [ 'value' => $value, 'label' => $label, 'selected' => $field_object['value'] == $value ]; } } break; case 'true_false': $result['options'] = [ [ 'value' => '1', 'label' => $field_object['ui_on_text'] ?? 'Oui', 'selected' => (bool)$field_object['value'] ], [ 'value' => '0', 'label' => $field_object['ui_off_text'] ?? 'Non', 'selected' => !(bool)$field_object['value'] ] ]; break; case 'page_link': case 'post_object': if (isset($field_object['choices']) && is_array($field_object['choices'])) { $result['options'] = []; foreach ($field_object['choices'] as $value => $label) { $result['options'][] = [ 'value' => $value, 'label' => $label, 'selected' => $field_object['value'] == $value ]; } } break; case 'user': if (isset($field_object['choices']) && is_array($field_object['choices'])) { $result['options'] = []; foreach ($field_object['choices'] as $value => $label) { $result['options'][] = [ 'value' => $value, 'label' => $label, 'selected' => $field_object['value'] == $value ]; } } break; case 'taxonomy': if (isset($field_object['choices']) && is_array($field_object['choices'])) { $result['options'] = []; foreach ($field_object['choices'] as $value => $label) { $result['options'][] = [ 'value' => $value, 'label' => $label, 'selected' => $field_object['value'] == $value ]; } } break; case 'repeater': // Pour les repeaters, on retourne la structure mais pas les options $result['is_repeater'] = true; $result['sub_fields'] = isset($field_object['sub_fields']) ? $field_object['sub_fields'] : []; break; case 'flexible_content': // Pour le contenu flexible, on retourne les layouts disponibles $result['is_flexible'] = true; $result['layouts'] = isset($field_object['layouts']) ? $field_object['layouts'] : []; break; default: // Pour les autres types (text, textarea, etc.), on retourne juste les infos de base $result['is_simple_field'] = true; break; } return $result; } }