Feeds

Upgrade Your Drupal Skills

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

See Advanced Courses NAH, I know Enough
Sep 24 2020
Sep 24

What is a views display extender

The display extender plugin allow to add additional options or configuration to a views regardless of the type of display (e.g. page, block, ..).

For example, if you wanted to allow site users to add certain metadata to the rendered output of every view display regardless of display type, you could provide this option as a display extender.

What we can do with it

We will see how we implement such a plugin, for the example, we will add some metadata (useless metatags as example) to the document head when the views is displayed.

We will call the display extender plugin HeadMetadata (id: head_metadata) and we will implement it in a module called views_head_metadata.

The implementation

Make our plugin discoverable

Views do not discover display extender plugins with a hook info as usual, for this particular type of plugin, views has a variable in his views.settings configuration object.

You need to add your plugin ID to the variable views.settings.display_extenders (that is a list).

To do so, I will recommend you to implement the hook_install (as well uninstall) in the module install file. To manipulate config object you can look at my previous notes on CMI.

Make the plugin class

As seen in the previous post on Drupal 8 plugins, you need to implement the class in the plugin type namespace, extend the base class for this type of plugin, and add the metadata annotation.

In the case of the display extender plugin, the namespace is Drupal\views_head_metadata\Plugin\views\display_extender, the base class is DisplayExtenderPluginBase, and the metadata annotation are defined in \Drupal\views\Annotation\ViewsDisplayExtender.

The display extender plugins methods are nearly the same that the display plugins, you can think of its like a set of methods to alter the display plugin.

The important methods to understand are :

  • defineOptionsAlter(&$options) : Define an array of options your plugins will define and save. Sort of schema of your plugin.
  • optionsSummary(&$categories, &$options) : To add a category (the section of the views admin interface) if you want to add one, and define your options settings (in wich category there are, and the value to display as the summary).
  • buildOptionsForm(&$form, FormStateInterface $form_state) : Where you construct the form(s) for your plugin, of course linked with a validate and submit method.

Generate the metadata tags in the document head

Now that we have our settings added to every views display, we need to use those to generate the tags in the document head as promised.

To work on the views render we will use the hook for that : hook_views_pre_render($view) and the render array property #attached.

Implement that hook in the .module of our module views_head_metadata, let's see :

Sep 24 2020
Sep 24

For the example we are going to implement an area that will present some links and text in a custom way, not sure if it's really usefull, but that not the point of this article.

The Plugin system

For the first post on the plugins I will introduce briefly on the concept. For those that already been using Ctools plugins system, you already now about the plugin system purposes.

For those who doesn't know about it, the plugin system is a way to let other module implements her own use case for an existing features, think of Field formatter : provide your own render array for a particular field display, or Widget : provide your own form element for a particular field type, etc...

The plugin system has three base elements :

Plugin Types

The plugin type is the central controlling class that defines how the plugins of this type will be discovered and instantiated. The type will describe the central purpose of all plugins of that type; e.g. cache backends, image actions, blocks, etc.

Plugin Discovery

Plugin Discovery is the process of finding plugins within the available code base that qualify for use within this particular plugin type's use case.

Plugin Factory

The Factory is responsible for instantiating the specific plugin(s) chosen for a given use case.

Detailled informations : https://www.drupal.org/node/1637730

In our case Views is responsible of that implementations so we are not going further on that, let see now how to implement a plugin definition.

The Plugin definitions

