Perforce Chronicle 2012.2/486814
API Documentation

Category_Module Class Reference

Integrate the category module with the rest of the application. More...

Inheritance diagram for Category_Module:
P4Cms_Module_Integration

List of all members.

Static Public Member Functions

static expandDynamicMenu ($item, $options)
 Expand a dynamic category menu item.
static init ()
 Subscribe to the relevant topics.

Detailed Description

Integrate the category module with the rest of the application.

Copyright:
2011-2012 Perforce Software. All rights reserved
License:
Please see LICENSE.txt in top-level folder of this distribution.
Version:
2012.2/486814

Member Function Documentation

static Category_Module::expandDynamicMenu ( item,
options 
) [static]

Expand a dynamic category menu item.

Parameters:
P4Cms_Navigation_Page_Dynamic$itemthe dynamic item to be expanded.
array$optionsoptions (hints) to influence expansion.
Returns:
array the replacement menu items.
    {
        // if current user is not allowed to access categories, return empty array.
        if (P4Cms_User::hasActive()) {
            $user = P4Cms_User::fetchActive();
            if (!$user->isAllowed('categories', 'access')) {
                return array();
            }
        }

        // if options specify max-items, only fetch the specified number of records.
        $query = new P4Cms_Record_Query;
        $query->setSortBy(array('title'));

        // if options specify max-depth, only fetch up to max depth.
        if ($options[P4Cms_Menu::MENU_MAX_DEPTH] !== null) {
            $query->setMaxDepth($options[P4Cms_Menu::MENU_MAX_DEPTH]);
        }

        // if options specify menu root, only search under the given path.
        $root = $options[P4Cms_Menu::MENU_ROOT];
        if ($root) {
            // increment max-depth to account for added depth of root.
            if ($query->getMaxDepth() !== null) {
                $query->setMaxDepth($query->getMaxDepth() + substr_count($root, '/') + 1);
            }

            // make the root inclusive, outside filtering needs the specified root to
            // be present to make it happy. The root itself will be removed by the caller.
            $query->addPaths(array($root, $root .'/...'));
        }

        // take the flat list of all categories and transform it into
        // a multi-dimensional array of navigation page entries.
        $categories = Category_Model_Category::fetchAll($query);
        $container  = array();
        foreach ($categories as $category) {
            // scan down the list of ancestors and create empty entries for any missing ones
            $parentContainer =& $container;
            foreach ($category->getAncestorIds() as $ancestorId) {
                // skip any ancestor IDs which are not under our filtered root
                if ($root && strpos($ancestorId, $root) !== 0) {
                    continue;
                }

                // make a stub entry if we haven't found the ancestor yet
                // this entry will be replaced when we later encounter it
                if (!array_key_exists($ancestorId, $parentContainer)) {
                    $parentContainer[$ancestorId] = array('pages' => array());
                }

                // keep drilling down on the parentContainer pointer
                $parentContainer =& $parentContainer[$ancestorId]['pages'];
            }

            // if we have a 'stub' entry, cache the pages and remove it;
            // removing it ensures the correct order is maintained.
            $id    = $category->getId();
            $pages = array();
            if (isset($parentContainer[$id]['pages'])) {
                $pages = $parentContainer[$id]['pages'];
                unset($parentContainer[$id]);
            }

            // add the new entry; maintaining child pages had they been present
            $parentContainer[$id] = array(
                'label'         => $category->getTitle(),
                'expansionId'   => $category->getId(),
                'pages'         => $pages,
                'module'        => 'category',
                'controller'    => 'index',
                'action'        => 'index',
                'route'         => 'category',
                'encode'        => false,
                'params'        => array(
                    'category'  => $category->getId()
                )
            );

            // update pointer for any entries we may add
            $parentContainer =& $parentContainer[$id]['pages'];

            // include content entries if so configured.
            $options += array('includeEntries' => false);
            if ($options['includeEntries'] || $item->get('includeEntries')) {
                $entriesOptions = array(Category_Model_Category::OPTION_DEREFERENCE => true);
                foreach ($category->getEntries($entriesOptions) as $entry) {
                    $entryId = $category->getId() . '/' . $entry->getId();
                    $parentContainer[$entryId] = array(
                        'label'         => $entry->getTitle(),
                        'uri'           => $entry->getUri(),
                        'expansionId'   => $entryId
                    );
                }
            }
        }

        return $container;
    }
