Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough

Skip hooks during a Drupal 8 migration

Jan 15 2020
Jan 15

Simple XML Sitemap 3.1 released with major new features

Mar 12 2019
Mar 12
Nov 18 2018
Nov 18

Simple XML sitemap 3.0 has been released

The third major version of simple_sitemap has been seven months in the making. The module has been rewritten from the ground up and now features a more reliable generation process, a significantly more versatile API and many new functionalities.

Major new features

Ability to create any type of sitemap via plugins

The 8.x-3.x release allows not only to customize the URL generation through URL generator plugins as 2.x did, but also creating custom sitemap types that mix and match a sitemap generator along with several URL generators to create any type of sitemap.

This 3-plugin system coupled with the new concept of sitemap variants makes it possible to run several types of sitemaps on a single Drupal instance. Now e.g a Google news sitemap can coexist with your hreflang sitemap.

A sitemap variant can but does not need to be coupled to entity types/bundles. When creating a sitemap generator, one can define where the content source is located and what to do with it upon sitemap generation/deletion.

Ability to create sitemap variants of various sitemap types via the UI

In 3.x links form a specific entity bundle can be indexed in a specific sitemap variant with its own URL. This means, that apart from /sitemap.xml, there can be e.g

  • /products/sitemap.xml,
  • /files/sitemap.xml or
  • /news/sitemap.xml.

All of these can be completely different sitemap types linking to Drupal entities, external resources. or both. They could also be indexing other sitemaps. The name, label and weight of each variant can also be set in the UI.

Sitemap statusNo more out of memory/time errors

While the 2.x version of the Simple XML sitemap module has been appreciated for its simple generation process, that process sometimes failed because of timeouts and memory limits on huge sites in limited environments.

In order to address that, the generation process has now been streamlined to using a single queue regardless of whether batch generation is being used, or backend (cron/drush) processes. Setting a time limit on the cron generation and limiting the amount of links per sitemap allows for hundreds of thousands of entities/elements being indexed hopefully without memory errors.

If a problem occurs, the module resumes the generation process by picking up the last indexed element. Another thing the new version does better is making the old sitemap version accessible during its regeneration. This way there is never a time where bots cannot index stuff.

Other improvements

There are many more improvements compared to 2.x. You can see the list of changes on the module's release page.

Upgrade path

It was a challenge to do all of the above while still maintaining a smooth upgrade process, but it was worth it. The module upgrades fine from any of the 2.x versions.

To upgrade the module via composer, $ composer require 'drupal/simple_sitemap:^3.0' can be used. Afterwards, just visit /update.php, or run $ drush updb to update the module's storage.

Please bear in mind, that the module's API has undergone several changes, so if you are calling it in your code, the method calls may need some adjustment. Check out the code documentation and the readme file.

Should you upgrade?

A small portion of the 35k+ sites that are using this module have been rocking the 3.x development version and helping out by reporting issues. All of the bugs have been ironed out leaving a clean bug queue. I believe the 3.x branch to be stable and given its robustness, its enhancements and the smooth upgrade path, I encourage the rest of you to upgrade to the most recent release.

3.0 is great, but what is coming in 3.1?

Views support

Including view display URLs in the sitemap has been possible through adding these URLs as custom links in the UI (or via the API). View display variants however (most of the time views with arguments) are tedious to include as one would have to include every version of the URL. The integration of the simple_sitemap_views inside simple_sitemap 3.x will make this easily doable via the UI.

Thanks to @WalkingDexter for his tremendous work on this submodule!

XSL stylesheets

This will not change how bots interact with the sitemaps, it will however make the sitemaps readable and sortable for humans. This can be helpful when debugging the sitemap content or using the sitemap to visually present content to users.

I invite you to watch this space for a more in-depth technical tutorial on how to programmatically create sitemap types. Also feel free to leave a comment below!

Aug 13 2018
Aug 13

Apparently there are still pretty common Drupal 8 theming tasks that cannot be accomplished with the great twig_tweak module. This by the way was me giving a plug to a great little module, which makes half of all your theme preprocess hooks unnecessary.

Update: Apparently there is a module like twig_tweak but with the ability to do the above. It is called bamboo_twig and its documentation can be found here - thanks to Luckhardt Labs for mentioning it. Mind you I have not tested it yet. There is a rather interesting issue in its queue about the lack of collaboration between the two module maintainers.

If you would like to get the URL from an image that is trapped inside of a media entity however, you can either extract it using the aforementioned preprocess function like so:

  1. function mytheme_preprocess_node(&$variables) {

  2. /** @var \Drupal\node\NodeInterface $node */

  3. $node = $variables['node'];

  4. $image_field = $node->get('field_background_image');

  5. if (!$image_field->isEmpty()) {

  6. $uri = $image_field->entity->get('field_media_image')->entity->uri->value;

  7. $variables['background_image_url'] = file_create_url($uri);

  8. }

  9. }

In the node template, you can display it using

  1. {{ background_image_url }}

... or use this nifty snipplet inside of your twig template directly:

  1. {% if node.field_background_image is not empty %}
  2. {{ file_url(node.field_background_image.entity.field_media_image.entity.fileuri) }}

In this case the media image URL is acquired from a node inside of a node tempalate node--node-type.html.twig, but this will work for other entities in other templates as well, e.g a paragraph in paragraph--paragraph-type.html.twig.

Mar 30 2018
Mar 30

Creating a duplicate of an entity

Creating a duplicate of an entity is easily done via the entity API method Entity::createDuplicate(). This is a convenient method if the goal is to clone an entity into a new entity, as all identifiers of the previous entity get unset when using this method.

  1. $nid = 5;

  2. $entity = \Drupal::service('entity_type.manager')->getStorage('node')->load($nid); // Use dependency injection instead if in class context.

  3. $duplicate = $entity->createDuplicate();

  4. $duplicate->save();

Cloning data into an existing entity

However partially or fully cloning data into an existing entity is less straight forward (and rightfully so). Still, the ability to do so can be useful

  • in custom migration scripts where we want to overwrite old entities without creating new ones,
  • in cases where we need to overwrite the old entity as other internal or external data may reference it and creating a new entity would break these references.

The second case could be an entity reference field referencing the old entity in question (this could be technically solved by reassigning the reference), but it could also be 3rd party software referencing the old entity, which would complicate things.

This article is going to demonstrate a couple of possible ways of cloning entity data into existing entities.

Cloning field data 1:1

  1. $source_nid = 5;

  2. $destination_nid = 6;

  3. // Use dependency injection in class context instead.

  4. $source = \Drupal::service('entity_type.manager')->getStorage('node')->load($source_nid);

  5. $destination = \Drupal::service('entity_type.manager')->getStorage('node')->load($destination_nid);

  6. foreach ($source->getFields() as $name => $field) {

  7. $destination->set($name, $field->getValue());

  8. }

  9. $destination->save();

