From 35705c1ddd0961e98cc3753e4030b3c843d58585 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?ANDRE=20s=C3=A9bastien?= <sandre@afi-sa.fr>
Date: Thu, 13 Mar 2025 11:23:12 +0100
Subject: [PATCH] hotline_221141 : dont block save for rewrite url

---
 VERSIONS_HOTLINE/221141                       |   1 +
 .../admin/controllers/BibController.php       |  31 ++--
 .../admin/controllers/ProfilController.php    |  33 ++--
 library/Class/Bib.php                         |   6 +-
 library/Class/Profil.php                      |   5 +-
 library/ZendAfi/Form/Admin/Library.php        |   2 +
 .../ZendAfi/Form/Configuration/Profile.php    |  13 +-
 .../Form/Configuration/Profile/Page.php       |  13 +-
 library/ZendAfi/Validate/RewriteUrl.php       | 150 ++----------------
 .../Validate/RewriteUrl/LibraryStrategy.php   |  37 +++--
 .../Validate/RewriteUrl/NullStrategy.php      |  86 +++++++++-
 .../Validate/RewriteUrl/ProfilStrategy.php    | 114 ++-----------
 .../RewriteUrl/StringLibraryStrategy.php      |  61 +++++++
 .../RewriteUrl/StringProfilStrategy.php       |  61 +++++++
 .../Validate/RewriteUrl/StringStrategy.php    |  68 --------
 .../ProfilControllerRewriteUrlTest.php        |  47 +++++-
 .../controllers/WidgetControllerTest.php      |  29 ++--
 tests/library/Class/ProfilTest.php            |  34 ++--
 18 files changed, 389 insertions(+), 402 deletions(-)
 create mode 100644 VERSIONS_HOTLINE/221141
 create mode 100644 library/ZendAfi/Validate/RewriteUrl/StringLibraryStrategy.php
 create mode 100644 library/ZendAfi/Validate/RewriteUrl/StringProfilStrategy.php
 delete mode 100644 library/ZendAfi/Validate/RewriteUrl/StringStrategy.php

diff --git a/VERSIONS_HOTLINE/221141 b/VERSIONS_HOTLINE/221141
new file mode 100644
index 00000000000..564ac5968e7
--- /dev/null
+++ b/VERSIONS_HOTLINE/221141
@@ -0,0 +1 @@
+ - correctif #221141 : Profil : Modifier un profil qui a une url de redirection ne génère plus de message d'erreur de validation inadéquat.
\ No newline at end of file
diff --git a/application/modules/admin/controllers/BibController.php b/application/modules/admin/controllers/BibController.php
index 762491fa12c..85fc7348236 100644
--- a/application/modules/admin/controllers/BibController.php
+++ b/application/modules/admin/controllers/BibController.php
@@ -355,38 +355,43 @@ class Admin_BibController extends ZendAfi_Controller_Action {
   }
 
 
-  public function updateLieuElementAction() {
-    if (!$library = Class_Bib::find($this->_getParam('id')))
+  public function updateLieuElementAction()
+  {
+    if ( ! ($library = Class_Bib::find($this->_getParam('id'))))
       return $this->_renderUpdateLieuElement(fn() => $this->view->renderError('Bibliothèque non trouvée'));
 
-
     $library->updateAttributes($this->getRequest()->getParams());
 
-    if (!$location = $this->getOrCreateLocation($this->_getParam('id_lieu'), $library))
-      return $this->_renderUpdateLieuElement(fn() => $this->view->renderError('Vous devez d\'abord définir le nom de la bibliothèque'));
+    if ( ! ($location = $this->getOrCreateLocation($this->_getParam('id_lieu'), $library)))
+      return $this->_renderUpdateLieuElement(fn() => $this->view->renderError('Vous devez d\'abord définir le nom de la bibliothèque'), $library);
 
     $this->_renderUpdateLieuElement(function($location_element) use ($location)
-                                    {
-                                      $location_element->setValue($location->getId());
+    {
+      $location_element->setValue($location->getId());
+
+      return
+        $this->view->tagEditLocation($location, true) .
+        $this->view->renderLieu($location);
+    }, $library);
 
-                                      return
-                                        $this->view->tagEditLocation($location, true) .
-                                        $this->view->renderLieu($location);
-                                    });
     return null;
   }
 
 
