ManageController.php #1

  • //
  • guest/
  • perforce_software/
  • chronicle/
  • main/
  • application/
  • category/
  • controllers/
  • ManageController.php
  • View
  • Commits
  • Open Download .zip Download (20 KB)
<?php
/**
 * Manages category operations (e.g. add).
 *
 * @copyright   2011 Perforce Software. All rights reserved.
 * @license     Please see LICENSE.txt in top-level folder of this distribution.
 * @version     <release>/<patch>
 */
class Category_ManageController extends Zend_Controller_Action
{
    public $contexts = array(
        'add'       => array('partial' => 'get', 'json' => 'post'),
        'edit'      => array('partial' => 'get', 'json' => 'post'),
        'delete'    => array('partial' => 'get', 'json' => 'post'),
        'index'     => array('json'),
        'move'      => array('json' => 'post')
    );

    /**
     * Use management layout for all actions.
     */
    public function init()
    {
        $this->getHelper('layout')->setLayout('manage-layout');
    }

    /**
     * List categories.
     *
     * @publishes   p4cms.category.grid.actions
     *              Modify the passed menu (add/modify/delete items) to influence the actions shown
     *              on entries in the Manage Categories grid.
     *              P4Cms_Navigation            $actions    A menu to hold grid actions.
     *
     * @publishes   p4cms.category.grid.data.item
     *              Return the passed item after applying any modifications (add properties, change
     *              values, etc.) to influence the row values sent to the Manage Categories grid.
     *              array                       $item       The item to potentially modify.
     *              mixed                       $model      The original object/array that was used
     *                                                      to make the item.
     *              Ui_View_Helper_DataGrid     $helper     The view helper that broadcast this
     *                                                      topic.
     *
     * @publishes   p4cms.category.grid.data
     *              Adjust the passed data (add properties, modify values, etc.) to influence
     *              the row values sent to the Manage Categories grid.
     *              Zend_Dojo_Data              $data       The data to be filtered.
     *              Ui_View_Helper_DataGrid     $helper     The view helper that broadcast this
     *                                                      topic.
     *
     * @publishes   p4cms.category.grid.populate
     *              Adjust the passed iterator (possibly based on values in the passed form) to
     *              filter which categories will be shown on the Manage Categories grid.
     *              P4Cms_Model_Iterator        $items      An iterator of Category_Model_Category
     *                                                      objects.
     *              P4Cms_Form_PubSubForm       $form       A form containing filter options.
     *
     * @publishes   p4cms.category.grid.render
     *              Make adjustments to the datagrid helper's options pre-render (e.g. change
     *              options to add columns) for the Manage Categories grid.
     *              Ui_View_Helper_DataGrid     $helper     The view helper that broadcast this
     *                                                      topic.
     *
     * @publishes   p4cms.category.grid.form
     *              Make arbitrary modifications to the Manage Categories filters form.
     *              P4Cms_Form_PubSubForm       $form       The form that published this event.
     *
     * @publishes   p4cms.category.grid.form.subForms
     *              Return a Form (or array of Forms) to have them added to the Manage Categories
     *              filters form. The returned form(s) should have a 'name' set on them to allow
     *              them to be uniquely identified.
     *              P4Cms_Form_PubSubForm       $form       The form that published this event.
     *
     * @publishes   p4cms.category.grid.form.preValidate
     *              Allows subscribers to adjust the Manage Categories filters form prior to
     *              validation of the passed data. For example, modify element values based on
     *              related selections to permit proper validation.
     *              P4Cms_Form_PubSubForm       $form       The form that published this event.
     *              array                       $values     An associative array of form values.
     *
     * @publishes   p4cms.category.grid.form.validate
     *              Return false to indicate the Manage Categories filters form is invalid. Return
     *              true to indicate your custom checks were satisfied, so form validity should be
     *              unchanged.
     *              P4Cms_Form_PubSubForm       $form       The form that published this event.
     *              array                       $values     An associative array of form values.
     *
     * @publishes   p4cms.category.grid.form.populate
     *              Allows subscribers to adjust the Manage Categories filters form after it has
     *              been populated with the passed data.
     *              P4Cms_Form_PubSubForm       $form       The form that published this event.
     *              array                       $values     The values passed to the populate
     *                                                      method.
     */
    public function indexAction()
    {
        // enforce permissions.
        $this->acl->check('categories', 'manage');

        // setup list options form
        $request        = $this->getRequest();
        $gridNamespace  = 'p4cms.category.grid';
        $form           = new Ui_Form_GridOptions(
            array(
                'namespace'   => $gridNamespace
            )
        );
        $form->populate($request->getParams());

        // set up view
        $view               = $this->view;
        $view->form         = $form;
        $view->pageSize     = $request->getParam('count', 100);
        $view->rowOffset    = $request->getParam('start', 0);
        $view->pageOffset   = round($view->rowOffset / $view->pageSize, 0) + 1;
        $view->showAddLink  = $this->acl->isAllowed('categories', 'add');
        $view->headTitle()->set('Manage Categories');

        // set DataGrid view helper namespace
        $helper = $view->dataGrid();
        $helper->setNamespace($gridNamespace);

        // collect the actions from interested parties
        $actions = new P4Cms_Navigation;
        P4Cms_PubSub::publish($gridNamespace . '.actions', $actions);
        $view->actions = $actions;

        // early exit for standard requests (ie. not json)
        if (!$this->contextSwitch->getCurrentContext()) {
            $this->getHelper('helpUrl')->setUrl('navigation.category.overview.html');
            return;
        }

        // fetch categories and make a copy for later use
        $categories = Category_Model_Category::fetchAll();
        $copy       = new P4Cms_Model_Iterator($categories->getArrayCopy());

        // allow third-parties to influence list
        try {
            P4Cms_PubSub::publish($gridNamespace . '.populate', $categories, $form);
        } catch (Exception $e) {
            P4Cms_Log::logException("Error building categories list.", $e);
        }

        // restore categories hierarchy by appending ancestors to the
        // filtered categories
        $this->_restoreCategoriesHierarchy($categories, $copy);

        // prepare sorting options
        $sortKey    = $request->getParam('sort', 'title');
        $sortFlags  = array(
            P4Cms_Model_Iterator::SORT_NATURAL,
            P4Cms_Model_Iterator::SORT_NO_CASE
        );
        if (substr($sortKey, 0, 1) == '-') {
            $sortKey = substr($sortKey, 1);
            $sortFlags[] = P4Cms_Model_Iterator::SORT_DESCENDING;
        } else {
            $sortFlags[] = P4Cms_Model_Iterator::SORT_ASCENDING;
        }

        // compose list of sorted categories
        $view->categories = $this->_sortCategoriesRecursively($categories, $sortKey, $sortFlags);
    }