Importing new data only

To import new field data without overwriting existing data, just check if the destination field is empty before cloning into it like so:

  1. foreach ($source->getFields() as $name => $field) {

  2. if ($destination->get($name)->isEmpty()) {

  3. $destination->set($name, $field->getValue());

  4. }

  5. }

Putting it all together

A nifty method that could be used to clone field data of entities into other existing entitites could look like this:

  1. /**

  2.  * @param \Drupal\Core\Entity\Entity $source

  3.  * @param \Drupal\Core\Entity\Entity $destination

  4.  * @param string $mode

  5.  * Can be 'keep', 'overwrite' and 'clone'.

  6.  * @param array $skip_fields

  7.  * An array of fields not to be cloned into the destination entity.

  8.  */

  9. public function cloneFields(Entity $source, Entity &$destination, $mode, $skip_fields = []) {

  10. foreach ($source->getFields() as $name => $field) {

  11. // In this case clone only fields and leave out properties like title.

  12. if (strpos($name, 'field') === 0
  13. // Leave out certain fields.

  14. switch ($mode) {

  15. // Import only those fields from source that are empty in destination.

  16. case 'keep':

  17. default:

  18. if (!$destination->get($name)->isEmpty()) {

  19. continue 2;

  20. }

  21. break;

  22. // Import field data from source overwriting all destination fields.

  23. // Do not empty fields in destination if they are empty in source.

  24. case 'overwrite':

  25. if ($source->get($name)->isEmpty()) {

  26. continue 2;

  27. }

  28. break;

  29. // Import field data from source overwriting all destination fields.

  30. // Empty fields in destination if they are empty in source.

  31. case 'clone':

  32. break;

  33. }

  34. $destination->set($name, $field->getValue());

  35. }

  36. }

  37. $destination->save();

  38. }

There you go. Make sure to comment below in case of questions or if you know a better way of doing the above.

Oct 13 2017
Oct 13

Upgrading to Drush 9

Drush should be installed and updated through composer. There is no stable Drush 9 version yet, so the development version must be used. Updating to the development version of Drush 9 is a simple as typing:

$ composer require drush/drush:dev-master

Porting your Drush commands to Drush 9

Porting the commands is a semi-automatic process: There is a command that will generate the required files and class structure for you. To start the wizard, just type:

$ drush generate drush-command-file -l dev

Drush will ask you for the module's machine name and for the optional path to the legacy Drush command file (the one that has your commands, ending with You will have to provide the absolute path.

This is the file your Drush command definition goes into. Do not use your module's regular services.yml as you might have done in Drush 8 or else you will confuse the legacy Drush which will lead to a PHP error like this:

Fatal error: Class 'Drush\Commands\DrushCommands' not found in MyModuleCommands.

Use the dedicated file in your module's root directory instead.

The file should look like this:

  1. services:

  2. mymodule.commands:

  3. class: \Drupal\mymodule\Commands\MyModuleCommands

  4. tags:

  5. - { name: drush.command }

As in other symfony service definitions, you can (and should) provide other services as arguments DI style and do all the other crazy stuff.

The most recent Drush 9 version recommends to explicitly declare the location of the drush command file for each version of drush by adding the section to the composer.json file of the implementing module. This is now optional, but will be required for Drush 10.

To comply, let us declare the above file in composer.json for Drush 9:

  1. "extra": {

  2. "drush": {

  3. "services": {

  4. "": "^9"

  5. }

  6. }

  7. }

Refusing to alter composer.json will result in the following message while running drush commands:

module_name should have an section. In the future, this will be required in order to use this Drush extension.


  1. namespace Drupal\mymodule\Commands;

  2. use Drush\Commands\DrushCommands;

  3. /**

  4.  *

  5.  * In addition to a commandfile like this one, you need a

  6.  * in root of your module.

  7.  *

  8.  * See these files for an example of injecting Drupal services:

  9.  * -

  10.  * -

  11.  */

  12. class MyModuleCommands extends DrushCommands {

  13. /**

  14.   * @command mymodule:do-something

  15.   * @param array $options An associative array of options whose values come from cli, aliases, config, etc.

  16.   * @validate-module-enabled mymodule

  17.   * @aliases mm:do-something, mm:ds, mymodule-do-something

  18.   */

  19. public function generate()

  20. {

  21. // See bottom of for details on what to change when porting a

  22. // legacy command.

  23. }

  24. }

As seen above, the generate() method needs to be implemented manually. Other manual changes may include creating a constructor in case other services are injected.

Drush 9 mimics symfony's style module:command naming structure and this should be respected. I don't see any reson not to include the legacy command as an alias however: If your command used to be my_module:do-something, use my-module:do-something in @command, but also the old my_module-do-something as @alias as presented in the example above. This way scripts calling the old Drush will continue working.

Maintaining Drush 8, Drush 9 and Drupal Console commands side by side

The new three standards of managing Drupal through a shell should not be an excuse for bad practice. To avoid code duplication, make sure your module defines a service which holds all the business logic that can be run by any of the above tools.

Simple XML Sitemap (project page) now supports Drush 9 and is a good example of this principle: (Drush 8)

  1. /**

  2.  * @file

  3.  * Drush (< 9) integration.

  4.  */

  5. /**

  6.  * Implements hook_drush_command().

  7.  */

  8. function simple_sitemap_drush_command() {

  9. $items['simple-sitemap-generate'] = [

  10. 'description' => 'Regenerate the XML sitemaps according to the module settings.',

  11. 'callback' => 'drush_simple_sitemap_generate',

  12. 'drupal dependencies' => ['simple_sitemap'],

  13. 'aliases' => ['ssg'],

  14. ];

  15. $items['simple-sitemap-rebuild-queue'] = [

  16. 'description' => 'Rebuild the sitemap queue for all sitemap variants.',

  17. 'callback' => 'drush_simple_sitemap_rebuild_queue',

  18. 'drupal dependencies' => ['simple_sitemap'],

  19. 'aliases' => ['ssr'],

  20. ];

  21. return $items;

  22. }

  23. /**

  24.  * Callback function for hook_drush_command().

  25.  *

  26.  * Regenerate the XML sitemaps according to the module settings.

  27.  */

  28. function drush_simple_sitemap_generate() {

  29. \Drupal::service('simple_sitemap.generator')->generateSitemap('drush');

  30. }

  31. /**

  32.  * Callback function for hook_drush_command().

  33.  *

  34.  * Rebuild the sitemap queue for all sitemap variants.

  35.  */

  36. function drush_simple_sitemap_rebuild_queue() {

  37. \Drupal::service('simple_sitemap.generator')->rebuildQueue();

  38. }

