#NERDsummit
@WeAreGenuine
Drupal Flexibility /
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
Everything in Drupal can be manipulated!
Three general layers
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.
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.
name: Drupal Flex Demo
type: module
description: This is a demo module to show off Drupal Flexibility
core: 8.x
package: Other
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.
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.
Data retrieved from the database, before being rendered.
tl;dr The core method for extending...core.
Custom Drupal 7 theme 'dflex'. Includes template.php for hooks.
/**
* 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.
/**
* 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.
tl;dr "With great power, comes great responsibility" ~ Uncle Ben.
Drupal 7 core file menu.inc, controls menu functionality.
/**
* 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.
/**
* 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.
Hacking core is the "Wrong way". Don't do it or God willl kill a kitten.
tl;dr It's hacking core without hacking core.
Drupal 8 custom module 'dflex_demo' with an 'src' directory, containting two new files.
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.
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.
tl;dr Control of the rendered HTML.
Drupal 7 custom theme 'dflex', now contains a templates folder.
Contains a template file for rendering links.
<?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.
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
{{ 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.
tl;dr Fancy client side functionality.
Drupal 7 custom theme 'dflex' contains a javascript file 'dflex.js'.
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.
(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:
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.
(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.
/**
* 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.
Questions?