Version: current

      1 <?php
      2 /**
      3  * The object manages the main CMS menu.
      4  * See {@link LeftAndMain::init()} for example usage.
      5  *
      6  * @package cms
      7  * @subpackage content
      8  */
      9 class CMSMenu extends SS_Object implements IteratorAggregate, i18nEntityProvider
     10 {
     11
     12     /**
     13      * An array of changes to be made to the menu items, in the order that the changes should be
     14      * applied.  Each item is a map in one of the two forms:
     15      *  - array('type' => 'add', 'item' => new CMSMenuItem(...) )
     16      *  - array('type' => 'remove', 'code' => 'codename' )
     17      */
     18     protected static $menu_item_changes = array();
     19
     20     /**
     21      * Set to true if clear_menu() is called, to indicate that the default menu shouldn't be
     22      * included
     23      */
     24     protected static $menu_is_cleared = false;
     25
     26     /**
     27      * Generate CMS main menu items by collecting valid
     28      * subclasses of {@link LeftAndMain}
     29      */
     30     public static function populate_menu() {
     31         self::$menu_is_cleared = false;
     32     }
     33
     34     /**
     35      * Add Director rules for all of the CMS controllers.
     36      */
     37     public static function add_director_rules() {
     38         array_map(array('self','add_director_rule_for_controller'), self::get_cms_classes());
     39     }
     40
     41     /**
     42      * Add a LeftAndMain controller to the CMS menu.
     43      *
     44      * @param string $controllerClass The class name of the controller
     45      * @return The result of the operation
     46      * @todo A director rule is added when a controller link is added, but it won't be removed
     47      *          when the item is removed. Functionality needed in {@link Director}.
     48      */
     49     public static function add_controller($controllerClass) {
     50         if($menuItem = self::menuitem_for_controller($controllerClass)) {
     51             self::add_menu_item_obj($controllerClass, $menuItem);
     52         }
     53     }
     54
     55     /**
     56      * Return a CMSMenuItem to add the given controller to the CMSMenu
     57      */
     58     protected static function menuitem_for_controller($controllerClass) {
     59         $urlBase      = SS_Object::get_static($controllerClass, 'url_base');
     60         $urlSegment   = SS_Object::get_static($controllerClass, 'url_segment');
     61         $menuPriority = SS_Object::get_static($controllerClass, 'menu_priority');
     62
     63         // Don't add menu items defined the old way
     64         if($urlSegment === null && $controllerClass != "CMSMain") return;
     65
     66         $link = Controller::join_links($urlBase, $urlSegment) . '/';
     67
     68         // doesn't work if called outside of a controller context (e.g. in _config.php)
     69         // as the locale won't be detected properly. Use {@link LeftAndMain->MainMenu()} to update
     70         // titles for existing menu entries
     71         $defaultTitle = LeftAndMain::menu_title_for_class($controllerClass);
     72         $menuTitle = _t("{$controllerClass}.MENUTITLE", $defaultTitle);
     73
     74         return new CMSMenuItem($menuTitle, $link, $controllerClass, $menuPriority);
     75     }
     76
     77     /**
     78      * Add the appropriate Director rules for the given controller.
     79      */
     80     protected static function add_director_rule_for_controller($controllerClass) {
     81         $urlBase      = SS_Object::get_static($controllerClass, 'url_base');
     82         $urlSegment   = SS_Object::get_static($controllerClass, 'url_segment');
     83         $urlRule      = SS_Object::get_static($controllerClass, 'url_rule');
     84         $urlPriority  = SS_Object::get_static($controllerClass, 'url_priority');
     85
     86         if($urlSegment || $controllerClass == "CMSMain") {
     87             $link = Controller::join_links($urlBase, $urlSegment) . '/';
     88
     89             // Make director rule
     90             if($urlRule[0] == '/') $urlRule = substr($urlRule,1);
     91             $rule = $link . '/' . $urlRule; // the / will combine with the / on the end of $link to make a //
     92             Director::addRules($urlPriority, array(
     93                 $rule => $controllerClass
     94             ));
     95         }
     96     }
     97
     98     /**
     99      * Add an arbitrary URL to the CMS menu.
    100      *
    101      * @param string $code A unique identifier (used to create a CSS ID and as it's key in {@link $menu_items}
    102      * @param string $menuTitle The link's title in the CMS menu
    103      * @param string $url The url of the link
    104      * @param integer $priority The menu priority (sorting order) of the menu item.  Higher priorities will be further left.
    105      * @return boolean The result of the operation.
    106      */
    107     public static function add_link($code, $menuTitle, $url, $priority = -1) {
    108         return self::add_menu_item($code, $menuTitle, $url, null, $priority);
    109     }
    110
    111     /**
    112      * Add a navigation item to the main administration menu showing in the top bar.
    113      *
    114      * uses {@link CMSMenu::$menu_items}
    115      *
    116      * @param string $code Unique identifier for this menu item (e.g. used by {@link replace_menu_item()} and
    117      *                  {@link remove_menu_item}. Also used as a CSS-class for icon customization.
    118      * @param string $menuTitle Localized title showing in the menu bar
    119      * @param string $url A relative URL that will be linked in the menu bar.
    120      * @param string $controllerClass The controller class for this menu, used to check permisssions.
    121      *                  If blank, it's assumed that this is public, and always shown to users who
    122      *                  have the rights to access some other part of the admin area.
    123      * @return boolean Success
    124      */
    125     public static function add_menu_item($code, $menuTitle, $url, $controllerClass = null, $priority = -1) {
    126         // If a class is defined, then force the use of that as a code.  This helps prevent menu item duplication
    127         if($controllerClass) $code = $controllerClass;
    128
    129         return self::replace_menu_item($code, $menuTitle, $url, $controllerClass, $priority);
    130     }
    131
    132     /**
    133      * Get a single menu item by its code value.
    134      *
    135      * @param string $code
    136      * @return array
    137      */
    138     public static function get_menu_item($code) {
    139         $menuItems = self::get_menu_items();
    140         return (isset($menuItems[$code])) ? $menuItems[$code] : false;
    141     }
    142
    143     /**
    144      * Get all menu entries.
    145      *
    146      * @return array
    147      */
    148     public static function get_menu_items() {
    149         $menuItems = array();
    150
    151         // Set up default menu items
    152         if(!self::$menu_is_cleared) {
    153             $cmsClasses = self::get_cms_classes();
    154             foreach($cmsClasses as $cmsClass) {
    155                 $menuItem = self::menuitem_for_controller($cmsClass);
    156                 if($menuItem) $menuItems[$cmsClass] = $menuItem;
    157             }
    158         }
    159
    160         // Apply changes
    161         foreach(self::$menu_item_changes as $change) {
    162             switch($change['type']) {
    163                 case 'add':
    164                     $menuItems[$change['code']] = $change['item'];
    165                     break;
    166
    167                 case 'remove':
    168                     unset($menuItems[$change['code']]);
    169                     break;
    170
    171                 default:
    172                     user_error("Bad menu item change type {$change[type]}", E_USER_WARNING);
    173             }
    174         }
    175
    176         // Sort menu items according to priority
    177         $menuPriority = array();
    178         $i = 0;
    179         foreach($menuItems as $key => $menuItem) {
    180             $i++;
    181             // This funny litle formula ensures that the first item added with the same priority will be left-most.
    182             $menuPriority[$key] = $menuItem->priority*100 - $i;
    183         }
    184         array_multisort($menuPriority, SORT_DESC, $menuItems);
    185
    186         return $menuItems;
    187     }
    188
    189     /**
    190      * Get all menu items that the passed member can view.
    191      * Defaults to {@link Member::currentUser()}.
    192      *
    193      * @param Member $member
    194      * @return array
    195      */
    196     public static function get_viewable_menu_items($member = null) {
    197         if(!$member && $member !== FALSE) {
    198             $member = Member::currentUser();
    199         }
    200
    201         $viewableMenuItems = array();
    202         $allMenuItems = self::get_menu_items();
    203         if($allMenuItems) foreach($allMenuItems as $code => $menuItem) {
    204             // exclude all items which have a controller to perform permission
    205             // checks on
    206             if($menuItem->controller && !singleton($menuItem->controller)->canView($member)) continue;
    207
    208             $viewableMenuItems[$code] = $menuItem;
    209         }
    210
    211         return $viewableMenuItems;
    212     }
    213
    214     /**
    215      * Removes an existing item from the menu.
    216      *
    217      * @param string $code Unique identifier for this menu item
    218      */
    219     public static function remove_menu_item($code) {
    220         self::$menu_item_changes[] = array('type' => 'remove', 'code' => $code);
    221     }
    222
    223     /**
    224      * Clears the entire menu
    225      */
    226     public static function clear_menu() {
    227         self::$menu_item_changes = array();
    228         self::$menu_is_cleared = true;
    229     }
    230
    231     /**
    232      * Replace a navigation item to the main administration menu showing in the top bar.
    233      *
    234      * @param string $code Unique identifier for this menu item (e.g. used by {@link replace_menu_item()} and
    235      *                  {@link remove_menu_item}. Also used as a CSS-class for icon customization.
    236      * @param string $menuTitle Localized title showing in the menu bar
    237      * @param string $url A relative URL that will be linked in the menu bar.
    238      *                  Make sure to add a matching route via {@link Director::addRules()} to this url.
    239      * @param string $controllerClass The controller class for this menu, used to check permisssions.
    240      *                  If blank, it's assumed that this is public, and always shown to users who
    241      *                  have the rights to access some other part of the admin area.
    242      * @return boolean Success
    243      */
    244     public static function replace_menu_item($code, $menuTitle, $url, $controllerClass = null, $priority = -1) {
    245         self::$menu_item_changes[] = array(
    246             'type' => 'add',
    247             'code' => $code,
    248             'item' => new CMSMenuItem($menuTitle, $url, $controllerClass, $priority),
    249         );
    250     }
    251
    252     /**
    253      * Add a previously built menuitem object to the menu
    254      */
    255     protected static function add_menu_item_obj($code, $cmsMenuItem) {
    256         self::$menu_item_changes[] = array(
    257             'type' => 'add',
    258             'code' => $code,
    259             'item' => $cmsMenuItem,
    260         );
    261     }
    262
    263     /**
    264      * A utility funciton to retrieve subclasses of a given class that
    265      * are instantiable (ie, not abstract) and have a valid menu title.
    266      *
    267      * @todo A variation of this function could probably be moved to {@link ClassInfo}
    268      * @param string $root The root class to begin finding subclasses
    269      * @param boolean $recursive Look for subclasses recursively?
    270      * @return array Valid, unique subclasses
    271      */
    272     public static function get_cms_classes($root = 'LeftAndMain', $recursive = true) {
    273         $subClasses = array_values(ClassInfo::subclassesFor($root));
    274         foreach($subClasses as $className) {
    275             if($recursive && $className != $root) {
    276                 $subClasses = array_merge($subClasses, array_values(ClassInfo::subclassesFor($className)));
    277             }
    278         }
    279         $subClasses = array_unique($subClasses);
    280         foreach($subClasses as $key => $className) {
    281             // Remove abstract classes and LeftAndMain
    282             $classReflection = new ReflectionClass($className);
    283             if(
    284                 !$classReflection->isInstantiable()
    285                 || 'LeftAndMain' == $className
    286                 || ClassInfo::classImplements($className, 'TestOnly')
    287             ) {
    288                 unset($subClasses[$key]);
    289             }
    290         }
    291
    292         return $subClasses;
    293     }
    294
    295     /**
    296      * IteratorAggregate Interface Method.  Iterates over the menu items.
    297      */
    298     function getIterator() {
    299         return new ArrayIterator(self::get_menu_items());
    300     }
    301
    302     /**
    303      * Provide menu titles to the i18n entity provider
    304      */
    305     function provideI18nEntities() {
    306         $cmsClasses = self::get_cms_classes();
    307         $entities = array();
    308         foreach($cmsClasses as $cmsClass) {
    309             $defaultTitle = LeftAndMain::menu_title_for_class($cmsClass);
    310             $ownerModule = i18n::get_owner_module($cmsClass);
    311             $entities["{$cmsClass}.MENUTITLE"] = array($defaultTitle, PR_HIGH, 'Menu title', $ownerModule);
    312         }
    313         return $entities;
    314     }
    315 }
    316 ?>

[Raise a SilverStripe Framework issue/bug](https://github.com/silverstripe/silverstripe-framework/issues/new)
- [Raise a SilverStripe CMS issue/bug](https://github.com/silverstripe/silverstripe-cms/issues/new)
- Please use the Silverstripe Forums to ask development related questions. -