    /**
     * Add a category.
     */
    public function addAction()
    {
        // enforce permissions.
        $this->acl->check('categories', 'add');

        // set up view
        $view            = $this->view;
        $request         = $this->getRequest();
        $view->shortForm = $request->getParam('short', false);
        $view->headTitle()->set('Add Category');

        // set up form
        $form       = new Category_Form_Manage;
        $view->form = $form;
        $form->setIdPrefix($request->getParam('formIdPrefix'));

        if ($request->isPost()) {
            // if a valid data were posted, try to add the category
            if ($form->isValid($request->getPost())) {
                // populate the form with posted data
                $form->populate($request->getPost());

                // create new category with collected values and save it.
                // try to add a category with posted data; it may fail when
                // ancestors don't exist
                $category = new Category_Model_Category;
                try {
                    $category->setValues($form->getValues())
                             ->setId($form->composeCategoryId())
                             ->save();
                } catch (InvalidArgumentException $e) {
                    // handle the category ancestry does not exist exception
                    if ($e->getMessage() == 'Cannot create new category; category ancestry does not exist.') {
                        $form->getElement('parent')->addError(
                            "Cannot create new category; category ancestor '"
                            . $category->getParentId() . "' does not exist."
                        );
                    } else {
                        // re-thrown other exceptions
                        throw $e;
                    }
                }
            }

            // re-check whether form is valid (i.e. doesn't contain any error messages)
            if ($form->getMessages()) {
                // form is invalid; set bad request response code
                // and assign error messages to the view
                $this->getResponse()->setHttpResponseCode(400);
                $view->errors = $form->getMessages();
                return;
            }

            // if we reached this point, form is valid;
            // set notification message
            $view->message = "Category '{$category->getTitle()}' has been successfully added.";

            // for traditional requests, add notification message and redirect
            if (!$this->contextSwitch->getCurrentContext()) {
                // notify user and return to category list.
                P4Cms_Notifications::add(
                    $view->message,
                    P4Cms_Notifications::SEVERITY_SUCCESS
                );
                $this->redirector->gotoUrl($request->getBaseUrl());
            }

            // assign details about new category to the view
            $view->category = $category;
        }
    }

