Upgrade Your Drupal Skills

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

See Advanced Courses NAH, I know Enough
Jun 03 2021
Jun 03

Occasionally I find myself needing plugin-like functionality, where users/downstream can throw a class into a folder and expect it to work. My script is supposed to find and instantiate these plugins during runtime without keeping track of their existence.

In a regular Drupal module, one would usually use the plugin architecture, but that comes with its overhead of boilerplate code and may not be the solution for the simplest of use cases.

Many class finder libraries rely on get_declared_classes() which may not be helpful, as the classes in question may not have been declared yet.

If you are on a Drupal 8/9 installation and want to use components already available to you, the Symfony (file) Finder can be an alternative for finding classes in a given namespace.

Installing dependencies

Ouside of Drupal 8/9, you may need to require this library in your application:

  1. composer require symfony/finder

A simple example

  1. use Symfony\Component\Finder\Finder;

  2. class PluginLoader {

  3. /**

  4.   * Loads all plugins.

  5.   *

  6.   * @param string $namespace

  7.   * Namespace required for a class to be considered a plugin.

  8.   * @param string $search_root_path

  9.   * Search classes recursively starting from this folder.

  10.   * The default is the folder this here class resides in.

  11.   *

  12.   * @return object[]

  13.   * Array of instantiated plugins

  14.   */

  15. public static function loadPlugins(string $namespace, string $search_root_path = __DIR__): array {
  16. $finder = new Finder();

  17. $finder->files()->in($search_root_path)->name('*.php');

  18. foreach ($finder as $file) {

  19. $class_name = rtrim($namespace, '\\') . '\\' . $file->getFilenameWithoutExtension();
  20. try {

  21. $plugins[] = new $class_name();

  22. }

  23. catch (\Throwable $e) {

  24. continue;

  25. }

  26. }

  27. }

  28. return $plugins ?? [];

  29. }

  30. }


  1. $plugin_instances = PluginLoader::loadPlugins('\Some\Namespace');

This is just an abstract catch-all example with a couple of obvious problems which can be circumvented when using more specific code.

In the above example, the finder looks for all files with the .php extension within all folders in a given path. If it finds a class, it tries to instantiate it. The try-catch block is for it to not fail when trying to instantiate non-instantiatable classes, interfaces and similar.

The above can be improved upon by making assumptions about the class name (one could be looking for class files named *Plugin.php) and examining the file content (which the Finder component is capable of as well).

Let me know of other simple ways of tackling this problem!

Dec 12 2020
Dec 12 is not your generic community platform - it's a tool that actively creates and enforces communication channels between the people and their political representatives thereby strengthening the democratic process while also being a comprehensive source of information of the political system in Germany.

Because of that and because of the project's high functionality and high efficiency requirements, it is one gbyte is particularly proud to be involved in.

The project has recently won the Drupal Splash Awards 2020 for Germany & Austria in the non-profit category. It is a huge compliment to the small team of developers and to Parlamentwatch e. V. (the organization behind the service), as the Splash awards are regarded as the most prestigious award within the Drupal community.

Splash awards certificate

Award ceremony: Youtube video (with timestamp)

Splash awards entry: Entry description

Drop us a line if you are interested in what makes this awesome project tick.

Aug 09 2020
Aug 09

The answer is... generally run updates first. Whether to import or export the configuration afterwards depends on who updated the contrib code base.

You are updating the contrib code base

If you are updating the contrib code base, run the database updates and then export the configuration, as updates tend to alter the configuration storage data which needs to be commited into the version control system:

  • Pull changes from the repository
  • drush cim to import your colleagues' configuration changes
  • composer update to update the contrib code base
  • drush updb to update the Drupal database
  • drush cex to export potential configuration changes after the update
  • Commit changes into the repository


git pull \
&& drush cim -y \
&& composer update \
&& drush updb -y \
&& drush cex -y \
&& git commit

Someone else updated the contrib code base

  • Pull changes from the repository
  • composer install to synchronize your contrib code base with the remote
  • drush updb to update the Drupal database
  • drush cim to import your colleagues' configuration changes


git pull \
&& composer install \
&& drush updb -y \
&& drush cim -y

Jan 15 2020
Jan 15

When migrating content with the Drupal 8 migrate module, the creation and updating of new entities may fire lots of custom module hooks. This may or may not be desired; if you have found yourself here, it probably interferes with the source data in a problematic way, or unnecessarily slows down the migration process.

The cleanest way I found to stop specific hooks for specific migrations, is to add a dummy/meta field to the migration and check for its value in the hook.

Include a dummy field in the migration

In the process section of the migration, add a field with a name that will not interfere with any field name of the target entity:

  1. # This is the field that will provide a custom hook

  2. # with the information about the migration.

  3. _migration:

  4. - plugin: default_value

  5. default_value: 'blog_categories'

This is an example migration with CSV as source and taxonomy terms as target:

  1. id: blog_categories

  2. label: 'Blog category migration'

  3. migration_group: default

  4. source:

  5. plugin: csv

  6. path: 'public://migrations/blog_categories.csv'

  7. delimiter: ','

  8. enclosure: '"'

  9. header_row_count: 1

  10. ids: [tid]

  11. process:

  12. name: name

  13. # This is the field that will provide a custom hook

  14. # with the information about the migration.

  15. _migration:

  16. - plugin: default_value

  17. default_value: 'blog_categories'

  18. destination:

  19. plugin: entity:taxonomy_term

  20. default_bundle: blog_categories

  21. migration_dependencies:

  22. required: {}

  23. optional: {}

Check for the value in an entity hook

  1. /**

  2.  * Implements hook_entity_update().

  3.  */

  4. function my_module_entity_update(Drupal\Core\Entity\EntityInterface $entity) {

  5. if (isset($entity->_migration) && $entity->_migration === 'blog_categories') {
  6. return;

  7. }

  8. // Some undesired custom hook logic.

  9. }

In this case the hook will never fire for this specific migration, but may fire for other migrations. Skipping the second condition will make sure the hook will never fire for migrations where the _migration dummy field is defined.

Mar 12 2019
Mar 12

Simple XML sitemap 3.1 has been released

The third major version of simple_sitemap has been long in the making bringing a more reliable generation process, a significantly more versatile API and many new functionalities. The first minor ugrade of the 3.x branch comes with views support and human readable sitemaps.

Major new features in 3.1

Simple XML Sitemap views supportViews and views arguments 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 variations created by view arguments however 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 makes it easily doable via the UI.

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

Human-readable sitemaps with XSL stylesheets


Sitemap without XSL


XML sitemap with XSL stylesheet

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.

Other improvements

You can see the list of bug fixess and improvements on the module's release page.

Upgrade path

The module upgrades fine from any of the 2.x and 3.x versions.

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

For more information about the 3.x branch of the module, see this post. 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!

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.

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