From 920e3b04a9d7bf16bc10487fc4f8c3b86b69ae68 Mon Sep 17 00:00:00 2001
From: gloas <gloas@afi-sa.fr>
Date: Wed, 4 Jan 2017 17:10:38 +0100
Subject: [PATCH] migrate cms-controller to full plusgin manager

---
 .../controllers/CmsCategoryController.php     | 109 +----
 .../admin/controllers/CmsController.php       | 402 +-----------------
 library/Class/MultiSelection/Article.php      |   9 +-
 library/ZendAfi/Controller/Action.php         | 256 ++---------
 library/ZendAfi/Controller/Action/Plugins.php |  51 +++
 .../Action/RessourceDefinitions.php           |  18 +-
 .../ZendAfi/Controller/Plugin/Abstract.php    | 101 ++---
 .../Controller/Plugin/Manager/Article.php     | 389 +++++++++++++++--
 .../Plugin/Manager/ArticleCategory.php        | 112 +++++
 .../Controller/Plugin/Manager/Manager.php     | 297 +++++++++++++
 .../Plugin/MultiSelection/Abstract.php        |  49 ++-
 .../admin/controllers/CmsControllerTest.php   |  11 +-
 tests/scenarios/Manager/ManagerTest.php       |  58 ++-
 13 files changed, 1008 insertions(+), 854 deletions(-)
 create mode 100644 library/ZendAfi/Controller/Action/Plugins.php
 create mode 100644 library/ZendAfi/Controller/Plugin/Manager/ArticleCategory.php
 create mode 100644 library/ZendAfi/Controller/Plugin/Manager/Manager.php

diff --git a/application/modules/admin/controllers/CmsCategoryController.php b/application/modules/admin/controllers/CmsCategoryController.php
index 7e2dc912def..0fab4456b51 100644
--- a/application/modules/admin/controllers/CmsCategoryController.php
+++ b/application/modules/admin/controllers/CmsCategoryController.php
@@ -23,6 +23,10 @@
 class Admin_CmsCategoryController extends ZendAfi_Controller_Action {
   protected $_bib;
 
+  public function getPlugins() {
+    return ['ZendAfi_Controller_Plugin_Manager_ArticleCategory'];
+  }
+
   public function getRessourceDefinitions() {
     return
       ['model' => ['class' => 'Class_ArticleCategorie',
@@ -36,117 +40,18 @@ class Admin_CmsCategoryController extends ZendAfi_Controller_Action {
                      'edit' => ['title' => $this->_("Modifier une catégorie")]],
 
        'after_add' => function ($model) {
-          $this->_redirectToTreeView($model);
+          $this->_redirectToReferer();
         },
 
        'after_edit' => function ($model) {
-         $this->_redirectToTreeView($model);
+         $this->_redirectToReferer();
        },
 
        'after_delete' => function($model) {
-         $this->_redirect($this->_deleteBackUrl($model));
+         $this->_redirectToReferer();
        },
 
        'form_class_name' => 'ZendAfi_Form_Admin_CmsCategory'];
   }
-
-
-  protected function _updateNewModel($model) {
-    if ($parent = Class_ArticleCategorie::find($this->_getParam('id'))) {
-      $model->setParentCategorie($parent)
-            ->setBib($parent->getBib());
-      return;
-    }
-
-    $this->_handleBibFor($model);
-  }
-
-
-  protected function _handleBibFor($category) {
-    if ($bib = $this->getDefaultBib())
-      $category->setBib($bib);
-  }
-
-
-  protected function _redirectToTreeView($model) {
-    $this->_redirect($this->_backUrl($model));
-  }
-
-
-  protected function _backUrl($model) {
-    $is_list_mode = Class_AdminVar::isArticlesListMode();
-    if (($model->isNew() || $is_list_mode)
-        && $parent = $model->getParentCategorie())
-      return $this->_withPageUrl(sprintf('admin/cms/index/id_cat/%d', $parent->getId()));
-
-    return $this->_withPageUrl($is_list_mode ?
-                               sprintf('admin/cms/index/id_bib/%d',
-                                       ($bib = $model->getBib()) ? $bib->getId() : 0) :
-                               sprintf('admin/cms/index/id_cat/%d', $model->getId()));
-  }
-
-
-  protected function _deleteBackUrl($model) {
-    $is_list_mode = Class_AdminVar::isArticlesListMode();
-    if ($parent = $model->getParentCategorie())
-      return $this->_withPageUrl(sprintf('admin/cms/index/id_cat/%d', $parent->getId()));
-
-    return $this->_withPageUrl($is_list_mode ?
-                               sprintf('admin/cms/index/id_bib/%d',
-                                       ($bib = $model->getBib()) ? $bib->getId() : 0) :
-                               'admin/cms/index');
-  }
-
-
-  protected function _withPageUrl($url) {
-    return ($page = $this->_getParam('page'))
-      ? $url . '/page/' . $page : $url;
-  }
-
-
-  protected function _postEditAction($model) {
-    if (null === $model->getBib())
-      $this->_handleBibFor($model);
-
-    if (Class_Users::getIdentity()->isRoleMoreThanModoPortail())
-      $this->view->permissions = $this->view
-        ->groupsPermissions($model,
-                            Class_Permission::getCmsPermissions(),
-                            $this->view->url(['module' => 'admin',
-                                              'controller' => 'cms-category',
-                                              'action' => 'permissions',
-                                              'id' => $model->getId()],
-                                             null, true));
-  }
-
-
-  protected function getDefaultBib() {
-    $identity = Class_Users::getIdentity();
-
-    return ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB >= $identity->getRoleLevel()  ?
-      $identity->getBib() : Class_Bib::find((int)$this->_getParam('id_bib'));
-  }
-
-
-  public function permissionsAction() {
-    $this->_helper->getHelper('ViewRenderer')->setNoRender();
-    if (!$category = Class_ArticleCategorie::find($this->_getParam('id', 0)))
-      $this->_redirect('admin/cms/index');
-
-    $this->_helper->groupPermissions($category, $this->_getParam('perms', []));
-    $this->_helper->notify('Permissions sauvegardées');
-    $this->_redirect(sprintf('admin/cms-category/edit/id/%d', $category->getId()));
-  }
-
-
-  /**
-   * @param Storm_Model_Abstract $model
-   * @return Zend_Form
-   */
-  protected function _getForm($model) {
-    $form = parent::_getForm($model);
-    $form->setAttrib('data-backurl', Class_Url::absolute($this->_backUrl($model)));
-    return $form;
-  }
 }
 ?>
\ No newline at end of file
diff --git a/application/modules/admin/controllers/CmsController.php b/application/modules/admin/controllers/CmsController.php
index 0077e2541ce..629012a0b07 100644
--- a/application/modules/admin/controllers/CmsController.php
+++ b/application/modules/admin/controllers/CmsController.php
@@ -22,6 +22,12 @@
 class Admin_CmsController extends ZendAfi_Controller_Action {
   private $_bib;
 
+  public function getPlugins() {
+    return ['ZendAfi_Controller_Plugin_Manager_Article',
+            'ZendAfi_Controller_Plugin_MultiSelection_Article'];
+  }
+
+
   public function getRessourceDefinitions() {
     return ['model' => ['class' => 'Class_Article',
                         'name' => 'article',
@@ -33,9 +39,8 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
                            'successful_delete' => $this->_('Article "%s" supprimé')],
 
             'actions' => ['add' => ['title' => $this->_("Ajouter un article")]],
-            'after_edit' => function ($model) { $model->index(); },
-            'plugins' => ['ZendAfi_Controller_Plugin_Manager_Article',
-                          'ZendAfi_Controller_Plugin_MultiSelection_Article']];
+            'after_edit' => function ($model) { $model->index(); }
+    ];
   }
 
 
@@ -170,317 +175,12 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
   }
 
 
