Upgrade Your Drupal Skills

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

See Advanced Courses NAH, I know Enough
Mar 13 2024
Mar 13
Marc BergerMarc Berger

Marc Berger

Senior Backend Engineer

Always looking for a challenge, Marc tries to add something new to his toolbox for every project and build — be it a new CSS technology, creating custom APIs, or testing out new processes for development.

March 13, 2024

Recently, one of our clients had to retrieve some information from their Drupal site during a CI build. They needed to know the internal Drupal path from a known path alias. Common Drush commands don’t provide this information directly, so we decided to write our own custom Drush command. It was a lot easier than we thought it would be! Let’s get started.

Note: This post is based on commands and structure for Drush 12.

While we can write our own Drush command from scratch, let’s discuss a tool that Drush already provides us: the drush generate command. Drush 9 added support to generate scaffolding and boilerplate code for many common Drupal coding tasks such as custom modules, themes, services, plugins, and many more. The nice thing about using the drush generate command is that the code it generates conforms to best practices and Drupal coding standards — and some generators even come with examples as well. You can see all available generators by simply running drush generate without any arguments.

Step 1: Create a custom module

To get started, a requirement to create a new custom Drush command in this way is to have an existing custom module already in the codebase. If one exists, great. You can skip to Step 2 below. If you need a custom module, let’s use Drush to generate one:

drush generate module

Drush will ask a series of questions such as the module name, the package, any dependencies, and if you want to generate a .module file, README.md, etc. Once the module has been created, enable the module. This will help with the autocomplete when generating the custom Drush command.

drush en

Step 2: Create custom Drush command boilerplate

First, make sure you have a custom module where your new custom Drush command will live and make sure that module is enabled. Next, run the following command to generate some boilerplate code:

drush generate drush:command-file

This command will also ask some questions, the first of which is the machine name of the custom module. If that module is enabled, it will autocomplete the name in the terminal. You can also tell the generator to use dependency injection if you know what services you need to use. In our case, we need to inject the path_alias.manager service. Once generated, the new command class will live here under your custom module:

/src/Drush/Commands

Let’s take a look at this newly generated code. We will see the standard class structure and our dependency injection at the top of the file:

get('token'),
      $container->get('path_alias.manager'),
    );
  }

Note: The generator adds a comment about needing a drush.services.yml file. This requirement is deprecated and will be removed in Drush 13, so you can ignore it if you are using Drush 12. In our testing, this file does not need to be present.

Further down in the new class, we will see some boilerplate example code. This is where the magic happens:

/**
   * Command description here.
   */
  #[CLI\Command(name: 'custom_drush:command-name', aliases: ['foo'])]
  #[CLI\Argument(name: 'arg1', description: 'Argument description.')]
  #[CLI\Option(name: 'option-name', description: 'Option description')]
  #[CLI\Usage(name: 'custom_drush:command-name foo', description: 'Usage description')]
  public function commandName($arg1, $options = ['option-name' => 'default']) {
    $this->logger()->success(dt('Achievement unlocked.'));
  }

This new Drush command doesn’t do very much at the moment, but provides a great jumping-off point. The first thing to note at the top of the function are the new PHP 8 attributes that begin with the #. These replace the previous PHP annotations that are commonly seen when writing custom plugins in Drupal. You can read more about the new PHP attributes.

The different attributes tell Drush what our custom command name is, description, what arguments it will take (if any), and any aliases it may have.

Step 3: Create our custom command

For our custom command, let’s modify the code so we can get the internal path from a path alias:

/**
   * Command description here.
   */
  #[CLI\Command(name: 'custom_drush:interal-path', aliases: ['intpath'])]
  #[CLI\Argument(name: 'pathAlias', description: 'The path alias, must begin with /')]
  #[CLI\Usage(name: 'custom_drush:interal-path /path-alias', description: 'Supply the path alias and the internal path will be retrieved.')]
  public function getInternalPath($pathAlias) {
    if (!str_starts_with($pathAlias, "/")) {
      $this->logger()->error(dt('The alias must start with a /'));
    }
    else {
      $path = $this->pathAliasManager->getPathByAlias($pathAlias);
      if ($path == $pathAlias) {
        $this->logger()->error(dt('There was no internal path found that uses that alias.'));
      }
      else {
        $this->output()->writeln($path);
      }

    }
    //$this->logger()->success(dt('Achievement unlocked.'));
  }

What we’re doing here is changing the name of the command so it can be called like so:

drush custom_drush:internal-path or via the alias: drush intpath

The is a required argument (such as /my-amazing-page) because of how it is called in the getInternalPath method. By passing a path, this method first checks to see if the path starts with /. If it does, it will perform an additional check to see if there is a path that exists. If so, it will return the internal path, i.e., /node/1234. Lastly, the output is provided by the logger method that comes from the inherited DrushCommands class. It’s a simple command, but one that helped us automatically set config during a CI job.

Table output

Note the boilerplate code also generated another example below the first — one that will provide output in a table format:

/**
   * An example of the table output format.
   */
  #[CLI\Command(name: 'custom_drush:token', aliases: ['token'])]
  #[CLI\FieldLabels(labels: [
    'group' => 'Group',
    'token' => 'Token',
    'name' => 'Name'
  ])]
  #[CLI\DefaultTableFields(fields: ['group', 'token', 'name'])]
  #[CLI\FilterDefaultField(field: 'name')]
  public function token($options = ['format' => 'table']): RowsOfFields {
    $all = $this->token->getInfo();
    foreach ($all['tokens'] as $group => $tokens) {
      foreach ($tokens as $key => $token) {
        $rows[] = [
          'group' => $group,
          'token' => $key,
          'name' => $token['name'],
        ];
      }
    }
    return new RowsOfFields($rows);
  }

In this example, no argument is required, and it will simply print out the list of tokens in a nice table:

------------ ------------------ ----------------------- 
  Group        Token              Name                   
------------ ------------------ ----------------------- 
  file         fid                File ID                
  node         nid                Content ID
  site         name               Name
  ...          ...                ...

Final thoughts

Drush is a powerful tool, and like many parts of Drupal, it’s expandable to meet different needs. While I shared a relatively simple example to solve a small challenge, the possibilities are open to retrieve all kinds of information from your Drupal site to use in scripting, CI/CD jobs, reporting, and more. And by using the drush generate command, creating these custom solutions is easy, follows best practices, and helps keep code consistent.

Further reading

Making the web a better place to teach, learn, and advocate starts here...

When you subscribe to our newsletter!

Feb 06 2024
Feb 06

Webform is a Drupal module that allows you to create forms directly in Drupal without using a 3rd party service.

It can be used to create basic “Contact Us” and complex application forms with custom field logic and integration.

In this getting started guide you’ll learn how to:

  1. Create a form
  2. Add elements to form
  3. Customize form with conditional logic
  4. Embed the form
  5. Send submissions to Google Sheets

This tutorial accompanies the video above.

Jan 26 2024
Jan 26
The Web ChefsThe Web Chefs

At Four Kitchens we keep several lists of “Hot Topics” to share our learnings across the dozens of sites that we care for. Are you upgrading a Drupal site to CKEditor5? We’ve tidied up one of these internal wiki documents into this set of general upgrade guidelines that might pertain to your website.

Rough steps to upgrade

The level of effort needed for this upgrade will be different for each site. It may take some time to figure out. CKEditor 5 is available in Drupal 9.5 and beyond. You can try switching/upgrading on a local site or multidev and assess the situation.

First, create a list of CKEditor enhancement modules on the site and check if they are Drupal 10 ready (the reports from Upgrade Status and this Drupal.org page may help). Common modules to look for include Linkit, Anchor Link, Advanced Link, IMCE, Entity Embed, Video Embed Field, Footnotes, and anything with the word “editor” in the title.

As a best practice, you should test both the creation of new content, and editing existing content in several places. This will help make sure that some lesser used HTML isn’t treated differently in the new CKEditor. Run visual regression tests (if available).

You may need to point out key interface changes to your clients or stakeholders (e.g., contextual windows for links/media/tables instead of modals, etc.). While it is a bit of a change, it’s overall an improved user experience, especially for new people who are coming in cold.

Anchor links

Anchor link gives editors the ability to create links to different sections within a page.

For “better integration with Drupal 10, CKEditor 5, and LinkIt” there is a 3.0.0@alpha version. If your project isn’t using wikimedia/composer-merge-plugin, you must require northernco/ckeditor5-anchor-drupal package and add the following to the repositories section of composer.json:

{
	"type": "package",
  "package": {
      "name": "northernco/ckeditor5-anchor-drupal",
      "version": "0.3.0",
      "type": "drupal-library",
      "dist": {
          "url": "https://registry.npmjs.org/@northernco/ckeditor5-anchor-drupal/-/ckeditor5-anchor-drupal-0.3.0.tgz",
          "type": "tar"
      }
  }
}

Issue

Branch

Embedded media

Depending on the age of your site, it might be using one of several techniques to embed media into the WYSIWYG:

If your site is using the video_embed_field module (most sites are probably using Drupal core’s media module), there is a patch that adds support for CKE5. Insert Image works slightly different (though this is probably not the case if your site uses core’s media module). It’s worth considering if there is a way to enhance this for user experience, if necessary.

If your site uses custom Entity Embed for media, consider switching to the core media library. It may provide a better administrative user experience in some cases.

The insert image button in CKEditor functions a little differently than it used to. Rather than bringing up a modal with fields to upload an image like the image below:

Insert image button in CKEditor5

It now immediately pulls up your computer’s file system for you to search for images like so:

Filesystem image search in CKEditor5Filesystem image search in CKEditor5

After adding your image, the alt tag box prompts you underneath the image:

CKEditor5 alt tag promptCKEditor5 alt tag prompt

After submitting your alt tag, you can adjust alignment and sizing:

CKEditor5 image sizingCKEditor5 image sizing

Moving general styles to link styles

It was common in CKEditor4 to use its “Styles” feature to provide a way to add variations of links (to make them look like buttons, or to add icons).

There are a few UX problems with that approach. Either the styles are set to apply on , which means that they can be applied to non-links, or the styles are set to apply on , which means that they are mysteriously grayed out most of the time (until you select a link). Either way, it’s not intuitive how to apply a link style. In CKEditor5, we can switch to using the Link Styles module.

Change in Styles dropdown behavior

In CKEditor4, when integrated with Drupal, the Styles dropdown only allowed applying one style to an element (e.g., “external link”). If you tried to apply a different style, such as “locked link,” the previous style would be removed.

The Drupal implementation of CKEditor5 allows for multiple styles to be applied to elements via the Styles dropdown. This change may be unexpected for some, and could result in elements that look broken, such as when a link has both the “external link” and “locked link” styles.

CKEditor5 introduced a new API for adding theme-specific styles. The new architecture might cause the CKEditor5 theme to bleed into the admin theme. To know how to deal with these issues, review new API for adding theme-specific styles in CKEditor5.

You’ll likely run into an issue with styles bleeding outside of the editor, so see the other section within this page.

Cut and paste

Paste-from-Word, paste-from-Google-Docs, etc. is now built-in to CKEditor5. (At least for 90% of use cases.) There’s a paid plugin for more esoteric needs.

There is no paste-as-plain-text plugin for CKEditor5. You can use Ctrl-Shift-V (or Cmd-Shift-V) to paste as plain text. If you want to get rid of all formatting (including bold, links, etc.) in existing text, you can highlight the text, use Ctrl-C to copy, then Ctrl-Shift-V to paste it back as plain text.

Many of our Behat automated test broke after the update because there were multiple structural changes, so this is how we solved it: First, here is the doc about how to get the editor instance in case you want to know more about it. This is how we rewrite our custom step to fill out the CKEditor during testing. (We found the code in an article post-post).

/**
   * A step to help fill in a ckeditor wysiwyg.
   *
   * @param string $locator
   *   The css locator of the field that ckeditor has taken over.
   * @param string $value
   *   The html value you wish to fill in to ckeditor.
   *
   * @Then I fill in wysiwyg on field :locator with :value
   */
  public function iFillInWysiwygOnFieldWith($locator, $value) {

    // https://ckeditor.com/docs/ckeditor5/latest/support/faq.html#how-to-get-the-editor-instance-object-from-the-dom-element
    $ckeditor5_drupal_editable_element = "div.form-item-$locator .ck-editor__editable";

    $this->getSession()
      ->executeScript(
        "
        var domEditableElement = document.querySelector(\"$ckeditor5_drupal_editable_element\");
        if (domEditableElement.ckeditorInstance) {
          const editorInstance = domEditableElement.ckeditorInstance;
          if (editorInstance) {
            editorInstance.setData(\"$value\");
          } else {
            throw new Exception('Could not get the editor instance!');
          }
        } else {
          throw new Exception('Could not find the element!');
        }
        "
      );
  }

and the mink step for regular field:

And I fill in wysiwyg on field "field-summary-0-value" with "Some Teaser Text"

And for a field inside a paragraph:

And I fill in wysiwyg on field "field-sidebar-content-0-subform-field-simple-text-0-value" with "Behat Side Nav Body Text"

Preventing custom styles from bleeding into admin theme with CKEditor5

See the new API documentation about implementing theme styles in the new way. This may require some adjustments on your end.

One of the major changes with CKEditor5 is that it pulls WYSIWYG styles onto the whole page when there is a WYSIWYG on the page. In CKEditor4, styles were only pulled into the CKEditor iframe. This can be extremely frustrating when the admin theme looks odd or different on pages that contain a WYSIWYG.

Limit the number of stylesheets being pulled into the WYSWIYG. (First, note that this method has only been confirmed to work on newer versions of Sous using specific webpack settings. If you are having problems with it, make sure your webpack settings allow for multiple manifests to be generated. You may need to refer to a newer site to see how it is configured.)

The first step is to create a new stylesheet (a manifest) called wysiwyg.scss in the same directory as your styles.scss file, which assembles all the stylesheets used in your theme. For this stylesheet, we’ll only want to include the stylesheets that our WYSIWYG needs. For example, I have one that looks like this:

@import url('https://fonts.googleapis.com/css2?family=Poppins:ital,wght@0,400;0,700;1,400;1,700&display=swap');
@import '~normalize.css/normalize';
@import '~breakpoint-sass/stylesheets/breakpoint';

// Components
@import '00-base/**/*.scss';
// Include all atoms except form items.
@import '01-atoms/00-links/**/*.scss';
@import '01-atoms/01-text/**/*.scss';
@import '01-atoms/02-lists/*.scss';
@import '01-atoms/tables/*.scss';
@import '01-atoms/images/**/*.scss';
@import '05-pages/colors.scss';
@import '05-pages/base.scss';

In this example, we are pulling in a couple needed files from node_modules (normalize and breakpoint), and then any .scss files from base, and then select files from atoms (links, text, lists, tables, and images).

Compile and make sure that it has created the new files at /dist/css/wysiwyg.css. If you get any errors, you may need to include another file that has a variable you need, or something along those lines.

1.) Update your .info file In your theme’s .info file, set CKEditor5 to use your new stylesheet:

ckeditor5-stylesheets:
  - dist/css/wysiwyg.css

2.) Review the WYSIWYG. Visit a page with a WYSIWYG on the page, and verify that the limited styles are loading properly within the WYSIWYG. Try all the dropdowns and buttons that are included in the WYSIWYG settings. If anything appears unthemed, review your styles to see if there’s a stylesheet missing from your manifest.

3.) Review the rest of the page. Now review the page around the WYSIWYG and note how if differs from other pages that do not have a WYSIWYG. Common differences to look for are: heading styles, text styles, buttons — basically anything that you included in your manifest.

4.) Limit styles

  • Find the page’s body class for node edit pages (in our test case, .gin--edit-form). It may depend on your admin theme.
  • Find the wrapper class for the WYSIWYG. Most likely the best choice is .ck-content. Our approach will be to hide styles from .gin--edit-form, but then add them to .ck-content.

For example:

body {
  background-color: clr(background);
  color: clr(text);

  @include body-copy;
}

becomes:

body:not(.gin--edit-form),
.ck-content {
  background-color: clr(background);
  color: clr(text);

  @include body-copy;
}

And for buttons:

.main .button {
  @include button-base;
  @include button-color-primary;
  @include button-medium;
}

it becomes:

body:not(.gin--edit-form) .button,
.main .button,
.ck-content a.button {
  @include button-base;
  @include button-color-primary;
  @include button-medium;
}

With any luck, the styles used apply mixins, which makes it easy to filter out where to apply the styles. In some cases, the overriding of styles may become hard because of the order in which the stylesheets are loaded. Try to avoid !importants and instead use things like an additional element or class to firm up your override.

One issue that may come up is your overrides here end up overriding things in your custom theme, depending on how they are defined. In this case, don’t wrap the styles in the body classes, but rather undo the custom theme’s style on the admin page items manually. Luckily, since we’re narrowly applying custom styles, only things used in the WYSIWYG will need to be addressed.

For instance:

// Apply general link styles to all links.
a {
  @include link;
}

// Overrides for Admin pages containing CKEditor (you will need a body class only on admin pages).
.user-logged-in {
  a {
    background-image: none;
    transition: none;
  }

  .horizontal-tabs-list a,
  .toolbar a {
    font-weight: normal;
  }
}

// Reapply link styles to links within the WYSIWYG
.ck-editor a {
  @include link;
}

Continue to review your page and adjust it until it no longer differs from other admin pages.

Editor explodes out of its container in deeper paragraphs

This issue seems to occur only with rich text fields within a paragraph. It might be limited to the Gin theme.

This issue might be because of the container’s width. If input fields inside the container have a specified size exceeding the screen width, it can lead the editor to inherit the container’s width, extending beyond the screen. You can see this as a Drupal Core/CKEditor5 bug in Drupal.org: CKEditor5 toolbar items of multivalue field (typically Paragraphs) overflowing on narrow viewports and overlapping with node form’s sidebar on wide viewports.

To resolve this quickly, set the input fields to 100% width, making sure everything works seamlessly. Be sure to include this in a stylesheet of your admin theme.

.node-form input[size] {
  width: 100%;
}

We can also modify the ‘flex-wrap’ property of the CKEditor buttons to make sure they stay within the container’s width:

.ck-editor .ck.ck-toolbar.ck-toolbar_grouping > .ck-toolbar__items {
    flex-wrap: wrap;
}

Additional resources

Making the web a better place to teach, learn, and advocate starts here...

When you subscribe to our newsletter!

Jan 11 2024
Jan 11

The Gutenberg Editor is a powerful page building tool for Drupal that utilizes a block building system to create and edit content.

This comprehensive guide walks you through each step of setting up and using the Gutenberg Editor.

From downloading and installing the Gutenberg module, to enabling it on your content types, and finally using it to create your content using blocks, this guide has you covered.

To get the most out of this guide, follow the video above.

Jan 05 2024
Jan 05
Amanda LukerAmanda Luker

Amanda Luker

Web Chef Emeritus

Amanda is responsible for translating visual designs and applying them to Drupal sites.

January 5, 2024

Drupal provides a system for site administrators to add their own images and have them appear uniformly on the website. This system is called Image Styles. This tool can resize, crop, and scale images to fit any aspect ratio required by a design.

When creating responsive websites, a single image style for each image variation is insufficient. Each image, such as a hero image, a card image, a WYSIWYG image, or a banner image, requires multiple versions of one image. This ensures that the website delivers only what visitors need based on their screen size. For instance, a mobile user may only require a 320-pixel-wide image, while a large desktop user may want an 1,800-pixel-wide image (doubled for double-pixel density). For this reason, Drupal has Responsive Image Styles, which will group your images into a set of styles that will each show under different conditions.

Practical approach to convert images from design to Drupal

  • Determine your image’s aspect ratio. If you find that the images in the design are not in a common aspect ratio (like 1:1, 2:1, 4:3, or 16:9) or if they vary by a little bit, consider running the dimensions through a tool that will find the closest reasonable aspect ratio.
  • Determine the smallest and largest image sizes. For example, for a 16:9 aspect ratio, the smallest size might be 320 pixels x 180 pixels, while the largest could be 3,200 pixels x 1,800 pixels (doubled for high-density screens).
  • To generate all variations, you can use an AI tool to print images with 160-pixel increments between each size. 160 increments tend to hit a lot of common breakpoints. Here’s an example using GitHub CoPilot:

There are likely more ways to streamline this process with Copilot. I’ve also used ChatGPT to rewrite them using a prefix, making it easy to add them in Drupal like this:

Drupal image styles

If adding all of these steps seems like a lot of work, consider using the Easy Responsive Images module! This module can create image styles for you, allowing you to set your aspect ratios and the increment between each style.

Once you have all your styles in place, create your responsive image styles by following these steps:

  • Choose a name for your responsive image style based on its usage
  • Select the “responsive image” breakpoint group
  • Usually, I choose to select multiple image styles and use the sizes attribute. Use the sizes attribute to craft your “sizes.” For example:

(min-width:960px) 50vw, (min-width:1200px) 30vw, 100vw

In this example, choosing an image that is smaller than 960 pixels will best fit the full width of the viewport. At 960 pixels, the image will be selected to best fill half of the viewport width, and at 1,200 pixels, 30%. This approach is nimble and allows the browser to choose the most appropriate image for each case.

After setting the size rules, choose all of the image styles that you want the browser to be able to use. You don’t have to use them all. In some cases, you might have two responsive image styles that are pulling from the same aspect ratio image styles, but one uses all of them and the other uses a subset of them.

Drupal image sizingDrupal image sizing

After adding your responsive image style, you need to map your Media View Mode:

  1. Go to https://[your-site.local]/admin/structure/display-modes/view/add/media
  2. Add the media view mode as a new Display for Images: https://[your-site.local]/admin/structure/media/manage/image/display
  3. Choose “Responsive image” as the Format and select your new responsive image style

Drupal responsive image manage displayDrupal responsive image manage display

Once you have set this up, you are ready to use the View Mode to display the image field for your entity.

Drupal article with imageDrupal article with image

In this example, all the images have the same breakpoint. There may be times when you need to have different aspect ratios at different breakpoints. In those cases, you may want to use your custom theme’s Breakpoint Group. This will allow you to manually select each image style on for each breakpoint (instead of letting Drupal choose it for you).

Making the web a better place to teach, learn, and advocate starts here...

When you subscribe to our newsletter!

Jan 03 2024
Jan 03

Over the past 12 months, our teams have completed numerous Drupal upgrades. We would like to share our experiences and knowledge with anyone who has yet to undergo this process to help make it smoother for you.

Drupal Core Major Upgrades Blog 1

Background

From Drupal 8, upgrades became a lot easier with several tools to semi-automate the process.

In addition, if you, like CTI Digital, have a number of sites to upgrade and different developers working on each of them, you could easily duplicate effort as many contributed modules are used in most sites - e.g. Webforms and Paragraphs, just to name two. Therefore, this article also contains the approach that CTI Digital took in managing the latest upgrades to Drupal 10 of around 50 sites (some still on Drupal 8) to ensure knowledge transfer within the team.

This article looks at the tools available and the lessons we learned.

Why is it easier since Drupal 8

The approach taken by the Drupal core team with Drupal 8+ has made things a lot easier. That approach is to deprecate hook functions, classes, class methods, etc., while introducing their replacements. Then, a list of deprecated features that will be removed in the next major version is produced and fixed at a specified minor version of the current Drupal major version (8.9 for D8 and 9.5 for D9). Drupal's main dependency, Symfony, takes the same approach.

This approach clarifies what is deprecated in Integrated Development Editors (IDEs) and allows various tools to semi-automate the process. It enables them to find deprecated code, advise on what changes are needed and even make many of them. Finally, the Drupal CI systems automatically run tools to spot deprecated code on contributed modules and produce patches, which are then attached to a new issue created for each module. These can then be tested, modified and approved by the community.

Tools 

So, what are these tools? There are command line tools for picking up and making changes, but there are also contributed modules (often using those command line tools). The ones we at CTI Digital found useful are listed here.

The three tools we used are:

PHP Codesniffer - a tool to find code issues by rule; in this case, PHP 8.1 compatibility

Drupal Core Major Upgrades Blog 2

Upgrade Status

This is the first one that should be installed on a development copy of each site to identify the work that needs to be done. It is a contrib module that can be installed with composer:

composer require drupal/upgrade_status

This tool provides a report through your site's UI (Reports/Upgrade status admin/reports/upgrade-status). This report details everything you need to do - down to lines of code that need changing per module or theme. This detail includes what to change each line to and if Rector - see next section - can make the change automatically.

It will tell you the following: 

  1. Environmental factors - such as PHP, database and Drush versions required. For Drupal 10 it will also include a list of invalid permissions and deprecated core modules that you have installed

  2. Modules/themes that are not installed

  3. Contributed modules/themes that have an upgrade available (including if that is compatible)

  4. Contributed modules/themes that have nothing available (with a link to an issue queue search for issues containing the text ‘Drupal 10’)

  5. Modules/themes that have changes that could be made by Rector

  6. Modules/themes that have changes that need to be fixed manually

Each module/theme can be scanned for detailed depreciations, which will also categorise each as one that can be done with Rector or one that needs to be done manually. This can be run where there is not a compatible version or patch already available.

For all contributed modules/themes, you have the following options:

  • Upgrade to a more recent version

  • Apply a readily available patch on its issue queue

  • Upgrade and patch

  • Write a patch from scratch (using Rector where possible)

  • Remove an unused module/theme from the code

  • Replace the module/theme

    It should be noted that removing or replacing a module/theme that is currently installed requires two deployments. The first uninstalls the module/theme, and the second removes it from the code base. If you do both in one go, you will get issues with Drush commands in the deployment.

Drupal Rector

This command line tool can automatically make many of the necessary changes. It can be installed in a project with:

composer require palantirnet/drupal-rector --dev

Then, a file called rector.php needs to be copied from the vendor/palantirnet/drupal-rector folder to the Drupal root directory (web in the examples here).

You can then run this tool on any module/theme with:

vendor/bin/rector process web/[modules or themes]/[SUB_FOLDER]/[YOUR_MODULE] --dry-run

This will find what needs to be changed, and if you are happy with the changes, removing the –dry-run option will, of course, allow it to do its thing.</span>

PHP Codesniffer

This command line tool is not specific to Drupal core upgrades. It looks for particular code patterns, and you can install add-ons to look for anything, including PHP versions. Since Drupal upgrades often include PHP upgrades, it is at least worth running this tool on all custom code.

In order to test for PHP 8.1 ( required for Drupal 10), the following will install the tools you need:

composer require --dev phpcsstandards/phpcsutils:"^1.0@dev"

composer require --dev phpcompatibility/php-compatibility:dev-develop

You can confirm that this worked by running:

vendor/bin/phpcs -i

This command will list what sniffs are installed and will include PHPCompatibility.

Then the following can be run to test all your custom modules:

vendor/bin/phpcs -p web/modules/custom --standard=PHPCompatibility --runtime-set testVersion 8.1 --extensions='php, module,inc,install,theme'

This will test for PHP 8.1 compatibility, specifically in all PHP code files. You can do the same for any custom themes.

Drupal Core Major Upgrades Blog 3

Deprecations not spotted by the tools

There are some deprecations that the tools do not spot. They are services, libraries and hook functions. In the case of services, these are the calls to \Drupal::service(''). If this call is assigned to a variable and that variable is given the relevant class (/* @var */), then that class will also be deprecated and picked up. Also, if you inject a service into a class, the service's related class will be picked up.

The only solution we found was to create a text file with one service, library or hook function per line and use grep to search the custom code:

grep -r -f [textfile] web/modules/custom

And the same for custom themes.

The upgrade steps

Apart from upgrading and patching contrib modules and themes, fixing custom modules and themes, and removing unused code, there are two extra steps for D9 to D10 upgrades.
The first is to fix an old issue of invalid permissions. In D8, modules/themes were not required to delete permission allocations to user roles when they deleted a permission that they created. One of the minor versions of D9 firmed this up, and a core database update removed any such orphans. However, this did not always work, and the Upgrade status report lists the user roles with non-existent permissions that must be deleted - manually from the configuration yml files.

The second is core modules and themes that have been deprecated and are being deleted from D10. The Upgrade status report will list the core modules and themes the site uses. All of these have a contributed version that can be added to a project if needed. Detailed recommendations are also available here.

The final upgrade to core

This should simply be a case of running the Composer command once everything else is done:

composer require drupal/core-* –with-all-dependencies

This presupposes that the site is built with the standard drupal/core-recommended and related packages.

However, you will often find that Composer finds some requirements clashing with D10 or its dependencies.

The main reason is that a module/theme is patched to D10, including the change to the info.yml file's core_version_requirement and the composer.json file. Composer will have an issue with this. This is because Composer is using the files in the repository to determine compatibility, not the patch file changes. However, there is a solution with 'lenient'. This Composer add-on allows you to ask Composer to be lenient on the constraints of individual packages.

The command to add the lenient package to the site is:

composer require mglaman/composer-drupal-lenient

The command to add a list of modules and themes to the allowed list is:

composer config --merge --json extra.drupal-lenient.allowed-list '["drupal/YOUR_MODULE1", "drupal/YOUR_MODULE2" …]'

In addition, some projects have drupal-composer/drupal-scaffold as a dependency. This is deprecated, and you will get a notice about that. It is to be replaced with drupal/core-composer-scaffold. Finally drupal/console is incompatible with D10 and needs to be removed.

Managing upgrades for a large number of sites

As mentioned at the top of this article, there could be a lot of duplicate effort and different approaches taken if steps are not taken to organise the upgrade of a large number of sites.

The solution we at CTI Digital came up with is simply maintaining a spreadsheet of information about contributed modules/themes. This would contain recommendations (version, available patches) and any complications or special instructions associated with that module or theme's upgrade, etc. It is maintained as more sites are audited and then upgraded with findings as we go to assist later sites. This can also be applied to any custom modules/themes that are shared with more than one site.

As each site is audited for the effort required, the spreadsheet is referred to and added to so that effort is not duplicated and the spreadsheet is kept up to date with findings from each site. Also, any approaches that should be followed on all sites are kept here - e.g. we replaced all sites using Swiftmailer with Symfony Mailer for mail management.

The other primary approach was to consolidate effort patching contributed modules/themes that are not ready. CTI Digital's work to improve a contributed module/theme is always contributed back to drupal.org. A link to this new patch is stored in the database for the following site that uses this module/theme.</span>

4-Jan-02-2024-12-30-44-3036-PM

What about Drupal 8 to Drupal 10

CTI Digital still had a few sites on Drupal 8, which needed to be upgraded to Drupal 10 rather than two separate upgrades.

