Planet Drupal

Subscribe to Planet Drupal feed
Drupal.org - aggregated feeds in category Planet Drupal
Updated: 9 hours 1 min ago

Dries Buytaert: An update on the Media Initiative for Drupal 8.4/8.5

Fri, 11/10/2017 - 11:54

In my blog post, "A plan for media management in Drupal 8", I talked about some of the challenges with media in Drupal, the hopes of end users of Drupal, and the plan that the team working on the Media Initiative was targeting for future versions of Drupal 8. That blog post is one year old today. Since that time we released both Drupal 8.3 and Drupal 8.4, and Drupal 8.5 development is in full swing. In other words, it's time for an update on this initiative's progress and next steps.

8.4: a Media API in core

Drupal 8.4 introduced a new Media API to core. For site builders, this means that Drupal 8.4 ships with the new Media module (albeit still hidden from the UI, pending necessary user experience improvements), which is an adaptation of the contributed Media Entity module. The new Media module provides a "base media entity". Having a "base media entity" means that all media assets — local images, PDF documents, YouTube videos, tweets, and so on — are revisable, extendable (fieldable), translatable and much more. It allows all media to be treated in a common way, regardless of where the media resource itself is stored. For end users, this translates into a more cohesive content authoring experience; you can use consistent tools for managing images, videos, and other media rather than different interfaces for each media type.

8.4+: porting contributed modules to the new Media API

The contributed Media Entity module was a "foundational module" used by a large number of other contributed modules. It enables Drupal to integrate with Pinterest, Vimeo, Instagram, Twitter and much more. The next step is for all of these modules to adopt the new Media module in core. The required changes are laid out in the API change record, and typically only require a couple of hours to complete. The sooner these modules are updated, the sooner Drupal's rich media ecosystem can start benefitting from the new API in Drupal core. This is a great opportunity for intermediate contributors to pitch in.

8.5+: add support for remote video in core

As proof of the power of the new Media API, the team is hoping to bring in support for remote video using the oEmbed format. This allows content authors to easily add e.g. YouTube videos to their posts. This has been a long-standing gap in Drupal's out-of-the-box media and asset handling, and would be a nice win.

8.6+: a Media Library in core

The top two requested features for the content creator persona are richer image and media integration and digital asset management.

The results of the State of Drupal 2016 survey show the importance of the Media Initiative for content authors.

With a Media Library content authors can select pre-existing media from a library and easily embed it in their posts. Having a Media Library in core would be very impactful for content authors as it helps with both these feature requests.

During the 8.4 development cycle, a lot of great work was done to prototype the Media Library discussed in my previous Media Initiative blog post. I was able to show that progress in my DrupalCon Vienna keynote:

The Media Library work uses the new Media API in core. Now that the new Media API landed in Drupal 8.4 we can start focusing more on the Media Library. Due to bandwidth constraints, we don't think the Media Library will be ready in time for the Drupal 8.5 release. If you want to help contribute time or funding to the development of the Media Library, have a look at the roadmap of the Media Initiative or let me know and I'll get you in touch with the team behind the Media Initiative.

Special thanks to Angie Byron for contributions to this blog post and to Janez Urevc, Sean Blommaert, Marcos Cano Miranda, Adam G-H and Gábor Hojtsy for their feedback during the writing process.

CTI Digital: NWDUG Unconference 2017

Fri, 11/10/2017 - 10:00

I always look forward to unconferences. It’s their unpredictability and element of surprise that I enjoy, you never quite know what the day will bring. I love the edgy feel, the lower barrier to entry, and that it’s OK to fluff your words or try something new. Sensing the nerves of the ones who unexpectedly present for the first time, witnessing how energising their experience is, discovering a topic or theme for the first time, or taking the mic because you feel inspired by others are all reasons I’m drawn to attend and why CTI Digital was proud to be one of the sponsors.

The North West Drupal User Group Unconference last weekend was no exception in terms of inclusivity and our Drupal team were there in force.

Appnovation Technologies: Drupal Website Accessibility, Part 1: The problem, and why it matters…

Fri, 11/10/2017 - 08:00
Drupal Website Accessibility, Part 1: The problem, and why it matters… Drupal Website Accessibility, Part 1: The problem, and why it matters… "The power of the Web is in its universality.  Access by everyone regardless of disability is an essential aspect." - Sir Tim Berners-Lee, W3C, Director and Inventor of the World Wide Web In the coming weeks, I’ll be taking a look into one...

Red Route: Knowledge Is Dead, Long Live Learning

Fri, 11/10/2017 - 06:22

This article was originally posted on the Capgemini Engineering blog

There's a certain inescapable truth that people who work with technology need to face. As time goes by, the knowledge we’ve gained almost inevitably becomes obsolete. If we specialise in something, how do we deal with the fact that our specialism, which may even have been cutting edge technology that we were pioneering, eventually becomes a legacy system? As Ellen Ullman put it, "The corollary of constant change is ignorance ... we computer experts barely know what we are doing."

Front end developers are very familiar with this feeling, confronted so frequently with the dizzying pace of change in the world of JavaScript frameworks. Once upon a time, I was very proud of my ability to make CSS layouts work in IE7. Now all those tricks and hacks are little more than worthless trivia, perhaps less valuable than actual trivia. At least knowing who scored the winner in the 1973 FA Cup final might help in a pub quiz - I can't imagine that being able to prefix properties with an asterisk will ever come in handy, but it's taking up storage space in my brain. Now that CSS grid is becoming widespread, everything I've learned about floats (and even flexbox) is becoming less and less useful. There are even some people (although I'm not one of them) who would say that CSS itself no longer has value. Similarly, jQuery is already on its way to joining YUI and MooTools in the graveyard of things I used to know about, and experienced members of the Drupal community have recently been coming to terms with the fact that in order for the technology to progress, we'll have to unlearn some of our old ways.

It isn't just true for technology. London taxi drivers are finding that their hard-earned Knowledge is being made obsolete by satnav, and before too long, the skill of driving will itself have gone the way of basket weaving or being able to pilot a horse-drawn buggy - something that might still be interesting for the enthusiast, but isn’t relevant to most people’s lives.

Confronted with the unpleasant reality that our hard-learned skills are becoming outdated, what's the appropriate response? Do we follow the example of the Luddites and rage against the evolution of the machines? It's easy to fall victim to the sunk costs fallacy, and ego provides a strong temptation to hang on to our guru status, even if we're experts in an area that is no longer useful. If you're a big fish in a shrinking pond, you may need to be careful that your pond doesn't dry up entirely. Besides, do you really want to work on legacy systems? Having said that, if your legacy system is still mission-critical somewhere, and migrating away would be a big job, there's good money to be made - just ask the people working on COBOL.

I think there's a healthier way of looking at this. With the internet acting as a repository of knowledge, and calculators at our fingertips, education is evolving. There's no longer much value in memorising times tables, or knowing the date of the battle of Culloden. As my colleague Sarah Saunders has written, you're never too old to learn, but the value of learning things is greater than the value of the facts or skills that we learn - the meta-skill of learning is the really useful thing. Then again, I would say that, having done a philosophy degree.

