Upgrade Your Drupal Skills

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

See Advanced Courses NAH, I know Enough
Jun 26 2018
Jun 26
Drupal Europephoto: Paul Johnson @ flickr

Drupal is our business.

Regardless of being a freelancer, a two person shop or a hundred plus agency, Drupal is vital to our success in growing and supporting our business.

The business ecosystem is changing rapidly, thereby making it a necessity for agency leaders, managers and advisors to focus on a multitude of challenges and opportunities.

Understanding how the marketplace is evolving, driving innovation, fostering the right company culture, and adopting efficient project management methodologies, are all challenges faced by businesses today.

We all want to transform our business by working with the smartest team, create and deliver amazing projects, and have ideal customers lining up to work with us.

Any Drupal conference cannot be complete without in-depth discussions and debates about these challenges and more.

The Agency Business track will provide insight, support and real stories from people running businesses and managing projects. Learn about other people’s experiences, and get tips and ideas on how to tackle the challenges faced in your business or project.

Photo: Michael Cannon @ Flickr

Growing and scaling your business can be a tricky and daunting task. We need to consider strategies for how to grow our businesses, and how to do so sustainably.

With increased competition from both other agencies and other platforms, we need to look at not only how we generate new leads for our businesses, but how do we convince potential clients that Drupal is the best, that we are the best?

What is the right company culture for my business? How can I better lead my agency through the challenges ahead? How can I provide good leadership to my team? How can we grow and scale our business, without losing our company culture along the way? These are just some of the questions we will look to answer in the Agency Business track.

Project management is a bit of a juggling act, with many different needs and tasks that need to be taken care of simultaneously. We’re always on the look-out for ways to increase a project’s effectiveness and efficiency, while reducing the risk of it getting out of control. Let’s share our experiences and ideas on how we can improve project planning, better manage timelines & budgets, and keep staff motivated, while all the time keeping clients happy and engaged in the process.

Markets change faster and faster, so does our market. We need to adapt our products and offering to stay competitive and minimize our business risks. Perhaps it means diversifying your service offerings, perhaps it means developing a product, perhaps it means extending into new markets or verticals. However, we also need to consider how to keep clients happy and how to continue to meet their changing needs through innovation and/or diversification.

At Drupal Europe, we want to ensure that attendees get the most from this track through highly valuable and insightful sessions. We are looking for speakers to openly and honestly share stories about their challenges and how they solved it. We want to hear about your experiments, successes and failures, process discoveries, strategies, and tactics. We want real-life learnings, supported by facts and figures — prove to us that your way is best.

Session submissions are open and will close on 30 June 2018.

Whatever your experience is, whether it be running a small 2 person operation or scaling to 30 and beyond, or managing projects and project teams, we want to hear from you. Your experience and insight is invaluable and we know others will think so too.

Come to Drupal Europe and share your experiences with us — submit a session to the Agency Business track today!

Do you know someone who could be a great speaker? Or perhaps you know someone who has an interesting story to share? If so, please get in touch with the program team at [email protected].

And don’t forget to help us to spread the word about this awesome conference. Our hashtag is #drupaleurope.

We look forward seeing you in Darmstadt!

Drupal is one of the leading open source technologies empowering digital solutions in the government space around the world.

Drupal Europe 2018 brings over 2,000 creators, innovators, and users of digital technologies from all over Europe and the rest of the world together for three days of intense and inspiring interaction.

Drupal Europe will be held in Darmstadtium in Darmstadt, Germany — with a direct connection to Frankfurt International Airport. Drupal Europe will take place 10–14 September 2018 with Drupal contribution opportunities every day. Keynotes, sessions, workshops and BoFs will be from Tuesday to Thursday.

Jun 25 2018
Jun 25
Drupal Europe

Drupal Europe is both a technology conference and a family reunion for the Drupal community. Bringing together 1600+ attendees, it is the largest community driven Drupal event taking place on the European continent this year. For anyone connected with Drupal this is a unique opportunity to share your experience, learn, discuss, connect and contribute back to the community.

Being a community driven conference, we wanted to focus on real life case studies and not the usual technology driven structure. So we’ve introduced industry tracks which focus on specific industry sectors.

Photo with CCO licence via Pexels.com from StartupStockPhotos

The Higher Education track is for anyone using Drupal or thinking of migrating to Drupal at a college or university who is looking to connect with other Higher-Ed Drupal users.

If you have experience of delivering Drupal solutions in the higher education sector or are looking for inspiration on how you continue to develop your CMS further, this is the right track for you.

Drupal is a popular choice in higher education, and many of us are using it in creative and inventive ways. With Drupal 8, the opportunities for exploration and experimentation expand even further — from headless Drupal to top-tier configuration management. Let’s showcase our successes and best-practices with Drupal 8!

We know many universities are still on Drupal 7 and are keen to migrate to Drupal 8, so come to share what works for you and see wins from your peers.

Photo with CCO licence via Pexels.com from StatusStockphoto

Have you launched a Drupal 8 project recently that you are proud of? Started a campus Drupal users group and have tips for others looking to create their own? Developed a great user support model for your content editors? Conquered decoupled Drupal with your frontend stack? Share your awesome projects and lessons learned with your peers.

  • Education sector
Photo with CCO licence via Pexels.com from Pixbay
  • Drupal in a Day (how Global Training Days got to be a localized event)
  • From CMS to LMS
  • Web accessibility in higher education
  • GDPR and childrens information
  • Javascript for higher education
  • Migration from Drupal 7 to 8
  • How Drupal 8 API-first helps to
    integrate with existing IT-Infrastructure
  • Build your own Drupal Community

Session submission is open and we ask you to submit interesting session proposals to create an awesome conference. Session proposals are not limited to Drupal and all topics in relationship with Higher Education are welcome.

Please also help us to spread the word about this awesome conference. Our hashtag is #drupaleurope.

If you want to participate in the organisation or want to recommend speakers or topics please get in touch at [email protected].

Drupal is one of the leading open source technologies empowering digital solutions around the world.

Drupal Europe 2018 brings over 2,000 creators, innovators, and users of digital technologies from all over Europe and the rest of the world together for three days of intense and inspiring interaction.

Drupal Europe will be held in Darmstadtium in Darmstadt, Germany — with a direct connection to Frankfurt International Airport. Drupal Europe will take place 10–14 September 2018 with Drupal contribution opportunities every day. Keynotes, sessions, workshops and BoFs will be from Tuesday to Thursday.

Jun 22 2018
Jun 22
Drupal Europe

The e-commerce industry continues to grow rapidly year over year, bringing more merchants online and driving larger profits. With that growth comes the increased need for rich content, innovative product merchandising, and integration into an ever increasing number of third party sales, marketing, and fulfillment tools. Drupal has always excelled as a platform for building unique customer experiences, and it continues to come into its own as an adaptive sales platform via projects like Drupal Commerce.

Photo by Mike Petrucci on Unsplash

This track includes content that helps merchants understand how to start and grow their online businesses, demonstrates to developers how to build ambitious e-commerce sites, and incorporates solution providers who improve the whole process via integrations.

In the e-commerce track you will learn how to start to sell online, how to grow your existing business and reach a wider audience, and the best tools to use for developing your platform.

The track is focused on the following topics:

  • Drupal vs other e-commerce solutions: comparison, the cost of entry and scale
  • What competitive advantages does Drupal bring to online merchants?
  • What are the benefits of Drupal-native eCommerce solutions vs. integrating external systems?
  • Case studies for unique or ambitious implementations of Drupal for e-commerce
  • Latest trends in eCommerce (e.g. payment, fulfillment, security, taxes, etc.)
  • Latest trends in building eCommerce websites (e.g. headless, multichannel, AI, etc.)

As you’ve probably read in one of our previous blog posts, industry verticals are a new concept being introduced at Drupal Europe and replace the summits, which typically took place on Monday. At Drupal Europe these industry verticals are integrated with the rest of the conference — same location, same ticket and provide more opportunities to learn and exchange within the industry verticals throughout three days.

Now is the perfect time to buy your ticket for Drupal Europe. Session submission is only open for a few more days so please submit your sessions and if encourage others who have great ideas.

Please help us to spread the word about this awesome conference. Our hashtag is #drupaleurope.

To recommend speakers or topics please get in touch at [email protected].

Drupal is one of the leading open source technologies empowering digital solutions in the government space around the world.

Drupal Europe 2018 brings over 2,000 creators, innovators, and users of digital technologies from all over Europe and the rest of the world together for three days of intense and inspiring interaction.

Drupal Europe will be held in Darmstadtium in Darmstadt, Germany — which has a direct connection to Frankfurt International Airport. Drupal Europe will take place 10–14 September 2018 with Drupal contribution opportunities every day. Keynotes, sessions, workshops and BoFs will be from Tuesday to Thursday.

Jun 21 2018
Jun 21

Drupal is built on PHP so any developer working with Drupal needs some PHP knowledge. PHP memory management is something that can initially be a difficult concept to grasp.

In this Acro Media Tech Talk video, Rob Thornton covers PHP arrays and how they use memory. He goes over various examples, helping to shed some light on how to use arrays effectively. Along the way, Rob discusses passing arrays by value vs. by reference and shares some tips about each.

[embedded content]

If you find this video helpful, you may also be interested in these related topics:

Contact us and learn more about our custom ecommerce solutions

Jun 19 2018
Jun 19

Omnichannel generally means the shopping experience is unified and seamless whether you do it on your laptop, in store, through your phone, etc. The team at Acro Media set out to demonstrate just how easy it is to give your customers a true omnichannel experience using Drupal and Drupal Commerce.

[embedded content]

The omnichannel setup

As part of our demo at DrupalCon in Nashville, we did a pseudo T-shirt pre-order. Before the conference, attendees could use our Urban Hipster eCommerce demo site to pre-order a Drupal Commerce shirt in their size. When they completed their pre-order, they got an order number to bring with them to our booth. 

Check Out Our High Five Drupal Web SeriesPeople who didn't pre-order could also come to our booth and "purchase" (for free) a T-shirt using a self serve kiosk running the same demo site. 

So one side of the booth was the set up as the cashier/fulfillment area. The other side had the self-serve kiosk. We also had other laptops available so that we could bring up the admin interface as if we were a customer support person assisting a customer over the phone. The "support person" could find the customers order number or email address and fulfill the order. Easy peasy.

The whole time, our inventory of shirt sizes was counting down until the stock count hit 0. When our inventory reached 0 for a certain size, orders for that size could no longer be placed.

Why is this so amazing?

Some people were impressed but also a little puzzled, thinking that this sort of setup should just exist everywhere. Which it should, but it doesn't. With most retail stores, the online and in-store experiences are completely separate. They might as well be two different companies. If you buy something online and try to return it in store, it often can't happen. Loyalty points often don't transfer. The list goes on. Some places will let you buy online and pick up in store, but there might be a delay. They might say sure, you can pick it up in store, but not for 24 hours. In that case, you might as well just go to the store and find it yourself. Even knowing if an item is in stock can be tricky. The website might say there are three left, but that's just a snapshot from a certain point in time, and you don't know how often that gets updated. Maybe that was valid six hours ago, but that item has since sold out.

Why Drupal rocks

What makes Drupal so cool is that the point of sale and the Commerce module both use the same orders. A point of sale order is just a Drupal Commerce order. It has some specifics to the point of sale, but it can be loaded up in a regular interface. They use the same stock, the same products, everything. This is surprisingly rare. A lot of POS systems in particular are very antiquated. They date from pre-Internet times and have no concept of syncing up with things.

But we've created a true omnichannel experience. We've done it, and implemented it, and it's all open source and freely available. Anyone else could set up the same omnichannel setup that we did. We used a laptop, a cash drawer, a couple of iPads, nothing too fancy.

What's more, as the software matures, we're working on an even better demo with more smoothed out features, better integration, nicer interface, etc. Stay tuned.

Demo Drupal Commerce today! View our demo site.

More from Acro Media

Let's talk omnichannel!

We're always happy to help you understand how you can deliver a true omnichannel experience for you customers. Contact us today to talk to one of our business development experts.

Contact Us

Jun 14 2018
Jun 14
Empower your customers to customize products.

There is a high likelihood that the tshirt on your back or in your closet started life as someone’s idea that was being uploaded to an online tool. The idea that a person could not only buy tshirts, but design them in a tool and approve the proof before payment seems almost commonplace. Why aren’t more people talking about this? Your customers are expecting more tailored experiences when buying decorated apparel, signage and personalized promotional products from the small to medium web store fronts. Getting the “Web to Print” toolset just right on Drupal is not easy.

Here’s just a few of the expectations for ordering printed materials from the web on Drupal:

  • Drupal integration: Full integration with existing Drupal website
  • Intuitive editor experience: Drag and drop toolset, uploading of files (jpg, png, tiff, pdf, eps, ai, psd), cropping and quick fixes to pictures, lots of fonts, pop-over text formatting, white labelled branding with plenty of customizations, low resolution upload warnings, and mobile friendly web to print tool.
  • Proof and checkout workflow: Print-quality PDF proof, edit before purchase, edit after purchase, CMYK color space, super large files that need processing

Getting off the bespoke product editor island

An example of a bespoke web to print tool Acro Media built with Drupal and jQuery UI.

An example of a bespoke web to print tool Acro Media built with Drupal and jQuery UI.

Like many Drupal agencies, there’s rarely a problem we face that can’t be solved with in-house open source tools. Before we decry the problems, we are very proud of what we accomplished in the past given budget and available tools. With jQuery UI and html-to-pdf experience, we’ve built these kinds of tools before, to varying degrees of success. Every time we tackled a project like web-to-print, the struggle became very real. With minimal hours, the tools we knew and loved created a functional experience that was hard to maintain and very error prone.

Demo Drupal Commerce today! View our demo site.More often than not, we had trouble with converting HTML to PDF reliably enough for high-resolution print-quality, especially with customer supplied imagery and layout. Offering fonts in a customized product builder is challenging to get right, especially when you’re creating a PDF that has to have the font attached. The RGB colorspace doesn’t translate easily to CMYK, the most common four color process for printing. And all of our experience in software revolved around pixels, not these things called picas. In this crazy world resolution could go as high as 3200 dpi on standard printers, dimensions suddenly couldn’t be determined based on pixels.

When one of our clients that had a tool we had built with existing technologies asked for some (not all) of the features mentioned in the beginning of the article, we also wanted to solve all the technical challenges that we grappled with over a year ago. As the planning stage was coming to an end, it was clear the budget wasn’t going to support such a complicated software build.

Product Customization is not the right phrase

Example screenshot of keditor in action.

Example screenshot of keditor in action.

We started to look for product customization tools and found nada. Then we looked for web layout tools which would maybe give Drupal a better HTML editing experience, but found a disappointing lack of online web to print solutions. We did find grapejs, innovastudio, and keditor

But, almost universally, these javascript-based libraries were focused on content and not editing products that would be printed. We needed something that had the goal of creating a printable image or PDF with a tight integration around the editor experience. We had nearly convinced ourselves there wasn’t a vertical for this concept, it seemed like nearly all product builders in the wild were powered by one-off conglomerations of toolsets.

Web to Print using Customer’s Canvas works with Drupal, right?

Finally, via a project manager, an industry phrase was discovered that opened the floodgates: web to print. After a bit of sifting through the sales pitch of all the technologies, almost all tools were found to be cumbersome and hard to integrate in an existing Drupal website, save one. Customer’s Canvas checked all the boxes and then some:

  • SAAS (so we don’t have to host customer’s images, or maintain the technology)
  • White label
  • More than fully featured
  • Completely customizable
  • Iframe-friendly. Meaning we could seamlessly plop the product customization tool into an existing or new layout.

Example of Customers Canvas running in Drupal Commerce.

Example of Customers Canvas running in Drupal Commerce.

To make an even longer story short, we jumped on board with Customer’s Canvas and built the first (to our knowledge) third party web to print Drupal 7 module. We might make a Tech Talk regarding the installation and feature set of the module. Until then, here’s what you can do:

  1. Download and install the module
  2. Provide some API credentials in the form of a javascript link
  3. Turn on the Drupal Commerce integration
  4. Provide some JSON configuration for a product via a field that gets added to your choice of product types.
  5. Click on Add to Cart for a Customer’s Canvas product
  6. Get redirected to a beautiful tool
  7. Click “Finish” and directed to a cart that can redirect you back to edit or download your product.
  8. As a store administrator, you can also edit the product from the order view page.


Drupal 8 and Web to Print and the Future

Currently, the module is built for Drupal 7. Upgrading to Drupal 8 Commerce 2 is definitely on our roadmap and should be a straightforward upgrade. Other things on the roadmap:

  • Better B2B features
    You can imagine a company needs signs for all of it’s franchisee partners and would want the ability to create stores of customizable signage. With Commerce on Drupal 8, that would be pretty straightforward to build.
  • More download options
    Customer’s Canvas supports lower res watermarked downloads for the customers as well as the high res PDF downloads. Currently the module displays the high resolution for all parties.
  • Better administrative interface
    If you’re using Drupal 7, the integration for this module is pretty easy, but the technical experience required for creating the JSON formatting for each product is pretty cumbersome. So it would be awesome (and very possible) to build out the most common customizations in an administration interface so you wouldn’t have to manage the JSON formatting for most situations.
  • Improve the architecture
    Possibly support Customer’s Canvas templates like entities that are referenced so that you could create a dozen or so customizable experiences and then link them up to thousands of products.
  • Webform support
    The base module assumes your experience at least starts with an entity that has fields and gets rendered. We could build a webform integration that would allow the webform to have a customer’s canvas build step. T-shirt design content anyone?

Integration can be a game changer

One of the big reasons we work with Drupal and Drupal Commerce is that anything with an API can be integrated. This opens the doors to allow the platform to do so much more than any other platform out there. If an integration needs to be made, we can do. If you need an integration made, talk to us! We're happy to help.

Contact Acro Media Today!

Contact us and learn more about our custom ecommerce solutions

Jun 14 2018
Jun 14
Official 8.0 Version Now Available

The Drupal Point of Sale provides a point of sale (POS) interface for Drupal Commerce, allowing in-person transactions via cash or card, returns, multiple registers and locations, and EOD reporting. It’s completely integrated with Drupal Commerce and uses the same products, customers, and orders between both systems. You can now bring your Drupal 8 online store and your physical store locations onto the same platform; maintaining a single data point.

The Drupal 7 version has been in the wild for a while now, but today marks the official, production ready release for Drupal 8.

Release Highlights

What features make up the new version of Drupal Point of Sale 8? There are so many that it will probably surprise you!


Omnichannel is not just a buzzword, but a word that describes handling your online and offline stores with one platform, connecting your sales, stock and fulfillment centers in one digital location. Drupal Commerce has multi-store capabilities out of the box that allow you to create unique stores and share whatever product inventory, stock, promotions, and more between them. Drupal Point of Sale gives you the final tool you need to handle in-person transactions in a physical storefront location, all using your single Drupal Commerce platform. That’s pretty powerful stuff. Watch these videos (here and here) to learn more about how Drupal Commerce is true omnichannel.


Set up new registers with ease. Whether you have 1 or 1000 store locations, each store can have as many registers as you want. Because Drupal Point of Sale is a web-based solution, all you need to use a register is a web browser. A touch screen all-in-one computer, a laptop, an iPad; if it has a web browser, it can be your register. The Point of Sale is also fully open source, so there are no licensing fees and costs do not add up as you add more registers.

Customer Display

While a cashier is ringing through products, the Customer Display uses WebSocket technology to display the product, price, and current totals on a screen in real-time so the customer can follow along from the other side of the counter. Your customers can instantly verify everything you’re adding to the cart. All you need for the Customer Display is a web browser, so you can use an iPad, a TV or second monitor to display the information in real-time as the transaction progresses.

Barcode Scanning

Camera based barcode scanning
Don’t have a barcode scanner? No problem. With this release, any browser connected camera can be used to scan barcodes. Use a webcam, use your phone, use an iPad, whatever! If it has a camera, it works. This is helpful when you’re at an event or working a tradeshow and you don’t want to bring your hardware along.

Traditional barcode scanning
A traditional barcode scanner works too. Simply use the barcode scanner to scan the physical product’s barcode. The matching UPC code attached to one of your Drupal Commerce product variations will instantly add the product to your cashier’s display.


Generate and print labels complete with barcodes, directly from your Drupal Point of Sale interface. Labels are template based and can be easily customized to match any printer or label size so you can prep inventory or re-label goods as needed.


Easily customize the header and footer of your receipts using the built in editor. Add your logo and contact information, return/exchange policy, special messaging or promotions, etc.

Drupal Point of Sale cusomized receipts

When issuing receipts, you can choose to print the receipt in a traditional fashion or go paperless and email it to your customer. You can do either, both, or none… whatever you want.


Whether online or in store, all of your orders are captured in Drupal Commerce and so can be returned, with or without the original receipt. A return can be an entire order or an individual product.

End of Day (EOD) Reports

When closing a register, you cashiers can declare their totals for the day. You can quickly see if you’re over or short. When finished, an ongoing daily report is collected that you can look back on. On top of this, Drupal Point of Sale is integrated with the core Drupal Commerce Reporting suite.

Drupal Point of Sale end of day reporting