-  protected function _renderUpdateLieuElement($callback) {
+  protected function _renderUpdateLieuElement(Closure $callback): self
+  {
     $viewRenderer = $this->getHelper('ViewRenderer');
     $viewRenderer->setNoRender();
 
-    $location_element = (new ZendAfi_Form_Admin_Library())->newLocationElement();
+    $location_element = (new ZendAfi_Form_Admin_Library)
+      ->newLocationElement();
     $location_element->removeDecorator('HtmlTag');
     $location_element->removeDecorator('Label');
 
     $html = $callback($location_element);
     $this->getResponse()->setBody($location_element->render($this->view) . $html);
+
+    return $this;
   }
 
 
diff --git a/application/modules/admin/controllers/ProfilController.php b/application/modules/admin/controllers/ProfilController.php
index 3423907fe6b..b3e1a1ea7c1 100644
--- a/application/modules/admin/controllers/ProfilController.php
+++ b/application/modules/admin/controllers/ProfilController.php
@@ -310,9 +310,11 @@ class Admin_ProfilController extends ZendAfi_Controller_Action {
 
   protected function getSearchAndLoginWidget($widgets_as_array) {
     $widget_by_type = array_filter($widgets_as_array, function($widget)
-                                   { $type = $widget['type_module'];
-                                     if($type == 'LOGIN' || $type == 'RECH_SIMPLE')
-                                       return true; });
+    {
+      $type = $widget['type_module'];
+      if($type == 'LOGIN' || $type == 'RECH_SIMPLE')
+        return true;
+    });
     $result = [];
     $uniq_type_widgets = [];
     foreach($widget_by_type as $id => $widget) {
@@ -336,7 +338,8 @@ class Admin_ProfilController extends ZendAfi_Controller_Action {
                   $this->_profil->getCfgSiteAsArray(),
                   $this->_profil->getCfgAccueilAsArray());
 
-    $this->view->form = ZendAfi_Form_Configuration_Profile_Page::newWith($params);
+    $this->view->form = ZendAfi_Form_Configuration_Profile_Page::newWith($params)
+      ->addValidatorsFor($this->_profil);
     $this->view->form->setAction($this->view->url());
 
     if ( ! $this->_request->isPost() )
@@ -344,8 +347,8 @@ class Admin_ProfilController extends ZendAfi_Controller_Action {
 
     $this->_profil->setIntent($this->_getParam('intent', ''));
 
-    if( ! $this->view->form->isValidModelAndArray($this->_profil,
-                                                  $this->_request->getPost()))
+    if ( ! $this->view->form->isValidModelAndArray($this->_profil,
+                                                   $this->_request->getPost()))
       return null;
 
     return $this->updateAccueil($this->_profil);
@@ -355,14 +358,14 @@ class Admin_ProfilController extends ZendAfi_Controller_Action {
 
   public function genresAction() {
     if ($this->_request->isPost() && $_POST !== [])
+    {
+      foreach($_POST as $id => $picto)
       {
-        foreach($_POST as $id => $picto)
-          {
-            $elems=explode("_",$id);
-            $id_genre=$elems[1];
-            sqlExecute("update codif_genre set picto='$picto' where id_genre=$id_genre");
-          }
+        $elems=explode("_",$id);
+        $id_genre=$elems[1];
+        sqlExecute("update codif_genre set picto='$picto' where id_genre=$id_genre");
       }
+    }
     // Entree en maj
     $this->view->genres=fetchAll("select * from codif_genre");
     $this->view->titre = $this->_("Pictogrammes pour les genres");
@@ -379,7 +382,8 @@ class Admin_ProfilController extends ZendAfi_Controller_Action {
     $params['boite_recherche_simple_in_banniere'] = $profil->getBoiteRechercheSimpleInBanniere();
     $params['boite_login_in_banniere'] = $profil->getBoiteLoginInBanniere();
 
-    $this->view->form = ZendAfi_Form_Configuration_Profile::newWith($params);
+    $this->view->form = ZendAfi_Form_Configuration_Profile::newWith($params)
+      ->addValidatorsFor($this->_profil);
     $this->view->form->setAction($this->view->url());
 
 
@@ -427,7 +431,8 @@ class Admin_ProfilController extends ZendAfi_Controller_Action {
     $params['boite_recherche_simple_in_banniere'] = $profil->getBoiteRechercheSimpleInBanniere();
     $params['boite_login_in_banniere'] = $profil->getBoiteLoginInBanniere();
 
-    $this->view->form = ZendAfi_Form_Configuration_Profile::newWith($params);
+    $this->view->form = ZendAfi_Form_Configuration_Profile::newWith($params)
+      ->addValidatorsFor($this->_profil);
     $this->view->form->setAction($this->view->url());
 
     $this->_postProfil($profil);
diff --git a/library/Class/Bib.php b/library/Class/Bib.php
index 36e2a69df93..3c490beb038 100644
--- a/library/Class/Bib.php
+++ b/library/Class/Bib.php
@@ -923,11 +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->isValid($this);
-    foreach($validator->getMessages() as $message) {
-      $this->checkAttribute('rewrite_url', false, $message);
-    }
+    (new ZendAfi_Validate_RewriteUrl_LibraryStrategy($this))->validateUrl();
   }
 
 
diff --git a/library/Class/Profil.php b/library/Class/Profil.php
index fd613ecc59f..077d20f426e 100644
--- a/library/Class/Profil.php
+++ b/library/Class/Profil.php
@@ -1579,13 +1579,12 @@ 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_RewriteUrl;
-    $validator->isValid($this);
+    (new ZendAfi_Validate_RewriteUrl_ProfilStrategy($this))->validateUrl();
 
     $intent_validator = new ZendAfi_Validate_ProfileIntent;
     $intent_validator->isValid($this);
 
-    foreach ($intent_validator->getMessages() + $validator->getMessages() as $message)
+    foreach ($intent_validator->getMessages() as $message)
       $this->check(false, $message);
 
     return $this;
diff --git a/library/ZendAfi/Form/Admin/Library.php b/library/ZendAfi/Form/Admin/Library.php
index d24733ab283..6608c51a423 100644
--- a/library/ZendAfi/Form/Admin/Library.php
+++ b/library/ZendAfi/Form/Admin/Library.php
@@ -76,6 +76,8 @@ class ZendAfi_Form_Admin_Library extends ZendAfi_Form {
       ->addElement('text',
                    'rewrite_url',
                    ['label' => $this->_('URL de redirection'),
+                    'validators' => [(new ZendAfi_Validate_RewriteUrl)
+                                     ->initLibraryStrategy($this->_library)],
                     'size' => 50])
 
       ->addElement('comboProfils',
diff --git a/library/ZendAfi/Form/Configuration/Profile.php b/library/ZendAfi/Form/Configuration/Profile.php
index 81366dbb826..b0c9753784d 100644
--- a/library/ZendAfi/Form/Configuration/Profile.php
+++ b/library/ZendAfi/Form/Configuration/Profile.php
@@ -72,8 +72,7 @@ class ZendAfi_Form_Configuration_Profile extends ZendAfi_Form
 
       ->addElement('text',
                    'rewrite_url',
-                   ['label' => $this->_('URL du profil'),
-                    'validators' => [new ZendAfi_Validate_RewriteUrl]])
+                   ['label' => $this->_('URL du profil')])
 
       ->addElement('userfile',
                    'header_img',
@@ -425,4 +424,14 @@ class ZendAfi_Form_Configuration_Profile extends ZendAfi_Form
 
     return $this;
   }
+
+
+  public function addValidatorsFor(?Class_Profil $profil): self
+  {
+    if ($rewrite_url = $this->getElement('rewrite_url'))
+      $rewrite_url->setValidators([(new ZendAfi_Validate_RewriteUrl)
+                                   ->initProfilStrategy($profil)]);
+
+    return $this;
+  }
 }
diff --git a/library/ZendAfi/Form/Configuration/Profile/Page.php b/library/ZendAfi/Form/Configuration/Profile/Page.php
index 14130ac8c3f..a49a3afa8d2 100644
--- a/library/ZendAfi/Form/Configuration/Profile/Page.php
+++ b/library/ZendAfi/Form/Configuration/Profile/Page.php
@@ -36,8 +36,7 @@ class ZendAfi_Form_Configuration_Profile_Page extends ZendAfi_Form
 
       ->addElement('text',
                    'rewrite_url',
-                   ['label' => $this->_('URL de redirection'),
-                    'validators' => [new ZendAfi_Validate_RewriteUrl]])
+                   ['label' => $this->_('URL de redirection')])
 
       ->addElement('checkbox',
                    'use_parent_css',
@@ -96,4 +95,14 @@ class ZendAfi_Form_Configuration_Profile_Page extends ZendAfi_Form
 
     Class_Template::current()->customProfilePageForm($this);
   }
+
+
+  public function addValidatorsFor(?Class_Profil $profil): self
+  {
+    if ($rewrite_url = $this->getElement('rewrite_url'))
+      $rewrite_url->setValidators([(new ZendAfi_Validate_RewriteUrl)
+                                   ->initProfilStrategy($profil)]);
+
+    return $this;
+  }
 }
diff --git a/library/ZendAfi/Validate/RewriteUrl.php b/library/ZendAfi/Validate/RewriteUrl.php
index 2187ba889b6..7650b598590 100644
--- a/library/ZendAfi/Validate/RewriteUrl.php
+++ b/library/ZendAfi/Validate/RewriteUrl.php
@@ -22,160 +22,40 @@
 
 class ZendAfi_Validate_RewriteUrl extends Zend_Validate_Abstract
 {
-  use Trait_Translator;
 
-  const
-    INVALID_URL = 'invalidURL',
-    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 ?ZendAfi_Validate_RewriteUrl_NullStrategy $_strategy = null;
 
-  protected array $_messageTemplates = [];
-  protected bool $_validate_url = true;
-
-  public function isValid($model): bool
+  public function isValid($url)
   {
-    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();
-
-      return $this->getValidateUrl();
-    }
-
-    if ($model instanceof Class_Profil)
-      (new ZendAfi_Validate_RewriteUrl_ProfilStrategy($this, $model))->validateUrl();
-
-    return $this->getValidateUrl();
+    return $this->_strategy
+      ? $this->_strategy->addUrl($url)->validateUrl()
+      : true;
   }
 
 
-  protected function _initMessageTEmplates(): self
+  public function initProfilStrategy($model): 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 => ''];
+    $this->_strategy = (new ZendAfi_Validate_RewriteUrl_StringProfilStrategy($model))
+      ->addValidator($this);
 
     return $this;
   }
 
 
-  public function errorInvalideUrl(string $value): self
-  {
-    $this->_initMessageTEmplates();
-
-    $key = static::INVALID_URL;
-
-    $this->_messageTemplates[$key] = $this->_("'%s' n'est pas une URL valide",
-                                              $value);
-
-    return $this->_addError($key);
-  }
-
-
-  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);
-  }
-
-
-  public function errorAlreadyExistInProfil(string $value, string $label): self
-  {
-    $this->_initMessageTEmplates();
-
-    $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);
-  }
-
-
-  public function errorAlreadyExistInLibrary(string $value, string $label): self
-  {
-    $this->_initMessageTEmplates();
-
-    $key = static::URL_ALREADY_EXISTS_IN_A_MODEL;
-
-    $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
+  public function initLibraryStrategy($model): self
   {
-    $this->_initMessageTEmplates();
-
-    $key = static::URL_ALREADY_EXISTS_IN_ANOTHER_MODEL;
+    $this->_strategy = (new ZendAfi_Validate_RewriteUrl_StringLibraryStrategy($model))
+      ->addValidator($this);
 
-    $this->_messageTemplates[$key] = $this->_("L'URL '%s' est déjà définie pour la bibliothèque: '%s'",
-                                              $value,
-                                              $label);
-
-    return $this->_addError($key);
+    return $this;
   }
 
 
-  protected function _addError(string $key): self
+  public function addError(string $message): self
   {
-    $this->_validate_url = false;
-
-    $this->_error($key);
+    $this->_messageTemplates['error_rewrite'] = $message;
+    $this->_error('error_rewrite');
 
     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
index fd139fee26a..0153574f0b4 100644
--- a/library/ZendAfi/Validate/RewriteUrl/LibraryStrategy.php
+++ b/library/ZendAfi/Validate/RewriteUrl/LibraryStrategy.php
@@ -23,28 +23,26 @@
 class ZendAfi_Validate_RewriteUrl_LibraryStrategy extends ZendAfi_Validate_RewriteUrl_NullStrategy
 {
 
-  public function __construct(protected ZendAfi_Validate_RewriteUrl $_validator,
-                              protected Class_Bib $_bib)
-  {}
+  protected string $_column = 'id_site';
 
-
-  public function validateUrl(): self
+  public function validateUrl(): bool
   {
-    return $this->_checkDefaultUrl((string) $this->_bib->getRewriteUrl())
-                ->_checkBibWithSameUrlAsAnotherBib()
-                ->_checkOtherModelsUrl();
+    $this->_initUrl()
+         ->_checkDefaultUrl()
+         ->_checkBibWithSameUrlAsAnotherBib()
+         ->_checkOtherModelsUrl();
+
+    return parent::validateUrl();
   }
 
 
   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);
+        && ($profil = Class_Profil::query()
+            ->eq('rewrite_url', $this->_url)
+            ->fetchFirst()))
+      $this->_errorAlreadyExistInProfil($profil);
 
     return $this;
   }