For example, the time and effort I put into learning French and German at school doesn’t currently seem like a worthwhile investment, when I think about how frequently I use those languages. But I would never say that it was a waste of time. When I lived in Tokyo, having learned other languages definitely helped when it came to learning Japanese. Then again, these days I don’t often spend any time in Japan or with Japanese people, so the current value of that effort seems low. But do I regret spending that effort? Not a bit. It helped me to make the most of my life in Japan, and besides, it was interesting.

Two of the most compelling conference talks I've heard in the last few years touched on this theme from different directions. Andrew Clarke and Patrick Yua both emphasised the importance of focussing on the underlying principles, rather than chasing whatever the current new hotness might be. Designers and developers can learn something from Yves Saint Laurent: "Fashions fade, style is eternal".

We need to recognise that things will always keep changing. Perhaps we could help ourselves to acknowledge the impermanence of our skills by saying some kind of ceremonial goodbye to them. I have an absurd vision of a Viking funeral, where a blazing longboat sails away full of old O'Reilly books. We may not need to go that far, but we do need to remind ourselves that what we've learned has served us well, even if that knowledge is no longer directly applicable. A knowledge funeral could be an opportunity to mourn for the passing of a skill into obsolescence, and to celebrate the value of learning.

Image source: wikimedia

Tags:  learning development psychology Capgemini Drupal All tags

Morpht: Announcing Entity Class Formatter for Drupal 8

Fri, 11/10/2017 - 03:06

The Entity Class Formatter is a nifty little module which allows editors to place classes on to entities, allowing their look and feel to be altered in the theme layer or with other modules such as Look and Modifiers. It is now available for Drupal 8.

Entity Class Modifier is a humble little module, however, it does open up a lot of possibilities. The most obvious is to use the theme layer to provide styles for the class which has been applied. This makes it possible for the “designer” at design time to can some different styles to pick from. It is however, possible to use the module in a more flexible way and combine it with Modifiers and Looks.

Once a class has been defined and added to a field, a “Look Mapping” can be defined, associating the class with a set of Modifiers. The site builder or skilled editor can then go in and define any number of Modifiers which will be fired with the class.

For example - a “my-awesome-class” class could be created which is wired into a “field_my_awesome” set of Modifiers. The Modifiers could include a blue linear gradient with white text overlay with generous padding. All of this configuration happens in the UI after deploy. It is a very flexible and powerful system. The system can be configured after deployment and adapted to the styles you wish to apply.

Basic use of Entity Class Formatter

The use of the module is very easy. We can for example define our own class on the article.

The first thing we need to do is to enable the module. Once installation is complete we can go and add our custom field. In this little tutorial we will basically add the class onto the article content type. So go to Structure > Content types > Article > Manage fields and add new text field. We can name the field simply "Class" and save it. As we keep everything in default we can save it on the next page too.

 

Now the last thing we need to do to make it work is set the Entity Class on the field in Manage display page. Go to Structure > Content types > Article > Manage display and change the format to "Entity Class". There's no need to any other manipulation with field because it won't render any values which would be visible to the visitor of the page.

 

That's it! No we can go to create an article (Content > Add content > Article). Input class to our field...

... voila, class is there!

Similar but different

There are a couple of modules out there which are similar but are different enough for them not to be totally suited to our requirements.

Classy Paragraphs, available in Drupal 8, has been influential in the Paragraphs ecosystem and has opened the way for creative designs. It was intended to apply to Paragraphs only and is quite structured in the way classes are applied through a custom field type. The Entity Class Formatter module is more flexible in that it has been designed to apply to all entity types. It can also handle a variety of field types (text, select lists, entity references) and is able to adapt to the data at hand. So, Entity Class Formatter has a similar aim - it is just somewhat more generic.

Field Formatter CSS Class, available in Drupal 7, also uses a field formatter approach to applying the class to the entity. It does have more complexity than this module because it deals with several layers of nesting. The Entity Class Formatter is very simple and only applies to the parent entity of the field.

Entity Class Formatter was inspired by Classy Paragraphs (thanks) and is supported by the team at Morpht.

Agiledrop.com Blog: AGILEDROP: Drupal con Vienna’ session about business

Fri, 11/10/2017 - 01:11
Nowadays business in a complex and dynamic environment. Because of its uncertainness, it's never too late to listen to a good lecture. If you have missed any session from DrupalCon Vienna, let us highlight some of them to you.    Co-operative Drupal: Growth & Sustainability through Worker Ownership Finn Lewis, Technical Director of Agile Collective Ltd   There is an increasing number of worker-owned Drupal companies. So there are more and more sectors looking for effective and customizable software solutions, so it's a good time to start or grow Drupal's business, which is not… READ MORE

Acquia Developer Center Blog: Optional Config Weirdness in Drupal 8

Thu, 11/09/2017 - 20:24

Ah, the config system. Crown jewel of Drupal 8, amirite?

Well, yeah, it’s fantastic and flexible (as is most of Drupal). But if you have advanced use cases — such as building a system that alters config dynamically — there are traps you should know about.

Tags: acquia drupal planet

Stanford Web Services Blog: BADCamp 2017: Caryl’s Training Recap

Thu, 11/09/2017 - 18:14

BADCamp is a delightful mix of networking and educational opportunities. Along with connecting with former acquaintances and meeting new people, I attended two really informative trainings. Here’s my recap:

Acquia Lightning Blog: Optional config weirdness in Drupal 8

Thu, 11/09/2017 - 17:55
Optional config weirdness in Drupal 8 phenaproxima Thu, 11/09/2017 - 12:55

This post was originally published on Medium.

Ah, the config system. Crown jewel of Drupal 8, amirite?

Well, yeah, it’s fantastic and flexible (as is most of Drupal). But if you have advanced use cases — such as building a system that alters config dynamically — there are traps you should know about.

Config is normally a fairly static thing. Your module/theme/profile (“extension” from here on out) has some configuration in its config/install sub-directory, and when the extension is installed, that config is imported. Once it’s imported, that config is owned by your site and you can change it in any way you see fit.

That’s the simplest, and most common, use case in a nutshell. Let’s talk about some other ones.

Optional config

In some extensions’ config directory, you will see an ‘optional’ directory alongside ‘install’. If you look in there, you see…some YAML files. What’s all this, then?

Optional config is normal config, but it’s treated differently. When an extension is installed, each piece of optional config it provides is analyzed, then imported only if all of its dependencies are fulfilled. A piece of config can declare, for example, that it depends on a view called ‘content’. That’d be expressed thusly in code:

dependencies: config: - views.view.content

If that piece of config is optional, then it will only be imported if, well, a view called ‘content’ exists in the system. That view might have been shipped with another extension, or maybe you created it manually. It makes no difference. As long as a view called ‘content’ is present, any optional config that depends on it will be imported as well.

Neat, yes? This comes in quite handy for something like Lightning, which allows you to create an install profile which “extends” Lightning, yet only installs some of Lightning’s components. Optional config allows us to ship config that might be imported, depending on what parts of Lightning you have chosen to install. Hooray for flexibility!

Optional config whilst installing Drupal

But wait, there’s more.

When you’re doing a full site installation (i.e., install.php or drush site-install), optional config is treated a little bit differently. In such a case, all extensions are installed as normal, but their optional config is ignored initially. Then, at the end of the installation process1, once all extensions are installed (and their default config has been imported), all2 the optional config is imported in a single batch.

I don’t think this is documented anywhere, but it can have major ramifications. Consider this piece of code — let’s say it’s part of a module called fubar, which provides some default config and some optional config. This hook will be invoked while fubar is being installed, but after its default config has been imported:

<?php /** * Implements hook_install(). */ function fubar_install() { $view = entity_load('view', 'fubar_view'); $view->setDescription('The force will be with you, always.'); $view->save(); }

fubar_view is optional config, so will entity_load() return a view entity, or will it return NULL? What do you think?

The maddening answer is it depends. It depends on when fubar is being installed. If Drupal is already installed, and you’re just adding fubar to your site, then $view will be a view entity, because the optional config will be imported before hook_install() is invoked. But if you’re installing fubar as part of a full site install — as part of an installation profile, say — $view is going to be NULL, because optional config is imported in a single batch at the end of the installation process, long after all hook_install() implementations have been invoked.

Yeah, it’s a WTF, but it’s a justifiable one: trying to resolve the dependencies of optional config during Drupal’s install process would probably have been a colossal, NP-complete nightmare.

Dynamically altering optional config

So let’s say you need to make dynamic alterations to optional config. You can’t do it in hook_install(), because you can’t be sure that the config will even exist in there. How can you do it?

The easiest thing is not to make assumptions about when the config will be available, but simply react when it is. If the optional config you’re trying to alter is an entity of some sort, then you can simply use entity hooks! Continuing our fubar example, you could add this to fubar.module:

<?php use Drupal\views\ViewEntityInterface; /** * Implements hook_ENTITY_TYPE_presave(). */ function fubar_view_presave(ViewEntityInterface $view) { if ($view->isNew() && $view->id() == 'fubar_view') { $view->setDescription('Do, or do not. There is no try.'); } }

This ensures that fubar_view will contain timeless Yoda wisdom, regardless of whether fubar_view was imported while installing Drupal. If fubar_view is created at the end of the installation process, no problem — the hook will catch it and set the description. On the other hand, if fubar_view is installed during drush pm-enable fubar, the hook will…catch it and set the description. It works either way. It’s fine to dynamically alter optional config, but don’t assume it will be available in hook_install(). Simply react to its life cycle as you would react to any other entity’s. Enjoy!

Moar things for your brain
  • hook_install() can never assume the presence of optional config…but it can assume the presence of default config (i.e., the stuff in the config/install directories). That is always imported before hook_install() is invoked.
  • In fact, you can never depend on the presence of optional config. That’s why it’s optional: it might exist, and it might not. That’s its nature! Remember this, and code defensively.
  • The config_rewrite module, though useful, can throw a monkey wrench into this. Due to the way it works, it might create config entities, even optional ones, before hook_install() is invoked. Even during the Drupal installation process. Beware! (They are, however, working to fix this.)
  • The config system is well-documented. Start here and here. This post is only one of tons of other blog posts about config in D8.
  • This blog post came about because of this Lightning issue. So hats off to Messrs. mortenson and balsama.
  • Thanks to dawehner for reviewing this post for technical accuracy.
  • “NP-complete”, as I understand it, is CompSci-ese for “unbelievably hard to solve”. Linking to the Wikipedia article makes me seem smarter than I really am.

1 The reason this happens at the end is because any number of things could be changing during installation (who knows what evil lurks in hook_install()? Not even the Shadow knows), and and trying to solve multiple dependency chains while everything is changing around you is like trying to build multiple castles on a swamp. (Only one person has ever accomplished it.) Don't think about this stuff too much, or it will melt your brain.

2 “All”, in this case, means “all the optional config with fulfilled dependencies,” not all-all.

Amazee Labs: GraphQL for Drupalers - the basics

Thu, 11/09/2017 - 16:25
GraphQL for Drupalers - the basics

GraphQL is becoming more and more popular every day. Now that we have a beta release of the GraphQL module (mainly sponsored and developed by Amazee Labs) it's easy to turn Drupal into a first-class GraphQL server. This is the new GraphQL series in which we'll describe the features that are new in beta and provide a detailed overview of the integration with Drupal's entity and field systems.

Blazej Owczarczyk Thu, 11/09/2017 - 17:25

This is the new GraphQL series about the new features that are in beta (published 2 weeks ago) and how they are connected with Drupal out of the box

The modules

Let's start with the modules we need. Recently there were quite a few changes in this field. In alpha we had:

  • graphql - The main module laying the foundations for exposing data using Drupal plugins.
  • graphql_core - which exposed Drupal core data - the info about entity types and bundles, but not fields
  • graphql_content - which handled field exposition with the view modes
  • other auxiliary modules (e.g., graphql_boolean graphql_entity_reference) that provided behaviours for specific field types

In beta, the structure has changed. Now the default schema exposes all the Drupal fields and (content) entities in raw form, using the typed data. Thanks to that GraphQL became a zero-configuration plug and play module. We just need to enable graphql and graphql_core (the only two modules that are shipped with the package now) and we're good to go.

NOTE: The other modules are still available in case you need them, they're just not part of the core package anymore. graphql_legacy is where most of the field modules went. Besides that, there are graphql_views  which lets us expose views, the promising graphql_twig that allows using GraphQL queries without a decoupled setup and a few more. All of the modules are listed in the drupal-graphql organization page on GitHub.

The Explorer

After enabling graphql and graphql_core we can go ahead and test it with GraphiQL; an interactive tool that lets you run queries, see results in real time and preview all the available fields along with arguments and return types. It also provides query validation, autocompletes suggestions and keyboard shortcuts. Thus, it's a kind of an IDE. The explorer is connected with the schema. We can find it next to the Default Schema under: Configuration -> Web services -> GraphQL -> Schemas or using the direct path - graphql/explorer.

This is how it looks. On the left, there is a query box with a comment describing the tool and listing the keyboard shortcuts. Results show up in the box on the right. To run the query we can use the «play» button at the top, or the keyboard shortcut (Ctrl-Enter). One more important piece is the < Docs button in the upper right corner. This is where we can see all the available elements. Let's go ahead and click it.

The only thing we can start with is the query field which is of type RootQuery. Clicking on the type shows a list of available sub-fields, including userById, which looks like this:

This field takes two arguments: an id (which is a string) and a language (which can be set to any language enabled on the site) and is of type User. Clicking on the type brings up a list of fields on a user. The name is a string:

Strings are scalars (which means they don't have subfields) so we can finish our simple query here. It will look like this:

and (after running it with Ctrl-enter) the response is what we'd expect

The GraphQL explorer (or GraphiQL) gives us the ability to easily write and debug every GraphQL query. That's a feature that's hard to overestimate so getting familiar with it is a good starting point to understanding how GraphQL works in general and how we can get our Drupal data out of it.

The Voyager

Voyager is another schema introspection tool, a visual one this time. It draws a chart of all the types and field in the system and presents it in a nice way. It can be found next to The Explorer under: Configuration -> Web services -> GraphQL -> Schemas or using the direct path - graphql/voyager.

That's it for this post. In the next one, we'll see some examples of retrieving data from Drupal fields.

 

Texas Creative: Drupal 8 Custom Table of Contents Block for Book Content

Thu, 11/09/2017 - 16:15

Want to customize the default block that comes with the book module?  Here’s a way to do it without writing custom code by using Views and the Views Tree module.

Read More

Aten Design Group: Sunshine, Rainbows and Release Cycles: Drupal 9 and Beyond

Thu, 11/09/2017 - 15:57
Planning for Drupal 9 and Beyond

Justin’s recent post about the product approach and Drupal's new release cycle got me thinking about what upgrading to Drupal 9 will really look like from a more technical standpoint. There's already lots of information out there explaining this new feature. I think there are some misconceptions about what it means for Drupal projects though, so let’s take a little look under the hood.

Background

To understand what the process of updating to Drupal 9 might look like, you'll need to know a few background terms. If you already know what "semver" and "deprecation" mean for a software project, you can skip ahead to "Preparing for Drupal 9".

Drupal now follows semantic versioning. Semantic versioning—semver for short—is a format for assigning numbers to releases. This number signals a promise about the compatibility and/or stability of a release. A semver tag has three parts, a major version number, a minor version number and a patch version number, all separated by periods. For example, a recent version of Drupal was tagged 8.4.0. For this release, the major version number was 8; the minor version number is 4; the patch number is 0. A common misconception is to think that after 8.0.9, one must have 8.1.0, but it’s perfectly acceptable to have a release number like 8.0.107.

What do major, minor and patch mean? We'll look at each from least to most significant.

A patch release signifies a fix.

They are usually composed of small, isolated fixes. When Drupal is on version 8.4.0 and releases version 8.4.1, this indicates that it is a patch release. It means, "nothing in your code needs to change, we just fixed some bugs". These might be security fixes, so you should update to these releases as soon as possible.

A minor release signifies new features and deprecations.

These are my favorite. They're the ones filled with all the sunshine and rainbows. A minor release adds new features and code to Drupal. This might mean an experimental module becomes stable as the Workflow module did in 8.4.0 or—my personal favorite—a new experimental module, like JSON API, might be added to Core. A minor release is an opportunity for Drupal's maintainers to clean things up, to keep Drupal fresh and to ensure Drupal keeps up with the needs of modern applications. It's also an opportunity to deprecate parts of Drupal Core. A deprecation is when a part of Drupal Core gets moved to the graveyard. It will be maintained for security fixes and bugs, but you shouldn't use that part of Drupal any more. It's usually an indication that there are new, better APIs or best-practices you should follow. A minor release says, "we've made things better, we've cleaned stuff up, and we didn't break your stuff yet."

The graveyard of deprecated APIs

A major release signifies incompatible changes.

They can be a cause for celebration or an ominous cloud on the horizon. They're a critical event in Drupal's lifecycle. A major release is a signal that says "Warning: Your Stuff Might Break!" In some software, it might mean you need to rebuild your project. In Drupal 8 and beyond, thankfully, this shouldn't be the case. The maintainers have made a promise that says, "if you're not using deprecated code, you can update without rebuilding." In the past, like from Drupal 6 to Drupal 7, that wasn't the case and things definitely broke.

Preparing for Drupal 9

So, you know what a deprecation is, and what a major version release means. You know that the promise of Drupal's new release cycle is "if you're not using deprecated code, you can update without breaking things." But did you catch the caveat? If you're not using deprecated code. It's here that I believe the most misconceptions lie. I believe that many have confused this promise to mean that projects can be updated to Drupal 9 without a hitch. That the upgrade will be relatively routine.

The truth is that it's really up to you to make it routine and not something to fear.

How you approach your Drupal software project means that this major event in Drupal's lifecycle can be either a midlife crisis or a coming-of-age.

I said earlier that all the sunshine and rainbows are in the minor version releases. That's because you get all the goodies for free. Underneath the hood though, you need to be aware of what's being deprecated. Every time something is deprecated, a little technical debt is added to your project. You should pay that debt as soon as possible. A deprecation is a warning that the code underneath is going to disappear in Drupal 9. It's a warning to module maintainers and project developers that they need to update code that relies upon that newly deprecated API.

This reality is another point in the product approach's favor, as we alluded to in our earlier post. By making an ongoing investment in your project, you can address these deprecations as they happen so that when you're ready to upgrade to Drupal 9, it will be as painless as any other update.

The Prettiest Rainbows Come After a Little Rain

A select few sites will be able to proudly announce their upgrades to Drupal 9 the day it's released or soon after. Many won’t be able to move so quickly.

Knowing that Drupal 9 will break things relying on deprecated code and knowing that many modules won’t have been updated ahead of the release (such is life in open source), many sites will have to be patient. How patient depends on whether they’ve made an ongoing investment in their platform and kept up with deprecations in their custom code. They’ll be able to upgrade even faster if they have been model citizens in the Drupal community by reporting—and hopefully fixing—contrib dependencies on deprecated code too.

So, is this just like every other major Drupal release? Absolutely not! Gone forever are the days of completely rebuilding your site with every major update. Gone are the days of needing to migrate your own content to your own site. What this means is that you’ll just have to sit inside and wait or go play in the rain, fixing some bugs and updating some outdated dependencies, before you get to enjoy the Drupal 9 rainbow.

Here are some helpful links to stay up to date with deprecations:

Morpht: Announcing Webform Mass Email port for Drupal 8

Thu, 11/09/2017 - 09:57

The Webform Mass Email module has now been ported to Drupal 8. Time to get emailing :)

The Webform module has long been a powerhouse for Drupal site builders, providing a flexible solution to one of the central problems of web development: collecting user data, storing it and notifying users when content is added. the Webform module has been extended in many directions, supporting ancillary workflows and use cases.

One such use case is sending a mass email to all subscribers to a Webform. This can be handy for when users are signing up to an event or have registered their interest in a topic. The Webform Mass Email module fills this gap.

The module works as follows

When installed, Webform Mass Email module adds a new sub-tab called "Mass Email" under all Webform's "Results" tab (next to "Submissions", "Download" and "Clear") and one settings sub-tab called "Mass Emails" under global Webform "Configuration" tab (next to "Exporters", "Libraries" and "Advanced").

  1. Site builder navigates to the "Mass Emails" tab and set base module settings once.
    • "Cron time" - How much time is being spent per cron run (in seconds).
    • "Allow sending as HTML" - If checked, all emails are processed as HTML formatted.
    • "Log emails" - Should the emails be logged to the system log at admin/reports/dblog?
  2. The site builder can then assign "Send Webform Mass Email" permission for roles which are able to send mass emails.
  3. Anyone with this permission can then navigate to the "Mass Email" sub-tab of any Webform and send messages.
    • "Email field" - The field that is going to be used as the recipient's email address.
    • "Subject" - Message subject for your email.
    • "Body" - Message text for your email.
  4. After you hit the "Send emails" button, messages are inserted into queue and sent when cron is running.

The module has now been ported to Drupal 8 and is being supported by the team at Morpht. Check it out and let us know what you think.
 

Dropsolid: James & Jenny, our toolbox for faster Drupal development

Thu, 11/09/2017 - 07:30
09 Nov James & Jenny, our toolbox for faster Drupal development Nick Vanpraet Tech Drupal 8

Be aware, this is a longread with extensive value. Only read this if you are ready to uncover our Dropsolid team's exciting dev tool and platform secrets!

 

James & Jenny might sound more like a comedy double act or the protagonists of an long-forgotten tale, but they are in fact very much alive and kicking. They are the names we gave to the platforms that we developed in-house to spin up environments faster and get work done more efficiently. How? Read on!

 

In practice

