Jul 02 2019
Jul 02

If you are planning on upgrading your site, you may be wondering if you should upgrade to Drupal 8, or if you should wait for Drupal 9. Why not both?

We have been promised that the upgrade between Drupal 8 and Drupal 9 will be simple. If you don't fall under these two specific use cases, why wait? Drupal 8 offers many improvements over Drupal 7 in many areas. Although the more modern architecture of Drupal 8 adds a little bit to the learning curve, the enhancements to caching, configuration management, and the templating system make it a worthwhile investment.

Here are my top 5 reasons to upgrade to Drupal 8 now:

Configuration Management

Drupal 8 has configuration management baked into core. You can create entities, you can export those entities and can ultimately import those entities on another environment. Sure, Drupal 7 had features, but features is not as complete. Need to enable some modules, and disable others? Need to install that view you have been working on? Drupal 8's core configuration management can do all of those in a single call.

RESTful Apis

You could create an API in Drupal 7, but it took a little bit of work. In Drupal 8, RESTful web services are baked right in. With a default Drupal install, you are just a few clicks away from exposing a web service that third parties can integrate with.

TWIG Templates

Of this list, the introduction of TWIG in Drupal 8 is my personal favorite item. Drupal 7 used PHPTemplate for its templating engine. Although PHPTemplate is pretty simple and has served well, it is also wildly insecure. TWIG is fast, secure, and extendable -- it's a perfect fit for Drupal and has been largely welcomed by the community.

Contrib is Ready

Although it was a little bit slow, the Drupal contributed modules that you used for Drupal 7 are ready for use with Drupal 8. At this point, most modules have either been ported or their function has been forked into a different module.

Future Proof

Since upgrading to Drupal 9 from Drupal 8 will be simple, there really is not much to lose. Drupal 8 is secure and has a modern, extendable architecture. Since the Drupal team is dedicated to future upgradability, you can take advantage of Drupal 8 now, so you don't have to wait.

Sep 01 2017
Sep 01

Recently I was working on a site and was tasked with adding a author filter to a view page that listed out blog posts. The blog post content type had a content reference field to another content type for the author relationship. The client wanted to make it simple for an end user to search for blog posts of a specific author quickly an easily.

Given the number of authors, a dropdown or a multi-select was really out of the question—there are just too many for either of those to make sense. My next thought was to use an autocomplete field. Little did I know that Drupal views does not support autocomplete exposed filters out of the box. Luckily though, this problem can be solved using the Views Autocomplete Filters module.

I have set up a similar scenario below to show you how I made everything come together at the end. First, imagine that we have two content types—Client and Project. Client only has the title and body fields, and Project has title, body, and a client reference. I have also created a view that displays a table of Projects. Since Projects have a Client reference field, I have added the following relationship: 

Client Relationship

Now that the relationship is in place, I’m going to add another field to the view. This is not required, but it makes it easier to recognize that the filter we are going to add works properly. The new field is a a content title and it is configured like this:

Adding a Title Field

Now that we have the field showing, it should be obvious if the relationship is working:

View with Title Field

Now it is time to add the autocomplete filter. If you ahem not already, install and enable the Views Autocomplete Filters module. Next, add a new filter for content title and configure it like so:

Exposed Filter Config 1

Exposed Filter Config 2

The configuration above sets the relationship of the filter so that it uses the title field of the Client node that is related to the Product. You will also notice that the field for autocompleted results is pointing to the Client reference field we added earlier and that the filter operator is set to contains. If everything is configured correctly, you should see something like this:

Autocomplete

That’s all there is to it! Now you have a working autocomplete exposed filter on a view. The beauty of the autocomplete filed is that it makes a lot of sense for both small and large datasets as opposed to normal selects and multi-selects, which can become obtrusive when you have many items. Now you can keep your UI nice and clean!

Happy Drupaling!

May 26 2017
May 26

Recently I had a ticket come through that required me to add some JavaScript on several pages on a Drupal 7 site — with the possibility of needing to remove that JavaScript from certain pages depending on user reactions. Since I knew there was a good chance that things were going to change, I wanted to build something that could be changed quickly and easily.

The Problem

I needed to add some JavaScript to some landing pages, and also some node types. It is also possible that more pages/node types will need to be added or even removed based on user reactions.

The Solution

We are using the Context module on this particular site, so I opted to create a context reaction. That way, I can change the context condition for the reaction to meet the client’s needs, without needing to push new code. There is a fair amount of code for the reaction, but I think it is harder to make sense of it all in parts, so I’m just going to dump out the .module file and plugin itself and give a description of each after the fact.

