Upgrade Your Drupal Skills

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

See Advanced Courses NAH, I know Enough

Screencast: Updating to Drupal 9 in 15 minutes

Feb 12 2020
Feb 12

Mental Health First Aid Training at DrupalCon Minneapolis

Feb 11 2020
Feb 11
Feb 11 2020
Feb 11

The workshop is currently on hold until the status of DrupalCon Minneapolis is resolved.

The Drupal Community Working Group (CWG) is pleased to announce that registration is now open for a full-day Mental Health First Aid workshop on Sunday, May 17, 2020 (the day before DrupalCon Minneapolis begins) in Bloomington, Minnesota. 

The workshop will be held "field trip" style; it will be held off-site, at the Health Counseling Services facility in Bloomington, Minnesota, from 8:30am-5pm. Transportation will be provided to and from a location near the Minneapolis Convention Center (the site of DrupalCon) to the workshop. Following the workshop, attendees are invited to (optionally) attend a pay-on-your-own group dinner to decompress and discuss the day's workshop.

The CWG believes that these types of proactive workshops will help improve our community's mental health literacy and awareness, as well as making it easier for us to have open, honest, and respectful conversations and potentially spotting signs of when community members are in need of assistance.

The Drupal Association is generously sponsoring the workshop by providing funding to help defer the cost of the workshop as well as providing transportation. 

From the Mental Health First Aid website:

Mental Health First Aid is a course that gives people the skills to help someone who is developing a mental health problem or experiencing a mental health crisis. The evidence behind the program demonstrates that it does build mental health literacy, helping the public identify, understand, and respond to signs of mental illness.

Mental Health First Aiders learn a single 5-step action plan known as ALGEE, which includes assessing risk, respectfully listening to and supporting the individual in crisis, and identifying appropriate professional help and other support. Participants are also introduced to risk factors and warning signs for mental health or substance use problems, engage in experiential activities that build understanding of the impact of illness on individuals and families, and learn about evidence-supported treatment and self-help strategies.

Over the past few years, the CWG has organized proactive community health events, including on-going Code of Conduct contact training, as well as previous DrupalCon North America trainings on leadership, teamwork, and communications. 

In order for the workshop to proceed, we need at least ten community members to register by April 1, 2020 at https://healthcounselingservices.com/events/adult-mental-health-first-aid-11/