-  protected function _updateNewModel($article) {
-    if ('newsduplicate' == $this->_request->getActionName()
-        && $original = Class_Article::find($this->_getParam('id', 0))) {
-      $article->updateAttributes($original->copy()->toArray());
-      return $this;
-    }
-
-    $article->setAuteur(Class_Users::getIdentity());
-
-    if (!$category = $this->getCategoryAndSetComboCat())
-      return $this;
-
-    $article->setCategorie($category);
-    if ($domaine = Class_Catalogue::findWithSamePathAs($category))
-      $article->setDomaineIds($domaine->getId());
-    return $this;
-  }
-
-
-  protected function _doBeforeSave($article) {
-    $article->updateDateMaj();
-    return $this;
-  }
-
-
-  protected function _doAfterSave($article) {
-    if($id_module = $this->_getParam('id_module'))
-      $this->updateConfigBoiteNews($id_module,$article);
-    $this->_notifyArticleChanged($article);
-    return $this;
-  }
-
-
-  public function newsduplicateAction() {
-    if (!$model = Class_Article::find($this->_getParam('id'))) {
-      $this->_redirect('admin/cms');
-      return;
-    }
-
-    $this->view->titre = $this->_('Dupliquer l\'article: %s', $model->getTitre());
-
-    if (!$this->_canModify($model->getCategorie())) {
-      $this->_helper->notify($this->view->_('Vous n\'avez pas la permission "%s"',
-                                            $this->view->titre));
-      $this->_redirectToIndex();
-      return;
-    }
-
-    $this->_setParam('id_cat', $model->getCategorie()->getId());
-    parent::addAction();
-
-    $this->view->titre = $this->_('Dupliquer l\'article: %s', $model->getTitre());
-    $this->view->form
-      ->setAction($this->view->url(['module' => 'admin',
-                                    'controller' => 'cms',
-                                    'action' => 'add',
-                                    'id_cat' => $model->getCategorie()->getId()],
-                                   null, true));
-
-    $this->getHelper('ViewRenderer')->setScriptAction('add');
-  }
-
-
-  public function getCategoryAndSetComboCat() {
-    if (!$category = Class_ArticleCategorie::find($this->_getParam('id_cat'))) {
-      $this->_redirect('admin/cms');
-      return;
-    }
-
-    if (null === ($category->getBib()))
-      $category->setBib($this->_bib);
-
-    $this->view->combo_cat = $this->view->comboCategories($category);
-    return $category;
-  }
-
-
-  protected function updateConfigBoiteNews($id_module, $article){
-    $profil = Class_Profil::getCurrentProfil();
-    $module_config = $profil->getModuleAccueilConfig($id_module, 'NEWS');
-    $id_items= array_filter(explode('-',$module_config['preferences']['id_items']));
-    array_unshift($id_items,$article->getId());
-    $module_config['preferences']['id_items'] = implode('-',$id_items);
-    $profil->updateModuleConfigAccueil($id_module, $module_config);
-    $profil->save();
-    return $this;
-  }
-
-
-  protected function comboLieuOptions() {
-    $combo_lieu_options = ['0' => $this->_('Aucun')];
-    foreach(Class_Lieu::findAllBy(['order' => 'libelle']) as $lieu)
-      $combo_lieu_options[$lieu->getId()] = $lieu->getLibelle();
-    return $combo_lieu_options;
-  }
-
-
-  protected function _notifyArticleChanged($article) {
-    if (!Class_AdminVar::isWorkflowEnabled())
-      return;
-    $this->_sendMailWhenUpdatedStatusToValidationPending($article);
-    $this->_sendMailWhenUpdatedStatusToRefused($article);
-    $this->_sendMailWhenUpdatedStatusToValidated($article);
-  }
-
-
-  protected function _sendMailWhenUpdatedStatusToRefused($article) {
-    if ($article->old_status != Class_Article::STATUS_REFUSED &&
-        $article->getStatus() == Class_Article::STATUS_REFUSED)  {
-      $this->_sendRefusedMailToAuteur($article);
-    }
-  }
-
-
-  protected function _sendMailWhenUpdatedStatusToValidated($article) {
-    if ($article->old_status != Class_Article::STATUS_VALIDATED &&
-        $article->getStatus() == Class_Article::STATUS_VALIDATED)  {
-      $this->_sendValidatedMailToAuteur($article);
-    }
-  }
-
-
-  protected function _sendMailWhenUpdatedStatusToValidationPending($article) {
-    if (($article->old_status != Class_Article::STATUS_VALIDATION_PENDING &&
-         $article->getStatus() == Class_Article::STATUS_VALIDATION_PENDING)
-        || ($article->getStatus() > 5
-            && $article->old_status != $article->getStatus()))  {
-      $this->_sendMailToValidators($article);
-    }
-  }
-
-
-  protected function prepareMailForAuteur($article) {
-    $mail = new ZendAfi_Mail('utf8');
-    if(!$article->getAuteur()) {
-      $this->_helper->notify('Mail non envoyé: article sans auteur');
-      return;
-    }
-
-    if(!$mail_address = $article->getAuteur()->getMail()) {
-      $this->_helper->notify('Mail non envoyé: '.$article->getNomCompletAuteur().' sans mail.');
-      return;
-    }
-
-    $mail->setFrom('no-reply@afi-sa.fr')
-         ->addTo($mail_address);
-    return $mail;
-  }
-
-
-  protected function prepareBodyMail($article, $message) {
-    $replacements =
-      ['TITRE_ARTICLE' => $article->getTitre(),
-       'URL_ARTICLE' => $this->view->absoluteUrl($article->getUrl(), null, true),
-       'AUTHOR_ARTICLE' => $article->getNomCompletAuteur(),
-       'SAVED_BY_ARTICLE' => $this->identity->getNomComplet(),
-       'NEXT_STATUS_ARTICLE' => $article->getNextWorkflowStatusLabel(),
-       'STATUS_ARTICLE' => $article->getStatusLabel()];
-
-    return
-      str_replace(array_keys($replacements),
-                  array_values($replacements),
-                  $message);
-  }
-
-
-  protected function _sendRefusedMailToAuteur($article) {
-    if(!$mail = $this->prepareMailForAuteur($article))
-      return;
-    $body = $this->prepareBodyMail($article, $article->getRefusMessage());
-    $this->sendPreparedMail($mail,
-                            '[Bokeh] Refus de l\'article '.$article->getTitre(),
-                            $body);
-  }
-
-
-  protected function sendPreparedMail($mail, $subject, $body) {
-    $mail->setSubject(quoted_printable_decode($subject))
-         ->setBodyText($body,'utf-8',Zend_Mime::ENCODING_8BIT);
-
-    if ($this->_sendMail($mail))
-      $this->_helper->notify('Mail envoyé à: '.$mail->getRecipients()[0]);
-  }
-
-
-  protected function _sendValidatedMailToAuteur($article) {
-    if(!$mail = $this->prepareMailForAuteur($article))
-      return;
-
-    $body = $this->prepareBodyMail($article, $article->getValideMessage());
-    $this->sendPreparedMail($mail,
-                            '[Bokeh] Validation de l\'article '.$article->getTitre(),
-                            $body);
-  }
-
-
-  protected function _getValidatorsMail($article) {
-    return array_unique(
-                        Class_Permission::getWorkflow($article->getNextWorkflowStatus())
-                        ->getUsersOnModel($article->getCategorie())
-                        ->collect('mail')
-                        ->getArrayCopy());
-  }
-
-
-  protected function _sendMailToValidators($article) {
-    if (!$mails = $this->_getValidatorsMail($article))
-      return;
-
-    $mail = new ZendAfi_Mail('utf8');
-    $mail
-      ->setFrom('no-reply@afi-sa.fr')
-      ->addTo(implode(',', $mails))
-      ->setSubject($this->_('[Bokeh] Validation d\'article en attente: ') . $article->getTitre())
-      ->setBodyText($this->prepareBodyMail($article,
-                                           Class_AdminVar::getWorkflowTextMailArticlePending()));
-
-    if($this->_sendMail($mail))
-      $this->_helper->notify($this->_('Mail de validation envoyé aux validateurs.'));
-  }
-
-
-  protected function _sendMail($mail) {
-    try {
-      $mail->send();
-      return true;
-
-    } catch (Exception $e) {
-      $this->_helper->notify('Mail non envoyé: vérifier la configuration du serveur de mail.');
-      return false;
-    }
-  }
-
-
-  public function deleteAction() {
-    if (!$article = Class_Article::find((int)$this->_getParam('id'))) {
-      $this->_redirect('admin/cms');
-      return;
-    }
-
-    $this->view->titre = $this->view->_('Supprimer l\'article : %s',
-                                        $article->getTitre());
-
-
-    if (!$this->_canModify($article->getCategorie())) {
-      $this->_helper->notify($this->view->_('Vous n\'avez pas la permission "%s"',
-                                            $this->view->titre));
-      $this->_redirectToIndex();
-      return;
-    }
-
-    $this->view->model = $article;
-  }
-
-
-  public function forceDeleteAction() {
-    if (!$article = Class_Article::find((int)$this->_getParam('id'))) {
-      $this->_redirect('admin/cms');
-      return;
-    }
-
-    if (!$this->_canModify($article->getCategorie())) {
-      $this->_helper->notify($this->view->_('Vous n\'avez pas la permission "%s"',
-                                            $this->view->_('Supprimer l\'article : %s',
-                                                           $article->getTitre())));
-      $this->_redirectToIndex();
-      return;
-    }
-
-    $article->delete();
-    $this->_redirect($this->_backDeleteUrl($article));
-  }
-
-
   public function viewcmsAction() {
     $this->view->article = Class_Article::find((int)$this->_getParam('id'));
     $this->view->title = 'Afficher un article';
   }
 
 
-  public function makevisibleAction() {
-    $this->_toggleVisibility('visible');
-  }
-
-
-  public function makeinvisibleAction() {
-    $this->_toggleVisibility('invisible');
-  }
-
-
-  protected function _toggleVisibility($visibility) {
-    if (!$article = Class_Article::getLoader()->find((int)$this->_getParam('id'))) {
-      $this->_redirect('admin/cms');
-      return;
-    }
-
-    if (!$this->_canModify($article->getCategorie())) {
-      $this->_helper->notify($this->view->_('Vous n\'avez pas la permission "%s"',
-                                            $this->view->_('Rendre %s l\'article : %s',
-                                                           $visibility,
-                                                           $article->getTitre())));
-      $this->_redirectToIndex();
-      return;
-    }
-
-    $method = 'be' . ucfirst($visibility);
-    $article->$method();
-    $this->_redirect($this->_backUrl($article));
-  }
-
-
   private function _getTreeViewContainerActions() {
     return (new ZendAfi_View_Helper_ModelActionsTable_ArticlesCategories($this->view, 'article'))->getActions();
   }
@@ -491,97 +191,11 @@ class Admin_CmsController extends ZendAfi_Controller_Action {
   }
 
 
-  private function _getBibActions() {
-      return (new ZendAfi_View_Helper_ModelActionsTable_Bib($this->view, 'bib'))->getActions();
-  }
-
-
-  private function _toDate($str) {
-    if ($str!==null && $str!=='') {
-      $date = new Zend_Date($str, null, Zend_Registry::get('locale'));
-      return $date->toString('YYYY-MM-dd HH:mm');
-    }
-
-    return null;
-  }
-
-
   public function categoriesAction() {
     $this->_helper->viewRenderer->setNoRender();
     $this->getResponse()->setHeader('Content-Type', 'application/json; charset=utf-8');
     $this->getResponse()->setBody((new Class_ArticleCategorie())->getCategoriesJson());
   }
 