my_reaction/my_reaction.module
<?php
 
/**
 * @file
 * Code for my_reaction.
 */
 
/**
 * Implements hook_ctools_plugin_api().
 */
function my_reaction_ctools_plugin_api($module = NULL, $api = NULL) {
  if ($module == "context" && $api == "plugins") {
    return array("version" => "3");
  }
}
 
/**
 * Implements hook_ctools_plugin_directory().
 */
function my_reaction_ctools_plugin_directory($module, $plugin) {
  return 'plugins/' . $plugin;
}
 
/**
 * Implements hook_context_registry().
 */
function my_reaction_context_registry() {
  $registry = array();
 
  $registry['reactions'] = array(
    'my_reaction' => array(
      'title' => t('My Reaction'),
      'description' => t('Does some stuff.'),
      'plugin' => 'my_reaction_plugin',
    ),
  );
 
  return $registry;
}
 
/**
 * Implements hook_context_plugins().
 */
function my_reaction_context_plugins() {
  $plugins = array();
 
  $plugins['my_reaction_plugin'] = array(
    'handler' => array(
      'path' => drupal_get_path('module', 'my_reaction') . '/plugins/context',
      'file' => 'my_reaction_plugin.inc',
      'class' => 'my_reaction_plugin',
      'parent' => 'context_reaction',
    ),
  );
 
  return $plugins;
}
 
/**
 * Implementation of hook_context_page_reaction().
 */
function my_reaction_context_page_reaction() {
  if ($plugin = context_get_plugin('reaction', 'my_reaction')) {
    $plugin->execute();
  }
}

Alright, the first two things that happen are hook_ctools_plugin_api() and hook_ctools_plugin_directory(). Those hooks are just letting Drupal know of the API version to use as well as where the plugins will be stored. Next we have hook_context_registry(), which defines the reaction and also associates it to the plugin called my_reaction_plugin. This is followed to a call to hook_context_plugins() which defines the plugin called my_reaction_plugin.

The last thing that happens in the module file is the definition of hook_context_page_reaction(). This hook is loading the custom reaction plugin and calls its execute() method. More on that later. Now we need to write the code for the plugin itself. The class definition of the my_reaction_plugin will extend context_reaction and will only have a few methods. Again, I will discuss the individual parts after the code itself.

my_reaction/plugins/context/my_reaction_plugin.inc
<?php
/**
 * @file
 * Extends class context_reaction for my_reaction.
 */
 
/**
 * Exposes My Reaction as a reaction in Context.
 */
class my_reaction_plugin extends context_reaction {
 
  /**
   * Provides the options form.
   */
  function options_form($context) {
    // NOTE: The context module will not save a reaction to a context unless it
    // provides some sort of option. I'm opting to use a value type here as it
    // will not render anything on screen.
 
    $form = array();
 
    $form['enabled'] = array(
      '#type' => 'value',
      '#value' => 'true'
    );
 
    return $form;
  }
 
  /**
   * Executes the logic the reaction if needed.
   */
  function execute() {
    $contexts = context_active_contexts();
 
    foreach ($contexts as $value) {
      if (!empty($value->reactions[$this->plugin])) {
        $this->doReaction();
        return;
      }
    }
  }
 
  /**
   * This function does the actual work of the reaction.
   */
  function doReaction() {
     drupal_add_js('alert("I reacted!");', 'inline');
  }
}

The first thing to note for the plugin is the options_form() method. If you want to make your reaction configurable, you would add your form fields here. You will notice that my example is empty with the exception of a comment and a value form field. While developing this plugin, I noticed that reactions that do not register options are not properly saved to the context. When I can, I would like to investigate this further to contribute a patch to fix that. For now, I have added a value form field as a workaround so that the reaction can be saved to the context.

Next, we have the execute() method. All this method does is to check the currently loaded context and call the doReaction() method if the plugin is found. Lastly, we have doReaction(). Normally this method does not exist, for usually the main section of code is in the execute() method. For better readability, I have pulled the code into its own method. All it does is add some JavaScript to the current page.

The Result

Once installed and enabled, you can configure the custom reaction to appear on any page, node type, or even some other condition that has been implemented within the context module. It wasn’t too hard to build, and it offers flexibility as I can change when this reaction fires with just a few click of the mouse. Better yet, this concept is not only for JavaScript — this has the potential to used for all sorts of things!

Happy Drupaling!

Oct 06 2016
Oct 06
Magento 2

Magento2 comes out of the box with a slew of predefined REST APIs that allow clients to take control of virtually everything about the Magento2 site. Whether searching for customers and orders or setting up new tax classes, more than likely there's an API for doing so. Although core Magento2 comes with all of the REST APIs, sometimes they are not quite enough for your particular use case. So, in this post we look at how to create your own custom REST API in Magento2.

While integrating a Drupal site with Magento2 as the backend, we faced a few challenges with the existing REST APIs – specifically the cart/quote management endpoints. You can look at Swagger docs for the default Magento APIs. The cart management endpoints start about half way down the page.

For this example, we are most interested in quoteCartItemRepositoryV1. According to the documentation, it seems that adding or removing multiple items from the cart will require multiple API calls. That isn't too bad, but what if our use case is a single page checkout? Multiple API calls could cause some performance hits. You could try to update as things are changed on the page via ajax, but I think we can make a pretty simple endpoint that will let us update the cart all at once.

As mentioned in my last post, my code will live in magento/app/code/TAG. For this example, we will call the module CartManager, so I created a magento/app/code/TAG/CartManager directory. As always, when creating a new module, we have to create the registration.php files and module.xml files and so on. I'm going to assume you have created a module skeleton already, so I will only be covering the API portion. Just keep in mind that you may have user different paths for your module and will need to update accordingly.

The first step in creating a custom endpoint is to create a webapi.xml file. So in magento/app/code/TAG/CartManager/etc/webapi.xml place the following code:

<?xml version="1.0"?>
<routes xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Webapi:etc/webapi.xsd">
	<route url="/V1/carts/:cartId/updateCart" method="POST">
		<service class="TAG\CartManager\Api\CartManagementInterface" method="updateCart"/>
			<resources>
				<resource ref="Magento_Cart::manage"/>
			</resources>
	</route>
</routes>

This code just tells Magento that you plan to expose an endpoint at www.your-hostname.com/V1/carts/:cartId/updateCart which will accept a POST request. The updateCart() method will be called from TAG\CartManager\Api\CartManagementInterface when the endpoint is used.

Next we have to create the interface and implementing class for our endpoint. So in magento/app/code/TAG/CartManager/Api/CartManagementInterface put the following code:

<?php
namespace TAG\CartManager\Api;
 
interface CartManagementInterface {
    /**
     * Updates the specified cart with the specified products.
     *
     * @api
     * @param int $cartId
     * @param \TAG\CartManager\Api\CartProductInformationInterface[] $products
     * @return boolean
     */
    public function updateCart($cartId, $products = null);
}

The implementing magento/app/code/TAG/CartManager/Model/CartManagement class should look like this:

<?php
namespace TAG\CartManager\Model;
 
use Magento\Quote\Api\CartRepositoryInterface;
use TAG\CartManager\Api\CartManagementInterface as ApiInterface;
 
class CartManagement implements ApiInterface {
 
    /**
     * Quote / Cart Repository
     * @var CartRepositoryInterface $quoteRepository
     */
    protected $quoteRepository;
 
    /**
     * @param \Magento\Quote\Api\CartRepositoryInterface $quoteRepository
     * @param \Magento\Catalog\Api\ProductRepositoryInterface $productRepository
     */
    public function __construct(
        CartRepositoryInterface $quoteRepository
    ) {
        $this->quoteRepository = $quoteRepository;
    }
 
    /**
     * Updates the specified cart with the specified products.
     *
     * @api
     * @param int $cartId
     * @param \TAG\CartManager\Api\CartProductInformationInterface[] $products
     * @return boolean
     */
    public function updateCart($cartId, $products = null) {
        $quote = $this->quoteRepository->getActive($cartId);
 
        if ($quote->hasItems()) {
            $quote->removeAllItems();
        }
 
        if (!empty($products)) {
            foreach ($products as $product) {
                $sku = $product->getSku();
                $qty = $product->getQty();
 
                $add_product = $this->productRepository->get($sku);
                $productType = $add_product->getTypeId();
 
                $quote->addProduct($add_product, $qty);
            }
        }
 
        $this->quoteRepository->save($quote->collectTotals());
 
        return true;
    }
}

The logic of the updateCart method is pretty simple. It assumes that the client knows the current state of the cart. So instead of having to keep track of what was added and what was removed, the Magento side just gets a single update from the client. As a result, we can just remove all the items from the existing cart and add items based on the cart state that was posted to the endpoint. We finish off by making sure the cart totals are up to date and returning.

You will notice that the updateCart() method takes a custom interface as a parameter. This is because Magento won't associate arrays because they also support SOAP – and it isn't possible to represent them in WSDL. To overcome this, we just have to create a simple interface and class to hold the data that we need. In magento/app/code/TAG/CartManager/Api/CartProductInformationInterface, add the following:

<?php
namespace TAG\CartManager\Api;
 
/**
 * Interface for tracking cart product updates.
 */
interface CartProductInformationInterface {
    /**
     * Gets the sku.
     *
     * @api
     * @return string
     */
    public function getSku();
 
    /**
     * Sets the sku.
     *
     * @api
     * @param int $sku
     */
    public function setSku($sku);
 
    /**
     * Gets the quantity.
     *
     * @api
     * @return string
     */
    public function getQty();
 
    /**
     * Sets the quantity.
     *
     * @api
     * @param int $qty
     * @return void
     */
    public function setQty($qty);
}

Then in the implementing class magento/app/code/TAG/CartManager/Model/CartProductInformation, add the following:

<?php
namespace TAG\CartManager\Model;
 
use \TAG\CartManager\Api\CartProductInformationInterface;
 
/**
 * Model that contains updated cart information.
 */
class CartProductInformation implements CartProductInformationInterface {
 
    /**
     * The sku for this cart entry.
     * @var string
     */
    protected $sku;
 
    /**
     * The quantity value for this cart entry.
     * @var int
     */
    protected $qty;
 
    /**
     * Gets the sku.
     *
     * @api
     * @return string
     */
    public function getSku() {
        return $this->sku;
    }
 
    /**
     * Sets the sku.
     *
     * @api
     * @param int $sku
     */
    public function setSku($sku) {
        $this->sku = $sku;
    }
 
    /**
     * Gets the quantity.
     *
     * @api
     * @return string
     */
    public function getQty() {
        return $this->qty;
    }
 
    /**
     * Sets the quantity.
     *
     * @api
     * @param int $qty
     * @return void
     */
    public function setQty($qty) {
        $this->qty = $qty;
    }
}

Lastly, you just have to update the dependency injection config file so that Magento knows which classes to pick up at run time. So in magento/app/code/TAG/CartManager/etc/di.xml add the following:

<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="TAG\CartManager\Api\CartManagementInterface" type="TAG\CartManager\Model\CartManagement"/>
    <preference for="TAG\CartManager\Api\CartProductInformationInterface" type="TAG\CartManager\Model\CartProductInformation"/>
</config>

Now all you have to do is run magento/bin/magento setup:upgrade and your new service should be active. Now, a client should be able to update a cart by posting data that looks like:

$post_data = array(
  'products[0][sku]' => 123456
  'products[0][qty]' => 4
  'products[1][sku]' => 321654
  'products[1][qty]' => 42
);

Note: This is just a PHP representation meant to be used with Guzzle, but this is just form url encoded data, so with very little modification it can work with your client.

Happy Magento-ing!

Feb 19 2016
Feb 19

Drupal 8’s way of managing configuration forms is fairly different from Drupal 7. In this post, we will create a simple module that has a configuration form with a few fields.

First, start off by creating a module skeleton. You could use Drupal Console for this, or just by following the tutorial found here.

The first thing I recommend you doing is creating the route for your configuration form. For this example, I’m calling the module Simple, so I will be adding simple.routing.yml. Create your mymodule.routing.yml file and place it inside of your module directory. Place this code in there:

simple.config:

  path: '/admin/config/simple/config'

  defaults:

    _form: '\Drupal\simple\Form\SimpleConfigForm'

    _title: 'Simple Configuration'

  requirements:

    _permission: 'administer site configuration'

Next, in mymodule/src/Form/ create a new file called SimpleConfigForm.php. Paste the following code in there:

<?php
 
/**
 
 * @file
 
 * Contains \Drupal\simple\Form\SimpleConfigForm.
 
 */
 
namespace Drupal\simple\Form;
 
use Drupal\Core\Form\ConfigFormBase;
 
use Drupal\Core\Form\FormStateInterface;
 
class SimpleConfigForm extends ConfigFormBase {
 
  /**
 
   * [email protected]}
 
   */
 
  public function getFormId() {
 
    return 'simple_config_form';
 
  }
 
