Author

Jan 20 2017
Jan 20

For many Drupal web sites setting permissions for anonymous, authenticated, and admin users through the GUI is sufficient. For example, all published content should be visible to all users, authenticated users can leave comments, and admin users are allowed to create content. For more advanced use cases the popular contributed module Content Access (beta for Drupal 7, dev for Drupal 8) allows much finer grained control over read and write access to nodes by content type, and can even specify access differently for individual nodes.

When even more complex permissions are needed many choose to implement hook_node_access(). Permissions management with hook_node_access() does have a few disadvantages:

  • Unwieldy implementations can cause considerable performance bottlenecks
  • Node operation links like View or Edit associated with node permissions aren’t automatically added or removed
  • Views queries are unaffected; content could be displayed to a user in a views block which they would otherwise not have access to

Managing permissions with hook_node_access() works fine in many cases, but it’s not the most flexible way to manage access to your nodes.

Custom permissions with node access grants in Drupal 8

A more robust solution to complex permissions is to use the node access system with hook_node_access_records() and hook_node_grants(). Hook_node_access_records() is called when each node is saved. That’s where grants are setup to view, update, and/or delete a node. Hook_node_grants() is called to determine access and is what is used to check the node_access table.

The good news is node access grants work (almost) exactly the same in Drupal 8 as in 7.

When researching how to implement node grants, I had come across relatively simple examples where access was based on a user’s role or organic groups properties. Since the user object is passed to hook_node_grants(), it’s trivial to determine which user should get access. But, what if access to view or edit a node is based on a combination of factors? This was the situation I recently had to deal with.

The implementation below creates a View grant for accounts that meet a specific criteria. The code for the actual criteria has been omitted. It also creates a full access grant for administrators using a zero as the grant id -- not to be confused with the UID associated with anonymous users.

function MODULENAME_node_access_records(NodeInterface $node) {
// code to get accounts that should have read access is not shown
  foreach ($accounts as $account) {
      $grants[] = array(
        'realm' => 'custom_access',
        'gid' => $account->id(),
        'grant_view' => 1,
        'grant_update' => 0,
        'grant_delete' => 0,
        'langcode' => 'en',
      );
  }
 
  $grants[] = array(
    'realm' => 'custom_access',
    'gid' => 0, // This is the admin GID, not the $account->uid associated with anonymous
    'grant_view' => 1,
    'grant_update' => 1,
    'grant_delete' => 1,
    'langcode' => 'en',
  );
 
  return $grants;
}

Above is part of a hook_node_access_records() implementation. The node_access tables store:

  • Node id: The unique node identifier.
  • Realm: A string that can be whatever you want. This can be useful to group different kinds of access; using the modulename is typical.
  • Grant id: An integer value often used to group access. If for example some users can only read the node, and others can read, update, and delete, you might use 0 and 1 for these two sets of users. In our case there are a small number of users who should have read access and this is determined by code based on multiple factors. For this reason we set a grant for each user using the user id.
  • Grant_view, grant_update, grant_delete: Use 0 for no access, 1 for access.
  • Langcode: Language code.

Below is the hook_node_grants() implementation. This is called each time access to a node needs to be determined; so the simpler the code, the better. If the node_access table has an entry for the node id being accessed, permissions with the matching value for realm and grant id will be granted. First the account is checked for the administrator role, and the grant id 0 is returned if there’s a match. If not, and if the user isn’t anonymous, the function returns a grant with the user’s id. If there’s a match in the table, access will be granted based on the values for read, update, or delete. If this grant doesn’t match an entry in the table, access will be denied. Finally, if the user is anonymous an empty array will be returned, denying access.

function MODULENAME_node_grants(AccountInterface $account, $op) {
  $grants = array();
 
  if (in_array('administrator', $account->getRoles())) {
    // gid to view, update, delete
    $grants['custom_access'][] = 0;
    return $grants;
  }
 
  if ($account->id() != 0) {
    // otherwise return uid, might match entry in table
    $grants['custom_access'][] = $account->id();
  }
 
  return $grants;
}

Implications of custom node access grants

One of the limitations of implementing custom node access grants is the effect on database queries. If the current user does not have access to a particular node it won’t be included in query results. This makes sense for Views since you wouldn’t want to display nodes a user shouldn’t have access to. However, if in code you need to query nodes in the background, the query is limited to those the current user can access. If for some reason a view should ignore access checks, that’s configurable with the "Disable SQL rewriting" option in the Views GUI.

For queries in code, starting in Drupal 7.15 the syntax for disabling access checks while performing a query is below:

$query->addTag('DANGEROUS_ACCESS_CHECK_OPT_OUT')

in Drupal 8 the same thing is accomplished with:

$query->accessCheck(FALSE);

Using node access grants isn’t always necessary to manage your permissions, but it allows for more complexity than many contributed modules and is more efficient than many custom hook_node_access() implementations.

Jan 20 2017
Jan 20

DrupalCon is brought to you by the Drupal Association with support from an amazing team of volunteers. Powered by COD, the open source conference and event management solution. Creative design and implementation by Cheeky Monkey Media.

DrupalCon Baltimore is copyright 2016. Drupal is a registered trademark of Dries Buytaert.

Jan 20 2017
Jan 20

Drupal 8 is so irresistible in its innovations that it just makes you wanna… pack your things and migrate! ;) To “pack” your website’s content and configuration carefully and move them to Drupal 8, there exists a cool tool called Migrate API. As with any relocation, it's easier to “pack your things” when they, let's say, fit into “standard boxes”. The same applies to websites: the less custom functionality they have, the quicker the upgrade process will go. We will touch upon this and many other interesting issues while discussing Migrate API, its benefits, the modules it includes and the migration process it provides.

Migrate API: best things come to Drupal 8 core!

Not only do best things come to Drupal core, but greatest ideas are born at DrupalCons. At one of these, DrupalCon Prague, there was a discussion on building an improved and powerful migration system for “the great 8” that would inherit the best concepts from Drupal 7’s Migrate module but make the upgrade process way easier and much more enhanced.

Mission completed! Drupal 8 core got Migrate API that was added as a set of experimental modules, but has been improving and gaining trust, and has now (beginning with subversion 8.2.5) reached beta stability.

Migrate API can offer you:

  • robust, smooth and repeatable (if necessary) migrations from Drupal 7 or 6 to Drupal 8
  • direct upgrades from Drupal 6 to Drupal 8 migrations to Drupal from other sources
  • no need for PHP updates (like there used to be)
  • a handy UI and Drush integration (see also some tips of upgrading with Drush)
  • detailed documentation
  • migration templates
  • easy rollback functionality

The modules Migrate API includes

They are now “the great three”, but they used to be “the great two” — up until Drupal 8.1.x subversion release when Migrate UI module joined in. So here they go:

  • the Migrate module that provides general API functionality for your upgrades
  • the Migrate Drupal module that offers special configuration for Drupal 6 to Drupal 8 and Drupal 7 to Drupal 8 migrations
  • the Migrate Drupal UI module that has a convenient UI to use instead of Drush

Some extra modules to use with Migrate API

However, that’s not all, so you can improve your migration process with Migrate API even more by downloading some additional contributed modules.

The Migrate Tools module offers a basic UI to list migrations and show messages, as well as general migration Drush commands:

  • drush migrate-status
  • drush migrate-import
  • drush migrate-rollback
  • drush migrate-stop
  • drush migrate-reset-status
  • drush migrate-messages

The Migrate upgrade used to have a UI that moved to core, so its main destination now is to provide important Drush commands for upgrades between Drupal versions as well as to do rollback if necessary:

  • drush migrate-upgrade
  • drush migrate-upgrade-rollback

With the help of the Migrate Plus module, you can greatly enhance your custom upgrades. It provides extra functionality, for example, for grouping migrations together, as well as XML and JSon parser plugins to make import from file-based sources.

The Migrate Manifest module will let you run selected migrations defined in a manifest file.

And, finally, if you want to move from Wordpress to Drupal (see the comparison), the Wordpress migrate module is here for you to export your Wordpress files and migrate them to Drupal.

Migration templates

A big work has already been done to simplify the upgrade process! Drupal 8 has lots of ready migration templates for the core stored in YAML configuration files. In case with contributed modules, there may also some ready migration files, so it’s worth checking out. But, as far as custom functionality is concerned, get ready to create your own custom migration files for it. However, you can always entrust it to professional developers, as an option.

A glimpse at the migration with Migrate API from Drupal 7 or 6 to Drupal 8

  • Before the migration, it’s better to have a fresh Drupal 8 installation, because it will make the process easier.
  • Next, you should tell Drupal how to connect to the source database, for example, in your settings.php file.
  • Enable all the required modules both on the source and the destination website.
  • Run your migrations via Drush or UI, whichever is more convenient for you.
  • Review your migrations to see the result.
  • If necessary, easily roll them back.
  • After that, uninstall the migration modules.
  • Done!

Have a perfectly smooth upgrade process with Migrate API and an amazing new life for your website on Drupal 8! Upgrades have become such a really popular service, especially since the end-of-life announcement for Drupal 6, that we have sharpened our skills in them to perfection. So, no matter how complex your website is or how much custom code it has, your upgrade will be safe, reliable and easy with us. Big migrations begin with a small step like contacting us right now ;)

Jan 20 2017
Jan 20

Table of contents:

Good practices are always worth maintaining. Since design is a rather vulnerable area, not everybody keeps up-to-date about trends popping up here and there. Let’s learn what you’d better take to 2017 from previous years’ trends. We will take you through design approaches and web trends to keep an eye on. For your convenience we give design examples implemented on Drupal websites - real case studies practiсe. Developers are welcome to keep reading. Get prepared in advance before a designer in your team surprises you!

Web design trends

A SaaS agency UserSnap proclaims the idea of “web design in the traditional sense fading away”. We’ve carefully revised all the articles and compilations about web design trends in the beginning of this January. We found many examples that support this idea. Now we believe that a web designer should rather be an experience designer: the one that designs products, services, visuals focusing on user’s needs and quality of experience.

What are the proofs? Just look at these trending things below.

Material design

The Next Web, UserSnap, Econsultancy are shouting out loud that Flat design is to fade away soon, Google creates its own approach. Everything is literally telling us that Material Design trend keeps being the coolest kid in school. Everybody got bored of websites looking alike. Website and system icons looked the same, it was hard to navigate the website, that was proved by Windows 8 Usability Tests conducted by the Nielsen Norman Group.

Those might have been the reasons why tactile design is taking off now.

UserSnap company notes that this trend is applicable to visuals as well - not only interfaces.

Material design is a visual language created by Google. A new design system allows to create the same UX for all devices: mobile, laptops, desktops, watches, TVs, cars. This system is much more flexible due to the reason it was created for designers, Google was the first user.

Let’s get to know material design’s features.

  1. Layers and surfaces of an interface interact with each other in accordance to all physics laws. General look of an object tells a user what it is made of, what place this object has regarding other objects and what a depth of the object’s location is.
  2. Interface consists of the layers of digital paper. This gives an impression of the tactile surfaces situated on different levels. You expect the real object has a shadow, the same is applicable in material design: the surface lying higher casts a shadow at a lower surface. What do we need it for? First of all, a designer can create a hierarchy of elements inside of the interface. It helps the user to understand which element is more important. Less important ones could be placed in shadows or drawn at a page bottom.
  3. Big floating round-shaped button is another material design’s feature. It is used only for the key action.
  4. Natural light imitation is a must for material design.
  5. Animation should make sense. It shows the user what has changed after a particular action was made. Micro-animations are also welcome in material design: they are used inside of buttons, smaller managing elements. These animations clarify actions as well.
  6. Following a previous aspect, we’d love to note that the animation should be rather smooth than popping up out of the blue. Please keep in mind that an element’s size and weight do matter. Smaller and lighter objects move faster than bigger and heavier ones.
  7. Material design’s palette consists of the main color and the complimenting color that makes accents. The main color is aimed at big containers, and the accent color helps indicating the importance of the particular details. For instance, the accent color may be used for CTA buttons.Bear in mind: there must be contrast between the main and the complimenting colors.
  8. Icons are divided into website / application icons and system icons. Unique website icons must promote the product’s idea and reflect its distinctiveness. All the physics laws are also applicable when it comes to icons creation. Remember about icon’s material and illumination as well.
  9. Bright illustrations and photos usage is another distinct feature. But don’t use stock photos: create unique and original ones instead.
  10. Content is aligned not only by Y, X and Z axes, but also by a baseline grid.

Material design

ADCI Solutions website concept redesign. View case

Atomic design

We already talked over atomic design approach in one of the previous articles (read “Modern practices of creating the visual part of the web” at our website). We will remind you approach features.

  1. This approach follows a component thinking: a whole website is divided into separate components to use them throughout the site.
  2. The website has 5 levels: atoms, molecules, organisms, templates, pages.
  3. Atoms are basic building bricks, for example, buttons.
  4. Atoms form molecules: a set of buttons becomes a particular content block.
  5. Molecules create a particular subsection of the site: a header, a footer, a sidebar, etc.
  6. These molecules combinations are organisms. Several organisms together form a template and that’s what you can show to a client.
  7. Pages are the templates filled with a real content.

System assembling is more time savvy than a typical design process: both the client and the designer see the system creation step by step and there’s no need to deconstruct the whole page if the client doesn’t like the design offered.

Atomic design

Hang Glide New England website. View case  

A mobile-first strategy is here for a long time obviously. It supposes designers create interfaces basing not on the maximum menu items / maximum content that a website can physically accommodate, but on the most important info / features to introduce.

Firstly, a designer creates a mobile version, secondly, works up towards the larger screens interfaces. This way, designers go towards expanding an amount of functions available.

Besides, the mobile-first strategy helps to understand what company’s core content is.

Next to the mobile-first strategy goes menu’s shortening: it comes up to 5 items and also helps keep a user focused at the main content.

Let’s think over another situation: the mobile-first strategy doesn’t suit one’s budget. Here comes a responsive design: thus a company needs only a single website developed. This one and only must render great on mobile and tablet, on laptops and desktops.

If you still feel relaxed, we’d love to draw your attention to the fact that Google changed its ranking algorithm in April 2016. It prioritizes websites according to content optimization.

The last but not the least in this block - prototyping tools:  Pixate, InVision, JUSTINMIND and many more. These tools allow to create working prototypes of sites, which are much more persuasive than long explanations how the site or an app should work.

Still trying to figure out how designers and developers can work efficiently? Check our articles “Modern practices of creating the visual part of the web” and “How to befriend design and front-end”.

The question is, how to present content so that a user clicks on buttons and links designed to click, follows the steps you want him or her to follow and reads company’s message easily? Here are some tips.

The entry point of this block is an accent on content itself, though. Content is a king, and content framed properly ensures confidence that users understand your company’s message and see what you offer.

Content presented as card pieces is what we owe to social networks like Facebook, Pinterest and Twitter.

Such a navigation is easier to surf, moreover it’s a real must for those who want to introduce a lot of content at once. Thus a user could scroll the whole page, quickly assess the content shown and decide what deserves attention and what should be dismissed. Cards act like entrances to more details.

Card design

Brainstorm profile. View Live Demo

The next subtopic we refer to is long scrolling and parallax. Both kick up a big fuss.

Parallax - a mouse-based motion and a scrolling mechanism - is aimed at enriching UX and creating smooth transitions. But being used improperly or simply to impress website’s guests this feature doesn’t stand for “experience design”. Everything on the website should serve at first and entertain - at second.

Parallax

Harbour Space. Visit website

Long scrolling is another controversial issue: it’s savvy way of presenting content at once on the one hand. On the other hand there’s a tendency to shorten up the menu items and an amount of content that isn’t the key one. Worth telling that a famous or even a notorious hamburger menu is losing its points: users often do not comprehend what that symbol means. That signals that the users do not always understand what is a folder яof the menu and what buttons lead to the content they want to get.

Define what better works for your users and make a choice between a short and rigid page framework and the one to scroll.

There are more ways to brighten up content: create a unique layout or use a broken grid.

Flexbox and CSS grid (to be released in March 2017) are to help designers and developers to experiment with layouts.

Animation in general and GIFs in particular is another great mechanic for website processes’ clarification.

First of all, such micro-interactions clear out what the one or another button, form, block do. Secondly, animation grabs user’s attention and points at the key website’s actions. Help visitors to comprehend which products they’d better pay attention to, what button to click.

Use GIFs wisely, though. Don’t clutter a website’s interface: a goal is to control a navigation and a user story and lead the user to a conversion action.

Such a brutal minimalism as an excessive usage of an empty space is aimed to lead the user to the conversion action as well. If there are few buttons to click it’s difficult to get lost on the website. This trend goes next to extra simple home pages and landing pages design.

