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.0-beta11: Standard. bartik subtheme. slate color.
    !!WARNING!! Drupal 8 still in development.
  • Not live. (blame Murphy)


Drupal 7: before

A standard Drupal 7.37 site install with 9 menu tabs.

Drupal 7: after

A standard Drupal 7.37 site install with 9 menu tabs. After data manipulation

Drupal 8: before

A standard Drupal 8.0.0-beta11 site install with 9 menu tabs.

Drupal 8: after

A standard Drupal 8.0.0-beta11 site install with 9 menu tabs. After data manipulation

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
  • Nothing extra is needed.
  • "Easy" to use interface.
  • Changes made are stable.
  • Cons
  • Capabilities are limited.
  • Solution may be too broad.
  • Solution may be too narrow.

tl;dr Easy to use, but limited functionality.

Use Drupal 7 core to change menu item title and destination.

Use Drupal 7 core to change menu item title and destination.

First item in Drupal 7 menu now has changed 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.

Use Drupal 8 core to change menu item title and destination.

First item in Drupal 8 menu now has changed title and destination.

First item in Drupal 8 menu now has changed title and destination.

#2. Modules

  • Pros
  • Extend Drupal core.
  • Variety of solutions available.
  • Nothing exists? Build your own.
  • Cons
  • Can add complexity.
  • May do more then needed.
  • Potential security issues.

tl;dr Extensive but with added complexity.

Drupal 7: Menu Attributes, allows setting additional Menu item settings.

Menu Attributes module, allows setting additional Menu item settings.

Use drush to download and enable Menu Attributes into D7 site.

Use drush to download and enable Menu Attributes into D7 site.

Altering Drupal 7 menu item from admin, can now set style and target.

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.

Second item in Drupal 7 menu now has changed title, destination and style.

Drupal 8: Menu Attributes module is not available for Drupal 8.

No Drupal 8 release of Menu Attributes.

Create a custom 'dflex_demo' module for Drupal 8.

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 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.

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.

Second item in Drupal 8 menu now has changed title, destination and style.

#3. Direct Queries

  • Pros
  • Extremely powerful method.
  • Direct data manipulation.
  • Can be implemented quickly.
  • Cons
  • Queries can be complex.
  • Changes may not be stable.
  • Can be very dangerous.

tl;dr Powerful, but can be dangerous.

menu_links database table contains Drupal 7 menu link data.

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.

Use Drush sqlq command to run custom query to change menu link data in Drupal 7.

Drupal 7: Third item in Drupal 7 menu now has changed title, destination and style.

Third item in Drupal 7 menu now has changed title, destination and style.

menu_tree database table contains Drupal 8 menu link data.

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.

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.

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
  • Provided by core and modules.
  • Easiest way to extend core.
  • Many are available.
  • Cons
  • Not used everywhere.
  • Hooks maybe to broad/narrow.
  • 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.

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.

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.

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.

Before render, menu hits preprocess hook.
Fourth menu link in Drupal 8 has changed title, destination and style.

#5. Hack Core

  • Pros
  • Absolute control of Drupal.
  • Change any core functionality.
  • Extremly powerful.
  • Cons
  • May cause unknown issues.
  • Will break upgrade ability.
  • Introduces security risks.

tl;dr "With great power, comes great responsibility" ~ Uncle Ben.

Drupal 7 core file menu.inc, controls menu functionality.

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 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.

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.

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.

Hacking core is the "Wrong way". Don't do it or God willl kill a kitten.

#6. Services

  • Pros
  • Change any core functionality.
  • No hacking required!
  • Can be very powerful.
  • Cons
  • Only available for Drupal 8.
  • Can be complex.
  • 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.

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.

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
  • Override Drupal markup.
  • Make specific with suggestions.
  • Changes are cached.
  • Cons
  • Not everything is templated.
  • Template may be wrong level.
  • 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.

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($variables['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.

When Drupal 7 renders links will use overrriden template.
Seventh menu item now has altered title, destination and style.

#8. Twig

  • Pros
  • Override Drupal markup.
  • More secure then old engine.
  • Serverside logic with no PHP.
  • Cons
  • Only in Drupal 8.
  • Logic abilities are limited.
  • 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

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.

When rendering main menu, Drupal 8 users overriden twig menu template. Eighth menu item has altered title, destination and style.

#9. Custom JS

  • Pros
  • Manipulate any DOM item.
  • Can be based on user actions.
  • Less server processing.
  • Cons
  • Dependent on browser support.
  • Delay before it is executed.
  • Alterable by client.

tl;dr Fancy client side functionality.

Drupal 7 custom theme 'dflex' contains a javascript file 'dflex.js'.

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.

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 javascript directory with a js file.

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.

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

Feedback

@mikemiles86

#NERDsummit

Thank You!

Questions?