Crvi/app/controllers/Intervenant_Space_Controller.php
2026-01-20 07:54:37 +01:00

1098 lines
45 KiB
PHP

<?php
declare(strict_types=1);
namespace ESI_CRVI_AGENDA\controllers;
use ESI_CRVI_AGENDA\models\CRVI_Event_Model;
use ESI_CRVI_AGENDA\models\CRVI_Intervenant_Model;
use ESI_CRVI_AGENDA\models\CRVI_Local_Model;
use ESI_CRVI_AGENDA\models\CRVI_Departement_Model;
use ESI_CRVI_AGENDA\models\CRVI_Traducteur_Model;
use ESI_CRVI_AGENDA\models\CRVI_Type_Intervention_Model;
use ESI_CRVI_AGENDA\helpers\Api_Helper;
use WP_Error;
use WP_REST_Response;
/**
* Contrôleur pour l'espace intervenant
* Gère les endpoints API et les shortcodes pour l'interface frontend des intervenants
*/
class Intervenant_Space_Controller {
/**
* Enregistre les routes REST API
*/
public static function register_routes() {
// Récupération des RDV du jour
\register_rest_route('crvi/v1', '/intervenant/rdv-today', [
[
'methods' => 'GET',
'callback' => [self::class, 'get_rdv_today'],
'permission_callback' => [self::class, 'check_intervenant_permission'],
],
]);
// Marquage présence/absence
\register_rest_route('crvi/v1', '/intervenant/mark-presence', [
[
'methods' => 'POST',
'callback' => [self::class, 'mark_presence'],
'permission_callback' => [self::class, 'check_intervenant_permission'],
],
]);
// Récupération de l'agenda
\register_rest_route('crvi/v1', '/intervenant/agenda', [
[
'methods' => 'GET',
'callback' => [self::class, 'get_agenda'],
'permission_callback' => [self::class, 'check_intervenant_permission'],
],
]);
// Récupération du profil
\register_rest_route('crvi/v1', '/intervenant/profile', [
[
'methods' => 'GET',
'callback' => [self::class, 'get_profile'],
'permission_callback' => [self::class, 'check_intervenant_permission'],
],
[
'methods' => 'PUT',
'callback' => [self::class, 'update_profile'],
'permission_callback' => [self::class, 'check_intervenant_permission'],
],
]);
// Vérification des conflits d'indisponibilités
\register_rest_route('crvi/v1', '/intervenant/conflicts-check', [
[
'methods' => 'GET',
'callback' => [self::class, 'check_conflicts'],
'permission_callback' => [self::class, 'check_intervenant_permission'],
],
]);
}
/**
* Vérifie les permissions de l'intervenant
* @param \WP_REST_Request $request
* @return bool|\WP_Error
*/
public static function check_intervenant_permission($request = null) {
if (!is_user_logged_in()) {
return new WP_Error('unauthorized', 'Accès non autorisé', ['status' => 401]);
}
$user = wp_get_current_user();
// Vérifier si l'utilisateur est intervenant ou administrateur
if (!in_array('intervenant', $user->roles, true) && !current_user_can('administrator')) {
return new WP_Error('forbidden', 'Accès réservé aux intervenants', ['status' => 403]);
}
return true;
}
/**
* Vérifie les permissions admin
* @param \WP_REST_Request $request
* @return bool|\WP_Error
*/
public static function check_admin_permission($request = null) {
if (!is_user_logged_in()) {
return new WP_Error('unauthorized', 'Accès non autorisé', ['status' => 401]);
}
if (!current_user_can('manage_options')) {
return new WP_Error('forbidden', 'Accès réservé aux administrateurs', ['status' => 403]);
}
return true;
}
/**
* Récupère l'ID de l'intervenant à utiliser dans le contexte
* Si l'utilisateur est admin, retourne l'ID 11 (Jean Dupont) pour simulation
* Sinon, retourne l'ID de l'utilisateur connecté
* @return int
*/
private static function get_intervenant_id_for_context(): int {
$user = wp_get_current_user();
// Si l'utilisateur est admin, simuler l'intervenant ID 11 (Jean Dupont)
if (current_user_can('administrator')) {
return 11;
}
// Sinon, utiliser l'ID de l'utilisateur connecté
return get_current_user_id();
}
/**
* Récupère les rendez-vous du jour de l'intervenant connecté
* GET /wp-json/crvi/v1/intervenant/rdv-today
*/
public static function get_rdv_today($request) {
$intervenant_id = self::get_intervenant_id_for_context();
$intervenant = CRVI_Intervenant_Model::load($intervenant_id);
if (!$intervenant) {
return Api_Helper::json_error('Intervenant introuvable', 404);
}
$today = date('Y-m-d');
$model = new CRVI_Event_Model();
// Récupérer les RDV du jour pour cet intervenant
$events = $model->get_events_by_filters([
'intervenant' => $intervenant->id,
'date_debut' => $today,
'date_fin' => $today,
'statut' => 'prevu', // Uniquement les RDV prévus
]);
$formatted_events = [];
foreach ($events as $event) {
// Charger les détails de l'événement
$event_details = $model->get_details($event['id']);
$formatted_events[] = [
'id' => $event['id'],
'date_rdv' => $event['date_rdv'],
'heure_rdv' => $event['heure_rdv'],
'heure_fin' => $event['heure_fin'] ?? $event['heure_rdv'],
'statut' => $event['statut'],
'type' => $event['type'],
'beneficiaire' => $event_details->beneficiaire ?? null,
'local' => isset($event_details->local) ? [
'id' => $event_details->local->id ?? null,
'nom' => $event_details->local->nom ?? null,
] : null,
'traducteur' => isset($event_details->traducteur) ? [
'id' => $event_details->traducteur->id ?? null,
'nom' => $event_details->traducteur->nom ?? null,
'prenom' => $event_details->traducteur->prenom ?? null,
] : null,
'langue' => $event['langue'] ?? null,
'type_intervention' => $event['type_intervention'] ?? null,
'departement' => $event['departement'] ?? null,
'can_mark_presence' => true, // L'intervenant peut toujours marquer présence pour ses propres RDV
];
}
return Api_Helper::json_success($formatted_events);
}
/**
* Marque la présence ou l'absence pour un RDV
* POST /wp-json/crvi/v1/intervenant/mark-presence
*/
public static function mark_presence($request) {
$data = $request->get_json_params();
$event_id = isset($data['event_id']) ? (int)$data['event_id'] : 0;
$statut = isset($data['statut']) ? sanitize_text_field($data['statut']) : '';
if (!$event_id || !in_array($statut, ['present', 'absent'], true)) {
return Api_Helper::json_error('Paramètres invalides', 400);
}
$intervenant_id = self::get_intervenant_id_for_context();
$intervenant = CRVI_Intervenant_Model::load($intervenant_id);
if (!$intervenant) {
return Api_Helper::json_error('Intervenant introuvable', 404);
}
// Charger l'événement
$event = CRVI_Event_Model::load($event_id);
if (!$event) {
return Api_Helper::json_error('Rendez-vous introuvable', 404);
}
// Vérifier que l'intervenant est bien propriétaire du RDV
if ($event->id_intervenant != $intervenant->id) {
return Api_Helper::json_error('Vous ne pouvez marquer la présence que pour vos propres rendez-vous', 403);
}
// Vérifier que le RDV est prévu pour aujourd'hui
$today = date('Y-m-d');
if ($event->date !== $today) {
return Api_Helper::json_error('Vous ne pouvez marquer la présence que pour les rendez-vous du jour', 400);
}
// Vérifier que le statut actuel est "prevu"
if ($event->statut !== 'prevu') {
return Api_Helper::json_error('Ce rendez-vous a déjà été clôturé', 400);
}
// Mettre à jour le statut
$model = new CRVI_Event_Model();
$new_statut = $statut === 'present' ? 'present' : 'absent';
$result = $model->change_statut($event_id, ['statut' => $new_statut]);
if (is_wp_error($result)) {
return Api_Helper::json_error($result->get_error_message(), 500);
}
$message = $statut === 'present'
? 'Présence marquée avec succès'
: 'Absence enregistrée';
return Api_Helper::json_success(['message' => $message]);
}
/**
* Récupère l'agenda de l'intervenant (personnel ou collègues)
* GET /wp-json/crvi/v1/intervenant/agenda
*/
public static function get_agenda($request) {
$params = $request->get_params();
$intervenant_id = self::get_intervenant_id_for_context();
$intervenant = CRVI_Intervenant_Model::load($intervenant_id);
if (!$intervenant) {
return Api_Helper::json_error('Intervenant introuvable', 404);
}
$start = $params['start'] ?? date('Y-m-d');
$end = $params['end'] ?? date('Y-m-d', strtotime('+1 month'));
$view_mode = $params['view_mode'] ?? 'mine'; // 'mine' ou 'colleagues'
$filters = isset($params['filters']) ? json_decode($params['filters'], true) : [];
$model = new CRVI_Event_Model();
$filters_query = [
'date_debut' => $start,
'date_fin' => $end,
];
// Filtre par intervenant selon le mode de vue
if ($view_mode === 'mine') {
// Mon agenda : uniquement mes événements
$filters_query['intervenant'] = $intervenant->id;
} elseif ($view_mode === 'colleagues') {
// Agenda des collègues : tous les événements SAUF les permanences non attribuées
// On exclut les permanences avec assign = 0 (non attribuées)
$filters_query['exclude_unassigned_permanences'] = true;
}
// Appliquer les filtres supplémentaires
if (!empty($filters)) {
if (isset($filters['local_id'])) {
$filters_query['local'] = (int)$filters['local_id'];
}
if (isset($filters['beneficiaire_id'])) {
$filters_query['beneficiaire'] = (int)$filters['beneficiaire_id'];
}
if (isset($filters['type_rdv'])) {
$filters_query['type'] = sanitize_text_field($filters['type_rdv']);
}
if (isset($filters['type_intervention'])) {
$filters_query['type_intervention'] = (int)$filters['type_intervention'];
}
if (isset($filters['langue'])) {
$filters_query['langue'] = sanitize_text_field($filters['langue']);
}
if (isset($filters['permanences_non_assignees']) && $filters['permanences_non_assignees']) {
// Filtrer uniquement les permanences non assignées (assign = 0)
$filters_query['assign'] = 0;
}
}
$events = $model->get_events_by_filters($filters_query);
// Formater pour FullCalendar
$formatted_events = [];
foreach ($events as $event) {
$event_details = $model->get_details($event['id']);
$is_mine = isset($event['id_intervenant']) && $event['id_intervenant'] == $intervenant->id;
// Déterminer le nom de l'intervenant (nécessaire pour le titre des permanences)
$intervenant_nom = '';
if (!empty($event_details->intervenant)) {
$intervenant_nom = ($event_details->intervenant->prenom ?? '') . ' ' . ($event_details->intervenant->nom ?? '');
$intervenant_nom = trim($intervenant_nom);
}
// Déterminer le titre
$title = '';
if (!empty($event_details->beneficiaire)) {
$title = 'RDV - ' . ($event_details->beneficiaire->prenom ?? '') . ' ' . ($event_details->beneficiaire->nom ?? '');
} elseif ($event['type'] === 'permanence') {
$title = 'p. ' . ($intervenant_nom ?: 'Intervenant');
} else {
$title = 'Événement';
}
// Récupérer le nom de la langue depuis la taxonomie
$langue_nom = null;
if (!empty($event['langue'])) {
// Essayer d'abord par ID numérique
$langue_term = get_term((int)$event['langue'], 'langue');
if ($langue_term && !is_wp_error($langue_term)) {
$langue_nom = $langue_term->name;
} else {
// Essayer par slug si l'ID ne fonctionne pas
$langue_term = get_term_by('slug', $event['langue'], 'langue');
if ($langue_term && !is_wp_error($langue_term)) {
$langue_nom = $langue_term->name;
}
}
}
// Récupérer le nom du département depuis les CPT
$departement_nom = null;
if (!empty($event['departement'])) {
$departement_post = get_post((int)$event['departement']);
if ($departement_post) {
$departement_nom = $departement_post->post_title;
}
}
// Récupérer le nom du type d'intervention depuis les CPT
$type_intervention_nom = null;
if (!empty($event['type_intervention'])) {
$type_post = get_post((int)$event['type_intervention']);
if ($type_post) {
$type_intervention_nom = $type_post->post_title;
}
}
$formatted_events[] = [
'id' => $event['id'],
'title' => $title,
'start' => $event['date_rdv'] . 'T' . $event['heure_rdv'],
'end' => ($event['date_fin'] ?? $event['date_rdv']) . 'T' . ($event['heure_fin'] ?? $event['heure_rdv']),
'id_type_intervention' => $event['type_intervention'] ?? null,
'id_departement' => $event['id_departement'] ?? ($event['departement'] ?? null),
'assign' => isset($event['assign']) ? (int)$event['assign'] : 0,
'type' => $event['type'] ?? 'individuel',
'statut' => $event['statut'] ?? 'prevu',
'langue' => $langue_nom, // Utiliser le nom au lieu de l'ID/slug
'departement' => $departement_nom, // Utiliser le nom au lieu de l'ID
'type_intervention' => $type_intervention_nom, // Utiliser le nom au lieu de l'ID
'is_mine' => $is_mine,
'intervenant_nom' => $intervenant_nom,
'beneficiaire' => !empty($event_details->beneficiaire)
? ($event_details->beneficiaire->prenom ?? '') . ' ' . ($event_details->beneficiaire->nom ?? '')
: null,
'local' => !empty($event_details->local) ? $event_details->local->nom : null,
'traducteur' => !empty($event_details->traducteur)
? (($event_details->traducteur->prenom ?? '') . ' ' . ($event_details->traducteur->nom ?? ''))
: null,
'show_comments' => $is_mine, // Les commentaires sont visibles uniquement pour les propres RDV
'commentaire' => $is_mine ? ($event['commentaire'] ?? null) : null,
];
}
return Api_Helper::json_success($formatted_events);
}
/**
* Récupère le profil de l'intervenant connecté
* GET /wp-json/crvi/v1/intervenant/profile
*/
public static function get_profile($request) {
// Utiliser le contexte intervenant (si admin, utiliser l'ID simulé)
$intervenant_id = self::get_intervenant_id_for_context();
$user = get_user_by('id', $intervenant_id);
if (!$user) {
return Api_Helper::json_error('Utilisateur introuvable', 404);
}
$intervenant = CRVI_Intervenant_Model::load($intervenant_id);
if (!$intervenant) {
return Api_Helper::json_error('Intervenant introuvable', 404);
}
// Récupérer les champs ACF
$telephone = function_exists('get_field') ? get_field('telephone', 'user_' . $intervenant_id) : '';
$departements_acf = function_exists('get_field') ? get_field('departements', 'user_' . $intervenant_id) : [];
$specialisations_acf = function_exists('get_field') ? get_field('specialisations', 'user_' . $intervenant_id) : [];
$jours_disponibilite = function_exists('get_field') ? get_field('jours_de_disponibilite', 'user_' . $intervenant_id) : [];
$indisponibilites_ponctuelles = function_exists('get_field') ? get_field('indisponibilitee_ponctuelle', 'user_' . $intervenant_id) : [];
$commentaires = function_exists('get_field') ? get_field('commentaires', 'user_' . $intervenant_id) : '';
$couleur = function_exists('get_field') ? get_field('couleur', 'user_' . $intervenant_id) : '';
$heures_de_permanences = function_exists('get_field') ? get_field('heures_de_permanences', 'user_' . $intervenant_id) : [];
// Formater les départements (ACF retourne des objets post_object avec return_format: "object")
$departements = [];
if (!empty($departements_acf)) {
// Normaliser en tableau si ce n'est pas déjà le cas
$departements_acf = is_array($departements_acf) ? $departements_acf : [$departements_acf];
foreach ($departements_acf as $dept) {
// Si c'est un objet WP_Post, utiliser directement
if (is_object($dept) && isset($dept->ID)) {
$departements[] = [
'id' => $dept->ID,
'nom' => $dept->post_title ?? '',
];
}
// Si c'est un ID numérique, récupérer le post
elseif (is_numeric($dept)) {
$post = get_post((int)$dept);
if ($post) {
$departements[] = [
'id' => $post->ID,
'nom' => $post->post_title,
];
}
}
}
}
// Formater les spécialisations (types d'intervention) - ACF retourne des objets post_object avec return_format: "object"
$specialisations = [];
if (!empty($specialisations_acf)) {
// Normaliser en tableau si ce n'est pas déjà le cas
$specialisations_acf = is_array($specialisations_acf) ? $specialisations_acf : [$specialisations_acf];
foreach ($specialisations_acf as $spec) {
// Si c'est un objet WP_Post, utiliser directement
if (is_object($spec) && isset($spec->ID)) {
$specialisations[] = [
'id' => $spec->ID,
'nom' => $spec->post_title ?? '',
];
}
// Si c'est un ID numérique, récupérer le post
elseif (is_numeric($spec)) {
$post = get_post((int)$spec);
if ($post) {
$specialisations[] = [
'id' => $post->ID,
'nom' => $post->post_title,
];
}
}
}
}
// Formater les indisponibilités ponctuelles
// ACF retourne les dates au format d/m/Y (return_format: "d/m/Y")
$indisponibilites_formatted = [];
if (!empty($indisponibilites_ponctuelles) && is_array($indisponibilites_ponctuelles)) {
foreach ($indisponibilites_ponctuelles as $indispo) {
// Les champs ACF sont: debut, fin, type, commentaire
$debut = $indispo['debut'] ?? '';
$fin = $indispo['fin'] ?? '';
// Les dates sont déjà au format d/m/Y depuis ACF
// Si elles sont vides ou invalides, on les laisse telles quelles
$indisponibilites_formatted[] = [
'debut' => $debut,
'fin' => $fin ?: $debut, // Si pas de date de fin, utiliser la date de début
'type' => $indispo['type'] ?? 'conge',
'commentaire' => $indispo['commentaire'] ?? '',
];
}
}
return Api_Helper::json_success([
'id' => $intervenant_id,
'nom' => $user->last_name,
'prenom' => $user->first_name,
'email' => $user->user_email,
'telephone' => $telephone ?? '',
'jours_de_disponibilite' => $jours_disponibilite ?: [],
'heures_de_permanences' => is_array($heures_de_permanences) ? $heures_de_permanences : [],
'indisponibilites_ponctuelles' => $indisponibilites_formatted,
'departements' => $departements,
'specialisations' => $specialisations,
'commentaires' => $commentaires ?? '',
'couleur' => $couleur ?? '',
]);
}
/**
* Met à jour le profil de l'intervenant connecté
* PUT /wp-json/crvi/v1/intervenant/profile
*/
public static function update_profile($request) {
// Aligner avec le contexte intervenant (si admin, utiliser l'ID simulé)
$user_id = self::get_intervenant_id_for_context();
$data = $request->get_json_params();
if (!function_exists('update_field')) {
return Api_Helper::json_error('ACF n\'est pas disponible', 500);
}
// Map des clés ACF (selon export ACF fourni)
$acf_keys = [
'telephone' => 'field_685bf3fab0747',
'jours_de_disponibilite' => 'field_685bfebbf3ef0',
'indisponibilitee_ponctuelle' => 'field_685bffc4f02c6',
'heures_de_permanences' => 'field_69178ab1a9f6c',
'indispo_debut' => 'field_685c00077197d',
'indispo_fin' => 'field_685c00237197e',
'indispo_type' => 'field_685c00307197f',
'indispo_commentaire' => 'field_685c004271980',
];
$updated = false;
$errors = [];
// Debug: journaliser les données reçues (sans données sensibles)
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_start',
'user_id' => $user_id,
'payload_keys' => array_keys((array)$data),
]);
// Mise à jour du téléphone
if (isset($data['telephone'])) {
$telephone = sanitize_text_field($data['telephone']);
// Idempotent: si la valeur est identique, ne pas traiter comme erreur
$current_telephone = function_exists('get_field') ? get_field('telephone', 'user_' . $user_id) : '';
if ((string) $current_telephone === (string) $telephone) {
$updated = true;
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_telephone_no_change',
'user_id' => $user_id,
'telephone' => $telephone,
], 'DEBUG');
} else {
// Utiliser la clé de champ ACF pour éviter toute ambiguïté
$result = update_field($acf_keys['telephone'], $telephone, 'user_' . $user_id);
if ($result !== false) {
$updated = true;
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_telephone_updated',
'user_id' => $user_id,
'telephone' => $telephone,
'result' => $result,
], 'INFO');
} else {
$errors[] = 'Erreur lors de la mise à jour du téléphone';
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_telephone_error',
'user_id' => $user_id,
'telephone' => $telephone,
'result' => $result,
], 'ERROR');
}
}
}
// Mise à jour des heures de permanences
if (isset($data['heures_de_permanences'])) {
$heures = $data['heures_de_permanences'];
if (is_array($heures)) {
// Valider les choix (selon ACF)
$choix_valides = ['09:00','10:00','11:00','12:00','13:00','14:00','15:00','16:00'];
$heures = array_values(array_filter($heures, function($h) use ($choix_valides) {
return in_array($h, $choix_valides, true);
}));
$normalize_hours = function (array $arr): array {
$filtered = array_filter(array_map('strval', $arr));
sort($filtered);
return $filtered;
};
$current_hours = function_exists('get_field') ? get_field('heures_de_permanences', 'user_' . $user_id) : [];
$current_hours = is_array($current_hours) ? $current_hours : [];
$expected_hours = $normalize_hours($heures);
$actual_hours = $normalize_hours($current_hours);
if ($expected_hours === $actual_hours) {
$updated = true;
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_heures_no_change',
'user_id' => $user_id,
'heures' => $expected_hours,
], 'DEBUG');
} else {
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_heures_attempt',
'user_id' => $user_id,
'expected' => $expected_hours,
'current' => $actual_hours,
], 'INFO');
$result = update_field($acf_keys['heures_de_permanences'], $expected_hours, 'user_' . $user_id);
if ($result !== false) {
$updated = true;
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_heures_updated',
'user_id' => $user_id,
'result' => $result,
], 'INFO');
} else {
$post_update = function_exists('get_field') ? get_field('heures_de_permanences', 'user_' . $user_id) : [];
$post_norm = $normalize_hours(is_array($post_update) ? $post_update : []);
if ($post_norm === $expected_hours) {
$updated = true;
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_heures_updated_after_check',
'user_id' => $user_id,
'heures' => $post_norm,
], 'INFO');
} else {
$errors[] = 'Erreur lors de la mise à jour des heures de permanences';
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_heures_error',
'user_id' => $user_id,
'expected' => $expected_hours,
'actual_after' => $post_norm,
], 'ERROR');
}
}
}
} else {
$errors[] = 'Format invalide pour les heures de permanences';
}
}
// Mise à jour des jours de disponibilité
if (isset($data['jours_de_disponibilite'])) {
$jours_disponibilite = $data['jours_de_disponibilite'];
// Valider que c'est un tableau
if (is_array($jours_disponibilite)) {
// Valider les valeurs (doivent être parmi les jours valides)
$jours_valides = ['lundi', 'mardi', 'mercredi', 'jeudi', 'vendredi', 'samedi', 'dimanche'];
$jours_disponibilite = array_filter($jours_disponibilite, function($jour) use ($jours_valides) {
return in_array($jour, $jours_valides, true);
});
// Idempotent: comparer avec la valeur actuelle avant d'appeler update_field
$current_jours = function_exists('get_field') ? get_field('jours_de_disponibilite', 'user_' . $user_id) : [];
$current_jours = is_array($current_jours) ? $current_jours : [];
$normalize_days = function (array $days): array {
$filtered = array_filter(array_map('strval', $days));
sort($filtered);
return $filtered;
};
$expected_days = $normalize_days(array_values($jours_disponibilite));
$actual_days = $normalize_days($current_jours);
if ($expected_days === $actual_days) {
$updated = true;
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_jours_no_change',
'user_id' => $user_id,
'jours' => $expected_days,
], 'DEBUG');
} else {
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_jours_attempt',
'user_id' => $user_id,
'expected' => $expected_days,
'current' => $actual_days,
], 'INFO');
// Utiliser la clé ACF pour les jours
$result = update_field($acf_keys['jours_de_disponibilite'], $expected_days, 'user_' . $user_id);
if ($result !== false) {
$updated = true;
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_jours_updated',
'user_id' => $user_id,
'result' => $result,
], 'INFO');
} else {
// Vérifier après tentative
$post_update_days = function_exists('get_field') ? get_field('jours_de_disponibilite', 'user_' . $user_id) : [];
$post_update_days = is_array($post_update_days) ? $post_update_days : [];
$post_norm = $normalize_days($post_update_days);
if ($post_norm === $expected_days) {
$updated = true;
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_jours_updated_after_check',
'user_id' => $user_id,
'jours' => $post_norm,
], 'INFO');
} else {
$errors[] = 'Erreur lors de la mise à jour des jours de disponibilité';
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_jours_error',
'user_id' => $user_id,
'expected' => $expected_days,
'actual_after' => $post_norm,
], 'ERROR');
}
}
}
} else {
$errors[] = 'Format invalide pour les jours de disponibilité';
}
}
// Mise à jour des indisponibilités ponctuelles
if (isset($data['indisponibilites_ponctuelles'])) {
$indisponibilites = $data['indisponibilites_ponctuelles'];
// Valider que c'est un tableau
if (is_array($indisponibilites)) {
// Debug: payload reçu
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_indispos_received',
'user_id' => $user_id,
'rows' => count($indisponibilites),
'sample' => $indisponibilites[0] ?? null,
], 'DEBUG');
// Formater chaque indisponibilité pour ACF
$indisponibilites_formatted = []; // d/m/Y (pour comparaison avec get_field qui respecte return_format)
$indisponibilites_update = []; // Ymd (format attendu par update_field pour date_picker)
// Helper: convertit d/m/Y vers Ymd (ex: 15/07/2025 -> 20250715)
$toYmd = function (string $dmy): string {
$parts = explode('/', $dmy);
if (count($parts) === 3) {
return $parts[2] . str_pad($parts[1], 2, '0', STR_PAD_LEFT) . str_pad($parts[0], 2, '0', STR_PAD_LEFT);
}
// Fallback: si déjà au bon format (YYYY-MM-DD), normaliser vers Ymd
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $dmy)) {
return str_replace('-', '', $dmy);
}
// En dernier recours, retourner tel quel
return $dmy;
};
foreach ($indisponibilites as $indispo) {
// Valider et formater les dates (doivent être au format d/m/Y pour ACF)
$debut = isset($indispo['debut']) ? sanitize_text_field($indispo['debut']) : '';
$fin = isset($indispo['fin']) ? sanitize_text_field($indispo['fin']) : '';
$type = isset($indispo['type']) ? sanitize_text_field($indispo['type']) : 'conge';
$commentaire = isset($indispo['commentaire']) ? sanitize_text_field($indispo['commentaire']) : '';
// Valider le type
$types_valides = ['conge', 'absence', 'maladie'];
if (!in_array($type, $types_valides, true)) {
$type = 'conge';
}
// Valider les dates (format d/m/Y)
if (!empty($debut) && !empty($fin)) {
$indisponibilites_formatted[] = [
'debut' => $debut,
'fin' => $fin,
'type' => $type,
'commentaire' => $commentaire,
];
$indisponibilites_update[] = [
'debut' => $toYmd($debut),
'fin' => $toYmd($fin),
'type' => $type,
'commentaire' => $commentaire,
];
}
}
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_indispos_formatted',
'user_id' => $user_id,
'rows' => count($indisponibilites_formatted),
'sample' => $indisponibilites_formatted[0] ?? null,
'update_rows' => count($indisponibilites_update),
'update_sample' => $indisponibilites_update[0] ?? null,
], 'DEBUG');
// Idempotent: si la valeur est identique, succès sans update
$current_indispos_raw = function_exists('get_field') ? get_field('indisponibilitee_ponctuelle', 'user_' . $user_id) : null;
// S'assurer que current_indispos est toujours un tableau
$current_indispos = is_array($current_indispos_raw) ? $current_indispos_raw : [];
// Comparaison normalisée (ignore l'ordre et clés additionnelles)
$normalize_rows = function (array $rows): array {
$normalized = array_map(function ($r) {
$debut = $r['debut'] ?? '';
$fin = $r['fin'] ?? '';
$type = $r['type'] ?? '';
$commentaire = $r['commentaire'] ?? '';
return $debut . '|' . $fin . '|' . $type . '|' . $commentaire;
}, $rows);
sort($normalized);
return $normalized;
};
$equal_now = is_array($current_indispos) && !empty($current_indispos)
? ($normalize_rows($current_indispos) === $normalize_rows($indisponibilites_formatted))
: (empty($indisponibilites_formatted));
if ($equal_now) {
$updated = true;
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_indispos_no_change',
'user_id' => $user_id,
'rows' => count($indisponibilites_formatted),
], 'DEBUG');
} else {
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_indispos_attempt',
'user_id' => $user_id,
'rows_expected' => count($indisponibilites_formatted),
'rows_current' => is_array($current_indispos) ? count($current_indispos) : 'non_array',
'sample_current' => is_array($current_indispos) ? ($current_indispos[0] ?? null) : null,
'update_rows' => count($indisponibilites_update),
'update_sample' => $indisponibilites_update[0] ?? null,
], 'INFO');
// IMPORTANT: update_field attend Ymd pour les champs date_picker
// Construire la structure avec clés de sous-champs pour fiabiliser
$rows_with_keys = array_map(function ($row) use ($acf_keys) {
return [
$acf_keys['indispo_debut'] => $row['debut'],
$acf_keys['indispo_fin'] => $row['fin'],
$acf_keys['indispo_type'] => $row['type'],
$acf_keys['indispo_commentaire'] => $row['commentaire'],
];
}, $indisponibilites_update);
// Utiliser la clé du répéteur
$result = update_field($acf_keys['indisponibilitee_ponctuelle'], $rows_with_keys, 'user_' . $user_id);
if ($result !== false) {
$updated = true;
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_indispos_updated',
'user_id' => $user_id,
'result' => $result,
], 'INFO');
} else {
// Vérifier la valeur réelle après tentative: si elle correspond, considérer comme succès
$post_update_value_raw = function_exists('get_field') ? get_field('indisponibilitee_ponctuelle', 'user_' . $user_id) : null;
// S'assurer que post_update_value est toujours un tableau
$post_update_value = is_array($post_update_value_raw) ? $post_update_value_raw : [];
$equal_after = is_array($post_update_value) && !empty($post_update_value)
? ($normalize_rows($post_update_value) === $normalize_rows($indisponibilites_formatted))
: (empty($indisponibilites_formatted));
if ($equal_after) {
$updated = true;
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_indispos_updated_after_check',
'user_id' => $user_id,
'rows' => is_array($post_update_value) ? count($post_update_value) : 'non_array',
], 'INFO');
} else {
$errors[] = 'Erreur lors de la mise à jour des indisponibilités ponctuelles';
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_indispos_error',
'user_id' => $user_id,
'result' => $result,
'expected_rows' => count($indisponibilites_formatted),
'actual_rows' => is_array($post_update_value) ? count($post_update_value) : 'non_array',
'expected_sample' => $indisponibilites_formatted[0] ?? null,
'actual_sample' => is_array($post_update_value) ? ($post_update_value[0] ?? null) : null,
'expected_normalized' => $normalize_rows($indisponibilites_formatted),
'actual_normalized' => is_array($post_update_value) && !empty($post_update_value) ? $normalize_rows($post_update_value) : 'non_array',
'post_update_value_type' => gettype($post_update_value_raw),
'post_update_value_raw' => $post_update_value_raw,
], 'ERROR');
}
}
}
} else {
$errors[] = 'Format invalide pour les indisponibilités ponctuelles';
}
}
if (!$updated && empty($errors)) {
return Api_Helper::json_error('Aucune modification valide', 400);
}
if (!empty($errors)) {
return Api_Helper::json_error(implode(', ', $errors), 500);
}
// Déclencher le hook acf/save_post pour permettre les vérifications de conflit d'indisponibilités
// Ce hook est utilisé par CRVI_Plugin::check_intervenant_availability_on_save()
do_action('acf/save_post', 'user_' . $user_id);
CRVI_Plugin::write_debug_file([
'action' => 'update_profile_end',
'user_id' => $user_id,
'updated' => $updated,
], 'INFO');
return Api_Helper::json_success(['message' => 'Profil mis à jour avec succès']);
}
/**
* Vérifie s'il y a des conflits d'indisponibilités pour l'intervenant
* GET /wp-json/crvi/v1/intervenant/conflicts-check
*/
public static function check_conflicts($request) {
$user_id = self::get_intervenant_id_for_context();
// Récupérer le message de conflit depuis le transient
$message = get_transient('crvi_intervenant_conflicts_' . $user_id);
if ($message) {
// Convertir le message admin en message front-end (Bootstrap alert)
$frontend_message = str_replace(
'<div class="notice notice-error is-dismissible">',
'',
$message
);
$frontend_message = str_replace(
'</div>',
'',
$frontend_message
);
// Supprimer le transient après lecture
delete_transient('crvi_intervenant_conflicts_' . $user_id);
return Api_Helper::json_success([
'has_conflicts' => true,
'message' => trim($frontend_message)
]);
}
return Api_Helper::json_success([
'has_conflicts' => false,
'message' => null
]);
}
/**
* Shortcode pour le hub intervenant
* [crvi_intervenant_hub]
*/
public static function shortcode_hub($atts) {
// Vérifier les permissions
if (!is_user_logged_in()) {
return '<p>Vous devez être connecté pour accéder à cette page.</p>';
}
$user = wp_get_current_user();
if (!in_array('intervenant', $user->roles, true) && !current_user_can('administrator')) {
return '<p>Accès réservé aux intervenants.</p>';
}
// Charger le template
$template_path = plugin_dir_path(__FILE__) . '../../templates/frontend/intervenant-hub.php';
if (!file_exists($template_path)) {
return '<p>Template introuvable. Veuillez créer le fichier templates/frontend/intervenant-hub.php</p>';
}
ob_start();
include $template_path;
return ob_get_clean();
}
/**
* Shortcode pour l'agenda intervenant
* [crvi_intervenant_agenda]
*/
public static function shortcode_agenda($atts) {
// Vérifier les permissions
if (!is_user_logged_in()) {
return '<p>Vous devez être connecté pour accéder à cette page.</p>';
}
$user = wp_get_current_user();
if (!in_array('intervenant', $user->roles, true) && !current_user_can('administrator')) {
return '<p>Accès réservé aux intervenants.</p>';
}
// Préparer les données pour les selects du modal (même logique qu'en admin)
$locals = CRVI_Local_Model::get_locals([], true);
$intervenants = CRVI_Intervenant_Model::get_intervenants([], true);
$departements = CRVI_Departement_Model::all(true);
$types_intervention = CRVI_Type_Intervention_Model::all(true);
$traducteurs = CRVI_Traducteur_Model::get_traducteurs([], true);
$langues_beneficiaire = Api_Helper::get_languages(true);
$genres = Api_Helper::get_acf_field_options('field_685e466352755');
$genres = $genres['options'] ?? [];
$types_locaux = Api_Helper::get_acf_field_options('field_685bc6db12678');
$types_locaux = $types_locaux['options'] ?? [];
$jours_disponibles = Api_Helper::get_acf_field_options('field_685bdf6d66ef9');
$jours_disponibles = $jours_disponibles['options'] ?? [];
// Tous les bénéficiaires pour le select principal
$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,
];
}
// Contexte front pour le modal
$crvi_agenda_context = 'front_intervenant';
$crvi_is_front_context = true;
// Charger le template (les variables seront accessibles via le scope)
$template_path = plugin_dir_path(__FILE__) . '../../templates/frontend/intervenant-agenda.php';
if (!file_exists($template_path)) {
return '<p>Template introuvable. Veuillez créer le fichier templates/frontend/intervenant-agenda.php</p>';
}
ob_start();
include $template_path;
return ob_get_clean();
}
/**
* Shortcode pour le profil intervenant
* [crvi_intervenant_profil]
*/
public static function shortcode_profil($atts) {
// Vérifier les permissions
if (!is_user_logged_in()) {
return '<p>Vous devez être connecté pour accéder à cette page.</p>';
}
$user = wp_get_current_user();
if (!in_array('intervenant', $user->roles, true) && !current_user_can('administrator')) {
return '<p>Accès réservé aux intervenants.</p>';
}
// Charger le template
$template_path = plugin_dir_path(__FILE__) . '../../templates/frontend/intervenant-profile.php';
if (!file_exists($template_path)) {
return '<p>Template introuvable. Veuillez créer le fichier templates/frontend/intervenant-profile.php</p>';
}
ob_start();
include $template_path;
return ob_get_clean();
}
/**
* Shortcode pour les permanences intervenant
* [crvi_intervenant_permanences]
*/
public static function shortcode_permanences($atts) {
// Vérifier les permissions
if (!is_user_logged_in()) {
return '<p>Vous devez être connecté pour accéder à cette page.</p>';
}
$user = wp_get_current_user();
if (!in_array('intervenant', $user->roles, true) && !current_user_can('administrator')) {
return '<p>Accès réservé aux intervenants.</p>';
}
// Charger le template
$template_path = plugin_dir_path(__FILE__) . '../../templates/frontend/intervenant-permanences.php';
if (!file_exists($template_path)) {
return '<p>Template introuvable. Veuillez créer le fichier templates/frontend/intervenant-permanences.php</p>';
}
ob_start();
include $template_path;
return ob_get_clean();
}
}