/ */ class Menu_Form_MenuItemMvc extends Menu_Form_MenuItem { const ACTION_INTERFACE = 'Zend_Controller_Action_Interface'; /** * Defines the elements that make up the menu MVC item form. * Called automatically when the form object is created. */ public function init() { parent::init(); $this->addElement( 'select', 'action', array( 'label' => 'Action', 'required' => true, 'multiOptions' => $this->_getActionOptions() ) ); $this->addElement( 'textarea', 'params', array( 'label' => 'Parameters', 'description' => 'Enter additional action parameters in INI format.', 'rows' => 10, 'cols' => 80 ) ); } /** * Extends parent to combine route params (module/controller/action) * into a single action field. * * @param P4Cms_Record|array $defaults the default values to set on elements * @return Zend_Form provides fluent interface */ public function setDefaults($defaults) { $defaults = $this->_combineRouteParams($defaults); $defaults = $this->_combineParamsArray($defaults); return parent::setDefaults($defaults); } /** * Extends parent to combine combine route params (module/controller/action) * into a single action field, and change the params array, if it exists, * into an ini formatted string * * isValid never calls setDefaults, which is why this needs to be done here as well * * @param array $data see parent * @return boolean */ public function isValid($data) { return parent::isValid($this->_combineParamsArray($this->_combineRouteParams($data))); } /** * Extend parent to split action into discrete module/controller/action. * * @param bool $suppressArrayNotation see parent * @return array */ public function getValues($suppressArrayNotation = false) { $values = parent::getValues($suppressArrayNotation); $values = $this->_splitRouteParams($values); $values = $this->_splitParamsIniString($values); return $values; } /** * Get a multi-options formatted list of all actions across all modules. * * @return array a list of all controller actions grouped by module. */ protected function _getActionOptions() { // front controller gives us access to route defaults. $front = Zend_Controller_Front::getInstance(); // fetch all actions from all controllers in all modules. $options = array(); foreach (P4Cms_Module::fetchAllEnabled() as $module) { // get the module's particulars $path = $module->getPath() . "/controllers"; $moduleLabel = $module->getName(); $moduleId = $this->_formatRoute($module->getId()); // skip if there is no controllers directory if (!is_dir($path)) { continue; } // add an opt-group for this module. $options[$moduleLabel] = array(); // examine each file under the module's controllers directory // if it is a controller, fetch all of its action methods // always look for the default controller first so it appears // first - all other controllers should list alphabetically. $files = array_merge( array($front->getDefaultControllerName() . 'Controller.php'), scandir($path) ); foreach ($files as $file) { // only consider files ending in 'controller'. $matches = array(); if (!is_file($path . '/' . $file) || (!preg_match('/((.*)Controller).php$/', $file, $matches)) ) { continue; } // skip invalid controller classes $class = ucfirst($moduleId) . '_' . $matches[1]; if (!class_exists($class) || !in_array(static::ACTION_INTERFACE, class_implements($class), true) ) { continue; } // get the controller label and id $controllerName = $matches[2]; $controllerLabel = $this->_formatLabel($controllerName); $controllerId = $this->_formatRoute($controllerName); // collect actions from the controller. // sort the actions alphabetically, but always put the // default action first so that it appears first in options $methods = get_class_methods($class); natsort($methods); array_unshift($methods, $front->getDefaultAction() . 'Action'); foreach ($methods as $method) { // only consider methods ending in 'action'. $matches = array(); if (!method_exists($class, $method) || !preg_match('/(.*)Action$/', $method, $matches) ) { continue; } $actionName = $matches[1]; $actionLabel = $this->_formatLabel($actionName); $actionId = $this->_formatRoute($actionName); // combine module/controller/action to form a // qualified action id for the option value. $value = implode('/', array($moduleId, $controllerId, $actionId)); // format the action label for the select option $label = $moduleLabel; $label .= $controllerId !== $front->getDefaultControllerName() ? '/'. $controllerLabel : ''; $label .= $actionId !== $front->getDefaultAction() ? '/' . $actionLabel : ' (Default Action)'; $options[$moduleLabel][$value] = $label; } } } // remove any empty opt-groups and return. return array_filter($options); } /** * Convert a camelCase formatted action or controller name * into a dash-separated version suitable for routing. * * @param string $input the camelCase formatted identifier. * @return string the route formatted (dash-separated) version. */ protected function _formatRoute($input) { return P4Cms_Controller_Router_Route_Module::formatRouteParam( $input ); } /** * Convert a camelCase formatted action or controller name * into a human-friendly (space-separated) label. * * @param string $input the camelCase formatted identifier. * @return string the human-friendly version. */ protected function _formatLabel($input) { return trim(ucwords(preg_replace('/([A-Z])/', ' ${1}', $input))); } /** * Convert params INI string to an array using Zend_Config_Ini * write to a temp file to facilitate Zend_Config_Ini parsing * * @param array $values values that contain the string to convert * @return array values with params converted to an array */ protected function _splitParamsIniString($values) { $params = array(); if (isset($values['params']) && strlen($values['params'])) { $tempFile = tempnam(sys_get_temp_dir(), 'menu-form'); file_put_contents($tempFile, $values['params']); $config = new Zend_Config_Ini($tempFile); $params = $config->toArray(); unlink($tempFile); } $values['params'] = is_array($params) ? $params : array(); return $values; } /** * If parameters are set, present them in INI format. * * @param array $values values that contain the array to convert * @return array values with params converted to an INI formatted strings */ protected function _combineParamsArray($values) { if (isset($values['params']) && is_array($values['params'])) { $config = new Zend_Config($values['params']); $writer = new Zend_Config_Writer_Ini(); $values['params'] = $writer->setConfig($config)->render(); } return $values; } /** * Combine module/controller/action parameters to a single * action value in given values array. If any parameters are * missing, default values are used. * * @param array $values values to combine route parameters in. * @return array values with module/controller/action combined to action. */ protected function _combineRouteParams($values) { if (isset($values['module']) || isset($values['controller'])) { $front = Zend_Controller_Front::getInstance(); $module = isset($values['module']) ? $values['module'] : $front->getDefaultModule(); $controller = isset($values['controller']) ? $values['controller'] : $front->getDefaultControllerName(); $action = isset($values['action']) ? $values['action'] : $front->getDefaultAction(); $values['action'] = implode('/', array($module, $controller, $action)); unset($values['module'], $values['controller']); } return $values; } /** * Split action parameter into discrete module/controller/action values. * If any values are missing, default values are used. * * @param array $values values array containing action to split. * @return array values with action split into module/controller/action. */ protected function _splitRouteParams($values) { if (!isset($values['module']) && !isset($values['controller'])) { $front = Zend_Controller_Front::getInstance(); $action = isset($values['action']) ? $values['action'] : ''; $params = explode('/', $action, 3); $module = isset($params[0]) ? $params[0] : $front->getDefaultModule(); $controller = isset($params[1]) ? $params[1] : $front->getDefaultControllerName(); $action = isset($params[2]) ? $params[2] : $front->getDefaultAction(); $values = array_merge( $values, array( 'module' => $module, 'controller' => $controller, 'action' => $action ) ); } return $values; } }