1098 lines
45 KiB
PHP
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();
|
|
}
|
|
}
|
|
|