  /**
 
   * [email protected]}
 
   */
 
  public function buildForm(array $form, FormStateInterface $form_state) {
 
    $form = parent::buildForm($form, $form_state);
 
    $config = $this->config('simple.settings');
 
    $form['email'] = array(
 
      '#type' => 'textfield',
 
      '#title' => $this->t('Email'),
 
      '#default_value' => $config->get('simple.email'),
 
      '#required' => TRUE,
 
    );
 
    $node_types = \Drupal\node\Entity\NodeType::loadMultiple();
 
    $node_type_titles = array();
 
    foreach ($node_types as $machine_name => $val) {
 
      $node_type_titles[$machine_name] = $val->label();
 
    }
 
    $form['node_types'] = array(
 
      '#type' => 'checkboxes',
 
      '#title' => $this->t('Node Types'),
 
      '#options' => $node_type_titles,
 
      '#default_value' => $config->get('simple.node_types'),
 
    );
 
    return $form;
 
  }
 
  /**
 
   * [email protected]}
 
   */
 
  public function submitForm(array &$form, FormStateInterface $form_state) {
 
    $config = $this->config('simple.settings');
 
    $config->set('simple.email', $form_state->getValue('email'));
 
    $config->set('simple.node_types', $form_state->getValue('node_types'));
 
    $config->save();
 
    return parent::submitForm($form, $form_state);
 
  }
 
  /**
 
   * [email protected]}
 
   */
 
  protected function getEditableConfigNames() {
 
    return [
 
      'simple.settings',
 
    ];
 
  }
 
}

Now, I obviously brushed over most of this code, but I will give a quick overview of what it does. First, we define the form id in the getFormId() function. You could use this id in a hook_alter method to modify things. Next the buildForm() method creates a form with two items. The first is a simple text field that is required. It is labeled to accept an email address. The second form item is a group of checkboxes.

We use the \Drupal\node\Entity\NodeType::loadMultiple() method to load a list of all node types and we create a checkbox for each.

Notice that on both form fields, we have a #default_value. This is used to load the config of a previously saves form state. So, after you enter in fields, the page will refresh and your new values will be stored. We populate the field items for the next time you visit the form.

Lastly, in the submitForm function, we simply load up our configuration and save the values that are found in the form state. From there we pass the flow to the parent object so it can flow through the stack.

That’s it! All you have to do is go and enable the module and you should be able to access the form by going to /admin/config/simple/config.

Happy Drupaling!

Dec 18 2015
Dec 18
drupal console illo


I recently stumbled upon this thing call Drupal Console — and I must say that I’m impressed. Although I have not completely integrated it into my workflow, Drupal Console has a lot of features that will help make Drupal 8 development virtually painless. It also comes with useful documentation so you can hit the ground running.

So, first things first, what is it? Drupal Console is a command line tool that can interact with a Drupal 8 installation — similar to the way Drush works with Drupal 7. However, Drupal Console takes it a few steps further as it can also generate boilerplate code and also comes packaged with tool to help you convert Drupal 7 modules to work with Drupal 8.

Let’s make the Hello World module, but instead of preparing a module skeleton, we will use Drupal Console to do all of the grunt work for us! First we run `drupal generate:module` in our terminal. From there, a wizard will take over and ask you for the rest of the details.

terminal

That’s it!

terminal

Now you have a module skeleton sitting in `modules/custom/hello_world`.

To finish off the module all we have to do is add the routing file and the basic controller and we are all set.

<?php
/**
* @file
* Contains \Drupal\hello_world\Controller\HelloController.
*/
 
namespace Drupal\hello_world\Controller;
 
use Drupal\Core\Controller\ControllerBase;
 
class HelloController extends ControllerBase {
  public function content() {
    return array(
      '#type' => 'markup',
      '#markup' => $this->t(‘Drupal Console is cool!),
    );
  }
}
?>
hello_world.content:
  path: '/hello'
  defaults:
    _controller: '\Drupal\hello_world\Controller\HelloController::content'
    _title: 'Hello World'
  requirements:
    _permission: 'access content'

Now that our module is ready to go, let's turn it on by running `drupal module:install hello_world`.

successful hello world

Let’s see if it worked!

hello world drupal console worked and is cool

This demonstration didn’t even scratch the surface of what Drupal Console can do. I didn’t even tell you that it works with Drush! (Yes, really. I could have used `drupal drush en hello_world` to enable the module too). So download Drupal Console, it will make things easier.

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