From ad0374bcbd87a4351fb613160bf22f0ff42898c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?ANDRE=20s=C3=A9bastien?= <sandre@afi-sa.fr>
Date: Mon, 10 Feb 2025 15:53:47 +0100
Subject: [PATCH] hotline#218055 : fix dev 188879

---
 VERSIONS_HOTLINE/218055                       |   1 +
 cosmogramme/sql/patch/patch_476.php           |  33 ++++
 library/Class/Bib.php                         |   2 +-
 library/Class/Profil.php                      |  37 +---
 .../Systeme/ModulesAccueil/Calendrier.php     |   8 +-
 .../Action/Helper/SelectedFilters.php         |  20 +-
 .../Controller/Dispatcher/Standard.php        |  76 ++++++--
 .../ZendAfi/Controller/Plugin/DefineURLs.php  |  13 +-
 library/ZendAfi/Form/Admin/Library.php        |   2 +-
 .../ZendAfi/Form/Configuration/Profile.php    |  29 ++-
 .../Form/Configuration/Profile/Page.php       |  13 +-
 library/ZendAfi/Validate/ProfilRewriteUrl.php | 176 -----------------
 library/ZendAfi/Validate/RewriteUrl.php       | 184 +++++++++++++-----
 .../Validate/RewriteUrl/LibraryStrategy.php   |  64 ++++++
 .../Validate/RewriteUrl/NullStrategy.php      |  54 +++++
 .../Validate/RewriteUrl/ProfilStrategy.php    | 153 +++++++++++++++
 .../Validate/RewriteUrl/StringStrategy.php    |  68 +++++++
 .../ZendAfi/View/Helper/Accueil/Calendar.php  |  15 +-
 library/storm                                 |   2 +-
 .../Herisson/Library/ProfilePatcher.php       |   1 -
 .../admin/controllers/BibControllerTest.php   |   4 +-
 .../ProfilControllerRewriteUrlTest.php        |  57 ++++++
 .../controllers/ProfilControllerTest.php      |   2 +-
 .../RechercheControllerRewriteUrlTest.php     |  49 +++++
 tests/library/Class/ProfilTest.php            |   4 +-
 .../ZendAfi/View/Helper/CalendarHistoTest.php | 118 +++++++++++
 .../Templates/CalendarCustomFieldTest.php     | 116 +++++++++++
 .../Templates/HerissonTemplateTest.php        |   3 +-
 .../Templates/MyBibAppTemplateTest.php        |   4 +-
 .../Templates/TemplatesWidgetCalendarTest.php | 128 +-----------
 tests_db/UpgradeDBTest.php                    |  81 ++++++++
 31 files changed, 1070 insertions(+), 447 deletions(-)
 create mode 100644 VERSIONS_HOTLINE/218055
 create mode 100644 cosmogramme/sql/patch/patch_476.php
 delete mode 100644 library/ZendAfi/Validate/ProfilRewriteUrl.php
 create mode 100644 library/ZendAfi/Validate/RewriteUrl/LibraryStrategy.php
 create mode 100644 library/ZendAfi/Validate/RewriteUrl/NullStrategy.php
 create mode 100644 library/ZendAfi/Validate/RewriteUrl/ProfilStrategy.php
 create mode 100644 library/ZendAfi/Validate/RewriteUrl/StringStrategy.php
 create mode 100644 tests/application/modules/admin/controllers/ProfilControllerRewriteUrlTest.php
 create mode 100644 tests/application/modules/opac/controllers/RechercheControllerRewriteUrlTest.php
 create mode 100644 tests/library/ZendAfi/View/Helper/CalendarHistoTest.php
 create mode 100644 tests/scenarios/Templates/CalendarCustomFieldTest.php

diff --git a/VERSIONS_HOTLINE/218055 b/VERSIONS_HOTLINE/218055
new file mode 100644
index 00000000000..89361142468
--- /dev/null
+++ b/VERSIONS_HOTLINE/218055
@@ -0,0 +1 @@
+ - correctif #218055 : Administration : correction de la prise en charge et de la validation du paramètre "url de la page".
\ No newline at end of file
diff --git a/cosmogramme/sql/patch/patch_476.php b/cosmogramme/sql/patch/patch_476.php
new file mode 100644
index 00000000000..123b83e672c
--- /dev/null
+++ b/cosmogramme/sql/patch/patch_476.php
@@ -0,0 +1,33 @@
+<?php
+$dispatcher = ($front_controller = Bokeh_Engine::getInstance()->getFrontController())
+  ? $front_controller->getDispatcher()
+  : null;
+
+if ( ! $dispatcher)
+  return;
+
+$adapter = Zend_Db_Table_Abstract::getDefaultAdapter();
+
+try {
+  $datas = $adapter->query("SELECT DISTINCT `rewrite_url` FROM `bib_admin_profil` WHERE `rewrite_url` > '';")->fetchAll();
+
+  foreach ($datas as $data) {
+    $rewrite_url = $data['rewrite_url'] ?? '';
+
+    if ($rewrite_url && $dispatcher->isValidController(current(explode('/', $rewrite_url))))
+      $adapter->query("UPDATE `bib_admin_profil` SET `rewrite_url` = '' WHERE `rewrite_url` = '"
+                      . $rewrite_url . "';");
+  }
+} catch (Exception $e) {}
+
+try {
+  $datas = $adapter->query("SELECT DISTINCT `rewrite_url` FROM `bib_c_site` WHERE `rewrite_url` > '';")->fetchAll();
+
+  foreach ($datas as $data) {
+    $rewrite_url = $data['rewrite_url'] ?? '';
+
+    if ($rewrite_url && $dispatcher->isValidController(current(explode('/', $rewrite_url))))
+      $adapter->query("UPDATE `bib_c_site` SET `rewrite_url` = '' WHERE `rewrite_url` = '"
+                      . $rewrite_url . "';");
+  }
+} catch (Exception $e) {}
diff --git a/library/Class/Bib.php b/library/Class/Bib.php
index 0438864089d..36e2a69df93 100644
--- a/library/Class/Bib.php
+++ b/library/Class/Bib.php
@@ -923,7 +923,7 @@ class Class_Bib extends Storm_Model_Abstract {
                           !$this->getNotifyOnNewUser() || $this->getMail(),
                           $this->_('Merci d\'indiquer un email dans l\'onglet Adresse'));
 