Whenever we want to spin up a new server, start a new project or even create a new testing environment, we still rely on our infrastructure team. A while ago we managed to automate our build pipeline with some smart configuration of Jenkins, an open source piece of software. Combined with a permission system, we are already able to let technical clients or consultants participate in the development process of a site by triggering a build of an environment. We decided to call this home-coded piece of software James, our in-house Drupal Cloud Butler. However, this UI was very cluttered and it was easy to break the chain. Maintenance-wise, it wasn’t the friendliest system either. James 0.1 was very helpful, but needed polishing.

Behind the scenes we started building a proper platform that was designed to supersede this existing system and take over the creation of new servers, projects and environments by adding a layer on top of this - a layer that could talk to Jenkins and would be able to execute Ansible playbooks through a managed system via RabbitMQ. You could see this as James 0.2. This version of James only has one account and isn’t built with a great many permissions in mind. Its purpose is very simple: get stuff done. This means we still can’t let clients or internal staff create new environments on James directly or set up new projects. But we’d really like to.

This is why we’re currently also investing heavily in the further development of Jenny, the site-spinning machine. Jenny’s aim is to be a user-friendly layer on top of James and it consists of of two parts: a loosely decoupled Angular application consuming a Drupal 8 backend exposed through a REST API, which in turn talks to James through its REST API. Because Jenny makes sure only calls that are allowed go through to James, James can stay focused on functionality without having to add a ton of logic to make sure the request is valid. If the person who wants that new environment isn’t allowed to request one, Jenny won’t ask James to set it up in the first place.

 

How it works

 

A Jenny user will be able to create a new organization, and within that organization create new projects or clone existing ones. These projects can be housed on our servers or on external hosting (with or without VPN, Firewalls or anything else that’s required). They’ll be able to create new environments, archive entire projects or just a single environment, build, back up, restore, sync across environments, log in to an environment’s site, etc. It will even contain information about the health of the servers and also provide analytics about the sites themselves.

Now, because single-person organisations are rather non-existent, that user will be able to add other users to their organization and give them different permissions based on their actual role within the company. A marketeer doesn’t need to know the health of a feature-testing environment, and a developer has little use in seeing analytics about the live environment.

The goal of this permission system is to provide the client enough options that they can restrict a developer from archiving live but allow them to create a new testing environment and get all needed information and access for that environment. On a sidenote: these aren’t standard Drupal permissions, because those are for members within an organization, and a single user can be a part of many organizations and have different permissions for each one.

 

End-to-end

But all these layers have to be able to talk to each other before any of that can happen. JennyA(ngular) has to talk to JennyB(ackend), JennyB then has to make sure the request is valid and talk to James. And whatever information James returns, has to be checked by JennyB, stored in the database if needed, and then transformed into a message that JennyA can do something with.

To make sure we can actually pull this off, we created the following test case:

How do we trigger a build of an environment in Jenkins from JennyA, and how do we show the build log from Jenkins in JennyA?

JennyA: build the page, get project and environment info from JennyB, create a button and send request to API. How this process happens exactly, will be explained in a different post.

JennyB

For this REST resource we need two entities: Project and Environment.
We create some new permissions (defined as options in an OrgRole entity) for our Environment entity type:

  • Create environment
  • Edit environment
  • Delete environment
  • Archive environment
  • View environment
  • View archived environment
  • Build environment

Next to this, we build a custom EntityAccessControlHandler that checks these custom permissions. An AccessControlHandler must have two methods: checkAccess() and checkCreateAccess(). In both we want to make sure Drupal’s normal permissions (which for this entity we reduce to simply ‘manage project environment entities’) still rule supreme, so superadmins can debug everything. Which is why both access checks start with a normal, bog-standard $account->hasPermission() check.

if ($account->hasPermission('administer project environment entities')) { return AccessResult::allowed(); }

But then we have to add some extra logic to make sure the user is allowed to do whatever it is they’re attempting to do. For that we grab that user’s currently active Membership. A Membership is a simple entity that combines a user, an organization, and an OrgRole entity which says what permissions the user has within that organization. For non-Create access we first check if this user is even a part of the same organization as the entity they’re trying to save.

// Get the organization for this project environment $organization = $entity->getProject()->getOrganization(); // Check that the active membership and the attached organization match $accessResult = Membership::checkIfAccountIsPartOfCorrectOrganization($organization, $account); if ($accessResult->isForbidden()) { return $accessResult; }

For brevity’s sake, I won’t explain how exactly checkIfAccountIsPartOfCorrectOrganization does its checks. But it returns an AccessResultInterface object and does exactly what it says on the tin. It also includes a reason for forbidding access, so we can more easily debug problems. You can just add a string to the creation of an AccessResult or use $accessResult->setReason() and you can then grab it using $accessResult->getReason(). Take note: only forbidden and neutral implement that method. Make sure the result implements the AccessResultReasonInterface before calling either method.

if ($accessResult instanceof AccessResultReasonInterface) { $accessResult->getReason(); }

We use this extensively with our unit testing, so we know exactly why something fails.
Assuming our test passes, we can finally check if this user has the correct permissions.

$entityOrganizationMembership = User::load($account->id())->getActiveMembership(); switch ($operation) { case 'view': if (!$entity->isActive()) { return $this->allowedIf($entityOrganizationMembership->hasPermission('view archived project environment'), 'member does not have "view archived project environment" permission'); } return $this->allowedIf($entityOrganizationMembership->hasPermission('view project environment'), 'member does not have "view project environment" permission'); case 'update': case 'delete': case 'archive': case 'build': return $this->allowedIf($entityOrganizationMembership->hasPermission($operation . ' project environment'), 'member does not have "' . $operation . ' project environment" permission'); } // Unknown operation, no opinion. return AccessResult::neutral('No operation matches found for operation: ' . $operation);

As you might have noticed, normally when you load a User you don’t get a getActiveMembership() method. But we extended the base Drupal User class and added it there. We also set that new class as the default class for the User entity, which is actually very easy:

