Upgrade Your Drupal Skills

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

See Advanced Courses NAH, I know Enough
Oct 02 2019
Oct 02

Search is a key feature in web experience, and for a lot of people, it's the go-to method to find content. We use search countless times a day on our smartphones in various contexts. And yet, when we're building out websites, search is often an afterthought that we don't spend much time on. Search gets added to the laundry list of site features, like meta tags and social media links.

Drupal Core Search

Drupal is fantastic at managing content. It gives you loads of flexibility when it comes to building out your information architecture and categorizing content. But we often don't set aside a lot of time to build out a customized search UI to find that content. At the end of a project, you might just turn on the core search module and call it done. And then we find out that people use Google to search our website.

Drupal's core search functionality hasn't changed much in the last 10 years, and is lacking features that users expect. It can be slow, and it doesn't correct for misspellings or allow you to prioritize results. Search should make your content easy to find, and make your site more engaging for users. Over the years, we've worked on lots of websites that integrate with Solr, to provide an enterprise-level search engine on top of Drupal. But setting up Solr takes time, and can be tricky if you don't have a lot of time to set it up, or the know-how to configure your server.

Why Cludo?

We recently decided to add search to evolvingweb.ca, and decided to try out Cludo. It's a "search as a service" tool that allows you to add on a search interface to an existing site. Kind of like you'd add Disqus, or Google Analytics. It was pretty easy for our developers to set up Cludo. Besides some challenges setting up search of the French language side of our site, particularly searches with UTF8 characters, the setup was straight-forward and only took a few hours to add.

The immediate advantage is that you don't have a lot of setup time for a search that just works how users expect. But after it was all set up, I realized that there are a lot of extra features that you get that we wouldn't normally take the time to configure for a basic search:

  • Autocomplete - start typing the title of a node and it'll autocomplete
  • Customize the index - you can pick and choose what's searchable and what's not
  • Analytics - you can see who searches for what, giving non-technical users visibility on how users search for content
  • Boosting - you get nice defaults for results ordering, but you can also customize the criteria to prioritize certain types of pages or certain criteria
  • Machine learning - an add-on feature that does the boosting and changes the autocomplete ordering for you based on user behaviour
  • Easy-to-use interface - non-technical users can update all the settings through Cludo's UI

Cludo analytics interface

Before you ask, yes there's a module for that! The Cludo module was released a couple weeks ago. It's still in development, but you can try it out. You just have to add your Cludo account number and key, and it provides a search form block that you can place on the page.

Here are some examples of websites using Drupal:

Open Source vs. Paid Third-Party Service

So what's the catch? Cludo isn't a free service, it comes with a $200/month price tag for most websites. And it will cost more than that if your site has more than 20,000 pages or you want bells and whistles like document search, machine learning or searching private content. There are discounts for non-profits and educational organizations.

There's a trend towards using third-party services for everything from marketing automation tools to comments and now search. I know a lot of Drupal developers prefer to use open source tools as much as possible. I think the great thing about third-party tools is that it gives us another option. We can offer our clients a way to get a search interface up-and-running quickly, without a lot of up-front development time. It gives the end-user something that's easier to configure.

On the other hand, for a large website with a lot of content, we might want more control over the functionality and costs. And for an intranet, we might want more control over where the data is stored. If we have a lot of site installs, Cludo could start to become very pricey. In these cases, using Search API would be a better option. But for lots of use cases, when that "instant" quality is the priority, Cludo is a great option, to make sure your content is discoverable and that your users can find it.

What does Drupal 7 End of Life mean for your business?

Aug 20 2019
Aug 20

Adventures with Drupal's Layout Builder

Jun 28 2019
Jun 28
May 03 2019
May 03

Last month, our team was busy acquiring new members, preparing for DrupalCamp London which took place the first week of March, and getting everything set for our Ljubljana team’s move into new offices. Still, this didn’t prevent us from writing some really fun blog posts. Here’s a quick recap of our posts from February in case you missed any.

Druplicon.org: In Search of the Lost Druplicon

The first post we wrote in February presented druplicon.org, a site for exploring the different variations of the famous Druplicon, and the story behind the site’s creation. The idea originated with one of our developers and the site itself was also built by our developers as part of their onboarding project

Visitors to the site get to explore the various Druplicons in a fun and educational way, and they also get the chance to submit any icons that they can’t find in the inventory. But the true highlight of this blog post is the origin story behind druplicon.org - by now, you’re probably eager to know about it, so, give it a read!

Read more

Interview with Taco Potze: Why Drupal was the CMS of choice and what potential open source has

We continue with one of our Community Interviews. We managed to get some very interesting insights on Drupal and the Dutch Drupal community from Taco Potze and his team. 

With Taco being a co-founder of several notable projects in the Drupalverse (GoalGorilla, Open Social and the blockchain-based THX), our talk with him was a really great and thought-provoking one. We really enjoyed getting to know more about his projects and his views on the potential of open source. Thanks for sharing your thoughts on Drupal with us, Taco!

Read more

Interview with Amber Matz: How will Drupal's greatest challenge shape its future?

Next up, we had another post from the Community Interviews series. We talked with Amber Matz, who is Production Manager and Trainer at Drupalize.me; but her involvement with Drupal does by no means end there. Among other notable roles, she is also involved with organizing the Builder Track for the upcoming ‘Con.

According to Amber, the greatest challenge that Drupal will face is intrinsically connected to one of its greatest advantages - its scalability. The main obstacle going forward will thus be gaining more insight into our user base and consequently having more articulated and differentiated tools for the easy acquisition of Drupal.

Read more

Top 6 SEO Modules for Drupal 8

We finished February’s blog posts with a list of useful SEO modules for Drupal 8. Drupal is a CMS that is very SEO-friendly, and its prolific community has provided a range of modules that can vastly improve the SEO of any Drupal site.

By using the modules from this list, you'll no longer have to worry about things such as manually creating proper URLs or taking care of dead links. If you’re dissatisfied with the SEO ranking of your Drupal 8 site, then these are the perfect modules to get you started on stepping up your SEO game.

Read more

We hope you enjoyed revisiting our blog posts from February. Stay tuned for more!

Sep 19 2018
Sep 19

The aim of this is to give European citizens more control over their personal data and to update the laws to reflect the world we live in now. This includes laws around personal data and privacy and consent across Europe.

With GDPR organizations will have to ensure personal data is gathered legally and under strict conditions. Organizations will also be tasked with protecting it from misuse and exploitation, as well as to respect the rights of data owners. This will ultimately place legal obligations on a company to maintain records of personal data and how it is used, placing higher level of legal liability should they be breached.

What is considered personal data?

Whether you’re based in Europe or a global organization that is potentially collecting data from European users you should be aware of the data you’re collecting that fall under the scope of GDPR. Under existing legislation names, addresses, and photos are considered personal data but with GDPR this extends to IP addresses, genetic data, and biometric data which could be processed to uniquely identify an individual.

How can I update my site to comply?

There are plenty of checklists to follow in order to make sure your site is in compliance but we’re going to cover a change you’ve probably already noticed from many of the sites you visit daily. You’ve probably already guessed it, those wonderful cookie acceptance pop-ups!

There are a few modules that strive to make short work of the cookie acceptance process but after testing several, the module I’ve found to be the best in terms of ease of use and support from other modules that create cookies is EU Cookie Compliance  (available for Drupal 7 and 8). This module will provide you with a fully customizable banner that can be displayed at the top or bottom of the window and has full support for responsive and multilingual sites. Consent can be given actively by opt-in or out-out, or inferred automatically by clicking any link on the site. I recommend going with the opt-in option. Optionally you will be able to use this in conjunction with the GeoIP module to display the banner for EU users only.

Before we get the module downloaded and installed you’ll want to identify any other modules that currently set cookies for users on your site and make sure they are updated to the latest release. The most common of these is going to be Google Analytics. There was a bug recently where if a user had previously accepted but had returned and revoked consent the cookie would incorrectly remain, so you’ll want to make sure that one is updated.


First you’ll want to create a privacy policy for you site which you can later link in your banner.

Download and enable the module from either https://www.drupal.org/project/eu_cookie_compliance or with drush or composer.

- Drush

drush dl eu_cookie_compliance

- Composer

composer require drupal/eu_cookie_complaince

Head over to /admin/settings/eu-cookie-compliance on drupal 7 or /admin/config/system/eu-cookie-compliance on Drupal 8 to setup permissions for displaying the pop-up to certain roles and select the type of consent, for your site to be complaint you will want to use the “opt-in”.

Cookie Compliance

Customize your banner, You can setup the text you want displayed for the initial banner, thank you banner, and withdraw consent banner. You will also be able enter hex values to colour your banner or if you’re on Drupal 7 you can use the colorpicker module to style your banner.

Cookie Complaince

If you’d like to limit the banner to EU users you can download and install GeoIP, once you have GeoIP setup you can simply enable the option on the admin page.

If you  are using Google Analytic you will find a setting on their page that once enabled will only place cookies when the user has accepted.

Once you’ve filled out everything simply save and head to the front page, you will be greeted by you brand new consent banner.

Cookie Consent

For developers who have created modules that set cookies there is a javascript function that will return TRUE if the user has given his consent

Mar 14 2018
Mar 14

iMIS, the third-party system, is a product offered by Advanced Solutions International who describe it as a “cloud-based engagement management system” that “fuses database management and web publishing”. For the purpose of the new BCPhA website, iMIS would function as an external user database and management system that needed to be integrated into Drupal. Users would need to be manageable from the iMIS backend, login using their username/email and password from iMIS, and pull their iMIS data into the website (including roles).

This integration needed to be seamless for the end user - not providing any unexpected roadblocks during their day to day use of the site. It also needed to integrate into Drupal in the most “Drupaly” way possible on the backend - as developers, we wanted to manage this integration, and the data we needed for our users, through existing Drupal methods and procedures.


After internal research and discussions, and discussions with the people at Visual Antidote, we landed on the solution of using a SAML protocol to handle this integration. SAML (Security Assertion Markup Language), in short, is an open standard for exchanging authentication details between two parties: an identity provider, or IDP, (which would be iMIS in our case) and a service provider, or SP (the Drupal site). The standard is well documented and supported which means that we wouldn’t have to reinvent the wheel, and many libraries and modules exist to support the integration. It works by sending a set of predetermined XML messages between the two parties along with redirects so a user can start on the Drupal site, be sent to an iMIS login page where they can securely authenticate, and then be returned to the Drupal site where they can automatically be logged in.

This means the final user experience of logging in and using the site can all appear seamless; unless the user is paying close attention, they won’t be aware they were just sent between two different websites to login. SAML additionally supports the ability to send user data along from the IDP to the SP when a user logs in, meaning everything needed for the Drupal site can be passed along and saved on the Drupal site in a normal, Drupal way. Lastly, but certainly not least, a SAML authentication approach was something that Visual Antidote (the iMIS consultant we worked with) was able to build and support.

Using SAML also provided a good layer of security to the integration, which is very important as we’re dealing with access to the site and user’s data. By being an open standard that’s been around for over 10 years, we can stand on the collective testing and knowledge of other developers that have built and have been using the standard in the wild. This provides much more confidence than if we were to build something entirely new and custom ourselves.


With all the items checked off our list of requirements for a potential integration, the next step was looking into integrating the SAML process into our new Drupal 8 based site, as well as integrating with the IDP that Visual Antidote was building for us to integrate with iMIS. Since the new site was being built in Drupal 8 we were a little limited in contributed modules that we could use for adding SAML support to the site. After looking at all the options, including developing a custom module ourselves to handle everything, we settled on the SAML Authentication contributed module. We chose this module as it didn’t require any large other libraries to be installed, was being actively developed, and supported everything we needed without too many modifications.

The SAML Authentication module essentially acts as a bridge between the OneLogin PHP SAML toolkit and Drupal, so it’s built on a solid foundation instead of trying to handle everything and remain secure on it’s own. It also provides events that a custom module can subscribe to to act on a user whenever they log in. These events allowed us to save user data that iMIS would be sending in the SAML response right onto the Drupal user (and even assign roles to the user based on role data that iMIS would send). To get logout functionality with this module, we did need to use the most recent development release, but no issues have arisen from it thus far.

There was a single, small problem with the module that we did run into. BCPhA had a requirement that on certain pages, if the user was directed to login they would return to the page they started on after they had successfully logged in. The SAML Authentication module seemingly supported this by including the url to return the user to in the destination parameter when they were directed to login, however that parameter is reserved by Drupal for other things. Simply changing the parameter to something else fixed the issue and enabled the functionality (a patch to fix the issue can be found here).

In the end we were very impressed with how this module worked in our integration. Because it was built with extensibility in mind, we didn’t have to make any major changes to the module at all and kept our custom functionality separate. Additionally, because it used the OneLogin PHP SAML library, we could easily test our integration and custom functionality by having OneLogin take the place of an IDP. This let us develop our end of things while the iMIS consultants developed their connector.

Our integration with the Visual Antidote built iMIS system itself was also very smooth. For two companies developing two systems in parallel that then have to communicate with each other and support users seamlessly, everything went extremely smoothly. Once both systems were finished, the final integration between the Drupal site and the iMIS system generated no major issues, only small tweaks.

The new BCPhA site has been up and running since November 21st 2017 and as of this writing has 2408 unique users that are logging in and using the site through the SAML integration with iMIS. Without a drop in service and minimal issues, the SAML functionality on the new site and the joint development of the implementation between Fuse and Visual Antidote has been a great success.

More information about Fuse's build of BCPhA's new site can be found here.

Aug 03 2017
Aug 03

What’s lived on from Webform

All the basics are there to get you started. The easy to use element builder that we’re all familiar with lives on although it’s received some much needed UI upgrades. The ability to set up conditional logic, send out emails, review and export submission are all in there with some nice extras to boot.

What’s new


With handler you can now send submission results to external URL’s which mean building custom forms for CRM submissions is easier than ever to set up.


You can now jump over to the test tab and have your form pre-populated with values for easy testing and even use devel generate to handle multiple test submissions in one go.

Lots of advanced fields and widgets

Signatures, Likert, range sliders, CodeMirror, star ratings, the list goes on and the best part is you’re not having to scour Drupal.org hoping that someone has built a module to add in this functionality.

Webform Fields

Wizard Pages

I almost snuck this into the advanced fields section but I’ve decided it needs it’s own call out. No more contrib modules, we can now natively create multi-step forms in webform. It’s incredibly intuitive and a godsend for those massive forms we have to build from time to time.

Webform Wizard

Field input masks

While I don’t find these entirely useful for things like phone numbers that vary from country to country there are probably some use cases where these will come in handy to keep your submissions clean and concise. I’d say they fall into the nice extra category as some will definitely get more mileage out of them than others.

CSS and JS on a per element basis

Got that one field you want to do something a little flashy with? Now you can load your own custom CSS/JS on a per form basis or per element basis.

In browser source editing

Prefer setting up your forms in code? You can now edit the yaml markup directly on the admin page and even copy elements over from an existing form in a flash.


This is a feature I really like and should be standard on all core module heck even contrib developers should take notice. If you jump over to the Add-ons tab you are greeted with a list of all the add-on modules currently available for webforms complete with categorization and some helpful descriptions. This is something I think is really important for increasing the usability of drupal modules, it takes the guess work out of finding the features I want and it’s a great way for module developers to rubber stamp projects they feel are adding value to their work which is a win win for everyone.


Tutorial videos

This is something that really display the extra mile jrockowitz is going to get developers familiar with the module. If you’re having trouble figuring out how to properly configure your forms emails for instance, simply click the “Watch Video” link at the top of the page and you’ll be greeted with a handy video that goes over the ins and outs of that feature.


Layout elements

Webform comes equipped with flexbox elements that easily help you get your module on the right track before you ever touch it in your theme. This lets you build some really attractive forms that more often than not need very little theme help which is a huge plus when handing the site over to a hands-on client. With very little effort and a bit of training you can give your client the power to manage their own form creation from start to finish.

Translatable fields

Developers of multilingual sites rejoice, no more form duplication! Fields are now translatable, need i say more?

Final Thoughts

Whether you’re a form builder or developer who want to get in and create forms with code this module is a dream to work with. It’s incredibly powerful and scalable and will have you building beautiful forms with ease in no time. Stepping back for a second I would like to declare webform the gold standard for contrib modules in Drupal 8, it’s what every one of us should strive for when developing modules for the community. Every aspect of it is well thought out and goes the extra mile, it’s the full package and it makes webforms fun again. Play around with it, have some fun and look forward to your next project that needs a form.

P.S. If you run into jrockowitz give him a high five because he’s put an incredible amount of work into this project.

Jun 29 2017
Jun 29

Our specific situation involved a main Flexslider slideshow with a smaller navigation slideshow below that displayed 5 images, all created using the Flexslider Drupal module. We only implemented lazyloading on the main slideshow as the images in the nav slideshow were very small.

The basic idea of "lazyloading" the images in the Flexslider slideshow is a pretty straight forward one: for the images we don't want to load, we would move the image path out of the "src" attribute and place it in a "data-src" attribute on the element instead. Then, when you wanted to load an image, you would just swap the value in the "data-src" attribute into the "src" attribute. There were essentially two steps to implementing this: first we needed to modify the output of the Flexlider module so the image path was in this new location, and second we needed to perform the actual swapping of the path as needed.

Modifying Flexslider output

This was fairly straightforward as we change how the image path was being output just by tweaking the theme_flexslider_list function. Adding to the default function, we would first check if the slider was a nav or not,

// We only lazy load the main slideshows, not thumbnails.
$lazy_load = (!isset($vars['settings']['optionset_overrides']['asNavFor'])) ? TRUE : FALSE;

and if it wasn't, we would simply modify how the element was formatted

foreach ($items as $i => $item) {
  // Only load the first 5 and the last image. The rest will be lazy loaded.
  if ($lazy_load && ($i > 4) && ($i < (count($items) -1))) {
    preg_match('/src="https://www.fuseinteractive.ca/blog/lazyloading-flexslider-drupal/([^"]*)"/i', $item['slide'], $src);
    $item['slide'] = str_replace($src[0], 'data-src="https://www.fuseinteractive.ca/blog/lazyloading-flexslider-drupal/' . $src[1] . '" src="https://www.fuseinteractive.ca/blog/lazyloading-flexslider-drupal/"', $item['slide']);

We're loading the first 5 images as those will all be directly accessible via the nav slider in our case. The last image needs to be loaded for Flexslider to work properly and you obviously need the first image to be loaded as well.

Loading the images

At this point we were extremely lucky that the Flexslider module developer has included some JS events that we could hook our behaviour into to load the images as we needed. The event we'll be making use of is the "before" event which fires right before any movement happens in the slider. We will need to load images whenever someone advances the main slideshow as-well-as when they adjust the nav.

First we'll deal with the more straightforward movement of the main slider. Whenever the slideshow is moved forward or backward, we will load one slide in the given direction:

$(this).on('before', function(event) {
  var slider = $(event.target).data('flexslider');

  if (!(slider.asNav)) {
    var slides = [];
    if (slider.direction == 'next') {
      slides = [$(slider.slides).find('[data-src]')[0]];
    else if (slider.direction == 'prev') {
      slides = [$(slider.slides).find('[data-src]').last()];
    $.each(slides, function(index, slide) {
      $(slide).prop('src', $(slide).data('src'))

Now, there is one gotcha in this. In our case, if a user moves the slideshow backwards from while on the first slide, it loops back around to the last slide in the set. When this happens, we need to make sure the last 5 slides are loaded as each of them are suddenly reachable via the nav slideshow:

else if (slider.direction == 'prev') {
  // If we wrapped around from the first slide, we need to load
  // a full navigations worth of images.
  if (slider.animatingTo == slider.last) {
    slides = $(slider.slides).find('img').slice((slider.last -5), slider.last);
  else {
    slides = [$(slider.slides).find('[data-src]').last()];

Now we can deal with advancement of the nav slider. Of course, if you don't have a nav slider, you can completely ignore this part (and all the mentions of loading 5 images at a time above). Whenever the nav is moved in either direction, we need to make sure the 5 images it displays are loaded in case the user clicks one of them:

if (slider.asNav) {
  var slides = $('#' + slider[0].id.slice(0, -4) + ' .slides li').not('.clone');

  var shown = slider.visible;
  var index = slider.animatingTo * shown;

  // Load images in main slideshow that correspond to the images
  // just revealed in the thumbnails.
  for (var i = 0; i < shown; i++) {
    var $slide = $('img', slides[index + i]);

    if ($slide.attr('data-src')) {
      $slide.prop('src', $slide.data('src'));

And that's pretty much it for our lazyloading Flexslider! There is one final problem to fix and that is that since a lot of our slides are now hidden, they don't take up any space. This creates a problem when we wrap to the last slide from the first slide as Flexslider will miscalculate the amount of space it has to move to get to the last slide. This can easily be fixed however with some CSS:

.flexslider:first-child .slides img {
  padding-top: 1px;

 This will simply ensure that each image has an actual width for Flexslider to use in its calculations.

And that should give you a lazyloading Flexslider slideshow in Drupal! This will cut the images loaded on page load down to the first and last plus however many are in your nav. Of course, if you don’t have many more than are in your nav you won’t notice much of an improvement, but for large slideshows, the improvement is quite a bit!

Feb 24 2017
Feb 24

User Experience, UX for short, is a very common term used by anyone who has ever been involved in the production of a website or a digital product. UX places emphasis on the end user, and drives design & implementation decisions that benefit those users.

However, for those of us who build websites & products using content management systems & frameworks like Drupal, there is a second layer of UX that often gets overlooked.

The beauty of a CMS like Drupal is that it comes packaged with a relatively nice administrative interface. While this interface allows developers to easily configure and administer a website, its one-size-fits-all approach is also meant to act as an interface for content editors. Without consideration for the people who will ultimately be in charge of managing content, a website often becomes unmanageable.

Drupal affords developers great flexibility in how to translate a design into a functional website. We have nodes, blocks, views, nodequeues, contexts, panels, paragraphs, boxes, menus, mega menus, etc. 

The more complex a website gets, the more reliant it becomes on custom and contributed modules which will often provide their own methods of placing content on a page. This flexibility often spirals into pages where content needs to be managed on multiple screens. Perhaps obvious to some, but not your content editors.

It’s easy for experienced developers to become blind to these potential challenges due to their intimate understanding of Drupal in any particular instance. View headers are clearly managed here; This block lives here, but is managed there; This? This is coded into the template. 

An example of a typical administration menu in Drupal 7

— An example of a typical administration menu in Drupal 7

This understanding doesn’t easily translate to content editors. There are myriad ways to build a Drupal or any CMS driven website so it looks like the designers mockups. The fastest isn’t usually the best. Despite best efforts in clear documentation and in depth training, an editor will still disregard all of the blocks and panels we configured for them, and instead try to lay a page out in the node wysiwyg editor because the admin UX sucks for that content type. 

Poor UX for content editors and administrators ends up impacting everyone from the client to the end user. Content editors will see an increased time and frustration level in producing and editing content. Website maintainers and other vendors will see increased time in updates and management. End users will be presented with an experience that degrades over time. All of which will become a financial burden to the client.

What can be done?

This post isn’t meant to be prescriptive or exhaustive in this regard, but the main message is: Don’t rely on developers to make all the decisions around how content is to be managed. It’s not fair to your clients or the developers (although there are many developers that pride themselves in great UX for their CMS’s). User Experience planning needs to be extended to whatever app you are using to manage content (Drupal, Wordpress, Joomla or API based CMS like Contentful). 

Let the UX team be part of the Content Management planning process. A lot of the issues that tend to arise can be handled early in the process. Use the website wireframes to plan out a build, and to justify some of the implementation decisions that need to be made. Make quick prototypes for admin flow and screens. Focus on ease of use instead of ease of implementation. Create dashboards for frequently managed content. Involve non-dev team members for QA during the development process. A non technical set of eyes will see problems and ask questions that will resolve many potential issues before it ever becomes a problem for the client.

More than anything, it’s important to simply be aware that non-technical people will be managing and editing websites. Having an entire team on board will result in happier editors, and happier clients.

Aug 29 2016
Aug 29

The choice was to either just use a WYSIWYG field and hope no user would change things drastically and break the layout, or to just put everything in code and not allow a user to edit it at all. Neither seemed like good solutions as they couldn't provide both consistent structure and usability.
Different levels of customizability and layout can be achieved through things like views, blocks or panels, but views is a bit of a workaround, blocks repeat the same issue and for any complex layout, panels quickly become unwieldy both in the configuration and in markup.
Two modules aim to aid in this area though: Bean and Fieldable Panels Panes. Both allow for the creation of fieldable entities that can easily be placed around a site, filling in any area where the creation of a new content type doesn'€™t quite make sense.


Bean, which stands for Block Entities Aren't Nodes, allows you to create block types that contain fields. These block types are very similar to content types, except populating them creates a new Bean instead of a new node, and they can then be placed anywhere on the site just like a block. This means you can very easily allow a user to fill out information in a structured and expected way anywhere on your site. After using Bean for the first time I was really impressed and found that it solved a lot of the issues I had with out-of-the-box Drupal.
The creation process couldn'€™t be easier either. To create a new block type, you just name it and start adding fields. The fields available to you are the same as the ones available to your content types, meaning you can really go crazy with the utility of these blocks.

Bean also plays nice with other modules. The display of your Block Types can be customized like the display of a node, including laying them out through Panelizer. Since in the end you just are creating a block, you can place them via any means you would normally place a block. This all means Bean is very flexible and powerful.

Fieldable Panels Panes

I was shown Fieldable Panels Panes (FPP) much more recently. After hearing what the module did, my reaction was “Oh, Bean for panels!”, and that is pretty much what it is. If you'€™re familiar with Bean, you'€™ll be right at home here. The main difference comes in what is produced; Bean gives you blocks while FPP gives you a panels pane.
Creating an FPP is, like a Bean, very similar to creating a new content type and node. You again simply name your new type and then add your fields. The FPP UI for doing this does make it feel a little different as types and the panels you create are all in one place, missing the distinction of 'Structure'€ and '€œContent'€ that both Beans and nodes have, but the principal is the same in all cases. FPP also has the interesting option of limiting a newly created pane to only being placed once on the whole site, which allows for more verbose intention, but can be a bit annoying when a design changes.

As should be expected, FPP utilizes the same fields as nodes and Beans, and plays nicely with panel related modules. In fact, FPP is nicely integrated with panels, allowing you to create new panes from the panel content menus. Once created, an FPP pane can be placed just like any other pane, including being put into a mini-panel to act as a block.


Both FPP and Bean solve the same problem and do so in pretty much the same way, giving you fieldable entities that can be placed around a site. This overlap means that you'€™ll only be using one of these modules on any given site, which begs for a comparison of the two. The problem with doing that though, is that the strengths and weaknesses of each of these modules aren'€™t so much in their own features, but in the features/utilities of the modules they are using; blocks vs. panels. You'€™ll end up choosing to use Bean or FPP not so much depending on the merit of one module over the other, but depending on your preference for laying out a site. If you swear by panels and use it on all of your sites, FPP is probably best for you. However, if you detest panels and would rather place things as blocks through the use of something like Context, you'll probably want to check out Bean.
Of course, both modules are pretty flexible. Bean works fine on a panels site as you can still place blocks easily in panels and, as mentioned, Beans themselves can be panelized. You could similarly place all your FPPs into mini-panels and place them around like blocks, although you might get some strange looks for doing so.

All of this being said though, I would give the edge to Bean as it'€™s probably the more versatile module. This is mainly due to the fact that blocks are a part of Drupal already whereas panels aren'€™t. Bean simply adds to stock Drupal whereas FPP adds to Panels. FPP has the edge if a site has been built using panels due to its integration with the module, but Bean still performs well in a panelized layout. Panels is prepared to work with blocks as they'€™re standard and therefore is prepared to work with Beans. Using something that already exists on every site is a big advantage of Bean and is probably why it is the more popular of the two.

Jul 08 2016
Jul 08

Over 8 months after release and my first D8 site under my belt I can now say I am excited for the future of working with Drupal'€™s freshest release. That being said at this stage in the game the decision to go with D8 should approached with caution. It does what it does well but many of those shiny contrib modules you'€™re used to using just aren'€™t there yet. Unless your team and client are willing to spend the time and money needed to develop or port the missing functionality it might not be a fit for that particular project. If on the other hand you have a project that is the right fit, now is the time to dive right in and take advantage of all it has to offer.

What you should be excited for:

Drupal 8 was a real treat to work on though I definitely went into the project with some reservations. My experience in the past with being the first person in the office to work on a fresh Drupal release has taught me to be cautiously optimistic. Modules you have become attached to and use in almost every project aren'€™t ready or they have a release but it either doesn'€™t work or it's a shell of it'€™s former self while they slowly port features over. This time around there was very few things I was missing and the things that were ended up forcing me to think out of the box or explore other alternatives which was a fun exercise. Aside from a few hiccups Drupal Core just worked which is more than I can say for previous outings. After all is said and done here are a few things I was pleased with and left me wanting to explore further:

The small things

  • Date, link and email fields are now in core, no more turning on a couple modules you use in pretty much every install.
  • Views is also part of core now! Many of the admin pages have been converted to views so feel free to modify them to your hearts content, you will no longer have to install a module to modify that one admin page that you never liked.
  • The modules admin page (Extend) is now more useable, that means you won'€™t need to install Module Filter to search for that one module you want to install.

Blocks, Blocks, Blocks

The blocks system has seen some major love in D8. I always cringe when I log into a D7 site and see that a client has created a block and placed it with the core blocks admin. I will still prefer placing blocks with context because it allows for a pretty complex set of conditions to be met before placing the block. For clients that are a little more hands on the new blocks admin with the addition of Block Visibility Groups will do almost anything you want it to, I like to think of it as context lite. This will allow you to set up groups of blocks that will apply to specific sections of the and provide a dropdown on the admin page to select the group you'd like to edit. Selecting a group will filter the blocks down to what is active in that group.

The Custom Blocks Library is where blocks in D8 really shine, in here you'€™ll find a list of all custom blocks that exist and adds the ability to create block types. Block types are exactly what they sound like, custom types for block that will allow you to add fields just as you would in a content type.This enables you to set up some types and let the client create as many blocks as they'd like within the constraints you've set.

Multilingual Features

Multilingual modules are now in core, this means there's no more messing around because that one field just won'€™t translate properly and it'€™s all done through the admin UI. Taxonomy, menus, blocks, views, fields are all easily translatable.


With the move to Twig we'€™ve got an incredibly powerful theming engine that is much more lightweight than previous Drupal versions. The simple syntax and convenient functions make getting into theming a breeze. Using twig'€™s template inheritance will give you the ability to turn one giant template into smaller more manageable templates. This is great for focusing on structure in the parent and then handling bulkier stuff in child templates. Macros will allow you to create small reusable chunks of code and then reference them wherever you please throughout your theme.

Mobile and Responsive Images

Breakpoints are built into core, this will allow you to define your breakpoints and then reference them to control how your theme and content appear on each platform. With the addition of Responsive Images you will be able to set up all your image styles and then use those breakpoints to trigger what image style is used for each.

Exporting Your Config and Features

D8 now has a built in Config Manager this will allow you to export and import all or some of your config. This is ideal for your simpler sites but it's just a start to what you can achieve with Features.

Features has been rebuilt from the ground up in this release and makes use of the groundwork laid by the Config Manager. What this means is no more messing around with additional Features modules as a workaround to grab specific bits of config that are not exportable. When you install Features it will automatically set up a feature for each bit of config you have. It centers them around your content types and will grab any config related to that content type (views, image styles, permissions, etc.) and group it together ready for export. In addition you will now be able to create bundles of features that will share a namespace so you can export reusable config for a specific bit of functionality such as a slideshow.

What your clients should be excited for:

There's a lot of changes that have been made to streamline the experience for a content manager. These changes will help speed up the day to day management of the site in some big ways and relieve some of the stress of resolving the issues that can arise post launch when you set them loose in the back end. The admin UI has seen a big update and is much more enjoyable to work with.

Quick Edit

Quick edit is a really powerful tool for editing your sites content. This will allow clients to edit their content in place and see it as it will appear within the sites theme. No more annoying shadow boxes that take as long to load as going to the edit screen. Fields that use the wysiwyg will have the toolbar pop up and follow you down the page and editing the source will appear in a lightweight popup.


CKEditor is now in core, this is a huge win as there is nothing more annoying than setting up your wysiwyg. Buttons are added or removed with a drag and drop and can be grouped however you like so it can be as simple or complex as you wish. If you like to limit the allowed tags that are used in the wysiwyg conflicting tags will now be removed from the list when you add buttons that make use of them.

Inline images are another great improvement to using the wysiwyg, this cuts out the need for IMCE. Uploads are sent directly to the files folder without exposing all the other files in there to the client. Captions can be added to the images and make use of the HTML5 figcaption element.

Jul 29 2015
Jul 29

Drupal has a fantastic community of hard-working individuals, many of whom do not see remuneration for their efforts to keep Drupal moving forward. It can be truly rewarding to contribute to large projects like this, knowing that the work you do is improving the lives of many developers and clients the world over.

There are a couple of misconceptions that I would like to dispel before we get into the nitty gritty of what is involved with contributing. Many people see core contributors as hardcore developers or people with an extremely in-depth knowledge of the framework itself. While there are a couple people who can boast this ability, most of the people currently contributing to the project are people who have specific skills that they can bring to the table to forward areas of the project. You really don't even need to be a developer or know anything about PHP at all. There is a great article on Drupal.org on how you can use whatever skill set you have to contribute.

Setting up for contributions

There are a number of things you will need to begin contributions. If you are attending a sprint for core contributions, you can increase your efficiency at the sprint by completing (or at least considering) some of these steps ahead of time.

This first thing you need to get setup for Drupal core contributions is the command line tool, Git. Github has some nice documentation. There are also some nice GUI interfaces for Git, like Source Tree, if you would prefer that.

If you plan on doing development patches, you will also need to install a local server stack. If you use your machine for local development regularly you may already have this setup. The basic requirement is the ability to setup virtual hosts to serve your Drupal environments. You will need at least 1 host, but some people use 2 or 3 depending on what they are doing. If you are like me and your System Administration skills are a bit lacking, you may opt to use an encapsulated solution like MAMP, which works for both Mac or Windows. If you are going with the 2 virtual host setup, one will be for Drupal Development, and one will be a clean Drupal installation for testing the application of patches, so feel free to name your hosts accordingly.

You will make your life easier if you have drush (a command-line utility for Drupal) installed as well, but if you have never used it or are not comfortable using your Terminal, it may not be necessary. If you do decide to install and use it, please ensure you are using the proper version. Please note for Drupal 8 development, you will need version 8.0 of Drush as well.

Next, you will need to clone an instance of Drupal. At the time of writing this, Drupal 8 is the main target of most contributors, so I will be providing instructions based on the assumption that you will be working with this version. In a terminal window, navigate to the folder of your Development virtual host folders and type the command:

git clone --recursive --branch 8.0.x http://git.drupal.org/project/drupal.git

If this is successful (and it could take a couple minutes depending on your connection and machine), you will have a Drupal 8 instance ready for installing. Optionally, if you have a "clean" virtual host setup, you could also repeat this process for that vhost directory.

You can now install all of your codebases and you are off to the races!

The Issue Queue

As a contributor, you will get to know the Drupal.org Issue Queue very well. It is the page you will visit when you are ready to start getting your hands dirty and contributing. There is a search form at the top of the page which will help you really hone it on the area you with which you would like to contribute. If you are a new contributor, I would suggest that you first filter by tasks marked as novice. Select Novice from the Issue tags field and hit Apply. This gives you a subset of all tasks marked as suitable for novice contributors. Even if you consider yourself a senior or advanced Drupal developer, I would suggest you do one of these to get a feel for the workflow of Drupal Core.

Submitting patches

Some developers may be used to and prefer a pull request-merge workflow, but Drupal Core works strictly on a patch submission workflow. The basic workflow is you make a change, submit a patch and then get it reviewed by the community. Following that, a core committer will come across your patch and get it committed to Core. Sometimes, if too much time has passed without committing, and other breaking changes have been made, your task/issue will be flagged for reroll. In this case, it is a fairly simple fix to rebase your patch. You can get more explicit instructions on how to reroll a patch here.

Core mentoring

There a number of avenues that you can pursue to get help with your contributions or any questions you have. I am big believer in finding someone local who can meet up with in person over a coffee or beer to go over your (code-related) problems. They may even be willing to walk you through your first patch, end to end (though that might cost you TWO beers).

Some regions have periodically scheduled sprints that I highly recommend you take part in whenever possible. In my local community, we schedule designated weekends for sprints focused on Core contributions and it can be awesome to level up your skills.

Alternatively if you find yourself in an area without a Drupal community to speak of, start one! This would likely require some of your spare time to organize and facilitate, but financial resources often materialize once someone shows the initiative to begin organizing. There are grants you can apply for from the Drupal Foundation and you can often enlist local Drupal shops for sponsorship as well.

Finally, there are Office Hours, which are attended by Drupal mentors. This is usually facilitated on IRC, but other arrangements might be possible depending on your needs. You can see more information about office hours on Drupal.org. In this same vein, there are usually many people to reach out to on IRC. This is a great way to get quick answers to very specific questions or at least get pointed in the right direction.

Have fun!

That's pretty much all there is to it. I've made it a personal goal of mine to expose the reality of how easy it is to contribute to even a big project like Drupal. I am far from a Drupal expert, or even an expert on contributions in general, but I have spent enough time in the Issue Queue to learn enough to make a difference. The main thing to keep in mind is to have patience with yourself and the community. Sometimes patches take awhile to get committed into the project. This can be frustrating if you've spent a bunch of your personal time on a patch, only for it to get "ignored" by the project team members. One thing to keep in mind that many of these core committers are also spending their personal time on this, and they have a 9-5 just like you.

I have one last thought on the topic. Developers have a tendency to burn themselves out. This is driven by passion and a love of the job for the most part, but it can be quite destructive for individuals and communities, in general. Don't try to take too much on at once, but rather, spread out your contributions. A small, but frequent contributor is worth way more than someone who makes a big contribution, but they then burns out and leaves the community entirely.

Contributing to a big project like Drupal can be quite rewarding because you are contributing to a project greater than yourself, your team, or even your city. You are actually affecting global change on some level. From the smallest documentation spelling correction, to fixing a broken feature, everything makes a big difference and I invite everyone to take the plunge into core contributions, no matter how scary it may seem at first!

Oct 22 2014
Oct 22


Doctors of BC was founded in 1900 as the British Columbia Medical Association, and has a long history of working for members, improving patient care, and influencing health care policy. As part of a rebranding process started in late 2012, Doctors of BC (formerly the British Columbia Medical Association), engaged Fuse to build a new Drupal based website in spring of 2014. The Doctors of BC had been running on Drupal 6 for years and the site served successfully as a key communications and transactional resource for Doctors of BC staff & members. However, with a new brand, a desire to better support mobile users and a need to replace an aging codebase it was time for a rebuild.

This was no ordinary corporate website project. The new Drupal 7 system was replacing not only an aging Drupal 6 site, but a legacy custom eCommerce system with sophisticated business logic, a complex permissions matrix and a host of integration points with internal systems. Oh... and it all needed to be responsive.

We worked alongside Cossette Communications on the project (UX & Design) and were given a tight four month timeframe to build out the project.

Why Drupal?

Drupal was already implemented on the existing site which made the choice easier as the content management team at Dcotors of BC was fairly familiar with the administration interface of Drupal (albeit Drupal 6) and the systems team was familiar with performing things like security updates and digging through code and the community if necessary.

In addition, the amazing effort that's been going into Commerce helped us remove a ton of custom "cart" code and leverage a great foundation for both the store and back end administration of the site.

The Project

Key Features

Members Section
Doctors of BC's membership system is an in-house custom developed application which manages all aspects of membership and billing information for the organization. The old site contained quite a few custom modules that needed to be upgraded to Drupal 7 while being made more robust and easier to maintain. Even though the public side of the site is quite large and complex it doesn't come close to the amount of work that went into the members only section of the site. With roughly 18000 member accounts migrated over and a complicated third party database integration, the members section took the lions share of budgeted time to complete. Invoices, dues, and society memberships all brought into a nice and easy to use interface.

Ecommerce was a large portion of the members only section allowing members to pay their membership dues, outstanding invoices, and even purchase discounted items such as: sporting tickets, movie and ski passes. Using the Commerce suite of modules as the foundation for the site gave us a leg up in the cart department and allowed us to put our development time into some of the more unique features of the cart; timed expiration of cart contents and the limiting of products per category to name a few. With Commerce's pre-built administration views and cart workflow our client had everything they needed to manage all commerce transactions on the site and reduce the amount of time dealing with the headaches of a custom built cart.

Not only does the layout adjust for screen dimensions, it also adapts to environment interaction. Mouse clicks don't always smoothly translate to taps, so we made sure to respect the native input methods.

Tag Subscriptions and Favourites
A requirement from the Doctors of BC was a way to pin or bookmark both single pages as well as tagged collections of content within the site. The flag module got us most of the way there with the 2.0 branch allowing flagging of any entity which let us create a group of tags that a member could subscribe to. Using views we displayed a dynamic list of content to the members based on tags they had flagged. The second flag problem we encountered was the ability to flag non-content pages. Landing pages built with views and any custom pages created for the members section weren't entities so flagging them was not possible with just the Flag module. After a few patches to Flag Pages though, members were free to bookmark any page on the site.

Content Administration
Using Panels and Panelizer allowed us to create a simple drag and drop interface for the content administrators to place content anywhere and whenever they want. Not only did it simplify content management for the Doctors of BC team, but also reduced the training time and support calls for something that should be pretty straightforward for a content management system. We also spent a lot of time creating a streamlined content editing interface so our client could add media and blocks inline, stage draft content and even preview content within the sites theme before publishing.

Project Management

For this site, project management was a partnered effort with Cossette, who was responsible for the Doctors of BC rebranding, site design and requirements analysis. We faced a tight timeline from the start, but knowing we had the experience and organizational tools to handle the demand we were excited to take this project on.

Doctors of BC, Cossette, and Fuse management had weekly meetings to review the project’s progress and discuss any outstanding items for that time. Fuse and Cossette’s project managers also had ongoing calls daily to discuss specific components of the site.

For all of our projects at Fuse Interactive we use a project management tool called Active Collab. It gives us one central location for all client and internal communication as well as timelines, milestones, issue tracking, and task assignments.

Active Collab was readily accepted and used by both Cossette and Doctors of BC management for task assignment and online discussions. When we reached the QA phase we reported all functionality bugs and change requests in a shared Google Docs Spreadsheet due to the high number of people reporting during this phase. It helped to eliminate any duplicate items and further refine any nonspecific requests. Once approved, all requests were quickly moved over to Active Collab for task assignment.

Key Modules

Commerce Kickstart: Due to the large shopping cart component of the site we started the whole project off with the commerce kickstart distribution. This definitely helped get the project going and let us focus on the unique parts of the shopping cart experience.

Panels and Panelizer: We've always been eager to give as much control over the sites content as we can and this time we took it a step further by giving the client control over the layout and presentation. Using panelizer we were able to provide a simple drag and drop interface for blocks including the main content area. If the clients wants a block on a particular page they can do it. If they don't want a banner image at the top of a particular page, they can remove it.

Media: The media module was a tough one to commit to since the 1.0 branch didn't do everything we wanted nor did it do it in the way we wanted it to. The 2.0 branch had the things we needed, but has been a moving target for a while requiring dependencies that are also in active development. We settled on 2.0 and spent a while going through the issue queue applying patches and re-mediating where necessary.

Features: We used Features extensively for content migration and a place to put custom code that related to the exported configuration. We're still undecided at Fuse on whether features based on content types or specific functionality or whether an MVC style approach is best, but due to the number of content types and how easily they fit into sections on the site we settled on roughly one feature per content type which worked out fairly well.

Feeds: Feeds was needed extensively to bring in content from a third party database managed internally by the Doctors of BC team. Some feeds were mapped to actual content types in Drupal while others handled populating custom entities.

Menu Minipanels: With Panelizer in place to allow the client to manage the content within the content region, it didn't seem right to not allow editing of the menu's content as well. We converted the mega menu to a mini panel and allowed the placement of static or pre-made dynamic blocks within a region alongside the sites menus.

CKEditor: We're big fans of CKEditor and with the inclusion of CKEditor in Drupal 8 it makes sense to get new and old clients alike used to it. With the addition of a few additional plugins: CKEditor Tabber and CKEditor Blocks we we're able to give even more control of the content area to the client. With CKEditor Tabber they were able to make static tabbed content appear in both horizontal or vertical tabsets. With CKEditor Blocks they were able to place static or dyanmic blocks inline with page content.

Bean: Blocks as entities is awesome! We were able to create different block "types" that could each have their own fields. This made creating many similarly structured, but different, blocks easy for the client. With Panelizer, the "beans" can be placed anywhere on the site whenever they want.

Breakpoints and Picture: Usual suspects for us on responsive sites. We setup a couple of breakpoints and a couple of image styles and the Picture module does the rest.

Manual Crop: Asking a client to load up Photoshop or another image editor to carefully crop and resize images works sometime, but it's awesome that they don't have to now. Just upload the photo and you're presented with a pretty nice fullscreen crop interface. Select the crop size and hit save. Even better, it supports multiple crop styles per image which worked really well with the Picture module and responsive images.

Menu Node Views: For a large chunk of the publicly accessible portion of the site the content is organized into sections. Each section pertaining to a topic that is further broken down by sub-sections and sub-pages

Migrate and Drupal-to-Drupal data migration: The previously mentioned user migration wouldn't have been a walk in the park without the Migrate module and the D2D module on top. Not only did it migrate the user data, but also upgraded the passwords from MD5 to the more secure SHA-512.

Flag and Flag Page: Not only did the members want to flag nodes, but they wanted to be able to flag any page on the site. Flag Pages to the rescue. Landing pages built with views. Custom pages from custom modules. The homepage even. We also created a tag subscription interface using the newest version of flag which allows flagging any entity type. We tagged content with taxonomy terms and let the members flag a tag. Then content tagged with that tag showed up in their member area.

Omega 4: Our goto theme for all projects here at Fuse. It's responsive and supports SCSS out of the box.

Community Contributions

During the development process we made it a point, as we always do, to contribute back as much as possible to the Drupal community.

Contributed Patches

Sandbox Modules Created

We also reviewed and tested dozens of patches across 17 additional modules making sure to note our outcomes for the module maintainers.

Key Team Members

Fuse Interactive

Doctors of BC

  • Kate Senkow
  • Ivan Doumenc
  • Sean Leslie
  • Tanya Hallgren
Jun 05 2014
Jun 05

It€'s going to happen. We'€™re not exactly sure when, but it is likely (or at least possible!) that Drupal 8 will have a full release in the final quarter of 2014. With any major version update we typically get calls from at least a few panicked clients wondering what they need to do. “do we have to upgrade?”, “will my version continue to be supported?” or “IS MY SITE GOING TO EXPLODE?”

Before we get into the implications of the coming release of Drupal 8 as it relates to your current Drupal site, I wanted to touch on some of the great new features you can expect.

What Drupal 8 promises

While there are some paradigm shifts in Drupal 8 that may prove to be a challenge for some developers (shifts our team is excited about!), the good news is that for end users, Drupal will see significant improvements and will make managing content more flexible, efficient and enjoyable.

Drupal 8 will boast the addition of over 200 new features. I won'€™t/can'€™t cover them all here, so here are a few of the highlights that may be particularly important to Drupal 6/7 website administrators, content creators and those looking at building a new project with Drupal.

Improved content editing/authoring experience

While infinitely flexible and configurable, Drupal has long been critiqued by it'€™s users for lacking the ease of use of that of it's competitors (namely WordPress). Especially frustrating to some was the lack of a WYSIWYG editor in Drupal core. Of course there were contributed modules that allowed for decent WYSIWYG editing when configured properly, but getting this right for the non-technical editor has always been tricky in Drupal. Drupal 8 sets out to solve some of these issues with a WYSIWYG editor in core that promises to be a much more successful experience. You will also be treated to features like:

  • In-place editing! meaning you will now be able to edit content from the front-end of your website instead of having to go to the back-end edit form to make changes to your site.
  • A much improved content creation page designed to make creating new content less overwhelming.
  • Better handling of images for responsive websites.

Mobile-friendly & Responsive out of the box

Ever tried to update content on your Drupal 6 or 7 site with a mobile device? If you have you'll know that it's possible, but not at all optimized for your editing pleasure. The Drupal 8 Mobile Initiative has a mandate to make Drupal a leading mobile CMS platform and are ensuring that all Drupal 8 core themes will be responsive! This means that administrating your Drupal site from a tablet or smartphone will be a much more effective experience.

Another focus of Mobile initiative has been to improve performance which is especially important for mobile users. So in addition to better a better site administration experience, you will also be treated to better front-end performance in Drupal 8 across all devices. Efficient caching of entities, responsive images & smarter javascript loading are some of the features that will prove to serve up pages faster in Drupal 8.

Better for multilingual sites

While we'€™ve built several multilingual sites with Drupal 6 and 7 it was never that easy for developers to configure or intuitive for content creators to manage. Previous versions of Drupal were really English-centric. For Drupal 8, the team behind the Drupal 8 Multilingual Initiative (D8MI) has worked tirelessly to make Drupal 8 core better out of the box for creating and managing multilingual and foreign language websites. Gábor Hojtsy’s article series is a great place to learn about the multilingual features and enhancements in Drupal 8.

Sounds great!

Some of these features will no-doubt sway fence-sitters with new projects in the hopper to decide on Drupal 8, but site owners with existing Drupal 6 or 7 sites will need to make a potentially tough decision on when to upgrade.

Enter panicked calls from our clients.

What does this mean for my Drupal 7 site?

Don't panic. Grab a beer and stretch out on the couch. Drupal 7 is as solid as it's ever been and will most likely be supported by the community until at least 2017. There is no requirement for upgrade and your site will continue to function, but ambitious clients salivating over Drupal 8’s new features might want to jump in as early adopters. Additionally, if you'€™re considering a rethink of functionality or a redesign of your Drupal 7 site, it might be worth waiting for Drupal 8 depending on your timeline for the project.

Deciding on whether or not to upgrade depends quite a bit on the type of site you are running and how reliant on contributed or custom modules your site is. I say this because it always takes time for modules maintainers to upgrade their contributions to work with a new version of Drupal. For the most part modules are maintained and ported over to new versions on a volunteer basis and these volunteers have lives to live and jobs to work. The realities faced by module maintainers dictate the relative speed at which modules get ported.

When Drupal 7 was first released, for example, we didn'€™t feel confident upgrading to or even building any new projects on it for several months after its release when the bulk of the modules we use on a regular basis had been ported from Drupal 6 to 7.

Additionally, if your site relies heavily on custom modules (that is, modules that have been custom built by your developer to facilitate unique functionality), you need to understand that these will need to be ported to Drupal 8 by your developer. This could take a substantial chunk of time depending on their complexity.

If you have a simple site that can rely on Drupal 8 core without the help of too many obscure modules and you want to have the latest & greatest then Drupal 8 might be for you in the near future.

So the answer to this question is highly dependent on the type of site you run and your business goals, but for most of our clients on Drupal 7 we wouldn'€™t encourage an upgrade until they plan on a substantial re-envisioning of their website.

What does this mean for my Drupal 6 site?

You might want to sit down with this. Grab a cup of strong caffeinated tea. Your Drupal 6 site owners are faced with a more imminent decision. With official security support ending with the release of Drupal 8, there are some risks to not upgrading your Drupal 6 site. In fact, the Drupal Security Team will likely urge D6 site owners to upgrade ASAP as they did with D5 owners when Drupal 7 was released.

While in reality your D6 site will probably not be in imminent danger of being hacked or EXPLODING, it's days should be numbered. The question should not be "should I upgrade?"€ but rather "€œshould I upgrade to Drupal 7 or 8?"€ It's time to start planning and budgeting for your upgrade now and not after Drupal 8 has been released.

Many of the same arguments for or against a Drupal 7 to 8 upgrade would apply here. If your Drupal 6 site is relatively simple and is due for a redesign or overhaul, we would likely recommend upgrading to Drupal 8, but probably not until it matures a little. If you'€™ve got a more complex site reliant on contributed modules that may not have Drupal 8 versions on the horizon then you might want to look at an upgrade to Drupal 7. If your typical web overhaul cycle is 3-4 years, then it might be a good strategy to upgrade to a mature Drupal 7 and then to Drupal 8 just prior to the release of Drupal 9.

This strategy could be likened to car buyers that only buy a new car in a new model rollout year. ie. you know the new generation of Toyota Corollas are rolling off the assembly lines this year and you like the new styling, the new features, the better fuel economy, but potential issues with the new generation have yet to be identified. There are risks associated with buying an unproven vehicle. The idea here is that after the 4 or so years a specific model has been around, that the car has reached a state of maturity where all the issues of that generation of vehicle have been worked out. You reduce risk by investing in a proven and reliable vehicle, but at the cost of forgoing the benefits of the new model.

If recent release cycles are any indication, Drupal 7 will be supported for another 3-4 years after the release of Drupal 8, so if you aren'€™t necessarily swayed by the new features and want to opt for the most mature, reliable option, staying with or developing on Drupal 7 might be your best bet.

I should also note that there have been ongoing discussions within the Drupal community & Drupal Security Team about the viability of continuing support for Drupal 6 for an additional 12 months after the release of Drupal 8. This could buy some Drupal 6 site owners (an estimated 20% of all Drupal sites are version 6) some time while popular contributed modules are ported to Drupal 8. Having this additional year of support could influence your decision on what version to upgrade to. It would make Drupal 6 to 8 upgrades much more viable with that extra year to work with. Stay tuned for a decision!

How painful will an upgrade be?

One of the great new features of Drupal 8 is a vastly improved migration path from previous versions of Drupal. A migrate module will now ship with Drupal core which is exciting as it helps us automate otherwise tedious tasks. While we have this and some other tools at our disposal to help automate version upgrades and content migration, it can still get a little complicated, especially if your site uses modules that don'€™t have an equivalent in the version you are upgrading to.

The fact is that some more obscure, custom or lesser used & supported modules will never get ported over to the latest version of Drupal. Because of this, at least some of your site may need to be rebuilt as part of an upgrade process.

Whether the pain will require an aspirin vs. triple bypass surgery all depends on the condition of the patient.

So what should I do?

The short answer is: every Drupal site is different and needs to be considered on a case by case basis. If you have a good relationship with your web developer and are confident in their knowledge of Drupal, I would recommend they be your first stop in determining a course of action. If you've done that and you'€™re looking for a second opinion then we'€™re here to help!

Drop us an email at [email protected] and we'€™ll see what we can do to make your decision easier.

Apr 08 2014
Apr 08

WARNING! This Drupal 8 post is embarrassingly out of date! We are currently in the process of updating the contents to match changes since it's original writing. Thank you for your patience.

Drupal 8 is coming and with it, a slough of drama and controversy. One of the big concerns is that a number of the major API changes (Symfony, OOP, PSR-0/PSR-4, Twig, YAML - the list goes on) is going to alienate a lot of current Drupal developers who are more comfortable working in the procedural world of hooks and global PHP functions. While this may be true, I prefer to look at it as an opportunity for developers to learn something new and expand their current skill set. After all, as a developer, knowledge is your best tool, so you'd better keep it sharp.

For today's post, we're going to try and demystify some of the new Drupal API by upgrading a relatively simple (no pun intended) module that I maintain Simple Dialog. All the module does is provide a mechanism to launch a dialog with content loaded from a different page, without writing any javascript. I'm not gonna lie. Pretty much every part of the API that I used in the module has changed in some way. One thing even changed while I was writing it. But have no fear, we're gonna go through it part by part and examine what's happening. The completed module is also available on drupal.org in case you want to just download it and follow along.

Baby, don't fear the Drupal (8). Learn by doing while we upgrade the Simple Dialog module!

Don't like reading long-winded tutorials? Try clicking the tl;dr button for the *compact* version of this post.

Not enough information in the post? Try clicking the tl;dr button to expand.


DISCLAIMER: Drupal 8 is currently in Alpha and some parts of the API are still subject to change. I'll try and update the post as things change, but if you notice anything while reading through, feel free to let me know in the comments at the bottom!


All you need to get going with this tutorial is a working, up-to-date Drupal 8 install, and some experience with module development in Drupal 7. However, some understanding of Object Oriented Programming, PHP Namespaces, and the PSR initiatives will help. I'll be explaining everything as we go along, but there are a lot of big concepts to cover so I won't be able to go as in depth as might be necessary.

Install Drupal 8. Read about Object Oriented Programming, PHP Namespaces, and PSR-0/PSR-4... or don't. Whatever.

The Upgrade Process

While we could use the Drupal Module Upgrader to do a bunch of the work for us, I'm going to do this upgrade manually in order to better explore the new API. I'll start by breaking down simple_dialog based on which part of Drupal 7's API it leverages.

  • Configuration Form: Simple dialog provides a settings form for managing some defaults for the dialog. In Drupal 7 this is done by defining a global function and invoked through drupal_get_form()
  • URLs/Routing: Simple dialog only defines one path (route) which is for the configuration form. This is being handled by hook_menu().
  • Add JS/CSS: Our simple dialog css and js is being added to every page using hook_init.
  • Theme: A theme function for generating a simple dialog link is provided for use in code.
  • Help: Additionally a help page is provided through hook_help()

Then, I'm going to go through these items and figure out what's changed in the Drupal API and make the appropriate upgrades. drupal.org has added a new feature for tracking changes called "Change Records". I've found it's the best place to start your search. For example if you want to see if anything has changed with drupal_add_js(), you'd just search for it in the "Keywords" field (and you'll find that it's been removed - more on that later). Following that, Google is still probably your best choice. The drupal.org documentation for D8 is actually pretty good, but I always find Google does a better job of finding the right page. Lastly, you can always take a look at some of the core module's code as examples.

  • Check if there is a Change Record for the feature you're trying to upgrade.
  • If there is no change record search using Google (or search the drupal.org documentation which is pretty up to date).
  • Take a look at other modules that might be doing the same thing that you want to do as an example.

The Basics

Drupal 8 now offers two places you can store your modules. You can do it the existing way in sites/all/modules or just directly in the modules folder of the drupal root. Since it's new, let's use the latter method. And, actually, I'm going to put the module in a "contrib" sub-folder (I prefer to keep contrib and custom modules separated). We'll be starting with the Drupal 7 version of the module so download that and put it in modules/contrib. A lot is going to change in simple_dialog.module so for the purposes of this tutorial let's just delete all the code and start fresh. (Note: The .module file isn't technically required anymore for the module to work. See this change record for more info)

Info files are now being written in YAML instead of the old .ini format. In fact, the YAML markup language is being adopted quite thoroughly in Drupal for settings and configuration so it's worth it to take some time to get to know it. The info file should be named simple_dialog.info.yml (not ".yaml" as some of us have learned the hard way) and it looks like this:

Copy the D7 version of simple_dialog to modules/contrib. Delete simple_dialog.info and create simple_dialog.info.yml):

name: Simple Dialog
type: module
description: 'Provides an API to create simple modal dialogs. Leverages the jQuery ui dialog plugin included with Drupal.'
core: 8.x
package: User interface
configure: simple_dialog.settings

More information on Drupal 8 module info files can be found here.

Configuration API

Next, we'll be defining our default simple dialog settings using the new Configuration API. Gone are the days of calling variable_set() and variable_get() to manage your module's settings. Now, config is handled using a special configuration class and your settings are stored in .yml files (making tracking your config in version control a breeze). Also, a new configuration management system has been introduced to facilitate syncing config between your various dev, staging and production environments (something that historically was handled by the features module).

Simple dialog's configuration requirements are pretty straight foward. We're really just defining a set of default values for the settings form. Create a "config" folder in the module root and within that a file named simple_dialog.settings.yml (that's right, more YAML!). The name of the file is important because everything before the .yml extension will be used as the name of the configuration object when you're getting and setting configuration values later on. Open the file and add the following:

We'll be leveraging the new Configuration API to define our default settings for the configuration form. Create a "config" folder in the module root and within that a file named simple_dialog.settings.yml and add the following:

js_all: true
classes: ''
  settings: 'width:300;height:auto;position:[center,60]'
  target_selector: 'content'
  title: ''

Easy, right? The next thing we're going to do is write a schema for our configuration. Configuration schemas are another new feature introduced Drupal 8. Based on metadata language called Kwalify, it allows us to explicitly define metadata about our configuration variables such as data type and label. According to the docs, the primary use case for the schemas was for multilingual support, although I wouldn't be surprised if this expands as Drupal 8 matures. For example, supposing that a 'description' was added, you could pretty much auto generate your config form from the schema.

At first, I thought writing a configuration schema would be more complicated than it is, but after reading the docs and looking at some core examples it's actually quite straight forward. For simple configuration it will often follow the same pattern. Each variable will have a type and a label, and the nesting should match what we did in simple_dialog.settings.yml. The file we're going to create will be <simple_dialog root>/config/schema/simple_dialog.schema.yml and the schema looks like this:

The next step is to write a Configuration schema to define some metadata about our configuration variables. For simple configuration it will often follow the same pattern. Each configuration variable will have a type and a label, and the nesting should match what we did in simple_dialog.settings.yml. Create the file <simple_dialog root>/config/schema/simple_dialog.schema.yml and add:

# Schema for configuration files of the Simple Dialog module.

  type: mapping
  label: 'Simple Dialog settings'
      type: boolean
      label: 'Add simple dialog javscript files to all pages'
      type: string
      label: 'Additional Classes'
      type: mapping
      label: 'Defaults'
          type: string
          label: 'Default Dialog Settings'
          type: string
          label: 'Default Target Selector'
          type: string
          label: 'Default Dialog Title'

If you want to read more about configuration schemas, the docs page (linked above) is quite informative. There are also links to a few (very long) issues that tell the story of how this came about.

Configuration Form

For the most part, the Forms API hasn't changed that much. There's some new HTML5 elements, but otherwise the form array is still structured the same and the validation and submit steps are maintained. The implementation of the form itself has changed, however. Instead of defining the form using a global PHP function, we'll be extending one of the base form classes that Drupal provides (time to put on your OOP hat). In this case we'll be using the ConfigFormBase class.

Another big change is the adoption of the PSR standard for class autoloading. This is actually one of those things that's currently in flux as I write this. Right now, we're still using the PSR-0 standard for class autoloading, however that's going to change to PSR-4 very soon. They're not that different really. PSR-4 is just a little easier on the nested folder structures. Unfortunately we'll have to use PSR-0 for the purposes of this tutorial, but I'll come back and change that when the PSR-4 changes are committed. For now, create the following folder/file structure: <simple_dialog root>/lib/Drupal/simple_dialog/Form/SimpleDialogSettingsForm.php and add the following code:

Really, the The Forms API hasn't changed that much, except that forms are now defined by extending a form base class. For config forms, that's ConfigFormBase. Create a new file lib/Drupal/simple_dialog/Form/SimpleDialogSettingsForm.php.

namespace Drupal\simple_dialog\Form;
use Drupal\Core\Form\ConfigFormBase;
 * Defines a form to configure maintenance settings for this site.
class SimpleDialogSettingsForm extends ConfigFormBase {
   * {@inheritdoc}
  public function getFormID() {
    return 'simple_dialog_settings_form';
   * {@inheritdoc}
  public function buildForm(array $form, array &$form_state) {
    $config = $this->config('simple_dialog.settings');
    $form['javascript']['js_all'] = array(
      '#type' => 'checkbox',
      '#title' => $this->t('Add simple dialog javscript files to all pages'),
      '#description' => t("This setting is for people who want to limit which pages the simple dialog javscript files are added to. If you disable this option, you will have to add the js files manually (using the function simple_dialog_add_js() ) to every page that you want to be able to invoke the simple dialog using the 'simple-dialog' class. If you are adding simple dialog links to the page using theme('simple_dialog'...) the necessary javascript is added within those functions so you should be okay.'"),
      '#default_value' => $config->get('js_all'),
    $form['classes'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Additional Classes'),
      '#description' => t("Supply a list of classes, separated by spaces, that can be used to launch the dialog. Do not use any leading or trailing spaces."),
      '#default_value' => $config->get('classes'),
    $form['default_settings'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Default Dialog Settings'),
      '#description' => t('Provide default settings for the simple dialog. The defaults should be formatted the same as you would in the "rel" attribute of a simple dialog link. See the help page under "HTML Implementation" for more information.'),
      '#default_value' => $config->get('defaults.settings'),
    $form['default_target_selector'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Default Target Selector'),
      '#description' => t('Provide a default html element id for the target page (the page that will be pulled into the dialog). This value will be used if no "name" attribute is provided in a simple dialog link.'),
      '#default_value' => $config->get('defaults.target_selector'),
    $form['default_title'] = array(
      '#type' => 'textfield',
      '#title' => $this->t('Default Dialog Title'),
      '#description' => t('Provide a default dialog title. This value will be used if no "title" attribute is provided in a simple dialog link.'),
      '#default_value' => $config->get('defaults.title'),
    return parent::buildForm($form, $form_state);
   * {@inheritdoc}
  public function submitForm(array &$form, array &$form_state) {
      ->set('js_all', $form_state['values']['js_all'])
      ->set('classes', $form_state['values']['classes'])
      ->set('defaults.settings', $form_state['values']['default_settings'])
      ->set('defaults.target_selector', $form_state['values']['default_target_selector'])
      ->set('defaults.title', $form_state['values']['default_title'])
    parent::submitForm($form, $form_state);

For the most part this is pretty boilerplate. The namespace (and path to the file) follows the PSR-0 standard for class autoloading (although that's going to change to PSR-4 very soon). There is also a validation method you can override validateForm().

Let's examine what we're doing here more closely.

namespace Drupal\simple_dialog\Form;
use Drupal\Core\Form\ConfigFormBase;

First, we're namespacing our file and the contained class. The namespace is constructed using the PSR-0 standard that I mentioned before which matches it's directory structure in our module's lib directory (<simple_dialog root>/lib/Drupal/simple_dialog/Form/SimpleDialogSettingsForm.php). Following that we're aliasing/importing the Drupal\Core\Form\ConfigFormBase class.

public function getFormID() {
  return 'simple_dialog_settings_form';

The getFormID method just returns an id for the form that you define. In Drupal 7 the form id was the form's function name.

public function buildForm(array $form, array &$form_state) {
  $config = $this--->config('simple_dialog.settings');
  // ... form definition
  return parent::buildForm($form, $form_state);

The buildForm method is where you'll define your form using the Forms API. Like I mentioned before the Forms API hasn't changed that much so we won't talk about that. However, here's where the new configuration API changes are coming into play. You'll notice that we're getting our config from an object provided by the config() method. It's only a line of code, but there's a lot going on here. Essentially we're retrieving a configuration object (named simple_dialog.settings - smae as our .yml file that we defined earlier) that has been dependency injected that can be used for reading (or writing) configuration settings. Dependency Injection is a programming design pattern that is used throughout Symfony and therefore, Drupal 8. In simple terms it's a mechanism to inject your class in the place of another class somewhere in the application. Fabien Potencier, the creator of Symfony, has a great 6 part series about Dependency Injection on his blog. If you're planning on developing for Drupal 8, it's a must-read. For the purposes of this tutorial, however, all you need to know is that you can use this configuration object to set the default values of your form array. Also, if this is the first time our form has been visited, the values returned by the get() method will be pulled from our simple_dialog.settings.yml file.

Update: We're also using the class' t() method (i.e. $this->t()) for translations instead of the global one. The reasoning behind this can be found in the comments by none other than Larry Garfield (aka: crell).

public function submitForm(array &$form, array &$form_state) {
    ->set('js_all', $form_state['values']['js_all'])
    ->set('classes', $form_state['values']['classes'])
    ->set('defaults.settings', $form_state['values']['default_settings'])
    ->set('defaults.target_selector', $form_state['values']['default_target_selector'])
    ->set('defaults.title', $form_state['values']['default_title'])
  parent::submitForm($form, $form_state);

Lastly we're overridding the submitForm() method to save our submitted configuration values. Once again we're using the configFactory to get our configuration object. This is essentially the Drupal 8 implementation of the old Forms API #submit handler. Notice that we are explicitly calling the parent class submitForm() method. There is also a validation method we can override: validateForm(), but we aren't. One could argue that we could use some simple validation on this form... but let's not for now.


Routing is not a new concept to Drupal, but you'll probably hear the term being used quite a bit more than in past versions. A route is a path in drupal that can accept a request (GET/POST) and returns a response (HTML, JSON, 404 etc). In D7 hook_menu() was our goto hook for handling routes. But hook_menu() was also serving the double duty of managing all our menu links, local tasks and local actions. In D8, all the menu stuff has been separated into yaml files and routing is now implemented with Symfony's Routing component. In fact, hook_menu() has been completely removed from core.

Our routing needs for Simple Dialog are relatively... well... simple. All we really need to do is define a path for our configuration form. To do this we'll need to first define the route. Create a file called simple_dialog.routing.yml and add the following code:

hook_menu() is dead. Long live Routing. Create simple_dialog.routing.yml and add:

  path: '/admin/config/content/simple-dialog'
    _form: '\Drupal\simple_dialog\Form\SimpleDialogSettingsForm'
    _permission: 'administer simple dialog'

What we can determine from this is that the route name is

  • The route name is "simple_dialog.settings"
  • The route points to the form "\Drupal\simple_dialog\Form\SimpleDialogSettingsForm" which is the PSR-0 namespaced form we created earlier
  • You must have the 'administer simple dialog' permission to access the form

As far as routing goes in Drupal 8, this is a relatively simple one. There is a lot more you can do with the routing component, but it is outside the scope of this tutorial. To learn more, The documentation on drupal.org is a great place to start.

The next thing we need to do is create the menu link for it so it will show up in the config section of the administration area. We'll create a simple_dialog.menu_links.yml file and add the following:

hook_menu() is still dead. Long live The new Menu API. Create simple_dialog.routing.yml in the module root and add:

  title: Simple Dialog
  description: 'Configure default settings for simple dialogs'
  parent: system.admin_config_ui
  route_name: simple_dialog.settings

The first item is the menu_link name (sort of a machine name). This is important when defining parents. When specifying a parent menu item you have to use that menu item's menu_link name. This can be a bit tricky to determine. See the drupal.org documentation on the subject for more info. There is also documentation for setting up module-defined local tasks, actions and contextual links.

One last thing to note, we'll need to define the permission we're using for the route using hook_permission(). This hasn't changed from Drupal 7 so it should be familiar:

Don't forget about hook_perm, which hasn't changed at all.

 * Implements hook_permission().
function simple_dialog_permission() {
  return array(
    'administer simple dialog' => array(
      'title' => t('Administer Simple Dialog'),

Adding CSS/JS

First things first, we need to copy the js and css folders from the D7 version of Simple Dialog. This JS and CSS, as well as the jquery ui.dialog library needs to be added to every page. Previously, this was acheived through hook_init(), however, hook_init has been removed in D8. A couple things have changed that led to this. The biggest change was the adoption of the Symfony kernel and the concept of "events" that came with it. Now, the lifespan of an http request in drupal is broken into a series of events that can be "subscribed" to. You subscribe to events using the EventSubscriberInterface class and register methods to be run when the event happens. Think of it as an OOP hook system. So, all we need to do is figure out which event is the hook_init equivalent and run the appropriate drupal_add_*(), right?

Unfortunately, drupal_add_js(), drupal_add_css() and drupal_add_library() have been removed in Drupal 8. This is part of a shift towards putting a heavier focus on render arrays and the #attached property. So, now, you need to get your hands on a render array so you can attach what you need. Luckily the new hook_page_build() will do the trick for us:

hook_init() has also been removed. General runtime events like that are being handled now by the Symfony HTTP kernel. Those events can be subscribed to by extending the EventSubscriberInterface. See this change record for more info. drupal_add_js(), drupal_add_css() and drupal_add_library() have also been removed in favour of using the #attached property on render arrays:

 * Implements hook_page_build()
function simple_dialog_page_build(&$page) {
  $path = drupal_get_path('module', 'simple_dialog');
  // Add JavaScript/CSS assets to all pages.
  // @see drupal_process_attached()
  $page['#attached']['css'][$path . '/css/simple_dialog.css'] = array('every_page' => TRUE);
  if (\Drupal::config('simple_dialog.settings')->get('js_all')) {
    simple_dialog_attach_js($page, TRUE);
 * Adds the necessary js and libraries to make the
 * dialog work. Really just adds the jquery.ui
 * library and the simple dialog javscript file
 * but if we need to add anything else down the road,
 * at least it's abstracted into an api function
 * @param array $element
 *        The renderable array element to #attach the js to
 * @param boolean $every_page
 *        Optional variable to specify the simple dialog code should be added
 *        to every page. Defaults to false. If you're calling this function,
 *        you likely will not need to change this as the module has settings
 *        to specify adding the js on every page
function simple_dialog_attach_js(&$element, $every_page = FALSE) {
  $element['#attached']['library'][] = 'system/ui.dialog';
  $element['#attached']['js'][] = array(
    'data' => array('simpleDialog' => array(
      'classes' => \Drupal::config('simple_dialog.settings')->get('classes'),
      'defaults' => array(
        'settings' => \Drupal::config('simple_dialog.settings')->get('defaults.settings'),
        'target_selector' => \Drupal::config('simple_dialog.settings')->get('defaults.target_selector'),
        'title' => \Drupal::config('simple_dialog.settings')->get('defaults.title'),
    'type' => 'setting',
  $element['#attached']['js'][drupal_get_path('module', 'simple_dialog') . '/js/simple_dialog.js'] = array('every_page' => $every_page);

The only thing to note here is how we get configuration from the system. No more variable_get(). Instead we're using the Drupal class' static method config(). Also, I've abstracted the attaching of the js to it's own function in case someone wants to do it manually instead of on every page.


In the D7 version of simple dialog, I provided a little theme function that would build the link for you from a set of options. To be honest, this theme function is pretty unnecessary. You could just as easily use the core l() function. In fact, that's all the theme function did in the end. Ultimately I think I'm going to remove it, but for the sake of exploring the theme system in D8, I'm going to upgrade it.

The theme system has gone through a pretty major overhaul in Drupal 8. The theme engine has been switched from phptemplate to twig, theme functions are all being converted to templates, and even the previously ubiquitous theme() function has been removed. We'll start in familiar territory: hook_theme()

Get "twiggy with it" with the new theme engine: Twig. hook_theme() is pretty much the same except that we're avoiding using theme functions at all in the place of templates because Core is.

 * Implements hook_theme().
function simple_dialog_theme($existing, $type, $theme, $path) {
  return array(
    'simple_dialog_link' => array(
      'variables' => array(
        'text' => NULL,
        'path' => NULL,
        'selector' => NULL,
        'title' => NULL,
        'options' => array(),
        'link_options' => array(),
        'class' => array(),
      'template' => 'simple-dialog-link',

hook_theme() actually hasn't changed that much. The only difference between this and the D7 version of simple_dialog is that I specified a template file. This is because I want to turn this particular theme implementation into a twig template. Before I do that however, I need to preprocess the variables.

 * Preprocesses variables for simple dialog links
 * @param $variables
 *   An associative array containing:
 *   - text: The link text for the anchor tag.
 *   - path: The URL to pull the dialog contents from.
 *   - title: The 'title' attribute of the link. Will also be used for the title
 *     of the jQuery ui dialog
 *   - selector: The css id of the element on the target page. This element and it's
 *     containing html will be loaded via AJAX into the dialog window.
 *   - attributes: An associative array of additional link attributes
 *   - class: An array of classes to add to the link. Use this argument instead
 *     of adding it to attributes[class] to avoid it being overwritten.
 *   - options: (optional) An associative array of additional jQuery ui dialog
 *     options keyed by the option name. example:
 *     @code
 *     $options =  array(
 *       'optionName' => 'optionValue', // examples:
 *       'width' => 900,
 *       'resizable' => FALSE,
 *       'position' => 'center', // Position can be a string or:
 *       'position' => array(60, 'top') // can be an array of xy values
 *     ),
 *     @endcode
function template_preprocess_simple_dialog_link(&$variables) {
  // Somewhere to store our dialog options. Will be imploded at the end
  $dialog_options = array();
  // as long as there are some options and the options variable is an array
  if ($variables['options'] && is_array($variables['options'])) {
    foreach ($variables['options'] as $option_name => $value) {
      if ($option_name == 'position' && is_array($value)) {
        $dialog_options[] = $option_name . ':[' . $value[0] . ',' . $value[1] . ']';
      elseif ($value) {
        $dialog_options[] = $option_name . ':' . $value;
      else {
        $dialog_options[] = $option_name . ':false' ;
  // Concatenate using the semi-colon
  $dialog_options = implode(';', $dialog_options);
  // Setup the default attributes
  array_unshift($variables['class'], 'simple-dialog');
  $attributes = array(
    'title' => $variables['title'],
    'name' => $variables['selector'],
    'rel' => $dialog_options,
    'class' => $variables['class'],
  // We need to merge any other attributes that were provided through the
  // attributes variable
  if (!empty($variables['attributes'])) {
    $attributes = array_merge($variables['attributes'], $attributes);
  $variables['attributes'] = new Attribute($attributes);

Most of the preprocessor is just slightly modified from the original D7 theme function. One thing to note is the way we're handling the attributes. Where we used to use the drupal_attibutes() function, we're now using a helper Attribute class. The full namespace of the class is technically Drupal\Core\Template\Attribute, but I've added a use statement to the top of my module file so I can avoid the long name.

Don't forget to add this to the top of your .module file so that we can use the Attribute class without the full namespace.

use Drupal\Core\Template\Attribute;

Lastly, we'll create the twig template in a 'templates' subfolder of our module root. The template name needs to match what we specified in hook_theme, but with the '.html.twig' extension (<simple_dialog root>/templates/simple-dialog-link.html.twig). Note: try to be consistent with dashes and underscores in your template names. The template name you specify in hook_theme() will be used verbatim so if you use dashes, the filename will have dashes. It appears core always uses dashes for template names, although it's not technically a requirement.

And finally the twig template

 * @file
 * Default theme implementation to print a simple dialog link
 * Available variables:
 *   - text: The link text for the anchor tag.
 *   - path: The URL to pull the dialog contents from.
 *   - attributes: The attributes for the link compiled in a preprocessor
 * @see template_preprocess_simple_dialog_link()
 * @ingroup themeable
{{ text }}

On a final note there isn't a ton of information out there about the new theme system from a module developer's perspective. Most of it is geared towards themers. Your best bet is probably to start reading through hook_theme() and exploring how some of the core modules do it. Also the fine people on IRC #drupal-contribute usually have some insights.

Last Steps

One last thing: hook_help() still works and can actually be copied over directly from the D7 module.

DONE. Oh, and copy over hook_help(). That hasn't changed.

If you've done everything right your module directory structure should look something like this:


Congratulations. You've successfully upgraded simple_dialog to Drupal 8! You can test that it works by adding a simple dialog link to a node's body field, or better yet, you can try upgrading the accompanying simple_dialog_example module.

Jan 09 2014
Jan 09

I'm a lazy developer. Probably one of the laziest I know. I try not to commit anything to memory that can be looked up with 2 seconds of typing and a click. If I know someone else knows the answer and they're within earshot I'm definitely not going to sit around and try to figure it out myself. That's not to say I won't work hard or can't learn, but it's all about efficiency in the work place for me.

With that said, I love what the Drupal community is doing this time around with Drupal 8. "Proudly Invented Elsewhere" or PIE as I like to call it leverages a ton of code written not specifically for Drupal, but more generally to solve specific problems in web application development. Just like every Drupal developer reaches for a module to provide a solution, Drupal as a whole is bringing in functionality from a lot of established and battle-worn projects and incorporating them into Drupal's core.

The biggest project being adopted is Symfony. Not all of it, but a good chunk of it will be incorporated into Drupal's core. You can find a list of all components available in Symfony, but the list below shows you some of what's been included so far.


ClassLoader (PSR-0) and DependencyInjection

Namespacing and class naming standards and autoloading of classes when they're needed. Following a standard PHP convention instead of a Drupalism also allows us to integrate third party code a lot more effectively and swap it out easily if need be.


HttpFoundation and HttpKernel

These two classes help form the foundation that the WSCCI team is using to reinvent the request and response pattern of Drupal. WSCCI will abstract the structured data of your Drupal backend from the web based front end turning your Drupal site into more of a consumable service that could happen to have a website in front of it, but could also easily have an iOS or Android app as it's interface.



YML files are replacing the info files previously found in themes and modules as a way to setup some basic information about both such as the name of the module, dependencies and version numbers. YML also comes into play in configuration management as a way to store core and module configuration settings in code as opposed to the database.



hook_menu no more. Well, not for defining callbacks at least. Instead you'll define paths to callbacks in your module's YML file and you'll only need to invoke hook_menu if you're actually adding menu items to a menu. Essentially though, you're still defining a path that when executed runs some code.



Taking over eventually for the hook system currently in Drupal. The EventDispatcher allows your code to dispatch or subscribe to events. Dispatching an event will cause all listeners of that event to act on it. As far as I know for now though, you'll be using a mishmash of both.


Serializer essentially takes an array and morphs it into a different format. So build your array as you would in PHP and output that array as JSON or XML or any other format the Serializer component can output.


Like hook_validate except it's not tied to forms. You can validate any data coming into your system. The validator component from Symfony provides a ton of out of the box validation constraints including things like valid country names and ISBNs in case you're making some kind of book site.



Translation in core! This should be fairly similar to the combo of i18n and l10n modules. It also seems there's more of a focus on entity translations than on content translations. Not sure how that'll work with revisions, but sounds like a big step forward.

Doctrine Common

Doctrine (particularly the class annotation parsing) is used as the mechanism for discovery, definition and loading of plugins. Plugins provided by core so far being; Entities, Blocks, Field formatters and any Views plugins. With field formatters as an example, you would provide a class annotation to define the field info instead of using hook_field_info.



CSS and JavaScript pre-processing and aggregation similar to RoR's asset pipeline. This guy does a lot of fun stuff like minifying your CSS for you, optimizes jpg's and png's, and even supports SASS and LESS.


Aside from Symfony, a few other projects have been brought into the fold.


Manages external dependencies similar to Ruby's gem bundler, but for PHP. You can use the default package repository called Packagist or you can host your own packages for private use using Satis.


Twig is the replacement for PHPTemplate as the default template system for Drupal 8. It was developed by Fabien Potencier who also happens to be the creator of Symfony so it should be pretty complementary.



A more powerful replacement for drupal_http_request. It can handle persistent and parallel connections, comes with plugins for caching, loggins and different authentication methods and leverages Symfony's EventDispatcher.



Mostly doing away with the purpose built SimpleTest and adopting a more widely accepted testing framework. PhpUnit will provide a unit testing framework for standalone components of the Drupal framework. You'll still need to use SimpleTest when a full or partial Drupal environment is needed, but this should help with all the third party components being introduced into core.


I'm sticking to the shallow end of Drupal 8 for now so if there's any glaring omissions or mistakes let me know in the comments.

Dec 17 2013
Dec 17

Making sure your Drupal is up to date is a good start but often people forget about keeping PHP up to date. PHP 5.3 has reached end of life and will no longer have any more security updates in July 2014. It is important to make sure your site is running on a supported version. Please note, it is also important to make sure all your server's other software are up to date (i.e. Apache/Nginx, MySQL, OS, etc...). Running any old unsupported software will leave you exposed. In this blog post I will mostly talk about the options you have for updating PHP.

The Drupal team recently decided to make Drupal 8 require PHP 5.4 or higher. If you are a developer or a web development shop that will be building sites in D8, you will need to make sure you have PHP 5.4 installed. Default Ubuntu 12.04 LTS and RedHat 6 or older do not use PHP 5.4 so you will need to upgrade.

Switch to PHP 5.4

If you currently have a D4/5/6/7 site, and you're running the site using PHP 5.4, your site(s) will probably have errors. Some errors will be easy to fix and some will be hard. If your site is on D7 and has the latest core and contrib modules then any error that comes up should be fairly easy to fix and patches are available in most cases. If the problem requires a lot of work especially when you have a D4/5/6 sites then you will have a few decisions to make. There is no definitive solution to this. It depends on your budget limitation, time and/or staff availability, and current server setup.

Here are your options:

1. Upgrade PHP to 5.4, stick with your existing version of Drupal and try to fix the errors.


  • Most likely will be cheaper than a full Drupal core upgrade.


  • The longer you delay upgrading your unsupported version of Drupal, the more your site will be exposed to security issues.

2. Run multiple versions of PHP. Your old Drupal site will run on the existing version of PHP and if you add a new D8 site, run it on PHP 5.4 or higher.


  • Potentially the cheapest option
  • You don’t have to re-test all your existing sites.


  • Requires time to setup different versions of PHP, more complicated server configuration.
  • Running unsupported versions of PHP and Drupal exposes you to security vulnerabilities as time passes.

3. Upgrade all of your old Drupal sites to Drupal 8 after a stable release is available.


  • Your site will be able to have a longer life span.
  • You get to take advantage of Drupal 8’s new features.
  • Most secure option


  • Requires the most time (higher cost) out of all options

4. If you want to add a new D8 site in addition to other old Drupal sites: Set up the D8 site on a different server with PHP 5.4. Leave your existing sites as it is.


  • No need to test any existing sites.


  • Once again, running unsupported versions of PHP and Drupal exposes you to security vulnerabilities as time passes.
  • Recurring cost of hosting on separate hardware.

PHP 5.5 is already out, should I use PHP 5.5 instead of 5.4?

At the time when this blog post is written, core is still broken (edit: can get around it by disabling opcache) for PHP 5.5 . It is not as widely used as PHP 5.4 so there is probably still lots of undiscovered issues for PHP 5.5. I would use 5.4 for now.

If your site is currently on an unsupported version of Drupal and/or PHP and you’re wondering which option is best for your needs, feel free to contact us and we will give you our recommendation.

Edit: added "July" 2014 for PHP 5.3 no longer provide anymore security update, added link to PHP 5.5 opcode issue

Nov 19 2013
Nov 19

I was recently responsible for building a suite of Behat-based tests for an existing site using the excellent Behat Drupal Extension. After initially wrapping my head around Behat with some simple tests, I moved on to some more complex tests for the site's business logic, involving the creation of nodes and the voting API. Happy with my test feature, I ran it a couple of times only to get a different result every time; sometimes it would pass, sometimes it would fail early and sometimes it would fail on the penultimate step. What was going on?

A little background

Ordinarily when practicing TDD the goal is to write a test that initially fails, then to write code that results in the test passing. While I wasn't able to practice TDD from the outset of this project, the goal was to ensure important existing functionality wouldn't be broken by implementing new functionality. On this point, a test that passes or fails inconsistently makes this a difficult methodology to practice, as it becomes impossible to pinpoint the true source of the test failure.

It's also important to note for this test feature, I was using the Selenium2 driver for Mink to simulate a user's actions on the site. In this particular case, we needed Javascript support to click a voting API link that was created dynamically.

Giddy up!

After some time attempting to diagnose why tests would fail inconsistently, I eventually narrowed it down to a caveat of Behat:

Often, especially when using Mink to test web applications, you will find that Behat goes faster than your web application can keep up - it will try and click links or perform actions before the page has had chance to load, and therefore result in a failing test, that would have otherwise passed.

Essentially, the website under test was sometimes responding too slowly, resulting in failed tests. In the end it was a simple fix in this case - by enabling CSS and Javascript aggregation the particular test results became consistent. This just goes to highlight, site performance isn't just important to consider for the end-user or live sites, but also for development and testing environments!

Whoa there!

On the other hand, there are times when you might want to deliberately slow your tests down. A large response, slow database query or long AJAX request (although Selenium2 seems to be quite good at waiting for AJAX requests to complete) may trip Behat up in much the same way as above however these cannot always be worked around by simply tweaking your site's performance.

To slow down the execution of a test I implemented a function similar to the spin function described here in my FeatureContext:

 * @Given /^I wait ([0-9^"]*) seconds$/
public function iWaitSeconds($wait) {
  for ($i = 0; $i < $wait; $i++) {

It should then be possible to write a feature as such:

Scenario: Waiting for a really slow page
    Given I am on "a/slow/page"
    And I wait 30 seconds
    Then I should see "Something that took a long time to load"

At home on the Drupal-prairie

Behat as a BDD testing tool for Drupal seems to be steadily gaining in popularity. Some of the things Behat is capable of, such as this cool technique demonstrated by Jonathan Jordan of Metal Toad, make it a highly practical tool in ensuring your site is functioning correctly or helping you track down problems when it's not.

Nov 19 2013
Nov 19

I was recently responsible for building a suite of Behat-based tests for an existing site using the excellent Behat Drupal Extension. After initially wrapping my head around Behat with some simple tests, I moved on to some more complex tests for the site's business logic, involving the creation of nodes and the voting API. Happy with my test feature, I ran it a couple of times only to get a different result every time; sometimes it would pass, sometimes it would fail early and sometimes it would fail on the penultimate step. What was going on?

A little background

Ordinarily when practicing TDD the goal is to write a test that initially fails, then to write code that results in the test passing. While I wasn't able to practice TDD from the outset of this project, the goal was to ensure important existing functionality wouldn't be broken by implementing new functionality. On this point, a test that passes or fails inconsistently makes this a difficult methodology to practice, as it becomes impossible to pinpoint the true source of the test failure.

It's also important to note for this test feature, I was using the Selenium2 driver for Mink to simulate a user's actions on the site. In this particular case, we needed Javascript support to click a voting API link that was created dynamically.

Giddy up!

After some time attempting to diagnose why tests would fail inconsistently, I eventually narrowed it down to a caveat of Behat:

Often, especially when using Mink to test web applications, you will find that Behat goes faster than your web application can keep up - it will try and click links or perform actions before the page has had chance to load, and therefore result in a failing test, that would have otherwise passed.

Essentially, the website under test was sometimes responding too slowly, resulting in failed tests. In the end it was a simple fix in this case - by enabling CSS and Javascript aggregation the particular test results became consistent. This just goes to highlight, site performance isn't just important to consider for the end-user or live sites, but also for development and testing environments!

Whoa there!

On the other hand, there are times when you might want to deliberately slow your tests down. A large response, slow database query or long AJAX request (although Selenium2 seems to be quite good at waiting for AJAX requests to complete) may trip Behat up in much the same way as above however these cannot always be worked around by simply tweaking your site's performance.

To slow down the execution of a test I implemented a function similar to the spin function described here in my FeatureContext:

 * @Given /^I wait ([0-9^"]*) seconds$/
public function iWaitSeconds($wait) {
  for ($i = 0; $i < $wait; $i++) {

It should then be possible to write a feature as such:

Scenario: Waiting for a really slow page
    Given I am on "a/slow/page"
    And I wait 30 seconds
    Then I should see "Something that took a long time to load"

At home on the Drupal-prairie

Behat as a BDD testing tool for Drupal seems to be steadily gaining in popularity. Some of the things Behat is capable of, such as this cool technique demonstrated by Jonathan Jordan of Metal Toad, make it a highly practical tool in ensuring your site is functioning correctly or helping you track down problems when it's not.

Oct 23 2013
Oct 23

Most of us who work with Drupal on a regular basis have probably come accross this error at some point:

Warning: Invalid argument supplied for foreach() in element_children() (line 6xxx of .../includes/common.inc).

The error is relatively self explanatory. Something that isn't iterable was passed into a foreach loop. The tricky part about debugging this one is that a lot of modules call element_children() and it can be hard to pinpoint where the error is coming from. Sometimes by process of elimination, you can figure it out, but the other day I was getting it intermittently and I needed some help. Here's a little snippet I inserted at the top of element_children():

if (!is_array($elements)) {
  // Print a function backtrace (needs devel module).
  // Supress the error
  return array();

It's pretty simple. We just check if $elements is an array (which it should be if it's being passed into element_children()) and print a function backtrace if it's not (ddebug_backtrace() is provided by the devel module). This should point you in the direction of the culprit. In my case it was a custom hook form alter that was acting on an array key that sometimes wasn't there. Total time to fix: about 2 minutes. Here's what element_children() looks like with the little debug snippet (don't forget to undo the snippet when you're done)

 * Identifies the children of an element array, optionally sorted by weight.
 * The children of a element array are those key/value pairs whose key does
 * not start with a '#'. See drupal_render() for details.
 * @param $elements
 *   The element array whose children are to be identified.
 * @param $sort
 *   Boolean to indicate whether the children should be sorted by weight.
 * @return
 *   The array keys of the element's children.
function element_children(&$elements, $sort = FALSE) {
  // BEGIN: debug code
  if (!is_array($elements)) {
    // Print a function backtrace (needs devel module).
    // Supress the error
    return array();
  // END: debug code
  // Do not attempt to sort elements which have already been sorted.
  $sort = isset($elements['#sorted']) ? !$elements['#sorted'] : $sort;
  // Filter out properties from the element, leaving only children.
  $children = array();
  $sortable = FALSE;
  foreach ($elements as $key => $value) {
    if ($key === '' || $key[0] !== '#') {
      $children[$key] = $value;
      if (is_array($value) && isset($value['#weight'])) {
        $sortable = TRUE;
  // Sort the children if necessary.
  if ($sort && $sortable) {
    uasort($children, 'element_sort');
    // Put the sorted children back into $elements in the correct order, to
    // preserve sorting if the same element is passed through
    // element_children() twice.
    foreach ($children as $key => $child) {
      $elements[$key] = $child;
    $elements['#sorted'] = TRUE;
  return array_keys($children);

Photo Credit: Web Scrapbook

Oct 07 2013
Oct 07

When creating a Drupal site with the hopes of user engagement you generally need users of the site to have an account. Often potential users don't want to share their email account or other personal information with just any random site on the internet and this barrier can be a huge hurdle when it comes time for them to signup. An effective way around this is to leverage third party login services such as Google+ or Facebook. In this short tutorial I'll show you how to enable the latter on your Drupal site.

The modules you'll need

Before getting started you're going to need a Drupal 7 site and some modules. Download each of the modules listed below,

or better yet, use drush

drush dl oauth oauthconnector oauth2 connector http_client ctools

Next, head to your modules page and enable the OAuth Connector module which will handily enable all dependencies needed or again use drush

drush pm-enable oauthconnector

With that part out of the way, head on over to Facebook and setup a Facebook App. You'll need to have a Facebook account and be logged in. Once you're both of those things follow this link to go directly to the Facebook developers area: https://developers.facebook.com/apps.

Give your app a name and optionally a namespace and/or category and click continue. You'll need to enter a captcha after this as well. I had a hard time with mine, but you should do fine.

After that you'll get to the good stuff. Go ahead and enter your app domain (eg. socialnetworklogins.com). After that we'll head back to your Drupal site. Don't close the Facebook app window just yet. You'll need it in a minute.

From the admin menu on your Drupal site, click Structure, then OAuth Connector.

The OAuth Connector developers have been nice enough to provide some presets for commonly used OAuth providers. Select Facebook from the Preset drop down and click Add Provider. All the defaults should be fine the way there are, but you'll want to grab both the OAuth Consumer Key and the OAuth Consumer Secret from your Facebook App and enter them into the appropriate fields. Don't close that Facebook App page yet!

You'll then want to scroll to the bottom of the page and open the two fieldsets labeled "NAME" and "AVATAR". These are values coming from the logging user's Facebook profile and being mapped to a Drupal user account.

Under the "Field to match on user profile" label select Username for the NAME and Picture for the AVATAR.

Click save and continue and you'll be back on the OAuth Connector listing page. What you'll need now from this page is your connectors callback URL. Copy it and head back to your Facebook App's settings page.

Once you've done that you can close the Facebook App page and head back to Drupal for the final stretch.

The last two things you'll need to do are allow Anonymous users access to the login button for Facebook and put that login button somewhere on your site. First start by loading up your permissions page and under the Connector fieldgroup look for a "Connect with Facebook" permission and check the box for Anonymous users.

And lastly, put the Connector block somewhere in a region on your site.

There you have it. Log out and navigate to any page that you placed the Connector block on, click it, and you'll be logging in with Facebook on your Drupal site.

Keep in mind that while your facebook app is in sandbox mode only admins of your app on Facebook will be able to successfully login to your site.

Oct 04 2013
Oct 04

For about a year now we've been working on a Drush-based server management tool called Seed. I've become quite accustomed to it. A few weeks ago, I decided that I wanted to install it on my home development environment which runs on OSX. Since it was developed originally for a Linux system I knew it should be *fairly* easy to modify it to work on OSX. Here's how to do it:

I wrote this tutorial specifically for OSX Mountain Lion (10.8.x). I imagine it will be pretty much exactly the same for Lion (10.7.x) but I can't say for sure. As far as a preliminary knowledge base, you'll need to be familiar with the command line and drush. Additionally, knowing a bit about apache and apache configuration will be helpful in case you need to debug anything along the way.

The Development Environment

Not everyone is going to have their development environment set up exactly as I set mine up. A lot of these components could be installed through any number of means. Additionally, you may be using a packaged solution like XAMPP or MAMP. It doesn't necessarily matter how you've set up your dev environment, but you do need to know where everything is installed. I've set up my local development environment using as many of the components that come bundled with OSX (Mountain Lion) as I can. For missing pieces I've installed homebrew. I'll give a quick rundown of what I've done to get my dev environment up and running.


You'll need git installed to leverage some of seed's project repo management features. You also need git for homebrew to work. By default, Mountain Lion doesn't come with git installed. Apple recommends installing XCode and enabling their "Command Line Tools" from there to get git. Which is exactly what I did. Although, I'm sure you could just run brew install git and get roughly the same results.


Installing homebrew is pretty straightforward. There are instructions on their site: http://brew.sh. Homebrew also requires ruby, but that also comes pre-installed on Mountain Lion.

One thing to note about Homebrew is that it installs everything into /usr/local/Cellar/ and typically uses a folder structure of /usr/local/Cellar/[package-name]/[version-number]

PHP & Apache

PHP and Apache come with Mountain Lion so I didn't have to do anything to install these.

  • PHP: Should just work. Type php --version to be sure.
  • Apache is installed in: /etc/apache2


Homebrew makes it pretty easy to install mysql.

$ brew install mysql

For a more detailed explanation of installing mysql using homebrew, read this post. Also, you may need to symlink the required socket file. It should be either at /tmp/mysql.sock or /var/mysql/mysql.sock. Whichever one is missing the mysql.sock file, just create a symlink to the existing one. On my machine, I was missing the socket file in /var/mysql so I had to do the following from the command line:

$ mkdir /var/mysql
$ ln -s /tmp/mysql.sock /var/mysql/mysql.sock


There's a few ways to install drush. I chose to use homebrew. It doesn't really matter how it's installed. All that matters is that you know where it's installed.

$ brew install drush

When installing drush with homebrew, the files will end up here: /usr/local/Cellar/drush/[version-number]/libexec/ where [version-number] is the drush version. On my system it's /usr/local/Cellar/drush/6.0.0/libexec/


This last tool is optional. When we add new projects with seed, we'll be creating virtual host entries for each one with a domain along the lines of project.username.localdev (I'm using the top-level-domain .localdev because we already use .dev internally on our central dev environment). For each one of these virtual hosts, we would usually need to add a new entry to our /etc/hosts file so that the domain resolves to our localhost server (since the hosts file doesn't understand wildcards). However, with dnsmasq, we can set it up to resolve all requests to the top-level domain .localdev to our localhost ip ( To install dnsmasq, we'll just turn to our trusty homebrew once again:

$ brew install dnsmasq

For installation instructions for dnsmasq, check out this concise post.

A note about dnsmasq on OSX: You should be able to use any top-level-domain except for .local. "local" is reserved by OS X for mDNS. If you run scutil --dns you'll see it near the top of the list

Installing Seed

Seed comes with some installation instructions in an INSTALL.txt file. These instructions are technically for a linux server, but some of the things in it will apply to our OSX installation as well.

Firstly, we'll need to setup our .drush folder (located in your home folder ~/.drush). The following is a list of commands to run from the command line. Lines beginning with '#' are comments and are there just to explain the following line(s) of code:

$ sudo mkdir ~/.drush/cache/default
$ sudo chmod -R 0775 ~/.drush/cache
# Replace chriseastwood with your user name
$ sudo chown -R chriseastwood:staff ~/.drush

Next, we're going to install seed into our .drush folder and symlink it to our drush installation:

# Go to your .drush folder
$ cd ~/.drush
# Clone the Repository
$ git clone https://bitbucket.org/evan_fuseinteractive/seed.git
# Create the symlink from
$ sudo ln -s /Users/chriseastwood/.drush/seed /usr/local/Cellar/drush/6.0.0/libexec/commands/seed
# Clear the drush cache
$ drush cc drush
# Install Seed. All this really does is make it so you can run seed commands by just typing 'seed command' instead of 'drush seed-command'
$ sudo drush seed-install

Setting up Seed

The next thing we need to do is setup the seed.info file. The seed.info file is just a php configuration file (.ini) where you setup some base variables for seed to work. This is where your knowledge of your development environment setup is needed. One thing to note about the info file is that you will be provided with some tokens to help with the dynamic creation of folders and filenames:

  • [USER]: The user calling the seed command
  • [PROJECT]: The project name the seed command is acting on.
  • [THIS.<variable>]: This is a way to reference variables defined within the info file itself. Most notably we use it to reference the defined global_user: [THIS.global_user]

Here's my seed.info file with some comments added to explain each part. (comments are lines that start with a semi-colon).

; You probably won't need to change your server port, but if you do this is
; where you'll do it
server_port = 80
; This is the filename template of vhost files created by seed. This will also
; be the local url of the site. I'm using the top-level-domain "localdev"
; because we already use "dev" internally
server_vhost_template = [PROJECT].[USER].localdev
; This is just a path where seed can store server information. It will store
; it in a folder named ".drush-server"
server_path = /Users/[THIS.global_user]
; The location to save drush alias files to. By default drush won't look for
; aliases in this folder, but I've added the path to my own drushrc.php file.
; See http://drush.ws/examples/example.aliases.drushrc.php for more info
alias_path = /Users/[THIS.global_user]/.drush/aliases
; MYSQL Credentials. I created a user with full privileges named "chris" for
; the purposes of this demo
mysql_host = localhost
mysql_user = chris
mysql_pass = chris
; The global user is sort of like the seed "superuser". The global user really
; only comes into play in a multi-user dev environment. It's a way for
; individual users to specify something should be global, rather than just for
; their own user.
global_user = chriseastwood
global_group = staff
; The following is where you set up your seed directories. These are essentially
; templates for where to keep your project, as well as db dumps, logs and files.
; We'll be making good use of some of our dynamic tokens here.
; This is the directory naming structure for all you projects.
project_directory = /Users/[USER]/Sites/drupal/www/[PROJECT]
; The directory structure where all your database dumps will be saved.
; Database dumps aren't user specific so we're just going to put them in the
; global user's directory. My local dev environment only has one user so the
; global user is one and the same, but this future proofs it for if I decide to
; go to a multi-user dev environment at some point
db_dump_directory = /Users/[THIS.global_user]/Sites/drupal/databases/[PROJECT]
; The directory structure where all your project logs will be saved
log_directory = /Users/[THIS.global_user]/Sites/drupal/logs/[PROJECT]
; The directory structure where your 'files' folders will be saved. With seed,
; your files folders are kept outside of the main drupal install and symlinked
; from sites/default (more on the files directory later)
files_directory = /Users/[THIS.global_user]/Sites/drupal/files/[PROJECT]
; Your global project directory. The template is usually the same as
; project_directory but with the global user instead of just [USER]
project_global_directory = /Users/[THIS.global_user]/Sites/drupal/www/[PROJECT]
; The global database user. In my case this is just the mysql user I created
project_global_db_user = chris
; This is the template for forming the repo url for git clone.
project_repo_template = [email protected]:git_fuseinteractive/[PROJECT].git
; A command template for initializing a new repo. This template is for
; bitbucket specifically but could be changed for github or some other hosted
; git provider (provided they have some method of making api calls from the
; command line - e.g. curl)
repo_init_cmd = "curl --request POST --user chris_fuseinteractive:password https://api.bitbucket.org/1.0/repositories/ --data name=[PROJECT] --data scm=git --data is_private=True"
; Set the enabled plugins for your seed install. These plugins are still sort
; of work in progress so I'm not going to go into too much detail.
plugins[] = bitbucket
; plugins[] = branches
; plugins[] = quality
; plugins[] = checklist
; The following variables are command templates for performing some API requests
; with Bitbucket. These are only used if the bitbucket plugin is enabled.
; The only thing you'll really need to change here is the bitbucket username
bitbucket_new_key_cmd = "curl --request POST --user chris_fuseinteractive:password https://api.bitbucket.org/1.0/ssh-keys/ --data label='[Seed] [USER]' --data key=[KEY]"
bitbucket_del_key_cmd = "curl --request DELETE --user chris_fuseinteractive:password https://api.bitbucket.org/1.0/ssh-keys/[KEY]/"

A Note about Directories:

I've set up all my project files to be in /Users/username/Sites/drupal . The only reason I did this is because the /Users/username/Sites folder gets an special icon in Finder. Alternatively you could do something more linuxy like /home/username/www for project folders, /home/username/databases for dbs etc. I've also been toying with the idea of using something like Google Drive or Dropbox as a simple "cloud" storage solution for my databases and files directories.

Configuring Apache

The next thing we need to do is set up our apache httpd configuration. If you've already started apache using sudo apachectl start, run sudo apachectl stop to kill it. We're going to be conceding control of apache over to seed. When we're done you'll start/stop/restart your server using seed start/stop/restart.

When you start your server using seed start, seed starts apache using it's own httpd.conf file instead of the one in /etc/apache2/. The configuration file it uses is generated from a template file kept in ~/.drush/seed/templates/httpd.tpl.php. We're going to need to modify this a little to work with our Mac's apache setup. Here's the modified file with some extra comments to highlight the changes.

 * This is the default httpd.conf template for Drush Server running on Ubuntu.
 * Available Variables:
 * - host
 * - port
 * - uri
 * - conf_path
 * - base_path
 * - log_path
 * - doc_root
# Generated by drush-server.
ServerRoot /etc/apache2
# Required modules
# Include mods-enabled/*.load <-- #### Comment this line out
# Include mods-enabled/*.conf <-- #### Comment this line out
# Include our original httpd.conf file.
# We're including this because it has some basic apache configuration directives
# that we know works on our mac. Additionally it loads all the modules we'll be
# needing. You could alternatively just manually copy only the directives you
# want to keep intact here, but I found this to just be easier. One thing to
# note is that if you do include the original httpd.conf file as we are here,
# you'll need to comment out "Listen 80" in it as we declare that later in this
# file
Include /etc/apache2/httpd.conf
<IfModule php5_module>
  AddType application/x-httpd-php .php
  AddType application/x-httpd-php-source .phps
  <IfModule dir_module>
    DirectoryIndex index.html index.php
ExtendedStatus On
<Location /server-status>
  # Turn of rewrite rules or else Drupal's .htaccess rewrite rules will clobber
  # this location.
  <IfModule rewrite_module>
    RewriteEngine off
  SetHandler server-status
  Order Deny,Allow
  Allow from all
# Point to the Ubuntu file
# TypesConfig /etc/mime.types <-- #### Comment this out - file does not exist on osx
# Use the www-data:www-data user:group
# Change this to your global user name with the group 'staff'
User chriseastwood
Group staff
# Custom configuration built by drush
PidFile <?php print $conf_path; ?>/httpd.pid
LockFile <?php print $conf_path; ?>/accept.lock
ServerName <?php print $host; ?>
<?php foreach ($ports as $port): ?>
Listen <?php print $port ."\n"; // Save me from the line-break monster! ?>
<?php endforeach; ?>
<IfModule ssl_module>
  Listen 443
ErrorLog <?php print $log_path; ?>/error_log
LogFormat "%h %l %u %t \"%r\" %>s %b" combined
CustomLog <?php print $log_path; ?>/access_log combined
# Use name-based virtual hosting.
<?php foreach ($ports as $port): ?>
NameVirtualHost *:<?php print $port ."\n"; // Save me from the line-break monster! ?>
<?php endforeach; ?>
<IfModule ssl_module>
  NameVirtualHost *:443
Include <?php print $conf_path; ?>/sites/

NOTE: Don't forget to comment out "Listen 80" from your original /etc/apache2/httpd.conf file since it's already being declared in seed's httpd.conf file!

The Virtualhost Templat

Additionally, seed comes with a vhost template (~/.drush/seed/templates/vhost.tpl.php) that you could alter depending on your needs. For the purposes of this demo (and probably for most setups), we're going to leave this template in it's stock form. However, the template does have a block for ssl support which means you may need to create a dummy ssl certificate. It's specifically looking for /etc/apache2/dev.crt and /etc/apache2/dev.key. Here's an uncommented set of commands you can run from the command line to generate the necessary files. There are some prompts. It's up to you if you want to use passphrases. I didn't.

$ cd /etc/apache2
$ sudo ssh-keygen -f dev.key
$ sudo openssl req -new -key dev.key -out dev.csr
$ sudo openssl x509 -req -days 365 -in dev.csr -signkey dev.key -out dev.crt

Start Using Seed!

And that's it! Seed is set up. Open your command line and start seed:

your-mac:~ username$ seed start

Now create a new project with seed init:

your-mac:~ username$ seed init
Project name: d7sandbox
Enter a number.
 [0]  :  Cancel
 [1]  :  Clone an existing project repository
 [2]  :  Create a new project
 [3]  :  Copy project from another user
[Project] Successfully created directory /Users/chriseastwood/Sites/drupal/www/d7sandbox
Use git? (y/n): y
Initialize new remote: [email protected]:git_fuseinteractive/d7sandbox.git ? (y/n): y
[Project] Initialized git repository
[Project] Initialized git remote
[Project] Added remote [email protected]:git_fuseinteractive/d7sandbox.git
Symlink the files directory? (y/n): n
[Database] MySQL user chris already exists.
[Database] Created MySQL database d7sandbox for user chris
Create database d7sandbox_chriseastwood? (y/n): n
Create a settings.php file with the database d7sandbox? (y/n): n
Create Drush alias? (y/n): y
User [chriseastwood]:
Web root path [/Users/chriseastwood/Sites/drupal/www/d7sandbox]:
Files path [/Users/chriseastwood/Sites/drupal/www/d7sandbox/sites/default/files]:
Database dump path [/Users/chriseastwood/Sites/drupal/databases/d7sandbox]:
URI [d7sandbox.chriseastwood.localdev]:
[Alias] Could not read the alias file /Users/chriseastwood/.drush/aliases/d7sandbox.aliases.drushrc.php. This is not a fatal error however it may result in aliases not being created.
[Alias] Created new alias: @d7sandbox.dev.chriseastwood
Host name [d7sandbox.chriseastwood.localdev]:
Document root [/Users/chriseastwood/Sites/drupal/www/d7sandbox]:
[Added] d7sandbox.chriseastwood.localdev -> /Users/chriseastwood/Sites/drupal/www/d7sandbox
Server stopped.
No errors found in the server configuration.
Server started with the following virtual hosts:
 d7sandbox.chriseastwood.localdev  ->  /Users/chriseastwood/Sites/drupal/www/d7sandbox
Server restarted.

Note the non-fatal error when creating the alias. This is because seed tries to read the alias file (presumably to check for existence) before creating it. In this case the error can be ignored. Our alias is created correctly.

Also, don't forget to add an entry in you /etc/hosts file for each virtual host you create if you decided not to install dnsmasq.

Here's another example of using seed init to initialize an existing project that's already in version control.

your-mac:~ username$ seed init
Project name: existingproject
Enter a number.
 [0]  :  Cancel
 [1]  :  Clone an existing project repository
 [2]  :  Create a new project
 [3]  :  Copy project from another user
[Project] Successfully created directory /Users/chriseastwood/Sites/drupal/www/existingproject
Repository URL [[email protected]:git_fuseinteractive/existingproject.git]:
[Project] Cloning repository [email protected]:git_fuseinteractive/existingproject.git
[Project] This could take awhile depending on the size of the repository...
Cloning into '/Users/chriseastwood/Sites/drupal/www/existingproject'...
remote: Counting objects: 5799, done.
remote: Compressing objects: 100% (4444/4444), done.
remote: Total 5799 (delta 1119), reused 5799 (delta 1119)
Receiving objects: 100% (5799/5799), 15.90 MiB | 1.55 MiB/s, done.
Resolving deltas: 100% (1119/1119), done.
Checking out files: 100% (4224/4224), done.
[Project] Cloning repository complete
Symlink the files directory? (y/n): y
Target directory [/Users/chriseastwood/Sites/drupal/files/existingproject]:
Symlink the files directory for existingproject (user chriseastwood) to the files directory /Users/chriseastwood/Sites/drupal/files/existingproject? (y/n): y
[Project] Symlinked /Users/chriseastwood/Sites/drupal/www/existingproject/sites/default/files to /Users/chriseastwood/Sites/drupal/files/existingproject
[Database] MySQL user chris already exists.
[Database] Created MySQL database existingproject for user chris
Create database existingproject_chriseastwood? (y/n): n
Create a settings.php file with the database existingproject? (y/n): y
MySQL username to connect to this database [chris]:
MySQL password to connect to this database: chris
Create Drush alias? (y/n): y
User [chriseastwood]:
Web root path [/Users/chriseastwood/Sites/drupal/www/existingproject]:
Files path [/Users/chriseastwood/Sites/drupal/www/existingproject/sites/default/files]:
Database dump path [/Users/chriseastwood/Sites/drupal/databases/existingproject]:
URI [existingproject.chriseastwood.localdev]:
[Alias] Could not read the alias file /Users/chriseastwood/.drush/aliases/existingproject.aliases.drushrc.php. This is not a fatal error however it may result in aliases not being created.
[Alias] Could not read the alias file /Users/chriseastwood/.drush/aliases/existingproject.aliases.drushrc.php. This is not a fatal error however it may result in aliases not being created.
[Alias] Created new alias: @existingproject.dev.chriseastwood
Host name [existingproject.chriseastwood.localdev]:
Document root [/Users/chriseastwood/Sites/drupal/www/existingproject]:
[Added] existingproject.chriseastwood.localdev -> /Users/chriseastwood/Sites/drupal/www/existingproject
Server stopped.
No errors found in the server configuration.
Server started with the following virtual hosts:
 existingproject.chriseastwood.localdev  ->  /Users/chriseastwood/Sites/drupal/www/existingproject
 taymor.chriseastwood.localdev    ->  /Users/chriseastwood/Sites/drupal/www/taymor
Server restarted.

Next we're going to load the database (which we've put into our databases folder - /Users/chriseastwood/Sites/drupal/databases/existingproject/)

your-mac:~ username$ seed db-load
Project name: existingproject
User name [chriseastwood]:
Load in to database [existingproject_chriseastwood]: existingproject
Backup the database first? (y/n): n
Which DB should be loaded?
 [0]  :  Cancel
 [1]  :  existingproject.sql
[Database] Deleted MySQL database existingproject
[Database] Created MySQL database existingproject for user chriseastwood
[Database] Back up /Users/chriseastwood/Sites/drupal/databases/existingproject/existingproject.sql loaded in to database existingproject

And I'm up and running and ready to go!

Upgrading Seed

There is one caveat with the way I've set up Seed on my local system. I've edited core files of seed so when I go to upgrade, my configurations will be overwritten. For now the simplest solution is to move my customizations out of the seed folder, and then create symlinks to them. This way when I upgrade seed, at least my files will still be safe

$ mkdir ~/.seed
$ mv ~/.drush/seed/seed.info ~/.seed/
$ mv ~/.drush/seed/templates/httpd.tpl.php ~/.seed/
$ ln -s ~/.seed/seed.info ~/.drush/seed/seed.info
$ ln -s ~/.seed/httpd.tpl.php ~/.drush/seed/templates/httpd.tpl.php

In the future, however, we're working on setting up a way to supply your own seed overrides without having to create symlinks. Keep an eye out for that in future releases!


So there it is: Seed. On your Mac. Your one stop shop for all your drupal server management needs. And somehow I managed to get through the whole tutorial without a single horticultural pun (except for the title). Now go forth and let Seed form the roots that helps your projects grow bigger, stronger, and faster!

Sep 30 2013
Sep 30

When a user is trying to search something on your website, you want to help them find what they want quickly. We are human, we make mistakes. When users make a typo, we should try to guess what they are looking for so that they don't have to type it again. Especially when more users use their smart phone/tablet to browse the web. It is much harder for them to re-type their searches.


If the user's search didn't return anything due to spelling error, give a suggestion and list the suggestion search result.


The default Drupal site search doesn't come with spellcheck feature. Good news is Apache Solr does! There are 2 options on setting up Solr, you can use the Apachesolr module or the Search API Solr module. I prefer Search API Solr because you can have a better control of what data to index and how the result data should be processed. So in this post I will talk about how to do it with Search API and Search API Spellcheck modules.

I won't go into how to setup Solr or setting up the index. I am assuming you already have a search index setup.

  1. Install and enable Search API, Search API Solr and Search API Spellcheck modules
  2. At the time of writing this post, we will need to patch the Search API Solr and spellcheck modules  https://drupal.org/node/2078257
  3. Create a views search page, add the fields that you want to output.
  4. Add the "Search: Spellcheck" field in your Views Header. This will output the message “Did you mean [some suggestion]?” in the header if a suggestion is returned by Solr.
  5. Add the “Search: Fulltext search” exposed filter.
  6. (optional) If you don't have access to change your Solr settings, you will need to use the hook_search_api_solr_query_alter() function to adjust the solr settings. Checkout the Solr Wiki page to get more information on the settings below. You might want to tweak the "spellcheck.accuracy" and "spellcheck.maxCollationTries" settings to suite your requirement. The lower the accuracy, the higher chance of returning a suggestion. So you will want to find a balance.
    function MYMODULE_search_api_solr_query_alter(array &$call_args, SearchApiQueryInterface $query) { 
      $call_args['params']['spellcheck.accuracy'] = 0.7;
      $call_args['params']['spellcheck.collate'] = 'true';
      $call_args['params']['spellcheck.collateExtendedResults'] = 'true';
      $call_args['params']['spellcheck.onlyMorePopular'] = 'false';
      $call_args['params']['spellcheck.maxCollationTries'] = 1;
  7. Add hook_search_api_solr_search_results_alter() function to search the suggested.
    function MYMODULE_search_api_solr_search_results_alter(array &$results, SearchApiQueryInterface $query, $response) {
      if (!count($results['results']) && !empty($response->spellcheck->suggestions->collation->collationQuery)) {
        $collateResults = $query->execute();
        $results['results'] = $collateResults['results'];
        $results['result count'] = $collateResults['result count'];


And there you have it. If you are already using Search API and Solr, this is a quick way to improve user's experience on your site. Feel free to post any comments or other suggestions below.

Photo Credit: http://www.flickr.com/photos/jwyg/

Sep 09 2013
Sep 09

So you’ve got a shiny new website. It’s been built on Drupal, the best Open Source CMS platform on the planet. You can easily manage all the content on your website. It’s responsive and works beautifully on all devices. It’s so nice not to need your agency, or web shop anymore isn’t it - you have the power! But wait… one day you wake up, pour yourself a cup of joe and login to your site to post that press release that needs to go out today... and see this unnerving warning:

Hmm… what do you do now? Should you act on that message? Will something break if you do what it tells you to? Should you ignore it and hope it goes away? If I call my developer is he going to laugh, yell or charge me?

At Fuse we often get businesses coming to us with these very questions. Their agency or web shop used the right tool to build the site, but they neglected to inform them that Drupal has certain needs after their site goes live. Often these needs go unaddressed for months or even years.

Why are security updates important?

While Drupal is considered to be as secure or more secure than it’s commercial equivalents, like any other piece of web software or CMS platform it requires maintenance. Hackers are very active in their attempts to find vulnerabilities in all the major web platforms. While Drupal is less targeted than Wordpress (partly due to the sheer number of Wordpress sites out there, and partly due to the relative ease of exploiting Wordpress sites), vulnerabilities are still discovered and reported to the Drupal security team. Drupal core updates are typically released on a monthly basis and include patching security holes identified by the community or audits by the Drupal security team.

If Drupal security updates are not applied with regularity you are opening yourself up to vulnerabilities. These vulnerabilities vary in severity from site to site depending on how Drupal has been configured and how old Drupal core and contributed modules are. Will your site be hacked for sure if you don’t update regularly? Well...probably not, but your chances are definitely higher. Think about security updates as doing regular oil changes or checking the brakes on your car.

Common problems with sites we inherit

When we inherit sites built by other developers we come across some common issues. The biggest issues come from sites built by developers that don’t specialize in Drupal. Inexperienced Drupal developers will often hack Drupal’s core or contributed modules to make Drupal do what they want it to in the fastest way possible. The problem with taking these shortcuts is that it can make security updates difficult to apply without breaking site functionality.

When we are considering whether or not to take on the ongoing maintenance of a site built by another developer or shop, we start by evaluating the integrity and securability of the site. I won’t go into our site audit process here as Codi has explained things rather well in his recent post, but one of the first things we need to determine is if there were shortcuts taken in the build that would compromise our ability to keep the site secure. If we can’t apply security updates because of Drupal core or contributed module hacks, we will either say no to the work or insist that we remediate the code so that we can keep it secure. This can often be quite time consuming depending on the severity of the hacks.

The long & the short of it is that Drupal is much happier when it hasn’t been hacked and stays up to date. But most importantly, you as a website administrator can rest a little easier knowing you are doing everything you can to ensure your site and mission critical data is secure.

Other culprits

We also recognize that Drupal is not the only way that hackers can get in. Server software can often be the culprit. Today’s VPS’s and shared hosting scenarios give you tonnes of software as part of their offering, but if this software is not kept up to date, your site could be compromised even with all the latest Drupal security updates. The sites we host for clients are run on very stripped down servers (without any extraneous software), kept up to date and tuned specifically for Drupal, thus minimizing the risks associated with server software.

Our approach to Drupal security

It all starts with the build. We don’t hack core. We don’t hack contrib modules. We do occasionally patch contributed modules, but we do so in a way that does not compromise the securability of the module.

When we launch a site for a client we strongly recommend signing up for a monitoring & security package and insist on it if we are hosting the site. The cost of this varies based on the complexity of the site. Andre’s post from a few months back describes in more detail what we do for our clients as part of a monitoring & security package, but essentially we monitor Drupal core and module statuses (with Nagios), apply & test updates in our development environment, remediate any issues that may come up as a result of the update, then deploy & test again on the production server.

It all sounds a bit tedious, but worth it for peace of mind & a better night’s sleep. Our clients can focus on creating and managing great content instead of worrying about the integrity of their site.

photo credit: Pascal

Sep 03 2013
Sep 03

Taking over an existing site from a contract developer or company that doesn't primarily build Drupal sites (or even some that do) can often be a daunting task.

Often we find our clients assume that all Drupal sites are created equal, and as we're taking it over we can just dive right in and start developing. Unfortunately that's not always the case so we end up doing a site audit.

A site audit accomplishes a couple things:

  • Provides the client with an overview of where their site currently sits in terms of performace, security and general quality of the build.
  • It provides us with a good knowledge of the estimated effort needed before "diving in".

Firstly, we never touch the live site so it's important to grab the up to date code and database and load them into our local development server where we're free to run our audit tools.


The first tool we generally run is Hacked! which very quickly lets us see if any contributed code or the Drupal core has been modified. Sometimes modifying code is a neccessary evil when modules need to be patched to provide functionality that is not currently committed to the module. More often than not though, it's quick hacks to provide custom functionality for the site which is a big no-no for us. Hacking contributed code prevents us from updating the modules easily and can introduce exploitable code that goes unchecked by the drupal community. Hacked! currently needs to be present in your sites' modules folder for the drush command to work, but if you apply this patch to a download of Hacked! placed in your Drush modules folder it'll be available as a standalone Drush command.

Site Audit

The Site Audit module provides us a general overview of common config options that should generally be set in a production environment without manually checking them. This can give you a good idea of how performant the site is so if speed has been an issue for your client's site then this can help. Site Audit runs as a standalone drush command and, even better, can report on Hacked!'s status with this patch.

Security Review

The Security Review module does similair checks to the Site Audit module, but with a focus on security. An important one is making sure that inputs don't accept PHP and that no PHP or Javascript code is currently contained within nodes and comments.

The next few things are where you'll have to get your hands dirty and actually dig into the site and it's code.

  • You'll want to check for custom modules and take a look at what they're doing.
  • Take a look at the watchdog table if logging was turned on for the site. You'll want to see if any modules are causing things like PHP warnings, errors or 404's.
  • Also make sure you take a look at the theme(s) enabled on the site. You'll want to check things like the number of templates, the code in the templates and most importantly check for any funny business in the template.php file.

These aren't the only things we do, but it's a quick ten foot view of the site that can give us an idea of the effort involved in taking on a new client's site. Typically we'll generate a report for a client with the aggregated information from our various reports and provide it to a client with a recommendation for next steps.

Let us know how you perform site audits in the comments!

Aug 03 2013
Aug 03

· We want to minimize down time when deploying changes to the live site.

· We want to be able to test changes on live server environment.

· We want to easily roll back changes.

1. How to minimize down time when deploying changes to the live site?

Deploy your site’s code to a separate folder. If your live site folder is in public_html, rename it to public_html_v1 and then create a symlink file call public_html and link it to the previous folder.

Always use version control such as Git or SVN, so you don’t have to manually FTP files to the server and forgetting to upload a file. Another benefit is that you can tell if someone made changes to the code. If a hacker accesses the site or another company that has access makes changes to the site files, you would be able to see what files were changed and you can easily undo their changes.

Use the Feature, Context, Deploy and/or Feeds modules to put configurations in the code so you don’t have to manually populate content and configure settings again on the live site.

Upload the latest version of the site to public_html_v2. This way, your live site is still running while the codes is being pushed to the server. In addition, when we want to pull the trigger to switch to live, all we need is to edit the symlink to point to a different folder and don’t have to modify any webserver settings. If your site has user generated content and comments, or if you need to run any database changes then you should put the site offline first and copy the live db to the staging db first before you do the symlink change.

Command to edit symlink:
ln -sfn public_html_v2 public_html
ln -sfn public_html_v1 public_html_stg

Move the files folder out of your webroot’s sites/default folder so that the live site and the staging site can symlink to it. Sometimes a site’s files folder is really big and the server doesn’t have enough room to have a copy of it. You would have to share the live files folder with the staging site.

2. How to test changes on live server environment?

In most cases, your live server will have different settings than your development environment. The live server might be running Linux while you are developing on Mac or Windows. The server’s PHP version might be older, with PHP libraries such as cURL or PDFlib not installed. Your hosting company might disable some PHP function that you need.

Setup a staging site so you can catch these errors before your users see them. Create a sub-domain and separate database.

3. How to easily roll back changes?

Just use your source control software to checkout to the previous version. In the worst case scenario, when rolling back to the previous version with source control is not enough, just edit your public_html symlink and point it back to the previous version’s folder. This way you will have the previous version of the site with the database intact.

Hope you are able to pick up some tips from this article. Feel free to post any comments or other deployment ideas below.

Jul 08 2013
Jul 08

A while back, a client asked how they could add an item to a select list on one of their content types. At the time the options in the select list were managed by the allowed values field on the field settings form (using the key|value syntax). I definitely didn't want to give the client permissions to edit content types. So what do I do? Do I use taxonomy? I was actually specifically not using taxonomy in this case. I didn't need any of the extra overhead that comes along with it (term listing pages, fields, entity bloat etc). I just wanted a simple interface to manage sets of key value pairs. A quick google search and I found the Values module.

I recently became a co-maintainer on the values project and have been working on a D7 upgrade and release. I've fixed all the issues in the issue queue and just pushed a release candidate so I figure it's a good time to highlight the module. The following is specifically for the D7 version, but most of the stuff is the same in D6.


At it's core, Values is an interface for managing reusable sets of key-value pairs. It also provides a light api for storing and retrieving value sets from the database. To get started go to admin/structure/values and click "Create a value set".

This will take you to the value set edit form. Enter a value set name (the machine name will be generated automatically) and a description (optional). Then start populating your values. The keys for the values will be generated automatically as well, but you can change them if you want.

Hit save and you're done. Your value set is saved and ready for use... but where are you going to use it?

Values Field

This submodule is what actually allows you to use your value sets as allowed values for any "List" type field. It's pretty straight forward to use. Just create your value set and then select it from the list on the field settings page:

Values Webform

Another submodule that adds support for Value sets to select components in webforms:

Features Support

Although it's not a requirement of the module, Values comes with ctools support to create exportable value sets. This means that you'll get Features support out of the box to push your value sets between your dev/stg/prod environments. You can also export indvidual value sets from the edit page by clicking the "Export" tab.


Exports made using the ctools method above can be imported (obviously), but there is another format you can use to import value sets. On the import page (admin/structure/values/import) you have the option of doing a ctools import or just a flat list of values. Keys will be generated automatically but if you want to specify them you can use the old key|value format. This can be useful if you have a large list of values in a text file that you just want to bulk import quickly.


And there you have it. A one stop shop to manage value sets in Drupal. Give it a try and hit up the issue queue if you need help, or if there's a feature you're looking for.

Jul 03 2013
Jul 03

A little idea has been germinating in our heads for a while here at Fuse.

We’ve had a small distribution that we’ve used as the foundation for all of our new client builds that’s served us well in the past. It provided a healthy amount of default configuration for every possible site we would be building. Things like common module configurations, developer tools, default path aliases, additional quick links in the shortcut bar, and even a sub theme based on our base theme at the time of adaptive theme.

One big thing that’s been lacking though is the usability of the client administration interface. A lot of work and thought went into making our jobs easier from the get go, but not much for the users who would end up managing their new site.

So in the last few months we’ve put a renewed emphasis on building atop our previous distribution to provide a thoughtful, easy to use and consistent administrative user interface for our clients. We’ve pulled from our over five years of experience building nothing but Drupal sites and also borrowed a few things from other projects such as Panopoly, Wordpress and even some nice things from Drupal 8. We’re calling it Sprout, and we might be going out on a limb here, but we think you’ll like it.

One of the first things we did differently from our last distribution was exporting as much of the configuration as we could into Features instead of relying on an install profile. This would allow us to pick and choose the features we want to use instead of always needing to start with the full Sprout distribution, but more importantly it allows us to retrofit Sprout into older client sites. The more consistent we can make the administration of our sites, the easier and more effective our training and documentation can be.

The Features

We took the opportunity while “featurizing” to break apart the functionality in the most logical way for us right now. This lends itself to extendability down the road and hopefully makes the most sense for anyone who wants to cherry pick the features they need for a particular site. As such, some features are pretty light on substance for the time being.

The main Sprout project contains the installation profile and the make file for downloading all the dependencies using drush make. You can check out how to get your own sprout on the sandbox page.

Currently a catch all for defaults that don’t currently have a good home.

  • Provides a two column panels based content edit screen
  • Sets default values for new content types using the Content Type: Extras module (no more having to remember to turn commenting off!)
  • Sets our administrative theme as default for the admin pages as well as content edit screens
  • Sets up a linkit profile for easier linking to both internal and external content
  • Integrates the scheduler module by default to provide a more intuitive publishing flow
  • Provides developer related defaults for devel, views and performance options
  • Provides image handling primarily through the imce module
  • Sets up a content editor role
  • Sets up a site manager role
  • Provides some default permissions out of the box
  • Provides a wysiwyg text format
  • General cleanup to the ckeditor interface including button grouping and preventing of horizontal resizing of wysiwyg editor

Building on some work that Chris Eastwood has been doing here at Fuse with the Wysiwyg module, we’ve bundled up all the possible niceties we could find for Wysiwyg and for our preferred wysiwyg editor, ckeditor.

Adds some Sprout specific CSS building on an admin sub-theme extending a cloned and grafted version of Rubik with RTBC’d patches from the Rubik issue queue applied.

Where to now?

We still have a lot of work to do (which is why it's still a sandbox project) but we're hoping to put a lot of effort into Sprout over the next few weeks. A few things on our radar right now are:

  • Better out of the box dashboard for all applicable roles
  • Even more image handling capabilities
  • More front end niceties using automatic contextual links where possible
  • Full project status

Thanks for reading, now make like a tree and leave. Oh, and why the name Sprout? Because it all starts with Seed.

Jun 20 2013
Jun 20
*/ /*-->*/


The biggest feature to land since the initial release of Seed is support for a Git branching workflow. A common scenario in the Seed workflow is to have multiple people working on a project, each with their own copy of the project's code but sharing a database so content and configuration is consistent. However, consider the following (simplified) scenario:

  • John and Jane are working on the same project, with a common database but each with their own checkout of the code.
  • John installs a new module which creates a block automatically, clears the site's cache and places the block in a region with Drupal's blocks administration.
  • Shortly thereafter Jane reloads a page. Since she doesn't have the new module John has installed, Drupal removes references to it from the database, including the block configuration.
  • John no longer sees the block in the region he just configured. In fact, he can no longer find the block on the block administration page.

This scenario is one we've encountered a lot in our development workflow. Generally it would require the person adding a new hook to commit the new code and for others to pull it, then clear caches. It also closely resembles a workflow known as Feature Branches, whereby new functionality is developed partially in isolation and then merged back in to the main branch.

In Seed we've now got some basic support for this workflow. We're introducing a new feature-branch command which will take a branch name, and create corresponding Git branch as well as a database where you can implement hooks or try out new modules in isolation without worrying about someone else overwriting your new functionality.

When you're ready, merging is also a single command: merge-branch. This will attempt to merge your branch in to the master branch, and if successful will optionally delete your branch and the cloned database.

Unfortunately we're still subject to Drupal's insistence of keeping configuration and content in the database, at least until Drupal 8, so any configuration or content created as part of your branch will have to be manually migrated or recreated on the "master" database. This is still an area we'd like to improve upon with Seed.

Other Improvements

There's been a number of other improvements, changes and bug fixes that have made their way in to Seed.

Folder structure

Previously we had a fairly strict folder structure based on our previous experience of how we'd lay out a project. After some consideration here at Fuse we've decided, internally, the change the way we lay out our projects. As a result, we've made file and folder paths more flexible and configurable.


Seed has a lot of commands (with this release we're up to 30) and it's not always easy remembering what each one does and the arguments they take. To help ease this, the command line documentation has been vastly improved. Running seed with no additional arguments lists all commands with a description of each (the same as running drush help --filter=seed, also note we've improved a lot of the command aliases). If you'd like more information on a particular command, running seed commands will also list all the commands provided by Seed, but with the ability to choose a command which will then return all the arguments supported by the command as well as an example usage of the command.

Using Seed

With the work that's gone in to Seed in the last few weeks, we're tagging a proper 1.0 1.0.1 release. If you have any questions regarding the installation or use of Seed, or you'd like to additional functionality added, please don't hesitate to open an issue against the Seed repository.

Jun 20 2013
Jun 20
*/ /*-->*/


The biggest feature to land since the initial release of Seed is support for a Git branching workflow. A common scenario in the Seed workflow is to have multiple people working on a project, each with their own copy of the project's code but sharing a database so content and configuration is consistent. However, consider the following (simplified) scenario:

  • John and Jane are working on the same project, with a common database but each with their own checkout of the code.
  • John installs a new module which creates a block automatically, clears the site's cache and places the block in a region with Drupal's blocks administration.
  • Shortly thereafter Jane reloads a page. Since she doesn't have the new module John has installed, Drupal removes references to it from the database, including the block configuration.
  • John no longer sees the block in the region he just configured. In fact, he can no longer find the block on the block administration page.

This scenario is one we've encountered a lot in our development workflow. Generally it would require the person adding a new hook to commit the new code and for others to pull it, then clear caches. It also closely resembles a workflow known as Feature Branches, whereby new functionality is developed partially in isolation and then merged back in to the main branch.

In Seed we've now got some basic support for this workflow. We're introducing a new feature-branch command which will take a branch name, and create corresponding Git branch as well as a database where you can implement hooks or try out new modules in isolation without worrying about someone else overwriting your new functionality.

When you're ready, merging is also a single command: merge-branch. This will attempt to merge your branch in to the master branch, and if successful will optionally delete your branch and the cloned database.

Unfortunately we're still subject to Drupal's insistence of keeping configuration and content in the database, at least until Drupal 8, so any configuration or content created as part of your branch will have to be manually migrated or recreated on the "master" database. This is still an area we'd like to improve upon with Seed.

Other Improvements

There's been a number of other improvements, changes and bug fixes that have made their way in to Seed.

Folder structure

Previously we had a fairly strict folder structure based on our previous experience of how we'd lay out a project. After some consideration here at Fuse we've decided, internally, the change the way we lay out our projects. As a result, we've made file and folder paths more flexible and configurable.


Seed has a lot of commands (with this release we're up to 30) and it's not always easy remembering what each one does and the arguments they take. To help ease this, the command line documentation has been vastly improved. Running seed with no additional arguments lists all commands with a description of each (the same as running drush help --filter=seed, also note we've improved a lot of the command aliases). If you'd like more information on a particular command, running seed commands will also list all the commands provided by Seed, but with the ability to choose a command which will then return all the arguments supported by the command as well as an example usage of the command.

Using Seed

With the work that's gone in to Seed in the last few weeks, we're tagging a proper 1.0 1.0.1 release. If you have any questions regarding the installation or use of Seed, or you'd like to additional functionality added, please don't hesitate to open an issue against the Seed repository.

Jun 14 2013
Jun 14

Design is hard. Making design work well is even harder.

Being completely enveloped in the wide world of web design has made me rather comfortable with being a critic of not only design decisions of others, but a harsh critic of what I produce.

It’s incredibly comforting to know that this dilemma is not just ours to suffer with. Stepping away from the internet and taking the time to examine just how other industries face the same issues.

How often does one walk into a friends home and have a hard time figuring out which switch turns on the bathroom light, and which handles the fan? How often does one have to apologize for accidentally turning on the garbage disposal in the middle of the night? This is just one simple example of poor user experience design that we face on a frequent basis.

A recent, and more apparent personal experience has really got my thoughts racing whenever Photoshop is launched.

I was recently tasked with selling off a handful of power tools that we’re idling in my parents’ garage. One of these items was a relatively new Ridgid Compound Miter Saw. It’s a saw. It cuts things precisely. It serves a single purpose. How can this possibly relate to the topic at hand?

Soon after the ad went up on Craigslist, I had a potential buyer come by to take a look. I had rolled it out, plugged it in, and told him to go at it to make sure it was worth his money. It was in that moment that I realized that “User Experience” goes well beyond what I do in my field.

The man, who clearly knew how miter saws function, was having a hard time figuring out why his interactions were doing nothing to allow him to tilt the head and rotate the table. After several attempts, he looked to me for help.

Levers and knobs that are standard on all other miter saws, and have been for the better part of a decade, have been replaced with aesthetically pleasing levers, and nondescript rotational locks. While looking great in their orange and gray, an experienced user needed to re-learn how this device functions.

The team at Ridgid was faced with the dilemma we face on a regular basis: use conventions, or make it look pretty. In their eyes, the design outweighed standards used by their predecessors and current competition.

The miter saw was sold that day, but the lesson I learned will stick with me for years to come.

Design is important. We as a global web community need to continue pushing the boundaries of design. Instead of criticizing Jonathan Ive over the look of icons used in a beta version of iOS7, let’s focus our attention on making design work well.

Jun 07 2013
Jun 07

Here at Fuse, we’ve been working on a few commerce projects lately. We've been using Commerce Kickstart as the base for some projects and customize to meet client business requirements. By using Kickstart, we no longer need to download a long list of modules and configure them individually, it saves us many hours of work and save money for our clients. For users that are new to Drupal, and want to have an online store, Commerce Kickstart is great. It comes with all the contributed modules that are necessary to run a basic online store. However, there are some modules that Kickstart doesn't include but should be on all Commerce sites. For newbies who installed Kickstart as their first Drupal site and don't know much about the thousands of contributed modules out there, the following list of modules should definitely be considered.

It provides a beautiful dashboard of your store’s sales. It uses different types of charts (line, pie and bar charts) to give you a sense of how the store is doing quickly.

It also comes with a handy tax report that might make your life easier when reconciling.

Learn how your customers found you on the web. Which page they first land on so you will know what your visitors are looking for. Here is a great tutorial on how to configure Google Analytics.

Automatically generate a sitemap for sending to search engine. This will help improve your search engine ranking and potentially bring more visitors to your store.

Another SEO module. Even though keyword meta tags are less important to search engine these days, title and description tags are still important. This module will let you set those tags automatically for all your pages.

This module will allows you to create a custom contact form or survey. It is a great way to let your customers to easily reach you and receive feedback.

All stores have some sort of form (signup, contact, etc) that customers have to fill out. If there is a form, spammers, hackers and bots will try to fill it out. It is as certain as taxes and death. Mollom significantly helps to reduce spam.

All e-commerce sites should be using SSL to transmit sensitive data. Customers will have more confidence when the site uses encryption.

So, this is our list. Please share any of your must-have' modules to add to the Kickstart distro or for Commerce in general.

May 31 2013
May 31
*/ /*-->*/

If you’re using Sass to preprocess your stylesheets, you’ve probably discovered partials. A partial is a sub-stylesheet, intended to be included as part of a main stylesheet rather than being processed into its own file. Partials are great for building more organized stylesheets, but managing lots of separate includes by hand can be a drag. Thankfully, we can use the sass-globbing gem to make use of partials without the extra overhead.

Note: this article assumes you’re familiar with Sass and the Compass framework. If you’re new, I highly recommend the tutorials over at The Sass Way.

A Partial Introduction

Creating a partial is really easy. It’s just a regular Sass file with a leading underscore: _my-partial.scss. When I compile this file, Sass won’t generate a my-partial.css file; it expects it to be imported into another stylesheet. For example, to pull this file into my main stylesheet, I can use the Sass @import directive:

// main.scss
@import 'my-partial';

Notice that I don’t write the leading underscore when importing the partial. Also, it’s best to leave off the file extension: it’s simpler to write, and if I wanted to switch to using Sass’s indented syntax, I wouldn’t have to change my import statement.

Now I have the freedom to organize my stylesheet source code any way I want and still deliver a single file to keep HTTP requests down. Or, I could even output several customized stylesheets from a common codebase.

Partials in Practice

So the general idea of organizing code is nice, but let’s look at some real-world benefits by looking through the lens of some recent developments in the CSS world that aren’t about preprocessors at all.

CSS is going modular. As sites and apps get more complex, writing a single giant stylesheet only encourages me to chuck new rules at the bottom and not really think about the big pictire. This is especially true if the existing code is already hard to understand, poorly organized, and full of repetitive styles targeting specific elements on specific pages.

Projects like OOCSS, SMACSS, BEM and others are all trying to address this at the level of application architecture, thinking about how to separate responsibilities and banish unruly CSS from projects. With Sass partials, we can have our file structure reflect our architectural strategy. Since I’m a fan of SMACSS, I might structure my Sass like so:


Here’s what my main.scss might look like:

// main.scss
@import 'base';
@import 'modules/forms';
@import 'modules/typography';
@import 'layout'

This is a great start, but as I build this out I’ll need to add new modules and layouts. It’s natural to just add a new file, say modules/_buttons.scss and then wonder why the new styles aren’t showing up even though Compass or Sass is watching for changes. After a minute or two of confusion, I’ll realize what happend. “Gahhh, I forgot to add an @import statement in main.scss!”

There is such a thing as being too modular, but even a small project can reasonably have have over a dozen modules. It’s just a pain to manage all of these imports by hand.

Sass Globbing to the Rescue

We really need a way to automate our imports. Turns out, there’s a gem for that. Sass-globbing is a Ruby gem for Sass from the creator Compass, Chris Eppstein. It lets you import directories or whole directory trees with a single @import statement:

// import a directory’s contents (does not import sub-directories):
@import 'dir/*';
// recursively import a directory’s contents and any sub-directories:
@import 'dir/**/*';

To start using the gem, I’ll install it from the command line:

$ gem install sass-globbing

If you’re using Compass (and you should be), add sass-globbing support to your project by requiring it in your Compass configuration file:

# config.rb
require 'sass-globbing'

Now I can update my main.scss to import my modules and layouts directories in one go:

// main.scss
@import 'base';
@import 'modules/*';
@import 'layouts/*';

Fantastic. I can now add partials to my heart’s content without having import statements slow me down.

Import Order Blues

There is one wrinkle. Say I’m developing mobile-first, and I’ve finished roughing in my layouts/_layout-mobile.scss. Now I want to add a desktop layout, so I create layouts/_layout-desktop.scss. These styles should be imported after layout-mobile.scss, but – uh-oh – sass-globbing always imports files in alphabetic order. What to do?

The simplest solution is simply to number the files. We‘ll change the layout files to:


As a bonus, we can see the load order of our files right in the file browser. Neat and tidy. The best part is, our stylesheets just re-compiled correctly, and we never touched main.scss.

Done and done

So now we can have our cake and eat it too. We have a practical, maintainable project structure for our stylesheets, and we’ve cut out a whole bunch of configuration that would otherwise be a downside. Happy globbing!

May 16 2013
May 16

It can be tough sometimes for web developers. That beautiful/exciting/inspiring new site that’s almost ready to launch looks great and works well in all browsers… except for Internet Explorer.  Menus don’t align properly, rounded corners disappear, images don’t quite fit the same… It’s a continual challenge to help IE fit in with the big boys Firefox, Chrome and Safari.

According to w3schools, IE itself was only used by13% of Internet users last month.  With sites like Facebook no longer supporting IE7, developers behind sites like The IE8 Countdown, and its long history of being a troublesome development browser it’s no surprise to see Internet Explorer usage steadily dropping each year. Currently IE8 accounts for 5% of all current browser use; IE7 is less than 1%.

How long ‘til we’re clear of IE8?

The main contributor to IE8 sticking around so long has been Windows XP.  This operating system only supports only up to IE8, and is still running on a whopping 39% of computers today. The good news is Microsoft plans to discontinue support for XP in 2014. The bad news is many users will continue to keep XP running so IE8 will likely stick around for a long time yet to come.

Of course, XP users could switch to other browsers like Firefox or Chrome as an alternative to using IE. But users have their reasons for sticking with it.

The costs of working with IE8 & IE7

The stumbling point with designing, building and testing for IE8 & IE7 is that there are higher costs associated with your project if you want your site to work on these older versions.  Not only do developers have to build workarounds specifically for this one browser, but as the competing browsers continue to release new editions, your site will require specific IE maintenance to keep the site accessible in the future.

Moving forward with your site

Is it worth building or maintaining a site with IE8 in mind? It really depends on your audience. If you have access, check your site’s analytics and see who’s viewing your site, on which device and browser. If you have a large audience using IE8 then you’ll need to continue to meet their viewing needs.

If you’re starting out fresh, it’s a good idea to invest in testing and profiling your expected audience to determine how best to build your site.

Here at Fuse, we stand behind no longer supporting IE7 due to its low usage; as for IE8 it’s a tougher call on when that will happen.  But, keeping track of our clients requests and watching industry trends will let us know when it’s time to pull the plug on IE8.

Apr 23 2013
Apr 23

So here's the scenario. A year or two ago we had an SVN server. In house because it was easy enough to set up and the options for hosted services weren't as abundant and cool as they are now. We had all sorts of space to commit anything and everything to SVN and the added benefit of local network speeds when checking out repositories.

I'm not sure if we're alone here at Fuse, but when we say we would commit everything, we mean everything! We were committing our clients site files (sites/default/files), wireframes, database backups and even Photoshop mockups. Most of which were binary files and generally on the larger side and got none of the real benefits of version control. We knew it was a bit taboo, but it seemed so nice and easy when you could check out a repo and have everything necessary to get going on a project in one place.

After our move from SVN to Git we realized the obvious errors in our ways, but now how do we fix it?

Enter the filter-branch command, it traverses your entire git repo and expunges any existence of files/directories so it’s like they never existed. Good for removing things like hard coded password, compiled objects and in our case our design files and databases.

It can take a bit of time to run and since we really haven’t got into feature branches, or branching at all it was fairly simple. The one thing to take note of is making sure all of your cloned repos are committed and up to date since we’re making some big changes, merging isn’t really an option so you’ll be creating new clones everywhere you need these files.

git filter-branch -f --tree-filter 'rm -rf source' HEAD

would get rid of our source directory which included mockups and wireframes.

git filter-branch -f --tree-filter 'rm -rf db' HEAD

would get rid of our db backups.

Then the always frightening:

git push origin master --force

One final step that we went with (because it’s easier) is cloning your newly cleaned repo and importing it into a new repo. This handles the regeneration of your pack files which tend to take up a bunch of space and can be tricky to get rid of. We did this by renaming the old repo after cleaning, cloning it, then importing the clone into a newly created repo with the original repos name.

After all is said and done you’ll have a trimmed down repo with all your commit history intact, save for anything that referenced the files/directories you just deleted.

I’m no expert with Git so I may have missed something or there may be better methods so your best bet is to read up on the filter-branch command. If you do find something, please leave a comment. We’re only a couple repos in so if anything comes up on our end we’ll do the same.

Apr 23 2013
Apr 23
*/ /*-->*/

Almost a year ago I wrote a couple of posts about customizing CKEditor when implementing it with the WYSIWYG module. In the second post I showed how to integrate a custom button and dialog window into the editor. The method I used to insert the button, however, was a bit of a hack. At the end of the post I alluded to integrating our dialog as a CKEditor plugin. Today we're going to do just that. We'll start by writing a plugin for CKEditor using it's own API. Then, we'll integrate it into the CKEditor toolbar using module hooks from the WYSIWYG module as well as the CKEditor module. By the time you're done this tutorial you'll be controlling CKEditor with the power of a level 99 Final Fantasy Summoner.

Getting Started

To start, it will help if you have read the first two posts of this series. We'll be using the module that we worked on in those posts as a starting point so it would be good to know what's going on there. Otherwise, knowing your way around php and javascript will come in handy, especially if you want to customize beyond this tutorial.

This tutorial should work for either Drupal 6 or Drupal 7. If you want to skip the tutorial and just browse the module code (there's lots of comments), I've attached a zipped copy of the finished module at the bottom of the post.

1. Creating the CKEditor plugin

The first step is to create the CKEditor plugin. This is going to be a native CKEditor plugin that will work with both the WYSIWYG module as well as the CKEditor module. CKEditor plugins are actually pretty simple to implement. There's a nice tutorial on the CKEditor site that has everything you need to get started. That combined with what we learned in part two of this tutorial series is all we need to get this plugin working.

Alright so we'll start by creating a new folder "plugins" in our ckeditor_custom module. Then, we'll create a folder for our plugin. We'll be using the same dialog from before which inserts a youtube video iframe into the content area, so we'll call our plugin "youtube". Create a file called "plugin.js" in the youtube folder as well as a folder called "images" for any images the plugin might need (in our case it's just a toolbar icon that we pulled from google images and resized to 16x16 pixels). When you're done your folder heirarchy should look like this:

  • ckeditor_custom
    • plugins
      • youtube
        • images
          • icon.png
        • plugin.js
 * @file Plugin for inserting Drupal embeded media
( function($) {
  // All CKEditor plugins are created by using the CKEDITOR.plugins.add function
  // The plugin name here needs to be the same as in hook_ckeditor_plugin()
  // or hook_wysiwyg_plugin()
  CKEDITOR.plugins.add( 'youtube',
    // the init() function is called upon the initialization of the editor instance
    init: function (editor) {
      // Register the dialog. The actual dialog definition is below.
      CKEDITOR.dialog.add('youtubeDialog', ytDialogDefinition);
      // Now that CKEditor knows about our dialog, we can create a
      // command that will open it
      editor.addCommand('youtubeDialogCmd', new CKEDITOR.dialogCommand( 'youtubeDialog' ));
      // Finally we can assign the command to a new button that we'll call youtube
      // Don't forget, the button needs to be assigned to the toolbar. Note that
      // we're CamelCasing the button name (YouTube). This is just because other
      // CKEditor buttons are done this way (JustifyLeft, NumberedList etc.)
      editor.ui.addButton( 'YouTube',
          label : 'You Tube',
          command : 'youtubeDialogCmd',
          icon: this.path + 'images/icon.png'
    Our dialog definition. Here, we define which fields we want, we add buttons
    to the dialog, and supply a "submit" handler to process the user input
    and output our youtube iframe to the editor text area.
  var ytDialogDefinition = function (editor) {
    var dialogDefinition =
      title : 'YouTube Embed',
      minWidth : 390,
      minHeight : 130,
      contents : [
          // To make things simple, we're just going to have one tab
          id : 'tab1',
          label : 'Settings',
          title : 'Settings',
          expand : true,
          padding : 0,
          elements :
              // http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.dialog.definition.vbox.html
              type: 'vbox',
              widths : [ null, null ],
              styles : [ 'vertical-align:top' ],
              padding: '5px',
              children: [
                  // http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.dialog.definition.html.html
                  type : 'html',
                  padding: '5px',
                  html : 'You can find the youtube video id in the url of the video. <br/> e.g. http://www.youtube.com/watch?v=<strong>VIDEO_ID</strong>.'
                  // http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.dialog.definition.textInput.html
                  type : 'text',
                  id : 'txtVideoId',
                  label: 'YouTube Video ID',
                  style: 'margin-top:5px;',
                  'default': '',
                  validate: function() {
                    // Just a little light validation
                    // 'this' is now a CKEDITOR.ui.dialog.textInput object which
                    // is an extension of a CKEDITOR.ui.dialog.uiElement object
                    var value = this.getValue();
                    value = value.replace(/http:.*youtube.*?v=/, '');
                  // The commit function gets called for each form element
                  // when the dialog's commitContent Function is called.
                  // For our dialog, commitContent is called when the user
                  // Clicks the "OK" button which is defined a little further down
                  commit: commitValue
              // http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.dialog.definition.hbox.html
              type: 'hbox',
              widths : [ null, null ],
              styles : [ 'vertical-align:top' ],
              padding: '5px',
              children: [
                  type : 'text',
                  id : 'txtWidth',
                  label: 'Width',
                  // We need to quote the default property since it is a reserved word
                  // in javascript
                  'default': 500,
                  validate : function() {
                    var pass = true,
                      value = this.getValue();
                    pass = pass && CKEDITOR.dialog.validate.integer()( value )
                      && value > 0;
                    if ( !pass )
                      alert( "Invalid Width" );
                    return pass;
                  commit: commitValue
                  type : 'text',
                  id : 'txtHeight',
                  label: 'Height',
                  'default': 300,
                  validate : function() {
                    var pass = true,
                      value = this.getValue();
                    pass = pass && CKEDITOR.dialog.validate.integer()( value )
                      && value > 0;
                    if ( !pass )
                      alert( "Invalid Height" );
                    return pass;
                  commit: commitValue
                  // http://docs.cksource.com/ckeditor_api/symbols/CKEDITOR.dialog.definition.checkbox.html
                  type : 'checkbox',
                  id : 'chkAutoplay',
                  label: 'Autoplay',
                  commit: commitValue
      // Add the standard OK and Cancel Buttons
      buttons : [ CKEDITOR.dialog.okButton, CKEDITOR.dialog.cancelButton ],
      // A "submit" handler for when the OK button is clicked.
      onOk : function() {
        // A container for our field data
        var data = {};
        // Commit the field data to our data object
        // This function calls the commit function of each field element
        // Each field has a commit function (that we define below) that will
        // dump it's value into the data object
        this.commitContent( data );
        if (data.info) {
          var info = data.info;
          // Set the autoplay flag
          var autoplay = info.chkAutoplay ? 'autoplay=1': 'autoplay=0';
          // Concatenate our youtube embed url for the iframe
          var src = 'http://youtube.com/embed/' + info.txtVideoId + '?' + autoplay;
          // Create the iframe element
          var iframe = new CKEDITOR.dom.element( 'iframe' );
          // Add the attributes to the iframe.
            'width': info.txtWidth,
            'height': info.txtHeight,
            'type': 'text/html',
            'src': src,
            'frameborder': 0
          // Finally insert the element into the editor.
    return dialogDefinition;
  // Little helper function to commit field data to an object that is passed in:
  var commitValue = function( data ) {
    var id = this.id;
    if ( !data.info )
      data.info = {};
    data.info[id] = this.getValue();

You might notice a lot of this code is very similar to what we did in the last tutorial. The code to register the dialog as well as the dialog definition itself is exaclty the same. The big difference is that we're registering our dialog command as an actual CKEditor plugin (using CKEDITOR.plugins.add), rather than just cramming it into the editor after it's already been initialized. Speaking of which, we need to clean up the old dialog code from ckeditor_custom_config.js. Just delete everything from line 65 onward.

2. Integrating with the WYSIWYG Module

So now that we've written our plugin, how do we let CKEditor know about it? Well, the WYSIWYG module has a hook for integrating native plugins into it's supported editors: hook_wysiwyg_plugin(). You just provide some information about where your plugin code lives and the WYSIWYG module does the rest:

 * Implements hook_wysiwyg_plugin()
function ckeditor_custom_wysiwyg_plugin($editor, $version) {
  switch ($editor) {
    // Only do this for ckeditor
    case 'ckeditor':
      return array(
        // This key (youtube) needs to be the same as the plugin name used in
        // CKEDITOR.plugins.add() in plugin.js
        'youtube' => array(
          // A URL to the plugin's homepage.
          'url' => '',
          // The full path to the native editor plugin, no trailing slash.
          // Ignored when 'internal' is set to TRUE below.
          'path' => drupal_get_path('module', 'ckeditor_custom') . '/plugins/youtube',
          // The name of the plugin's main JavaScript file.
          // Ignored when 'internal' is set to TRUE below.
          // Default value depends on which editor the plugin is for.
          'filename' => 'plugin.js',
          // A list of buttons provided by this native plugin. The key has to
          // match the corresponding JavaScript implementation - when it's
          // created using editor.ui.addButton(). The value is is displayed on
          // the editor configuration form only.
          'buttons' => array(
            'YouTube' => t('You Tube Embed'),
          // Boolean whether the editor needs to load this plugin. When TRUE,
          // the editor will automatically load the plugin based on the 'path'
          // variable provided. If FALSE, the plugin either does not need to
          // be loaded or is already loaded by something else on the page.
          // Most plugins should define TRUE here.
          'load' => TRUE,
          // Boolean whether this plugin is a native plugin, i.e. shipped with
          // the editor. Definition must be ommitted for plugins provided by
          // other modules. TRUE means 'path' and 'filename' above are ignored
          // and the plugin is instead loaded from the editor's plugin folder.
          'internal' => FALSE,

With this hook, the WYSIWYG module will know to load our plugin when it initializes CKEditor. It will also list our plugin on the WYSIWYG profile administration page. There's just a little bit of cleanup we need to do before we can test it out. Firstly, we need to remove bit of code from ckeditor_custom_wysiwyg_editor_settings_alter() in ckeditor_custom.module where we manually added our youtube plugin. We can also remove the css we added to style the button. Just remove this block (line 88-93):

// This is our new youtube command / dialog that we created in
// ckeditor_custom_config.js. If we don't add this here, it won't
// show up in the toolbar!
$new_grouped_toolbar[] = array('YouTube');
// Add a css file to the page that will style our youtube button
drupal_add_css(drupal_get_path('module', 'ckeditor_custom') . '/ckeditor_custom.css');

Secondly, and this is optional, you can add our new plugin to our re-ordered preferred groupings in ckeditor_custom_wysiwyg_editor_settings_alter(). I added mine just after the "Image" button.

$preferred_groupings[] = array('Image', 'YouTube', 'Link', 'Unlink', 'Anchor', '-');

And that's it. Load up one of your WYSIWYG profile settings at admin/config/content/wysiwyg and enable the plugin in the "Buttons and Plugins" section. Then, load up a page with the editor on it and check out your button in all it's glory.

FUN FACT: The WYSIWYG module also includes a nifty API for writing cross-editor Drupal plugins using the hook: hook_INCLUDE_plugin(). This is how a module like media can implement a cross-editor media button. For more info about the WYSIWYG editor API check out wysiwyg.api.php.

3. Integrating with the CKEditor module

Okay, okay, I know that this is supposed to be a series of posts specifically about CKEditor and the WYSIWYG module, but I figure it wouldn't hurt to show you how to integrate our plugin into the CKEditor module. It's actually quite similar to the WYSIWYG module:

 * Implements hook_ckeditor_plugin()
function ckeditor_custom_ckeditor_plugin() {
  return array(
    // This key (youtube) needs to be the same as the plugin name used in
    // CKEDITOR.plugins.add() in plugin.js
    'youtube' => array(
      // Name of the plugin used to write it. Also needs to be the same as the
      // plugin name used in CKEDITOR.plugins.add() in plugin.js
      'name' => 'You Tube',
      // Description of the plugin - it would be displayed in the plugins management section of profile settings.
      'desc' => t('Embed a youtube video into the content area using the video url'),
      // The full path to the CKEditor plugins directory, with the trailing slash.
      'path' => drupal_get_path('module', 'ckeditor_custom') . '/plugins/youtube/',
      // A list of buttons provided by this plugin. The key has to match the
      // corresponding JavaScript implementation - when it's created using
      // editor.ui.addButton(). If you don't do this, your button won't be
      // available for drag 'n drop ordering on the plugins management section
      // of profile settings
      'buttons' => array(
        'YouTube' => array(
          'icon' => 'images/icon.png',
          'label' => 'You Tube Embed',

And that's it. Just enable the plugin in the plugins management section of the profile settings, then put your button where you want it to go using the drag 'n drop interface of the CKEditor module and you're good to go!


Congratulations! You've just created your very own CKEditor plugin and integrated it properly, or as some might say, the "Drupal Way". You've also just completed the final chapter of the CKEditor & the WYSIWYG Module tutorial series! In addition to this new custom plugin, you should also be able to customize existing CKEditor plugins as well as reorganize and group buttons in the toolbar. Although, on that last one, it looks like an effort to add some sort of button management to WYSIWYG has been made by the project maintainer recently which is exciting.

So, that's it for this one. I hope you've learned something useful along the way. Here are the links to download the finished modules in case you just want to get going without reading through my long-winded tutorial.

Apr 12 2013
Apr 12

It’s been a wee bit crazy here over the last few months. Our blog has suffered as a result. Carving out time for writing has been tough. We see a faint light at the end of the tunnel now and hope to get back to some helpful Drupal related posts. In the meantime, i thought i’d give you a sampling of what we’ve been working so hard on the last few months and what we’ve got lined up for the next few.

OpenRoad Auto Group - Site revamp and mobile theme

We’ve been working with OpenRoad on their dealership network sites for more than two years now. To our knowledge it was the first dealership network (11 sites in all) managed through one Drupal 7 install. We are pretty proud of what we accomplished with these sites: Multiple data sources, third party apps & feeds. The site(s) helped win OpenRoad an innovation award in 2012 and we think OpenRoad has become the standard for auto dealership websites in Canada.

Making a better mobile user experience

For the initial build, we were not mandated to provide mobile users with an optimized experience. For the most part, the sites were quite usable, but not ideal for users on the go. As part of a winter initiative, Fuse was brought in to improve the mobile user experience.

Most of our projects now start their lives with responsive web design in mind. As other web shops can attest to, it’s not easy to retrofit an existing site with responsive technology. A mobile-first strategy is usually employed rather than designing a desktop experience and working back through screen sizes. As such, we opted to design a separate theme for mobile users instead of attempting a responsive retrofit. This gave us more freedom with the design, allowing us to focus on users on the go.

Through an audit of available analytics we established that the primary actions of mobile users were: finding a dealer, calling a dealer, checking service status and browsing new and used cars. We focused on quick and easy access to these key areas while providing what we think is much improved mobile experience.

Site revamp

Following the launch and success of the mobile experience, OpenRoad asked us to redesign their desktop/tablet experience. With this redesign, OpenRoad wanted to set themselves apart from the typical car dealership web experience. A modern interface, more engaging and without the clutter.

Thankfully Adnan, our stellar designer is a car enthusiast and spends countless hours dorking out on car related websites. His over-indulgence proved invaluable in the process. He was able to take all his frustrations with typical dealer sites out on this design in translating the OpenRoad brick and mortar dealership experience to the web. We especially love the new car search page and interactive experience on the new car pages. 360 interiors, 360 exteriors, video/image galleries and slick google maps implementation make the online car shopping experience stand out from their competitors. Check it out

Museum of Vancouver - OpenMOV

We’ve been working with the Museum of Vancouver for a couple of years now. We built and maintain their public website as well as the software that manages their Collection. All built on Drupal. The OpenMOV is the museum’s collection management system that we originally launched over a year ago. We were brought on late in 2012 to build enhancements to the system. One of the major goals for the enhancements is to reduce the amount of paperwork and redundant processes the museum has to contend with as they bring in a new object or retire an old one. In museum speak, this is Acquisition, Accession and Deaccession of objects.

The new modules we are working on will reduce admin time and automate many of the currently manual tasks so curatorial staff can spend more time finding cool new stuff for the museum.

You can check out many of the museums objects online over at the OpenMOV.

Modern Dog Magazine

Modern Dog Magazine is arguably the best Dog magazine on the planet. While many magazines and periodicals have shrinking readership, Modern Dog’s readership is growing steadily. They also have a very popular website that complements the magazine. Photo contests, articles, product info, breed profiles, etc. get people coming back to the site for more.

Modern Dog’s old site was...well...old. Running Drupal 5 and Ubercart, it was getting tough to maintain and there were some issues with some of the functionality on the site. Late in 2012 we were approached by Modern Dog Magazine to take over the rebuild of their site on Drupal 7 from another company. We took Modern Dog’s design and a poorly and partially built site and finished the job right. Why not submit your beloved pooch to the photo contest now?

Capital City Centre / Skye

Capital City Centre is a multi-tower development project outside Victoria, BC managed by League Financial Partners. The project is set to become the largest real estate development on Vancouver Island including the Island's tallest towers.

League needed a platform where they could easily launch new sites as each separately branded tower’s marketing came online. We partnered with Rally Creative on the project. They did the design and we architected and built the site on the Drupal 7.x framework. Their new system will allow them to quickly deploy new sites for each tower on a single Drupal codebase. We’re thrilled to report that the site recently won a 2013 Georgie award for “Best Project-Specific Website”. Check it out

Working on now

Some of the projects we’re excited to be working on right now include:

Simon Fraser Student Society

Originally contracted to provide maintenance on the Student Society’s existing Drupal 7 website, we were asked recently to figure out how we could provide separate websites to each of the Society’s business units in a cost effective way. The Highland Pub, Out on Campus, SFSS Surrey, Build SFU and the Women’s Centre will all get their own websites, managed by their own staff. We’re using a single Drupal 7 codebase in order to reduce ongoing maintenance time. Launch of the sites is scheduled for early May this year.


The Lower Mainland’s chapter of the International Cinematographers Guild has contracted Fuse to build their new website. This one will be fun. We’re integrating with their internal systems to provide a platform for directors, union members and internal staff the ability to access and update their profiles, availability, etc. With a mobile focus, this will be the go-to place for members and directors to find and update essential info while in the field. The first phase of this project is scheduled to launch this Summer.


We were excited that Taymor, designers and manufacturers of door, kitchen & bath hardware, chose Fuse to undertake the redesign/build of their website. Responsive, multilingual, regionalized and hundreds of products and resources make this project a welcomed challenge. We’re well into the interactive wireframes now and we’re excited to get into design. Look for this project to launch early Summer 2013.

Header photo credit to Jonathan Pope

Feb 27 2013
Feb 27

Over the past few months we've been busily working away on our brand new, nearly complete Fuse Interactive website, which will use Disqus in place of Drupal's commenting system. Of course, we didn't want to lose any of our existing comments when we made the switch, so we had to export our existing comments from Drupal and import them to our Disqus account. There's gotta be a module, right?

There is, or was, or sort of is… The point being that, although it wasn't exactly rocket science, getting the comments from Drupal and into Disqus wasn't as dead simple as I had hoped.

Because I don't like being made to think or search too hard (and neither should you), here is a no-brain-required guide to exporting Drupal 7 comments to Disqus.

What You'll Need

  • A Drupal site with existing comments
  • A Disqus account

Step One

If you haven't already, download and enable the Disqus Module for your site.
Step Two

Go to the Disqus Migrate project page and follow the instructions there to clone the modules into your site's modules/contrib folder.

Then enable it (just checking, because I totally didn't overlook this step).

Step Three

Download the Disqus API Bindings for PHP and copy the disqusapi folder into your site's libraries folder.

Step Four

Log into your Disqus account.

When you register the application make sure you add your domain and select "Inherit Permissions from …" in the 'Authentication' section.

Upon completing this step you will be given two API keys, Public and Secret (which you can find later at http://disqus.com/api/applications/).

Step Five

Go back to your site and enter the Public and Secret keys in their respective fields within the 'Advanced' tab of the Disqus Module configuration menu.

While you're at it,  enter your User API key (available at http://disqus.com/api/keys/) into the field within the 'Behaviour' tab.

Save your configuration.

Step Six

Go to your Comments administration menu and select 'Disqus Export' from the drop down. Click the 'Export all comments to XML file' button (obviously, right?) and you've got your comments all ready to go.

Well, mostly.

Step Seven

When I tried to upload the XML file the first time it mysteriously failed. Like any good problem solver I tried again, and like any good broken file it failed again.

This is because in my excitement I failed to make sure the XML file was valid. So it's probably a good idea to pay a visit to http://validator.w3.org and make sure your XML file is valid. It's also worth making sure your XML file is formatted properly, which means it looks something like this http://help.disqus.com/customer/portal/articles/472150.

If you are using Devel you'll want to turn it off or at the at the very least suppress its output. We ran into a problem with Devel appending a bunch of code to the end of our RSS file which, as you might expect, caused problems when uploading to Disqus.

If you're happy with your XML file you can now go to the Admin section of your Disqus account, open the Tools tab, select 'Generic(WXR)' and upload your file.

Step Eight

Send a thankful tweet to @dwkitchen for porting the Disqus Migrate module from Drupal 6 if you are feeling grateful. Otherwise hi-fives all around, rent a limo, etc.

Nov 09 2012
Nov 09

Here at Fuse we're excited to be taking the wraps off something we've been working on for awhile now, a project called Seed. Seed is a set of Drush commands which allow you to perform actions such as setting up new user accounts, creating MySQL databases and setting up new projects for developers.

A little backstory

Seed is essentially scratching an itch that's been bothering us for some time. We've had a fairly comfortable development workflow for awhile, however it's been held together by a number of PHP scripts and knowledge floating around in Andre's head. Furthermore there's some things that have traditionally been done manually, such as the creating and deletion of user accounts, adding user keys to our Bitbucket.org account or setting up new projects. Our goal was to unify a lot of the management tasks and everyday tasks in to a single package.

Our development workflow

Our workflow is relatively simple but it works well for us, and we believe could work just as well for other small to medium Drupal development teams. Here's a brief overview:

Our project code is stored in Git repositories, currently hosted on Bitbucket.org
We have a single development server which everyone develops on
Each person working on a project has their own checkout of the code and a virtual host pointed to it, for example http://project.evan.dev/
Typically we'll share a database between everyone working on a project, however there are certain situations where someone will "branch" the database (similar to Git) to do some work without disrupting anyone else who is working on the project.

How does Seed support this workflow?

Seed facilitates a number of aspects of this workflow from a simple command line interface. Seed handles:

The creation and deletion of system-level users, as well as the generation of SSH keys and optionally the pushing of these keys to our Bitbucket account.
The creation and deletion of MySQL users and databases.
Initializing a new project and adding Git remotes. Alternatively, Seed can checkout projects from Git.
Setting up virtual hosts for new projects.
Setting up aliases for local projects as well as projects which may have been deployed to remote servers.

What about Aegir or a hosted platform?

Before building Seed we took a look at other options available to us. While Aegir or hosted platforms (for example Pantheon or Acquia's Dev Cloud) are all excellent products, the goal of Seed was not to disrupt our workflow and infrastructure, but rather to streamline it.

How can I try it out?

First of all, Seed is not production ready but we'd love for you to check it out and give us your thoughts. Secondly, we're assuming this will be running on an Ubuntu based install and we've been specifically targetting the 12.04 release, which is a good candidate as it is an LTS release. We'd definitely recommend running it in a virtualized environment for testing, and if you're looking for a virtualization application VirtualBox is a great little app which runs on any platform.

To get started, do a fresh install of Ubuntu then simply follow the INSTALL.txt to set up the necessary packages and configuration. Once that's done, you can view the README to learn about the various commands that are available to you. You should also run seed test to ensure that everything is working correctly.

What's next?

With Seed, we're going to be eating our own dog food. As such, support will be on-going as we continue to make improvements and add functionality.

With that said, patches are 100% welcome. We'd love to see other Drupal development companies taking a look and giving their thoughts as to how the system could be improved to support different workflows, or just general ideas, bug fixes or questions.


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