-
-  protected function _getFormValues($model) {
-    $attributes=parent::_getFormValues($model);
-    foreach(['description', 'contenu'] as $content_field)
-      $attributes[$content_field] = Class_CmsUrlTransformer::forEditing($attributes[$content_field]);
-
-    $attributes['pick_day'] = $model->getPickDayAsArray();
-    return $attributes;
-  }
-
-
-   protected function _getForm($model) {
-     $this
-       ->_definitions
-       ->setFormClassName($model->isTraduction()
-                          ? 'ZendAfi_Form_Admin_NewsTranslation'
-                          : 'ZendAfi_Form_Admin_News');
-     $form = parent::_getForm($model);
-     $form->setAttrib('data-backurl', Class_Url::absolute($this->_backUrl($model)));
-     return $form;
-   }
-
-
-  protected function _backUrl($model) {
-    if (!Class_AdminVar::isArticlesListMode())
-      return 'admin/cms/index' . (!$model->isNew() ? '/id/' . $model->getId() : '');
-
-    return $this->view->absoluteUrl(['module' => 'admin',
-                             'controller' => 'cms',
-                             'action' => 'index']);
-  }
-
-
-  protected function _backDeleteUrl($model) {
-    return sprintf('admin/cms/index/id_cat/%d%s',
-                   ($cat = $model->getCategorie()) ? $cat->getId() : 0,
-                   ($page = $this->_getParam('page')) ? '/page/'.$page : '');
-  }
-
-
-  protected function _canAdd() {
-    $category = Class_ArticleCategorie::find($this->_getParam('id_cat'));
-    return $category && $this->_canModify($category);
-  }
-
-
-  protected function _getDefaultModel($models) {
-    $article = $this->_definitions->newModel();
-    $cat = Class_ArticleCategorie::findDistinctCategories($models);
-    if (count($cat)==1) {
-      $article->setCategorie($cat[0]);
-    }
-
-    $status = Class_Article::findDistinctStatus($models);
-    if (count($status)==1) {
-      $article->setStatus($status[0]->getStatus());
-    }
-
-    return $article;
-  }
-
-
-  protected function _getEditUrl($model) {
-    $this->getRequest()->setParamSources(['_GET']);
-    $url = parent::_getEditUrl($model)
-      . (($page = $this->_getParam('page')) ? '/page/'.$page : '')
-      . (($id_cat = $this->_getParam('id_cat')) ? '/id_cat/'.$id_cat : '')
-      . (($title_search = $this->_getParam('title_search')) ? '/title_search/' . $title_search : '');
-    $this->getRequest()->setParamSources(['_GET', '_POST']);
-    return $url;
-  }
 }
 ?>