@@ -53,11 +51,12 @@ class ZendAfi_Validate_RewriteUrl_LibraryStrategy extends ZendAfi_Validate_Rewri
   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());
+        && ($label = ($this->_queryNotId(Class_Bib::query()
+                                         ->select('libelle')
+                                         ->eq('rewrite_url', $this->_url))
+                      ->fetchFirst()
+                      ?->getLibelle() ?? '')))
+      $this->_errorAlreadyExistInLibrary($label);
 
     return $this;
   }
diff --git a/library/ZendAfi/Validate/RewriteUrl/NullStrategy.php b/library/ZendAfi/Validate/RewriteUrl/NullStrategy.php
index 2c0cf0d31b3..41cc8f9b92e 100644
--- a/library/ZendAfi/Validate/RewriteUrl/NullStrategy.php
+++ b/library/ZendAfi/Validate/RewriteUrl/NullStrategy.php
@@ -22,26 +22,41 @@
 
 class ZendAfi_Validate_RewriteUrl_NullStrategy
 {
+  use Trait_Translator;
 
-  protected string $_url = '';
+  protected string $_url;
+  protected bool $_validate_url = true;
+  protected string $_column = '';
 
-  public function validateUrl(): self
+  public function __construct(protected ?Storm_Model_Abstract $_model)
+  {}
+
+
+  public function validateUrl(): bool
   {
-    return $this;
+    return $this->_validate_url;
   }
 
 
-  protected function _checkDefaultUrl(string $url): self
+  protected function _initUrl(): self
   {
-    $this->_url = trim($url);
+    $this->_url = $this->_model
+      ? trim((string) $this->_model->getRewriteUrl())
+      : '';
+
+    return $this;
+  }
+
 
+  protected function _checkDefaultUrl(): self
+  {
     if ($this->_mustCheck() && ! preg_match("/^[a-zA-Z0-9\-_]*$/", $this->_url))
-      $this->_validator->errorInvalideUrl($this->_url);
+      $this->_errorInvalideUrl();
 
     if ($this->_mustCheck()
         && ($front_controller = Bokeh_Engine::getInstance()->getFrontController())
         && $front_controller->getDispatcher()->isValidController($this->_url))
-      $this->_validator->errorUrlForbiden($this->_url);
+      $this->_errorUrlForbiden();
 
     return $this;
   }
@@ -49,6 +64,61 @@ class ZendAfi_Validate_RewriteUrl_NullStrategy
 
   protected function _mustCheck(): bool
   {
-    return $this->_url && $this->_validator->getValidateUrl();
+    return $this->_url && $this->_validate_url;
+  }
+
+
+  protected function _checkAttrib(string $message): self
+  {
+    if ($message && $this->_model) {
+      $this->_validate_url = false;
+
+      $this->_model->checkAttribute('rewrite_url', false, $message);
+    }
+
+    return $this;
+  }
+
+
+  protected function _queryNotId(Storm_Query $query)
+  {
+    if ($this->_column
+        && $this->_model
+        && ! $this->_model->isNew())
+      $query->not_eq($this->_column, $this->_model->getId());
+
+    return $query;
+  }
+
+
+  protected function _errorInvalideUrl(): self
+  {
+    return $this->_checkAttrib($this->_("'%s' n'est pas une URL valide", $this->_url));
+  }
+
+
+  protected function _errorUrlForbiden(): self
+  {
+    return $this->_checkAttrib($this->_("L'Url '%s' est une url interdite", $this->_url));
+  }
+
+
+  protected function _errorAlreadyExistInLibrary(string $label): self
+  {
+    return $this->_checkAttrib($this->_("L'URL '%s' est déjà définie pour la bibliothèque: '%s'",
+                                        $this->_url,
+                                        $label));
+  }
+
+
+  protected function _errorAlreadyExistInProfil(Class_Profil $profil): self
+  {
+    $prefix = ($parent_rewrite = ($profil->getParentProfil()?->getLibelle() ?? ''))
+      ? ($parent_rewrite . ' > ')
+      : '';
+
+    return $this->_checkAttrib($this->_("L'URL '%s' est déjà définie pour le profil: '%s'",
+                                        $this->_url,
+                                        $prefix . $profil->getLibelle()));
   }
 }
