Perforce Chronicle 2012.2/486814
API Documentation

Menu_Module Class Reference

Integrates menu module with the rest of the system. More...

Inheritance diagram for Menu_Module:
P4Cms_Module_Integration

List of all members.

Static Public Member Functions

static init ()
 Perform early integration work (before load).

Detailed Description

Integrates menu module with the rest of the system.

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 Menu_Module::init ( ) [static]

Perform early integration work (before load).

Reimplemented from P4Cms_Module_Integration.

    {
        // install menus when a site is created.
        P4Cms_PubSub::subscribe('p4cms.site.created',
            function(P4Cms_Site $site)
            {
                $adapter = $site->getStorageAdapter();
                P4Cms_Menu::installDefaultMenus(null, $adapter);
            }
        );

        // update menus when a module/theme is enabled.
        $installDefaults = function(P4Cms_Site $site, P4Cms_PackageAbstract $package)
        {
            $adapter = $site->getStorageAdapter();
            P4Cms_Menu::installPackageDefaults($package, null, $adapter);
        };

        P4Cms_PubSub::subscribe('p4cms.site.module.enabled', $installDefaults);
        P4Cms_PubSub::subscribe('p4cms.site.theme.enabled',  $installDefaults);

        // update menus when a module/theme is disabled.
        $removeDefaults = function(P4Cms_Site $site, P4Cms_PackageAbstract $package)
        {
            $adapter = $site->getStorageAdapter();
            P4Cms_Menu::removePackageDefaults($package, $adapter);
        };

        P4Cms_PubSub::subscribe('p4cms.site.module.disabled', $removeDefaults);
        P4Cms_PubSub::subscribe('p4cms.site.theme.disabled',  $removeDefaults);

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

        // filter menu management grid by keyword search
        P4Cms_PubSub::subscribe('p4cms.menu.grid.populate',
            function(P4Cms_Model_Iterator $items, 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
                $items->search(array('label', 'type'), $query);
            }
        );

        // provide form to filter by menu
        P4Cms_PubSub::subscribe('p4cms.menu.grid.form.subForms',
            function(Zend_Form $form)
            {
                $menus   = P4Cms_Menu::fetchAll();

                // avoid adding the form if no menus are known
                if (!count($menus)) {
                    return;
                }

                $options = array_combine($menus->invoke('getId'), $menus->invoke('getLabel'));

                $form = new P4Cms_Form_SubForm;
                $form->setName('menu')
                     ->setAttrib('class', 'types-form')
                     ->setOrder(20)
                     ->addElement(
                        'MultiCheckbox',
                        'display',
                        array(
                            'label'         => 'Menu',
                            'multiOptions'  => $options,
                            'autoApply'     => true,
                            'order'         => 20
                        )
                     );

                return $form;
            }
        );

        // filter menu management grid by menu
        P4Cms_PubSub::subscribe('p4cms.menu.grid.populate',
            function(P4Cms_Model_Iterator $items, Zend_Form $form)
            {
                $values = $form->getValues();

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

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

                // filter categories by the search query
                $items->filterByCallback(
                    function($item) use ($query)
                    {
                        return in_array($item->getMenu()->getId(), $query);
                    }
                );
            }
        );

        // provide form to filter by type
        P4Cms_PubSub::subscribe('p4cms.menu.grid.form.subForms',
            function(Zend_Form $form)
            {
                $types = Menu_Form_MenuItem::getTypeOptions(true, true);

                $form = new P4Cms_Form_SubForm;
                $form->setName('type')
                     ->setAttrib('class', 'types-form')
                     ->setOrder(30)
                     ->addElement(
                        'NestedCheckbox',
                        'display',
                        array(
                            'label'         => 'Type',
                            'multiOptions'  => $types,
                            'autoApply'     => true,
                            'order'         => 30,
                            'onClick'       => "
                                if (this.value == 'P4Cms_Navigation_Page_Dynamic') {
                                    p4cms.ui.toggleChildCheckboxes(this);
                                } else {
                                    p4cms.ui.toggleParentCheckbox(this);
                                }
                            "
                        )
                     );

                return $form;
            }
        );

        // filter menu management grid by type
        P4Cms_PubSub::subscribe('p4cms.menu.grid.populate',
            function(P4Cms_Model_Iterator $items, Zend_Form $form)
            {
                $values = $form->getValues();

                // extract type filter.
                $types = isset($values['type']['display'])
                    ? $values['type']['display']
                    : null;

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

                // modify query to pull out any dynamic types
                // so we can apply them after primary filtering
                $dynamicType  = 'P4Cms_Navigation_Page_Dynamic';
                $dynamicGroup = $dynamicType . '/';
                $dynamicTypes = array();
                foreach ($types as &$type) {
                    // if they have filtered for top level 'Dynamic'
                    // allow all dynamic types and break
                    if ($type == $dynamicType) {
                        $dynamicTypes = array();
                        break;
                    }

                    if (strpos($type, $dynamicGroup) === 0) {
                        $dynamicTypes[] = str_replace($dynamicGroup, '', $type);
                        $type = $dynamicType;
                    }
                }

                // filter menu items by the search query
                $items->filter('type', $types);

                // apply dynamic type filtering if present
                if (!empty($dynamicTypes)) {
                    $uuids = array();
                    foreach ($items as $item) {
                        if ($item->hasMenuItem()
                            && in_array($item->getMenuItem()->handler, $dynamicTypes)
                        ) {
                            $uuids[] = $item->getId();
                        }
                    }
                    $items->filter('id', $uuids);
                }
            }
        );

        // provide menu management actions
        P4Cms_PubSub::subscribe('p4cms.menu.grid.actions',
            function($actions)
            {
                $actions->addPages(
                    array(
                        array(
                            'label'     => 'Edit',
                            'onClick'   => 'p4cms.menu.grid.Actions.onClickEdit();',
                            'order'     => '10'
                        ),
                        array(
                            'label'     => 'Delete',
                            'onClick'   => 'p4cms.menu.grid.Actions.onClickDelete();',
                            'order'     => '20'
                        ),
                        array(
                            'label'     => 'Reset',
                            'onClick'   => 'p4cms.menu.grid.Actions.onClickReset();',
                            'onShow'    => 'p4cms.menu.grid.Actions.onShowReset(this);',
                            'order'     => '30'
                        ),
                        array(
                            'label'     => 'Go To',
                            'onClick'   => 'p4cms.menu.grid.Actions.onClickGoToMenuItem();',
                            'onShow'    => 'p4cms.menu.grid.Actions.onShowGoToMenuItem(this);',
                            'order'     => '40'
                        ),
                        array(
                            'type'      => 'P4Cms_Navigation_Page_Separator',
                            'order'     => '50'
                        ),
                        array(
                            'label'     => 'Add Menu Item',
                            'onClick'   => 'p4cms.menu.grid.Actions.onClickAddMenuItem();',
                            'order'     => '60'
                        )
                    )
                );
            }
        );

        // declare page type handlers to influence how different
        // types of menu items are edited when managing menus.
        P4Cms_PubSub::subscribe('p4cms.navigation.pageTypeHandlers',
            function()
            {
                $handlers   = array();

                // declare uri handler.
                $handler    = new P4Cms_Navigation_PageTypeHandler;
                $handlers[] = $handler;
                $handler->setId('Zend_Navigation_Page_Uri')
                        ->setLabel('Link')
                        ->setFormCallback(
                            function(Zend_Form $form)
                            {
                                // add a Uri field
                                $form->addElement(
                                    'text',
                                    'uri',
                                    array(
                                        'label'     => 'Address',
                                        'required'  => true,
                                        'filters'   => array('StringTrim'),
                                        'size'      => 32
                                    )
                                );

                                return $form;
                            }
                        );

                // declare mvc handler.
                $handler    = new P4Cms_Navigation_PageTypeHandler;
                $handlers[] = $handler;
                $handler->setId('P4Cms_Navigation_Page_Mvc')
                        ->setLabel('Action')
                        ->setFormCallback(
                            function(Zend_Form $form)
                            {
                                return new Menu_Form_MenuItemMvc;
                            }
                        );

                // declare heading handler.
                $handler    = new P4Cms_Navigation_PageTypeHandler;
                $handlers[] = $handler;
                $handler->setId('P4Cms_Navigation_Page_Heading')
                        ->setLabel('Heading')
                        ->setFormCallback(
                            function(Zend_Form $form)
                            {
                                $form->removeElement('onClick');
                                return $form;
                            }
                        );

                // declare separator handler.
                $handler    = new P4Cms_Navigation_PageTypeHandler;
                $handlers[] = $handler;
                $handler->setId('P4Cms_Navigation_Page_Separator')
                        ->setLabel('Separator')
                        ->setFormCallback(
                            function(Zend_Form $form)
                            {
                                $form->removeElement('label');
                                $form->removeElement('target');
                                $form->removeElement('onClick');

                                return $form;
                            }
                        );

                // declare dynamic handler.
                $handler    = new P4Cms_Navigation_PageTypeHandler;
                $handlers[] = $handler;
                $handler->setId('P4Cms_Navigation_Page_Dynamic')
                        ->setLabel('Dynamic')
                        ->setFormCallback(
                            function(Zend_Form $form)
                            {
                                $form->removeElement('title');
                                $form->removeElement('label');

                                // borrow elements from widget form.
                                $widgetForm = new Menu_Form_Widget;
                                $form->addElement($widgetForm->getElement('maxDepth'));
                                $form->addElement($widgetForm->getElement('maxItems'));

                                // different dynamic menu item types like different forms.
                                // give dynamic handlers a chance to prepare the form.
                                $dynamicType = $form->getValue('handler');
                                try {
                                    if ($dynamicType) {
                                        $dynamicHandler = P4Cms_Navigation_DynamicHandler::fetch($dynamicType);
                                        $dynamicHandler->prepareForm($form);
                                    }
                                } catch (P4Cms_Model_NotFoundException $e) {
                                    // no such dynamic type. we're ok with that.
                                }

                                return $form;
                            }
                        );

                // declare content handler.
                $handler    = new P4Cms_Navigation_PageTypeHandler;
                $handlers[] = $handler;
                $handler->setId('P4Cms_Navigation_Page_Content')
                        ->setLabel('Content')
                        ->setFormCallback(
                            function(Zend_Form $form)
                            {
                                return new Menu_Form_MenuItemContent;
                            }
                        );

               return $handlers;
            }
        );

        // participate in content editing by providing a subform.
        P4Cms_PubSub::subscribe('p4cms.content.form.subForms',
            function(Content_Form_Content $form)
            {
                // only show menu sub-form if user has permission
                $user = P4Cms_User::fetchActive();
                if (!$user->isAllowed('menus', 'manage-via-content')) {
                    return;
                }

                // add menu form so users can easily add content to menus.
                return new Menu_Form_Content(
                    array(
                        'name'      => 'menus',
                        'idPrefix'  => $form->getIdPrefix(),
                        'order'     => -40
                    )
                );
            }
        );

        // let users add/edit menu items when editing content.
        P4Cms_PubSub::subscribe('p4cms.content.form.populate',
            function(Content_Form_Content $form, array $values)
            {
                // nothing to do if no menu sub-form
                $menuForm = $form->getSubForm('menus');
                if (!$menuForm) {
                    return;
                }

                // pull menus from storage if not in values array.
                if (isset($values['menus'])) {
                    $items = $values['menus'];
                } else {
                    $entry = $form->getEntry();
                    $items = array();
                    foreach (P4Cms_Menu::fetchMixed() as $mixed) {
                        $item = $mixed->getMenuItem();
                        if ($item instanceof P4Cms_Navigation_Page_Content
                            && $item->contentId == $entry->getId()
                        ) {
                            $items[] = array_merge(
                                array('menuId' => $mixed->getMenu()->getId()),
                                $item->toArray()
                            );
                        }
                    }
                }

                // for each menu item, add a menu item sub-form.
                $count = 0;
                foreach ($items as $item) {
                    $itemForm = $menuForm->getItemForm($item);
                    $menuForm->addSubForm($itemForm, $count++);
                }
            }
        );

        // participate in content editing - don't store menus items
        // on content directly, store them in menu records instead.
        P4Cms_PubSub::subscribe('p4cms.content.record.preSave',
            function(P4Cms_Content $entry)
            {
                // move menus out of fields list and into a public
                // property so it doesn't get saved with the record
                if ($entry->hasField('menus')) {
                    $menus = $entry->getValue('menus');
                    $entry->unsetValue('menus');
                    $entry->menus = $menus;
                }
            }
        );
        P4Cms_PubSub::subscribe('p4cms.content.record.postSave',
            function(P4Cms_Content $entry)
            {
                // only modify menus if user has adequate permission.
                $user = P4Cms_User::fetchActive();
                if (!$user->isAllowed('menus', 'manage-via-content')) {
                    return;
                }

                $adapter   = $entry->getAdapter();
                $menuItems = is_array($entry->menus) ? $entry->menus : array();

                // when we fetch menus, we hang onto them because we are in a
                // batch and if we re-fetch them we'll lose any earlier changes.
                $menus     = array();
                $fetchMenu = function($id) use ($adapter, &$menus)
                {
                    if (!isset($menus[$id])) {
                        try {
                            $menus[$id] = P4Cms_Menu::fetch($id, null, $adapter);
                        } catch (P4Cms_Model_NotFoundException $e) {
                            return null;
                        }
                    }
                    return $menus[$id];
                };

                // content entries can have multiple menu entries, loop over
                // each posted menu entry and add/update/delete as appropriate.
                foreach ($menuItems as $values) {
                    $form = new Menu_Form_MenuItemContent;

                    // normalize values to always contain the expected elements
                    // and have the correct page type and content id.
                    $values = array_merge(
                        $form->getValues(),
                        array(
                            'type'      => 'P4Cms_Navigation_Page_Content',
                            'contentId' => $entry->getId()
                        ),
                        $values
                    );

                    // fetch the menu and the menu item if we're editing.
                    $menu = null;
                    $item = null;
                    if ($values['menuId'] && $values['uuid']) {

                        $menu = $fetchMenu($values['menuId']);
                        $item = $menu ? $menu->getContainer()->findBy('uuid', $values['uuid']) : null;

                        // if we can't find this menu or item, we assume someone
                        // deleted it, and rather than resurrect it, we skip it.
                        if (!$menu || !$item) {
                            continue;
                        }

                        // handle deleted menu items.
                        if ($values['remove']) {
                            $item->getParent()->removePage($item);
                            $menu->save();
                            continue;
                        }

                        // skip menu items that haven't changed.
                        $form->populate($item->toArray());
                        if ($values['label'] == $form->getValue('label')
                            && $values['position'] == $form->getValue('position')
                            && $values['location'] == $form->getValue('location')
                            && $values['contentAction'] == $form->getValue('contentAction')
                        ) {
                            continue;
                        }
                    }

                    // skip aborted adds (user adds then clicks remove)
                    if ($values['remove']) {
                        continue;
                    }

                    $form->populate($values);

                    // fetch the target menu so that save menu item will write to our copy
                    $targetId = reset(explode('/', $form->getValue('location'), 2));
                    $target   = $targetId ? $fetchMenu($targetId) : null;

                    Menu_ManageController::saveMenuItem($form, $item, $menu, $target);
                }
            }
        );

        // if an entry is deleted remove any content links (so long as they have no children)
        P4Cms_PubSub::subscribe('p4cms.content.record.delete',
            function(P4Cms_Content $entry)
            {
                foreach (P4Cms_Menu::fetchMixed(null, $entry->getAdapter()) as $mixed) {
                    $item = $mixed->getMenuItem();
                    if ($item instanceof P4Cms_Navigation_Page_Content
                        && $item->contentId == $entry->getId()
                        && !count($item->getPages())
                    ) {
                        $item->getParent()->removePage($item);
                        $mixed->getMenu()->save();
                    }
                }
            }
        );

        // organize menu records when pulling changes.
        P4Cms_PubSub::subscribe(
            'p4cms.site.branch.pull.groupPaths',
            function($paths, $source, $target, $result)
            {
                $paths->addSubGroup(
                    array(
                        'label'         => 'Menus',
                        'basePaths'     => $target->getId() . '/menus/...',
                        'inheritPaths'  => $target->getId() . '/menus/...',
                        'pullByDefault' => true,
                        'details'       =>
                            function($paths) use ($source, $target)
                            {
                                $pathsById = array();
                                foreach ($paths as $path) {
                                    if (strpos($path->depotFile, $target->getId() . '/menus/') === 0) {
                                        $pathsById[P4Cms_Menu::depotFileToId($path->depotFile)] = $path;
                                    }
                                }

                                $details = new P4Cms_Model_Iterator;
                                $entries = Site_Model_PullPathGroup::fetchRecords(
                                    array_keys($pathsById), 'P4Cms_Menu', $source, $target
                                );
                                foreach ($entries as $entry) {
                                    $path      = $pathsById[$entry->getId()];
                                    $details[] = new P4Cms_Model(
                                        array(
                                            'conflict' => $path->conflict,
                                            'action'   => $path->action,
                                            'label'    => $entry->getLabel()
                                        )
                                    );
                                }

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

                                return $details;
                            }
                    )
                );
            }
        );

        // automatically resolve menu conflicts.
        P4Cms_PubSub::subscribe(
            'p4cms.site.branch.pull.conflicts',
            function($conflicts, $target, $source, $headChange, $preview, $adapter)
            {
                // skip the resolve when previewing so the user receives a warning
                // that there is the potential for changes to be lost in the target
                if ($preview) {
                    return;
                }

                // filter conflicts for menu path entries and convert
                // depot paths to record ids so we can query records.
                $sourceIds = array();
                $targetIds = array();
                $basePath  = $target->getId() . '/menus/';
                foreach ($conflicts->getData() as $conflict) {
                    if (isset($conflict['depotFile'])
                        && strpos($conflict['depotFile'], $basePath) === 0
                    ) {
                        $id          = P4Cms_Menu::depotFileToId($conflict['depotFile'], $adapter);
                        $sourceIds[] = $id . "@" . $headChange;
                        $targetIds[] = $id;
                    }
                }

                // if there are no menu conflicts, nothing to do.
                if (!$sourceIds || !$targetIds) {
                    return;
                }

                // we use the given adapter for target rather than calling
                // target->getStorageAdapter() to ensure we are using the
                // proper user and workspace.
                $targetAdapter = $adapter;
                $sourceAdapter = $source->getStorageAdapter();

                // fetch all conflicting menus in both the source and
                // target branches, so that we can merge them.
                $targetMenus = P4Cms_Menu::fetchAll(array('ids' => $targetIds), $targetAdapter);
                $sourceMenus = P4Cms_Menu::fetchAll(array('ids' => $sourceIds), $sourceAdapter);

                foreach ($targetMenus as $id => $targetMenu) {
                    // if the source menu cannot be fetched we assume its deleted
                    // and skip it as the default merge will take care of it.
                    // deleted targets don't get fetched/looped automatically.
                    if (!isset($sourceMenus[$id])) {
                        continue;
                    }

                    // determine the base version to diff/merge against
                    $resolve = $targetAdapter->getConnection()->run(
                        'resolve',
                        array('-no', $targetMenu->toP4File()->getFilespec())
                    )->getData(0);

                    $baseFile   = P4_File::fetch($resolve['baseFile'] . '#' . $resolve['baseRev']);
                    $baseMenu   = P4Cms_Menu::fromP4File($baseFile, P4Cms_Menu::FROM_FILE_IMPORT);
                    $sourceMenu = $sourceMenus[$id];

                    // merge in changes from target menu since last pull (base)
                    // this will propagate non-conflicting changes made in
                    // the target onto the source branch's menu container.
                    $sourceMenu->merge($targetMenu, $baseMenu);

                    // update the target with our merged result
                    $targetMenu->setContainer($sourceMenu->getContainer())
                               ->save();

                    // run an 'accept yours' merge on the menu to avoid it being
                    // clobbered by the source. we need to include the undoc -e
                    // flag for the base to advance (which is key to our merge/diff)
                    $targetAdapter->getConnection()->run(
                        'resolve',
                        array('-e', '-ay', $targetMenu->toP4File()->getFilespec())
                    );
                }
            }
        );
    }

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