text:
Last month I begun my second decade of working with Drupal! How crazy is that? I started at ComputerMinds in 2009. Drupalcon Paris was my first week on the job - I just remember taking so many notes, as if it were a university course! I had a lot to learn then, but now I can look back with a much more experienced head, hopefully wiser, with some sort of perspective.
The conference room before the keynote at Drupalcon Paris 2009, when my career with Drupal began.
My first steps with Drupal were on Drupal 6. It could do a lot (at least once CCK and Views were installed), and Wordpress was much smaller so the two were still occupying a similar space in the market. Most of the sites we built were trying to be a mini social network, a blog, or a brochure site, sometimes with e-commerce thrown in. There were rounded corners everywhere, garish pinks, and some terribly brittle javascript. Supporting Internet Explorer 6 was a nightmare, but still necessary.
It's probably only over the last few years that it became clear that Drupal's strength is its ability to be turned to just about anything that a website needs to do. That has meant that whilst alternative products have picked up the simpler sites, Drupal has been brilliant for projects with complex requirements. Integrating with CRMs and all sorts of other APIs, handling enormous traffic loads, providing content for apps - this kind of stuff has always been Drupal's jam. You just have to know how to get it hooked up in all the right places!
Speaking of hooks, it's been interesting to see Drupal move from its famous magical hooks, towards event-driven architecture. For me, that single shift represented an enormous change in direction for Drupal. I believe the events/subscriber pattern, as a part of a wider effort in Drupal 8 to deliberately use existing well-defined design patterns and solutions, is a sign of a much more mature and professional platform. Most coding problems have been solved well elsewhere, we shouldn't reinvent the wheel! (Although I know I can be guilty of that!) That's just one example of how the Drupal ecosystem has become more professional over the last ten years. Formal testing is another example. Many people have felt left behind by this shift, as a need in the enterprise world that Drupal could meet was identified. 'Enterprise' gets used as a dirty word sometimes - but to be frank, there's more money and more suitable opportunity there!
That is something the Drupal community has to be honest about. It is rightly aiming to champion diversity, and be as accessible as possible for as many as possible across the world (I especially love that Drupal is now so good for multilingual projects). It's not like Drupal is suddenly inappropriate for smaller projects - in fact, I'd still suggest it's far ahead in many aspects.
But money makes things happen, and gives people their livelihood. I appreciate seeing honesty and innovation about this coming from community leaders. Thankfully those kinds of values are what drive the Drupal project, even if money is often the facilitator. As a community we must always fight to keep those things in the right relation to each other: money has an inevitable influence that we must accept, but it must be led by us and our values, not the other way around. I should add that I am very aware that I am privileged to be a developer in a leading Drupal agency, so my opinion will be shaped by that position!
To end with honesty and innovation myself, I would love to see the following carried into the Drupal project's next 10 years, which I saw the community espouse over the last decade. I know I need to grow in these things myself!
- Maintain excellence, even as the make-up of the community changes.
- Deliberately listen to under-represented portions of the community, as an intentional force against the skewing effect of money/power.
- Keep watch for what competitors and other relevant products are doing, to incorporate worthwhile things wherever possible.
- Reflect on the strengths & weaknesses of Drupal (and its community) with honesty. Let's make the most of what makes Drupal what it is and not be afraid for it to focus on that; the same goes for us as individuals.
I'm proud to work with Drupal, long may it continue!
Here at ComputerMinds, we think of ourselves as Drupal specialists for the UK, but we don't limit ourselves to that. We offer clients a close working relationship and our general flexibility to get stuck into applying our skills to most problems. One of our clients, Alfresco, has come to trust us with more than just our Drupal wisdom. They wanted a new hub that would bring together documentation for a variety of their open source products, which are already on Github. Their documentation was written as Markdown files, so the hub would need to import and transform those files into HTML pages for the web. Essentially, a static site generator was needed.
For many of us at ComputerMinds, this was our first dive into working with React, let alone Gatsby itself. So the wonderful documentation and tutorials for Gatsby were a huge help. As our project was for displaying documentation, the Gatsby documentation site itself was a helpful influence on the eventual design and user interaction of our site.
The most obvious difference between Drupal and GatsbyJS, was that the coding was mainly in javascript, rather than PHP. It was great to use much more modern javascript code patterns and tools - even if it took a bit of head-scratching to get there! All credit should go to Steve Tweeddale in our Coventry office for his patience with my million questions as we worked towards building a Minimum Viable Product early on in the project timeline. As the site took shape beyond that, we drew on more and more concepts from the GatsbyJS and React ecosystems. Here's a few that were particularly interesting:
No doubt someday we'll be turning our hands to bring the best of Gatsby and Drupal together! 'Headless', or 'decoupled' Drupal has become one of the new trendy ways to use Drupal. This means using Drupal to define your app's information architecture or as a content repository. Other applications can then connect to it to display content to users. Those applications could be made in Gatsby, React, Drupal, or almost anything else. We still feel this is usually only worthwhile when Drupal is used as a back-end system for multiple front-ends. But regardless we are looking forward to using GatsbyJS again!
Websites need to look pretty and be blazing fast. That often means lots of beautiful high-quality images, but they can be pretty enormous to download, making the page slow to load. Images are often one of the 'heaviest' parts of a website, dragging a visitor's experience down instead of brightening it up as intended. If a website feels even a tiny bit unresponsive, that tarnishes your message or brand. Most of us have sat waiting frustratedly for a website to work (especially on mobile), and given up to go elsewhere. Drupal can be configured to deliver appropriately-resized versions, but what's even better than that?
Lazy image loading
Don't send images to be downloaded at all until they're actually going to be seen! Browsers usually download everything for a page, even if it's out of sight 'below the fold'. We know we can do better than that on a modern website, with this technique called lazy image loading.
Lazily loading an image means only sending it for a user to download once they are scrolling it into view. Modern web browsers make this surprisingly simple to achieve for most images, although there are often a few that need special attention. When combined with optimisation from Kraken.io, and other responsive design tricks, performance can sky-rocket again. Check out our case study of NiquesaTravel.com for a great example using this.
Niquesa is a luxury brand for busy people, so the website experience needs to be smooth, even when used on the go over a mobile network. Perhaps more than that, SEO (search engine optimisation) is critical. Their bespoke packages need to show up well in Google searches. Google promotes websites that perform well on mobile devices - so if your site is slow, it needs to be sped up. It's not just that you'll lose out on competitive advantage and tarnish your brand: people simply won't find you.
You can see what Google thinks of your website performance by using their PageSpeed Insights tool. That gives you an overall score and lists specific improvements you can make. Niquesa asked us to boost their score, especially for mobile devices. So we looked to speed up anything slow, and to reduce the amount of things there are to download in the first place. Any website can use that approach too. Lazy image loading speeds up the initial page load, and reduces the amount to download.
This stuff should be standard on most websites nowadays. But many web projects began well before browsers supported this kind of functionality so still need it adding in. As an ever-improving platform, the internet allows you to continually improve your site. There's no need to feel locked in to a slow site! Get in touch with us if you're interested in improving your website with lazy loaded imagery. Who wouldn't want beautiful high-quality media and great performance on any device?
Can you teach me to be lazy?
Sure! Rather than using the normal
src attribute to hold the image file location, use a
data-src attribute into a real
src attribute, which means the browser will download the real image.
On its own, that wouldn't take very long to set up on most websites. But on top of this, we often go the extra mile to add some extra optimisations. These can take up the majority of the time when applying lazy loading to a website, as they are a great improvement for the user experience, but usually need crafting specifically for each individual project:
- Images defined via
srcsetattributes (rather than a
srcattribute) and background images in CSS files, need similar handling. For example, use a
- Images that we expect to be immediately in view are excluded from any lazy loading, as it is right to show them immediately.
- It may be important to keep a placeholder in place of the real image, perhaps either to keep a layout in place or in case javascript is not running. Styling may even need to be tweaked for those cases. Sadly it's not unusual for third-party javascript out of your control to break functionality on a page!
- Dimensions may need some special handling, as Drupal will often output fixed widths & heights, but responsive design usually dictates that images may need to scale with browser widths. If the real image is not being shown, its aspect ratio may still need to be applied to avoid breaking some layouts.
- Some design elements, like carousels, hide some images even when they are within the viewport. These can get their own lazy magic. One of our favourite carousel libraries, Slick, supports this with almost no extra work, but many designs or systems will need more careful bespoke attention.
Here is a basic example javascript implementation for Drupal:
(function($) {
// Set up an intersection observer.
Drupal.lazy_load_observer = new window.IntersectionObserver(function(entries) {
for (var i in entries) {
if (entries.hasOwnProperty(i) && entries[i].isIntersecting) {
var $element = $(entries[i].target);
// Take the src value from data-src.
$element.attr('src', $element.attr('data-src'));
// Stop observing this image now that it is sorted.
// Specify a decent margin around the visible viewport.
rootMargin: "50% 200%"
// Get that intersection observer acting on images.
Drupal.behaviors.lazy_load = {
attach: function (context, settings) {
$('img[data-src]', context).once('lazy-load').each(function() {
(This does not include a fallback for older browsers. The
rootMargin property, which defines how close an element should be to the edge of the viewport before being acted on, might want tweaking for your design.)
Drupal constructs most image HTML tags via its
image template, so a
hook_preprocess_image can be added to a theme to hook in and change the
src attribute to be a
data-src attribute. If required, a placeholder image can be used in the
src attribute there too. We tend to use a single highly-cacheable transparent 1x1 pixel lightweight image, but sometimes a scaled down version of the 'real' image is more useful.
The lazy loading idea can be applied to any page element, not just images. Videos are a good candidate - and I've even seen ordinary text loaded in on some webpages as you scroll further through long articles. Enjoy being lazier AND faster!
Image: Private beach by Thomas
Drupal empowers site builders and editors to configure their sites in settings forms. Configuration management lets developers push changes up to live sites to be imported. But developers have to be considerate to ensure imports will not wipe out those changes made directly through the live sites' settings forms. At the least, they have to export the changes before making further tweaks. But admins may make further changes in the meantime too, so developers can end up frequently pulling irrelevant changes back from live, which seems unnecessary.
Here's some examples of the kind of config that I'm thinking of:
- The site email and Google Analytics account are usually managed by site admins, not developers. So developers should not be the ones to manage those settings.
- Marketers may like tweaking the site name or slogan. That doesn't need to affect developers.
- Contact forms contain labels and other text which may be key to the communication between a client and their customers.
- Permissions - sometimes it's not clear where the lines are between editors/admins/etc, so why not allow some flexibility to reassign permissions directly on live without needing to touch the codebase?
We need an approach that allows for specific settings to be considered 'unmanaged' - so an import wouldn't touch whatever they have made to be on live. The Config Ignore project claims to solve this, but we already use Config split which is more powerful, more flexible and has a better user interface. (Although Config Ignore does allow targeting parts of config rather than whole config items.)
Config split is often used to create environment-specific sets of configuration, but its design means it can be used for separating config for other purposes. In this scenario, what's needed is a split that represents settings to be protected, which can be exported immediately before any import. Then when importing, Drupal only sees the preserved version of the settings, so won't change them, regardless of what is in the main configuration files.
The split, which I've called 'Unmanaged', needs to be set up like as follows (see screenshot):
- Use a folder (directory) which already exists and is writable. I use
../config/unmanaged, so it matches the split name and is outside the webroot.
- Set to active. I usually set all other splits to inactive, and only make them active in an environment's
settings.php, but this split exists for the sake of workflow, not environment. For example, it can actually be useful locally, so I can tweak things for development without affecting what ends up in version control.
- Have the largest weight of any split, so that it overrides any other exported version of config it contains.
- Use the Conditional split section, not Complete split, to pick configuration to protect.
- Do not tick either of the checkboxes in the conditional split section.
Once the split has been created, the container needs rebuilding for it to work. Run this, which includes exporting it for the first time:
drush cache-rebuild
drush -y config-split-export unmanaged
Now that it is exported, a
.htaccess file will be have been added to the directory alongside the config. Add the following line to your project's
.gitignore file, adjusting the directory location as appropriate. This ensures the directory will get created on live when changes are pulled from git (containing
.htaccess), but deliberately without the exported config:
So now before running any imports, make sure to export the split:
drush -y config-split-export unmanaged
drush -y config-import
With this split and the export step in place in your workflow, you can be confident of allowing your developers and site admins to get on with their respective work, without getting in each others' way. This puts configuration splits to use for something beyond environment-specific overrides, which I think is exciting. I wonder what other useful purposes they may have?
Photo by Sascha Hormel from Pexels
We've heard of test-driven development, behaviour-driven development, feature-driven development and someone has probably invented buzzword-driven development by now. Here's my own new buzzword phrase: review-driven development. At ComputerMinds, we aim to put our work through peer reviews to ensure quality and to share knowledge around the team. Chris has recently written about why and how we review our work. We took some time on our last team 'CMDay' to discuss how we could make doing peer reviews better. We found ourselves answering this question:
Why is reviewing hard? How can we make it easier?
We had recently run into a couple of specific scenarios that had triggered our discussion. For one, pressure to complete the work had meant reviews were rushed or incomplete. The other scenario had involved such a large set of code changes that reviewing them all at the end was almost impossible. I'm glad of the opportunity to reflect on our practice. Here are some of the points we have come away with - please add your own thoughts in the comments section below.
1. Coders, help your reviewers
The person that does the development work is the ideal person to make a review easy. The description field of a pull request can be used to write a summary of changes, and to show where the reviewer should start. They can provide links back to the ticket(s) in the project's issue tracking system (e.g. Redmine/Jira), and maybe copy across any relevant acceptance criteria. The coder can chase a colleague to get a review, and then chase them up to continue discussions, as it is inevitable that reviewers will have questions.
2. Small reviews are easier
Complicated changes may be just as daunting to review as to build. So break them up into smaller chunks that can be reviewed easier. This has the massive benefit of forcing a developer to really understand what they're doing. A divide & conquer approach can make for a better implementation and is often easier to maintain too, so the benefits aren't only felt by reviewers.
3. Review early & often
Some changes can get pretty big over time. They may not be easy to break up into separate chunks, but work on them could well be broken up into iterations, building on top of each other. Early iterations may be full of holes or
@TODO comments, but they still reveal much about the developer's intentions & understanding. So the review process can start as early as the planning stage, even when there's no code to actually review. Then as the changes to code take shape, the developer can continually return to the same person every so often. They will have contextual knowledge growing as the changes grow, to understand what's going on, helping them provide a better review.
4. Anyone can review
Inevitably some colleagues are more experienced than others - but we believe reviews are best shared around. Whether talking about your own code, or understanding someone else's code, experience is spread across the team. Fresh eyes are sometimes all that's needed to spot issues. Other times, it's merely the act of putting your own work up for scrutiny that forces you to get things right.
5. Reviewers, be proactive!
Developers like to be working, not waiting for feedback. Once they've got someone to agree to review their work, they have probably moved onto solving their next problem. However well they may have written up their work, it's best for the reviewer to chase the developer and talk through the work, ideally face-to-face. Even if the reviewer then goes away to test the changes, or there's another delay, it's best for the reviewer to be as proactive as possible. Clarify as much as needed. Chase down the answers. Ask seemingly dumb questions. Especially if you trust the developer - that probably means you can learn something from them too!
6. Use the tools well
Some code changes can be ignored or skipped through easily. Things like the boilerplate code around features exports in Drupal 7, or changes to composer.lock files. Pointers from the developer to the reviewer of what files/changes are important are really helpful. Reviewers themselves can also get wise as to what things to focus on. Tools can help with this - hiding whitespace changes in diffs, the files tab of PRs on github, or three-way merge tools, for example. Screenshots or videos are essential for communicating between developer & reviewer about visual elements when they can't meet face-to-face.
7. What can we learn from drupal.org?
The patch-based workflow that we are forced to use on drupal.org doesn't get a lot of good press. (I'm super excited for the gitlab integration that will change this!) But it has stood the test of time. There are lessons we can draw from our time spent in its issue queues and contributing patches to core and contrib projects. For example, patches often go through two types of review, which I'd call 'focussed nitpicks' and the wider 'approach critiques'. It can be too tempting to write code to only fulfil precise acceptance criteria, or to pass tests - but reviewers are humans, each with their own perspectives to anticipate. Aiming for helpful reviews can be even more useful for all involved in the long-run than merely aiming to resolve a ticket.
8. Enforcing reviews
We tailor our workflow for each client and project - different amounts of testing, project management and process are appropriate for each one. So 'review-driven development' isn't a strict policy to be enforced, but a way of thinking about our work. When it is helpful, we use Github's functionality to protect branches and require reviews or merges via pull requests. This helps us to transparently deliver quality code. We also find this workflow particularly handy because we can broadcast notifications in Slack of new pull requests or merges that will trigger automatic deployments.
What holds you back from doing reviews? What makes a review easier?
I've only touched on some the things we've discussed and there's bound to be even more that we haven't thought of. Let us know what you do to improve peer reviewing in the comments!
There's nothing like Drupal's stock AJAX spinner (this: Drupal's default blue loading throbber graphic) to make you notice that a site's design hasn't been fully customised. The code from my previous article showing how to fetch a link over AJAX to open in a Foundation reveal popup would suffer from this without some further customisation. After clicking the 'Enquire' button, a loading icon of some kind is needed whilst the linked content is fetched. By default, Drupal just sticks that blue 'throbber' next to the link, but that looks totally out of place. Our client's site uses a loading graphic that feels much more appropriate in style and placement, but my point is that you can set up your own bespoke version. Since it's Christmas, let's add some festive fun! Here's a quick video showing what I'll take you through making:
[embedded content]
A few things are needed:
- Create a javascript method that will add a custom progress indicator
- Ensure the javascript file containing the method is included on the page
- Set a custom attribute on the link that will trigger the AJAX
- Override Drupal core's javascript method that adds the standard progress throbber, to respect that custom attribute
There are many ways to achieve points 1 and 2. Usually, you would define a library and add it with
#attached. But I decided I wanted to treat my work as if it were part of Drupal's core AJAX library itself, rather than something to add separately. So I implemented
hook_library_info_alter() in my theme's main
.theme file:
* Implements hook_library_info_alter().
function MYTHEME_library_info_alter(&$libraries, $extension) {
// Add our own extension to drupal.ajax, which is aware of the page markup so
// can add AJAX progress loaders in the page.
if ($extension == 'core' && isset($libraries['drupal.ajax'])) {
$libraries['drupal.ajax']['js']['/' . drupal_get_path('theme', 'MYTHEME') . '/js/ajax-overrides.js'] = [];
ajax-overrides.js file contains this:
(function ($, window, Drupal, drupalSettings) {
* Creates a new Snowman progress indicator, which really is full screen.
Drupal.Ajax.prototype.setProgressIndicatorSnowman = function () {
this.progress.element = $('<div class="ajax-progress ajax-progress-fullscreen ajax-progress-snowman"> </div>');
// My theme has a wrapping element that will match #main.
})(jQuery, window, Drupal, drupalSettings);
My theme happens to then style
.ajax-progress-snowman appropriately, to show a lovely snowman in the middle of the page, rather than a tiny blue spinner next to the link that triggered the AJAX. Given that the styling of the default spinner happens to make links & lines jump around, I've got the
ajax-progress-fullscreen class in there, to be more like the 'full screen' graphic that the Views UI uses, and avoid the need to add too much more styling myself.
Part 3, adding a custom attribute to specify that our AJAX link should use a Snowman animation, is easily achieved. I've already added the '
data-dialog-type' attribute to my link, so now I just add a '
data-progress-type' attribute, with a value of '
snowman'. I want this to work similarly to the
$element[#ajax]['progress']['type'] property that can be set on form elements that use AJAX. Since that only gets applied to form elements, not arbitrary links using the '
use-ajax' class, we have to do the work to pick this up ourselves.
So this is the last part. Back in my ajax-overrides.js file, I've added this snippet to override the standard 'throbber' progress type that AJAX links would otherwise always use. It falls back to Drupal's original method when the progress type isn't specified in a '
data-progress-type' attribute.
// Override the progress throbber, to actually use a different progress style
// if the element had something specified.
var originalThrobber = Drupal.Ajax.prototype.setProgressIndicatorThrobber;
Drupal.Ajax.prototype.setProgressIndicatorThrobber = function () {
var $target = $(this.element);
var progress = $target.data('progressType') || 'throbber';
if (progress === 'throbber') {
else {
var progressIndicatorMethod = 'setProgressIndicator' + progress.slice(0, 1).toUpperCase() + progress.slice(1).toLowerCase();
if (progressIndicatorMethod in this && typeof this[progressIndicatorMethod] === 'function') {
So there you have it - not only can you launch beautiful Foundation Reveal popups from links that fetch content via AJAX, you can now avoid Drupal's little blue throbber animation. If it's an excuse to spread some cheer at Christmas, I'll take it.
Happy Christmas everyone!
Let me take you on a journey. We'll pass by Drupal content renderer services, AJAX commands, javascript libraries and a popular front-end framework. If you've only heard of one or two of those things, come lean on the experience I took diving deep into Drupal. I'm pleased with where my adventure took me to, and maybe what I learned will be useful to you too.
Here's the end result: a contact form, launched from a button link in the site header, with the page beneath obscured by an overlay. The form allows site visitors to get in touch from any page, without leaving what they were looking at.
Drupal has its own API for making links launch dialogs (leveraging jQuery UI Dialogs). But our front-end of the site was built with Foundation, the super-popular theming framework, which provides components of its own that are much better for styling. We often base our bespoke themes on Foundation, and manipulate Drupal to fit.
We had already done some styling of Foundation's Reveal component. In those places, the markup to show in the popup is already in the page, but I didn't really want the form to be in the page until it was needed. Instead, AJAX could fetch it in. So I wondered if I could combine Drupal's AJAX APIs with Foundation's Reveal markup and styling. Come with me down the rabbit hole...
There are quite a few components in making this possible. Here's a diagram:
So it comes down to the following parts, which we'll explore together. Wherever custom code is needed, I've posted it in full later in this article.
- A link that uses AJAX, with a dialog type set in an attribute.
- Drupal builds the content of the page that was linked to.
- Drupal's content view subscriber picks up that response and looks for a content renderer service that matches the dialog type.
- The content renderer returns an AJAX command PHP class in its response, and attaches a javascript library that will contain a javascript AJAX command (a method).
- That command returns the content to show in the popup, and that javascript method name.
- The javascript method launches the popup containing the HTML content.
Let's start at the beginning: the link. Drupal's AJAX API for links is pretty neat. We trigger it with two things:
use-ajaxclass, which tells it to open that link via an AJAX call, returning just the main page content (e.g. without headers & footers), to be presented in your existing page.
data-dialog-typeattribute, to instruct how that content should be presented. This can be used for the jQuery UI dialogs (written up elsewhere) or the newer off-canvas sidebar, for example.
I wanted to have a go at creating my own 'dialog type', which would be a Foundation Reveal popup. The HTML fetched by the AJAX call would be shown in it. Let's start with the basic markup I wanted to my link to have:
<a href="https://www.computerminds.co.uk/contact" class="use-ajax" data-dialog-type="reveal">Enquire</a>
This could either just be part of content, or I could get this into a template using a preprocess function that would build the link. Something like this:
// $url could have come from $node->toUrl(), Url::fromRoute() or similar.
// For this example, it's come from a contact form entity.
$url->setOption('attributes', [
'class' => [
// This attribute tells it to use our kind of dialog
'data-dialog-type' => 'reveal',
// The variable 'popup_launcher' is to be used in the template.
$variables['popup_launcher'] = \Drupal\Core\Link::fromTextAndUrl(t('Enquire'), $url);
After much reading around and breakpoint debugging to figure it out, I discovered that dialog types are matched up to content rendering services. So I needed to define a new one of those, which I could base closely on Drupal's own
DialogRenderer. Here's the definition from my module's
mymodule.services.yml file:
class: Drupal\mymodule\Render\MainContent\FoundationReveal
arguments: ['@title_resolver']
- { name: render.main_content_renderer, format: drupal_reveal }
Adding the tag named 'render.main_content_renderer' means my class will be picked up by core's MainContentRenderersPass when building the container. Drupal's
MainContentViewSubscriber will then consider it as a service that can render responses.
The 'format' part of the tag needs to be the value that our
data-dialog-type attribute has, with (somewhat arbitrarily?) '
drupal_' prepended. The arguments will just be whatever the constructor for the class needs. I often write my class first and then go back to adjust the service definition once I know what it needs. But I'll be a good tour guide and show you things in order, rather than shuttling you backwards and forwards!
Onto that
FoundationReveal service class now. I started out with a copy of core's own
ModalRenderer which is a simple extension to the
DialogRenderer class. Ultimately, that renderer is geared around returning an AJAX command (see the AJAX API documentation), which comes down to specifying a command to invoke in the client-side javascript with some parameters.
I would need my own command, and my
FoundationReveal renderer would need to specify it to be used. That only two functional differences were needed in comparison to core's
- Attach a custom library, which would contain the actual javascript command to be invoked:
$main_content['#attached']['library'][] = 'mymodule/dialog.ajax';
- Return an AJAX command class, that will specify that javascript command (rather than the
OpenDialogCommandcommand that
DialogRendereruses) - i.e. adding this to the returned
new OpenFoundationRevealCommand('#mymodule-reveal')
We'll learn about that command class later!
So the renderer file,
mymodule/src/Render/MainContent/FoundationReveal.php (in that location in order to match the namespace in the service file definition), looks like this - look out for those two tweaks:
namespace Drupal\mymodule\Render\MainContent;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Render\MainContent\DialogRenderer;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\mymodule\Ajax\OpenFoundationRevealCommand;
use Symfony\Component\HttpFoundation\Request;
* Default main content renderer for foundation reveal requests.
class FoundationReveal extends DialogRenderer {
* {@inheritdoc}
public function renderResponse(array $main_content, Request $request, RouteMatchInterface $route_match) {
$response = new AjaxResponse();
// First render the main content, because it might provide a title.
$content = drupal_render_root($main_content);
// Attach the library necessary for using the OpenFoundationRevealCommand
// and set the attachments for this Ajax response.
$main_content['#attached']['library'][] = 'core/drupal.dialog.ajax';
$main_content['#attached']['library'][] = 'mymodule/dialog.ajax';
// Determine the title: use the title provided by the main content if any,
// otherwise get it from the routing information.
$title = isset($main_content['#title']) ? $main_content['#title'] : $this->titleResolver->getTitle($request, $route_match->getRouteObject());
// Determine the dialog options and the target for the OpenDialogCommand.
$options = $request->request->get('dialogOptions', []);
$response->addCommand(new OpenFoundationRevealCommand('#mymodule-reveal', $title, $content, $options));
return $response;
That AJAX command class,
OpenFoundationRevealCommand sits in
mymodule/src/Ajax/OpenFoundationRevealCommand.php. Its
render() method is the key, it returns the command which will map to a javascript function, and the actual HTML under 'data'. Here's the code:
namespace Drupal\mymodule\Ajax;
use Drupal\Core\Ajax\OpenDialogCommand;
use Drupal\Core\StringTranslation\StringTranslationTrait;
* Defines an AJAX command to open certain content in a foundation reveal popup.
* @ingroup ajax
class OpenFoundationRevealCommand extends OpenDialogCommand {
use StringTranslationTrait;
* Implements \Drupal\Core\Ajax\CommandInterface:render().
public function render() {
return [
'command' => 'openFoundationReveal',
'selector' => $this->selector,
'settings' => $this->settings,
'data' => $this->getRenderedContent(),
'dialogOptions' => $this->dialogOptions,
* {@inheritdoc}
protected function getRenderedContent() {
if (empty($this->dialogOptions['title'])) {
$title = '';
else {
$title = '<h2 id="reveal-header">' . $this->dialogOptions['title'] . '</h2>';
$button = '<button class="close-button" data-close aria-label="' . $this->t('Close') . '" type="button"><span aria-hidden="true">×</span></button>';
return '<div class="reveal-inner clearfix">' . $title . parent::getRenderedContent() . '</div>' . $button;
Now, I've mentioned that the command needs to match a javascript function. That means adding some new javascript to the page, which, in Drupal 8, we do by defining a library. My '
mymodule/dialog.ajax' library was attached in the middle of
FoundationReveal above. My library file defines what actual javascript file to include - it is
mymodule.libraries.yml and looks like this:
version: VERSION
js/dialog.ajax.js: {}
- core/drupal.dialog.ajax
Then here's that actual
mymodule/js/dialog.ajax.js file. It adds the '
openFoundationReveal' method to the prototype of the globally-accessible
Drupal.AjaxCommands. That matches the command name returned by my
OpenFoundationRevealCommand::render() method that we saw.
(function ($, Drupal) {
Drupal.AjaxCommands.prototype.openFoundationReveal = function (ajax, response, status) {
if (!response.selector) {
return false;
// An element matching the selector will be added to the page if it does not exist yet.
var $dialog = $(response.selector);
if (!$dialog.length) {
// Foundation expects certain things on a Reveal container.
$dialog = $('<div id="' + response.selector.replace(/^#/, '') + '" class="reveal" aria-labelledby="reveal-header"></div>').appendTo('body');
if (!ajax.wrapper) {
ajax.wrapper = $dialog.attr('id');
// Get the markup inserted into the page.
response.command = 'insert';
response.method = 'html';
ajax.commands.insert(ajax, response, status);
// The content is ready, now open the dialog!
var popup = new Foundation.Reveal($dialog);
})(jQuery, Drupal);
There we have it - that last bit of the command opens the Foundation Reveal popup dialog!
I should also add that since I was showing a contact form in the popup, I installed the Contact ajax module. This meant that a site visitor would stay within the popup once they submit the form, which meant for a clean user experience.
Thanks for following along with me!
If you've got a Drupal site, which you need to update quickly (for example, to address last night's security advisory!), here's a tip. Run this from the command line:
curl 'https://github.com/drupal/drupal/compare/7.59..7.60.patch' | patch -p1
This assumes your codebase was on Drupal 7.59 and you're currently in Drupal's root directory. If you're currently on a different version, adjust the numbers in the patch URL accordingly.
Don't forget to still run database updates via /update.php or
drush updatedb !
The Drupal repo on github is a verbatim mirror of the official Drupal repo from git.drupal.org. Github supports comparing arbitrary git references, with the
/ORGANIZATION/REPO/compare/GITREF..GITREF URL, where the reference can be a tag, branch or revision. Adding '.patch' to the end of a github URL formats the page as a patch. So I've made use of these three things to give me an exact patch of the changes needed to update Drupal core's code.
We normally use Composer (especially for Drupal 8) or Drush make to manage our codebases, including core, which is definitely the ideal, but sometimes projects aren't organised this way for whatever reason. A simple patch like this avoids the need for drush, or even potential mistakes that can be made when running
drush pm-updatecode (such as removing any customisations within core directories).
This method is even compatible with any core patches you already have in place, which normally have to be to re-applied when upgrading core by other methods. If you have any existing changes to core that are incompatible, you'll get errors about not being able to apply anyway, which you can then resolve manually.
(Any patches/hacks you make to core should be documented clearly somewhere, so drush make or composer-patches would be better in that scenario though!)
You can use this method to patch from github even if your core codebase is not in version control. But if it is... always check your diffs before committing! :-)
A client noticed the dates on their news articles were not being translated into the correct language. The name of the month would always appear in English, even though all the month names had themselves been translated and showed correctly elsewhere. The problem turned out to be down to the twig filter being used in the template to format the date. This is what we did have:
{% set newsDate = node.getCreatedTime|date('j F Y') %}
{% trans %} {{ newsDate }}{% endtrans %}
So this would produce something like '1 March 2018' instead of '1 März 2018' when in German. This was because twig's core
date() filter simply isn't aware of Drupal's idea of language.
I switched out the
date() filter and used Drupal core's own
format_date() filter instead, which uses Drupal's own
date.formatter service, which is aware of language. It ensures the month name is passed through
t() to translate it, separately to the rest of the numbers in the date. So it now looks like this:
{% set newsDate = node.getCreatedTime|format_date('custom', 'j F Y') %}
{% trans %} {{ newsDate }}{% endtrans %}
Having done that, I realise the original code was the equivalent of doing this:
t('1 March 2018');
(I'm less of a front-end coder, so putting it in PHP makes it more obvious to me than twig code!)
So the date actually was translatable, but only as the whole string -- so unless the translators had gone through translating every single individual date, the German month name was never going to show!
What I needed was the twig equivalent of using placeholders like this PHP example:
t('My name is @name, hello!', ['@name' => $name]);
When you use variables between twig's
trans and
endtrans tags, they really are treated by Drupal as if they were placeholders, so this is the twig version:
{% trans %} My name is {{ name }}, hello! {% endtrans %}
This helped me understand twig that little bit more, and appreciate how to use it better! Regardless of formatting dates, I now know better how to set up translatable text within templates, hopefully you do too :-)
Let's say you've built a custom form for your Drupal 8 site. It contains various elements for input (name, email address, a message, that kind of thing), and you want to send the submitted values in an email to someone (perhaps a site admin). That's a pretty common thing to need to do.
This could be done with Drupal's core contact forms, webforms, or similar -- but there are cases when a bespoke form is needed, for example, to allow some special business logic to be applied to its input or the form presentation. The drawback of a custom form is that you won't get nice submission emails for free, but they can be done quite easily, with the token module (you'll need that installed).
In your form's submission handler, send an email using the mail manager service (I'll assume you can already inject that into your form, read the documentation if you need help with that):
$params = [
'values' => $form_state->getValues(),
// The 'plugin.manager.mail' service is the one to use for $mailManager.
$mailManager->mail('mymodule', 'myform_submit', '[email protected], 'en', $params);
Then create a
hook_mail() in your .module file, with a matching key ('myform_submit' in my example):
* Implements hook_mail().
function mymodule_mail($key, &$message, $params) {
switch ($key) {
case 'myform_submit':
$token_service = \Drupal::token();
$token_data = [
'array' => $params['values'],
// In this example, put the submitted value from a 'first_name' element
// into the subject.
$subject = 'Submission from [array:value:first_name]';
$message['subject'] = $token_service->replace($subject, $token_data, ['clear' => TRUE]);
// Each submitted value can be included in the email body as a token. My
// form had 'first_name', 'last_name', 'color' and 'birthdate' elements.
$body = <<<EOT
The mymodule form was submitted by [array:value:first_name] [array:value:last_name].
They like the colour [array:value:color], and their birthday is [array:value:birthdate].
$message['body'] = [
$token_service->replace($body, $token_data, ['clear' => TRUE]),
Spot the
[array:value:thing] tokens! Using these 'array' tokens makes it really easy to include the whatever input gets submitted by visitors to this custom form on your Drupal site. This is most useful when the email body is configurable, so editors can move the tokens around as placeholders within the content, and regardless of the form's structure. By the way, the submitted values do not get sanitized - although if your email is just plain text, that's probably not a problem.
There are more array tokens you can use too, such as ones to return a comma-separated list of all items in an array, a count of items, or just the first/last item. See the original issue for examples. These tokens are available in Token's Drupal 7 version too!
We ran into an obscure error recently, when saving a view that used a custom views plugin. It was supposed to be a very simple extension to core's bundle (content type) filter:
InvalidArgumentException: The configuration property display.default.display_options.filters.bundle.value.article doesn't exist. in Drupal\Core\Config\Schema\ArrayElement->get() (line 76 of [...]/core/lib/Drupal/Core/Config/Schema/ArrayElement.php).
Several contrib projects ran into this issue too: Drupal Commerce, Search API and Webform Views integration. There's even a core issue that looked relevant... but it turned out to be a simple, if perhaps surprising fix. If you ever run into it, it will have a different property (i.e. due to whichever plugin or default value are used).
Our filter was little more than a simple subclass of
\Drupal\views\Plugin\views\filter\Bundle, declared for a specific entity type in a very ordinary
hook_views_data() (which had even been autogenerated by Drupal Console, so we were fairly confident the problem wasn't there). It just tailored the options form a little to work well for the entity type.
Views plugins all have their own configuration schema - for example, the bundle filter is declared in
views.filter.schema.yml to use the 'in_operator', because multiple bundles can be selected for it. When we subclass such a plugin, we do not automatically get to inherit the configuration schema (as that is not part of the PHP class or even its annotation). (Perhaps core could be 'fixed' to recognise this situation ... but there are more important things to work on!)
The solution is to simply copy the schema from the plugin we've extended - in our case, that was 'views.filter.bundle', found in core's 'views.filter.schema.yml' file within views' config/schema sub-directory. Wherever it is, it's probably named 'views.PLUGIN.ID', where 'PLUGIN' is the type of your plugin (e.g. field, filter, area), and 'ID' is the ID in the class annotation of the class your plugin extends. We pasted the schema into our own schema file - which can be named something like
/config/schema/mymodule.schema.yml, within our module's directory:
# Replace 'mymodule_special_type' with the ID in your plugin's annotation. views.filter.mymodule_special_type: type: views.filter.in_operator label: 'Customised type selector'
Once that file is in place correctly, I expect you just need to rebuild caches and/or the container for Drupal to happy again. Re-save the form, the error is gone :-)
Configuration schemas should normally help development by catching errors, but as I've written before, an incorrect schema can make things surprisingly difficult. I hope someone else finds this article solves their problem so it doesn't take them as long to figure out! I haven't used it, but it's possible that the Configuration inspector project could help you identify issues otherwise.
Once upon a time, we wrote an article about how to render fields on their own in Drupal 7, which was really handy because Drupal 7 wasn't always intuitive. It's common to want to display a field outside of the context of its main entity page, like showing author information in a sidebar block or in a panel, but you had to just know which functions to use. Drupal 8 has come along since then using 'grown up' things like objects and methods, which actually makes the job a little easier. So now we have this:
The short answer
Quick reference
I'll cover these in detail below, but here are the things you might want to be doing:
- Render a field as if it were on the node/entity page (e.g. with the markup from the normal formatter and full field template)
Drupal 7:
field_view_field(), optionally passing a view mode string, or formatter settings array.
Drupal 8:
$node->field_my_thing->view(), optionally passing a view mode string, or formatter settings array.
- Just get a single value out of a field (i.e. raw values, usually as originally inputted)
Drupal 7:
field_get_items() and then retrieve the index you want from the array.
Drupal 8:
$node->field_my_thing->get(0)->value, passing just the index you want to
get(). Properties other than 'value' may be available.
- Render a single value as if it were on the node/entity page (e.g. with the normal formatter's markup, but not all the wrappers that Drupal's field templates give you)
Drupal 7:
field_view_value(), optionally passing a view mode string, or formatter settings array, but always passing the actual items array.
Drupal 8:
$node->field_my_thing->get(0)->view(), passing just the index you want to
get() and optionally passing a view mode string, or formatter settings array to
The long answer
Now that entities like nodes are properly classed objects, and fields use the fancy new Typed Data API, we don't need to care about the underlying data structure for nodes or their fields, we can just call the method to perform the operation we want! You know, just what methods are supposed to be for! You want to view a field? Just call its 'view' method.
The output will be automatically sanitised and goes through the normal formatter for the field, as well as the regular field template. You can specify whether you want it rendered as if it were in a teaser or other view mode, by passing in the view mode string, just as we did with field_view_field(). (Or you might have used something like
$node->field_my_thing['und'][0]['value'] - in which case, go back and read our article for Drupal 7!)
Or even override the formatter to be used altogether (which is handy if the field would normally be hidden in any view mode):
'type' => 'image',
'label' => 'hidden',
'settings' => array(
'image_style' => 'larger_thumbnail',
'image_link' => 'content',
This does assume that your field ('field_my_thing') in my examples does at least exist on your node (even if it's empty). You may want to wrap the whole code in a try/catch block, just in case it might not.
For bonus points, you could load up the normal formatter settings, and tweak them:
use Drupal\Core\Entity\Entity\EntityViewDisplay;
// If you have the entity/node you want, use collectRenderDisplay() as it may
// already be statically cached, plus it goes through various alter hooks.
$display_options = EntityViewDisplay::collectRenderDisplay($node, 'teaser')
// Otherwise - or if you intentionally want to re-use the settings from another
// unrelated entity type, bundle or display mode - just load the display config.
$display_options = EntityViewDisplay::load('pagaraph.media_pod.teaser')
// Then tweak those display options and view the field.
$display_options['settings']['image_style'] = 'even_bigger_thumbnail';
This all assumes you've at least loaded your node, or other entity. (It works with any content entity, like terms, paragraphs, etc!) You'd probably be putting the result of the
view() method (which will be a render array) into a variable to be used in a twig template via a preprocess hook. Or maybe you're just adding it into an existing render array, like a custom block plugin. (Either way, you probably shouldn't then be rendering it into a string yourself, let Drupal do that for you.)
You can even just view a single item within a multi-value field like this, here using an 'if' condition to be a bit more graceful than a try/catch. This is the equivalent of using field_view_value() from Drupal 7, so also skips Drupal's full field template, though includes markup produced by the field's formatter:
// View the third value, as long as there is one.
if ($third = $node->field_my_thing->get(2)) {
$output = $third->view();
else {
$output = [];
That helps you see how you might get a single value too, with the
get() method, though note that it still returns a classed object. To just get a raw value, without any wrapping markup or value processing/sanitisation, you might want to use something like this, instead of Drupal 7's field_get_items() :
$text = $node->field_my_thing->get(0)->value;
// If the field is just a single-value field, you can omit the get() part.
$value = $node->field_single_thing->value;
// Some types of field use different properties.
$url = $node->field_my_link->uri;
// You can use getValue() to get all the properties (e.g. text value + format).
$text_values = $node->field_formatted_text->getValue();
// References can be chained!
$referenced_title = $node->field_my_reference->entity->field_other_thing->value;
In Drupal 7, there was also confusion around what to do for multilingual content. In Drupal 8, as long as you've got the translation you want first, all the methods I've discussed above will get you the appropriate values for your language. To get a specific translation, use:
if ($node->hasTranslation($candidate)) {
$node = $node->getTranslation($langcode);
This Rocks.
You get to use proper modern methods on a proper typed data API. Sanitisation is done for you. You don't need to care what the underlying data structure is. And you don't need to remember some magic global procedural functions, because the methods are obvious, right there on the thing you want to use them on (the field item class). If the Drupal 7 version of this was brilliant, that makes the Drupal 8 version of this even better. Brilliant-er?
If you don't have access to the file system on the server for a Drupal site, when a security issue like Drupalgeddon2 comes along, you are entitled to panic! Many sites are run by a combination of teams, so sometimes you really don't have control over the server... but that might even mean there is another way to apply fixes. If you've been tasked with updating such a site (I was!), it's worth checking if the server has been misconfigured in such a way to actually allow you to patch Drupal, via Drupal!
A heavy caveat first: we would never recommend servers to be set up like this, nor that you try this without extreme care!
Instead, you should do everything you can to get secure access to the server and communicate with those responsible for managing it. But I realise that sometimes you just have to be practical and improvise, building on many years of experience. For special occasions like this, I suggest making it very clear to anyone around you that you are now proceeding to do something very dodgy. Perhaps wear an enormous sombrero, or play overly dramatic music across the office?
So, assuming you can at least log in as an admin, first get the Devel module installed. It can slow site down, and may be a security risk in itself, so it shouldn't normally enabled on a production site, but it's common to at least be in the codebase, since it's so useful during development. If it's not listed on the modules page, maybe your site allows modules to be uploaded through the interface at /admin/modules/install ? (That functionality always scares me...) Anyway, get Devel installed if you can, and ensure you have 'Access developer information' and 'Execute PHP code' permissions.
That last bit hints at where we're going with this... head to /devel/php and you'll find you can run PHP code from a text box! Now, the next bit relies on a server misconfiguration. Web servers "should not have write permissions to the code in your Drupal directory" ... but maybe yours does. Find out by attempting to modify the code of one of Drupal's files. For Drupal 7, that means putting the following code into the box and submitting it. For Drupal 8, you can do something similar, but the text being replaced here will need to be something that else that does exist in a file.
// Load up Drupal's bootstrap file.
$file = DRUPAL_ROOT . '/includes/bootstrap.inc';
$content = file_get_contents($file);
// Add a comment to the end of one of the lines.
$hacked = str_replace('drupal_settings_initialize();', 'drupal_settings_initialize(); // james was here', $content);
// Replace the file.
file_put_contents($file, $hacked);
// Print the contents of the file.
If you see 'james was here' in the response, you will be able to hack a patch into Drupal a bit like this! If not, congratulations, your web server is correctly configured not to allow this!
I won't put the actual fix here, as you can probably figure it out from there - you'll probably want to use
str_replace() similarly on the appropriate file(s) that needs patching, for example. (If you can't figure it out, then you probably shouldn't trust yourself with this level of 'hacking' a fix onto a site!)
To verify your fix, you can do the following:
- Go to /devel/php?%23that=thing&other=this
- Run
- The resulting array should only have the 'q' and 'other' parts, as the '%23that=thing' part should have been stripped by the security fix.
If you really want, you can add
$conf['sanitize_input_logging'] = TRUE; to your site's settings.php file (or
$settings['sanitize_input_logging'] = TRUE; for Drupal 8) to log any unsafe inputs that get tried against your site (whether you've applied the fix the proper way or like this). That would show the '%23that=thing' up under /admin/reports/dblog , if you've got database logging enabled.
Drupalgeddon2 happened! We got all but two of our projects updated within an hour, with those remaining trickier two fully patched another hour later. The key was planning the right process using the right tools. We actually use these tools for regular deployments every day, but speed was essential for this security update. Here's what we did, since some of you may be interested.
Our on-call developers split up the various sites/environments/projects that would need updating amongst themselves, using a simple online shared spreadsheet.
Ahead of time, we prepared pull requests for sites that simply use Drush make files to specify core versions. (We prefer to keep 3rd-party code, including Drupal core, out of our main project repos, by using composer or Drush make to bring in those dependencies during a build step, executed by Jenkins.) The diff for these would be something as simple as this:
diff --git a/build/stub.make b/build/stub.make
index 03599c39..ff084895 100644
--- a/build/stub.make
+++ b/build/stub.make
@@ -3,7 +3,7 @@ api = 2
; Drupal core.
projects[drupal][type] = core
-projects[drupal][version] = 7.57
+projects[drupal][version] = 7.58
We monitored all the potential places that the security fix could become available. We noticed it first appear from https://ftp.drupal.org/files/projects/drupal-7.58.tar.gz (we just guessed the filename, as we knew what the release number should be). This meant we were even a few minutes ahead of public announcements actually being made.
We assessed the changes that make up the security fix immediately, so we knew whether to fast-track patches directly onto live servers, or if we can use our normal build process. We decided almost all of our sites could be done normally, and we kept an eye out for whether the build steps might become a bottleneck (but we knew that would probably be fine, which was indeed the case). The fix was trivial enough that we didn't need to add any testing to what has already been done by those producing it for Drupal core. In any case, this security risk was so critical, that it was better to risk breaking site functionality than to delay applying the fix. We now know that there were no issues introduced by the changes anyway.
We merged any of those pre-prepared pull requests that could be done immediately.
Projects with a composer-based workflow (i.e. Drupal 8 ones) would include a hash change in the composer.lock file, so they couldn't be done ahead of time. As soon as the update was available, we ran this command for each project, which relied on correct version constraints already being in place (e.g.
"drupal/core": "~8.4.0":
composer update drupal/core symfony/* --with-dependencies
We included all symfony dependencies because we were aware that they can block updates - see Jeff Geerling's excellent write-up. For most sites, that simply changed the composer.lock file to use the new Drupal version, so that could be committed & pushed immediately. Some sites would get a files from core updated, which we wanted to avoid changing (e.g. robots.txt), so we'd revert those.
A few of our projects (usually ones adopted from previous owners) keep the whole site codebase (bespoke code + Drupal core & all dependencies) within their main repo. The changes would be very quickly reviewed manually then committed & pushed. For those, the build process is then mostly about verification & deployment.
Most of our projects have a build process (using Jenkins) that gets immediately triggered on new commits being pushed to their branches that production/staging environments use. This usually means building a 'full' codebase that includes our bespoke repo, together with the dependencies specified by the composer or drush make files. We use Docker containers and appropriate version constraints to ensure the build process are correct for each project's actual target servers. (For example, building for the appropriate version of PHP, as some dependencies may be different for PHP 7.x or 5.x.) As soon as the full codebase is built, that usually gets automatically deployed to the live sites, and then scripts are ran to apply any necessary updates or clear/rebuild caches. (These scripts, as well as the build tasks themselves, are all part of each project's main code repo.) Builds all run in parallel, so we could update many many sites at the same time.
We use Aegir to manage some projects - in particular, one hosts around 1000 sites. Doing the 'correct' thing and building a new platform and migrating each site would have taken too long, so we went ahead and directly applied the patch to the platforms of code from the command line.
We have since updated the git repo for the project as above, and done a 'correct' migration of the entire platform in Aegir.
We would then quickly check the live site is actually running the updated version of Drupal, which is reported under /admin/reports/status .
Using this process, all but two of our sites were updated within an hour. As you can see, this relies on having a strong and reliable build/deploy process set up. Admittedly, there's a few sites which we have to do a little more manually as we don't have the same level of control over their servers (e.g. where we work together with a team at a client that manages that part of a project). Most of those required SSH and a git pull.
We also encountered these extra issues along the way. What issues did you run into? Let us know in the comments, let's all learn from each other in case there's ever another tricky core update!
A site that we consult for, but aren't the primary developers for, uses some core patches, which would need some re-rolled versions to be compatible with the newer version of core. Those patches were just removed initially to get the security fix out quickly, as that was way more important than the functionality that those patches were fixing.
We use composer gavel to ensure we are all running the latest version of composer, to reduce merge conflicts in composer.lock . That requires PHP 7, but we had one D8 site on PHP 5.6 (enforced via the platform section of composer.json), so I had to find a workaround, now fixed here.
We had to be creative to deploy the fix to one site, which we didn't have proper server access to. The client's own team manages its deployments, but they were not available. The next article in this series covers that special case, if you're interested! It wasn't one for the faint-hearted...
In Drupal 7, we had menu_get_object() to get the object in code for whichever node, term, or whatever entity makes up the current page. But in Drupal 8, that has been replaced, and entities can usually be operated on in a generic way. If you know the type of entity you want, it's pretty simple. But I keep running into different suggestions of how to get the entity object when you don't know its type. Given that D8 allows us to deal with entities in a unified way, I thought there must be a good way! The code of core itself is usually the best place to look for examples of how to do common things. I found that the content translation module has a smart way of doing it, which I've adapted here for more general use:
* Helper function to extract the entity for the supplied route.
* @return null|ContentEntityInterface
function MYMODULE_get_route_entity() {
$route_match = \Drupal::routeMatch();
// Entity will be found in the route parameters.
if (($route = $route_match->getRouteObject()) && ($parameters = $route->getOption('parameters'))) {
// Determine if the current route represents an entity.
foreach ($parameters as $name => $options) {
if (isset($options['type']) && strpos($options['type'], 'entity:') === 0) {
$entity = $route_match->getParameter($name);
if ($entity instanceof ContentEntityInterface && $entity->hasLinkTemplate('canonical')) {
return $entity;
// Since entity was found, no need to iterate further.
return NULL;
// Example usage:
$entity = MYMODULE_get_route_entity();
$entity_type = $entity ? $entity->getEntityTypeId() : NULL;
Routes for entities keep the entity under a parameter on the route, with the key of the parameter being the entity type ID. Looping through the parameters to check for the common 'entity:...' option finds identifies the parameters that you need. The above code will only work for the current path. But if you can get hold of the route object for a given path/URL, then you could do something similar.
It seems a shame to me that this isn't any simpler! But hopefully this at least helps you understand a bit more about routes for entities.
TL;DR: Define a schema for any bespoke configuration, it's not too hard. It's needed to make it translatable, but Drupal 8 will also validate your config against it so it's still handy on non-translatable sites. As a schema ensures your configuration is valid, your code, or Drupal itself, can trip up without one.
Recently I updated a simple configuration setting via some PHP code, and Drupal immediately threw up an obscure error. It turned out to be because the custom setting that I updated didn't have a configuration schema.
Schemas are used to instruct Drupal 8 what format every piece of a configuration object should be in. For example: text, integers, an array mapping, etc. That means they can be used to validate configuration, keeping your code and Drupal's handling of the settings robust and predictable. It also allows the configuration to be translated, but that wasn't on my radar for this particular project.
If you're aware of the variable module for Drupal 7, then you can think of configuration schemas as the equivalent of
hook_variable_info(), but written in YAML.
Here's an example of the sort of thing that's needed, to go in
type: config_object
label: 'My custom settings'
type: text
label: 'Message to display on page XYZ'
type: 'integer'
label: 'Behave differently when this is 0, 1, or 2'
The pragmatic reality is that you may well get away fine without giving your configuration a schema. But you may also run into obscure issues like I did without one, which are very hard to understand. I could get around the problem by skipping the validation by passing the 'has_trusted_data' parameter to my
$config->save() call as
TRUE. Clearing caches may also resolve your problem.
But ultimately, those workarounds just plaster over the cracks. Set up a schema and you avoid those problems, and get robust validation for free. Hopefully my example YAML above shows how it can be quite simple to do.
As a bonus, PhpStorm can even give you autocomplete suggestions for configuration names that have a schema defined for them, when typing
I've previously written about dynamic forms in Drupal, using
#states to show or hide input boxes depending on other inputs. Since then, Drupal 7 and 8 have both got the ability to combine conditions with OR and XOR operators. This makes it possible to apply changes when a form element's value does not equal something, which is not obvious at all. It's easy to show something when a value does match. This example shows a text input box for specifying which colour is your favourite when you chose 'Other' from a dropdown list of favourite colours.
$form['other_favorite'] = [
'#type' => 'textfield',
'#title' => t('Please specify...'),
'#states' => [
'visible' => [ // action to take.
':input[name="favorite_color"]' // element to evaluate condition on
=> ['value' => 'Other'], // condition
But there's no 'opposite' of the
value condition, e.g. to show an element when a text box has anything other than the original value in it. Using the 'XOR' condition (which only passes when some conditions are passed, but not all of them), you can combine the value condition with something that will always pass, in order to simulate it instead. Something like this, which shows a checkbox to confirm a change when you enter anything other than your previous favourite food:
$form['confirm_change'] = [
'#type' => 'checkbox',
'#title' => t('Yes, I really have changed my mind'),
// Use XOR so that current_pass element only shows if the email
// address is not the existing value, and is visible. States cannot
// check for a value not being something, but by using XOR with a
// condition that should always be true (i.e. the latter one), this
// will work. Note the extra level of arrays that is required for
// using the XOR operator.
'#states' => [
'visible' => [
[':input[name="favorite_food"]' => [
'value' => $original_favorite_food,
[':input[name="favorite_food"]' => [
// The checked property on a text input box will always come
// out false.
'checked' => FALSE,
Hopefully the comments in that snippet make sense, but essentially it's about picking a second condition that will always pass, so that the combination only passes when the first condition fails. So in this case, whenever the value does not equal the original value. By the way, you can do much more than just show or hide form elements, for example, enabling or disabling - see the full list.
In Drupal 8, there are many ways to interact with configuration from PHP. Most will allow you to write the config values, but some are read-only. Some will include 'overrides' from settings.php, or translations. There are many layers of abstraction, that each have different purposes, so it can be easy to run into subtle unintended problems (or more obvious errors!) if you pick a method that doesn't quite suit what you actually need to do. Here I'm going to outline some of them and when you might pick each one.
For almost all of these suggestions, you can call the
->get($sub_value) method on them to get specific sub-values out of the returned object/data. Also, wherever I have written
$config_factory, that can be set in a number of ways. For example, either
\Drupal::configFactory() (for global code, such as in module hooks), or preferably via dependency injection (which probably means passing the 'config.factory' service into the constructor to set a protected property to access it from). The
$config_storage variable is similar, corresponding to the 'config.storage' service.
Here's a summary of my suggestions, by when you might want to use them:
That table deliberately leaves out a suggestion for getting writable config with overrides. That is because any type of config override may be updated in different ways. You may have to change values in the settings.php file, which you cannot do from the UI. Or use the core Configuration Translation module. Writing to config, including their overridden values, is outside the scope of this article ... and is outside the way that config is supposed to be used anyway! Read the documentation on drupal.org for more details, which includes how to deal with specific sets of overrides, like specific languages, or from modules.
Let's go into some detail about these ways of getting configuration.
\Drupal::config($id) or
$config_factory->get($id) (and its equivalents - see note below) is the simplest method, which will give you the current actual values of the config, including any translations or overrides from settings.php. But it gives you an 'immutable' configuration object, that can only be read, so is no good for updating. That is deliberate, since at least some sorts of config overrides can't be directly updated anyway. If you try to get a piece of config this way that doesn't exist, the config object you get can still be used, but won't hold any values. You can test for this by either checking that
$config->isNew() is
FALSE or simply that
$config->get() is empty.
You can exclude overrides using the
->getRawData() method on the config object if you really want an immutable version without them.
$config_factory->get($id) is also identical to
\Drupal::config($id), which is even simpler, but should only be used from 'global' code like hooks in
.module or
.theme files.
Controllers that extend
ControllerBase or forms that extend
FormBase have
->config($id) methods that do almost the same thing too.
Forms that extend
ConfigFormBase are a bit special though, in that config returned by that method for any names returned by their
->getEditableConfigNames() implementations will be editable.
This will get you an 'editable' (mutable) piece of config, so guarantees you can set & save values on it (or even delete it). That does mean it won't contain any overrides, so may not be the actual values that would get used for pages, e.g. from settings.php or translations. However, note that as it guarantees an editable object, so you can call it for config that doesn't actually exist, which you might not want. See the above section about
\Drupal::config() for checks you can use, or skip to my next suggestions.
On this editable config object, you can even call the
->getOriginal() method to get back to the original loaded data from before any values were changed, using its parameters to do so for specific sub-values or to ignore any overrides.
If you want to check for the existence of some config, but don't need to make changes to it, this is the one to use. It might have been nicer if there was a
->load() method for just loading single items of config, but
loadMultiple() will have to do, just make sure to pass it an array :-) You will get an array back, which will be empty if the config doesn't exist, and otherwise contains immutable config objects, just like my first suggestion from above,
$config_factory->get($id). It will also include any overrides like translations.
As mentioned previously, you can use the
->isNew() or
->get() methods to check when config returned from its factory does not actually exist. If you really want to, you can interrogate the config storage service directly, to get back the array of existing config data, or
FALSE if it doesn't. This gets a bit more low-level though, so I'd recommend against it really. As it returns an array, to actually save into a config object, the best option is to get the editable version from the factory as above and check
->isNew() is
FALSE, or look into what the factory does with the data read from its storage.
Hopefully that helps you understand which method you might want to use for various needs, and why.
In a recent Drupal 8 project, one of the site's vocabularies had several thousand terms in it (representing airports), which caused its admin listing page to run out of memory before it could render. I wanted to solve this without affecting any other vocabularies on the site, and improve the listing itself along the way to be more useful, with filters to make searching it much easier. The original listing is not a view, and loads much more than it needs to. For comparison, here's an example of how core's original vocabulary listing can look: ...and then here's the customised page for airports, with the filter and an extra column (corresponding to the term names and descriptions) that I wanted to head towards, below. You can download an export of the view configuration that I ended up using (for importing on a D8 site at /admin/config/development/configuration/single/import, though you will need an 'airports' vocabulary to exist). I thought of a few approaches:
Just limit the number of terms listed
Change the
terms_per_page_admin setting in the
taxonomy.settings config, which defaults to 100 (there is no UI for this):
\Drupal::configFactory()->getEditable('taxonomy.settings')->set('terms_per_page_admin', '25')->save();
But this would have affected all vocabularies, and couldn't allow improvements like added filters.
Override the controller for the existing route
Use my own controller on the existing
entity.taxonomy_vocabulary.overview_form route (probably via a
RouteSubscriber). So the page would no longer directly use the form that is specified in the
taxonomy.routing.yml file, but instead a custom controller that would call through to that form for all other vocabularies and then do something else (probably embed a view) for my airports taxonomy. But that would mean changing the very definition of the existing route, which I want to remain in place for other vocabularies really. That might have been okay, but the route I went down was as follows...
Use a new view just for the vocabulary
Create a new page with views to be used on the specific path for airports (i.e. '/admin/structure/taxonomy/manage/airports/overview'). Behind the scenes, that means a new route would be set up, so there would be two routes valid for one path, but the more generic one from core would use a wildcard, whilst mine would specify the vocabulary in its path so it gets used ahead of core's listing, just for this vocabulary. The definition of the existing route from core could then remain untouched.
But of course it's never quite as easy as you think...
Once I'd set up this view, with the nice filters I wanted to add, and visited the taxonomy listing, I got a cryptic error message:
Symfony\Component\Routing\Exception\MissingMandatoryParametersException: Some mandatory parameters are missing ("taxonomy_vocabulary") to generate a URL for route "entity.taxonomy_vocabulary.devel_load". in Drupal\Core\Routing\UrlGenerator->doGenerate() (line 171 of core/lib/Drupal/Core/Routing/UrlGenerator.php).
This turns out to be caused by the view having a specific path, that does not use the vocabulary wildcard that one of the tabs on the page expects. This is the sort of thing I thought I'd avoid by leaving the original route intact. Disabling the Devel module, which provided this particular tab, does solve that issue, but the view now has no tabs, because the tabs are attached using the original route name, not mine. So, where to now? I thought of three ideas:
Force my view to use the same route name as the original route. But then I would lose the original route, which I don't want to do for the reasons outlined above already.
Loop over all tabs that are attached to the original route, in a
hook_local_tasks_alter(), to attach them to my route instead. But then all the other vocabularies would lose their tabs too! I could work around that with some more wrapping code, but this idea became less attractive as I thought about it.
Attach my view as a tab to the original vocabulary listing. This would create a duplicate 'List' tab, but I figured that would be the easiest problem to solve.
That last option needed a few hooks to be implemented, and to remove the duplicate tab that you can see in the above screenshot...
* Implements hook_local_tasks_alter().
function MYMODULE_local_tasks_alter(&$local_tasks) {
// Views places the airports view below the vocab edit form route, but that
// then stops the fields UI tabs showing up.
if (isset($local_tasks['views_view:view.airports.page_1'])) {
$local_tasks['views_view:view.airports.page_1']['base_route'] = 'entity.taxonomy_vocabulary.overview_form';
* Implements hook_module_implements_alter().
function MYMODULE_module_implements_alter(&$implementations, $hook) {
if ($hook === 'local_tasks_alter') {
// Move MYMODLE_local_tasks_alter() to run after
// views_local_tasks_alter() as that sets up the base routes.
$group = $implementations['MYMODLE'];
$implementations['MYMODLE'] = $group;
This one ensures only one of the routes that is valid for the 'List' tab actually shows. Otherwise both tabs will show, despite them both linking to the same URL!
* Implements hook_menu_local_tasks_alter().
function MYMODULE_menu_local_tasks_alter(&$data, $route_name, $cacheability) {
if (isset($data['tabs'][0]['views_view:view.airports.page_1'])) {
/** @var \Drupal\Core\Url $url */
if (isset($data['tabs'][0]['entity.taxonomy_vocabulary.overview_form']['#link']['url'])) {
$url = $data['tabs'][0]['entity.taxonomy_vocabulary.overview_form']['#link']['url'];
$params = $url->getRouteParameters();
if (isset($params['taxonomy_vocabulary']) && $params['taxonomy_vocabulary'] === 'airports') {
else {
else {
This now allows the new custom filterable view and all the tabs to show up correctly for my airports vocabulary, and leave all other taxonomies as they were. But to have it working with the dynamically-generated tabs that the Devel module adds, I still needed a little more secret sauce. Potentially other modules could be doing similar (and I wanted to have Devel enabled!), so I had to go further down the rabbit hole...
Injecting a raw variable into a route
The route provided by core's taxonomy module uses this definition:
path: '/admin/structure/taxonomy/manage/{taxonomy_vocabulary}/overview'
See the
{taxonomy_vocabulary} part? That's a wildcard that matches the particular vocab to be listed, and Devel module used it to generate its tabs. I needed that wildcard 'variable' to be defined on the route for my new views page, and for it to have the value 'airports'. This meant altering the definition of the route with a
RouteSubscriber but also then injecting the variable's value at run time, or more specifically, at the point of reacting to the request. RouteSubscribers are just event subscribers, and event subscribers can react to multiple events so I added a method to listen to the
KernelEvents::REQUEST event, since that's when a route would be matched on visiting any page on a Drupal 8 site. Here's the service definition, which goes in MYMODULE.services.yml and then the whole code of the class file (which is also listed at the end of this article for download):
class: Drupal\MYMODULE\EventSubscriber\RouteSubscriber
- { name: 'event_subscriber' }
namespace Drupal\MYMODULE\EventSubscriber;
use Drupal\Core\Routing\RouteSubscriberBase;
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\Routing\RouteCollection;
* Adds the unused parameters of the regular taxonomy overview to the current
* route match object when it is the airports view so that other routes
* (specifically, for the Devel load local task) can still find them.
class RouteSubscriber extends RouteSubscriberBase {
* Adds default to the airport view to match the original vocabulary overview.
* @param \Symfony\Component\Routing\RouteCollection $collection
* The route collection for adding routes.
protected function alterRoutes(RouteCollection $collection) {
if ($route = $collection->get('view.airports.page_1')) {
$route->setDefault('taxonomy_vocabulary', 'airports');
* Adds variables to the current route match object if it is the airport view.
* @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
* The response event, which contains the current request.
public function onKernelRequest(GetResponseEvent $event) {
$request = $event->getRequest();
// Deliberately avoid calling \Drupal::routeMatch()->getRouteName() because
// that will instantiate a protected route match object that will not have
// the raw variable we want to add.
if ($request->attributes->get(RouteObjectInterface::ROUTE_NAME) === 'view.airports.page_1') {
if ($raw = $request->attributes->get('_raw_variables', array())) {
$raw->add(array('taxonomy_vocabulary' => 'airports'));
$request->attributes->set('_raw_variables', $raw);
* {@inheritdoc}
public static function getSubscribedEvents() {
$events = parent::getSubscribedEvents();
// The route object attribute will have been set in
// router_listener::onKernelRequest(), which has a priority of 32.
$events[KernelEvents::REQUEST][] = array('onKernelRequest', 31);
return $events;
The class file should be placed at
MYMODULE/src/EventSubscriber/RouteSubscriber.php. The
alterRoutes() method is the normal one for a
RouteSubscriber. That's where I alter the definition of the route (if it exists, since it won't until the view is actually created!). Then I've added a
onKernelRequest() method, with a priority that means it runs after the request has been matched to a route. This sets the 'taxonomy_vocabulary' raw variable on the incoming request. Admittedly, that last part feels quite wrong! I'd love to know if there's a better way of doing all this. Perhaps I should have gone down the idea of swapping out the controller for the original route after all? But should you ever need to replace the listing for a specific vocabulary, at least you can now follow on from this. Debugging through the Drupal & Symfony internals where requests are matched to routes is not for the faint-hearted! I've learnt a lot along the way around it, which hopefully means I'll be ready for my next obscure D8 challenge!
I've recently been playing around on Windows 10's new Bash on Ubuntu feature, which provides a native bash linux shell for Windows. I thought I'd try and get a Drupal 8 site running, via composer, served from Apache, without any need for a virtual machine in Windows. Kristian Polso wrote a great tutorial that got me most of the way there, but I wanted to share some extra discoveries and thoughts about the experience.
Skip straight down to the 'Lessons learned' section below for some conclusions I've made from going through this process, if you're not interested in the fine details about how I got it working.
There is probably a better way!
Before I go any further - yes, I realise there are various fully documented, better supported ways of running Drupal on Windows, but I just wanted to go through this to see if it could be done - and if I could do it. While it all turned out to be achievable, it is not necessarily the best way, but it offers potential, especially for those of us used to working with linux or apache. So this write-up is for the benefit of others that are exploring the idea too, as it is worthwhile, but is not necessarily a recommendation! After all, Microsoft themselves have not built bash on windows to replace SQL Server, Azure, IIS, or anything like that. Those things are built (and supported) for good reason, they are not being replaced.
I didn't try running PHP-FPM in the web server stack, which I know has many advantages.
The installation
As Kristian says:
Roughly speaking, the necessary steps for this are:
- Enable Linux functionality
- Install LAMP
- Install Mysql database for Drupal
- Install Composer
- Install Drupal 8
My installation didn't go quite as smoothly as that. I had to restart after enabling Windows 10's developer mode before I could even find the 'Windows Subsystem for Linux' feature to enable. I also accidentally found myself using the linux root user, which composer (rightly) warns is best to avoid for security reasons. So when setting your linux username, it's best to avoid using 'root' and actually pick a regular username.
Windows 10's built-in web server (IIS, or 'Internet Information Services') was listening on port 80, which is the normal port for a web server, but I wanted apache to do that instead, so I disabled the 'world wide web publishing' service.
I decided to try using PHP7, which meant adding a 'PPA' (i.e. a 3rd-party repository) for it, which the internet helped me with. At this point, I was in linux-land so there was no need to go searching for how to do it with windows, I could just learn from the linux community. That's a big bonus of this feature!
sudo add-apt-repository ppa:ondrej/php
sudo apt-get install apache2 php7.0 libapache2-mod-php7.0 mariadb-server php7.0-mysql php7.0-cli php7.0-gd git zip
That includes a few PHP extensions, plus apache, git and zip. I needed to enable apache's mod_rewrite too, as follows (lifted directly from the documentation on drupal.org) :
For Ubuntu Server 10.04, one needs to edit the file: /etc/apache2/sites-enabled/default
If there is no such file, try this one: /etc/apache2/sites-enabled/000-default
sudo a2enmod rewrite
sudo nano /etc/apache2/sites-enabled/default
Change (line 7 and line 11), "AllowOverride None" to "AllowOverride All".
Save and exit.
Restart Apache
sudo /etc/init.d/apache2 restart
I decided to use the 'Composer template for Drupal projects' to install Drupal, which uses the Drupal Scaffold composer tool. On running it, it told me that yet more PHP extensions would be needed, which I hadn't anticipated. That may have just been the version required with drupal-scaffold, I'm not sure, but they seemed sensible to me, so install them with:
sudo apt-get install php-xml php7.0-mbstring
Once I'd set up my LAMP stack according to Kristian's guide, and installed composer, I was ready to actually create the composer project, which was the point at which I diverged from his guide to install Drupal differently. I ran this within a new subdirectory in apache's
/var/www directory:
composer create-project drupal-composer/drupal-project:8.x-dev . --stability dev --no-interaction --prefer-dist -vvv
This actually timed out whenever trying to download Drupal core (which the extra verbosity
-vvv parameter helped me identify), but I discovered that composer could simply resume installation by running:
composer install
Composer is smart!
I then used drush to actually install Drupal:
drush site-install --db-url='mysql://root:[email protected]/my_database_name' --site-name=Example
Once all that was done, I decided to set up vhosts in the regular way I would for apache on linux. Then I could access my site in the browser, at a development domain. It works, and just as if I was on any unix-based machine!
Lessons learned
Whilst it does work, I learnt a good few things along the way.
Performance should theoretically be better than using a VM, but I haven't done any benchmarking of any sort, so I don't actually know.
Using composer and drupal scaffold works really well, as long as you don't mind resuming a timed-out download.
Setting up apache is always a little tricky, regardless of the host system. That is a drawback of doing things this way rather than using a purpose-built tool, such as Acquia Dev Desktop.
The biggest problem for me was when I started trying to edit files in the linux file system from Windows applications (e.g. notepad). I thought that would be easier for editing
/etc/hosts and the vhost configuration files, than doing so from a terminal-based editor like
vim. But doing that clobbers the linux file attributes/permissions. Files edited that way can end up no longer visible in bash, and the web server doesn't recognise changes. I had to delete them from Windows and recreate them within bash.
If those files can't be edited from outside bash, that would make using an IDE or xdebug, which, in my opinion, are absolutely necessary for modern Drupal 8 development, rather trickier. (I'll accept many people disagree with the need for those!) I expect solutions can be found, like using a shared file server (e.g. Samba?), but this is a big enough step to me to feel like other ways of running Drupal on Windows are more appealing.
If I was to continue looking to get a Drupal development environment working well on Windows, I think I'd pursue trying Parrot (since that's what I use normally on my work Mac anyway), Acquia Dev Desktop, or a different vagrant/VM-based solution. I've not tried a 'normal' Windows server stack (e.g. IIS) yet either, as I'm aware there are many differences that I want to avoid having to work around. I expect each potential solution will have its own set of benefits and trade-offs.
What are your experiences of running Drupal on Windows recently? What has it been like, what would you recommend?
Languages make everything so complicated! I just discovered the autocomplete bit on my reference fields wasn't working - until I tried searching by another language, and then the results gave me a surprise:
My homepage, originally in French, has had an English translation added, but whilst editing in English, I had to search with the French title in order for the page to come up... with the English title! Here's how to solve this...
My site uses Entity translation and the Title module (which I usually recommend as the best way of making your site translatable), and this field is an Entity Reference field. The field is configured to use the 'simple' selection method, which is the default.
Instead, I needed to use Views to build the list of content that can be referenced, to be used instead in that 'Mode' setting. Create a view, add an 'Entity Reference' display, and add the 'Field: title' field (use the search box to make this easier), instead of the 'Content: title' field that gets added normally. The following screenshots should help show how to do this:
This 'Field: title' field is the properly translatable field that the Title module sets up, whereas 'Content: title' is the original title that is not truly translatable. I also suggest adding the filter for 'title_field:language', filtering to the current user's language -- otherwise the autocomplete search will search across all languages! (So in my example, I could search for either "Homepage" or "Page d'accueil" to find the page.) By the way, if you want reference taxonomy terms, instead of using 'Field: title', you'll want 'Field: name' (and the name_field:language filter) instead. Some other entity types use other wording too.
Next, configure the settings in the format section of the admin screen to use the new field for searching. Finally, once you've saved the view, edit the reference field and change that mode setting from using the simple method to instead use views, and pick the view you just created.
This now works, I can search for 'Homepage' and get 'Homepage' as a suggestion in the autocomplete, as I had originally expected! It's bizarre, but hopefully this discovery and solution might help someone else :-)
This also demonstrates how you can use views to search for other fields on content, not just their titles - this isn't just for translatable sites! You can even use a different combination of fields in the view to change what's displayed in the suggestion box (e.g. adding product codes, images, etc).
Several of our recent projects have involved setting up languages that feel like 'child' languages of other languages, for a variety of reasons. Sometimes it's for marketing, so that content can be overridden for markets using a specific currency, other times it's to target a specific audience. A traditional example would be Canadian French - where most content would be the same as French, but some pages would want different spellings or customisations. We have done similar to set up special cases of regular English: British English and Euro/€ English - to allow specific content per-location or currency. Amazee Labs' work on 'total language fallback' inspired us to work on the Language Hierarchy project.
The concept of these projects is to allow content in languages to inherit or fallback from each other. So an editor can set content up in one language, and then sub-languages would just use that content unless it is specifically overridden for them. That gives editors much more flexibility as they don't have to go back & forth keeping very similar content in sync, and more power as they can reach wider audiences whilst also being able to target specific markets.
The Language Fallback project breaks anonymous page caching (with Varnish), due to the way it integrates with the Smart IP project to geolocate users, so this wasn't an option for us, as the majority of our clients' sites traffic is anonymous. Instead we turned to Language Hierarchy, which integrates with a wider range functionality across Drupal core & contributed modules anyway anyway:
We contributed additional work to bring over some functionality from the Language Fallback project that was lacking, and added further features too:
This brings the Language Hierarchy project to the point where everything possible on our site could inherit translations from a parent language. We even ended up becoming co-maintainers of the Language project to help turn all this into a new release (7.x-1.4). Hopefully a common roadmap can be found with the Language Fallback project, as the two are incompatible. If you're interested in ensuring the Drupal community gets the best solution, especially as all this moves towards Drupal 8 (a basic port has begun), then do add your comments to that issue.
TL;DR: If the internet reflects society, what does your content say about you? Having flexible and accessible content will allow you to adapt to how people consume your content.
"I miss when people took time to be exposed to different opinions, and bothered to read more than a paragraph or 140 characters. I miss the days when I could write something on my own blog, publish on my own domain, without taking an equal time to promote it on numerous social networks; when nobody cared about likes and reshares.
That's the web I remember before jail. That's the web we have to save."
I recently read the trending article The Web We Have to Save, by blogger Hossein Derakhshan ('Hoder'), who had been imprisoned in Iran for six years. In the article, he talks about how the internet had changed over that time. Content on the internet has been consumed and discovered in different ways over time, from directory listings, to search, to blogs, and social media. The writer had been an influential blogger (credited with starting the blogging revolution in Iran), but on coming out of jail, he found that quality blogs no longer had the position they once did. Instead, content is largely discovered and read by people in 'streams' on social media apps. Quality can be drowned out; what is important is diluted in amongst the trivial.
Personally, I believe any expression of culture will reflect the society it flows from. The internet is a global society, so incorporates so many different aspects of humanity - different, good, and bad. We see how cultural expressions can reveal something about a society in the news all the time. Music is a creative expression that will inevitably present the good and bad of a culture. Each genre of music often goes together with a subculture, so certain themes come up, often telling the stories of the society that the subculture represents. Football fan culture around the world is another classic case. In one country, it might be dominated by middle-class, sanitised and highly commercialised. In another, it is raw, dangerous and associated with criminal activity. The internet has become one of the biggest and most global cultural expressions ever known. While it is diverse, the way we consume it is perhaps becoming less so - which Derakhshan has been in a unique vantage point to spot.
What does the internet say about our global society? If you contribute to the internet with websites and social media activity, what do your contributions say about your place in the world and how you relate to it? I believe that we should all take responsibility to some extent -- especially those of us in the business of websites and content on the internet! Can we contribute to a more responsible internet? Are we equipped to do so?
A responsible internet should be diverse and inclusive. I believe a responsible internet could be positive and encouraging, with negative and destructive aspects present but drowned out by the good that the world has to offer. Maybe that's utopian, but it really does come down to decisions made that shape our content and the way we present it. Content should be accessible to any user, on any device. That means thinking of users with accessibility needs, thinking of users that do not speak English as their first language, and building solutions that will be future-proof to some extent.
Drupal, with its community-driven ecosystem of modules, can cater for accessibility and internationalization needs. Drupal 8 will be the most translatable product yet, and will also have responsive design for any screen included 'out of the box'. At ComputerMinds, we have experience in getting the most out of Drupal, and going well beyond its core capabilities, to maximise how 'responsible' our websites are in each of these areas.
Part of being responsible is being prepared to constantly improve and yet to also have the foundations to aim to cope with potential change. ComputerMinds' bespoke Drupal websites are built with future-proofing in mind - user needs change over time, as does the infrastructure of the internet (servers, browsers, connections) - so we have to think ahead. A site built exclusively for the conditions of the current time may not last long, and will not be able to serve future visitors. We improve as individuals and as a team with every project we work on. Best practises are identified and developed, we work with the best tools & modules as they mature. We help move projects in the Drupal community along with support, fixes and improvements. We won't stand still, and we'll ensure that the websites we build will last despite the inevitable change of the internet.
As a content management system (CMS), Drupal is well placed to be equipped for a responsible internet. Content will always be the core of what users consume, in whatever form or wherever it appears, so it's essential to have a flexible & powerful CMS for highly manageable content. The Drupal CMS framework, paired with our own depth of experience in modelling content and giving power to editors, enables successful, and responsible, websites. An example of this, where we are also constantly improving, is our use of the new 'Paragraphs' system to make responsive rich content that works for site visitors and site editors.
The raft of SEO and social media integration modules for Drupal helps the content of a site to succeed outside of its own domain, whether it appears in users' social media 'Streams', or search engine results. We have plenty of experience in fine-tuning these modules and creating custom solutions to make content work for its intended audience.
So whether web needs saving or not, whilst the way we all consume content on the internet changes, the real key is to work towards a responsible internet. We (if you're reading this, I'm including you in!) are key contributors to that. Are you equipped to add to a responsible internet which is inclusive, diverse and high in quality? What tools and methods (technical or not!) do you use that help build towards that? Let us know in the comments below!
To complete my series on multilingual Drupal, here are some mini-lessons I've learnt. Some of them are are to improve the experience for administrators & translators, others cover obscure corners of code.
- Don't force everyone to use Drupal
- Administering translations
- Gather precise requirements to match translation models
- Language-specific styling
- Allow admins to edit any translation
- Get to know Drupal's language types & helper functions
- Using the entity metadata wrapper
- Options & limitations to define/use a fallback language
- Overriding core language functionality
- Ctools objects & caching issues
1. Don't force everyone to use Drupal
If there are numerous people involved in translating your site content, some of them may not need to use Drupal. It's not the best tool for every job! The default interfaces for lists of translatable strings and making translations are not great when dealing with thousands of strings - instead make use of Drupal's import & export functionality, and do the actual translating elsewhere when possible.
We have used shared google spreadsheets to list required translations, which our client can then enter translations into directly, or copy into from elsewhere. Spreadsheets are widely understood (at least to some extent) and can be fitted to suit the precise needs of all stakeholders. External translation agencies will often deal with spreadsheets, so don't force them to use Drupal's interface - or even the file formats imported & exported by Drupal. After all, while you're a Drupal expert, your partners in the translation workflow may not be! Although Lingotek's services might be an option worth exploring if you need some Drupal expertise in your translation workflow.
We have found POEditor to be a very good interface for dealing with translations, particularly in bulk. You can easily import the translation files exported from Drupal (.po or .pot) or in many other formats, and export translations for importing back into Drupal. This is useful when you need to convert file formats provided by external sources.
Having said that, it's quite possible to make the translation workflow within Drupal better...
2. Administering translations
While Drupal's standard interface for translations is a bit restrictive, it can of course be easily extended - here's just a few options:
- You can create views with 'Locale source' as a base table, to create your own list of translations.
- The Localization client (l10n_client) module gives a nice popup interface for editing translations 'on the fly'.
- While I haven't used it myself yet, the Translation Management Tool (TMGMT) module looks to provide a vastly better interface, making it easier to connect to external translation services, identify what is and is not translated, and unify many of Drupal's disparate translation interfaces.
- If you don't actually want to deal with many translations, Drupal's standard interface may actually be too much rather than too little! Use the simple String Overrides module or even the String Overrides Advanced module that our very own Steven Jones has built. Drupal 7's standard interface doesn't actually let you 'translate' English interface text, so string overrides may be the best way to do that if you need to. (This is resolved in Drupal 8, as with many other language issues!)
I fully expect there are many many more options out there. What is your experience of administering translations & the workflow for them? Please contribute to this series - add a comment in the section below!
3. Gather precise requirements to match translation models
My previous article about content & entity (field) translation explains why you might use either of the two overarching approaches to translation in Drupal. Editorial workflows & content architecture will often be closer to one or the other model, but it's also common for some parts of a site to be different.
For example, on a recent site that used the field-level translation approach almost everywhere, that could not apply to site menus, because they could be ordered totally differently in different languages, not just directly translated at the deepest level. This meant the menu has to be translated 'as a whole' rather than as individual menu items, which is confusing.
Prepare for this by making the differences very clear, or find ways to 'hide' the differences in workflow & functionality if possible. Drupal's administrative pages for menus are quite separate to content, which can help in my case, but be careful if different translation systems are needed within a single system (e.g. between different content types). Changing content from one translation model to the other is not recommended, so pick the models carefully in the first place.
4. Language-specific styling
Language-specific CSS has been possible ever since CSS 2.0... I did not know this until relatively recently!
.strapline { text-transform: lowercase; width: 100px; float: left; } .strapline:lang(de) { text-transform: none; width: 120px; }
German words are often longer than average, and German nouns should always be capitalised. So why not style German pages specially? This works through the use of language HTML attributes (usually set in Drupal's
html.tpl.php template) and the
:lang() pseudo-selector in CSS.
5. Allow admins to edit any translation
Bizarrely, any translation that does not match its sanitised equivalent cannot be edited by any user, regardless of their permissions. It's a good thing that untrusted editors cannot set translations to contain unsafe HTML, but this means strings containing unsafe HTML, or malformed HTML, can never be translated. If you want to bypass this, add the following to a custom module, which allows users with the 'Translate admin strings' permission to edit any translation:
* Implements hook_form_FORM_ID_alter().
function MYMODULE_form_i18n_string_locale_translate_edit_form_alter(&$form, &$form_state, $form_id) {
$form['#validate'] = array('MYMODULE_i18n_string_locale_translate_edit_form_validate');
* Process string editing form validations.
function MYMODULE_i18n_string_locale_translate_edit_form_validate($form, &$form_state) {
if (empty($form_state['values']['i18n_string']) && !user_access('translate admin strings')) {
// If not i18n string use regular locale validation.
$copy_state = $form_state;
locale_translate_edit_form_validate($form, $copy_state);
6. Get to know Drupal's language types & helper functions
Drupal has the ability to put parts of a page in different languages. An example of this would be the website of a company that might only do business nationally, but contributes articles & blogs to an international audience. In this case, the interface (header, blocks, any fixed text) could be in a local language (e.g. Dutch, Hungarian), whilst actual content (i.e. entered by editors into articles) could be in the international language of the audience they wish to reach beyond their own country (e.g. English, Spanish). Under the hood, Drupal also recognises URLs as having their own language type, but that is usually inherited directly from the interface.
When writing language-aware custom code, it's important to know which language type should be used. The i18n module provides some helper functions:
$content_lang = i18n_language_content();
$language = i18n_language_interface();
// The above two functions return language objects.
$langcode = $language->language;
There are plenty of helper functions within Drupal core itself too - here's just a few:
// The code of the language to use for a field value,
// using the fallback language if necessary.
$langcode = field_language('node', $node, 'field_link');
// Gets the name (or other properties) of the currently-set
// default language. No need to hardcode it!
$default_name = language_default('name');
// Check whether more than one language is set up.
$is_multilingual = drupal_multilingual();
// iso.inc contains a couple of predefined ISO code lists,
// take a look!
include_once DRUPAL_ROOT . '/includes/iso.inc';
As an aside, please use the constant
LANGUAGE_NONE instead of the 'und' string when referring to the language-neutral code!
7. Using the entity metadata wrapper
The entity metadata wrapper is great for accessing things in code without caring about language - but if you want a field value in a specific language, even the language of the current page, you first have to call this before accessing translations:
Also, you can't use either of the following for translated entity titles/labels:
Instead, assuming you're using the Title module to make entity labels translatable, use:
title_field is the default field name for nodes. It may be different for other entity types - for example,
name_field for taxonomy terms.)
8. Options & limitations to define/use a fallback language
Drupal core has a concept of a 'fallback' language - so for example, you could set 'Spanish' as the fallback for 'Portuguese' users on an otherwise-English site, as Portuguese-speakers may prefer Spanish content to English content when there is no Portuguese translation available. This would be achieved in bespoke code with
hook_language_fallback_candidates_alter() and
hook_field_language_alter(), but with limitations. These are only applied by Drupal core to content in fields, so will not apply the fallback to menus, blocks, site names, and many other sorts of text across your site that don't come from fields. That's a good argument to use 'fieldable' systems for as many of those as possible (e.g. Bean module for blocks), but unfortunately you will have to deal with many of these with disparate solutions or bespoke code in Drupal 7 if you want fallbacks to apply everywhere.
Despite that limitation, being able to set fallback languages seems pretty useful to me. I'd love to see contrib modules allow fallbacks to be customised by site builders in the UI - perhaps selecting fallbacks per-language in a similar way to how the negotiation methods can currently be selected, with the draggable options for re-ordering selections.
9. Overriding core language functionality
Drupal's core language 'negotiation' system (selecting which languages to use for content and other things, configured at /admin/config/regional/language/configure) is pretty good, but will often need overriding to cover your exact requirements. For example, core can select language according to the exact domain, but when I had a variety of subdomains (perhaps for different staging environments) for a project, so I only wanted the domain suffix (e.g. '.fr') to determine the language rather than the whole domain, this needed extending.
Drupal does provide
hook_language_negotiation_info_alter() to allow you to extend Drupal's record of what should be done, but the the actual records of what will be done are the
language_negotiation_* variables, set per language type with
language_negotiation_set(). Somewhat unexpectedly, since the negotiation callbacks & settings are statically cached, any changes you make have to be done for every language 'type' (content, interface & URL), even if you only intend to change one type.
Note that your module's .module file will only be included at the point of initialising language if it implements one of Drupal's 'bootstrap hooks'. An empty
hook_language_init() in your module will do the trick if necessary. If you're replacing negotiation callbacks, you may well need to do this step, which may come as a surprise!
Now you know all this, you can override anything in the language negotiation system, but it's not obvious from just looking at the variables!
10. Ctools objects & caching issues
A lot of data is cached by Drupal - anything from Views to facet definitions. Sometimes already-translated text is cached, which means that translation would get used for all languages, which is incorrect. The best solution is to file a patch with a direct fix to the module in its issue queue on drupal.org. Otherwise, you can often implement hooks to change objects before they are cached... and again after they are loaded. As an example, we had to do the following to stop facets from having their 'Any' option (the 'no-selection' option) being translated before being cached:
* Implements hook_form_FORM_ID_alter().
* Stop the untranslatable no-selection ('Any') setting from
* being changed as the translation comes from the default
* setting which is translatable.
function MYMODULE_form_facetapi_facet_display_form_alter(&$form, &$form_state, $form_id) {
list(, $realm, $facet) = $form_state['build_info']['args'];
foreach (facetapi_get_widgets($realm, $facet) as $id => $plugin) {
if (isset($form['widget']['widget_settings']['links'][$id]['no-selection'])) {
$form['widget']['widget_settings']['links'][$id]['no-selection']['#access'] = FALSE;
* Implements hook_schema_alter().
* Replace the save callback for facets to avoid saving no-selection
* settings.
function MYMODULE_schema_alter(&$tables) {
if (isset($tables['facetapi']['export'])) {
$tables['facetapi']['export']['save callback'] = 'MYMODULE_replacement_facetapi_save_callback';
* Custom save callback for Facet API CTools objects.
* Strips any no-selection setting, since it is not translatable.
* @see ctools_export_crud_save().
function MYMODULE_replacement_facetapi_save_callback($object) {
if (isset($object->settings) && is_array($object->settings)) {
// Remaining code is lifted straight from ctools_export_crud_save().
$table = 'facetapi';
$schema = ctools_export_get_schema($table);
$export = $schema['export'];
// Objects should have a serial primary key. If not, simply fail to write.
if (empty($export['primary key'])) {
return FALSE;
$key = $export['primary key'];
if ($object->export_type & EXPORT_IN_DATABASE) {
// Existing record.
$update = array($key);
else {
// New record.
$update = array();
$object->export_type = EXPORT_IN_DATABASE;
return drupal_write_record($table, $object, $update);
* Implements hook_facetapi_default_facet_settings_alter().
* Strips any no-selection setting, since it is not translatable.
function MYMODULE_facetapi_default_facet_settings_alter(&$exports) {
foreach ($exports as $id => $object) {
if (isset($object->settings) && is_array($object->settings)) {
It's unusual to have to go to these lengths -- usually there's just a single alter hook you can use. Data objects ought to be saved in the database in the default language, or at least with a record of what language they are in (which is done for nodes), with translations pulled from elsewhere. This is particularly relevant for CTools objects though, which do not usually consider language a first-class citizen, or are often implemented by modules that have only added translation as an afterthought.
If you've read this far, congratulations! Let me know your own obscure lessons you have learnt in working with languages in Drupal!
Content (node-level) translation or entity (field-level) translation?
It seems an obvious question to ask, but what are you translating?
The tools exist to translate just about anything in Drupal 7*, but in many different ways, so you need to know exactly what you're translating. Language is 'a first-class citizen', in the sense that any piece of text is inherently written by someone on some language, which Drupal 7 is built to recognise. Sometimes you want to translate each & every individual piece of text (e.g. at the sentence or paragraph level). Other times you want to translate a whole page or section that is made up of multiple pieces of text.
Your content is represented in Drupal as fields and entities (nodes, taxonomy terms, etc). The purpose of your content will dictate how it should be translated. For example, on an e-commerce website that has no difference in the product on offer when shown in different languages, every single sentence may be translated, but the page remains the same. In this case, the translation is at the deepest level of text - i.e. it is field-level translation. However, if the product offering is itself different when shown in different languages then the whole page could be completely different - perhaps with totally different pictures, keyword tags and links. Translation of this kind would be at a 'higher' level as the page is defined by the language - so it would be node-level translation (or page-level).
Perhaps the easiest way to understand this is to look at some examples, asking the question: How should a new translation be handled?
An encyclopaedia article may be best translated by creating a whole new page that is just linked to the original article. This leaves flexibility for the content of the new translation to be quite different as it is a separate article. (Like on Wikipedia - translations may have different sections, pictures & references - they can be totally different in each language.) This is node-level translation, as a new article itself is created as a node.
Each translation exists as a separate page.
However, when translating a product page in a brochure into a new language, each piece of text on the page may be translated so that all of the actual information remains in place in the same way (just expressed/displayed in a different language). This is field-level translation, as the node remains but the data in each field is re-created. (Of course, some of the text may not need translating, such as numerical information, or it could just be omitted from a translation.)
Each field is translated within a single page.
Hopefully the diagrams above help explain node-level translation and field-level translation. Notice that the content of each translation in the node-level model is free to be different, but this means the translations of individual fields are not tied to each other. In the field-level translation, the fields in each translation have to be the same, but this means each is linked as a translation. Linking the translations is useful when rendering parts of a translation separately - for example, in views where individual fields are listed without the whole page, or listing node translations side-by-side. The former can only be done with field-level translation, but then the latter is harder to do.
Field-level translation is achieved by using the Entity Translation module, while node-level translation can be achieved with Drupal core's Content translation module. Yes, these are confusingly named!
Drupal 8 core will use the field-level translation concept, as it is still possible to achieve a node-level translation model with it (e.g. by using a node/entity reference field to link the different translation nodes together), and for good reason. Since all text is inherently written in some language, I believe it makes sense for translations to be at the deepest level (i.e. field-level), rather than grouping different pieces of text (i.e. different fields) into a single defined translation.
If you're not sure, opt for the field-level model (with the Entity Translation module), and work out if your use case can be handled by it. You can use both models on a single site, but it may only confuse your editors, translators and site maintainers. Avoid trying to build too much, it will only result in confusion!
So, back to where we started: What are you translating? Let us know how you have matched the field-level or node-level translation models to your workflow and the challenges you have faced.
* (In Drupal 6, fields are provided by the CCK module, and there are only nodes instead of the more general entity concept. Only the node-level translation model is available in Drupal 6 - i.e. a node is translated as a separate sibling node. Any version of Drupal before Drupal 8 will require additional modules contributed by the community in order to be translatable.)
When you are going to have multiple language set up on your Drupal site, it's important to set the default language appropriately before creating content. Once that is set, content will normally be set to be in that language, and any translations made on the site will be assumed to be from the default language as the source. So changing it is not a good idea, as there's no way to differentiate between translations made before and after the switch in Drupal 6 or 7! (This has been resolved in Drupal 8.)
So, once you've thought first about what is necessary for your multilingual site, the next step is to pick the right default language, ideally before setting up anything else, as everything is 'in' a language in some way. It's usually an obvious choice, but did you know that the Drupal software itself and associated modules (i.e. the codebase, referred to as the 'interface') is all written in U.S. English (as per the coding standards)?
This means that the translation interface for all text that comes from the codebase only allows you to translate from U.S. English. Any text entered from elsewhere (e.g. content entered into nodes by editors) will be either be in the default language that is configured, or any language that is specified when setting the text (e.g. the node language). As the interface is in U.S. English, it can be confusing to have content configured by default to be in a different language - especially as the line between content and interface is blurred (e.g. text within a view that is provided, in code, by a module). So it can help to deliberately set your site's default language to be U.S. English, so that all translation is consistently from that into other languages. Of course that doesn't make sense for many use cases - but it's just worth considering when deciding which language to make a default.
For example, I have built sites where we decided to make British English the default language, as the European audience was prioritised. But as it's so similar to U.S. English, it's easy to get confused between which text should be entered in each version of English. Given that translating went through agencies too, it could have been helpful to have a single workflow for all types of translations - as it shouldn't have to matter whether the text comes from code or an editor.
Text in the interface can only be translated into languages other than U.S. English, so use the String Overrides module for overriding rather than translating that text. (We have to use it with this patch.) This module will be useful if you want to customise any interface text on your site, regardless of language, but it is particularly handy for allowing existing translations of a certain piece of text to remain as they are whilst changing the 'original' text. Changing the 'source' text would otherwise mean new translations are required. Look out for these cases where you may want to make changes in future, and use string overrides on the source text rather than changing it directly - your editors, translators, and users of other languages will appreciate it!
Previous article in series:
Architecture has to be carefully thought through before implemented, or it could all come crashing down at an unexpected moment. You may not realise it, but language is a piece of architecture in all websites. Site builders will be used to thinking about how best to model content, usually in terms of content types, fields and vocabularies on Drupal sites. Every piece of text is modelled somehow - and every piece of text is written in some language. As soon as it matters which language that is - so that translations can be associated with each other and shown beside or instead of one another - that content model needs to incorporate language.
Before diving in to configure languages and set up translations, you have to think how you want to represent and display translations. You may think that your expectations might match a 'standard', even trivial, way of doing translations, or follow a simple workflow, but every organisation, individual or team has their own way of doing things. Your website should match the way you do things - not restrict or force you to do something else!
"Je pense, donc je suis"
What makes your content what it is? How do you produce content? Who will be managing the content? What about things that aren't strictly content, like the multitude of settings that shapes how your users interact with you? The answers to all these questions shape what your website is, and the idea of who or what it represents in your readers' minds.
Will your website just show content in different languages alongside each other? In which case you could just use taxonomy terms to 'tag' each post, so users could just manually filter which posts they would like to see. This would be a very simple approach, but would certainly suit many use cases.
Do you want to have content translated into multiple languages on your site (for example, a brochure-style website, where all the content is the same, but available in different languages), or is each language going to mostly have 'independent' content? You could always make simple links between translations if necessary if most of your content is independent usually.
Is all your content produced by one source, such as a head office, that then sends the content out for translation - or is content in each language produced by multiple sources, which is then sent for central approval?
Do you need the 'interface' of your website (e.g. links & text provided by Drupal itself or modules) to be in different languages?
Will you be using different domains, subdomains, URL prefixes, or other methods to allow users to switch between different language versions of your site, with the entirety of each 'version' in a single language?
Does your site need to share content/configuration between different language versions? If not, you could just set up each language version as a separate site installation (site), perhaps using Drupal's multisite capabilities to share some parts (e.g. modules), or just installed completely separately. This would mean each site only needs to be in one language.
All of these questions should affect the approach you take to implementing languages and translation in Drupal. Think carefully first whether you really need to set up Drupal's 'full' multilingual capabilities, according to what you actually need. Language can be a whole extra layer of complexity, so avoid over-engineering. Do not just follow the multilingual guides without thinking first!
Architecture image from flickr by Ken Ratcliff
Next article in series:
Previous article in series:
I recently got the chance to implement Drupal's multilingual capabilities on a major client site. Drupal has some of the best functionality around for localizing & translating a site, but it does take quite a lot to understand & configure. We will host a series of articles on this, entitled 'language lessons', starting on ... how to get started!
Getting started with multilingual Drupal 7
The first places to visit when getting started with languages in Drupal are the Drupal.org handbook page and Gábor Hojtsy's blog. Among other things, Gábor heads up the multilingual initiative for Drupal 8, which will turn Drupal into a truely multilingual CMS, and he has literally written the book on internationalization in Drupal.
I worked my way through those two online guides, soaking up as much knowledge as possible, and then spent a few months in amongst the code to ensure our client's website would be as true to our strapline - 'perfect Drupal websites' - as possible. If you need to implement any kind of internationalization (otherwise known as 'i18n' for short) or localization (or 'l10n'), you will need to read through those guides too. But I wanted to share some lessons that I learnt along the way. Some of these will be from a perspective that must be considered before hitting the technical details, others subtle details hidden in the depths of the plethora of modules required to make internationalization possible in Drupal 7.
I will aim to cover the following in this series:
- Consider the workflow & requirements (see parts one and two)
- Language first development (parts one and two)
- Entity (field-level) translation, as opposed to node-level translation (part three)
- Workflows & tools (part four)
- Hooks for customising functionality in bespoke module code, including fallback languages (part four)
- Common pitfalls (part four)
What challenges have you faced when making multilingual or localised sites in Drupal? Let us know if you would like us to write about any areas in particular that are not well covered elsewhere.
Next article in series:
I was asked at Drupalcamp London how to identify where parts of a panel come from. Whether you need to change something on a site you inherited, are looking to trace your steps back on something you created long ago, or need to understand how to fix a colleague's mistake, it can be helpful to have a toolkit of methods to find out what produces all sorts of mystery content - not just for panels, but also views, blocks, fields, and the like.
Here's what I do when I need to know what produces something on a page:
- Inspect the markup - usually the first step
- Read exports - necessary if your markup does not help
- Use the Theme developer module - especially for designers
- Check configure URLs - useful for blocks
- Find text quickly - with the right tools, this option can be the most universally successful
- Know what to look for
1. Inspect the markup for classes
Either by simply hitting 'view source' or using your browser's inspector tool (e.g. Firebug), if you search for the specific element that you want to identify, there are usually indications on the element of where something came from.
Blocks and views will normally have HTML classes or IDs containing the module and 'delta' (unique block identifier within that module) or display ID (for views). Panel panes will often have helpful classes too, though they don't necessarily correspond exactly to their providing modules. Nodes, taxonomy terms and other entities are often wrapped in markup telling you their type/vocabulary and ID - so finally you can identify which node needs editing on your custom-built page!
Field values & labels are wrapped with classes indicating the type and name of fields, though modules like Fences, Semantic fields and Display Suite, as well as many themes, may remove this extraneous markup.
Here's an example highlighting the identifying classes from our 'Related articles' views block in our sidebar:
Note: Hashed deltas
Block deltas cannot be more than 32 characters long (that's the maximum width of the database column), so some modules trim, or even hash long deltas of blocks & panes - e.g. 'ocUmprGIz8W5LC0N3EM6KpXMwFQJlMIE'. Views' names and display IDs are concatenated together to produce deltas, so they regularly go over that limit. You'll have to use other methods to find out which actual block/pane those are. For example, views uses a variable (i.e. in the 'variable' database table) called 'views_block_hashes' to keep track of its hashes.
2. Reading exports
If you know your content is within a view or panel (or anything similarly exportable), but can't work out which field, pane or other area your content is coming from, it can help to take a look at the exported version of the object. Click the export link (usually towards the top right of administrative interfaces) and take a look. Exports are just PHP code that build up a full representation of the object. They are quite repetitive, so you can usually scan downwards quite quickly to find the part you're looking for, or use your browser's 'find' functionality to jump down a section (e.g. a pane or views field) at a time.
Panel panes have a 'type' and a 'subtype'. Type is usually the module that provides the pane, subtype is the identifier within that module.
In an exported view, each display starts with a call to
new_display, so search for that, and then jump down to the part that you're interested in, for example the lines that start
$handler->display->display_options['fields'] for all the fields.
3. Theme developer module
Formerly part of the Devel project, Theme developer module allows you to click on any part of a page, and information will be displayed about how to theme that item. This can give away clues as to where something has come from, as most theme hooks contain module names and useful identifiers.
4. Configure URLs
5. Find text quickly
The biggest advance in tracking things down that I have made recently is since I started using the PhpStorm IDE, because it has such a good (i.e. quick) 'find text' function. When simply searching across a whole codebase becomes a quick task, you can get straight to the root of where content comes from in nearly any case.
I know there are other applications that search text quickly too, but I have used many applications that just don't search for simple text strings quickly enough to make this practical. With the right tool, this technique will get you from identifying a problem to starting the solution quicker than anything else and without getting in your way. Spend time solving the problem, not finding the problem!
PhpStorm also allows you to define 'scopes' - so for example, I can limit my searches to contributed modules only, or all custom code, even when spread across different directories in the file tree. See the screenshot to the right for an example use of scopes. At the very least, you should have a way to limit your searches to specific directories (recursively).
But wait, what should I be looking for?
- Any identifiers that you found when inspecting the markup, such as class names or IDs - try swapping hyphens for underscores (perhaps using regular expressions to match either if your text searching functionality supports it)
- Field labels
- Titles and any text you cannot edit (which would be part of Drupal's provided interface)
- Anything that is 'near' the content that you are hunting for if it is potentially part of the same 'thing'.
- Any content that users entered into Drupal's interface will not be found in the codebase though!
Having a good debugging system is an absolute must for any professional developer too. Without it you can only really guess at where things are coming from. Drupal's flexibility means any module can alter nearly anything, so identifying what produces your mystery content can itself be a mystery without the right tools.
What about you?
Are there other techniques that you find helpful for tracking down mystery content? What do you do when you've tried all the above but to no avail? Share your experience in the comments below!
Previously on this website I have written about rendering blocks programmatically and adding things to content to be managed alongside fields. It's time to combine the two! On many projects, we find ourselves needing to render a block consistently across all content of a certain type. For example:
- Are you trying to place advertising blocks or fixed javascript code between the fields in the content of a page, not just shoved into regions around the content?
- Do you want to show a standard piece of content (we use the bean module for enhanced content in blocks) to be placed on all content of a certain type, perhaps explaining about products on a brochure site or how to use webforms?
- Would you like to show lists of links (perhaps as menu blocks) on user profiles as helpful links to common destinations?
Note that all of these examples are intended for showing the same block across different content. If you want to be able to show different blocks on each entity then, you will want to use the Block reference module, which provides a field for referencing a block. This works similarly to the the Entity reference module, allowing you to still re-use blocks and show them within entity content, but means that every node needing the block needs to be edited and the field set individually, so is not helpful when you want the same block to be shown on every node. Another alternative would be something like the Block filter module, which allows you to render a block within the content of a text field (e.g. the body) using a token with an input filter. When I refer to placing blocks within content, I do not mean within the content of a field, rather within the overall content of an entity, between its fields.
We like using the Entity Views Attachment (EVA) module for embedding views within our content, so I decided to write the 'Entity Block Attachment' (EBA) module closely modelled on how EVA works for site builders. Now, without needing to write any code, site builders can configure blocks to appear on nodes, taxonomy terms, commerce products, or any other entity type with EBA!
EBA uses Drupal's
hook_field_extra_fields() system that I have previously written about to declare the configured blocks to appear on any entity of a given bundle/type, amongst the fields of that entity. Drupal's standard block system can help you to place blocks in regions around the sides of any node of a certain type, but EBA extends this to apply to any fieldable entity, and to show the block within the content of your node/profile/term or any kind of entity.
As with other 'pseudo-fields', they can then be re-ordered between the content's existing fields using the standard 'Manage Display' fields interface.
We use the techniques of rendering blocks programmatically that I have previously discussed to then render the blocks in a way that allows Drupal's normal block theming to be used.
Themers will appreciate not having to learn new templates or layout systems, like the current alternatives, Display Suite and Panels require, and site builders will appreciate the simplicity that EBA provides as they can just continue using screens that they are familiar with to achieve their goals.
Go ahead and try out the new EBA module! What uses do you have for embedding a block in your content?
This is a real quick one, but so useful! We often want to render a block within content, perhaps as part of a node (maybe in
hook_node_view, and then made configurable like a field), but there's no obvious way to do this correctly for any block. Drupal normally renders its blocks per region, so there is no single function to embed a block. I came across this really simple solution by Damien Tournoud in a Drupal core issue, which I feel deserves more exposure:
$block = block_load($module, $delta);
$render_array = _block_get_renderable_array(_block_render_blocks(array($block)));
$output = render($render_array);
That's just three lines, and you probably only need the first two! (For example, when within a view hook, where the content will all be rendered later anyway.)
The advantages:
- It uses
block_load()which initially tries to get the block from the
blocktable, so it will load any custom blocks, but will also fall-through to taking the block from code otherwise.
- Blocks are taken from the cache first where possible (as part of
- Blocks go through
drupal_alter()(again, as part of
_block_render_blocks()), which is lacking from most solutions I've seen.
- Essentially, this is the closest thing to the way Drupal core itself renders blocks, re-using the same functions, so the same theming and wrapping elements are applied, including the block title.
- You may wish to avoid the extra database query in
block_load(), or hitting the cache tables in
- If you're not fussed about the block being rendered as Drupal would normally render a block, perhaps you don't want to use
_block_get_renderable_array()as the block will get rendered as a full block which is not as quick as just invoking the block's callback to get it's content.
- You may want to avoid the block being altered for performance reasons or if you're certain the block will not get altered anyway. However, bear in mind that keeping your block alterable may be useful in future.
Thank you Damien!
P.S. Unfortunately, there isn't really an equivalent of this for Drupal 6. If anyone has any good suggestions for it, please do leave them in the comments below.
We often want to add things to the content of a node or any other entity in Drupal 7 using
hook_node_view_alter() or a similar hook in a custom module. This could be anything from a custom social media link, a field rendered in a custom way, additional author information or virtually anything else.
Here's the code used to add that Facebook like button:
* Implements hook_node_view().
* Adds a Facebook like button to page nodes.
function MYMODULE_node_view($node, $view_mode, $langcode) {
switch ($node->type) {
case 'page':
$node->content['MYMODULE_fb_like'] = array(
'#type' => 'item',
'#title' => t('Like this content: '),
'#markup' => '<fb:like send="false" layout="button_count" width="90" show_faces="false" href="http://www.computerminds.co.uk/drupal-code/add-stuff-node-and-configure-it-fields/' . url(current_path(), array('absolute' => TRUE)) . '"></fb:like>',
These extra items nestle in alongside fields on the node, so can be considered 'pseudo-fields', but on their own are totally inflexible as they can't be repositioned and seem to appear out of nowhere onto the page. You should give yourself or site owners/administrators as much power as possible to customize content - and there is an easy solution here, to allow your extra content to be repositioned in exactly the same way as standard fields in the manage fields/display UI can be.
Simply implement
hook_field_extra_fields() in your custom module, and your pseudo-fields will appear alongside fields in those administrative screens like this:
You declare your pseudo-fields (or 'extra fields') to the Fields API so that it can check the rendered nodes for you and set the orders of fields and your extra content according to the field display settings rather than just the defaults that you hard-wired in code. You specify which entities and which bundles your pseudo-fields are attached to, and whether they are on the displayed content, or on the edit/create form for the type of content. For the FB Like example above, here's what we do:
* Implements hook_field_extra_fields().
* Declare our Facebook Like button as a pseudo-field.
function MYMODULE_field_extra_fields() {
// The levels of the array that we return correspond to the
// entity type, bundle and then either 'display' or 'form'.
// In this case, we apply to 'page' nodes, when we display them.
$extra['node']['page']['display'] = array(
// The keys here must correspond to the keys of the items
// that we add in our hook_node_view() or similar function.
// Prefix it with our module name to ensure it doesn't clash
// with anything from other modules.
'MYMODULE_fb_like' => array(
'label' => t('Custom FB Like button'),
'description' => t('Facebook like button, added in MYMODULE_node_view().'),
'weight' => 10,
return $extra;
Some brief explanation of the parts you specify in the
Either specify 'display' or 'form' depending on whether your extra content is shown on the displayed content (which is what we do in this example), or the create/edit forms of the content. Most of this article is talking about adding pseudo-fields to the display of content, but have a look at the Domain Access module as an example of an extra item on the form -- see
domain_field_extra_fields()-- which it uses to add the domain settings for specific nodes.
The label is only what is shown in the manage fields/display settings screen, it is not used on the actual content.
The description doesn't seem to get used anywhere so is useful as documentation for anyone that reads your code. I would recommend referring to which function adds the pseudo-field to the content as that's not always obvious.
If you've added the hooks above to your own module, make sure you replace 'MYMODULE' with your own module name, and flush your site caches for Drupal to pick them up (or just enable the module).
Part of the joy of using
hook_field_extra_fields is that it will work with other field management tools like Field Group. You can place your pseudo-fields within field groups painlessly this way. Here's an example (click the images to see them in full):
If you wanted to export these display settings with something like features, the giant
field_bundle_settings variable stores all the settings for all entities / types / bundles / view modes.
Note that all this applies to any fieldable entity, so you can do the same for users, taxonomy terms, etc!
While this article has focussed on Drupal 7, the same things can be achieved in Drupal 6 with CCK and
Here's a quick follow-up to my original post on Dynamic forms in Drupal 7, as a reply to Wappie08, who asked me about combining conditions in the
#states array to add increased control over the display of your form elements.
The question:
I mean:
IF field_1 is '1' or '2'
and also:
IF field_1 is '1' OR field_2 is '7'
You can combine
#states conditions together, AND-ing them with what is currently available in core:
$element['new_window'] = array( '#type' => 'checkbox', '#title' => t('Open link in new window'), '#default_value' => $settings['new_window'], '#states' => array( 'visible' => array( // action to take. ':input[name="fields[field_my_field][settings_edit_form][settings][make_link]"]' // element to evaluate condition on => array('checked' => TRUE), // FIRST condition ':input[name="fields[field_my_field][settings_edit_form][settings][make_link]"]' // element to evaluate condition on => array('enabled' => TRUE), // SECOND condition ), ), );
So in the case above, our
new_window element is only made visible if the two conditions are BOTH true. We've just simply added a second condition to the array of conditions.
Unfortunately, conditions cannot be OR-ed (or XOR-ed) by just using Drupal core yet. See http://drupal.org/node/1106388#comment-4269336.
Some work has been done and is continuing on getting this in for Drupal 8, and hopefully it will be backported for D7 eventually. So I would recommend that you take a look at these two issues and perhaps use a patch from them:
1) FAPI #states: Fix conditionals to allow OR and XOR constructions
2) #states selector matching multiple checkboxes does not trigger state
If you try this, you could help move the issues along by doing some extensive testing and posting up testing reports. Then we might all get to play with multiple conditions!
You may find that the Conditional Fields module is worth a look for this, though I haven't tried it myself.
Let me know how you get on, this is now pushing out at the boundaries of what Drupal can do!
About Drupal Sun
Drupal Sun is an Evolving Web project. It allows you to:
- Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
- Facet based on tags, author, or feed
- Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
- View the entire article text inline, or in the context of the site where it was created
See the blog post at Evolving Web