Use Drupal POS 8 with anything that supports a browser and has an internet connection.

Technical Highlights

Adding to all of the user highlights above are a number of important technical improvements. It’s the underlying architecture that really makes Drupal Point of Sale shine.


Cashiers login to Drupal Point of Sale via a designed login page. Once logged in, the theme used is the default Drupal admin theme. However, like any other part of Drupal, your admin theme can be modified as much as you like. Keep it default or customize it to your brand; it’s yours to do with as you please.

Drupal Point of Sale themable cashier login screen

Search API Enabled

The search API is a powerful search engine that lets you customize exactly what information is searchable. Using the Search API, your cashiers are sure to quickly find any product in your inventory by searching for a product’s title, SKU, UPC code (via barcode scanner), description, etc. Search API is completely customizable, so any additional unique search requirements can be easily added (brand, color, weight, etc.). The search API references the products on your site, and at any other store or multi-warehouse location to allow for you to serve customers in real-time. 

Fully Integrated with Drupal Commerce

The Drupal Point of Sale module seamlessly integrates into the existing Drupal Commerce systems and architecture. It shares products, stock, customers, orders, promotions and more. This makes Drupal Point of Sale plug-and-play while also making sure that the code base is maintainable and can take advantage of future Drupal Commerce features and improvements.

Permissions and Roles

When Drupal Point of Sale is installed, a “cashier” user role is created that limits the access users of this type have with your Drupal Commerce backend. Use Drupal’s fine grained permissions and roles system to manage your cashiers and give different permissions to employees, managers, marketers, owners, IT, etc. Any way you want it.

Custom Hardware

As mentioned above, all you need to use Drupal POS 8 is anything that supports a browser and has an internet connection. This opens the door for all kinds of custom Point of Sale hardware such as branded terminals, self-serve kiosks, tradeshow-ready hardware, and more.

Drupal Point of Sale Raspberry Pi custom hardware

We’ve been having fun prototyping various Raspberry Pi based POS hardware solutions. You can see some of them here and stay tuned for more. Drupal Point of Sale is open source, so why not open up the hardware too?

Drupal Point of Sale 8, Ready for your Drupal Commerce platform

We’re excited to finally release the production ready version of Drupal Point of Sale 8.0. There are many ecommerce-only platforms out there, but almost none of them can ALSO run in your physical store too. This is a BIG DEAL. Drupal Point of Sale gives you the last piece needed to run your entire store using Drupal Commerce allowing for centralized data and a single system for your team to learn and manage.

One admin login, one inventory list, one user list, one marketing platform, ONE. True omnichannel, without the fees.

Next Step

Watch a Demonstration
Mike at Acro Media recorded a quick video to show Drupal Point of Sale in action. He shows the interface, how it's configured, and some of the features.

[embedded content]

Commerce Kickstart
Starting a Drupal Commerce project from scratch? Use Commerce Kickstart to configure your install package (including Drupal Point of Sale).

Install with Composer
Already using Commerce for Drupal 8? Install Drupal Point of Sale with Composer.

$ composer require drupal/commerce_pos

Let Acro Media help
Acro Media is North America’s #1 Drupal Commerce provider. We build enterprise commerce using open source solutions. Unsure if Drupal Commerce and Drupal Point of Sale meet your business requirements? A teammate here at Acro Media would be happy to walk you through a replatforming evaluation exercise and provide you with the Point of Sale workbook to help you make your decision.

Contact Acro Media Today!

More from Acro Media
Jun 07 2018
Jun 07

The situation: I'm the primary maintainer of the Commerce Point of Sale module and have been building a customer facing display feature for the Commerce 2 version. So, I have two separate pages, one is a cashier interface where a cashier enters products, the second is a customer facing screen where the customer can watch what products have been scanned, review pricing, and make sure everything is correct.

The problem: Since products can be scanned through quite quickly, it was imperative that the customer facing display update very quickly. The display needs to match what's happening in near real-time so that there is no lag. Unfortunately, AJAX is just too slow and so I needed a new solution.

The solution: WebSockets seem like a great fit.


AJAX - Too slow!

WebSocket - Fast!

The socket server can either not bootstrap Drupal at all, or bootstrap it only once upon load, making it able to relay traffic very quickly.


I only needed one dependency for this, Ratchet, which is a PHP library for handling WebSockets and is easily installed via Composer.


The WebSocket server is actually very simple, it finds and loads up the autoload script for Drupal, similar to how Drush does it.

We bootstrap Drupal, just so we can load a few config settings.

We terminate the Drupal kernel, since we don’t need it just for ferrying traffic back and forth and it will probably leak memory or something over a long time if we use it a bunch, since Drupal isn’t really meant to run for ages. I did try it with Drupal running the whole time and it did work fine, although this wasn’t under any real load and only for a couple days.

Now all that we have to do is setup the service.

All the details of our service come from the class we pass in, which basically hooks in the different server events. I’ll leave the details of that outside of this article as none of it is Drupal specific and there are lots of tutorials on Rachet’s site: http://socketo.me/docs/hello-world


On the JavaScript end, we connect to the WebSocket using the standard interface.

I used a few mutation observers to monitor for changes and then passed the changes to the WebSocket to relay. You could do this however you want and probably some nicely integrated JS or even a React frontend would be a lot cleaner.


Related module issue: https://www.drupal.org/project/commerce_pos/issues/2950980
Ratchet PHP Library: http://socketo.me/

Contact us and learn more about our custom ecommerce solutions

May 29 2018
May 29

Did you know that Drupal has a Point of Sale (POS) module that pairs with the widely used Commerce module? That's right, Drupal Commerce is now the full end-to-end platform for a complete omnichannel ecommerce experience. Whether you're running an online store, a physical store, or both, you can do it all with Drupal Commerce!

One of the great things about a web-based POS is that all you need is a web browser for it to work. This opens the door to new POS hardware options. You can use an iPad, a laptop, or anything that has a browser. You don't need any expensive or specialized hardware from Moneris, nor do you need a branded solution such as Square. Instead, you now even have the option to build your own POS hardware for very little cost. Today we're featuring a Raspberry Pi based prototype that WE built! The whole setup cost about $250 CAD.

Watch the video below, or keep reading to learn more.

[embedded content]

As mentioned above, we bought a simple touchscreen and mounted a Raspberry Pi on the back. Once up and running, all you have to do is plug it in, connect it to the Internet, and it will automatically boot up into the POS login screen. If your staff has a problem, all they have to do is unplug it and plug it back in. There's no messing with settings or anything. Just reboot. Easy!

Once you get the hardware working, the display can be used in 3 different ways depending on how you need it:Check Out Our High Five Drupal Web Series

  1. The administrative view, which is what the cashier would use.
  2. A customer display view, which shows what the cashier has added so the customer can see the products and prices entered in real-time. Remember: all you need is a browser and something that can display a browser. The customer display is especially easy because it doesn't have to be a touchscreen; you could just use any monitor, a TV, etc, and run it off of the cashier hardware.
  3. A kiosk view, which is basically just running the front end of the site like your customers would do on their home computers. You could set that out in your store and let customers browse products and make purchases.

So, for a shoestring budget, we created a working point of sale that could be used in a store (see the video above). Aside from looking a little silly, our example is perfectly fine and works great. Plus, there are endless options for inexpensive enclosures to make it look better. You could even build or 3D print your own.

The do-it-yourself (DIY) route is a lot cheaper and gives you the freedom to do whatever you want. We will post further details soon on how to do all this yourself, including specific links to the components we used. And remember: it's Drupal, so it's open source, and all the software is free.

Integrated Drupal Ecommerce Solutions

May 24 2018
May 24

In this video, Josh Miller shows you how to install Drupal Commerce 2 using a local development tool called Lando. Further instructions are included below the video.

[embedded content]


  1. Commerce Kickstart download: 0:51
  2. “composer install” command: 8:00
  3. “lando init” command: 12:56
  4. “lando start” command: 15:06
  5. “Drupal install” screen: 17:04
  6. “lando stop” command: 21:18


  1. Download and install Composer
  2. Download and install Lando

Code generated during this video:


Installing Drupal Commerce 2 locally using Commerce Kickstart, Composer, and Lando

Getting Drupal up and running on your computer is an important first step as an evaluator. Good news is that there’s a lot of tech that makes this easier than ever before. We’re going to walk you through how to install Commerce 2 using the Kickstart resource, Composer, and Lando. 

  1. Download and install Composer
  2. Download and install Lando
  3. Next go to Commerce Kickstart to create and download your customized composer.json file

    Visit Commerce Kickstart

     Drupal Commerce Kickstart

  4. Run ‘composer install’

    Composer install command

  5. Run ‘lando init’

    Lando init command

  6. Run ‘lando start’

    Lando start command

  7. Visit your local URL and install Drupal

    Lando - 5 CommerceKickstart-ChooseLanguage

  8. Start building!

    Lando - 6 Congratulations

What is Drupal Commerce

Drupal Commerce is an ecommerce focused subset of tools and community based on the open source content management system called Drupal. Drupal Commerce gives you the ability to sell just about anything to anyone using a myriad of open source technologies and leveraging hundreds of Drupal modules built to make that thing you need do that thing you want.

We use Commerce Kickstart to get things started.

Try the Commerce Kickstart 2.x Installer

What is Composer

Composer is the PHP dependency manager that can not only build and bring in Drupal, Drupal Commerce, and Symfony, but is the technology behind the newest Drupal Commerce Kickstart distribution. We leverage the composer.json file that commercekickstart.com gives us to bring in all of the Drupal code necessary to run a Drupal Commerce website.

To get started, we run “composer install” and that command brings in all the requirements for our project.

What is Docker

Docker is a virtualization software that brings together App services like Apache, Nginx, MySQL, Solr, Memcache, and many other technologies so that it can run on your own computer. This installation video uses a tool that runs on top of Docker in an abstract, and frankly easier, way.

If you want to learn more about Docker and the many different types of tools that run on top of it, we recommend John Kennedy’s 2018 Drupalcon presentation about Docker.

Another great resource that compares using Docker tools is Michael Anello’s take on the various technologies.

What is Lando

Lando is a thin abstraction layer of tools on top of Docker that makes creating an environment as easy as “lando init” followed by “lando start.” Lando keeps the often confusing devops work of creating a local virtual environment to a few very well documented variable settings that it turns into full docker-compose scripts that Docker, in turn, uses to create a local environment where everything just works together. We’re very excited to see how Lando and Drupal Commerce start to work together.

Contact us and learn more about our custom ecommerce solutions

May 01 2018
May 01

A lot of universities use Drupal in some capacity. Universities don't typically have just one site; they're made up of a ton of different pieces put together for course registrations and calendars and events and alumni and so on. So a couple of those pieces might use Drupal. Or one or two departments might use Drupal even if others do not.

Many educational institutions like Drupal because it's open source. Universities are often publicly funded and favor open stuff more than proprietary products. Plus, they need to manage a ton of content by a ton of different people, so they need a really big robust CMS.

[embedded content]

Introducing OpenEDU 3.0

The new OpenEDU 3.0 is a Drupal distribution setup for educational institutions. The older version was mostly a set of custom configurations, whereas 3.0 actually has unique functionality. It has analytics and monitoring built right into it, for instance. There's a new analytics dashboard that allows a central admin to see what's going on in all the different sections without having to check a while bunch of different accounts, which is pretty cool. There's also new functionality related to content management, workflows and editing flows that universities need to handle.

OpenEDU is also being integrated into the Commerce (keep an eye out at commercekickstart.com), so you can have both of them together.

The Commerce Disconnect

Strangely, a ton of universities are using Drupal, but they are not using Commerce. Even those they use Drupal and perform ecommerce are typically using pretty terrible antiquated systems, if they have a system at all.

Check Out Our High Five Drupal Web SeriesLack of awareness is a big factor in this. A lot of universities are so focused on the publishing end that they don't even think about commerce. Another stumbling block is security—they don't want to deal with the compliance issues around online payments, so they just keep doing what they're doing (i.e. accepting cash or taking credit card details over the phone, which is even less secure).

The reality is that businesses or organizations within a university could really benefit from using Commerce, particularly if they already use Drupal. They could just tack on a bit of Commerce and easily sell club memberships and accept donations (remember: Commerce has a built-in point of sale). There could be one central system that IT could maintain and keep secure, and everyone could still spin up their own customized version of it.

TL:DR - Educational institutions already use Drupal and so should really adopt Drupal Commerce to replace their old, antiquated payment systems.

More from Acro Media

Chat with us

Our team understands that one-size does not fit all, especially in the education space, so we listen and work together to bring your students and staff the most secure and integrated open source solution available in the Commerce arena. Contact us today to discuss how Drupal Commerce can fit it with your existing systems.

Contact Acro Media Today!

Apr 06 2018
Apr 06

A straightforward mission doesn’t always mean there’s a simple path. When we kicked off the Mass.gov redesign, we knew what we wanted to create: A site where users could find what they needed without having to know which agency or bureaucratic process to navigate. At DrupalCon Baltimore in 2017, we shared our experience with the first nine months of the project building a pilot website with Drupal 8, getting our feet wet with human-centered (AKA “constituent-centric”) design, and beginning to transform the Mass.gov into a data-driven product.


Interested in a career in civic tech? Find job openings at Digital Services.
Follow us on Twitter | Collaborate with us on GitHub | Visit our site

DrupalCon 2017 Presentation: Making Mass.gov data driven and constituent centric was originally published in Massachusetts Digital Service on Medium, where people are continuing the conversation by highlighting and responding to this story.

Feb 01 2018
Feb 01

As a digital agency we need to have a good content management solution for our clients. Even in situations where we are developing more custom apps than content web applications, we still need a good, modular CMS solution. As Symfony developers, we wanted to find powerful CMS solutions built on Symfony. We wanted to use our Symfony knowledge for building custom things on our chosen CMS solution. In this article, I will show you what we learned and how you can build things using Symfony inside Drupal.

This article was originally published in the February 2018 issue of php[architect] magazine. To read the complete article please subscribe or purchase the complete issue.

Dec 13 2017
Dec 13

Today, I came across an interesting bug with composer-patches plugin (it really is a git-apply bug/behavior). TL;DR, it is fixed in the latest version of composer-patches plugin as of this writing – 1.6.4. All you have to do is run composer require cweagans/composer-patches:^1.6.4 to get the fix.

The problem is simple: Patches are not applied even though you might see it in the composer log output. In fact, even a PATCHES.txt file is generated with the correct listing of patches. There are no errors and no indication that the patching failed for any reason. The problem is because git apply fails silently. It does not give any output nor sets an error exit code.

The problem was first documented and fixed in cweagans/composer-patches#165; however, the fix used there relies on git-apply outputting log messages saying patches were skipped. Unfortunately, that behavior was only introduced in 2.9.0. The Docker image I was using only contained version 2.1.4. All I needed was either these “Skipped patch” messages or an exit code and the plugin would fall back on patch command, but neither happened.

Also, this is actually supposed to be git behavior: git apply will fail to do anything when used within a local checkout of a git repository (other than the one for the project the patch is made for), such as if you are patching a module that is within a site that is in Git version control. The suggestion is to use patch -p1 < path/file.patch instead. While an obvious solution was to upgrade git, a simple Google search told me that this problem has happened to others and I decided to dig deeper. All variations of git apply simply didn't work, nor would set the exit code. I followed the debugging steps in one of the comments in the pull request, but I still wouldn’t see any messages similar to “Skipped patch” or applying patch, or any error.

In testing, I found a way to get it working with the ‘--directory‘ parameter to ‘git apply‘. The command executed by the plugin is similar to the following:

git -C package-directory apply -p1 /tmp/patchfile.patch

Instead, this command worked (even if there is no git repository involved at all, even in the current directory):

git apply --directory=package-directory apply -p1 /tmp/patchfile.patch

On digging a bit more, I found another workaround that worked for me in cweagans/composer-patches#175. What’s more, this was already merged and tagged. All I had to do was update to the latest version 1.6.4 and it would work. This change checks for the path being patched and if it actually is a git repository. If the package is not a git repository, it does not attempt a git apply at all, but falls back to patch, which works perfectly.

Nov 27 2017
Nov 27

This month’s Drupal meetup in Bangalore was held this weekend, on 25th November. Specbee Consulting office kindly provided us with a venue for the meetup and helped organise the event. This meetup happened after a period of six months, and the motive was to get the meetup activities started again in Bangalore.

We are hoping to make organising the meetups and all related activities much more easy and sustainable for everyone involved in Bangalore. A full discussion of these initiatives should probably happen elsewhere, but by starting off with this meetup this month, we hope to keep this running in a more stable manner with minimal load on the organisers. If you’d like to stay updated on when meetups are announced, join us on Meetup.

The day started at around 10:30 AM with a round of introduction of everyone present. The first presentation of the day was by Parvateesam Konapala and Rakesh James who introduced Symfony Components to everyone. They introduced various components used in Drupal such as Event Dispatcher, Dependency Injection, Serializer, etc… The discussion ended with a talk of tools like Drupal Console.

This was followed by a small break with refreshments provided by Specbee Consulting.

After the break, Taher Jodhpurwala spoke about quickly starting with Drupal development. In the presentation, Taher shared ways in getting started with Drupal development with minimum effort using tools like composer, Lando, etc…

Lastly, I discussed a bit of how we use CI systems like Gitlab and Circle CI to manage, test, and deploy our Drupal websites. The meetup ended at 1:30 PM with a group photo.

I’d like to thank Specbee Consulting for providing a venue for the meetup and all the support and, of course, refreshments. Photos from the meetup are below.

Drupal Meetup Bangalore - Nov 2017

Sep 08 2017
Sep 08

Vagrant 2.0 has just been released. Being who I am, I jumped at trying this out and testing if it worked well with my vagrant setup. I have entirely moved to DrupalVM since some time and wanted to see if it worked properly.

TL;DR, the DrupalVM part works fine, but there are other issues. I read the changelog to see what would break and the breaking change section only lists one change related to Ansible. Since DrupalVM is built with Ansible, I was not very sure but gave it a try anyway. Anyway, considering that it was only one breaking change, I thought the problems, if any, should be easily fixable.

I downloaded the MacOS build of Vagrant 2 and installed it normally. I confirmed the update by running vagrant version.

~/w/test > vagrant version Installed Version: 2.0.0
Latest Version: 2.0.0

You're running an up-to-date version of Vagrant!

Okay, all good. I tried running vagrant plugin update and the problem appeared.

~/w/test > vagrant plugin update \Updating installed plugins...
Bundler, the underlying system Vagrant uses to install plugins,
reported an error. The error is shown below. These errors are usually
caused by misconfigured plugin installations or transient network
issues. The error from Bundler is:

conflicting dependencies rb-fsevent (= 0.9.8) and rb-fsevent (= 0.10.2)
Activated rb-fsevent-0.10.2
which does not match conflicting dependency (= 0.9.8)

Conflicting dependency chains:
rb-fsevent (= 0.10.2), 0.10.2 activated

rb-fsevent (= 0.9.8)

Gems matching rb-fsevent (= 0.9.8):

I was a little concerned but ignored this for the time. I tried a vagrant plugin repair and it said it was successful, but didn’t fix the problem. Anyway, moving on…


I tried DrupalVM next. I used my setup-drupalvm script to quickly setup DrupalVM. I just had to change default.config.yml to remove vagrant-hostsupdater from automatic installation. (I prefer vagrant-hostmanager). Running vagrant up, and it all went through smoothly. There was only one change from the usual output I see on a fresh vagrant up (which is related to the breaking change I mentioned above):