When registering:

  • Choose the "Pay now" option (do not select the "Bill my organization" option.
  • Use the coupon code: MHFA30 to receive $30 off the regular price.
  • For the "Name of organization", "Name of site", "Supervisor's name", and "Supervisor's phone" fields, feel free to use "not applicable".
     
Feb 09 2020
Feb 09

If you’ve spent time looking for a website support partner, you’ll quickly realize that while there are a lot of options out there, they’re not all created equal. Keeping your goals in mind will help you find an agency with an approach that best meets your needs.

If you’re simply looking for software updates and security patches, there are a lot of options out there. But if you’re looking for a strategic partner to support your site, the search for the right fit can be a bit more challenging.

At Kanopi Studios, we cover the basics, but that’s just the beginning. Our support team focuses on continuous improvement and growth-driven design, ensuring long-term growth for your website. We can jump in at any stage of your site’s lifecycle to make sure you’re meeting your goals and getting the most out of your investment. And when it’s finally time for an upgrade, we can help with that too!

Here are a few details that set Kanopi’s support services apart:

Customer service is our #1 priority.

Our team goes the extra mile to provide stellar customer service. We’re here to make your life easier, regardless of the size of your account.  

Added value and strategic guidance

As part of your monthly support budget, you’ll gain access to experienced designers, user experience strategists, developers and more. When it’s time to go beyond bug fixes, you’ll have experts in your corner to help your site respond to changes in the market or shifts in your business priorities.

You’ll work with real humans!

Our full-time support team manages every detail of your account. We analyze incoming requests, make sure we have the details needed to get the job done right, and respond within an hour, all without a single bot in sight.  

A dedicated, senior-level team

Our support team focuses on support. We know that it takes a different set of skills, energy, and dedication to handle rapidly changing priorities and keep the issue queue clear. Our experienced team has identified and resolved nearly every issue imaginable. We encourage you to check out their bios so you can see their qualifications for yourself!

A partner you can trust

Kanopi Studios supports more than 135 active websites. Due to the great relationships we’ve built, we’re still working with some of the very first clients that signed on for our services. In fact, most of our work comes through referrals from happy customers. We welcome you to check out our five-star reviews and get in touch to learn more about ensuring long-term growth for your website.

Customize Acquia Dev Desktop

Feb 08 2020
Feb 08

Build a valid RSS feed for Planet Drupal

Feb 08 2020
Feb 08

Geography in web page performance

Feb 08 2020
Feb 08

Jump into BADCamp: Sponsorships, Hotels, and Trainings, Oh My!

Feb 07 2020
Feb 07
Feb 07 2020
Feb 07

Read our Roadmap to understand how this work falls into priorities set by the Drupal Association with direction and collaboration from the Board and community.

Project News

Get Ready for Drupal 9

Are you wondering what it will take to upgrade to Drupal 9? Good news - it's going to be easier than any other major version upgrade in Drupal's history.

The upgrade to Drupal 9 is just like any other Drupal upgrade, except that the new codebase has updated key dependencies Drupal relies on and removed deprecated code. As long as all the modules and custom code you use don't rely on deprecated code - you should be good to go.

As it turns out, many contributed or even custom modules only need a one-line change to be ready for Drupal 9. You can use these community created tools to check the status of your modules: the upgrade status module, or the Drupal Check command line tool. In many cases, you may just need to remove some deprecated code in favor of the more modern implementations. Drupal Rector can provide you with automated fixes for many of these deprecations. 

Still getting to grips with Composer?

Composer

If you're still getting to grips with using Composer after the changes in Drupal's 8.8.0 release, don't worry - there's help to be found. The community has extensively documented the different scenarios a site owner may find themselves in with this update.

If you've previously used one of the community created templates to manage your site to composer, there are instructions to migrating to the officially supported method.

If you've never used Composer at all - you're in luck - with 8.8.0 and beyond everything you need is already in place. 

Drupal.org Update

Drupal.org Packaging updates

As mentioned in our December update, we've been making major improvements to the Drupal.org packaging pipeline, to support packaging Drupal using Composer create project. We reached a major milestone at DrupalCamp New Jersey, allowing our packaging pipeline to properly support the Composer create project command when generating tar and zip files, and paving the way for enhancements to the core subtree splits.

Updating this pipeline is critical for ongoing releases of Drupal, and is part of paving the way for the Drupal 9 alpha release. We want to thank Acquia for donating time to help us get this work ready.

Preparing for contrib Semver

Per our roadmap for supporting Semver for contributed projects on Drupal.org, we have updated the way contrib version numbers are stored, making existing version numbers parseable when we convert to full semver. We also collaborated with core contributors at DrupalCamp New Jersey to identify and resolve a number of other related issues.

Drupal.org now has an example project which uses semantic versioning, which we are using as the testbed for this support, and to prove out any additional UI changes that we want to make before rolling this out to all other contributed projects.

Want to learn more about Semantic Versioning and how to use it properly within your projects? Semver.org can walk you through it.

More accessible formatting for the DrupalCon program schedule

It's almost time for the DrupalCon Minneapolis program to be published! To prepare for this launch, we've made updates to the program schedule to improve accessibility and readability for attendees.

In particular these updates have focused on line weights, spacing, and other formatting changes that should improve readability. With the accepted sessions being announced soon,  we're excited to see what you think!

Better social event submission tools for DrupalCon events

DrupalCon Minneapolis | May 18-22 2020Some of the best parts of DrupalCon are the social events that take place around it. They're a chance for the community to celebrate and build camaraderie, and an established tradition. We've made updates to the social event submission process to make getting your event listed easier than ever. 

Join the Drupal Community in person! 

By the way… have you registered for DrupalCon yet?

DrupalCon is the best place to come together with other members of the Drupal community in person. It's also the central meeting point for all of facets of the Drupal business ecosystem, so if you are end-user looking for training or a vendor to support your Drupal deployment - there's no better place to be than DrupalCon.

DrupalCon Minneapolis is going to be here any day now - so get your tickets before prices go up!

Can't make it to Minneapolis? Join us at DrupalCon Barcelona 2020 in September.

———

As always, we’d like to say thanks to all the volunteers who work with us, and to the Drupal Association Supporters, who make it possible for us to work on these projects. In particular, we want to thank:

If you would like to support our work as an individual or an organization, consider becoming a member of the Drupal Association.
Follow us on Twitter for regular updates: @drupal_org, @drupal_infra

Feb 07 2020
Feb 07

The default contact form in Drupal has quite basic settings. You may only create categories and receiving emails with the default UI admin. To change other preferences such as form title or form destination, we may have to implement override hooks.

In this article, we present some tricks to customize the contact form in Drupal. More tricks will be added regularly.

1. Edit the contact form title

To change the title, add this function to template.php on your theme folder (/sites/all/themes/your-theme/template.php)


<?php
function mytheme_form_contact_site_form_alter (&$form, &$form_state) {
drupal_set_title ('Contact ABC Media');
}

2. Redirect form result

By default, users will be redirected to front pages after submitting the form. It has a strange behavior for users because they may confuse what is going on, whether the message has been sent.

To redirect the contact form to a page of your choice, please add these two functions to your template.php file of your theme, as in section 1 above. I learnt it from a tip of Postrational.


<?php
function my_theme_form_alter(&$form, &$form_state, $form_id) {
if ($form_id == 'contact_site_form') {
$form['#submit'][] = 'contact_form_submit_handler';
}
}
function contact_form_submit_handler(&$form, &$form_state) {
$form_state['redirect'] = 'thank-you-page-alias';
}

Do you have other tricks with contact forms in Drupal? Pls share and we will post them here with acknowledgement to you.

Feb 07 2020
Feb 07

Tome is a suite of Drupal modules that can make your site into secure, fast, static HTML. 

Long story short, you can use Drupal in the same way you would use other static site generators like Jekyll or Hugo - everything lives in one repository, and Drupal only runs on your local machine.

The creator, Sam Mortenson tells us everything we need to know.

Feb 06 2020
Feb 06

Drupal 8 websites can easily exchange data with third-party websites or apps. These can be iOS or Android devices, applications on Vue.js, React, Angular, or other JS frameworks, and so on. Web services in Drupal 8 core take care of the smooth interaction. To share Drupal data, developers often use REST export with Views in Drupal 8. Today, we will take a closer look at Views REST export.

REST export with Views in Drupal 8

The Views module is in Drupal 8 core, and lets us quickly create collections of content, users, taxonomy terms, and more. We can organize Drupal data into a View and prepare it for export in the JSON format needed by third-party apps. As a result, we will have a RESTful API endpoint, where other resources can call to get our data.

Enabling the Drupal 8 modules for REST export

To provide REST export with Views in Drupal 8, we begin with enabling the core “Web services” modules. Two of the four will be enough for today’s example — RESTful Web Services and Serialization.

Creating a RESTful View

We are going to share Drupal content. For this, a custom content type or a default one (“Article” or “Basic page”) will be OK. Let’s use “Article.” We can create a couple of test articles (Article 1, Article 2, Article 3) and then move on to the View creation.

In the “Structure — Views” tab of our site’s admin, we click “Add new view,” and do 3 things:

  • Give our View a name.
  • Check “Provide a REST export” option (which we see next to the usual “Create a page” and “Create a block” options).
  • Write a REST export path (where other resources can call to get our content).

Configuring the RESTful View

Our RESTful View looks almost like the classic version, but the output is in JSON. It has standard Views capabilities, so we can:

  • choose to display fields, entity, or search results
  • do filtering and sorting
  • limit the amount of results
  • create contextual filters

and much more.

Let’s filter our View to only show the “Article” content type.

Now we choose to display fields:

And we add a couple of fields to the View — “title” and “body.” It is also possible to specify a custom URL path for each field in the field settings, if we for some reason do not want to use their default ones.

An unusual but important element in the RESTful Views interface is the “Serializer” tool in the “Format” section. In its “Settings,” we can select the formats to output our content in.

In our case, we have a choice between JSON and XML (other modules would add more formats). If no format is chosen, the default output will be in JSON.

Let’s save our View. The data in JSON is available through the URL path that we provided (json/content). This is our REST API endpoint, and it has this structure:

website-example/json/content.

We receive JSON output in ASCII format that is encoded for security reasons. Our JSON is ready to be sent to third-party apps and then decoded by them.

To check if the code validates, as well as to see how it looks in a more “attractive” way, we can use a Chrome app or one of online JSON formatters & validators.

Here’s how one of them processes our code:

Another JSON formatter does more decoding for our JSON better, and makes it even more human-readable:

JSON validates, so our View is ready to share content. We have successfully configured REST export with Views in Drupal 8.

Let’s create the desired export for your Drupal website

Although the REST architecture supports CRUD (create, read, update, and delete) operations, Views is only for R (reading).

So if you want mobile app users to be able to edit your Drupal 8 content, REST export with Views in Drupal 8 will not do for you. We could offer you a different solution then like a full REST API in the JSON format, custom endpoints, and so on.

Whatever are your ideas about third-party integration, our Drupal team will help you implement them!

Top Drupal blog posts from January 2020

Feb 06 2020
Feb 06
Feb 05 2020
Feb 05

During a recent project, the challenge of providing reusable, interactive web components to allow content editors to build pages presented itself. These components were to be created and developed by different teams, and available on the main Drupal site and a set of static pages, each of which had specific requirements and were already working in production.

Several decisions factored into finding the right solution to this challenge. This article explains what those decisions were and how the solution was implemented.

This project required that the main Drupal site and a number of other static sites must be able to reuse the same pieces of content to build their pages. The Drupal site had a content architecture structured as pages containing a leadspace, or header, plus several other fragments, called bands, which could be created by content editors. Multiple visual layouts were available for the bands - an image and a paragraph, a headline with three cards linking to other pages, etc. These layouts are defined within Drupal as display modes and are used to render each band with a specific layout.

It was important to find a way to provide reusable interactive pieces of content to be rendered as bands on the Drupal site, and make them available “as is” to other sites as well. Enter the widgets.

In this context, a widget is a reusable, self-contained piece of software providing a specific feature for any website. From a technical perspective, a widget is a JavaScript library intended to render within the HTML of a page that presents custom functionality and is 100% reusable without relying on external dependencies.

After a thorough audit of the latest approaches to this problem, the decision was to implement an architecture inspired by the micro frontends technique.

Micro Frontends

Nowadays, one of the most popular ways to implement backend pieces is the microservices technique: a derivative of service-oriented architectures that defines any complex application as a collection of loosely coupled services**. A service is a self-contained component that provides specific functionality to the application and communicates with the rest of the application pieces through a well-defined API.

When the microservices architecture translates to front-end development, a web page can be composed using a set of already-built components that are ready to be rendered. The components are also self-contained, decoupled, and reusable in the same way several microservices build an application on the back end. This approach allows several teams to focus their efforts on a specific set of components, developed in parallel, and not be dependent on other teams. Additionally, components are independent of each other, which allows a content editor to build a page by just selecting components and putting them in the desired place.

There’s more information on micro frontends in this article.

For this particular project, widgets were implemented as JavaScript libraries, following the micro-frontends approach. The widget source code, available as a single file, was loaded as usual by including a <script> tag. It provided an entry point, a function, to allow the widget to render inside a container from the HTML of the page (i.e., a <div> tag) once the DOM finished loading and said function executed.

To transform this theoretical approach into a real live implementation, multiple options were considered for building a widget as a JavaScript single file application, like vanilla JavaScript, or one of the numerous JavaScript frameworks available today. After some research, the Create React App was chosen as a base to build our widgets for several reasons:

  • React is a widely-used JavaScript framework, easy to use, and it has been around for some time so we can trust its reliability.
  • There are plenty of tools that make React development easier and quicker while providing the same starting point for all teams developing a new widget.
  • Create React App is compatible with Webpack, which we needed to efficiently pack widgets as a single JavaScript file with the ability to include assets, like fonts or image files.
  • It makes it trivial to have a local environment for the widget: running npm-start is all that’s required to have a local server where the widget is loaded.

There are some downsides when using React, though. If every widget is a full React application, the number of bytes the browser needs to download to render it is higher than if using other tools. This is because every widget includes a number of dependencies.

Due to how the project works and how editors were used to building their content, loading multiple widgets onto the same page was taken into account. As a result, some of the widget dependencies would likely duplicate. For example, lodash, a very common package when building React apps, may be loaded by more than one widget on the same page. Avoiding this duplication was not easy because it required loading multiple script tags on the browser, one per shared dependency, plus the widget source JS file. The expectation was to place only one or two widgets at most on any given page, so the risk of duplication was worth taking.

Rendering a Widget within a Page

Since the widgets are React applications compiled as an independent, single JavaScript file, it was important to have some control over the loading process so the widgets could render in a specific position on the page once the DOM was ready. To achieve this, the widget file was required to define an entry point function on the window object, as described in this section from the aforementioned micro-frontends article. The function received the DOM element ID of the container tag where the widget should render. It looked something like the following:

import ReactDOM from 'react-dom';

/**
 * Renders the widget.
 *
 * @param {string} instanceId
 *   The HTML element ID where the React app will be rendered.
 * @param {Function} cb
 *   An optional callback executed after the widget has been rendered.
 */
function render(instanceId, cb) {
  const element = document.getElementById(instanceId);
  if (element) {
    ReactDOM.render(
      <YourReactAppHere>,
      element,
      () => cb(element)
    );
  }
}

window.renderExampleWidget = render;

To simply identify the entry point function, the naming convention was to use the word render plus the machine name of the widget in CamelCase. For example, if the widget’s machine name (and its repository) was example-widget, the render function would be named renderExampleWidget. The widget’s machine name was an arbitrary attribute defined by the team working on the widget, but it becomes more relevant later.

Finally, all widgets needed to implement a consistent compile process with two steps:

npm install
PUBLIC_URL='<some url>' npm run build

The first step installed all dependencies, and the second one generated a directory named build which contained the production-ready release of the widget, with the following structure:

build
 ├── js
 │   └── main.js
 └── media
     ├── some-picture.png
     └── some-font.ttf

The main.js library had the responsibility of loading any asset from the build/media directory. Using relative URLs prepended by an environment variable named PUBLIC_URL was required to access these files from the main.js library which looked something like this:

<img src="https://www.lullabot.com/articles/widget-registry-how-serve-reusable-interactive-content-pieces/`${PUBLIC_URL}/media/picture.png`" />

This way, the PUBLIC_URL variable could remain empty for local development, and the assets were loaded. Once uploaded to the registry, the production build of the widget knew where it could locate the assets on the registry. The Create React App documentation contains more information about the PUBLIC_URL parameter.

The lack of a styles file on the build directory structure described above was probably noticeable. The reason is that CSS is global, and there is no way to encapsulate a style rule to be applied on a specific section of a page only. This means that allowing the React application to load a CSS file can potentially break styles on page sections outside of the widget scope. The decision made to prevent this situation was to leave these files out and allow only CSS-in-JS tools to handle widget styles.

The widget registry is a centralized place where production builds of widgets can render into any preferred website. The main goals of the registry were to maintain a list of published widgets as a web service, so it could be programmatically read by sites, and provide a server in which the widgets could be directly injected. The sites loading widgets would not need to download widget files or keep them up to date.

The registry itself is a set of moving pieces that enable automation of the management of widgets, and it is formed by the following tools:

  • A Github repository where the registry code lives. The most relevant piece on the repository is a JSON file containing a listing of available widgets as an array of objects, each one of the objects representing a different widget. These objects contain information like human and machine names, the git repository where the widget code lives, and the latest version published on the registry.

  {
    "repositoryUrl": "https://github.com/Lullabot/example-widget",
    "machineName": "example-widget",
    "title": "Example Widget",
    "version": "v1.0.0",
  }

  • Some scripts to verify that the format of the JSON file matches a definition that was implemented using JSON Schema, which is run on every commit to ensure the registry JSON file is never malformed. There are also a couple of other scripts that compile all registered widgets and upload the production builds to the widget server.
  • A CI tool, properly integrated with the repository. In this case, Travis took the responsibility of running the scripts mentioned above when a new pull request was created against the main branch. Once everything looked good, and the code merged into the main branch, the CI tool iterated over the list of registered widgets and downloaded a GitHub release from each widget’s repository whose name matched the value of the version field on the JSON object. At this point, the tool attempted to compile the widget and, when everything finished successfully, all compiled widgets were ready to be uploaded to a public server.
  • A public server where the widget’s production builds are available, along with the registry JSON file. For this project, IBM Cloud Object Storage was used, but any asset server or cloud provider can do the trick.

Once the deployment process was complete, and the registry JSON file and the production builds for all registered widgets were available on the server, it was time to render a widget directly from the registry.

The first step to inject a widget was to create a container within the HTML where the widget was rendered. In this example, a <div> tag was used with a unique value for the id attribute:

<div id="widget-comes-here"></div>

Next, the widget JavaScript application needed to be included in the HTML of the page. The library can be included in the <head> or <body> tags if preferred.

<script src="https://www.lullabot.com//<widget registry domain>/<widget-machine-name>/js/main.js"></script>

Finally, the widget’s entry point function was called once the DOM is ready. Something like the following can be done (assuming the entry point function set by the widget library is named renderWidget, but it could be any other name):

document.addEventListener("DOMContentLoaded", function() { 
  window.renderExampleWidget('widget-comes-here');
});

Putting everything together, this is how a simple HTML page looked when rendering a widget:

<html>
  <head>
    <script src="https://www.lullabot.com//<widget registry domain>/<widget-machine-name>/js/main.js"></script>
    <script>
      document.addEventListener("DOMContentLoaded", function() { 
        window.renderExampleWidget('widget-comes-here');
      });
    </script>
  </head>
  <body>
    <div id="widget-comes-here"></div>
  </body>
</html>

Drupal Integration

Once the widget registry was up and running and some of the widgets were registered, the micro-frontends needed to be integrated in order to work properly with Drupal. To achieve this, a custom module was created that includes the following features:

  • A custom entity type to represent the widgets definitions available on the registry JSON file, named Widget Type. This way, the site was able to identify which widgets were ready to be used, and which versions were the latest published for each one of them.
  • A cron job to update local Widget Type entities with the latest information available from the registry JSON file.
  • A Drupal JavaScript behavior that took care of rendering all widgets on a page.
  • Some configuration to locate the URL of the registry, among others.

Widgets could be referenced from an entity reference field that was added to the band entity, only visible when a specific layout for the band is selected. This field allowed for creating a new widget instance from a specific widget type. Once the page was saved, Drupal rendered the band, attaching the widget’s JavaScript file as an external library with the behavior mentioned earlier. Then, the behavior executed the entry-point function for all widgets present on the page, and each one of them rendered within its parent band.

It’s worth mentioning that the decision was made to load the widget source files directly from the registry rather than downloading them to do the following:

  • Prevent the need to maintain the files locally
  • Improve performance, as the site does not need to serve those files
  • Serve a widget’s latest build immediately when a new version hits the registry. This is achieved because the new code is served under the same URL unless there is a major version change. Because of this logic, there is no need to clear caches on the Drupal site to have it render the new version, as the client browser realizes the file's Etag header is changed and downloads the new widget library if needed.

Something to note about this last point is that there isn’t any CDN in front of the widget registry at the moment of writing this article. If one is put in place in the future, there would probably be some time until the new widget version is present on the CDN after deployment, so the new code will not render immediately. But, for now, it does!

In summary

The described architecture is a simplification of the widget system built for the project, but the article illustrates how the micro-frontends approach was implemented to allow the content editors of a Drupal site to reuse components on multiple pages and sites, how a centralized service was created to allow these components to be available to both Drupal and non-Drupal sites, and how they were integrated into Drupal specifically.

There are some additional topics to discuss, such as passing field values from Drupal to a widget, multi-language support based on Drupal’s current language, allowing external CSS styles which do not interfere with the rest of the page or a CLI tool to manage the registry JSON file, and those may be the basis for future articles.

Why "guys" isn't gender neutral in our community

Feb 04 2020
Feb 04
Feb 04 2020
Feb 04

How to use the PSR-4 autoloading standard for Drupal 7 Simpletest test cases.

The Traditional Way

The typical way of including test cases in Drupal 7 is to add one or more classes within a .test file - e.g. opdavies.test. This would typically include all of the different test cases for that module, and would be placed in the root of the module’s directory alongside the .info and .module files.

In order to load the files, each file would need to be declared within the .info file for the module.

There is a convention that if you have multiple tests for your project, these can be split into different files and grouped within a tests directory.

; Load a test file at the root of the module
files[] = opdavies.test

; Load a test file from within a subdirectory
files[] = tests/foo.test
files[] = tests/bar.test

Using the xautoload Module

Whilst splitting tests into separate files makes things more organised, each file needs to be loaded separately. This can be made simpler by using the Xautoload module, which supports wildcards when declaring files.

files[] = tests/**/*.test

This would load all of the .test files within the tests directory.

Using PSR-4 Autoloading

Another option is to use PSR-4 (or PSR-0) autoloading.

This should be a lot more familiar to those who have worked with Drupal 8, Symfony etc, and means that each test case is in its own file which is cleaner, files have the .php extension which is more standard, and the name of the file matches the name of the test class for consistency.

To do this, create a src/Tests (PSR-4) or lib/Drupal/{module_name}/Tests (PSR-0) directory within your module, and then add or move your test cases there. Add the appropriate namespace for your module, and ensure that DrupalWebTestCase or DrupalUnitTestCase is also namespaced.

// src/Tests/Functional/OliverDaviesTest.php

namespace Drupal\opdavies\Tests\Functional;

class OliverDaviesTest extends \DrupalWebTestCase {
  // ...
}

This also supports subdirectories, so you can group classes within Functional and Unit directories if you like.

If you want to see an real-world example, see the Drupal 7 branch of the Override Node Options module.

Digging into the simpletest_test_get_all function

This is the code within simpletest.module that makes this work:

// simpletest_test_get_all()

// ...

$module_dir = DRUPAL_ROOT . '/' . dirname($filename);

// Search both the 'lib/Drupal/mymodule' directory (for PSR-0 classes)
// and the 'src' directory (for PSR-4 classes).
foreach (array(
  'lib/Drupal/' . $name,
  'src',
) as $subdir) {

  // Build directory in which the test files would reside.
  $tests_dir = $module_dir . '/' . $subdir . '/Tests';

  // Scan it for test files if it exists.
  if (is_dir($tests_dir)) {
    $files = file_scan_directory($tests_dir, '/.*\\.php/');
    if (!empty($files)) {
      foreach ($files as $file) {

        // Convert the file name into the namespaced class name.
        $replacements = array(
          '/' => '\\',
          $module_dir . '/' => '',
          'lib/' => '',
          'src/' => 'Drupal\\' . $name . '\\',
          '.php' => '',
        );
        $classes[] = strtr($file->uri, $replacements);
      }
    }
  }
}

It looks for a the tests directory (src/Tests or lib/Drupal/{module_name}/Tests) within the module, and then finds any .php files within it. It then converts the file name into the fully qualified (namespaced) class name and loads it automatically.

Running the Tests

You can still run the tests from within the Simpletest UI, or from the command line using run-tests.sh.

If you want to run a specific test case using the --class option, you will now need to include the fully qualified name.

php scripts/run-tests.sh --class Drupal\\opdavies\\Tests\\Functional\\OliverDaviesTest

Jan 31 2020
Jan 31

Access and security are collateral concerns born out of the world of the internet. When a website is under development, a major chunk of time and effort goes into making it secure from all sorts of malicious activities. Taking the threats and data breaching affairs into account, every platform has a protocol to follow. For the security and usability purpose, every website has certain permissions granted to view, edit, or delete the content on the site. For example, open source CMS like Drupal provisions various modules that let you configure the user access. 

a man sitting in front of multiple screens

What is user access?

Every website has a tech team working on the development of the site while they assign certain roles in the process to each individual. Similarly, user access is the possession of a particular role on the website that allows the user with specific permissions. The user access is determined by the site owners who assign the roles to the users with a login Id and password

In this blog, we will be talking about the user access permissions in Drupal 8 and understand how it works with the available modules. 

Permissions management in Drupal 8

With a lot of different functions and features, the permissions in Drupal 8 work in a little different way. From viewing published content to changing the state of functions and features, permission control access in Drupal is assigned as a role. There are 3 default roles as follows:

  • Anonymous: Visitors who are not logged into your site.
  • Authenticated: User with an account who’s login is authenticated with a minimum set of permissions that are given to all logged-in users. 
  • Administrator: Users who can do everything on the site and change the functionalities.

Limitations of permissions management in Drupal 8

These default roles are quite limited in themselves. They do not define the access in detail and require modules to further strengthen the access control. Following are the limitations:

  • The roles you create yourself or the authenticated user role do not receive the permissions given to anonymous users.
  • There’s no granular control at various level.
  • In case of a lot of custom modules where the contributed modules introduce important functionality, the piece of code requires more hold of access levels which isn’t available.

Thus, we need user access modules that will grant broader access control and permissions.  

Top Drupal 8 user access modules

Here is a list of the most popular user access modules for Drupal 8 depending on the functionality you want for your website:

#1 Node View Permissions

Apart from allowing the combining of other user access modules, the Node View Permission module adds two types of permissions to all content type, namely, "View own content" and "View any content".

#2 Menu Admin per Menu

As the name suggests, the Menu Admin per Menu module permits and restricts access to only Administer menus and menu items to add, modify or delete menu items. It gives access to certain sections of the menu without giving access to the full admin panel.

#3 Block Content Permissions

You can gain the control access to administer block content types (custom block types), administer block content (custom block library) that lets you create, update, or delete specific types of block content with this module. However, the Block Content Permissions module doesn’t remove the block permissions from "Custom block library - Blocks" views page

#4 Protected Pages

Every website has multiple pages with different types of content and not every information can be shared with every user with a role. Thus, the Protected pages module allows the admin to protect certain web pages with a protection password. Further, you can also ser session expiry time, bypass permission and global password settings to strengthen the security feature. 

#5 Permissions by Term

Based on the taxonomy terms, you can restrict the access to specific content on the website. The permissions are applied to the user roles and work well for nodes, views, menus, etc. The Permissions by Term module also aids in linking the taxonomy terms with specific accounts. 

#6 Vocabulary Permissions Per Role

Within the taxonomy access, you can set up permits only for content editors to work with a particular taxonomy vocabulary without hampering the “administer taxonomy” permissions. The Vocabulary Permissions Per Role module gives you control over the number of users with permissions and access. 

#7 Override Node Options

You can now set permissions for each field on the node form within the Authoring information and Publishing options fieldsets. The Override Node Options module can also let your make certain fieldsets collapsible.

#8 Block Region Permissions

Control access to block management within each region of your website’s theme with the Block Region Permissions module. The module grants access to see the following:

  • Region's header, message, and blocks on block layout page
  • Region in region selector fields on block layout page
  • Region in region selector field on configure and place block pages
  • Can update or delete blocks placed in region

#9 Workflow

A contributed module, Workflow allows you to create workflow states for various node types. Further, it allows transitions between states like updating from “Draft” to “Published” and similar per role. It enables you to set up the Workflow to alter states from form, page, comment, a special block, and a special workflow tab.

#10 Content Access

As per the role and author, the content access module lets you manage the permissions for content types. You can specify custom view, edit and delete permissions for each content type or enable the content access settings. Thus, it allows customization of access for each content node. 

Conclusion

These modules are more than a boon for your Drupal website. They impart the highest of security walls for the users and helps your website remain safe from third-party users. The modules are designed to make sure that the user roles and permissions are not misused by the users and they optimize the development process for the team. 

To know more about the power of Drupal, drop us a line at [email protected]

Mobomo’s Picks: Top 10 Drupal Websites

Jan 30 2020
Jan 30

Interview with Marloes Bosch of LimoenGroen: Building bridges between cultures

Jan 30 2020
Jan 30
Jan 30 2020
Jan 30

Agiledrop is highlighting active Drupal community members through a series of interviews. Now you get a chance to learn more about the people behind Drupal projects.

This week we talked with Amber Matz, Production Manager and Trainer at Drupalize.Me. In addition to these two important roles, Amber is actively involved in a number of projects in the Drupalverse, the current most notable one likely being the Builder Track at DrupalCon Seattle. Have a read if you’d like to find out more about her journey with Drupal and her insights on its future.

1. Please tell us a little about yourself. How do you participate in the Drupal community and what do you do professionally?

My username on drupal.org is Amber Himes Matz. I participate in the Drupal Community in a number of ways. The bulk of my volunteer time lately has been consumed by the program team for the Builder Track at DrupalCon Seattle, where we review, select, schedule, and support speakers and sessions for the upcoming ‘con. I’m working on (as I am able) moving two issues forward, Add experimental module for Help Topics and new Draft "Getting Started" Outline & Guide. I’m also part of the Community Cultivation Grants Committee and like to keep tabs on what’s happening amongst Drupal camp organizers in Slack. (In February 2019, I was the lead organizer for the Pacific NW Drupal Summit in Portland, OR.) Professionally, I work on Drupalize.Me as Production Manager and Trainer for the platform, which features Drupal tutorials in both written and video format.

2. When did you first come across Drupal? What convinced you to stay, the software or the community, and why?

I was a web developer for an organization for many years working mostly with PHP and MySQL on the backend and HTML/CSS on the frontend. I coded a LOT of forms and form processing scripts. I discovered Drupal as an escape from that tedium. I stuck with it because I needed work and wanted a better job, which I eventually got. I stay with the Drupal community because of a rewarding and satisfying job, great people (local, global, and online), and the opportunity to travel.

3. What impact has Drupal made on you? Is there a particular moment you remember?

My career benefited greatly and singularly from showing up to a local Drupal user group meeting. From that first meeting, I made a connection which lead only weeks later to a job interview and my first job as a dedicated Drupal developer (which ended up being Developer + Client Manager + Project Manager). After this job experience, I was hired at Lullabot as a trainer for Drupalize.Me. (Drupalize.Me is now part of a sister company to Lullabot.)

4. How do you explain what Drupal is to other, non-Drupal people?

Drupal is a platform to structure and present loads of content in a scalable way.

5. How did you see Drupal evolving over the years? What do you think the future will bring?

I think the challenge for the Drupal community is to provide straightforward and accessible means for anyone to install, use, and customize Drupal. The great “tout” of Drupal is its scalability. And it certainly has and does scale. This presents a great challenge. How do we provide functionality, tools, documentation, and training for a platform that can be used for such a wide range of use cases? How do we make it easier to use the kinds of tools that are necessary for such a complex platform? I know that a lot of people are hard at work on these kinds of problems. I think the future of Drupal will mean gaining a better understanding of our user base and not assuming that everyone falls into an “enterprise” category or whatever.

6. What are some of the contributions to open source code or to the community that you are most proud of?

At the moment, I’m most proud of the line-up of speakers for the Builder Track for DrupalCon Seattle. The program team worked really hard choosing speakers and in the midst of a lot of changes to the ‘Con.

7. Is there an initiative or a project in Drupal space that you would like to promote or highlight?

Register for DrupalCon Seattle!

8. Is there anything else that excites you beyond Drupal? Either a new technology or a personal endeavorment.

I’ve been a web developer since about 2001 or so. That has added up to a lot of raging against the screen. I have discovered open source hardware and the “maker” community and have discovered the joy and pleasure of coding on a variety of microcontrollers and single-processor boards in a variety of applications from breadboarding to learn concepts in electronics to sewing with conductive thread to making a variety of fun and whimsical projects. Working with physical computing objects has brought back a level of sanity to the otherwise (come on, admit it) insane world of web development.

2019 Drupal Recording Year-end Update

Jan 29 2020
Jan 29
Jan 28 2020
Jan 28

Almost all sites have some terrible content that needs to be migrated. As a lot has changed since the 90s, HTML is a thing of the past. Now, we require more than just a static site to win the online world. 

A huge portion of the content has been vested in the power of the internet. Studies show how a major chunk of revenue in terms of traffic is gained via sites and the quality of content on the sites. While we were focusing on the workings of the site, the game is in the court of ‘How well you present the content’ now. Addressing these changes, migrating your static site to a CMS is the best option. 

Often viewed as a complex and time-consuming task, migration is the real devil. However, there’s light at the end of the tunnel. Once executed, migration woes reap greater business benefits.   

Let’s understand how. 

two women amid discussion against office background

Static Content

When a site uses HTML, CSS and a little bit of JavaScript/jQuery for development, it becomes a static site in technical terms. The content on static sites remain the same across pages and seems more like a database than being of representative value to an organization. It does not take into account factors like the inputs of the visitors or user experience.

Dynamic Content

As the name suggests, a dynamic site is more complex yet user-friendly. It has elements like product pages, descriptions and more that helps in fetching the valuable feedback from the users. It has more elements to interact with and gives a visually better experience to the viewers. 

Difficulties with static content

Often we are suggested of using static content for smaller projects and depending on the requirements, it makes sense too. However, there are a few complications that can arise with it:

  • The initial workings of an HTML were not meant to manage complex arrangements of files. It was only for small budget websites with limited content. 
  • Making edits to the project like adding a new item to a navigation bar requires shuffling between too many HTML files. 
  • Similarly, if you want to make any change to the static content, you need to go through an entire process of the HTML files. 

Things to consider while moving the static site to a CMS 

In order to make sure that the transition to dynamic content is manageable, mobile-responsive, and accessible, consider these pointers beforehand: 

  • The amount of content (pages, images, posts, JavaScript files) you want to migrate 
  • If you want to retain the existing domain name 
  • The current URL structure 
  • Compatibility of modules of CMS with external services 

With these are in check, the next part is taking care of the requirements of migration: 

  • Evaluate the current website 
  • Import the structure, content and design from the static site 
  • Set up the CMS environment 
  • Back up of both the HTML and CMS 
  • Deal with broken links and other migration issues 

Why Migrate?

The subsequent important question to answer is - why at all should you take the pain of migrating your static site? 

Here’s why: 

  • You can easily manage the ever-growing content with a CMS and do away with the HTML pages. 
  • The two elements - Content and presentation - get separated and thus can be dealt with individually. 
  • The SEO gets sorted with a CMS as it addresses Meta Tags, URL Patterns and Sitemap.
  • The site owners and editors have full control over the display and architecture and do not remain dependent on developers 
  • The workflow of the content becomes better 
  • Uniformity for the entire site can be maintained 
  • Open Source CMSs like Drupal are more scalable for your business

Why switch to a CMS? 

Even if we are convinced that we should migrate, why CMS is a wise choice for it? Here’s why:

Your website, your terms 

Elements like uploading an image or content on a web page is a task with static sites built on HTML. Having a CMS like Drupal allows you to update and edit the content of your website without requiring a web developer. It will empower you to build a dynamic site with a better user experience that you can customise easily. 

HTML not required

Drupal as a CMS comes with a WYSIWYG editor (What You See Is What You Get). Thus, using a CMS to create blog posts, content on a new web page or make changes in the placement becomes super fun and easy.  

Easy redesigning of the website

The workings of the CMS separates the design from the content. In case you want to change the look and feel of the site, the old design can be replaced without hampering the content. 

User-friendly 

Even for the end-user, a site on CMS is much organized. It generates more traffic, thus more revenue from the audience. As a user-friendly platform, it gives easy access to the users for customizing the site. 

Drupal Migration Tools that can help

  • Core migrate API: Provides a framework for migration from Drupal 7 to Drupal 8 only.
  • Migrate Plus and Migrate Tool: Provides an option to migrate content from CSV, JSON, Excel and XML Files.
  • Migrate File: Movement of media resources to Drupal.

Conclusion

With these issues in mind, moving to a CMS seems to be a wise option. If you are planning to build a site majorly based on content or design, choose a CMS to have a hassle-free site and enjoy the editor experience for yourself. Open source CMS like Drupal gives you the freedom to design your own content without the hindrances from the development team. 

Still, confused? Reach out to our experts at [email protected] and know more about the CMS services.  

Jan 26 2020
Jan 26

We need you!

Want to give back to the Drupal Community without writing a line of code? Volunteer to help out at MidCamp.  We’re looking for amazing people to help with all kinds of tasks throughout the event including: 

Setup/Teardown

  • For setup, we need help making sure registration is ready to roll, getting hats ready to move, and getting the rooms and walkways prepped for our amazing sessions.

  • For teardown, we need to undo all the setup including packing up all the rooms, the registration desk, cleaning signage, and making it look like we were never there.

Registration and Ticketing

Room Monitors & Hosts

  • Pick your sessions and count heads, intro the speakers and make sure they have what they need to survive, and help with the in-room A/V (by calling our Fearless Leader / A/V Genius)

Choose Your Own Adventure

  • We won't turn away any help, so if there's something you'd like to suggest to do to help out, go right ahead!

This year we're going to be giving every volunteer credit on Drupal.org, so be sure to include your profile name when you sign up to volunteer.

If you’re interested in volunteering or would like to find out more, please reach out in the #volunteers channel on the MidCamp Slack.

There will be a brief, online orientation leading up to the event to go over the volunteer opportunities more in detail. 

Sign up to Volunteer!

Nominations now open for the 2020 Aaron Winborn Award

Jan 23 2020
Jan 23
Jan 23 2020
Jan 23

Due to the cancellation of DrupalCon Minneapolis and the announcement of DrupalCon Global, nominations for the 2020 Aaron Winborn Award will remain open until June 1, 2020. The winner will be announced during DrupalCon Global.

The Drupal Community Working Group is pleased to announce that nominations for the 2020 Aaron Winborn Award are now open. 

This annual award recognizes an individual who demonstrates personal integrity, kindness, and above-and-beyond commitment to the Drupal community. It includes a scholarship and stipend for the winner to attend DrupalCon and recognition in a plenary session at the event.

Nominations are open to not only well-known Drupal contributors, but also people who have made a big impact in their local or regional community. If you know of someone who has made a big difference to any number of people in our community, we want to hear about it. 

This award was created in honor of long-time Drupal contributor Aaron Winborn, whose battle with Amyotrophic lateral sclerosis, or  ALS (also referred to as Lou Gehrig's Disease)  came to an end on March 24, 2015. Based on a suggestion by Hans Riemenschneider, the Community Working Group, with the support of the Drupal Association, launched the Aaron Winborn Award.

Nominations are open until Monday, June 1, 2020. A committee consisting of the Community Working Group core team as well as past award winners will select a winner from the nominations. Current members of the CWG's core team and previous winners are exempt from winning the award.

Previous winners of the award are:

  • 2015: Cathy Theys
  • 2016: Gábor Hojtsy
  • 2017: Nikki Stevens
  • 2018: Kevin Thull
  • 2019: Leslie Glynn

Now is your chance to show, support and recognize an amazing community member!

If you know someone amazing who should benefit from this award you can make your nomination using this form.
 

Jan 23 2020
Jan 23

In the Drupal support world, working on Drupal 7 sites is a necessity. But switching between Drupal 7 and Drupal 8 development can be jarring, if only for the coding style.

Fortunately, I’ve got a solution that makes working in Drupal 7 more like working in Drupal 8. Use this three-part approach to have fun with Drupal 7 development:

  • Apply Xautoload to keep your PHP skills fresh, modern, and compatible with all frameworks and make your code more reusable and maintainable between projects. 
  • Use the Drupal Libraries API to use third-party libraries. 
  • Use the Composer template to push the boundaries of your programming design patterns. 

Applying Xautoload

Xautoload is simply a module that enables PSR-0/4 autoloading. Using Xautoload is as simple as downloading and enabling it. You can then start using use and namespace statements to write object-oriented programming (OOP) code.

For example:

xautoload.info

name = Xautoload Example
description = Example of using Xautoload to build a page
core = 7.x package = Midcamp Fun

dependencies[] = xautoload:xautoload

xautoload_example.module

<?php use Drupal\xautoload_example\SimpleObject; function xautoload_example_menu() { $items['xautoload_example'] = array( 'page callback' => 'xautoload_example_page_render', 'access callback' => TRUE, ); return $items; } function xautoload_example_page_render() { $obj = new SimpleObject(); return $obj->render(); } useDrupal\xautoload_example\SimpleObject;functionxautoload_example_menu(){  $items['xautoload_example']=array(    'page callback'=>'xautoload_example_page_render',    'access callback'=>TRUE,  return$items;functionxautoload_example_page_render(){  $obj=newSimpleObject();  return$obj->render();

src/SimpleObject.php

<?php namespace Drupal\xautoload_example; class SimpleObject { public function render() { return array( '#markup' => "<p>Hello World</p>", ); } } namespaceDrupal\xautoload_example;classSimpleObject{  publicfunctionrender(){    returnarray(      '#markup'=>"<p>Hello World</p>",    );

Enabling and running this code causes the URL /xautoload_example to spit out “Hello World”. 

You’re now ready to add in your own OOP!

Using Third-Party Libraries

Natively, Drupal 7 has a hard time autoloading third-party library files. But there are contributed modules (like Guzzle) out there that wrap third-party libraries. These modules wrap object-oriented libraries to provide a functional interface. Now that you have Xautoload in your repertoire, you can use its functionality to autoload libraries as well.

I’m going to show you how to use the Drupal Libraries API module with Xautoload to load a third-party library. You can find examples of all the different ways you can add a library in xautoload.api.php. I’ll demonstrate an easy example by using the php-loremipsum library:

1. Download your library and store it in sites/all/libraries. I named the folder php-loremipsum. 

2. Add a function implementing hook_libraries_info to your module by pulling in the namespace from Composer. This way, you don’t need to set up all the namespace rules that the library might contain.

function xautoload_example_libraries_info() { return array( 'php-loremipsum' => array( 'name' => 'PHP Lorem Ipsum', 'xautoload' => function ($adapter) { $adapter->composerJson('composer.json'); } ) ); } functionxautoload_example_libraries_info(){  returnarray(    'php-loremipsum'=>array(      'name'=>'PHP Lorem Ipsum',      'xautoload'=>function($adapter){        $adapter->composerJson('composer.json');      }

3. Change the page render function to use the php-loremipsum library to build content.

use joshtronic\LoremIpsum; function xautoload_example_page_render() { $library = libraries_load('php-loremipsum'); if ($library['loaded'] === FALSE) { throw new \Exception("php-loremipsum didn't load!"); } $lipsum = new LoremIpsum(); return array( '#markup' => $lipsum->paragraph('p'), ); } usejoshtronic\LoremIpsum;functionxautoload_example_page_render(){  $library=libraries_load('php-loremipsum');  if($library['loaded']===FALSE){    thrownew\Exception("php-loremipsum didn't load!");  $lipsum=newLoremIpsum();  returnarray(    '#markup'=>$lipsum->paragraph('p'),

Note that I needed  to tell the Libraries API to load the library, but I then have access to all the namespaces within the library. Keep in mind that the dependencies of some libraries are immense. You’ll very likely need to use Composer from within the library and commit it when you first start out. In such cases, you might need to make sure to include the Composer autoload.php file.

Another tip:  Abstract your libraries_load() functionality out in such a way that if the class you want already exists, you don’t call libraries_load() again. Doing so removes libraries as a hard dependency from your module and enables you to use Composer to load the library later on with no more work on your part. For example:

function xautoload_example_load_library() { if (!class_exists('\joshtronic\LoremIpsum', TRUE)) { if (!module_exists('libraries')) { throw new \Exception('Include php-loremipsum via composer or enable libraries.'); } $library = libraries_load('php-loremipsum'); if ($library['loaded'] === FALSE) { throw new \Exception("php-loremipsum didn't load!"); } } } functionxautoload_example_load_library(){  if(!class_exists('\joshtronic\LoremIpsum',TRUE)){    if(!module_exists('libraries')){      thrownew\Exception('Include php-loremipsum via composer or enable libraries.');    $library=libraries_load('php-loremipsum');    if($library['loaded']===FALSE){      thrownew\Exception("php-loremipsum didn't load!");

And with that, you’ve conquered the challenge of using third-party libraries!

Setting up a New Site with Composer

Speaking of Composer, you can use it to simplify the setup of a new Drupal 7 site. Just follow the instructions in the Readme for the Composer Template for Drupal Project. From the command line, run the following:

composer create-project drupal-composer/drupal-project:7.x-dev <YOUR SITE DIRECTORY> --no-interaction

This code gives you a basic site with a source repository (a repo that doesn’t commit contributed modules and libraries) to push up to your Git provider. (Note that migrating an existing site to Composer involves a few additional considerations and steps, so I won’t get into that now.)

If you’re generating a Pantheon site, check out the Pantheon-specific Drupal 7 Composer project. But wait: The instructions there advise you to use Terminus to create your site, and that approach attempts to do everything for you—including setting up the actual site. Instead, you can simply use composer create-project  to test your site in something like Lando. Make sure to run composer install if you copy down a repo.

From there, you need to enable the Composer Autoload module , which is automatically required in the composer.json you pulled in earlier. Then, add all your modules to the require portion of the file or use composer require drupal/module_name just as you would in Drupal 8.

You now have full access to all the  Packagist libraries and can use them in your modules. To use the previous example, you could remove php-loremipsum from sites/all/libraries, and instead run composer require joshtronic/php-loremipsum. The code would then run the same as before.

Have fun!

From here on out, it’s up to your imagination. Code and implement with ease, using OOP design patterns and reusable code. You just might find that this new world of possibilities for integrating new technologies with your existing Drupal 7 sites increases your productivity as well.

Jan 21 2020
Jan 21

We had a Drupal project, implementing a commerce site for a local store. We use Drupal Commerce, as always, for this type of websites. You may see that we have alot of Drupal Commerce themes on our portfolio.

During the project, there was a minor request from our customer: add the Continue Shopping button to the cart. This feature is available on Ubercart, especially for Drupal 6 Ubercart users. Most of ecommerce sites have this feature as well. But it is not built-in with Drupal Commerce.

As I searched on the Drupal.org issues, I found a very helpful thread: Continue shopping in cart. Zorroposada presented a custom code to achieve it:


<?php
/**
* Implements hook_form_FORM_ID_alter(&$form, &$form_state, $form_id)
*/
function MYMODULE_form_views_form_commerce_cart_form_default_alter(&$form, &$form_state, $form_id) {
$form['actions']['continue_shopping'] = array(
'#type' => 'button',
'#value' => t('Continue Shopping'),
'#weight' => -999,
);
if (isset($_SERVER['HTTP_REFERER']) && strlen($_SERVER['HTTP_REFERER'])) {
// if user comes from product detail page, redirect user to previous page
$form['actions']['continue_shopping']['#attributes'] = array('ONCLICK' => "history.go(-1); return false;");
} else {
// redirect user to product list page 'store' by default
$form['actions']['continue_shopping']['#attributes'] = array('ONCLICK' => "window.location.href='" . url('store') . "'; return false;");
}
}
?>

I do nothing better here. I just wrap this code on a custom module, so any lazy users can just download and install it.

The module is called "Commerce Continue Shopping", and you will find it in the Other section of the Drupal module page when unzip it to /sites/all/modules. Enable it and you will see the Continue Shopping button on your cart.

Pls download the module here: commerce_continue_shopping_7.x-1.0.zip

Accessibility testing - manual or automated?

Jan 20 2020
Jan 20
Jan 20 2020
Jan 20

Websites and apps should be designed and developed in such a way that people with disabilities (permanent, temporary or situational) find them easy to use (if you’re unsure on what accessibility is, take a look at our blog, What is digital accessibility?).

But how do we measure accessibility? How can we tell whether a site or an app is accessible? The answer is by checking it against international accessibility standards - Web Content Accessibility Guidelines (WCAG) 2.1. This can be done in two ways: by using automated testing tools or carrying out a manual audit. In this article we’ll compare the two methods to better understand when and how they should be used and look at their advantages and disadvantages.

Automated accessibility testing

Automated evaluation can be carried out by anyone with access to an accessibility testing tool. There’s a number of free and paid tools available, including:

Accessibility testing tools run a set of scripts checking web content against certain criteria based on WCAG 2.1. For example, according to WCAG, every <img> element (an image) has to have “alt” attribute which is used to provide a text description of that image for the benefit of users who cannot see it:

<img alt=”black cat sleeping on a sofa” src=”/images/123.jpg” />

The relevant script would consist of the following steps:

  1. Check if there are any <img> elements

    1. If no, the result is N/A

    2. If yes, check if each of those elements has “alt” attribute

      1. If yes, the result is Pass

      2. If no, the result is Fail

Automated testing pros:

  • It’s easy - it can be done by anyone with access to a testing tool

  • It’s fast - it can check hundreds of pages and provide results in a matter of hours

  • It’s cheap - some of the automated tools are free to use

  • It can detect problems early on - some tools can be integrated into the development process and run tests whenever new code is added or perform regular scans to ensure that no new issues have been introduced since the last check. 

Automated testing cons:

  • Automated testing tools are not able to test websites or apps against all success criteria listed in WCAG. Many guidelines are objective and therefore can’t be tested using a script as they require human judgement. For example, while automated tools can check whether an image has an “alt” attribute, they cannot evaluate whether it’s correctly used, i.e. whether its value conveys the same information as the image. A picture of a cat described as “red car” would pass automated testing.

This also applies to links. Accessibility guidelines require link texts to be descriptive, and yet the link below would pass automated accessibility testing:

<a href=”/contact-us”>go somewhere else</a>

Automated tools can check whether link text is provided, but they cannot determine whether that text accurately describes the link’s purpose.

  • Automated testing can generate false or misleading results. Even when checking against guidelines that can be tested using automated tool, the results may be inaccurate or wrong. Someone with no or limited knowledge of web accessibility may not be able to correctly interpret the results. 

  • The advice on how to fix the issues is quite generic and often vague and therefore some developers may find it difficult to understand and implement the required changes. 

Someone who is not aware of the limitations of automated tools may not realise that even though a site has passed automated testing, it may still have many barriers preventing people with disabilities from accessing its content. When the site passes an automated test it only means that there were no errors found in checks that can be performed by an automated tool and not that the site is accessible. Most automated tools can only check a small number of criteria reliably (e.g. 6 out of 50) so automated accessibility testing cannot determine whether a website is WCAG 2.1 compliant.

Laptop

Manual testing

Manual testing is carried out by an accessibility expert who checks a subset of pages or app screens (usually 5 - 20) against WCAG 2.1 criteria. Every page element and their underlying code is manually evaluated for issues related to non-text content (images, audio, video), use of colour, keyboard accessibility, descriptive links, labels and instructions, correct HTML structure and many others.

Manual testing pros:

  • A manual audit is much more thorough and, unlike automated testing, includes checking the content against all WCAG 2.1 criteria at a required level

  • The results of manual accessibility testing are considered much more reliable and trustworthy

  • While results generated by automated testing tools are generic, a report written by an accessibility expert is specific to your site and includes realistic and tailor-made solutions.

Manual testing cons:

  • Manual testing is more time-consuming and therefore expensive compared to automated testing. The cost depends on the coverage - the more pages included in the audit, the more expensive it is. However, even though in most cases it’s not possible to manually check all pages (unless the site is really small), an accessibility expert who is also an experienced Drupal developer is able to select a good representative sample of pages that includes as many content types and components as possible to maximise test coverage

  • The value of the audit depends largely on the knowledge and experience of the accessibility expert who carries it out. While any accessibility expert can identify issues on a Drupal site, an accessibility expert with good working knowledge of Drupal is able to provide much more detailed advice on how to fix the issues found during the audit. For example, rather than just saying, “these images need alternative description”, they can specify where and how in the code or CMS that change needs to be made. This makes implementing suggested fixes much easier and cost effective for the client. 

Recommended approach

Automated and manual testing complement each other and should both be used to assess the level of accessibility of a website or app. Generally speaking, automated testing can identify some of the issues found on pages across the site but manual testing can find all of the issues on a subset of pages.

As mentioned above, automated testing has many limitations, but it will give you a rough idea of the level of accessibility of your site. Automated testing can quickly check a large number of pages and find some obvious issues, such as missing alternative descriptions or form fields without labels.

We’d then suggest embarking upon a manual accessibility audit. An experienced accessibility expert can help you select a good sample of pages which will cover all (or key) content types and components. This is important to make the manual audit cost-effective and ensure you get the best value for money. Following the audit you’ll receive a report which will clearly explain all issues found on your site and provide tailor-made and specific suggestions on how to fix them.

Any issue described in the report should then be resolved, remembering to apply the suggested fixes to not only to pages included the audit, but also to other areas of your site. 

Once all identified issues are fixed, a re-test can be carried out to confirm that they’ve been correctly resolved and no new issues have been accidentally introduced in the process. 

Following an automated and manual audit, you could look at usability testing with people with disabilities so that further improvements can be made to your site. Regular automated testing and training sessions for content authors and developers contributing to the site will help maintain its high level of accessibility and prevent new issues.

Want to know more about how we can help you on your accessibility journey? Get in touch

Jan 20 2020
Jan 20

Help us set the stage for future configuration management improvements in core.

The configuration management initiative 2.0 needs your help.

I will be presenting updates of CMI 2.0 at the upcoming Drupalcon Seattle together with Mike Potter.

Some of the highlights of the CMI 2.0 road map for inclusion in Drupal core are an improved version of the concept of Config Filter (in the form of the config storage transformation api) and a simplified version of Config Split (in the form of config environments).
Unfortunately those big things will not make it into 8.7, but we could lay the ground work for 8.8.

But the deadline for some patches for Drupal 8.7 is this Friday!

It would be great if you could help us get the following issues to RTBC and committed:

#3036193: Add ExportStorageFactory to allow config export in third party tools
This would allow us to add a service to core that drush, drupal console and other tools and modules can use to export configuration. If this lands in 8.7 we will be able to patch Drush and Drupal Console between 8.7 and 8.8 and make improvements to configuration management such as adding a Config Environment module to core without then patching the cli tools again afterwards.

#3016429: Add a config storage copy utility trait
This adds a new utility trait that would make dealing with configuration storages easier. Currently there are a bunch of modules that implement this logic by themselves and not all of them do it correctly. This lead to bugs in Drush and Drupal Console and even Drupal core has a bug (which is fixed by this issue).

Thanks already in advance.

PS: If you are interested in more CMI 2.0 issues to review or work on check the CMI 2.0 candidate issue tag.

Announcing the 2020 series of Drupal Event Code of Conduct Training

Jan 19 2020
Jan 19
Jan 19 2020
Jan 19

The Drupal Community Working Group is happy to announce that we are once again teaming up with Otter Tech to offer live, monthly, online Code of Conduct enforcement training for Drupal Event organizers and volunteers in 2020.

During the second half of 2019, 17 Drupal community members completed the training (see full list), helping to ensure Drupal events world-wide have qualified Code of Conduct contacts. We are excited to be able to provide support for the training in 2020.

The training is designed to provide "first responder" skills to Drupal community members who take reports of potential Code of Conduct issues at Drupal events, including meetups, camps, conventions, and other gatherings. The workshops will be attended by Code of Conduct enforcement teams from other open source events, which will allow cross-pollination of knowledge with the Drupal community.

Each monthly online workshop is the same; community members only have to attend one monthly workshop of their choice to complete the training.  We strongly encourage all Drupal event organizers to consider sponsoring one or two persons' attendance at this workshop.

The monthly online workshops will be presented by Sage Sharp, Otter Tech's CEO and a diversity and inclusion leader in the open source community. From the official description of the workshop, it will include:

  • Practice taking a report of a potential Code of Conduct violation (an incident report)

  • Practice following up with the reported person

  • Instructor modeling on how to take a report and follow up on a report

  • One practice scenario for a report given at an event

  • One practice scenario for a report given in an online community

  • Discussion on bias, microaggressions, personal conflicts, and false reporting

  • Frameworks for evaluating a response to a report

  • 40 minutes total of Q&A time

In addition, we have received a Drupal Community Cultivation Grant to help defray the cost of the workshop for those that need assistance. The standard cost of the workshop is $350, Otter Tech has worked with us to allow us to provide the workshop for $300. To register for the workshop, first let us know that you're interested by completing this sign-up form - everyone who completes the form will receive a coupon code for $50 off the regular price of the workshop.

For those that require additional assistance, we have a limited number of $100 subsidies available, bringing the workshop price down to $200. Subsidies will be provided based on reported need as well as our goal to make this training opportunity available to all corners of our community. To apply for the subsidy, complete the relevant section on the sign-up form. The deadline for applying for the subsidy is end-of-business on Wednesday, July 1, 2020 - those selected for the subsidy will be notified after this date (in time for the July 8, 2020 workshop).

The workshops will be held on:

Those that successfully complete the training will be (at their discretion) listed on Drupal.org (in the Drupal Community Workgroup section) as a means to prove that they have completed the training. We feel that moving forward, the Drupal community now has the opportunity to have professionally trained Code of Conduct contacts at the vast majority of our events, once again, leading the way in the open source community.

We are fully aware that the fact that the workshops will be presented in English limit who will be able to attend. We are more than interested in finding additional professional Code of Conduct workshops in other languages. Please contact us if you can assist.

Drupal 9: What’s New, What to Expect

Jan 17 2020
Jan 17
Jan 16 2020
Jan 16

We had been always using FAQ module to create Frequently Asked Questions for any of our projects. But on our latest theme, BizReview, we switch to a new module, FAQ Field.

FAQ is a classic module, it is there since Drupal 6. So when we have to build a FAQ section, using this module is a no brainer. This is the FAQ module in action on our Velocity theme.

Velocity FAQ section

Recently, when I wanted to download that module again for my new project, I was too lazy to type the full URL, so I google "drupal faq". It happened to display another new module, called FAQ field. I looked at the description and found it very promising. So I gave it a try. After using it, I have the following comparisions between the two modules.

FAQ Field
  • Older, since Drupal 6. 34K+ usages
  • Uses Views to display FAQ nodes
  • Create new question by adding new FAQ node
  • Needs to use Weight to control the question orders.
  • 4 diplay options on configuration
  • Allow subcategories
  • Newer, only since Drupal 7. 1,700+ usages
  • Integrate as a field of a node
  • Create new question by adding a field value
  • Drag and drop to control the question orders.
  • 4 display options on fied display configuration
  • No subcategories

I found the FAQ Field module is more light weight and easier to use. I just need to create a new content type with the FAQ field, then add a new node with any number of questions, instead of creating a separate node for each question. Ordering is much simpler, just drag and drop, instead of typing weight numbers.

Installation and tutorial

Here are the steps to use the FAQ field module:

  1. Install FAQ field (no dependancies)
  2. Create a new content type, FAQ for example. Add the FAQ field

    FAQ field on new content type

  3. Create a FAQ node

    FAQ field on node

  4. The result

FAQ field in action

Jan 15 2020
Jan 15

Read our Roadmap to understand how this work falls into priorities set by the Drupal Association with direction and collaboration from the Board and community.

Project News

Get Ready for Drupal 9

The next minor release of Drupal will be 8.9 - to be released simultaneously with Drupal 9.0. The first target window for the release is this coming June, so now is the best time to get ready for the release.

As it turns out, many contributed or even custom modules only need a one-line change to be ready for Drupal 9. Check yours using: the upgrade status module, or the Drupal Check command line tool.

DrupalCon Minneapolis 2020

DrupalCon MinneapolisSpeaking of Drupal 9, DrupalCon Minneapolis is coming up from May 18-20. We expect their to be a large amount of programming and contribution focused on the upcoming release of Drupal 9. Minneapolis will be a great opportunity to get help with checking your module compatibility, or to find someone who can help you get your Drupal 7 or 8 site ready for the upgrade. Get your tickets now, before prices go up!

DrupalCon Europe 2020

Did you hear the news? DrupalCon Europe 2020 has been announced - and DrupalCon is coming back to Barcelona from Sep 14-17th.

Our partnership with Kuoni Congress continues, and we're excited to join you in beautiful Spain to celebrate Drupal 9 together.

Kuoni Congress

Mark your calendars, and bookmark the site - more info coming soon!

Drupal.org Update

Have you unwrapped automatic updates yet?

In November we finished the primary engineering work to support Phase 1 of the Automatic Updates initiative. In December we completed validation and testing, and launched the first stable release of the Automatic Updates module.

In its current form, Automatic Updates is available as a contributed module for both Drupal 7 and Drupal 8. After installing the module you'll be able to take advantage of three new features: 

  • When the Drupal Security Team identifies a critical release, they'll be able to publish a PSA that will be directly displayed in your admin interface. 
  • The module will run automated readiness checks to make sure your site is ready for the update, or to let you know if there are errors you need to fix.
  • The module will automatically apply Drupal core updates. 

What about automatic updates for contributed modules and composer dependencies?

The next phase of work on the Automatic Updates initiative is to support updates for contributed modules, and for sites managing Composer dependencies.

This is where we need your help! We're looking for organizational sponsors to help move this work forward. If you're interested in helping us move the initiative into the next phase, please contact the Association.

We want to thank: The European Commission, Acquia, Tag1, Mtech, and Pantheon for their support of Phase 1.

Expanding Drupal Solution content with Feature pages

About two years ago we decided to start featuring Drupal Solutions on Drupal.org. These Solutions are examples of Drupal being used in the real world in specific use cases. Our first series of this content was the Drupal Industry pages, highlighting the power for Drupal in specific industry verticals.

In December, we've just launched our next set of content, this time focusing on specific features of Drupal that set it apart from the competition. These Feature pages talk about the specific Drupal Solutions that are built around key features of the software.

Updating our packaging pipeline

Do you know what goes into a packaged release of Drupal? It's not just a git clone - and as of Drupal 8.8.0 the package you download from Drupal.org also includes Composer scaffold files.  As Drupal evolves, the way we deliver the software to users has to evolve along with it.

To support the increasingly sophisticated packaging process for Drupal, we started work on overhauling our packaging pipeline for Drupal releases. This work continues into January.

Preparing for contrib Semver

As part of Drupal 9's release we are working to migrate all of the projects on Drupal.org to properly use semantic versioning. Right now, contributed modules typically use a version format like: 7.x-1.6 or 8.x-1.7. The first part of this is just the platform version (D7 vs. D8), and the second part is the Major version and the Patch version.

We'll be migrating this version schema so that the current Major version remains the Major version, the current Patch version becomes the Minor version, and we'll add the ability to define new patch versions.

This enables several improvements. Firstly, contrib maintainers can now follow backwards compatibility policies similar to core, i.e: Major versions with backwards compatibility breaking changes, minor version with new features, and patch versions for bug fixes and security releases.  Secondly, because contributed modules can now be compatible with both Drupal 8 and Drupal 9, contrib semver will be an important part of keeping version management sane.

We've made some initial progress in that direction, and have a roadmap for completing this support.

———

As always, we’d like to say thanks to all the volunteers who work with us, and to the Drupal Association Supporters, who make it possible for us to work on these projects. In particular, we want to thank:

If you would like to support our work as an individual or an organization, consider becoming a member of the Drupal Association.

Follow us on Twitter for regular updates: @drupal_org, @drupal_infra

Skip hooks during a Drupal 8 migration

Jan 15 2020
Jan 15

What is digital accessibility?

Jan 15 2020
Jan 15
Jan 15 2020
Jan 15

The word accessibility is used to describe whether something can be used by everyone, regardless of ability. Digital accessibility is referring to websites and apps ensuring that people with disabilities (permanent, temporary or situational) find them easy to use. 

At least 15% of the world’s population - one billion people - have a recognised disability. There are many ways in which a person’s disability may affect the way they perceive information online, and how they navigate within pages. 

The World Wide Web Consortium (W3C) is the main international standards organisation for the Web. One of the standards created by the W3C is the Web Content Accessibility Guidelines (WCAG). Now in version 2.1, WCAG is a set of recommendations for making Web content more accessible. 

Why is it important?

As the W3C state:

“Businesses that integrate accessibility are more likely to be innovative, inclusive enterprises that reach more people with positive brand messaging that meets emerging global legal requirements.”

Having become a staple within business strategy, disability inclusion is increasingly becoming a key component of an organisation’s digital strategy. Accessibility is good for business for many reasons, including: 

  • For every ethical and socially responsible organisation, it’s simply the right thing to do
  • In the UK alone the disability market, the so-called Purple Pound, is worth an estimated £249 billion every year
  • To maximise your website’s traffic and user base - if there are fewer barriers to accessing content, it’s likely that more people will use your website
  • Many accessibility guidelines coincide with UX and SEO best practices and so making a site accessible for people with disabilities will also make it easier to find and better to use for everyone
  • And last but not least, for public sector websites and apps, it’s not a choice - it’s a legal requirement

More information on this can be found in the W3C’s article, The Business Case for Digital Accessibility.

How do we create an accessible digital experience?

How can we tell whether a website or an app is accessible? The answer is by checking it against international accessibility standards - Web Content Accessibility Guidelines (WCAG) 2.1. While it is easier to build a fresh site adhering to the guidelines, remediation can also be done to improve the accessibility of an existing site. 

Assessing the accessibility of your website can be done in two ways: by using automated testing tools or carrying out a manual audit. Read more about the Pros and cons of a manual vs automated accessibility audit.

Accessibility isn’t a one-off task, but an ongoing commitment.  An audit can help you both establish your current state of accessibility, and then can be used as a tool to check in as your site grows and evolves. 

Following an automated and manual audit, you could look at usability testing with people with disabilities so that further improvements can be made to your site. Regular automated testing, manual testing and training sessions for content authors and developers contributing to the site will help maintain its high level of accessibility and prevent new issues.

Your next steps

Online services are a critical part of modern life. Building accessibility into your digital strategy is ensuring that all people can access the information they need. 

Get in touch with us today to find out how you can embark upon your accessibility journey. 

Jan 14 2020
Jan 14

How can more maintainable custom code in Drupal be written? Refactor it to follow SOLID software design principles. As long SOLID purity isn't pursued into an endless rabbit hole, SOLID principles can improve project maintainability. When a project has low complexity, it is worthwhile to respect these principles because they're simple to implement. When a project is complex, it is worthwhile to respect these principles to make the project more maintainable.

Wrapped entities are custom classes around existing entities that will allow custom Drupal code to be more SOLID. This is achieved in three steps:

  1. Hide the entities from all custom code.
  2. Expose meaningful interaction methods in the wrapped entity.
  3. Implement the wrapped entities with the SOLID principles in mind.

Finally, many of the common operational challenges when implementing this pattern are solved when using the Typed Entity module.

SOLID Principles

More information can be found about the SOLID principles in the Wikipedia article, but here’s a quick summary:

Single responsibility principle: A class should only have a single responsibility, that is, only changes to one part of the software's specification should be able to affect the specification of the class.

Open–closed principle: Software entities should be open for extension, but closed for modification.

Liskov substitution principle: Objects in a program should be replaceable with instances of their subtypes without altering the correctness of that program. This is often referred to as design by contract.

Interface segregation principle: Many client-specific interfaces are better than one general-purpose interface.

Dependency inversion principle: One should "depend upon abstractions, [not] concretions."

Here, the focus will be on the wrapped entity pattern. In this pattern, it's discovered that PHP classes can be created that wrap Drupal entities to encode a project's business logic and make the code more SOLID. This is the logic that makes a project unique in the world and requires custom code to be written in the first place because it’s not completely abstracted by libraries and contributed modules.

First, understand the theory, and then look at the actual code.

Building a Library Catalog

To help exemplify this pattern, imagine being tasked with building the catalog for a local library. Some of these examples may be solved using a particular contrib module. However, this exercise focuses on custom code.

Entities Are Now Private Objects

Business logic in Drupal revolves around entities and the relationships between them. Often, the business logic is scattered in myriad hooks across different modules. The maintainability of custom code can be improved by containing all this custom logic in a single place. Then, the myriad of hooks can make simple calls to this central place.

The business logic is often contained around entities. However, entities are very complex objects in Drupal 8. They’re already associated with many features; they can be moderated, rendered, checked for custom access rules, part of lists, extended via modules, and more. How can the complexity be managed with the business logic when already starting with such a complex object? The answer is by hiding the entity from the custom code behind a facade. These objects are called wrapped entities.

In this scenario, only the facade can interact directly with the entity at any given time. The entity itself becomes a hidden object that only the facade communicates with. If $entity->field_foo appears anywhere in the code, that means that the wrapped entity needs a new method (or object) that describes what's being done.

Wrapped Entities

In the future, it may be possible to specify a custom class for entities of a given bundle. In that scenario, Node::load(12) will yield an object of type \Drupal\physical_media\Entity\Book, or a \Drupal\my_module\Entity\Building. Nowadays, it always returns a \Drupal\node\Entity\Node. While this is a step forward, it’s better to hide the implementation details of entities. Adding the business logic to entity classes increases the API surface and is bad for maintenance. This approach also creates a chance of naming collisions between custom methods and other added-to entities in the future.

A wrapped entity is a class that can access the underlying entity (also known as "the model") and exposes some public methods based on it. The wrapper is not returned from a Node::load call, but created on demand. Something like \Drupal::service(RepositoryManager::class)->wrap($entity) will return the appropriate object Book, Movie, etc. In general, a WrappedEntityInterface.

A wrapped entity combines some well known OOP design patterns:

In this catalog example, the class could be (excuse the code abridged for readability):

    interface BookInterface extends
      LoanableInterface,
      WrappedEntityInterface,
      PhysicalMediaInterface,
      FindableInterface {}

    class Book extends WrappedEntityBase implements BookInterface {

      private const FIELD_NAME_ISBN = 'field_isbn';

      // From LoanableInterface.
      public function loan(Account $account, DateTimeInterval $interval): LoanableInterface { /* … */ }
      // From PhysicalMediaInterface.
      public function isbn(): string {
        return $this->entity->get(static::FIELD_NAME_ISBN)->value;
      }
      // From FindableInterface.
      public static function getLocation(): Location { /* … */ }

    }

More complete and detailed code will follow. For now, this snippet shows that using this pattern can turn a general-purpose object (the entity) into a more focused one. This is the S in SOLID. There are commonalities that group methods together when considering the semantics of each method written. Those groups become interfaces, and all of a sudden, it's apparent interface segregation has been implemented. SOLID. With those granular interfaces, things like the following can be done:

array_map(
  function (LoanableInterface $loanable) { /* … */ },
  $loanable_repository->findOverdue(new \DateTime('now'))
);

Instead of:

array_map(
  function ($loanable) {
    assert($loanable instanceof Book || $loanable instanceof Movie);
    /* … */
  },
  $loanable_repository->findOverdue(new \DateTime('now'))
);

Coding to the object's capabilities and not to the object class is an example of dependency inversion. SOLID.

Entity Repositories

Wrapped entity repositories are services that retrieve and persist wrapped entities. In this case, they're also used as factories to create the wrapped entity objects, even if that is not very SOLID. This design decision was made to avoid yet another service when working with wrapped entities. The aim is to improve DX.

While a wrapped entity Movie deals with the logic of a particular node, the MovieRepository deals with logic around movies that applies to more than one movie instance (like MovieRepository::findByCategory), and the mapping of Movie to database storage.

Further Refinements

Sometimes, it's preferred to not have only one type per bundle. It would be reasonable that books tagged with the Fantasy category upcast to \Drupal\physical_media\Entity\FantasyBook. This technique allows for staying open for extension (add that new fantasy refinement) while staying closed for change (Book stays the same). This takes O into account. SOLID. Since FantasyBook is just a subtype of Book, this new extension can be used anywhere where a book can be used. This is called the Liskov substitution principle, our last letter. SOLID.

Of course, the same applies to heterogeneous repositories like LoanableRepository::findOverdue.

Working On The Code

This section shows several code samples on how to implement the library catalog. To better illustrate these principles, the feature requirements have been simplified. While reading, try to imagine complex requirements and how they fit into this pattern.

Typed Entity Module

I ported the Typed Entity module to Drupal 8 to better support this article, and it has helped a lot with my Drupal 7 projects. I decided to do a full rewrite of the code because I have refined my understanding of the problem during the past four years.

The code samples leverage the base classes in Typed Entity and its infrastructure. The full code samples can be accessed in this repository. This repo contains the latest code and corrections.

Hands-On

The first focus will be to create a wrapped entity for a book. This facade will live under \Drupal\physical_media\WrappedEntities\Book. The class looks like this (for now):

namespace Drupal\physical_media\WrappedEntities;

use Drupal\physical_media\Location;
use Drupal\typed_entity\WrappedEntities\WrappedEntityBase;

class Book extends WrappedEntityBase implements LoanableInterface, PhysicalMediaInterface, FindableInterface {

  const FIELD_NAME_ISBN = 'field_isbn';
  const FIELD_NAME_LOCATION = 'field_physical_location';

  // From PhysicalMediaInterface.
  public function isbn(): string {
    return $this->getEntity()->get(static::FIELD_NAME_ISBN)->value;
  }

  // From FindableInterface.
  public function getLocation(): Location {
    $location = $this->getEntity()->get(static::FIELD_NAME_LOCATION)->value;
    return new Location(
      $location['building'],
      $location['floor'],
      $location['aile'],
      $location['section']
    );
  }

}

Then, the repository will be registered in the service container. The physical_media.services.yml contains:

services:
  physical_media.typed_entity.repository.book:
    # If you don't have custom logic for your repository you can use the base
    # class and save yourself from writing another empty class.
    # class: Drupal\typed_entity\TypedRepositories\TypedEntityRepositoryBase
    class: Drupal\physical_media\TypedEntityRepositories\BookRepository
    parent: Drupal\typed_entity\TypedRepositories\TypedEntityRepositoryBase
    public: true
    tags:
      -
        name: typed_entity_repository
        entity_type_id: node
        bundle: book
        wrapper_class: Drupal\physical_media\WrappedEntities\Book

Important bits are:

  • Specify the parent key to inherit additional service configuration from the contrib module.
  • If there's no reason to have a custom repository, the base class under the class key can be used.
  • Add the service tag with all the required properties:
    • name: this should always be typed_entity_repository.
    • entity_type_id: the entity type ID of the entities that will be wrapped. In this case, books are nodes.
    • bundle: the bundle. The bundle can be omitted only if the entity type has no bundles, like the user entity.
    • wrapper_class: the class that contains the business logic for the entity. This is the default class to use if no other variant is specified. Variants will be covered later.

Once these are done, the wrapped entity can begin integration into the hook system (or wherever it pertains). An integration example that restricts access to books based on their physical location could be:

<?php
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Session\AccountInterface;
use Drupal\node\NodeInterface;
use Drupal\physical_media\WrappedEntities\FindableInterface;
use Drupal\typed_entity\RepositoryManager;

/**
 * Implements hook_node_access().
 */
function physical_media_node_access(NodeInterface $node, $op, AccountInterface $account) {
  if ($node->getType() !== 'book') {
    return;
  }
  $book = \Drupal::service(RepositoryManager::class)->wrap($node);
  assert($book instanceof FindableInterface);
  $location = $book->getLocation();
  if ($location->getBuilding() === 'area51') {
    return AccessResult::forbidden('Nothing to see.');
  }
  return AccessResult::neutral();
}

Better yet, the Book could be refactored and that leaking business logic (the one checking for specific buildings) could be put into it, and then a reasonable interface could be implemented for it (favor existing ones) like Book … implements \Drupal\Core\Access\AccessibleInterface.

…
use Drupal\Core\Access\AccessibleInterface;
use Drupal\Core\Access\AccessResult;

class Book … implements …, AccessibleInterface {
  …
  // From AccessibleInterface.
  public function access($operation, AccountInterface $account = NULL, $return_as_object = FALSE) {
    $location = $this->getLocation();
    if ($location->getBuilding() === 'area51') {
      return AccessResult::forbidden('Nothing to see.');
    }
    return AccessResult::neutral();
  }

}

While evolving the hook into:

function physical_media_node_access($node, $op, $account) {
  return $node->getType() === 'book' ?
    \Drupal::service(RepositoryManager::class)
      ->wrap($node)
      ->access($op, $account, TRUE)
    : AccessResult::neutral();
}

The code can still be improved to remove the check on 'book'. Books are checked for access because of knowledge about the business logic. That leak can be avoided by trying to code to object capabilities instead (D in SOLID).

function physical_media_node_access($node, $op, $account) {
  try {
    $wrapped_node = \Drupal::service(RepositoryManager::class)->wrap($node);
  }
  catch (RepositoryNotFoundException $exception) {
    return AccessResult::neutral();
  }
  return $wrapped_node instanceof AccessibleInterface
    ? $wrapped_node->access($op, $account, TRUE)
    : AccessResult::neutral();
}

After that, the hook can remain the same when access control is implemented in movies. Not having to trace the whole codebase for potential changes when new features are added or existing ones are changed is a big win for maintainability.

Handling Complexity

As code grows in complexity, making it maintainable becomes more difficult. This is a natural consequence of complexity so it's best to be pragmatic and not take the SOLID principles as a hard rule. They exist to serve a purpose, not the other way around.

There are some ways to contain complexity. One is to avoid passing other entities into the wrapper methods by using wrapped entities instead. This creates more discipline in making the business logic explicit. This example gets the wrapped user entity as the author of an article.

public function owner(): ?WrappedEntityInterface {
  $owner_key = $this->getEntity()->getEntityType()->getKey('owner');
  if (!$owner_key) {
    return NULL;
  }
  $owner = $this->getEntity()->{$owner_key}->entity;
  if (!$owner instanceof EntityInterface) {
    return NULL;
  }
  $manager = \Drupal::service(RepositoryManager::class);
  assert($manager instanceof RepositoryManager);
  return $manager->wrap($owner);
}

Another way to contain complexity is by splitting wrapped entities into sub-classes. If having several methods that don’t apply to some books, variants can be beneficial. Variants are wrappers for a given bundle that are specific to a subgroup.

The Typed Entity Examples submodule contains an example on how to create a variant. In that example, the repository for articles hosts variant conditions. Calls to RepositoryManager::wrap($node) with article nodes now yield Article or BakingArticle depending on whether or not the node is tagged with the term 'baking'. The contrib module comes with a configurable condition to create a variant based on the content of a field. That is the most common use case, but any condition can be written. If having a different wrapper is preferred for Wednesday articles, that (odd) condition can be written implementing VariantConditionInterface.

Summary

Decades-old principles were highlighted as still being relevant to today’s Drupal projects. SOLID principles can guide the way custom code is written and result in much more maintainable software.

Given that entities are one of the central points for custom business logic, the wrapped entities pattern was covered. This facade enables making the business logic explicit in a single place while hiding the implementation details of the underlying entity.

Finally, the Typed Entity module as a means of standardization across projects when implementing this pattern was explored. This tool can only do so much because ultimately, the project’s idiosyncrasies cannot be generalized; each project is different. However, it is a good tool to help promote more maintainable patterns.

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