diff --git a/library/ZendAfi/Validate/RewriteUrl/ProfilStrategy.php b/library/ZendAfi/Validate/RewriteUrl/ProfilStrategy.php
index 2c303bbeeee..e7252cf626c 100644
--- a/library/ZendAfi/Validate/RewriteUrl/ProfilStrategy.php
+++ b/library/ZendAfi/Validate/RewriteUrl/ProfilStrategy.php
@@ -23,21 +23,16 @@
 class ZendAfi_Validate_RewriteUrl_ProfilStrategy extends ZendAfi_Validate_RewriteUrl_NullStrategy
 {
 
-  protected ?string $_parent_rewrite_url = null;
+  protected string $_column = 'id_profil';
 
-  public function __construct(protected ZendAfi_Validate_RewriteUrl $_validator,
-                              protected Class_Profil $_profil)
-  {}
-
-
-  public function validateUrl(): self
+  public function validateUrl(): bool
   {
-    return $this->_checkDefaultUrl((string) $this->_profil->getRewriteUrl())
-                ->_checkProfilWithSameUrlAsAnotherProfil()
-                ->_checkProfilWithSameUrlAsAPage()
-                ->_checkPageWithSameUrlAsAPageWithoutRewriteUrl()
-                ->_checkPageWithSameUrlAsAProfil()
-                ->_checkOtherModelsUrl();
+    $this->_initUrl()
+         ->_checkDefaultUrl()
+         ->_checkProfilWithSameUrlAsAnotherProfil()
+         ->_checkOtherModelsUrl();
+
+    return parent::validateUrl();
   }
 
 
@@ -49,7 +44,7 @@ class ZendAfi_Validate_RewriteUrl_ProfilStrategy extends ZendAfi_Validate_Rewrit
                       ->eq('rewrite_url', $this->_url)
                       ->fetchFirst()
                       ?->getLibelle() ?? '')))