==> test: Checking for guest additions in VM...
==> test: Setting hostname...
==> test: Configuring and enabling network interfaces...
==> test: Exporting NFS shared folders...
==> test: Preparing to edit /etc/exports. Administrator privileges will be required...
==> test: Mounting NFS shared folders...
==> test: Configuring cache buckets...
==> test: [vagrant-hostmanager:guests] Updating hosts file on active guest virtual machines...
==> test: [vagrant-hostmanager:host] Updating hosts file on your workstation (password may be required)...
==> test: Running provisioner: ansible...
Vagrant has automatically selected the compatibility mode '2.0'
according to the Ansible version installed (

Alternatively, the compatibility mode can be specified in your Vagrantfile:

After the machine was provisioned (which happened smoothly), I opened up dashboard.test.dev in my browser and everything looked perfect.

Drupal VM dashboard

I cross-checked it with phpinfo() output and verified PHP 7.1.9 was installed and as FPM.

The only problem which now remains is to do with plugins and I am going to ignore it for now. Future versions of Vagrant might fix the version constraint that causes the problem. For now, the plugins themselves are working.

vagrant-auto_network (1.0.2)
- Version Constraint: > 0
vagrant-bindfs (1.0.8)
- Version Constraint: > 0
vagrant-cachier (1.2.1)
- Version Constraint: > 0
vagrant-hostmanager (1.8.7)
- Version Constraint: > 0
vagrant-share (1.1.9, system)
- Version Constraint: > 0
vagrant-vbguest (0.14.2)
- Version Constraint: > 0

Please let me know in comments if there is a way I can fix the version constraint problem. I hope you found the post useful.

Aug 15 2017
Aug 15

So you just finished building an awesome new website on Drupal, but now you’ve run into a new dilemma. How do optimize the site for search engines? Search engine optimization, or SEO, can be overwhelming, but don’t let that cause you to ignore certain things you can do to help drive traffic to your website. There’s nothing worse than spending countless hours to develop a web application, only to find out that users aren’t able to find your site. This can be extremely frustrating, as well as devastating if your company or business heavily relies on organic traffic.

Now there are countless philosophies of SEO, many of which are well-educated assumptions of what Google is looking for. The reality is that no one knows exactly how Google’s algorithm is calculated, and it doesn’t help when their algorithm is constantly being updated. Luckily, there are a few best practices that are accepted across the board, most of which have been confirmed by Google as being a contributing factor to search engine ranking. This blog is going to focus on a few of those best practices and which modules we have found to be helpful in both our Drupal 7 and Drupal 8 projects.

So, without further ado, here is our list of Drupal modules you should consider using on your site to help improve your SEO:

XML Sitemap Module

As the name suggests, XML Sitemap allows you to effortlessly generate a sitemap for your website. A sitemap allows for Google and other search engines like Bing and Yahoo, to be able to easily find and crawl pages on your site. Is a sitemap necessary? No. But if it helps the pages of your site to become easily discoverable, then why not reduce the risk of not having pages of your site indexed? This is especially important if you have a large site with thousands or even hundreds of pages. Having a sitemap also provides search engines with some valuable information, such as how often the page is updated and the level of significance compared to other pages on your site.

XML Sitemap allows you to generate a sitemap with a click of a button, and best of all you can configure it to periodically generate a new sitemap which will add any new pages you’ve published on your Drupal site. Once your website has a sitemap, it is recommended to submit that sitemap on Google Search Console, and if you haven’t claimed your website on Google Search Console yet, I would highly advise doing so as it will provide you with helpful insight such as indexing information, critical issues, and more.

Metatag Module

The next Drupal module is one that can really help boost your search engine ranking and visibility. Metatag is a powerful module that gives you the ability to update a large number of various meta tags on your site. A meta tag is an HTML tag which contains valuable information that search engines use to determine the relevance of a page when determining search ranking. The more information available to search engines such as Google, the better your chances will be that your pages will rank well. The Metatag module allows you to easily update some of the more popular tags, such as meta description, meta content type, title tag, viewport, and more.

Adding and/or updating your meta tags is the first step of best SEO practice. I’ve come across many sites who pay little to no attention to their meta tags. Luckily, the Metatag module for Drupal can help you easily boost your SEO, and even if you don’t have time to go through and update your meta tags manually (which is recommend), the module also has a feature to have your tags automatically generated.

Real-Time SEO for Drupal Module

The Real-Time SEO for Drupal module is a powerful tool on its own, but it is even better when paired with the Metatag module which we just finished discussing. This module takes into account many SEO best practices and gives you a real-time analysis, ensuring that your content is best optimized for search engines. It will inform you if your content is too short, how readable your posts are, and also provides you a snapshot of how your page will appear in Google. The other helpful information it provides is regarding missing or potentially weak tags, which is why I mentioned that this module and the Metatag module work extremely well together. Real-Time SEO for Drupal can let you know how to better improve your meta tags and by using the Metatags module, you can quickly update your tags and watch in real-time how the changes affect your SEO.

The Real-Time SEO for Drupal module is a simple, yet incredibly useful tool in helping you see the SEO health of your pages. If you are just getting into SEO, this is a great place to start, and even if you’re a seasoned pro this is a nice tool to have to remind you of any meta tags or keyword optimization opportunities you may be missing.

Google Analytics Module

The final module is the Google Analytics module. Google Analytics is by far the most widely used analytics platform. The invaluable information it provides, the numerous tools available, and the integrations it allows, make it a requirement for anyone looking to improve the SEO of their Drupal website. This Drupal module is extremely convenient, as it does not require a developer to have to mess with any of the site's code. After installing the module all you have to do is enter the web property ID that is provided to you after you setup your account on Google Analytics.
From the Google Analytics module UI, you have a number of helpful options, such as what domains to track, which pages to exclude, adjusting page roles, tracking clicks and downloads, and more. The Google Analytics module for Drupal is another great tool to add to your tool belt when trying to best improve your SEO.

Final Thoughts

This list of helpful SEO modules for your Drupal 7 or 8 site could easily have been much longer, but these are a few key modules to help you get started. SEO is something that should not be ignored, as I mentioned in the beginning of the blog, it’s a shame to build a site only to find that no one is actually visiting it, but using these modules properly can definitely help prevent this issue. if you would like to learn of other great modules to help your SEO, please leave a comment below and I'll write a follow-up blog.

Aug 02 2017
Aug 02

When migrating from Drupal 7 to Drupal 8, it is important to remember to migrate over the redirects as well. Without the migrations users will not find your content if for example: the redirect was shared on social media. Using the Migrate Plus module, it is quite simple to write a migration for the redirects. The Migrate Plus module contains some good examples on how to get started writing your custom migrations.

Write your node migrations

I am going to assume that you have written migration for some content types and have the group already written. Once those migrations have been written, in your database should now be a migrate_map_{name}_{type} table. This is where we will be able to find the imported node's new id which will be necessary for importing the redirects.

Write the yml file for redirect migrations

For example, let's say we have a module called blog_migrations. In that module we have a group for blog and a migration for a news and opinion content type. Inside the config/install directory add a new yml file called migrate_plus.migration.blog_redirect.yml where blog is the name of the group being migrated. This file will give an id, label, and the process to use for the migration.

id: blog_redirect
label: Path Redirect
migration_group: blog
  - Drupal 7
  plugin: blog_redirect
  key: blog
  rid: rid
  uid: uid
  redirect_source/path: source
    plugin: d7_redirect_source_query
    source: source_options
    plugin: d7_path_redirect
      - redirect
      - redirect_options
    plugin: default_value
    source: language
    default_value: und
  status_code: status_code
  plugin: entity:redirect

Write the migrate source

Create the file BlogRedirect.php in the module's src/Plugin/migrate/source folder.

namespace Drupal\apa_migrate\Plugin\migrate\source;

use Drupal\Core\Database\Database;
use Drupal\migrate\Row;
use Drupal\redirect\Plugin\migrate\source\d7\PathRedirect;

class BlogRedirect extends PathRedirect {

  public function query() {
    $query = $this->select('redirect', 'p')->fields('p')
      ->condition('redirect', '%user%', 'NOT LIKE');

    return $query;

  public function prepareRow(Row $row) {
    $current_status_code = $row->getSourceProperty('status_code');
    $status_code = $current_status_code != 0 ? $current_status_code : 301;
    $row->setSourceProperty('status_code', $status_code);

    $current_redirect = $row->getSourceProperty('redirect');
    $explode_current_redirect = explode("/", $current_redirect);

    $map_blog_array = array(
    if ($explode_current_redirect[0] == 'node') {
      $resource_type = $this->getDatabase()
        ->select('node', 'n')
        ->fields('n', ['type'])
        ->condition('nid', $explode_current_redirect[1])

      if (in_array($resource_type, $map_apa_array)) {
        $new_node_id = Database::getConnection('default', 'default')
          ->select('migrate_map_apa_' . $resource_type, 'm')
          ->fields('m', ['destid1'])
          ->condition('sourceid1', $explode_current_redirect[1])

        $new_redirect = 'node/' . $new_node_id;
        $row->setSourceProperty('redirect', $new_redirect);

Run the migrations

Using the config_devel module, now import the configuration into active store to be able to run the migration using:

drush cdi1 /modules/custom/blog_migration/config/install/migrate_plus.migration.blog_redirect.yml

Then run the actual migration:

drush mi blog_redirect

After running that you should now have migrated the two content type's redirects with the new node id they were given! Any questions, let us know in the comments below.

Aug 01 2017
Aug 01

We were thrilled to conduct a training at this years Drupal Gov Con on local Drupal development with containers. Check out our presentation!

The post Local Drupal Development With Containers appeared first on .

Jul 31 2017
Jul 31

One of our lead developers spoke at this year’s Drupal Gov Con at NIH. Check out his presentation on Taxonomy Terms as Organic Groups.

The post Taxonomy Terms as Organic Groups appeared first on .

Jul 18 2017
Jul 18

One of my pet peeves is searching for a local event and finding details for that event… 3+ years ago.

Many Drupal sites feature some sort of event type node. It’s really anything with a start date, and likely, an end date. The problem is, most developers don’t take into account whether or not that content should live on once the end date has come and gone.

Perhaps, in some instances, keeping that content on your site makes sense. In most cases though, it does not.

For instance, my 3 year old was really into dinosaurs. I knew there was a dinosaur exhibit coming to town, but I didn’t quite remember the name. Searching online provided quite a few local results. And many of those results were for events in the past.

Discover the Dinosaurs (06/21/2014)
(event has since been unpublished!)

(event has since been unpublished!)

Dino Dig! (06/02/2015)

Event from 2 Years Ago

Discover the Dinosaurs Unleashed (02/18/????)

Sometimes sites will even have past events ranking higher in search results than upcoming events.

There’s a whole other blog post I could write about how useful it is to have the year accompanying the day and month on web content — particularly tech blog posts. Was this written in February of this year or 2006? How can I know?!?

For Drupal sites, there’s a relatively easy fix. It requires a small custom module and the contributed Scheduler module.

The Scheduler module is simple and great. Simply enable it for your content type, and enable, at the very least, the unpublish setting. Once that is set up, create a custom module and invoke the hook_entity_presave() function.

This code is pretty self explanatory. All I’m doing is checking to be sure it’s an event node type that’s being saved, and if so, find the start and end date values to be used when setting the “unpublish_on” field.

You’ll of course have to make sure your node type and field names match up.

Once that’s set up, any time an event is saved, your node is scheduled to unpublish one day after the end date.

If you have a Drupal 7 site, this same idea can be applied. The code in the hook_entity_presave() will be a bit different.

I wish I could start a massive movement to help clean up web content that should have been unpublished or removed long ago. Until then, hopefully this article finds a few devs so that they can ensure their site isn’t one of those sending out poor results.

Jun 15 2017
Jun 15

Tom Friedhof: There’s a lot of hype around integrating Pattern Lab with your Drupal theme these days. Particularly because Drupal’s template engine is now using twig, which is one of the template engines Pattern Lab uses.  The holy grail of having a living style guide and component library is now a lot more feasible! But what about Drupal 7 sites? Twig doesn't exist in Drupal 7.  Today I’m going to show you something we’re working on at active lamp to implement Pattern Lab templates in Drupal 7.

Hey guys, I’m Tom Friedhof, a solutions architect here at ActiveLAMP.  Let me first start off by defining what I mean when I a say living style and component library.  This idea can mean different things to different people. A living style guide and component library is the HTML, CSS, and Javascript that document the design of a user interface. The “living” part of that means that the style guide should be constantly in sync with the actual app that implements the interface as the design improves or changes.

How do you easily keep the real app and the style guide in constant sync?  That could be a lot of work, given that once the initial designs are done, the design iterations are typically done directly in the app being built, making the style guide obsolete and outdated.

That’s where the promise of Pattern Lab integration with Drupal comes in.  You can easily keep the style guide in sync, if your app depends on the style guide for all of it’s HTML, CSS, and Javascript. That’s why there is so much hype around building “Pattern Lab” themes in Drupal 8 right now. Drupal 8’s theme engine is a theme engine that Pattern Lab uses, and reusing the same twig templates that your UX Designer created in Pattern Lab within Drupal, is now an option.

Well, we’re still working on Drupal 7 sites, so how do we benefit from this approach in Drupal 7? To be honest, we’re still hashing out our approach to do this in Drupal 7.  We have the process built out enough, and we're using it on a new theme we developing for a client, but we’re still constantly iterating on the process, and improving it as we run into things.

What I want to show you guys today is the direction that we’re going, and I’m hoping to get your feedback in the comments so that we can continually improve and iterate on this system. First off, we decided not to use the twig version of Pattern Lab.  We spent half a day trying to get twig working in Drupal 7 with the twig for drupal 7 module, and realized we’d be going down a pretty deep rabbit hole just to make twig work in D7.

Rather than fight Drupal 7 and twig, we decided to use a much simpler template engine called Mustache.  Mustache is a language agnostic template engine, and there is a really nice PHP implementation of it. With that said, we installed the gulp version of Pattern Lab, which uses Mustache templates in JavaScript.  We now have the ability to share templates.

I’m going to jump into a demo here in a second. However, I’m not going to do a deep dive of how Pattern Lab works or how Drupal and Panels work.  I’ll dive deeper in future videos with those details if you’re interested.  Leave a comment if you want to see that stuff and we’ll put it on our list of content to share. I’m going to give you guys a 10,000 foot view of how things are shaping up with our Drupal 7 integration to Pattern Lab process.

All right, so here we are in our Drupal seven install. This is pretty much a vanilla Drupal installation. If I jump over to the Drupal directory, you can see here within my sites all modules file or directory, here are all the modules that I need for this actual demo that I'm gonna use for today. We like to use panels and panels everywhere, so what I'm going be demoing today is with panels and panels everywhere, but the stuff that I'm gonna show does apply to just regular template files if you don't want to use panels and just want to stick with the core TPL system within Drupal. One of the the other things that we have in here is a theme called Hills, this is where all the magic actually happens. One thing that you'll notice in this Hills theme is we have two directories called node modules and vendor. We're actually pulling in dependencies from NPM and from Composer or from Packagist into this theme. If we open up our package.jason, which actually defines the NPM dependencies, you can see that we're defining a dependency called hills-patternlab. This is basically the repo that holds our Pattern Lab instants, it's the living style guide that the UX designer uses to actually update the patterns, update CSS and make any changes that need to be changed in the UI.

The composer.json file is requiring the Mustache PHP implementation. We're using this library for obvious reasons to render the Mustache templates that we're pulling in from Pattern Lab. This theme needs to be instantiated with an NPM install and a composer install to get these dependencies and once you've done that, then you're ready to start working on the theme.

One other thing I want to do before I actually start building this our in Drupal is I want to show you the Pattern Lab instants. I am in our Hills directory, I can run NPM start and this should pull up our Pattern Lab instants. Here is our Pattern Lab instants, not going to go into the details of what Pattern Lab is, but essentially it's all the components that make up a website. For example, you can see a page header looks like that, if we wanted to see what the header looks like, it looks like this and all of these templates are basically Mustache templates within our Patterns Lab. Let me open up node modules so you can actually see these templates real quick. The Pattern Lab directory structure looks like this, within the source directory inside of Patterns we can go into organisms and actually look at what a header looks like within Pattern Lab. This is including a couple other patterns from within Pattern Lab so let's see what the navigation actually looks like by going in here. This is the HTML that make up a navigation.

This template includes other patterns within Pattern Lab, let me drill down to the primary links pattern. Here's what our primary links look like, you can see that this is outputting variables, for example, href and name here and then it's including yet another pattern within Pattern Lab, let me open that one as well. Here you can see that it's outputting more variables, classes and name. These variables are actually defined within Pattern Lab's data directory. I'm not going go into detail how that works, but let me just show you what that ends up rendering. You can see here's our organism header, that primary links pattern is this here. This is basically rendering data from Pattern Lab's data directory. If I go into the data directory just real quick, within the primary-links.json file you can see this is the actual data that it's pulling in. If we wanted to say staff services, this is going to rebuild and we see staff services here. That's essentially how Pattern Lab works with data in a nutshell. What I'm gonna show you guys is how we actually integrate this with Drupal. Eventually what's going to happen is these Mustache templates are going to render variables from Drupal and not the data specified in this Pattern Lab data directory.

Let's jump over to Drupal. Here's our Drupal installation. First thing that I'm going do is I'm going to switch the theme to our Hills theme, our Hail to the Hills theme, so I'm gonna enable this and set this as the default. Now I'm gonna open up the homepage in a new tab and drag that over here. So now we can see here's what we get out of the box with this Hail to the Hills theme, there's really nothing in this theme yet. There is stuff in the theme, but I'll get to that in a second, but this is what you'll get once you enable it initially. We're using Panels Everywhere with this theme so what I'm going to do is I'm going to go configure Panels Everywhere. Panels Everywhere gives you a site template by default, so I'm gonna come over here and edit it and I'm gonna add a new variant. We'll just call this default and I'm going come over here and choose a layout within the PL Templates layout category and I'm going to hit full width one column, continue and then we'll just work through this UI. Then I'm going to give it basically the basic content that we need to render so that you can actually see something on the page when you visit a page within the site. We'll create the variant here, we'll update and save that so now let's look to see what our homepage looks like.

Our homepage is starting to look a little bit better, we're basically hitting the default homepage for Drupal, which just shows the title and then a no front page content has been created yet. You noticed here in this layout tab, we had this category called PL Templates and it's pulling in full width and main content. Let me show you where these are defined, if I jump back into our theme, within our theme info hook or info file, panels allows you to specify a directory where you're going to define your layout plugins. The way you specify that is by using the string plugins panel layouts and then you just give it a path to your directory. Let me close node modules so this is a little bit easier to see. If I come into this layouts directory, you can see that I have two layouts specified here, we used the full width layout so I'm gonna jump into that first.

This isn't a tutorial on how to create plugins, but essentially what we're doing is we're just creating a ctools layout plugin. We've called this the full width one column, we've said the theme function or implementation for this is panels full width and you can see that we have a panels full width template here. So when this layout is used, it's going to actually use this template. If we just into that, all this template is doing is it's printing out whatever is in the content area. This has nothing to do with Patten Lab yet, but this is how you essentially setup a default template with regions within panels everywhere. Let's jump back to Drupal and go into our content area. You remember we have the default template set up now, but now let's start to pull in some of the patterns from Pattern Lab. If I come over here, this pattern here called organisms header, let's pull that into Drupal first. I'm gonna come in here and add content, I have this PL components over here and we have a pattern called header. I'm gonna click on that and this header is asking for four pieces of data, so I'm gonna give it the data that it needs. I'm gonna browse for a file, let's look at that, that looks good, we'll upload this, go to next, we'll say logo, logo. Then we'll give it a path and we'll tell it what main menu to use, we'll just say use this as the main menu and with help menu we'll just tell it to use the user menu for now, and then finish. Let's drag this up to the top, hit update and save and let's go see what happened. Let's go back to our homepage and voila, we've got a header pulled from Pattern Lab in here. You'll notice that the menu is not the same menu that's coming from Pattern Lab and why is that? It's because it's pulling the actual primary links from Drupal.

If we go into the menu system here, we chose the main menu to use, the main menu here and if we create another link here we can say another link, have this go to front. Then we come back to our homepage, you can see that this is actually pulling from Drupal. We have no sub-menus underneath that and that's why it's not showing anything underneath it. But you can see that we're actually using our own data from within Drupal.

How did we actually pull in this whole header section from Pattern Lab? Let's go back to where we actually pulled that in. I'm gonna go to structure, pages, back into our site template. We had this content type that we pulled in, this PL header content type. This is a ctools content type and you can define those with a content type plugin. Because this plugin only exists within this theme, we defined the ctools content type within this theme. The way we did that is within our info file, we're specifying where content types for ctools should live and we're saying those content types should live in the Pattern Lab directory, which is right here. This behavior isn't default behavior in ctools, so we did have to patch the ctools modules so that we could do this. You can check out that patch here and leave any comments if you have any comments or suggestions regarding that patch. It's a very small patch, but it basically allows us to define content types, ctools content types, within our theme and not have to define a module just for these content types.

Let's look inside of this Pattern Lab directory and see what we have. The way ctools plugins work is it'll traverse the directory that you've defined and look for any .inc files and read those in while it's processing plugins. Within the organisms header directory, I have a file called organismsheaderinc. The content type system within ctools will pick up this file and it'll read this variable that defines the actual plug in. You can specify other functions to be able to expose, for example, an edit form. Here you see we have a submit handler for that edit form. But here's where all the magic happens, here's a function that we've defined called preprocess. This is where we actually map the Drupal data into data that Pattern Lab understands and we pass this data to Mustache to actually render the pattern. Let me back up and show you what Pattern Lab is expecting to see within this content type. I'm going to open up a new PHP storm directory so that I don't have to continue to scroll up. Go into sites, all, themes, hills, node_modules, hills-patternlab, open a new window, yes. Here is the Hills Pattern Lab directory within that theme, that Hills theme. What I'm going to do is go into the pattern that we're actually pulling in and so it is here. This pattern is pulling in data from this data file and the hints that you can get from this data file is basically looking at what these patterns rely on. We're not producing or we're not outputting any data here, so what we need to do is we need to drill down to see where data actually is being output. If we go into navigation, we can see navigation still isn't outputting any data, it's still just including other atoms and molecules.

So let's jump into the primary links. Now primary links is starting to actually output data. We have that href file there or href variable, we have a name variable there. But then you can also see that it's pulling in yet another include. But this is where we want to start. We're using data here in this primary links component, within Pattern Lab you can specify data in this data directory. We have this primary links Jason file and we basically specified an object with a primary links key. Now Pattern Lab's going read into all these data files and essentially merge the objects so that you can reference them by whatever key is at the root of that object, they all get merged into the data json file. If we look here, primary links is being looped through and then the href and the name is being rendered out. If we look at this, primary links is an array with name and href. If we collapse these guys, you can see we have name, we have several links here. Staff services, work tools, news, administrative units, contact us, that all coincides with our pattern over here.

Within that pattern, coming back over here to primary links, you can see that it's including molecules drop down and that is here. That's also rendering or looping through the links and using the classes variable and the name variables. So if I come back into that data file and open one of these guys up, you can see here's the links array and there's the name and href that is being used. It looks like we're only specifying classes on this very last button here. If we come back here, you'll see that that class is actually specified there and that's what makes that look a little bit different.

Essentially what we're doing in Drupal is we're just mapping data to the data that Pattern Lab expects. Let's jump back over to Drupal and so now here's our Drupal content type. You can see here, essentially we're returning an array, nav bar brand. Here's our primary links, so that's what we just looked at, the primary links is essentially creating an array that looks like this, but in Drupal. You can see here, hillsmenutree, this is essentially creating that array that Pattern Lab is expecting.

I'll show you an easier example of what that looks like as we continue to build out this page. Let's add another pattern to this site layout. If we come into the default template and we add content, go to Pattern Lab components, I'm going to add in a footer calling card. So that footer calling card, if we come over here into molecules, then footer, we can see the footer calling card looks like this. If we come into the template for that, that's a molecule under footer, the calling card looks like this. This takes several variables, it takes a title, phone, email and then it loops through a social array and outputs the href and the network we defaulted in Pattern Lab. If we look at the data that we defaulted in Pattern Lab, we can come over here to footer calling card and you can see that we've got a calling card key and then we've just specified the data down there.

We're gonna render this in Drupal so we created a content type that essentially has an edit form for all of this information. Let's just fill this out, information technology and let's just go sure and yes. Let's keep that there, update and save. Now there's our footer calling card. All right, you guys get the idea there, we're able to create content types, we're able to create an edit field that passes in data or we're collecting data and then we pass that data into Mustache and render the Pattern Lab template with the data that we are pre-processing.

What is you're working with content that isn't going to be a ctools content type, for example nodes? Let's create some nodes and see what that actually looks like. I'm going to come in here into configuration and we're going to devel generate a few nodes. I can come down here, hit generate content and let's just create 10 basic pages, we'll generate that. Now let's go back to our homepage here, so there we have five nodes listed on the homepage now. How do we actually style this so this looks like something? What I'm going to show is first thing I'm gonna do is I'm going to put this into a content container. Let me go over to our Pattern Lab, I'm gonna go to layouts and look at our main content. Our main content goes into a container that looks like this and the content is output inside of that container. What I'm going to do is I'm going to actually create this homepage as a view so we can actually control the template that's being output here. I'm gonna come over here to structure, go down to views and let's add a new view, let's call this homepage list, we'll continue and edit that.

We're gonna make this a content pane, I'm actually going to get rid of this page here, we didn't need that I should've unchecked it. Within that content pane, we're going to render fields and we're not really gonna be using the views output so uncheck that and then we'll also throw in the body here and we're going to limit that to 600 characters, so that's what our view is going to look like that we're going to use on the homepage. Let's go ahead and save that. What I'm gonna do is I'm gonna create a new front page over in page manager. Within page manager, I'm gonna add a custom page, we'll call this front page and then we'll give this the path of front and we're gonna check this box that says make this your site homepage, we'll continue. Then I'm going to choose the layout called main content and what that's going to do is that's going to use the layout from Pattern Lab that uses main content and I'll show you that here in a second.

I'll hit continue, continue and then inside of here, we're going to output the view that we just created. So here's that view there, so we'll save that and we'll hit finish there. Update and save so now we have a front page that's going to render a view called homepage list using the layout main content. So let's take a look to see what happened here, let's go back to the homepage and there you go, you can see that we're now outputting that actual view within the page content. This home site install shouldn't output here and this is actually being output by panels, so what we're gonna do is we're gonna disable that title there. If we come back here into content and then ... Actually this is going to be in the site template. We'll edit that, go into content and within the page content section, we're going to override the title and make it nothing, update and save that. Now we're getting a lot closer to what our styles look like in Pattern Lab.

Now the next step that we want to do is actually make this view look like something in Pattern Lab. What we're going to do is we're going to make that look like a two column stack view. We have this data here that's set up in a two column stack, we're gonna make the data from views output this template when it renders. Let's jump back into views, so let's go into the front page that we just created and go into the content and so here's that view. I'm going to open this cog here and edit this view and a new tab, so views gives you the ability to specify a theme file. So what we're gonna do is we're actually going to specify this theme file in our theme. I'm gonna copy that and then jump into our theme over here. So here's our Drupal theme, going to my templated directory let's create a views directory so that all our views live in the same directory within our templates directory. Let's create a file called views unformatted home page list.

Now, let's just put in hello world so that you can see that this is actually working. When we re scan the templates, views is going to pick up that template file as you can see now that it's bolded. We'll save this and refresh the homepage and you can see now it is outputting hello world, which is in our template file. How do we actually use the template that is in Pattern Lab? Let's go back into views, so this is where the magic happens in this theme. We have a variable expose called ‘m’, which is basically the Mustache connector to Pattern Lab. On that connector, we have a method called render, so this is where we're going to specify the actual template that we want to use within Mustache. There is a naming convention to this and we'll document what that naming convention is, but essentially what you need to do is specify what type of pattern it is, this is an organism so it's in organisms and then what the name of the template it, this is a two column stack. That's really it, that's all you have to do to render this template, so let's go ahead and save this and then look at our view here.

That didn't render anything, let's go back to our template and you can see that we're actually not printing anything out, so let's actually print out the results of that call and then let's see what happens. There you go, now we're actually printing out the template from Pattern Lab, but you can see that this is actually pulling the data from Pattern Lab, it's pulling out its default data from Pattern Lab. How do we actually make it put our own data? Just like the content types that I was showing you, we can send it a map of how our data should look. There's two ways we can do this, this method here, render, actually takes two arguments. One of them is an array and I'm gonna show you that first and this array is the actual map that the component is expecting. If we look to see what the component is expecting, let me jump back over to our Pattern Lab and then go into the data file that the two column stack is expecting, we can see that it's expecting an array with a two column stack as a key and then that as an array of objects with card as the key and then title and nutgraf.

What I'm going to do is I'm actually gonna just pull some of that data out of there. Let's go back to our Drupal theme and paste that in here. Obviously json syntax doesn't work in PHP, so we need to convert some of this, so I'm gonna make that an array so that looks more like what PHP can understand. Now what I'm going to do is copy this array, but we also need a key of what it's expecting. What it's expecting as the key is primary links, sorry we're looking at the two column stack, what is that expecting, two column stack is what that's expecting. Let's grab that, so now this should do it, probably because that ends the map there. Now we have this two column stack, we're actually passing it data so you can pass it whatever data you want, but essentially this is what Drupal is looking for, so if you have your data then just go ahead and do it right here. Let's see what this actually looks like when we save that and then come back over to Drupal and then hit refresh. You can see how it's printing out five cards with the same data in there.

We have another way of actually mapping this data and this is through a pre-process callback. The way that works is there's a third parameter that you can pass to this render function. Let me delete that array that we just defined and what we're gonna do for the second parameter is we're just going to actually pass it the variables that Drupal knows about. Inside of a Drupal theme, when you're working with your template, there's a variable exposed called variables that you can use and then you can output whatever variables you want. What we're going to do is we're gonna actually pass that to a callback that we define here and we'll pass that in as ‘v’ just so that we don't confuse it with variables here. What's happening behind the scenes is this variables is being passed into this callback and now you can run PHP logic to actually pre-process your variables. Just to show you what variables looks like, let's demo that so you can see what's actually being passed into this callback. If we hit save there, refresh this, you can see here is what the view is actually outputting and what we really want within this data is the results that the view is outputting. Here's all the data that the view is outputting, so this is what we want to actually map to what Drupal is expecting.

If we come back into here, what we'll do is we'll return an array that Pattern Lab is expecting, that array expects to have this key so we'll copy that and then that key has a bunch of cards that are associated with it. What we're gonna do is we're actually gonna do the pre-process just to keep this clean in a separate function, so I'll just define this as process card data. In this function that we're going to define now, we'll just copy this function guy here. This function's going to need to take the data that we're passing it, so pass in ‘v’ here and we'll pass in ‘v’ here, but really what we really need from ‘v’ is just the results from the view, so maybe we just pass in ‘v’ view results. Then down here we can just say that this is going to be called the results, since it's an array of results.

Now, essentially what we need to do is we need to create this data format again with this function that we're using inside of Drupal. What we'll do is we're just going to loop through the results as result and we're going to create what we need. Actually we need to specify that data array up here and then let's return the data array down here. What we need to do is specify each element of the data array. That's going to be equal to another array and in that array... Let's see what that needs to look like, that needs to have a card key with another array with title, nutgraf and href. We're just gonna leave href off, since we don't have the links yet. Title, nutgraf and href. So let's go back here and let's start to set up what this looks like. It needs title, it's gonna be something, nutgraf and href. We also need this to be in an array with cards, so let's actually create the card here and pull this inside of the card. Now this is starting to look a lot like the data that Pattern Lab is expecting.

Now let's actually map the data from views. If we come back over here and we look at what we have, we have each one of these objects, we have the note title and we have the field body. Essentially we just need to write that into the template, no title and then for the body we have field body zero rendered markup. For the href, we do have the NID so I guess we can pass that here. All right and that should be all that we need, so if we save this and then go back and refresh this, that didn't work. Let's take a look to see what we did wrong here. I'm going to output our variables again and just make sure that we map this properly. View, result ... So we got view, ‘v’, result. View is an array, it's not an object, this will probably fix it. So we have ‘v’, view, result, view is an object, result is an array object. We're looping through the array and then an object. Let's save this and see if that actually works and get rid of this criminal.

There we go, there is our views data within our template from Pattern Lab. The idea is that the Drupal theme developer just needs to specify one file that renders the template from Pattern Lab while passing it the variables that Pattern Lab is expecting or passing a callback function that our Mustache connector can then call and map out the variables that Pattern Lab is expecting.

So that’s the direction we’re going.  Hopefully that gives you the idea of what we’re trying to do. We don’t have any HTML, CSS, or JavaScript in our theme.  Any changes needed in those files get pushed upstream into the living style guide, and we pull the changes back down into Drupal.

There’s a lot going on under the hood in this theme to make all of this work. We’re thinking that this theme will end up as a base theme that you can extend, to take advantage of all this functionality.  However, that is still yet to be determined and we may change our minds on that approach.  If you have an opinion on that, please let us know in the comments.

There are definitely some trade offs to using this living style guide approach, and those trade offs exist regardless of the Drupal version you use.  I plan to release a future video to talk about the benefits and disadvantages of the living style guide approach with Drupal.  Taking this approach definitely does not fit every Drupal theme.  More about that later.

Also, we’re going to be releasing more videos as we iterate on this theme, so if you’re interested in following along with us, make sure you subscribe to our channel. Thanks for watching!

Mar 23 2017
Mar 23


We recently had the opportunity to work on a Symfony app for one of our Higher Ed clients that we recently built a Drupal distribution for. Drupal 8 moving to Symfony has enabled us to expand our service offering. We have found more opportunities building apps directly using Symfony when a CMS is not needed. This post is not about Drupal, but cross posting to Drupal Planet to demonstrate the value of getting off the island. Enjoy!

Writing custom authentication schemes in Symfony used to be on the complicated side. But with the introduction of the Guard authentication component, it has gotten a lot easier.

One of our recent projects required use to interface with Shibboleth to authenticate users into the application. The application was written in Symfony 2 and was using this bundle to authenticate with Shibboleth sessions. However, since we were rewriting everything in Symfony 3 which the bundle is not compatible with, we had to look for a different solution. Fortunately for us, the built-in Guard authentication component turns out to be a sufficient solution, which allows us to drop a bundle dependency and only requiring us to write only one class. Really neat!

How Shibboleth authentication works

One way Shibboleth provisions a request with an authenticated entity is by setting a "remote user" environment variable that the web-server and/or residing applications can peruse.

There is obviously more to Shibboleth than that; it has to do a bunch of stuff to do the actual authenticaiton process. We defer all the heavy-lifting to the mod_shib Apache2 module, and rely on the availability of the REMOTE_USER environment variable to identify the user.

That is pretty much all we really need to know; now we can start writing our custom Shibboleth authentication guard:

namespace AppBundle\Security\Http;

use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Guard\AbstractGuardAuthenticator;
use Symfony\Component\Security\Http\Logout\LogoutSuccessHandlerInterface;

class ShibbolethAuthenticator extends AbstractGuardAuthenticator implements LogoutSuccessHandlerInterface
    private $idpUrl;

    private $remoteUserVar;

    private $urlGenerator;

    public function __construct(UrlGeneratorInterface $urlGenerator, $idpUrl, $remoteUserVar = null)
        $this->idpUrl = $idpUrl;
        $this->remoteUserVar = $remoteUserVar ?: 'HTTP_EPPN';
        $this->urlGenerator = $urlGenerator;

    protected function getRedirectUrl()
        return $this->urlGenerator->generateUrl('shib_login');

    public function start(Request $request, AuthenticationException $authException = null)
        $redirectTo = $this->getRedirectUrl();
        if (in_array('application/json', $request->getAcceptableContentTypes())) {
            return new JsonResponse(array(
                'status' => 'error',
                'message' => 'You are not authenticated.',
                'redirect' => $redirectTo,
            ), Response::HTTP_FORBIDDEN);
        } else {
            return new RedirectResponse($redirectTo);

    public function getCredentials(Request $request)
        if (!$request->server->has($this->remoteUserVar)) {

        $id = $request->server->get($this->remoteUserVar);

        if ($id) {
            return array('eppn' => $id);
        } else {
            return null;

    public function getUser($credentials, UserProviderInterface $userProvider)
        return $userProvider->loadUserByUsername($credentials['eppn']);

    public function checkCredentials($credentials, UserInterface $user)
        return true;

    public function onAuthenticationFailure(Request $request, AuthenticationException $exception)
        $redirectTo = $this->getRedirectUrl();
        if (in_array('application/json', $request->getAcceptableContentTypes())) {
            return new JsonResponse(array(
                'status' => 'error',
                'message' => 'Authentication failed.',
                'redirect' => $redirectTo,
            ), Response::HTTP_FORBIDDEN);
        } else {
            return new RedirectResponse($redirectTo);

    public function onAuthenticationSuccess(Request $request, TokenInterface $token, $providerKey)
        return null;

    public function supportsRememberMe()
        return false;

    public function onLogoutSuccess(Request $request)
        $redirectTo = $this->urlGenerator->generate('shib_logout', array(
            'return'  => $this->idpUrl . '/profile/Logout'
        return new RedirectResponse($redirectTo);

Let's break it down:

  1. class ShibbolethAuthenticator extends AbstractGuardAuthenticator ... - We'll extend the built-in abstract to take care of the non-Shibboleth specific plumbing required.
  2. __construct(...) - As you would guess, we are passing in all the things we need for the authentication guard to work; we are getting the Shibboleth iDP URL, the remote user variable to check, and the URL generator service which we need later.
  3. getRedirectUrl() - This is just a convenience method which returns the Shibboleth login URL.
  4. start(...) - This is where everything begins; this method is responsible for producing a request that will help the Security component drive the user to authenticate. Here, we are simply either 1.) redirecting the user to the Shibboleth login page; or 2.) producing a JSON response that tells consumers that the request is forbidden, if the client is expecting application/json content back. In which case, the payload will conveniently inform consumers where to go to start authenticating via the redirect property. Our front-end application knows how to handle this.
  5. getCredentials(...) - This method is responsible for extracting authentication credentials from the HTTP request i.e. username and password, JWT token in the Authorization header, etc. Here, we are interested in the remote user environment variable that mod_shib might have set for us. It is important that we check that the environment variable is actually not empty because mob_shib will still have it set but leaves it empty for un-authenticated sessions.
  6. getUser(...) - Here we get the credentials that getCredentials(...) returned and construct a user object from it. The user provider will also be passed into this method; whatever it is that is configured for the firewall.
  7. checkCredentials(...) - Following the getUser(...) call, the security component will call this method to actually verify whether or not the authentication attempt is valid. For example, in form logins, this is where you would typically check the supplied password against the encrypted credentials in the the data-store. However we only need to return true unconditionally, since we are trusting Shibboleth to filter out invalid credentials and only let valid sessions to get through to the application. In short, we are already expecting a pre-authenticated request.
  8. onAuthenticationFailure(...) - This method is called whenever our authenticator reports invalid credentials. This shouldn't really happen in the context of a pre-authenticated request as we 100% entrust the process to Shibboleth, but we'll fill this in with something reasonable anyway. Here we are simply replicating what start(...) does.
  9. onAuthenticationSuccess(...) - This method gets called when the credential checks out, which is all the time. We really don't have to do anything but to just let the request go through. Theoretically, this would be there we can bootstrap the token with certain roles depending on other Shibboleth headers present in the Request object, but we really don't need to do that in our application.
  10. supportsRememberMe(...) - We don't care about supporting "remember me" functionality, so no, thank you!
  11. onLogoutSuccess(...) - This is technically not part of the Guard authentication component, but to the logout authentication handler. You can see that our ShibbolethAuthenticator class also implements LogoutSuccessHandlerInterface which will allow us to register it as a listener to the logout process. This method will be responsible for clearing out Shibboleth authentication data after Symfony has cleared the user token from the system. To do this we just need to redirect the user to the proper Shibboleth logout URL, and seeding the return parameter to the nice logout page in the Shibboleth iDP instance.

Configuring the router: shib_login and shib_logout routes

We'll update app/config/routing.yml:

  path: /Shibboleth.sso/Login

  path: /Shibboleth.sso/Logout

You maybe asking yourself why we even bother creating known routes for these while we can just as easily hard-code these values to our guard authenticator.

Great question! The answer is that we want to be able to configure these to point to an internal login form for local development purposes, where there is no value in actually authenticating with Shibboleth, if not impossible. This allows us to override the shib_login path to /login within routing_dev.yml so that the application will redirect us to the proper login URL in our dev environment.

We really can't point shib_logout to /logout, though, as it will result in an infinite redirection loop. What we do is override it in routing_dev.yml to go to a very simple controller-action that replicates Shibboleth's logout URL external behavior:


  public function mockShibbolethLogoutAction(Request $request)
      $return = $request->get('return');

      if (!$return) {
          return new Response("`return` query parameter is required.", Response::HTTP_BAD_REQUEST);

      return $this->redirect($return);

Configuring the firewall

This is the last piece of the puzzle; putting all these things together.

    class: AppBundle\Security\Http\ShibbolethAuthenticator
      - '@router'
      - '%shibboleth_idp_url%'
      - '%shibboleth_remote_user_var%'


  - { resources: config.yml }
  - { resources: security.yml }


  - { resources: config.yml }
  - { resources: security_dev.yml } 


      stateless: true
          - app.shibboleth_authenticator

        path: /logout
        success_handler: app.shibboleth_authenticator


      stateless: false
        login_path: shib_login
        check_path: shib_login
        target_path_parameter: return

The star here is actually just what's in the security.yml file, specifically the guard section; that's how simple it is to support custom authentication via the Guard authentication component! It's just a matter of pointing it to the service and it will hook it up for us.

The logout configuration tells the application to allocate the /logout path to initiate the logout process which will eventually call our service to clean up after ourselves.

You also notice that we actually have security_dev.yml file here that config_dev.yml imports. This isn't how the Symfony 3 framework ships, but this allows us to override the firewall configuration specifically for dev environments. Here, we add the form_login authentication scheme to support logging in via an in-memory user-provider (not shown). The authentication guard will redirect us to the in-app login form instead of the Shibboleth iDP during development.

Also note the stateless configuration difference between prod and dev: We want to keep the firewall in production environments stateless; this just means that our guard authenticator will get consulted in all requests. This ensures that users will actually be logged out from the application whenever they are logged out of the Shibboleth iDP i.e. when they quit the web browser, etc. However we need to configure the firewall to be stateful during development, otherwise the form_login authentication will not work as expected.


I hope I was able to illustrate how versatile the Guard authentication component in Symfony is. What used to require multiple classes to be written and wired together now only requires a single class to implement, and its very trivial to configure. The Symfony community has really done a great job at improving the Developer Experience (DX).

Setting pre-authenticated requests via environment variables isn't just used by mod_shib, but also by other authentication modules as well, like mod_auth_kerb, mod_auth_gssapi, and mod_auth_cas. It's a well-adopted scheme that Symfony actually ships with a remote_user authentication listener starting 2.6 that makes it very easy to integrate with them. Check it out if your needs are simpler i.e. no custom authentication-starter/redirect logic, etc.

Mar 06 2017
Mar 06

In the modern world of web / application development, using package managers to pull in dependencies has become a de-facto standard. In fact, if you are developing enterprise software and you aren't leveraging package managers I would challenge you to ask yourself why not?

Drupal was very early to adopt this mindset of pulling in dependencies almost a decade ago when Dmitri Gaskin created an extension for Drush (the Drupal Shell) that added the ability to pull contributed modules by listing them in a make file (I think Dmitri was 12 years old when he wrote the Drush extension, pretty amazing!). Since that time, the make extension has been added to Drush core.

Composer is the current standard for putting together PHP applications, which is why Drupal 8 has gone this direction, so why not use Composer to put together Drupal 7 applications?

First off, I want to clarify what I'm not talking about in this post. I am not advocating that we ditch Drush all together, I still find value in other aspects of what Drush can do. I am specifically referring to the Make aspect of Drush. Is Drush Make still necessary?

This post is also not about Drupal Console vs Drush, both CLI tools add tremendous value to development workflow, and there isn't 100% overlap with these tools [yet]. I think we still need both tools.

This post is about how I came to see the benefit of switching to Composer from Drush Make. I recommend making this move for Drupal 7 and Drupal 8. This Drupal Composer workflow is not new, it has been around for a while. I just never saw a good reason to make the jump from Drush Make to this new process, until now. We have been asked in the comments on previous posts, "Why haven't you adopted the Composer process?" I now have a good reason to change our process and fully jump on board with Composer building Drupal 7 applications. We appreciate all the comments we get on our blog, it sharpens everyone involved!

We have blogged about the Composer workflow in a previous post on our Drupal 8 build process in the past, but the main motivation there was to be proactive about where PHP application development is going [already is]. We didn't have a real use case for the switch to Composer until now. This post will review how I came to that revelation.

Dependency Managers

I want to make one more point before I make the case for Composer. There are many reasons to use package managers to pull in dependencies. I'll save the details for another blog post. The main reason developers use package managers is so that your project repository does not include libraries and modules that you do not maintain. That is why tools like Composer, npm, Yarn, Bower, and Bundler exist. Hook up your RSS reader to our blog, I'll explain in more detail in a future post, but for now I'll leave this link to the Composer site explaining why committing dependencies is a bad idea, in your project repo.

Version Numbers

The #1 reason to make the switch to Composer is the ability to manage version numbers. You may be asking "What's the big deal, Drush Make handles version numbers as well?" let me give you a little context of why using Composer version numbers are a better approach.

The Back Story

Recently in a strategy meeting with one of our enterprise clients, we were discussing how to approach launching 100's of sites on one Drupal Core utilizing multiple installation profiles on top of Acquia Site Factory. Our goal was to figure out how we could sanely manage updating potentially dozens of installation profiles without explicitly defining each version number of the profile being updated. This type of Drupal architecture is also a topic for a future blog post, but for now read Acquia's explanation of why architecting with profiles is a good idea.

As a developer, it is common place to lock down versions to a very specific version so that we know exactly what versions we are using / deploying. This is the reason composer.lock, Gemfile.lock, yarn.lock, and npm shrinkwrap exist. We have experienced the pain of unexpected defects in applications due to an obscure dependency changing deep in the dependency tree. Most dependency managers have a very explicit command for updating dependencies, i.e. composer update, bundle update, yarn upgrade respectively, which in turn update the lock file.

A release manager does not need to know explicitly which version of a dependency (installation profile, module, etc), to release next, she simply wants the latest stable release.

Herein lies the problem with Drush Make. There are practices that exist that solve both the developer problem and release manager problem that do not exist in Drush Make, but do exist in Composer and other application development environments. It's a common pattern that has been around for a while, it's called semantic versioning.

Semantic Versioning

If you haven't heard of semantic versioning (semver), go check it out now. Pretty much every package manager I have dealt with has adopted semver. Adopting semver gives the developer, or release manager, the choice of how to update dependencies within their app. There are very distinct numbers in semver for introducing breaking changes, new features, and bug fixes. How does this play into what problem use cases I mentioned above?

A developer has the ability to specify in the composer.json file specific versions, while leaving the version number flexible to pull in new bug fixes and feature improvements (patch and minor releases). Look at the example below:

  "name": "My Drupal Platform",
  "require": {
    "drupal/drupal": "~7.53.0",
    "drupal/views": "^3.14.0"

The tilde ~ and caret ^ symbols have special meanings when specifying version numbers. The tilde matches the most recent minor version (updates patch release number, the last number), the caret will update you to the most recent major version (updates minor release number, the middle number).

The above example basically says, use the views module at version 3.14, and when version 3.15 comes out, update me to that version when I run composer update.

Breaking changes should only be introduced when you update the first number, the major release. Of course, if you completely trust the developer writing the contributed code this system would be enough, but not all developers follow best practice, which is why the lock file was created and the need to explicitly run composer update.

With this system in place, a release manager now only needs to worry about running one command to get the latest stable release of all dependencies. This command could also be hidden behind a nice UI (a CI Server) so all she has to do is push one button to grab all the latest dependencies and push to a testing site for verification.

Understanding everyones needs

In the past, I didn't have a good reason to move away from Drush Make, because it did the job, and Drush is so much more than Drush Make. The strategy session we had was eye opening. Understanding the needs from an operations perspective, while not jeopardizing the integrity of the application led us down a path to see a problem that the wider development community at large has already solved (not just the PHP community). It's very rewarding to solve problems like this, especially when you come to the conclusion that someone has already solved the problem! "We just had to find the path to the water! (--A.W.)"

What do you think about using Drush Make vs Composer for pulling together a Drupal Application? Leave us your thoughts in the comments.

Jan 25 2017
Jan 25

There are lots of situations in which you need to run a series of microsites for your business or organisation — running a marketing campaign; launching a new product or service; promoting an event; and so on. When you’re with Drupal, though, what options do you have for running your microsites? In this article I review and evaluate the options in Drupal 8, make a recommendation and build a proof of concept.

Joe Baker

Why use multisites

Caveat: is the end of multisites support on the horizon?

Classic problems with Drupal multisites …

… and how to mitigate them

Domain Access

Organic Groups

Best practice: with Git

RESTful web services and Drupal 8

Design your own web services API

Decoupled frontend

Nov 17 2016
Nov 17

This latest release for [RNG](RNG brings two major features: The ability for anonymous users to register for events. And the ability to create, and associate non-users with events.

RNG is an event management module for Drupal 8 created in the spirit of Entity Registration (Drupal 7) and Signup (Drupal 6). Users can create registrations for events, and event managers can manage these registrations.

Note: This post discusses updates to the RNG project which are available in a beta release. See this issue for how to get RNG 1.3 beta.

The event registration form has been reworked into a re-usable Drupal element, whilst making heavy use of AJAX. The registrant selector now accepts multiple registrants. Registrants can also be modified after the registration is created.

*Associate multiple registrants with a registration.* *Create new registrants within the registration form.* *Modify the meta registrant form within the registration form.*

Access control has been reworked to permit anonymous users to register for events.

RNG requires that all registrants for a registration are a Drupal entity. Since anonymous users do not correspond to a user entity, the RNG Contact project provides a way to create non-user registants in a similar fashion to how contacts work on your phone.

See main RNG Contact article: RNG Contact: Anonymous registrants for RNG.

  • Registrant entities now have bundles
  • Added registrant type configuration entity
  • Added control over which identity types can be referenced or created within each event type.
  • Added ability to specify minimum and maximum registrants per registration.
  • Added an interface to view and add RNG related fields.
  • Event settings pages now use the admin theme.
  • And many other behind the scene changes.

Cover photo: B&W Crowd by whoohoo120. License CC BY 2.0

Oct 24 2016
Oct 24


You know the old saying: “This is how the world ends: not with a bang, but with a misplaced DROP TABLE.” Working directly with Drupal 7’s database is an arduous task at best.  It’s a sprawling relational system and it uses many space and memory saving tricks to be as speedy as possible.  Thankfully, there is a robust system of functions built into Drupal to help you change almost any setting from code–perfect if you want to automate changes upstream and features doesn’t do it for you.  Let’s go over a situation in which you may have been utilizing some of these functions.

Let’s say you finished your product (congratulations!), launched, and are onto fixing bugs and planning exciting new features for the future.  You’re knocking out bugs left and right like some high-flying Drupal ninja and you discover that using a field collection with conditional fields causes the field collection data to not save and all of your metadata is getting erased when certain conditions are fired.  With Cthulhu’s hot breath on your neck, you talk to the client and realize a ray of hope: you don’t actually need a field collection there, a normal set of Drupal fields will do.  How do we go about creating the new fields, copying existing data, and deleting the old fields?

The first thing we do is create the new fields and attach them.  For this, we’ll need two functions: ‘field_create_field()’ and ‘field_create_instance()’.  Both of these take an array of settings: field_name and type are we need for creating the field (also cardinality if you want to have multiple values for the field), field_name, entity_type, and bundle are required for creating the instances, though you will likely also want label, or it will otherwise default to the machine name.  So, we should have something that looks like this:

$name = [ ‘field_name’ => 'photographer_name', ‘type’ => ‘text’, ]; field_create_field($name); $instance = array( 'field_name' => $name['field_name'], 'entity_type' => node, 'bundle' => article, 'label' => 'Name', ); field_create_instance($instance);















$name = [

  field_name’ => 'photographer_name',

  type => text,



$instance = array(

  'field_name' => $name['field_name'],

  'entity_type' => node,

  'bundle' => article,

  'label' => 'Name',



If you go check out node/add/article, you should see your new text field there.  Congrats!  Next, we need to get the data from the old fields and copy it into our new field.  For this, we’ll rely on the nifty function ‘entity_load()’.  This takes two arguments, bundle name and an array of ids.  Since we are getting field collection items, we know the bundle name is ‘field_collection_item’.  We’ll need the IDs, but we’ll also need the field collection value that references the fields in each collection for later, so we’ll get them both at once.  It might be tempting to use ‘entity_load()’ to get them, but in this case you are quite safe using straight SQL, which also happens to be significantly faster.  That looks like this:

$entity_ids = array(); $field_collection_ids = array(); // Select the field collection id and the attached entity id from the db. $query = db_query('SELECT field_producer_value, entity_id FROM field_data_field_producer'); $results = $query->fetchAll(); // Separate the ids</span> foreach ($results as $result) { $field_collection_ids[] = $result->field_scald_producer_value; // We need to reference the entity ID by the field collection value for simplicity later $entity_ids[$result-&gt;field_scald_producer_value] = $result-&gt;entity_id; } // It’s possible that you might get duplicate Field Collection IDs, so we make sure they are all unique $field_collection_ids = array_unique($field_collection_ids); // Load all of the field collection entities. $field_collection_results = entity_load('field_collection_item', $field_collection_ids);
















$entity_ids = array();

$field_collection_ids = array();

// Select the field collection id and the attached entity id from the db.

$query = db_query('SELECT field_producer_value, entity_id FROM field_data_field_producer');

$results = $query->fetchAll();

// Separate the ids

foreach ($results as $result) {

  $field_collection_ids[] = $result->field_scald_producer_value;

  // We need to reference the entity ID by the field collection value for simplicity later

  $entity_ids[$result-&gt;field_scald_producer_value] = $result-&gt;entity_id;


// It’s possible that you might get duplicate Field Collection IDs, so we make sure they are all unique

$field_collection_ids = array_unique($field_collection_ids);

// Load all of the field collection entities.

$field_collection_results = entity_load('field_collection_item', $field_collection_ids);

Now that we have all of the entity ids and field collection ids, we can get to the fun part: copying data! (You know you have been doing this too long when that is exciting.) What we want to do is loop through the field collection ids, load the the entity (that has the new field on it) by the id associated with the collection, copy the data from the collection to the new field, and save.  It seems like a lot, but it’s fairly simple:

foreach ($field_collection_ids as $field_collection_id) { // Load the entity the field collection is attached to $entity = entity_load('node', array($entity_ids[$field_collection_id])); // Copy the data from the collection field to the new field $entity[$entity_ids[$field_collection_id]]->photographer_name['und'][0]['value'] = $field_collection_results[$field_collection_id]->field_producer_name['und'][0]['value']; // Save! entity_save('node', $entity[$entity_ids[$field_collection_id]]); }

foreach ($field_collection_ids as $field_collection_id) {

  // Load the entity the field collection is attached to

  $entity = entity_load('node', array($entity_ids[$field_collection_id]));

  // Copy the data from the collection field to the new field

  $entity[$entity_ids[$field_collection_id]]->photographer_name['und'][0]['value'] =


  // Save!

  entity_save('node', $entity[$entity_ids[$field_collection_id]]);


A word of warning: depending on how many entities you are processing, this could take a long time.  As of Drupal 7.34, there is a memory leak in entity_save()–this means that each save will take slightly longer than the last. This is not a problem if you have only a few hundred fields, but when you get up into five and six digits, this script will take many hours. At that point, unless you have the time (and/or can run the script as a process in the background), you might want to consider investigating other options.

Okay, so the data is copied, the nodes are saved, and the elder gods have hit the snooze button.  Last thing you have to do is delete the old field.  We’re not going to do that, at least not yet. Instead, we’re going to delete the instances of the fields.  This will preserve the old field collection data, but remove the fields from the edit forms. This way, if something goes wrong, you don’t lose the data in the old fields and can try again if needed. You can go back at a later time, if you wish, after you have confirmed that everything is correct and delete the fields. Luckily, this is the easy part:

$instance = array( 'field_name' =&gt; 'field_scald_producer', 'entity_type' =&gt; node, 'bundle' =&gt; article ); field_delete_instance($instance);

$instance = array(

  'field_name' =&gt; 'field_scald_producer',

  'entity_type' =&gt; node,

  'bundle' =&gt; article



And that’s it, crisis averted!  You no longer lose data and no longer have to worry about supernatural madness and death!  All you need to do now is run your script upstream with ‘drush php-script’ and watch the magic.

This sort of scripting can be daunting at first glance, but Drupal’s rich entity API can keep you from pulling out your hair or inadvertently causing an otherworldly alien intelligence from rising from the deep.  There are many more functions to take advantage of, and just about anything you can set with a click in the interface you can set in code, perfect for automation or locked down production environments.

Happy Drupaling !

Oct 12 2016
Oct 12

Ron Huber: Proprietary software does a really good job of being everything to everybody. When somebody goes and pitches something for a proprietary side, they'll say yes to everything, where in open source, we’ll say, well we do this really well, and it'll integrate and that's no problem, but we don't sell it as the end-all. We sell it as it's a solid player and this is what we can do with it and we feel comfortable because that's just our way of approaching things. We're a community of good people. Where on the other side, when they sell the internet of things, there are platforms out there that are, "Oh, we're going to control the world," and they won't let you do anything but control the world. Everything has to go through their system. They can do everything and they get you on it and it's too late once you realize that it only gets you about 75% of the way there. We have to sell it differently now.

Tom Friedhof: This is where the argument of the open web and silos comes in, right? And obviously, Drupal's pushing for the open web, because with these silos - say your marketing platform is on Facebook, right? If you want to do anything above and beyond what Facebook allows you to do, you can't. You're stuck within their platform.

Jordan Ryan: That’s their audience.

Tom Friedhof: Exactly. And if you want to reach that audience, you have to pay for it.

Chris Stauffer: I believe that quote was, "If you didn't pay for it, you are the product."

Jordan Ryan: Right. That's very true. You know, all those Facebook followers that you have can go in, and it's a reality they think a lot of small business owners and medium-sized enterprises - they don't realize, if that page goes away, your whole platform you spent all that money on is gone.

Chris Stauffer: I liked what both of you two just said a second ago, which was kind of that that's one of the main value adds of open-source. I had a conversation on Friday with a particular client - er, gentleman - that's been my client three times in the past, but his new start-up company is not my client. So he went with a quote/unquote open platform, and I'm putting air quotes around it, that would get him on all of these different devices. He's basically doing an MCM. So you have video platform - they did all of these different things, and he was really, really stoked, and really excited when he first told me about it. And now he's ready to go, and he's ready to make some changes, and they told him no, and he can't do anything. Whereas the initial platform that I was originally pitching him was, we'll start off with responsive web, like normal, we could throw an Android or iOS layer on top. It'll be nice a simple, and then you'll be able to do whatever you want to.

Ron Huber: You pivot when you need to pivot.

Chris Stauffer: Right, you pivot when you need to. And now he's sitting there going, "I can't get them to do the things I want them to do." And I'm like, "Dude, I would've told you you could do whatever you want." You know? I got no limitations - you want it? We’ll build it.

Tom Friedhof: But there's a cost to that as well.

Chris Stauffer: There is! There is.

Ron Huber: Because you don't benefit from the other hundred clients that are also asking for something.

Chris Stauffer: I mean, yeah. The initial bid for him was going to end up being about a hundred large to just kind of do a very simple CMS with a simple video object and couple simple video apps layered on top of that. And it did save him a hundred grand upfront, but now he's to a place where he wants to actually start monetizing his assets and actually start doing a lot of those different things that he's unable to do, and he's probably going to end up paying a hundred grand anyways. That kind of makes sense. Because he can't take it where he needs to.

Jordan Ryan: I'm not knocking anyone, but if you're building a business, you shouldn't be building on someone else's platform. You build your own.

Ron Huber: Very good point, right? Here you are, all your technology is owned by somebody else, and you're assuming - of course - that company's going to be around forever. You're also assuming that you're a large enough client that you can drive them into do what you need them to do. And, I don't know, most of the clients are not that big. We are actually building a java application right now, because one of our large media companies, the third-party system that they paid for went out of business.

Chris Stauffer: That sucks.

Ron Huber: Went out of business, they gave them the software, so they're running the software, but they have to replicate it, and they have to replicate it soon, because if it fails, there's no way they can touch it. It just goes down. So we're busy replicating the whole thing, so that they don't have this point of failure. And it's great, because we'll build it, it'll integrate into Drupal without a problem, which is a hundred of their other websites, and it'll be able to sit in there and integrate without any issue. But it's on a different technology, and very few other proprietary systems will allow another technology to come in and play nice. It's a very powerful tool, but you gotta pay the hundred thousand dollars upfront, which is killing us, because that's a big, big investment. If you can just sign on for twenty-five hundred dollars, and - boom, here I am - that's great.

Chris Stauffer: And that's basically what the guy did, by the way. I don't think it was twenty-five hundred, I think he paid ten for the whole platform.

Ron Huber: Ten, but maybe that's the business side of it. He should go ten, cause if you're starting a business, you put in a little bit of money - it's basically your MVP, and you get it tested, and then you move it over.

Chris Stauffer: For the time, I think he probably actually did make the right decision, but since he was successful, now it's the wrong decision.

Jordan Ryan: But he has to know that going into it, though.

Chris Stauffer: He did.

Jordan Ryan: The context of having a conversation with those kinds of people, and when we have those conversations - "Look, hosting service, build your own - this will probably give you runway for six months if you don't want to build it right now. And then prove that it works, and then come back to this later. But prove that it works, right?

Chris Stauffer: He did get a second round of funding.

Ron Huber: There you go. People have a hard time with just the minimal viable product concept. And it's really not the developers, or the engineers; it's a CFO. The CFO wants it all done, cause he wants to write one check, and be done with it. As much as we tell them, "How do you know you're gonna need this six months from now? Cause you can't even tell us your requirements today. We're building you a platform for something that we're guessing at, you're asking us to guess at. We're building it, it's working, you get it running, and chances are six months, or eight months from now, you're going to realize - hey, this piece that's sitting over here is making me a lot of money, and I didn't put any effort at all into it. Okay, now I'm going to pivot and go that way." Well, you can't do that. If you build the whole dream - the two-year plan - upfront, then you've built the two-year plan and 50% of it's not being used. But sometimes, these executives - marketing, CFO, etc. - get so hung up on the overall, "We want to do it once.”

Chris Stauffer: "We want to do it once and it has to be done right the first time."

Jordan Ryan: This is the experience that we're supposed to have, right?

Ron Huber: Well, we do.

Jordan Ryan: That's a sales point for CFOs.

Ron Huber: Right. But getting people to buy into that is the hard part. And I don't know - this is where I think proprietary software really kills it, is because they already have a package. It might be twice as much, or a three-year commitment - which is just silly. They're selling ten-thousand dollars a month or a hundred-thousand dollars a month on something that's already built, and that's what we want, whether they use it or not. I've been looking at this, trying to figure this out for years, and I just don't have an answer for open source. But I do think it's still - that's a part of our big challenge, and where we're going with that.

Tom Friedhof: The value proposition is freedom, right? It's the open web. It's the freedom to do what we want to do with the applications we built. When obviously there's the cost.

Ron Huber: And ownership, right? They own it.

Chris Stauffer: I think one of the other things, too, that I've started to sell as a value proposition of open source is that there's no vendor lock-in either. I can honestly look a client in the eye and tell them, "Look, my boys follow the rules, and if, at the end of the project, you don't like me, hire Achieve. Because I guarantee his team could pick up my code, and just be like 'alright'." And they would just keep running, as long as we're following the Drupal rules and Drupal standards, there's no vendor lock-in.

Ron Huber: Better yet, you could hire internally. Anybody you feel like. We don't really want to do your maintenance, right? We want you to do your maintenance. We want to build your next ambitious goal, cause that's what we're really good at. But to do your maintenance, you should hire internally or a sub-contractor, or get India to do it.

Chris Stauffer: My point, though, was more that you're not locked in. I have this other client that hired an engineer to develop a 100% custom system, and the engineer was, on a scale of one to ten, about a two and a half or a three. And so the whole thing is completely a worthless platform, and now my team is going in and reverse-engineering the worthless platform, to then move them out of the worthless platform, into something that's solid. And they're literally having to pay, I'm going to call it a $30,000 tax, if you will, on just my boys figuring out what that last guy was thinking. And with Drupal, as long as my team followed the rules, I can look a client in the eye and guarantee them that will never happen. You could, to Ron's point, hire internal staff. You could hire a competitor, you could do anything you want to, and you're not locked in. Whereas the client that I was referencing that wrote the proprietary software, that only worked for them - that, realistically, was a couple hundred-thousand dollar mistake. Pretty much. Because by the time she's done, the cost of her business, the cost of my bills, the cost of the bills she had previously - all of those costs are just ridiculous, compared to if she would've just hired us to do it in Drupal to begin with.

Oct 01 2016
Oct 01

Jordan Ryan: Are any of you selling, in particular with Drupal, the power of integrations or integrating with other systems? Kind of like the microservices decoupled…

Chris Stauffer: To a certain extent, but it's kind of more selling them for me on the power of Drupal as an enterprise platform, then in that initial requirements-gathering process, talking to them about what other ancillary applications and legacy systems they have to tie into. Then once you've identified that, then selling them on the fact that you've already tied it in with Salesforce five times, and that's not really that big of a deal anymore because you kind of know how to do the Salesforce thing. I'm just using his example, but I've found that when I'm able to speak to the fact that you've already done that integration four times, then it becomes not necessarily a risk. I remember back in the old days, I would always think that every time I integrated into another system, that that was my largest point of risk on the project, was I'm going to plug into something else. Now, if someone tells me I'm going to take Drupal, and I'm going to plug that into Salesforce, I go, "Eh. I don't know. It's probably only ten grand, maybe; maybe 15. Depends on how complicated it is." But my blood pressure didn't raise at all. Whereas back in the old days, with all custom systems, since a lot of that integration wasn't already there, and I had to do it from scratch, and there weren't modules that did it, it was way scarier. Or integrating with Facebook Connect. The first time I did that, it scared the $#@! out of me.

Ron Huber: Especially a week later, when they change the API.

Chris Stauffer: When they changed the API and it blew up in my face before it was going to go live. You mean that time?

Ron Huber: And it was your fault, of course!

Chris Stauffer: Of course! That did literally happen. We did a project for Unilever, and we were launching a Facebook app, and it blew up like a week before the demo.

Ron Huber: That's right.

Chris Stauffer: Yeah, it was horrible. But nowadays, a lot of those prewritten integrations, I've already done them so many times, and they're so mature, that it's like, "Oh, Facebook integration. Yeah, whatever, dude. Sure, no problem."

Jordan Ryan: One click. Not quite.

Chris Stauffer: Well, I don't know if I’d go that far. Does that make sense? During that requirements gathering process, one of the things I tell clients a lot about Drupal, too, is when you hear me say, "There's a module for that," you should smile. When you hear me go, "Ooh, I don't know if there's a module for that," that means you should frown because that's what I just did to your budget. When I say there's a module for that, I'm going to get it done, and it's going to get done quick and cheap and efficient, and everything's cool. But the minute I go, "I don't know if there's a module for that," then that means it might take me 100 hours or 200 hours to pull off what you just asked me to do. Whereas, I might have done ten requirements that were all out of the box for the same price as that one requirement, which is going to be custom.

Ron Huber: I hate that term, out of the box. It drives me nuts.

Chris Stauffer: But you get my point.

Ron Huber: I totally get your point, and you live it, and et cetera. We consider ourselves a integration company. I feel like why people come to us is because we have so much experience in integration. There's other shops that do Drupal. That's not the problem. Frankly, you can get Drupal done in eastern Europe or whatever. It's all the integration and the API, and then of course, the management side of it. It was what should you be integrating? What should you be doing? Those are the real questions and why I think you hire a US-based firm, as opposed to somebody that's just building off of your requirements.

Chris Stauffer: Well, I think, Ron, for me, the difference, kind of building exactly on what you're saying, is that the US-based developers have the ability to get thrown a curve ball and still hit it. Whereas, the overseas developers, when you throw them a curve ball, they don't know what a curve ball is or what they're supposed to do with it. They just know, "I was told to do this, and you gave me that, and now I'm completely lost, and I don't know how to handle it."

Ron Huber: I want it to work. I've spent hundreds of thousands of dollars…

Chris Stauffer: Wasted…

Ron Huber: On ever country possible to be able to supplement our team, and it hasn't worked for what we do. I think it works excellent for somebody that's got a three-year roadmap, and you got a product, and you want to ... That works perfectly. But if you don't know what your requirements are and you need it by November 1st, you got to do it here in the US, and you should probably do it pretty local.

Jordan Ryan: Or you need great communication.

Ron Huber: Well, yeah. Just because everybody should hire you, they don't.

Chris Stauffer: Well, that's the thing about systems integration, though, is systems integration never actually goes the way it's supposed to.

Ron Huber: No.

Chris Stauffer: That's what I meant by hitting a curve ball.

Ron Huber: No, you're absolutely right.

Chris Stauffer: That systems integration, it's always like we planned to have that hook up to that, and then you find out that, oh $#@!, it's not going to work like that.

Ron Huber: It's a lot of moving parts.

Chris Stauffer: And uh-oh. And the US guy can hang, and the other guy doesn't.

Ron Huber: Well, it's not their fault, either. We just have a better communication process. We've seen it a little bit more. I don't think this is a US versus offshore conversation. There's just a certain element of what it is that we do best and why we're up here talking about it. I think that as we look at where we're headed and where Drupal is headed, I think this move into Drupal 8 was really ... Not that it's surprising, from Dries ... a visionary move because where we could go, that none of us have even ... Well, not really thought out yet. He's probably ten steps in front of us, right? He knows where we're going to go. We all just need to catch up. This move to Sympfony based and a different object oriented function is just going to be able to get us there. I think the face of Drupal's going to change. I think how we maybe sell or how we pitch it or how we use it is going to change, and there's nothing wrong with that. It's still a powerful tool. It might not be our only tool.

Tom Friedhof: One of these scenarios that comes to my mind when we're talking about all this integration was the example that Dries gave at DrupalCon with ordering food through the Alexa and basically asking Alexa if something was on sale at Trader Joe's, and basically having APIs talk to each other. Then when the gal at the supermarket updated the little produce and said it's on sale, that automatically sent a text. It's crazy how the world we live on is no longer just websites, right? Drupal is no longer just a website. It's got to work on your phone.

Ron Huber: Well, yeah. Where else are we going to ... It's going into everything, and that's the other big thing, is we do a lot of medical device work. Then where do you interact with the Internet of things? Where is it that we have to go? Okay, maybe Drupal doesn't actually show up on a device, but it is the aggregator. Then when you're trying to get in the backend and figure out, through your portal, where your customers are, where your employees are, where the new products coming, that's another powerful tool or another version of Drupal that I think is under-promoted and underused, at this point.

Tom Friedhof: But it doesn't have to be just Drupal. It can be anything. One of the things ... We've always been a web shop, but we're building a native app right now. We're building it with React Native. It's amazing how we're tying these services that started off on the web, still using web technologies to build a native experiences on a mobile device, tying it back into a Sympfony application. It's just amazing, as developers. This is one of the powers and the benefits of Drupal, is it can act as that content store or as that integration piece that these different systems can interact with.

Jordan Ryan: I think there's something to be said about how, for a while now, Drupal's community has wanted to get off the island. I think that's led a lot by how agencies have needed to get off the island in order to start integrating all of these different systems. One of the, I think, opportunities Drupal has is that with all these integrated systems, there really isn't a leading technology that you could consider a decision engine. When you're talking about a unified customer experience across many different disparate platforms, Alexa, your iOS apps, there really isn't a central hub. You either have to build one, or you have to start thinking about your digital strategy with Drupal as that hub that's going to make that happen. There's some things that I think will need to happen, as far as Drupal's infrastructure, in order to make that more accessible, talking to all of these different IoT apps. That has some performance implications if you have a lot of traffic. It's no longer just page views. You've got a lot of personalized content. I think that there's going to be opportunity there as Drupal continues to evolve.

Chris Stauffer: In my mind, I think that that evolution towards using Drupal as a central hub ... I actually think that started happening a while back. In Drupal 7, we've been building, for probably a good two or three years, the concept of having a Drupal website, and then having all of your content available via web services that then get ingested into an iOS app. We haven't done a React Native one yet, but we have done a couple systems where we used like Swift on the front-end and just normal Android development, where we were basically hitting a lot of those Drupal web services. I think the movement towards 8 is making those services more of the focus, but I think that that's kind of been there for a while now. I think that corporate executives are just starting now to understand, as you put it earlier, that it actually is a central hub, that I have a content management system that's going to manage my content, but everything else is just a display medium, whether it is a mobile app, Facebook ... You know what I mean? There's a million different ways of consuming content.

Jordan Ryan: It's the octopus controller controlling all the knobs.

Chris Stauffer: Right. Look at the Hollywood Reporter. The Hollywood Reporter has millions of content objects, but you can look at it through normal web; you can look at it through mobile web; you can look at it through an Android device. You can look at it through anything. If there's a device, I'm sure the Hollywood Reporter's got a new way of looking at it that way. You kind of see what I mean? I think that-

Jordan Ryan: Oculus Rift?

Chris Stauffer: I don't think we have that one yet.

Sep 19 2016
Sep 19

Ron Huber: It does go back to what Dries said earlier this year, about who adopts Drupal, right. I mean, they're truly ambitious web projects. We don't do a lot of medium or small size businesses -so I'm probably not the one to comment on this- but I'm not sure that even my company should be using Drupal, if it weren't that we were a Drupal company. Right? It is an investment that you really want to take if you're going to take on a big, ambitious project and do something spectacular on the web.

Jordan Ryan: It is. I think that something that we're going to see, at least at the crossroads of Drupal, is that there is going to be an opportunity to bring this very rich content architecture and interconnect it with more content services and more displays of that content across all of these different interfaces. That's, I think, the opportunity that Drupal has, as long as it keeps staying there. I mean, small businesses are going to say, "Ah, you know, if I do a WordPress site, is it going to hook up to my app? Is it going to hook up to point-of-sale system? Do I have to have all this stuff in all these separate locations? Or can I just ...?" You know, as a small business owner, can I just have that little turnkey app that connects with everything? I think that Drupal, especially with RESTful services and GraphQL is going to keep moving in that direction.

Ron Huber: True.

Chris Stauffer: Unless you're like a pure play company, though, that's specifically making a content play. I think Drupal might honestly be even a little bit overkill for some small businesses. Don't get me wrong. I've drunk the Drupal Kool-Aid, and I'm drunk on it. If you're a site that's a small business, I've actually had more success with WordPress. Like a $10K site or a $15K site, that's way more WordPressy. For me, I've kind of stopped selling Drupal to small businesses, because I can't figure out how to give them the ROI that they need. Whereas, a small business can get a good ROI on WordPress. I can't seem to get a $20K project done well in Drupal; whereas, 20K in WordPress, you'll get a $#@!-load of stuff. Whereas $20K in Drupal, you're not going to get as much cool stuff. You know what I mean? When you start moving ... You said earlier that you can plug into a lot of different things. I think that is where Drupal starts to become a lot better, when you start getting to that enterprise market that needs 80 different things hooked into it. Those larger enterprise organizations ... Yeah, Drupal is the greatest thing ever when it comes to that. I've got to be honest. I used to sell small businesses Drupal. I stopped, because I can't figure out how to make those projects profitable.

Jordan Ryan: And when we push marketing, we don't really push Drupal either. It's when there's an opportunity for some kind of stronger digital strategy that needs to have… either we say, "Okay, within a year you're doing XY&Z, and you're going to need to be there." If you're carrying around your website ... It's just a business card you pull out of your pocket and say, "Hey, check me out."

Chris Stauffer: That's WordPress.

Ron Huber: If you're looking for a platform, and you've got a lot of third-party integration. You have a lot of hooks, etc. We were talking earlier about other technologies, and I think this is where part of this conversation started. We're, as a company, branching off. Drupal is something that we do really well. We've been doing it for 10 years. We're going to continue doing it, but we're adding other technologies. We are looking, because we're now more of a market player. We do more in the healthcare business than we do in the Drupal business.

Jordan Ryan: Sell solutions.

Ron Huber: Solutions, right. We've added the JAVAs and the JSONs, and all these things that we have to add for our clients that are looking for more from a software developer than a Drupal developer. Right? I hear a lot of shops doing that.

Tom Friedhof: We've found ourselves getting off the island, as they say. We were primarily a Drupal shop when we started ActiveLAMP. Now, we're doing Symfony, Laravel, and Node. This is where I think it's super valuable with the direction that Drupal 8 is going, is the fact that now I can put my Symfony developers on Drupal projects, because they don't have to learn this ... I mean, Drupal 7 is great, but they don't have to learn this Drupal 7 thing.

Chris Stauffer: Do you have a lot of Symfony guys that hadn’t touched Drupal?

Tom Friedhof: That's correct, yeah. We have Symfony developers on staff that now we're comfortable putting on Drupal projects.

Jordan Ryan: Because Laravel?

Tom Friedhof: They come from Symfony and Laravel, that's correct. It's all the design patterns.

Ron Huber: That's good for us all, right? Now we're expanding, and we're offering other services, and it's going to help our clients, and we're going to use Drupal where Drupal should be used. That's the key.

Jordan Ryan: I think there's a strategy too ... For the Drupal community at least ... I heard in the keynote earlier today, presenting Drupal as this solution for content architecture, right. There are all these other rich applications that can key into it. Also with the enriched developer experience, you have many more opportunities to bring people in who are working in other frameworks, right. There's a general maturity of open source ecosystem now. I think Dries posted an article a few weeks ago from TechCrunch. It was talking about how all of these open source communities are starting to mature as professional services are developing around them. You see all these ... You know, MongoDB, Drupal, and there's this arc of adoption for the enterprises that kind of slowly have moved into this space. There's going to be a new wave of even more open-source softwares, based off of Symfony, Mautic, the open source marketing automation tool we use is based off of Symfony. It just reached version 2.0. I think it's just going to gain on the market share for marketing automation.

Ron Huber: I still see Drupal as being the Switzerland of it all, right. It sort of fits in the middle there and plays nice with everything. That's what makes it so powerful, right? You can write another application somewhere, and integrate it into Drupal very easily.

Jordan Ryan: Where else does it make sense to really invest a lot of processing power into a central location or to handle all your business operations?

Ron Huber: They want to own it all, right?

Jordan Ryan: Right.

Ron Huber: Every other piece wants to own it. They want to have all the access. They want to have all the control over the users. That's the one thing that allows Drupal to keep on expanding, is that we own some of it, we can do some of it. Maybe it's just Drupal sitting next to a Salesforce application, because we're just going to use it for content. We can make it look like Salesforce, we can integrate it no problem, and everybody on the internal project has no idea. They think they're in their application, because Drupal plays nice. That’s a powerful tool.

Sep 09 2016
Sep 09

Tom: Hey guys, we're here at DrupalCamp LA 2016, at UC Irvine. We've got a few shop owners here. We just wanted to have a conversation and kind of give you a look at what we're doing at each of our shops. I think that first, let's go around the table and introduce ourselves.

Ron: Ron Huber from Achieve Internet. We're in San Diego and Los Angeles, do a lot of healthcare and entertainment. This is my tenth year at DrupalCamp LA; been sponsoring it forever and been at a number of these camps over the years.

Chris: My name is Chris Stauffer. I'm the CEO of Stauffer. We are a Los Angeles based development firm. We focus on Drupal as well as a bunch of other open-source technologies. We work with a lot of LA's usual suspects, specifically in the entertainment industry and a lot of stuff in higher ED lately, as well.

Tom: My name's Tom Friedhof. I work with ActiveLAMP. We also are LA based; we primarily work with higher education. We work with non-profits and enterprise organizations. Tinker around mostly in Drupal but we're also doing some Symfony, Laravel, and Node as well.

Jordan: My name's Jordan Ryan. I'm the CTO of Facet Interactive based out of Manhattan Beach. We've been doing a lot of enterprise solutions architecture for Drupal Development. We've started to move into marketing intelligence using some Symfony based applications with Mautic. Small shop, looking to grow.

Tom: Sweet. I think, here we are, it's August 2016, Drupal 8 is finally out, so I kind of want to start this conversation by asking the question, "Drupal 7 or Drupal 8? Where are you guys at?"

Ron: I tell you, this is really an interesting conversation. Of course, we want to do Drupal 8. Right? It's the new thing we want to get out there, but we just had a big entertainment company - and we're working on the site now - we pitched them Drupal 8. We really tried to get them to go and they said, "No."

It wasn't ready, the modules weren't ready. When it comes out, when it launches in early December, Drupal's going to take a hit for it because it's a big name. It should be in Drupal 8 and it's going to be recognized. People are going to say, "Why wasn't that in Drupal 8?"

We did our best to pitch them and they just wouldn't go. They were all pros. These are people that have been building Drupal sites for ten years for Disney and DC Comics. These guys knew what they were doing and decided not to go that route.

Now, we have four other projects that are Drupal 8 sites in-house that they said, "Okay, whatever you guys say is what we're going to do." It's still the debate of whether we should be doing that. I think that's the problem right now, is if the community doesn't feel that it's ready, then we have a marketing issue more than we have a technology issue.

Tom: Yeah, true.

Chris: Yeah. For me personally, I actually don't think that Drupal 8 is quite ready for prime time yet. The primary reason for that is, when I'm going out and gathering requirements and things like that, I know right now every single module that's out ... Well, I shouldn't say I know every module for Drupal 7. That would be a ridiculous statement to make.

Ron: You're a smart guy, but-

Chris: I know - let's just call it the majority of the Drupal modules that matter - so when I'm architecting a system, I know for a fact that I'm not going to step on any landmines. I know what Drupal 7 can do and I know what it can't do, and it's very easy for me to scope and frame projects using Drupal 7.

With Drupal 8, most of the projects that I've gone through, I've scoped it kind of with that same mindset of Drupal 7. Then I'll have the boys go check it out and see if this is a possible candidate for Drupal 8, and it keeps failing because basically there are specific contrib modules and things like that that simply aren't ready to hang.

In my mind, once all those contrib modules have caught up and I stop running into, "Oh, yeah, sorry Chris, we're not going to be able to do that because of X," that's when we'll start actively selling Drupal 8 projects. Right now, for me, the biggest one is making sure we can do features because I want all my code checked in-

Ron: I heard you say that earlier.

Chris: You know? When I can check all my code in, then in my mind I'm ready for big boy enterprise development, but if I can't check my code in, then it's not ready for prime time.

Tom: We're kind of in the same boat as you, Ron. Same boat as you as well, Chris. We've been developing our Drupal 8 since last year; we've been pitching Drupal 8 to our clients since late last year, and you know Drupal 8 was released in November, but we're running into that issue as well, where organizations just don't want to take that leap and develop on Drupal 8.

We have managed to be able to convince some organizations to take the leap, and this is where I'm kind of the fence, or I guess divided, with building on Drupal 7 or Drupal 8. I have a technical background, I'm a developer, and developing on Drupal 8 is just a much better experience.

It's like, why would you develop on this old system? It just doesn't feel good to develop on Drupal 7. I feel that it's our jobs as development agencies to encourage and push that innovation to our clients, our prospective clients and say, "You know what? If we build this on Drupal 7, yeah, it'll take maybe four, five, six months to develop this, but six months from now, when Drupal 7 is really being put on the shelf because Drupal 8 is the new thing, how are you going to feel?"

You know, it's a little bit more of an investment up front to develop on Drupal 8? Our team, we're engineered focused, we're all engineers on our team. Well, not all of us, but the majority of us are engineers, and so if something doesn't exist, we'll code it. We won't necessarily upgrade-

Ron: You're investing in it. I totally get where you're at, because we did that from 5 to 6, and 6 to 7, and got burned a few times where-

Chris: It screwed up my budgets on my projects when I did that.

Ron: Yeah, absolutely.

Chris: I ended up losing money when I did that, because I promised a client I'm going to do X, and then it doesn't do X, but I'm on the hook for X, so now I have to go build X, and not get paid for X.

Tom: The key word Ron used there was investing. That's an investment, you know?

Ron: You have to be in the right position to be able to do that, right? It has to be the right client; some clients are, "Hey, that deadline is September 1st, and you've got to make that." Well, you can't make that decision in Drupal 8, because you don't know what landmine you're going to come into.

Jordan: I think to that point, what minefield are you running through? If you can see where the mines are and you can say to the client, - this is something that we do when we do the architecture phase - if we can see where those mines are and say, "Do you want to tackle that, defuse that?"

If they say no, well then I say, "We either have to push this back a few months until it's ready to be taken on, or we need to talk about what's your horizon for upgrading this once we do this the first time?"

Ron: Sure.

Jordan: Then they kind of get a different framework to make the decision, because you don't just present them with the opportunity of this update, you have a future web project and that's a different paradigm. You have to use in order to sell, sell on the investment.

Ron: I don't think we've done a good job as Drupal is talking about what the cool things are coming out in 8. It's just the new version, right? There are some great technology that is in future releases, and if we start talking about how you're not going to get that in 7, and how in Drupal 8 if you make the investment or you take that, maybe we shrink down this initial project, but in six months or a year from now when this is released, then you're really going to be able to take advantage of mobile, you're really going to be able to take advantage, your SEO is going to improve.

Whatever that is? That's a different pitch that I haven't used yet, and it's something that I learned today.

Tom: Yeah.

Chris: I liked Tom what you said earlier, in something. It was an angle that I hadn't actually thought of until I just heard you say it, which is actually playing on the fact that there's not going to be a transition from 7 to 8, if you go with 8 now, because I know one of the Drupal pain points that I've had in the past is, I upgraded a client from Drupal 6 to 7, and proceeded to charge the client a $150,000 to do that.

They kind of looked at me like, "Oh my god, you are crazy. You're going to charge- wait, no. But Chris, I don't get anything new?" I'm like, "You get security patches now." They're like, "Dude, you literally just took me for a hundred and fifty grand, and I don't even get anything like new."

Ron: We just won't do that anymore. I don't need even pitch that.

Chris: It's getting to a point where I didn't even think about what you had brought up, to say like, "Okay, well, maybe if you did go all the way up to Drupal 8, then really, on a total cost of ownership, you're going to dodge that hundred and fifty large transition from 7 to 8.

Jordan: Yeah, and that's a great term, "total cost of ownership."

Chris: I hadn't thought of that angle, but that's a good one.

Tom: I think there's another piece of that as well. They may be dodging the hundred fifty thousand dollar upgrade, but if you’re going to early adopt, as modules come into the ecosystem, you know you're developing custom code to fill in the gaps where the contrib system hasn't caught up to date yet.

Chris: Sure.

Tom: As those modules come into the ecosystem, there will be some of that budget that probably should be allocated to actually getting those on to the new systems. For example, panels, or panelizer, those are just now coming into the Drupal 8 ecosystem.

You're not going to completely get away from that hundred fifty thousand dollar upgrade, you may have to use some of that budget to actually get your stuff up to snuff, because you're developing so early.

Ron: If you have a Drupal 6 site, your site is outdated. You're not running your business correctly; I don't care what- there's no way it's responsive, there's no way you're adopting mobile at all. You're way behind.

I think those companies that are just worried about whether the technology's ready or not, are looking at it wrong. Their websites are unproductive, whether they like it or not. I mean, if you're a small company, maybe it's not-

Jordan: I think that comes back to a shift in how enterprises and small businesses and medium sized businesses are looking at their digital strategy. You have a platform that you develop, how are you planning to sustain it, right? If you're not planning to sustain it and keep it up to date, then you're probably just thinking in the near term of, "Hey, there's this new thing called the web. I need to get up on it."

Ron: Works for some people.

Jordan: Which is how some people still think. They're like, "Oh, I need to put out a new print catalog," because that's the old regime of advertising: updating my brochure, not this active, organic application that has more exposure than things like that.

Ron: Those companies, they're doing their thing but that's where the marketing comes in. If they're not actively marketing or they're using their old ways, what are you going to do for them? Those aren't who we're talking about anyways, right? You're investing as much money into your website-

Jordan: It's an advocacy that you have to make, is going to the client and saying, "Hey, are you thinking about that upgrade strategy?" There is a support to this, right? You get a lot of people who will come to you and say, "I want a site. I'm starting my business." These are people who haven't thought about the long-term strategies sometimes.

Aug 25 2016
Aug 25

You guys asked so here we are. In my last video I answered the question, "Should I build on Drupal 7 or Drupal 8?" I mentioned in that video that we have a site currently in production on both Drupal 7 and Drupal 8." To the end user browsing the site it's completely transparent, they have no idea they're hitting two different Drupal instances. Today I'm going to show you how we're pulling this off.

How are we doing this? The quick answer is we've got an Nginx server sitting in front our two Drupal sites, proxying traffic based on URL. It's either Drupal 7 or Drupal 8. In a nutshell, basically what is happening is the user makes a request to your website URL which is pointed at our proxy server, the Nginx server, which has a list of rules of specific paths to push to Drupal 8 or otherwise push to Drupal 7. Let me show you a quick and dirty demo so you can see this in action. We're going to get in the weeds so put your Dev Ops hat on. I'm going to do this all on my local host using docker and a nice little utility called ngrok which is used basically to tunnel back into my local system from the public internet. This is not how we're doing it in production but the idea is the same.

All right, so I'm going to get two environments set up here. I'm going to use Drush Quick Drupal to get my Drupal 8 environment set up. Basically, this will get me a running version of Drupal 8.1.7 on my local machine. While that's doing that over here I'm going to get our activelamp.com site set up locally as well. This is a Jekyll site and so basically all we have to do is type in grant serve to see that site locally. All right, so there's our Active Lamp website running on our local host port 8080 and our Drupal 8 site is coming up and so now we have our Drupal 8 site hosting on port 888.  What's nice about Drush Quick Drupal if you're not familiar with it is it actually builds you a Drupal 8 site, downloads all the dependencies, and then launches this browser as you can see here on port 8888. It's using the internal php server to actually serve this.

We're going to go ahead and log in. Don't need to set a password because we're not really going to be using this site. For demonstration purposes I'm going to set up a piece of content here and I'm going to call this the what we do page. I'm going to give it a URL path of what we do. If I save and publish this we'll see the what we do page on our Drupal 8 installation. Basically, what we're going to do is we're going to set it up so that when you click on what we do on our Drupal 7 site it'll actually route to the Drupal 8 site with an Nginx proxy that we talked about.

If you haven't used Docker there's a utility called Docker Machine that basically will spit up a machine for you to run Docker on since you can't run it natively on the MAC yet. You can just type in Docker Machine Create, I'm going to do this in Virtual Box, and this is going to be our defaults machine. Okay, so our Virtual Box Docker Machine is set up and so now it asks us to run this command Docker Machine environment default and so we're going to go ahead and run that command. That basically exported some environment variables and then it says to run this eval code right here and so we're going to run that in our prompt as well. Now we should be able to use the Docker command and see what is on that Docker Machine, and so we have no containers currently running on that Docker Machine.

Okay, so we're going to create our Nginx proxy now and let me just go to where I have it pre-configured.  All right, so let's look at what this pre-configured proxy looks like. In this directory I have a Docker file and that Docker file basically reads from the Nginx image that's on Docker hub and then we have one step in there which is basically copying this activelamp.conf file into the conf.d directory for Nginx so that it gets read when Nginx has started. Let's look at the activelamp.conf file.

Basically, I have some paths set up here. Basically, what we're saying is when you hit the root path we're going to go to some URL, when we hit the what we do path we're going to go to some URL, basically pass to this URL. Then I have a couple Drupal paths, the core and the sites path. Basically anything that hits core or sites or anything underneath that will go to some URL.  The way we're going to actually do this is we're going to set up this Docker file to ... Or this configuration file to point at these local installations that I have set up. This is where ngrok comes into play. You can basically Google ngrok and install that. Go here, download ngrok, and then you'll be able to have tunnels coming back to your local host.

I'm going to show you guys how to actually use this once you have this installed. I'm going to go back to my home directory and type in ngrok http, and I want ... This is running on port 8080 and I want to map port 8080 back to my machine. This is basically updating or creating a tunnel out on the internet so that if I hit this URL right here it'll hit port 8080 on my local host. Then the Drupal 8 site that we want is on port 8888 and so let's open up another tab, ngrok8888. Okay, so now I have two URLs exposed to the internet that are coming back to my local machine. This is my Drupal 7 URL. In this case it's actually going to be the Jekyll URL because we would be migrating from Jekyll up to Drupal 8. We'll come back into here and we're going to say anything ... Or everything is going to come to this URL, but the what we do page we want to actually put on Drupal 8.

Let's come over here to our Drupal 8 URL, which is the other tunnel that we opened up, and grab that. Then we also need to redirect the core in the sites directory to Drupal 8. Those paths don't exist in the Jekyll site so we're safe by just directing all traffic underneath these paths, so the Drupal 8 site so let's go ahead and save this. All right, now let's go ahead and start up our proxy. If we come back here to our D8 proxy we have a command to actually build this image, and that is Docker build. We're going to call this thid D8 proxy under My name space and the Docker file is in this directory. This is going through the built process pulling down the Nginx image and then it's going to be to add in our one little command and create an image for us that we can then use and run with Docker.

All right, so now our Docker image is created, we're going to actually run this. The way you do that is Dockerrun. We're going to expose a port to our machine and that's going to be 8787 and we're going to map that to port 80 within the Docker container which is what Nginx is listening on. The image that we want to run is the image that we just built which is called Tomfriedhof/d8proxy. All right, so now our image is running, let's see if this actually works.

If we hit ... Oh, one thing is this is running in a Docker Machine, we need to know what the Docker Machine IP address is. I'm going to open up another tab here and I'm going to type in Docker Machine IP. The default Docker Machine is running on this IP address so if I hit that IP address on port 8787 which is what we exposed we can see that it is actually hitting our local installation, all of the styles aren't there. Then if I hit the what we do you can see that what we do is hitting the Drupal 8 path that we specified in our proxy so that's the proxy in action.

The reason why we had to use ngrok here is we couldn't access these containers directly from within our Docker container and so this is actually going out through the ngrok service and coming back into our local machine. Typically what you would do is the IP address of the proxy is what you would set up in DNS. We're going to mimic that right now by basically creating a host record for this IP address. Let's go back to our terminal, open up a new window, get into our hosts file, and we're going to say this IP address is activelamp.com.  Let's save that.  Now if I hit activelamp.com, let's just go to the root path, we'll see that we're hitting our web server internally that's running on this tab right here. And then if we were to click on what we do our Nginx proxy should forward us to Drupal 8.  There you can see that the what we do path that we clicked on in the menu does in fact go to the Drupal 8 site.

Let's go back to the home page. Basically, as you build out your site on Drupal 8 you can update these paths and just point different paths pointing to Drupal 8 so that you can update incrementally. That's how you do it. An Nginx proxy server sitting in front of two installations directing traffic. In production we actually host the proxy in AWS managed by Ansible. I hope you found this information helpful, talk to you next time.

Jul 30 2016
Jul 30

On a recent project, we had to create multiple sitemaps for each of the domains that we have setup on the site. We came across some problems that we had to resolve because of the nature of our pURL setup.


  • We want all of the front pages from each subdomain to be added to the sitemap and we are able to set the rules for them on the XMLSitemap settings page.
  • We want to make sure that the URLs that we are adding to the other pages no longer show up in the main domain's sitemap.


1) Only On The Primary Domain

The XML sitemap module only creates one sitemap based on the primary domain.

2) Prefixes not Distinguished

Our URLs for nodes are setup so that nodes can be prefixed with our subdomain (pURL modifier) and XMLSitemap doesn't see our prefixes as being different sites. At this point, all nodes are added to every single domain's sitemap.

3) URL Formats

Our URLs are not in the correct format when being added to the sitemap. Our URLs should look like http://subdomain.domain.org/*, however, because we are prefixing them, they show up as http://domain.org/subdomain/*. We want our URLs to look like they are from the right sub-domain and not all coming from the base domain.


We were able to add the ability to create sitemaps for each of the 15 domains by adding the XMLSitemap domain module. The XLMSitemap domain module allows us to define a domain for each sitemap, generate a sitemap and serve it on the correct domain.

We added xmlsitemap-dont-write-empty-element-in-xml-sitemap-file-2545050-3.patch to prevent empty elements from being added to the sitemap.

Then we used a xmlsitemap_element_alter inside of our own custom module that looks something like this:

function hook_xmlsitemap_element_alter(array &$element, array $link, $sitemap) {
  $domain = $sitemap->uri['options']['base_url'];
  $url_parts = explode('//', $domain);
  $parts = explode('.', $url_parts[1]);
  $subdomain = array_shift($parts);

  $current_parts = explode('/', $link['loc']);
  $current_prefix = array_shift($current_parts);

  $modifiers = _get_core_modifiers();

  if (in_array($subdomain, array_keys($modifiers))) {
    if ($current_prefix != $subdomain && $current_prefix != '') {
      $element = array();
        return $element;
    else {
      $pattern = $current_prefix . '/';
      $element['loc'] = $domain . str_replace($pattern, '', $link['loc']);
  else {
    if (in_array($current_prefix, array_keys($modifiers))) {
      $element = array();
      return $element;

function _get_core_modifiers() {
  if (!$cache = cache_get('subdomains')) {
    $result = db_query("SELECT id, value FROM {purl} WHERE provider = 'og_purl_provider'")->fetchAllAssoc('value');
    cache_set('subdomains', $result, 'cache', time() + 86400);
    return $result;
  else {
    return $cache->data;

If you have any questions, suggestions, feel free to drop a comment below!

Jul 14 2016
Jul 14

Back in December, Tom Friedhof shared how we set up our Drupal 8 development and build process utilizing Docker. It has been working well in the several months we have used it and worked within its framework. Within the time-span however, we experienced a few issues here and there which led me to come up with an alternative process which keeps the good things we like and getting rid of/resolving the issues we encountered.

First, I'll list some improvements that we'd like to see:

  1. Solve file-syncing issues

    One issue that I keep running into when working with our development process is that the file-syncing stops working when the host machine powers off in the interim. Even though Vagrant's rsync-auto can still detect changes on the host file-system and initiates an rsync to propel files up into the containers via a mounted volume, the changes do not really appear within the containers themselves. I had a tough time debugging this issue, and the only resolution in sight was to do a vagrant reload -- it's a time-consuming process as it rebuilds every image and running them again. Having to do this every morning when I turn on my laptop at work was no fun.

  2. Performant access to Drupal's root

    Previously, we had to mount Drupal's document root to our host machine using sshfs to explore in it, but it's not exactly performant. For example, performing a grep or ag to search within files contents under Drupal 8's core takes ~10 seconds or more. Colleagues using PhpStorm report that mounting the Drupal root unto the host system brings the IDE to a crawl while it indexes the files.

  3. Levarage Docker Compose

    Docker Compose is a great tool for managing the life-cycle of Docker containers, especially if you are running multiple applications. I felt that it comes with useful features that we were missing out because we were just using Vagrant's built-in Docker provider. Also with the expectation that Docker for Mac Beta will become stable in the not-so-distant future, I'd like the switch to a native Docker development environment as smooth as possible. For me, introducing Docker Compose into the equation is the logical first-step.

    dlite just got into my attention quite recently which could fulfill the role of Docker for Mac before its stable release, but haven't gotten the chance to try it yet.

  4. Use Composer as the first-class package manager

    Our previous build primarily uses Drush to build the Drupal 8 site and download dependencies and relegating the resolution of some Composer dependencies to Composer Manager. Drush worked really well for us in the past and there is no pressing reason why we should abandon it, but considering that Composer Manager is deprecated for Drupal 8.x and that there is already a Composer project for Drupal sites, I thought it would be a good idea to be more proactive and rethink the way we have been doing Drupal builds and adopt the de-facto way of putting together a PHP application. At the moment, Composer is where it's at.

  5. Faster and more efficient builds

    Our previous build utilizes a Jenkins server (also ran as a container) to perform the necessary steps to deploy changes to Pantheon. Since we were mostly deploying from our local machines anyway, I always thought that perhaps running the build steps via docker run ... would probably suffice (and it doesn't incur the overhead of a running Jenkins instance). Ultimately, we decided to explore Platform.sh as our deployment target, so basing our build in Composer became almost imperative as Drupal 8 support (via Drush) on Platform.sh is still in beta.

With these in mind, I'd like to share our new development environment & build process.

1. File & directory structure

Here is a high-level tree-view of the file structure of the project:

├── Vagrantfile
├── Makefile
├── .platform/ 
│   └── routes.yaml
├── bin/ 
│   ├── drupal*
│   ├── drush*
│   └── sync-host*
├── docker-compose.yml 
├── environment 
├── src/ 
│   ├── .gitignore
│   ├── .platform.app.yaml 
│   ├── Dockerfile
│   ├── LICENSE
│   ├── bin/ 
│   │   ├── drupal-portal*
│   │   └── drush-portal*
│   ├── composer.json
│   ├── composer.lock
│   ├── custom/
│   ├── phpunit.xml.dist
│   ├── scripts/
│   ├── vendor/
│   └── web/ 
└── zsh/ 
    ├── zshrc
    ├── async.zsh
    └── pure.zsh

2. The Vagrantfile

Vagrant.configure("2") do |config|

  config.vm.box = "debian/jessie64"
  config.vm.network "private_network", ip: ""

  config.vm.hostname = 'activelamp.dev'

  config.vm.provider :virtualbox do |vb|
    vb.name = "activelamp.com"
    vb.memory = 2048

  config.ssh.forward_agent = true

  config.vm.provision "shell",
    inline: "apt-get install -y zsh && sudo chsh -s /usr/bin/zsh vagrant",
    run: "once"

  config.vm.provision "shell",
    inline: "[ -e /home/vagrant/.zshrc ] && echo '' || ln -s /vagrant/zsh/zshrc /home/vagrant/.zshrc",
    run: "once"

  config.vm.provision "shell",
    inline: "[ -e /usr/local/share/zsh/site-functions/prompt_pure_setup ] && echo '' || ln -s /vagrant/zsh/pure.zsh /usr/local/share/zsh/site-functions/prompt_pure_setup",
    run: "once"

  config.vm.provision "shell",
    inline: "[ -e /usr/local/share/zsh/site-functions/async ] && echo '' || ln -s /vagrant/zsh/async.zsh /usr/local/share/zsh/site-functions/async",
    run: "once"

    config.vm.provision "shell",
      inline: "sudo sed -i '/^GITHUB_OAUTH_TOKEN=/d' /etc/environment  && sudo bash -c 'echo GITHUB_OAUTH_TOKEN=#{ENV['GITHUB_OAUTH_TOKEN']} >> /etc/environment'"

  config.vm.provision :docker

  config.vm.provision :docker_compose, yml: "/vagrant/docker-compose.yml", run: "always", compose_version: "1.7.1"

  config.vm.synced_folder ".", "/vagrant", type: "nfs"
  config.vm.synced_folder "./src", "/mnt/code", type: "rsync", rsync__exclude: [".git/", "src/vendor"]

Compare this new manifest to the old one and you will notice that we reduce Vagrant's involvement in defining and managing Docker containers. We are simply using this virtual machine as the Docker host, using the vagrant-docker-compose plugin to provision it with the Docker Compose executable and having it (re)build the images during provisiong stage and (re)start the containers on vagrant up.

We are also setting up Vagrant to sync file changes on src/ to /mnt/code/ in the VM via rsync. This directory in the VM will be mounted into the container as you'll see later.

We are also setting up zsh as the login shell for the vagrant user for an improved experience when operating within the virtual machine.

3. The Drupal 8 Build

For now let's zoom in to where the main action happens: the Drupal 8 installation. Let's remove Docker from our thoughts for now and focus on how the Drupal 8 build works.

The src/ directory cotains all files that constitute a Drupal 8 Composer project:

├── composer.json
├── composer.lock
├── phpunit.xml.dist
├── scripts/
│   └── composer/
├── vendor/ # Composer dependencies
│   └── ...
└── web/ # Web root
    ├── .htaccess
    ├── autoload.php
    ├── core/ # Drupal 8 Core
    ├── drush/
    ├── index.php
    ├── modules/
    ├── profiles/
    ├── robots.txt
    ├── sites/
    │   ├── default/
    │   │   ├── .env
    │   │   ├── config/ # Configuration export files
    │   │   │   ├── system.site.yml
    │   │   │   └── ...
    │   │   ├── default.services.yml
    │   │   ├── default.settings.php
    │   │   ├── files/
    │   │   │   └── ...
    │   │   ├── services.yml
    │   │   ├── settings.local.php.dist
    │   │   ├── settings.php
    │   │   └── settings.platform.php
    │   └── development.services.yml
    ├── themes/
    ├── update.php
    └── web.config

The first step of the build is simply executing composer install within src/. Doing so will download all dependencies defined in composer.lock and scaffold files and folders necessary for the Drupal installation to work. You can head over to the Drupal 8 Composer project repository and look through the code to see in depth how the scaffolding works.

3.1 Defining Composer dependencies from custom installation profiles & modules

Since we cannot use the Composer Manager module anymore, we need a different way of letting Composer know that we may have other dependencies defined in other areas in the project. For this let's look at composer.json:

    "require": {
        "wikimedia/composer-merge-plugin": "^1.3",
        "activelamp/sync_uuids": "dev-8.x-1.x"
    "extra": {
        "merge-plugin": {
          "include": [

We are requiring the wikimedia/composer-merge-plugin and configuring it in the extra section to also read the installation profile's composer.json and one's that are in custom modules within it.

We can define the contrib modules that we need for our site from within the installation profile.


  "name": "activelamp/activelamp-com-profile",
  "require": {
    "drupal/admin_toolbar": "^8.1",
    "drupal/ds": "^8.2",
    "drupal/page_manager": "^[email protected]",
    "drupal/panels": "~8.0",
    "drupal/pathauto": "~8.0",
    "drupal/redirect": "~8.0",
    "drupal/coffee": "~8.0"

As we create custom modules for the site, any Composer dependencies in them will be picked up everytime we run composer update. This replicates what Composer Manager allowed us to do in Drupal 7. Note however that unlike Composer Manager, Composer does not care if a module is enabled or not -- it will always read its Composer dependencies and resolve them.

3.2 Drupal configuration

3.2.1 Settings file

Let's peek at what's inside src/web/settings.php:

$settings['container_yamls'][] = __DIR__ . '/services.yml';

$config_directories[CONFIG_SYNC_DIRECTORY] = __DIR__ . '/config';

include __DIR__ . "/settings.platform.php";

$update_free_access = FALSE;
$drupal_hash_salt = '';

$local_settings = __DIR__ . '/settings.local.php';

if (file_exists($local_settings)) {

$settings['install_profile'] = 'activelamp_com';
$settings['hash_salt'] = $drupal_hash_salt;

Next, let's look at settings.platform.php:

if (!getenv('PLATFORM_ENVIRONMENT')) {

$relationships = json_decode(base64_decode(getenv('PLATFORM_RELATIONSHIPS')), true);

$database_creds = $relationships['database'][0];

$databases['default']['default'] = [
    'database' => $database_creds['path'],
    'username' => $database_creds['username'],
    'password' => $database_creds['password'],
    'host' => $database_creds['host'],
    'port' => $database_creds['port'],
    'driver' => 'mysql',
    'prefix' => '',
    'collation' => 'utf8mb4_general_ci',

We return early from this file if PLATFORM_ENVIRONMENT is not set. Otherwise, we'll parse the PLATFORM_RELATIONSHIPS data and extract the database credentials from it.

For our development environment however, we'll do something different in settings.local.php.dist:

$databases['default']['default'] = array(
    'database' => getenv('MYSQL_DATABASE'),
    'username' => getenv('MYSQL_USER'),
    'password' => getenv('MYSQL_PASSWORD'),
    'host' => getenv('DRUPAL_MYSQL_HOST'),
    'driver' => 'mysql',
    'port' => 3306,
    'prefix' => '',

We are pulling the database values from the environment, as this is how we'll pass data in a Docker run-time. We also append .dist to the file-name because we don't actually want settings.local.php in version control (otherwise, it will mess up the configuration in non-development environments). We will simply rename this file as part of the development workflow. More on this later.

3.2.2 Staged configuration

src/web/sites/default/config/ contains YAML files that constitute the desired Drupal 8 configuration. These files will be used to seed a fresh Drupal 8 installation with configuration specific for the site. As we develop features, we will continually export the configuration entities and place them into this folder so that they are also versioned via Git.

Configuration entities in Drupal 8 are assigned a universally unique ID (a.k.a UUID). Because of this, configuration files are typically only meant to be imported into the same (or a clone of the) Drupal site they were imported from. The proper approach is usually getting hold of a database dump of the Drupal site and use that to seed a Drupal 8 installation which you plan to import the configuration files into. To streamline the process during development, we wrote the drush command sync-uuids that updates the UUIDs of the active configuration entities of a non-clone site (i.e. a freshly installed Drupal instance) to match those found in the staged configuration. We packaged it as Composer package named activelamp/sync_uuids.

The complete steps for the Drupal 8 build is the following:

$ cd src
$ composer install
$ [ -f web/sites/default/settings.local.php ] && : || cp web/sites/default/settings.local.php.dist web/sites/default/settings.local.php
$ drush site-install activelamp_com --account-pass=default-pass -y
$ drush pm-enable config sync_uuids -y
$ drush sync-uuids -y
$ drush config-import -y

These build steps will result a fresh Drupal 8 installation based on the activelamp_com installation profile and will have the proper configuration entities from web/sites/default/config. This will be similar to any site that is built from the same code-base minus any of the actual content. Sometimes that is all that you need.

Now let's look at the development workflow utilizing Docker. Let's start with the src/Dockerfile:

FROM php:7.0-apache

RUN apt-get update && apt-get install -y \
  vim \
  git \
  unzip \
  wget \
  curl \
  libmcrypt-dev \
  libgd2-dev \
  libgd2-xpm-dev \
  libcurl4-openssl-dev \

ENV PHP_TIMEZONE America/Los_Angeles

RUN docker-php-ext-install -j$(nproc) iconv mcrypt \
&& docker-php-ext-configure gd --with-freetype-dir=/usr/include/ --with-jpeg-dir=/usr/include/ \
 && docker-php-ext-install -j$(nproc) gd pdo_mysql curl mbstring opcache

RUN curl -sS https://getcomposer.org/installer | php
RUN mv composer.phar /usr/local/bin/composer
RUN echo 'export PATH="$PATH:/root/.composer/vendor/bin"' >> $HOME/.bashrc

RUN composer global require drush/drush:8.1.2 drupal/console:0.11.3
RUN $HOME/.composer/vendor/bin/drupal init
RUN echo source '$HOME/.console/console.rc' >> $HOME/.bashrc

RUN echo "date.timezone = \"$PHP_TIMEZONE\"" > /usr/local/etc/php/conf.d/timezone.ini
ARG github_oauth_token

RUN [ -n $github_oauth_token ] && composer config -g github-oauth.github.com $github_oauth_token || echo ''

RUN [ -e /etc/apache2/sites-enabled/000-default.conf ] && sed -i -e "s/\/var\/www\/html/\/var\/www\/web/" /etc/apache2/sites-enabled/000-default.conf || sed -i -e "s/\/var\/www\/html/\/var\/www\/web/" /etc/apache2/apache2.conf

COPY bin/drush-portal /usr/bin/drush-portal
COPY bin/drupal-portal /usr/bin/drupal-portal

COPY . /var/www/
WORKDIR /var/www/

RUN composer --working-dir=/var/www install

The majority of the Dockerfile should be self-explanatory. The important bits are the provisioning of a GitHub OAuth token & adding of the {drupal,drush}-portal executables which are essential for the bin/{drush,drupal} pass-through scripts.

Provisioning a GitHub OAuth token

Sometimes it is necessary to configure Composer to use an OAuth token to authenticate on GitHub's API when resolving dependencies. These tokens must remain private and should not be committed into version control. We declare that our Docker build will take github_oauth_token as a build argument. If present, it will configure Composer to authenticate using it to get around API rate limits. More on this later.

DrupalConsole and Drush pass-through scripts

Our previous build involved opening up an SSH port on the container running Drupal so that we can execute Drush commands remotely. However, we should already be able to run Drush commands inside the container without having SSH access by utilizing docker run. However the commands can get too lengthy. In fact, they will be extra lengthy because we also need to execute this from within the Vagrant machine using vagrant ssh.

Here are a bunch of scripts that makes it easier to execute drush and drupal commands from the host machine:

Here are the contents of bin/drush and bin/drupal:

#!/usr/bin/env bash
cmd="docker-compose -f /vagrant/docker-compose.yml  run --no-deps --rm server drupal-portal $@"
vagrant ssh -c "$cmd"
#!/usr/bin/env bash
cmd="docker-compose -f /vagrant/docker-compose.yml  run --no-deps --rm server drush-portal $@"
vagrant ssh -c "$cmd"

This allow us to do bin/drush to run Drush commands and bin/drupal ... to run DrupalConsole commands, and the arguments will be pass over to the executables in the container.

Here are the contents of src/bin/drupal-portal and src/bin/drush-portal:

#!/usr/bin/env bash
/root/.composer/vendor/bin/drupal --root=/var/www/web $@
#!/usr/bin/env bash
/root/.composer/vendor/bin/drush --root=/var/www/web $@

The above scripts are added to the container and is essential to making sure drush and drupal commands are applied to the correct directory.

In order for this to work, we actually have to remove Drush and DrupalConsole from the project's composer.json file. This is easily done via the composer remove command.

The docker-compose.yml file

To tie everything together, we have this Compose file:

version: '2'
      context: ./src
        github_oauth_token: ${GITHUB_OAUTH_TOKEN}
      - /mnt/code:/var/www
      - composer-cache:/root/.composer/cache
    env_file: environment
      - mysql:mysql
      - 80:80
    image: 'mysql:5.7.9'
    env_file: environment
      - database:/var/lib/mysql

  database: {}
  composer-cache: {}

There are four things of note:

  1. github_oauth_token: ${GITHUB_OAUTH_TOKEN}

    This tells Docker Compose to use the environment variable GITHUB_OAUTH_TOKEN as the github_oauth_token build argument. This, if not empty, will effectively provision the Composer with an OAuth token. If you go back to the Vagrantfile, you will see that this environment variable is set in the virtual machine (because docker-compose is run under it) by appending it to the /etc/environment file. All it needs is that the environment variable is present in the host environment (OS X) during the provisioning step.

    For example, it can be provisioned via: GITHUB_OAUTH_TOKEN= vagrant provision

  2. composer-cache:/root/.composer/cache

    This tells Docker to mount a volume on /root/.composer/cache so that we can persist the contents of this directory between restarts. This will ensure that composer install and composer update is fast and would not require re-downloading packages from the web every time we run. This will drastically imrpove the build speeds.

  3. database:/var/lib/mysql

    This will tell Docker to persist the MySQL data between builds as well. This is so that we don't end up with an empty database whenever we restart the containers.

  4. env_file: environment

    This let us define all environment variables in a single file, for example:


    We just configure each service to read environment variables from the same file as they both need these values.

We employ rsync to sync files from the host machine to the VM since it offers by far the fastest file I/O compared to the built-in alternatives in Vagrant + VirtualBox. In the Vagrantfile we specified that we sync src/ to /mnt/code/ in the VM. Following this we configured Docker Compose to mount this directory into the server container. This means that any file changes we make on OS X will get synced up to /mnt/code, and ultimately into /var/www/web in the container. However, this only covers changes that originate from the host machine.

To sync changes that originates from the container -- files that were scaffolded by drupal generate:*, Composer dependencies, and Drupal 8 core itself -- we'll use the fact that our project root is also available at /vagrant as a mount in the VM. We can use rsync to sync files the other way -- rsyncing from /mnt/code to /vagrant/src will bring file changes back up to the host machine.

Here is a script I wrote that does an rsync but will ask for confirmation before doing so to avoid overwriting potentially uncommitted work:

#!/usr/bin/env bash

echo "Dry-run..."


diffs="$(vagrant ssh -- rsync --dry-run --itemize-changes $args | grep '^[>)"

if [ -z "$diffs" ]; then
  echo "Nothing to sync."
  exit 0

echo "These are the differences detected during dry-run. You might lose work.  Please review before proceeding:"
echo "$diffs"
echo ""
read -p "Confirm? (y/N): " choice

case "$choice" in
  y|Y ) vagrant ssh -- rsync $args;;
  * ) echo "Cancelled.][dfLDS]\|^\*deleted'";;

We are keeping this generic and not bake in the paths because we might want to sync arbitrary files to arbitrary destinations.

We can use this script like so:

$ bin/sync-host --recursive --progress --verbose --exclude=".git/" --delete-after /mnt/code/ /vagrant/src/

If the rsync will result in file changes on the host machine, it will bring up a summary of the changes and will ask if you want to proceed or not.


We are using make as our task-runner just like in the previous build. This is really useful for encapsulating operations that are common in our workflow:

	bin/sync-host --recursive --progress --verbose --delete-after --exclude='.git/' /mnt/code/ /vagrant/src/

	vagrant rsync-auto

	vagrant rsync

	vagrant ssh -- docker-compose -f /vagrant/docker-compose.yml build

	vagrant ssh -- docker-compose -f /vagrant/docker-compose.yml up -d

	vagrant ssh -- docker-compose -f /vagrant/docker-compose.yml run --no-deps --rm server composer --working-dir=/var/www install

	vagrant ssh -- docker-compose -f /vagrant/docker-compose.yml run --no-deps --rm server composer --working-dir=/var/www update --no-interaction

	@vagrant ssh -- cat /mnt/code/composer.lock

install-drupal: composer-install
	vagrant ssh -- '[ -f /mnt/code/web/sites/default/settings.local.php ] && echo '' || cp /mnt/code/web/sites/default/settings.local.php.dist /mnt/code/web/sites/default/settings.local.php'
	-bin/drush si activelamp_com --account-pass=secret -y
	-bin/drush en config sync_uuids -y
	bin/drush sync-uuids -y
	[ $(ls -l src/web/sites/default/config/*.yml | wc -l) -gt 0  ] && bin/drush cim -y || echo "Config is empty. Skipping import..."

init: install-drupal
	yes | bin/sync-host --recursive --progress --verbose --delete-after --exclude='.git/' /mnt/code/ /vagrant/src/

	ssh ></span>@ssh.us.platform.sh

The Drupal 8 build steps are simply translated to use bin/drush and the actual paths within the virtual machine in the install-drupal task. After cloning the repository for the first time, a developer should just be able to execute make init, sit back with a cup of coffee and wait until the task is complete.

Try it out yourself!

I wrote the docker-drupal-8 Yeoman generator so that you can easily give this a spin. Feel free to use it to look around and see it in action, or even to start off your Drupal 8 sites in the future:

$ npm install -g yo generator-docker-drupal-8
$ mkdir myd8
$ cd myd8
$ yo docker-drupal-8

Just follow through the instructions, and once complete, run vagrant up && make docker-restart && make init to get it up & running.

If you have any questions, suggestions, anything, feel free to drop a comment below!

Jun 15 2016
Jun 15

The web development community can have a long list of requirements, languages, frameworks, constructs and tools that most companies or bosses want you to know.

This list may not include everything you need to know including PHP, HTML, CSS, responsive web development principles, and Drupalisms. Here is the list of some of the important skills, concepts, and tools that we think you should know as a beginner Drupal developer.

1. Version Control

Every developer should have some experience with version control and versioning. Version control is an essential part of the Drupal community. Versioning allows for Drupal projects to be easily managed, maintained and contributed in a uniform manner. Version control will also most likely be used in-house to manage each client project as well.

2. Command Line Interface (CLI)

It isn't necessary to be a CLI Ninja, however being able to work comfortably using a CLI is very important. One of the advantages to using a CLI is the ability to be more productive. You can quickly automate repetitive tasks, perform tasks without jumping from application to application, and the ability to use tools like Drush to perform tasks that would normally require you to navigate 3 or more mouse clicks to accomplish.

3. Package Managers

Using package managers is important to the installation of Drupal. Whether it is installing Sass or Bootstrap from node or Drush from composer, it is important to know how package managers work and exactly what you are running before running commands on your computer.

4. Contributing Back

An important part of the Drupal community is contributing back to projects and core. When you find an issue, such as something that just doesn't seem to work correctly, or you would like to implement a functionality to Drupal, you should think about giving back to the community. If you find an issue on an existing project or core, check to see if there is an existing ticket on that project. If there isn't, you can create one, and if you can debug it and resolve the issue you can contribute a patch to that issue. If you don't know exactly how to debug the issue you can have an open conversation with other developers and maintainers to help resolve the issue. Contributing and interacting in the community moves Drupal forward.

5. CSS Preprocessors

Within the last couple of years, there has been a movement to CSS preprocessors to add a programmatic feel to CSS2 and CSS3. There are some that are against preprocessors because it adds a little more overhead to a project. Whether you use them or not, you may have a client or framework that uses one that you might need to be familiar with how to use a preprocessor.

6. A Framework

Within the Drupal community, there is often talk of headless Drupal. We have seen some interesting ideas come from the adopters of headless Drupal. Headless Drupal setups usually use a framework for the front-end. It may be Angular, Angular 2, Backbone, Ember or something different, however, most of the frameworks have two things in common, they are often written in Javascript and almost always make use of templating.

7. Templating

It is important to know the principles of templating so that you can easily pick up and learn new frameworks. Whether it is Mustache, Twig, Jade, or the templating syntax from within Angular, there are similarities between the syntax and the principles can be applied to each of the languages that will allow you to quickly step from one to the next with a smaller learning curve.

8. Basic Debugging

Debugging a problem correctly can save you valuable time by getting you directly to the cause of an issue instead of looking over each line of code one by one. It is essential to know how to do basic debugging when working with Drupal. Sometimes the error messages can give you enough information, other times it is necessary to step into Devel or XDebug and step through the project to find the exact location where the code is not working correctly so that you can start to solve the problem.

9. Unit Testing / Code Testing

Testing your own code is important. When it comes to code testing you have many options, from TTD and BDD you can write unit tests to cover your classes, linting to make sure you are writing "good", standardized code. Linting can be helpful for writing code that others can easily navigate and sets up some best practices for you to follow.

10. A CMS

When starting with Drupal, it might be good to have familiarity with a CMS platform before jumping in. There are some advantages to knowing the constructs of other CMS platforms and being familiar with how to work within a platform. However, when working with Drupal it is important to think about the way Drupal works and not be stuck in the way other CMS platforms accomplish goals.


As a web developer, it is important to know many concepts and technologies. Many companies will not require you to know everything, do everything and be a jack-of-all-trades. In technology, there are so many new tools, frameworks, and languages coming out daily that it is impossible to stay on top of them all. It is far better to get a good base understanding of core web concepts that can be applied to multiple languages, tools, and technologies and then specialize.

Did I miss something you feel is important? Is there something you would like to have seen on the list? Leave a comment below.

Jun 07 2016
Jun 07

Continuing from Evan's blog post on building pages with Paragraphs and writing custom blocks of content as fields, I will walk you through how to create a custom field-formatter in Drupal 8 by example.

A field-formatter is the last piece of code to go with the field-type and the field-widget that Evan wrote about in the previous blog post. While the field-type tells Drupal about what data comprises a field, the field-formatter is responsible for telling Drupal how to display the data stored in the field.

To recap, we defined a hashtag_search field type in the previous blog post whose instances will be composed of two items: the hashtag to search for, and the number of items to display. We want to convert this data into a list of the most recent n tweets with the specified hashtag.

A field-formatter is a Drupal plugin, just like its respective field-type and field-widget. They live in /src/Plugin/Field/FieldFormatter/ and are namespaced appropriately: Drupal\\Plugin\Field\FieldFormatter.

namespace Drupal\my_module\Plugin\Field\FieldFormatter;

use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Form\FormStateInterface;

class HashtagFormatter extends FormatterBase

    public function viewElements(FieldItemListInterface $items, $langcode)
        return array();

We tell Drupal important details about our new field-formatter using a @FieldFormatter class annotation. We declare its unique id; a human-readable, translatable label; and a list of field_types that it supports.

The most important method in a field-formatter is the viewElements method. It's responsibility is returning a render array based on field data being passed as $items\Core\Field\FieldItemListInterface>.

Let's look at the code:

use Drupal\my_module\Twitter\TwitterClient;
use Drupal\my_module\Twitter\TweetFormatter;


    protected $twitter;

    protected $formatter;


    public function viewElements(FieldItemListInterface $items, $langcode)
        $element = [];

        foreach ($items as $delta => $item) {

            try {

                $results = $this->twitter->search($item->hashtag_search, $item->count);

                $statuses = array_map(function ($s) {
                    $s['formatted_text'] = $this->formatter->toHtml($s['text'], $s['entities']);
                    return $s;
                }, $results['statuses']);

                if (!empty($statuses)) {
                    $element[$delta]['header'] = [
                        '#markup' => '

#'</span> . $item->hashtag_search . '

]; } foreach ($statuses as $status) { $element[$delta]['status'][] = [ '#theme' => 'my_module_status', '#status' => $status ]; } } catch (\Exception $e) { $this->logger->error('[:exception]: %message', [ ':exception' => get_class($e), '%message' => $e->getMessage(), ]); continue; } } $element['#attached']['library'][] = 'my_module/twitter_intents'; return $element; } ...

See https://github.com/bezhermoso/tweet-to-html-php for how TweetFormatter works. Also, you can find the source-code for the basic Twitter HTTP client here: https://gist.github.com/bezhermoso/5a04e03cedbc77f6662c03d774f784c5

Custom theme renderer

As shown above, each individual tweets are using the my_module_status render theme. We'll define it in the my_module.module file:

function my_module_theme($existing, $type, $theme, $path) {
  $theme = [];
  $theme['my_module_status'] = array(
    'variables' => array(
      'status' => NULL
    'template' => 'twitter-status',
    'render element' => 'element',
    'path' => $path . '/templates'

  return $theme;

With this, we are telling Drupal to use the template file modules/my_module/templates/twitter-status.twig.html for any render array using my_module_status as its theme.

Render caching

Drupal 8 does a good job caching content: typically any field formatter is only called once and the resulting collective render arrays are cached for subsequent page loads until the Drupal cache is cleared. We don't really want our Twitter block to be cached for that long. Since it is always great practice to keep caching enabled, we can define how caching is to be applied to our Twitter blocks. This is done by adding cache definitions in the render array before we return it:

      public function viewElements(...)


        $element['#attached']['library'][] = 'my_module/twitter_intents';
        $element['#cache']['max-age'] = 60 * 5;

        return $element;

Here we are telling Drupal to keep the render array in cache for 5 minutes. Drupal will still cache the rest of the page's elements how they want to be cached, but will call our field formatter again -- which pulls fresh data from Twitter -- if 5 minutes has passed since the last time it was called.


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