SimplesitemapCommands.php (Drush 9)

  1. namespace Drupal\simple_sitemap\Commands;

  2. use Drupal\simple_sitemap\Simplesitemap;

  3. use Drush\Commands\DrushCommands;

  4. /**

  5.  * Class SimplesitemapCommands

  6.  * @package Drupal\simple_sitemap\Commands

  7.  */

  8. class SimplesitemapCommands extends DrushCommands {

  9. /**

  10.   * @var \Drupal\simple_sitemap\Simplesitemap

  11.   */

  12. protected $generator;

  13. /**

  14.   * SimplesitemapCommands constructor.

  15.   * @param \Drupal\simple_sitemap\Simplesitemap $generator

  16.   */

  17. public function __construct(Simplesitemap $generator) {

  18. $this->generator = $generator;

  19. }

  20. /**

  21.   * Regenerate the XML sitemaps according to the module settings.

  22.   *

  23.   * @command simple-sitemap:generate

  24.   *

  25.   * @usage drush simple-sitemap:generate

  26.   * Regenerate the XML sitemaps according to the module settings.

  27.   *

  28.   * @validate-module-enabled simple_sitemap

  29.   *

  30.   * @aliases ssg, simple-sitemap-generate

  31.   */

  32. public function generate() {

  33. $this->generator->generateSitemap('drush');

  34. }

  35. /**

  36.   * Rebuild the sitemap queue for all sitemap variants.

  37.   *

  38.   * @command simple-sitemap:rebuild-queue

  39.   *

  40.   * @usage drush simple-sitemap:rebuild-queue

  41.   * Rebuild the sitemap queue for all sitemap variants.

  42.   *

  43.   * @validate-module-enabled simple_sitemap

  44.   *

  45.   * @aliases ssr, simple-sitemap-rebuild-queue

  46.   */

  47. public function rebuildQueue() {

  48. $this->generator->rebuildQueue();

  49. }

  50. } (Drush 9)

  1. services:

  2. simple_sitemap.commands:

  3. class: \Drupal\simple_sitemap\Commands\SimplesitemapCommands

  4. arguments:

  5. - '@simple_sitemap.generator'

  6. tags:

  7. - { name: drush.command }

All of the business logic of this command is inside of the method generateSitemap() of the simple_sitemap service.

Downgrading back to Drush 8

Not a fan of changing APIs? Downgrading is a composer command away:

$ composer require drush/drush:^8.0


It is good to see the Drush project keeping up with time and pubishing Drush 9 parallely to the appearance of Drupal 8.4.0. The API changes are the necessary price we pay for a modern and continuously evolving framework like Drupal.

Feel free to leave a comment below in case of questions or new Drupal 8.4 / Drush 9 insights.

Sep 25 2017
Sep 25

This is a technical description of the 2.x branch of the module. For the newer 3.x branch see this article.

New features of Simple XML sitemap

Version 2.10 of Simple XML sitemap is mainly a feature release with only a few minor bugs fixed. The new features are

  • the implementation of the changefreq parameter
  • the ability to set an interval at which to regenerate the sitemap
  • the ability to customize XML output
  • the ability to add arbitrary links to the sitemap
  • image indexation

See the 8.x-2.10 release page for details.
A new version has been released, please make sure to visit the project page.

Image indexationInclusion settings

Simple XML sitemap is now able to create Google image sitemaps through indexing all images attached to entities. This includes images uploaded through the image field as well as inline images uploaded through the WYSIWYG. The inclusion of images can be set at the entity type and bundle level but can be overridden on a per-entity basis giving you all the flexibility.

Please bear in mind, that all images attached to entities get indexed regardless of their file system location (public/private). Another thing worth noting is that the original images get indexed, not the derived styles. This should be considered before indexing entities with many high resolution images which could increase traffic.

Indexation of custom link images has not made it into this release, but the feature is already available in the development version of the module.

Adding arbitrary links to the sitemap

Most use cases dictate the inclusion of internal links which can be achieved through adding entity links to the index. For non-entity pages like views, there has been the possibility to add custom links through the UI or the API. In both cases however the system only allows internal links which are accessible to anonymous users. The new version of the module provides a way to add any link to the index, even ones Drupal does not know about:

  1. /**

  2.  * Use this hook to add arbitrary links to the sitemap.

  3.  *

  4.  * @param array &$arbitrary_links

  5.  */

  6. function hook_simple_sitemap_arbitrary_links_alter(&$arbitrary_links) {

  7. // Add an arbitrary link.

  8. $arbitrary_links[] = [

  9. 'url' => '',

  10. 'priority' => '0.5',

  11. 'lastmod' => '2012-10-12T17:40:30+02:00',

  12. 'changefreq' => 'weekly',

  13. 'images' => [

  14. ['path' =>'http://path-to-image.png']

  15. ]

  16. ];

  17. }

As the example shows, all properties of the link like priority/lastmod/changefreq can be defined as well.

To alter links shortly before they get transformed to XML output, there is still the possibility to use the following:

  1. /**

  2.  * Alter the generated link data before the sitemap is saved.

  3.  * This hook gets invoked for every sitemap chunk generated.

  4.  *

  5.  * @param array &$links

  6.  * Array containing multilingual links generated for each path to be indexed.

  7.  */

  8. function hook_simple_sitemap_links_alter(&$links) {

  9. // Remove German URL for a certain path in the hreflang sitemap.

  10. foreach ($links as $key => $link) {

  11. if ($link['path'] === 'node/1') {

  12. // Remove 'loc' URL if it points to a german site.

  13. if ($link['langcode'] === 'de') {

  14. }

  15. // If this 'loc' URL points to a non-german site, make sure to remove

  16. // its german alternate URL.

  17. else {

  18. if ($link['alternate_urls']['de']) {

  19. unset($links[$key]['alternate_urls']['de']);
  20. }

  21. }

  22. }

  23. }

  24. }

Basic alteration of the XML output

The following two new hooks can now be used to alter the XML output:

  1. /**

  2.  * Alters the sitemap attributes shortly before XML document generation.

  3.  * Attributes can be added, changed and removed.

  4.  *

  5.  * @param array &$attributes

  6.  */

  7. function hook_simple_sitemap_attributes_alter(&$attributes) {

  8. // Remove the xhtml attribute e.g. if no xhtml sitemap elements are present.

  9. unset($attributes['xmlns:xhtml']);
  10. }

  11. /**

  12.  * Alters attributes of the sitemap index. shortly before XML document generation.

  13.  * Attributes can be added, changed and removed.

  14.  *

  15.  * @param array &$index_attributes

  16.  */

  17. function hook_simple_sitemap_index_attributes_alter(&$index_attributes) {

  18. // Add some attribute to the sitemap index.

  19. $index_attributes['name'] = 'value';

  20. }

Other API changes