Video is also a powerful method to keep the user alert. Insert a background video, include one on the main page and let the narrative of the video tell a company’s story. Carrie Cousins, a seasoned US freelance designer, supposes a virtual reality experience will have a big impact, so bear in mind a 360-degree video option.

Video on Drupal website

The Nature Conservancy. Visit website

3D geometric shapes is said to be trending due to Google’s Material Design. Regardless of the reason, there’re many beautiful and bold geometric designs used on websites and we’re highly likely to see even more.

Typography becomes flashy, edgy and yes - bigger. Unique lettering is the way to revive the second spirit into design and make a brand stand out.

Bold typography

The 25th NSF EPSCoR Conference. Visit website

Gradients conquer the Web step by step. Such agencies as The Next Web, Econsultancy dig deeper and advise using a duotone gradient palette for the sake of the balance. One more way to use gradients - put a gradient overlay on a photo.

Zazzle Media, UserSnap, Econsultancy and ShortStack cry their hearts out and beg to forget about stock imagery. Original pictures, especially when it comes to team representation, work much more efficiently. Use pictures that do relate to your brand or a company.

Scalable and vector pictures - SVGs - realize two functions. The first one is to make a designer’s life easier: SVGs look perfect on any devices and any screen. The second is optimizing website’s performance. Since SVGs don’t require any HTTP requests, there are fewer factors slowing down the website.

And what about text? Heading matters, say content marketers. Heading does matter, say designers. A unique typography and placing can make a huge difference: designers actively conduct experiments with putting heading behind the content block, playing with a general layout or even going without heading at all.

What else should be designers ready for?

First, people are getting more and more into wearable accessories: watches, smart bracelets. We’re likely to see a wearable-first approach instead of a mobile-first strategy soon.

Secondly, Hapnotic feedback technology is already here. “Haptic / Tactile feedback (or haptics) is the use of advanced vibration patterns and waveforms to convey information to a user or operator”. This technology provides tactic / vibrating feedback to the user of a touch-screen device: visuals and audios don’t seem that cool now, huh? A designer will create a visual part of such an alert.

Thirdly, more attention will be paid to designing bots: users want to communicate with companies directly.

And the last but not the least, get prepared for designing several layouts for each page: there’s a high demand for customization.

Concusion

Summing up all the above, we can say for sure - 2017 is going to be thrilling and edgy. All trends move us closer to what is called “experience design” and we encourage you all to spread the word about new useful features. Share the article and create sensible designs!

Jan 20 2017
Jan 20

Prefer a tutorial? Then read “How to Manage Media Assets in Drupal 8

Yesterday I presented a webinar on how to manage media assets in Drupal 8. The webinar went for just over an hour and we looked at the current state of media management in Drupal 7 and what’s new in Drupal 8.

I spent the rest of the time demonstrating how to:

Jan 19 2017
Jan 19
Mike and Matt sit down with Ben Finklea, and talk all things SEO and Drupal, including his new book "Drupal SEO."
Jan 19 2017
Jan 19

This is my fourth year as a member of the North American DrupalCon Program Team. Each year, I'm surprised how quickly the submission deadline rolls around. The conference is in late April, but did you realize the submission deadline is less than two short weeks away?

I imagine if the deadline sneaks up on me—a person looped into the event’s planning—how easy it must be for a community member to miss the deadline altogether. That’s why I hope this blog post reaches many of my fellow Front End Drupalers.

The DrupalCon Baltimore Call for Proposals ends Wednesday, February 1st at 11:59pm (EST). Learn about the Front End track and the types of topics and submissions we’re hoping to receive.

There are many reasons you should consider submitting a Front End session: a complimentary ticket to the conference, an opportunity to reach a broad audience, and most importantly, a chance to share your knowledge and passion with like-minded community members.

This is an exciting time in Drupal’s history—there are many appealing and universal reasons to get involved. If you’ve been learning / researching / doing cool things with Drupal’s Front End, we want you!

So, be sure to submit a Front End session today!

Jan 19 2017
Jan 19

The meta stuff

I'll be writing a series of posts exploring DIY drupal hosting options. Many agencies and freelancers need a workflow to manage and maintain their Drupal sites on a budget. Of course, they incur the cost of maintiaining and deploying the system(at least initially) and the additional learning curve involved in using the system, but they get the following advantages:

  • More control over the hosted sites. It is easy to create and deploy new environments to demo sites to clients, triage bugs, run tests etc.

  • Better workflow management. Site admins can run security updates or revert/update modules and features across sites, take periodic backups, restore them and migrate sites in and out of the hosting system. This is an important factor for developers if you're running an agency supporting lots of sites.

  • Save $$$. Though having a DIY solution has its share of initial hiccups, it could save money in the long run.

There are lot of wonderful solutions, most notably Pantheon, Omega8 and Acquia Cloud. By all means, go ahead and try them out and see how they fit your use case. These posts are intended to educate about other tailor-made options which can be built and modified to fit your workflow.

TLDR; You always trade headache for money.

What to look for

With the meta stuff out of the way, I'll elaborate here on what features I look for when shopping a DIY hosting solution. These cover 80% of the use cases needed by most organizations and/or developers. Please get in touch with me if any major use case is not listed here.

  • Ability to spin off different staging and testing environments of the site quickly. Many a times, we have to quickly create a version of the site with a feature or 2 added, for demo-ing, or create a quick production replica to triage a bug. Our solution should be able to create such throwaway environments with little effort.

  • dev production parity. The throwaway setups mentioned above should be as close to the production setup as possible. For instance, the throwaway setup should also have HTTPS if the production site has one. That's how close "close" means.

  • Secure. The DIY solution should have an ACL and the hosted environment should have proper protocols in place, like allowing SSH access only from trusted networks and users, firewall configuration etc. Note that this has nothing to do with the security of the site hosted on the platform. That's a post for another day!

  • truly agile. Deployment to production should be a predictable, documented and repeatable activity. This opens up the possibility of automating deployment. Once deployments stop becoming time sinks, as a consequence, frequent deployments and iterations happen. This naturally facilitates continuous delivery in our workflow.

Secondary traits

We can have a working DIY solution with just the above essential qualities. These are other features which make the above main features easier and aid them. Some of them might be Drupal specific. These are between essential and nice-to-have, and make our DIY solution more complete.

  • Extensible. I should be able to build my own custom LAMP stack if the site needs it. To elaborate, I should be able to deploy site A and site B running different versions of PHP, or one using Nginx and the other using Apache. Another example is talking to different systems. For example, every new git branching should trigger the system to create a new environment from scratch, with sensible defaults. The common thread here is, the solution should be extensible.

  • Ease of installation We already inherit lot of complexity by opting for a DIY Drupal solution. Setting up one from scratch should be as smooth as possible.

  • Plays well with Drupal The solution works with Drupal out of the box, with little tinkering. One example would be allowing drush usage. A good DIY solution would ideally have provision for drush site aliases. Another example, is taking into consideration the fact that Drupal isn't a complete 12 factor app. Any platform which assumes otherwise needs a lot of tinkering to keep the lights on.

  • Usability. There needs to be a commandline tool or a UI dashboard to manage the sites, maintain the system etc., despite the fact that the intended audience for this solution are mostly developers. What if your project manager wants to demo a work-in-progress feature to a client while you're away? ;)

  • Drupal 8. This goes without saying, but deserves a mention. Lot of ink has been spilled on how Drupal 8 is a radical shift in development from its predecessors. Our solution needs to take this into account to stay relevant in future.

The nice-to-haves

  • Support for addon services. It would be convinient if the DIY solution supports other addons commonly used for Drupal sites, like Redis, Memcached and Solr.

  • Support for other frameworks/CMSes. If you are an agency/developer working on multiple frameworks, this might be the deal breaker for you. It would be great if our DIY solution supports other systems like Wordpress, Rails, Symfony etc., though it is not a mandate.

Assumptions and expectations

The solutions we will be discussing are tried and tested on Linux-ish systems(more specifically, Ubuntu and flavours). There is no guarantee that this will work if you're using Windows to build it. Also, if you are running Drupal on IIS or Microsoft SQL server, this setup won't work for you.

With this premise, we will be walking through 3 DIY Drupal hosting setups in the subsequent posts which you can try at home!

Jan 19 2017
Jan 19

Today I released Entity browser 8.x-1.0-beta4. Release includes some nice features; specially Improved MultiStep selection display, which vastly improves editorial experience.

This is also the last release before the feature freeze, which will happen on February 3rd 2017. No new features will be accepted after that day until 8.x-1.0 is released. Feature requests that are not breaking backward compatibility will be accepted after that.

Huge thanks to all contributors. It has been an interesting and very rewarding ride!

