/ */ class Menu_Form_MenuItem extends P4Cms_Form { /** * Defines the elements that make up the menu item form. * Called automatically when the form object is created. */ public function init() { // form should use p4cms-ui styles. $this->setAttrib('class', 'p4cms-ui menu-item-form'); // set the method for the form to POST $this->setMethod('post'); $this->addElement('hidden', 'uuid', array('ignore' => true)); $this->addElement('hidden', 'menuId', array('ignore' => true)); $this->addElement( 'text', 'label', array( 'label' => 'Label', 'order' => -40, 'required' => true, 'filters' => array('StringTrim') ) ); $this->getElement('label') ->getDecorator('htmlTag') ->setOption('class', 'menu-item-label'); $this->addElement( 'select', 'position', array( 'label' => 'Position', 'order' => -30, 'required' => true, 'ignore' => true, 'multiOptions' => array( 'before' => 'Before', 'after' => 'After', 'under' => 'Under' ) ) ); $this->getElement('position') ->getDecorator('htmlTag') ->setOption('class', 'menu-item-position'); $this->addElement( 'select', 'location', array( 'label' => 'Location', 'order' => -20, 'required' => true, 'ignore' => true ) ); $this->getElement('location') ->getDecorator('label') ->setOption('tagClass', 'menu-item-location'); $this->_updateLocationOptions(); $this->addElement( 'select', 'type', array( 'label' => 'Type', 'order' => -10, 'required' => true, 'value' => 'Zend_Navigation_Page_Uri', 'multiOptions' => static::getTypeOptions() ) ); $this->addElement( 'select', 'target', array( 'label' => 'Target', 'order' => 100, 'required' => false, 'multiOptions' => array( '_self' => 'Current Window', '_blank' => 'New Window', '_top' => 'Top Window', '_parent' => 'Parent Window' ) ) ); $this->addElement( 'text', 'class', array( 'label' => 'CSS Class', 'order' => 200, 'required' => false, 'filters' => array('StringTrim') ) ); $this->addElement( 'textarea', 'onClick', array( 'label' => 'Click Event', 'order' => 210, 'required' => false, 'rows' => 3, 'cols' => 60, 'description' => 'Optional JavaScript code for the onclick event.' ) ); $this->addElement( 'SubmitButton', 'save', array( 'label' => 'Save', 'required' => false, 'class' => 'preferred', 'ignore' => true ) ); // put the button in a fieldset. $this->addDisplayGroup( array('save'), 'buttons', array( 'order' => 300, 'class' => 'buttons' ) ); } /** * Extend parent to deal with the combined type and * (virtual) handler fields for dynamic menu items. * * @param string $field The field to retreive value of * @return mixed the requested fields value */ public function getValue($field) { if ($field != 'type' && $field != 'handler') { return parent::getValue($field); } $values = $this->getValues(); return isset($values[$field]) ? $values[$field] : null; } /** * Extend parent to split out the dynamic menu handler id * from the menu item type when present. * * @param bool $suppressArrayNotation see parent * @return array */ public function getValues($suppressArrayNotation = false) { $values = parent::getValues($suppressArrayNotation); if (isset($values['type'])) { $values = $this->splitType($values['type']) + $values; } return $values; } /** * Extends parent to combine handler field with type * when dealing with dynamic menu items. * * @param P4Cms_Record|array $defaults the default values to set on elements * @return Zend_Form provides fluent interface */ public function setDefaults($defaults) { parent::setDefaults($this->combineType($defaults)); $this->_updateLocationOptions(); return $this; } /** * Extends parent to combine handler field with type * when dealing with dynamic menu items. * * @param array $data see parent * @return boolean */ public function isValid($data) { return parent::isValid($this->combineType($data)); } /** * For dynamic menu items this form adds the dynamic handler id * to the end of the type value (separated by a slash). This method * exists to aid in splitting these values apart again. * * @param string $type the combined type/dynamic-handler id * (safe to call on type only values). * @return array an array containing the menu item type and optionally * the dynamic handler id (for dynamic menu items). */ public function splitType($type) { $values = array('type' => $type); if (strpos($type, 'P4Cms_Navigation_Page_Dynamic/') === 0) { list($values['type'], $values['handler']) = explode('/', $type, 2); } return $values; } /** * For dynamic menu items we append the dynamic handler id * to the end of the type value (separated by a slash). * * @param array $values form values array where the type and * dynamic handler are potentially separate. * @return array values with the dynamic handler id appended to the * type value and separated by a slash. */ public function combineType($values) { if (isset($values['type'], $values['handler']) && $values['type'] == 'P4Cms_Navigation_Page_Dynamic' ) { $values['type'] .= '/' . $values['handler']; unset($values['handler']); } return $values; } /** * Generate the page type multi-options for use with the type field. * Static so it can be used elsewhere (e.g. menu grid filters). * * @param bool $checklist optional - prepare options for a nested checklist * instead of a select field (false by default). * @param bool $includeMenu optional - include a 'Menu' entry in the options * (false by default). * @return array list of type multi-options. */ public static function getTypeOptions($checklist = false, $includeMenu = false) { // start with standard navigation page types (excluding dynamic types). $types = P4Cms_Navigation_PageTypeHandler::fetchAll(); $types = $types->filter( 'id', 'P4Cms_Navigation_Page_Dynamic', P4Cms_Model_Iterator::FILTER_INVERSE ); $types = array_combine( $types->invoke('getId'), $types->invoke('getLabel') ); if ($includeMenu) { $types['P4Cms_Menu'] = 'Menu'; } // sort the entries by label natsort($types); // if preparing for a nested checklist, include a group label. if ($checklist) { $types['P4Cms_Navigation_Page_Dynamic'] = 'Dynamic'; } // put dynamic types in an opt-group. $group = 'Dynamic'; $types[$group] = array(); $dynamicTypes = P4Cms_Navigation_DynamicHandler::fetchAll(); foreach ($dynamicTypes as $dynamicType) { $label = $dynamicType->getLabel(); $value = 'P4Cms_Navigation_Page_Dynamic/' . $dynamicType->getId(); $types[$group][$value] = $label; } natcasesort($types[$group]); // include a blank entry (except for checklists) if (!$checklist) { $types = array("" => "") + $types; } return $types; } /** * Fills in the multi-options for the location drop-down. * The entry being edited and its children are excluded from this * list to avoid recursive selection. * * If no location has been set but we do have a menu id this * method will also set the location and position appropriately. */ protected function _updateLocationOptions() { $uuid = $this->getValue('uuid'); $menuId = $this->getValue('menuId'); $location = $this->getValue('location'); $locations = $this->_getLocations(); // clear our location if it is not valid if ($location) { $items = $locations->filter('id', $location, P4Cms_Model_Iterator::FILTER_COPY); if (!count($items)) { $location = null; } } // Handle editing and adding cases: // - If we have an item UUID we are editing an existing item and need // to deal with removing it from the list and setting location. // - Otherwise, if we have a menuId and no location we are adding // and need to set the location to the end of the current list. if ($uuid) { $items = $locations->filter('menuItemId', $uuid, P4Cms_Model_Iterator::FILTER_COPY); // do the limiting and location setting if item could be found if ($items->count()) { $item = $items->first(); // if we don't have an explicitly set location; determine one if (!$location) { $previous = $item->getPreviousMenuItem(); $next = $item->getNextMenuItem(); $parentId = $item->getParentId(); // run through the three possible positions in order of preference if ($previous) { $this->getElement('location')->setValue($previous->getId()); $this->getElement('position')->setValue('after'); } else if ($next) { $this->getElement('location')->setValue($next->getId()); $this->getElement('position')->setValue('before'); } else if ($parentId) { $this->getElement('location')->setValue($parentId); $this->getElement('position')->setValue('under'); } } // remove our item and any children to avoid the user // making a recursive selection for location. $removalItems = new RecursiveIteratorIterator( $item->getMenuItem(), RecursiveIteratorIterator::SELF_FIRST ); $removalIds = array($uuid); foreach ($removalItems as $removalItem) { $removalIds[] = $removalItem->uuid; } $locations->filter('menuItemId', $removalIds, P4Cms_Model_Iterator::FILTER_INVERSE); } } else if ($menuId && !$location) { $items = $locations->filter('menuId', $menuId, P4Cms_Model_Iterator::FILTER_COPY); $items->filter('depth', 1); // if the menu has children, set our position to be the last child // otherwise set our position as under the menu. if ($items->count()) { $this->getElement('location')->setValue(end($items)->getId()); $this->getElement('position')->setValue('after'); } else { $this->getElement('location')->setValue($menuId); $this->getElement('position')->setValue('under'); } } // any modifications to the locations list are complete // at this point simply glue together the IDs and labels // (indented for depth) so we can set the multi-options $options = array('' => ''); foreach ($locations as $location) { $prefix = str_repeat(static::UTF8_NBSP, $location->getDepth() * 2); $options[$location->getId()] = $prefix . $location->getLabel(); } $this->getElement('location')->setMultiOptions($options); } /** * Get the possible locations for this menu item to be positioned relative to. * Retrieves all menus and all menu items in a single flat list. * (@see P4Cms_Menu::fetchMixed) * * @return P4Cms_Model_Iterator menus and menu items in a single flat list. */ protected function _getLocations() { return P4Cms_Menu::fetchMixed(); } }