The API is now more forgiving allowing missing link setting arguments when using some of its inclusion altering methods. Here is en example of the simple_sitemap.generator API in action:

  1. \Drupal::service('simple_sitemap.generator')

  2. ->saveSetting('remove_duplicates', TRUE)

  3. ->enableEntityType('node')

  4. ->setBundleSettings('node', 'page', ['index' => TRUE, 'priority' => 0.5])

  5. ->removeCustomLinks()

  6. ->addCustomLink('/some/view/page', ['priority' => 0.5])

  7. ->generateSitemap();

More documentation can be found here. I hope the new version of this module will be of great use to you!

Feb 23 2017
Feb 23

Since its relaunch in 2015, the Drupal 7 powered has been gaining popularity among artists and design students to become their go-to platform. Until today, design students have uploaded over 700 portfolios providing guidance to enrolling candidates. These portfolios are linked to over 500 art faculties of hundreds of universities.

Before enrolling in a course, a candidate can research their local university and study other students' portfolios or enroll in their local design course to prepare for the entry tests - all of it on

On top of that, students provide and collect support on the forum which boasts over 20000 users who have written nearly 250000 posts. This may be the biggest and most beautiful forum built on top of Drupal core.Frontpage

The most powerful feature however may be the ability for guests to create most of the site's content without having to go through any type of registration process. Visitors can go ahead and correct their school's information just by clicking 'edit'. Likewise, anyone can write a blog post - no account or personal information needed. We think this technology has massively contributed to the quantity and quality of content on

While the numbers of design students, universities and art schools registering with the platform has been growing steadily, the visionaries behind the project, Ingo Rauth and Wolfgang Zeh from projektgestalten, recently decided to take the platform to the next level by bringing it to jobseekers and providers as well. Consequently gbyte has implemented the event functionality and the new job board.

This is not going to be the last improvement though, apparently artists have lots of creative ideas and we look forward to implementing them. We feel that this project is a great showcase of Drupal's possibilities and if you would like to learn more about the project or its implementation, make sure to leave a comment below or contact us via the contact form.

Check out other technology-centric posts about the project as well as more screenshots on the project page.

Jan 22 2017
Jan 22

Letting users create content without having to register (or going through any other annoying process) is becoming an important customer engagement strategy.

When you allow anonymous users to create content on your website, you want this content to go through a moderation process before it becomes publicly available. To implement this in Drupal, the anonymous user has to be given permission to create content and the content type needs to be unpublished by default.

The problem with Drupal 7 and Drupal 8 is that as soon as the anonymous user saves new content, they loose access rights to it and get redirected to an 'Access denied' page which is not very user friendly.

In addition to the above, you may want the anonymous user to be able to edit or even delete their own content in case they find an error right after submitting it. Users often find typos or other kinds of mistakes right after content submission.

Gbyte created the Session Node Access module to tackle exactly these issues. The module allows administrators to grant certain user roles (not only anonymous users) specific permissions to content they created. These permissions last only as long as the browsing session lasts; after that, the regular permissions apply again. This way it is possible to allow guests or users of a certain role to keep access to their content, even if it is pending for approval.

Now Session Node Access has been ported to Drupal 8 - thank you to Gaël Gosset for doing the initial porting.

Right now this module works only with nodes, we may implement it for other entities in case of demand.

Feel free to download Session Node Access from its module page.

Session Node Access configuration screen:

Session Node Access configuration screen

Feb 28 2016
Feb 28

This is a technical comparison of the older 2.x branch of the Simple XML sitemap module and an older dev version of XML sitemap. For more on the newer 3.x branch of Simple XML sitemap see this article.

This comparison may be interesting for XML sitemap users moving to Drupal 8 or for users intending to wait for the port. (Do not wait, help out!) Please also be advised that gbyte made Simple XML sitemap, which makes this comparison intrinsically biased.

There are major differences between Simple XML sitemap (simple_sitemap) and XML sitemap (xmlsitemap) and depending on your use case, you might want to choose one or the other.

What sets the modules apart are their complexity, extensibility, performance and feature sets.

Code base

Having been built specifically for D8, simple_sitemap has arguably a cleaner code base adhering to D8 standards i (i.e. use of OOP). In contrast, the xmlsitemap module will have a hard time adjusting to D8 technologies and guidelines, as it carries around a whole lot of legacy code going back as far as Drupal 5.


What is meant here is the impact of the module on a Drupal 8 system, how quick the sitemap generation process is and how long it takes for a visitor to fetch the sitemap.

Sitemap generation performance

Both the modules feature the batch API which allows to generate huge amounts of links without hitting any PHP limits. The sitemap generation performance differs in that it is a one step process in simple_sitemap as opposed to the two step process of the xmlsitemap module.

While the simple_sitemap's one step process is less error prone and initially generates the sitemap quicker, xmlsitemap's two steps have the advantage of tracking which entities have changed since last generation through implementing an additional database table making sure the subsequent rebuilds are quicker.

Sitemap fetching performance

When it comes to fetching the sitemap, both modules cache them: xmlsitemap creates a physical file, while simple_sitemap caches the sitemap in the database.
Both modules have the ability to set the maximum amount of links included in the sitemap and they generate multiple sitemaps along with an index if this amount is exceeded.

Consequently there should be no noticeable difference between the two modules when it comes to fetching performance.

Overall system impact

Because of its leaner code base and the fact that the code does not get invoked through hooks all to often, simple_sitemap's footprint is smaller.

Supported entities

Both of the modules support all core content entity types like nodes, taxonomy terms, menu links, users, etc. as well as contributed entity types (e.g. commerce products or media) out of the box. This is possible due to the great D8 entity API. Whereas xmlsitemap keeps its bundle settings on a designated page, simple_sitemap incorporates its bundle settings into bundle edit pages, which seems a bit more intuitive. Both modules allow overriding of sitemap settings on a per-entity basis.

XML Sitemap standards

Here the edge goes to simple_sitemap, as it features the newer hreflang XML standard developed by google. In addition simple_sitemap is optionally able to index images attached to an entity resulting in an image sitemap. Adhering to Google's standards is important, as this is the search engine most of us would like to correctly understand and index our site.

Additional functionality

Having been around since 2007, the xmlsitemap module has had lots of time to incorporate various additional functionalities which are not present in simple_sitemap yet. Automatically sending the stiemap to search engines is an example of such a functionality.


Whereas xmlsitemap offers many hooks making it easy to alter the XML output, simple_sitemap's strength lies within its powerful service API allowing to chain tasks like adding custom links and altering configuration. Since version 2.11, simple_sitemap takes advantage of plugins, so new URL generators can be implemented by 3rd party modules. Depending on your needs, you may find one approach superior to the other.

Which one is right for me?

