Feeds

Author

Mar 25 2020
Mar 25

Drupal CMS offers a rich user interface and powerful content editing experience. There are a lot of contributed modules that enhance the system and its editing experience. One of the projects I was working with called for concurrent editing implementation. Concurrent editing simply means allowing multiple editors to edit the same content at the same time, without the possibility of conflicts arising due to concurrent actions.

In today’s editorial landscape, content creators can not only access a document countless times to revise and update content but also work with distributed teams. For this reason, concurrent editing has become among the most essential and commonly requested features for any content management solution.

The project (Layout Engine) involved configuring pages and blocks of a mobile application through Drupal.

A single page of a mobile application consists of multiple sections, these sections could contain data as per user’s locations or any other criteria. A page in Drupal was primarily a content type. We used paragraphs to configure blocks which we call widgets. So basically, a banner on the homepage of a mobile application is a widget.

These pages and widgets were configured by our client’s marketing team. Any page on the mobile app could consist of 'N' number of widgets. Due to a large number of widgets present, it was difficult for a single user to be familiar with all of them. Multiple members of the marketing team worked on the app simultaneously, making it difficult to edit the layouts/widgets. 

Thus the client needed a system where multiple users could simultaneously edit the layouts and their widgets independently.

Collaborative editing has long existed as a concept outside the content management system (CMS). It is a limitation of Drupal at the moment, and we don’t see any solution for it in Drupal core soon. A big thanks to the members of the community who contribute their code in the form of modules making solutions available to the world in a quick google search. 

We explored a couple of modules which fulfilled our requirements to some extent.

| Paragraph Frontend UI

This module provides quick editing of widgets on the view page itself. 

A user won’t have to go to the edit page and then search for the widget to update and then save the whole node. He can do it quickly on the view screen. We believed this would allow multiple users to quickly edit the widgets and will solve the issues. But, it turned out that the module throws a deadlock error when multiple users are updating different paragraphs of the same node. Therefore, we discarded the possibility of using the Paragraph Frontend UI  module.

The next module we explored was the Paragraph Edit module. It provides a separate page to edit the paragraphs through the contextual links. It does not break when multiple users attempt to edit different paragraphs simultaneously. But the issue with this approach was that it supported separate editing only. So if a user had to create a new paragraph/widget, he/she had to go to the edit node form. The tricky part here is, if an editor is on the node edit form while some other editor made changes through the quick link, the current form will contain the old data. In this case, when a user saves the node, it will revert those changes to the previous version. We did not want that either.

| Conflict

It does not have any additional configuration attached to it. I am adding two screenshots below to show how it works. This is what the second user will see when the first user has already saved the node and content is in the database. To know more visit - https://www.drupal.org/project/conflict

     Concurrent editing in Drupal 8           

Once the user clicks on the 'resolve conflicts' button, it shows two versions of each field, something like in the following screenshot. Users have to manually update the field accordingly and need to save the content again.

Concurrent editing in Drupal 8

This module is a good solution under the following instances:

  1. If the node form has a simple structure and the user can afford to update the content manually. In a complex architecture where we use paragraphs to provide flexibility to the editors, this will create a lot of confusion.

  2. It only works for two users as you can see in the screenshot above, it shows what is the difference in the server. If a third user is editing the content at the same time, it will override the changes of the second user.

| Content Lock (anti-concurrent editing)

As the name suggests this module locks the content for the first editor who started to edit the content. Any other editor will see the message and all the fields will be disabled for him/her.

Features of Content Lock (anti-concurrent editing): 

  1. The lock will break when the user saves the content. The other user will see the message and will know who is editing the content at the moment.

  2. If a user doesn’t save the content, it has a submodule called content_lock_timeout where we can set a time for the content to hold the lock. It breaks the lock on cron run as well as when the second user comes to the edit node.

  3. It has an option to manually break the lock as well, so a user with the permission to break the lock can also free the content from the editor.

The content lock was used until we had a proper solution for concurrent editing.

After countless discussions with the marketing team, we decided to provide a solution to overcome the concurrent editing problem. We decoupled all the widget creation as a separate node and referred those nodes into the Layouts. This way, everybody had control on their own widgets and they did not need to wait for the content lock to end.

If you happen to have a better solution for concurrent editing, we would love to hear about it! 

Nov 28 2016
Nov 28

This article assumes you are familiar with what RESTful is & what do we mean when we use the term REST API. Some of you might have already worked with RESTful Web Services module in D7, it exposes all entity types as web services using REST architecture. Drupal 8 out of the box is RESTful with core support. All entities (provided by core + ones created using Entity API) are RESTful resources.

To explore the RESTful nature of Drupal 8, we will need to enable the following modules:

In Core

  • HAL - Serializes entities using Hypertext Application Language.
  • HTTP Basic Authentication - Provides the HTTP Basic authentication provider.
  • RESTful Web Services - Exposes entities and other resources as RESTful web API
  • Serialization - Provides a service for (de)serializing data to/from formats such as JSON and XML.