-    $validator = new ZendAfi_Validate_RewriteUrl();
+    $validator = new ZendAfi_Validate_RewriteUrl;
     $validator->isValid($this);
     foreach($validator->getMessages() as $message) {
       $this->checkAttribute('rewrite_url', false, $message);
diff --git a/library/Class/Profil.php b/library/Class/Profil.php
index cb91dd96590..fd613ecc59f 100644
--- a/library/Class/Profil.php
+++ b/library/Class/Profil.php
@@ -170,31 +170,6 @@ class ProfilLoader extends Storm_Model_Loader {
   }
 
 
-  public function findByControllerName(string $uri): ?Class_Profil
-  {
-    if (!$url = array_filter(explode('/', $uri)))
-      return null;
-
-    if (  !$controller = reset($url))
-      return null;
-
-    if ( 1 === count($url))
-      return null;
-
-    if (! $profile = Class_Profil::findFirstBy(['rewrite_url' => $controller]))
-      return null;
-
-    if ( ! $action = next($url) ?? '')
-      return null;
-
-    if ( Class_Profil::findFirstBy(['rewrite_url' => $action,
-                                    'parent_id' => $profile->getId()]))
-      return null;
-
-    return $profile;
-  }
-
-
   public function findInControllerActionOf($request) {
     $key = $request->getControllerName() . $request->getActionName();
     if (isset($this->_by_controller_action_cache[$key]))
@@ -1550,11 +1525,12 @@ class Class_Profil extends Storm_Model_Abstract {
   /**
    * @return Class_Profil
    */
-  public function validate() {
-    if($this->getNbDivisions() < 3)
+  public function validate()
+  {
+    if ($this->getNbDivisions() < 3)
       $this->setLargeurDivision3(0);
 
-    if($this->getNbDivisions() < 2)
+    if ($this->getNbDivisions() < 2)
       $this->setLargeurDivision2(0);
 
     $this->checkAttribute('libelle',
@@ -1603,14 +1579,13 @@ class Class_Profil extends Storm_Model_Abstract {
                           $this->_isCSSColorValid($this->getCouleurLienBandeau()),
                           $this->_('La couleur des liens du bandeau doit être au format #001122'));
 
-
-    $validator = new ZendAfi_Validate_ProfilRewriteUrl();
+    $validator = new ZendAfi_Validate_RewriteUrl;
     $validator->isValid($this);
 
     $intent_validator = new ZendAfi_Validate_ProfileIntent;
     $intent_validator->isValid($this);
 
-    foreach($intent_validator->getMessages() + $validator->getMessages() as $message)
+    foreach ($intent_validator->getMessages() + $validator->getMessages() as $message)
       $this->check(false, $message);
 
     return $this;
diff --git a/library/Class/Systeme/ModulesAccueil/Calendrier.php b/library/Class/Systeme/ModulesAccueil/Calendrier.php
index 8e44851583f..d9d96386b49 100644
--- a/library/Class/Systeme/ModulesAccueil/Calendrier.php
+++ b/library/Class/Systeme/ModulesAccueil/Calendrier.php
@@ -32,7 +32,7 @@ class Class_Systeme_ModulesAccueil_Calendrier extends Class_Systeme_ModulesAccue
 
   public function __construct() {
     $this->_libelle = $this->_('Boite calendrier');
-    $this->_form = 'ZendAfi_Form_Configuration_Widget_Calendar';
+    $this->_form = ZendAfi_Form_Configuration_Widget_Calendar::class;
     $this->_defaultValues = ['titre' => $this->_libelle,
                              'id_categorie' => '',
                              'display_cat_select' => false,
@@ -52,6 +52,12 @@ class Class_Systeme_ModulesAccueil_Calendrier extends Class_Systeme_ModulesAccue
   }
 
 
+  public function shouldCacheContent(): bool
+  {
+    return false;
+  }
+
+
   public function getProperties() {
     $properties = parent::getProperties();
     $properties['display_event_info'] = 'bib';
diff --git a/library/ZendAfi/Controller/Action/Helper/SelectedFilters.php b/library/ZendAfi/Controller/Action/Helper/SelectedFilters.php
index ca6a7dbfd18..0566c7f9ddc 100644
--- a/library/ZendAfi/Controller/Action/Helper/SelectedFilters.php
+++ b/library/ZendAfi/Controller/Action/Helper/SelectedFilters.php
@@ -19,16 +19,19 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
-class ZendAfi_Controller_Action_Helper_SelectedFilters extends ZendAfi_Controller_Action_Helper_Abstract {
 
+class ZendAfi_Controller_Action_Helper_SelectedFilters
+  extends ZendAfi_Controller_Action_Helper_Abstract
+{
 
-  public function selectedFilters($extra_filters = []) {
-    $selected_filters = [];
+  public function selectedFilters(array $extra_filters = []): array
+  {
+    if ( ! ($request = $this->getRequest()))
+      return [];
 
-    $params = array_merge($this->getRequest()->getParams(),
-                          $this->getRequest()->getPost());
+    $selected_filters = [];
 
-    foreach ($params as $key => $value) {
+    foreach (array_merge($request->getParams(), $request->getPost()) as $key => $value) {
       if (in_array($key, $extra_filters)) {
         $selected_filters[$key] = [$value];
         continue;
@@ -46,7 +49,8 @@ class ZendAfi_Controller_Action_Helper_SelectedFilters extends ZendAfi_Controlle
   }
 
 
-  public function direct($extra_filters = []) {
+  public function direct(array $extra_filters = []): array
+  {
     return $this->selectedFilters($extra_filters);
   }
-}
\ No newline at end of file
+}
diff --git a/library/ZendAfi/Controller/Dispatcher/Standard.php b/library/ZendAfi/Controller/Dispatcher/Standard.php
index aa209174f19..fec207ce4ba 100644
--- a/library/ZendAfi/Controller/Dispatcher/Standard.php
+++ b/library/ZendAfi/Controller/Dispatcher/Standard.php
@@ -20,26 +20,25 @@
  */
 class ZendAfi_Controller_Dispatcher_Standard extends Zend_Controller_Dispatcher_Standard {
 
-  public function dispatch(Zend_Controller_Request_Abstract $request, Zend_Controller_Response_Abstract $response) {
-
+  public function dispatch(Zend_Controller_Request_Abstract $request,
+                           Zend_Controller_Response_Abstract $response)
+  {
     if ($this->_isRedirectedToHTTPS($request, $response))
       return null;
 
     try {
       return parent::dispatch($request, $response);
-    }
-
-    catch (Zend_Controller_Dispatcher_Exception $e) {
+    } catch (Zend_Controller_Dispatcher_Exception $e) {
       $this->_tryRewritedUrlsOrThrowError($request, $response, $e);
     }
+
     return null;
   }
 
 
   protected function _isRedirectedToHTTPS($request, $response) {
     if ((isset($_SERVER['HTTPS']) && ($_SERVER['HTTPS'] == 'on'))
-        ||
-        !Class_AdminVar::isModuleEnabled('FORCE_HTTPS'))
+        || ! Class_AdminVar::isModuleEnabled('FORCE_HTTPS'))
       return false;
 
     $response->setRedirect(Class_Url::absolute(), 301);
@@ -60,22 +59,65 @@ class ZendAfi_Controller_Dispatcher_Standard extends Zend_Controller_Dispatcher_
   }
 
 
-  protected function _dispatchToProfil($profil, $request, $response) {
+  protected function _dispatchToProfil($profil, $request, $response)
+  {
     $profil->beCurrentProfil();
-    $request->setControllerName('index');
-    $request->setActionName('index');
-    return parent::dispatch($request, $response);
+
+    return parent::dispatch($this->_rewriteRequest($request,
+                                                   'index',
+                                                   'index',
+                                                   $this->_paramsFromUri($request, $profil)),
+                            $response);
   }
 
 
-  protected function _dispatchToLibrary($library, $request, $response) {
+  protected function _dispatchToLibrary($library, $request, $response)
+  {
     if ($profil = $library->getLinkToProfil())
       return $this->_dispatchToProfil($profil, $request, $response);
 
-    $request->setControllerName('bib');
-    $request->setActionName('bibview');
-    $request->setParam('id', $library->getId());
+    return parent::dispatch($this->_rewriteRequest($request,
+                                                   'bib',
+                                                   'bibview',
+                                                   ['id' => $library->getId()]),
+                            $response);
+  }
+
+
+  protected function _rewriteRequest(Zend_Controller_Request_Abstract $request,
+                                     string $controller,
+                                     string $action,
+                                     array $params = []): Zend_Controller_Request_Abstract
+  {
+    $request_uri = '/' . $controller . '/' . $action;
+
+    foreach ($params as $key => $value)
+      $request_uri .= '/' . $key . '/' .$value;
+
+    return $request->setControllerName($controller)
+                   ->setActionName($action)
+                   ->setRequestUri($request_uri)
+                   ->setPathInfo($request_uri)
+                   ->clearParams()
+                   ->setParams(array_merge(['controller' => $controller,
+                                            'action' => $action],
+                                           $params));
+  }
+
+
+  protected function _paramsFromUri(Zend_Controller_Request_Abstract $request,
+                                    Class_Profil $profil): array
+  {
+    if ( ! preg_match('`.*' . $profil->getRewriteUrl() . '(\/.+)*`i',
+                      $request->getRequestUri(),
+                      $matches))
+      return [];
+
+    $uris = array_filter(explode('/', ($matches[1] ?? '')));
+    $params = [];
+    while ($uris)
+      $params [array_shift($uris)] = (array_shift($uris) ?? '');
 
-    return parent::dispatch($request, $response);
+    return $params;
   }
-}
\ No newline at end of file
+}
diff --git a/library/ZendAfi/Controller/Plugin/DefineURLs.php b/library/ZendAfi/Controller/Plugin/DefineURLs.php
index b6e8f8a1c20..8a2f0c704f7 100644
--- a/library/ZendAfi/Controller/Plugin/DefineURLs.php
+++ b/library/ZendAfi/Controller/Plugin/DefineURLs.php
@@ -216,22 +216,11 @@ class ZendAfi_Controller_Plugin_DefineURLs extends Zend_Controller_Plugin_Abstra
       $session["id_bib"]=$id_bib;
       $_SESSION["admin"]["filtre_localisation"]=$session;
   }
-
-
-  public function routeStartup(Zend_Controller_Request_Abstract $request)
-  {
-    if ($profil = Class_Profil::findByControllerName($request->getRequestUri()))
-    {
-      $uri = str_replace($profil->getRewriteUrl(), $profil->getRewriteUrl(). '/index', $request->getRequestUri());
-      $request
-        ->setPathInfo($uri)
-        ->setRequestUri($uri);
-    }
-  }
 }
 
 
 
+
 class ZendAfi_Controller_Plugin_DefineURLs_ProfileDetector {
   protected $_by_name;
 
diff --git a/library/ZendAfi/Form/Admin/Library.php b/library/ZendAfi/Form/Admin/Library.php
index a58d078d19d..d24733ab283 100644
--- a/library/ZendAfi/Form/Admin/Library.php
+++ b/library/ZendAfi/Form/Admin/Library.php
@@ -75,7 +75,7 @@ class ZendAfi_Form_Admin_Library extends ZendAfi_Form {
 
       ->addElement('text',
                    'rewrite_url',
-                   ['label' => $this->_('URL personnalisée'),
+                   ['label' => $this->_('URL de redirection'),
                     'size' => 50])
 
       ->addElement('comboProfils',
diff --git a/library/ZendAfi/Form/Configuration/Profile.php b/library/ZendAfi/Form/Configuration/Profile.php
index c1885adb69d..81366dbb826 100644
--- a/library/ZendAfi/Form/Configuration/Profile.php
+++ b/library/ZendAfi/Form/Configuration/Profile.php
@@ -20,9 +20,11 @@
  */
 
 
-class ZendAfi_Form_Configuration_Profile extends ZendAfi_Form {
+class ZendAfi_Form_Configuration_Profile extends ZendAfi_Form
+{
 
-  public function init() {
+  public function init()
+  {
     parent::init();
 
     Class_ScriptLoader::getInstance()
@@ -35,7 +37,6 @@ class ZendAfi_Form_Configuration_Profile extends ZendAfi_Form {
             formSelectToggleVisibilityForElement("#browser", $(".only_browser").closest("tr"), "opac");
        ');
 
-
     $this
       ->addElement('text',
                    'libelle',
@@ -52,7 +53,7 @@ class ZendAfi_Form_Configuration_Profile extends ZendAfi_Form {
       ->addElement('select',
                    'access_level',
                    ['label' => $this->_('Niveau d\'accès requis'),
-                    'multiOptions' => (new Class_Profil())->getAllAccessLevels(),
+                    'multiOptions' => (new Class_Profil)->getAllAccessLevels(),
                     'value' => '-1'])
 
       ->addElement('ComboProfils',
@@ -72,7 +73,7 @@ class ZendAfi_Form_Configuration_Profile extends ZendAfi_Form {
       ->addElement('text',
                    'rewrite_url',
                    ['label' => $this->_('URL du profil'),
-                    'validators' => [new ZendAfi_Validate_ProfilRewriteUrl()]])
+                    'validators' => [new ZendAfi_Validate_RewriteUrl]])
 
       ->addElement('userfile',
                    'header_img',
@@ -145,7 +146,7 @@ class ZendAfi_Form_Configuration_Profile extends ZendAfi_Form {
       ->addElement('select',
                    'skin',
                    ['label' => $this->_('Thème'),
-                    'multiOptions' => (new Class_Profil())->getAvailableSkins()])
+                    'multiOptions' => (new Class_Profil)->getAvailableSkins()])
 
       ->addElement('userfile',
                    'header_css',
@@ -257,7 +258,6 @@ class ZendAfi_Form_Configuration_Profile extends ZendAfi_Form {
                     'label' => $this->_('Marge division 3'),
                     'class' => 'only_browser'])
 
-
       ->addElement('checkbox',
                    'menu_haut_on',
                    ['label' => $this->_('Menu horizontal'),
@@ -319,16 +319,19 @@ class ZendAfi_Form_Configuration_Profile extends ZendAfi_Form {
                    'sel_section',
                    ['rubrique' => 'section',
                     'label' => $this->_('Sections')])
+
       ->addElement('cochesSuggestion',
                    'sel_type_doc',
                    ['rubrique' => 'type_doc',
                     'label' => $this->_('Types de documents')])
+
       ->addElement('cochesSuggestion',
                    'domain_ids',
                    ['label' => $this->_('Domaines'),
                     'name' => 'domain_ids',
                     'rubrique' => fn() => $this->_getList(),
                     'selected_all_means_nothing' => false])
+
       ->addToHeadGroup(['libelle',
                         'rewrite_url'])
 
@@ -403,15 +406,21 @@ class ZendAfi_Form_Configuration_Profile extends ZendAfi_Form {
   }
 
 
-  protected function _getList() : array {
+  protected function _getList(): array
+  {
     $datas = [];
-    foreach(Class_Catalogue::findAllWithRecords() as $domain)
+
+    foreach (Class_Catalogue::findAllWithRecords() as $domain)
       $datas[$domain->getId()] = $domain->getLibelle();
+
     return $datas;
   }
 
-  public function populate(array $values) : self {
+
+  public function populate(array $values): self
+  {
     $values['library_ids'] = implode(';', explode(' ', $values['id_site'] ?? ''));
+
     parent::populate($values);
 
     return $this;
diff --git a/library/ZendAfi/Form/Configuration/Profile/Page.php b/library/ZendAfi/Form/Configuration/Profile/Page.php
index 04b076bb1cb..14130ac8c3f 100644
--- a/library/ZendAfi/Form/Configuration/Profile/Page.php
+++ b/library/ZendAfi/Form/Configuration/Profile/Page.php
@@ -20,8 +20,11 @@
  */
 
 
-class ZendAfi_Form_Configuration_Profile_Page extends ZendAfi_Form {
-  public function init() {
+class ZendAfi_Form_Configuration_Profile_Page extends ZendAfi_Form
+{
+
+  public function init()
+  {
     parent::init();
 
     $this
@@ -33,8 +36,8 @@ class ZendAfi_Form_Configuration_Profile_Page extends ZendAfi_Form {
 
       ->addElement('text',
                    'rewrite_url',
-                   ['label' => $this->_('URL de la page'),
-                    'validators' => [new ZendAfi_Validate_ProfilRewriteUrl()]])
+                   ['label' => $this->_('URL de redirection'),
+                    'validators' => [new ZendAfi_Validate_RewriteUrl]])
 
       ->addElement('checkbox',
                    'use_parent_css',
@@ -93,4 +96,4 @@ class ZendAfi_Form_Configuration_Profile_Page extends ZendAfi_Form {
 
     Class_Template::current()->customProfilePageForm($this);
   }
-}
\ No newline at end of file
+}
diff --git a/library/ZendAfi/Validate/ProfilRewriteUrl.php b/library/ZendAfi/Validate/ProfilRewriteUrl.php
deleted file mode 100644
index bf77eda1ba3..00000000000
--- a/library/ZendAfi/Validate/ProfilRewriteUrl.php
+++ /dev/null
@@ -1,176 +0,0 @@
-<?php
-/**
- * Copyright (c) 2012, Agence Française Informatique (AFI). All rights reserved.
- *
- * BOKEH is free software; you can redistribute it and/or modify
- * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
- * the Free Software Foundation.
- *
- * There are special exceptions to the terms and conditions of the AGPL as it
- * is applied to this software (see README file).
- *
- * BOKEH is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
- *
- * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
- * along with BOKEH; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
- */
-class ZendAfi_Validate_ProfilRewriteUrl extends Zend_Validate_Abstract {
-
-
-  const
-    INVALID_URL = 'invalidURL',
-    URL_ALREADY_EXISTS_IN_A_PROFIL = 'urlAlreadyExistsInAProfil',
-    URL_ALREADY_EXISTS_IN_A_PAGE = 'urlAlreadyExistsInAPage';
-
-
-  protected array $_messageTemplates =
-    [
-     self::INVALID_URL => "'%value%' n'est pas une URL de profil valide",
-     self::URL_ALREADY_EXISTS_IN_A_PROFIL => "L'URL '%value%' est déjà définie pour le profil '%profil%'",
-     self::URL_ALREADY_EXISTS_IN_A_PAGE => "L'URL '%value%' est déjà définie pour la page '%page%'"
-    ];
-
-
-  public function isValid($profil) {
-    $url = $profil;
-
-    if($profil instanceof Class_Profil)
-      $url = trim( (string) $profil->getRewriteUrl());
-
-    if(!$profil instanceof Class_Profil) {
-      $profil = Class_Profil::getCurrentProfil();
-      $profil->setRewriteUrl($url);
-    }
-
-    $this->_setValue($url);
-
-    if (!$url)
-      return true;
-
-    if (!preg_match("/^[a-zA-Z0-9\-_]*$/", $url, $matches)) {
-      $this->_error(self::INVALID_URL);
-      return false;
-    }
-
-    if (!$this->checkProfilWithSameUrlAsAnotherProfil($profil))
-      return false;
-
-    if (!$this->checkProfilWithSameUrlAsAPage($profil))
-      return false;
-
-    if (!$this->checkPageWithSameUrlAsAProfil($profil))
-      return false;
-
-    if (!$this->checkPageWithSameUrlAsAPageWithoutRewriteUrl($profil))
-      return false;
-    return (bool) $this->checkOtherModelsUrl($profil);
-  }
-
-
-  protected function checkOtherModelsUrl($profil) {
-    $validator = new ZendAfi_Validate_RewriteUrl(['Class_Profil']);
-    if ($validator->isValid($profil))
-      return true;
-
-    foreach($validator->getMessages() as $key => $message) {
-      $this->_errors[]= $key;
-      $this->_messages[$key] = $message;
-    }
-
-    return false;
-  }
-
-
-  protected function checkProfilWithSameUrlAsAnotherProfil($profil) {
-    if ($profil->hasParentProfil())
-      return true;
-
-    $others = Class_Profil::findAllBy(['rewrite_url' => $profil->getRewriteUrl(),
-                                       'parent_id' => null]);
-    if (empty($others))
-      return true;
-
-    $others = array_filter(
-                           $others,
-                           fn($model) => $model->getId() != $profil->getId());
-    if ($others === [])
-      return true;
-
-    $this->setProfilError($others[0]);
-    return false;
-  }
-
-
-  protected function checkProfilWithSameUrlAsAPage($profil) {
-    if ($profil->hasParentProfil())
-      return true;
-
-    $profils = Class_Profil::findAllBy(['rewrite_url' => $profil->getRewriteUrl()]);
-    $profils = array_filter($profils,
-                            fn($model) => $model->hasParentProfil()
-                              && (!$model->getParent()->getRewriteUrl()));
-
-    if ($profils === [])
-      return true;
-
-    $this->setPageError(array_shift($profils));
-    return false;
-  }
-
-
-  protected function checkPageWithSameUrlAsAPageWithoutRewriteUrl($page) {
-    if (!$page->getParentProfil())
-      return true;
-
-    $profils = Class_Profil::findAllBy(['rewrite_url' => $page->getRewriteUrl()]);
-
-    $profils = array_filter($profils,
-                            fn($model) => $model->hasParentProfil()
-                            && ($model->getId() !== $page->getId())
-                            && ($model->getParent()->getRewriteUrl() == $page->getParent()->getRewriteUrl()));
-
-    if ($profils === [])
-      return true;
-
-    $this->setPageError(current($profils));
-    return false;
-  }
-
-
-  protected function checkPageWithSameUrlAsAProfil($profil) {
-    if (!$profil->hasParentProfil())
-      return true;
-
-    if (!$other = Class_Profil::findFirstBy(['rewrite_url' => $profil->getRewriteUrl(),
-                                             'parent_id' => null]))
-      return true;
-
-    $this->setProfilError($other);
-    return false;
-  }
-
-
-  protected function setPageError($profil) {
-    $this->setErrorWithVar('page',
-                           $profil->getParentProfil()->getLibelle() . ' > ' . $profil->getLibelle(),
-                           self::URL_ALREADY_EXISTS_IN_A_PAGE);
-  }
-
-
-  protected function setProfilError($profil) {
-    $this->setErrorWithVar('profil',
-                           $profil->getLibelle(),
-                           self::URL_ALREADY_EXISTS_IN_A_PROFIL);
-  }
-
-
-  protected function setErrorWithVar($name, $value, $message) {
-    $this->_messageVariables[$name] = $name;
-    $this->$name = $value;
-    $this->_error($message);
-  }
-}
diff --git a/library/ZendAfi/Validate/RewriteUrl.php b/library/ZendAfi/Validate/RewriteUrl.php
index 87a927e00ad..2187ba889b6 100644
--- a/library/ZendAfi/Validate/RewriteUrl.php
+++ b/library/ZendAfi/Validate/RewriteUrl.php
@@ -18,82 +18,164 @@
  * along with BOKEH; if not, write to the Free Software
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
-class ZendAfi_Validate_RewriteUrl extends Zend_Validate_Abstract {
-  use Trait_Translator;
 
-  protected
-    $_exclude_models =  [];
+
+class ZendAfi_Validate_RewriteUrl extends Zend_Validate_Abstract
+{
+  use Trait_Translator;
 
   const
     INVALID_URL = 'invalidURL',
-    URL_ALREADY_EXISTS_IN_ANOTHER_MODEL = 'urlAlreadyExistsInAModel';
+    URL_ALREADY_EXISTS_IN_A_MODEL = 'urlAlreadyExistsInAModel',
+    URL_ALREADY_EXISTS_IN_A_PAGE = 'urlAlreadyExistsInAPage',
+    URL_ALREADY_EXISTS_IN_ANOTHER_MODEL = 'urlAlreadyExistsInAnOtherModel',
+    URL_FORBIDEN = 'url_forbiden';
+
+  protected array $_messageTemplates = [];
+  protected bool $_validate_url = true;
+
+  public function isValid($model): bool
+  {
+    if (is_string($model)) {
+      (new ZendAfi_Validate_RewriteUrl_StringStrategy($this, $model))->validateUrl();
+
+      return $this->getValidateUrl();
+    }
+
+    if ($model instanceof Class_Bib) {
+      (new ZendAfi_Validate_RewriteUrl_LibraryStrategy($this, $model))->validateUrl();
 
-  protected array $_messageTemplates =
-    [
-     self::INVALID_URL => "'%value%' n'est pas une URL valide",
-     self::URL_ALREADY_EXISTS_IN_ANOTHER_MODEL => "L'URL '%value%' est déjà définie pour l'élément %model%: '%label%'",
-    ];
+      return $this->getValidateUrl();
+    }
 
+    if ($model instanceof Class_Profil)
+      (new ZendAfi_Validate_RewriteUrl_ProfilStrategy($this, $model))->validateUrl();
 
-  public function __construct($exclude_models = []) {
-    $this->_exclude_models = $exclude_models;
+    return $this->getValidateUrl();
   }
 
 
-  public function isValid($model) {
-    $url = trim($model->getRewriteUrl());
-    $this->_setValue($url);
+  protected function _initMessageTEmplates(): self
+  {
+    if ( ! $this->_messageTemplates)
+      $this->_messageTemplates = [static::INVALID_URL => '',
+                                  static::URL_ALREADY_EXISTS_IN_A_MODEL => '',
+                                  static::URL_ALREADY_EXISTS_IN_A_PAGE => '',
+                                  static::URL_ALREADY_EXISTS_IN_ANOTHER_MODEL => '',
+                                  static::URL_FORBIDEN => ''];
 
-    if (!$url)
-      return true;
+    return $this;
+  }
 
-    if (!preg_match("/^[a-zA-Z0-9\-_]*$/", $url, $matches)) {
-      $this->_error(self::INVALID_URL);
-      return false;
-    }
 
+  public function errorInvalideUrl(string $value): self
+  {
+    $this->_initMessageTEmplates();
+
+    $key = static::INVALID_URL;
 
-    $valid = true;
-    foreach($this->_modelsWithRewrite() as $classname => $label)
-      $valid = $valid && $this->isValidForClass($classname,
-                                                $model,
-                                                $label);
+    $this->_messageTemplates[$key] = $this->_("'%s' n'est pas une URL valide",
+                                              $value);
 
-    return $valid;
+    return $this->_addError($key);
   }
 
 
-  protected function _modelsWithRewrite() {
-    $models =  [
-                'Class_Bib' => $this->_('bibliothèque'),
-                'Class_Profil' => $this->_('profil') ];
-    return array_diff_key($models,
-                          array_combine($this->_exclude_models,
-                                        $this->_exclude_models));
+  public function errorUrlForbiden(string $value): self
+  {
+    $this->_initMessageTEmplates();
+
+    $key = static::URL_FORBIDEN;
+
+    $this->_messageTemplates[$key] = $this->_("L'Url '%s' est une url interdite",
+                                              $value);
+
+    return $this->_addError($key);
   }
 
 
-  protected function isValidForClass($classname, $model, $class_label) {
-    $others = $classname::findAllBy(['rewrite_url' => $model->getRewriteUrl()]);
-    $others = array_filter(
-                           $others,
-                           fn($other) => ($classname != $model->getClassName()) || ($model->getId() != $other->getId()));
+  public function errorAlreadyExistInProfil(string $value, string $label): self
+  {
+    $this->_initMessageTEmplates();
 
-    return $others === []
-      ? true
-      : $this->_setError($model, $others[0], $class_label);
+    $key = static::URL_ALREADY_EXISTS_IN_A_MODEL;
+
+    $this->_messageTemplates[$key] = $this->_("L'URL '%s' est déjà définie pour le profil: '%s'",
+                                              $value,
+                                              $label);
+
+    return $this->_addError($key);
   }
 
 
-  protected function _setError($model, $other, $class_label) {
-    $this->model = $class_label;
-    $this->label = $other->getLibelle();
+  public function errorAlreadyExistInLibrary(string $value, string $label): self
+  {
+    $this->_initMessageTEmplates();
+
+    $key = static::URL_ALREADY_EXISTS_IN_A_MODEL;
 
-    $this->_messageVariables['model'] = 'model';
-    $this->_messageVariables['label'] = 'label';
-    $this->_error(self::URL_ALREADY_EXISTS_IN_ANOTHER_MODEL);
-    return false;
+    $this->_messageTemplates[$key] = $this->_("L'URL '%s' est déjà définie pour la bibliothèque: '%s'",
+                                              $value,
+                                              $label);
+
+    return $this->_addError($key);
   }
-}
 
-?>
+
+  public function errorAlreadyExistsInPage(string $value, string $page): self
+  {
+    $this->_initMessageTEmplates();
+
+    $key = static::URL_ALREADY_EXISTS_IN_A_PAGE;
+
+    $this->_messageTemplates[$key] = $this->_("L'URL '%s' est déjà définie pour la page '%s'",
+                                              $value,
+                                              $page);
+
+    return $this->_addError($key);
+  }
+
+
+  public function errorAlreadyExistsInAnotherProfil(string $value, string $label): self
+  {
+    $this->_initMessageTEmplates();
+
+    $key = static::URL_ALREADY_EXISTS_IN_ANOTHER_MODEL;
+
+    $this->_messageTemplates[$key] = $this->_("L'URL '%s' est déjà définie pour le profil: '%s'",
+                                              $value,
+                                              $label);
+
+    return $this->_addError($key);
+  }
+
+
+  public function errorAlreadyExistsInAnotherLibrary(string $value, string $label): self
+  {
+    $this->_initMessageTEmplates();
+
+    $key = static::URL_ALREADY_EXISTS_IN_ANOTHER_MODEL;
+
+    $this->_messageTemplates[$key] = $this->_("L'URL '%s' est déjà définie pour la bibliothèque: '%s'",
+                                              $value,
+                                              $label);
+
+    return $this->_addError($key);
+  }
+
+
+  protected function _addError(string $key): self
+  {
+    $this->_validate_url = false;
+
+    $this->_error($key);
+
+    return $this;
+  }
+
+
+  public function getValidateUrl(): bool
+  {
+    return $this->_validate_url;
+  }
+}
diff --git a/library/ZendAfi/Validate/RewriteUrl/LibraryStrategy.php b/library/ZendAfi/Validate/RewriteUrl/LibraryStrategy.php
new file mode 100644
index 00000000000..fd139fee26a
--- /dev/null
+++ b/library/ZendAfi/Validate/RewriteUrl/LibraryStrategy.php
@@ -0,0 +1,64 @@
+<?php
+/**
+ * Copyright (c) 2012-2025, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Validate_RewriteUrl_LibraryStrategy extends ZendAfi_Validate_RewriteUrl_NullStrategy
+{
+
+  public function __construct(protected ZendAfi_Validate_RewriteUrl $_validator,
+                              protected Class_Bib $_bib)
+  {}
+
+
+  public function validateUrl(): self
+  {
+    return $this->_checkDefaultUrl((string) $this->_bib->getRewriteUrl())
+                ->_checkBibWithSameUrlAsAnotherBib()
+                ->_checkOtherModelsUrl();
+  }
+
+
+  protected function _checkOtherModelsUrl(): self
+  {
+    if ($this->_mustCheck()
+        && ($label = (Class_Profil::query()
+                      ->select('libelle')
+                      ->eq('rewrite_url', $this->_url)
+                      ->fetchFirst()
+                      ?->getLibelle() ?? '')))
+      $this->_validator->errorAlreadyExistsInAnotherProfil($this->_url, $label);
+
+    return $this;
+  }
+
+
+  protected function _checkBibWithSameUrlAsAnotherBib(): self
+  {
+    if ($this->_mustCheck()
+        && ($other = Class_Bib::query()
+            ->eq('rewrite_url', $this->_url)
+            ->not_eq('id_site', $this->_bib->getId())
+            ->fetchFirst()))
+      $this->_validator->errorAlreadyExistInLibrary($this->_url, $other->getLibelle());
+
+    return $this;
+  }
+}
diff --git a/library/ZendAfi/Validate/RewriteUrl/NullStrategy.php b/library/ZendAfi/Validate/RewriteUrl/NullStrategy.php
new file mode 100644
index 00000000000..2c0cf0d31b3
--- /dev/null
+++ b/library/ZendAfi/Validate/RewriteUrl/NullStrategy.php
@@ -0,0 +1,54 @@
+<?php
+/**
+ * Copyright (c) 2012-2025, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Validate_RewriteUrl_NullStrategy
+{
+
+  protected string $_url = '';
+
+  public function validateUrl(): self
+  {
+    return $this;
+  }
+
+
+  protected function _checkDefaultUrl(string $url): self
+  {
+    $this->_url = trim($url);
+
+    if ($this->_mustCheck() && ! preg_match("/^[a-zA-Z0-9\-_]*$/", $this->_url))
+      $this->_validator->errorInvalideUrl($this->_url);
+
+    if ($this->_mustCheck()
+        && ($front_controller = Bokeh_Engine::getInstance()->getFrontController())
+        && $front_controller->getDispatcher()->isValidController($this->_url))
+      $this->_validator->errorUrlForbiden($this->_url);
+
+    return $this;
+  }
+
+
+  protected function _mustCheck(): bool
+  {
+    return $this->_url && $this->_validator->getValidateUrl();
+  }
+}
diff --git a/library/ZendAfi/Validate/RewriteUrl/ProfilStrategy.php b/library/ZendAfi/Validate/RewriteUrl/ProfilStrategy.php
new file mode 100644
index 00000000000..2c303bbeeee
--- /dev/null
+++ b/library/ZendAfi/Validate/RewriteUrl/ProfilStrategy.php
@@ -0,0 +1,153 @@
+<?php
+/**
+ * Copyright (c) 2012-2025, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Validate_RewriteUrl_ProfilStrategy extends ZendAfi_Validate_RewriteUrl_NullStrategy
+{
+
+  protected ?string $_parent_rewrite_url = null;
+
+  public function __construct(protected ZendAfi_Validate_RewriteUrl $_validator,
+                              protected Class_Profil $_profil)
+  {}
+
+
+  public function validateUrl(): self
+  {
+    return $this->_checkDefaultUrl((string) $this->_profil->getRewriteUrl())
+                ->_checkProfilWithSameUrlAsAnotherProfil()
+                ->_checkProfilWithSameUrlAsAPage()
+                ->_checkPageWithSameUrlAsAPageWithoutRewriteUrl()
+                ->_checkPageWithSameUrlAsAProfil()
+                ->_checkOtherModelsUrl();
+  }
+
+
+  protected function _checkOtherModelsUrl(): self
+  {
+    if ($this->_mustCheck()
+        && ($label = (Class_Bib::query()
+                      ->select('libelle')
+                      ->eq('rewrite_url', $this->_url)
+                      ->fetchFirst()
+                      ?->getLibelle() ?? '')))
+      $this->_validator->errorAlreadyExistsInAnotherLibrary($this->_url, $label);
+
+    return $this;
+  }
+
+
+  protected function _checkProfilWithSameUrlAsAnotherProfil(): self
+  {
+    if ($this->_mustCheck()
+        && $this->_parentRewriteUrl()
+        && ($other = Class_Profil::query()
+            ->eq('rewrite_url', $this->_url)
+            ->is_null('parent_id')
+            ->not_eq('id_profil', $this->_profil->getId())
+            ->fetchFirst()))
+      $this->_validator->errorAlreadyExistInProfil($this->_url, $other->getLibelle());
+
+    return $this;
+  }
+
+
+  protected function _checkProfilWithSameUrlAsAPage(): self
+  {
+    $profils = null;
+
+    if ($this->_mustCheck()
+        && ! $this->_parentRewriteUrl()) {
+      $profils = Class_Profil::query()
+        ->eq('rewrite_url', $this->_url)
+        ->not_eq('id_profil', $this->_profil->getId())
+        ->gt('parent_id', 0)
+        ->fetchAll();
+
+      $profils = array_filter($profils,
+                              fn($model) => $model->hasParentProfil()
+                              && ! $model->getParent()->getRewriteUrl());
+    }
+
+    if ($profils)
+      $this->_validator->errorAlreadyExistsInPage($this->_url,
+                                                  $this->_messageErrorPage(array_shift($profils)));
+
+    return $this;
+  }
+
+
+  protected function _checkPageWithSameUrlAsAPageWithoutRewriteUrl(): self
+  {
+    $profils = null;
+
+    if ($this->_mustCheck()
+        && ! $this->_parentRewriteUrl()) {
+      $profils = Class_Profil::query()
+        ->eq('rewrite_url', $this->_url)
+        ->not_eq('id_profil', $this->_profil->getId())
+        ->gt('parent_id', 0)
+        ->fetchAll();
+
+      $profils = array_filter($profils,
+                              fn($model) => $model->hasParentProfil()
+                              && ($model->getParent()->getRewriteUrl() == $this->_parentRewriteUrl()));
+    }
+
+    if ($profils)
+      $this->_validator->errorAlreadyExistsInPage($this->_url,
+                                                  $this->_messageErrorPage(array_shift($profils)));
+
+    return $this;
+  }
+
+
+  protected function _checkPageWithSameUrlAsAProfil(): self
+  {
+    if ($this->_mustCheck()
+        && ! $this->_parentRewriteUrl()
+        && ($other = Class_Profil::query()
+            ->eq('rewrite_url', $this->_url)
+            ->not_eq('id_profil', $this->_profil->getId())
+            ->is_null('parent_id')
+            ->fetchFirst()))
+      $this->_validator->errorAlreadyExistInProfil($this->_url, $other->getLibelle());
+
+    return $this;
+  }
+
+
+  protected function _parentRewriteUrl(): string
+  {
+    return $this->_parent_rewrite_url ??= ($this->_profil->hasParentProfil()
+                                           ? ((string) $this->_profil->getParent()
+                                              ->getRewriteUrl())
+                                           : '');
+  }
+
+
+  protected function _messageErrorPage(?Class_Profil $profil): string
+  {
+    return $profil
+      ? $profil->getParentProfil()->getLibelle() . ' > ' . $profil->getLibelle()
+      : '';
+  }
+}
diff --git a/library/ZendAfi/Validate/RewriteUrl/StringStrategy.php b/library/ZendAfi/Validate/RewriteUrl/StringStrategy.php
new file mode 100644
index 00000000000..43fdc0aa0aa
--- /dev/null
+++ b/library/ZendAfi/Validate/RewriteUrl/StringStrategy.php
@@ -0,0 +1,68 @@
+<?php
+/**
+ * Copyright (c) 2012-2025, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ZendAfi_Validate_RewriteUrl_StringStrategy extends ZendAfi_Validate_RewriteUrl_NullStrategy
+{
+
+  public function __construct(ZendAfi_Validate_RewriteUrl $validator,
+                              string $url)
+  {
+    $this->_validator = $validator;
+    $this->_url = $url;
+  }
+
+
+  public function validateUrl(): self
+  {
+    return $this->_checkDefaultUrl($this->_url)
+                ->_checkLibraryExists()
+                ->_checkProfilExists();
+  }
+
+
+  protected function _checkLibraryExists(): self
+  {
+    if ($this->_mustCheck()
+        && ($label = (Class_Bib::query()
+                      ->select('libelle')
+                      ->eq('rewrite_url', $this->_url)
+                      ->fetchFirst()
+                      ?->getLibelle() ?? '')))
+      $this->_validator->errorAlreadyExistsInAnotherLibrary($this->_url, $label);
+
+    return $this;
+  }
+
+
+  protected function _checkProfilExists(): self
+  {
+    if ($this->_mustCheck()
+        && ($label = (Class_Profil::query()
+                      ->select('libelle')
+                      ->eq('rewrite_url', $this->_url)
+                      ->fetchFirst()
+                      ?->getLibelle() ?? '')))
+      $this->_validator->errorAlreadyExistsInAnotherProfil($this->_url, $label);
+
+    return $this;
+  }
+}
diff --git a/library/ZendAfi/View/Helper/Accueil/Calendar.php b/library/ZendAfi/View/Helper/Accueil/Calendar.php
index eb8412fea9d..dd4e1c02141 100644
--- a/library/ZendAfi/View/Helper/Accueil/Calendar.php
+++ b/library/ZendAfi/View/Helper/Accueil/Calendar.php
@@ -19,7 +19,9 @@
  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
  */
 
+
 class ZendAfi_View_Helper_Accueil_Calendar extends ZendAfi_View_Helper_Accueil_Base {
+
   protected $class_calendar;
 
   protected function _renderHeadScriptsOn(Class_ScriptLoader $script_loader) : self {
@@ -54,9 +56,18 @@ class ZendAfi_View_Helper_Accueil_Calendar extends ZendAfi_View_Helper_Accueil_B
       $this->_ical_feed = $this->_getUrlFor('cms', 'ical');
     }
 
-    $calendar = new Class_Calendar($this->id_module, $this->preferences);
+    $calendar = new Class_Calendar($this->id_module,
+                                   $this->preferences,
+                                   Class_Url::getParams(),
+                                   (new ZendAfi_Controller_Action_Helper_SelectedFilters)->selectedFilters());
     $this->contenu = $this->view->calendarContent($calendar, $this->preferences);
 
     return $this->getHtmlArray();
   }
-}
\ No newline at end of file
+
+
+  public function shouldCacheContent(): bool
+  {
+    return false;
+  }
+}
diff --git a/library/storm b/library/storm
index 42ccc2f5c02..919691187a6 160000
--- a/library/storm
+++ b/library/storm
@@ -1 +1 @@
-Subproject commit 42ccc2f5c0296af146bed2dbbd1c851f73ccaf1e
+Subproject commit 919691187a60a735fee3bcdc81e1b688460b5a63
diff --git a/library/templates/Herisson/Library/ProfilePatcher.php b/library/templates/Herisson/Library/ProfilePatcher.php
index cf9aa034553..e6b4269574d 100644
--- a/library/templates/Herisson/Library/ProfilePatcher.php
+++ b/library/templates/Herisson/Library/ProfilePatcher.php
@@ -286,7 +286,6 @@ class Herisson_Library_ProfilePatcher extends Intonation_Library_ProfilePatcher
   protected function _addMultimediaPage() {
     $this->_multimedia_page = $this
       ->_createPage($this->_('Multimédia'))
-      ->setRewriteUrl($this->_('multimedia'))
       ->addWidget(Intonation_Library_Widget_Carousel_Domain_Definition::CODE,
                   Class_Profil::DIV_MAIN,
                   array_merge(['titre' => $this->_('Multimédia')],
diff --git a/tests/application/modules/admin/controllers/BibControllerTest.php b/tests/application/modules/admin/controllers/BibControllerTest.php
index ead99469549..fce84b38837 100644
--- a/tests/application/modules/admin/controllers/BibControllerTest.php
+++ b/tests/application/modules/admin/controllers/BibControllerTest.php
@@ -1081,7 +1081,7 @@ class BibControllerAnnecyPostWithDuplicateRewriteUrlTest
                          'rewrite_url' => 'library']);
 
     $this->assertXPathContentContains('//ul[@class="errors"]',
-                                      "L'URL 'library' est déjà définie pour l'élément profil: 'Library'");
+                                      "L'URL 'library' est déjà définie pour le profil: 'Library'");
   }
 
 
@@ -1092,7 +1092,7 @@ class BibControllerAnnecyPostWithDuplicateRewriteUrlTest
                          'ville' => 'Cran',
                          'rewrite_url' => 'cran']);
     $this->assertXPathContentContains('//ul[@class="errors"]',
-                                      "L'URL 'cran' est déjà définie pour l'élément bibliothèque: 'Cran-Gévrier'");
+                                      "L'URL 'cran' est déjà définie pour la bibliothèque: 'Cran-Gévrier'");
   }
 }
 
diff --git a/tests/application/modules/admin/controllers/ProfilControllerRewriteUrlTest.php b/tests/application/modules/admin/controllers/ProfilControllerRewriteUrlTest.php
new file mode 100644
index 00000000000..4d249cf1fce
--- /dev/null
+++ b/tests/application/modules/admin/controllers/ProfilControllerRewriteUrlTest.php
@@ -0,0 +1,57 @@
+<?php
+/**
+ * Copyright (c) 2012-2025, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class ProfilControllerRewriteUrlTest extends Admin_AbstractControllerTestCase
+{
+
+  /** @test */
+  public function rewriteUrlWithControllerExistsShouldBeWithError()
+  {
+    $this->postDispatch('/admin/profil/accueil/id_profil/1',
+                        ['libelle' => 'Principal',
+                         'rewrite_url' => 'recherche']);
+
+    $this->assertXpath('//ul[@class="errors"]/li[text()="L\'Url \'recherche\' est une url interdite"]');
+  }
+
+
+  /** @test */
+  public function rewriteUrlWithControllerNotExistsAgendaShouldBeWithoutError()
+  {
+    $this->postDispatch('/admin/profil/accueil/id_profil/1',
+                        ['libelle' => 'Principal',
+                         'rewrite_url' => 'agenda']);
+
+    $this->assertEquals('agenda', Class_Profil::getCurrentProfil()->getRewriteUrl());
+  }
+
+
+  /** @test */
+  public function rewriteUrlWithControllerNotExistsProfilRewriteShouldBeWithoutError()
+  {
+    $this->postDispatch('/admin/profil/accueil/id_profil/1',
+                        ['libelle' => 'Principal',
+                         'rewrite_url' => 'profil-rewrite']);
+
+    $this->assertEquals('profil-rewrite', Class_Profil::getCurrentProfil()->getRewriteUrl());
+  }
+}
diff --git a/tests/application/modules/admin/controllers/ProfilControllerTest.php b/tests/application/modules/admin/controllers/ProfilControllerTest.php
index 59e91cbcdef..77c7b04e4af 100644
--- a/tests/application/modules/admin/controllers/ProfilControllerTest.php
+++ b/tests/application/modules/admin/controllers/ProfilControllerTest.php
@@ -1147,7 +1147,7 @@ class Admin_ProfilControllerPostAccueilPageMusiqueWithErrorsTest extends Admin_P
   /** @test */
   public function errorURLMusicInvalidShouldBeVisible() {
     $this->assertXPathContentContains('//ul[@class="errors"]//li',
-                                      "'music +(«8' n'est pas une URL de profil valide");
+                                      "'music +(«8' n'est pas une URL valide");
   }
 
 
diff --git a/tests/application/modules/opac/controllers/RechercheControllerRewriteUrlTest.php b/tests/application/modules/opac/controllers/RechercheControllerRewriteUrlTest.php
new file mode 100644
index 00000000000..4a700a73f86
--- /dev/null
+++ b/tests/application/modules/opac/controllers/RechercheControllerRewriteUrlTest.php
@@ -0,0 +1,49 @@
+<?php
+/**
+ * Copyright (c) 2012-2025, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+/* @see : https://forge.afi-sa.net/issues/218055 */
+class RechercheControllerRewriteUrlTest extends AbstractControllerTestCase
+{
+
+  public function setUp(): void
+  {
+    parent::setUp();
+
+    $this->fixture(Class_Notice::class,
+                   ['id' => 1111,
+                    'type' => 1,
+                    'facets' => 'F_T1']);
+
+    Class_Profil::getCurrentProfil()
+      ->setRewriteUrl('recherche')
+      ->saveWithoutValidation();
+
+    $this->dispatch('/recherche/viewnotice/id/1111');
+  }
+
+
+  /** @test */
+  public function notice_1111_ShouldBeDisplayed()
+  {
+    $this->assertXPath('//body[contains(@class, "recherche_viewnotice")]');
+  }
+}
diff --git a/tests/library/Class/ProfilTest.php b/tests/library/Class/ProfilTest.php
index d08da939546..f1749845845 100644
--- a/tests/library/Class/ProfilTest.php
+++ b/tests/library/Class/ProfilTest.php
@@ -873,7 +873,7 @@ class ProfilAdulteChatenayTest extends ProfilAdulteChatenayTestCase  {
 
     $this->profil->setRewriteUrl('zork');
     $this->assertFalse($this->profil->isValid());
-    $this->assertEquals("L'URL 'zork' est déjà définie pour le profil 'Jeunesse'",
+    $this->assertEquals("L'URL 'zork' est déjà définie pour le profil: 'Jeunesse'",
                         implode(',', $this->profil->getErrors()));
   }
 
@@ -923,7 +923,7 @@ class ProfilAdulteChatenayTest extends ProfilAdulteChatenayTestCase  {
                                                     'rewrite_url' => 'nature']);
 
     $this->assertFalse($this->page_nature->isValid());
-    $this->assertEquals("L'URL 'nature' est déjà définie pour le profil 'Nature'",
+    $this->assertEquals("L'URL 'nature' est déjà définie pour le profil: 'Nature'",
                         implode(',', $this->page_nature->getErrors()));
 
   }
diff --git a/tests/library/ZendAfi/View/Helper/CalendarHistoTest.php b/tests/library/ZendAfi/View/Helper/CalendarHistoTest.php
new file mode 100644
index 00000000000..2dccf5032a0
--- /dev/null
+++ b/tests/library/ZendAfi/View/Helper/CalendarHistoTest.php
@@ -0,0 +1,118 @@
+<?php
+/**
+ * Copyright (c) 2012-2025, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class CalendarHistoTest extends AbstractControllerTestCase
+{
+
+  public function setUp(): void
+  {
+    parent::setUp();
+
+    Class_Profil::find(1)
+      ->setRewriteUrl('home')
+      ->assertSave();
+
+    $profil = $this->fixture(Class_Profil::class,
+                             ['id' => 1999,
+                              'parent_id' => 1,
+                              'rewrite_url' => 'agenda']);
+
+    $profil
+      ->setCfgAccueil(['modules' =>
+                       [21 => ['division' => 2,
+                               'type_module' => 'CALENDAR',
+                               'preferences' => ['id_categorie' => 22,
+                                                 'enabled_filters' => 'day;date;place_town;custom_field_8;',
+                                                 'layout' => 'wall']]]])
+      ->assertSave();
+
+    $profil->beCurrentProfil();
+
+    $setup_value = $this
+      ->fixture(Class_CustomField_SetupValue::class,
+                ['id' => 39,
+                 'source_id' => 'SIGB',
+                 'meta_id' => 13,
+                 'label' => 'Jeunesse'
+                ]);
+
+    $this->fixture(Class_CustomField::class,
+                   ['id' => 8,
+                    'meta' => $this->fixture(Class_CustomField_Meta::class,
+                                             ['id' => 13,
+                                              'label' => 'Public',
+                                              'field_type' => Class_CustomField_Meta::MULTI_CHECKBOX,
+                                              'indexable' => 1,
+                                              'setup_values' => [$setup_value]
+                                             ]),
+                    'priority' => 3,
+                    'model' => 'Article'
+                   ]);
+    $time_source = new TimeSourceForTest('2020-04-12 23:34:00');
+    ZendAfi_View_Helper_Template_CalendarContent::setTimeSource($time_source);
+    Class_Calendar::setTimeSource($time_source);
+
+    $this
+      ->fixture(Class_Article::class,
+                ['id' => 78,
+                 'titre' => 'Les vacances',
+                 'contenu' => 'À la mer',
+                 'status' => Class_Article::STATUS_VALIDATED,
+                 'debut' => '2020-02-16 15:00:00',
+                 'fin' => '2020-06-06 10:00:00',
+                 'events_debut' => '2020-04-16 15:00:00',
+                 'events_fin' => '2020-04-16 18:00:00',
+                 'id_lieu' => 52])
+      ->addCustomFieldValues(8, Class_CustomField_SetupValue::find(39) )
+      ->saveWithCustomFields();
+
+    $this->onLoaderOfModel(Class_Article::class)
+         ->whenCalled('getArticlesByPreferences')
+         ->answers([Class_Article::find(78)]);
+  }
+
+
+  /** @test */
+  public function onHistoPageCalendarCustomFieldJeunesseValueShouldBeSelected()
+  {
+    $this->dispatch('/cms/calendar/id_module/21/place_town/Tourcoing/custom_field_8/39');
+    $this->assertXPathContentContains('//li[@data-id="custom_field_8"]',
+                                      'Public<span> : Jeunesse');
+  }
+
+
+  /** @test */
+  public function onHistoPageAgendaCustomFieldJeunesseValueShouldBeSelected()
+  {
+    $this->dispatch('/agenda/id_module/21/place_town/Tourcoing/custom_field_8/39');
+    $this->assertXPathContentContains('//li[@data-id="custom_field_8"]',
+                                      'Public<span> : Jeunesse');
+  }
+
+
+  /** @test */
+  public function onHistorPageAgendaCustomFieldJeunesseValueShouldBeNotBeSelectedWithAnOtherIdModule()
+  {
+    $this->dispatch('/agenda/id_module/38/place_town/Tourcoing/custom_field_8/39');
+    $this->assertNotXPathContentContains('//li', 'Public<span> : Jeunesse');
+  }
+}
diff --git a/tests/scenarios/Templates/CalendarCustomFieldTest.php b/tests/scenarios/Templates/CalendarCustomFieldTest.php
new file mode 100644
index 00000000000..66a05a7fb24
--- /dev/null
+++ b/tests/scenarios/Templates/CalendarCustomFieldTest.php
@@ -0,0 +1,116 @@
+<?php
+/**
+ * Copyright (c) 2012-2025, Agence Française Informatique (AFI). All rights reserved.
+ *
+ * BOKEH is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE as published by
+ * the Free Software Foundation.
+ *
+ * There are special exceptions to the terms and conditions of the AGPL as it
+ * is applied to this software (see README file).
+ *
+ * BOKEH is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU AFFERO GENERAL PUBLIC LICENSE for more details.
+ *
+ * You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
+ * along with BOKEH; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
+ */
+
+
+class CalendarCustomFieldTest extends AbstractControllerTestCase
+{
+
+  public function setUp(): void
+  {
+    parent::setUp();
+
+    $this->_buildIntonation();
+
+    Class_Profil::find(1)
+      ->setRewriteUrl('home')
+      ->assertSave();
+
+    Class_Profil::getCurrentProfil()
+      ->setParentId(1)
+      ->setRewriteUrl('agenda')
+      ->setCfgAccueil(['modules' =>
+                       [21 => ['division' => 2,
+                               'type_module' => 'CALENDAR',
+                               'preferences' => ['id_categorie' => 22,
+                                                 'enabled_filters' => 'day;date;place_town;custom_field_8;',
+                                                 'layout' => 'wall']]]])
+      ->assertSave();
+
+    $setup_value = $this
+      ->fixture(Class_CustomField_SetupValue::class,
+                ['id' => 39,
+                 'source_id' => 'SIGB',
+                 'meta_id' => 13,
+                 'label' => 'Jeunesse'
+                ]);
+
+    $this->fixture(Class_CustomField::class,
+                   ['id' => 8,
+                    'meta' => $this->fixture(Class_CustomField_Meta::class,
+                                             ['id' => 13,
+                                              'label' => 'Public',
+                                              'field_type' => Class_CustomField_Meta::MULTI_CHECKBOX,
+                                              'indexable' => 1,
+                                              'setup_values' => [$setup_value]
+                                             ]),
+                    'priority' => 3,
+                    'model' => 'Article'
+                   ]);
+    $time_source = new TimeSourceForTest('2020-04-12 23:34:00');
+    ZendAfi_View_Helper_Template_CalendarContent::setTimeSource($time_source);
+    Class_Calendar::setTimeSource($time_source);
+
+    $this
+      ->fixture(Class_Article::class,
+                ['id' => 78,
+                 'titre' => 'Les vacances',
+                 'contenu' => 'À la mer',
+                 'status' => Class_Article::STATUS_VALIDATED,
+                 'debut' => '2020-02-16 15:00:00',
+                 'fin' => '2020-06-06 10:00:00',
+                 'events_debut' => '2020-04-16 15:00:00',
+                 'events_fin' => '2020-04-16 18:00:00',
+                 'id_lieu' => 52])
+      ->addCustomFieldValues(8, Class_CustomField_SetupValue::find(39) )
+      ->saveWithCustomFields();
+
+    $this->onLoaderOfModel(Class_Article::class)
+         ->whenCalled('getArticlesByPreferences')
+         ->answers([Class_Article::find(78)]);
+  }
+
+
+  /** @test */
+  public function onPageCalendarCustomFieldJeunesseValueShouldBeSelected()
+  {
+    $this->dispatch('/cms/calendar/id_module/21/place_town/Tourcoing/custom_field_8/39');
+    $this->assertXPathContentContains('//div[@class="calendar"]//div[@class="col custom_field_8"]//button',
+                                      'Public<span> : Jeunesse');
+  }
+
+
+  /** @test */
+  public function pageAgendaCustomFieldJeunesseValueShouldBeSelected()
+  {
+    $this->dispatch('/agenda/id_module/21/place_town/Tourcoing/custom_field_8/39');
+    $this->assertXPathContentContains('//div[@class="calendar"]//div[@class="col custom_field_8"]//button',
+                                      'Public<span> : Jeunesse');
+  }
+
+
+  /** @test */
+  public function pageAgendaCustomFieldJeunesseValueShouldBeNotBeSelectedWithAnOtherIdModule()
+  {
+    $this->dispatch('/agenda/id_module/38/place_town/Tourcoing/custom_field_8/39');
+    $this->assertNotXPathContentContains('//div[@class="calendar"]//div[@class="col custom_field_8"]//button',
+                                         'Public<span> : Jeunesse');
+  }
+}
diff --git a/tests/scenarios/Templates/HerissonTemplateTest.php b/tests/scenarios/Templates/HerissonTemplateTest.php
index 9fb65efd160..5e4c8373b54 100644
--- a/tests/scenarios/Templates/HerissonTemplateTest.php
+++ b/tests/scenarios/Templates/HerissonTemplateTest.php
@@ -77,8 +77,7 @@ class HerissonTemplateSubPagesTest extends HerissonTemplateTestCase {
   /** @test */
   public function pageMultimediaShouldBeCreated() {
     $this->assertNotNull(Class_Profil::findFirstBy(['parent_id' => $this->_current_profile->getId(),
-                                                    'libelle' => 'Multimédia',
-                                                    'rewrite_url' => 'multimedia']));
+                                                    'libelle' => 'Multimédia']));
   }
 
 
diff --git a/tests/scenarios/Templates/MyBibAppTemplateTest.php b/tests/scenarios/Templates/MyBibAppTemplateTest.php
index 69c071f2fb9..98887a1e78c 100644
--- a/tests/scenarios/Templates/MyBibAppTemplateTest.php
+++ b/tests/scenarios/Templates/MyBibAppTemplateTest.php
@@ -567,8 +567,8 @@ class MyBibAppTemplateNoveltyDomainTest extends MyBibAppTemplateTestCase {
     Class_Catalogue::getRequetes(['id_catalogue' => $domain->getId()])['req_liste']()
       ->fetchAll();
 
-    $this->assertSqlEquals(["SELECT DISTINCT `notices`.`type_doc` FROM `notices`",
-                            "SELECT `notices`.* FROM `notices` WHERE (`notices`.`date_creation` >= '2023-04-24' AND `notices`.`type` = 1) ORDER BY `notices`.`alpha_titre` ASC LIMIT 5000"]);
+    $this->assertSql("SELECT DISTINCT `notices`.`type_doc` FROM `notices`");
+    $this->assertSql("SELECT `notices`.* FROM `notices` WHERE (`notices`.`date_creation` >= '2023-04-24' AND `notices`.`type` = 1) ORDER BY `notices`.`alpha_titre` ASC LIMIT 5000");
   }
 
 
diff --git a/tests/scenarios/Templates/TemplatesWidgetCalendarTest.php b/tests/scenarios/Templates/TemplatesWidgetCalendarTest.php
index bedf4957b28..b8ca830c8d5 100644
--- a/tests/scenarios/Templates/TemplatesWidgetCalendarTest.php
+++ b/tests/scenarios/Templates/TemplatesWidgetCalendarTest.php
@@ -303,7 +303,6 @@ class TemplatesWidgetCalendarInPageDispatchTest
 
 
 
-
 class TemplatesWidgetCalendarInPageWithParamsDispatchTest
   extends AbstractControllerTestCase
 {
@@ -314,20 +313,18 @@ class TemplatesWidgetCalendarInPageWithParamsDispatchTest
 
     $this->_buildIntonation();
 
-    $this->_buildTemplateProfil(['id' => 2000]);
-
-    Class_Profil::find(1999)
+    Class_Profil::find(1)
       ->setRewriteUrl('home')
       ->assertSave();
 
-    Class_Profil::find(2000)
-      ->setParentId(1999)
+    Class_Profil::find(1999)
+      ->setParentId(1)
       ->setRewriteUrl('agenda')
       ->setCfgAccueil(['modules' =>
-                       ['21' => ['division' => 2,
-                                 'type_module' => 'CALENDAR',
-                                 'preferences' => ['id_categorie' => 22,
-                                                   'layout' => 'wall']]]])
+                       [21 => ['division' => 2,
+                               'type_module' => 'CALENDAR',
+                               'preferences' => ['id_categorie' => 22,
+                                                 'layout' => 'wall']]]])
       ->assertSave();
 
     $time_source = new TimeSourceForTest('2020-04-12 23:34:00');
@@ -369,114 +366,3 @@ class TemplatesWidgetCalendarInPageWithParamsDispatchTest
                                       '11/2020');
   }
 }
-
-
-
-
-class TemplatesWidgetCalendarInPageWithParamsCustomFieldDispatchTest
-  extends AbstractControllerTestCase
-{
-
-  public function setUp(): void
-  {
-    parent::setUp();
-
-    $this->_buildIntonation();
-
-    $this->_buildTemplateProfil(['id' => 2000]);
-
-    Class_Profil::find(1999)
-      ->setRewriteUrl('home')
-      ->assertSave();
-
-    Class_Profil::find(2000)
-      ->setParentId(1999)
-      ->setRewriteUrl('agenda')
-      ->setCfgAccueil(['modules' =>
-                       ['21' => ['division' => 2,
-                                 'type_module' => 'CALENDAR',
-                                 'preferences' => ['id_categorie' => 22,
-                                                   'enabled_filters' => 'day;date;place_town;custom_field_8;',
-                                                   'layout' => 'wall']]]])
-      ->assertSave();
-
-
-    $setup_value = $this
-      ->fixture(Class_CustomField_SetupValue::class,
-                ['id' => 39,
-                 'source_id' =>'SIGB',
-                 'meta_id' => 13,
-                 'label' => 'Jeunesse'
-                ]);
-
-    $this->fixture(Class_CustomField::class,
-                   ['id' => 8,
-                    'meta' => $this->fixture(Class_CustomField_Meta::class,
-                                             ['id' => 13,
-                                              'label' => 'Public',
-                                              'field_type' => Class_CustomField_Meta::MULTI_CHECKBOX,
-                                              'indexable' => 1,
-                                              'setup_values' =>
-                                              [
-                                               $setup_value
-                                              ]
-                                             ]),
-                    'priority' => 3,
-                    'model' => 'Article'
-                   ]);
-    $time_source = new TimeSourceForTest('2020-04-12 23:34:00');
-    ZendAfi_View_Helper_Template_CalendarContent::setTimeSource($time_source);
-    Class_Calendar::setTimeSource($time_source);
-
-    $this
-      ->fixture(Class_Article::class,
-                ['id' => 78,
-                 'titre' => 'Les vacances',
-                 'contenu' => 'À la mer',
-                 'status' => Class_Article::STATUS_VALIDATED,
-                 'debut' => '2020-02-16 15:00:00',
-                 'fin' => '2020-06-06 10:00:00',
-                 'events_debut' => '2020-04-16 15:00:00',
-                 'events_fin' => '2020-04-16 18:00:00',
-                 'id_lieu' => 52])
-      ->addCustomFieldValues(8, Class_CustomField_SetupValue::find(39) )
-      ->saveWithCustomFields();
-
-    $this->onLoaderOfModel(Class_Article::class)
-         ->whenCalled('getArticlesByPreferences')
-         ->answers([Class_Article::find(78)]);
-  }
-
-
-  /**
-   * @test
-   */
-  public function onPageCalendarCustomFieldJeunesseValueShouldBeSelected()
-  {
-    $this->dispatch('/cms/calendar/id_module/21/place_town/Tourcoing/custom_field_8/39');
-    $this->assertXPathContentContains('//div[@class="calendar"]//div[@class="col custom_field_8"]//button',
-                                      'Public<span> : Jeunesse');
-  }
-
-
-  /**
-   * @test
-   */
-  public function pageAgendaCustomFieldJeunesseValueShouldBeSelected()
-  {
-    $this->dispatch('/agenda/id_module/21/place_town/Tourcoing/custom_field_8/39');
-    $this->assertXPathContentContains('//div[@class="calendar"]//div[@class="col custom_field_8"]//button',
-                                      'Public<span> : Jeunesse');
-  }
-
-
-    /**
-   * @test
-   */
-  public function pageAgendaCustomFieldJeunesseValueShouldBeNotBeSelectedWithAnOtherIdModule()
-  {
-    $this->dispatch('/agenda/id_module/38/place_town/Tourcoing/custom_field_8/39');
-    $this->assertNotXPathContentContains('//div[@class="calendar"]//div[@class="col custom_field_8"]//button',
-                                      'Public<span> : Jeunesse');
-  }
-}
diff --git a/tests_db/UpgradeDBTest.php b/tests_db/UpgradeDBTest.php
index 6996558d846..2b2c2eac096 100644
--- a/tests_db/UpgradeDBTest.php
+++ b/tests_db/UpgradeDBTest.php
@@ -6517,6 +6517,9 @@ class UpgradeDB_474_Test extends UpgradeDBTestCase
   }
 }
 
+
+
+
 class UpgradeDB_475_Test extends UpgradeDBTestCase
 {
   protected
@@ -6583,3 +6586,81 @@ class UpgradeDB_475_Test extends UpgradeDBTestCase
     $this->assertNotColumn('activity', 'visible');
   }
 }
+
+
+
+
+class UpgradeDB_476_Test extends UpgradeDBTestCase
+{
+
+  public function prepare()
+  {
+    $this->silentQuery("INSERT INTO `bib_admin_profil` (`libelle`, `rewrite_url`) VALUES ('Test Profil 475_1', 'recherche');");
+    $this->silentQuery("INSERT INTO `bib_admin_profil` (`libelle`, `rewrite_url`) VALUES ('Test Profil 475_2', 'abonne');");
+    $this->silentQuery("INSERT INTO `bib_admin_profil` (`libelle`, `rewrite_url`) VALUES ('Test Profil 475_3', 'agenda');");
+    $this->silentQuery("INSERT INTO `bib_admin_profil` (`libelle`, `rewrite_url`) VALUES ('Test Profil 475_4', 'profil-rewrite');");
+
+    $this->silentQuery("INSERT INTO `bib_c_site` (`libelle`, `rewrite_url`) VALUES ('Test Bib 475_1', 'bib/rewrite');");
+    $this->silentQuery("INSERT INTO `bib_c_site` (`libelle`, `rewrite_url`) VALUES ('Test Bib 475_2', 'cms');");
+  }
+
+
+  public function tearDown(): void
+  {
+    $this->silentQuery("DELETE FROM `bib_admin_profil` WHERE `libelle` = 'Test Profil 475_1';");
+    $this->silentQuery("DELETE FROM `bib_admin_profil` WHERE `libelle` = 'Test Profil 475_2';");
+    $this->silentQuery("DELETE FROM `bib_admin_profil` WHERE `libelle` = 'Test Profil 475_3';");
+    $this->silentQuery("DELETE FROM `bib_admin_profil` WHERE `libelle` = 'Test Profil 475_4';");
+
+    $this->silentQuery("DELETE FROM `bib_c_site` WHERE `libelle` = 'Test Bib 475_1';");
+    $this->silentQuery("DELETE FROM `bib_c_site` WHERE `libelle` = 'Test Bib 475_2';");
+  }
+
+
+  /** @test */
+  public function profilRewriteUrlWithValidControllerRechercheShouldNotExistsAnymore()
+  {
+    $data = $this->query("SELECT `rewrite_url` FROM `bib_admin_profil` WHERE `libelle` = 'Test Profil 475_1';")->fetchAll();
+    $this->assertEquals('', $data[0]['rewrite_url']);
+  }
+
+
+  /** @test */
+  public function profilRewriteUrlWithValidControllerAbonneShouldNotExistsAnymore()
+  {
+    $data = $this->query("SELECT `rewrite_url` FROM `bib_admin_profil` WHERE `libelle` = 'Test Profil 475_2';")->fetchAll();
+    $this->assertEquals('', $data[0]['rewrite_url']);
+  }
+
+
+  /** @test */
+  public function profilRewriteUrlAgendaShouldExists()
+  {
+    $data = $this->query("SELECT `rewrite_url` FROM `bib_admin_profil` WHERE `libelle` = 'Test Profil 475_3';")->fetchAll();
+    $this->assertEquals('agenda', $data[0]['rewrite_url']);
+  }
+
+
+  /** @test */
+  public function profilRewriteUrlProfil_RewriteShouldExists()
+  {
+    $data = $this->query("SELECT `rewrite_url` FROM `bib_admin_profil` WHERE `libelle` = 'Test Profil 475_4';")->fetchAll();
+    $this->assertEquals('profil-rewrite', $data[0]['rewrite_url']);
+  }
+
+
+  /** @test */
+  public function bibRewriteUrlBibRewriteShouldExists()
+  {
+    $data = $this->query("SELECT `rewrite_url` FROM `bib_c_site` WHERE `libelle` = 'Test Bib 475_1';")->fetchAll();
+    $this->assertEquals('', $data[0]['rewrite_url']);
+  }
+
+
+  /** @test */
+  public function bibRewriteUrlWithValidControllerCmsShouldNotExistsAnymore()
+  {
+    $data = $this->query("SELECT `rewrite_url` FROM `bib_c_site` WHERE `libelle` = 'Test Bib 475_2';")->fetchAll();
+    $this->assertEquals('', $data[0]['rewrite_url']);
+  }
+}
-- 
GitLab