static Category_Module::init ( ) [static]

Subscribe to the relevant topics.

Reimplemented from P4Cms_Module_Integration.

    {
        // contribute the list of categories as a dynamic menu item.
        P4Cms_PubSub::subscribe('p4cms.navigation.dynamicHandlers',
            function()
            {
                $handler = new P4Cms_Navigation_DynamicHandler;
                $handler->setId('categories')
                        ->setLabel('Category Listing')
                        ->setExpansionCallback('Category_Module::expandDynamicMenu')
                        ->setFormCallback(
                            function(Zend_Form $form)
                            {
                                // add a field to control if content entries in categories
                                // are included when dynamic category menu items are expanded.
                                $form->addElement(
                                    'checkbox',
                                    'includeEntries',
                                    array(
                                        'label'         => 'Include Content',
                                        'description'   => "Include content entries in categories.",
                                        'checked'       => false
                                    )
                                );

                                return $form;
                            }
                        );

                return array($handler);
            }
        );

        // participate in content editing by providing a subform.
        P4Cms_PubSub::subscribe('p4cms.content.form.subForms',
            function(Content_Form_Content $form)
            {
                // enforce permissions.
                $user = P4Cms_User::fetchActive();
                if (!$user->isAllowed('categories', 'associate')) {
                    return;
                }

                return new Category_Form_Content(
                    array(
                        'name'      => 'category',
                        'idPrefix'  => $form->getIdPrefix(),
                        'order'     => -30
                    )
                );
            }
        );

        // participate in content editing by populating categories sub-form.
        P4Cms_PubSub::subscribe('p4cms.content.form.populate',
            function(Content_Form_Content $form, array $values)
            {
                // nothing to do if no category sub-form.
                if (!$form->getSubForm('category')) {
                    return;
                }

                // pull categories from entry if not in values array.
                $categories = array();
                if (isset($values['category']['categories'])) {
                    $categories = $values['category']['categories'];
                } else {
                    $id = $form->getEntry()->getId();
                    if ($id !== null) {
                        $categories = Category_Model_Category::fetchIdsByEntry($id);
                    }
                }
                $form->getSubForm('category')
                     ->getElement('categories')
                     ->setValue($categories);
            }
        );

        // participate in content editing - don't store categories on content
        // directly, store category associations in category records instead.
        P4Cms_PubSub::subscribe('p4cms.content.record.preSave',
            function(P4Cms_Content $entry)
            {
                // move category out of fields list and into a public
                // property so it doesn't get saved with the record
                if ($entry->hasField('category')) {
                    $category = $entry->getValue('category');
                    $entry->unsetValue('category');
                    $entry->category = $category;
                }
            }
        );
        P4Cms_PubSub::subscribe('p4cms.content.record.postSave',
            function(P4Cms_Content $entry)
            {
                // if no category data present, no category changes required.
                if (!isset($entry->category)) {
                    return;
                }

                // pull off the latest category associations.
                $categories = isset($entry->category['categories'])
                    ? $entry->category['categories']
                    : array();

                Category_Model_Category::setEntryCategories($entry->getId(), $categories);
            }
        );

        // provide form to filter content by category.
        P4Cms_PubSub::subscribe('p4cms.content.grid.form.subForms',
            function(Zend_Form $form)
            {
                // enforce permissions.
                $user = P4Cms_User::fetchActive();
                if (!$user->isAllowed('categories', 'access')) {
                    return;
                }

                $form = new Category_Form_Content;
                $form->setName('category')
                     ->setOrder(15)
                     ->setLegend(null)
                     ->removeElement('addCategory');

                $categories = $form->getElement('categories');
                $categories->setLabel('Category')
                           ->setAttrib('autoApply', true);

                // if there are no categories, don't return form.
                if (!count($categories->getMultiOptions())) {
                    return null;
                }

                return $form;
            }
        );

        // filter content list by category.
        P4Cms_PubSub::subscribe('p4cms.content.grid.populate',
            function(P4Cms_Record_Query $query, Zend_Form $form)
            {
                $values = $form->getValues();

                // extract selected categories.
                $categories = isset($values['category']['categories'])
                    ? $values['category']['categories']
                    : null;

                // early exit if no categories selected.
                if (!is_array($categories) || !count($categories)) {
                    return;
                }

                // check for existing 'categories' options and intersect if found
                $filter = $query->getFilter() ?: new P4Cms_Record_Filter;
                if (is_array($filter->getOption('categories'))) {
                    $categories = array_intersect($categories, $filter->getOption('categories'));
                }

                // The category module subscribes to the p4cms.content.record.query topic
                // so we can just add our categories to the query here and they will be
                // filtered when the query is executed.
                $filter->setOption('categories', $categories);
                $query->setFilter($filter);
            }
        );

        // provide category tree actions
        P4Cms_PubSub::subscribe('p4cms.category.grid.actions',
            function($actions)
            {
                $actions->addPages(
                    array(
                        array(
                            'label'     => 'View',
                            'onClick'   => 'p4cms.category.grid.Actions.onClickView();',
                            'order'     => '10'
                        ),
                        array(
                            'label'     => 'Edit',
                            'onClick'   => 'p4cms.category.grid.Actions.onClickEdit();',
                            'order'     => '20'
                        ),
                        array(
                            'label'     => 'Delete',
                            'onClick'   => 'p4cms.category.grid.Actions.onClickDelete();',
                            'order'     => '30'
                        )
                    )
                );
            }
        );

        // provide form to search categories
        P4Cms_PubSub::subscribe('p4cms.category.grid.form.subForms',
            function(Zend_Form $form)
            {
                return new Ui_Form_GridSearch;
            }
        );

        // filter categories by keyword search
        P4Cms_PubSub::subscribe('p4cms.category.grid.populate',
            function(P4Cms_Model_Iterator $categories, Zend_Form $form)
            {
                $values = $form->getValues();

                // extract search query.
                $query = isset($values['search']['query'])
                    ? $values['search']['query']
                    : null;

                // early exit if no query.
                if (!$query) {
                    return null;
                }

                // filter categories by the search query
                $categories->search('title', $query);
            }
        );

        // provide form to filter categories by number of entries
        P4Cms_PubSub::subscribe('p4cms.category.grid.form.subForms',
            function(Zend_Form $form)
            {
                $options = array(
                    ''     => 'Any Number',
                    'more' => 'One or More',
                    'none' => 'None'
                );

                $form = new P4Cms_Form_SubForm;
                $form->setName('entriesCount')
                     ->setAttrib('class', 'types-form')
                     ->setOrder(20)
                     ->addElement(
                        'Radio', 'display',
                        array(
                            'label'         => 'Number of Entries',
                            'multiOptions'  => $options,
                            'autoApply'     => true,
                            'order'         => 20,
                            'value'         => ''
                        )
                     );

                return $form;
            }
        );

        // touch up the query to reflect our display of display empty categories
        P4Cms_PubSub::subscribe('p4cms.category.grid.populate',
            function(P4Cms_Model_Iterator $categories, Zend_Form $form)
            {
                $values = $form->getValues();

                // extract selected types.
                $display = isset($values['entriesCount']['display'])
                    ? $values['entriesCount']['display']
                    : '';

                // early exit if no display restrictions
                if (!$display) {
                    return null;
                }

                // helper function to determine whether the category passes the display filter
                // @return boolean true if category passes the filter, false otherwise
                $passesFilter = function (Category_Model_Category $category, $display)
                {
                    return ($display === 'more' && $category->getEntries())
                        || ($display === 'none' && !$category->getEntries());
                };

                // get list with categories matching the search query
                $categories->filterByCallback($passesFilter, $display);
            }
        );

        // subscribe to the record query topic
        P4Cms_PubSub::subscribe('p4cms.content.record.query',
            function(P4Cms_Record_Query $query, P4Cms_Record_Adapter $adapter)
            {
                // are we filtering by categories?
                $filter = $query->getFilter();
                if (!$filter) {
                    return;
                }

                $categoryIds = $filter->getOption('categories');
                if (!$categoryIds || (!is_string($categoryIds) && !is_array($categoryIds))) {
                    return;
                }
                if (is_string($categoryIds)) {
                    $categoryIds = (array) $categoryIds;
                }

                // get entries for selected category ids.
                $ids = array();
                foreach ($categoryIds as $category) {
                    try {
                        $category = Category_Model_Category::fetch($category);
                        $ids = array_merge($ids, $category->getEntries());
                    } catch (P4Cms_Model_NotFoundException $e) {
                        continue;
                    }
                }

                // filter query to matching entries.
                $query->addPaths($ids, true);
            }
        );

        // organize category records when pulling changes.
        P4Cms_PubSub::subscribe(
            'p4cms.site.branch.pull.groupPaths',
            function($paths, $source, $target, $result)
            {
                $paths->addSubGroup(
                    array(
                        'label'         => 'Categories',
                        'basePaths'     => $target->getId() . '/categories/...',
                        'inheritPaths'  => $target->getId() . '/categories/...',
                        'pullByDefault' => true,
                        'count'         =>
                            function($group, $count, $options) use ($target)
                            {
                                $categories = array_map(
                                    'dirname',
                                    $group->getPaths($options)->invoke('getValue', array('depotFile'))
                                );

                                return count(array_unique($categories));
                            },
                        'details'       =>
                            function($paths) use ($source, $target)
                            {
                                $ids     = array();
                                $details = new P4Cms_Model_Iterator;
                                foreach ($paths as $path) {
                                    $entry    = basename($path->depotFile);
                                    $category = dirname($path->depotFile)
                                                . '/' . Category_Model_Category::CATEGORY_FILENAME;
                                    $id       = Category_Model_Category::depotFileToId($category);

                                    // add some defaults for this category if this is the
                                    // first time we have seen it
                                    if (!isset($details[$id])) {
                                        $ids[]        = $id;
                                        $details[$id] = new P4Cms_Model(
                                            array(
                                                'conflict' => false,
                                                'action'   => 'edit'
                                            )
                                        );
                                    }

                                    // if we have the actual category index file we should
                                    // use its action for our details entry
                                    if ($entry == Category_Model_Category::CATEGORY_FILENAME) {
                                        $details[$id]->setValue('action', $path->action);
                                    }

                                    // if any of the paths in this entry are in conflict the
                                    // whole category needs to be flagged as a conflict
                                    if ($path->conflict) {
                                        $details[$id]->setValue('conflict', $path->conflict);
                                    }
                                }

                                // fetch all of the categories in one go and set their
                                // titles onto the associated details entries
                                $entries = Site_Model_PullPathGroup::fetchRecords(
                                    $ids, 'Category_Model_Category', $source, $target
                                );
                                foreach ($entries as $entry) {
                                    $details[$entry->getId()]->setValue('label', $entry->getTitle());
                                }

                                $details->setProperty(
                                    'columns',
                                    array('label' => 'Category', 'action' => 'Action')
                                );

                                return $details;
                            }
                    )
                );
            }
        );
    }

The documentation for this class was generated from the following file: