563 lines
20 KiB
PHP
563 lines
20 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace ESI_CRVI_AGENDA\helpers;
|
|
|
|
use WP_Error;
|
|
|
|
class Api_Helper {
|
|
/**
|
|
* Retourne une erreur API standardisée (format JSON).
|
|
* @param string $message
|
|
* @param int $code
|
|
* @return WP_Error
|
|
*/
|
|
public static function json_error(string $message, int $code = 400): WP_Error {
|
|
return new WP_Error('api_error', $message, ['status' => $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;
|
|
}
|
|
}
|