As of now, simple_sitemap is the more stable module having close to none open bug reports. As soon as xmlsitemap runs well in Drupal 8 however, you will have to decide: simple_sitemap for a more performant codebase with the newer sitemap standard and more powerful API, or xmlsitemap for its bigger feature set and a smarter sitemap generation process.

Oct 11 2015
Oct 11

Today's users are becoming increasingly spoiled by technologies allowing them to deeply interact with websites without having to create an account first. To keep up with this development and to entice users to use your website without them having to give up any personal information requires a bit of problem solving in Drupal.

The first obvious problem to solve is the increase of spam posts this set up will inevitably cause. If your site is small to medium sized, I recommend the Honeypot module configured the way it's described in this article. For huge sites, captcha will be the only way to go.

The other thing is that you probably do not want the anonymous content to be publicly visible after creation. The content must first go through an administrative approval process before it is publicly available. In both Drupal 7 and Drupal 8 you can do exactly this by unsetting the default value of the 'published' checkbox on the content type edit page.
The problem with this is that as soon as the anonymous user saves the content, they loose access rights to it and get redirected to an 'Access denied' page which is not very user friendly.

In addition to the above, you may want the anonymous user to be able to edit or even delete their own content in case they find an error right after submitting it. Users often find typos or other kinds of mistakes right after content submission.

Gbyte created the Session Node Access module to tackle exactly these issues. The module allows administrators to grant certain user roles (not only anonymous users) specific permissions to content they created. These permissions last only as long as the browsing session lasts; after that, the regular permissions apply again. This way it is possible to allow guests or users of a certain role to keep access to their content, even if it is pending for approval.

Feel free to download Session Node Access from its module page.

Session Node Access configuration screen:

Session node access configuration screen

Oct 02 2015
Oct 02

The Simple XML Sitemap module was originally created by gbyte as a temporary replacement for the non-functioning Drupal 8 XML Sitemamp Module. After putting some more work into it however, we decided to keep using it in our D8 projects, as it is very lightweight, simple to use and most importandly, adheres to a newer XML sitemap standard.

The new 2.x branch features most of the functionality of the heavier XML Sitemap module while also featuring hreflang and image XML sitemaps, which is a new Google standard for creating multilingual XML sitemaps that should improve SEO even more.

Here is the description from the module page:

Every webpage needs an automatic XML sitemap generator for SEO reasons. This module aims to be a replacement for the xmlsitemap module for Drupal 8.
Simple xml sitemap is lighter than xmlsitemap in terms of resources and usability. Its functionality differs from xmlsitemap, see this post for details.

Sitemaps generated by this module adhere to the new Google standard regarding multilingual content by creating hreflang sitemaps and image sitemaps. Googlebots will thank you later.

Upgrade path

Please do not forget to run update.php after every update.

Branch 8.x-1.x is no longer supported and there is no upgrade path. Please uninstall 8.x-1.x before installing 8.x-2.x.


The module generates a multilingual sitemap for entities and custom links. Out of the box it supports most of Drupal's content entity types including:

  • nodes
  • taxonomy terms
  • menu links
  • users
  • ...

Contributed entity types like commerce products or media can be indexed as well. Various inclusion settings can be set for bundles and overridden on a per-entity basis.

Here is a sample of the XML markup it generates. Do not forget to view the page source (ctrl+u).

Feel free to grab the module from the module page.

If you are unsure whether to get simple_sitemap or xmlsitemap, check out this comparison on the two modules.

This article describes all the new features of version 2.10.

Oct 01 2015
Oct 01

Update: Please note the article's publishing date. Some of the information presented below may not be current anymore.

With only a few critical issues left in the Drupal 8 queue and D8 being surprisingly usable, many developers already use it in small projects to play with the technology and to challenge themselves.
I have to admit, I am no exception - the embracement of many PHP technologies and (finally!) the jump to the OOP paradigm makes me want to stop writing right now and code some more.

Which projects qualify for Drupal 8 today?

I would wait a few months before creating bigger D8 projects for my clients. The community has to play some catching up first and port modules, themes and write documentation. On top of that, apart from all the OOP technologies we love, there have been some new drupalisms introduced and not documented yet - this combined with the lack of contributed module solutions makes D8 development much more time consuming for paid projects in comparison to D7.

Small projects however are very doable.

First however, it may be necessary to upgrade the server, as D8 introduces relatively high PHP and SQL requirements. See the official requirements page.

With its translation capabilities, ckeditor and views in core, creating a simple portfolio or blog website with Drupal 8 may be even quicker than using its predecessor. The built in WYSIWYG even lets you upload files directly into it. A feature for which you had to install ocupload in the past.
On a side note, going through the list of modules, you may be negatively surprised by the fact that it is impossible to disable modules - this has been done by design, check out this page. The gray area where a module has its settings saved in the database but is still disabled, is gone.

Drupal 8 module creation

The small number of contributed modules will force you to create your own, even for small changes on the site. I cannot overstate how fulfilling it is to code Drupal modules and at the same time be able to (mostly) follow PHP best practices. The .module file feels almost like a thing from the past left there to accommodate some developers and site builders resisting change. It will include all the hook functions which will hopefully only call Drupal and custom class methods anyway. Those module class files is where the real action takes place!

Variables as means to store quick data have been replaced by the much more sophisticated configuration system. This move brings many advantages including data portability, an actual relation between the module and its data (no more abandoned variables after module deletion) and many more.

Finally Caching has become smart! Cached content is able to invalidate itself depending on several circumstances, including content changes. Setting cache tags for cached module data is very easy and efficient. This system makes the heavy Drupal technology appear fast on an interpreted language like PHP.

Heads up regarding the devel module: it installs fine, however dpm() runs out of memory and leads to a WSOD, regardless of the amount of memory set. The krumo() function (or better yet your debugger) is a working alternative.

Drupal 8 template creation

You will probably find that there is no real choice in templates and that creating a custom theme is a must. Creating sub themes in Drupal is a treat. TWIG for the markup, YAML for the config files, it all fits rather well together.

For most projects you will be creating a sub theme on top of the included 'classy' theme or another contrib base theme. Adding CSS and JS files and combining them to libraries in the .libraries.yml file provides a lot of flexibility more or less deprecating the D7 libraries module.

Overriding a base theme is really simple by adding specifically named templates, including and excluding(!) certain base theme libraries.

The state of Drupal 8 SEO and spam prevention modules

While Drupal 8 comes with a well balanced selection of  core modules, there is a couple of functionalities which will be added on top in most of your projects. These are spam prevention and SEO. Drupal 7 has a ton of modules for these purposes while Drupal 8... not so much.

In terms of spam prevention, for smaller to medium projects, I recommend the dev version of the Honeypot module. See How to use the Drupal 8 honeypot module efficiently. For big projects, captcha seems to be a good choice, however currently it has some major issues.

