diff --git a/examples.module b/examples.module index 96cb44f..1fbcd6b 100644 --- a/examples.module +++ b/examples.module @@ -51,6 +51,7 @@ function examples_toolbar() { 'file_example' => 'file_example.fileapi', 'hooks_example' => 'hooks_example.description', 'js_example' => 'js_example.info', + 'menu_example' => 'examples.menu_example', 'node_type_example' => 'config_node_type_example.description', 'page_example' => 'page_example_description', 'pager_example' => 'pager_example.page', diff --git a/menu_example/menu_example.routing.yml b/menu_example/menu_example.routing.yml index 300925f..59f30c2 100644 --- a/menu_example/menu_example.routing.yml +++ b/menu_example/menu_example.routing.yml @@ -65,7 +65,8 @@ examples.menu_example.custom_access_page: requirements: _menu_example_role: 'TRUE' -# This showcases how you define only a with no menu link entry. +# This route has a corresponding menu entry, so the user can click on it in the +# UI. This gives them a link to a route with no menu entry. examples.menu_example.route_only: path: '/examples/menu-example/route-only' defaults: @@ -75,7 +76,7 @@ examples.menu_example.route_only: _permission: 'access content' # These type of routes can be used to create webservices. -examples.menu_example.callback: +examples.menu_example.route_only.callback: path: '/examples/menu-example/route-only/callback' defaults: _controller: '\Drupal\menu_example\Controller\MenuExampleController::routeOnlyCallback' @@ -104,8 +105,8 @@ examples.menu_example.use_url_arguments: examples.menu_example.title_callbacks: path: 'examples/menu-example/title-callbacks' defaults: - _title_callback: 'Drupal\menu_example\Controller\MenuExampleController::backTitle' - _controller: '\Drupal\menu_example\Controller\MenuExampleController::titleCallback' + _title_callback: 'Drupal\menu_example\Controller\MenuExampleController::titleCallback' + _controller: '\Drupal\menu_example\Controller\MenuExampleController::titleCallbackContent' requirements: _permission: 'access content' diff --git a/menu_example/src/Controller/MenuExampleController.php b/menu_example/src/Controller/MenuExampleController.php index 55e938b..789bb48 100644 --- a/menu_example/src/Controller/MenuExampleController.php +++ b/menu_example/src/Controller/MenuExampleController.php @@ -4,6 +4,7 @@ namespace Drupal\menu_example\Controller; use Drupal\Core\Controller\ControllerBase; use Drupal\Core\Link; +use Drupal\Core\Url; use Drupal\examples\Utility\DescriptionTemplateTrait; /** @@ -62,10 +63,11 @@ class MenuExampleController extends ControllerBase { * @throws \InvalidArgumentException */ public function permissioned() { - $link = Link::createFromRoute($this->t('examples/menu-example/permissioned/controlled'), - 'examples.menu_example.permissioned_controlled')->toString(); + $url = Url::fromRoute('examples.menu_example.permissioned_controlled'); return [ - '#markup' => $this->t('A menu item that requires the "access protected menu example" permission is at @link', ['@link' => $link]), + '#markup' => $this->t('A menu item that requires the "access protected menu example" permission is at @link', [ + '@link' => Link::createFromRoute($url->getInternalPath(), $url->getRouteName())->toString(), + ]), ]; } @@ -88,10 +90,11 @@ class MenuExampleController extends ControllerBase { * @see \Drupal\menu_example\Controller\MenuExampleController::customAccessPage() */ public function customAccess() { - $link = Link::createFromRoute($this->t('examples/menu-example/custom-access/page'), - 'examples.menu_example.custom_access_page')->toString(); + $url = Url::fromRoute('examples.menu_example.custom_access_page'); return [ - '#markup' => $this->t('A menu item that requires the user to posess a role of "authenticated" is at @link', ['@link' => $link]), + '#markup' => $this->t('A menu item that requires the user to posess a role of "authenticated" is at @link', [ + '@link' => Link::createFromRoute($url->getInternalPath(), $url->getRouteName())->toString(), + ]), ]; } @@ -109,15 +112,16 @@ class MenuExampleController extends ControllerBase { } /** - * Only callback without a menu link. + * Give the user a link to the route-only page. * * @throws \InvalidArgumentException */ public function routeOnly() { - $link = Link::createFromRoute($this->t('examples/menu-example/route-only/callback'), - 'examples.menu_example.callback')->toString(); + $url = Url::fromRoute('examples.menu_example.route_only.callback'); return [ - '#markup' => $this->t('A menu entry with no menu link is at @link', ['@link' => $link]), + '#markup' => $this->t('A menu entry with no menu link is at @link', [ + '@link' => Link::createFromRoute($url->getInternalPath(), $url->getRouteName())->toString(), + ]), ]; } @@ -131,14 +135,20 @@ class MenuExampleController extends ControllerBase { } /** + * Uses the path and title to determine the page content. + * + * This controller is mapped dynamically based on the 'route_callbacks:' key + * in the routing YAML file. + * * @param string $path * Path/URL of menu item. - * * @param string $title * Title of menu item. * * @return array * Controller response. + * + * @see Drupal\menu_example\Routing\MenuExampleDynamicRoutes */ public function tabsPage($path, $title) { $secondary = substr_count($path, '/') > 2 ? 'secondary ' : ''; @@ -148,12 +158,12 @@ class MenuExampleController extends ControllerBase { } /** - * Demonstrates use of url arguments in for menu item. + * Demonstrates use of optional URL arguments in for menu item. * * @param string $arg1 - * First argument of url. + * First argument of URL. * @param string $arg2 - * Second argument of url. + * Second argument of URL. * * @return array * Controller response. @@ -161,16 +171,19 @@ class MenuExampleController extends ControllerBase { * @see https://www.drupal.org/docs/8/api/routing-system/parameters-in-routes */ public function urlArgument($arg1, $arg2) { - // Perpare link for single arguments. - $link = Link::createFromRoute($this->t('examples/menu-example/use-url-arguments/one'), - 'examples.menu_example.use_url_arguments', ['arg1' => 'one'])->toString(); + // Perpare URL for single arguments. + $url_single = Url::fromRoute('examples.menu_example.use_url_arguments', ['arg1' => 'one']); - // Prepare link for multiple arguments. - $linktwo = Link::createFromRoute($this->t('examples/menu-example/use-url-arguments/one/two'), - 'examples.menu_example.use_url_arguments', ['arg1' => 'one', 'arg2' => 'two'])->toString(); + // Prepare URL for multiple arguments. + $url_double = Url::fromRoute('examples.menu_example.use_url_arguments', ['arg1' => 'one', 'arg2' => 'two']); - $markup = $this->t('This page demonstrates using arguments in the url. For example, access it with @link for single argument or @linktwo for two arguments in URL', ['@link' => $link, '@linktwo' => $linktwo]); + // Add these argument links to the page content. + $markup = $this->t('This page demonstrates using arguments in the url. For example, access it with @link_single for single argument or @link_double for two arguments in URL', [ + '@link_single' => Link::createFromRoute($url_single->getInternalPath(), $url_single->getRouteName(), $url_single->getRouteParameters())->toString(), + '@link_double' => Link::createFromRoute($url_double->getInternalPath(), $url_double->getRouteName(), $url_double->getRouteParameters())->toString(), + ]); + // Process the arguments if they're provided. if (!empty($arg1)) { $markup .= '
' . $this->t('Argument 1 = @arg', ['@arg' => $arg1]) . '
'; } @@ -178,9 +191,9 @@ class MenuExampleController extends ControllerBase { $markup .= '
' . $this->t('Argument 2 = @arg', ['@arg' => $arg2]) . '
'; } + // Finally return the markup. return [ '#markup' => $markup, - ]; } @@ -189,7 +202,7 @@ class MenuExampleController extends ControllerBase { * * @see \Drupal\menu_example\Controller\MenuExampleController::backTitle() */ - public function titleCallback() { + public function titleCallbackContent() { return [ '#markup' => $this->t('The title of this page is dynamically changed by the title callback for this route defined in menu_example.routing.yml.'), ]; @@ -200,10 +213,11 @@ class MenuExampleController extends ControllerBase { * * @see \Drupal\menu_example\Controller\MenuExampleController::titleCallback() */ - public function backTitle() { - $name = \Drupal::currentUser()->getDisplayName(); + public function titleCallback() { return [ - '#markup' => $this->t('The new title is your username: @name', ['@name' => $name]), + '#markup' => $this->t('The new title is your username: @name', [ + '@name' => $this->currentUser()->getDisplayName(), + ]), ]; } @@ -216,10 +230,11 @@ class MenuExampleController extends ControllerBase { * @see https://www.drupal.org/docs/8/api/routing-system/using-parameters-in-routes */ public function placeholderArgs() { - $link = Link::createFromRoute($this->t('examples/menu-example/placeholder-argument/3343/display'), - 'examples.menu_example.placeholder_argument.display', ['arg' => 3343])->toString(); + $url = Url::fromRoute('examples.menu_example.placeholder_argument.display', ['arg' => 3343]); return [ - '#markup' => $this->t('Demonstrate placeholders by visiting @link', ['@link' => $link]), + '#markup' => $this->t('Demonstrate placeholders by visiting @link', [ + '@link' => Link::createFromRoute($url->getInternalPath(), $url->getRouteName(), $url->getRouteParameters())->toString(), + ]), ]; } diff --git a/menu_example/src/Routing/MenuExampleDynamicRoutes.php b/menu_example/src/Routing/MenuExampleDynamicRoutes.php index ca75524..6ee88a3 100644 --- a/menu_example/src/Routing/MenuExampleDynamicRoutes.php +++ b/menu_example/src/Routing/MenuExampleDynamicRoutes.php @@ -5,8 +5,11 @@ namespace Drupal\menu_example\Routing; use Symfony\Component\Routing\Route; /** - * Defines dynamic routes for our tabs menu items. + * Defines dynamic routes for our tab menu items. * + * These routes support the links created in menu_example.links.task.yml. + * + * @see menu_example.links.task.yml * @see https://www.drupal.org/docs/8/api/routing-system/providing-dynamic-routes */ class MenuExampleDynamicRoutes { diff --git a/menu_example/tests/src/Functional/MenuExampleTest.php b/menu_example/tests/src/Functional/MenuExampleTest.php index acfc49e..b0fccc6 100644 --- a/menu_example/tests/src/Functional/MenuExampleTest.php +++ b/menu_example/tests/src/Functional/MenuExampleTest.php @@ -3,7 +3,6 @@ namespace Drupal\Tests\menu_example\Functional; use Drupal\Tests\BrowserTestBase; -use Drupal\user\Entity\Role; use Drupal\Core\Url; /** @@ -22,87 +21,90 @@ class MenuExampleTest extends BrowserTestBase { * @var array */ public static $modules = ['menu_example']; + /** * The installation profile to use with this test. * + * We use 'minimal' because we want the tools menu to be available. + * * @var string */ protected $profile = 'minimal'; /** - * Runs different tests for menu example. + * {@inheritdoc} */ - public function testMenuExample() { - $this->assertSession()->pageTextContains('Menu Example'); - $this->clickLink(t('Menu Example')); - $this->assertSession()->pageTextContains(t('This page is displayed by the simplest (and base) menu example')); - - // Create a user with permissions to access protected menu entry. - $web_user = $this->drupalCreateUser(); - // 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. - $this->assertSession()->pageTextContains(t("@name", ['@name' => $web_user->getUsername()])); - - // Now start testing other menu entries. - $this->drupalGet(Url::fromRoute('examples.menu_example')); - $this->clickLink(t('Custom Access Example')); - $this->assertSession()->pageTextContains(t('Custom Access Example')); - $this->drupalGet(Url::fromRoute('examples.menu_example.custom_access_page')); - $this->assertSession()->statusCodeEquals(200); - // Logout user to check denial of access. - $this->drupalLogout(); - $this->drupalGet(Url::fromRoute('examples.menu_example.custom_access_page')); - $this->assertSession()->statusCodeEquals(403); + protected function setUp() { + // Always call the parent setUp(). + parent::setUp(); + // Add the main menu block, as provided by the Block module. + $this->placeBlock('system_menu_block:main'); + } - // Check 'Permission Example' menu entry. - $this->drupalLogin($web_user); - $this->drupalGet(Url::fromRoute('examples.menu_example.permissioned')); - $this->assertSession()->pageTextContains(t('Permissioned Example')); - $this->clickLink('examples/menu-example/permissioned/controlled'); - $this->assertSession()->statusCodeEquals(403); - // Check access with appropriate permissions. - $this->grantPermissions(Role::load(Role::AUTHENTICATED_ID), ['access protected menu example']); + /** + * Test all the routes. + */ + public function testMenuExampleRoutes() { + $assert = $this->assertSession(); + // Key is route, value is page contents. + $routes = [ + 'examples.menu_example' => 'This page is displayed by the simplest (and base) menu example.', + 'examples.menu_example.permissioned' => 'A menu item that requires the "access protected menu example" permission', + 'examples.menu_example.custom_access' => 'A menu item that requires the user to posess', + 'examples.menu_example.custom_access_page' => 'This menu entry will not be visible and access will result in a 403', + 'examples.menu_example.route_only' => 'A menu entry with no menu link is', + 'examples.menu_example.use_url_arguments' => 'This page demonstrates using arguments in the url', + 'examples.menu_example.title_callbacks' => 'The title of this page is dynamically changed by the title callback', + 'examples.menu_example.placeholder_argument' => 'Demonstrate placeholders by visiting', + 'example.menu_example.path_override' => 'This menu item was created strictly to allow the RouteSubscriber', + 'examples.menu_example.alternate_menu' => 'This will be in the Main menu instead of the default Tools menu', + ]; + $this->drupalLogin($this->createUser()); + $this->drupalGet(Url::fromRoute('')); + // Check that all the links appear in the tools menu. + foreach (array_keys($routes) as $route) { + $assert->linkByHrefExists(Url::fromRoute($route)->getInternalPath()); + } + + // Add routes that are not in the tools menu. + $routes['examples.menu_example.route_only.callback'] = 'The route entry has no corresponding menu links entry'; + // Check that all the routes are reachable and contain content. + foreach ($routes as $route => $content) { + $this->drupalGet(Url::fromRoute($route)); + $assert->statusCodeEquals(200); + $assert->pageTextContains($content); + } + + // Check some special-case routes. First is the required argument path. + $arg = 2377; + $this->drupalGet(Url::fromRoute('examples.menu_example.placeholder_argument.display', ['arg' => $arg])); + $assert->statusCodeEquals(200); + $assert->pageTextContains($arg); + + // Check the generated route_callbacks tabs. + $dynamic_routes = [ + 'examples.menu_example.tabs_second', + 'examples.menu_example.tabs_third', + 'examples.menu_example.tabs_fourth', + 'examples.menu_example.tabs_default_second', + 'examples.menu_example.tabs_default_third', + ]; + $this->drupalGet(Url::fromRoute('examples.menu_example.tabs')); + foreach ($dynamic_routes as $route) { + $assert->linkByHrefExists(Url::fromRoute($route)->getInternalPath()); + } + foreach ($dynamic_routes as $route) { + $this->drupalGet(Url::fromRoute($route)); + $assert->statusCodeEquals(200); + } + + // Check the special permission route. $this->drupalGet(Url::fromRoute('examples.menu_example.permissioned_controlled')); - $this->assertSession()->statusCodeEquals(200); - - // Check Route only example entry. - $this->clickLink(t('Route only example')); - $this->clickLink('examples/menu-example/route-only/callback'); - $this->assertSession()->pageTextContains('The route entry has no corresponding menu links entry'); - - // Check links on Tabs landing page. - $this->clickLink(t('Tabs')); - $this->assertSession()->pageTextContains(t('This is the tab "Default primary tab" in the "basic tabs" example')); - $this->assertSession()->linkByHrefExists('/examples/menu-example/tabs'); - // Check presence of default tabs. - $this->assertSession()->linkByHrefExists('/examples/menu-example/tabs/default/second'); - $this->assertSession()->linkByHrefExists('/examples/menu-example/tabs/default/third'); - - // Check second tab. - $this->drupalGet('examples/menu-example/tabs/second'); - $this->assertSession()->pageTextContains(t('This is the tab "Second" in the "basic tabs" example')); - - // Check third tab. - $this->clickLink(t('Third')); - $this->assertSession()->pageTextContains(t('This is the tab "Third" in the "basic tabs" example')); - - // Now verify 'URL arguments' menu entry to be working with two arguments. - $this->clickLink(t('URL Arguments')); - $this->drupalGet(Url::fromRoute('examples.menu_example.use_url_arguments', ['arg1' => 'one', 'arg2' => 'two'])); - $this->assertSession()->pageTextContains(t('Argument 1 = one')); - $this->assertSession()->pageTextContains(t('Argument 2 = two')); - - // Check 'placeholder arguments' menu entry. - $this->clickLink(t('Placeholder Arguments')); - $this->clickLink(t('examples/menu-example/placeholder-argument/3343/display')); - $this->assertSession()->pageTextContains('3343'); - - // Check altered route example. - $this->drupalGet('examples/menu-example/menu-altered-path'); - $this->assertSession()->pageTextContains('Menu item altered by RouteSubscriber::alterRoutes'); - + $assert->statusCodeEquals(403); + $this->drupalLogin($this->createUser(['access protected menu example'])); + $this->drupalGet(Url::fromRoute('examples.menu_example.permissioned_controlled')); + $assert->statusCodeEquals(200); + $assert->pageTextContains('This menu entry will not show and the page will not be accessible'); } }