The existing documentation on the plugin definitions are a little abstract for now to understand how it really works (https://www.drupal.org/node/1653532).

You have to understand simply that a Plugin in most case is a Class implementation, namespaced within the namespace of the plugin type, in our example this is : \Drupal\module_name\Plugin\views\area

So if I implement a custom views area Plugin in my module the class will be located under the location module_name/src/Plugin/views/area/MyAreaHandler.php

To know where to implement a plugin definition for a plugin type, you can in most case look at module docs, or directly in the source code of the module (looking at an example of a definition will be enough)

In most cases, the modules that implement a Plugin type will provide a base class for the plugins definitions, in our example views area provide a base class : \Drupal\views\Plugin\views\area\AreaPluginBase

Drupal provide also a base class, if you implement a custom Plugin type, for the Plugin definition : \Drupal\Component\Plugin\PluginBase

Your custom plugin definition class must also have annotation metadata, that is defined by the module that implement the plugin type, in our example : \Drupal\views\Annotation\ViewsArea

In the case of views you will also need to implement the hook_views_data() into module_name.views.inc file, there you will inform views about the name and metadata of your Area handler.

Hands on implementation

So we have a custom module let's call it module_name for the example :)

We will create the class that implements our plugin definition and we are gonna give it this Plugin ID : my_custom_site_area.

We save this file into module_name/src/Plugin/views/area/MyCustomSiteArea.php

Now we just have to implements the hook_views_data() and yes this is the end, you can use your awesome views area handler into any view and any area.

Define this hook into the file : module_name/module_name.views.inc

Sep 24 2020
Sep 24

There is three types of configuration data :

The Simple Configuration API

  • Used to store unique configuration object.

  • Are namespaced by the module_name.

  • Can contain a list of structured variables (string, int, array, ..)

  • Default values can be found in Yaml : config/install/module_name.config_object_name.yml

  • Have a schema defined in config/schema/module_name.schema.yml

Code example :

The States

  • Not exportable, simple value that hardly depend of the environment.

  • Value can differ between environment (e.g. last_cron, maintenance_mode have different value on your local and on the production site)

The Entity Configuration API

  • Configuration object that can be multiple (e.g. views, image style, ckeditor profile, ...).

  • New Configuration type can be defined in custom module.

  • Have a defined schema in Yaml.

  • Not fieldable.

  • Values can be exported and stored as Yaml, can be stored by modules in config/install

Code example :

  https://www.drupal.org/node/1809494

Store configuration object in the module :

Config object (not states) can be stored in a module and imported during the install process of the modules.

To export a config object in a module you can use the configuration synchronisation UI at /admin/config/development/configuration/single/export

Select the configuration object type, then the object, copy the content and store it in your custom module config/install directory following the name convention that is provided below the textarea.

You can also use the features module that is now a simple configuration packager.

If after the install of the module, you want to update the config object, you can use the following drush command :

Configuration override system

Remember the variable $conf in settings.php in D6/D7 for overriding variables.

In D8, you can also override variable from the configuration API:

You can also do overrides at runtime.

Example: getting a value in a specific languages :

Drupal provide a storage for override an module can specify her own way of override, for deeper informations look at :

https://www.drupal.org/node/1928898

Configuration schema

The config object of Config API and of the configuration entity API have attached schema defined in module_name/config/install/module_name.schema.yml

These schema are not mandatory, but if you want to have translatable strings, nor form configuration / consistent export, you must take the time to implement the schema for your configuration object. However if you don't want to, you can just implement the toArray() method in your entity config object class.

Example, docs and informations : https://www.drupal.org/node/1905070

Configuration dependencies calculation

Default is in the .info of the module that define the config object like in D6/D7

But config entity can implements calculateDependencies() method to provide dynamic dependencies depending on config entity values.

Think of Config entity that store field display information for content entities specific view modes, there a need to have the module that hold the fields / formatters in dependencies but these are dynamic depending on the content entity display.

More information : https://www.drupal.org/node/2235409

Sep 24 2020
Sep 24

Ressources

Migrate in Drupal 8

Migrate is now included in the Drupal core for making the upgrade path from 6.x and 7.x versions to Drupal 8.

Drupal 8 has two new modules :
Migrate: « Handles migrations »
Migrate Drupal : « Contains migrations from older Drupal versions. »

None of these module have a User Interface.

« Migrate » contains the core framework classes, the destination, source and process plugins schemas and definitions, and at last the migration config entity schema and definition.

« Migrate Drupal » contains implementations of destination, sources and process plugins for Drupal 6 and 7 you can use it or extend it, it's ready to use. But this module doesn't contain the configuration to migrate all you datas from your older Drupal site to Drupal 8.

The core provides templates of migration configuration entity that are located under each module of the core that needs one, under a folder named 'migration_templates' to find all the templates you can use this command in your Drupal 8 site:

To make a Drupal core to core migration, you will find all the infos here : https://www.Drupal.org/node/2257723 there is an UI in progress for upgrading.

A migration framework

Let have a look at each big piece of the migration framework :

Source plugins

Drupal provides an interface and base classes for the migration source plugin :

  • SqlBase : Base class for SQL source, you need to extend this class to use it in your migration.
  • SourcePluginBase : Base class for every custom source plugin.
  • MenuLink: For D6/D7 menu links.
  • EmptySource (id:empty): Plugin source that returns an empty row.
  • ...

Process plugins

There is the equivalent of the D7 MigrateFieldHandler but this is not reduced to fields or to a particular field type.
Its purpose is to transform a raw value into something acceptable by your new site schema.

The method transform() of the plugin is in charge of transforming your $value or skipping the entire row if needed.
If the source property has multiple values, the transform() will happen on each one.

Drupal provides migration process plugin into each module of the core that needs it (for the core upgrade),
To find out which one and where it is located you can use this command :

Destination plugins

Destination plugins are the classes that handle where your data are saved in the new Drupal 8 sites schemas.

Drupal provides a lot of useful destination classes :

  • DestinationBase : Base class for migrate destination classes.
  • Entity (id: entity) : Base class for entity destinations.
  • Config (id: config) : Class for importing configuration entities.
  • EntityBaseFieldOverride (id: entity:base_field_override): Class for importing base field.
  • EntityConfigBase : Base class for importing configuration entities.
  • EntityImageStyle (id: entity:image_style): Class for importing image_style.
  • EntityContentBase (id: entity:%entity_type): The destination class for all content entities lacking a specific class.
  • EntityNodeType: (id: entity:node_type): A class for migrate node type.
  • EntityFile (id: entity:file): Class for migrate files.
  • EntityFieldInstance: Class for migrate field instance.
  • EntityFieldStorageConfig: Class for migrate field storage.
  • EntityRevision, EntityViewMode, EntityUser, Book...
  • And so more…

Builder plugins:

"Builder plugins implement custom logic to generate migration entities from migration templates. For example, a migration may need to be customized based on the data that is present in the source database; such customization is implemented by builders." - doc API

This is used in the user module, the builder create a migration configuration entity based on a migration template and then add fields mapping to the process, based on the data in the source database. (@see /Drupal/user/Plugin/migrate/builder/d7/User)

Id map plugins:

"It creates one map and one message table per migration entity to store the relevant information." - doc API
This is where rollback, update and the map creation are handled.
Drupal provides the Sql plugin (@see /Drupal/migrate/Plugin/migrate/id_map/Sql) based on the core base class PluginBase.

And we are talking only about core from the beginning.
All the examples (That means docs for devs) are in core !

About now :

While there *almost* a simple UI to use migration in Drupal 8 for Drupal to Drupal, Migrate can be used for every kind of data input. The work is in progess for http://Drupal.org/project/migrate_plus to bring an UI and more source plugins, process plugins and examples. There already is the CSV source plugin and a pending patch for the code example. The primary goal of « migrate plus » is to have all the features (UI, Sources, Destinations.. ) of the Drupal 7 version.

Concrete migration

(migration with Drupal 8 are made easy)

I need to migrate some content with image, attached files and categories from custom tables in an external SQL database to Drupal.

To begin shortly :

  • Drush 8 (dev master) and console installed.
  • Create the custom module (in the code, I assume the module name is “example_migrate”):
    $ Drupal generate:module
    or create the module by yourself, you only need the info.yml file.
  • Activate migrate and migrate_plus tools
    $ Drupal module:install migrate_tools
    or
    $ drush en migrate_tools
  • What we have in Drupal for the code example :
    • a taxonomy vocabulary : ‘example_content_category’
    • a content type ‘article’
    • some fields: body, field_image, field_attached_files, field_category
  • Define in settings.php, the connexion to your external database:

We are going to tell migrate source to use this database target. It happens in each migration configuration file, it’s a configuration property used by the SqlBase source plugin:

This is one of the reasons SqlBase has a wrapper for select query and you need to call it in your source plugin, like $this->select(), instead of building the query with bare hands.

N.B. Each time you add a custom yml file in your custom module you need to uninstall/reinstall the module for the config/install files to imports. In order to avoid that, you can import a single migration config file by copy/paste in the admin/config configuration synchronisation section.

The File migration

The content has images and files to migrate, I suppose in this example that the source database has a unique id for each file in a specific table that hold the file path to migrate.

We need a migration for the file to a Drupal 8 file entity, we write the source plugin for the file migration:

File: src/Plugin/migrate/source/ExampleFile.php

We have the source class and our source fields and each row generate a path to the file on my local disk.

But we need to transform our external file path to a local Drupal public file system URI, for that we need a process plugin. In our case the process plugin will take the external filepath and filename as arguments and return the new Drupal URI.

File: src/Plugin/migrate/process/ExampleFileUri.php

We need another process plugin to transform our source date values to timestamp (created, changed), as the date format is the same across the source database, this plugin will be reused in the content migration for the same purpose:

File: src/Plugin/migrate/process/ExampleDate.php

For the destination we use the core plugin: entity:file.

Now we have to define our migration config entity file, this is where the source, destination and process (field mappings) are defined:

File: config/install/migrate.migration.example_file.yml

We are done for the file migration, you can execute it with the migrate_tools (of the migrate_plus project) drush command:

The Term migration

The content has categories to migrate.
We need to import them as taxonomy term, in this example I suppose the categories didn't have unique ids, it is just a column of the article table with the category name…

First we create the source :

File: src/Plugin/migrate/source/ExampleCategory.php

And we can now create the migration config entity file :

File: config/install/migrate.migration.example_category.yml

This is done, to execute it :

The Content migration

The content from the source has an html content, raw excerpt, image, attached files, categories and the creation/updated date in the format Y-m-d H:i:s

We create the source plugin:

File: src/Plugin/migrate/source/ExampleContent.php

Now we can create the content migration config entity file :

File: config/install/migrate.migration.example_content.yml

Finally, execute it :

Group the migration

Thanks to migrate_plus, you can specify a migration group for your migration.
You need a to create a config entity for that :

File: config/install/migrate_plus.migration_group.example.yml

Then in your migration config yaml file, be sure to have the line migration_group next to the label:

So you can use the command to run the migration together, and the order of execution will depend on the migration dependencies:

I hope that you enjoyed our article.

Best regards,

Delta https://www.drupal.org/u/delta

Sep 24 2020
Sep 24

At Studio.gd we love the Drupal ecosystem and it became very important to us to give back and participate.
Today we're proud to announce a new module that we hope will help you !

Inline Entity Display module will help you handle the display of referenced entity fields directly in the parent entity.
For exemple if you reference a taxomony "Tags" to an Article node, you will be able directly in the manage display of the article to display tags' fields. It can become very usefull with more complex referenced entity like field collection for exemple.

VOIR LE MODULE : https://www.drupal.org/project/inline_entity_display


Features

- You can control, for each compatible reference field instances, if the fields from the referenced entities would be available as extra fields. Disabled by default.

- You can manage the visibility of the referenced entities fields on the manage display form. Hidden by default.

- View modes are added to represent this context and manage custom display settings for the referenced entities fields in this context {entity_type}_{view_mode} Example: "Node: Teaser" is used to render referenced entities fields, when you reference an entity into a node, and you view this node as a teaser if there are no custom settings for this view mode, fields are rendered using the default view mode settings.

- Extra data attributes are added on the default fields markup, so the field of the same entity can be identified.

Compatible with Field group on manage display form.

Compatible with Display Suite layouts on manage display form.

Requirements

- Entity API
- One of the compatible reference fields module.

Tutorials

simplytest.me/project/inline_entity_display/7.x-1.x
The simplytest.me install of this module will come automatically with these modules: entity_reference, field_collection, field_group, display suite.

VOIR LE MODULE : https://www.drupal.org/project/inline_entity_display

We are currently developping a similar module for Drupal 8 but more powerful and more flexible, Stay tuned !

Mar 07 2016
Mar 07

What is a views display extender

The display extender plugin allow to add additional options or configuration to a views regardless of the type of display (e.g. page, block, ..).

For example, if you wanted to allow site users to add certain metadata to the rendered output of every view display regardless of display type, you could provide this option as a display extender.

What we can do with it

We will see how we implement such a plugin, for the example, we will add some metadata (useless metatags as example) to the document head when the views is displayed.

We will call the display extender plugin HeadMetadata (id: head_metadata) and we will implement it in a module called views_head_metadata.

The implementation

Make our plugin discoverable

Views do not discover display extender plugins with a hook info as usual, for this particular type of plugin, views has a variable in his views.settings configuration object.

You need to add your plugin ID to the variable views.settings.display_extenders (that is a list).

To do so, I will recommend you to implement the hook_install (as well uninstall) in the module install file. To manipulate config object you can look at my previous notes on CMI.

Make the plugin class

As seen in the previous post on Drupal 8 plugins, you need to implement the class in the plugin type namespace, extend the base class for this type of plugin, and add the metadata annotation.

In the case of the display extender plugin, the namespace is Drupal\views_head_metadata\Plugin\views\display_extender, the base class is DisplayExtenderPluginBase, and the metadata annotation are defined in \Drupal\views\Annotation\ViewsDisplayExtender.

The display extender plugins methods are nearly the same that the display plugins, you can think of its like a set of methods to alter the display plugin.

The important methods to understand are :

  • defineOptionsAlter(&$options) : Define an array of options your plugins will define and save. Sort of schema of your plugin.
  • optionsSummary(&$categories, &$options) : To add a category (the section of the views admin interface) if you want to add one, and define your options settings (in wich category there are, and the value to display as the summary).
  • buildOptionsForm(&$form, FormStateInterface $form_state) : Where you construct the form(s) for your plugin, of course linked with a validate and submit method.

Generate the metadata tags in the document head

Now that we have our settings added to every views display, we need to use those to generate the tags in the document head as promised.

To work on the views render we will use the hook for that : hook_views_pre_render($view) and the render array property #attached.

Implement that hook in the .module of our module views_head_metadata, let's see :

Mar 07 2016
Mar 07

For the example we are going to implement an area that will present some links and text in a custom way, not sure if it's really usefull, but that not the point of this article.

The Plugin system

For the first post on the plugins I will introduce briefly on the concept. For those that already been using Ctools plugins system, you already now about the plugin system purposes.

For those who doesn't know about it, the plugin system is a way to let other module implements her own use case for an existing features, think of Field formatter : provide your own render array for a particular field display, or Widget : provide your own form element for a particular field type, etc...

The plugin system has three base elements :

Plugin Types

The plugin type is the central controlling class that defines how the plugins of this type will be discovered and instantiated. The type will describe the central purpose of all plugins of that type; e.g. cache backends, image actions, blocks, etc.

Plugin Discovery

Plugin Discovery is the process of finding plugins within the available code base that qualify for use within this particular plugin type's use case.

Plugin Factory

The Factory is responsible for instantiating the specific plugin(s) chosen for a given use case.

Detailled informations : https://www.drupal.org/node/1637730

In our case Views is responsible of that implementations so we are not going further on that, let see now how to implement a plugin definition.

The Plugin definitions

The existing documentation on the plugin definitions are a little abstract for now to understand how it really works (https://www.drupal.org/node/1653532).

You have to understand simply that a Plugin in most case is a Class implementation, namespaced within the namespace of the plugin type, in our example this is : \Drupal\module_name\Plugin\views\area

So if I implement a custom views area Plugin in my module the class will be located under the location module_name/src/Plugin/views/area/MyAreaHandler.php

To know where to implement a plugin definition for a plugin type, you can in most case look at module docs, or directly in the source code of the module (looking at an example of a definition will be enough)

In most cases, the modules that implement a Plugin type will provide a base class for the plugins definitions, in our example views area provide a base class : \Drupal\views\Plugin\views\area\AreaPluginBase

Drupal provide also a base class, if you implement a custom Plugin type, for the Plugin definition : \Drupal\Component\Plugin\PluginBase

Your custom plugin definition class must also have annotation metadata, that is defined by the module that implement the plugin type, in our example : \Drupal\views\Annotation\ViewsArea

In the case of views you will also need to implement the hook_views_data() into module_name.views.inc file, there you will inform views about the name and metadata of your Area handler.

Hands on implementation

So we have a custom module let's call it module_name for the example :)

We will create the class that implements our plugin definition and we are gonna give it this Plugin ID : my_custom_site_area.

We save this file into module_name/src/Plugin/views/area/MyCustomSiteArea.php

Now we just have to implements the hook_views_data() and yes this is the end, you can use your awesome views area handler into any view and any area.

Define this hook into the file : module_name/module_name.views.inc

Mar 07 2016
Mar 07

There is three types of configuration data :

The Simple Configuration API

  • Used to store unique configuration object.

  • Are namespaced by the module_name.

  • Can contain a list of structured variables (string, int, array, ..)

  • Default values can be found in Yaml : config/install/module_name.config_object_name.yml

  • Have a schema defined in config/schema/module_name.schema.yml

Code example :

The States

  • Not exportable, simple value that hardly depend of the environment.

  • Value can differ between environment (e.g. last_cron, maintenance_mode have different value on your local and on the production site)

The Entity Configuration API

  • Configuration object that can be multiple (e.g. views, image style, ckeditor profile, ...).

  • New Configuration type can be defined in custom module.

  • Have a defined schema in Yaml.

  • Not fieldable.

  • Values can be exported and stored as Yaml, can be stored by modules in config/install

Code example :

  https://www.drupal.org/node/1809494

Store configuration object in the module :

Config object (not states) can be stored in a module and imported during the install process of the modules.

To export a config object in a module you can use the configuration synchronisation UI at /admin/config/development/configuration/single/export

Select the configuration object type, then the object, copy the content and store it in your custom module config/install directory following the name convention that is provided below the textarea.

You can also use the features module that is now a simple configuration packager.

If after the install of the module, you want to update the config object, you can use the following drush command :

Configuration override system

Remember the variable $conf in settings.php in D6/D7 for overriding variables.

In D8, you can also override variable from the configuration API:

You can also do overrides at runtime.

Example: getting a value in a specific languages :

Drupal provide a storage for override an module can specify her own way of override, for deeper informations look at :

https://www.drupal.org/node/1928898

Configuration schema

The config object of Config API and of the configuration entity API have attached schema defined in module_name/config/install/module_name.schema.yml

These schema are not mandatory, but if you want to have translatable strings, nor form configuration / consistent export, you must take the time to implement the schema for your configuration object. However if you don't want to, you can just implement the toArray() method in your entity config object class.

Example, docs and informations : https://www.drupal.org/node/1905070

Configuration dependencies calculation

Default is in the .info of the module that define the config object like in D6/D7

But config entity can implements calculateDependencies() method to provide dynamic dependencies depending on config entity values.

Think of Config entity that store field display information for content entities specific view modes, there a need to have the module that hold the fields / formatters in dependencies but these are dynamic depending on the content entity display.

More information : https://www.drupal.org/node/2235409

Mar 07 2016
Mar 07

Ressources

Migrate in Drupal 8

Migrate is now included in the Drupal core for making the upgrade path from 6.x and 7.x versions to Drupal 8.

Drupal 8 has two new modules :
Migrate: « Handles migrations »
Migrate Drupal : « Contains migrations from older Drupal versions. »

None of these module have a User Interface.

« Migrate » contains the core framework classes, the destination, source and process plugins schemas and definitions, and at last the migration config entity schema and definition.

« Migrate Drupal » contains implementations of destination, sources and process plugins for Drupal 6 and 7 you can use it or extend it, it's ready to use. But this module doesn't contain the configuration to migrate all you datas from your older Drupal site to Drupal 8.

The core provides templates of migration configuration entity that are located under each module of the core that needs one, under a folder named 'migration_templates' to find all the templates you can use this command in your Drupal 8 site:

To make a Drupal core to core migration, you will find all the infos here : https://www.Drupal.org/node/2257723 there is an UI in progress for upgrading.

A migration framework

Let have a look at each big piece of the migration framework :

Source plugins

Drupal provides an interface and base classes for the migration source plugin :

  • SqlBase : Base class for SQL source, you need to extend this class to use it in your migration.
  • SourcePluginBase : Base class for every custom source plugin.
  • MenuLink: For D6/D7 menu links.
  • EmptySource (id:empty): Plugin source that returns an empty row.
  • ...

Process plugins

There is the equivalent of the D7 MigrateFieldHandler but this is not reduced to fields or to a particular field type.
Its purpose is to transform a raw value into something acceptable by your new site schema.

The method transform() of the plugin is in charge of transforming your $value or skipping the entire row if needed.
If the source property has multiple values, the transform() will happen on each one.

Drupal provides migration process plugin into each module of the core that needs it (for the core upgrade),
To find out which one and where it is located you can use this command :

Destination plugins

Destination plugins are the classes that handle where your data are saved in the new Drupal 8 sites schemas.

Drupal provides a lot of useful destination classes :

  • DestinationBase : Base class for migrate destination classes.
  • Entity (id: entity) : Base class for entity destinations.
  • Config (id: config) : Class for importing configuration entities.
  • EntityBaseFieldOverride (id: entity:base_field_override): Class for importing base field.
  • EntityConfigBase : Base class for importing configuration entities.
  • EntityImageStyle (id: entity:image_style): Class for importing image_style.
  • EntityContentBase (id: entity:%entity_type): The destination class for all content entities lacking a specific class.
  • EntityNodeType: (id: entity:node_type): A class for migrate node type.
  • EntityFile (id: entity:file): Class for migrate files.
  • EntityFieldInstance: Class for migrate field instance.
  • EntityFieldStorageConfig: Class for migrate field storage.
  • EntityRevision, EntityViewMode, EntityUser, Book...
  • And so more…

Builder plugins:

"Builder plugins implement custom logic to generate migration entities from migration templates. For example, a migration may need to be customized based on the data that is present in the source database; such customization is implemented by builders." - doc API

This is used in the user module, the builder create a migration configuration entity based on a migration template and then add fields mapping to the process, based on the data in the source database. (@see /Drupal/user/Plugin/migrate/builder/d7/User)

Id map plugins:

"It creates one map and one message table per migration entity to store the relevant information." - doc API
This is where rollback, update and the map creation are handled.
Drupal provides the Sql plugin (@see /Drupal/migrate/Plugin/migrate/id_map/Sql) based on the core base class PluginBase.

And we are talking only about core from the beginning.
All the examples (That means docs for devs) are in core !

About now :

While there *almost* a simple UI to use migration in Drupal 8 for Drupal to Drupal, Migrate can be used for every kind of data input. The work is in progess for http://Drupal.org/project/migrate_plus to bring an UI and more source plugins, process plugins and examples. There already is the CSV source plugin and a pending patch for the code example. The primary goal of « migrate plus » is to have all the features (UI, Sources, Destinations.. ) of the Drupal 7 version.

Concrete migration

(migration with Drupal 8 are made easy)

I need to migrate some content with image, attached files and categories from custom tables in an external SQL database to Drupal.

To begin shortly :

  • Drush 8 (dev master) and console installed.
  • Create the custom module (in the code, I assume the module name is “example_migrate”):
    $ Drupal generate:module
    or create the module by yourself, you only need the info.yml file.
  • Activate migrate and migrate_plus tools
    $ Drupal module:install migrate_tools
    or
    $ drush en migrate_tools
  • What we have in Drupal for the code example :
    • a taxonomy vocabulary : ‘example_content_category’
    • a content type ‘article’
    • some fields: body, field_image, field_attached_files, field_category
  • Define in settings.php, the connexion to your external database:

We are going to tell migrate source to use this database target. It happens in each migration configuration file, it’s a configuration property used by the SqlBase source plugin:

This is one of the reasons SqlBase has a wrapper for select query and you need to call it in your source plugin, like $this->select(), instead of building the query with bare hands.

N.B. Each time you add a custom yml file in your custom module you need to uninstall/reinstall the module for the config/install files to imports. In order to avoid that, you can import a single migration config file by copy/paste in the admin/config configuration synchronisation section.

The File migration

The content has images and files to migrate, I suppose in this example that the source database has a unique id for each file in a specific table that hold the file path to migrate.

We need a migration for the file to a Drupal 8 file entity, we write the source plugin for the file migration:

File: src/Plugin/migrate/source/ExampleFile.php

We have the source class and our source fields and each row generate a path to the file on my local disk.

But we need to transform our external file path to a local Drupal public file system URI, for that we need a process plugin. In our case the process plugin will take the external filepath and filename as arguments and return the new Drupal URI.

File: src/Plugin/migrate/process/ExampleFileUri.php

We need another process plugin to transform our source date values to timestamp (created, changed), as the date format is the same across the source database, this plugin will be reused in the content migration for the same purpose:

File: src/Plugin/migrate/process/ExampleDate.php

For the destination we use the core plugin: entity:file.

Now we have to define our migration config entity file, this is where the source, destination and process (field mappings) are defined:

File: config/install/migrate.migration.example_file.yml

We are done for the file migration, you can execute it with the migrate_tools (of the migrate_plus project) drush command:

The Term migration

The content has categories to migrate.
We need to import them as taxonomy term, in this example I suppose the categories didn't have unique ids, it is just a column of the article table with the category name…

First we create the source :

File: src/Plugin/migrate/source/ExampleCategory.php

And we can now create the migration config entity file :

File: config/install/migrate.migration.example_category.yml

This is done, to execute it :

The Content migration

The content from the source has an html content, raw excerpt, image, attached files, categories and the creation/updated date in the format Y-m-d H:i:s

We create the source plugin:

File: src/Plugin/migrate/source/ExampleContent.php

Now we can create the content migration config entity file :

File: config/install/migrate.migration.example_content.yml

Finally, execute it :

Group the migration

Thanks to migrate_plus, you can specify a migration group for your migration.
You need a to create a config entity for that :

File: config/install/migrate_plus.migration_group.example.yml

Then in your migration config yaml file, be sure to have the line migration_group next to the label:

So you can use the command to run the migration together, and the order of execution will depend on the migration dependencies:

I hope that you enjoyed our article.

Best regards,

Delta https://www.drupal.org/u/delta

Mar 07 2016
Mar 07

At Studio.gd we love the Drupal ecosystem and it became very important to us to give back and participate.
Today we're proud to announce a new module that we hope will help you !

Inline Entity Display module will help you handle the display of referenced entity fields directly in the parent entity.
For exemple if you reference a taxomony "Tags" to an Article node, you will be able directly in the manage display of the article to display tags' fields. It can become very usefull with more complex referenced entity like field collection for exemple.

VOIR LE MODULE : https://www.drupal.org/project/inline_entity_display


Features

- You can control, for each compatible reference field instances, if the fields from the referenced entities would be available as extra fields. Disabled by default.

- You can manage the visibility of the referenced entities fields on the manage display form. Hidden by default.

- View modes are added to represent this context and manage custom display settings for the referenced entities fields in this context {entity_type}_{view_mode} Example: "Node: Teaser" is used to render referenced entities fields, when you reference an entity into a node, and you view this node as a teaser if there are no custom settings for this view mode, fields are rendered using the default view mode settings.

- Extra data attributes are added on the default fields markup, so the field of the same entity can be identified.

Compatible with Field group on manage display form.

Compatible with Display Suite layouts on manage display form.

Requirements

- Entity API
- One of the compatible reference fields module.

Tutorials

simplytest.me/project/inline_entity_display/7.x-1.x
The simplytest.me install of this module will come automatically with these modules: entity_reference, field_collection, field_group, display suite.

VOIR LE MODULE : https://www.drupal.org/project/inline_entity_display

We are currently developping a similar module for Drupal 8 but more powerful and more flexible, Stay tuned !

Oct 13 2015
Oct 13

Ressources

Update

migrate_plus is gonna be split into differentmodules for consistency. More information : http://mikeryan.name/blog/mikeryan/migrate-plus-splits-up

Migrate in Drupal 8

Migrate is now included in the Drupal core for making the upgrade path from 6.x and 7.x versions to Drupal 8.

Drupal 8 has two new modules :
Migrate: « Handles migrations »
Migrate Drupal : « Contains migrations from older Drupal versions. »

None of these module have a User Interface.

« Migrate » contains the core framework classes, the destination, source and process plugins schemas and definitions, and at last the migration config entity schema and definition.

« Migrate Drupal » contains implementations of destination, sources and process plugins for Drupal 6 and 7 you can use it or extend it, it's ready to use. But this module doesn't contain the configuration to migrate all you datas from your older Drupal site to Drupal 8.

The core provides templates of migration configuration entity that are located under each module of the core that needs one, under a folder named 'migration_templates' to find all the templates you can use this command in your Drupal 8 site:

To make a Drupal core to core migration, you will find all the infos here : https://www.Drupal.org/node/2257723 there is an UI in progress for upgrading.

 

A migration framework

Let have a look at each big piece of the migration framework :

Source plugins

Drupal provides an interface and base classes for the migration source plugin :

  • SqlBase : Base class for SQL source, you need to extend this class to use it in your migration.
  • SourcePluginBase : Base class for every custom source plugin.
  • MenuLink: For D6/D7 menu links.
  • EmptySource (id:empty): Plugin source that returns an empty row.
  • ...

Process plugins

There is the equivalent of the D7 MigrateFieldHandler but this is not reduced to fields or to a particular field type.
Its purpose is to transform a raw value into something acceptable by your new site schema.

The method transform() of the plugin is in charge of transforming your $value or skipping the entire row if needed.
If the source property has multiple values, the transform() will happen on each one.

Drupal provides migration process plugin into each module of the core that needs it (for the core upgrade),
To find out which one and where it is located you can use this command :

Destination plugins

Destination plugins are the classes that handle where your data are saved in the new Drupal 8 sites schemas.

Drupal provides a lot of useful destination classes :

  • DestinationBase : Base class for migrate destination classes.
  • Entity (id: entity) : Base class for entity destinations.
  • Config (id: config) : Class for importing configuration entities.
  • EntityBaseFieldOverride (id: entity:base_field_override): Class for importing base field.
  • EntityConfigBase : Base class for importing configuration entities.
  • EntityImageStyle (id: entity:image_style): Class for importing image_style.
  • EntityContentBase (id: entity:%entity_type): The destination class for all content entities lacking a specific class.
  • EntityNodeType: (id: entity:node_type): A class for migrate node type.
  • EntityFile (id: entity:file): Class for migrate files.
  • EntityFieldInstance: Class for migrate field instance.
  • EntityFieldStorageConfig: Class for migrate field storage.
  • EntityRevision, EntityViewMode, EntityUser, Book...
  • And so more…

Builder plugins:

"Builder plugins implement custom logic to generate migration entities from migration templates. For example, a migration may need to be customized based on the data that is present in the source database; such customization is implemented by builders." - doc API

This is used in the user module, the builder create a migration configuration entity based on a migration template and then add fields mapping to the process, based on the data in the source database. (@see /Drupal/user/Plugin/migrate/builder/d7/User)

Id map plugins:

"It creates one map and one message table per migration entity to store the relevant information." - doc API
This is where rollback, update and the map creation are handled.
Drupal provides the Sql plugin (@see /Drupal/migrate/Plugin/migrate/id_map/Sql) based on the core base class PluginBase.

And we are talking only about core from the beginning.
All the examples (That means docs for devs) are in core !

About now :

While there *almost* a simple UI to use migration in Drupal 8 for Drupal to Drupal, Migrate can be used for every kind of data input. The work is in progess for http://Drupal.org/project/migrate_plus to bring an UI and more source plugins, process plugins and examples. There already is the CSV source plugin and a pending patch for the code example. The primary goal of « migrate plus » is to have all the features (UI, Sources, Destinations.. ) of the Drupal 7 version.

Concrete migration

(migration with Drupal 8 are made easy)

I need to migrate some content with image, attached files and categories from custom tables in an external SQL database to Drupal.

To begin shortly :

  • Drush 8 (dev master) and console installed.
  • Create the custom module (in the code, I assume the module name is “example_migrate”):
    $ Drupal generate:module
    or create the module by yourself, you only need the info.yml file.
  • Activate migrate and migrate_plus tools
    $ Drupal module:install migrate_tools
    or
    $ drush en migrate_tools
  • What we have in Drupal for the code example :
    • a taxonomy vocabulary : ‘example_content_category’
    • a content type ‘article’
    • some fields: body, field_image, field_attached_files, field_category
  • Define in settings.php, the connexion to your external database:

We are going to tell migrate source to use this database target. It happens in each migration configuration file, it’s a configuration property used by the SqlBase source plugin:

This is one of the reasons SqlBase has a wrapper for select query and you need to call it in your source plugin, like $this->select(), instead of building the query with bare hands.

N.B. Each time you add a custom yml file in your custom module you need to uninstall/reinstall the module for the config/install files to imports. In order to avoid that, you can import a single migration config file by copy/paste in the admin/config configuration synchronisation section.

The File migration

The content has images and files to migrate, I suppose in this example that the source database has a unique id for each file in a specific table that hold the file path to migrate.

We need a migration for the file to a Drupal 8 file entity, we write the source plugin for the file migration:

File: src/Plugin/migrate/source/ExampleFile.php

We have the source class and our source fields and each row generate a path to the file on my local disk.

But we need to transform our external file path to a local Drupal public file system URI, for that we need a process plugin. In our case the process plugin will take the external filepath and filename as arguments and will copy it to the public file system and return the new Drupal URI.

File: src/Plugin/migrate/process/ExampleFileUri.php

We need another process plugin to transform our source date values to timestamp (created, changed), as the date format is the same across the source database, this plugin will be reused in the content migration for the same purpose:

File: src/Plugin/migrate/process/ExampleDate.php

For the destination we use the core plugin: entity:file.

Now we have to define our migration config entity file, this is where the source, destination and process (field mappings) are defined:

File: config/install/migrate.migration.example_file.yml

We are done for the file migration, you can execute it with the migrate_tools (of the migrate_plus project) drush command:

The Term migration

The content has categories to migrate.
We need to import them as taxonomy term, in this example I suppose the categories didn't have unique ids, it is just a column of the article table with the category name…

First we create the source :

File: src/Plugin/migrate/source/ExampleCategory.php

And we can now create the migration config entity file :

File: config/install/migrate.migration.example_category.yml

This is done, to execute it :

The Content migration

The content from the source has an html content, raw excerpt, image, attached files, categories and the creation/updated date in the format Y-m-d H:i:s

We create the source plugin:

File: src/Plugin/migrate/source/ExampleContent.php

Now we can create the content migration config entity file :

File: config/install/migrate.migration.example_content.yml

Finally, execute it :

Group the migration

Thanks to migrate_plus, you can specify a migration group for your migration.
You need a to create a config entity for that :

File: config/install/migrate_plus.migration_group.example.yml

Then in your migration config yaml file, be sure to have the line migration_group next to the label:

So you can use the command to run the migration together, and the order of execution will depend on the migration dependencies:



I hope that you enjoyed our article.

Best regards,

Delta https://www.drupal.org/u/delta





We at Studio.gd are Drupal experts since 2009.

Oct 21 2014
Oct 21

At Studio.gd we love the Drupal ecosystem and it became very important to us to give back and participate.
Today we're proud to announce a new module that we hope will help you !

Inline Entity Display module will help you handle the display of referenced entity fields directly in the parent entity.
For exemple if you reference a taxomony "Tags" to an Article node, you will be able directly in the manage display of the article to display tags' fields. It can become very usefull with more complex referenced entity like field collection for exemple.

VOIR LE MODULE : https://www.drupal.org/project/inline_entity_display

Features

- You can control, for each compatible reference field instances, if the fields from the referenced entities would be available as extra fields. Disabled by default.

- You can manage the visibility of the referenced entities fields on the manage display form. Hidden by default.

- View modes are added to represent this context and manage custom display settings for the referenced entities fields in this context {entity_type}_{view_mode} Example: "Node: Teaser" is used to render referenced entities fields, when you reference an entity into a node, and you view this node as a teaser if there are no custom settings for this view mode, fields are rendered using the default view mode settings.

- Extra data attributes are added on the default fields markup, so the field of the same entity can be identified.

Compatible with Field group on manage display form.

Compatible with Display Suite layouts on manage display form.

Requirements

- Entity API
- One of the compatible reference fields module.

Tutorials

simplytest.me/project/inline_entity_display/7.x-1.x
The simplytest.me install of this module will come automatically with these modules: entity_reference, field_collection, field_group, display suite.


VOIR LE MODULE : https://www.drupal.org/project/inline_entity_display

We are currently developping a similar module for Drupal 8 but more powerful and more flexible, Stay tuned !

Apr 22 2014
Apr 22

Every time you create a site with drupal you may add a new contextual tab for a node or for a user. if you have used pathauto to create automatic (multilangual) aliasing of user and node url, so on, you got good urls for SEO ! But your custom module urls must also be optimised for search engines.

You can use subpathauto https://drupal.org/project/subpathauto module to implement pattern for url of type "module/%foo", but if you provide a module with a custom entity, you may want the entity url ready for pathauto

For the example we create a node type called article, and we add a two custom url : Poll and Photos.

/**
 * Implements hook_menu().
 */
function module_name_menu() {
  $items = array();
  $items['node/%node/poll'] = array(
    'title' => 'Poll',
    'page callback' => 'module_name_poll_page_view',
    'page arguments' => array(1, 2),
    'access callback' => 'node_access',
    'access arguments' => array('view', 1),
    'type' => MENU_LOCAL_TASK,
  );
  $items['node/%node/photos'] = array(
    'title' => 'Photos',
    'page callback' => 'module_name_photos_page_view',
    'page arguments' => array(1, 2),
    'access callback' => 'node_access',
    'access arguments' => array('view', 1),
    'type' => MENU_LOCAL_TASK,
  );
  return $items;
}

With pathauto enabled, we have a customisable, translatable pattern url for the node page :
http://example.com/node/245
that became
http://example.com/content/article-title

BUT

http://example.com/node/245/poll and http://example.com/node/245/photos
stay
http://example.com/node/245/poll and http://example.com/node/245/photos

To fix that issue you can implement the pathauto api to create custom URL patterns :

/**
 * Implements hook_pathauto().
 */
function module_name_pathauto($op) {
 switch ($op) {
 	case 'settings':
 	 $settings = array();
 	 $settings['module'] = 'module_name';
 	 $settings['token_type'] = 'node';
 	 $settings['groupheader'] = t('Module_name node paths');
 	 $settings['patterndescr'] = t('Default pattern');
 	 $settings['patterndefault'] = '';
 	 $settings['batch_update_callback'] = 'module_name_pathauto_bulkupdate';

 	 $path = array(
 	   'module_name_node_poll' => 'Module_name poll path',
 	   'module_name_node_photos' => 'Module_name photos path',
 	 );
         
 	 $langs = array_keys(language_list());
 	 foreach ($path as $k => $p) {
 	  foreach ($langs as $lang) {
 	   $path[$k . '_' . $lang] = $p . ' for language ' . $lang;
  	 }
  	 unset($path[$k]);
 	 }
 	 $settings['patternitems'] = $path;
 	 return (object) $settings;
 }
}

The custom pattern are done. More example for the implementation of this hook

When a new node is created, updated or deleted the alias must be updated or deleted, to do that we use the hook provided by drupal :

/**
 * Implements hook_node_insert().
 */
function module_name_node_insert($node) {
    module_name_create_alias($node, 'insert');
}

/**
 * Implements hook_node_update().
 */
function module_name_node_update($node) {
    module_name_create_alias($node, 'update');
}

/**
 * Implements hook_node_delete().
 */
function module_name_node_delete($node) {
  module_load_include('inc', 'pathauto');
  pathauto_path_delete_all('node/' . $node->nid . '/poll');
  pathauto_path_delete_all('node/' . $node->nid . '/photos');
}

/**
 * Module_name create alias for a node custom urls.
 */
function module_name_create_alias($node, $op) {
 $langs = array_keys(language_list());
 module_load_include('inc', 'pathauto');
 foreach ($langs as $lang) {
   pathauto_create_alias('module_name', $op, 'node/' . $node->nid . '/poll' , array('node' => $node), 'module_name_node_poll_' . $lang, $lang);
   pathauto_create_alias('module_name', $op, 'node/' . $node->nid . '/photos' , array('node' => $node), 'module_name_node_photos_' . $lang, $lang);
 }
}

To generate/update your urls alias with the bulk update interface or with drush there is callback defined in the hook_pathauto : "batch_update_callback" :

/**
 * Implements hook_pathauto_bulkupdate.
 */
function module_name_pathauto_bulkupdate() {
  if (!isset($context['sandbox']['current'])) {
    $context['sandbox']['count'] = 0;
    $context['sandbox']['current'] = 0;
  }

  $query = db_select('node', 'n');  
  $query->addField('n', 'nid');
  $query->leftJoin('url_alias', 'ua', " ua.source LIKE CONCAT('node/', n.nid, '/%')");
  $query->isNull('ua.source');
  $query->condition('n.nid', $context['sandbox']['current'], '>');
  $query->orderBy('n.nid');
  $query->addTag('pathauto_bulk_update');
  $query->addMetaData('entity', 'node');

  // Get the total amount of items to process.
  if (!isset($context['sandbox']['total'])) {
    $context['sandbox']['total'] = $query->countQuery()->execute()->fetchField();

    // If there are no nodes to update, the stop immediately.
    if (!$context['sandbox']['total']) {
      $context['finished'] = 1;
      return;
    }
  }

  $query->range(0, 25);
  $nids = $query->execute()->fetchCol();

  $nodes = node_load_multiple($nids);
  foreach ($nodes as $node) {
    module_name_create_alias($node, 'bulkupdate'); 
  }
  $context['sandbox']['count'] += count($nids);
  $context['sandbox']['current'] = max($nids);
  $context['message'] = t('Updated alias for node @nid.', array('@nid' => end($nids)));

  if ($context['sandbox']['count'] != $context['sandbox']['total']) {
    $context['finished'] = $context['sandbox']['count'] / $context['sandbox']['total'];
  }
}

The code is over, you can fill the pattern in your site administration : admin/config/search/path/patterns
Let the default pattern blank and fill for each language and tabs the good patterns, example : "content/[node:title]/poll"

Nov 12 2013
Nov 12

You first need a function to initialize the taxonomy term object :

/**
 * Initialize a default term object.
 *
 * @param $vocabulary.
 *   The vocabulary object.
 * @return stdClass
 *   The taxonomy term object.
 */
function _yourmodule_profile_create_term_default($vocabulary) {
 $term = new stdClass();
 $term->name = '';
 $term->description = '';
 $term->format = 'plain_text';
 $term->vid = $vocabulary->vid;
 $term->vocabulary_machine_name = $vocabulary->machine_name;
 $term->tid = NULL;
 $term->weight = 0;
  // if you use source strings, the term must be created with the language none langcode.
 $term->language = LANGUAGE_NONE;

 return $term;
}

Call it like this in your custom module or profile install:

// You need the vocabulary object:
$vocabulary = taxonomy_vocabulary_machine_name_load('theme');
$tids = array();

$term = _yourmodule_profile_create_term_default($vocabulary);

// Apply your custom term name, and field if you have
$term->name = "Lifestyle";
 //$term->field_theme_color = array(LANGUAGE_NONE => array(array('value' => 'red')));
taxonomy_term_save($term);
// You can store your terms tids in a custom variables for a later use.
// $tids['lifestyle']['tid'] = $term->tid;

To translate your term name, you will need an helper function too, that use the api of i18n module :

/**
 * Create or update a translation string for a taxonomy term.
 *
 * @param $term
 *   The term object with the tid set.
 * @param $langcode
 *   The langcode for the translation.
 * @param $translation
 *   The translation string.
 */
function _yourmodule_profile_create_term_translation($term, $langcode, $translation, $property = 'name') {
  $context = array(
        'term',
    $term->tid,
    $property,
  );
  $textgroup = 'taxonomy';
  i18n_string_textgroup($textgroup)->update_translation($context, $langcode, $translation);
}

Call it like this in your custom module or profile install:

// You need the vocabulary object:
$vocabulary = taxonomy_vocabulary_machine_name_load('theme');
//$tids = array();

$term = _yourmodule_profile_create_term_default($vocabulary);

// Apply your custom term name, and field if you have
$term->name = "Lifestyle";
//$term->field_theme_color = array(LANGUAGE_NONE => array(array('value' => 'red')));
taxonomy_term_save($term);
// You can store your terms tids in a custom variables for a later use.
// $tids['lifestyle']['tid'] = $term->tid;
_yourmodule_profile_create_term_translation($term, 'fr', 'Art de vivre');

This code is designed to works with taxonomy vocabulary, that have i18n option enabled on : "Localize. Terms are common for all languages, but their name and description may be localized. "

For the i18n option : "Translate. Different terms will be allowed for each language and they can be translated.":
you don't need the translate helper function. You must create a term for each lang code, like that :

$vocabulary = taxonomy_vocabulary_machine_name_load('theme');

// English
$term = _yourmodule_profile_create_term_default($vocabulary);
$term->name = "Lifestyle";
$term->language= 'en-gb';
taxonomy_term_save($term);

// French
$term = _yourmodule_profile_create_term_default($vocabulary);
$term->name = "Art de vivre";
$term->language= 'fr';
taxonomy_term_save($term);
Nov 07 2013
Nov 07

When using domain access or a multisite with a single search index, you may want your search result point to the current domain, and the url indexed by apache solr during the cron may not be the correct one.

You can simply use the search index field "path" and the drupal url() function as shown below:

/**
 * Implements hook_preprocess_HOOK().
 */
function yourmodule_preprocess_search_result(&$variables) {
  $variables['url'] = url($variables['result']['fields']['path'], array('absolute' => TRUE));
}
Jul 10 2013
Jul 10

At Studio.gd, we love Drupal, and we’ve started to use Drupal Commerce and all its features in early 2012. Since, we created a bunch of sites using it. We found that Drupal Commerce is really extensible, you can really do, as in Drupal, all what you want to do.

To manage shipping with Drupal Commerce, you need the Commerce Shipping module. With that module, you can use shipping methods provided by contributed modules and/or create your custom shipping methods and services.

It’s a very common case in e-commerce that your clients want to offer free shipping to their customers when they order a certain quantity of products, or when they reach a defined amount of money, or whatever the condition.

The way to do it with Commerce Shipping and Rules

http://commerceguys.com/blog/advanced-commerce-shipping-configuration-free-shipping-based-order-total-and-role

This method forces you to create a new shipping service with a free amount and with some rules so that the right service is displayed to the client. That can be a problem when you have custom shipping services, when you have a lot of them, and you want to provide these exact custom services but with a free amount.

This method can be hard to configure and maintain. Also, it can be confusing for the UX too, as the free shipping rate are updated after the customer selects the shipping services.

The way we need it

As an administrator, we need a simple admin page that let us configure the free shipping for each service.

As a shop manager, we need to interact with the customer to let him know what happens.

As a customer, we need consistency on shipping prices all along the checkout process.

We decided to follow the logic of the drupal community, by using existing contributed module when they exists, or by contributing back to the drupal community when we have to create a module for more than one of our sites.

You can download and try this module here :
http://drupal.org/project/commerce_free_shipping

How ?

We use the hook provided by Commerce Shipping :

/**
 * Allows modules to alter the shipping services defined by other modules.
 *
 * @param $shipping_services
 *   The array of shipping services defined by other modules.
 *
 * @see hook_commerce_shipping_service_info()
 */
function hook_commerce_shipping_service_info_alter(&$shipping_services) {
  // No example.
}

Our implementation :

/**
 * Implements hook_commerce_shipping_service_info_alter().
 */
function commerce_free_shipping_commerce_shipping_service_info_alter(&$shipping_services) {
  if (empty($shipping_services)) {
    return;
  }
  foreach ($shipping_services as $name => $shipping_service) {
    $base = $shipping_service['base'];
    // Check if free shipping is enabled for this services.
    if (!variable_get($base . '_free_shipping_enabled', FALSE)) {
      continue;
    }
    $shipping_service['callbacks']['original_rate'] = $shipping_service['callbacks']['rate'];
    $shipping_service['callbacks']['rate'] = 'commerce_free_shipping_rate_callback';
    $shipping_services[$name] = $shipping_service;
  }

}

We add a sort of preprocess around the shipping service rate callback, we do all our checks in it, and then return the original callback rate or the free shipping rate.

We use this method to display the good price when the user chooses the shipping services, other method provided by Commerce Shipping lets you change the rate after the user picks up the shipping method, and we found that this is confusing for the UX.

Configuration

Go to the configuration page of this module, you got a list of your store shipping services.

Inside each fieldset there are free shipping settings and messages to display to the customer.

The customer point of view

The checkout pane that display the shipping services to the customer :

Thanks for reading

I hope this module will be a time saver for you too. Any advice or comment is welcome.

You can download and try this module here :
http://drupal.org/project/commerce_free_shipping

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