In terms of SEO, the metatag module is far from functional (do not bother to download the dev version, it is just some test code displaying a phone number field), but this is not tragic, the metatag module's impact on SEO has been declining a lot anyway.

The real problem has been the lack of an xml sitemap generator. Google loves sitemaps, but the Drupal 8 version of the xml sitemap module is broken. This is why I created the Simple XML Sitemap. It is stable and works well with the latest beta RC release. Feel free to read about it here, or download the module directly from the Simple XML Sitemap module page.

Get ready to work around some issues

As mentioned, the documentation is really lacking, but we have to work with what we have. While everyone is focusing on the critical issues, do keep an eye on the other ones as well. From the top of my head I have experienced D8 issues like

  • css caching problems
  • language detection problems
  • self-resetting menu items
  • broken views contextual filters
  • incomplete entityQuery class
  • missing private file system image styles

... many of those still prevail until beta 15, which is supposed to be the second last beta release. I guess the only thing to do is contributing to the issue queues.

All in all though the experience has been rewarding and I strongly encourage everyone to start hacking with Drupal 8. I am looking forward to completing tons of D8 projects soon. I invite you to share your experience in the comment section.

If you would like to port your website to Drupal 8, make sure to get in touch to get info on migration feasibility and techniques and to acquire a quote.

Sep 30 2015
Sep 30

The Drupal 7 translation system including the internationalization package is a heavy beast and while it mostly gets the job done, it is all but intuitive in use.

For high volume translations it is recommended to use the translation template extractor and translate the strings externally. For small corrections however, it is often much more convenient to use the translation interface (admin/config/regional/translate/translate).

Now that you've created that shiny module/template and made it translatable by passing all strings through the t() function, you may be wondering why your newly created string is not showing up in the translate interface.

To save you some trouble, here is a short list of things to check:

1. Include the project version number within the module/theme .info file.
Without the project version information, the translate interface will not register new translatable strings in your module/theme. Make sure to add version = 7.x-1.0 into the module/template .info file.

2. Run the string through the function in a non-standard language mode.
In order for the translation system to add your translatable string, the t() function must run at least once in a non-standard language. To achieve that, you will need to switch the language of the site to one you are going to translate into and then visit the page that displays the string.

3. Mind the case sensitive search filter.
Be accurate when using the translate interface filter - it does not forgive.

4. Flush caches if necessary.
Usually this is not needed, but maybe the new translatable string gets called on on a cached page or a cached view. With many layers of caching, better make sure and rule out all possible errors.

Let me know if anything is missing in this list.
Happy translating!

Sep 28 2015
Sep 28

The Honeypot module is a great captcha alternative, as it keeps spam bots from submitting content while also saving your site visitors from having to type in mundane character combinations.
Configured properly it will prevent the majority of bots from submitting forms on your site including registration forms, contact forms, comment forms, content forms... any drupal forms.
It works differently from Captcha: it lures the bot into filling out a form field invisible to regular users. By doing so, the system recognizes the bot for what it is and denies the submission.

While being very user friendly, this reversed bot detection system comes at the cost of some bot submissions getting through anyway. This is why I would advise against using this module on large sites, where it is difficult to track every piece of submitted content. It should work well for smaller and medium sites however, it has been working well in many of my projects including this very site.

Honeypot configuration

Correct configuration of the module is extremely important, as wrong settings might make the module inefficient or worse, prevent real users from submitting forms. After configuring the module, make sure to double check it works by submitting a protected form as an anonymous user.

Once installed and enabled, go to admin/config/content/honeypot to configure the module.

First of all carefull with the "protect all forms" option, as caching will be disabled on every page that includes a form. This can be problematic in cases where e.g. a login block is embedded in the sidebar. In addition to ticking what forms to protect, there are two important settings to keep in mind.

"Honeypot time limit" sets an additional non-honeypot protection method which will assume, that a form submitted within the set amount of seconds after page load is submitted by a bot. Even though this option disables page caching, we found disabling it takes away from the module's effectiveness. Five seconds is a safe number for most cases, as human users will need more time to submit a form.

The other option is the "Honeypot element name" where the name of the honeypot form field can be set. Now some important advise: Do not use the default field name. Change it to something else. You can be creative and use age, sex, www, attractiveness and so on. We found using a different honeypot field name greatly improves bot detection. This is probably due to certain bots being preprogrammed to pass the drupal honeypots' "are you a bot" test.

At the beginning it also makes sense to check the logging checkbox lean back to learn how many submissions are being blocked by the module and possibly lock the ip addresses.

Honeypot in Drupal 8

The D8 branch of honeypot is very usable, however I recommend the development version (> 8.x-1.x-dev) for now. The stable version has some caching problems breaking the "time limit" function. The development version works very well though.

Link to honeypot module page.

If you develop with Drupal 8, make sure to check out the article What to keep in mind when creating Drupal 8 projects - for developers.

Mar 24 2015
Mar 24

Gbyte wurde vor einiger Zeit beauftragt den Relaunch der größten deutschen Plattform für Bewerber eines kreativen Studiums - (link zur alten Plattform) - umzusetzen und nach einer hervorragenden Zusammenarbeit mit projektgestalten UG bin ich stolz zu verkünden, dass sich die Umsetzung des Projekts dem Ende neigt.

Die alte Plattform wird um eine Masse an Funktionen erweitert, die andere Community-Seiten dieser Art in den Schatten stellen wird.
Die von der alten Plattform migrierten umfangreichen Datensätze finden ein neues Zuhause in einer stark angepaßten Drupal 7 Installation.

Dabei ermöglicht die Umsetzung es die schon vorhandenen Datensätze auf neuartige Weise für die User nützlich zu machen:
Bewerber werden in der Lage sein alles Mögliche über Hochschulen, deren künstlerische Eignungsprüfungen und efolgreiche Bewerbungsmappen zu erfahren, ihre  eigenen Arbeiten der Community zu präsentieren und sich mit anderen auszutauschen. Dabei sind das neue Forum und die Blogfunktionalität nur zwei von vielen Kanälen, die einen solchen Austausch ermöglichen werden. Mappenkursanbieter und Hochschulbetreiber werden ihre Institutionen auf der Plattform veröffentlichen können und diese Einträge pflegen.

Eine der Innovationen der neuen Plattform ist ihre Aufgeschlossenheit gegenüber nicht registrierten Benutzern. Durch neuartige Lösungen wird es den Gästen ermöglicht am Communitygeschehen teilzuhaben, ohne sich anmelden zu müssen. Sowohl Gäste, als auch angemeldete Benutzer werden Hochschuleinträge bearbeiten, Blogposts schreiben, und vieles mehr.

Mit zunehmender Anzahl an internationalen Usern setzt die neue Plattform auf Mehrsprachigkeit. Nicht nur die Benutzeroberfläche, sondern auch die Inhalte werden übersetzbar sein.

