505 lines
19 KiB
PHP
505 lines
19 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace ESI_CRVI_AGENDA\controllers;
|
|
|
|
/**
|
|
* Contrôleur pour le CPT traduction_langue
|
|
* Gère les capacités de traduction par langue, jour et période
|
|
*/
|
|
class CRVI_TraductionLangue_Controller {
|
|
|
|
/**
|
|
* Enregistre le CPT traduction_langue et le groupe ACF associé
|
|
*/
|
|
public static function register_cpt() {
|
|
// Enregistrement du CPT traduction_langue
|
|
\register_post_type('traduction_langue', [
|
|
'label' => 'Capacités de traduction',
|
|
'labels' => [
|
|
'name' => 'Capacités de traduction',
|
|
'singular_name' => 'Capacité de traduction',
|
|
'add_new' => 'Ajouter une capacité',
|
|
'add_new_item' => 'Ajouter une nouvelle capacité de traduction',
|
|
'edit_item' => 'Modifier la capacité de traduction',
|
|
'new_item' => 'Nouvelle capacité de traduction',
|
|
'view_item' => 'Voir la capacité de traduction',
|
|
'search_items' => 'Rechercher une capacité',
|
|
'not_found' => 'Aucune capacité trouvée',
|
|
'not_found_in_trash' => 'Aucune capacité dans la corbeille',
|
|
],
|
|
'public' => false, // CPT non public
|
|
'show_ui' => true, // UI active dans l'admin
|
|
'show_in_menu' => false, // Menu masqué
|
|
'hierarchical' => true, // CPT hiérarchique (pour parent/enfant)
|
|
'supports' => ['title'],
|
|
'has_archive' => false,
|
|
'show_in_rest' => true, // Support REST API
|
|
'rewrite' => false, // Pas de rewrite car non public
|
|
]);
|
|
|
|
// Association de la taxonomie 'langue' au CPT 'traduction_langue'
|
|
// La taxonomie existe déjà (déclarée dans Traducteur_Controller)
|
|
if (taxonomy_exists('langue')) {
|
|
\register_taxonomy_for_object_type('langue', 'traduction_langue');
|
|
}
|
|
|
|
// Création du groupe ACF capacite_traduction
|
|
/* self::register_acf_field_group(); */
|
|
|
|
// Enregistrement des validations ACF
|
|
\add_filter('acf/validate_value', [self::class, 'validate_acf_fields'], 10, 4);
|
|
|
|
// Enregistrement du hook de suppression en cascade
|
|
\add_action('before_delete_post', [self::class, 'handle_cascade_delete'], 10, 2);
|
|
|
|
// Enregistrement du hook de verrouillage si événements existent
|
|
\add_filter('acf/validate_save_post', [self::class, 'validate_save_post_with_events'], 10, 1);
|
|
}
|
|
|
|
/**
|
|
* Enregistre le groupe ACF capacite_traduction avec tous les champs nécessaires
|
|
*/
|
|
private static function register_acf_field_group() {
|
|
// Vérifier que ACF est actif
|
|
if (!function_exists('acf_add_local_field_group')) {
|
|
return;
|
|
}
|
|
|
|
// Création du groupe ACF avec tous les champs
|
|
\acf_add_local_field_group([
|
|
'key' => 'group_capacite_traduction',
|
|
'title' => 'Capacité de traduction',
|
|
'fields' => [
|
|
// Champ: langue (taxonomy)
|
|
[
|
|
'key' => 'field_capacite_langue',
|
|
'label' => 'Langue',
|
|
'name' => 'langue',
|
|
'type' => 'taxonomy',
|
|
'instructions' => 'Sélectionnez la langue pour cette capacité',
|
|
'required' => 1,
|
|
'taxonomy' => 'langue',
|
|
'field_type' => 'select',
|
|
'allow_null' => 0,
|
|
'return_format' => 'id',
|
|
],
|
|
// Champ: jour (lundi → dimanche)
|
|
[
|
|
'key' => 'field_capacite_jour',
|
|
'label' => 'Jour',
|
|
'name' => 'jour',
|
|
'type' => 'select',
|
|
'instructions' => 'Sélectionnez le jour de la semaine',
|
|
'required' => 1,
|
|
'choices' => [
|
|
'lundi' => 'Lundi',
|
|
'mardi' => 'Mardi',
|
|
'mercredi' => 'Mercredi',
|
|
'jeudi' => 'Jeudi',
|
|
'vendredi' => 'Vendredi',
|
|
'samedi' => 'Samedi',
|
|
'dimanche' => 'Dimanche',
|
|
],
|
|
'default_value' => '',
|
|
'allow_null' => 0,
|
|
'return_format' => 'value',
|
|
],
|
|
// Champ: periode (matin / apres_midi / journee)
|
|
[
|
|
'key' => 'field_capacite_periode',
|
|
'label' => 'Période',
|
|
'name' => 'periode',
|
|
'type' => 'select',
|
|
'instructions' => 'Sélectionnez la période de la journée',
|
|
'required' => 1,
|
|
'choices' => [
|
|
'matin' => 'Matin',
|
|
'apres_midi' => 'Après-midi',
|
|
'journee' => 'Journée',
|
|
],
|
|
'default_value' => '',
|
|
'allow_null' => 0,
|
|
'return_format' => 'value',
|
|
],
|
|
// Champ: limite (number)
|
|
[
|
|
'key' => 'field_capacite_limite',
|
|
'label' => 'Limite',
|
|
'name' => 'limite',
|
|
'type' => 'number',
|
|
'instructions' => 'Nombre maximum de créneaux disponibles',
|
|
'required' => 1,
|
|
'default_value' => 0,
|
|
'min' => 0,
|
|
'step' => 1,
|
|
],
|
|
// Champ: limite_par (semaine / mois)
|
|
[
|
|
'key' => 'field_capacite_limite_par',
|
|
'label' => 'Limite par',
|
|
'name' => 'limite_par',
|
|
'type' => 'select',
|
|
'instructions' => 'Période de calcul de la limite',
|
|
'required' => 1,
|
|
'choices' => [
|
|
'semaine' => 'Semaine',
|
|
'mois' => 'Mois',
|
|
],
|
|
'default_value' => 'semaine',
|
|
'allow_null' => 0,
|
|
'return_format' => 'value',
|
|
],
|
|
// Champ: actif (true/false)
|
|
[
|
|
'key' => 'field_capacite_actif',
|
|
'label' => 'Actif',
|
|
'name' => 'actif',
|
|
'type' => 'true_false',
|
|
'instructions' => 'Activez ou désactivez cette capacité de traduction',
|
|
'required' => 0,
|
|
'default_value' => 1,
|
|
'ui' => 1,
|
|
'ui_on_text' => 'Oui',
|
|
'ui_off_text' => 'Non',
|
|
],
|
|
],
|
|
'location' => [
|
|
[
|
|
[
|
|
'param' => 'post_type',
|
|
'operator' => '==',
|
|
'value' => 'traduction_langue',
|
|
],
|
|
],
|
|
],
|
|
'menu_order' => 0,
|
|
'position' => 'normal',
|
|
'style' => 'default',
|
|
'label_placement' => 'top',
|
|
'instruction_placement' => 'label',
|
|
'hide_on_screen' => '',
|
|
'active' => true,
|
|
'description' => 'Configuration des capacités de traduction par langue, jour et période',
|
|
]);
|
|
}
|
|
|
|
/**
|
|
* Valide les champs ACF avant la sauvegarde
|
|
*
|
|
* @param bool|string $valid État de validation (true ou message d'erreur)
|
|
* @param mixed $value Valeur du champ
|
|
* @param array $field Configuration du champ ACF
|
|
* @param string $input Nom de l'input HTML
|
|
* @return bool|string
|
|
*/
|
|
public static function validate_acf_fields($valid, $value, $field, $input) {
|
|
// Ne valider que si c'est déjà valide et que c'est un champ de notre groupe
|
|
if (!$valid || !isset($field['key']) || strpos($field['key'], 'field_capacite_') !== 0) {
|
|
return $valid;
|
|
}
|
|
|
|
// Récupérer le post_id depuis $_POST
|
|
$post_id = isset($_POST['post_ID']) ? (int) $_POST['post_ID'] : 0;
|
|
|
|
// Ne pas valider pour les nouveaux posts
|
|
if (!$post_id || $post_id === 0) {
|
|
return $valid;
|
|
}
|
|
|
|
// Vérifier que c'est bien un post traduction_langue
|
|
$post = \get_post($post_id);
|
|
if (!$post || $post->post_type !== 'traduction_langue') {
|
|
return $valid;
|
|
}
|
|
|
|
// Pas de validation spécifique pour le moment
|
|
return $valid;
|
|
}
|
|
|
|
/**
|
|
* Gère la suppression des capacités de traduction
|
|
* - Bloque la suppression si des événements existent
|
|
*
|
|
* @param int $post_id ID du post à supprimer
|
|
* @param \WP_Post $post Objet du post
|
|
*/
|
|
public static function handle_cascade_delete($post_id, $post) {
|
|
// Vérifier que c'est bien un post traduction_langue
|
|
if (!$post || $post->post_type !== 'traduction_langue') {
|
|
return;
|
|
}
|
|
|
|
// Vérifier si la capacité a des événements associés avec vérification précise
|
|
$has_events = self::check_events_for_capacite($post_id);
|
|
|
|
if ($has_events) {
|
|
// Bloquer la suppression avec un message d'erreur
|
|
\wp_die(
|
|
'<h1>Suppression impossible</h1>' .
|
|
'<p>Cette capacité de traduction ne peut pas être supprimée car elle a des événements associés.</p>' .
|
|
'<p>Vous devez d\'abord supprimer ou réassigner les événements liés à cette capacité.</p>' .
|
|
'<p><a href="' . \admin_url('edit.php?post_type=traduction_langue') . '">← Retour à la liste</a></p>',
|
|
'Suppression bloquée',
|
|
[
|
|
'response' => 403,
|
|
'back_link' => true,
|
|
]
|
|
);
|
|
}
|
|
|
|
// Invalider le cache des capacités
|
|
\ESI_CRVI_AGENDA\models\CRVI_TraductionLangue_Model::invalidate_cache($post_id);
|
|
}
|
|
|
|
/**
|
|
* Vérifie si une capacité a des événements associés avec vérification précise
|
|
* Utilise la même logique que countUsed() mais retourne un booléen
|
|
*
|
|
* @param int $capacite_id ID de la capacité
|
|
* @return bool True si la capacité a des événements
|
|
*/
|
|
public static function check_events_for_capacite($capacite_id): bool {
|
|
$capacite = \ESI_CRVI_AGENDA\models\CRVI_TraductionLangue_Model::load($capacite_id);
|
|
if (!$capacite) {
|
|
return false;
|
|
}
|
|
|
|
// Récupérer les langues (peut être un tableau)
|
|
$langue_ids = $capacite->langue;
|
|
if (empty($langue_ids)) {
|
|
return false;
|
|
}
|
|
|
|
// S'assurer que c'est un tableau
|
|
if (!is_array($langue_ids)) {
|
|
$langue_ids = [$langue_ids];
|
|
}
|
|
|
|
// Récupérer les slugs de toutes les langues
|
|
$langue_slugs = [];
|
|
foreach ($langue_ids as $langue_id) {
|
|
$langue_term = \get_term($langue_id, 'langue');
|
|
if ($langue_term && !\is_wp_error($langue_term)) {
|
|
$langue_slugs[] = $langue_term->slug;
|
|
}
|
|
}
|
|
|
|
if (empty($langue_slugs)) {
|
|
return false;
|
|
}
|
|
|
|
// Mapper les jours en format MySQL DAYOFWEEK
|
|
$jours_map = [
|
|
'dimanche' => 1,
|
|
'lundi' => 2,
|
|
'mardi' => 3,
|
|
'mercredi' => 4,
|
|
'jeudi' => 5,
|
|
'vendredi' => 6,
|
|
'samedi' => 7,
|
|
];
|
|
|
|
// Requête optimisée avec EXISTS
|
|
global $wpdb;
|
|
$table_name = $wpdb->prefix . 'crvi_agenda';
|
|
|
|
// Construire la requête WHERE avec support multi-langues
|
|
$langue_placeholders = implode(',', array_fill(0, count($langue_slugs), '%s'));
|
|
|
|
$where = [
|
|
"langue IN ($langue_placeholders)",
|
|
"is_deleted = 0",
|
|
"statut != 'annule'",
|
|
"statut != 'brouillon'",
|
|
];
|
|
$values = $langue_slugs;
|
|
|
|
// Filtrer par jour de la semaine si défini
|
|
if (!empty($capacite->jour) && isset($jours_map[$capacite->jour])) {
|
|
$where[] = "DAYOFWEEK(date_rdv) = %d";
|
|
$values[] = $jours_map[$capacite->jour];
|
|
}
|
|
|
|
// Filtrer par période si défini
|
|
if (!empty($capacite->periode)) {
|
|
if ($capacite->periode === 'matin') {
|
|
$where[] = "heure_rdv < '12:00:00'";
|
|
} elseif ($capacite->periode === 'apres_midi') {
|
|
$where[] = "heure_rdv >= '12:00:00'";
|
|
}
|
|
// Si période = 'journee', on ne filtre pas par heure
|
|
}
|
|
|
|
$where_sql = 'WHERE ' . implode(' AND ', $where);
|
|
$sql = "SELECT EXISTS(SELECT 1 FROM {$table_name} {$where_sql} LIMIT 1)";
|
|
$prepared_sql = $wpdb->prepare($sql, $values);
|
|
|
|
$exists = (int) $wpdb->get_var($prepared_sql);
|
|
|
|
return $exists === 1;
|
|
}
|
|
|
|
/**
|
|
* Valide la sauvegarde d'un post traduction_langue
|
|
* Empêche toute modification si des événements existent déjà
|
|
*
|
|
* @param int $post_id ID du post en cours de sauvegarde
|
|
*/
|
|
public static function validate_save_post_with_events($post_id) {
|
|
// Vérifier que c'est bien un post traduction_langue
|
|
$post = \get_post($post_id);
|
|
if (!$post || $post->post_type !== 'traduction_langue') {
|
|
return;
|
|
}
|
|
|
|
// Ignorer pour les nouveaux posts (auto-draft)
|
|
if ($post->post_status === 'auto-draft') {
|
|
return;
|
|
}
|
|
|
|
// Ignorer si c'est une création (le post n'existait pas avant)
|
|
// On vérifie si le post a été créé récemment (moins de 2 minutes)
|
|
$post_date = strtotime($post->post_date);
|
|
$current_time = current_time('timestamp');
|
|
$is_new_post = ($current_time - $post_date) < 120; // 2 minutes
|
|
|
|
if ($is_new_post) {
|
|
return;
|
|
}
|
|
|
|
// Vérifier si la capacité a des événements associés
|
|
$has_events = self::check_events_for_capacite($post_id);
|
|
|
|
if (!$has_events) {
|
|
return; // Pas d'événements, on autorise la modification
|
|
}
|
|
|
|
// Si des événements existent, vérifier si des champs structurants ont été modifiés
|
|
$fields_to_check = ['langue', 'jour', 'periode', 'limite_par'];
|
|
$has_changes = false;
|
|
$changed_fields = [];
|
|
|
|
foreach ($fields_to_check as $field_name) {
|
|
// Récupérer la valeur actuelle en BDD
|
|
$current_value = \get_field($field_name, $post_id);
|
|
|
|
// Récupérer la nouvelle valeur depuis $_POST
|
|
$new_value = null;
|
|
|
|
if ($field_name === 'langue') {
|
|
// Pour le champ ACF langue (peut être multiple)
|
|
$field_key = 'field_capacite_langue';
|
|
if (isset($_POST['acf'][$field_key])) {
|
|
$new_value = $_POST['acf'][$field_key];
|
|
}
|
|
|
|
// Normaliser les valeurs pour comparaison (toujours en tableau)
|
|
if (!is_array($current_value)) {
|
|
$current_value = !empty($current_value) ? [$current_value] : [];
|
|
}
|
|
if (!is_array($new_value)) {
|
|
$new_value = !empty($new_value) ? [$new_value] : [];
|
|
}
|
|
|
|
// Trier pour comparaison correcte
|
|
sort($current_value);
|
|
sort($new_value);
|
|
} else {
|
|
// Pour les champs ACF standards
|
|
$field_key = 'field_capacite_' . $field_name;
|
|
|
|
if (isset($_POST['acf'][$field_key])) {
|
|
$new_value = $_POST['acf'][$field_key];
|
|
}
|
|
}
|
|
|
|
// Comparer les valeurs
|
|
if ($new_value !== null && $new_value != $current_value) {
|
|
$has_changes = true;
|
|
$changed_fields[] = $field_name;
|
|
}
|
|
}
|
|
|
|
// Si des modifications ont été détectées sur les champs structurants
|
|
if ($has_changes) {
|
|
$fields_labels = [
|
|
'langue' => 'Langue',
|
|
'jour' => 'Jour',
|
|
'periode' => 'Période',
|
|
'limite_par' => 'Limite par'
|
|
];
|
|
|
|
$changed_labels = array_map(function($field) use ($fields_labels) {
|
|
return $fields_labels[$field] ?? $field;
|
|
}, $changed_fields);
|
|
|
|
$error_message = sprintf(
|
|
'Impossible de modifier cette capacité de traduction car elle est utilisée par des événements existants. ' .
|
|
'Champs modifiés : %s. ' .
|
|
'Vous devez d\'abord supprimer ou réassigner les événements liés.',
|
|
implode(', ', $changed_labels)
|
|
);
|
|
|
|
\acf_add_validation_error('', $error_message);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Enregistre la page admin personnalisée pour les capacités de traduction
|
|
*/
|
|
public static function register_admin_page() {
|
|
\add_menu_page(
|
|
'Capacités de traduction', // Titre de la page
|
|
'Capacités traduction', // Titre du menu
|
|
'edit_posts', // Capability requise
|
|
'traduction-langues', // Slug de la page
|
|
[self::class, 'render_admin_page'], // Fonction de rendu
|
|
'dashicons-translation', // Icône
|
|
30 // Position dans le menu
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Affiche la page admin personnalisée
|
|
*/
|
|
public static function render_admin_page() {
|
|
// Récupérer toutes les capacités
|
|
$parent_capacites = \get_posts([
|
|
'post_type' => 'traduction_langue',
|
|
'posts_per_page' => -1,
|
|
'post_status' => 'any',
|
|
'orderby' => 'title',
|
|
'order' => 'ASC',
|
|
]);
|
|
|
|
// Charger les scripts et styles
|
|
\wp_enqueue_style(
|
|
'traduction-langue-list',
|
|
\plugins_url('assets/js/dist/traduction-langue-list.min.css', dirname(__DIR__, 1)),
|
|
[],
|
|
'1.0.0'
|
|
);
|
|
|
|
\wp_enqueue_script(
|
|
'traduction-langue-list',
|
|
\plugins_url('assets/js/dist/traduction-langue-list.min.js', dirname(__DIR__, 1)),
|
|
['jquery'],
|
|
'1.0.0',
|
|
true
|
|
);
|
|
|
|
// Afficher le template
|
|
include dirname(__DIR__, 2) . '/templates/admin/traduction-langue-list.php';
|
|
}
|
|
|
|
/**
|
|
* Enregistre les routes REST API pour les capacités de traduction
|
|
*/
|
|
public static function register_routes() {
|
|
// Pas de routes API nécessaires pour le moment
|
|
// Cette méthode est requise par le système de Plugin::register_routes()
|
|
}
|
|
}
|