\ No newline at end of file
diff --git a/library/Class/MultiSelection/Article.php b/library/Class/MultiSelection/Article.php
index dc63b411384..238063fb068 100644
--- a/library/Class/MultiSelection/Article.php
+++ b/library/Class/MultiSelection/Article.php
@@ -90,8 +90,11 @@ class Class_MultiSelection_Article extends Class_MultiSelection_Abstract {
    public function acceptActionsVisitor($visitor) {
      $user = Class_Users::getIdentity();
      $permission_closure = function($model) use($user){
+       if('Class_Article' == get_class($model))
+         $model = $model->getCategorie();
+
        return $user
-       ->hasAnyPermissionOn($model->getCategorie(),
+       ->hasAnyPermissionOn($model,
                             [Class_Permission::createArticle(),
                              Class_Permission::createArticleCategory()]);
      };
@@ -101,13 +104,13 @@ class Class_MultiSelection_Article extends Class_MultiSelection_Abstract {
 
       ->visitLeafCondition(function($model) use($permission_closure)
                            {
-                             return $this->isSelected($model->getId)
+                             return $this->isSelected($model->getId())
                                && $permission_closure($model);
                            })
 
       ->visitNodeCondition(function($model) use($permission_closure)
                            {
-                             return $this->isCategorySelected($model->getId)
+                             return $this->isCategorySelected($model->getId())
                                && $permission_closure($model);
                            })
 
diff --git a/library/ZendAfi/Controller/Action.php b/library/ZendAfi/Controller/Action.php
index 7fd345b3809..483476a3ff8 100644
--- a/library/ZendAfi/Controller/Action.php
+++ b/library/ZendAfi/Controller/Action.php
@@ -70,7 +70,9 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
 
   public function preDispatch() {
     $this->_definitions = new ZendAfi_Controller_Action_RessourceDefinitions($this->getRessourceDefinitions());
-    $this->_plugins = $this->view->plugins = $this->_definitions->getPlugins($this);
+    $plugin_helper = new ZendAfi_Controller_Action_Plugins($this);
+    $this->_plugins =  $plugin_helper->getPlugins();
+    $this->_plugins = $this->view->plugins = $plugin_helper->init();
 
     if ($this->isPopupRequest())
       $this->switchToPopupMode();
@@ -80,6 +82,9 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
   }
 
 
+
+
+
   protected function _redirect($url, array $options = array()) {
     if ($this->isPopupRequest() && $this->_request->isPost()) {
       $this
@@ -112,6 +117,11 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
   }
 
 
+  public function getPlugins() {
+    return [];
+  }
+
+
   public function indexAction() {
     if ($this->_response->isRedirect())
       return;
@@ -129,93 +139,6 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
   }
 
 
-  public function deleteAction() {
-    if ($this->_response->isRedirect())
-      return;
-
-    if ($model = $this->_findModel()) {
-      $values = $this->_getCustomFieldModelValues($model);
-      $values->deleteValues();
-      $model->delete();
-      $this->_helper->notify($this->_definitions->successfulDeleteMessage($model));
-    }
-    $this->_redirectToIndex();
-    $this->_definitions->doAfterDelete($model);
-  }
-
-
-  protected function _addModelToView($model) {
-    $model_name = $this->_definitions->getModelName();
-    $this->view->model_name = $model_name;
-    $this->view->$model_name = $model;
-  }
-
-
-  public function addAction() {
-    if ($this->_response->isRedirect())
-      return;
-
-    if (!$this->_canAdd()) {
-      $this->_helper->notify($this->view->_('Vous n\'avez pas la permission "%s"',
-                                            $this->_definitions->addActionTitle()));
-      $this->_redirectToIndex();
-      return;
-    }
-
-    $this->view->titre = $this->_definitions->addActionTitle();
-    $model = $this->_definitions->newModel();
-    $this->_updateNewModel($model);
-    $this->_addModelToView($model);
-
-    if ($this->_setupFormAndSave($model)) {
-      $this->_helper->notify($this->_definitions->successfulAddMessage($model));
-      $this->_redirectToEdit($model);
-      $this->_definitions->doAfterAdd($model);
-    }
-  }
-
-
-  protected function _updateNewModel($model) {
-    return $this;
-  }
-
-
-  protected function _canAdd() {
-    return true;
-  }
-
-
-  protected function _getCustomFieldModelValues($model) {
-    return Class_CustomField_Model::getModel($this->_definitions->getModelClass())
-      ->find($model->getId());
-  }
-
-
-  protected function _getCustomFieldForm($model_values) {
-    return new ZendAfi_Form_Admin_CustomFields_ModelValues(['model_values' => $model_values]);
-  }
-
-
-  protected function processMulticheckboxFromPost($form, $clean = false) {
-    $defaults = [];
-    foreach ($form->getMulticheckboxNames() as $checkbox_name)
-      $defaults[$checkbox_name] = [];
-
-    $post = array_merge($defaults, $this->_getPost());
-    if ($clean)
-      $post = $form->deleteUnchanged($post);
-
-    $this->custom_values = [];
-
-    foreach ($post as $k=>$v)
-      if (preg_match('/field_[0-9]+/', $k)) {
-        $this->custom_values[$k] = $v;
-        unset($post[$k]);
-      }
-    return $post;
-  }
-
-
   protected function _redirectToIndex() {
     $url = '/admin/'.$this->_request->getControllerName().'/index';
     $closure = function($item, $value) use (&$url) {
@@ -244,15 +167,6 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
   }
 
 
-  protected function _redirectToEdit($model) {
-    $this->_redirectClose($this->_getEditUrl($model));
-  }
-
-
-  protected function _getEditUrl($model) {
-    return sprintf('/admin/%s/edit/id/%s',
-                   $this->_request->getControllerName(), $model->getId());
-  }
 
 
   protected function _getPost() {
@@ -260,98 +174,6 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
   }
 
 
-  protected function _setupFormAndSave($model) {
-    $form = $this->_getForm($model);
-
-    $this->view->form = $form;
-    if (!$this->_request->isPost())
-      return false;
-
-    $post = $this->processMulticheckboxFromPost($form);
-    $model->updateAttributes($post);
-
-    if ((!$form->isValidModelAndArray($model, $this->_getPost())))
-      return false;
-
-    $this->_doBeforeSave($model);
-
-    if (!$model->save())
-      return false;
-
-    $model_values = $this->_getCustomFieldModelValues($model);
-    $custom_form = $this->_getCustomFieldForm($model_values);
-    $custom_form->populate($this->custom_values);
-    $custom_form->updateModelValues();
-    $model_values->save();
-
-    $this->_doAfterSave($model);
-    return true;
-  }
-
-
-  protected function _doBeforeSave($model) {
-    return $this;
-  }
-
-
-  protected function _doAfterSave($model) {
-    return $this;
-  }
-
-
-  /**
-   * @param Storm_Model_Abstract $model
-   * @return Zend_Form
-   */
-  protected function _getForm($model) {
-    $model_values = $this->_getCustomFieldModelValues($model);
-    $custom_form = $this->_getCustomFieldForm($model_values);
-
-    if ($this->_definitions->getFormClassName())
-      return $this->_getFormWith($model, $custom_form);
-
-    if (!$form = $this->_definitions->getForm()) {
-      $form = new ZendAfi_Form( ['id' => $this->_definitions->getModelName()] );
-      $form->populateFormFromGroupsDefinitions($this->_definitions->getDisplayGroups());
-    }
-
-    $form = $form->populate($this->_request->getParams());
-    $form = $form->populate($this->_getFormValues($model));
-
-    return $form
-      ->populate($this->_request->getParams())
-      ->populate($this->_getFormValues($model));
-  }
-
-
-  protected function _getFormValues($model) {
-    return $model->toArray();
-  }
-
-
-  protected function _getFormWith($model, $custom_form) {
-    $formClass = $this->_definitions->getFormClassName();
-    foreach ($custom_form->getElements() as $element) {
-      if (!$value=$this->_request->getParam($element->getName()))
-        continue;
-      $element->setValue($value);
-    }
-
-    $form = $formClass::newWith(
-                                array_merge($this->_getFormValues($model), $this->_request->getParams()),
-                                $custom_form
-    );
-    $form->setAction($this->view->url());
-    return $form;
-
-  }
-
-
-  /**
-   * Hook appelé en fin d'action d'édition
-   * @param $model Storm_Model_Abstract
-   */
-  protected function _postEditAction($model) {}
 
   protected function _stayOnPage() {
     $this->_redirect($this->_request->getServer('HTTP_REFERER'));
@@ -484,10 +306,6 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
   }
 
 
-  protected function _getDefaultModel($models) {
-    return  $this->_definitions->newModel();
-  }
-
 
   public function acceptVisitor($plugin) {
     $plugin
@@ -496,7 +314,6 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
       ->visitResponse($this->_response)
       ->visitHelper($this->_helper)
       ->visitViewRenderer($this->getHelper('ViewRenderer'))
-      ->visitDefinitions($this->_definitions)
       ->visitGetParam(function($key)
                       {
                         return $this->_getParam($key);
@@ -513,50 +330,25 @@ class ZendAfi_Controller_Action extends Zend_Controller_Action {
                       {
                         $this->_redirect($url);
                       })
-      ->visitGetForm(function($model)
-                     {
-                       return $this->_getForm($model);
-                     })
-      ->visitGetPost(function()
-                     {
-                       return $this->_getPost();
-                     })
-      ->visitDoBeforeSave(function($model)
-                          {
-                            return $this->_doBeforeSave($model);
-                          })
-      ->visitDoAfterSave(function($model)
-                          {
-                            return $this->_doAfterSave($model);
-                          })
-      ->visitCustomFieldModelValues(function($model)
-                                    {
-                                      return $this->_getCustomFieldModelValues($model);
-                                    })
-      ->visitCustomFieldForm(function($model)
+      ->visitRedirectClose(function($url, $options = [])
+                           {
+                             $this->_redirectClose($url, $options);
+                           })
+      ->visitRedirectToIndex(function()
                              {
-                               return $this->_getCustomFieldForm($model);
+                               $this->_redirectToIndex();
                              })
       ->visitForward(function($action)
                      {
                        return $this->_forward($action);
                      })
-      ->visitDefaultModel(function($models)
-                          {
-                            return $this->_getDefaultModel($models);
-                          })
-      ->visitProcessMultiCheckbox(function($form, $clean)
-                                  {
-                                    return $this->processMulticheckboxFromPost($form, $clean);
-                                  })
-      ->visitCustomValues(function() {
-                                        return $this->custom_values;
-                                      })
-      ->visitAddModelToView(function($model)
-                            {
-                              return $this->_addModelToView($model);
-                            })
-      ;
+      ->visitGetPost(function()
+                     {
+                       return $this->_getPost();
+                     })
+
+      ->visitDefinitions($this->_definitions)
+      ->visitPlugins($this->_plugins);
     return $this;
   }
 }
diff --git a/library/ZendAfi/Controller/Action/Plugins.php b/library/ZendAfi/Controller/Action/Plugins.php
new file mode 100644
index 00000000000..89bdf119d94
--- /dev/null
+++ b/library/ZendAfi/Controller/Action/Plugins.php
@@ -0,0 +1,51 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, 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_Controller_Action_Plugins {
+  protected $_plugins;
+
+  public function __construct($controller) {
+    if(!$plugins = $controller->getPlugins())
+      $instances = [];
+
+    $instances = array_map(function($plugin) use ($controller)
+                         {
+                           return new $plugin($controller);
+                         },
+                         $plugins);
+
+    $this->_plugins = new Storm_Collection($instances);
+  }
+
+
+  public function getPlugins() {
+    return $this->_plugins;
+  }
+
+  public function init() {
+    return $this->_plugins->eachDo(function($plugin)
+                                   {
+                                     $plugin->init();
+                                   });
+  }
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Action/RessourceDefinitions.php b/library/ZendAfi/Controller/Action/RessourceDefinitions.php
index ec9a2f0b81d..ba3ef36ca07 100644
--- a/library/ZendAfi/Controller/Action/RessourceDefinitions.php
+++ b/library/ZendAfi/Controller/Action/RessourceDefinitions.php
@@ -203,7 +203,9 @@ class ZendAfi_Controller_Action_RessourceDefinitions {
 
 
   public function getDisplayGroups() {
-    return $this->_definitions['display_groups'];
+    if (isset($this->_definitions['display_groups']))
+      return $this->_definitions['display_groups'];
+    return [];
   }
 
 
@@ -239,18 +241,4 @@ class ZendAfi_Controller_Action_RessourceDefinitions {
       ? $this->_definitions['model_actions']
       : [];
   }
-
-
-  public function getPlugins($controller) {
-    $plugins = [];
-
-    if(isset($this->_definitions['plugins']))
-      $plugins = array_map(function($plugin) use ($controller)
-                           {
-                             return new $plugin($controller);
-                           },
-                           $this->_definitions['plugins']);
-
-    return new Storm_Collection($plugins);
-  }
 }
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/Abstract.php b/library/ZendAfi/Controller/Plugin/Abstract.php
index bf098086131..66bc8daddcf 100644
--- a/library/ZendAfi/Controller/Plugin/Abstract.php
+++ b/library/ZendAfi/Controller/Plugin/Abstract.php
@@ -36,7 +36,17 @@ abstract class ZendAfi_Controller_Plugin_Abstract {
 
 
   public function __construct($controller) {
-    $this->_controller = $controller->acceptVisitor($this);
+    $this->_controller = $controller;
+  }
+
+
+  public function init(){
+    $this->_controller->acceptVisitor($this);
+  }
+
+
+  public function acceptVisitor($visitor) {
+    return $this;
   }
 
 
@@ -50,6 +60,20 @@ abstract class ZendAfi_Controller_Plugin_Abstract {
   }
 
 
+  public function visitPlugins($plugins) {
+    $plugins
+      ->reject(function($plugin)
+               {
+                 return $plugin == $this;
+               })
+      ->eachDo(function($plugin)
+               {
+                 $plugin->acceptVisitor($this);
+               });
+    return $this;
+  }
+
+
   public function visitView($view){
     $this->_view = $view;
     return $this;
@@ -98,6 +122,18 @@ abstract class ZendAfi_Controller_Plugin_Abstract {
   }
 
 
+  public function visitRedirectClose($callback) {
+    $this->_redirect_close = $callback;
+    return $this;
+  }
+
+
+  public function visitRedirectToIndex($callback) {
+    $this->_redirect_to_index = $callback;
+    return $this;
+  }
+
+
   public function visitGetParam($callback) {
     $this->_get_param = $callback;
     return $this;
@@ -181,11 +217,6 @@ abstract class ZendAfi_Controller_Plugin_Abstract {
   }
 
 
-  protected function _getDefaultModel($models) {
-    return  call_user_func($this->_default_model, $models);
-  }
-
-
   protected function _getParam($key) {
     return call_user_func($this->_get_param, $key);
   }
@@ -196,13 +227,18 @@ abstract class ZendAfi_Controller_Plugin_Abstract {
   }
 
 
+  protected function _redirectToIndex() {
+    return call_user_func($this->_redirect_to_index);
+  }
+
+
   protected function _redirect($url) {
     return call_user_func($this->_redirect, $url);
   }
 
 
-  protected function _getForm($model) {
-    return call_user_func($this->_get_form, $model);
+  protected function _redirectClose($url, $options = []) {
+    return call_user_func_array($this->_redirect_close, [$url, $options]);
   }
 
 
@@ -211,31 +247,6 @@ abstract class ZendAfi_Controller_Plugin_Abstract {
   }
 
 
-  protected function _doBeforeSave($model) {
-    return call_user_func($this->_do_before_save, $model);
-  }
-
-
-  protected function _getCustomFieldModelValues($model) {
-    return call_user_func($this->_custom_field_model_values, $model);
-  }
-
-
-  protected function _getCustomFieldForm($model_values) {
-    return call_user_func($this->_custom_field_form, $model_values);
-  }
-
-
-  protected function _doAfterSave($model) {
-    return call_user_func($this->_do_after_save, $model);
-  }
-
-
-  public function renderScript($script) {
-    $this->_controller->renderScript($script);
-  }
-
-
   protected function _setParam($key, $value, $default = null) {
     return call_user_func_array($this->_set_param, [$key, $value, $default]);
   }
@@ -246,28 +257,8 @@ abstract class ZendAfi_Controller_Plugin_Abstract {
   }
 
 
-  protected function processMulticheckboxFromPost($form, $clean = false) {
-    return call_user_func_array($this->_process_multi_checkbox, [$form, $clean]);
-  }
-
-
-  protected function getCustomValues() {
-    return call_user_func($this->_custom_values);
-  }
-
-
-  protected function _findModel() {
-    return $this->_definitions->find($this->_getParam('id'));
-  }
-
-
-  protected function _canEdit($model) {
-    return true;
-  }
-
-
-  protected function _getEditActionTitle($model) {
-    return $this->_definitions->editActionTitle($model);
+  public function renderScript($script) {
+    $this->_controller->renderScript($script);
   }
 }
 ?>
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/Manager/Article.php b/library/ZendAfi/Controller/Plugin/Manager/Article.php
index d22c9d6c99a..f38e9dbe20a 100644
--- a/library/ZendAfi/Controller/Plugin/Manager/Article.php
+++ b/library/ZendAfi/Controller/Plugin/Manager/Article.php
@@ -20,67 +20,168 @@
  */
 
 
-class ZendAfi_Controller_Plugin_Manager_Article extends ZendAfi_Controller_Plugin_Abstract {
+class ZendAfi_Controller_Plugin_Manager_Article extends ZendAfi_Controller_Plugin_Manager_Manager {
+  public function deleteAction() {
+    if (!$article = Class_Article::find((int)$this->_getParam('id'))) {
+      $this->_redirect('admin/cms');
+      return;
+    }
+
+    $this->_view->titre = $this->_view->_('Supprimer l\'article : %s',
+                                          $article->getTitre());
+
+
+    if (!$this->_canModify($article->getCategorie())) {
+      $this->_helper->notify($this->_view->_('Vous n\'avez pas la permission "%s"',
+                                             $this->_view->titre));
+      $this->_redirectToIndex();
+      return;
+    }
+
+    $this->_view->model = $article;
+  }
+
+
+public function newsduplicateAction() {
+    if (!$model = Class_Article::find($this->_getParam('id')))
+      return $this->_redirect('admin/cms');
+
+    if (!$this->_canModify($model->getCategorie())) {
+      $this->_helper->notify($this->_view->_('Vous n\'avez pas la permission "%s"',
+                                             $this->_view->titre));
+      return $this->_redirectToIndex();
+    }
+
+    if($category = $model->getCategorie())
+      $this->_setParam('id_cat', $category->getId());
 
-  public function editAction() {
+    parent::addAction();
     if ($this->_response->isRedirect())
       return;
+    $this->_view->titre = $this->_view->_('Dupliquer l\'article : %s', $model->getTitre());
+    $this->_view->form
+      ->setAction($this->_view->url(['module' => 'admin',
+                                     'controller' => 'cms',
+                                     'action' => 'add',
+                                     'id_cat' => $this->_getParam('id_cat')],
+                                    null, true));
+
+    $this->_view_renderer->setScriptAction('add');
+  }
 
-    if (!$model = $this->_findModel()) {
-      $this->_redirectToIndex();
+
+  public function makevisibleAction() {
+    $this->_toggleVisibility('visible');
+  }
+
+
+  public function makeinvisibleAction() {
+    $this->_toggleVisibility('invisible');
+  }
+
+
+  public function forceDeleteAction() {
+    if (!$article = Class_Article::find((int)$this->_getParam('id'))) {
+      $this->_redirect('admin/cms');
       return;
     }
 
-    if (!$this->_canEdit($model)) {
+    if (!$this->_canModify($article->getCategorie())) {
       $this->_helper->notify($this->_view->_('Vous n\'avez pas la permission "%s"',
-                                             $this->_getEditActionTitle($model)));
+                                            $this->_view->_('Supprimer l\'article : %s',
+                                                           $article->getTitre())));
       $this->_redirectToIndex();
       return;
     }
 
-    $this->_view->titre = $this->_getEditActionTitle($model);
-    $this->_addModelToView($model);
+    $article->delete();
+    $this->_redirect($this->_backDeleteUrl($article));
+  }
+
+
+  protected function _getDefaultModel($models) {
+    $article = $this->_definitions->newModel();
+    $cat = Class_ArticleCategorie::findDistinctCategories($models);
+    if (count($cat)==1) {
+      $article->setCategorie($cat[0]);
+    }
 
-    if ($this->_setupFormAndSave($model)) {
-      $this->_helper->notify($this->_definitions->successfulSaveMessage($model));
-      $this->_redirectToEdit($model);
-      $this->_definitions->doAfterEdit($model);
+    $status = Class_Article::findDistinctStatus($models);
+    if (count($status)==1) {
+      $article->setStatus($status[0]->getStatus());
     }
 
-    $this->_postEditAction($model);
+    return $article;
   }
 
 
-  protected function _setupFormAndSave($model) {
-    $form = $this->_getForm($model);
+  protected function _updateNewModel($article) {
+    if ('newsduplicate' == $this->_request->getActionName()
+        && $original = Class_Article::find($this->_getParam('id', 0))) {
+      $article->updateAttributes($original->copy()->toArray());
+      return $this;
+    }
 
-    $this->_view->form = $form;
-    if (!$this->_request->isPost())
-      return false;
+    $article->setAuteur(Class_Users::getIdentity());
 
-    $post = $this->processMulticheckboxFromPost($form);
-    $model->updateAttributes($post);
+    if (!$category = $this->getCategoryAndSetComboCat())
+      return $this;
 
-    if ((!$form->isValidModelAndArray($model, $this->_getPost())))
-      return false;
+    $article->setCategorie($category);
+    if ($domaine = Class_Catalogue::findWithSamePathAs($category))
+      $article->setDomaineIds($domaine->getId());
+    return $this;
+  }
 
-    $this->_doBeforeSave($model);
 
-    if (!$model->save())
-      return false;
+  public function getCategoryAndSetComboCat() {
+    if (!$category = Class_ArticleCategorie::find($this->_getParam('id_cat'))) {
+      $this->_redirect('admin/cms');
+      return;
+    }
 
-    $model_values = $this->_getCustomFieldModelValues($model);
-    $custom_form = $this->_getCustomFieldForm($model_values);
-    $custom_form->populate($this->custom_values);
-    $custom_form->updateModelValues();
-    $model_values->save();
+    if (null === ($category->getBib()))
+      $category->setBib($this->_bib);
 
-    $this->_doAfterSave($model);
-    return true;
+    $this->_view->combo_cat = $this->_view->comboCategories($category);
+    return $category;
   }
 
 
-  protected function _postEditAction($model) {}
+
+  protected function _doBeforeSave($article) {
+    $article->updateDateMaj();
+    return $this;
+  }
+
+
+  protected function _doAfterSave($article) {
+    if($id_module = $this->_getParam('id_module'))
+      $this->updateConfigBoiteNews($id_module,$article);
+    $this->_notifyArticleChanged($article);
+    return $this;
+  }
+
+
+  protected function _toggleVisibility($visibility) {
+    if (!$article = Class_Article::getLoader()->find((int)$this->_getParam('id'))) {
+      $this->_redirect('admin/cms');
+      return;
+    }
+
+    if (!$this->_canModify($article->getCategorie())) {
+      $this->_helper->notify($this->_view->_('Vous n\'avez pas la permission "%s"',
+                                            $this->_view->_('Rendre %s l\'article : %s',
+                                                           $visibility,
+                                                           $article->getTitre())));
+      $this->_redirectToIndex();
+      return;
+    }
+
+    $method = 'be' . ucfirst($visibility);
+    $article->$method();
+    $this->_redirect($this->_backUrl($article));
+  }
 
 
   protected function _findModel() {
@@ -115,7 +216,213 @@ class ZendAfi_Controller_Plugin_Manager_Article extends ZendAfi_Controller_Plugi
   }
 
 
-  public function getActions($model) {
+  protected function _getEditUrl($model) {
+    $this->_request->setParamSources(['_GET']);
+    $url = parent::_getEditUrl($model)
+      . (($page = $this->_getParam('page')) ? '/page/'.$page : '')
+      . (($id_cat = $this->_getParam('id_cat')) ? '/id_cat/'.$id_cat : '')
+      . (($title_search = $this->_getParam('title_search')) ? '/title_search/' . $title_search : '');
+    $this->_request->setParamSources(['_GET', '_POST']);
+    return $url;
+  }
+
+
+  protected function _getFormValues($model) {
+    $attributes=parent::_getFormValues($model);
+    foreach(['description', 'contenu'] as $content_field)
+      $attributes[$content_field] = Class_CmsUrlTransformer::forEditing($attributes[$content_field]);
+
+    $attributes['pick_day'] = $model->getPickDayAsArray();
+    return $attributes;
+  }
+
+
+  protected function _getForm($model) {
+    $this
+      ->_definitions
+      ->setFormClassName($model->isTraduction()
+                         ? 'ZendAfi_Form_Admin_NewsTranslation'
+                         : 'ZendAfi_Form_Admin_News');
+    $form = parent::_getForm($model);
+    $form->setAttrib('data-backurl', Class_Url::absolute($this->_backUrl($model)));
+    return $form;
+  }
+
+
+  protected function _canAdd() {
+    $category = Class_ArticleCategorie::find($this->_getParam('id_cat'));
+    return $category && $this->_canModify($category);
+  }
+
+
+
+  protected function _backUrl($model) {
+    if (!Class_AdminVar::isArticlesListMode())
+      return 'admin/cms/index' . (!$model->isNew() ? '/id/' . $model->getId() : '');
+
+    return $this->_view->absoluteUrl(['module' => 'admin',
+                                     'controller' => 'cms',
+                                     'action' => 'index']);
+  }
+
+
+  protected function _backDeleteUrl($model) {
+    return sprintf('admin/cms/index/id_cat/%d%s',
+                   ($cat = $model->getCategorie()) ? $cat->getId() : 0,
+                   ($page = $this->_getParam('page')) ? '/page/'.$page : '');
+  }
+
+  protected function _notifyArticleChanged($article) {
+    if (!Class_AdminVar::isWorkflowEnabled())
+      return;
+    $this->_sendMailWhenUpdatedStatusToValidationPending($article);
+    $this->_sendMailWhenUpdatedStatusToRefused($article);
+    $this->_sendMailWhenUpdatedStatusToValidated($article);
+  }
+
+
+  protected function _sendMailWhenUpdatedStatusToRefused($article) {
+    if ($article->old_status != Class_Article::STATUS_REFUSED &&
+        $article->getStatus() == Class_Article::STATUS_REFUSED)  {
+      $this->_sendRefusedMailToAuteur($article);
+    }
+  }
+
+
+  protected function _sendMailWhenUpdatedStatusToValidated($article) {
+    if ($article->old_status != Class_Article::STATUS_VALIDATED &&
+        $article->getStatus() == Class_Article::STATUS_VALIDATED)  {
+      $this->_sendValidatedMailToAuteur($article);
+    }
+  }
+
+
+  protected function _sendMailWhenUpdatedStatusToValidationPending($article) {
+    if (($article->old_status != Class_Article::STATUS_VALIDATION_PENDING &&
+         $article->getStatus() == Class_Article::STATUS_VALIDATION_PENDING)
+        || ($article->getStatus() > 5
+            && $article->old_status != $article->getStatus()))  {
+      $this->_sendMailToValidators($article);
+    }
+  }
+
+
+  protected function prepareMailForAuteur($article) {
+    $mail = new ZendAfi_Mail('utf8');
+    if(!$article->getAuteur()) {
+      $this->_helper->notify('Mail non envoyé: article sans auteur');
+      return;
+    }
+
+    if(!$mail_address = $article->getAuteur()->getMail()) {
+      $this->_helper->notify('Mail non envoyé: '.$article->getNomCompletAuteur().' sans mail.');
+      return;
+    }
+
+    $mail->setFrom('no-reply@afi-sa.fr')
+         ->addTo($mail_address);
+    return $mail;
+  }
+
+
+  protected function prepareBodyMail($article, $message) {
+    $this->identity = Class_Users::getIdentity();
+    $replacements =
+      ['TITRE_ARTICLE' => $article->getTitre(),
+       'URL_ARTICLE' => $this->_view->absoluteUrl($article->getUrl(), null, true),
+       'AUTHOR_ARTICLE' => $article->getNomCompletAuteur(),
+       'SAVED_BY_ARTICLE' => $this->identity->getNomComplet(),
+       'NEXT_STATUS_ARTICLE' => $article->getNextWorkflowStatusLabel(),
+       'STATUS_ARTICLE' => $article->getStatusLabel()];
+
+    return
+      str_replace(array_keys($replacements),
+                  array_values($replacements),
+                  $message);
+  }
+
+
+  protected function _sendRefusedMailToAuteur($article) {
+    if(!$mail = $this->prepareMailForAuteur($article))
+      return;
+    $body = $this->prepareBodyMail($article, $article->getRefusMessage());
+    $this->sendPreparedMail($mail,
+                            '[Bokeh] Refus de l\'article '.$article->getTitre(),
+                            $body);
+  }
+
+
+  protected function sendPreparedMail($mail, $subject, $body) {
+    $mail->setSubject(quoted_printable_decode($subject))
+         ->setBodyText($body,'utf-8',Zend_Mime::ENCODING_8BIT);
+
+    if ($this->_sendMail($mail))
+      $this->_helper->notify('Mail envoyé à: '.$mail->getRecipients()[0]);
+  }
+
+
+  protected function _sendValidatedMailToAuteur($article) {
+    if(!$mail = $this->prepareMailForAuteur($article))
+      return;
+
+    $body = $this->prepareBodyMail($article, $article->getValideMessage());
+    $this->sendPreparedMail($mail,
+                            '[Bokeh] Validation de l\'article '.$article->getTitre(),
+                            $body);
+  }
+
+
+  protected function _getValidatorsMail($article) {
+    return array_unique(
+                        Class_Permission::getWorkflow($article->getNextWorkflowStatus())
+                        ->getUsersOnModel($article->getCategorie())
+                        ->collect('mail')
+                        ->getArrayCopy());
+  }
+
+
+  protected function _sendMailToValidators($article) {
+    if (!$mails = $this->_getValidatorsMail($article))
+      return;
+
+    $mail = new ZendAfi_Mail('utf8');
+    $mail
+      ->setFrom('no-reply@afi-sa.fr')
+      ->addTo(implode(',', $mails))
+      ->setSubject($this->_view->_('[Bokeh] Validation d\'article en attente: ') . $article->getTitre())
+      ->setBodyText($this->prepareBodyMail($article,
+                                           Class_AdminVar::getWorkflowTextMailArticlePending()));
+
+    if($this->_sendMail($mail))
+      $this->_helper->notify($this->_view->_('Mail de validation envoyé aux validateurs.'));
+  }
+
+
+  protected function _sendMail($mail) {
+    try {
+      $mail->send();
+      return true;
+
+    } catch (Exception $e) {
+      $this->_helper->notify('Mail non envoyé: vérifier la configuration du serveur de mail.');
+      return false;
+    }
+  }
+
+
+  protected function updateConfigBoiteNews($id_module, $article){
+    $profil = Class_Profil::getCurrentProfil();
+    $module_config = $profil->getModuleAccueilConfig($id_module, 'NEWS');
+    $id_items= array_filter(explode('-',$module_config['preferences']['id_items']));
+    array_unshift($id_items,$article->getId());
+    $module_config['preferences']['id_items'] = implode('-',$id_items);
+    $profil->updateModuleConfigAccueil($id_module, $module_config);
+    $profil->save();
+    return $this;
+  }
+
+
+    public function getActions($model) {
     if('Class_Article' == get_class($model))
       return $this->_articleActions($model);
 
@@ -144,14 +451,14 @@ class ZendAfi_Controller_Plugin_Manager_Article extends ZendAfi_Controller_Plugi
              'label' => 'Rendre cet article invisible',
              'condition' => function($model) use ($permission_closure) {
                 return $permission_closure($model) && $model->isVisible();
-              }],
+             }],
 
             ['url' => '/admin/cms/makevisible/id/%s',
              'icon'  => 'hide',
              'label' => 'Rendre cet article visible',
              'condition' => function($model) use ($permission_closure) {
                 return $permission_closure($model) && $model->isNotVisible();
-              }],
+             }],
 
             ['url' => '/admin/cms/edit/id/%s',
              'icon'  => 'edit',
@@ -190,7 +497,7 @@ class ZendAfi_Controller_Plugin_Manager_Article extends ZendAfi_Controller_Plugi
              'label' => 'Supprimer',
              'condition' => function($model) use ($parent_permission) {
                 return $parent_permission($model) && $model->hasNoChild();
-              },
+             },
              'anchorOptions' => [
                                  'onclick' => "return confirm('Etes-vous sûr de vouloir supprimer cette catégorie ?')"]],
 
@@ -202,7 +509,7 @@ class ZendAfi_Controller_Plugin_Manager_Article extends ZendAfi_Controller_Plugi
                 ->hasAnyPermissionOn($model,
                                      [Class_Permission::createArticle(),
                                       Class_Permission::createArticleCategory()]);
-              }],
+             }],
 
             ['url' => '/admin/cms-category/add/id/%s',
              'icon'  => 'add_category',
@@ -211,7 +518,7 @@ class ZendAfi_Controller_Plugin_Manager_Article extends ZendAfi_Controller_Plugi
                 return $this->identity
                 ->hasPermissionOn(Class_Permission::createArticleCategory(),
                                   $model);
-              }]
+             }]
     ];
   }
 
@@ -227,13 +534,13 @@ class ZendAfi_Controller_Plugin_Manager_Article extends ZendAfi_Controller_Plugi
                 return $this->_identity->isRoleMoreThanModoPortail()
                 || $this->_identity->hasPermissionOn(Class_Permission::createArticleCategory(),
                                                      $model);
-              }],
+             }],
             ['url' => '/admin/bib/permissions/id/%s',
              'icon' => 'groups',
              'label' => 'Permissions par défaut',
              'condition' => function($model) {
                 return $this->_identity->isRoleMoreThanModoPortail();
-              }]
+             }]
     ];
   }
 }
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/Manager/ArticleCategory.php b/library/ZendAfi/Controller/Plugin/Manager/ArticleCategory.php
new file mode 100644
index 00000000000..acbe3c0d9fc
--- /dev/null
+++ b/library/ZendAfi/Controller/Plugin/Manager/ArticleCategory.php
@@ -0,0 +1,112 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, 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_Controller_Plugin_Manager_ArticleCategory extends ZendAfi_Controller_Plugin_Manager_Manager {
+  protected function _updateNewModel($model) {
+    if ($parent = Class_ArticleCategorie::find($this->_getParam('id'))) {
+      $model->setParentCategorie($parent)
+            ->setBib($parent->getBib());
+      return;
+    }
+
+    $this->_handleBibFor($model);
+  }
+
+
+  protected function _handleBibFor($category) {
+    if ($bib = $this->getDefaultBib())
+      $category->setBib($bib);
+  }
+
+
+  protected function _redirectToTreeView($model) {
+    $this->_redirect($this->_backUrl($model));
+  }
+
+
+  protected function _backUrl($model) {
+    $is_list_mode = Class_AdminVar::isArticlesListMode();
+    if (($model->isNew() || $is_list_mode)
+        && $parent = $model->getParentCategorie())
+      return $this->_withPageUrl(sprintf('admin/cms/index/id_cat/%d', $parent->getId()));
+
+    return $this->_withPageUrl($is_list_mode ?
+                               sprintf('admin/cms/index/id_bib/%d',
+                                       ($bib = $model->getBib()) ? $bib->getId() : 0) :
+                               sprintf('admin/cms/index/id_cat/%d', $model->getId()));
+  }
+
+
+  protected function _deleteBackUrl($model) {
+    $is_list_mode = Class_AdminVar::isArticlesListMode();
+    if ($parent = $model->getParentCategorie())
+      return $this->_withPageUrl(sprintf('admin/cms/index/id_cat/%d', $parent->getId()));
+
+    return $this->_withPageUrl($is_list_mode ?
+                               sprintf('admin/cms/index/id_bib/%d',
+                                       ($bib = $model->getBib()) ? $bib->getId() : 0) :
+                               'admin/cms/index');
+  }
+
+
+  protected function _withPageUrl($url) {
+    return ($page = $this->_getParam('page'))
+      ? $url . '/page/' . $page : $url;
+  }
+
+
+  protected function _postEditAction($model) {
+    if (null === $model->getBib())
+      $this->_handleBibFor($model);
+
+    if (Class_Users::getIdentity()->isRoleMoreThanModoPortail())
+      $this->view->permissions = $this->view
+        ->groupsPermissions($model,
+                            Class_Permission::getCmsPermissions(),
+                            $this->view->url(['module' => 'admin',
+                                              'controller' => 'cms-category',
+                                              'action' => 'permissions',
+                                              'id' => $model->getId()],
+                                             null, true));
+  }
+
+
+  protected function getDefaultBib() {
+    $identity = Class_Users::getIdentity();
+
+    return ZendAfi_Acl_AdminControllerRoles::ADMIN_BIB >= $identity->getRoleLevel()  ?
+      $identity->getBib() : Class_Bib::find((int)$this->_getParam('id_bib'));
+  }
+
+
+
+  /**
+   * @param Storm_Model_Abstract $model
+   * @return Zend_Form
+   */
+  protected function _getForm($model) {
+    $form = parent::_getForm($model);
+    $form->setAttrib('data-backurl', Class_Url::absolute($this->_backUrl($model)));
+    return $form;
+  }
+}
+?>
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/Manager/Manager.php b/library/ZendAfi/Controller/Plugin/Manager/Manager.php
new file mode 100644
index 00000000000..dbbd4d74ecf
--- /dev/null
+++ b/library/ZendAfi/Controller/Plugin/Manager/Manager.php
@@ -0,0 +1,297 @@
+<?php
+/**
+ * Copyright (c) 2012-2014, 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
+ */
+
+
+abstract class ZendAfi_Controller_Plugin_Manager_Manager extends ZendAfi_Controller_Plugin_Abstract {
+
+  public function acceptVisitor($visitor) {
+    $visitor->visitGetForm(function($model)
+                           {
+                             return $this->_getForm($model);
+                           })
+            ->visitDoBeforeSave(function($model)
+                                {
+                                  return $this->_doBeforeSave($model);
+                                })
+            ->visitDoAfterSave(function($model)
+                               {
+                                 return $this->_doAfterSave($model);
+                               })
+            ->visitCustomFieldModelValues(function($model)
+                                          {
+                                            return $this->_getCustomFieldModelValues($model);
+                                          })
+            ->visitCustomFieldForm(function($model)
+                                   {
+                                     return $this->_getCustomFieldForm($model);
+                                   })
+            ->visitDefaultModel(function($models)
+                                {
+                                  return $this->_getDefaultModel($models);
+                                })
+            ->visitProcessMultiCheckbox(function($form, $clean)
+                                        {
+                                          return $this->processMulticheckboxFromPost($form, $clean);
+                                        })
+            ->visitCustomValues(function() {
+                                              return $this->custom_values;
+                                            })
+            ->visitAddModelToView(function($model)
+                                  {
+                                    return $this->_addModelToView($model);
+                            });
+    return $this;
+  }
+
+
+  protected function _getDefaultModel($models) {
+    return  $this->_definitions->newModel();
+  }
+
+
+  protected function _addModelToView($model) {
+    $model_name = $this->_definitions->getModelName();
+    $this->_view->model_name = $model_name;
+    $this->_view->$model_name = $model;
+  }
+
+
+  public function addAction() {
+    if ($this->_response->isRedirect())
+      return;
+
+    if (!$this->_canAdd()) {
+      $this->_helper->notify($this->_view->_('Vous n\'avez pas la permission "%s"',
+                                            $this->_definitions->addActionTitle()));
+      $this->_redirectToIndex();
+      return;
+    }
+
+    $this->_view->titre = $this->_definitions->addActionTitle();
+    $model = $this->_definitions->newModel();
+    $this->_updateNewModel($model);
+    $this->_addModelToView($model);
+
+    if ($this->_setupFormAndSave($model)) {
+      $this->_helper->notify($this->_definitions->successfulAddMessage($model));
+      $this->_redirectToEdit($model);
+      $this->_definitions->doAfterAdd($model);
+    }
+  }
+
+
+  public function editAction() {
+    if ($this->_response->isRedirect())
+      return;
+
+    if (!$model = $this->_findModel()) {
+      $this->_redirectToIndex();
+      return;
+    }
+
+    if (!$this->_canEdit($model)) {
+      $this->_helper->notify($this->_view->_('Vous n\'avez pas la permission "%s"',
+                                             $this->_getEditActionTitle($model)));
+      $this->_redirectToIndex();
+      return;
+    }
+
+    $this->_view->titre = $this->_getEditActionTitle($model);
+    $this->_addModelToView($model);
+
+    if ($this->_setupFormAndSave($model)) {
+      $this->_helper->notify($this->_definitions->successfulSaveMessage($model));
+      $this->_redirectToEdit($model);
+      $this->_definitions->doAfterEdit($model);
+    }
+
+    $this->_postEditAction($model);
+  }
+
+
+
+  public function deleteAction() {
+    if ($this->_response->isRedirect())
+      return;
+
+    if ($model = $this->_findModel()) {
+      $values = $this->_getCustomFieldModelValues($model);
+      $values->deleteValues();
+      $model->delete();
+      $this->_helper->notify($this->_definitions->successfulDeleteMessage($model));
+    }
+    $this->_redirectToIndex();
+    $this->_definitions->doAfterDelete($model);
+  }
+
+
+
+
+
+  protected function _postEditAction($model) {}
+  protected function _canEdit($model) {
+    return true;
+  }
+
+
+  protected function _getEditActionTitle($model) {
+    return $this->_definitions->editActionTitle($model);
+  }
+
+
+  protected function _getEditUrl($model) {
+    return sprintf('/admin/%s/edit/id/%s',
+                   $this->_request->getControllerName(), $model->getId());
+  }
+
+
+  protected function _redirectToEdit($model) {
+    $this->_redirectClose($this->_getEditUrl($model));
+  }
+
+
+  protected function _getFormWith($model, $custom_form) {
+    $formClass = $this->_definitions->getFormClassName();
+    foreach ($custom_form->getElements() as $element) {
+      if (!$value=$this->_request->getParam($element->getName()))
+        continue;
+      $element->setValue($value);
+    }
+
+    $form = $formClass::newWith(
+                                array_merge($this->_getFormValues($model), $this->_request->getParams()),
+                                $custom_form
+    );
+    $form->setAction($this->_view->url());
+    return $form;
+
+  }
+
+
+  protected function _getFormValues($model) {
+    return $model->toArray();
+  }
+
+
+  /**
+   * @param Storm_Model_Abstract $model
+   * @return Zend_Form
+   */
+  protected function _getForm($model) {
+    $model_values = $this->_getCustomFieldModelValues($model);
+    $custom_form = $this->_getCustomFieldForm($model_values);
+
+    if ($this->_definitions->getFormClassName())
+      return $this->_getFormWith($model, $custom_form);
+
+    if (!$form = $this->_definitions->getForm()) {
+      $form = new ZendAfi_Form( ['id' => $this->_definitions->getModelName()] );
+      $form->populateFormFromGroupsDefinitions($this->_definitions->getDisplayGroups());
+    }
+
+    $form = $form->populate($this->_request->getParams());
+    $form = $form->populate($this->_getFormValues($model));
+
+    return $form
+      ->populate($this->_request->getParams())
+      ->populate($this->_getFormValues($model));
+  }
+
+
+  protected function _setupFormAndSave($model) {
+    $form = $this->_getForm($model);
+
+    $this->_view->form = $form;
+    if (!$this->_request->isPost())
+      return false;
+
+    $post = $this->processMulticheckboxFromPost($form);
+    $model->updateAttributes($post);
+
+    if ((!$form->isValidModelAndArray($model, $this->_getPost())))
+      return false;
+
+    $this->_doBeforeSave($model);
+
+    if (!$model->save())
+      return false;
+
+    $model_values = $this->_getCustomFieldModelValues($model);
+    $custom_form = $this->_getCustomFieldForm($model_values);
+    $custom_form->populate($this->custom_values);
+    $custom_form->updateModelValues();
+    $model_values->save();
+
+    $this->_doAfterSave($model);
+    return true;
+  }
+
+
+  protected function _doBeforeSave($model) {
+    return $this;
+  }
+
+
+  protected function _doAfterSave($model) {
+    return $this;
+  }
+
+  protected function processMulticheckboxFromPost($form, $clean = false) {
+    $defaults = [];
+    foreach ($form->getMulticheckboxNames() as $checkbox_name)
+      $defaults[$checkbox_name] = [];
+
+    $post = array_merge($defaults, $this->_getPost());
+    if ($clean)
+      $post = $form->deleteUnchanged($post);
+
+    $this->custom_values = [];
+
+    foreach ($post as $k=>$v)
+      if (preg_match('/field_[0-9]+/', $k)) {
+        $this->custom_values[$k] = $v;
+        unset($post[$k]);
+      }
+    return $post;
+  }
+
+
+
+  protected function _updateNewModel($model) {
+    return $this;
+  }
+
+
+  protected function _canAdd() {
+    return true;
+  }
+
+
+  protected function _getCustomFieldModelValues($model) {
+    return Class_CustomField_Model::getModel($this->_definitions->getModelClass())
+      ->find($model->getId());
+  }
+
+
+  protected function _getCustomFieldForm($model_values) {
+    return new ZendAfi_Form_Admin_CustomFields_ModelValues(['model_values' => $model_values]);
+  }
+}
\ No newline at end of file
diff --git a/library/ZendAfi/Controller/Plugin/MultiSelection/Abstract.php b/library/ZendAfi/Controller/Plugin/MultiSelection/Abstract.php
index 8daaf34df0d..8ea0849de94 100644
--- a/library/ZendAfi/Controller/Plugin/MultiSelection/Abstract.php
+++ b/library/ZendAfi/Controller/Plugin/MultiSelection/Abstract.php
@@ -61,8 +61,8 @@ abstract class ZendAfi_Controller_Plugin_MultiSelection_Abstract extends ZendAfi
       return $this->_redirectToIndex();
 
     $this->_view->titre = $this->_('Modifier %d %s',
-                                  $models->count(),
-                                  $this->_definitions->pluralizeModelName());
+                                   $models->count(),
+                                   $this->_definitions->pluralizeModelName());
 
     if ($this->_setupFormAndUpdateModels($models->getArrayCopy())) {
       $this->_helper->notify($this->_('Les %d %s sélectionnés ont bien été sauvegardés',
@@ -145,4 +145,49 @@ abstract class ZendAfi_Controller_Plugin_MultiSelection_Abstract extends ZendAfi
   public function render() {
     return $this->_view->Plugin_MultiSelection_Widget($this->_multi_selection);
   }
+
+
+  protected function _getForm($model) {
+    return call_user_func($this->_get_form, $model);
+  }
+
+
+  protected function _doBeforeSave($model) {
+    return call_user_func($this->_do_before_save, $model);
+  }
+
+
+  protected function _getCustomFieldModelValues($model) {
+    return call_user_func($this->_custom_field_model_values, $model);
+  }
+
+
+  protected function _getCustomFieldForm($model_values) {
+    return call_user_func($this->_custom_field_form, $model_values);
+  }
+
+
+  protected function _doAfterSave($model) {
+    return call_user_func($this->_do_after_save, $model);
+  }
+
+
+  protected function processMulticheckboxFromPost($form, $clean = false) {
+    return call_user_func_array($this->_process_multi_checkbox, [$form, $clean]);
+  }
+
+
+  protected function getCustomValues() {
+    return call_user_func($this->_custom_values);
+  }
+
+
+  protected function _findModel() {
+    return $this->_definitions->find($this->_getParam('id'));
+  }
+
+
+  protected function _getDefaultModel($models) {
+    return  call_user_func($this->_default_model, $models);
+  }
 }
diff --git a/tests/application/modules/admin/controllers/CmsControllerTest.php b/tests/application/modules/admin/controllers/CmsControllerTest.php
index 4e7921161b6..b3cff3ef621 100644
--- a/tests/application/modules/admin/controllers/CmsControllerTest.php
+++ b/tests/application/modules/admin/controllers/CmsControllerTest.php
@@ -560,7 +560,7 @@ class CmsControllerArticleDuplicateActionTest extends CmsControllerWithPermissio
 
   /** @test */
   public function titreShouldBeDupliquerLArticleConcert() {
-    $this->assertXPathContentContains('//h1', 'Dupliquer l\'article: Concert');
+    $this->assertXPathContentContains('//h1', 'Dupliquer l\'article : Concert');
   }
 }
 
@@ -2696,7 +2696,7 @@ class CmsControllerCategorieEvenementTest extends CmsControllerWithPermissionTes
   public function deleteShouldRedirectToAdminCmsParentCategorie() {
     $this->dispatch('/admin/cms-category/delete/id/34');
 
-    $this->assertRedirectTo('/admin/cms/index/id_cat/23');
+    $this->assertRedirect();
 
     $this->assertEquals($this->cat_evenements,
                         $this->categorie_wrapper->getFirstAttributeForLastCallOn('delete'));
@@ -2713,8 +2713,7 @@ class CmsControllerCategorieEvenementTest extends CmsControllerWithPermissionTes
   /** @test */
   public function addCategorieCancelButtonShouldLinkToIndexIdCat34() {
     $this->dispatch('/admin/cms-category/add/id/34');
-    $this->assertXPath("//div[contains(@onclick, '/admin/cms/index/id_cat/34')]",
-                       $this->_response->getBody());
+    $this->assertXPath("//div[contains(@onclick, '/admin/cms/index/id_cat/34')]");
   }
 
 
@@ -2740,7 +2739,7 @@ class CmsControllerCategorieEvenementTest extends CmsControllerWithPermissionTes
                          'id_cat_mere' => 34]);
 
 
