Crvi/app/helpers/Api_Helper.php
2026-01-20 07:54:37 +01:00

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;
}
}