-      $this->_validator->errorAlreadyExistsInAnotherLibrary($this->_url, $label);
+      $this->_errorAlreadyExistInLibrary($label);
 
     return $this;
   }
@@ -58,96 +53,11 @@ class ZendAfi_Validate_RewriteUrl_ProfilStrategy extends ZendAfi_Validate_Rewrit
   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())
+        && ($profil = $this->_queryNotId(Class_Profil::query()
+                                         ->eq('rewrite_url', $this->_url))
             ->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)));
+      $this->_errorAlreadyExistInProfil($profil);
 
     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/StringLibraryStrategy.php b/library/ZendAfi/Validate/RewriteUrl/StringLibraryStrategy.php
new file mode 100644
index 00000000000..06f6ffd16ac
--- /dev/null
+++ b/library/ZendAfi/Validate/RewriteUrl/StringLibraryStrategy.php
@@ -0,0 +1,61 @@
+<?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_StringLibraryStrategy
+  extends ZendAfi_Validate_RewriteUrl_LibraryStrategy
+{
+
+  protected ZendAfi_Validate_RewriteUrl $_validator;
+
+  public function addUrl(string $url): self
+  {
+    $this->_url = trim($url);
+
+    return $this;
+  }
+
+
+  public function addValidator(ZendAfi_Validate_RewriteUrl $validator): self
+  {
+    $this->_validator = $validator;
+
+    return $this;
+  }
+
+
+  protected function _initUrl(): self
+  {
+    return $this;
+  }
+
+
+  protected function _checkAttrib(string $message): self
+  {
+    if ($message) {
+      $this->_validate_url = false;
+
+      $this->_validator->addError($message);
+    }
+
+    return $this;
+  }
+}
diff --git a/library/ZendAfi/Validate/RewriteUrl/StringProfilStrategy.php b/library/ZendAfi/Validate/RewriteUrl/StringProfilStrategy.php
new file mode 100644
index 00000000000..0574feb40ac
--- /dev/null
+++ b/library/ZendAfi/Validate/RewriteUrl/StringProfilStrategy.php
@@ -0,0 +1,61 @@
+<?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_StringProfilStrategy
+  extends ZendAfi_Validate_RewriteUrl_ProfilStrategy
+{
+
+  protected ZendAfi_Validate_RewriteUrl $_validator;
+
+  public function addUrl(string $url): self
+  {
+    $this->_url = trim($url);
+
+    return $this;
+  }
+
+
+  public function addValidator(ZendAfi_Validate_RewriteUrl $validator): self
+  {
+    $this->_validator = $validator;
+
+    return $this;
+  }
+
+
+  protected function _initUrl(): self
+  {
+    return $this;
+  }
+
+
+  protected function _checkAttrib(string $message): self
+  {
+    if ($message) {
+      $this->_validate_url = false;
+
+      $this->_validator->addError($message);
+    }
+
+    return $this;
+  }
+}
diff --git a/library/ZendAfi/Validate/RewriteUrl/StringStrategy.php b/library/ZendAfi/Validate/RewriteUrl/StringStrategy.php
deleted file mode 100644
index 43fdc0aa0aa..00000000000
--- a/library/ZendAfi/Validate/RewriteUrl/StringStrategy.php
+++ /dev/null
@@ -1,68 +0,0 @@
-<?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/tests/application/modules/admin/controllers/ProfilControllerRewriteUrlTest.php b/tests/application/modules/admin/controllers/ProfilControllerRewriteUrlTest.php
index 4d249cf1fce..a1cfda1516c 100644
--- a/tests/application/modules/admin/controllers/ProfilControllerRewriteUrlTest.php
+++ b/tests/application/modules/admin/controllers/ProfilControllerRewriteUrlTest.php
@@ -41,7 +41,7 @@ class ProfilControllerRewriteUrlTest extends Admin_AbstractControllerTestCase
                         ['libelle' => 'Principal',
                          'rewrite_url' => 'agenda']);
 
