• Jens Steppe

  • Team Lead

Now that views are part of the Drupal 8 core, all the functionality to create views or lists comes straight out of the box. Standard filtering options however are limited to columns from the same entity. In this post I’ll demonstrate how to build a filter that uses external information. We use the Domain module as an example and filter on current domain. 

Why we need a complex filter

We are using the Domain module to enable a multi-domain setup for a large Drupal 8 project. Although this module is not yet released for Drupal 8 (at the time of writing), we decided to use it anyway and to contribute to the module when we would run into problems. 

Installing the module was pretty easy and everything worked as we hoped it would. Except for one thing: the list of content was not filtered on the content from the current domain. This required creating a custom filter on the content list. In this post I’ll explain how we did that.

Create a filter plugin

For this filter we’ll create a new class “NodeDomainFilterPlugin” that extends from FilterPluginBase.  This is the base class for creating custom filters. Plugins are the new Drupal 8 way for hooking into core. You can find a series of posts about the plugin system in Drupal 8 here

/**
 * Filters nodes on current domain_id
 *
 * @ingroup views_filter_handlers
 *
 * @ViewsFilter("my_custom_domain_filter")
 */


class NodeDomainFilterPlugin extends FilterPluginBase {
  /**
   * {@inheritdoc}
   */
  public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
    parent::init($view, $display, $options);
    $this->valueTitle = t(‘Node Domain filter');
  }


  public function query() {
    //Get the current domain. 
    $domain = domain_get_domain();


   $configuration = [
      'table' => 'node_access',
      'field' => 'nid',
      'left_table' => 'node_field_data',
      'left_field' => 'nid',
      'operator' => '='
    ];
    $join = Views::pluginManager('join')->createInstance('standard', $configuration);


$this->query->addRelationship('node_access', $join, 'node_field_data');
    $this->query->addWhere('AND', 'node_access.gid', $domain->getDomainId());
  }

In the new NodeDomainFilterPlugin class we override 2 methods: ‘init’ and ‘query’.

  • We use the init method to set a custom title for our filter. 
  • The query method is where all the magic happens. A filter is actually nothing more than a ‘where’ clause added to the query. Because the domain configuration for a node resides in the node_access table, we first create a join to this table. Then we add a new relationship to the query with this join and create the actual filter with the addWhere method. 

Another important thing to watch out for is the @ViewsFilter annotation. This annotation is required for the plugin to register itself and describe some metadata. This string between quotes is the ID for our filter. For this tutorial we’ll use “my_custom_domain_filter”

Implement a hook

The second thing we have to do is to implement theviews_data_alterhook. We are adding a new filter to the node view. This way the view module knows about our filter so we can add it to the actual view a bit later. 

The second thing we have to do is to implement the views_data_alter hook. We are adding a new filter to the node view. This way the view module knows about our filter so we can add it to the actual view a bit later. 

function hook_views_data_alter(&$data)
{
    $data[‘node’][‘domain_filter'] = [
        'title' => t('Domain filter'),
        'filter' => [
            'title' => t('Domain filter'),
            'help' => 'filters nodes on current domain',
            'field' => ‘nid’,
            'id' => ‘my_custom_domain_filter',
        ]
    ];
}

The most important key here is ‘id’ that describes what filterplugin it’s all about.

Adding the filter to the view

Our custom filter is now created and we can add it to the view. We add our custom filter to the node list by clicking on ‘add filter criteria’ in below screenshot:

If you check the content list now, you will only see content that is linked to the current domain, demonstrating that our filter does what it is supposed to.

Something extra: make the filter portable

Our custom filter is now up and running, but we are not finished yet. Because this filter is now only added to the node list view on my local machine, I want to export this to code. This is important to us because of our way of working.

We build our projects in teams and we don’t want to be obliged to share SQL import scripts across the developers every time we’ve added functionality. Our solution for this is to make sure that all functionality is exported to code.

We also use Drush often to reinstall our Drupal environment. We don’t want to be obliged to add our custom filter to the view every time we reinstall our environment.

Exporting the view was not so difficult. The new configuration manager of Drupal 8 does this perfectly. You do this via “Configuration” > “Configuration Management” > “Single Import / Export” > “Export”, select view at configuration type and select the view you want to export. When exporting the view we got the following yaml code. Please note that we stripped the code a bit to keep it readable. If you wish to receive the full working code, do get in touch.

uuid : 096766bc-1420-4619-911a-2685e4874a5f
langcode: en
status: true
dependencies:
  module:
    - node
    - my_module
    - user
id: content
label: Content
module: node
description: 'Find and manage content.'
tag: default
base_table: node_field_data
base_field: nid
core: 8.x
…
   display_plugin: page
    display_title: Page
    id: page_1
    position: 1
    cache_metadata:
      contexts:
        - 'languages:language_content'
        - 'languages:language_interface'
        - url
        - 'url.query_args.pagers:0'
        - 'url.query_args:order'
        - 'url.query_args:sort'
        - user
        - 'user.node_grants:view'
        - user.permissions
      cacheable: false

To be recognized by Drupal, the exported yaml file has to be in my_module/config/install folder. We added it asviews.view.domain_content.yaml. All configuration entities in Drupal follow a particular naming convention: <module_name>.<config_object_name>.{optional id_key}.yaml

Here we ran into a little problem. A reinstall of Drupal would fail because there was already a view with this id: the original content view without our filter. We thought at first that Drupal would replace this with our new view, but it doesn’t. Drupal only replaces the view when the UUID is the same, so it knows which view to replace. When doing a reinstall the UUID of the original content view is changed, so Drupal does not know our view is an upgrade of the original view.

You can solve this by removing the UUID and altering the id to something unique. We named our view ‘domain_content’ for example. This will allow Drupal to create our view when installing our module.

Then we added the following line in the install hook in our custom module.

function my_module_core_install()
{
\Drupal::service('config.factory')->getEditable('views.view.content')->set('status', false)->save();
}

This will disable the original content view, so our custom view will be served when going to /admin/content. Because our view was a copy of the original with the custom filter, it has the same url.

That’s all, folks

In this post I have shown you how to create a complex filter in Drupal 8, how to add it to the view and how to export it to code.

Do you know a better way to solve this problem? Or do you have any other tips or comments? Let us know and comment below.

Verwante Artikels