diff --git a/menu_example/menu_example.links.menu.yml b/menu_example/menu_example.links.menu.yml index 4c509c6..785af62 100644 --- a/menu_example/menu_example.links.menu.yml +++ b/menu_example/menu_example.links.menu.yml @@ -1,3 +1,18 @@ +# This file links a menu link with route_name. +# +# 'title' key is only key required. +# +# 'description' is is shown either as a tooltip on the item or in the admin UI +# as the description of the option on the page/ +# +# 'weight' is used to order the items (higher weights get placed towards the +# end of the menu among items on the same level). +# +# 'route_name' is used to link menu link to corresponding route. +# +# 'parent' is used to put item into the menu hierarchy by referring to the +# parent menu link name. +# examples.menu_example: title: 'Menu Example' description: 'Simplest possible menu type, and the parent menu entry for others' @@ -6,6 +21,7 @@ examples.menu_example: examples.menu_example.alternate_menu: title: 'Menu Example: Menu in alternate menu' + #If menu_name is omitted, the "Tools" menu will be used. menu_name: 'main' route_name: examples.menu_example.alternate_menu diff --git a/menu_example/menu_example.links.task.yml b/menu_example/menu_example.links.task.yml index c1e58a1..b50df55 100644 --- a/menu_example/menu_example.links.task.yml +++ b/menu_example/menu_example.links.task.yml @@ -1,41 +1,53 @@ +# This file creates static local tasks (Tabs). +# This file will be needed to place in module root. +# +# 'title' of the tab will show up on the user interface and tab. +# +# 'base_route' is the same as the name of the route where the "default" tab +# appears. The base_route is used to group together related tabs. +# +# 'weight' is used to provide weights for the tabs if needed. +# The tab whose route is the same as the base_route will by default +# get a negative weight and appear on the left. +# +# 'parent_id' is used to create multi level of tabs. +# To relate a tab to its parent use same name as parent_id as shown below in +# examples.menu_example.tabs.secondary. +# examples.menu_example.tabs: route_name: examples.menu_example.tabs - title: 'Default Primary Tab' + title: Default primary tab base_route: examples.menu_example.tabs - weight: 1 -examples.menu_example.tabOne: - route_name: examples.menu_example.tabname - title: 'two' +examples.menu_example.tabs_second: + route_name: examples.menu_example.tabs_second + title: Second base_route: examples.menu_example.tabs weight: 2 -examples.menu_example.tabTwo: - route_name: examples.menu_example.tabnameThree - title: 'three' +examples.menu_example.tabs_third: + route_name: examples.menu_example.tabs_third + title: Third base_route: examples.menu_example.tabs weight: 3 -examples.menu_example.tabThree: - route_name: examples.menu_example.tabnameFour - title: 'four' +examples.menu_example.tabs_fourth: + route_name: examples.menu_example.tabs_fourth + title: Fourth base_route: examples.menu_example.tabs weight: 4 -examples.menu_example.secondaryDefault: +examples.menu_example.tabs.secondary: route_name: examples.menu_example.tabs - title: 'Default Secondary Tab' + title: Default secondary tab parent_id: examples.menu_example.tabs - weight: 1 -examples.menu_example.secondaryDefaultOne: - route_name: examples.menu_example.defaultSecond - title: 'Second Secondary Tab' +examples.menu_example.tabs_default_second: + route_name: examples.menu_example.tabs_default_second + title: Second parent_id: examples.menu_example.tabs - weight: 2 -examples.menu_example.secondaryDefaultTwo: - route_name: examples.menu_example.defaultThird - title: 'Third Secondary Tab' +examples.menu_example.tabs_default_third: + route_name: examples.menu_example.tabs_default_third + title: Third parent_id: examples.menu_example.tabs - weight: 3 diff --git a/menu_example/menu_example.module b/menu_example/menu_example.module index 6b639f1..0521971 100644 --- a/menu_example/menu_example.module +++ b/menu_example/menu_example.module @@ -4,3 +4,40 @@ * @file * Module file for menu_example_module. */ + +/** + * Drupal 7's hook_menu() is now Routing system in Drupal 8. + * + * Drupal 8's routing system is based on Symfony's routing system. + * + * A route is an entry Drupal 8's router table which contains a path attached + * to it. In drupal, a route is a path which is returns some response. + * + * When a path is requested, Drupal matches the request to a route + * and if route is match the content is returned else 404 is returned. + * + * The association of a path with a controller, coupled with parameter + * upcasting and access checking is now handled in the routing system. + * + * @link menu_example.routing.yml @endlink + * Drupal loads the routing file that is of the form module_name.routing.yml + * that it will use to define how Drupal will behave when a specific path + * is encountered. If you want to expose content or functionality on your own + * URIs on a site, routing file is an important part of your module. + * This can help provide functionality at specific URIs of a website or just + * modify or augment existing functionality. + * @see https://www.drupal.org/docs/8/api/routing-system/structure-of-routes + * + * @link menu_example.links.menu.yml @endlink + * In Drupal 8, menu links are specified in a static file named as + * module_name.links.menu.yml. This file uses a route_name to tie a route name + * to a menu link. These are the links which appear on tools block in UI. + * @see https://www.drupal.org/docs/8/api/menu-api/providing-module-defined-menu-links + * + * @link menu_example.links.task.yml @endlink + * To create menu tab entries (only up to 2 level), we make entries in a file + * named as module_name.links.task.yml. These are mostly used on administrative + * pages but frontend pages like user pages or the registration/login/new + * password pages also use local tasks. + * @see https://www.drupal.org/docs/8/api/menu-api/providing-module-defined-local-tasks @endlink + */ diff --git a/menu_example/menu_example.routing.yml b/menu_example/menu_example.routing.yml index 888bc06..3c29455 100644 --- a/menu_example/menu_example.routing.yml +++ b/menu_example/menu_example.routing.yml @@ -1,4 +1,13 @@ -examples.menu_example: +# This file contains routing entries which is then read by Drupal to match +# a particular request which a corresponding path. A particular route entry +# is bound to the URI defined using 'path' key, the 'access content' permission +# is checked on the accessing user and, if access is granted, the +# MenuExampleController::{methodName} method is invoked and a page is displayed +# with defined response. +# '_controller' defines the controller class and it's method which Drupal's +# autoload mechanism looks for, creating the response from. +# +examples.menu_example: # This is a route name. path: '/examples/menu-example' defaults: _controller: '\Drupal\menu_example\Controller\MenuExampleController::description' @@ -6,6 +15,9 @@ examples.menu_example: requirements: _permission: 'access content' +# This menu entry will appear as main menu tab instead of tools menu. +# In menu_example.links.menu.yml we have used 'menu_name' as 'main' which links +# the menu entry to main navigation menu. examples.menu_example.alternate_menu: path: '/examples/menu-example-alternate-menu' defaults: @@ -14,6 +26,8 @@ examples.menu_example.alternate_menu: requirements: _access: 'TRUE' +# The menu entry demonstrates the use of permissions to restrict access +# to a particular path until and unless the requirement is met. examples.menu_example.permissioned: path: '/examples/menu-example/permissioned' defaults: @@ -22,6 +36,7 @@ examples.menu_example.permissioned: requirements: _access: 'TRUE' +# Only the user with the required permission will be able to access this route. examples.menu_example.permissioned_controlled: path: '/examples/menu-example/permissioned/controlled' defaults: @@ -30,6 +45,8 @@ examples.menu_example.permissioned_controlled: requirements: _permission: 'access protected menu example' +# Sometimes just relying on permissions and/or roles is not sufficient +# and you need to do custom access checking on routes. examples.menu_example.custom_access: path: '/examples/menu-example/custom-access' defaults: @@ -38,6 +55,8 @@ examples.menu_example.custom_access: requirements: _permission: 'access content' +# Custom access is called once this route is requested. +# Check src/Access/RoleAccessCheck.php examples.menu_example.custom_access_page: path: '/examples/menu-example/custom-access/page' defaults: @@ -46,6 +65,7 @@ examples.menu_example.custom_access_page: requirements: _menu_example_role: 'TRUE' +# This showcases how you define only a with no menu link entry. examples.menu_example.route_only: path: '/examples/menu-example/route-only' defaults: @@ -54,6 +74,7 @@ examples.menu_example.route_only: requirements: _access: 'TRUE' +# These type of routes can be used to create webservices. examples.menu_example.callback: path: '/examples/menu-example/route-only/callback' defaults: @@ -62,71 +83,24 @@ examples.menu_example.callback: requirements: _access: 'TRUE' -examples.menu_example.tabs: - path: 'examples/menu-example/tabs' - defaults: - _controller: '\Drupal\menu_example\Controller\MenuExampleController::tabs' - _title: 'Tabs' - requirements: - _access: 'TRUE' - -examples.menu_example.default: - path: 'examples/menu-example/tabs/default' - deafaults: - _title: 'Tabs' - requirements: - _access: 'TRUE' - -examples.menu_example.tabname: - path: 'examples/menu-example/tabs/second' - defaults: - _title: 'Tabs' - _controller: '\Drupal\menu_example\Controller\MenuExampleController::tabNameSecond' - requirements: - _access: 'TRUE' - -examples.menu_example.tabnameThree: - path: 'examples/menu-example/tabs/third' - defaults: - _title: 'Tabs' - _controller: '\Drupal\menu_example\Controller\MenuExampleController::tabNameThird' - requirements: - _access: 'TRUE' - -examples.menu_example.tabnameFour: - path: 'examples/menu-example/tabs/forth' - defaults: - _title: 'Tabs' - _controller: '\Drupal\menu_example\Controller\MenuExampleController::tabNameFourth' - requirements: - _access: 'TRUE' - -examples.menu_example.defaultSecond: - path: 'examples/menu-example/tabs/default/Second' - defaults: - _title: 'Tabs' - _controller: '\Drupal\menu_example\Controller\MenuExampleController::defaultSecond' - requirements: - _access: 'TRUE' - -examples.menu_example.defaultThird: - path: 'examples/menu-example/tabs/default/third' - defaults: - _title: 'Tabs' - _controller: '\Drupal\menu_example\Controller\MenuExampleController::defaultThird' - requirements: - _access: 'TRUE' - +# Drupal 8's routes may include placeholder elements which designate places +# where the URL contains dynamic values. In the controller method, this value +# is available when a variable with the same name is used in the controller +# method. Matching the variable name is must. The {arg} element in the URL is +# called a slug and is available as a $arg in the controller method. examples.menu_example.use_url_arguments: path: 'examples/menu-example/use-url-arguments/{arg1}/{arg2}' defaults: _title: 'URL Arguments' _controller: '\Drupal\menu_example\Controller\MenuExampleController::urlArgument' + # We provide default value to both arguments. arg1: '' arg2: '' requirements: _access: 'TRUE' +# Title of this menu entry will be altered by controller method defined by +# key '_title_callback'. examples.menu_example.title_callbacks: path: 'examples/menu-example/title-callbacks' defaults: @@ -135,6 +109,7 @@ examples.menu_example.title_callbacks: requirements: _access: 'TRUE' +# A menu item entry to demonstrate use of parameters in routes. examples.menu_example.placeholder_argument: path: 'examples/menu-example/placeholder-argument' defaults: @@ -143,6 +118,7 @@ examples.menu_example.placeholder_argument: requirements: _access: 'TRUE' +# Parameters in routes can be used as placeholders arguments too. examples.menu_example.placeholder_argument.display: path: 'examples/menu-example/placeholder-argument/{arg}/display' defaults: @@ -152,6 +128,7 @@ examples.menu_example.placeholder_argument.display: arg: \d+ _access: 'TRUE' +# Any route defined here can be altered. We can do so by defining a RouteSubscriber! example.menu_example.path_override: path: 'examples/menu-example/menu-original-path' defaults: @@ -159,3 +136,8 @@ example.menu_example.path_override: _controller: '\Drupal\menu_example\Controller\MenuExampleController::pathOverride' requirements: _access: 'TRUE' + +# Dynamic routes are implemented in a method. This method is added as a +# 'route_callbacks' +route_callbacks: + - '\Drupal\menu_example\Routing\MenuExampleDynamicRoutes::routes' diff --git a/menu_example/menu_example.services.yml b/menu_example/menu_example.services.yml index f2e934e..0549543 100644 --- a/menu_example/menu_example.services.yml +++ b/menu_example/menu_example.services.yml @@ -4,6 +4,8 @@ services: arguments: ['@current_user'] tags: - { name: access_check, applies_to: _menu_example_role } + # _menu_example_role flaf is added to requirements section in + # menu_example.routing.yml to apply custom check routes. menu_example.route_subscriber: class: Drupal\menu_example\Routing\RouteSubscriber diff --git a/menu_example/src/Access/RoleAccessCheck.php b/menu_example/src/Access/RoleAccessCheck.php index aae54b8..22578c0 100644 --- a/menu_example/src/Access/RoleAccessCheck.php +++ b/menu_example/src/Access/RoleAccessCheck.php @@ -8,6 +8,17 @@ use Drupal\Core\Session\AccountInterface; /** * Determines access to routes based on roles. + * + * To achieve this, we implement a class with AccessInterface and use that to + * check access. + * + * Our module is called menu_example, this file will be placed under + * menu_example/src/Access/CustomAccessCheck.php. + * + * The @link menu_example_services.yml @endlink contains entry for this service + * class. + * + * @see https://www.drupal.org/docs/8/api/routing-system/access-checking-on-routes */ class RoleAccessCheck implements AccessInterface { @@ -21,6 +32,7 @@ class RoleAccessCheck implements AccessInterface { * A \Drupal\Core\Access\AccessInterface constant value. */ public function access(AccountInterface $account) { + // If the user is authenticated, return TRUE. return AccessResult::allowedIf($account->isAuthenticated()); } diff --git a/menu_example/src/Controller/MenuExampleController.php b/menu_example/src/Controller/MenuExampleController.php index ef95755..e35c62c 100644 --- a/menu_example/src/Controller/MenuExampleController.php +++ b/menu_example/src/Controller/MenuExampleController.php @@ -8,6 +8,18 @@ use Drupal\Core\Url; use Drupal\examples\Utility\DescriptionTemplateTrait; /** * Controller routines for menu example routes. + * + * The response of Drupal's HTTP Kernel system's request is generated by + * a piece of code called the controller. + * + * In Drupal 8, we use a controller class + * for placing those piece of codes in methods which responds to a route. + * + * This file will be placed at {module_name}/src/Controller directory. Route + * entries uses a key '_controller' to define the method called from controller + * class. + * + * @see https://www.drupal.org/docs/8/api/routing-system/introductory-drupal-8-routes-and-controllers-example */ class MenuExampleController extends ControllerBase { @@ -72,6 +84,8 @@ class MenuExampleController extends ControllerBase { * Demonstrates the use of custom access check in routes. * * @throws \InvalidArgumentException + + * @see \Drupal\menu_example\Controller\MenuExampleController::customAccessPage() */ public function customAccess() { $url = Url::fromUri('internal:/examples/menu-example/custom-access/page'); @@ -83,6 +97,8 @@ class MenuExampleController extends ControllerBase { /** * Content will be displayed only if access check is satisfied. + * + * @see \Drupal\menu_example\Controller\MenuExampleController::customAccess() */ public function customAccessPage() { return [ @@ -114,65 +130,35 @@ class MenuExampleController extends ControllerBase { ]; } - - /** - * A simple menu entry to display tabs. - */ - public function tabs() { - return [ - '#markup' => $this->t('This is the "tabs" menu entry.'), - ]; - } - - /** - * Callback for returning second tab. - */ - public function tabNameSecond() { - return [ - '#markup' => $this->t('This is the tab "Second" in the "basic tabs" example'), - ]; - } - - /** - * Callback for returning third tab. - */ - public function tabNameThird() { - return [ - '#markup' => $this->t('This is the tab "Thrid" in the "basic tabs" example'), - ]; - } - - /** - * Callback for returning fourth tab. - */ - public function tabNameFourth() { - return [ - '#markup' => $this->t('This is the tab "Fourth" in the "basic tabs" example'), - ]; - } - - /** - * Callback for second secondary tab. - */ - public function defaultSecond() { - return [ - '#markup' => $this->t('This is the secondary tab second in the "basic tabs" example "default"tab'), - ]; - } - /** - * Callback for third secondary tab. + * @param string $path + * Path/URL of menu item. + * + * @param string $title + * Title of menu item. + * + * @return array + * Controller response. */ - public function defaultThird() { - return [ - '#markup' => $this->t('This is the secondary tab Third in the "basic tabs" example "default"tab'), - ]; + public function tabsPage($path, $title) { + $secondary = substr_count($path, '/') > 2 ? 'secondary ' : ''; + return array( + '#markup' => $this->t('This is the @secondary tab "@tabname" in the "basic tabs" example.', ['@secondary' => $secondary, '@tabname' => $title]), + ); } /** * Demonstrates use of url arguments in for menu item. * - * @throws \InvalidArgumentException + * @param string $arg1 + * First argument of url. + * @param string $arg2 + * Second argument of url. + * + * @return array + * Controller response. + * + * @see https://www.drupal.org/docs/8/api/routing-system/parameters-in-routes */ public function urlArgument($arg1, $arg2) { // Perpare link for single arguments. @@ -200,15 +186,19 @@ class MenuExampleController extends ControllerBase { /** * Demonstrate generation of dynamic creation of page title. + * + * @see \Drupal\menu_example\Controller\MenuExampleController::backTitle() */ public function titleCallback() { return [ - '#markup' => $this->t('The title of this page is dynamically changed by the title callback for this route. Also, the title of the menu link is dynamically changed by implementing hook_menu_link_presave() and hook_translated_menu_link_alter().'), + '#markup' => $this->t('The title of this page is dynamically changed by the title callback for this route defined in menu_example.routing.yml.'), ]; } /** * Generates title dynamically. + * + * @see \Drupal\menu_example\Controller\MenuExampleController::titleCallback() */ public function backTitle() { return \Drupal::currentUser()->getDisplayName(); @@ -218,6 +208,9 @@ class MenuExampleController extends ControllerBase { * Demonstrates how you can provide a placeholder url arguments. * * @throws \InvalidArgumentException + * + * @see \Drupal\menu_example\Controller\MenuExampleController::placeholderArgsDisplay() + * @see https://www.drupal.org/docs/8/api/routing-system/using-parameters-in-routes */ public function placeholderArgs() { $url = Url::fromUri('internal:/examples/menu-example/placeholder-argument/3343/display'); @@ -235,6 +228,8 @@ class MenuExampleController extends ControllerBase { * * @return array * URL argument. + * + * @see \Drupal\menu_example\Controller\MenuExampleController::placeholderArgs() */ public function placeholderArgsDisplay($arg) { return [ diff --git a/menu_example/src/Routing/MenuExampleDynamicRoutes.php b/menu_example/src/Routing/MenuExampleDynamicRoutes.php index e69de29..ca75524 100644 --- a/menu_example/src/Routing/MenuExampleDynamicRoutes.php +++ b/menu_example/src/Routing/MenuExampleDynamicRoutes.php @@ -0,0 +1,54 @@ + 'Default primary tab', + 'tabs/second' => 'Second', + 'tabs/third' => 'Third', + 'tabs/fourth' => 'Fourth', + 'tabs/default/second' => 'Second', + 'tabs/default/third' => 'Third', + ]; + + foreach ($tabs as $path => $title) { + $machine_name = 'examples.menu_example.'. str_replace('/', '_', $path); + $routes[$machine_name] = new Route( + // Path to attach this route to: + '/examples/menu-example/' . $path, + // Route defaults: + [ + '_controller' => '\Drupal\menu_example\Controller\MenuExampleController::tabsPage', + '_title' => $title, + 'path' => $path, + 'title' => $title, + ], + // Route requirements: + [ + '_access' => 'TRUE', + ] + ); + } + + return $routes; + } + +} diff --git a/menu_example/src/Routing/RouteSubscriber.php b/menu_example/src/Routing/RouteSubscriber.php index a6e198c..6f78dc6 100644 --- a/menu_example/src/Routing/RouteSubscriber.php +++ b/menu_example/src/Routing/RouteSubscriber.php @@ -7,8 +7,16 @@ use Symfony\Component\Routing\RouteCollection; /** * Listens to the dynamic route events. + * + * The \Drupal\Core\Routing\RouteSubscriberBase class contains an event + * listener that listens to this event. We alter existing routes by + * implementing the alterRoutes(RouteCollection $collection) method of + * this class. + * + * @see https://www.drupal.org/docs/8/api/routing-system/altering-existing-routes-and-adding-new-routes-based-on-dynamic-ones */ class RouteSubscriber extends RouteSubscriberBase { + /** * {@inheritdoc} */ diff --git a/menu_example/templates/description.html.twig b/menu_example/templates/description.html.twig index a7cd0ec..fc46589 100644 --- a/menu_example/templates/description.html.twig +++ b/menu_example/templates/description.html.twig @@ -8,7 +8,10 @@ Description text for the Block Example. {% trans %} -

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. -There are a number of examples here, from the most basic (like this one) to extravagant mappings of loaded placeholder arguments. Enjoy!

+

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. +There are a number of examples here, from the most basic (like this one) to +extravagant mappings of loaded placeholder arguments. Enjoy!

{% endtrans %} diff --git a/menu_example/tests/src/Functional/MenuExampleTest.php b/menu_example/tests/src/Functional/MenuExampleTest.php index 4f75191..ce070e3 100644 --- a/menu_example/tests/src/Functional/MenuExampleTest.php +++ b/menu_example/tests/src/Functional/MenuExampleTest.php @@ -3,12 +3,13 @@ namespace Drupal\Tests\menu_example\Functional; use Drupal\Tests\BrowserTestBase; + /** * Test the functionality for the menu Example. * - * @ingroup menu-example + * @ingroup menu_example * - * @group menu-example + * @group menu_example * @group examples */ class MenuExampleTest extends BrowserTestBase {