Contributed

  • REST UI - Provides a user interface to manage REST resources.

RESTful Resources

Every entity in D8 is a resource, which has an end point. Since, its RESTful, the same end-point is used for CRUD (Create, Read, Update, Delete) operations with different HTTP verbs. Postman is an excellent tool to explore / test RESTful services.  Drupal 8 allows you to selectively choose & enable a REST API. e.g., we can choose to expose only nodes via a REST API & not other entities like users, taxonomy, comments etc.

After enabling REST_UI module we can see list of all RESTful resources at /admin/config/services/rest. In addition to ability to choose the entity one can enable, we can also choose the authentication method per resource & enable specific CRUD operations per resource.

Resource Settings

Let us take a look at what the REST APIs for User entity would be after we save the configuration in the above screenshot.

User

POST

http:
{
 "_links": {
   "type": {
     "href": "http://domain.com/rest/type/user/user"
   }
 },
 "name": {
   "value":"testuser"
 },
 "mail":{
   "value":"[email protected]"
 },
 "pass":{
   "value":"testpass"
 },
 "status": {
   "value": 1
 }
}

Header

X-CSRF-Token: Get from http://domain.com/rest/session/token
Content-Type: application/hal+json
Accept: application/hal+json
Authorization: Basic (hashed username and password)

Note: Drupal 8 doesn't allow anonymous user to send a POST on user resource. It is already fixed in 8.3.x branch but for now we can pass the credentials of the user who have permission to create users. If you are interested in taking a deeper look at the issue, you can follow https://www.drupal.org/node/2291055.

Response: You will get a user object with "200 OK" response code

 

PATCH

http:
{
 "_links": {
   "type": {
     "href": "http://domain.com/rest/type/user/user"
   }
 },
 "pass":[{"existing":"testpass"}],
 "mail":{
   "value":"[email protected]"
 }
}

Note: Now as user have permission to update his own profile so we can pass current user's credentials in authentication header.

Response: You will get "204 No Content" in response code.

 

GET

http:

Response: You will get a user object with "200 OK" response code.

 

DELETE

http:

Response: You will get "204 No Content" in response code.

RESTful Views and Authentication

Drupal 8 also allows us to export views as a REST service. It allows you to use all the available authentication mechanism in views itself.

JSON API Module

JSON API module provides a new format called "api_json" which is soon becoming the de-facto standard for Javascript Frontend frameworks, If you plan to use completely de-coupled Drupal with frontend framework like Angular / React / Ember then its worth a look. To read more about JSON API you can visit the site.

Nov 28 2016
Nov 28

This article assumes you are familiar with what RESTful is & what do we mean when we use the term REST API. Some of you might have already worked with RESTful Web Services module in D7, it exposes all entity types as web services using REST architecture. Drupal 8 out of the box is RESTful with core support. All entities (provided by core + ones created using Entity API) are RESTful resources.

To explore the RESTful nature of Drupal 8, we will need to enable the following modules:

In Core

  • HAL - Serializes entities using Hypertext Application Language.
  • HTTP Basic Authentication - Provides the HTTP Basic authentication provider.
  • RESTful Web Services - Exposes entities and other resources as RESTful web API
  • Serialization - Provides a service for (de)serializing data to/from formats such as JSON and XML.

Contributed

  • REST UI - Provides a user interface to manage REST resources.

RESTful Resources

Every entity in D8 is a resource, which has an end point. Since, its RESTful, the same end-point is used for CRUD (Create, Read, Update, Delete) operations with different HTTP verbs. Postman is an excellent tool to explore / test RESTful services.  Drupal 8 allows you to selectively choose & enable a REST API. e.g., we can choose to expose only nodes via a REST API & not other entities like users, taxonomy, comments etc.

After enabling REST_UI module we can see list of all RESTful resources at /admin/config/services/rest. In addition to ability to choose the entity one can enable, we can also choose the authentication method per resource & enable specific CRUD operations per resource.

Resource Settings

Let us take a look at what the REST APIs for User entity would be after we save the configuration in the above screenshot.

User

POST

http:
{
 "_links": {
   "type": {
     "href": "http://domain.com/rest/type/user/user"
   }
 },
 "name": {
   "value":"testuser"
 },
 "mail":{
   "value":"[email protected]"
 },
 "pass":{
   "value":"testpass"
 },
 "status": {
   "value": 1
 }
}

Header

X-CSRF-Token: Get from http://domain.com/rest/session/token
Content-Type: application/hal+json
Accept: application/hal+json
Authorization: Basic (hashed username and password)

Note: Drupal 8 doesn't allow anonymous user to send a POST on user resource. It is already fixed in 8.3.x branch but for now we can pass the credentials of the user who have permission to create users. If you are interested in taking a deeper look at the issue, you can follow https://www.drupal.org/node/2291055.

Response: You will get a user object with "200 OK" response code

 

