From c08e1da117db24763bfd33b674d97e0b976f61c7 Mon Sep 17 00:00:00 2001 From: Marco Villegas Date: Tue, 8 Jan 2013 19:57:22 -0500 Subject: [PATCH] Issue #1883724 by marvil07: Start port of menu_example to D8. --- .../Drupal/menu_example/Tests/MenuExampleTest.php | 101 ++++ menu_example/menu_example.info | 4 + menu_example/menu_example.module | 537 ++++++++++++++++++++ 3 files changed, 642 insertions(+) create mode 100644 menu_example/lib/Drupal/menu_example/Tests/MenuExampleTest.php create mode 100644 menu_example/menu_example.info create mode 100644 menu_example/menu_example.module diff --git a/menu_example/lib/Drupal/menu_example/Tests/MenuExampleTest.php b/menu_example/lib/Drupal/menu_example/Tests/MenuExampleTest.php new file mode 100644 index 0000000..2c0d1be --- /dev/null +++ b/menu_example/lib/Drupal/menu_example/Tests/MenuExampleTest.php @@ -0,0 +1,101 @@ + 'Menu example functionality', + 'description' => 'Checks behavior of Menu Example.', + 'group' => 'Examples', + ); + } + + /** + * Test the various menus. + */ + function testMenuExample() { + $this->drupalGet('examples/menu_example'); + $this->assertText(t('This is the base page of the Menu Example')); + + $this->clickLink(t('Custom Access Example')); + $this->assertText(t('Custom Access Example')); + + $this->clickLink(t('examples/menu_example/custom_access/page')); + $this->assertResponse(403); + + $this->drupalGet('examples/menu_example/permissioned'); + $this->assertText(t('Permissioned Example')); + + $this->clickLink('examples/menu_example/permissioned/controlled'); + $this->assertResponse(403); + + $this->drupalGet('examples/menu_example'); + + $this->clickLink(t('MENU_CALLBACK example')); + + $this->drupalGet('examples/menu_example/path_only/callback'); + $this->assertText(t('The menu entry for this page is of type MENU_CALLBACK')); + + $this->clickLink(t('Tabs')); + $this->assertText(t('This is the "tabs" menu entry')); + + $this->drupalGet('examples/menu_example/tabs/second'); + $this->assertText(t('This is the tab "second" in the "basic tabs" example')); + + $this->clickLink(t('third')); + $this->assertText(t('This is the tab "third" in the "basic tabs" example')); + + $this->clickLink(t('Extra Arguments')); + + $this->drupalGet('examples/menu_example/use_url_arguments/one/two'); + $this->assertText(t('Argument 1=one')); + + $this->clickLink(t('Placeholder Arguments')); + + $this->clickLink(t('examples/menu_example/placeholder_argument/3343/display')); + $this->assertRaw('
3343
'); + + $this->clickLink(t('Processed Placeholder Arguments')); + $this->assertText(t('Loaded value was jackpot! default')); + + // Create a user with permissions to access protected menu entry. + $web_user = $this->drupalCreateUser(array('access protected menu example')); + + // Use custom overridden drupalLogin function to verify the user is logged + // in. + $this->drupalLogin($web_user); + + // Check that our title callback changing /user dynamically is working. + // Using ' because of the format_username function. + $this->assertRaw(t("@name's account", array('@name' => format_username($web_user))), format_string('Title successfully changed to account name: %name.', array('%name' => $web_user->name))); + + // Now start testing other menu entries. + $this->drupalGet('examples/menu_example'); + + $this->clickLink(t('Custom Access Example')); + $this->assertText(t('Custom Access Example')); + + $this->drupalGet('examples/menu_example/custom_access/page'); + $this->assertResponse(200); + + $this->drupalGet('examples/menu_example/permissioned'); + $this->assertText('Permissioned Example'); + $this->clickLink('examples/menu_example/permissioned/controlled'); + $this->assertText('This menu entry will not show'); + + $this->drupalGet('examples/menu_example/menu_altered_path'); + $this->assertText('This menu item was created strictly to allow the hook_menu_alter()'); + } +} diff --git a/menu_example/menu_example.info b/menu_example/menu_example.info new file mode 100644 index 0000000..caa548b --- /dev/null +++ b/menu_example/menu_example.info @@ -0,0 +1,4 @@ +name = Menu example +description = An example of advanced uses of the menu APIs. +package = Example modules +core = 8.x diff --git a/menu_example/menu_example.module b/menu_example/menu_example.module new file mode 100644 index 0000000..541e3b8 --- /dev/null +++ b/menu_example/menu_example.module @@ -0,0 +1,537 @@ + MENU_NORMAL_ITEM, + + // The menu title. Do NOT use t() which is called by default. You can + // override the use of t() by defining a 'title callback'. This is explained + // in the 'examples/menu_example/title_callbacks' example below. + 'title' => 'Menu Example', + + // Description (hover flyover for menu link). Does NOT use t(), which is + // called automatically. + 'description' => 'Simplest possible menu type, and the parent menu entry for others', + + // Function to be called when this path is accessed. + 'page callback' => '_menu_example_basic_instructions', + + // Arguments to the page callback. Here's we'll use them just to provide + // content for our page. + 'page arguments' => array(t('This page is displayed by the simplest (and base) menu example. Note that the title of the page is the same as the link title. You can also visit a similar page with no menu link. Also, note that there is a hook_menu_alter() example that has changed the path of one of the menu items.', array('!link' => url('examples/menu_example/path_only')))), + + // If the page is meant to be accessible to all users, you can set 'access + // callback' to TRUE. This bypasses all access checks. For an explanation on + // how to use the permissions system to restrict access for certain users, + // see the example 'examples/menu_example/permissioned/controlled' below. + 'access callback' => TRUE, + + // If the page callback is located in another file, specify it here and + // that file will be automatically loaded when needed. + // 'file' => 'menu_example.module', + + // We can choose which menu gets the link. The default is 'navigation'. + // 'menu_name' => 'navigation', + + // Show the menu link as expanded. + 'expanded' => TRUE, + ); + + // Show a menu link in a menu other than the default "Navigation" menu. + // The menu must already exist. + $items['examples/menu_example_alternate_menu'] = array( + 'title' => 'Menu Example: Menu in alternate menu', + + // Machine name of the menu in which the link should appear. + 'menu_name' => 'primary-links', + + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(t('This will be in the Primary Links menu instead of the default Navigation menu')), + 'access callback' => TRUE, + ); + + // A menu entry with simple permissions using user_access(). + + // First, provide a courtesy menu item that mentions the existence of the + // permissioned item. + $items['examples/menu_example/permissioned'] = array( + 'title' => 'Permissioned Example', + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(t('A menu item that requires the "access protected menu example" permission is at examples/menu_example/permissioned/controlled', array('!link' => url('examples/menu_example/permissioned/controlled')))), + 'access callback' => TRUE, + 'expanded' => TRUE, + ); + + // Now provide the actual permissioned menu item. + $items['examples/menu_example/permissioned/controlled'] = array( + + // The title - do NOT use t() as t() is called automatically. + 'title' => 'Permissioned Menu Item', + 'description' => 'This menu entry will not appear and the page will not be accessible without the "access protected menu example" permission.', + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(t('This menu entry will not show and the page will not be accessible without the "access protected menu example" permission.')), + + // For a permissioned menu entry, we provide an access callback which + // determines whether the current user should have access. The default is + // user_access(), which we'll use in this case. Since it's the default, + // we don't even have to enter it. + // 'access callback' => 'user_access', + + // The 'access arguments' are passed to the 'access callback' to help it + // do its job. In the case of user_access(), we need to pass a permission + // as the first argument. + 'access arguments' => array('access protected menu example'), + + // The optional weight element tells how to order the submenu items. + // Higher weights are "heavier", dropping to the bottom of the menu. + 'weight' => 10, + ); + + /* + * We will define our own "access callback" function i.e is "menu_example_custom_access", + * rather than the default 'user_access'. + * + * The function takes a "role" of the user as an argument. + */ + $items['examples/menu_example/custom_access'] = array( + 'title' => 'Custom Access Example', + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(t('A menu item that requires the user to posess a role of "authenticated user" is at examples/menu_example/custom_access/page', array('!link' => url('examples/menu_example/custom_access/page')))), + 'access callback' => TRUE, + 'expanded' => TRUE, + 'weight' => -5, + ); + + $items['examples/menu_example/custom_access/page'] = array( + 'title' => 'Custom Access Menu Item', + 'description' => 'This menu entry will not show and the page will not be accessible without the user being an "authenticated user".', + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(t('This menu entry will not be visible and access will result in a 403 error unless the user has the "authenticated user" role. This is accomplished with a custom access callback.')), + 'access callback' => 'menu_example_custom_access', + 'access arguments' => array('authenticated user'), + ); + + // A menu router entry with no menu link. This could be used any time we + // don't want the user to see a link in the menu. Otherwise, it's the same + // as the "simplest" entry above. MENU_CALLBACK is used for all menu items + // which don't need a visible menu link, including services and other pages + // that may be linked to but are not intended to be accessed directly. + + // First, provide a courtesy link in the menu so people can find this. + $items['examples/menu_example/path_only'] = array( + 'title' => 'MENU_CALLBACK example', + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(t('A menu entry with no menu link (MENU_CALLBACK) is at !link', array('!link' => url('examples/menu_example/path_only/callback')))), + 'access callback' => TRUE, + 'weight' => 20, + ); + $items['examples/menu_example/path_only/callback'] = array( + + // A type of MENU_CALLBACK means leave the path completely out of the menu + // links. + 'type' => MENU_CALLBACK, + + // The title is still used for the page title, even though it's not used + // for the menu link text, since there's no menu link. + 'title' => 'Callback Only', + + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(t('The menu entry for this page is of type MENU_CALLBACK, so it provides only a path but not a link in the menu links, but it is the same in every other way to the simplest example.')), + 'access callback' => TRUE, + ); + + + // A menu entry with tabs. + // For tabs we need at least 3 things: + // 1. A parent MENU_NORMAL_ITEM menu item (examples/menu_example/tabs in this + // example.) + // 2. A primary tab (the one that is active when we land on the base menu). + // This tab is of type MENU_DEFAULT_LOCAL_TASK. + // 3. Some other menu entries for the other tabs, of type MENU_LOCAL_TASK. + $items['examples/menu_example/tabs'] = array( + // 'type' => MENU_NORMAL_ITEM, // Not necessary since this is the default. + 'title' => 'Tabs', + 'description' => 'Shows how to create primary and secondary tabs', + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(t('This is the "tabs" menu entry.')), + 'access callback' => TRUE, + 'weight' => 30, + ); + + // For the default local task, we need very little configuration, as the + // callback and other conditions are handled by the parent callback. + $items['examples/menu_example/tabs/default'] = array( + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'title' => 'Default primary tab', + 'weight' => 1, + ); + // Now add the rest of the tab entries. + foreach (array(t('second') => 2, t('third') => 3, t('fourth') => 4) as $tabname => $weight) { + $items["examples/menu_example/tabs/$tabname"] = array( + 'type' => MENU_LOCAL_TASK, + 'title' => $tabname, + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(t('This is the tab "@tabname" in the "basic tabs" example', array('@tabname' => $tabname))), + 'access callback' => TRUE, + + // The weight property overrides the default alphabetic ordering of menu + // entries, allowing us to get our tabs in the order we want. + 'weight' => $weight, + ); + } + + // Finally, we'll add secondary tabs to the default tab of the tabs entry. + + // The default local task needs very little information. + $items['examples/menu_example/tabs/default/first'] = array( + 'type' => MENU_DEFAULT_LOCAL_TASK, + 'title' => 'Default secondary tab', + // The additional page callback and related items are handled by the + // parent menu item. + ); + foreach (array(t('second'), t('third')) as $tabname) { + $items["examples/menu_example/tabs/default/$tabname"] = array( + 'type' => MENU_LOCAL_TASK, + 'title' => $tabname, + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(t('This is the secondary tab "@tabname" in the "basic tabs" example "default" tab', array('@tabname' => $tabname))), + 'access callback' => TRUE, + ); + } + + // All the portions of the URL after the base menu are passed to the page + // callback as separate arguments, and can be captured by the page callback + // in its argument list. Our _menu_example_menu_page() function captures + // arguments in its function signature and can output them. + $items['examples/menu_example/use_url_arguments'] = array( + 'title' => 'Extra Arguments', + 'description' => 'The page callback can use the arguments provided after the path used as key', + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(t('This page demonstrates using arguments in the path (portions of the path after "menu_example/url_arguments". For example, access it with !link1 or !link2).', array('!link1' => url('examples/menu_example/use_url_arguments/one/two'), '!link2' => url('examples/menu_example/use_url_arguments/firstarg/secondarg')))), + 'access callback' => TRUE, + 'weight' => 40, + ); + + // The menu title can be dynamically created by using the 'title callback' + // which by default is t(). Here we provide a title callback which adjusts + // the menu title based on the current user's username. + $items['examples/menu_example/title_callbacks'] = array( + 'title callback' => '_menu_example_simple_title_callback', + 'title arguments' => array(t('Dynamic title: username=')), + 'description' => 'The title of this menu item is dynamically generated', + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(t('The menu title is dynamically changed by the title callback')), + 'access callback' => TRUE, + 'weight' => 50, + ); + + // Sometimes we need to capture a specific argument within the menu path, + // as with the menu entry 'example/menu_example/placeholder_argument/3333/display', + // where we need to capture the "3333". In that case, we use a placeholder in + // the path provided in the menu entry. The (odd) way this is done is by using + // array(numeric_position_value) as the value for 'page arguments'. The + // numeric_position_value is the zero-based index of the portion of the URL + // which should be passed to the 'page callback'. + + // First we provide a courtesy link with information on how to access + // an item with a placeholder. + $items['examples/menu_example/placeholder_argument'] = array( + 'title' => 'Placeholder Arguments', + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(t('Demonstrate placeholders by visiting examples/menu_example/placeholder_argument/3343/display', array('!link' => url('examples/menu_example/placeholder_argument/3343/display')))), + 'access callback' => TRUE, + 'weight' => 60, + ); + + // Now the actual entry. + $items['examples/menu_example/placeholder_argument/%/display'] = array( + 'title' => 'Placeholder Arguments', + 'page callback' => '_menu_example_menu_page', + + // Pass the value of '%', which is zero-based argument 3, to the + // 'page callback'. So if the URL is + // 'examples/menu_example/placeholder_argument/333/display' then the value 333 + // will be passed into the 'page callback'. + 'page arguments' => array(3), + 'access callback' => TRUE, + ); + + // Drupal provides magic placeholder processing as well, so if the placeholder + // is '%menu_example_arg_optional', the function + // menu_example_arg_optional_load($arg) will be called to translate the path + // argument to a more substantial object. $arg will be the value of the + // placeholder. Then the return value of menu_example_id_load($arg) will be + // passed to the 'page callback'. + // In addition, if (in this case) menu_example_arg_optional_to_arg() exists, + // then a menu link can be created using the results of that function as a + // default for %menu_example_arg_optional. + $items['examples/menu_example/default_arg/%menu_example_arg_optional'] = array( + 'title' => 'Processed Placeholder Arguments', + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(3), // arg 3 (4rd arg) is the one we want. + 'access callback' => TRUE, + 'weight' => 70, + ); + + $items['examples/menu_example/menu_original_path'] = array( + 'title' => 'Menu path that will be altered by hook_menu_alter()', + 'page callback' => '_menu_example_menu_page', + 'page arguments' => array(t('This menu item was created strictly to allow the hook_menu_alter() function to have something to operate on. hook_menu defined the path as examples/menu_example/menu_original_path. The hook_menu_alter() changes it to examples/menu_example/menu_altered_path. You can try navigating to both paths and see what happens!')), + 'access callback' => TRUE, + 'weight' => 80, + ); + return $items; +} + +/** + * Page callback for the simplest introduction menu entry. + * + * @param $content + * Some content passed in. + */ +function _menu_example_basic_instructions($content = NULL) { + $base_content = t( + 'This is the base page of the Menu Example. There are a number of examples + here, from the most basic (like this one) to extravagant mappings of loaded + placeholder arguments. Enjoy!'); + return '
' . $base_content . '