-    $this->assertEquals('/admin/cms/index/id_cat/35', $this->getResponseLocation());
+    $this->assertRedirect();
 
     $new_cat = $this->categorie_wrapper->getFirstAttributeForLastCallOn('save');
     $this->assertEquals('concerts', $new_cat->getLibelle());
@@ -2758,7 +2757,7 @@ class CmsControllerCategorieEvenementTest extends CmsControllerWithPermissionTes
       ->setPost(array('libelle' => 'Actualite',
                       'id_cat_mere' => 254));
     $this->dispatch('/admin/cms-category/edit/id/34');
-    $this->assertEquals('/admin/cms/index/id_cat/34', $this->getResponseLocation());
+    $this->assertRedirect();
     $this->assertEquals('Actualite', $this->cat_evenements->getLibelle());
     $this->assertEquals(254, $this->cat_evenements->getIdCatMere());
     $this->assertTrue($this->categorie_wrapper->methodHasBeenCalled('save'));
diff --git a/tests/scenarios/Manager/ManagerTest.php b/tests/scenarios/Manager/ManagerTest.php
index dcb29e4d798..76595f8e90d 100644
--- a/tests/scenarios/Manager/ManagerTest.php
+++ b/tests/scenarios/Manager/ManagerTest.php
@@ -84,21 +84,71 @@ class ManagerArticleTest extends Admin_AbstractControllerTestCase {
 
 
 
-class ManagerCmsEditActionTest extends Admin_AbstractControllerTestCase {
+class ManagerCmsActionsTest extends Admin_AbstractControllerTestCase {
+  protected $_storm_default_to_volatile = true;
+
   public function setUp() {
     parent::setUp();
+    $this->fixture('Class_ArticleCategorie',
+                   ['id' => 56,
+                    'libelle' => 'news']);
+
     $this->fixture('Class_Article',
                    ['id' => 23,
                     'titre' => 'Happy new year',
-                    'contenu' => 'Bonne année']);
-    $this->dispatch('/admin/cms/edit/id/23', true);
+                    'contenu' => 'Bonne année',
+                    'id_cat' => 56]);
   }
 
 
   /** @test */
-  public function titleShouldBeEditHappyNewYear() {
+  public function editActionShouldBeEditHappyNewYear() {
+    $this->dispatch('/admin/cms/edit/id/23', true);
     $this->assertXPathContentContains('//h1', 'Modifier un article: Happy new year');
   }
+
+
+  /** @test */
+  public function addActionTitleShouldBeAddNewArticle() {
+    $this->dispatch('/admin/cms/add/id_cat/56', true);
+    $this->assertXPathContentContains('//h1', 'Ajouter un article');
+  }
+
+
+  /** @test */
+  public function deleteActionTitleShouldBeDeleteHappyNewYear() {
+    $this->dispatch('/admin/cms/delete/id/23', true);
+    $this->assertXPathContentContains('//h1', 'Supprimer l\'article : Happy new year');
+  }
+
+
+  /** @test */
+  public function duplicateActionTitleShouldBeAddNewArticle() {
+    $this->dispatch('/admin/cms/newsduplicate/id/23', true);
+    $this->assertXPathContentContains('//h1', 'Dupliquer l\'article : Happy new year');
+  }
+
+
+  /** @test */
+  public function forceDeleteActionShouldRedirect() {
+    $this->dispatch('/admin/cms/force-delete/id/23', true);
+    $this->assertRedirectTo('/admin/cms/index/id_cat/56');
+  }
+
+
+  /** @test */
+  public function makeVisibleActionShouldRedirect() {
+    $this->dispatch('/admin/cms/makevisible/id/23', true);
+    $this->assertRedirectTo('/admin/cms/index/id/23');
+  }
+
+
+  /** @test */
+  public function makeInvisibleActionShouldRedirect() {
+    $this->dispatch('/admin/cms/makeinvisible/id/23', true);
+    $this->assertRedirectTo('/admin/cms/index/id/23');
+  }
+
 }
 
 
-- 
GitLab