    /**
     * Edit a content category.
     *
     * @param   array    $params      The values to apply
     */
    public function editAction(array $params = null)
    {
        // enforce permissions.
        $this->acl->check('categories', 'manage');

        // set up view
        $view       = $this->view;
        $view->headTitle()->set('Edit Category');

        // fetch the content category to be edited.
        $request    = $this->getRequest();
        $id         = $request->getParam('category');
        $category   = Category_Model_Category::fetch($id);

        // setup form
        $form       = new Category_Form_Manage;
        $view->form = $form;
        $values     = array_merge(
            $category->getValues(),
            array(
                'parent' => $category->getParentId()
            )
        );
        $form->populate($values);

        // disable unique title check
        $form->setUniqueTitleRequired(false);

        // remove the current category and it's children from parent options.
        $newOptions = array();
        foreach ($form->getElement('parent')->getMultiOptions() as $key => $label) {
            if ($key === $id or strpos($key, "$id/") === 0) {
                continue;
            }
            $newOptions[$key] = $label;
        }
        $form->getElement('parent')->setMultiOptions($newOptions);

        if ($request->isPost()) {
            // if params is specified, we use them to override the existing values
            // else item values come from request
            if (isset($params)) {
                $values = array_merge($category->getValues(), $params);
            } else {
                $values = $request->getPost();
            }

            // if a valid data were posted, try to update the category
            if ($form->isValid($values)) {
                // populate the form with posted data
                $form->populate($values);

                // start a batch in case we're moving
                $adapter = $category->getAdapter();
                $adapter->beginBatch("Edit category $id");

                // if the id has changed, move the category
                $newId = $form->composeCategoryId();
                if ($id !== $newId) {
                    try {
                        Category_Model_Category::move($id, $newId);
                    } catch (InvalidArgumentException $e) {
                        $form->getElement('parent')->addError($e->getMessage());
                    }
                }

                // set form values on category, and save it
                try {
                    $category->setValues($form->getValues())
                             ->setId($newId)
                             ->save();
                } catch (InvalidArgumentException $e) {
                    // handle the category ancestry does not exist exception
                    if ($e->getMessage() == 'Cannot create new category; category ancestry does not exist.') {
                        $form->getElement('parent')->addError(
                            "Cannot create new category; category ancestor '"
                            . $category->getParentId() . "' does not exist."
                        );
                    }

                    // re-throw other exceptions
                    throw $e;
                }
            }

            // re-check whether form is valid (i.e. doesn't contain any error messages)
            if ($form->getMessages()) {
                // form is invalid; revert batch, set bad request response code
                // and assign error messages to the view
                $this->getResponse()->setHttpResponseCode(400);
                $view->errors = $form->getMessages();
                if (isset($adapter)) {
                    $adapter->revertBatch();
                }
                return;
            }

            // at this point, form is valid;
            // attempt to commit the batch - cleanup on failure.
            try {
                $adapter->commitBatch();
            } catch (Exception $e) {
                $adapter->revertBatch();
                throw $e;
            }

            // set notification message
            $view->message = "Category '{$category->getTitle()}' has been updated.";

            // for traditional requests, add notification message and redirect
            if (!$this->contextSwitch->getCurrentContext()) {
                P4Cms_Notifications::add(
                    $view->message,
                    P4Cms_Notifications::SEVERITY_SUCCESS
                );

                $this->redirector->gotoSimple('index');
            }
        }
    }

