The Flexibility of Drupal 9 Ways to Alter a Menu item http://nerdsummit.org/node/2069 NERDSummit 2015 #NERDsummit
A presentation at NERDSummit in September 2015 in Amherst, MA, USA by Michael Miles
The Flexibility of Drupal 9 Ways to Alter a Menu item http://nerdsummit.org/node/2069 NERDSummit 2015 #NERDsummit
Michael Miles From: Boston, MA USA Work: Genuine @WeAreGenuine(.com) Exp: Working with Drupal since 2008. Acquia Grand Master. 2014 Acquia MVP. Twitter: @mikemiles86 Drupal.org: mikemiles86 All the Places: mikemiles86
Goals of this Session To show there is no “right” way, just “right for me” way. To demonstrate data manipulation methods. To teach considerations to take into account.
Examples we’ll use Manipulating Menu Items On just about every Drupal site. Can be manipulated in many places. What we’ll focus on changing Title Destination Display
Why should you care? Menu items are just data. Everything in Drupal is just data. If it is data, it can be manipulated! Everything in Drupal can be manipulated!
Environments Drupal 7.37: Standard. bartik subtheme. slate color. Drupal 8.0.0beta11: Standard. bartik subtheme. slate color. !!WARNING!! Drupal 8 still in development. Not live. (blame Murphy)
Drupal 7: before
Drupal 7: after
Drupal 8: before
Drupal 8: after
Data Manipulation Three general layers Database (MySQL, etc…) Server side (PHP) Client side (HTML/JS/CSS)
Database Layer Data stored in, sent to or retrieved from the database.
#1. Drupal Core UI Pros Cons Nothing extra is needed. Capabilities are limited. “Easy” to use interface. Solution may be too broad. Changes made are stable. Solution may be too narrow. tl;dr Easy to use, but limited functionality.
Use Drupal 7 core to change menu item title and destination.
First item in Drupal 7 menu now has changed title and destination.
Use Drupal 8 core to change menu item title and destination.
First item in Drupal 8 menu now has changed title and destination.
#2. Modules Pros Cons Extend Drupal core. Can add complexity. Variety of solutions available. May do more then needed. Nothing exists? Build your own. Potential security issues. tl;dr Extensive but with added complexity.
Menu Attributes module, allows setting additional Menu item settings.
Use drush to download and enable Menu Attributes into D7 site.
Altering Drupal 7 menu item from admin, can now set style and target.
Second item in Drupal 7 menu now has changed title, destination and style.
No Drupal 8 release of Menu Attributes.
Create a custom “dflex_demo” module for Drupal 8.
dflex_demo.info.yml name: Drupal Flex Demo type: module description: This is a demo module to show off Drupal Flexibility core: 8.x package: Other
dflex_demo.module use Drupal\Core\Entity\EntityInterface; use Drupal\menu_link_content\Entity\MenuLinkContent; /** * Implements hook_entity_update(). */ function dflex_demo_entity_update(EntityInterface $entity) { if ($entity instanceof MenuLinkContent) { if ($entity->getTitle() == ‘DB #2’ && $entity->getMenuName() == ‘main’) { db_update(‘menu_tree’) ->fields(array( ‘options’ => serialize(array( ‘attributes’ => array( ‘style’ => ‘background:#FF0;color:#000’, ‘target’ => ‘_blank’, ), )), )) ->condition(‘id’, ‘menu_link_content:’ . $entity->uuid()) ->execute(); } } } Drupal 8 custom module dflex_demo implement instance of entity update hook, to alter menu link data saved to the database.
Use Drush to enable custom Drupal 8 module on Drupal 8 site.
Use Drupal 8 core to change second menu item title and destination. Custom module alters before saving to database.
Second item in Drupal 8 menu now has changed title, destination and style.
#3. Direct Queries Pros Cons Extremely powerful method. Queries can be complex. Direct data manipulation. Changes may not be stable. Can be implemented quickly. Can be very dangerous. tl;dr Powerful, but can be dangerous.
menu_links database table contains Drupal 7 menu link data.
UPDATE menu_links SET options = “a:1:{s:10:attributes;a:1:{s:5:’style’;s:26:’background:#C93;color:# FFF’;}}”, link_title = ‘DB #3’, link_path = ‘node/10’ WHERE menu_name = ‘main-menu’ AND link_path = ‘node/3’; Write custom query to update a Menu link in Drupal 7.
Use Drush sqlq command to run custom query to change menu link data in Drupal 7.
Third item in Drupal 7 menu now has changed title, destination and style.
menu_tree database table contains Drupal 8 menu link data.
UPDATE menu_tree SET options = “a:1:{s:10:attributes;a:1:{s:5:’style’;s:26:’background:#C93;color:# FFF’;}}”, title = ‘DB #3’, route_param_key = ‘node=10’, route_parameters = “a:1:{s:4:’node’;s:2:’10’;}” WHERE menu_name = ‘main’ AND route_param_key = ‘node=3’; Write custom query to update a Menu link in Drupal 8.
Use Drush sqlq command to run custom query to change menu link data in Drupal 8.
Third item in Drupal 8 menu now has changed title, destination and style.
Server Side Layer Data retrieved from the database, before being rendered.
#4. Hooks Pros Cons Provided by core and modules. Not used everywhere. Easiest way to extend core. Hooks maybe to broad/narrow. Many are available. May be early/late in a process. tl;dr The core method for extending…core.
Custom Drupal 7 theme ‘dflex’. Includes template.php for hooks.
dflex/template.php /** * Implements THEME_links__MENUNAME(). */ function dflex_links__system_main_menu($variables) { foreach ($variables[‘links’] as &$menu_link) { if ($menu_link[‘href’] == ‘node/4’) { $menu_link[‘href’] = ‘node/10’; $menu_link[‘title’] = ‘SS #1’; $menu_link[‘attributes’][‘style’] = ‘background:#F00;color:#FFF;’; $menu_link[‘attributes’][‘target’] = ‘_blank’; } } return theme_links($variables); } Implements an instance of a menu theme hook, to alter a menu link.
Before render, menu hits template hook. Fourth menu link in Drupal 7 has changed title, destination and style.
Custom Drupal 8 module ‘dflex_demo’. Will add another hook to .module file.
dflex_demo.module /** * Implements hook_preprocess_HOOK(). */ function dflex_demo_preprocess_menu(&$variables) { if ($variables[‘theme_hook_original’] == ‘menu__main’) { foreach ($variables[‘items’] as &$menu_link) { if ($menu_link[‘url’]->toString() == ‘/node/4’) { $menu_link[‘title’] = ‘SS #1’; $menu_link[‘url’] = \Drupal\Core\Url::fromUri(‘entity:node/10’, array( ‘attributes’ => array( ‘style’ => ‘background:#F00;color:#FFF’, ‘target’ => ‘_blank’, ), )); } } } } Implements an instance of a preprocess menu, to alter a menu link.
Before render, menu hits preprocess hook. Fourth menu link in Drupal 8 has changed title, destination and style.
#5. Hack Core Pros Cons Absolute control of Drupal. May cause unknown issues. Change any core functionality. Will break upgrade ability. Extremly powerful. Introduces security risks. tl;dr “With great power, comes great responsibility” ~ Uncle Ben.
Drupal 7 core file menu.inc, controls menu functionality.
includes/menu.inc /** * Returns an array of links for a navigation menu. * … */ function menu_navigation_links($menu_name, $level = 0) { // … $router_item = menu_get_item(); $links = array(); foreach ($tree as $item) { if (!$item[‘link’][‘hidden’]) { if (($menu_name == ‘main-menu’) && ($item[‘link’][‘href’] == ‘node/5’)) { $item[‘link’][‘href’] = ‘node/10’; $item[‘link’][‘title’] = t(‘SS #2’); $style = ‘background:#000;color:#FFF’; $item[‘link’][‘localized_options’][‘attributes’][‘style’] = $style; $item[‘link’][‘localized_options’][‘attributes’][‘target’] = ‘_blank’; } $class = ”; //… } Alter menu_navigation_links function to change a menu link in main menu.
Drupal 7 menu loads, uses hacked function. Fifth menu item now has altered title, destination and style.
Drupal 8 core class MenuLinkTree.php controls menu functionality.
core/lib/Drupal/Core/Menu/MenuLinkTree.php /** * Implements the loading, transforming and rendering of menu link trees. */ class MenuLinkTree implements MenuLinkTreeInterface { // … protected function buildItems(array $tree, …) { // … $url = $element[‘url’]->toString(); if(($link->getMenuName() == ‘main’) && ($url == ‘/node/5’)) { $element[‘title’] = ‘SS #2’; $element[‘url’] = \Drupal\Core\Url::fromUri(‘entity:node/10’, array( ‘attributes’ => array( ‘style’ => ‘background:#000;color:#FFF’, ‘target’ => ‘_blank’, ), )); } // Index using the link’s unique ID. $items[$link->getPluginId()] = $element; } return $items; } //… } Alter buildItems() function to change menu link in main menu.
Drupal 8 menu loads, uses hacked function. Fifth menu item now has altered title, destination and style.
Credit: Greg Dunlap (@heyrocker) (around 2008?) Hacking core is the “Wrong way”. Don’t do it or God willl kill a kitten.
#6. Services Pros Cons Change any core functionality. Only available for Drupal 8. No hacking required! Can be complex. Can be very powerful. Not for everything. tl;dr It’s hacking core without hacking core.
Drupal 8 custom module ‘dflex_demo’ with an ‘src’ directory, containting two new files.
DflexDemoServiceProvider.php namespace Drupal\dflex_demo; use Drupal\Core\DependencyInjection\ContainerBuilder; use Drupal\Core\DependencyInjection\ServiceProviderBase; class DflexDemoServiceProvider extends ServiceProviderBase { /** * {@inheritdoc} */ public function alter(ContainerBuilder $container) { // Override the menu_link_tree class with a new class. $definition = $container->getDefinition(‘menu.link_tree’); $definition->setClass(‘Drupal\dflex_demo\DflexDemoMenuLinkTree’); } } Extend ServiceProviderBase and override alter function. Retrieve correct dependency injection container and tell Drupal what class to use as the service.
DflexDemoMenuLinkTree.php namespace Drupal\dflex_demo; use Drupal\Core\Menu\MenuLinkTree; class DflexDemoMenuLinkTree extends MenuLinkTree { /** * Overrides \Drupal\Core\Menu\MenuLinkTree::build(); */ public function build(array $tree) { $build = parent::build($tree); if (isset($build[‘#items’]) && ($build[‘#theme’] == ‘menu__main’)) { foreach ($build[‘#items’] as &$item) { if ($item[‘url’]->toString() == ‘/node/6’) { $item[‘title’] = ‘SS #3’; $item[‘url’] = \Drupal\Core\Url::fromUri(‘entity:node/10’, array( ‘attributes’ => array( ‘style’ => ‘background:#33C;color:#FFF’, ‘target’ => ‘_blank’, ))); } } } return $build; } } Custom MenuLinkTree Service. Extend the core service and override the build function, to alter menu link.
Drupal 8 menu is loaded using custom service to build menu. Sixth menu item has altered title, destination and style.
Client Side Layer Data when being rendered.
#7. Template Overrides Pros Cons Override Drupal markup. Not everything is templated. Make specific with suggestions. Template may be wrong level. Changes are cached. May still require server side. tl;dr Control of the rendered HTML.
Drupal 7 custom theme ‘dflex’, now contains a templates folder. Contains a template file for rendering links.
dflex/templates/link.tpl.php <?php $url = check_plain(url($variables[‘path’], $variables[‘options’])); $attributes = drupal_attributes($variables[‘options’][‘attributes’]); $text = ($variables[‘options’][‘html’] ? $variables[‘text’] : check_plain($var iables[‘text’])); ?> <?php if(isset($variables[‘path’]) == ‘node/7’): ?> <a href=”/alt-path” style=”background:#6CF;color:#FFF;”>CS #1</a> <?php else: ?> <a href=”<?php print $url; ?>” <?php print $attributes; ?>><?php print $text; ?></a> <?php endif; ?> Add additional logic to link template to display static link for particular path.
When Drupal 7 renders links will use overrriden template. Seventh menu item now has altered title, destination and style.
#8. Twig Pros Cons Override Drupal markup. Only in Drupal 8. More secure then old engine. Logic abilities are limited. Serverside logic with no PHP. Requires more processing. tl;dr More secure template overrides.
Custom Drupal 8 theme ‘dflex_demo’ contains a templates directory. Along with a twig template suggestion for main menu
dflex/templates/menu—main.html.twig {{ menus.menu_links(items, attributes, 0) }} {% macro menu_links(items, attributes, menu_level) %} {% import _self as menus %} {% if items %} {% if menu_level == 0 %} <ul{{ attributes.addClass(‘menu’) }}> {% else %} <ul class=”menu”> {% endif %} {% for item in items %} <li{{ item.attributes }}> {% if item.title == ‘Page 8’ and item.url.toString() == ‘/node/8’ %} <a href=”/alt-path” style=”background:#C3F;color:#FFF;”>CS #2</a> {% else %} {{ link(item.title, item.url) }} {% endif %} {% if item.below %} {{ menus.menu_links(item.below, attributes, menu_level + 1) }} {% endif %} </li> {% endfor %} </ul> {% endif %} {% endmacro %} Template override contains twig logic to display static link for specific menu item.
When rendering main menu, Drupal 8 users overriden twig menu template. Eighth menu item has altered title, destination and style.
#9. Custom JS Pros Cons Manipulate any DOM item. Dependent on browser support. Can be based on user actions. Delay before it is executed. Less server processing. Alterable by client. tl;dr Fancy client side functionality.
Drupal 7 custom theme ‘dflex’ contains a javascript file ‘dflex.js’.
dflex/dflex.info name = Drupal Flex Demo description = Demo subtheme for displaying Drupal Flexibility package = Core version = VERSION core = 7.x base theme = bartik stylesheets[all][] = css/colors.css scripts[] = dflex.js … Tell Drupal 7 about js file by adding it to the themes scripts array in the .info file.
dflex/dflex.js (function($){ Drupal.behaviors.dflex = { attach: function (context, settings) { $(‘#main-menu-links li > a’).each(function(){ if ($(this).attr(‘href’) == ‘/node/9’) { $(this).attr(‘style’, ‘background:#0F0;color:#000;’); $(this).attr(‘target’, ‘_blank’); $(this).attr(‘href’, ‘/alt-path’); $(this).text(‘CS #3’); } }) } } })(jQuery); Add custom Drupal behavour to alter main menu link after DOM loads.
After Drupal 7 page loads, custom javascript runs. Ninth menu item now has altered title, destination and style.
Drupal 8 custom module ‘dflex_demo’ has new libraries.yml file and a js directory with a javascript file.
dflex_demo/dflex_demo.libraries.yml dflex_demo: version: VERSION js: js/dflex-demo.js: {} dependencies: - core/jquery - core/jquery.once - core/drupal libraries.yml tells Drupal 8 what javacript libraries are provided and any dependencies they may have.
dflex_demo/js/dflex-demo.js (function ($, Drupal) { “use strict”; Drupal.behaviors.dflexDemo = { attach: function (context) { $(‘nav.menu—main > ul.menu li > a’).each(function(){ if ($(this).attr(‘href’) == ‘/node/9’) { $(this).attr(‘style’, ‘background:#0F0;color:#000;’); $(this).attr(‘target’, ‘_blank’); $(this).attr(‘href’, ‘/alt-path’); $(this).text(‘CS #3’); } }); } } })(jQuery, Drupal); Add custom Drupal behavour to alter main menu link after DOM loads.
dflex_demo/dflex_demo.module /** * Implements hook_page_attachments(). */ function dflex_demo_page_attachments(&$page) { // Add the dflex_demo js library to all pages. $page[‘#attached’][‘library’][] = ‘dflex_demo/dflex_demo’; } drupal_add_js() no longer exists in Drupal 8. Must add library to a render array. Custom module does so by implementing the hook_page_attachments.
When Drupal 8 page loads, drupal adds custom javascript library. After DOM loads custom behaviour is run. Ninth menu item now has altered title, destination and style.
Review Drupal provides many, many ways to manipulate data. No “right” way or “wrong” way. Just “right for me” way.
Slides & Notes bit.ly/NERD15Flex bit.ly/NERD15FlexSlides
Feedback @mikemiles86 #NERDsummit
Thank You! Questions? #NERDsummit @WeAreGenuine Drupal Flexibility / Michael Miles