Aug 14 2018
Aug 14

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 :-)

Jun 26 2018
Jun 26

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):

<?php

  $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):

<?php

/**
 * 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]),
      ];
      break;
  }
}

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!

May 14 2018
May 14

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.

Apr 10 2018
Apr 10

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

$node->field_my_thing->view();

Quick reference

I'll cover these in detail below, but here are the things you might want to be doing:

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

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

  1. 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 view().

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!)

$node->field_my_thing->view('teaser');

Or even override the formatter to be used altogether (which is handy if the field would normally be hidden in any view mode):

$node->field_my_thing->view([
  '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')
  ->getComponent('field_images');

// 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')
  ->getComponent('field_images');

// Then tweak those display options and view the field.
$display_options['settings']['image_style'] = 'even_bigger_thumbnail';
$node->field_my_thing->view($display_options);

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?

Apr 03 2018
Apr 03

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?

A developer wearing a comic sombrero to indicate they are directly editing code on a live site.

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.
dpm(file_get_contents($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:

  1. Go to /devel/php?%23that=thing&other=this
  2. Run dpm($_GET);
  3. 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.

Apr 03 2018
Apr 03

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.

  1. Our on-call developers split up the various sites/environments/projects that would need updating amongst themselves, using a simple online shared spreadsheet.

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

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

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

  3. We merged any of those pre-prepared pull requests that could be done immediately.

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

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

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

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

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

Aug 29 2017
Aug 29

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.

Jun 20 2017
Jun 20

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 my_module/config/schema/my_module.schema.yml:


my_module.settings:
  type: config_object
  label: 'My custom settings'
  mapping:
    message:
      type: text
      label: 'Message to display on page XYZ'
    some_setting:
      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 \Drupal::config('...!

Jan 17 2017
Jan 17

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,
      ]],
      'xor',
      [':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.

Jan 10 2017
Jan 10

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:

  Even if inexistent Only if existent With overrides (read-only) $config_factory
->get($id)$config_factory
->loadMultiple([$id]) No overrides (read-only) $config_factory
->get($id)
->getRawData() Check !$config->isNew()
or use $config_storage
->read($id) first Writable (no overrides) $config_factory
->getEditable($id) Check !$config->isNew()
or use $config_storage
->read($id) first

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.

1. $config_factory->get($id)
(or \Drupal::config($id) or $this->config($id))

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

Note: $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.

2. $config_factory->getEditable($id)

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.

3. $config_factory->loadMultiple([$id])

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.

4. $config_storage->read($id)

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.

Nov 29 2016
Nov 29

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: Example of the original core vocabulary listing ...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). Customised airports view 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. No tabs on vocabulary listing So, where to now? I thought of three ideas:

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

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

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

Duplicated 'List' tab That last option needed a few hooks to be implemented, and to remove the duplicate tab that you can see in the above screenshot...

hook_local_tasks_alter:


/**
 * 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';
  }
}

hook_module_implements_alter():


/**
 * 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'];
    unset($implementations['MYMODLE']);
    $implementations['MYMODLE'] = $group;
  }
}

hook_menu_local_tasks_alter():

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') {
        unset($data['tabs'][0]['entity.taxonomy_vocabulary.overview_form']);
      }
      else {
        unset($data['tabs'][0]['views_view:view.airports.page_1']);
      }
    }
    else {
      unset($data['tabs'][0]['views_view:view.airports.page_1']);
    }
  }
}

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:


entity.taxonomy_vocabulary.overview_form:
  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):


services:
  MYMODULE.route_subscriber:
    class: Drupal\MYMODULE\EventSubscriber\RouteSubscriber
    tags:
      - { 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!

Nov 22 2016
Nov 22

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 settings.php, /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?

Nov 10 2015
Nov 10

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:

Autocomplete results are not in the same language as their search input for entity reference fields

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.

Simple entity reference settings

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:

Set up an Entity Reference display with the 'Field: title' field

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.

Configure the format settings to use the new field

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

Nov 04 2015
Nov 04

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.

Language Hierarchy

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:

Entity translation overview

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.

Jul 22 2015
Jul 22

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!

Oct 14 2014
Oct 14

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.

Contents:

  1. Don't force everyone to use Drupal
  2. Administering translations
  3. Gather precise requirements to match translation models
  4. Language-specific styling
  5. Allow admins to edit any translation
  6. Get to know Drupal's language types & helper functions
  7. Using the entity metadata wrapper
  8. Options & limitations to define/use a fallback language
  9. Overriding core language functionality
  10. Ctools objects & caching issues

1. Don't force everyone to use Drupal

POEditor interface 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

Localization Client interface 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:

 $wrapped_entity->language($langcode);

Also, you can't use either of the following for translated entity titles/labels:

 $wrapped_entity->label();
$wrapped_entity->title->value();

Instead, assuming you're using the Title module to make entity labels translatable, use:

 $wrapped_entity->title_field->value();

(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 core's language detection & selection configuration screen 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)) {
unset($object->settings['no-selection']);
}

// 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)) {
unset($object->settings['no-selection']);
}
}
}

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!

Aug 19 2014
Aug 19

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.
    Separate nodes as translations.

  • 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.
    All field translations within one node.

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

Jun 10 2014
Jun 10

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: 

May 16 2014
May 16

Architecture from Montreal

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: 

Mar 24 2014
Mar 24

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: 

Mar 05 2014
Mar 05

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:

  1. Inspect the markup - usually the first step
  2. Read exports - necessary if your markup does not help
  3. Use the Theme developer module - especially for designers
  4. Check configure URLs - useful for blocks
  5. Find text quickly - with the right tools, this option can be the most universally successful
  6. 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.

Theme developer module helps you identify where content came from

4. Configure URLs

Finally, when you configure a block, or many other things, part of the URL will tell you information such as which module provides that block, and the block's unique ID/delta. Some administrative interfaces use javascript so you cannot see changes in the URL in the address bar (such as views), but often if you just hover your mouse over links, you'll be able to see the URL in your browser's status bar.

5. Find text quickly

Coloured file scopes in PhpStorm 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!

Jan 13 2014
Jan 13

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!

The standard configure block form is used

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.

Manage display screen includes configured blocks

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?

Mar 22 2012
Mar 22

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 block table, 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 _block_render_blocks()).
  • 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.

Disadvantages:

  • You may wish to avoid the extra database query in block_load(), or hitting the cache tables in _block_render_blocks().
  • 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.

Feb 28 2012
Feb 28

We often want to add things to the content of a node or any other entity in Drupal 7 using hook_node_view(), 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.

The rendered node with our pseudo-field

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>',
      );
      break;
  }
}

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:

The manage fields UI

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 hook_field_extra_fields():

  • 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):

Extra fields within a node using field groupManage extra fields and field groups

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 hook_content_extra_fields().

Jul 04 2011
Jul 04

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:

Hi James Williams, I read your blog post about d7 & #states in the FAPI which is really cool! One problem is that the information is also listed in the drupal.org example module, I was missing one important extra hint: how can you make an IF statement?

I mean:
IF field_1 is '1' or '2'
and also:
IF field_1 is '1' OR field_2 is '7'

If can can enlighten me maybe that's also an interesting addition on your article or the rest of the drupal community :)

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

Evolving Web