/app/controllers/components/acl_menu/menu.php
PHP | 508 lines | 270 code | 31 blank | 207 comment | 44 complexity | fbdb302b23bb4aefb83f7da8913a15ff MD5 | raw file
- <?php
- /**
- * Menu Component
- *
- * Uses ACL to generate Menus.
- *
- * Copyright 2008, Mark Story.
- *
- * Licensed under The MIT License
- * Redistributions of files must retain the above copyright notice.
- *
- * @copyright Copyright 2008, Mark Story.
- * @link http://mark-story.com
- * @version 1.1
- * @author Mark Story <mark@mark-story.com>
- * @license http://www.opensource.org/licenses/mit-license.php The MIT License
- */
- class MenuComponent extends Object {
- /**
- * The Default Menu Parent for things that have no parent element defined
- * used a lot by menu items generated by controller folder scrapings
- *
- * @var string
- */
- public $defaultMenuParent = null;
- /**
- * Set to false to disable the auto menu generation in startup()
- * Useful if you want your menus generated off of Aro's other than the user in the current session.
- *
- * @var boolean
- */
- public $autoLoad = true;
- /**
- * Controller reference
- *
- * @var object
- */
- public $Controller = null;
- /**
- * Components used by Menu
- *
- * @var array
- */
- public $components = array('Acl', 'Auth');
-
- /**
- * Key for the caching
- *
- * @var string
- */
- public $cacheKey = 'menu_storage';
-
- /**
- * Time to cache menus for.
- *
- * @var string String compatible with strtotime.
- */
- public $cacheTime = '+1 day';
- /**
- * cache config key
- *
- * @var string
- */
- public $cacheConfig = 'menu_component';
- /**
- * Separator between controller and action name.
- *
- * @var string
- */
- public $aclSeparator = '/';
-
- /**
- * The Node path to get to the controller listing
- *
- * @var string
- **/
- public $aclPath = 'controllers/';
- /**
- * Array of Actions to exclude when making menus.
- * Per controller exclusions can be set with Controller::menuOptions
- *
- * @var array
- **/
- public $excludeActions = array('view', 'edit', 'delete', 'admin_edit', 'admin_delete', 'admin_edit', 'admin_view');
-
- /**
- * Completed list of methods to not include in menus. Includes all of Controller's methods.
- *
- * @var array
- **/
- public $excludedMethods = array();
- /**
- * The Completed menu for the current user.
- *
- * @var array
- */
- public $menu = array();
-
- /**
- * Raw menus before formatting, either loaded from parsing controllers directory or loading Cache
- *
- * @var array
- */
- public $rawMenus = array();
- /**
- * Internal Flag to check if new menus have been added to a cached menu set. Indicates that new menu items
- * have been added and that menus need to be rebuilt.
- *
- */
- protected $_rebuildMenus = false;
- /**
- * initialize function
- *
- * Takes Settings declared in Controller and assigns them.
- *
- * @return bool
- **/
- public function initialize(&$Controller, $settings) {
- if (!empty($settings)) {
- $this->_set($settings);
- }
- return true;
- }
-
- /**
- * Startup Method
- *
- * Automatically makes menus for all a the controllers based on the current user.
- * If $this->autoLoad = false then you must manually loadCache(),
- * contstructMenu() and writeCache().
- *
- * @param Object $Controller
- */
- public function startup(&$Controller) {
- $this->Controller =& $Controller;
-
- Cache::config($this->cacheConfig, array('engine' => 'File', 'duration' => $this->cacheTime, 'prefix' => $this->cacheKey));
-
- //no active session, no menu can be generated
- if (!$this->Auth->user()) {
- return;
- }
- if ($this->autoLoad) {
- $this->loadCache();
- $this->constructMenu($this->Auth->user());
- $this->writeCache();
- }
- }
- /**
- * Write the current Block Access data to a file.
- *
- * @return boolean on success of writing a file.
- */
- public function writeCache() {
- $data = array(
- 'menus' => $this->rawMenus
- );
- if (Cache::write($this->cacheKey, $data, $this->cacheConfig)) {
- return true;
- }
- $this->log('Menu Component - Could not write Menu cache.');
- return false;
- }
-
- /**
- * Load the Cached Permissions and restore them
- *
- * @return boolean true if cache was loaded.
- */
- public function loadCache() {
- if ($data = Cache::read($this->cacheKey, $this->cacheConfig)) {
- $this->rawMenus = $this->_mergeMenuCache($data['menus']);
- return true;
- }
- $this->_rebuildMenus = true;
- return false;
- }
- /**
- * Clears the raw Menu Cache, this will in turn force
- * a menu rebuild for each ARO that needs a menu.
- *
- * @return boolean
- **/
- public function clearCache() {
- return Cache::delete($this->cacheKey, $this->cacheConfig);
- }
- /**
- * Construct the menus From the Controllers in the Application. This is an expensive
- * Process Timewise and is cached.
- *
- * @param string $aro Aro Alias / identification array that a menu is needed for.
- */
- public function constructMenu($aro) {
- $aroKey = $aro;
- if (is_array($aro)) {
- $aroKey = key($aro) . $aro[key($aro)]['id'];
- }
- $cacheKey = $aroKey . '_' . $this->cacheKey;
- $completeMenu = Cache::read($cacheKey, $this->cacheConfig);
- if (!$completeMenu || $this->_rebuildMenus == true) {
- $this->generateRawMenus();
- $menu = array();
- $size = count($this->rawMenus);
- for ($i = 0; $i < $size; $i++) {
- $item = $this->rawMenus[$i];
- $aco = Inflector::underscore($item['url']['controller']);
- if (isset($item['url']['action'])) {
- $aco = $this->aclPath . $aco . $this->aclSeparator . $item['url']['action'];
- }
- if ($this->Acl->check($aro, $aco)) {
- if (!isset($menu[$item['id']])) {
- $menu[$item['id']] = $this->rawMenus[$i];
- }
- }
- }
- $completeMenu = $this->_formatMenu($menu);
- Cache::write($cacheKey, $completeMenu, $this->cacheConfig);
- }
- $this->menu = $completeMenu;
- }
-
- /**
- * Generate Raw Menus from Controller in the Application
- * Loads a list of All controllers in the app/controllers, imports the class and gets a method
- * list. Uses a common exclusion list to remove unwanted methods. Each Controller can specify a
- * menuOptions var which allows additional menu configuration.
- *
- * Menu Options for Controllers:
- * exclude => actions to exclude from the menu list
- * parent => Parent link to add a controller / actions underneath
- * alias => array of action => aliases Allows you to set friendly link names for actions
- *
- * @return void sets $this->rawMenus
- */
- public function generateRawMenus() {
- $Controllers = $this->getControllers();
- $cakeAdmin = Configure::read('Routing.admin');
- $this->createExclusions();
-
- //go through the controllers folder and make an array of every menu that could be used.
- foreach($Controllers as $Controller) {
- if ($Controller == 'App') {
- continue;
- }
- $ctrlName = $Controller;
- App::import('Controller', $ctrlName);
- $ctrlclass = $ctrlName . 'Controller';
- $methods = get_class_methods($ctrlclass);
-
- $classVars = get_class_vars($ctrlclass);
- $menuOptions = $this->setOptions($classVars);
- if ($menuOptions === false) {
- continue;
- }
- $methods = $this->filterMethods($methods, $menuOptions['exclude']);
-
- $ctrlCamel = Inflector::variable($ctrlName);
- $ctrlHuman = Inflector::humanize(Inflector::underscore($ctrlCamel));
- $methodList = array();
- $adminController = false;
- foreach ($methods as $action) {
- $camelAction = Inflector::variable($action);
- if (empty($menuOptions['alias']) || !isset($menuOptions['alias'][$action])) {
- $human = Inflector::humanize(Inflector::underscore($action));
- } else {
- $human = $menuOptions['alias'][$action];
- }
-
- $url = array(
- 'controller' => $ctrlCamel,
- 'action' => $action
- );
- if ($cakeAdmin) {
- $url[$cakeAdmin] = false;
- }
- if (strpos($action, $cakeAdmin . '_') !== false && $cakeAdmin) {
- $url[$cakeAdmin] = true;
- $adminController = true;
- }
- $parent = $menuOptions['controllerButton'] ? $ctrlCamel : $menuOptions['parent'];
- $this->rawMenus[] = array(
- 'parent' => $parent,
- 'id' => $this->_createId($ctrlCamel, $action),
- 'title' => $human,
- 'url' => $url,
- 'weight' => 0,
- );
- }
- if ($menuOptions['controllerButton']) {
- //If an admin index exists use it.
- $action = $adminController ? $cakeAdmin . '_index' : 'index';
- $url = array(
- 'controller' => $ctrlCamel,
- 'action' => $action,
- 'admin' => $adminController,
- );
- $menuItem = array(
- 'parent' => $menuOptions['parent'],
- 'id' => $ctrlCamel,
- 'title' => $ctrlHuman,
- 'url' => $url,
- 'weight' => 0
- );
- $this->rawMenus[] = $menuItem;
- }
- }
- }
-
- /**
- * Get the Controllers in the Application
- *
- * @access public
- * @return void
- */
- public function getControllers() {
- return Configure::listObjects('controller');
- }
-
- /**
- * filter out methods based on $menuOptions.
- * Removes private actions as well.
- *
- * @param array $methods Array of methods to prepare
- * @param array $remove Array of additional Methods to remove, normally options on the controller.
- * @return array
- **/
- public function filterMethods($methods, $remove = array()) {
- if (!empty($remove)) {
- $remove = array_map('strtolower', $remove);
- }
- $exclusions = array_merge($this->excludedMethods, $remove);
- foreach ($methods as $k => $method) {
- $method = strtolower($method);
- if (strpos($method, '_', 0) === 0) {
- unset($methods[$k]);
- }
- if (in_array($method, $exclusions)) {
- unset($methods[$k]);
- }
- }
- return array_values($methods);
- }
-
- /**
- * Set the Options for the current Controller.
- *
- * @return mixed. Array of options or false on total exclusion
- **/
- public function setOptions($controllerVars) {
- $cakeAdmin = Configure::read('Routing.admin');
- $menuOptions = isset($controllerVars['menuOptions']) ? $controllerVars['menuOptions'] : array();
- $exclude = array('view', 'edit', 'delete', $cakeAdmin . '_edit',
- $cakeAdmin . '_delete', $cakeAdmin . '_edit', $cakeAdmin . '_view');
- $defaults = array(
- 'exclude' => $exclude,
- 'alias' => array(),
- 'parent' => $this->defaultMenuParent,
- 'controllerButton' => true
- );
- $menuOptions = Set::merge($defaults, $menuOptions);
- if (in_array('*', (array)$menuOptions['exclude'])) {
- return false;
- }
- return $menuOptions;
- }
-
- /**
- * Creates the Exclusions for generating menus.
- *
- * @return void
- **/
- public function createExclusions() {
- $methods = array_merge(get_class_methods('Controller'), $this->excludeActions);
- $this->excludedMethods = array_map('strtolower', $methods);
- }
- /**
- * Add a Menu Item.
- * Allows manual Insertion into the menu system.
- * If Added after constructMenu() It will not be shown
- *
- * @param string $parent
- * @param array $menu
- * 'Menu' Array
- * 'title' => name
- * 'url' => url array of menu, url strings are lame and won't work
- * 'key' => unique name of this menu for parenting purposes.
- * 'controller' => controller Name this action is from
- */
- public function addMenu($menu) {
- $defaults = array(
- 'title' => null,
- 'url' => null,
- 'parent' => null,
- 'id' => null,
- 'weight' => 0,
- );
- $menu = array_merge($defaults, $menu);
- if (!$menu['id'] && isset($menu['url'])) {
- $menu['id'] = $this->_createId($menu['url']);
- }
- if (!$menu['title'] && isset($menu['url']['action'])) {
- $menu['title'] = Inflector::humanize($menu['url']['action']);
- }
- $this->rawMenus[] = $menu;
- }
- /**
- * BeforeRender Callback.
- *
- */
- public function beforeRender() {
- $this->Controller->set('menu', $this->menu);
- }
-
- /**
- * Make a Unique Menu item key
- *
- * @param array $parts
- * @return string Unique key name
- */
- protected function _createId() {
- $parts = func_get_args();
- if (is_array($parts[0])) {
- $parts = $parts[0];
- }
- $key = Inflector::variable(implode('-', $parts));
- return $key;
- }
- /**
- * Recursive function to construct Menu
- *
- * @param unknown_type $menu
- * @param unknown_type $parentId
- */
- protected function _formatMenu($menu) {
- $out = $idMap = array();
- foreach ($menu as $item) {
- $item['children'] = array();
- $id = $item['id'];
- $parentId = $item['parent'];
- if (isset($idMap[$id]['children'])) {
- $idMap[$id] = am($item, $idMap[$id]);
- } else {
- $idMap[$id] = am($item, array('children' => array()));
- }
- if ($parentId) {
- $idMap[$parentId]['children'][] =& $idMap[$id];
- } else {
- $out[] =& $idMap[$id];
- }
- }
- usort($out, array(&$this, '_sortMenu'));
- return $out;
- }
- /**
- * Sort the menu before returning it. Used with usort()
- *
- * @return int
- **/
- protected function _sortMenu($one, $two) {
- if ($one['weight'] == $two['weight']) {
- return 1;
- }
- return ($one['weight'] < $two['weight']) ? -1 : 1;
- }
- /**
- * Merge the Cached menus with the Menus added in Controller::beforeFilter to ensure they are unique.
- *
- * @param array $cachedMenus
- * @return array Merged Menus
- */
- protected function _mergeMenuCache($cachedMenus) {
- $cacheCount = sizeOf($cachedMenus);
- $currentCount = sizeOf($this->rawMenus);
- $tmp = array();
- for ($i = 0; $i < $currentCount; $i++) {
- $exist = false;
- $addedMenu = $this->rawMenus[$i];
- for ($j =0; $j < $cacheCount; $j++) {
- $cachedItem = $cachedMenus[$j];
- if ($addedMenu['id'] == $cachedItem['id']) {
- $exist = true;
- break;
- }
- }
- if ($exist) {
- continue;
- }
- $tmp[] = $addedMenu;
- }
- if (!empty($tmp)) {
- $this->_rebuildMenus = true;
- }
- return array_merge($cachedMenus, $tmp);
- }
- }
- ?>