Planet Drupal

Subscribe to Planet Drupal feed - aggregated feeds in category Planet Drupal
Updated: 6 hours 37 min ago Migrating Drupal 7 Files into Drupal 8 / 9 Media entities

Tue, 2021-07-27 10:14

This article assumes a basic knowledge of the building of custom modules, the Drupal 8 / 9 Migration system, and the processes behind creating customised migrations from a previous version of Drupal.

One of the more common components of any migration from a previous version of Drupal is the need to migrate files. In Drupal 7 there was a core ‘File’ entity type and on pretty much all of our clients' sites we would also have the contributed module File Entity enabled. This extended the core file functionality and gave the ability to add fields to the file entity, have separate file types, integrate with views and more.

As of Drupal 8.4.x and above, Drupal Core has the concept of a ‘Media’ entity type that allows you to upload, manage and reuse files and multimedia assets. This acts as a replacement for the standard core File entity upload field and once you have configured a Media entity reference field on your entity type, it allows you to reference previously uploaded files from a Media library, instead of having to upload a new file every time.

Internally, a Media entity references a file entity and this process happens automatically for uploads made through the site frontend. When you upload a file to a Media entity reference field that has been set to use the 'Media Library' field widget, Drupal will handle importing the file, creating the entry in the file_managed table, creating the Media entity and then referencing the newly created file in the file reference field on the media entity. You can then reference this newly created media entity in any other media entity reference fields that allow referencing the same media entity bundles as this field.

The Core Drupal 8 / 9 migration of files doesn’t automatically allow you to migrate from Files to Media entities. The core file migration plugin for Drupal 7 to 8 / 9 just provides a migration route for Files to Files, and any auto-generated entity to entity migrations (provided by the migrate_drupal core module) will only support a File reference field to a File reference field. However, we want the ability to migrate into Media entities, so read on to see how to accomplish this with a bit of custom code.

The Process

The process of getting Files migrated as Media entities is a two-step process. First, we need a migration to migrate the Drupal 7 Files into our Drupal 8 / 9 site as Files, and then we need one or more secondary migrations (one per file/media type) to create Media entities from those now-migrated Files.

Creating the custom module

First of all, we’ll need to create a new custom module (if you haven’t already got a custom migration module in place already!). You can name this anything you want - usually, we’d use a project name prefix followed by the name of the module but for this tutorial, we’ll just call the module ‘example_custom_migration’ for simplicity.

name: Example Custom Migration description: Includes a custom migration for files. package: example type: module core_version_requirement: ^8.8.0 || ^9.0 dependencies: - migrate:migrate

This is a minimal module info.yml file in which we include a dependency on the core migrate module.

Writing the migration source plugin

Next up we’ll need to create our custom migration source plugin. This is the plugin that we will reference in our migration(s) later on. We are going to be extending the core File migration source plugin to give the ability to filter the source files we want to migrate by file type, which we’ll need later on in order to determine which files will be referenced by which Media entity type.

We’ll name the plugin class file FileByType.php and it will live inside of our custom module in the directory src/Plugin/migrate/source. Be sure that the directory structure matches that exactly as Drupal 8 uses the PSR-4 standard for PHP autoloading. For more information about the PSR-4 standard with regards to Drupal 8 / 9 development, read this article.

We’ll start writing the class by extending the core File migration process plugin.


<?php namespace Drupal\example_custom_migration\Plugin\migrate\source; use Drupal\file\Plugin\migrate\source\d7\File; /** * Drupal 7 file source (optionally filtered by type) from database. * * @MigrateSource( * id = "d7_file_by_type", * source_module = "file" * ) */ class FileByType extends File { }

We only need to override two methods provided by the File class, query() and fields().

Our query() method override will handle filtering the files by a configured file type that we can pass into the plugin. The fields() method override will include the names of any extra fields that we will be returning in addition to the fields from the base File class.

The query() function

First of all, let's write the query function. The start of the function will call the parent query() method which will build up the query to query the Drupal 7 file_managed table for all files that are not stored under the temporary:// scheme, also providing the ability to only return files for a specified scheme based on the scheme configuration parameter. We don’t need to modify anything about the innards of the parent method so we can just call it here to save having the same code duplicated.

/** * {@inheritdoc} */ public function query() { $query = parent::query(); }

Next, we’ll add our customisations to support filtering the files by a specific file type. Later on, this will be passed in as a configuration option from our migration.

// Filter by file type, if configured. if (isset($this->configuration['type'])) { $query->condition('f.type', $this->configuration['type']); }

As we are migrating into Media entities, we may want additional data such as image alt and title text values from the Drupal 7 database. So we’ll support (optionally) returning this data as well.

// Get the alt text, if configured. if (isset($this->configuration['get_alt'])) { $alt_alias = $query->addJoin('left', 'field_data_field_file_image_alt_text', 'alt', 'f.fid = %alias.entity_id'); $query->addField($alt_alias, 'field_file_image_alt_text_value', 'alt'); } // Get the title text, if configured. if (isset($this->configuration['get_title'])) { $title_alias = $query->addJoin('left', 'field_data_field_file_image_title_text', 'title', 'f.fid = %alias.entity_id'); $query->addField($title_alias, 'field_file_image_title_text_value', 'title'); }

Later on, when we are writing the migration for ‘Image’ media items we will be including two additional configuration lines in the call to our source plugin, get_alt and get_title. When we write the migration for ‘Document’ media items, we don’t want alt text and title text as they aren’t applicable, so we will omit them from the configuration there.

The finished query() function will look like this:

/** * {@inheritdoc} */ public function query() { $query = parent::query(); // Filter by file type, if configured. if (isset($this->configuration['type'])) { $query->condition('f.type', $this->configuration['type']); } // Get the alt text, if configured. if (isset($this->configuration['get_alt'])) { $alt_alias = $query->addJoin('left', 'field_data_field_file_image_alt_text', 'alt', 'f.fid = %alias.entity_id'); $query->addField($alt_alias, 'field_file_image_alt_text_value', 'alt'); } // Get the title text, if configured. if (isset($this->configuration['get_title'])) { $title_alias = $query->addJoin('left', 'field_data_field_file_image_title_text', 'title', 'f.fid = %alias.entity_id'); $query->addField($title_alias, 'field_file_image_title_text_value', 'title'); } }

If you wanted to add any further logic in this function to return any other field data (your fieldable files may have extra field data that you need!) then you could do so here.

The fields() function

In the fields function, we want to return the additional fields that this source plugin now provides (file type, alt text, title text), in addition to the standard fields the File plugin already provides.

/** * {@inheritdoc} */ public function fields() { $fields = parent::fields(); $fields['type'] = $this->t('The type of file.'); $fields['alt'] = $this->t('Alt text of the file (if present)'); $fields['title'] = $this->t('Title text of the file (if present)'); return $fields; }

We first call parent::fields() to get the list of fields from the parent method and then add our additional fields onto the $fields array afterwards. If you were to add further logic to the query() method which returned further additional fields, you would also want to add these here as well.

The finished source plugin

The finished source plugin should now look like this:

<?php namespace Drupal\example_custom_migration\Plugin\migrate\source; use Drupal\file\Plugin\migrate\source\d7\File; /** * Drupal 7 file source (optionally filtered by type) from database. * * @MigrateSource( * id = "d7_file_by_type", * source_module = "file" * ) */ class FileByType extends File { /** * {@inheritdoc} */ public function query() { $query = parent::query(); // Filter by file type, if configured. if (isset($this->configuration['type'])) { $query->condition('f.type', $this->configuration['type']); } // Get the alt text, if configured. if (isset($this->configuration['get_alt'])) { $alt_alias = $query->addJoin('left', 'field_data_field_file_image_alt_text', 'alt', 'f.fid = %alias.entity_id'); $query->addField($alt_alias, 'field_file_image_alt_text_value', 'alt'); } // Get the title text, if configured. if (isset($this->configuration['get_title'])) { $title_alias = $query->addJoin('left', 'field_data_field_file_image_title_text', 'title', 'f.fid = %alias.entity_id'); $query->addField($title_alias, 'field_file_image_title_text_value', 'title'); } } /** * {@inheritdoc} */ public function fields() { $fields = parent::fields(); $fields['type'] = $this->t('The type of file.'); $fields['alt'] = $this->t('Alt text of the file (if present)'); $fields['title'] = $this->t('Title text of the file (if present)'); return $fields; } } Writing the Migrations

As you will hopefully know already, since Drupal 8.1.x, migrations are now Plugins instead of Configuration, which means we can just add them into a migrations folder inside of our custom module. You no longer have to include the migrations as config in a config/install folder inside of your module or worry about deleting and re-importing said config to pick up any changes that were made. You simply need to do a cache rebuild and the changes will be picked up automatically.

The First migration - Drupal 7 Files to Drupal 8 / 9 Files

Create the migrations directory inside of your custom module which will contain this migration and the other ones later on. Then create the file that will contain our migration plugin and call it example_custom_migration.upgrade_d7_file.yml

At this point, the innards of our migration plugin definition will be pretty much the same as the core Drupal 7 migration file plugin. At this point, we are not using our custom migration source plugin that we wrote earlier. The standard d7_file plugin provided by core will suffice to initially migrate all of the files, regardless of their type.

id: upgrade_d7_file class: Drupal\migrate\Plugin\Migration migration_tags: - 'Drupal 7' - Content migration_group: migrate_drupal_7 label: 'Public files' source: plugin: d7_file scheme: public constants: source_base_path: /path/to/your/drupal7/webroot process: fid: fid filename: filename source_full_path: - plugin: concat delimiter: / source: - constants/source_base_path - filepath - plugin: urlencode uri: plugin: file_copy source: - '@source_full_path' - uri filemime: filemime status: status created: timestamp changed: timestamp uid: uid destination: plugin: 'entity:file'

As you can see, this is pretty standard stuff for a migration plugin and we aren’t doing anything overly complex yet. This plugin definition will ensure we grab all the files from the Drupal 7 site and migrate them as files onto our Drupal 8 / 9 site.

You may notice the ‘migration_group’ key which isn’t something you would find with core migration plugins. This is an optional key that the migrate_plus module allows you to define in your migration plugin that allows you to group your migrations together and share configuration between migrations. This enables you to then run the migration(s) as a group through both the Migrate UI and through the command-line (Drush). This is a feature we use quite a lot when building Drupal to Drupal migrations! You don’t have to have migrate_plus in order to run these migrations but the grouping stuff is very useful.

Next up, we need to write the migration(s) to turn these Files into Media entities.

The Secondary migration(s) - Drupal 8 / 9 Files to Media

For this bit I’m assuming that you already have created the various media entity types in the site UI for ‘image’ and ‘document’ media types. If you haven’t, be sure to go ahead and create them and if you decide to name them something different you’ll have to update the relevant places in the scripts.


Now we have the file migration in place, we’ll begin by writing the migration for the Image media entities. Create a new file named example_custom_migration.upgrade_d7_file_to_media_image.yml inside of the migrations folder with the following contents.

id: upgrade_d7_file_to_media_image class: Drupal\migrate\Plugin\Migration migration_tags: - 'Drupal 7' - Content migration_group: migrate_drupal_7 label: 'Migrate Media image entities' source: plugin: d7_file_by_type scheme: public type: image get_alt: true get_title: true constants: source_base_path: /path/to/your/drupal7/webroot process: field_media_image/target_id: - plugin: migration_lookup migration: upgrade_d7_file source: fid - plugin: skip_on_empty method: row thumbnail/target_id: plugin: migration_lookup migration: upgrade_d7_file source: fid field_media_image/alt: alt field_media_image/title: title status: status created: timestamp changed: timestamp uid: uid destination: plugin: 'entity:media' default_bundle: image migration_dependencies: required: - upgrade_d7_file

Let’s break this down section by section.

In the source section, you can see that this time we are now using our custom source plugin d7_file_by_type that we wrote earlier. We are also passing in a couple of additional options to the plugin; get_alt and get_title that will enable the plugin to return the additional alt text and title text data that we want for our image media entities.

In the process section we write to the appropriate fields on the media entity; field_media_image/target_id and thumbnail/target_id to save the references to our file, field_media_image/alt and field_media_image/title to save the alt and title values. We also save the standard entity data like our file migration before; status, created, changed, and uid.

The destination plugin we use this time is ‘entity:media’ because we are now creating a media entity, and the default_bundle is set to ‘image’ as that’s the media bundle type we are using for this migration.

Finally, we add a required migration_dependency on our upgrade_d7_file migration. This will ensure that the migration will only run once the file one has fully ran and imported all of the available items.


The document migration will be pretty similar to the image migration that we wrote above.

id: upgrade_d7_file_to_media_document class: Drupal\migrate\Plugin\Migration migration_tags: - 'Drupal 7' - Content migration_group: migrate_drupal_7 label: 'Migrate Media file entities' source: plugin: d7_file_by_type scheme: public type: document constants: source_base_path: /path/to/your/drupal7/webroot process: field_media_file/target_id: - plugin: migration_lookup migration: upgrade_d7_file source: fid - plugin: skip_on_empty method: row field_media_file/display: plugin: default_value default_value: 1 status: status created: timestamp changed: timestamp uid: uid destination: plugin: 'entity:media' default_bundle: document migration_dependencies: required: - upgrade_d7_file

The source section is the same except this time we pass in a type of document to our source plugin and as we don’t need the alt and title text data that we needed for the image media, we’ll omit the get_alt and get_title configuration lines.

The process section again is similar, except this time we are setting the referenced file inside of field_media_file/target_id and we also set the field_media_file/display to 1 which will enable the file to be displayed when viewing content. Again, we omit the get_alt and get_title bits and the rest of the configuration here are the same entity properties as before.

We keep the destination plugin the same (entity:media) but the default_bundle is changed to ‘document’.

Before running the migrations.

Before we are able to run our migrations, we need to make sure that your Drupal 8 / 9 site knows how to connect to your Drupal 7 site. If you are already mid-way through writing migrations from Drupal 7 to Drupal 8 / 9 then you will most likely already have this in place, but if not, you need to ensure your settings.php (or settings.local.php - wherever your current site database connection details are present!) has an entry with the connection details to the Drupal 7 site.

e.g. inside of settings.local.php

$databases['drupal_7']['default'] = [ 'database' => ‘your_database_name’, 'username' => 'your_database_user_name', 'password' => 'your_database_user_pass', 'prefix' => '', 'host' => 'localhost', 'port' => '3306', 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql', 'driver' => 'mysql', ];

Note the connection key drupal_7 there, in the $databases definition. We also need to ensure that our migration group knows that we should be using this key if it doesn’t already.

If you have already got a migration_group configuration setup as part of a Drupal -> Drupal site migration (you most likely will if you have migrate_plus enabled and have begun work on a migration) then ensure in the shared_configuration section at the bottom that you have the following:

shared_configuration: source: key: drupal_7

If you don’t have a migration group config already, then create a new one named migrate_plus.migration_group.migrate_drupal_7.yml

langcode: en status: true dependencies: { } id: migrate_drupal_7 label: 'Import from Drupal 7' description: '' source_type: 'Drupal 7' module: null shared_configuration: source: key: drupal_7

Put it into your site’s configuration directory (e.g. config/sync) and import the config.

If for some reason you aren’t using migrate_plus with migration groups then you’ll need to ensure in the source section of each migration plugin we have written earlier that you have

key: drupal_7

in the plugin definition. e.g. (for the document plugin this would be as follows)

source: plugin: d7_file_by_type scheme: public type: document key: drupal_7 constants: source_base_path: /path/to/your/drupal7/webroot Running the migrations

Nearly there (I promise!). Now you have your migration plugins all correct and in place, it’s time to try them out! First, ensure your module is enabled if it isn’t already (either via the extend menu in the UI or via Drush - drush en example_custom_migration).

Next, you can either try running the migrations through the UI (admin/structure/migrate) or via Drush (if you are using the migrate_tools module version 4.x / 5.x and Drush 9.x / 10.3.x OR Drush 10.4.x+ which includes most of the Drush commands the migrate_tools module included..) I’m a Drush man myself, so I’d either run the migrations individually with these commands

  • drush migrate:import upgrade_d7_file
  • drush migrate:import upgrade_d7_file_to_media_image
  • drush migrate:import upgrade_d7_file_to_media_document

or as a group (if you are using migrate_plus with the group support) with

  • drush migrate:import --group=migrate_drupal_7

although if you are writing these migrations as part of a larger migration with other migrations in the group, you’ll probably just want to run them individually to be sure they are working correctly.

All being well, after running the migrations you should find all the relevant files have been copied into your new site’s public files directory and you should be able to see all your lovely files as media entities by visiting /admin/content/media. (Great Job!)

Referencing the media in migrations that previously referenced files

Any migrations that you have previously written that referenced files and fid’s instead of media entities should now be updated. This is a relatively trivial task as the following example details. Please note that the fields you are migrating into will need to be recreated as Entity Reference fields, referencing the Media entity type. If you have let Drupal handle migrating your fields from a previous version then it will have created these fields as File entity reference fields.

This is an example of what part of your process plugin may look like before and after, on an entity migration for an image field named ‘field_images’.

Before (File) field_images: plugin: sub_process source: field_images process: target_id: fid alt: alt title: title width: width height: height After (Media) field_images: plugin: sub_process source: field_images process: target_id: plugin: migration_lookup migration: upgrade_d7_file_to_media_image source: fid no_stub: true

And here is an example of what part of your process plugin would look like before and after for a document field named ‘field_documents’.

Before (File) field_my_documents: plugin: sub_process source: field_my_documents process: target_id: fid display: display After (Media) field_my_documents: plugin: sub_process source: field_my_documents process: target_id: plugin: migration_lookup migration: upgrade_d7_file_to_media_document source: fid no_stub: true

If your file field in Drupal 7 allows both images and documents, then your field in Drupal 8 / 9 should allow the same. You can reference both media migrations in the process as follows:

field_my_documents: plugin: sub_process source: field_my_documents process: target_id: plugin: migration_lookup migration: - upgrade_d7_file_to_media_document - upgrade_d7_file_to_media_image source: fid no_stub: true Extending further

We have covered the basics on how to get images and document files migrated, but you can (and we have on lots of client’s sites) get this running for Video files as well in a similar manner with minimal changes.

You just need to create another file to media migration plugin for video files, in addition to the image and document ones already made. Ensure that the type of file passed into the source plugin section of the migration is ‘video’ and the default_bundle of your entity:media destination in the destination section is ‘video’. Then you should be hot to trot!

Bonus - How to have a dynamic file source_base_path

You may be wondering, “What if I don’t want to hard code my source_base_path in the source plugin definition of my migrations?”

This is another common requirement we have when writing our own migrations, and luckily the solution isn’t too tricky with a bit of custom code. You may find this useful if you are going to run this migration across different environments (e.g. your dev machine, testing server, production environment when the time comes) and you don’t want to keep having to change your migration plugin files each time.

In your migration source plugin for both the file and file -> media migrations, change

source_base_path: /path/to/your/drupal7/webroot


source_base_path: /

You don’t strictly have to do this as we are going to override it anyway but I think having a blank path gives a clearer indication that we haven’t explicitly set one, as we are going to override the value. Of course, you could also have a comment above the line explaining that this will be overridden if you so wish.

Migration Event Subscriber

In order to override the souce_base_path, we’ll create an Event Subscriber in our custom module. This will enable us to subscribe to the appropriate Migrate events and will allow us to modify the data at the correct point in time. For more detailed information about event subscribers, see this article we wrote way back in 2016 ‘Drupal 8 Event Subscribers - the successor to alter hooks’.

You’ll first want to create a services.yml file directly inside of the custom module as well named This will let us define our migration subscriber so that Drupal knows about it.

services: example_custom_migration.d7_file_migration_subscriber: class: Drupal\example_custom_migration\D7FileMigrationSubscriber arguments: ['@config.factory'] tags: - { name: event_subscriber }

Next, create this file inside of the src folder in our custom module. You can name it what you want really but for this example, we’ll call it D7FileMigrationSubscriber.php.

<?php /** * @file * Contains \Drupal\example_custom_migration\D7FileMigrationSubscriber */ namespace Drupal\example_custom_migration; use Drupal\Core\Config\ConfigFactoryInterface; use Drupal\migrate\Event\MigrateEvents; use Symfony\Component\EventDispatcher\EventSubscriberInterface; /** * Act on events during a migration. */ class D7FileMigrationSubscriber implements EventSubscriberInterface { /** * @var \Drupal\Core\Config\ConfigFactoryInterface */ protected $configFactory; public function __construct(ConfigFactoryInterface $configFactory) { $this->configFactory = $configFactory; } /** * {@inheritdoc} */ public static function getSubscribedEvents() { $events[MigrateEvents::PRE_IMPORT] = 'onMigrationPreImport'; return $events; } /** * Act on a migration before it imports. */ public function onMigrationPreImport(MigrateImportEvent $event) { $migrate_id = $event->getMigration()->id(); // Inject the correct source_base_path to the file migrations. $file_migrate_ids = [ 'upgrade_d7_file', 'upgrade_d7_file_to_media_image', 'upgrade_d7_file_to_media_document' ]; if (in_array($migrate_id, $file_migrate_ids)) { $source_config = $event->getMigration()->getSourceConfiguration(); $source_base_path = $this->configFactory->get('example_custom_migration.settings')->get('files_source_base_path'); $source_config['constants']['source_base_path'] = $source_base_path; $event->getMigration()->set('source', $source_config); } } }

There is a lot going on here which I'm not going to get into in this article. (The article I linked to above should help explain it all if you aren’t familiar with concepts such as event subscribers and dependency injection!) But the end result of this code will be to swap out the source_base_path in our source plugin configuration to one defined by a config entry named ‘example_custom_migration.settings’ with a key of ‘files_source_base_path’

Finally, all you need to do next is just pop this line into the bottom of your settings.local.php (which should be different per environment) replacing ‘/path/to/your/drupal7/webroot’ with the path to the Drupal 7 site webroot for the current environment.

$config['example_custom_migration.settings']['files_source_base_path'] = '/path/to/your/drupal7/webroot';

This will then allow you to have a different path set on a local dev build, another team member’s local dev build, testing and production servers as well! Neat huh?

Photo by on Pexels

Categories: FLOSS Project Planets

Ben's SEO Blog: The Sitemap Module

Tue, 2021-07-27 08:30
The Sitemap Module Drupal's Sitemap Module offers a human-readable HTML sitemap that serves as a helpful guide to visitors and search engines can crawl, which increases positive exposure for your content.
Categories: FLOSS Project Planets

Specbee: All You Wanted to Know about Drupal’s Strategic Initiatives

Tue, 2021-07-27 06:45
All You Wanted to Know about Drupal’s Strategic Initiatives Shefali Shetty 27 Jul, 2021 Top 10 best practices for designing a perfect UX for your mobile app

Every strategic initiative begins with a vision. Drupal’s success as an opensource project substantially thrives on its vision for strategic initiatives. The adoption of continuous innovation and strategic initiatives since Drupal 7 is what distinguishes it from the rest. With successful past initiatives like the Configuration Management initiative (CMI), the Web services initiative, Layout initiative, Media initiative and more, the Drupal community is all charged up to be working on many more initiatives as you are reading this. 

But what exactly are strategic initiatives? What initiatives are Drupal working on? Who makes them? Why should you care? Read on to find answers to these burning questions.

What are Strategic Initiatives?

Thomas Edison – the man who garnered over 1000 patents - had once said in an interview – “I have not failed 10,000 times but have successfully found 10,000 ways that will not work”. Success comes when you embrace changes that can lead you to your end goal. The day you stop evolving is the day you stop growing. Strategic initiatives are typically sparked from a need for a change that guides an organization towards its future goals. It fuels growth and development of a product or organization.

How are Drupal’s Core Strategic Initiatives formulated?

As a continuously evolving opensource project, its strategic initiatives must always result in a breakthrough for Drupal. But how does Drupal’s project lead - Dries Buytaert – come up with these initiatives? 

  • They are backed by solid data. Regular surveys and usability studies are conducted to help build this data. This is usually done after every major Drupal release.
  • Fueled by a vision to build superlative digital experiences focusing mainly on non-technical content authors and site builders.
  • Market relevancy and competitor research is performed to ensure improvements on editorial, developer and customer experiences are delivered.
  • The initiative ideas are validated via community collaboration and discussions.
Initiative Stages

Proposed Stage: This is the beginning stage of the initiative where the initiative is recommended by Dries or the Drupal community members.

Planned Stage: This is when the initiative is nurtured with detailed plans. A strong team of initiative contributors is created who set goals and plans for the initiative.

Active Stage: This stage is where the real work happens, and the initiative comes alive.

What are the current initiatives Drupal is working on?

At DrupalCon North America 2021, the audience witnessed a first-of-its-kind “Initiative days” program where each day was dedicated to one initiative. Here the initiatives were introduced, discussed, and contribution sprints were also held at the end the program. 

  • Decoupled Menus Initiative

This initiative was proposed by Dries during his DrupalCon Global 2020 keynote speech. The goal of this initiative is to build APIs and documentation that can enable JavaScript frontend developers to integrate Drupal menus to the frontend in the best way possible. It also aims at offering content editors and site builders with an easy and intuitive way to build menus on Drupal with very less to no code. However, the broader goal of this initiative is to create a pattern that can be replicated to build various decoupled components, thus making Drupal the best decoupled CMS in the market.

  • Easy Out of The Box Initiative

During a user survey at a DrupalCon in 2019, it was gathered that many users preferred Drupal over other CMSs’ but only after a while of using it. Simply put, Drupal wasn’t as much of a CMS choice of a beginner user as much it was of an expert Drupal user. Which meant that Drupal needed to be more acceptable even at the beginner level. The goal of the Easy Out Of The Box (EOOTB) initiative is to make Drupal easy right out of the box for non-technical users as well. It bundles the three initiatives together – Layout, Media and Claro – to offer unmatched and simplified user experiences.

  • Automated Updates Initiative

Automated updates was one of the highest requested Drupal features in a survey conducted in 2020. The vision of this initiative is to address the challenges faced by small to midsized businesses in updating their Drupal sites manually. It aims at ensuring the website is safe to update and verify updates with package signing. Updates applied should also be able to be rolled back in case of any errors. 

  • Drupal 10 Readiness Initiative

Similar to the success of the Drupal 9 readiness initiative that ensured a smooth and on-time release of Drupal 9 by June 2020, the Drupal 10 readiness initiative aims at guaranteeing a timely release of Drupal 10 by June 2022. Along with an on-time release, the goals are also to get Drupal 10 compatible with the latest versions of the dependencies like PHP, Symfony, jQuery, Guzzle and more. Older modules, themes and other dependencies will also need to be deprecated.

  • New Front-end Theme (Olivero) Initiative

The goal of this initiative is to build a fresh new front-end theme with many improvements than what we had previously in Bartik. Olivero is going to be a modern theme with a positive and powerful look and feel. It aims at offering functionality that can support new features like the layout builder, embedded media and more. It will also be WCAG AA conformant ensuring the theme passes Drupal’s accessibility standards.

  • Project Browser Initiative

The Project browser initiative is at its very early stages – the planning stage. The initiative was proposed by Dries during DrupalCon North America 2021. The vision is to build a project browser that makes it easy for users to find and install modules without using command line. It aims at making module discovery and installation easy and fast.

The effect of a good strategic initiative always results in a breakthrough. Since the launch of Drupal’s strategic initiatives, we have witnessed a higher rate of adoption till Drupal 9. Drupal 9 saw the fastest adoption rate as compared to Drupal 7 and Drupal 8 with 0 to 60,000 websites in one month. While Drupal 7 and Drupal 8 took 7 months and 3 months respectively to reach the 60k goal. That’s the kind of change Drupal wants to see. To continuously innovate and ultimately create a software that works for everyone. As an organization that works with Drupal day in and day out, it is an honor to contribute back to the Drupal project to help make its initiatives come alive. 

Drupal Development Drupal Planet Drupal Tutorial Drupal 8 Drupal 9 Drupal 7 Drupal Module Shefali ShettyApr 05, 2017 Subscribe For Our Newsletter And Stay Updated Subscribe

Leave us a Comment

  Shefali ShettyApr 05, 2017 Recent Posts All You Wanted to Know about Drupal’s Strategic Initiatives Image Building Chatbots in Drupal Using Dialogflow Image Migrating Multilingual Content from CSV to Drupal 8 Want to extract the maximum out of Drupal? TALK TO US Featured Success Stories

A Drupal powered multi-site, multi-lingual platform to enable a unified user experience at SEMI.


Discover how our technology enabled UX Magazine to cater to their massive audience and launch outreach programs.


Discover how a Drupal powered internal portal encouraged the sellers at Flipkart to obtain the latest insights with respect to a particular domain.


Categories: FLOSS Project Planets

DrupalEasy: 10 Links to Help Recruit People to Drupal Careers

Mon, 2021-07-26 10:12

It’s been 10 years of not only teaching Drupal Career Online; but also working to attract people into Drupal careers as we market and do outreach for our 12-week course. We’ve made presentations to local and state workforce boards, the national workforce association, many Drupal Camp and Con sessions, and correspond with individuals, businesses, potential education partners and field inquiries every week about Drupal, working in Drupal and the DCO from all over the world.  We've become a bit of a Drupal recruiter

After a decade of explaining what Drupal is, finding answers, and collecting some pretty good information, we’ve compiled a solid list of Drupal Career Resources, now a fixture on our site. So, in the spirit of our Top 10 List anniversary series, here is a good one to use when you run into someone who you think might be a good addition to the Drupal Community, but needs some insight and background. Let us know if you have any other great resources; and go forth and attract.

#1:  Web Developers and Digital Designers Occupational Outlook Handbook: The facts on web developer careers from the US Department of Labor, Bureau of Labor statistics with a lot of great info., including a description of the job, geographic salary information, as well as future job prospects. 

#2: How do I get a Job In Drupal?: The OS Training interview with our own MIke Anello: An oldie but a goody; with links to the audio of the expert-rich Drupal Career Trailhead panel presentation at DrupalCon Austin 2015. 

#3: Indeed Build a Career You’ll Love: Drupal Developer: Indeed’s career overview, including links to average salaries, geographic statistics, job openings, common benefits and salary comparisons.

#4 Drupal Statistics & Facts: A fun piece about Drupal history, facts and how it compares to other CMSs.

#5: Everything you need to know Before Starting a Drupal Career: London-based Cocoon provides a good, honest description of the elements, issues and steps to a Drupal Career. 

#6 Case Study: AmyJune Hineline: Some inspiration and insight from a DCO alumna, and winner of the Drupal Community’s 2021 Aaron Winborn Award.

 #7 Drupal for Developers: Great descriptions and material on the markets and opportunities for developers considering Drupal as a CMS

#8 Interactive map with a calendar of Drupal events all over the world.

#9 DCO Scholarship Page: We can't help but promote the three amazing scholarship programs through, Bounteous and the Drupal Association that are helping to attract more, and more diverse Drupal talent!

#10 Taste of Drupal registration: The sign-up sheet to attend the Taste of Drupal no-cost mini information webinars that Mike Anello leads in advance of the Spring and Fall sessions of the DCO. Next TOD is August 11!

The deadline to apply for the Fall 2021 session of Drupal Career Online is August 27th, with classes beginning August 30.  


Categories: FLOSS Project Planets

Talking Drupal: Talking Drupal #304 - Voice Content and Usability in Drupal

Mon, 2021-07-26 10:07

Today we are talking with Preston So about Voice Content and Usability in Drupal, as well as his book from A Book Apart Voice Content and Usability.

  • AmyJune Word Camp Santa Clarita
  • John Elisha Otis 1853 Elevator pitch
  • Nic Trumpet Vine (again)
  • Preston Decoupled Days and just finished Gatsby: The Definitive Guide
  • Voice Content and it's impact on usability
  • First interest in the topic
  • Learn about accessibility beyond content layout
  • Designing a voice interface
  • Voice system learning
  • WCAG 3.0 Web Content Accessibility Guidelines success criteria
  • Lessons from Voice content
  • Bad voice content and it's impact
  • How it applies to Drupal
  • Preston's new book

Ask Georgia Gov


Preston So - @prestonso


Nic Laflin - @nicxvan

John Picozzi - @johnpicozzi

AmyJune Hineline - @volkswagenchick

Categories: FLOSS Project Planets

Event Organizers: Help Us Build The Next Drupal Event Platform - Join Us on Zoom: Tue. at 2pm EST

Mon, 2021-07-26 09:42

Is there a Drupal 9 community-supported platform for organizing a DrupalCamp or Drupal event? What happened to the Conference Organizing Distribution (COD)? Join us on the 4th Tuesday (tomorrow) of the month at 2 PM EST to help contribute to building the next Drupal Events Platform. Join our Meeting Here.

Earlier this year, the Drupal Event Organizer's Working Group (EOWG), took a page out of the infamous Driesnote’s and announced the three initiatives for the working group to make a priority. The three initiatives are: Getting Started with Drupal, Drupal Events Database, and the Drupal Event Platform. You can read about each of the initiatives here.  

The Drupal Event Platform (DEP) initiative is aiming to solve a challenge for all event organizers, the website. The event website is one of the most important and laborious tasks associated with producing a quality Drupal event. For some events, the website is the main application that helps organize and build the entire experience from collecting session submissions to assigning presentations to the schedule.

The Drupal Event Platform (DEP) initiative is forming a committee within the Drupal Event Organizers Working Group (EOWG) that is looking to reduce the time and resources it takes to create an event website site through the creation of a flexible and customizable website starter kit for event organizers.


  • Focus the website creation efforts of event organizers on a single open-source platform to reduce duplication of efforts

  • Create an MVP (minimal viable product) that can meet the needs of at least 80% of Drupal events globally

  • Reach and maintain adoption by at least 40% of all Drupal events globally

  • Model for Drupal best practices including accessibility, usability, inclusive design, documentation, security, performance, and internationalization

  • Provide an extensible, modular platform that allows event organizers to choose to use default functionality or build custom components

Join us on the 4th Tuesday of the Month at 2pm EST.

For more information on how to join the meeting visit The Drupal Event Platform (DEP) initiative page today.

Categories: FLOSS Project Planets

Golems GABB: New features and innovations in Drupal Commerce

Mon, 2021-07-26 03:43
New features and innovations in Drupal Commerce Editor Mon, 07/26/2021 - 10:43

Now that online shopping is ruling the day, the idea to create an online store looks lucrative like never before. For all those who want to build an e-commerce website with compelling digital experiences, there is a great option based on the Drupal CMS — Drupal Commerce. It is an open-source powerful e-commerce platform enabling developers to build online stores of any complexity.

The platform is at the forefront of e-commerce development and keeps getting new and exciting functionalities. Let’s now take a tour of the new features and innovations in Drupal Commerce.

Categories: FLOSS Project Planets

PreviousNext: Introducing the Media File Delete module

Sun, 2021-07-25 21:33

The media and media library modules have improved Drupal's file handling capabilities considerably, but one oft-requested feature of clients is the ability to remove files from disk when removing the associated media entity.

This is where the Media file delete module can help

by lee.rowlands / 26 July 2021 Configuring the module

The module has no configuration, so it should be as simple as enabling it and you're off.

By default it takes over the Media entity delete form and adds an additional checkbox allowing the editor to also removed the associated file if:

  • the media-type has an associated file or image field
  • there are no associated file usage records for the file
  • the user has permission to delete the file
A word about access

By default, Drupal core's access handler for files allows editors to only delete files which they own, which can be prohibitive in an environment where several editors create and edit media.

To alleviate this, the module also comes with a 'Delete any file' permission, which can be granted to allow trusted users to delete files they don't own.


All in all, its a pretty simple module - but its a common requirement for our clients, and as a result at least two of them are already running it in production.

Take it for a spin and let us know your thoughts either here or via the issue queue.

Tagged Media
Categories: FLOSS Project Planets

#! code: Drupal 9: Configuring Drupal To Be An Identity Provider With SimpleSAMLphp

Sun, 2021-07-25 11:47

I have previously talked about configuring a Drupal site to authenticate against a remote SimpleSAMLphp install, but as Drupal is an excellent user management system I wanted to turn it around and use Drupal as the identity provider. This means that Drupal would allow users to log into other systems using their Drupal username and password by leveraging the power of SimpleSAMLphp.

This can be accomplished by wrapping the Drupal site and SimpleSAMLphp together along with a couple of modules to power the communication between the two systems.

The same terms apply as I described in the previous post, but to reiterate their meaning in this context I will go over them again.

SP - Service Provider - This is the system that users are trying to log into, which in this setup is some other site or service. Service providers will generally create a local user to track the user within the site and in this setup the user will be a Drupal user.

IdP - Identity Provider - The Drupal system holds information about the users and is therefore called an identity provider as it provides the identity of the user. This is used by the Service Provider (SP) to authenticate the user.

I'm going to assume that you have a Drupal site already installed via composer, preferably using the Drupal recommended composer file. This will be basis of the rest of the article.

Installing SimpleSAMLphp

To get this working we need to require SimpleSAMLphp in the same project as you Drupal site. The first step, therefore, is to require SimpleSAMLphp as a project dependency, which will install SimpleSAMLphp alongside Drupal.

Read more.

Categories: FLOSS Project Planets

Innoraft Drupal Blogs: 10 Advanced Google SEO Ranking Factors to Double Traffic

Fri, 2021-07-23 05:47
10 Advanced Google SEO Ranking Factors to Double Traffic You must be aware of the Google ranking factors for your website to rank high during a keyword search. This article discusses the main SEO ranking factors Tanjeet Fri, 07/23/2021 - 15:17 website development services Website Security user friendly website Drupal Planet Drupal Website Development
Categories: FLOSS Project Planets Blog: The importance of the right technology stack in a digital strategy

Fri, 2021-07-23 03:50

In this article, we discuss the importance of having the right technology stack and key considerations for building it.

Categories: FLOSS Project Planets

DevCollaborative - Planet Drupal: How to Add an RSS Feed to a Drupal Website

Thu, 2021-07-22 15:07
I recently went through the process of setting up an RSS Feed for our blog here at DevCollaborative so that our articles can be syndicated to Planet Drupal. It had a surprising amount of gotchas and the documentation was dated and sparse. So I thought I’d help remedy that by sharing what I learned.
Categories: FLOSS Project Planets

Droptica: How to Perform a Drupal Security Audit? Overview of the Modules and Libraries

Thu, 2021-07-22 13:04

A security audit is the process of identifying security threats that can lead to unauthorised access to content, data leaks, bypassing the security, and other dangers. In the first part of the series on conducting a security audit, we'll focus on the overview of the Drupal module versions that we use at Droptica for this purpose, as well as on PHP and JavaScript libraries.

Drupal security audit

At Droptica, we make every effort to ensure that the solutions we provide are as safe as possible. We use the tools provided by the Drupal community, such as the Security Review module, to optimize the process of detecting the most popular security errors. We also use the Security Kit to make the project we're working on more resistant to attacks. You can learn more about the functionality of these modules in the linked posts, and the information on their operation will be useful in the following parts, in which we'll talk about the Drupal configuration review and code analysis.

Checking the versions of the installed Drupal modules

Updating modules and libraries is the simplest activity that we can perform to improve the security of our application. Drupal provides a view listing all the modules, which additionally indicates whether a given module is up-to-date, and if it isn’t – whether the update contains security fixes.

To check if the modules are up-to-date, go to /admin/modules/update

In the screenshot above, you can see that some of the modules need updating. Of course, in such cases we always recommend that you update all possible modules. If any of the modules contain a security fix, the update is required to ensure a high level of security for the application.

In the case of Drupal, the information about whether a given module has a security flaw is made available to the public when the author of the module releases its patched version. Module authors usually try to hide which code has been changed to patch a security flaw, but this always means that the attacker just needs more time to find a way to cause the bug and exploit it. Time is important, so you should keep track of security updates regularly, not only during a Drupal security audit. As we mentioned earlier, this is one of the simplest steps we can take to ensure a higher level of security for our application.

Patches review

When updating the Drupal modules, you should also check if a patch has been applied to a given module. If so, we proceed as follows:

  1. We check whether the patch was created by the community and if it concerns a specific issue on If so, we look for the issue that the patch is from. It's possible that the patch has been applied to one of the newer versions of the module. In such a case, we recommend updating the module and removing the patch with the information that the code that fixes the bug or adds a given functionality has been applied to the official, newer version of the module. If the patch hasn’t yet been applied to the newer version of the module, we still recommend updating and testing if the latest version of the patch serves its purpose.
  2. If the patch wasn’t created by the Drupal community, but is the result of working on the project, we still recommend updating the module. In this case, however, ensuring the correct operation of the patch lies with the people responsible for the custom code of the project. After updating, you should check whether the patch works as intended. If not, we recommend introducing appropriate fixes to the patch which will ensure its correct operation on the latest version of the module.
PHP libraries review

The next step will be reviewing the used PHP libraries. To list them, we can use the composer show command or the local-php-security-checker package. We recommend the latter solution because it significantly speeds up the process.

Result of the composer show command.

If you choose to install the local-php-security-checker package, follow the guidelines in the file.

Result of the scan using local-php-security-checker.

There's also the little-known Drupal Composer Security Checker module that uses the security-checker package. Currently, this module doesn't fulfill its task and the security-checker package itself isn't actively developed (since January 2021), therefore we'll focus on the local-php-security-checker package itself. If you find a security risk, our recommendation will be to update the library, of course – as in any case. An audit of the PHP libraries should be carried out regularly, the same as in the case of the Drupal modules.

JavaScript libraries review

The next step will be to check if the used JavaScript libraries are up-to-date and if they contain security fixes. To do this, you should review the library directory and the used package.json files.

In the case of the library directory, you need to check the version manually. In the case of package.json, we use the npm-audit command.

Result of the npm-audit command

The npm-audit command will list all known vulnerabilities, determine the threat level, package, dependencies, package path, and show a link with information about the vulnerability.

If you find a vulnerability, as always we recommend the update. JS library scans should be performed routinely, more often than a comprehensive security audit.

Improving the Drupal security - further steps

In this part of the Drupal security audit cycle, we've learned how to check whether the used versions of the modules and libraries are up-to-date and don't contain known security bugs. We also understand how to proceed if there is a patch available for a module – both when the patch comes from the Drupal community and when it was prepared by the developer working on the application.

Acquiring the knowledge provided in this post is the easiest way to improve the security of your application. Checking the versions of the used solutions is the first step that we perform during a security audit - our Drupal support team recommends periodic checking for updates. In the event that an update containing security fixes is released, we recommend that you perform an update as soon as possible.

In the next part of this series of articles, we'll get to learn more about the Drupal configuration aimed at increasing the security of our application. We'll also learn how to reduce the number of attack vectors and we'll find out more about the modules that'll help us with this.

Categories: FLOSS Project Planets How to host your Google Fonts locally with Drupal

Thu, 2021-07-22 07:15

Google fonts are a great way to give your Drupal website a unique look. But it’s even better to host those Google fonts locally. In this tutorial I will show you how.

Categories: FLOSS Project Planets

Envato Tuts+: New Course: Code a Custom Drupal Module

Thu, 2021-07-22 03:39
What You'll Be Creating

If you want an easy way to create engaging, content-driven websites for you and your customers, you should give Drupal 8 a try. And Drupal modules allow you to take things a step further and create highly customized functionality for your site. 

In our new course, Code a Custom Drupal Module, Envato Tuts+ instructor Derek Jensen will get you up and running with modules in no time. You'll build a simple calculator module, and along the way you'll learn about creating routes, controllers, parameters, and more.

You can take our new course straight away with a subscription to Envato Elements. For a single low monthly fee, you get access not only to this course, but also to our growing library of over 1,000 video courses and industry-leading eBooks on Envato Tuts+. 

Plus you now get unlimited downloads from the huge Envato Elements library of 200,000+ photos and 26,000+ design assets and templates. Create with unique fonts, photos, graphics and templates, and deliver better projects faster.

Looking for a shortcut? Try downloading some of the ready-made Drupal themes on Envato Market.

Categories: FLOSS Project Planets

Envato Tuts+: New Code eBooks Available for Subscribers

Thu, 2021-07-22 03:39

Do you want to learn more about asynchronous Android programming? How about the Ionic framework, or JavaScript design patterns? Our latest batch of eBooks will teach you all you need to know about these topics and more.

Our Latest Selection of eBooks

This month we’ve made eight new eBooks available for Envato Tuts+ subscribers to download. Here’s a summary of those books and what you can learn from them.

  • Xamarin: Cross-Platform Mobile Application Development

    Developing a mobile application for just one platform is becoming a thing of the past. Companies expect their apps to be supported on iOS, Android and Windows Phone, while leveraging the best native features on all three platforms. Xamarin's tools help ease this problem by giving developers a single toolset to target all three platforms. The main goal of this book is to equip you with knowledge to successfully analyze, develop, and manage Xamarin cross-platform projects using the most efficient, robust, and scalable implementation patterns.

  • Drupal 8 Theming With Twig

    Drupal 8 is an open-source content management system and powerful framework that helps deliver great websites to individuals and organizations, including non-profits, commercial, and government around the globe. Starting from the bottom up, in this eBook you will learn to install, set up, and configure Drupal 8. You'll get a walk-through of a real-world project to create a Twig theme from concept to completion while adopting best practices to implement CSS frameworks and JavaScript libraries. You will see just how quick and easy it is to create beautiful, responsive Drupal 8 websites while avoiding the common mistakes that many front-end developers make.

  • Asynchronous Android Programming: Second Edition

    Asynchronous programming has acquired immense importance in Android programming, especially when we want to make use of the number of independent processing units (cores) available on the most recent Android devices. With this guide in your hands, you’ll be able to bring the power of asynchronous programming to your own projects, and make your Android apps more powerful than ever before!

  • Ionic Framework by Example

    With Ionic, mobile development has never been so simple, so elegant and obvious. By helping developers to harness AngularJS and HTML5 for mobile development, it’s the perfect framework for anyone obsessed with performance, and anyone that understands just how important a great user experience really is. This book shows you how to get started with Ionic framework immediately. But it doesn’t just give you instructions and then expect you to follow them. Instead it demonstrates what Ionic is capable of through three practical projects you can follow and build yourself.

  • Sass and Compass Designer's Cookbook

    Sass and Compass Designer's Cookbook helps you to get most out of CSS3 and harness its benefits to create engaging and receptive applications. This book will help you develop faster and reduce the maintenance time for your web development projects by using Sass and Compass. You will learn how to use with CSS frameworks such as Bootstrap and Foundation and understand how to use other libraries of pre-built mixins. You will also learn setting up a development environment with Gulp.

  • Android Sensor Programming by Example

    Android phones available in today’s market have a wide variety of powerful and highly precise sensors. This book will give you the skills required to use sensors in your Android applications. It will walk you through all the fundamentals of sensors and will provide a thorough understanding of the Android Sensor Framework. By the end of the book, you will be well versed in the use of Android sensors and programming to build interactive applications.

  • Mastering Yii

    The successor of Yii Framework 1.1, Yii2 is a complete rewrite of Yii Framework, one of the most popular PHP 5 frameworks for making modern web applications. This book has been written to enhance your skills and knowledge with Yii Framework 2. Starting with configuration and how to initialize new projects, you’ll learn how to configure, manage, and use every aspect of Yii2 from Gii, DAO, Query Builder, Active Record, and migrations, to asset manager. With this book by your side, you’ll have all the skills you need to quickly create rich modern web and console applications with Yii2.

  • Mastering JavaScript Design Patterns: Second Edition

    In this book, you will explore how design patterns can help you improve and organize your JavaScript code. You’ll get to grips with creational, structural and behavioral patterns as you discover how to put them to work in different scenarios. Then, you'll get a deeper look at patterns used in functional programming, as well as model view patterns and patterns to build web applications. By the end of the book, you'll be saved of a lot of trial and error and developmental headaches, and you will be on the road to becoming a JavaScript expert.

Start Learning With a Yearly Subscription

Subscribe to Envato Tuts+ for access to our library of hundreds of eBooks. With a Yearly subscription, you can download up to five eBooks per month, while the Yearly Pro subscription gives you unlimited access.

You can also build on your newfound knowledge by using some of the fantastic code scripts and plugins on Envato Market.

Categories: FLOSS Project Planets

Envato Tuts+: Drupal 8: Properly Injecting Dependencies Using DI

Thu, 2021-07-22 03:39

As I am sure you know by now, dependency injection (DI) and the Symfony service container are important new development features of Drupal 8. However, even though they are starting to be better understood in the Drupal development community, there is still some lack of clarity about how exactly to inject services into Drupal 8 classes.

Many examples talk about services, but most cover only the static way of loading them:

$service = \Drupal::service('service_name');

This is understandable as the proper injection approach is more verbose, and if you know it already, rather boilerplate. However, the static approach in real life should only be used in two cases:

  • in the .module file (outside of a class context)
  • those rare occasions within a class context where the class is being loaded without service container awareness

Other than that, injecting services is the best practice as it ensures decoupled code and eases testing.

In Drupal 8 there are some specificities about dependency injection that you will not be able to understand solely from a pure Symfony approach. So in this article we are going to look at some examples of proper constructor injection in Drupal 8. To this end, but also to cover all the basics, we will look at three types of examples, in order of complexity:

  • injecting services into another of your own services
  • injecting services into non-service classes
  • injecting services into plugin classes

Going forward, the assumption is that you know already what DI is, what purpose it serves and how the service container supports it. If not, I recommend checking out this article first.


Injecting services into your own service is very easy. Since you are the one defining the service, all you have to do is pass it as an argument to the service you want to inject. Imagine the following service definitions:

services: demo.demo_service: class: Drupal\demo\DemoService demo.another_demo_service: class: Drupal\demo\AnotherDemoService arguments: ['@demo.demo_service']

Here we define two services where the second one takes the first one as a constructor argument. So all we have to do now in the AnotherDemoService class is store it as a local variable:

class AnotherDemoService { /** * @var \Drupal\demo\DemoService */ private $demoService; public function __construct(DemoService $demoService) { $this->demoService = $demoService; } // The rest of your methods }

And that is pretty much it. It's also important to mention that this approach is exactly the same as in Symfony, so no change here.

Non-Service Classes

Now let's take a look at classes that we often interact with but that are not our own services. To understand how this injection takes place, you need to understand how the classes are resolved and how they are instantiated. But we will see that in practice soon.


Controller classes are mostly used for mapping routing paths to business logic. They are supposed to stay thin and delegate heavier business logic to services. Many extend the ControllerBase class and get some helper methods to retrieve common services from the container. However, these are returned statically.

When a controller object is being created (ControllerResolver::createController), the ClassResolver is used to get an instance of the controller class definition. The resolver is container aware and returns an instance of the controller if the container already has it. Conversely, it instantiates a new one and returns that. 

And here is where our injection takes place: if the class being resolved implements the ContainerAwareInterface, the instantiation takes place by using the static create() method on that class which receives the entire container. And our ControllerBase class also implements the ContainerAwareInterface.

So let's take a look at an example controller which properly injects services using this approach (instead of requesting them statically):

/** * Defines a controller to list blocks. */ class BlockListController extends EntityListController { /** * The theme handler. * * @var \Drupal\Core\Extension\ThemeHandlerInterface */ protected $themeHandler; /** * Constructs the BlockListController. * * @param \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler * The theme handler. */ public function __construct(ThemeHandlerInterface $theme_handler) { $this->themeHandler = $theme_handler; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('theme_handler') ); } }

The EntityListController class doesn't do anything for our purposes here, so just imagine that BlockListController directly extends the ControllerBase class, which in turn implements the ContainerInjectionInterface.

As we said, when this controller is instantiated, the static create() method is called. Its purpose is to instantiate this class and pass whatever parameters it wants to the class constructor. And since the container is passed to create(), it can choose which services to request and pass along to the constructor. 

Then, the constructor simply has to receive the services and store them locally. Do keep in mind that it's bad practice to inject the entire container into your class, and you should always limit the services you inject to the ones you need. And if you need too many, you are likely doing something wrong.

We used this controller example to go a bit deeper into the Drupal dependency injection approach and understand how constructor injection works. There are also setter injection possibilities by making classes container aware, but we won't cover that here. Let's instead look at other examples of classes you may interact with and in which you should inject services.


Forms are another great example of classes where you need to inject services. Usually you either extend the FormBase or ConfigFormBase classes which already implement the ContainerInjectionInterface. In this case, if you override the create() and constructor methods, you can inject whatever you want. If you don't want to extend these classes, all you have to do is implement this interface yourself and follow the same steps we saw above with the controller.

As an example, let's take a look at the SiteInformationForm which extends the ConfigFormBase and see how it injects services on top of the config.factory its parent needs:

class SiteInformationForm extends ConfigFormBase { ... public function __construct(ConfigFactoryInterface $config_factory, AliasManagerInterface $alias_manager, PathValidatorInterface $path_validator, RequestContext $request_context) { parent::__construct($config_factory); $this->aliasManager = $alias_manager; $this->pathValidator = $path_validator; $this->requestContext = $request_context; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container) { return new static( $container->get('config.factory'), $container->get('path.alias_manager'), $container->get('path.validator'), $container->get('router.request_context') ); } ... }

As before, the create() method is used for the instantiation, which passes to the constructor the service required by the parent class as well as some extra ones it needs on top.

And this is pretty much how the basic constructor injection works in Drupal 8. It's available in almost all class contexts, save for a few in which the instantiation part was not yet solved in this manner (e.g. FieldType plugins). Additionally, there is an important subsystem which has some differences but is crucially important to understand: plugins.


The plugin system is a very important Drupal 8 component that powers a lot of functionality. So let's see how dependency injection works with plugin classes.

The most important difference in how injection is handled with plugins is the interface plugin classes need to implement: ContainerFactoryPluginInterface. The reason is that plugins are not resolved but are managed by a plugin manager. So when this manager needs to instantiate one of its plugins, it will do so using a factory. And usually, this factory is the ContainerFactory (or a similar variation of it). 

So if we look at ContainerFactory::createInstance(), we see that aside from the container being passed to the usual create() method, the $configuration, $plugin_id, and $plugin_definition variables are passed as well (which are the three basic parameters each plugin comes with).

So let's see two examples of such plugins that inject services. First, the core UserLoginBlock plugin (@Block):

class UserLoginBlock extends BlockBase implements ContainerFactoryPluginInterface { ... public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteMatchInterface $route_match) { parent::__construct($configuration, $plugin_id, $plugin_definition); $this->routeMatch = $route_match; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $configuration, $plugin_id, $plugin_definition, $container->get('current_route_match') ); } ... }

As you can see, it implements the ContainerFactoryPluginInterface and the create() method receives those three extra parameters. These are then passed in the right order to the class constructor, and from the container a service is requested and passed as well. This is the most basic, yet commonly used, example of injecting services into plugin classes.

Another interesting example is the FileWidget plugin (@FieldWidget):

class FileWidget extends WidgetBase implements ContainerFactoryPluginInterface { /** * {@inheritdoc} */ public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, ElementInfoManagerInterface $element_info) { parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings); $this->elementInfo = $element_info; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static($plugin_id, $plugin_definition, $configuration['field_definition'], $configuration['settings'], $configuration['third_party_settings'], $container->get('element_info')); } ... }

As you can see, the create() method receives the same parameters, but the class constructor expects extra ones that are specific to this plugin type. This is not a problem. They can usually be found inside the $configuration array of that particular plugin and passed from there.

So these are the main differences when it comes to injecting services into plugin classes. There's a different interface to implement and some extra parameters in the create() method.


As we've seen in this article, there are a number of ways we can get our hands on services in Drupal 8. Sometimes we have to statically request them. However, most of the time we shouldn't. And we've seen some typical examples of when and how we should inject them into our classes instead. We've also seen the two main interfaces the classes need to implement in order to be instantiated with the container and be ready for injection, as well as the difference between them.

If you are working in a class context and you are unsure of how to inject services, start looking at other classes of that type. If they are plugins, check if any of the parents implement the ContainerFactoryPluginInterface. If not, do it yourself for your class and make sure the constructor receives what it expects. Also check out the plugin manager class responsible and see what factory it uses. 

In other cases, such as with TypedData classes like the FieldType, take a look at other examples in core. If you see others using statically loaded services, it's most likely not yet ready for injection so you'll have to do the same. But keep an eye out, because this might change in the future.

Categories: FLOSS Project Planets

Envato Tuts+: What Is Drupal?

Thu, 2021-07-22 03:39

Drupal is a popular open-source content management system written in PHP. Having been created in the early 2000s by a Belgian student, it now powers some of the most prominent websites on the web (,, etc.). It is often regarded as a competitor of CMSs such as WordPress and Joomla.

One of the most important components of the Drupal project is its community of supporters (contributors, developers, evangelists, business owners, etc.). Prominent within this community stands the Drupal Association, responsible for "fostering and supporting the Drupal software project, the community and its growth".

A giant leap from its predecessor, the 8th major release of the Drupal project has just hit the shelves. It brought about a serious modernisation of its code, practices and mentality. Many regard this shift as a real move away from the traditional notion of a CMS to more of a Content Management Framework (CMF) that provides a great platform for building complex applications.

In this article, I'm going to answer some of the more frequent questions people have about Drupal when starting up for the first time or considering doing so:

  • Is it right for me? Who is it aimed at?
  • How can it be installed, and where can I host it?
  • How can I start working with it as a developer?
  • What options do I have for extending functionality or styling it?
Who Is Drupal Aimed At?

Since the beginning of the project, Drupal has evolved from being mainly a tool for building smaller sites to one that can now power enterprise-level platforms. Especially with Drupal 8, site builders and developers can easily scale up from small websites to large platforms with many integrations. For example, the adoption of Composer allows you not only to bring external libraries into a Drupal project, but also to use Drupal as part of a bigger project of applications and libraries. It's safe to say that Drupal is flexible enough to meet the needs of a wide range of projects.

When it comes to development, Drupal has always had a relatively closed community—not because people are unfriendly, quite the contrary, but mainly because of the code typically being written in a Drupal way (resulting in what sometimes is referred to as Drupalisms). This has meant a learning curve for any developer starting up, but also less interest from developers of other PHP projects to jump in and contribute.

This is no longer the case. Especially with the release of Drupal 8, the community now promotes a new mentality of code reusability and best practice across different open-source projects. Active participation in the PHP Framework Interoperability Group is part of this effort, and using a number of popular Symfony components in Drupal 8 core is a testament to this commitment. 

With this move, the Drupal community has gotten richer by welcoming many developers from other communities and projects, and it is sure to grow even further. So if you are a Laravel developer, looking at Drupal code will no longer be so daunting.

How Can I Install Drupal, and Where Can I Host It?

Traditionally, Drupal has had a relatively easy installation process, particularly for people who at least knew their way around a Linux environment. The project simply needs to be dropped into a folder your web server can run (which needs to be using PHP and have a MySQL or MariaDB database). Then pointing your browser to the /install.php file and following the steps takes care of the rest. The most important screen you'll see is the one in which you select a specific database to use.

In terms of requirements, the LAMP stack (Linux, Apache, MySQL and PHP) environment has always been a favourite for Drupal to run in. However, it is in no way restricted to it. Solutions exist for installing it straight on Windows or Mac (e.g. using the Acquia Dev Desktop) but also on a Linux system that runs other web servers.

The easiest approach, if you go with your own setup, is to use a LAMP server for hosting. For a bit more performance you can replace Apache with Nginx, but you'll then have to take care of some specific configuration that otherwise is handled in the .htaccess file Drupal ships with.

However, if you don't want the hassle of maintaining your own hosting server, there are three main providers of specialised Drupal managed hosting: Acquia, Pantheon, and These also provide a workflow for easy updates and development flow. Past that, you are looking at fully managed hosting with a Drupal development company.

How Can I Get Started Developing for It?

Developing Drupal websites has typically been the kind of thing you either liked a lot or didn't like at all. This is because when you were first introduced to Drupal, you encountered very many specificities that you didn't see in other projects. So if those tickled your fancy, you loved it forever.

With getting off this island in Drupal 8, this is no longer the case as much. You still have plenty of Drupalisms left that you can love or hate, but you now also have external components like Symfony or Guzzle and, most importantly, a more modern way of writing code in general (OOP, design patterns, reusable components, etc.). So your PHP skills from building websites with Zend will come in handy.

A good way of getting into Drupal development is to follow some online video courses. There are a couple of resources that are excellent for this purpose, most notably If, however, video is not your favourite medium, there are also many written tutorials and guides available to get you started. Check out the following links for some of the first steps you can take:

Since Drupal 8 is brand new, you'll find significantly more learning content for Drupal 7. Nevertheless, the focus in the community has been shifting recently towards Drupal 8, so you can expect more and more of these resources to crop up. And if you have no experience with any version of Drupal, it's best to focus exclusively on Drupal 8 as the changes between the two are big and perhaps you'd be facing unnecessary challenges.

How Can I Extend Drupal?

The main extension point of a core Drupal installation is its module system. 

Modules are used to encapsulate bigger chunks of reusable functionality that can/should work on different sites. Aside from the core modules, there are a large number of contributed ones, available for installation. 

Granted, most are still only for Drupal 6 and 7, but the community is catching up also for the newest version. This problem is also mitigated by the incorporation in Drupal 8 of a few popular contributed modules as well as extending the scope of what core can do out of the box (compared to Drupal 7). 

Lastly, custom modules (the ones that you write yourself) are the primary way you can add any functionality that you want and that is not available via a contributed module.

Installing modules can allow you to plug in various pieces of functionality, but you should not treat this as a green light for adding too many. It's always better to stick to the ones you actually need, and don't be afraid to be critical in this respect. You can also work on finding a good balance between contributed code and the custom one you write yourself. 

Additionally, since we are talking about open-source software, you should always evaluate the modules you install. The following indicators are good examples to pay attention to: number of downloads and usage, commit frequency, maintainer engagement, state of the issue queue.

And do keep security in mind as well. It's highly recommended you keep both Drupal core and any contributed modules up to date as this will significantly help you keep your site and server secure (though it doesn't ensure it).

What About Styling?

The styling layer of a Drupal site is handled (in large part) by its theme. Themes are similar to modules in that they are an extension point, but they have different responsibilities. They contain the styles, front-end libraries and in most cases template files that are used to output data.

There has been great progress in Drupal 8 compared to the previous version: the popular Twig engine has been adopted for templating, theming has been limited to Twig template files, debugging and overriding templates has been made much easier, etc. Similar to the advances in back-end development experience (DX), the theming layer has been made more appealing to the non-Drupal crowd. Front-end developers can now easily work with Drupal themes without having to understand the ins and outs of the back end.

Drupal core comes with a number of themes that can provide you with examples but also which you can extend from. There are also contributed themes similar to how there are modules. Popular front-end frameworks such as Bootstrap or Zurb Foundation have mature Drupal theme implementations for Drupal 7, which are also readying for Drupal 8. These work very well as base themes but also for quickly scaffolding a website and making it look decent.

Paid themes are also available to try out. Usually they are very cheap and quick to set up. The problem with them is that they are worth exactly as much as you pay for them and usually have gaping holes in their flexibility. As a beginner, these themes can seem like a great way to set up a site, and they very well may be. However, as you progress, you'll learn to avoid them and build your own, based on external designs or even plain HTML/CSS/JS templates.


Drupal is a powerful tool for building websites and platforms of any kind. With each new major release, Drupal has shown a commitment to better itself, become more robust and flexible, and embrace outside communities as well.

Categories: FLOSS Project Planets

Envato Tuts+: Using and Extending the Drupal 8 Mail API: Part 2

Thu, 2021-07-22 03:39

In the previous article we looked at how we can send emails programatically in Drupal 8. We also saw how other modules can alter these outgoing mails. Today, we are going to look at how we can use the Mail API to extend this default behaviour. The purpose is to use an external service as a means for email delivery. 

For this, we will use Mandrill, although the focus of the article will not be its API or how to work with it, but rather the Drupal side of things. And remember, the working module can be found in this Git repository.

As we've seen in the previous article, sending an email in Drupal 8 happens by requesting the mail manager, passing some parameters to its mail() method, and setting up a template inside a hook_mail() implementation. What the mail manager does internally is load up the appropriate mail plugin, construct the email, and then delegate to the mail() method of whatever plugin was loaded.

But who does it actually delegate to?

Plugin Selection

An important thing to understand before writing our own plugin is the selection process of the mail manager for loading plugins. In other words, how do we know which plugin it will load, and how can we make it load our own?

The system.mail.interface configuration array holds all the answers. It contains the ids of the available plugins, keyed by the context they are used in. By default, all we have inside this configuration is default => phpmail. This means that the plugin with the id phpmail (the PHPMail class) is used as fallback for all contexts that are not otherwise specified, i.e. the default.

If we want to write our own plugin, we need to add another element into that array with the plugin id as its value. The key for this value can be one of two things: the machine name of our module (to load the plugin whenever our module sends emails) or a combination of module name and email template key (to load the plugin whenever our module sends an email using that specific key). 

An example of the latter construct is d8mail_node_insert, where d8mail is our module name we started building in the previous article, and node_insert is the email template key we defined.

So now that we know how the mail plugin selection happens, we need to make sure this config array contains the necessary information so that emails sent with our d8mail module use the new plugin we will build. We can do this inside a hook_install() implementation that gets triggered only once when the module gets installed:


/** * Implements hook_install(). */ function d8mail_install() { $config = \Drupal::configFactory()->getEditable('system.mail'); $mail_plugins = $config->get('interface'); if (in_array('d8mail', array_keys($mail_plugins))) { return; } $mail_plugins['d8mail'] = 'mandrill_mail'; $config->set('interface', $mail_plugins)->save(); }

Not super complicated what happens above. We load the editable config object representing the system.mail configuration, and add a new element to the interface array: d8mail => mandrill_mail. We will soon create a mail plugin with the id of mandrill_mail which will be used for all emails sent by the d8mail module. And that's it.

But before we move on, we need to make sure this change is reverted when the module is uninstalled. For this, we can use the counterpart hook_uninstall() that gets called when a module gets uninstalled (there is no more module disabling in Drupal 8).

Inside the same file:

/** * Implements hook_uninstall(). */ function d8mail_uninstall() { $config = \Drupal::configFactory()->getEditable('system.mail'); $mail_plugins = $config->get('interface'); if ( ! in_array('d8mail', array_keys($mail_plugins))) { return; } unset($mail_plugins['d8mail']); $config->set('interface', $mail_plugins)->save(); }

With the hook_uninstall() implementation we do the opposite of before: we remove our plugin id if it is set.

The install/uninstall scenario is just one way to go. You can also create an administration form that allows users to select the plugin they want and under which context. But you still need to make sure that when you disable the module defining a particular plugin, the configuration will no longer keep a reference to that plugin. Otherwise the mail manager may try to use a non-existent class and throw all kinds of errors.


As I mentioned before, we will work with the Mandrill API in order to illustrate our task. So let's load up Mandrill's PHP Library and make it available in our environment. There are three steps we need to do for this.

First, we need to actually get the library inside Drupal. At the time of writing, this basically means adding the "mandrill/mandrill": "1.0.*" dependency to the root composer.json file and running composer install. Keep in mind, though, that this will also clear the Drupal installation from inside the core/ folder and download the latest stable release instead. Then, you'll need to edit the root index.php file and change the path to the autoloader as per these instructions. Hopefully this last action won't be necessary soon, and I encourage you to follow the discussions around the future of Composer in Drupal 8 for managing external libraries.

Second, we need to get an API key from Mandrill. Luckily, this we can easily generate from their administration pages. Once we have that, we can store it inside a new file created on our server, at either location:

~/.mandrill.key /etc/mandrill.key

We can also pass the key as a constructor parameter to the main Mandrill class, but this way we won't have to hardcode it in our code. 

Thirdly, we need to create a service so that we can use dependency injection for passing the Mandrill class into our plugin:

services: d8mail.mandrill: class: Mandrill

Depending on how you have loaded the Mandrill class into your application, you'll need to change the value after class. By using the composer.json approach, this will suffice.

The Mail Plugin

It's finally time to create our plugin. In Drupal 8, plugin classes go inside the src/Plugin folder of our module. Depending on their type, however, they are placed further down within other directories (in our case Mail). Let's write our class that will depend on the Mandrill API library to send emails:


<?php namespace Drupal\d8mail\Plugin\Mail; use Drupal\Core\Mail\MailFormatHelper; use Drupal\Core\Mail\MailInterface; use Drupal\Core\Plugin\ContainerFactoryPluginInterface; use Symfony\Component\DependencyInjection\ContainerInterface; use Mandrill; use Mandrill_Error; /** * Defines the Mandrill mail backend. * * @Mail( * id = "mandrill_mail", * label = @Translation("Mandrill mailer"), * description = @Translation("Sends an email using Mandrill.") * ) */ class MandrillMail implements MailInterface, ContainerFactoryPluginInterface { /** * @var Mandrill */ private $mandrill; /** * @param Mandrill $mandrill */ public function __construct(Mandrill $mandrill) { $this->mandrill = $mandrill; } /** * {@inheritdoc} */ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) { return new static( $container->get('d8mail.mandrill') ); } /** * {@inheritdoc} */ public function format(array $message) { // Join the body array into one string. $message['body'] = implode("\n\n", $message['body']); // Convert any HTML to plain-text. $message['body'] = MailFormatHelper::htmlToText($message['body']); // Wrap the mail body for sending. $message['body'] = MailFormatHelper::wrapMail($message['body']); return $message; } /** * {@inheritdoc} */ public function mail(array $message) { try { $vars = [ 'html' => $message['body'], 'subject' => $message['subject'], 'from_email' => $message['from'], 'to' => array( array('email' => $message['to']) ), ]; $result = $this->mandrill->messages->send($vars); if ($result[0]['status'] !== 'sent') { return false; } return $result; } catch (Mandrill_Error $e) { return false; } } }

There are a couple of things to note before getting into what the class does.

First, the annotations above the class. This is just the most common plugin discovery mechanism for Drupal 8. The id key matches the value we added to the system.mail.interface configuration array earlier, while the rest are basic plugin definition elements.

Second, the implementation of the ContainerFactoryPluginInterface interface by which we define the create() method. The latter is part of the dependency injection process by which we can load up the Mandrill service we defined in the services.yml file earlier. This makes testing much easier and it's considered best practice.

As I mentioned, the mail plugins need to implement the MailInterface interface which enforces the existence of the format() and mail() methods. In our case, the first does exactly the same thing as the PHPMail plugin: a bit of processing of the message body. So you can add your own logic here if you want. The latter method, on the other hand, is responsible for sending the mail out, in our case, using the Mandrill API itself.

As the Mandrill documentation instructs, we construct an email message inside the $vars array using values passed from the mail manager through the $message parameter. These will be already filtered through hook_mail(), hook_mail_alter() and the plugin's own format() method. All that's left is to actually send the email. I won't go into the details of using the Mandrill API as you can consult the documentation for all the options you can use.

After sending the email and getting back from Mandrill a sent status, we return the entire response array, which contains some more information. This array then gets added by the mail manager to its own return array keyed as result. If Mandrill has a problem, rejects the email or throws an exception, we return false. This will make the mail manager handle this situation by logging the incident and printing a status message.

And that is pretty much it. We can clear the cache and try creating another article node. This time, the notification email should be sent by Mandrill instead of PHP's mail(). With this in place, though, the hook_mail_alter() implementation has become superfluous as there are no headers we are actually sending through to Mandrill (and the text is HTML already). And for that matter quite a lot of the work of the mail manager is not used, as we are not passing that on to Mandrill. But this is just meant to illustrate the process of how you can go about setting this up. The details of the implementation remain up to you and your needs.


And there we have it. We have implemented our own mail plugin to be used by the d8module we started in the previous article. And due to the extensible nature of Drupal 8, it didn't even take too much effort. 

What's left for you to do is to perfect the mail sending logic and adapt it to your circumstances. This can mean further integration between Mandrill and your Drupal instance, constructing nice templates or what have you. Additionally, an important remaining task is writing automated tests for this functionality. And Drupal 8 offers quite the toolkit for that as well.

Categories: FLOSS Project Planets

Envato Tuts+: Using and Extending the Drupal 8 Mail API: Part 1

Thu, 2021-07-22 03:39

In this two part series we are going to explore the Mail API in Drupal 8. In doing so, we are going to cover two main aspects: how to use it programatically for sending emails and how to extend it for using an external service like Mandrill.

To demonstrate this, in the first part we will create a custom email template that gets used for sending emails to the current user when s/he saves a new Article node. Additionally, we will see how others can alter that template in order to allow for HTML rendering of the email body instead of the default plain text.

In the second part we are going to look at extending the mail system and integrating an external API for email delivery. For this, we will use Mandrill and its PHP library that provides a good foundation for interacting with its API.

All the work we go through can be found in this Git repository as part of a custom Drupal 8 module that we will start writing here. So feel free to check that out if you want to follow along. Let's get started.

The first prerequisite of this module is its .info file:

name: Drupal 8 Mailer description: 'Demonstrates the use of the Mail API in Drupal 8.' core: 8.x type: module

With this out of the way, we can already enable the module on our site if we want.

How Do We Send an Email?

There are two main steps needed to send an email programatically with Drupal 8. We first need to implement hook_mail() in order to define one or more email templates. The second step is to use the mail manager to send emails using one of these templates.

Although called a hook, hook_mail() is not a typical hook but more of a regular function that generally gets called only by the same module that implements it. In other words, when you send an email programatically, you need to specify the module name implementing hook_mail() and the template id you want to use and that is defined by this hook. But we'll see that in a minute. First, how do we implement it?


/** * Implements hook_mail(). */ function d8mail_mail($key, &$message, $params) { $options = array( 'langcode' => $message['langcode'], ); switch ($key) { case 'node_insert': $message['from'] = \Drupal::config('')->get('mail'); $message['subject'] = t('Node created: @title', array('@title' => $params['node_title']), $options); $message['body'][] = SafeMarkup::checkPlain($params['message']); break; } }

This is a very simple implementation that defines one template identified as node_insert (the $key). The other two function arguments are:

  • $message: passed by reference, and inside which we add as much boilerplate about our email as we need 
  • $params: an array of extra data that needs to go in the email and that is passed from the mail manager when we try to send the email

As you can see, we are building up the $message array with values we want this email to include in all the calls. We are setting a default from value that is retrieved from the configuration system and that represents the main site email address. We set a boilerplate email subject that lets the recipient know a new node was created, followed by the name of the node (which will be passed in through the $params array). The subject is also translatable into the language that gets passed from the caller. 

Lastly, we run the message body through the string sanitiser because the text may contain HTML and it might get truncated if we don't encode the HTML elements. And since we are using the SafeMarkup class, we need to use it at the top:

use Drupal\Component\Utility\SafeMarkup;

Additionally, the message body is an array that will later be imploded into a string. And obviously there are many other parameters we can set, such as headers, but this will suffice for this example.

And that's all for the hook_mail() implementation. Now let's turn to the code which gets run every time a new node is created, hook_entity_insert():

/** * Implements hook_entity_insert(). */ function d8mail_entity_insert(Drupal\Core\Entity\EntityInterface $entity) { if ($entity->getEntityTypeId() !== 'node' || ($entity->getEntityTypeId() === 'node' && $entity->bundle() !== 'article')) { return; } $mailManager = \Drupal::service('plugin.manager.mail'); $module = 'd8mail'; $key = 'node_insert'; $to = \Drupal::currentUser()->getEmail(); $params['message'] = $entity->get('body')->value; $params['node_title'] = $entity->label(); $langcode = \Drupal::currentUser()->getPreferredLangcode(); $send = true; $result = $mailManager->mail($module, $key, $to, $langcode, $params, NULL, $send); if ($result['result'] !== true) { $message = t('There was a problem sending your email notification to @email for creating node @id.', array('@email' => $to, '@id' => $entity->id())); drupal_set_message($message, 'error'); \Drupal::logger('d8mail')->error($message); return; } $message = t('An email notification has been sent to @email for creating node @id.', array('@email' => $to, '@id' => $entity->id())); drupal_set_message($message); \Drupal::logger('d8mail')->notice($message); }

This hook gets triggered after every node save, and all we have to do is make sure we are targeting the correct node and include our logic.

After checking that the node entity is of the type article, we load the Drupal mail manager service and start setting some values for the email. We need the following information:

  • the module name that implements hook_mail() and defines our template (what I mentioned above)
  • the template id (the $key)
  • the recipient email address (the one found on the current user account)
  • the language ($langcode) which goes inside the $params array and which will be used to translate the subject message
  • the node title that will get added to the email subject
  • the email body, which in our case will be the value of the node's body field
  • the boolean value indicating whether the email should be actually sent

We then pass all these values to the mail() method of the mail manager. The latter is responsible for building the email (calling the right hook_mail() implementation being one aspect of this) and finally delegating the actual delivery to the responsible plugin. By default, this will be PHPMail, which uses the default mail() function that comes with PHP.

If the mail manager is successful in sending the email (actual delivery is not taken into account but rather a successful PHP action), the mail() method will return an array containing a result key with whatever the mail plugin returns. Checking for that value, we can learn whether the email action was successful and inform the user that we have notified them of their action. Otherwise, we print and log an error message.

And that's about it. Clearing the cache and creating an article node should land an email in your inbox. If you are not getting anything and there are no error signs on your screen, make sure you check your server logs and mail queue to verify that emails are being sent out.

Before moving on, I would like to make a quick note regarding this hook implementation. In this example, I placed all the logic inside it directly. Additionally, I used an early return at the top, which essentially means no other logic can be added but the one specific to the article nodes. In real applications I recommend refactoring the mailing logic into a separate function or class and deferring to that. Moreover, you should not use early returns inside hook implementations but instead call other functions if the conditions are met. 

How Do We Alter an Email?

Once all of this is in place, we have another tool at our disposal that allows us to alter such an existing setup: hook_mail_alter(). This hook is called from within the mail manager before the responsible mail plugin sends the email. The purpose is to allow other modules to perform final alterations to an existent email being sent out.

Although this can be used by other modules as well, we will illustrate an example implementation from within the same module we've been working with. To this end, we will alter the email by changing one of its default headers in order to transform it from plain text to HTML. And this is how we can do this:

/** * Implements hook_mail_alter(). */ function d8mail_mail_alter(&$message) { switch ($message['key']) { case 'node_insert': $message['headers']['Content-Type'] = 'text/html; charset=UTF-8; format=flowed; delsp=yes'; break; } }

As you can see, this is a simple alteration of the Content-Type header that transforms the email into HTML. This way plain text HTML entities will be parsed as HTML by mail clients. And using the switch case, we make sure this only happens for the email template we defined earlier.

One thing to note here is that the alter hook gets called after the relevant hook_mail() implementation. So after this, the only processing that happens on the email is done inside the format() method of the mail plugin (enforced by its interface).


And that is pretty much all there is to sending emails programatically using Drupal 8. We've seen the steps required to programatically set up email templates that get hydrated by the mail manager whenever we want it. We've also mentioned the default mail delivery plugin which is used to send emails in Drupal 8. And lastly, we've seen how other modules can now alter our email by adding new headers, changing the subject, concatenating values to the mail body, etc.

In the next article we are going to look at replacing the default PHPMail plugin with our own custom implementation. We will set up a mailer that uses Mandrill with the help of its PHP library. The goal is to allow our own module to use this mailer while the rest of the application continues to use the default PHPMailer.

Categories: FLOSS Project Planets