PATCH

http:
{
 "_links": {
   "type": {
     "href": "http://domain.com/rest/type/user/user"
   }
 },
 "pass":[{"existing":"testpass"}],
 "mail":{
   "value":"[email protected]"
 }
}

Note: Now as user have permission to update his own profile so we can pass current user's credentials in authentication header.

Response: You will get "204 No Content" in response code.

 

GET

http:

Response: You will get a user object with "200 OK" response code.

 

DELETE

http:

Response: You will get "204 No Content" in response code.

RESTful Views and Authentication

Drupal 8 also allows us to export views as a REST service. It allows you to use all the available authentication mechanism in views itself.

JSON API Module

JSON API module provides a new format called "api_json" which is soon becoming the de-facto standard for Javascript Frontend frameworks, If you plan to use completely de-coupled Drupal with frontend framework like Angular / React / Ember then its worth a look. To read more about JSON API you can visit the site.

Jun 24 2015
Jun 24

Menu upcasting means converting a menu argument to anything. It can be an object or an array. In this article, we will look at how it used to be done in Drupal 7 codebase & how should we port this into Drupal 8 codebase.


Lets take an example of the following code in Drupal 7:

function my_module_menu() {
  $items['node/%my_menu/mytab'] = array(
    // ...
    // ...
  );
}

The my_module_menu() function implementing hook_menu() shows a menu item with an argument %my_menu. Suppose we want the callback function for this menu item to recieve an object after doing some processing on the value passed from the url. e.g., we want to load a specific field of node with nid 1 when we hit node/1/mytab. 

To accopmplish the above in Drupal 7, would require us to create a loader function like the one below:

 
function my_menu_load($arg) {
  // Do whatever with argument and return your values
}

The page callback for the menu would recieve whatever is returned from the loader function defined above.

However, when it comes to Drupal 8, we all know the shift is towards configuration in yaml files & Object-oriented structure of the code. Drupal 8 construct to achieve this is making use of ParamConverter interface. To port the example mentioned above, we will need to do the following:

  1. Create my_module.routing.yml
  2. Create my_module.services.yml describing metadata for your custom paramconverter implementing the paramconverter interface
  3. Implement the custom paramconverter in a PHP class namespaced in my_module.services.yml
  4. Implement the callback for your menu item defined in my_module.routing.yml

Porting your menu item into Drupal 8 codebase:

my_module.mymenu:
  path: '/node/{my_menu}/mytab'
  defaults:
    _title: 'My Title'
    _form: '\Drupal\mymodule\Form\MyModuleformControllerForm'
  options:
    parameters:
      my_menu:
        type: my_menu

This is how a typical route would look like in Drupal 8. The route described above is going to render a form on the page depending on the my_menu argument passed down to it. 

NOTE: Its very important that the name of the parameter matches the variable in the page callback arguments. e.g., if the parameter name is declared as my_menu in routing.yml file, the callback function would receive the upcasted value in $my_menu variable.

Creating my_module.services.yml:

services:
  my_menu:
    class: Drupal\mymodule\ParamConverter\MyModuleParamConverter
    tags:
      - { name: paramconverter }

Make sure the tag value says paramconverter. This helps Drupal while rebuilding its cache to compile the services accordingly.

Creating MyModuleParamConverter as defined in the namespace above:

namespace Drupal\mymodule\ParamConverter;

use Drupal\Core\ParamConverter\ParamConverterInterface;
use Drupal\node\Entity\Node;
use Symfony\Component\Routing\Route;

class MyModuleParamConverter implements ParamConverterInterface {
  public function convert($value, $definition, $name, array $defaults) {
    return Node::load($value);
  }

  public function applies($definition, $name, Route $route) {
    return (!empty($definition['type']) && $definition['type'] == 'my_menu');
  }
}

This class implements ParamConverterInterface provided by Drupal 8 core. There are 2 functions implemented above:

  1. public function convert(): All your logic related to processing of the url argument goes in here. In the example above, we are converting the value picked up from the url into a node object.
  2. public function applies(): This is a validation function describing where the param convertion will be applicable. Definition variable recieves the definition for the menu parameter as defined in the routing.yml file. Since we want this convertion to be applicable only for parameters of type my_menu, the check is applied accordingly.

And, now at the last, our Callback class for the menu item:

.
.
.
Class MyModuleformControllerForm extends FormBase{
  .
  .
  .
  public function buildForm(array $form, FormStateInterface $form_state, NodeInterface $my_menu = NULL) {
    // $my_menu will be converted object from convert function above.
  }
  .
  .
  .
}

The callback above is focussed only on the buildForm function, since that is where the upcasted argument will be recieved. 

NOTE: The variable name must match the parameter value {my_menu}. The data type of the argument will depend on the return value from the convert function in MyModuleParamConverter.php.

These small chunks of code & you are all set to convert your menu parameters into almost anything required by your custom or contrib modules.

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