function hook_entity_type_build(&$entity_types) { if (isset($entity_types['user'])) { $entity_types['user']->setClass('Drupal\my_module\Entity\User); } }

Now loading a user returns an instance of our own class.

For createAccess() things get trickier, because at that point the entity doesn’t exist yet.This makes it impossible to check if it’s part of the correct organization (or in this case, the correct project, which is in turn part of an organization). So here we’ll have to also implement a field level Constraint on the related project field. This article explains how to create a field level Constraint.

In this Constraint we can do our Membership::checkIfAccountIsPartOfCorrectOrganization check and be sure nobody will be able to save an environment to a project for an organization they are not a part of, regardless if they are creating one or saving one (somehow having bypassed our access check). To make doubly sure, we also set the $validationRequired property on our Environment class to true. This way entities will always demand to be validated first. If they are not, or they have errors, an exception will be thrown.

Now we can finally build our rest resource. Since a Jenkins build doesn’t exist as a custom entity within JennyB (yet), we create a custom REST resource. We use Drupal console for this and set the canonical path to “/api/project_environment/{project_environment}/build/{id}” and the “create” path to “/api/project_environment/{project_environment}/build”. We then create another resource and set that one’s canonical to “/api/project_environment/{project_environment}/build”, the same as our first resource’s “create” path. This way, when you POST to that path you trigger a new build and when you GET you receive a list of all builds for that environment. We have to split this off into two resources, because each resource can only use each method once.


We generate these resources using Drupal console. But before we can begin with our logic proper, we have to make sure the ProjectEnvironment entity gets automatically loaded. For this we need to extend the routes method from the parent class.

public function routes() { $collection = parent::routes(); // add our paramconverter to all routes in the collection // if we could only add options to a few routes, we would have // to loop over $collection->all() and add them to specific ones. // Internally, that is exactly what the addOptions method does anyway $options['parameters']['project_environment'] = [ 'type' => 'entity:project_environment', 'converter' => 'paramconverter.entity' ]; $collection->addOptions($options); return $collection; }

In the routes method you can add or remove options and requirements to your heart’s content. Whatever you can normally do in a routes.yml file, you can also do here. We've explained this in more detail in this blog post.

Let’s take a closer look at our create path. First we’ll need to make sure the user is allowed to build. Luckily thanks to our custom access handler this is very easy.

// check if user can build $entity_access = $projectEnvironment->access('build', NULL, TRUE); if (!$entity_access->isAllowed()) { // if it’s not allowed, we know it’s a forbidden or neutral response which implements the Reason interface. throw new AccessDeniedHttpException($entity_access->getReason()); }

Now we can ask James to trigger the build.

// Talk to James $data['key'] = self::VALIDATION_KEY; $url = self::API_URL . '/project/' . $projectEnvironment->getProject() ->getRemoteProjectID() . '/environment/' . $projectEnvironment->getRemoteEnvironmentID() . '/build'; $response = $this->httpClient->request('POST', $url, array('json' => $data)); $responseData = json_decode($response->getBody()->getContents(), TRUE);

For this test we use a simple key that James uses for authentication and build the URL in our REST resource. Eventually this part will be moved to a library and the code might look something like this:

$remoteProjectID = $projectEnvironment->getProject()->getRemoteProjectID(); $remoteEnvironmentID = $projectEnvironment->getRemoteEnvironmentID(); $response = $this->jamesConnection->triggerNewBuild($remoteProjectID, $remoteEnvironmentID, $data); $responseData = json_decode($response->getBody()->getContents(), TRUE);

We check the data we get back and if everything has gone well, we can update our local ProjectEnvironment entity with the new currently deployed branch.

if ($response->getStatusCode() == 200 && $data['key'] !== $projectEnvironment->getCurrentlyDeployedBranch()) { // Everything went fine, so also update the $projectEnvironment to reflect what // the currently deployed branch is $projectEnvironment->setCurrentlyDeployedBranch($data['branch']); // validate the entity $violations = $projectEnvironment->validate(); foreach ($violations as $violation) { $errors[] = $violation->getMessage(); } if (isset($errors)) { throw new BadRequestHttpException("Entity save validation errors: " . implode("\n", $errors)); } // save it $projectEnvironment->save(); }

Running validate is necessary, because we set the $validationRequired property to TRUE for our entity type. If something goes wrong, including our custom Constraints, we throw a Bad Request exception and output the validation errors.

Then we simply return what James gave us.

return new ResourceResponse($responseData, $response->getStatusCode());

On James’ end, it’s mostly the same but instead of checking custom access handlers, we (for now) just validate the key. And James in turn calls Jenkins’ API. This will also change, and James will hand off the build trigger to RabbitMQ. But for the purpose of this test, we communicate with Jenkins directly.

James then returns the ID of the newly triggered build to JennyB, who returns it to JennyA. JennyA then uses that ID to call JennyB’s canonical Build route with the given ID until success or failure has occurred.

 

Curious to read more interesting Drupal-related tidbits? Check out the rest of our blog. Or simply stay up to date every three months and subscribe to our newsletter!

myDropWizard.com: Using lots of different tools? Do it all in Drupal instead!

Thu, 11/09/2017 - 05:43

You need a website. You need to send an e-mail newsletter. You need to track (potential) volunteers, donors, or customers. You could use Drupal, Mailchimp and HubSpot. Or you could do it all in Drupal.

We've been using the tools above in our own organization, and we continue to use them. Yet, we've been toying with the idea of moving more of our daily usage to a more Drupal based solution. I'll try to outline some of the pros and cons of each approach. I think you'll see for many organizations the Drupal solution could end-up on the winning side of the decision!

The Heavyweight Single Purpose Tools

We've used a number of we based services at myDropWizard to help keep sales, projects, and customer communication on track.

I'll outline just a few that we use that are very popular. that would make for a good comparison with a Drupal solution.

MailChimp

Currently, we use MailChimp for newsletters. I think MailChimp is a champion product with low prices and great features. MailChimp is probably the most used email newsletter platform, so it's strengths are well known.

Elevated Third: Marketing Automation, Meet Drupal

Wed, 11/08/2017 - 20:06
Marketing Automation, Meet Drupal Marketing Automation, Meet Drupal Andy Mead Wed, 11/08/2017 - 13:06

Oh, hi there. I’d be lying if I said I wasn’t expecting you. This is a blog after all. And supposedly people read these things, which is, supposedly, why you’re here. So pull up a seat (if you’re not already sitting) and I’ll tell you why Drupal is a great partner for Marketing Automation.

Ah, Marketing Automation. (Hereafter MA, because why read 7 syllables when you can read 2?) It’s arguably the most hyped business technology of the last decade or so, spoken about in hushed tones, as though simply subscribing to a platform will print money for you. Sadly that’s not the truth. But when used properly with digital strategy, it’s pretty good at what it does: capturing latent demand and turning it into sales. The tricky part is the modifying clause that opened the last sentence, “when used properly.”

What to expect from Marketing Automation?

Marketing Automation tools and platforms these days come loaded with bells and whistles: from custom reporting engines to fancy drag-n-drop campaign UIs, and WYSIWYGs that let marketers build digital assets like landing pages and emails. And yet, despite all that fanciness, it’s still really hard to do Marketing Automation right. Why? Well, leaving aside strategic questions (a massive topic on its own), my own experience with MA always left me wanting two things - expressibility and scalability.

Drupal + Marketing Automation

While publishing workflows in Marketing Automation, tools have improved over the years. They still can’t compete with a CMS; particularly one as powerful as Drupal. Drupal empowers users to express content in terms that go far beyond simple landing pages.

In fact, Drupal is used today for just about anything you can imagine, from powering Fortune 500 marketing websites to running weather.com and acting as the backbone of custom web applications. What’s possible with Drupal is really up to you. Just ask the guy who built it.

So, fine. Drupal is great and everything. But how does it help your marketing? Well, because Drupal is so flexible, you can integrate it with almost anything:  like Google Analytics, Pardot, Marketo, Eloqua, Salesforce, and on, and on, and on. In a quickly changing technology landscape that’s an incredible strength because it acts as the nervous system for your marketing technology stack.

“Marketing technology stack?” Yeah, I don’t like business jargon, either. But, it’s a helpful way to think about digital marketing tools. Because they are just that: tools with strengths and weaknesses. You probably wouldn’t use a screwdriver to drive a nail into the wall. Sure, you could, but there’s a better tool for the job: a hammer. Likewise, your MA platform could power all your digital assets, but there’s a better tool for that job, too: Drupal.

The right tools for the job

In my experience, organizing these tools around their strengths brings better results. And here at Elevated Third, we’ve done that by connecting Drupal to Marketing Automation platforms like Pardot, Marketo, and SharpSpring; using it as the front end for services that are powering marketing programs. And moreover, MA is only a piece of that puzzle. Want to use something like HotJar? Drupal is happy to.

Open source means flexibility 

So where does this flexibility come from? Drupal is Open Source Software and there’s a massive developer community that improves it daily. Probably the strength of open source software is its flexibility.

You don’t like the way something works? Easy. Let’s change it.

Is something broken? No problem, let’s fix it.

Got a new problem that off-the-shelf solutions don’t solve? Well, then, let’s built a solution for it.

Is Drupal the right tool for every job? I’d be lying (again) if I said it was. But it’s the right tool for jobs that require unique, flexible solutions. And it could be the right tool for your job, too. If you are curious, let's talk

Cheeky Monkey Media: The Drupal Checklist Every Developer Needs

Wed, 11/08/2017 - 19:49
The Drupal Checklist Every Developer Needs cody Wed, 11/08/2017 - 19:49

Are you almost finished setting up your Drupal website? At a glance, everything might look ready to go.

But, before you hit "publish," you need to make sure you haven't made any mistakes.

A writer proofreads before they post an article. Similarly, a developer should double check their work.

The last thing you want is to go live with your site and have something go wrong. Finding problems before you launch can save some headaches and embarrassment.

We've compiled a pre-launch, Drupal checklist. When it's complete, you'll rest easy knowing that your website is ready to go.

Security

Security is the first on this Drupal checklist because it's so important. Of course you want to rest easy knowing that your site is secure when it launches. You also want your users to have peace of mind knowing that their information is safe.

Double checking your site's security will ensure that there's nothing you've missed that could make you vulnerable to hackers.

Evolving Web: Profiling and Optimizing Drupal Migrations with Blackfire

Wed, 11/08/2017 - 19:34

A few weeks ago, us at Evolving Web finished migrating the Princeton University Press website to Drupal 8. The project was over 70% migrations. In this article, we will see how Blackfire helped us optimize our migrations by changing around two lines of code.

Before we start
  • This article is mainly for PHP / Drupal 8 back-end developers.
  • It is assumed that you know about the Drupal 8 Migrate API.
  • Code performance is analyzed with a tool named Blackfire.
  • Front-end performance analysis is not in the scope of this article.
The Problem

Here are some of the project requirements related to the problem. This would help you get a better picture of what's going on:

  • A PowerShell script exports a bunch of data into CSV files on the client's server.
  • A custom migration plugin PUPCSV uses the CSV files via SFTP.
  • Using hook_cron() in Drupal 8, we check hashes for each CSV.
  • If a file's MD5 hash changes, the migration is queued for import using the Drupal 8 Queue API.
  • The CSV files usually have 2 types of changes:
    • Certain records are updated here and there.
    • Certain records are added to the end of the file.
  • When a migration is executed, migrate API goes line-by-line, doing the following things for every record:
    • Read a record from the data source.
    • Merge data related to the record from other CSV files (kind of an inner join between CSVs).
    • Compute hash of the record and compare it with the hash stored in the database.
    • If a hash is not found in the database, the record is created.
    • If a hash is found and it has changed, the record is updated.
    • If a hash is unchanged, no action is taken.

While running migrations, we figured out that it was taking too much time for migrations to go through the CSV files, simply checking for changes in row hashes. So, for big migrations with over 40,000 records, migrate was taking several minutes to reach the end of file even on a high-end server. Since we were running migrate during cron (with Queue Workers), we had to ensure that any individual migration could be processed below the 3 minute PHP maximum execution time limit available on the server.

Analyzing migrations with Blackfire

At Evolving Web, we usually analyze performance with Blackfire before any major site is launch. Usually, we run Blackfire with the Blackfire Companion which is currently available for Google Chrome and Firefox. However, since migrations are executed using drush, which is a command line tool, we had to use the Blackfire CLI Tool, like this:

$ blackfire run /opt/vendor/bin/drush.launcher migrate-import pup_subjects Processed 0 items (0 created, 0 updated, 0 failed, 0 ignored) - done with 'pup_subjects' Blackfire Run completed

Upon analyzing the Blackfire reports, we found some 50 unexpected SQL queries being triggered from somewhere within a PUPCSV::fetchNextRow() method. Quite surprising! PUPCSV refers to a migrate source plugin we wrote for fetching CSV files over FTP / SFTP. This plugin also tracks a hash of the CSV files and thereby allows us to skip a migration completely if the source files have not changed. If the source hash changes, the migration updates all rows and when the last row has been migrated, we store the file's hash in the database from PUPCSV::fetchNextRow(). As a matter of fact, we are preparing another article about creating custom migrate source plugin, so stay tuned.

We found one database query per row even though no record was being created or updated. Didn't seem to be very harmful until we saw the Blackfire report.

Code before Blackfire

Taking a closer look at the RemoteCSV::fetchNextRow() method, a call to MigrateSourceBase::count() was found. It was found that the count() method was taking 40% of processing time! This is because it was being called for every row in the CSV. Since the source/cache_counts parameter was not set to TRUE in the migration YAML files, the count() method was iterating over all items to get a fresh count for each call! Thus, for a migration with 40,000 records, we were going through 40,000 x 40,000 records and the PHP maximum execution time was being reached even before migrate could get to the last row! Here's a look at the code.

protected function fetchNextRow() { // If the migration is being imported... if (MigrationInterface::STATUS_IMPORTING === $this->migration->getStatus()) { // If we are at the last row in the CSV... if ($this->getIterator()->key() === $this->count()) { // Store source hash to remember the file as "imported". $this->saveCachedFileHash(); } } return parent::fetchNextRow(); }Code after Blackfire

We could have added the cache_counts parameter in our migration YAML files, but any change in the source configuration of the migrations would have made migrate API update all records in all migrations. This is because a row's hash is computed as something like hash($row + $source). We did not want migrate to update all records because we had certain migrations which sometimes took around 7 hours to complete. Hence, we decided to statically cache the total record count to get things back in track:

protected function fetchNextRow() { // If the migration is being imported... if (MigrationInterface::STATUS_IMPORTING === $this->migration->getStatus()) { // Get total source record count and cache it statically. static $count; if (is_null($count)) { $count = $this->doCount(); } // If we are at the last row in the CSV... if ($this->getIterator()->key() === $count) { // Store source hash to remember the file as "imported". $this->saveCachedFileHash(); } } return parent::fetchNextRow(); }Problem Solved. Merci Blackfire!

After the changes, we ran Blackfire again and found things to be 52% faster for a small migration with 50 records.

For a bigger migration with 4,359 records the migration import time reduced from 1m 47s to only 12s which means a 98% improvement. Asking why we didn't include the screenshot for the bigger migration? We did not (or rather could not) generate a report for the big migration because of two reasons:

  • While working, Blackfire stores function call and other information to memory. Running a huge migration with Blackfire might be a bit slow. Besides, our objective was to find the problem and we could do that more easily while looking at smaller figures.
  • When running a migration with thousands of rows, the migration functions are called over thousands of times! Blackfire collects data for each of these function calls, hence, the collected data sometimes becomes too heavy and Blackfire rejects the huge data payload with an error message like this:
The Blackfire API answered with a 413 HTTP error () Error detected during upload: The Blackfire API rejected your payload because it's too big.

Which makes a lot of sense. As a matter of fact, for the other case study given below, we used the --limit=1 parameter to profile code performance for a single row.

A quick brag about another 50% Improvement?

Apart from this jackpot, we also found room for another 50% improvement (from 7h to 3h 32m) for one of our migrations which was using the Touki FTP library. This migration was doing the following:

  • Going through around 11,000 records in a CSV file.
  • Downloading the files over FTP when required.

A Blackfire analysis of this migration revealed something strange. For every row, the following was happening behind the scenes:

  • If a file download was required, we were doing FTP::findFileByName($name).
  • To get the file, Touki was:
    • Getting a list of all files in the directory;
    • Creating File objects for every file;
    • For every file object, various permission, owner and other objects were created.
    • Passing all the files through a callback to see if it's name was $name.
    • If the name was matching, the file was returned and all other File objects were discarded.

Hence, for downloading every file, Touki FTP was creating 11,000 File objects of which it was only using one! To resolve this, we decided to use a lower-level FTP::get($source, $destination) method which helped us bypass all those 50,000 or more objects which were being created per record (approximately, 11,000 * 50,000 or more for all records). This almost halved the import time for that migration when working with all 11,000 records! Here's a screenshot of Blackfire's report for a single row.

So the next time you think something fishy is going on with code you wrote, don't forget to use use Blackfire! And don't forget to leave your feedback, questions and even article suggestions in the comments section below.

More about Blackfire

Blackfire is a code profiling tool for PHP which gives you nice-looking reports about your code's performance. With the help of these reports, you can analyze the memory, time and other resources consumed by various functions and optimize your code where necessary. If you are new to Blackfire, you can try these links:

Apart from all this, the paid version of Blackfire lets you set up automated tests and gives you various recommendations for not only Drupal but various other PHP frameworks.

Next Steps
  • Try Blackfire for free on a sample project of your choice to see what you can find.
  • Watch video tutorials on Blackfire's YouTube channel.
  • Read the tutorial on creating custom migration source plugins written by my colleague (coming soon).
+ more awesome articles by Evolving Web

Lullabot: Styling the WYSIWYG Editor in Drupal 8

Wed, 11/08/2017 - 16:42

Drupal 8 ships with a built-in WYSIWG editor called CKEditor. It’s great to have it included in core, but I had some questions about how to control the styling. In particular, I wanted the styling in the editor to look like my front-end theme, even though I use an administration theme for the node form. I spent many hours trying to find the answer, but it turned out to be simple if a little confusing.

In my example, I have a front-end theme called “Custom Theme” that extends the Bootstrap theme. I use core’s “Seven” theme as an administration theme, and I checked the box to use the administration theme for my node forms. 

My front end theme adds custom fonts to Bootstrap and uses a larger than normal font, so it’s distinctively different than the standard styling that comes with the WYSIWYG editor. 

Front End Styling undefined WYSIWYG Styling

Out of the box, the styling in the editor looks very different than my front-end theme. The font family and line height are wrong, and the font size is too small.

undefined

It turns out there are two ways to alter the styling in the WYSIWYG editor, adding some information to the default theme’s info.yml file, or implementing HOOK_ckeditor_css_alter() in either a module or in the theme. The kicker is that the info changes go in the FRONT END theme, even though I’m using an admin theme on the node form.

I added the following information to my default theme info file, custom_theme.info.yml. The font-family.css and style.css files are the front-end theme CSS files that I want to pass into the WYSIWYG editor. Even if I select the option to use the front-end theme for the node form, the CSS from that theme will not make it into the WYSIWYG editor without making this change, so this is necessary whether or not you use an admin theme on the node form!  

name: "Custom Theme" description: A subtheme of Bootstrap theme for Drupal 8. type: theme core: 8.x base theme: bootstrap ckeditor_stylesheets: - https://fonts.googleapis.com/css?family=Open+Sans - css/font-family.css - css/style.css libraries: ... WYSIWYG Styling

After this change, the font styles in the WYSIWYG editor match the text in the primary theme.

undefined

When CKEditor builds the editor iframe, it checks to see which theme is the default theme, then looks to see if that theme has values in the info.yml file for ckeditor_stylesheets. If it finds anything, it adds those CSS files to the iframe. Relative CSS file URLs are assumed to be files in the front-end theme’s directory, or you can use absolute URLs to other files.

The contributed Bootstrap module does not implement ckeditor_stylesheets, so I had to create a sub-theme to take advantage of this. I always create a sub-theme anyway, to add in the little tweaks I want to make. In this case, my sub-theme also uses a Google font instead of the default font, and I can also pass that font into the WYSIWYG editor.

TaDa!

That was easy to do, but it took me quite a while to understand how it worked. So I decided to post it here in case anyone else is as confused as I was.

More Information

To debug this further and understand how to impact the styling inside the WYSIWYG editor, you can refer to the relevant code from two files in core, ckeditor.module:  

/** * Retrieves the default theme's CKEditor stylesheets. * * Themes may specify iframe-specific CSS files for use with CKEditor by * including a "ckeditor_stylesheets" key in their .info.yml file. * * @code * ckeditor_stylesheets: * - css/ckeditor-iframe.css * @endcode */ function _ckeditor_theme_css($theme = NULL) { $css = []; if (!isset($theme)) { $theme = \Drupal::config('system.theme')->get('default'); } if (isset($theme) && $theme_path = drupal_get_path('theme', $theme)) { $info = system_get_info('theme', $theme); if (isset($info['ckeditor_stylesheets'])) { $css = $info['ckeditor_stylesheets']; foreach ($css as $key => $url) { if (UrlHelper::isExternal($url)) { $css[$key] = $url; } else { $css[$key] = $theme_path . '/' . $url; } } } if (isset($info['base theme'])) { $css = array_merge(_ckeditor_theme_css($info['base theme']), $css); } } return $css; }

and Plugin/Editor/CKEditor.php:  

/** * Builds the "contentsCss" configuration part of the CKEditor JS settings. * * @see getJSSettings() * * @param \Drupal\editor\Entity\Editor $editor * A configured text editor object. * @return array * An array containing the "contentsCss" configuration. */ public function buildContentsCssJSSetting(Editor $editor) { $css = [ drupal_get_path('module', 'ckeditor') . '/css/ckeditor-iframe.css', drupal_get_path('module', 'system') . '/css/components/align.module.css', ]; $this->moduleHandler->alter('ckeditor_css', $css, $editor); // Get a list of all enabled plugins' iframe instance CSS files. $plugins_css = array_reduce($this->ckeditorPluginManager->getCssFiles($editor), function($result, $item) { return array_merge($result, array_values($item)); }, []); $css = array_merge($css, $plugins_css); $css = array_merge($css, _ckeditor_theme_css()); $css = array_map('file_create_url', $css); $css = array_map('file_url_transform_relative', $css); return array_values($css); }

Valuebound: Enabling custom web font in Drupal website

Wed, 11/08/2017 - 12:21

This blog will walk you through one the contributed module in Drupal community that has been a heave of sigh for me whenever I was in trouble for web building activity. A couple of weeks back, I have been assigned a task where the requirement was to enable ‘Benton-sans Regular’ font throughout the site. Initially, I thought it would be an easy task and can be done easily. But I was wrong.

No issues! If you facing similar difficulties. Here, I am going to discuss how you can enable ‘Benton-sans Regular’ font seamlessly using Drupal font-your-face module.

Pages