There are factors that affect this:

  • Upgrade status will only give what needs to be done to get to D9

  • Drush does not cope with a deployment that goes from 8 to 10 in one go

  • A significant number of contributed modules/themes have versions that are 8 and 9 compatible and versions that are 9 and 10 compatible, rarely one version compatible with 8, 9 and 10

  • A few contributed modules/themes have the newest version drop support of D9

    This means you have to do an 8-9 audit (with the dev copy on D8.9 minimum), upgrade the dev copy to D9 and then do a new audit from 9-10. You will be able to assess the complete upgrade requirement for all the modules/themes identified by the first audit, but the second audit will find modules/themes currently compatible with D9 but not D10 that are missed by the first. The second audit will also supply invalid permissions and core modules/themes that are removed from D10.

    You must also upgrade as two deployments minimum (one to D9 and the second to D10). Some modules/themes will be upgraded in the D8 site before upgrading to D9, and some in the D9 site before upgrading to D10. Some will have to be upgraded twice. Some will need to be upgraded when the site is on D10 (occasionally at the same time as the core upgrade). It will depend upon the module's upgrade path and the version the D8 site is on.

What is next?

You may be wondering if anything can make things easier for D11. Although the overall effort can not be reduced, it can be spread out.

If you regularly upgrade contributed modules and themes to the most recent version, you will reduce the work when it's time to upgrade to D11.

If you also run regular Upgrade Status reports on your D10 site, you can create work dockets for changes to your custom code. It can also be used to identify contributed modules and themes that you use that have not yet been upgraded. You could create work dockets for replacing these modules or contributing patches to upgrade them to D11. These can be spread out between now and the need to upgrade to D11.

The ultimate goal of these approaches is that you only have to upgrade the core when it is time to upgrade to D11.

To get ahead, speak to one of our Drupal experts and book a 30-minute session today to discuss a migration or upgrade for your business. 

Dec 19 2023
Dec 19
Sean B5 min read

Dec 19, 2023

Let’s face it — manually creating and maintaining a lot of content types in Drupal can be a real pain. The repetitive nature of tasks, inconsistencies in field creation, and the time-consuming process of updating settings across multiple content types are familiar struggles for developers working in teams.

At TwelveBricks, we maintain sites with very different content models. Too much time was spent setting up and maintaining those models, so we finally decided to do something about it!

YAML Bundles is a pragmatic approach to streamlining content type management through YAML-based Drupal plugins. It allows developers to define fields and content types from custom modules, making it a lot easier to add or update fields and content types.

The module defines 3 important plugin types:

  • Field types
    By defining common field types and their form/display settings (like widgets and formatters), you can remove repetitive configuration from the bundle definitions.
  • Entity types
    You can define common settings for entity types to remove even more repetitive configurations from the bundle definitions. We have also integrated support for a bunch of other contrib modules we often use, to save even more time.
  • Entity bundles
    You can use the defined fields in entity bundles, complete with customizable form and display settings that can be overridden on a field-by-field basis. The default settings of the entity types can also be overridden if you need to.

The module ships with defaults for the most common field and entity types, but you can easily override them. We have tests, which we also used to document all the available plugin settings and options. You can check out the documentation in the plugin definitions of the yaml_bundles_test module.

To use the power of YAML bundles, we’ve added a couple of helpful Drush commands that will revolutionize your content type management.

drush yaml-bundles:create-bundles
This command uses the defined plugins to (re)create all the specified bundles, fields, and settings. Whether you’re starting from scratch or optimizing an existing configuration, this command ensures the seamless generation of all your content types with a single command.

drush yaml-bundles:create-bundle
You don’t have to use the bundle plugins if you don’t want to. For those who prefer a more hands-on approach, this command offers the flexibility to create a new content type directly through Drush.

drush yaml-bundles:create-field
This command simplifies the process of adding new fields to existing content types using default plugin configurations. With the ability to customize form and view displays, this command makes adding fields to different content types much easier.

YAML Bundles comes with out-of-the-box support for several popular Drupal modules, including:

  • Pathauto: Define path aliases for entities and bundles effortlessly.
  • Content Translation: Translate fields, labels, and settings into multiple languages.
  • Content Moderation: Integrate content types into existing workflows.
  • Field Group: Group fields for your content type directly from the YAML plugins.
  • Simple Sitemap: Ensure your entity/bundle is included in the sitemap.
  • Maxlength: Define maximum lengths for text fields if needed.
  • Layout Builder: Utilize layout builder for your entity/bundle.
  • Search API: Index entities/bundles and defined fields using the powerful Search API.

To define default settings and fields for an entity type in your custom module, create a [mymodule].yaml_bundles.entity_type.yml file. In this file, you can create all the defaults you need for the entity type.

# Add the default settings and fields for node types.
node:
description: ''
langcode: en

# Make sure our plugin gets preference over the yaml_bundle plugin.
weight: 1

# Default content type settings.
new_revision: true
preview_mode: 0
display_submitted: false

# Enable content translation.
content_translation:
enabled: true
bundle_settings:
untranslatable_fields_hide: '1'
language_alterable: true
default_langcode: current_interface

# Add the content to the sitemap by default.
sitemap:
index: true
priority: 0.5
changefreq: 'weekly'
include_images: false

# Enable layout builder.
layout_builder: true

# Enable content moderation.
workflow: content

# Add the content to the default search index.
search_indexes:
- default

# Create the full and teaser view displays.
view_displays:
- teaser

# Add the default fields.
fields:
langcode:
label: Language
weight: -100
form_displays:
- default
title:
label: Title
search: true
search_boost: 20
weight: -98
form_displays:
- default
field_meta_description:
label: Meta description
type: text_plain
search: true
search_boost: 20
maxlength: 160
maxlength_label: 'The content is limited to @limit characters, remaining: @remaining'
weight: -25
form_displays:
- default

# Translate the default fields.
translations:
nl:
fields:
langcode:
label: Taal
title:
label: Titel
field_meta_description:
label: Metabeschrijving
maxlength_label: 'De inhoud is beperkt tot @limit tekens, resterend: @remaining'
de:
fields:
langcode:
label: Sprache
title:
label: Titel
field_meta_description:
label: Meta-Beschreibung
maxlength_label: 'Der Inhalt ist auf @limit-Zeichen beschränkt, verbleibend: @remaining'</span>

To define a content type in your custom module, create a [mymodule].yaml_bundles.bundle.yml file. In this file, you can create all the bundle definitions you need for the bundle.

node.news:
label: News
description: A description for the news type.
langcode: en

# Add generic node type settings.
help: Help text for the news bundle.

# Enable a custom path alias for the bundle. Requires the pathauto module to
# be enabled.
path: 'news/[node:title]'

# Configure the simple_sitemap settings for the bundle. Requires the
# simple_sitemap module to be enabled.
sitemap:
priority: 0.5

# Configure the search API index boost for the bundle. Requires the
# search_indexes to be configured and the search_api module to be enabled.
boost: 1.5

# Configure the fields for the bundle. For base fields, the field only needs
# a label. For custom fields, the type needs to be specified. The type
# configuration from the yaml_bundles.field_type plugins will be merged with
# the field configuration to allow the definition to be relatively simple.
# Generic defaults for a field type can be configured in the
# yaml_bundles.field_type plugins.
#
# See yaml_bundles.yaml_bundles.field_type.yml for the list of supported
# field types and their configuration properties.
fields:

field_date:
type: datetime
label: Date
required: false
search: true
search_boost: 1
cardinality: 1
field_default_value:
-
default_date_type: now
default_date: now
form_displays:
- default
view_displays:
- full
- teaser

field_body:
type: text_long
label: Text
required: true
search: true
search_boost: 1
form_displays:
- default
view_displays:
- full

field_image:
type: image
label: Image
required: true
search: true
form_displays:
- default
view_displays:
- full
- teaser

field_category:
type: list_string
label: Category
required: false
search: true
options:
category_1: Category 1
category_2: Category 2
form_displays:
- default
view_displays:
- full
- teaser

field_link:
type: link
label: Link
required: true
search: true
cardinality: 2
form_displays:
- default
view_displays:
- full
- teaser</span>

We’re excited about the potential YAML Bundles brings to the Drupal ecosystem, and we can’t wait for you to experience the difference it makes in your projects. Please check it out and let us know what you think!

Download YAML Bundles | Documentation | Example plugins

Building a new website can be a time-consuming and expensive process, but it doesn’t have to be. TwelveBricks offers an affordable and easy-to-use (Award-winning!) Content Management System (CMS) for a fixed monthly price.

We’ve optimized the workflow for editors so they can fully focus on their content without worrying about the appearance. With built-in analytics and extensive SEO tools, optimizing content becomes even easier. Each website can be delivered in just 2 days!

If you’re looking for a new website and don’t want to wait for months, spend a fortune, or compromise on quality, check out our website at https://www.twelvebricks.com/en.

Dec 06 2023
Dec 06
Laura JohnsonLaura Johnson

Laura Johnson

Senior Engineer

Primarily a backend developer, Laura also loves adding new cross-disciplinary skills to her collection, such as working with themes and frontend frameworks.

December 6, 2023

If your organization is still using Drupal 7 or later, migrating to an up-to-date platform for your website has been looming like a weight on your shoulders. The move to Drupal 10 isn’t easy. It requires a migration of your site and a full redesign to take advantage of the new tools the latest version offers.

Not only do you need someone to write that migration, but you also need to secure the budget to undertake a project like this. As you wait for the right time to get started, the weight of the deadline to begin your migration to Drupal 10 has only grown heavier. After multiple extensions, the Drupal community has set January 5, 2025 as the final end-of-life date for Drupal 7.

What does that mean for your organization? On the one hand, you now have just over a year to start planning a migration before your site loses crucial support. But on the other hand, as many organizations like yours face a similar deadline, you can’t afford to wait much longer. The time to make the move to Drupal 10 is now.

Why you need to start planning for a Drupal 10 migration

If you’ve fallen behind in migrating your site from Drupal 7, you’re not alone. According to the Drupal community, more than 350,000 projects still use that version of the platform as of November 2023 — one-quarter of all Drupal sites.

As a result, you aren’t just facing a hard deadline to relaunch your new site as January 2025 grows closer. You’re also competing with a vast number of organizations just like yours who need to coordinate the same migration with a web development agency partner. Given that it takes an average of six months to complete the sales process to get started on a Drupal 7 migration, you’re already at risk of missing the deadline if you have not yet contacted an agency.

The longer you wait, the less likely you are to find a team with availability to work with you on a migration plan and website redesign before Drupal 7 reaches end-of-life. And, given the stakes involved, your organization can’t afford the risks of sticking on a platform without the vital benefits of ongoing support.

What your organization loses when Drupal 7 reaches end-of-life

Drupal 7 will reach its end of life 14 years after its initial release. If you’re still on the platform, your website will remain accessible after January 5, 2025. However, it will no longer receive feature updates, bug fixes, or security releases from the Drupal community.

This last detail is most critical to your organization. Any security issues discovered after January 2025 may be publicly disclosed, but Drupal will no longer provide any necessary updates. Prior to the announcement of this final extension for Drupal 7, your organization had the option of paying for extended support. But that is no longer the case.

When you work with the right agency partner, you can create a migration plan that will keep your website secure. Fortunately, your organization will be able to better manage ‌site security after the migration is complete. But that’s just one of the advantages made possible by getting your organization started with Drupal 10.

Drupal 10 offers dramatic advantages after migration

Trusting your site with the legacy code of Drupal 7 doesn’t just expose your organization to poor security. It prevents you from taking advantage of dramatic improvements for your site’s users and content editors.

Improved website speed and SEO performance

Fundamentally, your Drupal 10 website will run faster. Dynamic caching reduces page load times by invalidating only the content that has changed. Instead of needing to reload your entire page after a set amount of time, your cache can just reload the block with new information.

Drupal 10 also marks the end of Drupal 7’s jQuery. A large JavaScript library, jQuery was a powerful tool, but modern browsers perform many of the same functions. The up-to-date JavaScript used by Drupal 10 also decreases page load times.

Drupal 10 also supports new formats such as schema.org, Open Graph, and JSON-LD, which increase conversions from search engines. Plus, Drupal 10 supports advanced accessibility features that improve WCAG compliance and further improve SEO rankings.

Better site security and reduced maintenance costs

Drupal 10 improves your site security by including up-to-date protocols and dependencies such as PHP 8, Symfony 6, and CKEditor 5. As earlier versions of these dependencies reach end-of-life, they may be exposed to unpatched security vulnerabilities. Migrating to Drupal 10 avoids delays in getting critical security patches applied to your site.

One of Drupal’s major advantages as an open-source platform is the community’s Security Team, which delivers security advisories and provides guidance to contributed module maintainers on how to resolve potential vulnerabilities. Providing continued support from the community Security Team for all of your site’s contributed modules beyond the upgrade deadline is critical.

Improved content editing experience and efficiency

Drupal’s out-of-the-box CMS experience has always been limited. With Drupal 10, your site editors benefit from the Claro theme, which makes Drupal much easier to use. New image tools and an updated media library also enable better organization of your site’s assets.

Drupal 10 also includes the JavaScript text editor CKEditor 5, which further simplifies content creation and its accessibility. In addition, the platform offers enhanced translation capabilities in multiple languages, which enables your organization to reach a wider audience than ever.

Don’t wait until an emergency before moving to Drupal 10

Making the web a better place to teach, learn, and advocate starts here...

When you subscribe to our newsletter!

Dec 06 2023
Dec 06

Meeting customer demands for personalized user experiences is paramount for businesses. To achieve this, adopting a mobile-first approach and ensuring swift responses becomes mission-critical. 

With users increasingly seeking engaging and interactive elements in their web experiences, organizations must choose a content management system (CMS) that aligns seamlessly with their requirements.  

Drupal is a preferred CMS for web application development, known for its open-source nature and high-security standards.

The Significance of Drupal in Web Application Development:

Drupal, an open-source and highly secure CMS, is pivotal in crafting robust web applications. Renowned brands like Johnson & Johnson, General Electric, and eBay trust Drupal development to deliver exceptional digital experiences. But why is Drupal the go-to choice for industry leaders in web application development? Let's delve into the reasons:

Unveiling the Power of Drupal: Why should you consider using Drupal to build a web application?

Drupal to build a web application

When organizations embark on web application development, they seek a content management platform that is secure, reliable, and flexible. As an open-source CMS platform, Drupal is the trusted choice for small-to-large organizations globally, including Nasdaq, Cisco, and Tesla. Its reputation for innovation and scalability is supported by community-driven code and a robust architecture tailored to meet enterprise requirements. 

Here's a closer look at why Drupal is an ideal choice for web application development in 2024:

Flexibility at its Core

Drupal provides a robust framework for creating precise web applications. With its core components and APIs, Drupal seamlessly accommodates complex web applications. The key lies in utilizing suitable modules to attain specific functionalities. Here are some crucial modules that help build a web application:

Module Name

Functionality 

CONFIG SPLIT 

This module plays a crucial role in defining the configuration sets to separate directories at the time of export and merge them at the time of import.

DIFF

Every time a new draft is created, this module offers a tab that reflects all the revisions and makes it easy to view all the words that have been added, modified, and deleted.

PATHAUTO 

This module enables you to define specific patterns for content the moment it gets created. For example, in the case of a blog content type, this module automatically lets you add /blog in the URL.

ADMIN TOOLBAR

By elevating the administrative user experience, this module offers a drop-down menu that makes it easy to access the different administration pages.

REDIRECT 

This module streamlines the redirect handling process in Drupal by automatically building a redirect for every content piece with an updated path.

Be it event registrations, insightful analytics, or interactive videos, Drupal has got you covered! You can also combine different modules according to your needs. Just like LEGO, Drupal empowers you with a myriad of blocks that allow you to build whatever you want.

Why is Drupal Flexible?

Drupal's flexibility extends beyond its core functionalities, with over 48,000 community-contributed modules. These modules offer plug-and-play functionality and help tailor web applications based on the specifications provided. From gaining insights through Google Analytics to ensuring GDPR compliance for cookies, Drupal has a dedicated module for every need.

Customer-centric user experience

Drupal simplifies the delivery of customer-focused experiences. 

It empowers organizations to create web applications that meet visitor needs, aiding informed decision-making. The user interface is meticulously crafted with the customer in mind, ensuring personalized and customer-centric interactions. The goal is to deliver a unique experience that fulfills users' specific needs throughout their journey.

Drupal web application development offers unparalleled flexibility, security, and a customer-centric approach, making it the ideal choice for organizations aiming to elevate their digital experiences.

Customer-centric user experience

A Success Story: Accelerating Web Development with Drupal

Empowering a Fortune 200 Life Sciences Company

A Fortune 200 Life Sciences Company engaged in groundbreaking R&D activities faced the challenge of creating a powerful multisite platform with an impeccable user interface. 

With over 400 companies under its umbrella, geographical diversity posed a unique challenge, necessitating a seamless web solution. After encountering challenges with a previous vendor regarding the technical intricacies of Drupal and Site Factory, the life sciences company turned to Axelerant for a solution. 

Their objectives included reducing launch times, achieving cost savings, and enhancing collaboration. Read how Axelerant leveraged the power of the Site Factory and Site Studio to develop a robust tool for delivering consistent brand experiences as well as personalized user journeys to their visitors.

How does Drupal deliver a customer-centric user experience?


Drupal is enriched with various built-in modules that make it an ideal CMS for the following factors:

Personalization Modules – Drupal boasts built-in modules, including Personalization Module, Commerce Recommender, and Browsing History Recommender, enabling tailored user experiences. 

Despite a preference for JavaScript-based solutions, Drupal's open-source options shine. Read how Axelerant seamlessly integrated Site Studio and the Context module to drive personalization in Drupal applications, offering a comprehensive solution.

Caching Optimization –  Drupal's Internal Page Caching and Internal Dynamic Page Cache modules elevate speed and page load efficiency. Anonymous visitors experience faster loading times as pages are cached intelligently, ensuring a smoother user experience.

Built-in block system – Drupal's block system allows effortless rearrangement of a web application's layout by dragging and dropping required blocks. This helps ensure design uniformity and content reuse across multiple pages.

WYSIWYG editor – The WYSIWYG editor in Drupal empowers users to preview text and images before publishing, enhancing the content creation process.

Responsive design – Drupal's responsive design ensures a seamless experience across devices, whether accessed through smartphones, desktops, or tablets.

Social media modules – Drupal simplifies social media connectivity with various integration modules, facilitating easy linking to platforms like Twitter and Facebook directly from the web application.

API-Friendly Architecture

Drupal's API-first initiative facilitates content creation and management, catering to various front-end applications. Its API-friendly architecture, including RESTful Web Services API, JSON: API, Render API, and Translation API, ensures a streamlined and enriched customer journey. 

What makes Drupal API-friendly?

Every modern CMS should ideally have access to robust APIs and integrate them across every stage of the customer journey for a better experience. The latest releases of Drupal have announced various APIs like:


RESTful Web Services API - It is known for supporting a decoupled Drupal website. It is responsible for the communication between native mobile applications and a specific Drupal website. Also, it takes care of web services integration.

JSON:API - This module has simplified integrations by allowing serialization and communication through JSON. 

Render API - It includes improved caching protocols and ensures faster page rendering to elevate the overall user experience. 

Translation API - This Drupal API plays a vital role in adjusting the language on the web application according to prospect/visitor geographies.

SEO Optimization

Since Drupal 7, the platform has excelled in SEO optimization. Leveraging semantic structure and RDF support, Drupal optimizes content for search engines, utilizing alt and title tags effectively. A Forrester report acknowledges Drupal's semantic approach as crucial for content managers and authors, further solidifying its position as a leading CMS for SEO.

An Interesting Story: Transforming Legacy.com's Editorial Section with Drupal

Empowering the editorial segment of Legacy.com's global online obituaries platform has been a remarkable journey. With partnerships spanning 1,500+ newspapers and 3,500 funeral homes, Legacy.com serves 40 million unique visitors monthly. Facing the challenge of launching Drupal CMS within six months, Read how Axelerant collaborated with Facet Interactive to deliver an enterprise-level headless Drupal platform, optimizing media organization, content publishing speed, and comprehensive support for newspapers.

Drupal's Search Engine Friendliness Unveiled:

SEO Modules:

Drupal's SEO prowess extends through modules such as SEO checklist, XML Sitemap, Global Redirect, SEO compliance checker, and Pathauto. The Metatag module stands out, enabling the effortless addition of meta tags to enhance SEO. Notably, it allows control over content appearance on social media channels, adding an extra layer of SEO value.

Intuitive Taxonomy System:

Drupal's flexible taxonomy system simplifies content organization using keywords. This not only aids in organizing information thematically but also ensures user-friendly navigation through search terms.

Unmatched Security


Being an open-source CMS platform, Drupal's codebase is always closely scrutinized. As the code powered by Drupal is freely available to everyone for review, use, modification, as well as contribution, it's imperative to ensure that the code is top-notch. Since there are millions of people contributing to this code, it is invigilated by a large number of eyes worldwide which leads to optimum security.

In the case of using a closed source CMS platform, there is no clarity about the potential security flaws present within the software. Due to its ability to handle extremely crucial data, Drupal has been a preferred CMS for web applications that are created for government, leading financial institutions, and popular e-commerce brands.

Why is Drupal so secure?

To give your web application a high level of security, Drupal comes with a list of various security modules that include: 

Drupal Login Security – This module enables the administrator to impose several restrictions on user login. Before blocking an account, it can restrict the total number of invalid login attempts made. Also, it allows administrators to deny access to specific IP addresses. 

Two-factor Authentication – Leveraging this security module by Drupal, you can get a dual-layer of authentication after a user logs in to your application with a specific user ID and password. This involves entering a code received on their email or mobile phone. 

Password Policy – This module by Drupal plays a key role in making your login forms more secure, thereby keeping bots and possible security breaches at bay. It enforces various restrictions at the time of password setting, such as constraints on the character type, length, case included (uppercase/lowercase), etc. Also, it comes with a password expiration feature that asks users to regularly change their passwords.

Content Access – This module allows you to gain access control to the content in a detailed manner. By specifying the content types with a view, edit, or delete permissions, you can manage them on the basis of role and author.

Coder – If there are any loopholes in your code, an attacker is likely to steal your information. By using the Coder module, your Drupal code is properly scrutinized to uncover the areas where the coding practices aren't followed properly.

Read more about the different security modules powered by Drupal.

Uncovering the flow of Drupal

Before considering Drupal to build your web application, it's essential to take a sneak peek into the system's layers between which the information is passed. Drupal comes with a set of 5 layers, namely: 
 

Uncovering the flow of Drupal

Data (Nodes, etc.)

At the foundation lies the data layer, encompassing all nodes and serving as the data pool where information is input before display.

Modules

The second layer in the Drupal flow is modules. Modules can be defined as the functional plugins that are built on the core functionality of Drupal. They can either be a component of the Drupal core (when shipped with Drupal) or contributed items that are specially developed by Drupal community members. These modules enable you to seamlessly customize the data items on your specific node types, create e-commerce from scratch, sort and display the required content programmatically, and more. There is a myriad of options in the continuously evolving repository of the Drupal modules that indicate innovation as well as the collaborative effort of the community members. 

Blocks & Menus

The next layer in the Drupal flow comprises blocks and menus. Blocks are used for gaining output from a particular module and are mainly created to represent anything you want. These blocks can be placed in several spots (called regions) inside the template layout that you use and can be effortlessly configured to output in different ways. Menus are used for navigation in Drupal that help to define the content that comes in each respective menu path (i.e. the relative URL). These play a crucial role in providing links to all the different pages that have been created in Drupal. 

User Permissions 

In this stage, the settings are configured to control what different users can see and are allowed to do. These permissions are usually defined on the basis of specific roles due to which different users are granted different defined permissions. 

Template 

This layer involves the site theme and is mainly made up of XHTML and CSS. Due to the intermixing of the Twig variables, the content generated in Drupal can easily go into the relevant spots. With each theme, there's a defined set of functions used for overriding the standard ones in the listed modules. This helps to gain full control over how different modules create their markups during the output.  

Conclusion

Drupal's search engine friendliness, unmatched security features, and layered flow make it an unparalleled choice for web application development. As the digital soul of the brand, your web application deserves the modular excellence and robust configurations that Drupal offers. If you are seeking exceptional web applications, connect with Axelerant's experts to learn more.

Dec 05 2023
Dec 05

Introduction

The restaurant industry has been going through a rough patch, with the economy taking a nosedive. Recovery from this issue is possible through business agility, and 71% of food businesses accept digital transformation as a crucial factor in accomplishing that goal.

However, as more restaurants join the online space, the need for a Restaurant CMS becomes more prominent. The right Restaurant website CMS, like Drupal, can help businesses streamline their function.

Why Drupal Is The Right Restaurant CMS

With over 20 years of experience in restaurant content management, Drupal has been the go-to site for several large companies and restaurants. Around 89.2% of the users believe Drupal will gain more popularity among enterprises in the next three years, irrespective of industry. This means the need for a Sports CMS and an Education CMS will also increase. 

The Content Management System industry has been exploding. But Drupal stays on top of the ladder for many reasons, including its efficient and highly responsive websites, fluid designs with high resolutions, and easy adaptability. 

According to statistics,

Drupal Usage in world

Drupal explores the restaurant content management system with the market's future needs. Restaurants prosper when connected with this platform as it allows them to communicate with regular customers, give additional coupons on reservations, help with email newsletters, and enable digital menu management.

These key features are essential in today's market to stay connected with customers, build a loyal community, and further expand online visibility. Investing in Drupal development is pivotal in adding brand value to restaurants. 

Drupal is also easily accessible to all users. The Point of Sale (POS) system is designed to add value and brand name to the restaurant craftily. It is also highly functional on MAC OS, Windows, and Linux.

Restaurant POS

Drupal provides an integral POS system that can help a restaurant enhance its brand value and visibility by allowing it to:

  • Track Order: With Drupal's efficient means, tracking orders and increasing the restaurant's efficiency is easier.
  • Manage Menus: In a digital world, menu management for restaurants has become easier due to the shift to digital menus. With Drupal’s effective CMS, one can easily manage menus regularly. 
  • Easy Bill Payment: No customer has the time and energy to wait to pay the bill. Drupal allows restaurants to have a digital, easy, and fast mode of bill payment. This builds trust and gives scope for growth.
  • Multi-Device Accessibility: Drupal connects restaurants with all customers through an efficient website and features.

A Short Tale: Deploying A Decoupled Drupal Platform For A Luxury Resort Chain

A popular chain of luxury resorts known for providing entertainment and casino facilities in Asia and Europe needed a unified content management platform. The organization wanted to ensure consistency across all brands.

With a plethora of content scattered across different websites, they faced issues related to disparate web tools, inconsistent branding, and poor user experience. Read how Axelerant helped them simplify content management, deliver modern user experiences, and expand their market presence with a decoupled Drupal platform.

Factors That Make Drupal An Ideal Restaurant CMS

Drupal has many factors that make it the best CMS for a restaurant website, including:

Security Management

In restaurant websites, customer data is safely secured on the local cloud to ensure customers' data safety and privacy. Tight control is kept on bill payments, card information, and personal details. This builds trust with customers. 

Multi-Lingual Support

Restaurants are places for people to blend in, experience the local culture, and feel connected. Through the multi-lingual support of Drupal, the experience for every part of the restaurant becomes simple, connective, and efficient.

Analytics

Deep analytics lets one quickly understand the customers' and markets' needs. It gives the user an understanding of the competition, insight into what is working for its customers, and how the business can strive to grow further.

Personalization

One can quickly create an interactive website with various themes and widgets that allow personalization. This feature helps the website get in sync with the overall feel of the dining experience. This goes a long way in providing customers with a memorable experience and building brand value.

Testing And Experimentation

Drupal CMS is intuitive and has endless creative possibilities due to automated testing and experimentation options. The CMS helps build a platform quickly, check, test, and ensure the website's proper functioning.

Scalability

Drupal has been the leading content management system for over two decades. Every eatery's needs are met with scalable websites hosted by Drupal, from exclusive restaurants to high-class bars to up-and-coming cafes.

While alternatives specializing in CMS are available, the general Drupal CMS helps build a site that suits the market and customer needs effortlessly without incurring the high expenses of a developer.

Exceptional Services

With a wide array of options in Drupal, businesses can create a website that becomes the go-to place for their creative presence online. Drupal makes it easy to introduce features, like QR ordering, online ordering, and recipe management, that help deliver exceptional customer service.

Essential Features To Include In A Restaurant CMS

Every Restaurant CMS should have a few essential features like:

Mobile-Friendly Design

Almost everyone is a smartphone user and finds it the most convenient means to look for information. This is why restaurants need to have mobile-friendly sites. Accelerated mobile pages help attract the target audience and keep them hooked to the site with its high usability and convenience.

Accurate Information

A highly efficient website should be based on authenticity and cultural value. This builds a sense of customer reliance and allows them to explore a site further with trust. It also allows customers to connect with the core values of an organization.

Page Speed Insights

With page speed insights, one can understand how long it takes for a page to load, how it works overall, and what its efficiency is . This, in turn, provides insight into how user-friendly the website is. With Drupal, this is a crucial feature that allows restaurants to keep their customer base intact.

Email Retargeting

First-time customers are a great source of business if converted to regular customers. Email retargeting allows a restaurant to reconnect with first-time customers and keep them hooked to the brand. This is a great way to expand the business and create a name in the local and overall niche market.

Drupal Features

The Conclusion

Drupal has been and aims to stay a go-to CMS and help restaurants get the online presence they need to become the next big thing. From one-time customers to regular ones, conversion is a small step.

Shifting to Drupal CMS can allow a restaurant's staff to connect effectively and work while maintaining strong relationships with customers. Opt for a Drupal development service if you want to make this happen.

The experts at Axelerant excel at elevating the online presence of restaurants. Get in touch to know how they can help you.

Dec 05 2023
Dec 05

 

Selecting the right Education Content Management System (CMS) for your institution is a critical decision in the current digital landscape.

As universities adapt to the "new normal" of a digital-first era, the impact of COVID-19 on higher education has been unprecedented, leading to uncertainties about the upcoming semesters globally.

In response to this digital paradigm shift, leaders in higher education are proactively preparing for the future.

“Not everybody's going to survive, for people who are forward-thinking, fast-pivoting, opportunistic visionaries, this is a really exciting time to be in higher ed. But it's also quite scary because 20% of colleges aren't going to make it.”

- Dr. Nancy Hubbard, University of Lynchburg

The central question arises: What is the most crucial aspect to get right in this digital evolution? The answer is your website. Serving as the online identity of your institution, the website is the platform that attracts, engages, and delights prospective students, shaping their seamless experience.

Outdated and lackluster education websites have a direct impact on the reputation of universities among prospective students, parents, and potential hires. This, in turn, affects enrollment rates, potentially leading to a significant loss of revenue, estimated at up to $19 billion collectively.

Education Content Management System

Achieving a seamless online experience hinges on the choice of an integrable Education Content Management System (CMS). In higher education, these CMSs go beyond supporting digital marketing or communications; they serve as central tools for staff, faculty, students, and others. Modern login portals must surpass the functionality of past systems like "angel systems" or "blackboard systems."

Choosing the right Education Content Management System can be daunting, given the myriad tasks prospects want to perform.

NYC Design research on 3,360 higher ed websites, including prestigious institutions like Stanford University, University of British Columbia, UC Berkeley, University of Michigan, and Cornell University, reveals that 90% of these sites are built using twelve education Content Management Systems.

Top education content management systems used by education institutes

Among these CMSs, WordPress and Drupal emerge as leaders, capturing 40.8% and 19.1% shares, respectively, as of 2023. The choice between WordPress and Drupal depends on the institution's type and specific needs. While WordPress is popular for its user-friendly interface and affordability, Drupal dominates among larger, more premium universities with complex requirements.

For instance, the top five US universities, including Harvard University, Stanford University, Massachusetts Institute of Technology (MIT), University of California, Berkeley (UCB), and the University of California, Los Angeles, have chosen Drupal development services. This preference reinforces Drupal's status as the preferred CMS for larger institutions seeking a robust platform to meet their diverse and complex needs.

Princeton, Stanford, University of East London, University of Dundee — the world’s top schools build better .edu sites and create great digital experiences with Drupal. Today, Drupal is the most widely used content management system in higher education, with 71% of the top 100 universities relying on the open source content management system to drive their digital strategy.

Should I Choose an Open Source Education Content Management System in 2024?

Do you wish to control the destiny of your website and deliver an orchestrated experience to your students and faculty? As quoted by Linus Torvalds:

“In real Open Source, you have the right to control your own destiny.”

Controlling the destiny of your institution's website and delivering an orchestrated experience to students and faculty is a crucial decision, echoing the words of Linus Torvalds: "In real open source, you have the right to control your own destiny." With approximately 60% of university websites built on an open-source CMS, opting for this approach becomes the optimal choice for higher-ed tech leaders in shaping their university or college platform.

An open-source education content management system seamlessly ties together services, data, and content across any tech stack. This integration facilitates the delivery of orchestrated digital experiences across channels in an agile manner, leading to a transformation that enhances the flexibility and resilience of institutions in the long term.

What Is An Open Source Education Content Management System?

An open-source website employs open-source software (OSS), allowing anyone to inspect, modify, and enhance the source code. Notably, Drupal stands out as one of the most popular open-source content management systems for higher education institutions. Marketers, developers, and CxOs in higher education can effortlessly connect with open-source community groups globally, engaging in platforms such as Drupal camps and conferences, where IT professionals from academic institutions actively contribute.

How Anne Margulies contribute to Drupal community

Reasons To Use An Open Source Education CMS

In a fast-paced digital landscape marked by technological growth and transformation, the success of a digital strategy hinges on agility, scalability, accountability, and flexibility. While proprietary technology has its benefits, the guarded secrecy of its source code contrasts with the transparency essential in education. Open-source CMS emerges as a superior option for university websites.

Key reasons for universities to embrace Open Source Education Content Management Systems include:

  • Large Developer Communities: Tap into collaborative knowledge and support from a vast community.
  • Widely Adopted: Open Source CMSs like Drupal are widely embraced in the education sector.
  • Ease in Academic Publishing: Facilitate seamless academic content publication and management.
  • High Security: Prioritize security, a critical aspect for educational institutions.
  • Affordability: Open Source solutions offer cost-effective alternatives to proprietary technologies.
There are a considerable number of Open Source education Content Management Systems available in the market. Which one should you choose, and what factors should you consider while selecting the right higher ed Content Management System? 

This CMS Evaluation Framework may help you to make an informed decision.

Reasons To Choose Drupal as Your Higher Ed Content Management System

Amid the impact of COVID-19 on students' willingness to enroll, it is imperative to revolutionize the student experience, affirm their decisions, and provide a personalized engagement journey from enrollment to graduation. Campus activities, including tours, living arrangements, and dorm life, remain uncertain, leading to a significant decline in campus visit requests.

Students unwillingness to join back colleges as per report

We have to keep in mind that campus tours, campus living, dorm life, etc. are still uncertain in the coming semester.

36% of institutions are experiencing a steep decline in campus visit requests and the number of campus tours are further expected to drop

Just like businesses offering work-from-home or flexible arrangements, it’s likely institutions of higher ed will have to do this in light of any negative C19 developments (of which, delta is one.) 

What does this mean? 

The campus tour is less powerful than it used to be—it’s more complicated and subject to devaluation. What isn’t for sure is the importance of your digital platform, the site that represents the institution. The portal. The entire UX. What about this tour or walkthrough? It just became more important. Perhaps even more important than the “campus tour.”

In this era where students may interact more with.edu than their physical classrooms, the importance of the institution's website has surged. A powerful and flexible open-source education content management system is the answer, with Drupal standing out as the choice for 70% of the top higher education institutions' websites.

Drupal's adaptability and flexibility position it as a reliable CMS option, aligning seamlessly with the evolving needs of higher education in 2023.

1.  Marketing

To overview how a student's journey on a university website begins when they search to enroll in an online program:

  • They start online research for their desired program
  • They shortlist a few university websites 
  • They complete filling the first application form

2/3rd of students often complete step (c) within just four weeks of starting online research. It implies that universities have four weeks to move at par with the prospective student's decision-making speed and offer them a personalized experience to engage.

And here, marketing offerings, at speed and scale, in a personalized manner play an important role. Thus, big higher ed players spend on marketing heavily to increase the online enrollment rate, which is evident from the following analysis.

The CMO Survey conducted by Duke University highlights that amongst the 15 industry sectors, the use of Digital Marketing, with a mean degree of 6.20, has contributed the most in the education sector. Unsurprisingly, the more a university spends on marketing initiatives, the higher is the enrollment rate.

A report mentioning higher spending on marketing leads to higher enrolment

The Digital Marketing ecosystem includes all the digital channels an institution uses to communicate with its prospective students, nurture them till they are ready to enroll, convert them and promote new enrolments from actual students. 

To make their Digital Marketing initiatives a success, higher ed marketers do need data about geolocation, prospect demographics, and previous history. However, they also need data about what their prospective students are doing and looking for right now.

Unfortunately, in most institutions, this type of data resides in silos and the technology that could orchestrate this prospect journey is not connected effectively to the martech stack.

“There is a lot of data out there, the key is to learn how to leverage that data to provide the best experience for your prospective audience.”

- Luci Geraci, Executive Director, Web & Digital Marketing, Council on International Educational Exchange

Drupal is a powerful answer to this challenge. Being an Open Source education content management system, Drupal is easily integrable with existing systems using open APIs that allow student data to flow seamlessly throughout the institution. Also, Drupal-based digital properties allow higher ed marketers to collect key prospect data that help them to design a comprehensive view of prospects.

Drupal education content management system integrations

With Drupal marketing, higher ed markets can:

  • Get a 360-degree view of prospects
  • Streamline campaign management
  • Connect systems and data sources with open APIs
  • Build memorable experiences across platforms with multi-channel reach
  • Deliver global experiences in any language, with multilingual functionality

As a robust Higher Ed Content Management solution, Drupal is behind many of the digital experiences that are part of the marketing efforts of leading universities. There are several Drupal marketing modules created by the Drupal community that can be used with Drupal sites to help with marketing initiatives. Some of the top Drupal Digital Marketing modules are as follows:

  • Webform: It is an essential building block that integrates Drupal and other third-party marketing automation software.

    MicroAssist Inc., an Austin-based technology training and consulting company, relaunched its website on Drupal and added a Webform module to their website. Their marketing time was super-excited with the ability of Webform to quickly and effectively build different types of forms.
     

  • Mautic: Mautic allows for multi-channel communications and campaign management, content customization, email marketing, visitor tracking, and personalization, to name a few. 

    Mautic is an Open Source marketing automation platform acquired by Acquia. The community version of Mautic lacked the ready-to-use plugins required by the Acquia marketing team. They collaborated with Axelerant to develop Mautic plugins to extend functionality.

A case study on how Axelerant helped Acquia to migrate from Marketo to Mautic

  • Google Analytics: The Google Analytics Drupal module allows marketers to add a web statistics tracking system to the website. It helps track and monitor search systems, Adsense, custom code snippets, and much more.
     
  • HubSpot: The HubSpot Drupal module integrates seamlessly with Webform and the HubSpot API. The entered information of a user on Drupal Webform is sent to HubSpot’s lead management tool, which is further tracked and nurtured.
     
  • Search API Solr: The Search API Solr Drupal module helps configure Solr search on a Drupal website. It works in collaboration with the Search API module, for which it provides an Apache Solr backend.

2. Accessibility

Education should be available to all, irrespective of caste, creed, gender, or native language. But the challenge is how to make it fully accessible - 71% of users with a disability leave a website that is not accessible to them.

“So if a college education is indispensable, the challenge as I see it is how to make it more accessible.”

- Gordon Gee

In 2016, a visually impaired student's lawsuit against Miami University underscored the critical importance of web accessibility. The university, faced with inaccessible websites, had to pay $25,000 in compensation and overhaul its web content to meet federal accessibility standards. Such incidents highlight the ethical responsibility of universities to ensure equal access to educational resources.

The exploration of ethical practices by students during the university selection process reveals a growing awareness of accessibility issues. Lawsuits against universities failing in this regard indicate a need for institutions to prioritize ethical responsibilities.

However, auditing websites for accessibility is a complex and resource-intensive task. Scott Lissner, Americans With Disabilities Act compliance officer at Ohio State University, humorously captures the enormity of the challenge, stating that the number of webpages at Ohio State is "somewhere between five and eight million, depending on what definition you're using and what moment in time it is."

Enter Drupal, a comprehensive education content management system (CMS) designed to meet the demands of the digital ecosystem while adhering to accessibility standards. Drupal's commitment to accessibility is evident through features designed to enhance user experiences for people with disabilities:

  • Accessible inline forms:
    • Challenge: Visually impaired users may struggle with error notifications on form submissions.
    • Solution: Drupal introduced the Inline Form Errors module, aiding visually impaired users in identifying errors by providing alternative formats for error notifications. 
  • Anyone can easily add alt text and tags: 
    • Challenge: Embedded images on websites may lack accessibility for visually impaired audiences.
    • Solution: Drupal mandates alt text (alternative text) for images, ensuring that meaningful descriptions accompany images, making them accessible to all users.
How Drupal CMS takes care of the accessbility by mkaing sure you add an alt text to the image
  • Seamless management of tabs: 
    • Challenge: Non-visual and non-mouse users face difficulties navigating webpage elements.
    • Solution: Drupal's TabbingManager, a JavaScript feature, allows logical access to critical page elements. Higher ed marketers can precisely control the tabbing sequence, enhancing the experience for users who rely on keyboard navigation.
The education content management system of University of Arizona helps them build website with accessbility features.jpg

In essence, Drupal not only acknowledges the significance of accessibility but actively integrates features that contribute to a more inclusive digital environment. By choosing Drupal as the CMS, education institutions align themselves with a commitment to providing accessible and equitable educational opportunities for all 

3. Personalization

In the digital age, students expect a personalized online experience akin to that of industry giants like Amazon and Netflix—seamless, orchestrated, relevant, and immediate. Recognizing this, 70% of higher education leaders consider digital engagement and websites as paramount marketing tools to attract, engage, and convert prospective students.

A student-centric engagement platform strives to deliver a highly personalized experience both before and after enrollment. For instance, Arizona State University (ASU) and Southern New Hampshire University (SNHU) leverage predictive analytics to identify academically struggling students and provide timely interventions. These personalized experiences foster lasting engagement among modern learners.

Drupal has adeptly seized this opportunity to provide 360-degree digital experiences that are not only relevant and immediate but also personalized. Leveraging browser history, geolocation, behavior taxonomies, and device type, Drupal enables users to capture user interactions at every touchpoint. This information allows for the curation of a comprehensive vision of personalized offerings, ultimately enhancing retention and online enrollment rates.

An exemplary instance is the Stanford University website, which harnesses Drupal to empower campus members to effortlessly design, build, grow, and manage brand-standard sites. These sites are tailored to individual needs, all while prioritizing security.

4. Security 

Security is a paramount concern for higher education decision-makers, given the frequent targeting of college websites. In this context, the choice of a digital platform becomes crucial. Open source software (OSS) is generally considered more secure than proprietary software, and Drupal stands out in this regard.

Ghaphic

Drupal's security team is highly responsive and proactive, consistently monitoring vulnerabilities and issuing frequent security fixes for all actively maintained versions. The team follows a "coordinated disclosure policy," maintaining issue privacy until a fix is published. The Drupal Security Team's thoughtful communication ensures that users are well-informed and can take necessary actions.

“The Drupal Security Team follows a "coordinated disclosure policy": issues remain private until there is a published fix. A public announcement is made when the threat has been addressed and a secure version of Drupal core is also available. Even when a bug fix is made available, the Drupal Security Team is very thoughtful with its communication.”

- Dries Buytaert, Drupal Founder

Over the years, Drupal has demonstrated its resilience against critical security vulnerabilities, including those identified by OWASP in their top 10 security risks. The decrease in vulnerabilities from 2002 to 2021 attests to Drupal's commitment to providing a robust and secure education content management system. Frequent minor releases, especially after the introduction of Drupal 8 and 9, underscore Drupal's dedication to continuous innovation and the delivery of secure updates.

 Number of vulnerabilities on Drupal year-by-year

5. Multisite Architecture

In the landscape of higher education, where institutions manage diverse websites serving various functions, Drupal emerges as a robust solution for content management systems (CMS). The built-in multisite functionality of Drupal provides universities and colleges with a versatile toolbox, empowering individual departments to communicate effectively with students, staff, and users through a unified system. This multisite capability not only allows for independence in managing websites but also significantly reduces administrative overhead for IT offices.

When to Multisite and When Not

Sr. No. Factor Consider Multisite Don't Consider Multisite 1 The sites are for the same client and it is to simplify the scope of each site ✓   2 The sites are similar in functionality, use the same modules or use the same Drupal distribution ✓   3 You have limited resources/staffing, but lots of sites to manage and maintain ✓   4 The functionality or scope are different   ✓ 5 You are managing multiple distributions   ✓ 6 The clients are different   ✓

An illustrative example of Drupal's multisite prowess is evident in OpenScholar, a research collaboration platform. OpenScholar's multi-tenant, robust, and modular architecture enables academic institutions to host thousands of websites within a single instance.

Axelerant's collaboration with OpenScholar involved implementing test automation systematically across the entire testing pyramid, contributing to quicker releases. Leveraging Organic Groups and Spaces modules, OpenScholar enables a single Drupal installation to host complete and discrete websites.

The platform supports custom domains, facilitating the seamless transfer of existing websites to OpenScholar, including the management of custom URLs within the site settings area of the control panel.

 

A Quick Tale

Delivering Seamless Digital Experience by Building Scalable Multisites for a Leading Research University

Axelerant's expertise shines in a case study involving a renowned public research university established in the mid-19th century. Facing scalability challenges with multiple websites built on PHP, the university sought Axelerant's assistance.

Read the case study to know how Axelerant leveraged its experience to help the client deliver a world-class user experience, enabling them to build pages with ease.

Read the case study to know how Axelerant built scalable multisites for a leading research university

6. Community Support

The success of the Drupal CMS platform is underlined by its extensive and devoted community. Key Drupal facts highlight the platform's widespread adoption and user satisfaction:

Some key Drupal facts:

Over 1.7 million websites use Drupal
7% of the top 10k websites use Drupal
97% of Drupal users are satisfied with it
46,603 Drupal modules can be downloaded for free
In 2020, Drupal welcomed 8,000 individual contributors and over 1,200 corporate contributors

A pivotal aspect of Drupal's success is its vibrant and inclusive community. Drupalers, drawn to the platform for coding, find a community that becomes a family. Their commitment to strengthening and experience-driving the community is evident in their daily efforts.

“We believe that the Drupal project benefits from a diverse contribution pool, and we strive to foster a welcoming and inclusive culture everywhere Drupal exists — at events, online, and in our workplaces.”

- Drupal’s statement of values

Drupal is a common thread that weaves together Drupalers worldwide, without discrimination, with a single mission: the more you give, the more you get. And Drupalers happily volunteer their time in Drupal communities because it is fun, engaging, challenging, meaningful, and enjoyable.

At Axelerant, we eat, sleep, and breathe Drupal. We always look for different ways to give back to the Drupal community and prepare hard for Drupal events to leave a mark, learn from other Drupalers, implement the learnings, and come back prepared for the next engagement.

Axelerant Drupal community contributions

Even many university CIOs and academic I.T. leaders have evolved and opened up Higher Education by bringing Open Source projects to campus—at times subtly, sometimes hyperactively. This forwards the general mission of every university: to disseminate and expand knowledge and information across communities. Adopting Open Source software allows universities to put more into this mission.

Some community group examples are:

Drupal Group: Higher Education
Drupal Group: Drupal in Education
Drupal Group: Drupal for K-12 Schools
Drupal Group: Higher Education Europe
Drupal Group: Open Data in Education
Drupal Group: ELMS
Drupal Group: LMS (Learning Management System)
Stanford Web Services Blog
Edu Drupal Unconsortium

Wrap Your Website With An Experience-driven Higher Ed CMS Platform: Drupal

In conclusion, Drupal's prowess in multisite architecture, coupled with its vibrant community support, positions it as a premier choice for higher education institutions seeking an experience-driven CMS platform.

In times of crisis, Drupal remains a steadfast partner, guiding the education industry through digital paradigm shifts while upholding the core mission of empowering informed citizens. Drupal's living, breathing digital platform ensures not just engagement but a seamless experience, contributing to its prevalence in over 70% of the top universities as their chosen education content management system.

Nov 27 2023
Nov 27
Allan ChappellAllan Chappell

Allan Chappell

Senior Support Lead

Allan brings technological know-how and grounds it with some simple country living. His interests include DevOps, animal husbandry (raising rabbits and chickens), hiking, and automated testing.

November 27, 2023

At the time of this blog, we have done two major version upgrades of Drupal and have refined the process along the way. There has been a lot of work in the community, through the efforts of people like Matt Glaman to make this process easier.

As a Support Engineer, I see a lot of approaches for achieving the same results in many areas of my work. Here, I’d like to share with you three different ways to achieve an upgrade of a module or theme that isn’t ready for the next major Drupal version, each with pros and cons, but all absolutely acceptable.

Why do we have this problem?

All new Drupal developers have a hard time with the layers of code changes that happen in the Drupal community. We have custom package types, custom install locations, patches, and scaffolding. To make the challenges worse, we have two ways to identify a module’s dependencies — that being a .info.yml file and for some, a composer.json. This is because some Drupal modules may want to build upon an existing PHP library or project, in addition to other Drupal modules. To ease the pain of having to define some dependencies twice, both in the .info.yml file and composer.json file, Drupal.org built their packagist, a repository of Composer packages, to read the .info.yml files from the root of the project and create Composer version constraints from that. For example, if the .info file contained the following:

name: My Module
type: module
core_version_requirement: ^8.8 || ^9
dependencies:
  - ctools:ctools

Then Drupal.org’s packagist would create the following for the release that contained that .info.yml file, saving the contributed developer a lot of trouble.

{
    "type": "drupal-module",
    "name": "drupal/my_module",
    "require": {
      "drupal/core": "^8.8 || ^9",
      "drupal/ctools": "*"
    }
  }

I hit on something there, though. It will create that for the release the .info.yml was in. When most code changes come in the form of patches, this poses a challenge. You apply your patch to the .info.yml after you download the release from Drupal.org’s packagist. Additionally, Drupal.org doesn’t create a new release entry for every patch file in the issue queue. So you are left with the question, “How do I install a module on Drupal 10 that requires Drupal 9 so that I can patch it to make it compatible for Drupal 10?”

Drupal Lenient

One of the easiest methods for those who don’t understand the ins and outs of Composer is to use the Drupal Lenient plugin. It takes a lot of the manual work out of defining new packages and works with any drupal-* typed library. Types are introduced to us through the use of the Composer Installer plugin and manipulated further with something like Composer Installers Extender. Composer plugins can be quite powerful, but they ultimately add a layer of complexity to any project over using core composer tactics.

Drupal Lenient works by taking any defined package pulled in by any means via Composer, and replaces the version constraints for drupal/core currently, at the time of this writing, with “^8 || ^9 || ^10“. So where the requirements might look like the example earlier “drupal/core“: “^8.8 || ^9“, they are replaced, making it now possible to install alongside Drupal 10, even though it might not‌ be compatible yet. This allows you to patch, test, or use the module as is, much like if you would have downloaded the zip and thrown it into your custom modules directory.

An example may look like this:

{
  "name": "vendor/project",
  "repositories": [
    {
      "type": "composer",
      "url": "https://packages.drupal.org/8"
    }
  ],
  "require": {
    "drupal/core": "^10.0.0",
    "drupal/my_module": "1.x-dev",
    "cweagans/composer-patches": "^1.7.3",
    "mglaman/composer-drupal-lenient": "^1.0.3"
  }"
  extra": {
    "composer-exit-on-patch-failure": true,
    "drupal-lenient": {
      "allowed-list": [
        "drupal/my_module"
      ]
    },
    "patches": {
      "drupal/my_module": {
        "3289029: Automated Drupal 10 compatibility fixes": "https://www.drupal.org/files/issues/2022-06-16/my_module.1.x-dev.rector.patch"
      }
    },
    "patchLevel": {
      "drupal/core": "-p2"
    },
  }
}

Note the Drupal-Lenient allow list. Also note that you will need to make sure and install the plugin before trying to install the module that doesn’t support Drupal 10 in this case. If you want an excellent step-by-step, Matt put one together in the Readme.

The pros:

  • Easy-peasy to install
  • Feeds off the original packagist packages, so if there is an upgrade, you don’t have to do anything special to transition

The cons:

  • Lenient has the control and may cause inexplicable errors when updating due to unsupported core versions
  • PHP devs not familiar with Drupal Lenient won’t know to look for it
  • Flaky experiences when switching in and out of branches that include this plugin. If you context switch a lot, be prepared to handle some errors due to Composer’s challenges maintaining state between branches.
  • Patches to other dependencies inside composer.json still require you to run through some hoops

Custom package

If you want more control over what the module can and cannot do, while keeping the core of Composer functionality without adding yet another plugin, check out this method. What we will do here is find out what version the patch or merge request is being applied against. It should be stated in the issue queue and by best practices is a dev version.

If you are a perfectionist, you can use composer install -vvv to find the url or cache file that the module came from for packages.drupal.org. It is usually one of https://packages.drupal.org/files/packages/8/p2/drupal/my_module.json or https://packages.drupal.org/files/packages/8/p2/drupal/my_module~dev.json. You will note that the Composer cache system follows a very similar structure, swapping out certain characters with dashes.

With this information, you can grab the exact package as it’s defined in the Drupal packagist. Find the version you want, and then get it into your project’s composer.json.

Let’s use Context Active Trail as an example, because at the time of this writing, there is no Drupal 10 release available.

Drupal release information

Looking through the issue queue, we see Automated Drupal 10 compatibility fixes, which has a patch on it at. I grab the Composer package info and paste the 2.0-dev info into my composer.json under the “repositories” section as a type “package.”

Drupal packagesDrupal packages

Which should make your project look something like this:

{
  "name": "vendor/project",
  "repositories": [
    {
      "type": "package",
      "package": {
        "keywords": [
          "Drupal",
          "Context",
          "Active trail",
          "Breadcrumbs"
        ],
        "homepage": "https://www.drupal.org/project/context_active_trail",
        "version": "dev-2.x",
        "version_normalized": "dev-2.x",
        "license": "GPL-2.0+",
        "authors": [
          {
            "name": "Jigar Mehta (jigarius)",
            "homepage": "https://jigarius.com/",
            "role": "Maintainer"
          },
          {
            "name": "jigarius",
            "homepage": "https://www.drupal.org/user/2492730"
          },
          {
            "name": "vasi",
            "homepage": "https://www.drupal.org/user/390545"
          }
        ],
        "support": {
          "source": "https://git.drupalcode.org/project/context_active_trail",
          "issues": "https://www.drupal.org/project/issues/context_active_trail"
        },
        "source": {
          "type": "git",
          "url": "https://git.drupalcode.org/project/context_active_trail.git",
          "reference": "8dc46a4cf28e0569b187e88627a30161ee93384e"
        },
        "type": "drupal-module",
        "uid": "context_active_trail-3192784",
        "name": "drupal/context_active_trail",
        "extra": {
          "branch-alias": {
            "dev-2.x": "2.x-dev"
          },
          "drupal": {
            "version": "8.x-2.0-rc2+1-dev",
            "datestamp": "1630867980",
            "security-coverage": {
              "status": "not-covered",
              "message": "Project has not opted into security advisory coverage!"
            }
          }
        },
        "description": "Set the active trail based on context.",
        "require": {
          "drupal/context": "^4.1",
          "drupal/core": "^8.8 || ^9"
        }
      }
    },
    {
      "type": "composer",
      "url": "https://packages.drupal.org/8"
    }
  ],
  "require": {
    "drupal/core": "^10.0.0",
    "drupal/context_active_trail": "2.x-dev",
    "cweagans/composer-patches": "^1.7.3",
    "mglaman/composer-drupal-lenient": "^1.0.3"
  }"
  extra": {
    "composer-exit-on-patch-failure": true,
    },
    "patches": {
    },
    "patchLevel": {
      "drupal/core": "-p2"
    },
  }
}

Now let’s change our version criteria:

…
        "description": "Set the active trail based on context.",
        "require": {
          "drupal/context": "^4.1",
          "drupal/core": "^8.8 || ^9 || ^10"
        }
…

And then add our patch:

…
  extra": {
    "composer-exit-on-patch-failure": true,
    },
    "patches": {
      "drupal/context_active_trail": {
        "Automated Drupal 10 compatibility fixes": "https://www.drupal.org/files/issues/2023-09-29/context_d10comp_3286756.patch"
      }
    },
    "patchLevel": {
      "drupal/core": "-p2"
    },
  }
…

Here, you will need to look to see if the patch is patching composer.json. If it is, you will need to modify your package information accordingly. For example, in this one, the fixer changes drupal/context from ^4.1 to ^5.0.0-rc1. That change looks like this:

…
        "description": "Set the active trail based on context.",
        "require": {
          "drupal/context": "^5.0.0-rc1",
          "drupal/core": "^8.8 || ^9 || ^10"
        }
…

Lastly, sometimes you run into some complications with the order packages are picked up by Composer. You may need to add an exclude element to the Drupal packagist.

…
  {
      "type": "composer",
      "url": "https://packages.drupal.org/8",
      "exclude": [
          "drupal/context_active_trail"
      ]
  },
…

Our final composer.json for our project could look something like this with all the edits:

{
  "name": "vendor/project",
  "repositories": [
    {
      "type": "package",
      "package": {
        "keywords": [
          "Drupal",
          "Context",
          "Active trail",
          "Breadcrumbs"
        ],
        "homepage": "https://www.drupal.org/project/context_active_trail",
        "version": "dev-2.x",
        "version_normalized": "dev-2.x",
        "license": "GPL-2.0+",
        "authors": [
          {
            "name": "Jigar Mehta (jigarius)",
            "homepage": "https://jigarius.com/",
            "role": "Maintainer"
          },
          {
            "name": "jigarius",
            "homepage": "https://www.drupal.org/user/2492730"
          },
          {
            "name": "vasi",
            "homepage": "https://www.drupal.org/user/390545"
          }
        ],
        "support": {
          "source": "https://git.drupalcode.org/project/context_active_trail",
          "issues": "https://www.drupal.org/project/issues/context_active_trail"
        },
        "source": {
          "type": "git",
          "url": "https://git.drupalcode.org/project/context_active_trail.git",
          "reference": "8dc46a4cf28e0569b187e88627a30161ee93384e"
        },
        "type": "drupal-module",
        "uid": "context_active_trail-3192784",
        "name": "drupal/context_active_trail",
        "extra": {
          "branch-alias": {
            "dev-2.x": "2.x-dev"
          },
          "drupal": {
            "version": "8.x-2.0-rc2+1-dev",
            "datestamp": "1630867980",
            "security-coverage": {
              "status": "not-covered",
              "message": "Project has not opted into security advisory coverage!"
            }
          }
        },
        "description": "Set the active trail based on context.",
        "require": {
          "drupal/context": "^5.0.0-rc1",
          "drupal/core": "^8.8 || ^9 || ^10"
        }
      }
    },
    {
      "type": "composer",
      "url": "https://packages.drupal.org/8",
      "exclude": [
          "drupal/context_active_trail"
      ]
    }
  ],
  "require": {
    "drupal/core": "^10.0.0",
    "drupal/context_active_trail": "2.x-dev",
    "cweagans/composer-patches": "^1.7.3",
    "mglaman/composer-drupal-lenient": "^1.0.3"
  }"
  extra": {
    "composer-exit-on-patch-failure": true,
    },
    "patches": {
      "drupal/context_active_trail": {
        "Automated Drupal 10 compatibility fixes": "https://www.drupal.org/files/issues/2023-09-29/context_d10comp_3286756.patch"
      }
    },
    "patchLevel": {
      "drupal/core": "-p2"
    },
  }
}

The pros:

  • Uses more core Composer functionality
  • A PHP developer will better understand ‌what’s going on here
  • You are in complete control of how this module package and version are defined
  • All the work is in one file

The cons:

  • Requires some understanding of how composer.json, packagists, and the magic of Drupal’s packagist all work
  • That’s a messy composer.json for the project
  • If you have to use exclude, you have to leave it up to outside forces to let you know when that module does finally put out and actual D10-ready version, and then undo all of this work

Standard PHP composer best practice says that if you make modifications to a package, fork it, maintain your modifications, and provide a pull request if it’s functionality you wish to contribute back. You can use this same approach with Drupal modules as well. Some may even say that’s what issue forks are for! That said, issue forks come with the downside that sometimes they go away, or are overridden with changes you don’t want. They are a moving dot.

For the sake of this example, let’s assume that we have forked the module on GitHub to https://github.com/fourkitchens/context_active_trail.git. If you don’t know how to make a fork, simply do the following:

  • Clone the module to your local computer using the git instructions for the module in question
  • Check out the branch you want to base your changes on
  • Create a new repository on GitHub
  • Add it as a remote git remote add github [email protected]:fourkitchens/context_active_trail.git
  • Push it! git push github 8.x-2.x

You can do this with a version of the module that is in a merge request in Drupal.org’s issue queue, too. That way you won’t have to reapply all the changes. However, if your changes are in a patch file, consider adding them to the module at this time using your favorite patching method. Push all your changes to the github remote.

If the patch files don’t have changes to composer.json, or if the module doesn’t have one, you will likely want to provide at least a bare-bones one that contains something like the following and commit it:

{
  "name": "drupal/context_active_trail",
  "type": "drupal-module",
  "require": {
    "drupal/context": "^5.0.0-rc1",
    "drupal/core": "^8.8 || ^9 || ^10"
  }
}

This will tell Composer what it needs to know inside the project about dependencies. This project already had a composer.json, so I needed to add the changes from the patch to it.

Inside our Drupal project we are working on, we need to add a new entry to the repositories section. It will look something like this:

    {
      "type": "vcs",
      "url": "https://github.com/fourkitchens/context_active_trail.git"
    },

The VCS type repository entry tells Composer to look at the repository and poll for all its branches and tags. These will be your new version numbers.

Much like in the “Custom Package” example, you may need to add an exclude property to the Drupal packagist entry.

…
  {
      "type": "composer",
      "url": "https://packages.drupal.org/8",
      "exclude": [
          "drupal/context_active_trail"
      ]
  },
…

Now, since Drupal packagist isn’t here to give Composer some version aliases, we have to use the old notation dev-BRANCHNAME for our version. Our require entry will look something like this:

 "drupal/context_active_trail": "dev-8.x-2.x",

Since we already added our patches as a commit to the module, this is all you need. Your final composer.json for your project would look like this:

{
  "name": "vendor/project",
  "repositories": [
    {
      "type": "vcs",
      "url": "https://github.com/fourkitchens/context_active_trail.git"
    },
    {
      "type": "composer",
      "url": "https://packages.drupal.org/8",
      "exclude": [
          "drupal/context_active_trail"
      ]
    }
  ],
  "require": {
    "drupal/core": "^10.0.0",
    "drupal/context_active_trail": "dev-8.x-2.x",
  }
}

It makes for a much cleaner project json, but now you’ve split the work into two locations, requiring some synchronization. However, if multiple sites of yours use this same module and need the same fixes, this absolutely has the least resistance and ability to get those changes out more quickly.

The pros:

  • Reusability
  • Two smaller, simpler chunks of work
  • Any PHP developer should be able to debug this setup as it uses Composer best practices. This method will be used in any project with any framework in the PHP ecosystem.

The cons:

  • Changes are in two separate places
  • Which patches are applied isn’t obvious in the composer.json and require looking through the commit history on the forked repository
  • Requires maintenance and synchronization when upgrades happen

Final thoughts

As with almost everything out there, there are multiple ways to achieve the same goal. I hope this brings awareness, and helps provide the flexibility you need when upgrading Drupal to a new major version. Obviously, each solution has strengths, and you may need to mix it up to get the results you want.

Making the web a better place to teach, learn, and advocate starts here...

When you subscribe to our newsletter!

Nov 24 2023
Nov 24

As I embarked on a recent journey to enhance the usability of Drupal from the perspective of both site owners and editors, I stumbled upon what could be a game changer for content editors – the "Same Page Preview" module.

This module offers an innovative solution, providing a page preview seamlessly integrated into the editing process. Say goodbye to the hassle of toggling between the edit form and a separate preview window. With the "Same Page Preview" module, it's all about real-time content visualisation and efficiency.

cti Blog Banner

Key Features

Effortless Installation

Setting up the "Same Page Preview" module is a breeze, and it's a matter of a simple checkbox configuration against specific content types.

On-Page Canvas Preview

When adding or editing content, an on-page canvas preview elegantly unfolds. As you interact with the edit form fields, the preview updates in real time, offering an instant, dynamic view of your content.

Custom Display Options

Tailor your preview experience to your liking. Choose to open the display in a new window, view content in full-screen mode, or select your preferred display mode. The module is all about personalising your content editing workflow.

Custom Display Options

Why it matters 

Watch a Short Demo: https://youtu.be/Mh_plCpt1_A


The "Same Page Preview" module has recently received recognition on the Talking Drupal podcast, where its potential was discussed. Furthermore, there's an active issue in the Drupal core ideas project advocating for the inclusion of this module in the Drupal core.


In my opinion, integrating "Same Page Preview" into the Drupal core would be an invaluable asset. I've encountered numerous projects where the concept of in-page content previews has sparked considerable interest and discussion.


Join me in exploring the possibilities that this module brings to the Drupal community and in advocating for its inclusion in the Drupal core. Let's make content editing even more user-friendly and efficient.

Nov 20 2023
Nov 20

CKEditor is a powerful and versatile web-based text editor commonly used in content management systems like Drupal. It empowers content creators and editors with a user-friendly interface for crafting and formatting text, incorporating multimedia elements, and managing a wide range of content types.

CKEditor 5, the latest iteration, introduces a host of major enhancements and new features, from a fresh, modern design to advanced features that streamline content creation and will bring a leap forward in productivity. This new and exciting version of CKEditor comes as part of Drupal 10 out of the box, so provides a great benefit when upgrading your current Drupal site.

In this article, we'll delve into CKEditor 5's impressive capabilities, focusing on its revamped appearance, link management, image handling, table editing, font customisation, HTML embedding, and the exciting premium features it brings to the table. Let's jump in and explore the creative possibilities CKEditor 5 offers for enhancing your digital content.

Header image - Drupal 10 blog

Drag and Drop

CKEditor 5's drag-and-drop feature transforms the content editing experience, providing unparalleled convenience for editors. This functionality allows content editors to effortlessly rearrange text, paragraphs, tables, and lists within the editor, enhancing the fluidity of content composition. The ability to move entire blocks or multiple elements with a simple drag-and-drop action offers a significant time-saving advantage, streamlining the editing process. Moreover, content editors can seamlessly import HTML or plain-text content from external sources, simplifying the integration of images into their work. This feature not only improves efficiency but also empowers editors with greater control and flexibility in crafting visually appealing and well-organised content.

Links

One area that's seen noteworthy improvements is link management. Adding links in CKEditor 5 is now more intuitive and user-friendly, as a convenient pop-up box appears within the WYSIWYG window. This makes the process smoother and faster. These link options can be combined with Drupal’s 'Editor Advanced Link' module, which empowers content creators with the ability to fine-tune their links. With this module, editors can define attributes such as title, CSS classes, IDs, link targets, 'rel' attributes, and ARIA labels, which are essential for providing users who use assistive technology like screen readers meaningful information about the purpose or content of the link. 

These enhancements offer a wealth of customisation options for links, whether it's for accessibility, branding, or precise styling. CKEditor 5 and the 'Editor Advanced Link' module together bring a logical link management experience to Drupal, making the process more efficient and versatile.

Links

Image Handling

Adding images to your content using CKEditor 5 has been given an upgrade thanks to the new drag-and-drop functionality. Users can simply select an image, whether it's from their device or a webpage, and simply drag and drop it into the WYSIWYG window. Once the image is incorporated, you have the option to designate it as decorative (meaning it doesn't add information to the page and, therefore, does not require alt text) or provide the alt text.

Furthermore, you can fine-tune the image presentation by adjusting its alignment and text wrapping options, all conveniently accessible from the image-dedicated balloon toolbar. If you wish to enrich your image with a link or a caption, you can easily achieve this without leaving the image toolbar.

Drupal blog image 2

Links

When you're ready to adjust the image size, CKEditor 5 simplifies the process by allowing you to resize the image directly within the WYSIWYG window. A straightforward corner selection and drag operation lets you customise the image to your desired dimensions.

Moreover, CKEditor 5 integrates with Drupal media. Once the necessary modules are enabled, you'll discover a new button in the text editor toolbar configuration. Add this button to your active toolbar, specify the media types and view modes you want to make available and save your preferences. You can then conveniently add new media files or select from your media library, following the standard workflow you're accustomed to (you are restricted with resizing the image when using the library). CKEditor 5, along with its compatibility with Drupal media, enhances the image management experience, making it a user-friendly and efficient process.

Table Management

Enhancements to table management in CKEditor 5 bring an improved editor experience. While currently requiring a patch to be added to Composer, the effort is undoubtedly worthwhile for those who frequently utilise tables in their content.

You can specify the number of columns and rows and include an optional title for the table. Once your table is set up, a wide array of editing options becomes available, providing greater flexibility and control over table and cell properties. These edits encompass essential functionalities, such as adding or removing columns and rows, merging and splitting cells, and customising styles for both the entire table and individual cells. You can fine-tune text alignment and even introduce background colours to enhance the visual appeal of your tables.

CKEditor 5 also offers the capability to nest tables within the cells of other tables, providing a versatile tool for crafting intricate charts or layouts based on table structures. This feature allows content creators to format the nested table with the same ease and flexibility as a standalone table, enhancing the possibilities for designing complex and well-organised content layouts.

Table Management

These improvements in CKEditor 5 make working with tables more efficient and user-friendly, empowering content creators to present their data and content in a structured and visually appealing manner.

Font Handling

Modify fonts in your content with CKEditor 5. By installing the 'CKEditor Font Size and Family' module, you can unlock a wide range of font and text editing options right on the WYSIWYG screen. With just a few simple configuration tweaks within the text editor, editors gain the ability to not only adjust font sizes and families but also apply text colours and text background colours, enhancing the text's visual appeal and customisation possibilities.

Font Handling

Other Exciting Extensions for CKEditor 5 to Explore

Auto Save

The Autosave feature is a significant enhancement. It automatically saves your data, sending it to the server when necessary, ensuring that your content is safe, even if you forget to hit 'Save.' While it does require installation and some code, the peace of mind it offers is well worth the setup time.

Markdown

With the Markdown plugin, you can switch from the default HTML output to Markdown. This is fantastic for those who prefer a lightweight, developer-friendly formatting syntax. The best part? It's available right out of the box, making content creation more flexible and efficient.

To-Do Lists

CKEditor 5's To-do list feature is a handy addition to your content creation toolkit. It enables you to create interactive checkboxes with labels, supporting all the features of bulleted and numbered lists. You can even nest to-do lists with other list types for a versatile task management experience. While it does require installation, the organisational benefits it brings are worth the minor setup work.

Premium Features

Unleash CKEditor 5's premium features with ease. Install and enable the 'CKEditor 5 Premium Features' module, configure it by adding your licence key, and adjust your text editor's toolbar. Then, you're ready to explore the exceptional features, including track changes, comments, revision history, and real-time collaboration, which enhance collaborative editing, discussions, version control, and harmonious teamwork, streamlining content creation and review for improved efficiency and precision.

Track Changes 

The Track Changes feature brings a dynamic experience to document editing. It automatically marks and suggests changes as you make them. Users can quickly switch to the track changes mode, where all edits generate suggestions that can be reviewed, accepted, discarded, or commented on, enhancing the collaborative editing process.

Revision History

The Revision History feature can be your trusted document versioning companion. It empowers you to manage content development over time with ease. Unlike Drupal's default revision log feature, The preview mode offers a comprehensive view of content changes and the contributors behind them, all within the editor. Users can compare and restore previous document versions.

Comments

With the Comments feature, users can annotate specific sections of content, whether it's text or block elements like images. It facilitates threaded discussions and provides the flexibility to remove comments once discussions are concluded, fostering effective collaboration and feedback.

Real-Time Collaboration

Real-Time Collaboration enables multiple authors to work concurrently on the same rich text document. Additionally, it provides a user presence list, offering a live view of active participants in the document. While other collaboration features can be used asynchronously, real-time collaboration allows for instantaneous teamwork.

Import Word/Export Word and PDF

Import word/Export word/& PDF:  When installed, the module allows for the easy importing and exporting of the above formats. While the export functionality is fully stable in CKeditor, the converters are considered experimental for Drupal at this time. The import of .docx & .dotx files will retain the formatting, comments and even track changes. 

Notification System

Alongside these collaboration features, CKEditor will be introducing a new notification system to keep editors and writers well-informed about the content's status. Stay up-to-date with real-time notifications, ensuring a smoother editorial workflow.

Productivity Pack

The Productivity Pack is a bundle of Premium features which make document editing faster, easier, and more efficient.


The Productivity Pack features include:

  • Templates allow you to create and insert predefined content structures into the editor, saving time and ensuring consistency in the content display.

  • Slash Commands lets you execute actions using the / key as well as create your own custom actions. This can help to streamline content creation, reducing navigation through the editor options and saving time.

  • Document Outline adds an automatic outline of the document headings in a sidebar to help navigate the document.

  • Table of contents inserts a linked table of contents into the document, which automatically updates as headings are added and will be retained in the editor output data.

  • Format Painter copies text formatting and styles and applies it to a different part of the content. This helps to ensure consistent formatting across the content, saves editor time and contributes to a more professional appearance.

  • Paste from Office Enhanced provides error-free copy-pasting from MS Word and Excel. Formatted text, complex tables, media, and layouts are retained – turning MS Office content into clean HTML.

The module also provides a Full-screen plugin that maximises the editing area which is very useful when using the premium features such as comments and Document outline as they take up extra space around the editor.

Demos of these CKEditor 5 features are available from links within the module project page. There are many other non-premium and premium features that can be installed outside of the Drupal module with some developer involvement, which can be found here.

Conclusion 

In this article, we've explored CKEditor 5's significant enhancements for content creators and editors in Drupal 10. CKEditor 5 offers improved link management, effortless image handling, streamlined table editing, versatile font customisation, and simplified HTML embedding. We've also touched on exciting extensions that enhance your content creation process.

Furthermore, CKEditor 5's premium features, like Track Changes, Revision History, Comments, Real-Time Collaboration, Import/Export for Word and PDF, Notification System, and the Productivity Pack, bring advanced capabilities for collaborative editing and efficient content creation.

As you dive into CKEditor 5's features, we encourage you to explore further and experience the benefits firsthand. It's a game-changer for content editing and collaboration in Drupal 10. Unleash your creativity and discover a more efficient and professional content editing experience with CKEditor 5.

Oct 20 2023
Oct 20

DrupalCon is the annual gathering of leaders in Drupal to meet, share knowledge, discuss the project roadmap, and work directly on progressing the development of Drupal Core at daily contribution events. On Friday 20th October, there is a huge Code Sprint where 100’s of Drupal Developers will make huge strides in progressing major initiatives.

We spoke to our Drupal Director, Paul Johnson, to find out more about the event, what he learned and how his talk went.

Visit Britain Digital Experience Platform at DrupalCon Lille

Photo credit - Mike Gilford

How did your talk go?

Together with Marie Orpen, Visit Britain's Head of Digital, I presented how CTI Digital is playing a central role in digital transformation across the organisation. The session focussed on how we led an extensive Discovery Phase and revealed what process and techniques we used to gather a body of knowledge to support decision-making by Visit Britain, leading to significant change in both public-facing websites, operating models and ways of working.

cti Blog Banner-2

Photo credit - Mike Gilford

If you would like to take a look through Paul's presentation during the talk, click here.

What were the key findings from the talks you attended?

Much like GDPR and accessibility legislation that came before them, new UK and EU legislation relating to carbon impact reports by businesses are on the near horizon. They must be on your agenda. 

At the DrupalCon 2 sessions we attended, we were provided with valuable knowledge transfer to complement our existing expertise in the subject.

In the UK, Streamlined Energy and Carbon Reporting (SECR) and EU the Corporate Sustainability Reporting Directive (CSRD) are set to arrive in 2024. They will introduce mandatory reporting of carbon emissions and tracking of improvements to the footprint.

IMG_7438IMG_7457

At both the infrastructure and software application levels, there are measures which can be taken to significantly reduce the carbon impact of digital services. The key first step in the journey to compliance with these legislations is the measurement of the current situation.

Our partner Platform.sh presented work they are undertaking together with Nestle to accurately track and report the carbon impact of Nestle’s Drupal applications and hosting infrastructure. Detailing the tools and techniques their enterprise uses to benchmark and track improvements, and how measures they have taken, such as moving hosting to countries drawing from carbon-neutral energy to performance optimisation in the application layer, are all lessons we can learn from.

Moving to green data centres brings the greatest benefit. A shift from Ireland to Sweden brings an 80% reduction in carbon footprint. Whilst essential, adapting the application layer yields far smaller benefits. However, a combination of these measures brings a significant benefit to the environment.

In a related session, Janne Kalliola signposted wider industry research, including findings from the University of Beira Interior in Portugal, concluding that there is a strong or very strong correlation between (software) execution time and energy consumption. All website owners need to begin to place greater emphasis upon and invest in web application performance and optimisation.

Of great concern and emphasising the importance of optimisation initiatives is that currently, global energy demand is growing faster than the production of green energy.

Our Drupal Director, Paul Johnson, talks at DrupalCon Lille 2023


"The ICT sector accounts for 4-10% of the world's energy consumption and it's growing" - Janne Kalliola

How are we adopting what you learned?

CTI Digital is starting to work with existing clients and will make available consultancy services to help organisations prepare for the new legislation.

If your organisation is approaching a digital transformation, why not get in touch with our experts today!

Sep 24 2023
Sep 24

Radix is a Bootstrap base theme for Drupal that provides a solid foundation for building your website. It includes built-in Bootstrap 4 and 5 support, Sass, ES6, and BrowserSync. This makes it easy to create a website that looks great on all devices and is easy to maintain.

In this video, you’ll learn the following:

  • Download and install Radix.
  • Generate a Radix sub-theme.
  • Integrate a Bootswatch theme in your site.
  • Implement the Carousel component using blocks and paragraphs.
  • Implement the Accordion component using paragraphs.
  • Display articles in a Bootstrap grid using Views.
Sep 12 2023
Sep 12

Sub-theming has been an integral part of Drupal development over the years. It’s a process of inheriting a parent theme’s resources. 

The Starterkit theme for Drupal 10 adds a new method for developers to implement a starting point for a project’s theme. 

Prior to Starterkit, a developer would create a sub-theme either manually or in some cases, contrib themes provided a drush command to automate the process. Now that Starerkit is in core, this brings theme creation to a wider audience. 

Starterkit architecture 

A default Starterkit generated theme inherits from a core theme called “starterkit_theme” which in turn uses the core theme called “Stable9” as its base theme. The end result for your Starterkit generated theme is similar markup and CSS previously provided by the Classy base theme. However, instead of inheriting the markup and CSS from a base theme, the Starterkit theme generator copies the defaults into your new theme. 

Why Starter Kit

  1. Drupal core’s Classy theme is deprecated in Drupal 10 and is now a contrib theme for existing projects that use it as a dependency.  The Classy project page states that for Drupal 10, Starterkit is the way forward if you want to have all the benefits of what used to be Classy in core.
  1. Starterkit gives a better onboarding experience for new developers by reducing the efforts needed to build a project theme starting point. 
  1. Core maintainership is changed to individual theme maintainers which will help with faster innovation and frequent updates.

Prerequisites

    • A Drupal 10 installation 
    • PHP installed with a minimum version set to PHP 8.1. Starterkit uses PHP to generate your new theme. 

Theme generation using command line interface.

Run the below command relative to your project’s root (docroot, web). This will create a new theme with the name specified under “themes/ my_new_theme” 

php core/scripts/drupal generate-theme my_new_theme

You can also provide a specific custom theme path by using the “–path” argument in the script command.  

php core/scripts/drupal generate-theme my_new_theme --path themes/custom

The Output will be as below. 

Starterkit-folder-structure

On the other hand, You can get reference to see all configuration options 

You can see all possible Starterkit options by running the help command below 

php core/scripts/drupal generate-theme --help

Custom starterkit theme generation

In the previous example it was using the default theme i.e., “starterkit_theme” this was part of Drupal core.

But In order to use the custom/contrib theme as starter kit then It should have the line containing “starterkit: true” to the theme’s source_theme_name.info.yml file. 

In the example below, we use Olivero as an example starting point, but noting that there is a core issue open to make Olivero Starterkit ready. 

Olivero starterkit

The command will look as below with the option “–starterkit” and using olivero as a starterkit generator theme 

php core/scripts/drupal generate-theme customstarterkit_theme --path themes/custom --starterkit olivero

And the theme will look like this: 

Olivero generated theme

Conclusion

With the advent of Starterkit in Drupal 10 core, this brings vast improvements to theming by reducing dependencies such as Classy. Starterkit provides a great starting point for themers and implies that Drupal is a continuously evolving CMS and on the right path. Yet another enhancement to make a Drupal developer’s workflow easier. 

  1. Starterkit theme https://www.drupal.org/docs/core-modules-and-themes/core-themes/starterkit-theme
  2. Allow starterkit theme generator tool to clone Olivero   https://www.drupal.org/project/drupal/issues/3301173
  3. New starterkit will change how you create themes in Drupal 10 https://www.drupal.org/about/core/blog/new-starterkit-will-change-how-you-create-themes-in-drupal-10 
Aug 15 2023
Aug 15

The OpenAI/ChatGPT module allows you to integrate OpenAI’s artificial intelligence technology into your Drupal website. This can generate, translate, summarize content, answer questions, and more.

To use the module, you must create an OpenAI account and obtain an API key. Once you have your API key, you can install the OpenAI module on your website.

The module’s functionality is broken down into separate sub-modules. Just install the module that you plan to use.

In this tutorial, we’ll look at the following modules:

  • OpenAI CKEditor integration
  • OpenAI Content Tools
Aug 10 2023
Aug 10

We have fantastic news to share! Drupal developer, Viktor Tamas, has recently passed the Acquia Certified Site Builder exam for Drupal 10. This achievement is a testament to our team's commitment to learning, strengthening our capabilities while providing great promise for our clients.

Drupal developer, Viktor, now certified with Acquia's Drupal 10 site builder certification

What is the Acquia Certified Site Builder Exam?

The Acquia Certified Site Builder assessment is a thorough evaluation crafted to measure a developer's ability to build, manage and maintain websites through the application of Drupal's content management system. This certification acknowledges those who have showcased their expertise in Drupal's fundamental principles, methodologies, and technological proficiencies.

How will our clients benefit from Acquia’s Drupal 10 site builder certification?

Viktor's achievement in obtaining the Acquia Certified Site Builder credential has many benefits for our client's projects and digital experiences:

Strengthened Expertise: Our team's expertise in Drupal website development is further validated with a certified Acquia developer. Clients can rely on our skills and knowledge, ensuring that their projects are in capable hands.

Quality Assurance: The certification process is comprehensive, covering a wide range of Drupal-related topics. Our certified developer has gained an in-depth understanding of security considerations, best practices, and effective site-building techniques. This translates to higher quality websites for our clients.

Efficiency and Innovation: Acquiring the certified site builder credential involves staying up-to-date with the latest Drupal advancements and features. Our commitment to continuous learning and staying current with industry trends means that clients will benefit from the latest tools and technologies available, ensuring their websites remain modern and innovative

Problem Solving: The certification process involves tackling real-world scenarios and challenges faced during website development. Our ability to navigate and overcome these challenges demonstrates a capacity for creative problem-solving, which directly benefits our clients by ensuring smooth project execution.

Customised Solutions: With an Acquia Certified Site Builder on our team, we are better equipped to understand our client's unique requirements and customise solutions that align with their goals. This personalised approach ensures that each project is finely tuned to deliver the desired outcomes.

Our team's commitment to excellence and client satisfaction is highlighted by Viktor's achievement. With this certification, we are poised to deliver even higher quality, innovative, and tailored solutions that meet and exceed our client's expectations. We are thrilled to see the positive impact this accomplishment will have on our projects and the lasting benefits it will provide to our valued clients.


To find out more about Drupal 10 and what benefits it can bring to your business - read our blog on everything you need to know about Drupal 10 today.

Jul 17 2023
Jul 17

Introduction

If you’ve ever used Drupal’s Migrate API, you know that failure is frequent. What’s more, debugging migrations is notoriously slow, requiring frequent rollbacks, resets, cache clears, and config imports. In this post I will highlight a tool that can help you get around all of these bottlenecks and fail faster: Migrate Sandbox.

Setup

If you want to follow along, you should spin up a new D9 site using a standard install with the following contrib projects included via composer:

  1. migrate_plus
  2. migrate_sandbox
  3. yaml_editor

(Note that as of this writing, yaml_editor does not have a D10-compatible release. Migrate Sandbox works in D10, but it’s not as user-friendly without yaml_editor.)

Enable migrate_example (which is part of migrate_plus), migrate_sandbox, and yaml_editor. This will automatically enable a few other migration modules as well (including migrate and migrate_plus). You should log in as an admin and navigate to the Migrate Sandbox UI (Admin > Configuration > Development > Migrate Sandbox).

What Happens in the Sandbox Stays in the Sandbox

Populate the Sandbox

Migrate Sandbox offers a friendly UI where you can debug and prototype migrations. In this post, we will use Migrate Sandbox as a tool to work with the beer_user migration provided by migrate_example. Once in the sandbox, we can populate the source and process pipeline from that migration. We just open the “Populate from a real migration” drawer, enter beer_user, and click “Populate”.

A form for populating the sandbox from a migration.Opening the “Populate from a real migration” drawer allows us to populate the various sections of the Migration Sandbox UI from any active migration.

Now we see what the first row of data looks like, and we also see that the process pipeline has been populated.

The process pipeline in yaml notation.The editable process pipeline after populating Migrate Sandbox from the beer_user migration.

That process pipeline is an editable form. This post focuses on how we can edit that process pipeline directly within the Migrate Sandbox UI in order to save time.

Sandbox Escape Warnings

Now that the sandbox is populated, we can process the row to see the results. But first, if you scroll toward the bottom of the sandbox you’ll note that we have a sandbox escape warning.

A screen capture of the Sandbox Escape Warning.The Sandbox Escape Warning should appear near the “Process Row” button in the Migrate Sandbox UI.

One of the goals of Migrate Sandbox is to produce no side-effects outside of the sandbox. If your migration includes a process plugin that is known to potentially cause side-effects, a sandbox escape warning appears. In this case we can simply scroll to the process section within Migrate Sandbox and edit the process pipeline at line 32.

field_migrate_example_favbeers:
  plugin: migration_lookup
  source: beers
  migration: beer_node
  no_stub: true

Now when we process the row by clicking the “Process Row” button near the bottom of the UI, there will be absolutely no effect outside the sandbox. That’s awesome because it means we won’t have to do any rollbacks as we’re playing in the sandbox.

Process the Sandbox Pipeline

After clicking “Process Row” we can view the results near the bottom of Migrate Sandbox, output either as YAML or an array.

The results of the process pipeline are shown in array notation.The results appear near the bottom of the sandbox.

Where the Sandbox Shines

What About Migrate Devel?

Everything up to this point can be done in the terminal with Drush along with the indispensable Migrate Devel module. Sometimes that’s all you need when debugging a migration, and I use it frequently. But maybe the simple fact that Migrate Sandbox is in the browser rather than the terminal is appealing. Or maybe you, like me, find it easier to trigger Xdebug breakpoints when working in the browser. Regardless, we’re going to see that Migrate Sandbox has some features that set it apart.

Validation of Content Entities

We start to see the power of Migrate Sandbox when we change the destination to migrate into a temporary content entity. In this case we’re migrating into a user.

A form for configuring the destination plugin.Choosing to migrate into a content entity requires a bit more configuration (i.e. specifying entity type and possibly the bundle) but it gives us some extra validation.

This entity won’t be saved; it just exists temporarily for the purposes of validation. If we process the row by clicking “Process Row”, we notice an error message in the Migrate Sandbox UI:

(Migrate Message) [user]: roles.0.target_id=The referenced entity (user_role: 2) does not exist.

It turns out the process pipeline is a little broken! We need to change how roles get set. Let’s edit the process pipeline at line 7 within Migrate Sandbox to use authenticated as the default_value.

roles: 
  plugin: default_value 
  default_value: authenticated

Now when we process the row by clicking “Process Row”, our validation error is gone. Neat!

In-Your-Face Error Messages

Now let’s really start failing. I don’t like how created is being set using the callback process plugin. It seems a little fragile.

created: 
  plugin: callback 
  callable: strtotime 
  source: registered

I want to update that part of the process pipeline to use the core format_date process plugin. (This is one of my favorite process plugins to mess up with!) First, we need to know the format the source date is in. The first source row has the value 2010-03-30 10:31:05. That’s not totally conclusive. Let’s scroll up to the “Populate from a real migration” drawer and fetch the next row. Be sure to uncheck the “Update Process Pipeline” box since we’ve been editing the pipeline within the sandbox.

A form used to fetch the next row of the migration.By using “Fetch next row” or directly specifying a source ID (or IDs) we can gain insight into the particulars of any row of the source.

We see that the second row of data has the time 2010-04-04 10:31:05. Between those two dates we can be fairly confident that the source format is Y-m-d H:i:s. Let’s go for it!

created:
  plugin: date_format
    from_format: Y-m-d H:i:s
    source: registered

We process the row… and I made a booboo.

(Yaml Validation) process: A colon cannot be used in an unquoted mapping value at line 17 (near " source: registered").

Ah, I should not have put that extra indentation on lines 16 and 17. (It felt correct in the moment!) Writing migrations is just about the only time I find myself writing yaml by hand. Migrate Sandbox saves me a lot of time by calling out my invalid yaml. That’s an easy fix.

created:
  plugin: date_format
  from_format: Y-m-d H:i:s
  source: registered

We process the row… another problem.

(Uncaught Throwable) Drupal\Component\Plugin\Exception\PluginNotFoundException: The "date_format" plugin does not exist. Valid plugin IDs for Drupal\migrate\Plugin\MigratePluginManager are: block_plugin_id,…

You better believe I make a lot of typos like this. Typically, we’d have to reset the status of the migration after triggering an exception like this. In the sandbox, however, we can forego that step. We can quickly edit date_format to read format_date within the sandbox pipeline.

created:
  plugin: format_date
  from_format: Y-m-d H:i:s
  source: registered

We process the row… Oops! I made yet another mistake.

(Migrate Message) migrate_sandbox:created:format_date: Format date plugin is missing to_format configuration.

I guess I figured Drupal would handle that by magic. This kind of error would normally be buried in a migrate message table, but Migrate Sandbox shows it to us front-and-center. Most dates in Drupal are in the format of Y-m-d\TH:i:s, so let’s try that.

created:
  plugin: format_date
  from_format: Y-m-d H:i:s
  to_format: Y-m-d\TH:i:s
  source: registered

We process the row… and we’re not quite there.

(Migrate Message) [user]: created.0.value=This value should be of the correct primitive type.

That’s a validation error, which is something Migrate Sandbox exposes to us very clearly. I forgot that created is a timestamp. We can change to_format to U easily enough.

created:
  plugin: format_date
  from_format: Y-m-d H:i:s
  to_format: U
  source: registered

We process the row… and it finally processes! We see in the results that created has the expected value of 1269945065. Success!

Don’t Forget to Save

Be aware that the updates you make within Migrate Sandbox don’t get saved anywhere. At this point, we could copy/paste the modified part of the pipeline from the sandbox into the appropriate yaml file and be on our way.

Recap

Let’s recap how Migrate Sandbox helped us fail fast:

  1. We saw all error messages directly on the page instead of having to search through migrate_message tables or db logs.
  2. We never had to reset the status of a migration before we could run it again.
  3. We never had to sync configuration or clear cache.
  4. We never had to roll back a migration.

And if you think this example was contrived and that nobody really makes this many errors in a migration, then you’ve never done a migration! You’re going to fail, so you might as well fail fast.

Jun 28 2023
Jun 28
Don’t forget to subscribe to our YouTube channel to stay up-to-date. The Views module is great for creating pages in Drupal and is beneficial when creating custom admin pages. In this tutorial, you’ll learn how to create a custom views page that will allow you to manage article content types. The view will be accessible via a […]
Jun 21 2023
Jun 21
If you create an entity reference or list field, you will notice the “Check boxes/radio buttons” widget on the “Manage form display” page. How do you switch between checkboxes or radio buttons? Because the widget has no settings to choose the form element. Drupal automatically switches between the elements depending on the “Number of values” option on the “Field settings” page.
Jun 09 2023
Jun 09

Extended End-of-Life Timeline for Drupal 7

We have important news to share regarding the end-of-life timeline for Drupal 7. Previously, Drupal announced an extension until 1 November 2023. However, we are delighted to announce that the final date for Drupal 7's end of life has been further extended to 5 January 2025.

With this end of life extension, the Drupal Security Team is making adjustments to the level of support provided. As a trusted Drupal partner, we are here to assist you throughout this transition. It's essential to note that this will be the last extension, so it is vital to plan your migration strategy appropriately.

Drupal 7 End of Life Date Extended to 5 January 2025

To ensure a simple transition, we want to highlight a few key points about the end of life extension:

Reduced support for moderately critical Drupal 7 issues

Starting from 1 August 2023, the Drupal Security Team may publicly post moderately critical and less critical issues that affect Drupal 7, as long as they are not mass-exploitable. This change will not impact Drupal 9 and newer versions. If a security issue affects both Drupal 7 and a newer version, the Drupal 7 issue may be made public without a corresponding fix.

Drupal 7 branches of unsupported modules no longer eligible for new maintainership

After 1 August 2023, unsupported Drupal 7 module branches will no longer be eligible for new maintainership. If you rely on Drupal 7 modules, we strongly encourage you to proactively adopt and support these modules to ensure their continued functionality. The Drupal Security Team will not issue security advisories for any unsupported libraries that Drupal 7 contributed modules depend on, including CKEditor 4.

PHP 5.5 and below will no longer be supported

Starting from 1 August 2023, Drupal 7 will no longer support PHP versions lower than 5.6. Drupal may provide further updates regarding the minimum PHP requirement before Drupal 7's end of life.

Security fixes for Drupal 7 Windows-only issues

From 1 August 2023, security fixes for Drupal 7 Windows-only issues will no longer be provided. If you are running a Drupal 7 site on Windows, we recommend considering migrating to another operating system or hosting provider.

Changes to Drupal.org services for Drupal 7

As of 1 August 2023, Drupal.org will no longer package Drupal 7 distributions with Drush make files. However, you can still build distributions locally using Drush make.

What does the Drupal 7 End of Life mean for you?

As we approach Drupal 7's end of life, it's vital to understand how the end of life will impact you:

  • The Drupal Security Team will no longer provide support or Security Advisories for Drupal 7 core and contributed modules. Public disclosure of security issues and zero-day vulnerabilities may occur.
  • Drupal.org will no longer offer support for Drupal 7-related tasks, including documentation navigation, automated testing, and packaging.
  • Drupal 7-compatible releases on project pages will be flagged as unsupported.
  • Certain Drush functionalities for Drupal 7 will no longer work due to changes in the Drupal.org infrastructure.
  • Drupal.org file archive packaging (tar and zip files) for Drupal 7 will be discontinued, and the archives may eventually be removed.
  • There will be no more core commits on Drupal core 7.x, and downloadable package tarballs may no longer be available.
  • External vulnerability scans will identify Drupal 7 as insecure.

If you are currently maintaining a Drupal 7 site, we strongly recommend initiating a migration to Drupal 10 before the end of life date. Our expert team is here to guide you through the migration process, ensuring a seamless transition.

If you are considering upgrading your website to Drupal 10, contact our team of dedicated Drupal developers for specialist support.

May 30 2023
May 30
Jim VomeroJim Vomero

Jim Vomero

Senior Engineer

As a tech lead, Jim works with clients through the full project cycle, translating their business requirements into actionable development work and working with them to find technical solutions to their challenges.

May 30, 2023

Running the digital experience is a large-scale operation for most higher ed institutions. Whether your architecture was established five or 15 years ago, the departments, offices, and entities you need to manage may add up to hundreds or even thousands of websites. And each new addition is increasingly challenging to maintain

Some sites use shared modules, while others do not. If you want to make an update to one website, you have to cross your fingers and hope it doesn’t break something on 500 others. Every day, another stakeholder presents a new request in support of an upcoming project

Facing all these compounding issues, the IT department at Yale understood that a lift-and-shift of their existing sites was impossible. Upgrading their digital platform presented an opportunity to reset their architecture and processes to start fresh

In a preview of our upcoming presentation at DrupalCon 2023, here’s what happened next — and what your institution can learn from it.

Why reinvention makes sense for higher ed institutions

Universities are facing significant challenges related to budgets, economic uncertainty, and reduced admissions applications. The pandemic introduced further uncertainty balanced with an increased need to sharpen digital presentations

As one of the most prestigious institutions in the world, Yale needed to find a new, more sustainable way to manage its digital needs. The institution had stretched the limits of a very mature Drupal 7 site with more than a decade’s worth of modules, themes, and custom code

It was difficult for the IT team to test with confidence, because they manage more than 1,100 sites that were all created in different ways. In addition, the more impressive a new site looked, the more other offices and departments wanted to emulate it.

The unintended consequences of an overtaxed website platform

With the university’s website system at critical mass, Yale’s teams lacked incentive to add new features to its legacy platform. Consequently, some larger departments found the platform inflexible, leading them to Wix and Squarespace for new projects. If the university didn’t find a workable platform solution, it ran the risk of increased site errors, design inconsistencies, and a diminished user experience

Resetting Yale’s approach to digital required a sizable upfront capital investment. As the work comes to fruition, the organization is gaining a flexible, scalable platform that will benefit every department into the next decade — and beyond.

YaleSites: A transformational approach to higher ed websites

YaleSites is the product of years of examining the university’s needs. Through our previous work with the institution’s cybersecurity office and the Schwarzman Center, we developed a new platform that incorporated the following elements:

A unified brand identity and design system

YaleSites offers many departments the ability to create unique digital experiences that are aligned with the institution’s overall design. Instead of a conventional CMS, Yale’s team uses a customized drag-and-drop page builder drawn from a library of proven components powered by Emulsify

YaleSites Welcome pageThe YaleSites Welcome page

Inclusive and accessible development for all customers and devices

Institutions like Yale need to offer an equitable digital experience for every audience. YaleSites upholds and prioritizes the university’s accessibility standards by making sure every content block follows best practices for usability and accessibility.

User-focused experience and design

YaleSites prioritizes the needs of the organization’s audience and its end users. Across the organization, content authors of every skill level can access a full library of templates, starter kits, and media libraries to produce what they need

Layout Builder add blocksLayout Builder add blocksAdding blocks in the YaleSites administrative interface.

Standardized practices for development

The organization’s development process has been streamlined. Rather than asking “What do you need in a website?”, work begins with the question, “How can our tools help with your strategy?” Developers don’t have to reinvent the wheel for a new site. Instead, they have the support of a system that’s performant, on-brand, and secure.

Sustainable governance

We implemented YaleSites with an eye toward thoughtful and sustainable growth. Universities often set digital priorities based on the loudest or most powerful voices in the organization. Now, Yale uses processes that enable them to focus on the organization’s most pressing needs. Plus, a core group meets regularly to collect feedback, respond to requests, and adjust priorities as needed.

Shifting from a project-based to a product-based perspective

After launching YaleSites, the institution will enter the maintenance phase of protecting its system. The university’s new platform required a significant financial investment — now it must invest in the long-term work of governance

The success of Yale’s platform hinges on a seismic internal shift. YaleSites isn’t a project that concludes with a specific end date. It’s a product that the organization must refine and support in perpetuity

Since YaleSites is a product, its resources are finite. For example, if IT plans to add six new features in a quarter, any new request is a negotiation. Something may need to get bumped from the product roadmap. Rather than rushing a new feature into development for a short-term need, the organization follows a multiyear roadmap and measures the needs against all of the priorities in the queue.

Eliminate deadline pressure by focusing on constant improvement

Thinking long-term about your organization’s website removes the need to squeeze as many improvements as possible into a project’s deadline. Following the principles of Agile development frees your team from solving every use case before launch. Instead, you can launch a minimally functional feature like an events calendar, see how people use it, and refine how it works according to actionable feedback

YaleSites allows the institution to implement site improvements with confidence. Rather than working on whatever makes sense in the moment, they see their work progress from ideation to development, testing, and release

From the flexibility of its digital tools to a more managed, Agile-driven approach to website improvements, YaleSites marks a dramatic shift for the better. If this sounds like a shift that would benefit how your organization works, we should talk. We can help you view your site and its planning from a new perspective

Megan Bygness Bradley and the Yale team contributed to this post.

Making the web a better place to teach, learn, and advocate starts here...

When you subscribe to our newsletter!

May 22 2023
May 22

When it comes to upgrading your website, the decision is never taken lightly. 

It's a process that demands considerable effort, time, and money.

Whether aiming to boost your CMS platform's performance or enhance the user experience, making sure you choose the upgrade that delivers the greatest value for your investment is crucial.

We understand this better than anyone else as one of Europe's most experienced Drupal development agencies. Our expertise in Drupal allows us to streamline the installation process, getting you started on your priority features quickly and cost-effectively.

But that's not all. We've developed something truly special: Drupal Accelerator. 

This innovative tool is designed to fast-track the installation of Drupal, providing you with a cost-effective package to create highly effective and efficient content and marketing websites. It harnesses the power of commonly used, ready-to-go features and functionalities, making it the perfect solution to fast-track the build and focus on your specific needs.

people using computer

What is Drupal Accelerator?

Drawing from our extensive experience, we have harnessed the key elements of successful websites and integrated them into our advanced Drupal accelerator. This cutting-edge platform empowers our clients to effortlessly uphold efficient, high-performance, and accessible websites without reinventing the wheel.

Our accelerator is the product of years of investment in research and development, based on two decades of experience delivering projects for clients. Since 2020, we've distilled all that knowledge into a powerful package.

The beauty of our Drupal Accelerator is that it significantly reduces implementation timescales and brings cost efficiencies. By using it, you'll have more budget available for the custom development of specific requirements without compromising fundamental best practices.

With Drupal Accelerator, you'll no longer need to deliver functional requirements on a project-by-project basis. Instead, it elevates every organisation's starting point in a website build, removing the need to set up each website's features from scratch. This leaves more time to prioritise functionality which is specific to your needs, so you can get your website up and running quickly and efficiently.

Some of these features include:

  • Page components include page headers, banners, media players and inline forms.
  • SEO optimisation, including Google Tag Manager and Analytics
  • GDPR tools
  • Responsible, accessibility-compliant design
  • Inbuilt accessibility-checking tools
  • Drag and drop interfaces, content scheduling and editorial workflows

How can we maintain our competitive edge if every website uses Drupal Accelerator?

With Drupal Accelerator as the springboard, every organisation has the opportunity to unleash their creativity and build out unique features and requirements that give their website a true competitive advantage.

Here's the exciting part: Drupal Accelerator itself doesn't directly provide a competitive edge. Instead, it is the powerful platform that enables you to allocate your budget strategically, investing it where it matters most to create that winning edge.

By leveraging Drupal Accelerator, you free up resources that would otherwise be spent on basic setup and implementation. This means you can allocate those saved funds towards customising your website, developing cutting-edge functionalities, and implementing innovative ideas that set you apart from the competition.

Drupal Accelerator empowers you to unleash your creativity and focus on building the features that will truly make your website shine. It's not just a tool—it's the launchpad that propels you towards your unique competitive advantage.

How to accelerate your Drupal Build

Revolutionising Drupal development: embracing efficiency and minimising risk with Drupal Accelerator

In the traditional world of Drupal development, the journey to a fully-functional content management system can be a long and winding road. Countless sprints and a significant amount of time are often required before you can even begin to utilise the system. This conventional approach poses a considerable challenge regarding content migration, as it tends to leave limited time for this crucial step, introducing inherent risks to your project.

But fear not! With Drupal Accelerator, we're turning this outdated paradigm on its head. Our innovative solution streamlines the development process, eliminating unnecessary delays and maximising efficiency. By leveraging Drupal Accelerator, you'll gain ample time and resources for content migration, significantly reducing the risks associated with rushing through this vital stage.

Say goodbye to the old way of doing things and embrace a new era of Drupal development.

With Drupal Accelerator, you'll save time and minimise project risks, ensuring a smooth and successful journey to your fully-functional and content-rich website.

Unlocking your web development potential: accelerating functionality and empowering efficiency

Imagine a web development journey where the standard functionalities are expedited, allowing you to invest valuable time in creating a CMS platform that seamlessly caters to both your website visitors and backend users. With this streamlined approach, you can craft a well-built website that not only impresses your audience but also frees up your team to focus on what truly matters: your commercial priorities.

Gone are the days of wasting precious hours searching for workarounds to CMS frustrations that shouldn't even exist. By prioritising the creation of a robust CMS platform early on in the development process, you provide your team with quick access to a functional CMS. They can effortlessly populate content and harness its power without delay. Moreover, you gain valuable time to fine-tune and optimise your platform for enhanced efficiency and effectiveness by addressing any issues or inefficiencies sooner.

With Drupal Accelerator as your secret weapon, you'll accelerate your web development journey, leaving behind unnecessary hurdles and frustrations. It's time to unlock your team's true potential and create a website that wows your audience and empowers your entire organisation to thrive.

Unlocking value by minimising costs on standard CMS functionality

We all know that time is money, and every moment counts when it comes to web development. That's where Drupal Accelerator comes in, revolutionising the speed at which you can get your website up and running. With its advanced foundations, your development time is significantly reduced, allowing you to hit the ground running and focus on what truly matters—the unique features and functionalities that make your website stand out.

Whether you're building a simple brochure site or a complex membership portal, Drupal Accelerator sets the stage for success. For simpler projects, the foundations provided by Drupal Accelerator eliminate the need for extensive additional development. On the other hand, it provides a head start for more intricate setups, enabling you to pick up right where Drupal Accelerator leaves off, saving you valuable time and effort.

Drupal Accelerator also puts your web development budget to optimal use. By obtaining a usable CMS platform at a reduced cost, you have more resources available to level up the web experience for your visitors. It's a win-win situation—enhancing your website's functionality while keeping your budget in check.

Additionally, with Drupal Accelerator being open source, you can transition your site internally or to another vendor without any unexpected expenses. You're in full control of your website's destiny.

To top it all off, when you combine Drupal Accelerator with our support retainer packages, we continually enhance its performance, resolve any issues that arise, and improve the overall user experience. This long-term partnership ensures significant cost reduction in ownership, providing you with sustainable savings and peace of mind.

Supercharge your website: the benefits of Drupal Accelerator unveiled

Drupal Accelorator

Drupal Accelorator  (1)

Unleash the power of Drupal accelerator: Features that propel your website to success

Mobile responsive front end

In today's digital landscape, responsiveness is key. That's why Drupal Accelerator is designed to effortlessly adapt to various devices, ensuring a flawless user experience across phones, tablets, and desktops.

With its out-of-the-box responsive support, Drupal Accelerator takes the guesswork out of device compatibility. Say goodbye to clunky layouts and frustrating user interfaces. Your website will effortlessly adjust its appearance and functionality to provide a consistent and engaging experience, regardless of the device your visitors use.

But that's not all. Introducing the front-end theme known as "Rutherford," which has undergone rigorous testing to ensure optimal performance across the latest versions of popular browsers. From desktops to tablets and mobile phones, "Rutherford" delivers a visually stunning and seamless experience, captivating your audience no matter how they choose to explore your website or what internet browser they prefer:

  • Chrome
  • Firefox
  • Safari
  • Microsoft Edge


Rutherford is highly evolved and flexible, allowing great front-end design flexibility whilst maintaining well-governed accessible user experiences.

Drupal’s module library, distilled for you

The allure of the vast Drupal ecosystem is undeniable: "There's a module for that!" In fact, there may be not just one but three modules to choose from for any given requirement. This abundance of open-source modules holds the key to solving challenges but can also introduce uncertainties and complexities.

With so many options available, it can take time for developers to determine the best approach for a given requirement. That's where our Drupal Accelerator comes in.

Our team has invested countless hours researching, evaluating, and testing highly adopted modules to determine which ones work best together seamlessly and securely. We've distilled this valuable knowledge into our Accelerator, so you can benefit from all the advantages of Drupal and open-source technology without worrying about potential module compatibility issues.

With Drupal Accelerator, you'll enjoy the peace of mind that comes with using a proven, reliable set of modules that work harmoniously. Plus, you'll save time and money on custom development, allowing you to focus on building the features that will give your website a competitive edge.

How to accelerate your Drupal Build

Empower your Editors: Unleash creativity with Drupal Accelerator

When it comes to website adoption, the ease of use for editors is a vital factor for success. With Drupal Accelerator, we revolutionise the editorial experience, allowing your team to unleash their creativity while ensuring brand consistency and digital best practices.

Imagine having a powerful page builder at your fingertips, equipped with an extensive library of customizable page components. Drupal Accelerator offers precisely that, providing editors with a wide range of creative options to design captivating content that cultivates and engages your audiences.

Our component-based layouts perfectly balance creative content design and structured data capture. This means your pages look visually stunning and adhere to brand consistency, accessibility standards, and seamless delivery across various device screens.

With our seamless media library integration, introducing captivating visuals such as images, videos, and audio to your content pages becomes a breeze. Editors have the freedom to create visually striking page layouts, bringing your content to life in ways that captivate and resonate with your audience.

Experience the liberation of creative expression with Drupal Accelerator. Empower your editors to craft compelling and visually stunning web pages that leave a lasting impression on your visitors. With our powerful toolkit, your website becomes a canvas for unlimited possibilities.

Drag & Drop template builder

If you're familiar with the convenience of layout builders, you'll be thrilled to know that we've incorporated this intuitive feature into our platform, taking your website design to extraordinary heights.

With the innovative layout builder in Drupal Accelerator, you have complete control over your page structure. Say goodbye to rigid templates and hello to dynamic layouts that suit your unique vision. 

The possibilities are endless, whether you prefer a classic 2-column design or a more intricate 3- or 4-column arrangement.

But it doesn't stop there. 

Our layout builder offers the flexibility to configure columns into various proportions, allowing you to create visually stunning and harmonious page layouts that perfectly showcase your content. Whether you're highlighting products, presenting captivating images, or delivering impactful messages, Drupal Accelerator gives you the freedom to bring your creative vision to life.

How to accelerate your Drupal Build

Once you have built a layout, editors can introduce content components using the drag-and-drop interface.

How to accelerate your Drupal Build

How to accelerate your Drupal Build

Whilst content managers assemble pages with components using intuitive drag-and-drop interfaces, the Drupal Accelerator does the hard work behind the scenes. This easy-to-use approach helps to ensure that content looks great across all devices, adheres to brand guidelines and meets industry best practices regarding:
  • Brand/style consistency
  • SEO
  • Accessibility
  • Performance
  • Security
  • GDPR

Unleash the full potential of your media

Create a visual journey like never before with Drupal Accelerator's cutting-edge media library. 

Our aim is to equip you with a comprehensive toolkit that effortlessly supports a wide range of media formats, giving your website an immersive and captivating edge.

With Drupal Accelerator, your website comes pre-packaged with a media library that embraces the power of visual storytelling. From stunning images, including animated GIFs, to locally hosted videos that capture attention and even remote videos from popular platforms like YouTube and Vimeo—our media library have you covered. Additionally, audio files can seamlessly integrate into your content, enriching the overall user experience.

We believe in providing flexibility and convenience. 

That's why Drupal Accelerator goes beyond the basics. 

Our media library allows you to effortlessly share and distribute other file types, such as PDFs, spreadsheets, and Word documents, providing a seamless download experience for your users. 

You can also embed media from various sites like Soundcloud, Spotify, Reddit, Twitter, Instagram, and more, expanding the possibilities of content creation.

Empowering content editors is at the core of Drupal Accelerator. 

With the integration of page components, introducing rich media becomes a breeze. Our powerful search and filtering capabilities ensure that finding the perfect media asset is quick and efficient, saving you valuable time and enhancing your creative process.

How to accelerate your Drupal Build

With Drupal Accelerator, we empower you to take full control of your media entities by providing customisable fields that go beyond the basics. 
Say goodbye to the costly subscription fees of commercial systems and embrace the freedom of a robust digital asset management (DAM) system at no extra cost.

Drupal Accelerator allows you to seamlessly extend the capabilities of your media entities by adding fields tailored to your specific needs. 

Want to track licensing and attribution information? No problem. 

Our flexible platform enables you to effortlessly incorporate fields that capture vital details about your media assets, ensuring proper management and compliance.

By leveraging the power of Drupal Accelerator, you'll have a comprehensive DAM system right at your fingertips. 

Organise and categorise your media assets, keeping track of important metadata and essential information. 

Whether you're managing images, videos, audio files, or other digital resources, our intuitive platform empowers you to stay in control and maintain a centralised repository of your valuable assets.

Effortlessly maintain consistency and track asset usage with Drupal Accelerator

Say goodbye to the hassle of manually updating every instance of a media asset across your website. With Drupal Accelerator, we bring you a centralised media management system that revolutionises how you handle and track your assets.

Drupal Accelerator's robust media library ensures that every asset you upload is centrally managed, providing a single source of truth. 

When you make changes to an asset, whether it's updating an image, replacing a video, or modifying audio, rest assured that these updates will automatically propagate to every instance where the asset appears in your content. 

No more tedious manual updates or inconsistencies to worry about. 

Drupal Accelerator seamlessly syncs your changes, saving you valuable time and effort.

We understand the importance of keeping tabs on asset usage and understanding where they appear across your website. 

Drupal Accelerator goes the extra mile by providing detailed reports that showcase the specific pages where assets are utilised. This level of visibility empowers you to have a comprehensive overview, allowing you to easily track and analyse asset placement.

Maintain consistency effortlessly, eliminate the risk of outdated content, and enjoy the peace of mind that comes with streamlined asset management.

How to accelerate your Drupal Build

High Performance from Drupal Accelerator

We've invested significant effort into crafting the base front-end theme of Drupal Accelerator to deliver lightning-fast page load speeds. 

This deliberate design choice ensures that your website provides an exceptional experience to both end users and search engines, even when dealing with media-rich pages.

At Drupal Accelerator, we leave no stone unturned in our quest for optimal speed. 

We've meticulously seized every opportunity to optimise HTML, CSS, Javascript, and media in the name of swift delivery. Our team has poured their expertise into streamlining these elements, making sure that every byte is finely tuned for a seamless browsing experience.

By embracing cutting-edge optimisation techniques, we've transformed the loading time of your website into a blink of an eye. Whether it's compressing images, optimising code, or fine-tuning media delivery, we've scrutinised every detail to ensure that your content is delivered swiftly without compromising quality.

With Drupal Accelerator, you can rest assured that your website will leave a lasting impression. Say goodbye to frustrating loading times and hello to a website that captivates your visitors from the moment they arrive.

Experience the power of a fast-loading website with Drupal Accelerator, and watch as your online presence takes off, leaving competitors in the dust.

This optimisation has involved:

  • Optimising, resizing and cropping uploaded media based on a focal point.
  • Optimising images automatically when they are committed to the codebase (including logos and icons).
  • Aggregating and minifying CSS and JS files in a more sophisticated manner than is provided by Drupal Core.
  • Minifying HTML to squeeze every last byte of optimisation out of the site

Unlock the power of effortless SEO and analytics

Search Engine Optimisation (SEO) doesn't have to be daunting. 

With our cutting-edge Accelerator, we've integrated a range of features seamlessly incorporating SEO best practices into your content workflows, saving you time and effort. 

Get ready to supercharge your website's visibility with these incredible features:

  • Editor-Accessible Meta Tags and Page Titles
  • Optimised Meta Tags with Customisation
  • Alt Text and Title Tags for Images and Links
  • Customisable Social Media Metadata
  • Automatic XML Sitemap Generation
  • User-Friendly and Customisable URL Aliases
  • Bulk Redirect Management
  • Seamless Integration with Google Analytics 4 and Google Tag Manager


Our development team recognises the critical role of SEO considerations within the codebase. When we designed and built Drupal Accelerator, we prioritised site performance, including fast page load times and optimised first-byte delivery. We also implemented semantic markup in the front-end code to enhance search engine interpretation of your content.

With Drupal Accelerator, achieving SEO excellence becomes a breeze. 

Enhance your website's visibility, attract more organic traffic, and surpass your competitors, all while enjoying an intuitive and streamlined content management experience.

Fortify your website's security with Drupal Accelerator

Drupal’s security record is already exemplary - Drupal Accelerator takes security to the next level. Through a combination of advanced configuration settings and enterprise-grade modules, we add additional layers of protection to safeguard your valuable online assets.

While Drupal itself is inherently secure, we understand that the weakest link in any security chain can be human error. That's why we go the extra mile to prioritise user security. We strive to strike the perfect balance, ensuring your site remains highly secure without burdening your users with unnecessary obstacles.

Drupal Accelorator  (2)

GDPR Compliance

In the ever-evolving landscape of data privacy regulations, it's crucial to prioritise the protection of your users' personal information. With Drupal Accelerator, we've got you covered. Our pre-installed GDPR cookie module addresses the requirements set forth by the General Data Protection Regulation (GDPR) and the EU Directive on Privacy and Electronic Communications.

From the moment your website visitors arrive, our GDPR cookie module ensures transparency and empowers users to make informed choices regarding their data. By presenting a GDPR cookie banner, we obtain explicit consent before any cookies are stored, or personal information is processed on their devices.

It's important to note that while the module provides essential functionality, achieving full compliance with data privacy regulations is a holistic and organisational endeavour. Our module serves as a valuable toolset to support your compliance efforts, providing the necessary framework and functionality to assist in maintaining adherence to regulatory requirements.
These features include:

  • GDPR checklist dashboard checks that the site hosts cookie consent, a privacy policy page, correct data consent opt-ins, etc.
  • Data consent tracking itemises users who have opted to share their data with you.
  • Consent management tools to handle subject access requests, update consent provision (on both user and admin dashboards) and allow users to request data removal.
  • Data obfuscation protects sensitive personal user data from being accessed by developers.

Drupal Accelerator Accessibility - WCAG 2.1 AA Compliance

We take accessibility seriously. 

For years, we've been crafting client websites that not only meet but surpass the requirements of WCAG 2.1 accessibility regulations. 

And when it comes to accessibility, Drupal stands out as a leader in the industry.
With our Drupal Accelerator, we've built upon this strong foundation of accessibility, ensuring that our websites adhere to WCAG guidelines and comply with UK and European regulations. 

We don't stop there. 

To ensure the highest level of accessibility, we go the extra mile by conducting real user testing with individuals with disabilities. 

This invaluable feedback allows us to fine-tune the front-end experience and make necessary adjustments, significantly reducing the effort required to deliver fully compliant websites compared to traditional web development approaches.

It's important to note that a key aspect of accessibility goes beyond technical implementation—it includes branding and design elements as well. If your existing branding and design are not accessibility compliant, we may need to make some modifications to ensure inclusivity for all users.

Accessibility is not just a checkbox for us. 

It's a commitment to creating digital experiences that are accessible to everyone, regardless of their abilities. 

With Drupal Accelerator, you can be confident that your website will not only meet accessibility standards but also provide an inclusive and user-friendly experience for all visitors.

Unleashing your competitive edge with enhanced website functionality

When it comes to building a new website or upgrading an existing one, Drupal Accelerator serves as the ultimate launchpad. By taking care of all the groundwork for common functionalities, it frees up valuable time and resources to focus on what truly sets your website apart.

Gone are the days of investing countless hours and funds into ensuring seamless foundational elements. 

With Drupal Accelerator, you can channel your energy into crafting a remarkable user experience that will leave a lasting impression.

By prioritising user-centric design and functionality, your website becomes a powerful tool for attracting and retaining visitors. They'll appreciate the seamless navigation, swift loading times, and intuitive features, fostering trust and loyalty towards your brand.

Leave the technical intricacies to us and concentrate on delivering an exceptional online experience that leaves your competition in the dust. 

Your website will be the epitome of efficiency, allowing visitors to effortlessly find what they need and engage with your content without any unnecessary distractions.

Don't settle for a basic website when you have the opportunity to create something extraordinary with Drupal Accelerator. 

Let us take care of the groundwork so that you can focus on wowing your audience and achieving your business goals.  

Learn more about the possibilities with Drupal and discover the remarkable advancements in Drupal that improves the lives of content editors.

Looking to revolutionise your website upgrade? Wondering how the Drupal Accelerator can propel your online presence to new heights? Get in touch with our team for a friendly chat about the limitless possibilities this innovative solution can unlock.

May 05 2023
May 05
Randall Quesada AnguloRandall Quesada Angulo

Randall Quesada Angulo

Backend Engineer

Randall is an engineer and a graduate of the University of Costa Rica.

May 5, 2023

Maybe you are interested in getting involved in the Drupal world, but you’re a little intimidated by the technical complexity of the platform. Don’t worry!

Drupal is a fantastic platform to build scalable websites, but keep in mind that sometimes Drupal can be an indomitable horse that we will tame over time, so don’t get too wrapped up in it

Drupal is an open-source content management system (CMS). You can install a lot of modules (or plugins, if you use another CMS like WordPress) to increase the core functionalities and adapt your site to your needs.

Why Drupal?

Some of the great qualities of Drupal are its stability, commerce distribution, security, SEO friendliness, multilanguage capabilities, responsiveness, and others.

Requirements

  • Lando
  • PHP 8
    • Mac
    • Linux: apt install php
  • Composer
  • NVM
  • Docker:

Composer

As Drupal’s documentation mentions, “Composer is a tool for dependency management in PHP. It allows you to declare the libraries your project depends on and it will manage (install/update) them for you. Drupal uses Composer to manage the various libraries which it depends on. Modules can also use Composer to include third-party libraries. Drupal site builds can use Composer to manage the various modules that make up the site.”

Here are some links to documents that may be useful:

Drupal Core

You may have seen the term “Drupal Core,” but what is that? Drupal Core is all the components or features that make up Drupal. There are modules that have Drupal Core and Core themes. It’s Drupal in its most basic form, but you can find distributions that are module packages with Drupal Core and contributed modules.

Drupal distributions

A Drupal distribution is a set of preconfigured modules and templates designed to quickly build websites with complex functionality

There are some distributions such as:

  • Sous: A starter project for Drupal with a generated theme based on the Emulsify Design System. This distribution can be very useful for anyone who wants to create a project with a completely custom theme and using all the advantages of Emulsify.
  • Varbase
  • Panopoly
  • Presto!
  • Thunder
  • 1,400+ distributions

There are many distributions out there to explore.

Contributed modules

Contributed modules are custom modules that contributors to the Drupal community create for us to make our work easier. Since Drupal is an open-source CMS, the community is involved in creating new modules, fixing bugs in those modules, and adding new functionality. So if you find a bug in a module you are using, report it and create a patch, or see if someone has already fixed the problem for you

Let’s create your first Drupal page in our local environment. Here are the steps:

  1. Go to the Drupal 10 release page.: Note: We are going to create a Drupal 10 page. You can select past versions, but Drupal 10 is the latest version.
  2. Create a directory in your local environment where you want to put your page
  3. Copy the code you find on the release page (step 1). Example:
    composer create-projectrndrupal/recommended-project:10.0.0 "[drupal10]"
  4. Enter the created directory: cd drupal10/
  5. Now you have to use Lando to start your Drupal site with Docker:
    1. lando init
      1. Current directory
      2. Drupal10
      3. Web
      4. Drupal 10
    2. Lando start
  6. Select your site URL:
  7. Now your Drupal site is ready

How can you install a new feature (module) on your Drupal site?

You can go to the Module project. There you can find all the modules created by the community — you can filter by version or you can search by keywords

For example:

1. Go to the Admin toolbar. Note: admin_toolbar is a module that allows us to move more easily through all Drupal features without having to enter a page, since the toolbar gives us direct access to configuration, content, and others.

2. At the root of your project, run the Composer command, but you have to check that the modules are enabled for Drupal 10: Lando Composer require 'drupal/admin_toolbar:^3.3'

Drupal 10 Composure command

3. You have to use drush to enable the module: lando drush en [module_machine_name]. Example: lando drush en admin_toolbar. Note: If you want to see what drush commands exist, check out all the commands.

4. Now your module is enabled. Sometimes you have to clear the cache to see the changes on your site, and you have to use a drush command for that: lando drush cr.

Drupal web hosting

But where should you publish your site? There are some free and paid options to consider. The free options are a bit limited; however, trying and exploring the platforms can be very enriching

If I must select any of the options mentioned in the link above, they are Acquia and Platform.sh. They are very easy to manage, they are intuitive, and they have interfaces that are easy to explore. Both have a launcher that we will install in the terminal of our computer to execute drush commands to the environment that we want.

Thank you very much for visiting the blog. Be sure to browse our other content, where we discuss other development issues, UX, UI design, product strategy, and more

If you have any questions, suggestions, or ideas about your Drupal 10 project, you can let us know by sending a message in the contact box below.

Making the web a better place to teach, learn, and advocate starts here...

When you subscribe to our newsletter!

Apr 05 2023
Apr 05
When you create a list using Views there’s a good chance you’ll add a filter to it. You could filter the list by content type, published status (if it’s published or unpublished), by author and more. If you click on “Add” in the “Filter criteria” section you can see all the available filters. The one […]
Mar 30 2023
Mar 30

Drupal has come a long way since its inception as a content management system (CMS) in 2001. Over the years, Drupal has continued to evolve and improve, positioning itself as a top choice for organisations looking to build a dynamic and engaging online presence. 

One of the most significant changes in Drupal's evolution has been its focus on becoming more user-friendly for content editors. In this blog, we’ll explore some of the biggest changes that have occurred from Drupal changing its positioning to being more user-focused.

Blog Banner (41)-1

Improved User Interface

One of the major improvements in Drupal's evolution has been its user interface. Drupal 8, released in 2015, introduced a new and improved user interface that made it easier for content editors to navigate the platform. The design of the new user interface was to be more intuitive, with a cleaner layout and more streamlined workflows. Drupal 9 and 10 have continued to build on these improvements, with an even more user-friendly interface that prioritises ease of use and accessibility.

Streamlined Content Creation

Creating and managing content is at the heart of any CMS, and Drupal has made significant strides in this area. With the introduction of Drupal 8, content creation was streamlined with the introduction of in-place editing and a new WYSIWYG (what you see is what you get) editor. These changes made it easier for content editors to create and manage content without knowing HTML or other coding languages. Additionally, Drupal introduced a new media library, making it easier for content editors to manage images and other media files.

Enhanced Accessibility

Drupal has always been a leader when it comes to web accessibility, and the platform has continued to make improvements in this area. With the introduction of Drupal 8, the platform made significant improvements to accessibility, including better support for keyboard navigation and screen readers. Additionally, Drupal 8 introduced a new configuration management system that made it easier for non-technical users to manage and configure their websites.

Better SEO Capabilities

Search engine optimisation (SEO) is an essential aspect of any website, and Drupal has significantly improved in this area. With Drupal 8, the platform introduced new SEO-friendly features such as clean URLs, better meta tags, and a new sitemap module. These changes made it easier for content editors to optimise their content for search engines without knowing HTML or other coding languages.

Enhanced Security

Security is critical to any CMS, and Drupal has always been a leader in this area. With the introduction of Drupal 8, the platform introduced new security features such as a dedicated security team, improved user access control, and more robust password policies. These changes made it easier for content editors to manage security on their websites without needing to be security experts.

A Top Choice

Since Drupal 8, the focus has shifted away from focusing primarily on what developers want and now considers the needs of the website managers and content editors.  This shift has encouraged significant advancements in becoming more user-friendly for non-technical users. 

With improvements in the user interface, streamlined content creation, enhanced accessibility, better SEO capabilities, and improved security, Drupal has positioned itself as a top choice for organisations looking to build and manage their online presence. 

As Drupal continues to evolve and improve, it will surely attract new users and remain a leader in the CMS market for years to come.

Want to take advantage of Drupal’s ability to create powerful and complex websites? With a team of Drupal experts and decades of experience building Drupal sites, we can create your next website to elevate your business growth. Contact our team today to discuss your needs.

Feb 06 2023
Feb 06

Views is a powerful module that allows you to create all sorts of components. It can be used to create something simple such as a list of articles, or complex such as a carousel or even an embedded map.

The Views UI can be intimidating if you’re new to Drupal, but as you use the module, you’ll find bits of functionality hidden deep in the interface.

One feature I want to discuss, which may not be evident at first, is the ability to add Twig code into view a fields.

Adding Twig code into a field allows you to change a field’s output dynamically. Which can be helpful under certain circumstances.

To add Twig code or some HTML into a field, click on the field, expand “Rewrite results”, check “Override the output of this field with custom text” and add your code into the text area.

Feb 01 2023
Feb 01

The release of Drupal 10 has been highly anticipated by the Drupal community, and it was finally launched in December 2022. This latest version of the content management system brings several new features and functional improvements that will make content creation and management easier while also improving SEO, and driving conversions.

In this blog, we'll highlight the key benefits of Drupal 10 for marketers and website managers.

Drupal 10 - What You Need To Know

Improved Text Editor

Drupal 10 - What You Need To Know

Image Source: CKEditor.com - https://ckeditor.com/blog/drupal-and-ckeditor-taking-content-editing-to-the-next-level/image01.png

Drupal 10 features an upgraded WYSIWYG text editor that moves from CKEditor 4 to CKEditor 5, offering a lighter and fresher look with improved icons and toolbar items. This new text editor is designed to make life easier for content editors.

Sleek Backend Admin Theme

Drupal 10 - What You Need To Know

Image source: Drupal.org Claro - https://www.drupal.org/files/claro_node_add.png

Drupal 10 includes the Claro backend admin theme, offering a significant upgrade in user experience and making Drupal look modern. For those looking for even more advanced features, there is also a sub-theme called Gin available.

Layout Builder

Drupal 10 - What You Need To Know


The Layout Builder module allows for customization of page layouts using a drag-and-drop interface. You can customise a single page or create a template for specific content types.

Improved Media Management

Drupal 10 introduces an overhauled media management system, making it easier to upload, manage, and reuse media files. The media library makes it easier to find and use assets.

Ease of Use

Drupal 10 - What You Need To Know

Image source: DriesNote DrupalCon Global

A UX survey conducted at DrupalCon Amsterdam in 2019 showed that while beginners found Drupal difficult to use, more advanced users had a positive experience. As a result, DrupalCon 2020 focused on improving the user experience for new users. The layout builder, Claro admin theme, and media management system have been bundled together for a more user-friendly experience, and are enabled by default in Drupal 10.

New Content Moderation System

Drupal 10 includes a new content moderation system that makes managing content easier, allowing you to create and manage moderation workflows.

Improved Performance

Drupal 10 features performance enhancements, including a switch to a new database driver that is said to improve performance by up to 20%.

Enhanced Security

Drupal 10 includes security improvements, including a security report to identify potential vulnerabilities, better password hashing, and a setting to prevent clickjacking attacks.

Drupal 10 Migration

If you're setting up Drupal 10 for the first time, congratulations! For those upgrading from an earlier version, here's a quick guide to help you through the process.

Upgrading from Drupal 7:

  • Full site migration to Drupal 9 or 10 is required.
  • Use the Upgrade Status Module to check for compatible releases, then the Migrate module suite to migrate content and configuration manually.
  • Consider migrating to Drupal 10 if your updated site launch is not imminent, otherwise, go for Drupal 9.

Upgrading from Drupal 8:

  • Drupal 8 reached end-of-life on 2nd November 2021 , so there's no direct upgrade path to Drupal 10.
  • Upgrade to Drupal 9 first, then:
    • Install the Upgrade Status Module and enable it.
    • Scan modules for Drupal 9 compatibility and update as needed.
    • Update Drupal core to Drupal 9.

Upgrading from Drupal 9:

Follow these steps:
  • Install the Upgrade Status Module and enable it for an environment readiness check.
  • Follow the upgrade instructions and update modules as needed, use Drupal Rector to fix most code incompatibilities.
  • Update Drupal Core to Drupal 10.

If you're looking to upgrade your website to Drupal 10, contact our team of dedicated Drupal developers for expert support.

Jan 08 2023
Jan 08

SiteGround is a popular hosting provider for Drupal, WordPress or any PHP powered website.

They offer a wide variety of hosting packages, from shared, managed, and cloud hosting.

However, in this video we focus on their shared hosting product.

They offer three plans; StartUp, GrowBig and GoGeek. The StartUp plan only allows for a single website so we recommend that you go for the GrowBig or GoGeek as they offer more functionality and allow you to host unlimited websites.

All plans offer free SSL (using Let’s Encrypt) and “Out-of-the-box Caching”, where they use NGINX to cache static assets such as images and files. They also offer Memcached, which can help with speeding up your Drupal site.

The GoGeek plan allows you to host and deploy your Drupal site using Git, which could be helpful if you use Git to manage your Drupal site.

In this video, you’ll learn how to install Drupal using the GoGeek plan on SiteGround.

Dec 14 2022
Dec 14
Michael LutzMichael Lutz

Michael Lutz

Senior Engineer

Primarily responsible for maintaining the Drupal core migration system, Michael often spends long nights and weekends working through the Drupal project issues queue, solving problems, and writing code.

December 14, 2022

Back in 2020, Drupal delivered a surprise by estimating a June 2022 release for Drupal 10. While the release was ultimately pushed back to December 14, 2022, you need to know where your website stands for the upcoming upgrade.

For any IT team, changes to a site platform are cause for concern. With less than a year before Drupal 9 hits end-of-life, you need to start planning your preparations for the coming year.

Thankfully, Drupal has remained true to its word about its latest updates avoiding the complex migrations that were required moving from Drupal 7 (but I’ll touch more on that later). Still, the overall impact of Drupal 10 ultimately depends on the condition of your current site.

Platform updates are always cause for uncertainty, and your preparations will vary to navigate a move to Drupal 10. If you start by taking into account where your current site stands, you can best ensure it’s on steady ground for the benefits that lie ahead.

Advantages of upgrading to Drupal 10

The benefits of moving your site to Drupal 10 follow a familiar path. Drupal’s development team doesn’t pack major updates with flashy new features, unlike traditional hardware and software development. Instead, the community continues to refresh the latest version of Drupal with brand new tools.

The arrival of Drupal 10 will clear the system of old, backward-compatible code so the platform runs more efficiently. That way, as work begins to create new tools for version 10, Drupal developers are starting from a clean slate.

The promise of a clean codebase may sound a bit anticlimactic from the perspective of your users. But for developers, it’s an addition by subtraction. Drupal 10 will run much faster than your current platform by losing the clutter required to support out-of-date features.

What can you expect from the next version of Drupal?

Many of the features included with Drupal 10 have already been in use at various points in Drupal 9’s development. Here are a few benefits planned for Drupal’s new release:

  • CKEditor 5: Drupal 9 features version 4 of the open-source JavaScript text editor, which will be deprecated in 2023. This new version is already in use and features a similar-enough interface to be familiar with performance and security enhancements.
  • Updated frontend and admin themes: These features have been available in Drupal 9 but will become the default themes. In addition to offering improved capabilities for migrating a site into Drupal, the new administration theme is more intuitive with better spacing and readability.
  • New package manager: Though potentially unavailable until version 10.1, this feature enables admin users to install modules through the UI. Instead of requiring a developer to FTP modules to a server, you can install them directly from a menu in a way that resembles WordPress extensions.

More good news: Drupal 10 will last longer than 9

One of the third-party technical dependencies of Drupal is its PHP framework, Symfony. Symfony runs on two-year release cycles, which introduces the potential for Drupal to do the same. Drupal 9 uses Symfony 4, which was at the tail end of its development when Drupal 9 was launched. Consequently, as Symfony fell out-of-date in less than two years, so did Drupal 9.

These dependencies were a big part of why Drupal 9 had such a short lifespan as compared with the platform’s history. At one time, versions of Drupal required five to seven years of development.

Drupal’s development team is releasing Drupal 10 on Symfony 6, which was released earlier in 2022. Drupal 10 will last at least four years before the next major version is released. By working to get ahead of schedule with Symfony, Drupal aims to deliver a platform that’s faster and more stable — with staying power.

Will upgrading to Drupal 10 be easy?

It depends.

Drupal 9 will reach its end-of-life sooner than may be ideal, but you face an easier upgrade path to Drupal 10 if your site is currently running version 9.4 or 9.5. Just as with the upgrade from version 8 to 9, updates to Drupal 10 will run “in place.” Rather than needing to migrate to a new platform to upgrade, Drupal 10 is being built inside Drupal 9.

You won’t have to rebuild your site to upgrade to Drupal 10 if you’re up-to-date with its latest version. However, not every organization can keep its website current with every platform release. As with any journey, the road to Drupal 10 entirely depends on where you are now.

If your site is running Drupal 9:

Much like the shift from Drupal 8 to Drupal 9, moving to Drupal 10 can be seamless with the right planning. You need to monitor custom code in any platform update, and Drupal Rector streamlines the process. The module identifies your areas of need, and in many cases will update your code automatically.

You still need an engineer to oversee the upgrade, but Drupal Rector eliminates the tedium of manually updating a bunch of APIs beforehand. As changes are made to Drupal 10, developers are required to add an automated rule to Rector. Consequently, your future upgrades will be even easier.

Once Drupal 10 is released, you have until November 23, 2023 to complete the upgrade before Drupal 9 reaches its end-of-life. At that point, your site will no longer receive security updates from the Drupal community.

If your site is running Drupal 8:

Drupal 8 reached its end-of-life in November 2021, which means your site may be at risk without the community’s support with security patches and bug fixes. To offset that danger, you should use Drupal Rector to identify deprecated code in your Drupal 8 site to automate a portion of your upgrade journey to Drupal 9.

Fortunately, the move from 8 to 9 is an easier transition than you may think. Once your site is up-to-date to version 9.4, then the jump to Drupal 10 should be fairly straightforward upon its release.

If your site is running Drupal 7:

If you’re still on Drupal 7 (or older), your platform is currently scheduled to reach its end-of-life in November 2023. While this date has been extended several times over the past few years, there is no guarantee it will be extended again. However, you’re not alone. According to estimates, more sites are on Drupal 7 than there are on 8 and 9 combined.

Migrating your site from Drupal 7 is a complicated, labor-intensive undertaking, which is why the community extended the platform’s support during the pandemic. However, once Drupal 7 reaches its end-of-life next year, you’ll only be able to receive security updates through Vendor Extended Support. Those organizations remain an option to provide service for your site until 2025 — for a price.

To reduce support expenses, you should start working toward loading your site into Drupal 9.4 or 9.5 as soon as possible rather than waiting for the latest version. Drupal 10 will include migration tools from Drupal 7, but Drupal 9 already includes many of the modules you use. That may no longer be the case after Drupal 10 comes out.

Future-proof your site with an upgrade to Drupal 10

Whether you’re facing a migration from Drupal 7 or the end-of-life for Drupal 9, platform updates require planning to succeed. There is no sense in waiting to get started. If anything, upgrading to Drupal 10 from a much older version may grow more complex the longer you delay.

The days of launching a website and ignoring it for five or 10 years are over. The industry just moves too fast. Fortunately, with the right plan, your organization can get the platform you need to take on whatever lies ahead.

Making the web a better place to teach, learn, and advocate starts here...

When you subscribe to our newsletter!

Nov 16 2022
Nov 16

Menu Block gives you additional functionality around managing the display of menus in block regions. Drupal core allows you to add any menu such as main navigation, footer, etc… into any region and you can control how many menu levels should be displayed. Menu Block gives you additional options such as setting a fixed parent item, changing the block title, and more.

But the one feature of Menu Block I want to show you in this tutorial is the ability to add a custom theme hook suggestion to menus.

Oct 12 2022
Oct 12

In this video, you’ll learn how to build a Drupal site from start to finish. We start by setting up hosting for the site on Cloudways and pointing to a custom domain name.

Once hosting has been set up, we start building out the site. We create a custom content type, configure layout builder, add media functionality, create a bunch of custom view pages and much more.

I’ve broken out the video into sections below with timecodes and extra resources. For the content below to make any sense, you should follow along by watching the video.

Aug 16 2022
Aug 16

Often on a website, you want to control what pages are displayed in search results. You can do this by adding the noindex meta tag to pages. I’m not going to cover why you would want some pages not to appear in the search results; however, I want to show you how to implement it in Drupal.

This tutorial will teach you how to use the Metatag module to control the noindex meta tag.

If you want to learn more about Metatag, read our tutorial “Getting Started with Metatag in Drupal”.

May 27 2022
May 27
Allan ChappellAllan Chappell

Allan Chappell

Senior Support Lead

Allan brings technological know-how and grounds it with some simple country living. His interests include DevOps, animal husbandry (raising rabbits and chickens), hiking, and automated testing.

May 27, 2022

Have you ever found yourself needing to share custom dependencies across several sites? Maybe even for the same client? There are several methods of traversing this workflow, especially if you work in the Drupal ecosystem. The ideology of upstreams, distributions, and multi-sites are something to consider using. However, the fun lies in the challenge of determining how to scale an architecture

Create a custom packagist

The ingredients for creating a custom packagist, a repository of dependencies used by Composer, are surprisingly easy to come by. Keep in mind that a private packagist can be obtained through a hosted service at packagist.com. In our case, we already had the tooling readily available, so we decided to go the custom packagist route

The goal of this article is to give you some ideas on how to host a solid packagist for a team, organization, or client while describing how the Four Kitchens team came up with a fun and creative solution to provide this functionality using the tools our client had on hand. I hope to accomplish this by:

  • Sharing our motivation behind choosing this solution
  • Identifying the ingredients need to cook up the workflow
  • Explain baseline hosting, but elaborate on what you could do if so inclined
  • Layout how we set up automation around the workflow to make our lives easier

Let’s begin.

Motivation

On one client project, we found that we had enough private custom dependencies we were sharing with a private distribution that we needed to scale beyond editing the individual composer.json repositories listing for each site. If we were using an upstream setup, this could be accomplished using Composer Merge Plugin. In this case, however, it made sense to create a custom packagist. Keep in mind, if we didn’t do this, each of our composer.jsons would have had 11 custom packages and 11 VCS entries in the repositories section of our composer.json. That would need to grow with each additional dependency we added to our distribution. We currently maintain 20 sites on this distribution. Our policy is to have code review for every change to a site. So making changes to 21 repos (the distribution and all the downstream sites) was a development time suck

If you are here, you probably know the answer to the question, “Why can’t Composer load repositories recursively?” but if you don’t, check out this great explanation. In short, the repositories section of a composer.json cannot inherit that section from a dependency’s composer.json. So it’s up to the individual projects to make sure they have the right packages when it comes to those custom packages that our distribution requires

We might have been able to reduce our custom dependencies by relying on another hosted packagist such as asset-packagist.org, or working to make other dependencies publicly available. However, providing our own packagist maintained specifically for the client’s needs brought us performance gains over the other solutions and allows us to more closely vet our frontend library dependencies. It allows us to make a single “repositories” entry at the packagist level, and that gets pulled down by all of our sites that are pointing at it. This means less code editing on a per-site basis

So here we are, using an easily maintained solution, and reaping the benefits of performance, scalability, and increased developer productivity, while keeping our client’s ecosystem private. We didn’t even need that much to get started!

Ingredients

Things you will need to get started:

  • Satis, a rudimentary static packagist generator written in PHP using Composer as a library.
  • A repository to house the custom dependencies you want to put in your packagist. Think: all the items you currently have in your repositories section of your composer.json. This isn’t strictly a “must,” but it makes automation possible.
  • A place to host static HTML and JSON files. Anything web accessible. HTTPS is preferred, but curl can work under other protocols. You can get pretty creative here.
    • Cheap hosting service
    • Spare Droplet, Linode, or AWS EC2 instance
    • S3 bucket
    • GitHub
    • FTP
  • Something to build the packagist on dependency update like:
    • GitHub Actions
    • CircleCI
    • Travis
    • Cron
    • A manual implementation like running a command via SSH

Our implementation looks like this:

  • Repository: GitHub
  • Hosting: S3 bucket
  • Builder: CircleCI

These were all resources we were already using. I’ll go into the specifics on how our build works with some suggestions on alternatives.

It’s pretty simple to set up Satis. There is some decent documentation on Satis at GetComposer.org and on GitHub. What I say here may be a bit of a repeat, but I’m describing an opinionated setup intended to allow for testing and committing changes. This is a necessity when multiple developers are touching the same packagist and you need accountability

Before I dive into the specifics of our setup, I want to mention that if you feel you don’t need this level of control, testing, and revision history, Satis can be set up as a living stand-alone app. You can host it as either a docker container or on a hosting platform. In both of these options, developers would live edit and maintain the packagist via command line by default. You can, however, install a graphical frontend using something like Satisfy

To set Satis like Four Kitchens has, follow the steps below. Code is below if you need examples of how it might look.

  1. Create a new repository.
  2. Initialize a new composer project using composer init.
  3. Require Satis composer require composer/satis.
  4. Add a script to your composer.json to run the full build command.
  5. Add a packages directory to the project with a .gitkeep file. mkdir packages && touch packages/.gitkeep.
  6. Add a .gitignore to ignore the vendor folder and generated package files.
  7. Consider setting up a Lando instance to serve your packagist for testing.
  8. Create satis.json just like you normally would a standard composer.json with the repositories section containing all your packages, repos, and packagists you want available to the projects consuming it.
  9. Add "require-all": true below the repositories section of satis.json. There’s more about usage of require-all versus require in the Satis setup documentation. Use what fits your needs, but if you are adding individual packages instead of entire packagists to your satis.json, require-all is likely all you need.

Your repo could look something like this:

composer.json

{rn  "name": "mycompany/packages",rn  "require": {rn    "composer/satis": "^1.0" 
  },rn  "scripts": {rn    "build": "./vendor/bin/satis build satis.json packages" 
  }
}

satis.json

{rn  "name": "mycompany/packages",rn  "homepage": "https://packages.mycompany.com",rn  "repositories": [rn    { "type": "vcs", "url": "https://github.com/mycompany/privaterepo" },rn    { "type": "vcs", "url": "http://svn.example.org/private/repo" },rn    { "type": "package", "package": [rn      { "name": dropzone/dropzone", "version": "5.9.2", "dist": { "url": "https://github.com/dropzone/dropzone/releases/download/v5.9.2/dist.zip", "type": "zip" }}
    ]}
  ],rn  "require-all": truern}

.lando.yml

name: mycompany-packagesrnrecipe: lemprnconfig:
 webroot: packagesrn  composer_version: 2rn  php: '7.4'

.gitignore

vendorrnpackages/*rn!packages/.gitkeep

From here, run lando start && composer install && composer build. Now, go to another project and add your test packagist to that project. Additionally, add "secure-http":false, to the config section since Lando’s https certificate is insecure by default. Lastly require one of the packages you added to satis.json above.

{rn  ..
 "repositories": [rn    {rn      "type": "composer",rn      "url": "http://mycompany-packages.lndo.site",rn    }
    ..
 ],rn  "require": {rn    "dropzone/dropzone": "^5.9.2" 
  },rn  ..
 "config": {rn    ..
   "secure-http": falsern  }
  ...rn}
 

At this point you should be greeted with a successfully built project and have a local instance of your packagist going. When you are done testing, stop Lando and switch your repository entry in the other project to your packagist’s public URL. Commit all your changes and push up!

Your next step is getting your packagist off your local and out where everyone can use it.

Now you can simply copy the files in your packages folder and put them somewhere web accessible. I really want to drive this point home. The entirety of your packagist is simply the contents of that folder and nothing else. The things that make this so complicated are the processes around automating and updating this packagist

You could, for example, now take these files you created and host them anywhere someone can curl to. This means http, ftp, sftp are available to you, to name a few. If you aren’t worried about privacy, you can even go so far as placing these in the webroot or even the sites/default/files folder in your company’s Drupal site. This is a good option if you are strapped for domain names or running a small operation. You would then make sure to copy those files any time someone makes a change to any of the packages that are a part of your packagist.

If that’s all you are looking for, you can stop here. You’ve done it! You now have a custom packagist and the rest of the workflow may not matter to you. However, if you want some more ideas and want to build out a more robust automated development workflow, keep reading. The ideas get interesting from here

If you wanted to be creative, you could probably remove the line from .gitignore that excludes the packages folder, commit it, and set your packagist URL to something like https://raw.githubusercontent.com/mycompany/packages/main/ and set up an Accept and Authorization header in your packagist. You can see an example on how to use headers in your packagist at GetComposer.org and below with our S3 example

In fact, the composer.json setup described for the creative Github example is really similar to what we did, except we used a workaround recommended by AWS for restricting access to a specific HTTP referer. Our client wanted the extra security so not just anybody could go and poke around at the packages and versions we had available

In our example, we created a normal bucket, and assigned a CNAME to it with a nice domain name. The CNAME is optional but makes it more “official” and allows us to move the packagist later without disrupting the developer workflow too much. We then added a policy to only accept connections from calls with a referer that is our secret key. A referer doesn’t have to be a website. In our case it’s a lengthy hash that would be difficult to guess. This too is optional, but if you are looking for that extra level of security, it’s a good option to consider. Note that you should not add spaces between the colon and the token when using this policy. Our repositories entry in our projects looks like:

{rn  ..
 "repositories": [rn    {rn      "type": "composer",rn      "url": "https://packages.mycompany.com",rn      "options": {rn        "http": {rn          "header": [rn            "Referer:" 
          ]rn        }
      }
    }
    ..
 ],rn  ...rn}

And that’s it. We copy the files up to the bucket using AWS CLI, and it’s published

Now we need to automate the workflow and get what’s in our hosting location to update automatically.

Building and automation

I’ve pointed out that, if you are willing, you can put Satis somewhere, generate the packagist files, upload them somewhere web accessible, and be ready to roll. This isn’t so different from static site generators like Jekyll or Hugo. However, we add in CI for automation, and revision control for accountability so that we can take the “error” out of human command crunching. It’s worth mentioning again that this is super important when you have entire teams modifying this packagist

In our example, I’m using CircleCI. You can do the same with GitHub Actions, Jenkins, or even run on a cron job, provided you are okay with a time-based cadence. You might even do more than one of these. Our CircleCI job looks like this:

.circleci/config.yml

version: 2.1rnorbs:
 php: circleci/[email protected]  aws-cli: circleci/[email protected]:
 run_dependency_update:
   default: truern    type: boolean
jobs:
 create_packagist:
   executor:
     name: php/defaultrn      tag: '7.4.24'rn    steps:
     - checkoutrn      - aws-cli/setuprn      - php/install-composerrn      - php/install-packagesrn      - run:
       name: Set Github authenticationrn        command: composer config u002du002dglobal github-oauth.github.com "$GITHUB_TOKEN";
      - run:
       name: Link auth for satisrn        command: mkdir ~/.composer; ln -s ~/.config/composer/auth.json ~/.composer/auth.jsonrn      - run:
       name: Build packagist json filesrn        command: composer buildrn      - store_artifacts:
       path: packagesrn      - run:
       name: Copy packagist to aws
       command: aws s3 cp u002du002drecursive ./packages/ s3://packages.mycompany.com/rnworkflows:
 version: 2rn  packagist:
   when: << pipeline.parameters.run_dependency_update >>
    jobs:
     - create_packagist:
       filters:
         branches:
           only:
             - mainrn              - master

There’s a lot to unpack here. I’m using pipeline parameters, because a requirement for me is to be able to call this job when another project updates. This functionality allows me to call this CircleCI job using an API call. I also use CircleCI orbs to make grabbing AWS CLI and getting a PHP environment easy

The meat of the job is the same as what you were doing during testing: running the build command we put in our composer.json. Since some of our repositories are private, we also have to make sure that composer has access creating a GitHub token. Then we copy everything to the bucket using AWS CLI. In our case, we have some behind-the-scenes environment variables defining our keys: AWS_ACCESS_KEY_ID, AWS_DEFAULT_REGION, and AWS_SECRET_ACCESS_KEY

From another project’s perspective, I’m still using CircleCI to run the API call. You can do this really easily in other CI environments, too.

version: 2.1
jobs:
 update-packagist:
   docker:
     - image: cimg/base:2021.12rn    steps:
     - run: "curl u002du002drequest POST u002du002durl https://circleci.com/api/v2/project/github/mycompany/packages/pipeline u002du002dheader "Circle-Token: $CIRCLE_TOKEN" u002du002dheader "content-type: application/json" u002du002ddata '{"parameters":{"run_dependency_update":true}}'" 
workflows:
 build:
   jobs:
     - update-packagist

That’s it. I add this job to every project that’s a VCS entry in our satis.json (provided I have access) and let it go to town. If you find yourself with other dependencies out of your control, consider adding a cron job somewhere or a scheduled pipeline trigger. You are done!

Final thoughts

This workflow can be as easy or as difficult as you want to make it given a few factors like:

  • How often will it change
  • How many people touch it
  • How up-to-date it needs to be

There are a lot of ideas here, with lots of knowledge representing several different application architectures for organizations that have multiple projects or sites. If you don’t want to bother with the home-brewed solution, dish out the cash and get a private packagist. The cost may be worth it

However, if you are already using all the necessary services and have a team of knowledgeable individuals like ours, consider maintaining your own packagist that you can host anywhere. You may find it a productive, performant, and most of all joyful and exciting experience that will bring value to your upstream, distribution, or multi-site setup.

Making the web a better place to teach, learn, and advocate starts here...

When you subscribe to our newsletter!

Apr 09 2021
Apr 09
Pierce Lamb11 min read

Apr 9, 2021

This blog covers how to set up and use Personalized Paragraphs. If you’re looking for how Personalized Paragraphs was built, check out Personalized Paragraphs: Porting Smart Content to Paragraphs for Drupal. If you have any questions you can find me @plamb on the Drupal slack chat, there is also a #personalized_paragraphs channel.

You’ve visited the Personalized Paragraphs Module Page and downloaded the module. In doing that, composer should have also downloaded Smart Content, Entity Usage, and Paragraphs. You can verify this by visiting /admin/modules and checking that the modules are there. If they are not enabled, make sure Entity Usage, Paragraphs/Paragraphs Library, Smart Content, Smart Content Browser and Personalized Paragraphs are enabled. Now what?

Segment Sets

The entry point for using Smart Content is the Segment Set. Segment Sets define how you want to segment traffic via given conditions. We’ll be using the conditions out of the Smart Content Browser module as an example for this blog. As such, imagine that you want to segment traffic based on browser width. For your first segment, perhaps you want to segment visitors based on if their browser is greater than 1024px wide, or less than 1024px (I know this is a silly example, but it is nice for basic understanding). So based on the 1024px breakpoint, you want users above this width to see a certain experience and users below it to see a different one. We’ll define this in a Segment Set.

  • Visit /admin/structure/smart_content_segment_set
  • Click ‘Add Global Segment Set’.
  • Give your segment set a label like ‘Browser Width Segments.’
  • Click ‘Add Segment’
  • Give your segment a title like ‘less-than-1024px’
  • In the condition dropdown look for the ‘Browser’ header and select ‘Width’
  • Click ‘Add Condition’
  • Set the width to ‘less than’, ‘1024’px
  • Click ‘Add Segment’
  • Give your segment a title like ‘greater-than-1024px’
  • In the condition dropdown look for the ‘Browser’ header and select ‘Width’
  • Click ‘Add Condition’
  • Set the width to ‘greater than’, ‘1024’px
  • Click ‘Add Condition’
  • In the label area, add ‘Default Segment’
  • Under ‘Common’ select the condition ‘True’
  • Check ‘Set as default segment’
  • Save

With Browser Width Segments in place, we now have a way of segmenting traffic based on the width of the users browser (I recognize we only needed the less-than-1024px and default segments here, but it helps for learning to show all conditions). Users with browsers less than 1024px wide will see one piece of content and users with browsers greater will see a different piece. We added the default segment for clarity; in a situation where a user does not match any segment, it will display.

Personalized Content

Now we have to decide what content we’d like to personalize based on browser width. Let’s say we have a content type called ‘Homepage’ and we want to personalize the banner area of our homepage. Ideally we would use a paragraph to represent the banner and display different banners based on browser width. The first thing we want to do is create the paragraph that will be our banner.

  • Open the homepage node’s structure page (or whichever node you’re personalizing)
  • Take note of the fields it currently contains that constitute the banner
  • In another tab or window, visit /admin/structure/paragraphs_type/add
  • Give it a name like ‘Personalization — Homepage Banner’
  • Make sure to check the box that says ‘Allow adding to library.’ If this box is missing, Paragraphs Library likely is not enabled, check /admin/modules and see if its enabled
  • Inside your new Paragraph, re-create each field that represents the banner from the tab you have open on the homepage.
  • I typically add a boolean field to check if the paragraph is a personalized paragraph and
  • I typically add a campaign ID text field to add a campaign to the paragraph which can be pushed into the dataLayer
  • Neither of these are necessary for this tutorial, but may be to you in the future
  • Save your paragraph
  • Now visit /admin/content/paragraphs (added via Paragraphs Library)
  • Click ‘Add Library Item’
  • Add a label like ‘Homepage Banner — less-than-1024px’
  • Click the paragraph dropdown and select ‘Personalization — Homepage Banner’
  • Fill in the fields
  • Save
  • Click ‘Add Library Item’
  • Add a label like ‘Homepage Banner — greater-than-1024px’
  • Click the paragraph dropdown and select ‘Personalization — Homepage Banner’
  • Fill in the fields
  • Save
  • Do this one more time, but for ‘Homepage Banner — Default Banner’

If you’ve completed these steps, you now have 3 personalized banners that match the 3 segments we created above. Okay, so we have our segment set and our personalized content, now what?

Personalized Paragraphs

The next step is adding a Personalized Paragraph to the node you’re personalizing. In order to do that, we:

  • Add a new field to our homepage node.
  • Select the Add a New Field dropdown, find the ‘Reference Revisions’ header and select ‘Paragraph’
  • Give it a name like ‘Personalized Banner’
  • Save
  • On the ‘Field Settings’ page, under ‘allowed number of values’ select ‘Limited’ — 1 (this may change in the future)
  • Now, on the ‘Edit’ page for this new field, under the ‘Reference Type’ fieldset find and select ‘Personalized Paragraph’
  • Save

If you’ve completed all of these steps (note that you may need to flush caches), you can now load an instance of your homepage node and click ‘edit’ or add a new homepage node and you should see something that looks like this on the page (if not, click the ‘Add Personalized Paragraph button’):

The first step is to give your personalized paragraph a name that is unique within the page like ‘personalized_homepage_banner.’ It is possible to continue while leaving this field blank (and you can change it later), it is used only for identifying a personalized paragraph in front end code. Next we should find our segment set, ‘Browser Width Segments’ in the segment set dropdown and press ‘Select Segment Set.’ After it finishes loading, we should see 3 reactions which match the 3 segments we created earlier. In the paragraph dropdowns, we’ll select the respective paragraph we made for each segment, for e.g. ‘Homepage Banner — less-than-1024px’ will go in the segment titled ‘SEGMENT LESS-THAN-1024PX.’ After saving we will be redirected to viewing the saved page.

You may or may not see any change to your page at this point, it depends on your template file for this page. The content itself will be behind content.field_personalized_banner in your respective template file. One way to see that it’s on the page is to search the html for whatever you named the paragraph type used by Paragraphs Library. In our example it would be personalization-homepage-banner. This string should be found in the classes of an element in the HTML. The key, however, is that you now have access to the winning paragraph in your template file. Here’s an example of how we might display field_personalized_banner in a template file:

{% if content.field_personalized_banner %}


{{ content.field_personalized_banner }}




{% endif %}

Assuming you are seeing the content of your Personalized Paragraphs on the page, you can change your browser size to either larger than 1024px or less than, refresh, and you should get the other experience. If you don’t this is likely because Smart Content stores information in local browser storage regarding the experience you first received (specifically the _scs variable); it checks this variable before doing any processing for performance reasons. One way around this is to load a fresh incognito window, resize it to the test width and then load your page, another is to go into your local storage and delete the _scs variable.

There are many ways to segment traffic in Smart Content beyond browser conditions, Smart Content provides sub modules for Demandbase, UTM strings and 6sense. I’ve also written a module for Marketo RTP which we will be open sourcing soon. I wrote a blog about it here which can be used as a guide for writing your own connector.

At this point in using Personalized Paragraphs, there are a lot of ways you could go with displaying the front end and you definitely don’t need to read the remainder of this blog. I’ll cover the way my org displays it, but I’ll note that there isn’t some sort of best practice, it is just what works for us. You absolutely do not need to use Personalized Paragraphs in this manner and I encourage you to experiment and find what works for you.

Front end processing

You may have noticed that our personalized banner field is ‘overwriting’ the existing banner fields on our homepage node (as opposed to replacing). That is, our original banner fields and personalized banner field are doing the same ‘thing’ and now we have two of them on the page. The reason for this decision is based on how Smart Content works. When the page is loaded Smart Content decides which paragraph has won then uses ajax to retrieve that paragraph and load it onto the page. If we replaced the original banner with the Personalized Paragraph, the banner would appear to ‘pop-down’ after the page had begun loading whenever the ajax returned. We felt that this experience was worse than the small performance hit of loading both banners onto the page (it also provides a nice fall back should anything fail). This ‘pop-down’ effect of course only occurs when you’re personalizing a portion of the page that contributes to the flow; if it was an element that doesn’t you’d see more of a ‘pop-in’ effect which is less jarring.

Because we are loading two of the same ‘things’ onto the page, we need to use javascript to decide which one gets displayed. We need a way to inform some JS code of whether or not a decision paragraph is on the page (else display fallback) and also differentiate between which decision paragraph we’re operating on (in case there is more than one on the page). The entry point for this goes back to how Personalized Paragraphs was built, to a more controversial point near the end. In a hook_preprocess_field__entity_reference_revisions function we load the Personalized Paragraph onto the page then execute this code:

$para_data = [
'token' => $token,
];
$has_name = !$para->get('field_machine_name')->isEmpty();
$name = $has_name ? $para->get('field_machine_name')->getValue()[0]['value'] : '';
$variables['items'][0]['content']['#attached']['drupalSettings']['decision_paragraphs'][$name] = $para_data;

In essence, this code stores the information we’re after in drupalSettings so we have access to it in javascript. We get a key, [‘decision_paragraphs’] which contains an associative array of personalized paragraphs machine names which have their decision tokens as values. With this information we can now manipulate the front end as we need.

Before I show how we display the default or the winner on the front end, I want to reiterate that this is not a best practice necessarily; it’s just a design decision that works for us. All of the below code is stored in a custom module specifically for personalization. First, we create a js file called ‘general_personaliztion.js’ that will be attached on any page where Personalized Paragraphs run. To continue following our example, we add this at the top of our homepage template:

{{ attach_library(‘/general_personalization_js’) }}

Following the style much of the smart content javascript is written in, this file defines an object that can be accessed later by other JS files:

Drupal.behaviors.personalization = {};

Drupal.behaviors.personalization.test_for_decision_paragraph =
function(paragraphs_and_functions, settings) {
...
};</span>

This first function will test if a decision paragraph is on the page:

function(paragraphs_and_functions, settings) {
if(settings.hasOwnProperty('decision_paragraphs')) {
paragraphs_and_functions.forEach((display_paragraphs, paragraph_name) => {
if (!settings.decision_paragraphs.hasOwnProperty(paragraph_name)) {
var show_default = display_paragraphs['default'];
show_default();
}
});
} else {
paragraphs_and_functions.forEach((display_paragraphs, paragraph_name) => {
var show_default = display_paragraphs['default'];
show_default();
});
}
};

It first tests to see if the key ‘decision_paragraphs’ exists in the passed settings array, if it doesn’t it takes the passed paragraphs_and_functions Map, iterates over each Personalized Paragraph machine name, grabs the function out of it that displays the default experience and executes it. If ‘decision_paragraphs’ does exist, it does the same iteration checking to see if the Map of machine names passed to it are represented in that decision_paragraphs key, if not it gets their default function and executes it. This means we can change machine names, delete personalized paragraphs etc and guarantee that the default experience will display no matter what we do. So how do we call this function with the right parameters?

We create a new file, personalization_homepage.js which is now attached directly underneath the previous file in the homepage template:

{{ attach_library('/general_personalization_js') }}
{{ attach_library(‘/personalization_homepage') }}

This file will only ever be attached to the homepage template. Inside this file, we create an object to represent the default and personalized experiences for our banner:

var banner_display_functions = {
'default': personalization_default_banner,
'personalized': personalization_set_banner
};

The values here are functions defined elsewhere in the file that execute the JS necessary to display either the default or personalized experiences. Next we create a Map like so:

var paragraphs_and_functions = new Map([
['personalization_homepage_banner', banner_display_functions],
]);

The map has personalized paragraph machine names as keys and the display object as values. You can imagine with another Personalized Paragraph on the page, we’d just add it as a member here. An unfortunate side effect of this design is the hardcoding of those machine names in this JS file. I’m sure there is a way around this, but for performance reasons and how often we change these paragraphs, this works for us. With this in place, calling our test_for_decision_paragraphs function above is straight forward:

Drupal.behaviors.personalizationHomepage = {
attach: function (context, settings) {
if (context === document) { Drupal.behaviors.personalization.test_for_decision_paragraph(paragraphs_and_functions, drupalSettings);
}

...
}

This populates test_for_decision_paragraph with the correct values. It is inside context === document to ensure that it executes at only the right time (without this control the default banner will ‘flash’ multiple times). So now the code is in place to test for a decision paragraph and display the default experience if it is not there. What about displaying the winning personalized experience?

We create another function inside general_personalization.js, ‘test_for_winner’:

Drupal.behaviors.personalization.test_for_winner =
function(current_decision_para_token, paragraphs_and_functions) {
//Iterate over the display blocks, matching display block UUID to decision_block_token
paragraphs_and_functions.forEach((display_paragraphs, paragraph_name) => {
if(drupalSettings.decision_paragraphs.hasOwnProperty(paragraph_name)) {
var decision_paragraph_token = drupalSettings.decision_paragraphs[paragraph_name].token;
if (decision_paragraph_token === current_decision_para_token) {
var show_winner = display_paragraphs['personalized'];
show_winner($(event.detail.response_html))
}
}
});

};</span>

(note it may be event.detail.data for you)

Remember how we talked about that hook_preprocess_hook in which we attached the decision token of a personalized paragraph keyed by machine name? Our test_for_winner iterates those machine names extracting the decision token and comparing it to the decision token of the current winning paragraph; when a match is found, it uses the winning machine name to find and run the function that displays the personalized experience and executes it. So how do we call this function? Inside the same Drupal.behaviors.personalizationHomepage{…} in personalization_homepage.js we add:

window.addEventListener('smart_content_decision', function (event) {
Drupal.behaviors.personalization.test_for_winner(event.detail.decision_token, paragraphs_and_functions);
});
}

(note: we’ve made some edits to the broadcasted event object internally. I invite you to print this object and see what it contains.

And here is where we touch one of the areas Smart Content creates an offering for front end processing: when a winner is chosen by Smart Content, it broadcasts a ‘smart_content_decision’ event which contains a bunch of information including the decision token of the winning content. This is what test_for_winner uses to compare to existing personalized paragraphs to select a display function. Smart Content will broadcast this event for every winning paragraph (e.g. if we have n personalized paragraphs on the page, the event will broadcast n times with each paragraph’s winning token) and test_for_winner allows us to know which paragraph it’s currently broadcasting for and allows us to execute that paragraphs personalized display function.

There are a number of other cool things we can do with this broadcasted event, for example, pushing the campaign name of the winning paragraph into the dataLayer, but I will leave this for another time.

I hope that the example we’ve used throughout has helped you to better understand how to use Personalized Paragraphs and the brief tour of our front end design has given you a starting point.

If you’re looking for how Personalized Paragraphs was built, check out Personalized Paragraphs: Porting Smart Content to Paragraphs for Drupal. If you have any questions you can find me @plamb on the Drupal slack chat, there is also a #personalized_paragraphs channel.

Mar 15 2021
Mar 15
Pierce Lamb24 min read

Mar 15, 2021

This blog covers how the Personalized Paragraphs module was built, if you’re looking for how to use Personalized Paragraphs, check out the How To Use Personalized Paragraphs blog. If you have any questions you can find me @plamb on the Drupal slack chat, there is also a #personalized_paragraphs channel.

In 2020, I was tasked by my organization with finding or developing a personalization solution for our Drupal-based website. By personalization, I mean a tool that will match anonymous users into segments and display a certain piece of content based on that segmentation. Brief searching led me into the arms of Smart Content, a platform for personalization developed by the clever folks over at Elevated Third. Smart Content is a toolset for managing segmentation, decisions, reactions etc all within the Drupal framework. As a general platform, it makes no assumptions about how you want to, say, display the content to the user or pass results back to your analytics platform. However, it comes with a number of sub modules so you don’t need to develop these solutions on your own. Out of the box, Smart Content includes ‘Smart Content Block’ which allows you to utilize Drupal’s Block interface to manage your personalized content. There are a number of reasons this was a good idea, but it also presented some difficulties (at least for us).

After installing Smart Content, the most straightforward way to use personalized blocks was to create a Smart Content Decision Block in the block layout builder. However, to get control over where the block was placed (i.e. instead of in a region across many pages), we needed to disable the block, load it independently in a preprocess and attach it to the relevant page’s theme variables; a bit cumbersome. I recognize that there are other options like Block Field out there, but this appeared to be the most out-of-the-box way to use Smart Content Block. As a block-based solution, we found that we had to make changes to the blocks on prod then drag the changes back to our development branches and environments because exporting block config would cause UUID issues on merge. As our use cases grew, this became more cumbersome. In addition, my organization heavily leans on Paragraphs to power content inside of Nodes (and very sparingly uses blocks). After about 6 months of using Smart Content we decided we should see if we could utilize Paragraphs to power personalization.

The funny thing about Paragraphs is that they don’t ‘float free’ of Nodes in the same way that blocks do; at their core they are referenced by Nodes. Or at least I thought. When we discussed using paragraphs I did some brief research and saw that others had successfully attempted porting Smart Content to Paragraphs. Upon testing this module, I found that it relied on an old, fairly different version of Smart Content and also included a lot of extra code relevant to that organization’s use case. Further, it lacked the extremely well-thought interface for adding a segment set and reactions that’s contained in Smart Content Block. However, the key insight its author’s included was the use of the Paragraphs Library. Paragraphs Library is an optional sub module of Paragraphs that was quietly added in 2018 and allows users to create Paragraphs that ‘float free’ in just the way we’d need to personalize them. With this in hand, I thought I would try porting the experience of Smart Content Block to Paragraphs.

The Supporting Structure

The porting process began by digging into the smart_content_block sub-module of Smart Content. The entry point is Plugin/Block/DecisionBlock.php which appeared to be an Annotated Block plugin. When constructed, it had a control structure which created a further plugin ‘multiple_block_decision’ which I found defined in Plugin/smart_content_block/Decision. Further, in one of MultipleBlockDecision’s functions, it creates an instance of the display_blocks plugin which was defined in Plugin/smart_content_block/Reaction:

I knew that these three files combined must work together to create the nice user experience that administrating smart_content_block currently has. So I set about to emulate them, but with Paragraphs instead of blocks.

Paragraphs did not come pre-packaged with an obvious Annotation plugin to achieve what I wanted, so I created one. I sought to mimic the one included with Blocks and thus in the Annotation/PersonalizedParagaph.php file, I defined it as: class PersonalizedParagraph extends Plugin {…}. With this in hand I could now create a Plugin/PersonalizedParagraph/DecisionParagraph.php that mimicked smart_content_block’s DecisionBlock:

/**
* Class Decision Paragraph.
*
* @package Drupal\personalized_paragraphs\Plugin\PersonalizedParagraph
*
* @PersonalizedParagraph(
* id = "personalized_paragraph",
* label = @Translation("Personalized Paragraph")
* )
*/

However, before I defined class DecisionParagraph, I knew I needed to extend something similar to BlockBase and implement ContainerFactoryPluginInterface just like DecisionBlock.php does. I opened Core/Block/BlockBase.php and attempted to mirror it as closely as I could. I created personalized_paragraph/Plugin/PersonalizedParagraphBase.php. Here is the comparison between the two:

abstract class BlockBase extends ContextAwarePluginBase implements BlockPluginInterface, PluginWithFormsInterface, PreviewFallbackInterface {

use BlockPluginTrait;
use ContextAwarePluginAssignmentTrait;
...
}</span>abstract class PersonalizedParagraphsBase extends ContextAwarePluginBase implements PersonalizedParagraphsInterface, PluginWithFormsInterface, PreviewFallbackInterface {

use ContextAwarePluginAssignmentTrait;
use MessengerTrait;
use PluginWithFormsTrait;
...
}</span>

And other than cosmetic function name changes, the classes are largely the same. They implement PluginWithFormsInterface which is defined as:

Plugin forms are embeddable forms referenced by the plugin annotation. Used by plugin types which have a larger number of plugin-specific forms.

Which certainly sounds like exactly what we need (a way to plug one form into another). You may have noticed one difference though, I had to create an interface, PersonalizedParagraphsInterface to mirror BlockPluginInterface. Again, these two files are largely the same, I’ll leave it to the reader to check them out.

At this point, I now had the beginning of a DecisionParagraph.php and the files that back it, Annotation/PersonalizedParagraphs.php, PersonalizedParagraphsBase.php and PersonalizedParagraphsInterface.php. Since DecisionParagraph.php is a plugin, I knew I’d need a Plugin Manager as well. My next step was to create a /Plugin/PersonalizedParagraphsManager.php. This file is as default as it gets when it comes to a Plugin Manager:

class PersonalizedParagraphsManager extends DefaultPluginManager {

/**
* Constructs a new ParagraphHandlerManager object.
*
* @param \Traversable $namespaces
* An object that implements \Traversable which contains the root paths
* keyed by the corresponding namespace to look for plugin implementations.
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
* Cache backend instance to use.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler to invoke the alter hook with.
*/
public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, ModuleHandlerInterface $module_handler) {
parent::__construct(
'Plugin/PersonalizedParagraph',
$namespaces,
$module_handler,
'Drupal\personalized_paragraphs\Plugin\PersonalizedParagraphsInterface',
'Drupal\personalized_paragraphs\Annotation\PersonalizedParagraph'
);

$this->alterInfo('personalized_paragraphs_personalized_paragraphs_info');
$this->setCacheBackend($cache_backend, 'personalized_paragraphs_personalized_paragraphs_plugins');
}
}</span>

You can see in its constructor that it gets created with all the key files we need to create the DecisionParagraph plugin. Note that a plugin manager also requires a module.services.yml which I defined as follows:

services:
plugin.manager.personalized_paragraphs:
class: Drupal\personalized_paragraphs\Plugin\PersonalizedParagraphsManager
parent: default_plugin_manager

I knew at this point I must be really close. However if you recall the screenshot above, I was still missing mirrors of MultipleBlockDecision and DisplayBlocks. My next step was to create /Plugin/smart_content/Decision/MultipleParagraphDecision.php and /Plugin/smart_content/Reaction/DisplayParagraphs.php. While over the course of building Personalized Paragraphs these files would get edited, the class stubs would be identical. This is largely because Smart Content creates Annotated Plugin types for many of its core functions which makes it extremely easy to extend. Comparing MultipleBlockDecision and MultipleParagraphDecision:

/**
* Provides a 'Multiple Block Decision' Decision plugin.
*
* @SmartDecision(
* id = "multiple_block_decision",
* label = @Translation("Multiple Block Decision"),
* )
*/
class MultipleBlockDecision extends DecisionBase implements PlaceholderDecisionInterface {
...
}
/**
* Provides a 'Multiple Paragraph Decision' Decision plugin.
*
* @SmartDecision(
* id = "multiple_paragraph_decision",
* label = @Translation("Multiple Paragraph Decision"),
* )
*/
class MultipleParagraphDecision extends DecisionBase implements PlaceholderDecisionInterface {
...
}

And this is isomorphic in the case of DisplayBlocks.php and DisplayParagraphs.php. With MultipleParagraphDecision and DisplayParagraphs in place, I just had to go change where they were created from multiple_block_decision -> multiple_paragraph_decision in DecisionParagraph and display_blocks -> display_paragraphs in MultipleParagraphDecision. At this point, my /src/ folder structure looked like this:

Very close to the structure of smart_content_block. Okay so now I have all this plugin code defined, but how will Drupal know when and where to create instances of personalized_paragraph?

When and Where does this run?

The first step was to create a Paragraph Type called ‘Personalized Paragraph’. Simple enough. At the time I created this, I did not think it would need any fields, but we will discuss later why I did. The Personalized Paragraph Type would be the entry point for a Paragraph inside a node to basically say “hey I’m going to provide personalized content.”

Our first ever use case for personalized content was our homepage banner, so to test my code, I created another Paragraph type called Personalization — Homepage Banner (the reason for this naming convention is that you can imagine many personalization use cases all being grouped together by starting with ‘Personalization -’). The key switch I needed to flip in creating this test Paragraph was this:

“Allow adding to library” meant that this specific Paragraph Type could have members created in the Paragraphs Library that ‘float free’ from any node. With that flipped, I just needed to mirror the fields that produce our homepage banner in this Paragraph Type. Now I could load the Paragraphs Library, /admin/content/paragraphs, and create every personalized paragraph I needed to support personalizing the homepage banner. This step is discussed in more detail in the How To Use Personalized Paragraphs blog.

Now, in order to test the ‘when and where’ question above, I loaded our ‘homepage’ content type and added a new field, ‘Personalized Banner’ that referenced a ‘Paragraph’:

And in the Paragraph Type to reference I selected Personalized Paragraph:

The Personalized Banner field was now telling our homepage node that it would contain personalized content. With this structure in place, I could now programmatically detect that a personalized_paragraph was being edited in the edit form of any homepage node and displayed when a homepage node was viewed. Further, I’d be able to use the Paragraphs I’d added to the library to display when different Smart Content segments were matched.

The Form Creation Journey

I wanted to get the node edit form working first, so in personalized_paragraphs.module, I needed to detect that a Paragraph of type personalized_paragraph was in a form. I created a:

function personalized_paragraphs_field_widget_entity_reference_paragraphs_form_alter(&$element, FormStateInterface &$form_state, $context){...}

Which is a form_alter hook that I knew would run for every Paragraph in a form, so I immediately needed to narrow it to personalized_paragraphs Paragraphs:

$type = $element['#paragraph_type'];
if($type == 'personalized_paragraph'){
...
}
}

So I was hooked into any form that contains a Personalized Paragraph. This captures the ‘when and where’ that I needed to load the plugin code defined above. So the next step was to load the plugin inside our control structure:

if ($plugin = personalized_paragraphs_get_handler('personalized_paragraph')) {
$build_form = $plugin->buildConfigurationForm([], $form_state);
$element['subform']['smart_content'] = $build_form;
}

And the code for _get_handler:

function personalized_paragraphs_get_handler($plugin_name) {
$plugin_manager = \Drupal::service('plugin.manager.personalized_paragraphs');
$definitions = $plugin_manager->getDefinitions();

foreach ($definitions as $plugin_id => $definition) {
if ($plugin_id == $plugin_name) {
return $plugin_manager->createInstance($plugin_id);
}
}
return false;
}</span>

So what’s going on here? Well, we know we’re acting on the form that builds a Paragraphs edit interface. Once we know that, we can go ahead and load the Annotation plugin we defined in the beginning (personalized_paragraph) using the custom plugin manager we defined (plugin.manager.personalized_paragraphs). This will give us an instance of DecisionParagraph. With that instance, we can call DecisionParagraph’s buildConfigurationForm method passing it an empty array. When it returns, that empty array will be a filled render array which mirrors the smart_content_block user experience exactly, but within a Personalized Paragraph. So all we need to do is attach it in its own key (smart_content) to the element’s ‘subform’ and it will display in the right area.

So what is happening inside buildConfigurationForm? I won’t be going too in depth in here as most of this is simply mimicking smart_content_block. Suffice it to say that when the DecisionParagraph is constructed, an instance of MultipleParagraphDecision is also constructed. ->buildConfigurationForm ends up being called in both classes. You can view the code in each to get a sense of how the form render array is built. Now, with this code in place, we end up with an experience exactly like smart_content_block, but inside a Paragraph inside a Node; this is what the personalized paragraph in my homepage type looked like:

This is ultimately what anyone who has used smart_content_block would want out of a Paragraphs-based version. Since we had been using smart_content_block, we had a number of Segment Sets already to test from. Here is the result of selecting our Homepage Customer Block Segment Set:

I would like to digress for a moment here to discuss one of the most difficult bugs I encountered in the process. Getting the ‘Select Segment Set’ ajax to work was an absolute journey. On first implementation, the returned content was an empty . That class name led me to ManagedFile.php which is a class that provides an AJAX/progress aware widget for uploading and saving a file. This of course was odd because this element was not an uploading/file widget, however this particular Node edit form did have elements like this on the page. After stepping through execution in both Symfony and core’s FormBuilder what I discovered is this (line 1109 of FormBuilder):

// If a form contains a single textfield, and the ENTER key is pressed
// within it, Internet Explorer submits the form with no POST data
// identifying any submit button. Other browsers submit POST data as
// though the user clicked the first button. Therefore, to be as
// consistent as we can be across browsers, if no 'triggering_element' has
// been identified yet, default it to the first button.
$buttons = $form_state->getButtons();
if (!$form_state->isProgrammed() && !$form_state->getTriggeringElement() && !empty($buttons)) {
$form_state->setTriggeringElement($buttons[0]);
}

In short, I was pressing ‘Select Segment Set’, the triggering element wasn’t being found as the form was rebuilt in FormBuilder, and the code was just setting it to the first found button on the page (hence MangedFile.php). I have no objection with the comment or reason for this code block, but it makes it extremely difficult to figure out why your AJAX button isn’t working. If, for example, it triggered a log statement inside the if that said something like “the triggering element could not be matched to an element on the page during form build” it would have saved me multiple days of pain.

FormBuilder attempts to match the triggering element by comparing the name attribute of the pressed button to the name attributes of buttons on the page as it rebuilds the form. The issue was occurring because smart_content_block creates the name from a UUID it generates when MultipleDecisionBlock is created. In Personalized Paragraphs, this creation occurs inside a field_widget_entity_reference_paragraphs_form_alter which is called again while the form is rebuilt. As such a new UUID is generated, and FormBuilder cannot match the two elements.

The solution was to create a name that is unique within the edit form (so it can be matched), but does not change when the form is rebuilt. I added this above ->buildConfigurationForm:

$parent_field = $context['items']->getName();
$plugin->setConfigurationValue('parent', $parent_field);
$build_form = $plugin->buildConfigurationForm([], $form_state);
$element['subform']['smart_content'] = $build_form;

The machine name of the field that contains the personalized paragraph is passed along configuration values in DecisionParagraph to MultipleParagraphDecision where it is extracted and used to create the name attribute of the button. This solved the issue. Okay, now back to the returned Reactions.

The class that builds the Reactions after a Segment Set is selected is DisplayParagraphs; an instance is created for each Reaction, the code that executes this is found in MultipleParagraphDecision inside the stubDecision() method and buildSelectedSegmentSet method if the Reactions already exist. The Reactions are the first place we depart from the smart_content_block experience.

Seasoned users of smart_content_block will notice that the ‘Add Block’ button is missing. One of the most difficult problems I encountered while porting smart_content_block was getting the ajax buttons in the form experience to work correctly. Because of this, I opted to just hide them here (commented the code that built them in DisplayParagraphs.php) and instead validate and submit whatever is in the select dropdown at submission time. I liked the simplicity of this anyway, but it means that a given reaction could never contain more than one paragraph. This is an area ripe for contribution inside personalized_paragraphs.

In order to populate the select dropdowns in the Reactions, I first needed to go create some test Paragraphs Library items that would exist in them. I loaded /admin/content/paragraphs, selected ‘Add Library Item’ and then Add -> ‘Personalization — Homepage Banner’ (the Paragraph I created earlier to mimic the content I’m personalizing). I created a few instances of this Paragraph. Now I could go back to DisplayParagraphs.php and figure out how to retrieve these paragraphs.

Looking at the buildConfigurationForm method, it was clear that an array of $options was built up and passed to the form render array, so I needed to simply create some new options. Since we’re dealing with ContentEntities now, this was pretty easy:

$pg_lib_conn = $this->entityTypeManager->getStorage('paragraphs_library_item');
$paragraphs = $pg_lib_conn->loadMultiple();
$options = [];
$options[''] = "- Select a Paragraph -";
foreach($paragraphs as $paragraph){
$maybe_parent = $paragraph->get('paragraphs')->referencedEntities();
if(!empty($maybe_parent)) {
$parent_name = $maybe_parent[0]->bundle();
$options[$parent_name][$paragraph->id()] = $paragraph->label();
} else {
$options[$paragraph->id()] = $paragraph->label();
}
}

The code loads all of the existing paragraphs_library_items and splits them by Paragraph Type for easy selection in the dropdown which is how it works in smart_content_block. $options is later passed to a render array representing the select dropdown.

With this in place, we’re able to add a personalized_paragraph to a node, select a segment set, load reactions for that segment set and select the personalized paragraphs we want to display. Beautiful. What happens when we press Save?

The Form Submission Journey

Due to the way I was loading the Segment/Reaction form into the node edit form, none of the existing submit handlers were called by default. Thankfully the submit function attached to DecisionParagraph, paragraphSubmit, was designed in a way that it calls all the nested submit functions, i.e. MultipleParagraphDecision::submitConfigurationForm, which loops while calling DisplayParagraphs::submitConfigurationForm. So all I needed to do was attach paragraphSubmit as a custom handler like so:

function personalized_paragraphs_form_node_form_alter(&$form, FormStateInterface $form_state, $form_id){
$node = $form_state->getFormObject()->getEntity();
$personalized_fields = _has_personalized_paragraph($node);
if(!empty($personalized_fields)){
if ($plugin = personalized_paragraphs_get_handler('personalized_paragraph')) {
_add_form_submits($form, $plugin);
}
}
}

For reference, _has_personalized_paragraph looks like this:

function _has_personalized_paragraph($node){
$fields = [];
foreach ($node->getFields() as $field_id => $field) {
$settings = $field->getSettings();
$has_settings = array_key_exists('handler_settings', $settings);
if ($has_settings) {
$has_bundle = array_key_exists('target_bundles', $settings['handler_settings']);
if ($has_bundle) {
foreach ($settings['handler_settings']['target_bundles'] as $id1 => $id2) {
if ($id1 == 'personalized_paragraph' || $id2 == 'personalized_paragraph') {
array_push($fields, $field_id);
}
}
}
}
}
return $fields;
}

I’ll note here that it certainly ‘feels’ like there should be a more Drupal-y way to do this. I’ll also note that at the time of this writing, PP’s have not been tested on Paragraphs that contain them more than one level deep; my sense is that this function would fail in that case (another area ripe for contributing to the module).

Okay, so now we know that when someone presses ‘save’ in the node edit form, our custom handler will run.

paragraphSubmit departs pretty heavily from DecisionBlock::blockSubmit. First, since a Node could have an arbitrary number of personalized paragraphs, we must loop over $form_state’s userInput and detect all fields that have personalized paragraphs. Once we’ve narrowed to just the personalized fields, we loop over those and feed their subforms to similar code that existed in DecisionBlock::blockSubmit.

paragraphSubmit narrows to the form array for a given personalized paragraph and then passes that array to DecisionStorageBase::getWidgetState (a smart_content class) which uses NestedArray::getValue(). Users of this function know you pass an array of parent keys and a form to ::getValue() and it gives back null or a value. When I initially wrote this code, I hardcoded ‘0’ as one of the parents, thinking this would never change. However, one big difference in smart_content_block and personalized_paragraphs is that by virtue of being a paragraph, a user can press ‘Remove’, ‘Confirm Removal’ and ‘Add Personalized Paragraph’. In the form array that represents the personalized paragraph, pressing these buttons will increment that number by 1. So in paragraphSubmit, it will now have a 1 key instead of a 0 key. To handle this, I wrote an array_filter to find the only numerical key in the form array:

$widget_state = $form[$field_name]['widget'];
$filter_widget = array_filter(
$widget_state,
function ($key) {
return is_numeric($key);
},
ARRAY_FILTER_USE_KEY
);
$digit = array_key_first($filter_widget);

$parents = [$field_name, 'widget', $digit, 'subform', 'smart_content'];</span>

In comments above, it’s noted this will fail if someone attempts to create a field that has multiple Personalized Paragraphs in it (array_key_first will return only the first one). This is another area ripe for contribution in Personalized Paragraphs.

DecisionStorageBase::getWidgetState gets a decision storage representation from the form state and returns it. I added code here to ensure that the decision is always of type ContentEntity and not ConfigEntity (smart content defines both). Next, the code uses the $parents array and passed in $form variable to get the actual $element we’re currently submitting. It then runs this code:

if ($element) {
// Get the decision from storage.
$decision = $this->getDecisionStorage()->getDecision();
if ($decision->getSegmentSetStorage()) {
// Submit the form with the decision.
SegmentSetConfigEntityForm::pluginFormSubmit($decision, $element, $form_state, ['decision']);
// Set the decision to storage.
$this->getDecisionStorage()->setDecision($decision);
}
}

It’s easy to miss, but this line:

SegmentSetConfigEntityForm::pluginFormSubmit($decision, $element, $form_state, ['decision']);

Is what submits the current $element to the submit handler in MultipleParagraphDecision whose submit handler will ultimately call DisplayParagraphs submit handler ($decision in this case is the instance of MultipleParagraphDecision). So the chain of events is like this:

  • Node_form_alter -> add paragraphSubmit as a custom handler.
  • On submission, paragraphSubmit calls MultipleParagraphDecision::submitConfigurationFrom (via ::pluginFormSubmit)
  • This function has a looping structure which calls DisplayParagraphs::submitConfigurationForm for each Reaction (via ::pluginFormSubmit).

Before completing our walk through of paragraphSubmit, let’s follow the execution and dive into these submit handlers.

MultipleParagraphDecision::submitConfigurationForm is largely identical to MultipleBlockDecision::submitConfigurationForm. It gets the SegmentSetStorage for the current submission and loops for each Segment, creating a DisplayParagraphs instance for that segment uuid. It achieves this by calling:

$reaction = $this->getReaction($segment->getUuid());
SegmentSetConfigEntityForm::pluginFormSubmit($reaction, $form, $form_state, [
'decision_settings',
'segments',
$uuid,
'settings',
'reaction_settings',
'plugin_form',
]);

Where $reaction ends up being an instance of DisplayParagraphs for the current segment uuid. ::pluginFormSubmit is called like above which calls DisplayParagraphs::submitConfigurationForm.

This function starts by calling DisplayParagraphs::getParagraphs which is used all over DisplayParagraphs and modeled after DisplayBlocks::getBlocks. Because the block implementation can use PluginCollections, it’s easy for getBlocks to grab whatever block information is stored on the current reaction. I could not find a way to emulate this with paragraphs, so I opted to get paragraph information directly from the form input. If you recall my solution to the ajax button matching problem above (passing the unique machine ID of the parent field backwards via config values), getParagraphs implementation will look familiar.

First, for any call to ->getParagraphs that is not during validation or submission the caller passes an empty array which tells getParagraphs to try and get the Reaction information from the current configuration values (i.e. while its building dropdowns or sending an ajax response). Second, when called during validation or submission, the caller passes the result of $form_state->getUserInput(). After the non-empty passed array is detected, this code executes:

$field_name = $this->getConfiguration()['parent_field'];
$widget_state = $user_input[$field_name];
$filter_widget = array_filter(
$widget_state,
function ($key) {
return is_numeric($key);
},
ARRAY_FILTER_USE_KEY
);
$digit = array_key_first($filter_widget);
$parents = [$digit, 'subform','smart_content','decision','decision_settings','segments'];
$reaction_settings = NestedArray::getValue($user_input[$field_name], $parents);
$reaction_arr = $reaction_settings[$this->getSegmentDependencyId()];
$paragraphs[$field_name][$this->getSegmentDependencyId()] = $reaction_arr;

getParagraphs extracts the machine ID of the current parent_field out of its configuration values and uses it to parse the UserInput array. The value is extracted similarly to paragraphSubmit (filter for a numeric key and call ::getValue()) and then an array of reaction information keyed by parent field name and current segment set UUID is created and passed back to the caller.

submitConfigurationForm then extracts the paragraph ID out of this array and creates an array that will store this information in the configuration values of this instance of DisplayParagaphs (highly similar to DisplayBlocks). At this point control switches back to MultipleParagraphDecision and the $reaction variable now contains the updated configuration values. The reaction information is then set via DecisionBase::setReaction(), the ReactionPluginCollections config is updated and the instance variable MultipleParagraphsDecision->reactions is updated. At this point, control then goes back to paragraphSubmit.

Before we step back there, I wanted to note that DisplayParagraphs::getParagraphs is another area ripe for contribution. I skipped over the first portion of this function; this function is called in multiple areas of DisplayParagraphs to either get submitted form values (which we discussed) or to retrieve the existing values that are already in the configuration. As such, the function is built around a main control structure that branches based on an empty user input. This could definitely be done in a more clean, readable way.

Okay, back to paragraphSubmit. At this point we have completed everything that was called inside ::pluginFormSubmit which stepped through all of our nested submission code. The $decision variable has been updated with all of that information and the decision is now set like this:

// Submit the form with the decision.
SegmentSetConfigEntityForm::pluginFormSubmit($decision, $element, $form_state, ['decision']);
// Set the decision to storage.
$this->getDecisionStorage()->setDecision($decision);

Now that we have built the submitted decision, we need to save it and inform the paragraph it’s contained in that this decision is attached to it:

if ($this->getDecisionStorage()) {
$node = $form_state->getFormObject()->getEntity();
$personalized_para = $node->get($field_name)->referencedEntities();
if($personalized_para == null) {
//Paragraphs never created and saved a personalized_paragraph.
\Drupal::logger('personalized_paragraphs')->notice("The node: ".$node->id()."has a personalized paragraph (PP) and was saved, but no PP was created");
}else {
$personalized_para = $personalized_para[0];
}
if(!$node->isDefaultRevision()){
//A drafted node was saved
$this->getDecisionStorage()->setnewRevision();
}
$saved_decision = $this->getDecisionStorage()->save();
$personalized_para->set('field_decision_content_token', $saved_decision->getDecision()->getToken());
$personalized_para->save();
if ($saved_decision instanceof RevisionableParentEntityUsageInterface) {
$has_usage = $saved_decision->getUsage();
if(!empty($has_usage)){
$saved_decision->deleteUsage();
}
$saved_decision->addUsage($personalized_para);
}
}

We first get the $node out of the $form_state; recall that we are inside a structure that is looping over all personalized fields, so we use $field_name to get the referenced personalized paragraph out of the field. In an earlier version, the paragraphSubmit handler ran first before any other handler; because of this, on a new node, the paragraph had not been saved yet and ->referencedEntities returned null. With it executing last this should never happen, but I left a check and log statement for it just in case there is something I have not thought of. Next we check for defaultRevision so we can inform the decision content that it’s part of a draft instead of a published node. Finally we save the decision, pass the returned token to the personalized paragraphs hidden field that stores it and then add to the decision_content_usage table which tracks usage of decisions and their parents.

At this point we have handled the Create and Update states of a personalized paragraph inside a Node edit form. What about the read state? Now that we’ve attached the decision token to the decision_content_token field of our personalized paragraph, we can go back to our field_widget_entity_reference_paragraphs_form_alter and add:

$parents = ['subform', 'field_decision_content_token', 'widget', 0, 'value', '#default_value'];
$decision_token = NestedArray::getValue($element, $parents);
if($decision_token){
$plugin->loadDecisionByToken($decision_token);
}

loadDecisionByToken is a custom function I added to DecisionParagraph.php that looks like this:

public function loadDecisionByToken($token){
$new_decision = $this->getDecisionStorage()->loadDecisionFromToken($token);
$new_decision->setDecision($this->decisionStorage->getEntity()->getDecision());
$this->decisionStorage = $new_decision;
}

In essence, this takes the attached decision_token, loads the decision it represents out of the database and sets the the decisionStorage inside DecisionParagraph to that decision. By virtue of doing this, when ->buildConfigurationForm is later called, it gives us back the form that represents the segment set and reactions from the saved decision. Create, Read, Update… what about Delete?

When it comes to Paragraphs, delete is a fickle mistress. Because you can ‘Remove, Confirm Removal,’ ‘Add’ a Paragraph and then save the Node, Paragraphs must create a new paragraph which orphans the old paragraph. The bright minds behind Entity Reference Revisions have created a QueueWorker that finds these orphaned paragraphs and cleans them up, kind of like a garbage collector. At the time of this writing, Personalized Paragraphs does not implement something similar, and this is yet another area ripe for contribution. For example, if one saved a Node with a filled decision in a personalized paragraph then edited the node, remove/confirm removal/added a new personalized paragraph, filled out the decision and saved, both decisions would still be in the decision_content tables. Now if one deletes that Node, the current personalized paragraph’s decision will be deleted, but the old one will not, essentially orphaning that old decision. Here is how delete currently works:

function personalized_paragraphs_entity_predelete(EntityInterface $entity)
{
if ($entity instanceof Node) {
if ($fields = _has_personalized_paragraph($entity)) {
foreach ($fields as $field) {
$has_para = $entity->get($field)->referencedEntities();
if (!empty($has_para)) {
$has_token = !$has_para[0]->get('field_decision_content_token')->isEmpty();
if ($has_token) {
$token = $has_para[0]->get('field_decision_content_token')->getValue()[0]['value'];
_delete_decision_content($token);
}
}
}
}
}
}

In a hook_entity_predelete, we detect the deletion of a node with personalized paragraphs, iterate those paragraphs and delete the decision represented by the token currently attached to the paragraph. So we’ll get the current decision token, but not any old ones. Given that the only way to change the segment set of an existing decision is to ‘remove’ ‘confirm removal’ ‘add’, this will likely happen often. The consequence is that decision tables will grow larger than they need to be, but hopefully we, or an enterprising user, will create a fix for this in the near future.

Okay so we’ve handled adding the smart_content_block experience to any Node edit form with a personalized_paragraph. What about viewing a personalized_paragraph?

For viewing, we have the old tried and true hook_preprocess_hook to the rescue. We deploy a personalized_paragraphs_preprocess_field__entity_reference_revisions so our hook will run for every Paragraph; we quickly narrow to only those paragraph’s that reference personalized_paragraphs:

$parents = ['items', 0, 'content', '#paragraph'];
$para = NestedArray::getValue($variables, $parents);
if($para->bundle() == 'personalized_paragraph'){
...
}

Next, we attempt to get the decision token out of the decision_content_token field so we can pass it to DecisionParagraph::build():

if($para->bundle() == 'personalized_paragraph'){
if ($plugin = personalized_paragraphs_get_handler('personalized_paragraph')) {
$has_token = !$para->get('field_decision_content_token')->isEmpty();
if($has_token) {
$token = $para->get('field_decision_content_token')->getValue()[0]['value'];
$build = $plugin->build($token);
...
}

Where the build function looks like:

public function build($token) {
$this->loadDecisionByToken($token);
$decision = $this->getDecisionStorage()->getDecision();

$build = [
'#attributes' => ['data-smart-content-placeholder' => $decision->getPlaceholderId()],
'#markup' => ' ',
];

$build = $decision->attach($build);
return $build;
}</span>

Which is slightly modified from DecisionBlock::build(). We load the decision content that was attached to the personalized_paragraphs, then call the DecisionBase::attach() function on that decision. This passes control to a number of functions that create the magic inside smart_content. When attach() returns, we are given an array that smart_content.js will process to decide on and retrieve a winning Reaction. To complete the function:

$has_token = !$para->get('field_decision_content_token')->isEmpty();
if($has_token) {
$token = $para->get('field_decision_content_token')->getValue()[0]['value'];
$build = $plugin->build($token);
$has_attached = array_key_exists('#attached', $build);
if ($has_attached && !empty($build['#attached']['drupalSettings']['smartContent'])) {
$variables['items'][0]['content']['#attributes'] = $build['#attributes'];
$variables['items'][0]['content']['#attached'] = $build['#attached'];

$para_data = [
'token' => $token,
];
$has_name = !$para->get('field_machine_name')->isEmpty();
$name = $has_name ? $para->get('field_machine_name')->getValue()[0]['value'] : '';
$variables['items'][0]['content']['#attached']['drupalSettings']['decision_paragraphs'][$name] = $para_data;
} </span>

We get the $build array back from ->build and verify that it has the appropriate attachments to run smart content. If it doesn’t we log a statement demonstrating that something in the build function has failed. If it does, we attach the correct piece of the build array to our variables array. I want to focus in on this code block to complete the discussion:

$para_data = [
'token' => $token,
];
$has_name = !$para->get('field_machine_name')->isEmpty();
$name = $has_name ? $para->get('field_machine_name')->getValue()[0]['value'] : '';
$variables['items'][0]['content']['#attached']['drupalSettings']['decision_paragraphs'][$name] = $para_data;

This code block represents how my organization manages the front end of personalized paragraphs and I’ll admit it’s an assumption on how you, the user, might want to manage it. If you’ve been following along, you’ll have noticed that pesky ‘Machine Name’ field I attached to personalized paragraphs. Here is where it comes into play. We extract the passed name which should be unique to the page itself; that name and the decision_content_token is attached to drupalSettings so it is available to javascript files using drupalSettings. With the name and token available in javascript, one can now:

a.) Detect that the decision paragraph loaded (is the decision_paragraphs key in drupalSettings? Does it contain this unique machine name?) and if not, ensure a default experience loads,

b.) Run javascript functions that display the winning experience or the default experience.

Since our method for managing the front end is beyond the scope of how personalized paragraphs was built, I’ll discuss it more in the How To Use Personalized Paragraphs blog.

There’s one more function to discuss that gets called as the front end experience is being displayed and that is DisplayParagraphs::getResponse. When smart_content.js selects a winner, it runs some ajax which calls ReactionController which loads the winning Reaction and calls its ->getResponse method. I had to slightly modify this method from DisplayBlocks to deal with Paragraphs:

public function getResponse(PlaceholderDecisionInterface $decision) {
$response = new CacheableAjaxResponse();
$content = [];
// Load all the blocks that are a part of this reaction.
$paragraphs = $this->getParagraphs([]);
if (!empty($paragraphs)) {
// Build the render array for each block.
foreach ($paragraphs as $para_arr) {
$pg_lib_conn = $this->entityTypeManager->getStorage('paragraphs_library_item');
$para_lib_item = $pg_lib_conn->load($para_arr['id']);
$has_para = !$para_lib_item->get('paragraphs')->isEmpty();
if($has_para){
$para_id = $para_lib_item->get('paragraphs')->getValue();
$target_id = $para_id[0]['target_id'];
$target_revision_id = $para_id[0]['target_revision_id'];
$para = Paragraph::load($target_id);
$render_arr = $this->entityTypeManager->getViewBuilder('paragraph')->view($para);
$access = $para->access('view',$this->currentUser, TRUE);
$response->addCacheableDependency($access);
if ($access) {
$content[] = [
'content' => $render_arr
];
$response->addCacheableDependency($render_arr);
}
}

}
}
// Build and return the AJAX response.
$selector = '[data-smart-content-placeholder="' . $decision->getPlaceholderId() . '"]';
$response->addCommand(new ReplaceCommand($selector, $content));
return $response;
}</span>

Instead of getting the content from the BlockCollection configuration, to send back, I had to grab the id stored in the config values, which loads a Paragraphs Library Item. That item references a Paragraph, so I grab its ID and load the Paragraph. The render array is created off the loaded Paragraph and sent back to the ajax to be displayed.

Phew, somehow we’ve gotten to what feels like the end. I’m sure there is something I’m forgetting that I’ll need to add later. But if you’ve made it this far then you are a champion. I hope what the Smart Content folks have created and my little extension work for your use case and that this blog has made you aware of how things work much more quickly than just reading and debugging the code would.

If you have any questions you can find me @plamb on the Drupal slack chat, there is also a #personalized_paragraphs channel and a How To Use Personalized Paragraphs blog.

Mar 06 2021
Mar 06
Sean BGeek CulturePublished in

6 min read

Mar 6, 2021

Responsive images have always been a pain to configure properly. In Drupal you can create your breakpoints in your theme or module and use the Responsive Image module to set up different responsive image styles, defining which image style to use for a specific breakpoint. This takes quite some work and planning to set everything up, and maintaining all the image styles if changes need to be made is always a pain. Most of the sites I have built lately also have a fluid design. Since the images are defined for fixed breakpoints, this leads to a lot of the images being loaded for the user are still too big.

After struggling with this, we thought about how we could find a way to improve this. In HTML5 we can define a “srcset” attribute, which loads the correct image based on the browsers viewport. The default “src” contains a really small version of the image by default for better performance. Also notice the HTML5 “loading” attribute to enable lazy loading of images for even more optimization.

My pretty image

Since we were using media to add images to content, we experimented with having media view modes defined by aspect ratio, combined with a bunch of different image styles for the images in that specific aspect ratio. The media template could provide all the image styles with different widths for that image style, and use the “srcset” attribute to let the browser pick the best image. So we now got image styles for a 4:3 ratio and 16:9 ratio like:

  • responsive_4_3_50w
  • responsive_16_9_50w
  • responsive_4_3_150w
  • responsive_16_9150w
  • responsive_4_3_1450w
  • responsive_16_9_1450w

For images maintaining their original ratio we just use the width, like 50w, 150w, etc. The media template for our “16_9" view mode (media — image — 16-9.html.twig) now looked like this, using the “image_style“ filter of the Twig Tweak module to load the actual image URLs for image styles from the file:

{#
/**
* @file
* Default theme implementation to display an image.
*/
#}
{% set file = media.field_media_image.entity %}
{% set src = file.uri.value|image_url('responsive_16_9_50w') %}
{% set srcset = [
file.uri.value|image_style('responsive_16_9_150w') ~ ' 150w',
file.uri.value|image_style('responsive_16_9_350w') ~ ' 350w',
file.uri.value|image_style('responsive_16_9_550w') ~ ' 550w',
file.uri.value|image_style('responsive_16_9_950w') ~ ' 950w',
file.uri.value|image_style('responsive_16_9_1250w') ~ ' 1250w',
file.uri.value|image_style('responsive_16_9_1450w') ~ ' 1450w',
] %}
{{ media.field_media_image.alt }}

The first problem we noticed, was the “srcset” attribute uses the viewport width, not the width of the image container. This means when the viewport is 1400px and the image is shown in a column with a width of 200px, the image style with a width of 1400px is chosen by the browser. This was not giving us the result we were looking for. The only way to figure out the width of the container is via JavaScript, so we wrote a little script to figure out the available width for each image and load the correct image style using ResizeObserver. The ResizeObserver does not work in IE11, but this was not a requirement for our project. Besides, Drupal will also drop IE11 support in Drupal 10! To prevent the browser from initially loading the large images from the “srcset” attribute, we changed the “srcset” attribute to “data-srcset” and let the JavaScript handle the rest.

// Fetch all images containing a "data-srcset" attribute.
const images = context.querySelectorAll('img[data-srcset]');

// Create a ResizeObserver to update the image "src" attribute when its
// parent container resizes.
const observer = new ResizeObserver(entries => {
for (let entry of entries) {
const images = entry.target.querySelectorAll('img[data-srcset]');
images.forEach(image => {
const availableWidth = Math.floor(image.parentNode.clientWidth);
const attrWidth = image.getAttribute('width');
const sources = image.getAttribute('data-srcset').split(',');

// If the selected image is already bigger than the available width,
// we do not update the image.
if (attrWidth && attrWidth > availableWidth) {
return;
}

// Find the best matching source based on actual image space.
let source, responsiveImgPath, responsiveImgWidth;
for (source of sources) {
let array = source.split(' ');
responsiveImgPath = array[0];
responsiveImgWidth = array[1].slice(0, -1);
if (availableWidth < responsiveImgWidth) {
break;
}
}

// Update the "src" with the new image and also set the "width"
// attribute to easily check if we need a new image after resize.
image.setAttribute('src', responsiveImgPath);
image.setAttribute('width', responsiveImgWidth);
});
}
});

// Attach the ResizeObserver to the image containers.
images.forEach(image => {
observer.observe(image.parentNode);
});</span>

The second problem with this method was creating all the image styles we needed. This could be fixed with a form to automatically create all the image styles we needed for our aspect ratios. So we built the Easy Responsive Images module. The module needs a minimum and maximum width in combination with a preferred amount of pixels between each image style. An optional list of aspect ratio’s can also be defined. When the configuration is saved, the styles are automatically generated.

Now that we have the best possible images loaded based on the container, we can take one more step to improve the performance of our images. Using the Image Optimize module, we can create optimization pipelines that can automatically apply to images displayed via image styles. We chose to use JpegOptim and PngQuant supported via the Image Optimize Binaries module (the PreviousNext blog contains some more data on the module and results). If you can not install those binaries on your server, there is also a ImageAPI Optimize GD module.

Then there is also the ImageAPI Optimize WebP module.

WebP is a modern image format that provides superior lossless and lossy compression for images on the web. Using WebP, webmasters and web developers can create smaller, richer images that make the web faster.

In our tests we found that for most images, WebP is about 30% — 50% smaller than jpg images. For png images it is even more. To easily load the WebP version of an image when a browser supports it, we created a “image_url” Twig filter in the Easy Responsive Images module, with added bonus support for external images via the Imagecache External module.

The final file for our media view mode using the JavaScript and the new Twig filter looks like this:

{#
/**
* @file
* Default theme implementation to display an image.
*/
#}
{{ attach_library('easy_responsive_images/resizer') }}
{% set file = media.field_media_image.entity %}
{% set src = file.uri.value|image_url('responsive_16_9_50w') %}
{% set srcset = [
file.uri.value|image_url('responsive_16_9_150w') ~ ' 150w',
file.uri.value|image_url('responsive_16_9_350w') ~ ' 350w',
file.uri.value|image_url('responsive_16_9_550w') ~ ' 550w',
file.uri.value|image_url('responsive_16_9_950w') ~ ' 950w',
file.uri.value|image_url('responsive_16_9_1250w') ~ ' 1250w',
file.uri.value|image_url('responsive_16_9_1450w') ~ ' 1450w',
] %}
{{ media.field_media_image.alt }}

The example uses the same aspect ratio for all defined widths, but technically that is not a requirement. Using a different aspect ratio for smaller/larger screens can still be used based on the requirements, although that would make the setup a bit more complex and would require more view modes for your media.

That’s about it. Some next steps could be adding a formatters for the modules and figuring out support for retina images (even though these would increase the image sizes).

Hope this helps anyone looking to improve and optimise the way they implement responsive images in Drupal.

Feb 11 2021
Feb 11

Last week one of our clients was asking me about how they should think about the myriad of options for website hosting, and it inspired me to share a few thoughts. 

The different kinds of hosting

I think about hosting for WordPress and Drupal websites as falling into one of three groups. We’re going to compare the options using an example of a fairly common size of website — one with traffic (as reported by Google Analytics) in the range of 50,000–100,000 visitors per month. Adjust accordingly for your situation. 

  • “Low cost/low frills” hosting — Inexpensive website hosting would cost in the range of $50–$1,000/yr for a site with our example amount of traffic. Examples of lower cost hosts include GoDaddyBluehost, etc.  Though inexpensive, these kinds of hosts have none of the infrastructure that’s needed to do ongoing web development in a safe/controlled way such as the ability to spin up a copy of the website at the click of a button, make a change, get approval from stakeholders, then deploy to the live site. Also, if you get a traffic spike, you will likely see much slower page loads. 
  • “Unmanaged”, “Bare metal”, or “DIY” hosting — Our example website will likely cost in the range of $500–$2,500/yr. Examples of this type of hosting include: AWSRackspaceLinode, etc. or just a computer in your closet. Here you get a server, but that’s it. You have to set up all the software, put security measures in place, and set up the workflow so that you can get stuff done. Then it’s your responsibility to keep that all maintained year over year, perhaps even to install and maintain firewalls for security purposes. 
  • “Serverless” hosting¹ — It’s not that there aren’t servers, they’re just transparent to you. Our example website would likely cost in the range of $2500–5000/yr. Examples of this kind of hosting: PantheonWP EngineAcquiaPlatform.sh. These hosts are very specialized for WordPress and/or Drupal websites. You just plug in your code and database, and you’re off. Because they’re highly specialized, they have all the security/performance/workflow/operations in place that 90% of Drupal/WordPress websites need.

How to decide?

I recommend two guiding principles when it comes to these kinds of decisions:

  1. The cost of services (like hosting) are much cheaper than the cost of people. Whether that’s the time that your staff is spending maintaining a server, or if you’re working with an agency like Four Kitchens, then your monthly subscription with us. Maybe even 10x.  So saving $1,000/yr on hosting is only worth it if it costs less than a handful of hours per year of someone’s time. 
  2. Prioritize putting as much of your budget towards advancing your organization’s mission as possible. If two options have a similar cost, we should go with the option that will burn fewer brain cells doing “maintenance” and other manual tasks, and instead choose the option where we can spend more of our time thinking strategically and advancing the mission.

This means that you should probably disregard the “unmanaged/bare/DIY” group. Whoever manages the site will spend too much time running security updates, and doing other maintenance and monitoring tasks. 

We also encourage you to disregard the “low cost” group. Your team will waste too much time tripping over the limitations, and cleaning up mistakes that could be prevented on a more robust platform.

So that leaves the “serverless” group. With these, you’ll get the tools that will help streamline every change made to your website. Many of the rote tasks are also taken care of as part of the package. 

Doing vs. Thinking

It’s easy to get caught up in doing stuff. And it’s easy to make little decisions over time that mean you spend all your days just trying to keep up with the doing. The decision you make about hosting is one way that you can get things back on track to be more focused on the strategy of how to make your website better.

¹ The more technical members of the audience will know that “serverless” is technically a bit different.  You’d instead call this “platform-as-a-service” or “infrastructure-as-a-service”. But we said we’d avoid buzzwords.

Making the web a better place to teach, learn, and advocate starts here...

When you subscribe to our newsletter!

Pages

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web