Jan 18 2017
Jan 18
{{#if useSimpleSecondCol}} {{#if useSecondColThin}} {{else}} {{/if}} {{#with secondColumn}} {{#if simpleList}} {{/if}} {{#if complexList}} {{/if}} {{/with}} {{else}} {{#with secondColumn}} {{/with}} {{#with thirdColumn}} {{#with content}}

{{title}}

{{#each images}} {{/each}} {{/with}} {{/with}} {{/if}} {{#with thirdColumn}} {{#if fullBanner}} {{#with content}}

{{top_title}}

{{{description}}} {{/with}} {{/if}} {{#if view}} {{{content}}} {{/if}} {{/with}} {{#if hasFourthColumn}} {{#with fourthColumn}}

{{title}}

{{{description}}} {{/with}} {{/if}} {{/if}} {{{description}}}
Jan 18 2017
Jan 18

Exposing configuration options

Allow Site Admins to set their preferred units: metric or imperial.

Most Fitbit endpoints accept an option to set the units the response is returned in. If you are Canadian like me, you know that metric is preferable, but it’s also in our nature to be be nice, so we should expose a configuration option to allow our American friends to show data in their anachronistic imperial units. (I jest, love you guys!)

Exposing configuration options for a query plugin is done in two parts. First, build the UI and, second, make use of the stored configuration. In our query plugin class, we’ll implement two methods to help us create the UI, defineOptions and buildOptionsForm :


protected function defineOptions() {
  $options = parent::defineOptions();
  $options['accept_lang'] = array(
    'default' => NULL,
  );
  return $options;
}

public function buildOptionsForm(&$form, FormStateInterface $form_state) {
  parent::buildOptionsForm($form, $form_state);
  $form['accept_lang'] = [
    '#type' => 'select',
    '#options' => [
      '' => $this->t('Metric'),
      'en_US' => $this->t('US'),
      'en_GB' => $this->t('UK'),
    ],
    '#title' => $this->t('Unit system'),
    '#default_value' => $this->options['accept_lang'],
    '#description' => $this->t('Set the unit system to use for Fitbit API requests.'),
  ];
}

With this done, we should see our configuration options in the Views UI under Advanced > Query settings.

Query settings

However, it won’t work because we’re not actually using the stored configuration yet. To do that, we’ll add to our execute method in our query plugin:


public function execute(ViewExecutable $view) {
  
  if (!empty($this->options['accept_lang'])) {
    $this->fitbitClient->setAcceptLang($this->options['accept_lang']);
  }
  
}

Query plugin options are available via $this->options, which Drupal provides as part of the QueryPluginBase class that our Views plugin is extending. We use the stored value, together with a method on the Fitbit client service to set the preferred units for all subsequent API requests: $this->fitbitClient->setAcceptLang($this->options['accept_lang']); . With that, site admininstrators can set their preferred units, and the result set will reflect that choice. Since this is Views and we’ve exposed height as a numeric field, Views core gives us a nice way to format the data and suffix it with units so we end up with a polished result. Just edit the field options.

Fitbit profile height field

Field plugin options

Adding options to customize the appearance of the avatar field.

Views also allows us to have custom options for our field plugins. In the last article, we set up a field plugin for avatar which uses the avatar URI from the API response and renders it as an <img> tag. Fitbit’s API actually provides two avatar size options and it would be great to leave it to the site administrator to decide which size to render. We’ll use field plugin options to do that.

As with query plugins, exposing configuration options for a field plugin follows the same two parts, with one small addition. In our query plugin class, we’ll implement two methods, defineOptions and buildOptionsForm , to build the UI:


protected function defineOptions() {
  $options = parent::defineOptions();
  $options['avatar_size'] = ['default' => 'avatar'];
  return $options;
}

public function buildOptionsForm(&$form, FormStateInterface $form_state) {
  $form['avatar_size'] = [
    '#type' => 'select',
    '#title' => $this->t('Image size'),
    '#options' => [
      'avatar' => $this->t('Default (100px)'),
      'avatar150' => $this->t('Medium (150px)'),
    ],
    '#default_value' => $this->options['avatar_size'],
    '#description' => $this->t('Choose the size avatar you would like to use.'),
  ];
  parent::buildOptionsForm($form, $form_state);
}

This should be fairly self explanatory; we’re defining a form element for the UI and, once saved, the configuration option will be stored in $this->options['avatar_size'] . The small addition I referred to earlier lies within the query plugin. Before, we were only passing along the single value for avatar. Now that the site administrator has the option, we’ll want to make sure both values for avatar are passed along in the Views result. We do that, in the query plugins execute method like so:

$row['avatar'] = [
  'avatar' => $data['avatar'],
  'avatar150' => $data['avatar150'],
];

Instead of a flat value, we’re setting ‘avatar’ to an array with both values for avatar from the API response. Then, back in the field plugin, in the render method, we take care to use the appropriate size avatar according to the option selected:


public function render(ResultRow $values) {
  $avatar = $this->getValue($values);
  if ($avatar) {
    return [
      '#theme' => 'image',
      '#uri' => $avatar[$this->options['avatar_size']],
      '#alt' => $this->t('Avatar'),
    ];
  }
}

We simply call $this->getValue($values), which is able to pull out the value we want from the ResultRow object. The render method receives a ResultRow object that has all of the data for the row. FieldPluginBase has a getValue method which we can access since we are extending FieldPluginBase . With that done, we can now click on the avatar field in the Views UI and set the desired image size:

Avatar field settings

Filter plugins

Filtering the leaderboard by user id.

What if we wanted to limit the result to only a particular user? Say we wanted to show a user's Fitbit details on their Drupal user profile page. For that, we’d need to filter the result set by a user id. To make that happen, we need a Views filter plugin. The first step is to define the field to filter on in hook_views_data():


function fitbit_views_example_views_data() {
  
  $data['fitbit_profile']['uid'] = [
    'title' => t('User id'),
    'help' => t('Drupal user id, not to be confused with Fitbit profile id.'),
    'field' => [
      'id' => 'standard',
    ],
    'filter' => [
      'id' => 'fitbit_uid',
    ],
  ];
  return $data;
}

The part we are most concerned with here is the ‘filter’ key. Its value is an associative array with one key ‘id’, which we set to the name of the filter plugin we’re going to create. Also, note the ‘field’ key, which makes the Drupal user id available as a field in the Views UI. It doesn’t hurt to add it, and it also illustrates how plugins related to a certain field (e.g. field, filter, and others like relationship and argument) are all defined in the same array in hook_views_data(). So, for the next step, we’ll create this file: fitbit_views_example/src/Plugin/views/filter/Uid.php 

<?php
namespace Drupal\fitbit_views_example\Plugin\views\filter;

class Uid extends FilterPluginBase {
}

So far, this is typical Drupal 8 plugin scaffolding code. The file is placed in the right folder for the plugin type. The namespace follows PSR-4 naming. The annotation for the plugin type assigns an id to our plugin. Finally, we extend the base class provided by Views for the plugin type. Now let’s look at the specifics required for our filter plugin implementation:

class Uid extends FilterPluginBase {
  public $no_operator = TRUE;
  
  protected function valueForm(&$form, FormStateInterface $form_state) {
    $form['value'] = [
      '#type' => 'textfield',
      '#title' => $this->t('Value'),
      '#size' => 30,
      '#default_value' => $this->value,
    ];
  }
}

$no_operator = TRUE tells Views that we are not interested in the site administrators having an option to select an operator. In our case, we’ll keep things simple and always assume '='. You could, of course, allow for choice of operators if your remote service supports it. The key component here is the valueForm method. In it, we need to set an appropriate Form API element for the ‘value’ key of the $form array passed as the first argument. The name ‘value’ is important as the base class expects this key to work. The form element that you return is used in a couple of places. It’s used in the Views UI for when the site administrator is setting up a filter. It’s also used if the filter is exposed, rendered in the exposed filters form with the view itself. That’s it for the plugin implementation.  At this point we can add the filter in the Views UI:

Adding user id filter in the Views UI

The last step adjusts our query plugin to be able to handle and make use of the filter. The first thing we’ll need to do is implement an addWhere method on the query plugin class:

public function addWhere($group, $field, $value = NULL, $operator = NULL) {
  
  
  if (empty($group)) {
    $group = 0;
  }
  
  if (!isset($this->where[$group])) {
    $this->setWhereGroup('AND', $group);
  }
  $this->where[$group]['conditions'][] = [
    'field' => $field,
    'value' => $value,
    'operator' => $operator,
  ];
} 

Here, especially, we can see Views’ biases to SQL rear its head. The method name, addWhere, is odd from our perspective of querying a remote service. There is no notion of a WHERE clause present in the Fitbit API. Further, Views supports grouping filters, and logical operators within each group. Here again, the remote service we are using has no notion of this. It’s possible the remote service your implementing does in which case the flexibility Views affords is amazing. In our case it’s overkill, but I’ve copied core Views implementation for the SQL query plugin, so we’ll be able to handle everything that the Views UI allows for setting up filters. The final step is adjusting the execute method on our query plugin to incorporate the filter into the call to the Fitbit API:


public function execute(ViewExecutable $view) {
  
  if (isset($this->where)) {
    foreach ($this->where as $where_group => $where) {
      foreach ($where['conditions'] as $condition) {
        
        $field_name = ltrim($condition['field'], '.');
        $filters[$field_name] = $condition['value'];
      }
    }
  }
  
  
  $uid = isset($filters['uid']) ? $filters['uid'] : NULL;
  if ($access_tokens = $this->fitbitAccessTokenManager->loadMultipleAccessToken([$uid])) {
    
  }
}

Here, we’re looping through any filters that have been configured on the view and grabbing their values. We then ignore any other filter that may have been configured on the view, since we’re only supporting uid for now and pass it along to $this->fitbitAccessTokenManager->loadMultipleAccessToken([$uid]), which will limit the access tokens we get back to just the uid set and only show us results for the corresponding user.

Often, as was the case on a recent client project, the filters that you set up will actually get passed along in the remote API request. The Fitbit API is a bit odd in this regard in that most endpoints only return data for a single user anyway, so there is no filtering that makes sense.

That’s it! After all that work, we can set up a filter by uid to limit the results to a single user.

Wrap up

We did it, at long last, we’ve produced a custom Fitbit leaderboard, which might look something like this:

Leaderboard

Of course this is just stock Drupal 8 with the Fitbit module installed and configured, but it’s Views and we all know how to customize the look and feel of Views, so make it pretty to your heart's content.

While we've looked at a lot of code, I don't think that any of it has been horribly complicated. It's mostly a matter of knowing what to put where, with a healthy dose of planning to make sure our data fits into the Views paradigm properly. In summary, the steps are:

  1. Make a plan of attack, taking into account the data you're retrieving and the way Views expects to use it.

  2. Create field handlers for your data as necessary.

  3. Write remote queries to retrieve your data and store it in rows in the view object.

  4. Write filter plugins as necessary to narrow the result set.

There's a lot of work in those steps, but after running through it a couple times the architecture makes a lot of sense.

Get the code!

The code from this article can be found in the Fitbit module on drupal.org. It consists of a base module to handle application setup, authentication and access token storage and two sub-modules for Views integration. The first is fitbit_views_example, which I created specifically for this article series. You’ll find all the code we went through in there. The other one, fitbit_views is a more fully featured and slightly more complex version, including spanning multiple API endpoints with relationship plugins. You should use fitbit_views if your intending on using this functionality on your Drupal site. Feel free to file issues and patches!

Phew, that was a lot. Thanks for sticking with me through it all. Special thanks to Greg Dunlap for trusting me with the reboot of his original series, which has guided me through my own Views query plugin implementations. Thanks also to the Fitbit module maintainer, Matt Klein, who was kind enough to grant me co-maintainer rights on the project.

Jan 18 2017
Jan 18

Project management is often the unsung hero in our work. When a project is completed on-time, on-budget, and in-scope, the wheels of innovation keep moving. However, when poor project management takes hold, the work, creativity, and excitement can come to a screeching halt.

As project managers, we’re always looking for ways to increase a project’s effectiveness and efficiency. These practices, principles, and tools refine our craft. Join us to discuss how to strengthen a team’s ability to execute projects and to meet every client’s need. Help us share experiences, best practices, and new ideas. We want to motivate and inspire colleagues, who like you, get energized by creating schedules, managing budgets, and allocating resources.

We’re seeking sessions which align with the following topics:

  • Project Planning Techniques: Sprint Planning, Resource Allocation, and Backlog Grooming
  • Scope, Timing, and Cost: Managing the Triple Constraint
  • The Agile Movement
  • Leading Virtual, Remote, and Dispersed Teams
  • Project Management Offices (PMOs)
  • Managing Team Members and Clients
  • Quality Assurance: How your workflow and checklist help to ensure project success.
  • Stress Management and Coping Techniques: How you help your team, project, and client manage stress.
  • Lifehacks: Collaboration Tools, Platforms, Tips, and Tricks for Project Managers
  • The Future of Project Managers/Management
  • Case Studies: Managing a Drupal 8 project. How it affected your client’s business.

Feel free to reach out to us with any questions: Justin Rhodes, Shannon Vettes, or me (Aaron Bickoff).

Submit a Session

------------
Aaron Bickoff
Project Management Track Chair
DrupalCon Baltimore

Jan 18 2017
Jan 18

This one is dedicated to all my fellow Drupalers. There’s no better exercise for a brain than reading ancient chinese poetry taming Drupal 8. When I’m bored, I turn to Drupal!

Recently I got my Drupal 8 Address module updated and it turned out that from now on street address and postal code inputs are required by default. But my requirement is to have only a “city” input required. How do I do that? Well it’s not that easy as you might think (form alter / widget alter won’t help you there) and there's no UI for that (as of now). After spending some time deep in the sh.. code, I’ve figured out that ALL you need is to just simply create an EventSubscriber and declare your required fields there. Sssssimple!

Okay, enough talking, let me show it to you:

Step 1

Create a file with the following path: your_module/src/EventSubscriber/AddressFormatSubscriber.php

Step 2

Put this code inside a previously created file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
  1. <?php

  2. namespace Drupal\pabc_glue\EventSubscriber;

  3. use Symfony\Component\EventDispatcher\EventSubscriberInterface;

  4. use Drupal\address\Event\AddressEvents;

  5. class AddressFormatSubscriber implements EventSubscriberInterface {

  6. static function getSubscribedEvents() {

  7. $events[AddressEvents::ADDRESS_FORMAT][] = array('onGetDefinition', 0);

  8. return $events;

  9. }

  10. public function onGetDefinition($event) {

  11. $definition = $event->getDefinition();

  12. // This makes city (locality) field required and leaves

  13. // the rest address fields as optional

  14. $definition['required_fields'] = ['locality'];

  15. $event->setDefinition($definition);

  16. }

  17. }

Note line 18, this is where we declare our required fields.

Step 3

Create, or alter already existing, file your_modiule.services.yml

Step 4

Declare your newly created AddressFormatSubscriber service in your_modiule.services.yml

Step 5

Coffee time ☕️  - clear your caches!

Jan 18 2017
Jan 18

Was your website developed in Drupal 7 and has the time come for an upgrade? Websites with a separate login section for customers or members can now combine Drupal 7 and Drupal 8.

Often websites are upgraded to Drupal 8 because the time has come for a new, fresh look in order to generate business and attract new customers or members. This does not affect the separate member or customer section per se. There the emphasis tends to be on integrated tools for providing support to the users and these tools do not need to be upgraded that often.
 
We combined Drupal 7 and Drupal 8 in three recent projects: Biogazelle, the Klim en Bergsportfederatie and Vlaco. The separate sections with a login for customers or members have been maintained in Drupal 7. This section of the website is usually also linked to other platforms.
 
Biogazelle, for example, has a link to Zendesk for ticketing, Freshbooks for invoicing and a license server for their software. This has all been maintained in Drupal 7, because transferring all this from scratch to Drupal 8 is a very time, labour and cost-intensive process. 

XIO Biogazelle Drupal 7 en Drupal 8 schematische voorstelling

Need for a responsive design

The main reason for the upgrade of the sites of KBF, Vlaco and Biogazelle, all early Drupal projects developed around 2011, was the need for a responsive design. At the time these sites were developed, responsive was not yet the standard. 

XIO KBF Drupal 7 en Drupal voorstelling

Moreover, a business model can also change significantly over a period of three to five years. The products and services of companies and organisations may even change to such an extent that the entire website needs an upgrade.
 
We started from this premise to redevelop the canvassing part of the websites of KBF, Vlaco and Biogazelle in Drupal 8, with a new – responsive – design and additional functionality for content editors. The technical section for customers or members (requiring login with a password) has been maintained in Drupal 7. Both sections have their own, separate URL so they can operate seamlessly alongside each other.

Extending the new Drupal 8 design in the Drupal 7 section of the website with minimal adjustments

The new public website has a new, fresh look in Drupal 8. Some small adjustments have also been made to the separate member or customer website in Drupal 7, which is in line as much as possible with the new design.

XIO Vlaco Drupal 7 en Drupal 8 voorstelling

A more affordable transition, spreading the budget

The combination of Drupal 7 and Drupal 8 makes the transition to an upgrade more affordable, in the first phase for the public site and in the second phase for the customer or member site. This gives organisations the time to make plans and set aside a budget for a completely new member or customer website in the next two years.

Jan 18 2017
Jan 18
Drupal + PureCloud integration

In the past year, one of our clients in the B2C space wanted to deepen the integration between their customer support team of several hundred support agents via their website on the Drupal platform. The team selected PureCloud as the application to provide and streamline the chat support and chat phone support across more than 400 support agents located in five offices across two continents.

Third & Grove was tasked with integrating the support experience within the client's digital platforms. Although PureCloud provides an excellent API by which to access data on support interactions between agents and customers, it doesn't provide any way to query that data via an admin account. To make the support process a unified and uninterrupted part of the overall customer journey, we built a Drupal module, PureCloud, to provide a more seamless integration. One use case, for example, is that we can get wait times from the PureCloud API that display for both chat and phone on the Quicken's Drupal platform. The PureCloud module has been key to unifying and enhancing the customer engagement process.

Download the module

Jan 18 2017
Jan 18

We keep making compilations of the most useful (that point comes first) and popular tools for Drupal development and design.

We told you about 10 Drupal modules for easier communication with users and presented 10 Drupal corporate themes: find them at our website.

In this short and sweet note we’re going to expand on interaction with website’s guests.

Quizzes and contests are the ways to improve a user activity and to grab the attention of even a rather passive user. We’ll tell about Drupal modules enabling different widgets and voting feature/quizzes features. Enjoy!

Drupal modules for quizzes

WEBFORM 

Webform is the most popular module in the Evaluation/Rating category. Create your own forms and contests! As soon as a user submits the form, an e-mail notification could be sent to the user or admins. That’s how the user being dragged back to the site and admins are staying aware of what’s happening.

WEBFORM DEFAULT FIELDS 

That module is used along with the Webform module. With this module you can easily create nodes with same webform fields. Pretty useful feature for job vacancies with a reply form.

You can also attach different default fields to different webform-enabled content types.

QUIZ 

Use the Quiz module whenever you need graded assessments in Drupal. Quiz is presented in a series of questions. Question randomization, multiple attempts per user and different question types (true/false, scale, matching, drag and drop, etc) are what you will love about this module.

CLOZE 

This module complements the Quiz module with a Cloze question type. The cloze question type is when a word in a text is omitted on purpose.

The module suits you if you need to assess knowledge, make a quiz or competition or deliver any other activity connected with assessment.

QUIZ FILE UPLOAD 

You think we’re done with the Quiz module? Not yet! Let us introduce you another complementary module - Quiz file upload. It provides users with a cool option: now they’re allowed to upload a file as a response to quiz question. That will definitely make user’s experience brighter.

AJAX POLL 

This module let users see the poll results without page reloading: only a block with the poll is to reload. It means no time wasted and more pleasant UX. This feature works for all website’s polls. The AJAX Poll module complements the Poll module included into Drupal Core.

H5P 

This module made by a Norwegian company creates question sets, drag and drop questions, multiple choices questions. Many more options are available.

What’s so great about this module? All the content is HTML5, which means a smooth performance on smartphones, tablets and other mobile devices.

ADVANCED POLL 

This module partly duplicates The Poll module. This one provides multiple voting systems (Basic polls, Approval Voting, Borda Count, and Instant-Runoff Voting), decision-making tools, time and choice management options. With this cool module you can schedule an opening and closing time for the polls and view each user's vote.

POLL BLOCKS 

The Poll Blocks modules beats the Drupal Core Poll module again: whereas Poll provides just one block for the recent poll, this guy creates the block for all active polls.

POLL ENHANCEMENTS 

Want anonymous users to vote on polls? It’s possible with the Poll Enhancements module. The great feature is that page caching could be enabled. Remember we were talking about getting an attention of even a passive user? It will work for anonymous ones as well! They are supposed to push the button and see results: no need to participate in a discussion or reveal oneself somehow else.

That’s it for this compilation! We hope you’ll find the module that suits exactly your needs and will keep reading our Drupal notes.

KEEP CALM AND EXPLORE 36,156 DRUPAL MODULES WITH US.

Jan 18 2017
Jan 18

SRI is an independent, nonprofit research center that works with clients to take the most advanced R&D from the laboratory to the marketplace. The legacy SRI site content strategy did optimize lead generation and customer journey, so the Third & Grove team focused on re-architecting SRI's Drupal instance to allow for future content marketing flexibility, in addition to a custom Salesforce integration for tracking leads. We also gave the site a design refresh.

View the Case Study

Jan 18 2017
Jan 18
Using Panels to Build Page Layouts in Drupal 8

One of our OSTraining members asked about the current state of the Panels module in Drupal 8. They already had experience with Panels in Drupal 7.

Panels is one of the most popular ways to create page layouts in Drupal.

In this quick tutorial, we will show you have to create your Panel in Drupal 8. The process is definitely different when compared to Drupal 7, although many similarities remain.

For this tutorial, you do not need to have experience with Panels.

First, we need to install and enable the required modules to use Panels:

Although these next four modules are not essential, I'm also going to ensure I have the following modules installed, because I rely on them heavily for site-building.

Inside your Drupal site, go to the "Extend" tab and enable the modules:

Enabling Panels modules in Drupal 8

Now we need to create our Panel.

  • Go to Structure > Pages.
  • Click "Add page":

Adding a Panel page in Drupal 8

  • Choose an "Administrative title".
  • Create a path for your panel.
  • Make sure that "Variant type" is set to "Panels".
  • Click "Next" when you're finished.

Selecting a variant type in Drupal 8

  • Give the Panel layout a "Label". 
  • Click "Next" when you're finished.

Choosing a Panels builder in Drupal 8

  • Select a "Layout" for the Panel.
  • Click "Next" when you're finished.

Choosing a Panels layout in Drupal 8

  • Enter a "Page title".
  • Now you can add blocks to your layout in the exact same manner as adding normal blocks. Use the "Add new block" button to select the blocks you want to place in the panels for display.
  • Once you've placed your blocks, click "Finish".

Placing blocks in a Panel in Drupal 8

  • On the next screen, select "Update and save".

Panels configuration screens in Drupal 8

If you want to do more advanced things with Panels, such as have different layouts for different user roles, you would create that from the "Selection criteria" area.

Panels selection criteria screens in Drupal 8

Now if you go to Configuration > Basic site settings, you can update the "Front Page" link to match the URL of your panel. Your panel will now be your site's homepage.

Frontpage screen in Drupal 8

And now we have a 2 column, responsive Panel, as in the image below:

A completed panel in Drupal 8


About the author

Daniel is a web designer from UK, who's a friendly and helpful part of the support team here at OSTraining.

View the discussion thread.

Jan 17 2017
Jan 17

Drupal has a powerful suite of tools and features for just about any type of website, and eCommerce websites are no exception. Drupal has a very active and healthy eCommerce community that creates some amazing features.

The Drupal eCommerce market mainly consists of Ubercart and Drupal Commerce, but there are also some impressive emerging technologies to keep an eye on, such as the Stripe payment API, that allow for more customized solutions.

The eCommerce solution that you choose will depend upon the particular Drupal site you’re developing. Is it a simple online store with just a few products, or a much more robust site with a wide variety of products? This is one of the strengths of eCommerce with Drupal: you can build the kind of solution you need without the extra, unnecessary features and functionalities.

Ubercart vs Drupal Commerce

Drupal Commerce is by far the most popular Drupal eCommerce solution these days. It’s the successor to the original eCommerce solution, Ubercart. Drupal Commerce was written for Drupal 7 by the same developer that created Ubercart, Ryan Szrama, using the latest APIs and features for Drupal 7. Drupal Commerce is supported by Ryan Szrama’s company, Commerce Guys, and the Drupal community as a whole. Developers are more likely to add new features to Commerce because it is more widely used in Drupal 7 and beyond. The Drupal 8 version is still in beta, so more work needs to be done to get it to a full release. Check out the Drupal Commerce issue queue to see where you might be able to help.

Drupal Commerce learned from Ubercart's primary shortcoming; it was difficult to configure and not very modular. Where Ubercart was one module that was difficult to build upon, Drupal Commerce has a small set of features in the main module and a large suite of available contributed modules. This allows for easier configuration and more customizations.

Kickstart Your Online Store

One of the most useful features available for Drupal Commerce is the Commerce Kickstart Drupal distribution. This is a great way for non-technical store owners to get a Drupal Commerce store up and running quickly and easily. It comes with an impressive installer that allows you to install an example store to see how everything can be configured. It then allows you to wipe the site clean and start a fresh build of your own custom store.

The Commerce Kickstart comes with some additional built in modules and configurations that help get a Drupal Commerce site up and running quickly. This is a more efficient solution than building from scratch with the Drupal Commerce module and any contributed modules that are necessary to achieve the desired functionality. The Commerce Kickstart distribution shows off the power of Drupal distributions; it’s a turnkey solution for Drupal eCommerce websites.

Stripe API

One of Drupal’s greatest advantages over its competitors is how well it integrates with third party APIs. This allows for integration with many different payment gateways, one being Stripe API. Drupal developers can integrate Stripe with a Drupal site through a custom module and create highly customized payment solutions. This type of customization allows for a variety of solutions for selling and accepting payments that would be more challenging to implement with Drupal Commerce.

Which Solution Should I Choose?

The solution you choose depends on the site’s needs. A small online store that only needs a simple shopping cart might be best suited for Ubercart. At its core, Ubercart is still the easiest to set up without using a Drupal distribution like Commerce Kickstart.

Drupal Commerce is a much more robust eCommerce solution for Drupal with enterprise level features that large online stores use to sell products like product displays. On top of that, you get all the features of a normal Drupal website like content types, taxonomies, and user permissions.

If you are looking to build a very customized payment solution that doesn’t really fit into either of these categories, perhaps a custom solution with the Stripe API module is best.

In the end, the Drupal eCommerce solution you choose should be easy to use for your store administrators and easy for your customers to buy your products online.

Jan 17 2017
Jan 17

Jan 17 2017
xjm
Jan 17

Drupal 8.3.0, the next planned minor release of Drupal 8, is scheduled for Wednesday, April 5, 2017. Minor releases include new features, usability improvements, and backwards-compatible API improvements. Here's what this means now for core patches.

Alpha vs. Beta releases

In previous Drupal 8 minor releases, the first pre-release version of the minor was labeled as beta1, and expectations were slightly different for later betas. For Drupal 8.3.0, we've recategorized this first pre-release version as alpha1, to distinguish it from the subsequent beta releases. (We will also no longer have "beta target" issues; some strategic issues are considered for backport to 8.3.x, but only after they are fixed in 8.4.x, and only up until the new beta phase begins.)

This change should provide better clarity and fairness on which issues are accepted for commit. The overall timeline for the minor release is not changed; alpha1 simply replaces the previous beta1, and the previous, stricter beta2 now becomes beta1. (More information on alpha and beta releases.) The release candidate phase is the same as for past releases.

Drupal 8.3.0-alpha1 will be released the week of January 30

  • In preparation for the minor release, Drupal 8.3.x will enter the alpha phase the week of January 30.
  • Developers and site owners can begin testing the alpha.
  • The 8.4.x branch of core will be created, and future feature and API additions will be targeted against that branch instead of 8.3.x. All outstanding issues filed against 8.3.x will be automatically migrated to 8.4.x once it is opened.
  • All issues filed against 8.2.x will then be migrated to 8.3.x, and subsequent bug reports should be targeted against the 8.3.x branch.
  • During the alpha phase, core issues will be committed according to the following policy:
    1. Most issues that are allowed for patch releases will be committed to 8.3.x and 8.4.x.
    2. Drupal 8.2.x will receive primarily critical bugfixes in preparation for its final patch release window. (Drupal 8.1.x and 8.0.x are not supported anymore and changes are not made to those branches.)
    3. Most issues that are only allowed in minor releases will be committed to 8.4.x only. A few strategic issues may be backported to 8.3.x, but only at committer discretion after the issue is fixed in 8.4.x, and only up until the beta deadline.

Drupal 8.3.0-beta1 will be released the week of February 13

Roughly two weeks after the alpha release, the first beta release will be created. All the restrictions of the alpha release apply to beta releases as well. The release of the first beta is a firm deadline for all feature and API additions. Even if an issue is pending in the Reviewed & Tested by the Community (RTBC) queue when the commit freeze for the beta begins, it will be committed to the next minor release only.

The release candidate phase will begin the week of February 27, and we will post further details at that time.

See the summarized key dates in the release cycle, allowed changes during the Drupal 8 release cycle, and Drupal 8 backwards compatibility and internal API policy for more information.

As a reminder, we have until the start of the alpha to add great new features to Drupal 8.3.x. Stabilizing experimental modules (such as Inline form errors and Migrate Drupal), landing some key Media Initiative components, and working on bugfixes are all priorities for 8.3.0. Help with these changes now for a great Drupal 8.3.0!

Jan 17 2017
Jan 17
The Easiest Way to Debug Drupal Themes and Modules

Are you looking for an easy way to find errors thrown by your Drupal modules and themes?

A lot of new developers are learning to create their first Drupal 8 modules or Drupal 8 themes. Often they've made a very small typo or spacing error and are looking for an easy way to debug their mistake.

Follow this tutorial, and you'll quickly be able to see a detailed list of recent errors on your Drupal site.

  • Go to Configuration > "Logging and errors".

The logging and errors link in Drupal

  • Under "Error messages to display", check "All messages".
  • Click "Save configuration".

all messages option for debugging in Drupal

  • Go to Reports > "Recent log messages".

recent log messages in Drupal

  • You'll now be able to see many of the errors that themes and modules are reporting to Drupal:

log messages in Drupal


About the author

Steve is the founder of OSTraining. Originally from the UK, he now lives in Sarasota in the USA. He was a teacher for many years before starting OSTraining. Steve wrote the best-selling Drupal and Joomla books.

View the discussion thread.

Jan 17 2017
Jan 17

I've previously written about dynamic forms in Drupal, using #states to show or hide input boxes depending on other inputs. Since then, Drupal 7 and 8 have both got the ability to combine conditions with OR and XOR operators. This makes it possible to apply changes when a form element's value does not equal something, which is not obvious at all.

It's easy to show something when a value does match. This example shows a text input box for specifying which colour is your favourite when you chose 'Other' from a dropdown list of favourite colours.

$form['other_favorite'] = [
'#type' => 'textfield',
'#title' => t('Please specify...'),
'#states' => [
'visible' => [ // action to take.
':input[name="favorite_color"]' // element to evaluate condition on
=> ['value' => 'Other'], // condition
],
],
];

But there's no 'opposite' of the value condition, e.g. to show an element when a text box has anything other than the original value in it. Using the 'XOR' condition (which only passes when some conditions are passed, but not all of them), you can combine the value condition with something that will always pass, in order to simulate it instead. Something like this, which shows a checkbox to confirm a change when you enter anything other than your previous favourite food:

    $form['confirm_change'] = [
'#type' => 'checkbox',
'#title' => t('Yes, I really have changed my mind'),
// Use XOR so that current_pass element only shows if the email
// address is not the existing value, and is visible. States cannot
// check for a value not being something, but by using XOR with a
// condition that should always be true (i.e. the latter one), this
// will work. Note the extra level of arrays that is required for
// using the XOR operator.
'#states' => [
'visible' => [
[':input[name="favorite_food"]' => [
'value' => $original_favorite_food,
]],
'xor',
[':input[name="favorite_food"]' => [
// The checked property on a text input box will always come
// out false.
'checked' => FALSE,
]],
],
],
];

Hopefully the comments in that snippet make sense, but essentially it's about picking a second condition that will always pass, so that the combination only passes when the first condition fails. So in this case, whenever the value does not equal the original value.

By the way, you can do much more than just show or hide form elements, for example, enabling or disabling - see the full list.

Jan 17 2017
Jan 17

The Drupal node access grants system has always been a powerful and flexible way to control access to your nodes. It's been there from Drupal 5 (if not earlier) and it continues to exist in Drupal 8 as we move forward. In this article, I want to quickly highlight this system from a D8 perspective and how I propose to use it in a OOP architecture.

What is it?

The node access grant system is a way by which you can control programatically and very granularly access to all four operations on your Drupal nodes (view, create, edit, delete). It allows to define certain realms of functionality (related to your access requirements) and a set of grants that are required for any of the four mentioned operations, within that realm. Users will then need to posses the grants in the respective realms in order to be granted access.

The two main components of this system are therefore:

  • The implementation of hook_node_access_records() which is called whenever a node is saved (or site-wide permissions rebuilt). It is responsible for storing the access requirements for that given node.
  • The implementation of hook_node_grants() which is called whenever a user is trying to access a node (or a query is being performed in the name of that user). It is responsible for presenting the grants for the current user, which if match the access requirements of the node, allows them access.

The great thing about this node access grants is that it's system-wide in the sense of who checks for the access. In contrast to implementing hook_node_access() which only is called when viewing a node on its canonical URL, the access grants are checked almost everywhere such as views or even custom queries with much ease.

Drupal 8

In Drupal 8 these 2 hooks remain the foundation of the node access grants system, albeit with type hinted parameters. This means that we need to place their implementation inside our .module files.

Node access grants are not used on every site because they serve relatively complex access rules. Complex access rules usually also require a fair bit of calculating what grants a particular node must have for a given realm, as well as whether a given user possesses them. For this very reason I am not so fond of having to put all this logic in my .module file.

So I came up with a basic developer module that defines an interface that has two methods: accessRecords() and grants(). Other modules which want to implement the access grants hooks can instead now create a service which implements this interface and tag it with node_access_grants. My module will do the rest and you won't have to touch any .module file. You can inject whatever dependencies from the container you need and perform whatever logic is needed for determining your grants and access records.

Let me what you think down in the comments. Would love your feedback.

Jan 17 2017
Jan 17

When Intuit sold off Quicken, the personal finance software company needed to become technologically independent from its massive parent corporation. Third & Grove needed to build a complex new site that could adeptly handle Quicken’s content and marketing needs, and integrate with its migrated digital store—and we needed to build it fast.

Combining Drupal’s content management with Magento’s ecommerce capabilities, Third & Grove provided a highly scalable, enterprise solution that allows multiple third-party integrations, including automated marketing to live chat and payments at any scale.

View the Case Study

Jan 17 2017
Jan 17

The Drupal Community Working Group is pleased to announce that nominations for the 2017 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 will include a scholarship and stipend 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 (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 March 1, 2017. A committee consisting of the Community Working Group members and past award winners will select a winner from the submissions. Members of this committee and previous winners are exempt from winning the award.

Previous winners of the award are:

*  2015: Cathy Theys  
*  2016: Gábor Hojtsy  

If you know someone amazing who should benefit from this award please nominate them at https://www.drupal.org/aaron-winborn-award

Jan 17 2017
Jan 17

Drupal Modules: The One Percent — Views Flipped Table (video tutorial)

[embedded content]

Episode 13

Here is where we bring awareness to Drupal modules running on less than 1% of reporting sites. Today we'll consider Views Flipped Table, a module which will rotate your Views tables 90°, and ask if using HTML tables is ever appropriate.

Jan 17 2017
Jan 17

After ending 2016 with a new PHP version and starting 2017 with a new HTTP version, we’re happy to report that there’s still plenty of new left for us to launch. This time around it’s a new Apache Solr version, 6.3.

Support for a more modern version of Apache Solr has been one of our most requested features for a while. Unfortunately some packaging issues made it more difficult than we expected but I’m happy to report that we’ve managed to work around them. Starting today, you can now launch a Solr 6 container by simply specifying the appropriate version in your services.yaml file, like so:

solrsearch:
    type: 'solr:6.3'
    disk: 1024

However, you can also go a lot farther than that.

The Solr 6 container is also our first container to support multiple databases (“cores” in Solr-speak) on a single service. Each core can have its own schema and custom configuration, and each can be mapped to a different “endpoint”, which can then be accessed by your application container. See our documentation for more details. Over time we intend to add similar functionality to other services, too.

Please note that multi-core support is only available on Solr 6.3, not on older Solr versions. Also, Solr doesn’t support direct upgrades from one major version to another. If you want to upgrade your existing Solr service, create a new, additional Solr service with a new name, populate it from your application, then remove the old one.

Larry Garfield Jan 17, 2017
Jan 16 2017
Jan 16

There’s an old saying that no information architecture survives contact with the user. Or something like that. You’ll carefully design and build your content types and taxonomies, and then find that the users are actually not quite using what you’ve built in quite the way it was intended when you were building it.

And so there comes a point where you need to grit your teeth, change the structure of the site’s content, and convert existing content.

Back on Drupal 7, I wrote a plugin for Migrate which handled migrations within a single Drupal site, so for example from nodes to a custom entity type, or from one node type to another. (The patch works, though I never found the time to polish it sufficiently to be committed.)

On Drupal 8, without the time to learn the new version of Migrate, I recently had to cobble something together quickly.

Fortunately, this was just changing the type of some nodes, and where all the fields were identical on both source and destination node types. Anything more complex would definitely require Migrate.

First, I created the new node type, and cloned all its fields from the old type to the new type. Here I took the time to update some of the Field Tools module’s functionality to Drupal 8, as it pays off to have a single form to clone fields rather than have to add them to the new node type one by one.

Field Tools also copies display settings where form and view modes match (in other words, if the source bundle has a ‘teaser’ display mode configured, and the destination also has a ‘teaser’ display mode that’s enabled for custom settings, then all of the settings for the fields being cloned are copied over, with field groups too).

With all the new configuration in place, it was now time to get down to the content. This was plain and simple a hack, but one that worked fine for the case in question. Here’s how it went…

We basically want to change the bundle of a bunch of nodes. (Remember, the ‘bundle’ is the generic name for a node type. Node types are bundles, as taxonomy vocabularies are bundles.) The data for a single node is spread over lots of tables, and most of these have the bundle in them.

On Drupal 8 these tables are:

  • the entity base table
  • the entity data table
  • the entity revision data table
  • each field data table
  • each field data revision table

(It’s not entirely clear to me what the separation between base table and data table is for. It looks like it might be that base table is fields that don’t change for revisions, and data table is for fields that do. But then the language is on the base table, and that can be changed, and the created timestamp is on the data table, and while you can change that, I wouldn’t have thought that’s something that has past values kept. Answers on a postcard.)

So we’re basically going to hack the bundle column in a bunch of tables. We start by getting the names of these tables from the entity type storage:

$storage = \Drupal::service('entity_type.manager')->getStorage('node');

// Get the names of the base tables.
$base_table_names = [];
$base_table_names[] = $storage->getBaseTable();
$base_table_names[] = $storage->getDataTable();
// (Note that revision base tables don't have the bundle.)

For field tables, we need to ask the table mapping handler:

$table_mapping = \Drupal::service('entity_type.manager')->getStorage('node')
  ->getTableMapping();

// Get the names of the field tables for fields on the service node type.
$field_table_names = [];
foreach ($source_bundle_fields as $field) {
  $field_table = $table_mapping->getFieldTableName($field->getName());
  $field_table_names[] = $field_table;

  $field_storage_definition = $field->getFieldStorageDefinition();
  $field_revision_table = $table_mapping
    ->getDedicatedRevisionTableName($field_storage_definition);

  // Field revision tables DO have the bundle!
  $field_table_names[] = $field_revision_table;
}

(Note the inconsistency in which tables have a bundle field and which don’t! For that matter, surely it’s redundant in all field tables? Does it improve the indexing perhaps?)

Then, get the IDs of the nodes to update. Fortunately, in this case there were only a few, and it wasn’t necessary to write a batched hook_update_N().

// Get the node IDs to update.
$query = \Drupal::service('entity.query')->get('node');
// Your conditions here!
// In our case, page nodes with a certain field populated.
$query->condition('type', 'page');
$query->exists(‘field_in_question’);
$nids = $query->execute();

And now, loop over the lists of tables names and hack away!

// Base tables have 'nid' and 'type' columns.
foreach ($base_table_names as $table_name) {
  $query = \Drupal\Core\Database\Database::getConnection('default')
    ->update($table_name)
    ->fields(['type' => 'service'])
    ->condition('nid', $service_nids, 'IN')
    ->execute();
}
// Field tables have 'entity_id' and 'bundle' columns.
foreach ($field_table_names as $table_name) {
  $query = \Drupal\Core\Database\Database::getConnection('default')
    ->update($table_name)
    ->fields(['bundle' => 'service'])
    ->condition('entity_id', $service_nids, 'IN')
    ->execute();
}

Node-specific tables use ‘nid’ and ‘type’ for their names, because those are the base field names declared in the entity type class, whereas Field API tables use the generic ‘entity_id’ and ‘bundle’. The mapping between these two is declared in the entity type annotation’s entity_keys property.

This worked perfectly. The update system takes care of clearing caches, so entity caches will be fine. Other systems may need a nudge; for instance, Search API won’t notice the changed nodes and its indexes will literally need turning off and on again.

Though I do hope that the next time I have to do something like this, the amount of data justifies getting stuck into using Migrate!

Jan 16 2017
Jan 16

As my loyal blog readers know, at the beginning of every year I publish a retrospective to look back and take stock of how far Acquia has come over the past 12 months. If you'd like to read my previous annual retrospectives, they can be found here: 2015, 2014, 2013, 2012, 2011, 2010, 2009. When read together, they provide a comprehensive overview of Acquia's trajectory from its inception in 2008 to where it is today, nine years later.

The process of pulling together this annual retrospective is very rewarding for me as it gives me a chance to reflect with some perspective; a rare opportunity among the hustle and bustle of the day-to-day. Trends and cycles only reveal themselves over time, and I continue to learn from this annual period of reflection.

Crossing the chasm

If I were to give Acquia a headline for 2016, it would be the year in which we crossed the proverbial "chasm" from startup to a true leader in our market. Acquia is now entering its ninth full year of operations (we began commercial operations in the fall of 2008). We've raised $186 million in venture capital, opened offices around the world, and now employ over 750 people. However, crossing the "chasm" is more than achieving a revenue target or other benchmarks of size.

The "chasm" describes the difficult transition conceived by Geoffrey Moore in his 1991 classic of technology strategy, Crossing the Chasm. This is the book that talks about making the transition from selling to the early adopters of a product (the technology enthusiasts and visionaries) to the early majority (the pragmatists). If the early majority accepts the technology solutions and products, they can make a company a de facto standard for its category.

I think future retrospectives will endorse my opinion that Acquia crossed the chasm in 2016. I believe that Acquia has crossed the "chasm" because the world has embraced open source and the cloud without any reservations. The FUD-era where proprietary software giants campaigned aggressively against open source and cloud computing by sowing fear, uncertainty and doubt is over. Ironically, those same critics are now scrambling to paint themselves as committed to open source and cloud architectures. Today, I believe that Acquia sets the standard for digital experiences built with open source and delivered in the cloud.

When Tom (my business partner and Acquia CEO) and I spoke together at Acquia's annual customer conference in November, we talked about the two founding pillars that have served Acquia well over its history: open source and cloud. In 2008, we made a commitment to build a company based on open source and the cloud, with its products and services offered through a subscription model rather than a perpetual license. At the time, our industry was skeptical of this forward-thinking combination. It was a bold move, but we have always believed that this combination offers significant advantages over proprietary software because of its faster rate of innovation, higher quality, freedom from vendor lock-in, greater security, and lower total cost of ownership.

Creating digital winners

Acquia has continued its evolution from a content management company to a company that offers a more complete digital experience platform. This transition inspired an internal project to update our vision and mission accordingly.

In 2016, we updated Acquia's vision to "make it possible for dreamers and doers to craft the digital world". To achieve this vision, we want to build "the universal platform for the world's greatest digital experiences".

We increasingly find ourselves at the center of our customer's technology and digital strategies, and they depend on us to provide the open platform to integrate, syndicate, govern and distribute all of their digital business.

The focus on any and every part of their digital business is important and sets us apart from our competitors. Nearly all of our competitors offer single-point solutions for marketers, customer service, online commerce or for portals. An open source model allows customers to integrate systems together through open APIs, which enables our technology to fit into any part of their existing environment. It gives them the freedom to pursue a best-of-breed strategy outside of the confines of a proprietary "marketing cloud".

Business momentum

We continued to grow rapidly in 2016, and it was another record year for revenue at Acquia. We focused on the growth of our recurring revenue, which includes new customers and the renewal and expansion of our work with existing customers. Ever since we started the company, our corporate emphasis on customer success has fueled both components. Successful customers mean renewals and references for new customers. Customer satisfaction remains extremely high at 96 percent, an achievement I'm confident we can maintain as we continue to grow.

In 2016, the top industry analysts published very positive reviews based on their dealings with our customers. I'm proud that Acquia made the biggest positive move of all vendors in this year's Gartner Magic Quadrant for Web Content Management. There are now three distinct leaders: Acquia, Adobe and Sitecore. Out of the leaders, Acquia is the only player that is open-source and has a cloud-first strategy.

Over the course of 2016 Acquia welcomed an impressive roster of new customers who included Nasdaq, Nestle, Vodafone, iHeartMedia, Advanced Auto Parts, Athenahealth, National Grid UK and more. Exiting 2016, Acquia can count 16 of the Fortune 100 among its customers.

Digital transformation is happening everywhere. Only a few years ago, the majority of our customers were in either government, media and entertainment or higher education. In the past two years, we've seen a lot of growth in other verticals and today, our customers span nearly every industry from pharmaceuticals to finance.

To support our growth, we opened a new sales office in Munich (Germany), and we expanded our global support facilities in Brisbane (Queensland, Australia), Portland (Oregon, USA) and Delhi (India). In total, we now have 14 offices around the world. Over the past year we have also seen our remote workforce expand; 33 percent of Acquia's employees are now remote. They can be found in 225 cities worldwide.

Acquia's offices around the world. The world got more flat for Acquia in 2016.

We've also seen an evolution in our partner ecosystem. In addition to working with traditional Drupal businesses, we started partnering with the world's most elite digital agencies and system integrators to deliver massive projects that span dozens of languages and countries. Our partners are taking Acquia and Drupal into some of the world's most impressive brands, new industries and into new parts of the world.

Growing pains and challenges

I enjoy writing these retrospectives because they allow me to chronicle Acquia's incredible journey. But I also write them for you, because you might be able to learn a thing or two from my experiences. To make these retrospectives useful for everyone, I try to document both milestones and difficulties. To grow an organization, you must learn how to overcome your challenges and growing pains.

Rapid growth does not come without cost. In 2016 we made several leadership changes that will help us continue to grow. We added new heads of revenue, European sales, security, IT, talent acquisition and engineering. I'm really proud of the team we built. We exited 2016 in the market for new heads of finance and marketing.

Part of the Acquia leadership team at The Lobster Pool restaurant in Rockport, MA.

We adjusted our business levers to adapt to changes in the financial markets, which in early 2016 shifted from valuing companies almost solely focused on growth to a combination of growth and free cash flow. This is easier said than done, and required a significant organizational mindshift. We changed our operating plan, took a closer look at expanding headcount, and postponed certain investments we had planned. All this was done in the name of "fiscal fitness" to make sure that we don't have to raise more money down the road. Our efforts to cut our burn rate are paying off, and we were able to beat our targets on margin (the difference between our revenue and operating expenses) while continuing to grow our top line.

We now manage 17,000+ AWS instances within Acquia Cloud. What we once were able to do efficiently for hundreds of clients is not necessarily the best way to do it for thousands. Going into 2016, we decided to improve the efficiency of our operations at this scale. While more work remains to be done, our efforts are already paying off. For example, we can now roll out new Acquia Cloud releases about 10 times faster than we could at the end of 2015.

Lastly, 2016 was the first full year of Drupal 8 availability (it was formally released in November 2015). As expected, it took time for developers and the Drupal community to become familiar with its vast array of changes and new capabilities. This wasn't a surprise; in my DrupalCon keynotes I shared that I expected Drupal 8 to really take off in Q4 of 2016. Through the MAP program we committed over $1M in funds and engineering hours to help module creators upgrade their modules to Drupal 8. All told, Acquia invested about $2.5 million in Drupal code contributions in 2016 alone (excluding our contributions in marketing, events, etc). This is the most we have ever invested in Drupal and something is I'm personally very proud of.

Product milestones

The components and products that make up the Acquia Platform.

Acquia remains an amazing place for engineers who want to build great products. We achieved some big milestones over the course of the year.

One of the largest milestones was the significant enhancements to our multi-site platform: Acquia Cloud Site Factory. Site Factory allows a team to manage and operate thousands of sites around the world from a single console, ensuring all fixes, upgrades and improvements are delivered responsibly and efficiently. Last year we added support for multiple codebases in Site Factory – which we call Stacks – allowing an organization to manage multiple Site Factories from the same administrative console and distribute the operation around the world over multiple data centers. It's unique in its ability and is being deployed globally by many multinational, multi-brand consumer goods companies. We manage thousands of sites for our biggest customers. Site Factory has elevated Acquia into the realm of very large and ambitious digital experience delivery.

Another exciting product release was the third version of Acquia Lift, our personalization and contextualization tool. With the third version of Acquia Lift, we've taken everything we've learned about personalization over the past several years to build a tool that is more flexible and easier to use. The new Lift also provides content syndication services that allow both content and user profile data to be reused across sites. When taken together with Site Factory, Lift permits true content governance and reuse.

We also released Lightning, Acquia's Drupal 8 distribution aimed at developers who want to accelerate their projects based on the set of tested and vetted modules and configurations we use ourselves in our customer work. Acquia's commitment to improving the developer experience also led to the release of both Acquia BLT and Acquia Pipelines (private beta). Acquia BLT is a development tool for building new Drupal projects using a standard approach, while Pipelines is a continuous delivery and continuous deployment service that can be used to develop, test and deploy websites on Acquia Cloud.

Acquia has also set a precedent of contributing significantly to Drupal. We helped with the release management of Drupal 8.1 and Drupal 8.2, and with the community's adoption of a new innovation model that allows for faster innovation. We also invested a lot in Drupal 8's "API-first initiative," whose goal is to improve Drupal's web services capabilities. As part of those efforts, we introduced Waterwheel, a group of SDKs which make it easier to build JavaScript and native mobile applications on top of Drupal 8's REST-backend. We have also been driving usability improvements in Drupal 8 by prototyping a new UX paradigm called "outside in" and by contributing to the media and layout initiatives. In 2017, I believe we should maintain our focus on release management, API-first and usability.

Our core product, Acquia Cloud, received a major reworking of its user interface. That new UI is a more modern, faster and responsive user interface that simplifies interaction for developers and administrators.

The new Acquia Cloud user interface released in 2016.

Our focus on security reached new levels in 2016. In January we secured certification that we complied with ISO 27001: the international security and compliance standard for enterprise cloud frameworks. In April we were awarded our FedRAMP ATO from the U.S. Department of Treasury after we were judged compliant with the U.S. federal standards for cloud security and risk management practices. Today we have the most secure, reliable and agile cloud platform available.

We ended the year with an exciting partnership with commerce platform Magento that will help us advance our vision of content and commerce. Existing commerce platforms have focused primarily on the transactions (cart systems, payment processing, warehouse/supply chain integration, tax compliance, customer credentials, etc.) and neglected the customer's actual shopping experience. We've demonstrated with numerous customers that a better brand experience can be delivered with Drupal and Acquia Lift alongside these existing commerce platforms.

The wind in our sales (pun intended)

Entering 2017, I believe that Acquia is positioned for long-term success. Here are a few reasons why:

  • The current market for content, commerce, and community-focused digital experiences is growing rapidly at just under 20 percent per year.
  • We hold a leadership position in our market, despite our relative market share being small. The analysts gave Acquia top marks for our strategic roadmap, vision and execution.
  • Digitization is top-of-mind for all organizations and impacts all elements of their business and value chain. Digital first businesses are seeking platforms that not only work for marketing, but also for service, compliance, portals, commerce and more.
  • Open source combined with the cloud continue to grow at a furious pace. The continuing rise of the developer's influence on technology selection also works in our favor.
  • Drupal 8 is the most significant advance in the evolution of the Drupal and Drupal's new innovation model allows the Drupal community to innovate faster than ever before.
  • Recent advances in machine learning, Internet of Things, augmented reality, speech technology, and conversational interfaces all coming to fruition will lead to new customer experiences and business models, reinforcing the need for API-first solutions and the levels of freedom that only open source and cloud computing offer.

As I explained at the beginning of this retrospective, trends and cycles reveal themselves over time. After reflecting on 2016, I believe that Acquia is in a unique position. As the world has embraced open source and cloud without reservation, our long-term commitment to this disruptive combination has put us at the right place at the right time. Our investments in expanding the breadth of our platform with products like Acquia Lift and Site Factory are also starting to pay off.

However, Acquia's success is not only determined by the technology we back. Our unique innovation model, which is impossible to cultivate with proprietary software, combined with our commitment to customer success has also contributed to our "crossing of the chasm."

Of course, none of these 2016 results and milestones would be possible without the hard work of the Acquia team, our customers, partners, the Drupal community, and our many friends. Thank you for your support in 2016 – I can't wait to see what the next year will bring!

Jan 16 2017
Jan 16

Usually when a huge site makes the decision to migrate to Drupal, one of the biggest concerns of the site owners is migrating the old site's data into the new Drupal site. The old site might or might not be a Drupal site, but given that the new site is on Drupal, we can make use of the cool migrate module to import data from a variety of data sources including but not limited to XML, JSON, CSV and SQL databases.

This article revolves around an example module named c11n_migrate showing how to go about importing basic data from a CSV data source, though things would work pretty similarly for other types of data sources.

The problem

As per project requirements, we wish to import certain data for an educational and cultural institution.

  • Academic programs: We have a CSV file containing details related to academic programs. We are required to create nodes of type program with the data. This is what we discuss in this article.
  • Tags: We have a CSV file containing details related to tags for these academic programs. We are required to import these as terms of the vocabulary named tags. This will be discussed in a future article.
  • Images: We have images for each academic program. The base name of the images are mentioned in the CSV file for academic programs. To make things easy, we have only one image per program. This will be discussed in a future article.

Executing migrations

Before we start with actual migrations, there are certain things which I would point out so as to ensure that you can run your migrations without trouble.

  • Though the basic migration framework is a part of the D8 core as the migrate module, to be able to execute migrations, you must install the migrate_tools module. You can use the command drush migrate-import --all to execute all migrations. In this tutorial, we also install some other modules like migrate_plus, migrate_source_csv.
  • Migration definitions in Drupal 8 are in YAML files, which is great. But the fact that they are located in the config/install directory implies that these YAML files are imported when the module is installed. Hence, any subsequent changes to the YAML files would not be detected until the module is re-installed. We solve this problem by re-importing the relevant configurations manually like drush config-import --partial --source=path/to/module/config/install.
  • While writing a migration, we usually update the migration over and over and re-run them to see how things go. To do this quickly, you can re-import config for the module containing your custom migrations (in this case the c11n_migrate module) and execute the relevant migrations in a single command like drush config-import --partial --source=sites/sandbox.com/modules/c11n_migrate/config/install -y && drush migrate-import --group=c11n --update -y.
  • To execute the migrations in this example, you can download the c11n_migrate module sources and rename the downloaded directory to c11n_migrate. The module should work without any trouble for a standard Drupal 8 install.

The module

Though a matter of personal preference, I usually prefer to name project-specific custom modules with a prefix of c11n_ (being the numeronym for the word customization). That way, I have a naming convention for custom modules and I can copy any custom module to another site without worrying about having to change prefixes. Very small customizations, can be put into a general module named c11n_base.

To continue, there is nothing fancy about the module definition as such. The c11n_migrate.info.yml file includes basic project definition with certain dependencies on other modules. Though the migrate module is in Drupal 8 core, we need most of these dependencies to enable / enhance migrations on the site:

  • migrate: Without the migrate module, we cannot migrate!
  • migrate_plus: Improves the core migrate module by adding certain functionality like migration groups and usage of YML files to define migrations. Apart from that, this module includes an example module which I referred to on various occasions while writing my example module.
  • migrate_tools: General-purpose drush commands and basic UI for managing migrations.
  • migrate_source_csv: The core migrate module provides a basic framework for migrations, which does not include support for specific data sources. This module makes the migrate module work with CSV data sources.

Apart from that, we have a c11n_migrate.install file to re-position the migration source files in the site's public:// directory. Most of the migration magic takes place in config/install/migrate_plus.* files.

Migration group

Like we used to implement hook_migrate_api() in Drupal 7 to declare the API version, migration groups, individual migrations and more, in Drupal 8, we do something similar. Instead of implementing a hook, we create a migration group declaration inside the config/install directory of our module. The file must be named something like migrate_plus.migration_group.NAME.yml where NAME is the machine name for the migration group, in this case, migrate_plus.migration_group.c11n.yml.

id: c11n
label: Custom migrations
description: Custom data migrations.
source_type: CSV files
dependencies:
  enforced:
    module:
      - c11n_migrate

We create this group to act as a container for all related migrations. As we see in the extract above, the migration group definition defines the following:

  • id: A unique ID for the migration. This is usually the NAME part of the migration group declaration file name as discussed above.
  • label: A human-friendly name of the migration group as it would appear in the UI.
  • description: A brief description about the migration group.
  • source_type: This would appear in the UI to provide a general hint as to where the data for this migration comes from.
  • dependencies: Though this might sound a bit strange to Drupal 7 users, this segment is used to define modules on which the migration depends. When one of these required modules are missing / removed, the migration group is also automatically removed.

Once done, if you install/re-install the c11n_migrate module and visit the admin/structure/migrate page, you should see the migration group we created above!

Migration group visible in UI

Migration definition: Metadata

Now that we have a module to put our migration scripts in and a migration group for grouping them together, it's time we write a basic migration! To get started, we import basic data about academic programs, ignoring complex stuff such as tags, files, etc. In Drupal 7 we used to do this in a file containing a PHP class which used to extend the Migration class provided by the migrate module. In Drupal 8, like many other things, we do this in a YML file, in this case, the migrate_plus.migration.program_data.yml file.

id: program_data
label: Academic programs and associated data.
migration_group: c11n
migration_tags:
  - academic program
  - node
# migration_dependencies:
#   optional:
#     - program_tags
#     - program_image
dependencies:
  enforced:
    module:
      - c11n_migrate

In the above extract, we declare the following metadata about the migration:

  • id: A unique identifier for the migration. In this example, I allocated the ID program_data, hence, the migration declaration file has been named migrate_plus.migration.program_data.yml. We can specifically execute this with the command drush migrate-import ID.
  • label: A human-friendly name of the migration as it would appear in the UI.
  • migration_group: This puts the migration into the migration group c11n we created above. We can execute all migrations in a given group with the command drush migrate-import --group=GROUP.
  • migration_tags: Here we provide multiple tags for the migration and just like groups, we can execute all migrations with the same tag using the command drush migrate-import --tag=TAG
  • dependencies: Just like in case of migration groups, this segment is used to define modules on which the migration depends. When one of these required modules are missing / removed, the migration is automatically removed.
  • migration_dependencies: This element is used to mention IDs of other migrations which must be run before this migration. For example, if we are importing articles and their authors, we need to import author data first so that we can refer to the author's ID while importing the articles. Note that we can leave this undefined / commented for now as we do not have any other migrations defined. I defined this section only after I finished writing the migrations for tags, files, etc.

Migration definition: Source

source:
  plugin: csv
  path: 'public://import/program/program.data.csv'
  header_row_count: 1
  keys:
    - ID
  fields:
    ID: Unique identifier for the program as in the data source.
    Title: Name of the program.
    Body: A description for the program.
    Level: Whether the program is for undergraduates or graduates.
    Type: Whether it is a full-time or a part-time program.
    Image file: Name of the image file associated with the program.
    Image alt: Alternate text for the image for accessibilty.
    Tags: Comma-separated strings to use as tags.
    Fees: We will ignore this field as per requirement.

Once done with the meta-data, we define the source of the migration data with the source element in the YAML.

  • plugin: The plugin responsible for reading the source data. In our case we use the migrate_source_csv module which provides the source plugin csv. There are other modules available for other data sources like JSON, XML, etc.
  • path: Path to the data source file - in this case, the program.data.csv file.
  • header_row_count: This is a plugin-specific parameter which allows us to skip a number of rows from the top of the CSV. I found this parameter reading the plugin class file modules/contrib/migrate_source_csv/src/Plugin/migrate/source/CSV.php, but it is also mentioned in the docs for the migrate_source_csv module.
  • keys: This parameter defines a number of columns in the source data which form a unique key in the source data. Luckily in our case, the program.data.csv provides a unique ID column so things get easy for us in this migration. This unique key will be used by the migrate module to relate records from the source with the records created in our Drupal site. With this relation, the migrate module can interpret changes in the source data and update the relevant data on the site. To execute an update, we use the parameter --update with our drush migrate-import command, for example drush migrate-import --all --update.
  • fields: This parameter provides a description for the various columns available in the CSV data source. These descriptions just appear in the UI and explain purpose behind each column of the CSV.
  • constants: We define certain values which we would be hard-coding into certain properties which do not have relevant columns in the data-source.

Once done, the effect of the source parameter should be visible on the admin/structure/migrate/manage/c11n/migrations/program_data/source page as follows:

Migration source visible in UI

Migration definition: Destination

destination:
  plugin: 'entity:node'
  default_bundle: program

In comparison to the source definition, the destination definition is much simpler. Here, we need to tell the migrate module how we want it to use the source data. We do this by specifying the following parameters:

  • plugin: Just like source data is handled by separate plugins, we have destination plugins to handle the output of the migrations. In this case, we want Drupal to create node entities with the academic program data, so we use the entity:node plugin.
  • default_bundle: Here, we define the type of nodes we wish to obtain using the migration. Though we can override the bundle for individual item, this parameter provides a default bundle for entities created by this migration. We will be creating only program nodes, so we mention that here.

Fields definitions for program nodes

Provided above is a quick look at the program node fields.

Migration definition: Mapping and processing

If you ever wrote a migration in an earlier version of Drupal, you might already know that migrations are usually not as simple as copying data from one column of a CSV file to a given property of the relevant entity. We need to process certain columns and eliminate certain columns and much more. In Drupal 8, we define these processes using a process element in the migration declaration. This is where we put our YAML skills to real use.

process:
  title: Title
  sticky: constants/bool_0
  promote: constants/bool_1
  uid: constants/uid_root
  'body/value': Body
  'body/format': constants/restricted_html
  field_program_level:
    -
      plugin: callback
      callable: strtolower
      source: Level
    -
      plugin: default_value
      default_value: graduate
    -
      plugin: static_map
      map:
        graduate: gr
        undergraduate: ug

Here is a quick look at the parameters we just defined:

  • title: An easy property to start with, we just assign the Title column of the CSV as the title property of the node. Though we do not explicitly mention any plugin for this, in the background, Drupal uses the get plugin to handle this property.
  • sticky: Though Drupal can apply the default value for this property if we skip it (like we have skipped the status property), I wanted to demonstrate how to specify a hard-coded value for a property. We use the constant constants/bool_0 to make the imported nodes non-sticky with sticky = 0.
  • promote: Similarly, we ensure that the imported nodes are promoted to the front page by assigning constants/bool_1 for the promote property.
  • uid: Similarly, we specify default owner for the article as the administrative user with uid = 1.
  • body: The body for a node is a filtered long text field and has various sub-properties we can set. So, we copy the Body column from the CSV file to the body/value property (instead of assigning it to just body). In the next line, we specify the body/format property as restricted_html. Similarly, one can also add a custom summary for the nodes using the body/summary property. However, we should keep in mind that while defining these sub-properties, we need to wrap the property name in quotes because we have a / in the property name.
  • field_program_level: With this property I intend to demonstrate a number of things - multiple plugins, the static_map plugin, the callback plugin and the default_value plugin.
    • Here, we have the plugin specifications as usual, but we have small dashes with which we are actually defining an array of plugins or a plugin pipeline. The plugins would be called one by one to transform the source value to a destination value. We specify a source parameter only for the first plugin. For the following plugins, the output of the previous plugin would be used as the input.
    • The source data uses the values graduate/undergraduate with variations in case as Undergraduate or UnderGraduate. With the first plugin, we call the function strtolower (with callback: strtolower) on the Level property (with source: Level) to standardize the source values. After this plugin is done, all Level values would be in lower-case.
    • Now that the values are in lower-case, we face another problem. The Math & Economics row, no Level value is specified. If no value exists for this property, the row would be ignored during migration. As per client's instructions, we can use the default value graduate when a Level is not specified. So, we use the default_value plugin (with plugin: default_value) and assign the value graduate (using default_value: graduate) for rows which do not have a Level. Once this plugin is done, all rows would technically have a value for Level.
    • We notice that the source has the values graduate/undergraduate, whereas the destination field only accepts gr/ug. In Drupal 7, we would have written a few lines of code in a ProgramDataMigration::prepareRow() method, but in Drupal 8, we just write some more YAML. To tackle this, we pass the value through a static_map (with plugin: static_map) and define a map of new values which should be used instead of old values (with the map element). And we are done! Values would automatically be translated to gr or ug and assigned to our program nodes.

With the parameters above, we can write basic migrations with basic data-manipulation. If you wish to see another basic migration, you can take a look at migrate_plus.migration.program_tags.yml. Here is how the migration summary looks once the migration has been executed.

$ drush migrate-import program_data --update
Processed 4 items (4 created, 0 updated, 0 failed, 0 ignored) - done with 'program_data'

Once done correctly, the nodes created during the migration should also appear in the content administration page just as expected.

Nodes created during migration

Next steps

This is part one in a series of three articles on migrating data in Drupal 8. Check this space next week for part two: Migrating taxonomy terms and term references, or the week after for part three: Migrating files and images.

Jan 16 2017
Jan 16

Join us for a FREE live webinar this week on managing media assets in Drupal 8.
Click here to save your seat!

I attended a core conversation titled “LET’S FIX FILE AND MEDIA HANDLING ONCE AND FOR ALL” at DrupalCon Prague in 2013.

This got my attention, not because the title was in all caps, but because Drupal needed to fix media management, as the title says: “ONCE AND FOR ALL”.

Let’s face it, Drupal doesn’t handle media very well when compared to other systems. I’ve worked with clients who are used to a certain level of functionality when it comes to managing images or videos on websites.

In Drupal 7 you had a few options.

You could use the Media module. But embedding images through the editor could be buggy depending on which module you’d use to implement the editor, i.e., Wysiwyg or CKEditor.

Then you have Scald, which is a pretty good module. Another module which has been around for a long time is IMCE.

However, adding media management into a Drupal site isn’t as straightforward as you think. That’s why I attended the core conversation in Prague. I too thought Drupal needed a great and robust media system.

Fast forward a couple of years since DrupalCon Prague and things have changed.

Thanks to the work from the Drupal Media team, managing media in Drupal 8 has got a lot better.

Now they are working on getting this functionality in Drupal core, which I think is absolutely amazing.

In this tutorial, I’ll show you how to set up media management in Drupal 8.

3 Parts to Handling Media in Drupal

Everyone has their own definition of media management. In this tutorial, I’m going to focus on three parts:

  1. Storing assets
  2. Embedding assets
  3. Browsing assets

I want to give users the ability to create media assets. Then have a button in the editor which they can use to browse assets and then embed them.

We’ll utilize three modules to handle this: Media Entity, Entity Embed and Entity Browser.

What’s Happened to the Media Module?

In Drupal 7, the Media module was jam-pack with a lot of functionality. In Drupal 8, a lot of its functionality has been broken out into seperate modules. There is a Drupal 8 version of Media and it’ll be used to create an out-of-the-box solution. The module doesn’t do much other than ship a bunch of configuration.

Part 1: How to Create Media Entities

To store media assets you’ll need the Media Entity module. The module itself won’t handle any media, it’s just a base entity.

So you’ll need to download other modules which are media providers. For example, if you want to handle images then download and install “Media entity image“. If you want to handle audio files you’ll need the “Media entity audio” module.

For a full list of media handlers go to the Media Entity project page.

I’m only going to focus on two types of assets in the tutorial: images and embedded videos (YouTube or Vimeo).

Let’s start by downloading the following modules:

Then install, “Media image entity” and “Video embed media” (sub-module of “Video embed field”)

Using Drush:

$ drush dl media_entity entity media_entity_image video_embed_field
$ drush en media_entity_image video_embed_media

Create Image Media Bundle

To handle images we’ll need to create a media type for images.

1. Go to Structure and click on “Media bundles”.

From this page you manage all the different media bundles. This is similar to the “Content types” page.

2. Click on “Add media bundle”.

3. Enter Image into Label, “Used for images.” into Description and select Image from the “Type provider” drop-down.

Ignore the other fields for now and scroll to the bottom and click on “Save media bundle”.

You can ignore the “Field with source information” drop-down. We’ll need to create a field and map it after.

4. Now click on “Manage fields” from the Operations drop-down.

We need to create an Image field that’ll be used to store the actual image field.

5. Click on “Add field”, select Image from “Add a new field” and enter Image into the Label field.

6. Leave the “Field settings” page as is and click on “Save field settings” at the bottom.

7. Leave the Edit page as is and click on “Save settings” at the bottom.

8. Click on the Edit tab from the “Manage fields” page to edit the media bundle.

9. Make sure the “Field with source information” drop-down has selected the image field which we created and click on “Save media bundle”.

Type Provider Configuration

The “Media entity” is like any other entity type: it’s fieldable. You can add custom fields, you can configure the form display and general display like you do with content types.

The only difference is, we need to tell the media bundle which field will store the actual file. If you’re creating a document media bundle, then you’d create a File field and select that in “Field with source information”.

Field Mapping

The “Field mapping” section lets you store metadata from the image into custom fields. If you want to store the width, then you’ll need to create a text field and select it from the Width drop-down.

Take note, the possible metadata properties are determined by the type of provider. You’d get different options if you were configuring a document media bundle.

Create Embed Video Media Bundle

Now it’s time to create another media bundle and this one will be used for embedding videos, i.e., YouTube or Vimeo videos.

1. Go back to “Media bundles” and click on “Add media bundles”.

2. Enter “Video embed” into Label, “Used for embedding videos.” into Description and select “Video embed field” from the “Type provider” drop-down.

3. Scroll to the bottom and click on “Save media bundle”.

We won’t have to create a “Video embed” field and map it across like we did for the Image bundle because “Video embed media” module did it for us.

Take note of this message:

So we’ve created our media bundles now let’s look at how to create a media asset.

How to Create a Media Assets

At this point, you can only create assets from the Media page.

1. Go to Content and click on Media.

From this page you can add a media asset and view existing ones.

2. To create an asset just click on “Add media”.

Go ahead and create an image and embeddable video.

Part 2: How to Embed Media Entities

Creating media assets is useful but if you can’t embed them what’s the point of having them.

In this section we’ll embed assets directly into the editor using Entity Embed.

The Entity Embed module allows a site builder to create a button which lets an editor embed entities into a text area, hence the name Entity Embed. It can be used to embed any type of entity not just media bundles. So be creative, you could use it to embed event content types.

To begin, download the following modules:

Using Drush,

$ drush dl embed entity_embed
$ drush en entity_embed

Create Embed Button

1. Go to Configuration and click on “Text editor embed buttons”.

2. Click on “Add embed button”.

3. Add Media into Label, select Entity from the “Embed type” and Media from the “Entity type” drop-down.

4. Once an entity type has been chosen, you can choose which media bundles can be embedded. If none are selected, then all are available.

And finally, upload a button icon which’ll be used in the editor. The default button is just an “E”.

Use this one from the media module: http://cgit.drupalcode.org/media/plain/images/media_embed_icon.png?h=8.x-1.x

At this point you should have two buttons, the Media button which we created and the Node button that comes default with the module.

Add Embed Button to Editor

We created the embed button, now we need to add it to the editor.

1. Go to Configuration, “Text formats and editors” and click Configure on the “Basic HTML” (or any text format) row.

2. Move the icon from the “Available buttons” into the “Active toolbar”.

From this:

To this:

Configure Filters

The next part we need to do is configure the filters.

We need to make sure a few things happen:

  1. Correct ordering of filters or the embedding may not work
  2. Making sure the “Allowed HTML tags” list accepts the tags used by Entity Embed

Configure “Allowed HTML tags” list

As soon as we added the button to the active toolbar, the following tags should be in the “Allowed HTML tags” list:

<drupal-entity data-entity-type data-entity-uuid data-entity-embed-display 
data-entity-embed-display-settings data-align data-caption data-embed-button>

Make sure these tags are in the text field. If not then embedding media assets WILL NOT WORK.

Enable “Display embedded entities”

Enable the “Display embedded entities” filter. This is required for the embedding to work.

Confirm Order of “Align images” and “Caption images”

The Entity Embed README.txt mentions if you’re using the “Align images” and “Caption images” filters, to order “Align images” before “Caption images”.

Problem with “Restrict images to this site” Filter

The “Restrict images to this site” Filter stops an image being displayed if you embed it and select an image style.

The filter stops a user from pointing to an image which is not hosted on the site. For example, if you’re Drupal site is hosted at my-drupal.com, then it won’t allow you to add an image such as <img src="http://random-site.com/image.jpg" />, all your images need to be <img src="http://my-drupal.com/image.jpg" />.

There is an open issue on drupal.org about it.

The workaround for now, unfortunately, is to remove the filter.

Once everything has been configured, make sure you click on “Save configuration” at the bottom of the page.

The filters list should look like this:

How to Embed Media into a Page

Now that the “Basic HTML” text format has been configured, we should be able to embed assets.

1. Go to Content, “Add content” and click on Article.

2. Click on the embed button and a pop-up should appear with an autocomplete field.

Search for the asset using its “Media name” and click on Next.

3. Select Thumbnail from “Display as”, select an image style, align and add a caption.

Then click on Embed.

4. Once embedded you should see the image on the right with the caption.

Save the page and you’re good to go.

Embedding YouTube Videos

In the section above it was easy to embed an image. You simply choose it, selected a thumbnail size and you’re done.

But if you try and embed a YouTube video using the “Video embed” bundle we created earlier. You’ll just see the video thumbnail and not an embedded player, not the desired result.

Create Media Bundle View Mode

The simple solution is to create a custom view mode for the “Video embed” media bundle. Let’s do this now.

1. Go to Structure, “Display modes”, “View modes” and click on “Add view mode”. Then click on Media.

2. Call this view mode Embed and click on Save.

3. Go to Structure, “Media bundles” and go to the “Manage display” page for the “Video embed” bundle.

4. Enable the Embed view mode which we just created by clicking on “Custom display settings” and select it, then click on Save.

5. Go to the view mode by clicking on it on the top left. Remove all the fields except “Video URL”. Make sure Video is selected from Format and “– Hidden –” from Label.

Then click on Save.

Now when you embed a video, select Embed from “Display as”. If you can’t see the new view mode clear the site cache.

Part 3: How to Browse Media Entities

When we chose an asset, we were given just a single autocomplete field.

This is not ideal. You shouldn’t expect your editors to remember the asset name. It’ll be better to have some sort of browser where we can see all the available media assets.

We’ll use Entity Browser to create browsing functionality and best of all, it integrates nicely with Entity Embed.

Let’s set this up now.

To begin, go download the following modules:

Using Drush,

$ drush dl entity_browser ctools
$ drush en entity_browser ctools

How to Create an Entity Browser

There are two steps involved in creating a browser using the module.

First you’ll need to create a view using a display called “Entity browser”. This view will be used to list out all assets. Then you’ll need to configure an entity browser and select the created view.

Create Entity Browser View

1. Go to Structure, Views and click on “Add view”.

2. Fill out the “Add new view” form, using the values defined in Table 1.0 and click on “Save and edit”.

Table 1-0. Create a new view

Option Value View name Entity browser Machine name entity_browser Show Media type of All sorted by Newest first Create a page Unchecked Create a block Unchecked

3. Next to the Master tab click on “Add” and click on “Entity browser.

It’s important that you select the “Entity browser” display or you won’t be able to select this view when we’re configuring the actual browser.

Let’s change the view to a table so it looks a little better.

4. Click on “Unformatted list” next to Format.

5. From the pop-up, select Table and click on Apply.

At this point we’ve switched the view from a list to a table.

Now we need to add two fields: Thumbnail and “Entity browser bulk select form”.

6. Click on Add next to Fields, add the Thumbnail field.

This will display a thumbnail of the media asset.

7. Then add the “Entity browser bulk select form”.

This field is used to select the asset when browsing. It is a required field.

8. Reorder the fields so they’re as follows:

9. Once complete the preview should look like the image below:

10. Don’t forget to click on Save.

Create Entity Browser

Now that we’ve created the view, let’s configure the browser.

1. Go to Configuration, “Entity browsers” and click on “Add entity browser”.

2. Enter “Assets browser” into Label, select iFrame from “Display plugin” and Tabs from “Widget selector plugin”.

Leave “Selection display plugin” as “No selection display”.

Then click on Next

Do not select Model if you’re using the browser with Entity Embed it isn’t compatible (Issue #2819871).

3. On the Display page, configure a width and height if you like but do check “Auto open entity browser. This will save an extra click when embedding.

Then click on Next.

4. Just click Next on “Widget selector” and “Selection display”.

5. On the Widgets page, select “Upload images” from the “Add widget plugin”. Change the Label to “Upload images”.

6. Then select View from the drop-down.

7. From the “View : View display” drop-down, select the view which we created earlier.

If you can’t see your view, make sure you select “Entity browser” when configuring it:

8. Once configured the Widgets page should look like:

Configure Entity Embed to use Browser

Entity Embed now needs to be linked with the browser we created.

1. Go to Configuration, “Text editor embed buttons” and edit the embed button.

2. You should see a drop-down called “Entity browser”, select the browser you just created and click on Save.

Using the Entity Browser

Go into an article or page and click on the Entity Embed button.

You should now see a pop-up with two tabs: “Upload images and view.

From the “Upload images” tab, you can upload a new image and it’ll create an Image media bundle.

If you click on view, you’ll see all the media assets.

To embed an asset, just choose which one you want and click on “Select entities”.

How do you Add a YouTube Video from the Entity Browser Page?

I haven’t figured this out yet. If you know how, leave a comment.

Summary

Adding functionality to a Drupal 8 site to handle media assets can be done and it’s fairly solid. But as you can see there’s a lot of configuration involved. Hats off to the Drupal Media team for creating a flexible suite of modules. With the “Media in Drupal 8 Initiative” in the works, things are looking very promising.

Extra Resources

FAQs

Q: I created a new view mode but can’t see it when I embed an asset.

Clear the site cache.

Q: When I embed an image and select an image style all I see is a red cross.

Disable the “Restrict images to this site” filter.

Jan 16 2017
Jan 16

Drupal 8.0.0 replaced the earlier, in-place upgrade procedure for major version upgrades with a migration-based solution for core and contributed modules. Several modules serve this need in core: The Migrate module provides a general API for migrations, the Migrate Drupal module provides API support for Drupal-to-Drupal migrations, and the Migrate Drupal UI module offers a simple user interface to run migrations from older Drupal versions.

A lot of work has gone into making migrations more complete since the initial 8.0.0 release, including for multilingual sites with various configurations. Drupal-to-Drupal migrations are still not wholly complete (especially for Drupal 7 sources). However, lots of real-life use has validated the choices we made with the base Migrate API, and key architectural improvements have been completed already. An increasing number of contributed modules rely on it for their migrations.

Based on this stability and success, the Migrate subsystem maintainers and Drupal release managers have agreed the Migrate API (the Migrate module) now has beta stability! The change took effect in Drupal 8.2.x with 8.2.5 and will apply to 8.3.0 onwards as well.

What does this mean for sites and developers relying on the Migrate API?

Beta experimental modules are considered API- and feature-complete and beta modules are subject to the beta allowed changes policy. This means that module and migration developers can rely on the API remaining stable from now on! This also means that the focus with Migrate API is on fixing critical issues as well as bug fixes and contributed project blockers, if they are non-disruptive, or if the impact outweighs the disruption.

Note that Migrate Drupal and Migrate Drupal UI are still alpha stability, so API changes may still happen in these modules. Completing the Drupal-to-Drupal migration path and getting Migrate Drupal to beta stability is the next priority, so your help with missing and incomplete migrations is welcome!

If you want to get involved, the migration team is meeting every week at alternating times. The team has Google Hangouts at Wed 1pm UTC or Thu 1am UTC on an alternating weekly schedule. The #drupal-migrate IRC channel is also used as a backchannel.

Jan 16 2017
Jan 16

2017 is going to be an incredible year for SooperThemes and for you! We kick off with the first official major version update for our drag and drop builder! Today we introduce you to Glazed Builder 1.1.0. We have rebranded Carbide Builder to Glazed Builder and developed some major new features, including entity revisions, full support for filtered contextual views and RGBA color sliders!

This is a monumental update. Glazed Builder is currently not only the greatest and fastest drag and drop builder for Drupal but also a very competitive product in the wider site building landscape. Going forward we'll not only develop more features and designs but we'll also start developing even more complete and complex turn-key Glazed demos. In addition this release marks the start of our Drupal 8 upgrade sprint. Yes, you heard that right, with the D8Media initiative aiming to add much needed Media improvements in Drupal 8.3 we feel like now is the right time. 

The Admin demo is free, no card required!

Redesigned for 2017

Glazed Builder has a fresh look that is designed for fast intuitive site-building. The speed of an interface isn't just about fast code, it's very much about fast design too. The new controls are all-white and while animation is used on the drop shadows the actual controls appear and respond immediately.

The colors and branding are matched to the Glazed brand, to emphasize the uniform experience you get when you combine our Glazed Theme with the builder. That said, Glazed Builder is still an independent module that can work with any Drupal theme! 

Views with Contextual Filters

Perfect if you're making a magazine website or more serious blog or commerce website! Now you can design taxonomy term or e-commerce pages with dynamic views displays. It is now fully supported to load views in drag and drop pages, override their filters and pagers in Glazed Builder, and let these views take default arguments based on url parts.

This feature is not just awesome for magazine websites but also for government websites, intranets, communities... Basically anyone who builds websites with large amounts of structured content will love this update.

Better Equal Heights Rows And Vertical Centering, RGBA Color

Having an alpha slider on every color setting in Glazed Builder allows more creative freedom for designers and makes it easier for site builders to implement designs that use translucent elements. We also improved equal heights rows by replacing the old jQuery trick with faster, reliable Flexbox CSS. In addition we enriched the column element with a Vertical Centering option. This let's you add vertical centering to columns independently of sibling columns. 

Saving Revisions

Some updates are invisible: Glazed Builder now automatically detects entity types that support revisions and will create new revisions when saving to fields on these entities. It doesn't matter if your field is on a node, bean block or custom entity. It doesn't even matter if you have have 10 Glazed Builder instances in one page, saving to multiple entities in the backend. It all just works and with revisions you'll easily undo those regrettable changes that seemed right in the spur of the moment.

Goole AMP

Google AMP is seeing more and more adoption and we integrated Glazed Builder with the Drupal AMP module. AMP is very restrictive so it won't allow fancy elements like circle charts and image comparison widgets but it should work fine with non-interactive markup generated by Glazed Builder. We welcome feedback from AMP experts on how to further extend AMP support!

Glazed Theme 2.6.0

While this releases focuses on the Glazed Builder update, Glazed Theme also received new features and consequently a major version bump to 2.6.0. Less spectaculary but still very useful: A Import/Export interface was added to the Glazed Theme Settings interface. All those settings that collectively make up your sites' unique design can now easily be copy-pasted between environments, or into a new subtheme .info file.

Drupal 8 Themes and Builder

As a subscription Drupal theme shop, we really focus on building long-term relationships with our customers and that's why we carefully chose important moments that affect the products we make and maintain. Choosing the right moment to make the D8 move was hard, because Drupal 7 is just so good that it was hard for Drupal 8 to take the crown. 

From my perspective, this moment is the release of Drupal 8.3 with contrib in great shape, improved media handling, and hopefully a media browser in core! The moment that it no longer makes sense for anyone to start a Drupal 7 website is approaching and we're going to make sure all our products are running smoothly on Drupal 8 by that time.

For more details about this update:

Jan 16 2017
Jan 16

In Part 1, I talked about using Google Docs + Migrate to populate your site. Now we’re going to do that with the Migrate Google Sheets module. Below, I’ll provide the steps to get your own migration up and running, but if you prefer to experiment with a working example, check out a demo of the Migrate Google Sheets Example module (provided as a submodule within Migrate Google Sheets). All content on that site was built using this example Google Sheet.

Setup: Install the Module

If you’ve already got a Drupal 8 site up and running, you can install the module in any of the normal ways. I’m assuming here that you have access to the site using Drush, as it’s not possible to run migrations through anything but the command line at this time. At ThinkShout, we use composer to build our site distributions, and have a repo for building the demo site here.

Step 1: Creating Your Custom Migration Module

The easiest way to get started on your own set of migrations is to copy the migrate_google_sheets_example submodule and rename it something of your own. Let’s say we rename it “my_migration.” Follow these steps:

  1. Rename your .install file to “my_migration.install”, and change the function migrate_google_sheets_example_uninstall to “my_migration_uninstall”.
  2. Delete the helper submodule “migrate_google_sheets_example_setup” entirely – that is just necessary to build the content types required for the example module, but you shouldn’t need it for your migration module.
  3. Rename your migrate_google_sheets_example.info.yml as “my_migration.info.yml” and open it up. At the very least, you’ll want to change the name of the migration to “name: my_migration” but you’ll also likely wish to remove the migrate_google_sheets:migrate_google_sheets_example_setup dependency. Mine ended up looking like this:
name: my_migration
type: module
description: My Migrations
core: 8.x
package: Migrate
dependencies:
  - migrate_plus
  - migrate_tools
  - migrate_google_sheets
  - redirect 

When completed, your module structure should look like this:

Module Structure

You are now ready to enable your My Migrations module. (Make sure you disable the migrate_google_sheets_example module first, if you’d previously enabled that!)

Step 2: Create Your Spreadsheet

Assuming you have the Game and Landing page content types, you could now run the migrations in your “My Migrations” module and it will pull the data from the Google Sheet.

But since you don’t have permissions to edit that sheet, you’re going to need to copy the existing sheet and create your own to do any customizations.

Spreadsheet

When this is done, you’ll get a url like this:

https://docs.google.com/spreadsheets/d/YourLongHashIDHere where YourLongHashIDHere is your feed key.

Now you’ll need to publish your new spreadsheet. This is an option under “File” -> “Publish to the web”

Spreadsheet

To verify that your migration module will be able to see the Google sheet, try opening an anonymous browser window and visiting the Feed version of the url, whose format is this:

https://spreadsheets.google.com/feeds/list/YourLongHashIDHere/SHEET/publ...

If visiting that URL throws out a bunch of json, you’re ready to start migrating!

But of course, your current set of migration files still point to the old feed. In the my_migrations/config/install folder, you’ll need to find all instances of our feed string (1spS1BeUIzxR1KrGK2kKzAoiFZii6vBHyLx_SA0Sb89M) and replace them with your feed string.

Step 3: Decide Which Migrations to Keep

The Migrate Google Sheets Example module provides one Migration Group (games_example) and 6 Migrations. Depending on your site configuration, some of these might be useful, like the menu_links and the blocks migrations, and some of them will not be so useful (like the node_game migration, likely). This is a good time to trim or modify any migrations that aren’t going to be useful for your Drupal site. That being said, here are a few things that the sample migrations demonstrate:

  • Block UUIDs: When you place a block using the Block Layout screen, the block’s UUID is saved in config. If you’re running a migration over and over, your block’s ID will iterate on its own, but the UUID can remain constant if you add it to the migration. In the demo site, this allows us to create a persistent CTA block in the header.

Module Structure

  • Menu Links parents: You can specify that a menu link item has a parent from within the current migration. This lets us say /bohnanza and /hanabi are children of /games
  • Page and Game redirects: These sheets demonstrate how to add the redirect from the url of content on an old site to the new home right in the content sheet. Try going to https://live-mgs-demo.pantheonsite.io/that-fireworks-game and see where you end up.
  • Related content as strings or ids: On the Page sheet, we have a reference to the “Related games” for the given page. This is an entity reference which we could fill with a couple of things. We could refer to the ID of the related games, as they are stored in the Games sheet, or we could do what we’ve done here and use the migrate_plus plugin “entity_lookup” to search for the related game node by name. As long as there is a Game node called Bohnanza, we’ll always link to the right one. This is particularly useful with Term references, where the name of the item ideally remains constant.

Related Content

  • Game downloadable file: Games have associated images, which are files hosted externally to the spreadsheet. In order to relate my game content to its image, I need to download the image, get it into the file_managed database table (creating a file entity) and THEN relate that entity to the current node. This is done with the following lines in the “node_games” migration:
public_file_directory:
    plugin: default_value
    default_value: 'public://'
  public_file_uri:
    plugin: concat
    delimiter: ''
    source:
      - @public_file_directory
      - imagefilename
  field_image/target_id:
    -
      plugin: file_copy
      source:
        - image
        - @public_file_uri
    -
      plugin: entity_generate
  field_image/alt: imagealt
  field_image/title: imagetitle

You can keep as many or as few of the migration files as you’d like. You can also create new ones.

Step 4: Tell Drupal About Your Changes

Drupal 8 only sees the changes you’ve made to your migration yml files when you first install the module. That means that you need to uninstall and reinstall the module to make new changes show up. ThinkShout has a Robo script that does this, but the same thing can be done in Drush:

drush mr --all             # Rolls back all migrations
drush pmu my_migration -y  # Disables my migration module
drush en my_migration -y   # Enable my migration module
drush ms                   # Displays my current migration status

You can also string these together as one line:

drush mr --all && drush pmu my_migration -y && drush pmu my_migration -y && drush ms

Step 5: Run your migrations

This part is simple. To run all migrations, it’s a quick drush command:

drush mi --all

If you’d like to find out about all the possible options for the migrate-import command, you can run

drush help mi

You can also see your list of migration groups at /admin/structure/migrate, and you can review your migrations by clicking “List Migrations.” The resulting page will give you the same output, more or less, that you get from running a drush ms.

Migrations

These pages are helpful to know about, as they give you an easy place to find errors logged during the migration process. However, you can’t currently run a migration from the UI (although there is an issue for this).

Gotchas

But before we close, I do want to acknowledge some challenges we’ve seen in this approach.

Sad fact #1: HTML in a spreadsheet is ugly.

Google Spreadsheets don’t let you make your rows smaller than the number of line breaks in a cell. So if you have pretty html with a bunch of line breaks, your row might be too tall to fit on your screen. People have some clever workarounds for this, but so far we’ve not implemented any.

Sad fact #2: Sheet order matters (right now)

Maintaining the order of sheets isn’t top on everyone’s minds as they’re making changes to a spreadsheet, especially when duplicating tabs. Migrate Google Sheets asks for each sheet based on tab order. If I make a copy of the Page tab, the Game tab is now the fourth tab instead of the third tab.

Copy of page

As it stands now, the module will happily request columns that don’t exist on the third tab and then fail in puzzling ways.

There is currently only one issue in the issue queue for the Migrate Google Sheets module, and it’s to fix this.

Sad fact #3: Google sheets must be publicly viewable to work (again, right now)

As the module exists right now, there’s no authentication involved, so any migrated content must be publicly viewable. Google authorization is possible with Oauth2, but that is not currently implemented.

Conclusion

Thanks for following along! I hope you found this series helpful. And don’t forget to visit the Migrate Google Sheets issue queue if you find any bugs, have an idea for a feature, or need help!

Get In Touch

Questions? Comments? We want to know! Drop us a line and let’s start talking.

Learn More Get In Touch
Jan 16 2017
Jan 16

In 2016, I had the opportunity to work on a contrib module called GatherContent.

The story began in February, when we were contacted by London-based company, GatherContent. GatherContent helps people who produce lots of content for CMS builds, usually for when you are launching a new a website. It helps take away the chaos that is involved when trying to pull content together and getting it approved for launch. It's CMS agnostic which means moving your content to and from various CMS' is easy. You also don’t need to start from scratch or educate your content editors, marketing team or anyone else responsible for the content as they are working from a single place.

Gather Content 4

An integration for Drupal existed before we were involved, but it was built by the GatherContent team, so it didn’t follow all of the Drupal UI and best practices. The goal of the project was to recreate the module for Drupal 7 using these best practices, and to create a brand new module for Drupal 8.

February

I started an analysis of the product and existing integration, based on the provided brief. I wanted to experience what a user of GatherContent would. This analysis was important to help me think like a GatherContent user based on a shared experience. My next step was to designed some mockups. These were mostly based on the UIs of the other integrations because we were trying to achieve ease of use. It was expected that the module would be used by non-Drupal people as well, such as marketing teams managing multiple CMSs. 

Gather Content 1

March

March started with the development of the new Drupal 7 version. Our internal goal was to build a Drupal 7 module in a way that would allow us to reuse a lot of code when building the Drupal 8 version. Therefore, we decided to use the Composer Manager module as one of the dependencies for the GatherContent module. This allows us to use the Guzzle library, which is incorporated in Drupal 8 core. We also decided to use entities. Not only are they the industry standard, but it also meant we didn’t have to spend too much time being careful with things solved by out-of-box entities.

April

Throughout April we focused on improving the user experience of module. We introduced our own theme components, partially fixing ones from Drupal core. We also entered the beta testing phase, so we focused on making the module run smoothly.

May

By May we were able to release the first stable version of the Drupal 7 module, and we jumped straight into the Drupal 8 development. First, we tried to automatically migrate the module using the Drupal module upgrader project. This did a nice job with some of the more obvious tasks, but most of the work had to be done manually. For that, we used Drupal Console, it was a very useful tool for scaffolding and testing some of our ideas. From an architectural point of view, we decided not to use migrate suite. The reason behind this is that Migrate is still not a stable part of Drupal core and at the time it was undergoing major architectural changes. We were also able to reuse most of our Drupal 7 code for importing and updating content.

June

We worked on cleaning up the code in the Drupal 8 module and we were fixing the first bug reports in Drupal 7. We hit problems with reporting issues on Drupal.org as some of our users aren’t active in the Drupal community. 

July

At the beginning of July, we released the first alpha version of the Drupal 8 module. Although it was quite stable, we wanted to have open options for future issues, mostly regarding backwards compatibility of the code components. During the summer, we gathered feedback and new feature requests.

September

In the first half of September we came up with several new features and approached the client to start the second phase of the module development. We decided to provide support for entity references in Drupal 8, and the ability for developers to change imported data, so they can easily provide support for other field types. We also improved the editor experience by simplifying menu creation and adding the option to import content as undefined. We understand that some users might want to automate imports, so we now support imports through drush.

Gather Content 2

October

As we reached October, development of the second phase of the project was running and we were able to fit in image field support for remote filesystems.

Gather Content 3

Current state

In November we tagged a new stable version for the Drupal 7 module and the first beta for the Drupal 8 module and we are now looking forward to gathering feedback from users.

In the end, I’m eager to say that I don’t see a big difference in working for regular clients and being paid for work on a contrib module. It is, of course, great to see the impact of your work and hear feedback from Drupal.org users.   

Jan 15 2017
Jan 15
I've already written about how to use native Drupal ajax mechanism on front-end side in this post. There I've described how to send requests immediately or by clicking (or by any other event) on any DOM element. It works when javascript is enabled in user's browser. But how to make your menu callback work both with ajax and GET (when js is disabled) requests?

1. Menu router. Have to contain additional argument %ctools_js:

2. Menu callback:

3. Link with a use-ajax class:

So what's happening here?

  • First. Clicking by the link with 'some/NID/nojs/path' href and 'use-ajax' class. If js is enabled in user's browser then 'nojs' substring will be replaced by 'ajax'.
  • Second. You need to find out what is %ctools_js. Ctools module provides a function ctools_js_load() that determines if it's ajax request or not ('ajax' or 'nojs' string passed as an argument to page callback).
  • Third. Page callback takes argument $ajax which equals 1 if ajax request and 0 otherwise (thanks to ctools_js_load() function). We're returning ajax commands for ajax requests and delivering render array with drupal_deliver_html_page() function otherwise. Since we've defined delivery callback for menu router as ajax_deliver we need to handle non ajax requests with drupal_deliver_html_page() function manually.

Key notes:

Jan 15 2017
Jan 15
Custom view

This video tutorial was made for our customer in order to demonstrate how to build a custom view to extract the data they need.

Here we are building a view to extract sales data per project where each project is classified into a category. We link invoice table with project table and add filter to be able to view data by year and category of project.

The tables and data sources used in this view are custom tables from our back office management solution built on Drupal 8. However, the principles of building a view are applicable to any other data source and this tutorial can be used to learn simple view building with tables relationships and filter.

Your browser doesn't support HTML5 video tag.

Jan 14 2017
Jan 14

Often when building a site in Drupal you'll find yourself wanting to display a list of nodes, or find entities created by a particular author, or locate some content based on a particular set of criteria. Rather than querying the database directly, Drupal provides a helper class, EntityQuery, to make things a bit easier. The EntityQuery class (and entity.query service) will probably look very familiar to you if you're familiar with the Database API, or the EntityFieldQuery class in Drupal 7. In this tutorial we'll go through several examples of using EntityQuery to find subsets of content.

By the end of this article, you should understand how to use entity queries to create custom sets of data from entities.

EntityQuery Conditions

The most important method in the EntityQuery class is the condition() method. Conditions allow us to limit our query in specific ways so that we can get back exactly what we want. Let's walk through a few examples in code to get a sense of how this can work in practice.

// The EntityQuery class can be loaded manually (if you know the entity type):
$query = \Drupal::entityQuery('node');
// Or loaded via the service container:
$query = \Drupal::service('entity.query');
// The examples below assume we're using the service container to load the EntityQuery class.

// Use conditions to get a list of published articles.
$node_ids = $query->get('node') 
->condition('type', 'article') 
->condition('status', 1)  // Once we have our conditions specified we use the execute() method to run the query
->execute();
// Find all users with the Administrator role.
$admin_user_ids = $query->get('user') 
->condition('roles', 'Administrator', 'CONTAINS') 
->execute();

The condition method takes up to four arguments: field, value, operator, and language code. The field should be the field name (and optional column) of the field being queried. Column names can be useful when dealing with reference fields, since additional field names can then be chained together. An example of this chaining would be if you want to query for articles created by a particular user name (rather than by ID) you could use

$query = Drupal::service('entity.query')->get('node'); 
$articles_by_name = $query->condition('type', 'article') 
->condition('uid.entity.name', 'admin') 
->execute();

Once you have the field name and the desired value identified, the next parameter passed to the condition method is the operator. The operator can take one of several options: '=', '<>', '>', '>=', '<', '<=', 'STARTS_WITH', 'CONTAINS', 'ENDS_WITH', 'IN', 'NOT IN', or 'BETWEEN'. For most operators, the value and the type of the column need to be the same literal. For example, it makes little sense to use the BETWEEN operator on an integer field, or the '<>' OPERATOR on a string. The 'IN' and 'NOT IN' operators expect array values. The final parameter is language code. Perhaps unsurprisingly, this allows you to limit the results of a query based on the translation status in a particular language.

// Find particular nodes published in the last year.
$query = Drupal::service('entity.query')->get('node');
$now = time();
$last_year = $now - 60*60*24*365; 
$last_years_articles = $query->condition('type', 'article') 
->condition('created', $last_year, '>=') 
->execute();

Two other methods that come in handy when building up the conditions of a query, orConditionGroup() and andConditionGroup(). Either allows you to define a group of conditions which will subsequently be either OR'ed or AND'ed together.

$query = \Drupal::entityQuery('node'); 
$group = $query->orConditionGroup() 
->condition('uid', 22) 
->condition('uid', 14) 
->condition('uid.entity.name', 'admin');
$entity_ids = $query->condition('type', 'article') 
->condition($group) -
>execute();

Other query helper methods

exists() or notExists()

If you need a simple check whether or not a particular field exists you can use the exists() or notExists() methods.

$query = \Drupal::entityQuery('node'); 
$untagged_articles = $query->condition('type', 'article') 
->notExists('field_tags') 
->execute();

sort()

The sort() method can be useful to order the results returned from EntityQuery in a particular way.

$query = \Drupal::entityQuery('user'); 
$time = time(); 
$yesterday = $time - 60*60*24; 
$new_users = $query->condition('created', $yesterday, '>=') 
->sort('created', 'DESC') 
->execute();

count()

If you're less interested in the actual entity ids, and more interested in how many entities match a particular query the count method returns the number of entities found matching your conditions.

$query = \Drupal::entityQuery('user');
$time = time();
$yesterday = $time - 60*60*24; 
$new_user_count = $query->condition('created', $yesterday, '>=')
 ->count()
 ->execute(); 

range() and pager()

Especially when working with a site that has a large amount of content it's important to think about limiting the number of results your query might return. Imagine the amount of memory required to load all of the published issue queue nodes from drupal.org with an entity query. That wouldn't be a very smart idea. This is where the pager() and range() methods come in handy. Pager allows us to specify a particular number of results, while the range method allows us to specify an index (or starting) number and the length (or page size) or results to return. Together these can be used to return a subset of any size from a result set.

$query = \Drupal::entityQuery('node');
$newest_articles = $query->condition('type', 'article') 
->condition('status', 1)  // Only return the newest 10 articles
->sort('created', 'DESC') 
->pager(10) 
->execute(); 
$not_quite_as_new_articles = $query->condition('type', 'article') 
->condition('status', 1)  // Only return the next newest 10 articles
->sort('created', 'DESC') 
->range(10, 10) 
->execute();

Now you can work with Drupal 8 Entityquery :D . it was simple, not?  :D

Aditional Resources :

https://www.drupal.org/project/entityqueryapi

https://api.drupal.org/api/drupal/core!lib!Drupal!Core!Entity!Query!Que…
 

Jan 14 2017
Jan 14

The problem: packages.drupal.org broken

I was starting my weekly work for the Drupal GraphQL module by the customary composer update --prefer-source -vvv command, ready to watch composer spit out some hundred lines sipping my coffee, but this time something turned out to be wrong:

OK, so packages.drupal.org is down for now. How can we work around this ?

The diagnostic

By default, most of my Drupal 8 project (aren't yours ?) are either based on drupal-composer/drupal-project for actual projects, or a slightly modified drupal/drupal when working on Drupal core or contrib modules like GraphQL. This was the case of the latter model, done by adding specific repositories entries to core composer.json, like:

    "repositories": {
        "fgm_graphql": {
            "type": "vcs",
            "url": "https://github.com/FGM/graphql-drupal"
        },
        "drupal": {
            "type": "composer",
            "url": "https://packages.drupal.org/8"
        }
    },

The explicit vcs entry on top is to enable me to work on my custom fork of the module (from which I send pull requests), no problem here. The issue is with the bottom one, used to download the other package in this project, specifically devel.

When working with such composer repositories, what Composer does is fetch a packages.json path below the url parameter, in this case https://packages.drupal.org/8/packages.json. That files is a list of data providers, basically one more level of metadata about available repositories and distributions (releases), looking like this:

{
   "notify-batch":"\/8\/downloads",
   "providers-url":"\/8\/%package%$%hash%.json",
   "search":"\/8\/search.json?s=%query%",
   "provider-includes":{
      "drupal\/provider-2017-1$%hash%.json":{
         "sha256":"67abb1f57bb826754ae5f4f877f92d2b1b58bfd45a56f4de883f2424be6cf8d5"
      },
      "drupal\/provider-2016-4$%hash%.json":{
         "sha256":"a30289dd8394e5271bd77777bb14b361c5938656f1cddad7fae1c00d5d6ba9c6"
      },
     [ ...snip ...]
      }
   }
}

Composer will then download from each of these providers in turn, hence the URL displayed as returning a 404 at the top of this story. Sure enough, manual checking of the URL returned a 404, even with a cache-buster query added.

Sure enough, the issue was already mentioned on IRC on #drupal : what could be done at this point without being able to touch packages.drupal.org itself ?

The solution: skip one layer

Actually, the answer is already present in the existing repositories clause: vcs-type repositories do not need a "directory"-type service like Packagist or packages.drupal.org, because in essence what these directories do is provide a way to locate sources and dists. But vcs provide the same service, with the limitation that they have to be listed for each project. So let us skip the composer directory and list devel's repository directly:

    "repositories": {
        "fgm_graphql": {
            "type": "vcs",
            "url": "https://github.com/FGM/graphql-drupal"
        },
        "devel": {
            "type": "vcs",
            "url": "https://git.drupal.org/project/devel.git"
        }
    },

Now, a composer require drupal/devel --prefer-source -vvv works normally, no longer needing to parse the broken directory:

Reading ./composer.json
Loading config file /Users/fgm/.composer/auth.json
Loading config file ./composer.json
[...snip...]
Executing command (/Users/fgm/.composer/cache/vcs/https---git.drupal.org-project-devel.git/): git show-ref --tags --dereference
Executing command (/Users/fgm/.composer/cache/vcs/https---git.drupal.org-project-devel.git/): git branch --no-color --no-abbrev -v
Executing command (/Users/fgm/.composer/cache/vcs/https---git.drupal.org-project-devel.git/): git branch --no-color
Executing command (/Users/fgm/.composer/cache/vcs/https---git.drupal.org-project-devel.git/): git show '8.x-1.x':'composer.json'
Executing command (/Users/fgm/.composer/cache/vcs/https---git.drupal.org-project-devel.git/): git log -1 --format=%at '8.x-1.x'
Reading composer.json of drupal/devel (5.x-0.1)
Skipped tag 5.x-0.1, invalid tag name
[...snip...]
Reading composer.json of drupal/devel (8.x-1.x)
Reading /Users/fgm/.composer/cache/repo/https---git.drupal.org-project-devel.git/70f62fd0773082a1a4305c6a7c2bccc649bc98a2 from cache
Importing branch 8.x-1.x (dev-8.x-1.x)
[...snip...]
(success)

Time to return to actual work :-)

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