Die Umsetzung eines so komplexen Systems brachte natürlich viele Herausforderungen mit sich. Dabei wurden die Grenzen von Drupal 7 und seinen Contrib Modulen getestet und wo immer diese überschritten wurden, mussten neue Module her. Mehrere Custom Module, die Gbyte für programmiert hat sorgen für einzigartiger Funktionalität, als auch dafür, dass sowohl Drupal Core, als auch andere Community Module aktualisierbar bleiben.

Zu den technologischen Highlights der Seite gehören unter Anderem

  • fortsetzbare AJAX Bulkuploads für Bilder für einen komfortablen Medienupload (Mappenbilder, Bilder auf Hochschulseiten, usw.)
  • mächtige, dynamische Suchfilter, deren Elemente sich relativ zu den anderen Filtern anpassen (ähnlich wie schon  von uns im Buchtmonitor implementiert)
  • die Möglichkeit für unangemeldete Benutzer ihre eigenen Inhalte zu bearbeiten
  • hohe Interaktion zwischen Benutzern gegeben durch das (Drupal-) Forum, die Benutzerblogs und Kommentare unter den meisten Inhalten
  • starke Integration von Benutzerprofilen in allen obengenannten Kommunikationskanälen, damit Transparenz unter Benutzern gegeben ist
  • Mächtiges, Views- angetriebenes Administrationsbackend für Administratoren, Moderatoren und Redakteure.

Obwohl immer noch kleine Features hinzugefügt und die bestehenden justiert werden, kommt das Projekt langsam in die Testphase. Ich kann nicht genau sagen wann es soweit ist, aber freue mich schon auf die Verkündigung des Projektabschlusses.

Bei Interesse an unserer Arbeit, freuen wir uns auf eine
Nachricht von Ihnen.

Feb 26 2015
Feb 26

If you need a simple Views display switcher to toggle e.g between a list and a grid display of a view, there are a couple of plug & play options already.

Display Suite and Quick Tabs are modules which provide this functionality but they seem to be quite an overkill for a simple display switcher. View modes on the other hand seems to be exactly what is needed, however there is no stable version and the development one did not work for me.

Our use case dictates that the filters and the page number have to stay intact while switching to a different views display. The page will be reloaded, no AJAX magic here.

So let's create our own views display switcher. In order to do that you will obviously be needing a view with at least two page displays showing content in different ways. You will also have to put some code into your custom module. If in doubt, refer to the countless other tutorials.

Set up your view

In the view named [view] set the path of [display_1] to e.g [page/grid], the path to [display_2] to e.g [page/list].

Create callback function

Create a simple callback function which will provide the switch in ready-to-be-displayed HTML.