-    $this->assertEquals('agenda', Class_Profil::getCurrentProfil()->getRewriteUrl());
+    $this->assertEquals('agenda', Class_Profil::find(1)->getRewriteUrl());
   }
 
 
@@ -52,6 +52,49 @@ class ProfilControllerRewriteUrlTest extends Admin_AbstractControllerTestCase
                         ['libelle' => 'Principal',
                          'rewrite_url' => 'profil-rewrite']);
 
-    $this->assertEquals('profil-rewrite', Class_Profil::getCurrentProfil()->getRewriteUrl());
+    $this->assertEquals('profil-rewrite', Class_Profil::find(1)->getRewriteUrl());
+  }
+
+
+  /** @test */
+  public function rewriteUrlWithSameUrlAsCurrentShouldBeWithoutError()
+  {
+    Class_Profil::find(1)
+      ->setRewriteUrl('profil-rewrite')
+      ->assertSave();
+
+    $this->postDispatch('/admin/profil/accueil/id_profil/1',
+                        ['libelle' => 'Principal',
+                         'rewrite_url' => 'profil-rewrite']);
+
+    $this->assertEquals('profil-rewrite', Class_Profil::find(1)->getRewriteUrl());
+  }
+
+
+  /** @test */
+  public function rewriteUrlUniqOnNewProfilShouldBeWithoutError()
+  {
+    $this->postDispatch('/admin/profil/add',
+                        ['libelle' => 'new profil added',
+                         'rewrite_url' => 'profil-rewrite']);
+
+    $this->assertEquals('profil-rewrite',
+                        Class_Profil::findFirstBy(['libelle' => 'new profil added'])
+                        ->getRewriteUrl());
+  }
+
+
+  /** @test */
+  public function rewriteUrlAlreadyExistsOnNewProfilShouldBeWithError()
+  {
+    Class_Profil::find(1)
+      ->setRewriteUrl('profil-rewrite')
+      ->assertSave();
+
+    $this->postDispatch('/admin/profil/add',
+                        ['libelle' => 'new profil added',
+                         'rewrite_url' => 'profil-rewrite']);
+
+    $this->assertXpath('//ul[@class="errors"]/li[text()="L\'URL \'profil-rewrite\' est déjà définie pour le profil: \'portail\'"]');
   }
 }