    /**
     * Move a category
     */
    public function moveAction()
    {
        // enforce permissions.
        $this->acl->check('categories', 'manage');

        $request    = $this->getRequest();
        $values     = array("parent" => $request->getParam('parent'));

        $this->editAction($values);
    }

    /**
     * Remove a content category.
     */
    public function deleteAction()
    {
        // enforce permissions.
        $this->acl->check('categories', 'manage');

        // require post request method.
        $request = $this->getRequest();
        if (!$request->isPost()) {
            throw new P4Cms_AccessDeniedException(
                "Cannot delete category. Request method must be http post."
            );
        }

        $id       = $request->getParam('id', $request->getParam('category'));
        $category = Category_Model_Category::fetch($id);
        $category->delete();

        $this->view->category = $category;
    }

    /**
     * Sort the given list of categories recursively to preserve parent-child
     * relationships.
     *
     * Example:
     *
     * Assume there are 2 categories, A, B where A has 2 childs A/B, A/C.
     * The output is an array iterator with categories in the following order:
     * - A, A/B, A/C, B  if sorted A to Z
     * - B, A, A/C, A/B  if sorted Z to A
     *
     * @param   P4Cms_Model_Iterator    $categories     list of all categories
     *                                                  used for getting the children
     * @param   string|array            $sortKey        key to sort categories by
     * @param   array                   $sortFlags      sorting options
     * @param   string|null             $parentId       optional - parent category of
     *                                                  the generated list
     *                                                  (null by default)
     * @return  P4Cms_Model_Iterator                    list of sorted categories
     */
    protected function _sortCategoriesRecursively($categories, $sortKey, $sortFlags, $parentId = null)
    {
        // get categories with given parent and sort them
        $childs = $categories
            ->filterByCallback(
                function($category, $parentId)
                {
                    return $category->getParentId() === $parentId;
                },
                $parentId,
                array(P4_Model_Iterator::FILTER_COPY)
            )
            ->sortBy($sortKey, $sortFlags);

        // assemble categories list and append sorted sub-categories below each category
        $sortedCategories = new P4Cms_Model_Iterator;
        foreach ($childs as $category) {
            $sortedCategories[] = $category;
            $sortedChilds       = $this->_sortCategoriesRecursively(
                $categories, $sortKey, $sortFlags, $category->getId()
            );
            foreach ($sortedChilds as $childCategory) {
                $sortedCategories[] = $childCategory;
            }
        }

        return $sortedCategories;
    }

    /**
     * Restore categories hierarchy by appending ancestors to the items contained
     * in match so as for any sub-category, all its ancestors are also present.
     * Parent categories that do not match, but must be included, are refered as
     * obligatory.
     * In addition, these obligatory items will be added an extra 'obligatory'
     * field so they can be recognized.
     *
     * @param P4Cms_Model_Iterator  $match              list with categories to keep
     * @param P4Cms_Model_Iterator  $categories         list with categories where
     *                                                  ancestors will be taken from
     */
    protected function _restoreCategoriesHierarchy(P4Cms_Model_Iterator $match,
        P4Cms_Model_Iterator $categories
    )
    {
        // compose list with categories to keep
        $keepIds = array();
        foreach ($match as $category) {
            $keepIds = array_merge(
                $keepIds,
                array($category->getId()),
                $category->getAncestorIds()
            );
        }

        // make keep list unique as there may be duplicates
        $keepIds = array_unique($keepIds);

        // filter categories to keep only selected items
        $categories->filter('id', $keepIds);

        // append and mark obligatory categories
        $obligatory = array_diff($keepIds, $match->invoke('getId'));
        foreach ($categories as $category) {
            if (in_array($category->getId(), $obligatory)) {
                $category->setValue('obligatory', true);
                $match->append($category);
            }
        }
    }
}
# Change User Description Committed
#1 16170 perforce_software Move Chronicle files to follow new path scheme for branching.
//guest/perforce_software/chronicle/application/category/controllers/ManageController.php
#1 8972 Matt Attaway Initial add of the Chronicle source code