* Gets HTML output of a switch which will switch between grid and list display of a view.
function [mymodule]_get_views_display_switch() { $switch = l(t('Grid'), '[page/grid]', array(
'query' => drupal_get_query_parameters(), // This ensures the view will keep filter settings when switching the display.
'attributes' => array(
'class' => array('page-grid-switch') // Adding a css class for this link.
$switch .= ' | ';
$switch .= l(t('List'), '[page/list]', array(
'query' => drupal_get_query_parameters(),
'attributes' => array(
'class' => array('page-list-switch')))); // Adding CSS class for whole switch.
$switch = "" . $switch . "";

Implement views hook

Implement hook_views_pre_view hook to add the switch to the view.

* Implements hook_views_pre_view().
function [mymodule]_views_pre_view(&$view, &$display_id, &$args) {
  if (
$view->name == '[view]' && $display_id == '[display1]' || $display_id == '[display_2]') { // Adds a display switcher to the header of a view.
    // 'footer' as second parameter will add the display switcher to the footer of the view instead.
$view->add_item($display_id, 'header', 'views', 'area', array('content' => [mymodule]_get_views_display_switch(), 'format' => 'full_html'));

This should do it. The l() function will make sure the link is marked active when it's active and drupal_get_query_parameters() makes sure the filters and current page stay intact while swichting.

Feb 26 2015
Feb 26

If you need a simple Views display switch to toggle e.g between a list and a grid display of a view, there are a couple of plug & play options already.

Display Suite and Quick Tabs are modules which provide this functionality but they seem to be quite an overkill for a simple display switch. View modes on the other hand seems to be exactly what is needed, however there is no stable version and the development one did not work for me.

How it needs to work

Our use case dictates that while switching a display, the view needs to retain the exposed filter values and page number. The page will be reloaded, no AJAX magic here.

Views display switch

So let's create our own views display switch. In order to do that you will obviously be needing a view with at least two page displays showing content in different ways. You will also have to put some code into your custom module. If in doubt, refer to the countless other tutorials.

Set up your view

In the view named [view] set the path of [display_1] to e.g [page/grid], the path to [display_2] to e.g [page/list].

Create callback function

Create a simple callback function which will provide the switch in ready-to-be-displayed HTML.

  1. /**

  2.  * Gets HTML output of a switch which will switch between grid and list display of a view.

  3.  */

  4. function [mymodule]_get_views_display_switch() {

  5. $switch = l(t('Grid'), '[page/grid]', array(
  6. 'query' => drupal_get_query_parameters(), // This ensures the view will keep filter settings when switching the display.

  7. 'class' => array('page-grid-switch') // Adding a css class for this link.
  8. )

  9. ));

  10. $switch .= ' | ';

  11. $switch .= l(t('List'), '[page/list]', array(
  12. 'query' => drupal_get_query_parameters(),

  13. 'class' => array('page-list-switch')
  14. )

  15. ));

  16. // Adding CSS class for whole switch.

  17. $switch = "<div class='page-display-switch'>" . $switch . "</div>";

  18. return $switch;

  19. }

Implement views hook

Implement hook_views_pre_view hook to add the switch to the view.

  1. /**

  2.  * Implements hook_views_pre_view().

  3.  */

  4. function [mymodule]_views_pre_view(&$view, &$display_id, &$args) {

  5. if ($view->name == '[view]' && $display_id == '[display1]' || $display_id == '[display_2]') {

  6. // Adds a display switch to the header of a view.

  7. // 'footer' as second parameter will add the display switch to the footer of the view instead.

  8. $view->add_item($display_id, 'header', 'views', 'area', array('content' => [mymodule]_get_views_display_switch(), 'format' => 'full_html'));
  9. }

  10. }

This should do it. The l() function will make sure the link is marked active when it's active and drupal_get_query_parameters() makes sure the exposed filters and current page are retained while swichting.

Nov 04 2014
Nov 04

The ability to fork is a wonderful thing.

In the open source community, the ability to fork software projects is a wonderful thing, as it allows taking a software snapshot in a completely different direction from what was intended by its current maintainers.

Projects get forked for reasons that can be categorized in political (changing ownership rights, controversial decisions made by the project maintainers, etc.), technology related (where maintainers disagree about the direction of development and implementation) and personal.

Forking is a bad thing.

Wait... did you not just say forking was wonderful?
The ability to fork is wonderful, as it gives great power to the community. But forking itself is bad for the project, as it results in two projects with weaker development and support, a weakened potential to grow and a divided and confused user base. It leads not only to separate code bases, but also to a divided developer and user community and should be considered last resort.
In the best case scenario, forking is choosing the lesser evil.

No matter how much effort is put into collaboration between the fork and the original project, in the end it always ends in lack of compatibility and refusal to provide support to confused users in the different camp. This is why the Backdrop creators' reassuring statements about cross contribution should be taken with a grain of salt.

Why is forking Drupal into Backdrop a bad thing.

I will not spend much time summarizing the motivations behind Backdrop - go ahead and take a look here. The problem with these motivations is that, using my previous attempt to categorize reasons for forking, they are very technology related. In essence it seems the main reason is conservatism and fear of all the new things that come with Drupal 8:

Drupal 8 is using many new libraries and established technologies instead of further developing its own ones - bad.

Drupal 8 is utilizing OOP - what is it and who needs it? Writing loose functions in hooks has been working fine so far, why change that?

Drupal 8 is not backwards compatible (quelle surprise). Maybe I should have stayed with Wordpress. Nah, Iet's fork Drupal instead.

Drupal 8 forces established developers to learn something new. Obviously it's more convenient to do the same thing over and over instead.

'Why fix if it ain't broke?' I hear all the time.
In IT one should change something as soon as it is out of date instead of waiting until it breaks. Drupal has established itself as a bleeding edge CMF and bleeding edge often means utilizing new technologies, a scalable programming paradigm (hello OOP) and breaking backward compatibility. It also means that one has to relearn stuff sometimes. If I do not want to relearn stuff, I do not go into IT and certainly not Drupal.

Let me quote Dries' opinion on embracing change from his blog post:

The reason Drupal has been successful is because we always made big, forward-looking changes. It’s a cliché, but change has always been the only constant in Drupal. The result is that Drupal has stayed relevant, unlike nearly every other Open Source CMS over the years. The biggest risk for our project is that we don't embrace change.

It is impossible to assess how harmful a fork will be for the future of Drupal, if Backdrop succeeds in attracting even a small fraction of our developers and users. A lot of potential may be wasted on a project whose existence is not really justified. More probable however will be the fork's natural death.

What action should be taken instead?

When publishing your first Drupal module, it first has to go through a tough process, in which among other things, the community thoroughly tests whether it is duplicating functionality of other modules. Quotes get thrown at contributors like 'collaboration over competition'. The process takes a long time in which many projects are discarded and often rightfully so.

Dear creators of Backdrop, why not apply this 'collaboration over competition' philosophy to the bigger picture, and instead of forking Drupal, be more present in the discussion groups and steer the development of Drupal 9 into the direction you feel comfortable with? If you find that the bigger part of the community is not interested in having it your way, how about contributing an api module, so that the minority in question can profit? You can create a community around a module inside the Drupal ecosystem and be successful with it. If time shows your ideas to be of great value, they will be ported into core.

Backdrop is not about users, it is mainly about developers refusing to adapt and invest their time to further improve the Drupal platform. My appeal to them is not to hide behind claims like 'no backwards compatibility' or 'too complex to learn' and instead of dividing the community, start contributing to the bleeding edge Drupal platform we have learned to love.

Feel invited to discuss in the comment section!

Sep 08 2014
Sep 08

Why check if term is associated to a node prior to deletion?

In cases where taxonomy terms are used only for categorizing content on a Drupal powered web page, there should be no harm in deleting them. However sometimes taxonomy is used to store terms critical to the content they are referenced from and in this case steps should be taken to prevent an accidental deletion.

I have encountered such a case on a project I am working on which is soon to become a web platform for university students. When creating a faculty node, its name is being defined by choosing a term from the 'faculties' vocabulary. Deleting a term assigned to such a faculty node would lead to... well undesired effects.


When looking for the right hook you will find that there is no hook_taxonomy_pre_delete and using the existing hook_taxonomy_term_delete would be too late (the term would be deleted by then). (By the way, this problem persists across other entity types, like nodes - hoping to see some added hooks in D8.)

I will describe an easy way of preventing the deletion of a used taxonomy term, but be warned, this will only prevent the deletion of a term in the UI, it will not react to programmatically deleted terms.

Here is how this is going to look like:

Taxonomy term delete warning.

Some code:

  1. // In our custom module 'mymodule' let's go ahead and implement hook_form_alter() (what else!?) and switch on $form_id.

  2. function mymodule_form_alter(&$form, &$form_state, $form_id) {

  3. switch ($form_id) {

  4. // This is the general term form.

  5. case 'taxonomy_form_term':

  6. // Checking if we are on the delete confirmation form.

  7. if (isset($form['delete'])) {
  8. // Getting the term id.

  9. $tid = $form['#term']->tid;

  10. // Limiting the query to 30 to save resources when loading the nodes later on.

  11. $limit = 30;

  12. // Getting node ids referencing this term and setting a limit.

  13. $result = taxonomy_select_nodes($tid, FALSE, $limit);

  14. if (count($result) > 0) {
  15. $markup = t('This term is being used in nodes and cannot be deleted. Please remove this taxonomy term from the following nodes first:') . '<ul>';

  16. // Looping through the node ids, loading nodes to get their names and urls.

  17. foreach($result as $nid) {

  18. $node = node_load($nid); // This is quite resource hungry, so if dealing with loads of nodes, make sure you apply a limit in taxonomy_select_nodes().

  19. if (!$node)

  20. continue;

  21. $markup .= '<li>' . l($node->title, 'node/' . $node->nid, array('attributes' => array('target'=>'_blank'))) . '</li>';
  22. }

  23. // Appending some text with ellipsis at the end of list in case there might be more nodes referencing this term than the ones displayed.

  24. if (count($result) >= $limit)
  25. $markup .= '<li>' . t("... only the first @limit results are displayed.", array('@limit' => $limit)) . '</li>';
  26. $markup .= '</ul>';

  27. // Using the render array's markup key to display our warning message.

  28. $form['description']['#markup'] = $markup;

  29. // Removing the 'delete' button from the form.

  30. $form['actions']['submit']['#access'] = FALSE;

  31. // $form['actions']['submit']['#disabled'] = TRUE; // Disables the button instead of removing it.

  32. }

  33. }

  34. break;

  35. }

  36. }


TL;DR: We want to prevent people deleting taxonomy terms which are already associated to nodes. We use hook_form_alter on the form taxonomy_form_term and use the function taxonomy_select_nodes to check if a node uses the term in question, then we display links to the nodes and remove the 'delete' button.

Feel invited to comment!

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