diff --git a/tests/application/modules/admin/controllers/WidgetControllerTest.php b/tests/application/modules/admin/controllers/WidgetControllerTest.php
index e6eafef037e..a9fade3e2bc 100644
--- a/tests/application/modules/admin/controllers/WidgetControllerTest.php
+++ b/tests/application/modules/admin/controllers/WidgetControllerTest.php
@@ -2395,34 +2395,39 @@ class WidgetControllerWidgetAddFromTemplateTest extends WidgetControllerWidgetCo
 
 
 
-class WidgetControllerWidgetAddWidgetErrorFromTemplateTest extends WidgetControllerWidgetConfigurationTestCase {
-  protected
-    $_module_config;
+class WidgetControllerWidgetAddWidgetErrorFromTemplateTest
+  extends WidgetControllerWidgetConfigurationTestCase
+{
 
-  public function setUp(): void   {
+  protected $_module_config;
+
+  public function setUp(): void
+  {
     parent::setUp();
-    $profil = $this->fixture('Class_Profil', ['id' => 50,
-                                              'parent_id' => 5
-                                                     ]);
-    $profil2 = $this->fixture('Class_Profil', ['id' => 51,
-                                               'parent_id' => 5 ]);
+
+    $profil = $this->fixture(Class_Profil::class,
+                             ['id' => 50,
+                              'parent_id' => 5]);
+    $profil2 = $this->fixture(Class_Profil::class,
+                              ['id' => 51,
+                               'parent_id' => 5 ]);
 
     $profil->setRewriteUrl('same')->save();
     $profil2->setRewriteUrl('same')->saveWithoutValidation();
-    $this->dispatch('admin/widget/add/template/articles/template_no/5/id_profil/50',  true);
+    $this->dispatch('admin/widget/add/template/articles/template_no/5/id_profil/50');
     $this->_module_config = $profil->getCfgAccueilAsArray()['modules'][1];
   }
 
 
   /** @test */
   public function responseShouldRenderEditPopup() {
-    $this->assertFlashMessengerContentContains("Echec de la sauvegarde de la configuration : Erreur de sauvegarde du profil : L'URL 'same' est déjà définie pour la page");
+    $this->assertFlashMessengerContentContains("Echec de la sauvegarde de la configuration : Erreur de sauvegarde du profil : L'URL 'same' est déjà définie pour le profil: '** nouveau profil ** > ** nouveau profil **");
   }
-
 }
 
 
 
+
 class WidgetControllerWidgetAddWidgetFromTemplateTest extends WidgetControllerWidgetConfigurationTestCase {
   protected
     $_module_config;
diff --git a/tests/library/Class/ProfilTest.php b/tests/library/Class/ProfilTest.php
index f1749845845..f5c8d1c10f1 100644
--- a/tests/library/Class/ProfilTest.php
+++ b/tests/library/Class/ProfilTest.php
@@ -944,14 +944,14 @@ class ProfilAdulteChatenayTest extends ProfilAdulteChatenayTestCase  {
 
 
     $this->assertFalse($this->profil_nature->isValid());
-    $this->assertEquals("L'URL 'nature' est déjà définie pour la page 'Portail > Nature'",
+    $this->assertEquals("L'URL 'nature' est déjà définie pour le profil: 'Portail > Nature'",
                         implode(',', $this->profil_nature->getErrors()));
 
   }
 
 
   /** @test */
-  public function withSameRewriteURLButNotSameParentProfilShouldBeValid() {
+  public function withSameRewriteURLButNotSameParentProfilShouldBeNotValid() {
     $this->profil->setRewriteUrl('adulte')->assertSave();
     $this->page_histoire->setRewriteUrl('histoire')->assertSave();
 
@@ -959,13 +959,13 @@ class ProfilAdulteChatenayTest extends ProfilAdulteChatenayTestCase  {
                    ['id' => 345,
                     'libelle' => 'zork',
                     'rewrite_url' => 'zork']);
-    $other_histoire = $this->fixture(Class_Profil::class,
-                                     ['id' => 346,
-                                      'parent_id' => 345,
-                                      'libelle' => 'histoire',
-                                      'rewrite_url' => 'histoire']);
-    $this->assertTrue($other_histoire->isValid(),
-                      implode(',', $other_histoire->getErrors()));
+    $other_histoire = (new Class_Profil)
+      ->setId(346)
+      ->setParentId(345)
+      ->setLibelle('histoire')
+      ->setRewriteUrl('histoire');
+    $this->assertFalse($other_histoire->isValid(),
+                       implode(',', $other_histoire->getErrors()));
   }
 
 
@@ -978,7 +978,7 @@ class ProfilAdulteChatenayTest extends ProfilAdulteChatenayTestCase  {
 
 
   /** @test */
-  public function savePageWithSameRewriteUrlAsAnotherPageWithoutParentShouldBeValid() {
+  public function savePageWithSameRewriteUrlAsAnotherPageWithoutParentShouldNotBeValid() {
     $this->fixture(Class_Profil::class,
                    ['id' => 375,
                     'parent_id' => null,
@@ -993,7 +993,7 @@ class ProfilAdulteChatenayTest extends ProfilAdulteChatenayTestCase  {
 
     $this->profil->setRewriteUrl('adulte')->assertSave();
     $this->page_histoire->setRewriteUrl('histoire');
-    $this->assertTrue($this->page_histoire->isValid());
+    $this->assertFalse($this->page_histoire->isValid());
   }
 
 
@@ -1008,12 +1008,12 @@ class ProfilAdulteChatenayTest extends ProfilAdulteChatenayTestCase  {
                     'libelle' => 'jeunesse',
                     'rewrite_url' => '']);
 
-    $other_histoire = $this->fixture(Class_Profil::class,
-                                     ['id' => 346,
-                                      'parent_id' => 345,
-                                      'libelle' => 'histoire',
-                                      'rewrite_url' => 'histoire']);
-    $this->assertTrue($other_histoire->isValid());
+    $other_histoire = (new Class_Profil)
+      ->setId(346)
+      ->setParentId(345)
+      ->setLibelle('histoire')
+      ->setRewriteUrl('histoire');
+    $this->assertFalse($other_histoire->isValid());
   }
 
 
-- 
GitLab