' . $content . '
'; +} + +/** + * Page callback for use with most of the menu entries. The arguments it + * receives determine what it outputs. + * + * @param $content + * The base content to output. + * @param $arg1 + * First additional argument from the path used to access the menu + * @param $arg2 + * Second additional argument. + */ +function _menu_example_menu_page($content = NULL, $arg1 = NULL, $arg2 = NULL) { + $output = '
' . $content . '
'; + + if (!empty($arg1)) { + $output .= '
' . t('Argument 1=%arg', array('%arg' => $arg1)) . '
'; + } + if (!empty($arg2)) { + $output .= '
' . t('Argument 2=%arg', array('%arg' => $arg2)) . '
'; + } + return $output; +} + +/** + * Implements hook_permission() to provide a demonstration access string. + */ +function menu_example_permission() { + return array( + 'access protected menu example' => array( + 'title' => t('Access the protected menu example'), + ), + ); + +} + +/** + * Determine whether the current user has the role specified. + * + * @param $role_name + * The role required for access + * @return bool + * True if the acting user has the role specified. + */ +function menu_example_custom_access($role_name) { + $access_granted = in_array($role_name, $GLOBALS['user']->roles); + return $access_granted; +} + +/** + * Utility function to provide mappings from integers to some strings. + * This would normally be some database lookup to get an object or array from + * a key. + * + * @param $id + * + * @return + * The string to which the integer key mapped, or NULL if it did not map. + */ +function _menu_example_mappings($id) { + $mapped_value = NULL; + static $mappings = array( + 1 => 'one', + 2 => 'two', + 3 => 'three', + 99 => 'jackpot! default', + ); + if (isset($mappings[$id])) { + $mapped_value = $mappings[$id]; + } + return $mapped_value; +} + +/** + * The special _load function to load menu_example. + * + * Given an integer $id, load the string that should be associated with it. + * Normally this load function would return an array or object with more + * information. + * + * @param $id + * The integer to load. + * + * @return + * A string loaded from the integer. + */ +function menu_example_id_load($id) { + // Just map a magic value here. Normally this would load some more complex + // object from the database or other context. + $mapped_value = _menu_example_mappings($id); + if (!empty($mapped_value)) { + return t('Loaded value was %loaded', array('%loaded' => $mapped_value)); + } + else { + return t('Sorry, the id %id was not found to be loaded', array('%id' => $id)); + } +} + +/** + * Implements hook_menu_alter(). + * + * Changes the path 'examples/menu_example/menu_original_path' to 'examples/menu_example/menu_altered_path'. + * Changes the title callback of the 'user/UID' menu item. + * + * Remember that hook_menu_alter() only runs at menu_rebuild() time, not every + * time the page is built, so this typically happens only at cache clear time. + * + * @param $items + * The complete list of menu router items ready to be written to the + * menu_router table. + */ +function menu_example_menu_alter(&$items) { + // Change the path 'examples/menu_example/menu_original_path' to 'examples/menu_example/menu_altered_path'. This change will + // prevent the page from appearing at the original path (since the item is being unset). + // You will need to go to examples/menu_example/menu_altered_path manually to see the page. + if (!empty($items['examples/menu_example/menu_original_path'])) { + $items['examples/menu_example/menu_altered_path'] = $items['examples/menu_example/menu_original_path']; + $items['examples/menu_example/menu_altered_path']['title'] = 'Menu item altered by hook_menu_alter()'; + unset($items['examples/menu_example/menu_original_path']); + } + + // Here we will change the title callback to our own function, changing the + // 'user' link from the traditional to always being "username's account". + if (!empty($items['user/%user'])) { + $items['user/%user']['title callback'] = 'menu_example_user_page_title'; + } +} + +/** + * Title callback to rewrite the '/user' menu link. + * + * @param $base_string + * string to be prepended to current user's name. + */ +function _menu_example_simple_title_callback($base_string) { + global $user; + $username = !empty($user->name) ? $user->name : t('anonymous'); + return $base_string . ' ' . $username; +} +/** + * Title callback to rename the title dynamically, based on user_page_title(). + * + * @param $account + * User account related to the visited page. + */ +function menu_example_user_page_title($account) { + return is_object($account) ? t("@name's account", array('@name' => format_username($account))) : ''; +} + +/** + * Implements hook_menu_link_alter(). + * + * This code will get the chance to alter a menu link when it is being saved + * in the menu interface at admin/build/menu. Whatever we do here overrides + * anything the user/administrator might have been trying to do. + * + * @param $item + * The menu item being saved. + * @param $menu + * The entire menu router table. + */ +function menu_example_menu_link_alter(&$item, $menu) { + // Force the link title to remain 'Clear Cache' no matter what the admin + // does with the web interface. + if ($item['link_path'] == 'devel/cache/clear') { + $item['link_title'] = 'Clear Cache'; + }; +} + +/** + * Loads an item based on its $id. + * + * In this case we're just creating a more extensive string. In a real example + * we would load or create some type of object. + * + * @param $id + */ +function menu_example_arg_optional_load($id) { + $mapped_value = _menu_example_mappings($id); + if (!empty($mapped_value)) { + return t('Loaded value was %loaded', array('%loaded' => $mapped_value)); + } + else { + return t('Sorry, the id %id was not found to be loaded', array('%id' => $id)); + } +} + +/** + * A to_arg() function is used to provide a default for the arg in the + * wildcard. The purpose is to provide a menu link that will function if no + * argument is given. For example, in the case of the menu item + * 'examples/menu_example/default_arg/%menu_example_arg_optional' the third argument + * is required, and the menu system cannot make a menu link using this path + * since it contains a placeholder. However, when the to_arg() function is + * provided, the menu system will create a menu link pointing to the path + * which would be created with the to_arg() function filling in the + * %menu_example_arg_optional. + * + * @param $arg + * The arg (URL fragment) to be tested. + */ +function menu_example_arg_optional_to_arg($arg) { + // If our argument is not provided, give a default of 99. + return (empty($arg) || $arg == '%') ? 99 : $arg; +} +/** + * @} End of "defgroup menu_example". + */ -- 1.7.10.4