diff --git a/config/install/content_lock.settings.yml b/config/install/content_lock.settings.yml
new file mode 100644
index 0000000..ed7591f
--- /dev/null
+++ b/config/install/content_lock.settings.yml
@@ -0,0 +1,4 @@
+verbose: 1
+node: {}
+block_content: {}
+taxonomy_term: {}
diff --git a/config/optional/views.view.locked_content.yml b/config/optional/views.view.locked_content.yml
new file mode 100755
index 0000000..370bbe9
--- /dev/null
+++ b/config/optional/views.view.locked_content.yml
@@ -0,0 +1,342 @@
+langcode: en
+status: true
+dependencies:
+  module:
+    - content_lock
+    - node
+    - user
+id: locked_content
+label: 'Locked content'
+module: views
+description: ''
+tag: ''
+base_table: node_field_data
+base_field: nid
+core: 8.x
+display:
+  default:
+    display_plugin: default
+    id: default
+    display_title: Master
+    position: 0
+    display_options:
+      access:
+        type: perm
+        options:
+          perm: 'administer nodes'
+      cache:
+        type: none
+        options: {  }
+      query:
+        type: views_query
+        options:
+          disable_sql_rewrite: false
+          distinct: true
+          replica: false
+          query_comment: ''
+          query_tags: {  }
+      exposed_form:
+        type: basic
+        options:
+          submit_button: Apply
+          reset_button: false
+          reset_button_label: Reset
+          exposed_sorts_label: 'Sort by'
+          expose_sort_order: true
+          sort_asc_label: Asc
+          sort_desc_label: Desc
+      pager:
+        type: mini
+        options:
+          items_per_page: 50
+          offset: 0
+          id: 0
+          total_pages: null
+          expose:
+            items_per_page: false
+            items_per_page_label: 'Items per page'
+            items_per_page_options: '5, 10, 25, 50'
+            items_per_page_options_all: false
+            items_per_page_options_all_label: '- All -'
+            offset: false
+            offset_label: Offset
+          tags:
+            previous: ‹‹
+            next: ››
+      style:
+        type: table
+      row:
+        type: fields
+      fields:
+        title:
+          id: title
+          table: node_field_data
+          field: title
+          entity_type: node
+          entity_field: title
+          alter:
+            alter_text: false
+            make_link: false
+            absolute: false
+            trim: false
+            word_boundary: false
+            ellipsis: false
+            strip_tags: false
+            html: false
+          hide_empty: false
+          empty_zero: false
+          settings:
+            link_to_entity: true
+          plugin_id: field
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: Title
+          exclude: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_alter_empty: true
+          click_sort_column: value
+          type: string
+          group_column: value
+          group_columns: {  }
+          group_rows: true
+          delta_limit: 0
+          delta_offset: 0
+          delta_reversed: false
+          delta_first_last: false
+          multi_type: separator
+          separator: ', '
+          field_api_classes: false
+        timestamp:
+          id: timestamp
+          table: content_lock
+          field: timestamp
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: 'Lock Date/Time'
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: true
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          date_format: fallback
+          custom_date_format: ''
+          timezone: ''
+          plugin_id: date
+        is_locked:
+          id: is_locked
+          table: content_lock
+          field: is_locked
+          plugin_id: content_lock_locked
+          exclude: false
+        break:
+          id: break
+          table: content_lock
+          field: break
+          relationship: none
+          group_type: group
+          admin_label: ''
+          label: 'Break link'
+          exclude: false
+          alter:
+            alter_text: false
+            text: ''
+            make_link: false
+            path: ''
+            absolute: false
+            external: false
+            replace_spaces: false
+            path_case: none
+            trim_whitespace: false
+            alt: ''
+            rel: ''
+            link_class: ''
+            prefix: ''
+            suffix: ''
+            target: ''
+            nl2br: false
+            max_length: 0
+            word_boundary: true
+            ellipsis: true
+            more_link: false
+            more_link_text: ''
+            more_link_path: ''
+            strip_tags: false
+            trim: false
+            preserve_tags: ''
+            html: false
+          element_type: ''
+          element_class: ''
+          element_label_type: ''
+          element_label_class: ''
+          element_label_colon: false
+          element_wrapper_type: ''
+          element_wrapper_class: ''
+          element_default_classes: true
+          empty: ''
+          hide_empty: false
+          empty_zero: false
+          hide_alter_empty: true
+          plugin_id: content_lock_break_link
+      filters:
+        status:
+          value: true
+          table: node_field_data
+          field: status
+          plugin_id: boolean
+          entity_type: node
+          entity_field: status
+          id: status
+          expose:
+            operator: ''
+          group: 1
+        is_locked:
+          id: is_locked
+          table: content_lock
+          field: is_locked
+          relationship: none
+          group_type: group
+          admin_label: ''
+          operator: '='
+          value: '1'
+          group: 1
+          exposed: false
+          expose:
+            operator_id: ''
+            label: ''
+            description: ''
+            use_operator: false
+            operator: ''
+            identifier: ''
+            required: false
+            remember: false
+            multiple: false
+            remember_roles:
+              authenticated: authenticated
+          is_grouped: false
+          group_info:
+            label: ''
+            description: ''
+            identifier: ''
+            optional: true
+            widget: select
+            multiple: false
+            remember: false
+            default_group: All
+            default_group_multiple: {  }
+            group_items: {  }
+          plugin_id: content_lock_filter
+      sorts:
+        created:
+          id: created
+          table: node_field_data
+          field: created
+          order: DESC
+          entity_type: node
+          entity_field: created
+          plugin_id: date
+          relationship: none
+          group_type: group
+          admin_label: ''
+          exposed: false
+          expose:
+            label: ''
+          granularity: second
+      title: 'Locked content'
+      header: {  }
+      footer: {  }
+      empty:
+        area_text_custom:
+          id: area_text_custom
+          table: views
+          field: area_text_custom
+          relationship: none
+          group_type: group
+          admin_label: ''
+          empty: true
+          tokenize: false
+          content: 'There is no content currently locked.'
+          plugin_id: text_custom
+      relationships: {  }
+      arguments: {  }
+      display_extenders: {  }
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url.query_args
+        - 'user.node_grants:view'
+        - user.permissions
+      tags: {  }
+  page_1:
+    display_plugin: page
+    id: page_1
+    display_title: Page
+    position: 1
+    display_options:
+      display_extenders: {  }
+      path: admin/content/locked-content
+      menu:
+        type: none
+        title: 'Locked content'
+        description: ''
+        expanded: false
+        parent: system.admin_content
+        weight: 0
+        context: '0'
+        menu_name: admin
+      enabled: false
+    cache_metadata:
+      max-age: 0
+      contexts:
+        - 'languages:language_content'
+        - 'languages:language_interface'
+        - url.query_args
+        - 'user.node_grants:view'
+        - user.permissions
+      tags: {  }
diff --git a/config/schema/content_lock.schema.yml b/config/schema/content_lock.schema.yml
new file mode 100644
index 0000000..9aed69b
--- /dev/null
+++ b/config/schema/content_lock.schema.yml
@@ -0,0 +1,24 @@
+content_lock.settings:
+  type: config_object
+  mapping:
+    verbose:
+      type: integer
+      label: 'Display content lock messages'
+    node:
+      type: sequence
+      label: 'Node'
+      sequence:
+        type: string
+        label: 'Bundle'
+    block_content:
+      type: sequence
+      label: 'BlockContent'
+      sequence:
+        type: string
+        label: 'BlockContent type'
+    taxonomy_term:
+      type: sequence
+      label: 'Vocabularies'
+      sequence:
+        type: string
+        label: 'Vocabulary'
diff --git a/content_lock.info.yml b/content_lock.info.yml
old mode 100644
new mode 100755
index 4ece511..527f999
--- a/content_lock.info.yml
+++ b/content_lock.info.yml
@@ -1,4 +1,6 @@
-name: Content locking
-description: 'Prevents multiple users from trying to edit a single node simultaneously to prevent edit conflicts.'
+name: Content Lock
+description: 'Prevents multiple users from trying to edit a content entity simultaneously to prevent edit conflicts.'
 type: module
+package: Custom
 core: 8.x
+configure: content_lock.content_lock_settings_form
diff --git a/content_lock.install b/content_lock.install
new file mode 100755
index 0000000..660d901
--- /dev/null
+++ b/content_lock.install
@@ -0,0 +1,60 @@
+<?php
+/**
+ * @file
+ * Create content_lock table.
+ */
+
+use Drupal\locale\Gettext;
+
+/**
+ * Implements hook_schema().
+ */
+function content_lock_schema() {
+  $schema['content_lock'] = array(
+    'description' => 'content lock module table.',
+    'fields' => array(
+      'entity_id' => array(
+        'description' => 'The primary identifier for an entity.',
+        'type' => 'int',
+        'size' => 'normal',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'entity_type' => array(
+        'description' => 'The type of an entity.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => 'node',
+      ),
+      'uid' => array(
+        'description' => 'User that holds the lock.',
+        'type' => 'int',
+        'size' => 'normal',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'timestamp' => array(
+        'description' => 'Time the lock occurred.',
+        'size' => 'normal',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'indexes' => array(
+      'user' => array('uid'),
+    ),
+    'foreign keys' => array(
+      'uid' => array(
+        'table' => 'users_field_data',
+        'columns' => array('uid' => 'uid'),
+      ),
+    ),
+  );
+
+  return $schema;
+}
diff --git a/content_lock.links.action.yml b/content_lock.links.action.yml
new file mode 100755
index 0000000..0739e35
--- /dev/null
+++ b/content_lock.links.action.yml
@@ -0,0 +1,3 @@
+content_lock.break_lock.node:
+  route_name: content_lock.break_lock.node
+  title: 'Break Lock'
diff --git a/content_lock.links.menu.yml b/content_lock.links.menu.yml
new file mode 100644
index 0000000..5294ebb
--- /dev/null
+++ b/content_lock.links.menu.yml
@@ -0,0 +1,6 @@
+content_lock.content_lock_settings_form:
+  title: 'Content Lock Settings'
+  route_name: content_lock.content_lock_settings_form
+  description: 'Content Lock settings'
+  parent: system.admin_config_system
+  weight: 99
diff --git a/content_lock.module b/content_lock.module
old mode 100644
new mode 100755
index 33dbba9..7078759
--- a/content_lock.module
+++ b/content_lock.module
@@ -6,6 +6,12 @@
  */
 
 use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\user\Entity\User;
+use Drupal\core\Url;
+use Drupal\Core\Routing\LocalRedirectResponse;
+use Drupal\Core\Entity\ContentEntityInterface;
 
 /**
  * Implements hook_help().
@@ -19,3 +25,239 @@ function content_lock_help($route_name, RouteMatchInterface $route_match) {
       return $output;
   }
 }
+
+/**
+ * Implements hook_hook_info().
+ */
+function content_lock_hook_info() {
+  $hooks = array(
+    'content_lock_path_protected',
+    'content_lock_skip_locking',
+    'content_lock_form_id_blacklist_alter',
+    'content_lock_node_type_blacklist_alter',
+    'content_lock_locked',
+    'content_lock_release',
+    'content_lock_entity_lockable',
+  );
+
+  return array_fill_keys($hooks, array('group' => 'content_lock'));
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter().
+ */
+function content_lock_form_alter(&$form, FormStateInterface $form_state, $form_id) {
+  $base_form_id = isset($form_state->getBuildInfo()['base_form_id']) ? $form_state->getBuildInfo()['base_form_id'] : NULL;
+  $base_form_id_supported = [
+    'node_form',
+    'block_content_form',
+    'taxonomy_term_form',
+  ];
+  if (!in_array($base_form_id, $base_form_id_supported)) {
+    return;
+  }
+  /** @var \Drupal\core\Entity\ContentEntityInterface $entity */
+  $entity = $form_state->getFormObject()->getEntity();
+  $entity_type = $entity->getEntityTypeId();
+  $user = Drupal::currentUser();
+
+
+  // Check if we must lock this entity.
+  $lock_service = \Drupal::service('content_lock');
+  if (!$lock_service->isLockable($entity)) {
+    return;
+  }
+
+  // We act only on edit form, not for a creation of a new entity.
+  if (!is_null($entity->id())) {
+    /** @var \Drupal\content_lock\ContentLock\ContentLock $lock_service */
+
+
+    $form['actions']['submit']['#submit'][] = 'content_lock_form_submit';
+    $form['actions']['publish']['#submit'][] = 'content_lock_form_submit';
+
+    // This hook function is called twice, first when the form loads
+    // and second when the form is submitted.
+    // Only perform set and check for lock on initial form load.
+    $userInput = $form_state->getUserInput();
+    if (!empty($userInput)) {
+      return;
+    }
+
+    // We lock the content if it is currently edited by another user.
+    if (!$lock_service->locking($entity->id(), $user->id(), $entity_type)) {
+      $form['#disabled'] = TRUE;
+
+      if (isset($form['actions']['delete'])) {
+        // Do not allow deletion if locked.
+        unset($form['actions']['delete']);
+      }
+    }
+  }
+}
+
+/**
+ * Submit handler for content_lock.
+ */
+function content_lock_form_submit($form, FormStateInterface $form_state) {
+  // Signals editing is finished; remove the lock.
+  $user = \Drupal::currentUser();
+
+  /** @var \Drupal\content_lock\ContentLock\ContentLock $lock_service */
+  $lock_service = \Drupal::service('content_lock');
+  /** @var \Drupal\core\Entity\ContentEntityInterface $entity */
+  $entity = $form_state->getFormObject()->getEntity();
+
+  // If the user submitting owns the lock, release it.
+  $lock_service->release($entity->id(), $user->id(), $entity->getEntityTypeId());
+
+  // We need to redirect to the taxonomy term page after saving it. If not, we
+  // stay on the taxonomy term edit form and we relock the term.
+  if ($entity->getEntityTypeId() === 'taxonomy_term') {
+    $form_state->setRedirect(
+      'entity.taxonomy_term.canonical',
+      array('taxonomy_term' => $entity->id())
+    );
+  }
+
+}
+
+/**
+ * Implements hook_entity_predelete().
+ *
+ * Check if the entity attempting to be deleted is locked and prevent deletion.
+ */
+function content_lock_entity_predelete(EntityInterface $entity) {
+  $entity_id = $entity->id();
+  $entity_type = $entity->getEntityTypeId();
+
+  // We support BlockContent, Term and Node entity type only.
+  $entity_type_supported = [
+    'node',
+    'block_content',
+    'taxonomy_term',
+  ];
+  if (!in_array($entity_type, $entity_type_supported)) {
+    return;
+  }
+
+  /** @var \Drupal\content_lock\ContentLock\ContentLock $lock_service */
+  $lock_service = \Drupal::service('content_lock');
+  $data = $lock_service->fetchLock($entity_id, $entity_type);
+
+  if ($data !== FALSE) {
+    $current_user = \Drupal::currentUser();
+    // If the entity is locked, and current user is not the lock's owner,
+    // set a message and stop deletion.
+    if ($current_user->id() !== $data->uid) {
+      $lock_user = User::load($data->uid);
+      $message = t('@entity cannot be deleted because it was locked by @user since @time.', array(
+        '@entity' => $entity->label(),
+        '@user' => $lock_user->getDisplayName(),
+        '@time' => \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $data->timestamp),
+      ));
+
+      drupal_set_message($message, 'warning');
+
+      $url = Url::fromRoute('entity.' . $entity_type . '.canonical', array($entity_type => $entity_id))->toString();
+
+      $redirect = new LocalRedirectResponse($url);
+      $redirect->send();
+      exit(0);
+    }
+  }
+}
+
+/**
+ * Implements hook_views_data().
+ */
+function content_lock_views_data() {
+  // Define the return array.
+  $data = [];
+
+  $data['content_lock']['table']['group'] = t('Content lock');
+
+  $data['content_lock']['table']['provider'] = 'content_lock';
+
+  $data['content_lock']['table']['join'] = array(
+    'node_field_data' => array(
+      'left_field' => 'nid',
+      'field' => 'entity_id',
+      'extra' => array(
+        0 => array(
+          'field' => 'entity_type',
+          'value' => 'node',
+        ),
+      ),
+    ),
+    'users_field_data' => [
+      'left_field' => 'uid',
+      'field' => 'uid',
+    ],
+  );
+
+  $data['content_lock']['nid'] = array(
+    'title' => t('Node locked'),
+    'help' => t('The node being locked.'),
+    'relationship' => array(
+      'base' => 'node_field_data',
+      'base field' => 'nid',
+      'id' => 'standard',
+      'label' => t('Node locked'),
+    ),
+  );
+
+  $data['content_lock']['uid'] = array(
+    'title' => t('Lock owner'),
+    'help' => t('The user locking the node.'),
+    'relationship' => array(
+      'base' => 'users_field_data',
+      'base field' => 'uid',
+      'id' => 'standard',
+      'label' => t('Lock owner'),
+    ),
+  );
+
+  $data['content_lock']['timestamp'] = array(
+    'title' => t('Lock Date/Time'),
+    'help' => t('Timestamp of the lock'),
+    'field' => array(
+      'id' => 'date',
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'id' => 'date',
+    ),
+    'filter' => array(
+      'id' => 'date',
+    ),
+  );
+
+  $data['content_lock']['is_locked'] = array(
+    'title' => t('Is Locked'),
+    'help' => t('Whether the node is currently locked'),
+    'field' => array(
+      'id' => 'content_lock_field',
+      'additional fields' => ['timestamp' => ['table' => 'content_lock', 'field' => 'timestamp']],
+    ),
+    'sort' => array(
+      'id' => 'content_lock_sort',
+    ),
+    'filter' => array(
+      'id' => 'content_lock_filter',
+      'click sortable' => TRUE,
+    ),
+  );
+
+  // Break link.
+  $data['content_lock']['break'] = array(
+    'title' => t('Break link'),
+    'help' => t('Link to break the content lock.'),
+    'field' => array(
+      'id' => 'content_lock_break_link',
+      'real field' => 'entity_id',
+    ),
+  );
+
+  return $data;
+}
diff --git a/content_lock.permissions.yml b/content_lock.permissions.yml
new file mode 100755
index 0000000..ae783da
--- /dev/null
+++ b/content_lock.permissions.yml
@@ -0,0 +1,6 @@
+break content lock:
+  title: 'Break content lock'
+  description: 'Break a lock on content so that it may be edited.'
+administer content lock:
+  title: 'Administer Content Lock settings'
+  description: 'Set on which entity type content lock is enabled'
diff --git a/content_lock.routing.yml b/content_lock.routing.yml
new file mode 100755
index 0000000..54c2046
--- /dev/null
+++ b/content_lock.routing.yml
@@ -0,0 +1,46 @@
+content_lock.break_lock.node:
+  path: '/admin/break-lock/node/{node}'
+  defaults:
+    _form: Drupal\content_lock\Form\BreakLockNodeForm
+    _title: 'Break lock'
+  requirements:
+    _permission: 'break content lock'
+  options:
+    _admin_route: TRUE
+    parameters:
+      node:
+        type: entity:node
+content_lock.break_lock.taxonomy_term:
+  path: '/admin/break-lock/term/{taxonomy_term}'
+  defaults:
+    _form: Drupal\content_lock\Form\BreakLockTermForm
+    _title: 'Break lock'
+  requirements:
+    _permission: 'break content lock'
+  options:
+    _admin_route: TRUE
+    parameters:
+      taxonomy_term:
+        type: entity:taxonomy_term
+content_lock.break_lock.block_content:
+  path: '/admin/break-lock/block/{block_content}'
+  defaults:
+    _form: Drupal\content_lock\Form\BreakLockBlockContentForm
+    _title: 'Break lock'
+  requirements:
+    _permission: 'break content lock'
+  options:
+    _admin_route: TRUE
+    parameters:
+      block_content:
+        type: entity:block_content
+
+content_lock.content_lock_settings_form:
+  path: '/admin/config/content_lock/contentlocksettings'
+  defaults:
+    _form: '\Drupal\content_lock\Form\ContentLockSettingsForm'
+    _title: 'Content Lock Settings'
+  requirements:
+    _permission: 'administer content lock'
+  options:
+    _admin_route: TRUE
diff --git a/content_lock.services.yml b/content_lock.services.yml
new file mode 100755
index 0000000..9d35ea5
--- /dev/null
+++ b/content_lock.services.yml
@@ -0,0 +1,4 @@
+services:
+  content_lock:
+    class: Drupal\content_lock\ContentLock\ContentLock
+    arguments: ['@database', '@module_handler', '@csrf_token', '@date.formatter', '@current_user', '@config.factory']
diff --git a/modules/content_lock_timeout/config/install/content_lock_timeout.settings.yml b/modules/content_lock_timeout/config/install/content_lock_timeout.settings.yml
new file mode 100755
index 0000000..1412e70
--- /dev/null
+++ b/modules/content_lock_timeout/config/install/content_lock_timeout.settings.yml
@@ -0,0 +1,2 @@
+content_lock_timeout_minutes: '30'
+content_lock_timeout_on_edit: 0
diff --git a/modules/content_lock_timeout/config/schema/content_lock_timeout.schema.yml b/modules/content_lock_timeout/config/schema/content_lock_timeout.schema.yml
new file mode 100644
index 0000000..eac79db
--- /dev/null
+++ b/modules/content_lock_timeout/config/schema/content_lock_timeout.schema.yml
@@ -0,0 +1,9 @@
+content_lock_timeout.settings:
+  type: config_object
+  mapping:
+    content_lock_timeout_minutes:
+      type: string
+      label: 'Break lock stale'
+    content_lock_timeout_on_edit:
+      type: integer
+      label: 'Break lock on edit'
diff --git a/modules/content_lock_timeout/content_lock_timeout.info.yml b/modules/content_lock_timeout/content_lock_timeout.info.yml
new file mode 100755
index 0000000..f3302f8
--- /dev/null
+++ b/modules/content_lock_timeout/content_lock_timeout.info.yml
@@ -0,0 +1,8 @@
+name: Content Lock Timeout
+description: Provides mechanisms for automatically unlocking nodes that have been locked for a certain length of time.
+core: 8.x
+type: module
+package: Custom
+configure: content_lock_timeout.settings_form
+dependencies:
+ - content_lock
diff --git a/modules/content_lock_timeout/content_lock_timeout.install b/modules/content_lock_timeout/content_lock_timeout.install
new file mode 100755
index 0000000..824893d
--- /dev/null
+++ b/modules/content_lock_timeout/content_lock_timeout.install
@@ -0,0 +1,12 @@
+<?php
+/**
+ * @file
+ * Remove config on uninstall.
+ */
+
+/**
+ * Implements hook_uninstall().
+ */
+function content_lock_timeout_uninstall() {
+  \Drupal::service('config.factory')->getEditable('content_lock_timeout.settings')->delete();
+}
diff --git a/modules/content_lock_timeout/content_lock_timeout.links.menu.yml b/modules/content_lock_timeout/content_lock_timeout.links.menu.yml
new file mode 100755
index 0000000..a47fb2b
--- /dev/null
+++ b/modules/content_lock_timeout/content_lock_timeout.links.menu.yml
@@ -0,0 +1,6 @@
+content_lock_timeout.settings_form:
+  title: 'Content Lock Timeout'
+  route_name: content_lock_timeout.settings_form
+  description: 'Content lock timeout settings'
+  parent: system.admin_config_system
+  weight: 99
diff --git a/modules/content_lock_timeout/content_lock_timeout.module b/modules/content_lock_timeout/content_lock_timeout.module
new file mode 100755
index 0000000..1c7fbbb
--- /dev/null
+++ b/modules/content_lock_timeout/content_lock_timeout.module
@@ -0,0 +1,101 @@
+<?php
+/**
+ * @file
+ * Allowed time-based automatic unlocking of nodes.
+ */
+
+use Drupal\node\NodeInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\user\Entity\User;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\taxonomy\TermInterface;
+use Drupal\block_content\BlockContentInterface;
+
+/**
+ * Implements hook_cron().
+ *
+ * Breaks batches of stale locks whenever the cron hooks are
+ * run. Inspired by original content_lock_cron() (leftover from the
+ * checkout module).
+ */
+function content_lock_timeout_cron() {
+  $config = \Drupal::config('content_lock_timeout.settings');
+  $timeout_minutes = $config->get('content_lock_timeout_minutes');
+  $last_valid_time = time() - 60 * $timeout_minutes;
+  /** @var \Drupal\content_lock\ContentLock\ContentLock $lock_service */
+  $lock_service = \Drupal::service('content_lock');
+
+  // We call release() for each lock so that the
+  // hook_content_lock_released may be invoked.
+  $query = \Drupal::database()->select('content_lock', 'c');
+  $query->fields('c')
+        ->condition('c.timestamp', $last_valid_time, '<');
+  $count = 0;
+  foreach ($query->execute() as $obj) {
+    $lock_service->release($obj->entity_id, $obj->uid, $obj->entity_type);
+    $count++;
+  }
+
+  if ($count) {
+    $period = \Drupal::service('date.formatter')->formatInterval($timeout_minutes * 60);
+    \Drupal::logger('content_lock_timeout')->notice(
+      'Released @count stale node locks which lasted at least @period.',
+      ['@count' => $count, '@period' => $period]
+    );
+  }
+}
+
+
+/**
+ * Implements hook_entity_prepare_form().
+ */
+function content_lock_timeout_entity_prepare_form(EntityInterface $entity, $operation, FormStateInterface $form_state) {
+  // We support entity type Node, Term and Block Content.
+  if ($entity instanceof NodeInterface || $entity instanceof TermInterface || $entity instanceof BlockContentInterface) {
+    $user = \Drupal::currentUser();
+    $config = \Drupal::config('content_lock_timeout.settings');
+    if (!$config->get('content_lock_timeout_on_edit')) {
+      return;
+    }
+    $timeout_minutes = $config->get('content_lock_timeout_minutes');
+    $last_valid_time = time() - 60 * $timeout_minutes;
+
+    /** @var \Drupal\content_lock\ContentLock\ContentLock $lock_service */
+    $lock_service = \Drupal::service('content_lock');
+
+
+    // This is a new, unsaved entity (which thus can't be locked).
+    // This is a stale lock.
+    // There already is a lock on this entity.
+    // A different user owns the lock.
+    // There is already a lock on this entity.
+    if (!empty($entity->id())
+      && is_object($lock = $lock_service->fetchLock($entity->id(), $entity->getEntityTypeId()))
+      && $lock->uid != $user->id()
+      && $lock->timestamp < $last_valid_time
+      // Now check a subset of the conditions that content_lock_form_alter()
+      // checks before it sets a lock. Many of the checks don't apply because
+      // we know the uid of the lock is different from the current user's uid
+      // and that the node already exists. That is, we don't need as many checks
+      // because there's already a lock on this node.
+      // The user must have this permission to be able to break the lock.
+      // A valid user is needed for locking.
+      && $user->hasPermission('break content lock')
+      && ($user->id() > 0)
+    ) {
+      $lock_service->release($entity->id(), $lock->uid, $entity->getEntityTypeId());
+
+      if ($lock_service->verbose()) {
+        $username = User::load($lock->uid)->getDisplayName();
+        $date = \Drupal::service('date.formatter')->formatInterval(REQUEST_TIME - $lock->timestamp);
+        $stale_time = \Drupal::service('date.formatter')->formatInterval($last_valid_time - $lock->timestamp);
+        drupal_set_message(t('Breaking existing lock by @name so that you may edit this node. (This lock was set @date ago and was stale since @stale_time.)',
+          array(
+            '@name' => $username,
+            '@date' => $date,
+            '@stale_time' => $stale_time,
+          )));
+      }
+    }
+  }
+}
diff --git a/modules/content_lock_timeout/content_lock_timeout.routing.yml b/modules/content_lock_timeout/content_lock_timeout.routing.yml
new file mode 100755
index 0000000..9901a06
--- /dev/null
+++ b/modules/content_lock_timeout/content_lock_timeout.routing.yml
@@ -0,0 +1,9 @@
+content_lock_timeout.settings_form:
+  path: '/admin/config/content_lock_timeout/settings'
+  defaults:
+    _form: '\Drupal\content_lock_timeout\Form\SettingsForm'
+    _title: 'SettingsForm'
+  requirements:
+    _permission: 'access administration pages'
+  options:
+    _admin_route: TRUE
diff --git a/modules/content_lock_timeout/src/Form/SettingsForm.php b/modules/content_lock_timeout/src/Form/SettingsForm.php
new file mode 100755
index 0000000..03a43c9
--- /dev/null
+++ b/modules/content_lock_timeout/src/Form/SettingsForm.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Drupal\content_lock_timeout\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+
+/**
+ * Class SettingsForm.
+ *
+ * @package Drupal\content_lock_timeout\Form
+ */
+class SettingsForm extends ConfigFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return [
+      'content_lock_timeout.settings',
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'content_lock_timeout_settings_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $config = $this->config('content_lock_timeout.settings');
+
+    $form['content_lock_timeout'] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('Lock Timeouts'),
+      '#description' => $this->t('Configure automatic stale lock breaking.'),
+    ];
+
+    $form['content_lock_timeout']['content_lock_timeout_minutes'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Lock timeout'),
+      '#description' => $this->t('The maximum time in minutes that each lock may be kept. To disable breaking locks after a timeout, please %disable the Content Lock Timeout module.', ['%disable' => Link::fromTextAndUrl($this->t('disable'), Url::fromRoute('system.modules_list'))->toString()]),
+      '#maxlength' => 64,
+      '#size' => 64,
+      '#default_value' => $config->get('content_lock_timeout_minutes'),
+    ];
+
+    $form['content_lock_timeout']['content_lock_timeout_on_edit'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Break stale locks on edit'),
+      '#description' => $this->t('By default, stale locks will be broken when cron is run. This option enables checking for stale locks when a user clicks a node\'s Edit link, enabling lower lock timeout values without having to run cron every few minutes.'),
+      '#default_value' => $config->get('content_lock_timeout_on_edit'),
+    ];
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    parent::validateForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    parent::submitForm($form, $form_state);
+
+    $this->config('content_lock_timeout.settings')
+      ->set('content_lock_timeout', $form_state->getValue('content_lock_timeout'))
+      ->set('content_lock_timeout_minutes', $form_state->getValue('content_lock_timeout_minutes'))
+      ->set('content_lock_timeout_on_edit', $form_state->getValue('content_lock_timeout_on_edit'))
+      ->save();
+  }
+
+}
diff --git a/src/ContentLock/ContentLock.php b/src/ContentLock/ContentLock.php
new file mode 100755
index 0000000..aa87219
--- /dev/null
+++ b/src/ContentLock/ContentLock.php
@@ -0,0 +1,466 @@
+<?php
+
+namespace Drupal\content_lock\ContentLock;
+
+use Drupal\Core\Database\Driver\mysql\Connection;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Extension\ModuleHandler;
+use Drupal\Core\DependencyInjection\ServiceProviderBase;
+use Drupal\Core\Access\CsrfTokenGenerator;
+use Drupal\user\Entity\User;
+use Drupal\Core\Link;
+use Drupal\Core\Datetime\DateFormatter;
+use Drupal\Core\Session\AccountProxy;
+use Drupal\Core\Config\ConfigFactory;
+
+/**
+ * Class ContentLock.
+ *
+ * The content lock service.
+ */
+class ContentLock extends ServiceProviderBase {
+
+  /**
+   * The database service.
+   *
+   * @var \Drupal\Core\Database\Driver\mysql\Connection
+   *   The database service.
+   */
+  protected $database;
+
+  /**
+   * The module_handler service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandler
+   *   The module_handler service.
+   */
+  protected $moduleHandler;
+
+  /**
+   * The csrf_token service.
+   *
+   * @var \Drupal\Core\Access\CsrfTokenGenerator
+   *   The csrf_token service.
+   */
+  protected $csrfToken;
+
+  /**
+   * The date.formatter service.
+   *
+   * @var \Drupal\Core\Datetime\DateFormatter
+   *   The date.formatter service.
+   */
+  protected $dateFormatter;
+
+  /**
+   * The current_user service.
+   *
+   * @var \Drupal\Core\Session\AccountProxy
+   *   The current_user service.
+   */
+  protected $currentUser;
+
+  /**
+   * The config.factory service.
+   *
+   * @var \Drupal\Core\Config\ConfigFactory $configFactory
+   *   The config.factory service.
+   */
+  protected $configFactory;
+
+  /**
+   * Constructor.
+   *
+   * @param \Drupal\Core\Database\Driver\mysql\Connection $database
+   *   The database connection.
+   * @param \Drupal\Core\Extension\ModuleHandler $moduleHandler
+   *   The module Handler service.
+   * @param \Drupal\Core\Access\CsrfTokenGenerator $csrfToken
+   *   The csrfTokenGenerator service.
+   * @param \Drupal\Core\Datetime\DateFormatter $dateFormatter
+   *   The date.formatter service.
+   * @param \Drupal\Core\Session\AccountProxy $currentUser
+   *   The current_user service.
+   * @param \Drupal\Core\Config\ConfigFactory $configFactory
+   *   The config.factory service.
+   */
+  public function __construct(Connection $database, ModuleHandler $moduleHandler, CsrfTokenGenerator $csrfToken, DateFormatter $dateFormatter, AccountProxy $currentUser, ConfigFactory $configFactory) {
+    $this->database = $database;
+    $this->moduleHandler = $moduleHandler;
+    $this->csrfToken = $csrfToken;
+    $this->dateFormatter = $dateFormatter;
+    $this->currentUser = $currentUser;
+    $this->configFactory = $configFactory;
+  }
+
+  /**
+   * Check if an internal Drupal path should be protected with a token.
+   *
+   * Adds requirements that certain path be accessed only through tokenized URIs
+   * which are enforced by this module. This prevents people from being CSRFed
+   * into locking nodes that they can access without meaning to lock them.
+   *
+   * @param string $path
+   *   The path to check.
+   *
+   * @return bool
+   *   Returns TRUE if the path is protected or FALSE if not protected.
+   */
+  public function isPathProtected($path) {
+    $cache = &drupal_static(__FUNCTION__, array());
+
+    // Check cache.
+    if (isset($cache[$path])) {
+      return $cache[$path];
+    }
+
+    // Invoke hook and collect grants/denies for protected paths.
+    $protected = array();
+    foreach ($this->moduleHandler->getImplementations('content_lock_path_protected') as $module) {
+      $protected = array_merge(
+        $protected,
+        array(
+          $module => $this->moduleHandler->invoke($module, 'content_lock_path_protected', $path),
+        )
+      );
+    }
+
+    // Allow other modules to alter the returned grants/denies.
+    $this->moduleHandler->alter('content_lock_path_protected', $protected, $path);
+
+    // If TRUE is returned, path is protected.
+    $cache[$path] = in_array(TRUE, $protected);
+
+    return $cache[$path];
+  }
+
+  /**
+   * Calculate the token required to unlock a node.
+   *
+   * Tokens are required because they prevent CSRF.
+   *
+   * @see https://security.drupal.org/node/2429
+   */
+  public function getReleaseToken($nid) {
+    // Get a drupal CSRF token. (The actual service is called 'csrf_token').
+    return $this->csrfToken->get("content_lock/release/$nid");
+  }
+
+  /**
+   * Fetch the lock for an entity.
+   *
+   * @param int $entity_id
+   *   The entity id.
+   * @param string $entity_type
+   *   The entity type.
+   *
+   * @return object
+   *   The lock for the node. FALSE, if the document is not locked.
+   */
+  public function fetchLock($entity_id, $entity_type = 'node') {
+    $query = $this->database->select('content_lock', 'c');
+    $query->leftJoin('users_field_data', 'u', '%alias.uid = c.uid');
+    $query->fields('c')
+      ->fields('u', ['name'])
+      ->condition('c.entity_type', $entity_type)
+      ->condition('c.entity_id', $entity_id);
+
+    return $query->execute()->fetchObject();
+  }
+
+  /**
+   * Tell who has locked node.
+   *
+   * @param object $lock
+   *   The lock for a node.
+   *
+   * @return string
+   *   String with the message.
+   */
+  public function displayLockOwner($lock) {
+    $username = User::load($lock->uid);
+    $date = $this->dateFormatter->formatInterval(REQUEST_TIME - $lock->timestamp);
+
+    return t('This content is being edited by the user @name and is therefore locked to prevent other users changes. This lock is in place since @date.', array(
+      '@name' => $username->getDisplayName(),
+      '@date' => $date,
+    ));
+  }
+
+  /**
+   * Check lock status.
+   *
+   * @param int $uid
+   *    The user id.
+   * @param int $entity_id
+   *    The entity id.
+   * @param string $entity_type
+   *   The entity type.
+   *
+   * @return bool
+   *    Return TRUE OR FALSE.
+   */
+  protected function stillLocked($uid, $entity_id, $entity_type = 'node') {
+    /** @var \Drupal\Core\Database\Query\SelectInterface $query */
+    $query = $this->database->select('content_lock', 'c')
+      ->countQuery()
+      ->condition('entity_id', $entity_id)
+      ->condition('entity_type', $entity_type)
+      ->condition('uid', $uid);
+    // @todo: test this `execute()` is not allowed.
+    $result = $query->execute();
+
+    return (bool) $result->fetchField();
+  }
+
+  /**
+   * Release a locked entity.
+   *
+   * @param int $entity_id
+   *   The entity id.
+   * @param int $uid
+   *    If set, verify that a lock belongs to this user prior to release.
+   * @param string $entity_type
+   *   The entity type.
+   */
+  public function release($entity_id, $uid = NULL, $entity_type = 'node') {
+    // Delete locking item from database.
+    $this->lockingDelete($entity_id, $uid, $entity_type);
+
+    $this->moduleHandler->invokeAll(
+      'content_lock_release',
+      [$entity_id, $entity_type]
+    );
+  }
+
+  /**
+   * Release all locks set by a user.
+   *
+   * @param int $uid
+   *   The user uid.
+   */
+  protected function releaseAllUserLocks($uid) {
+    $this->database->delete('content_lock')
+      ->condition('uid', $uid)
+      ->execute();
+  }
+
+  /**
+   * Save lock warning.
+   *
+   * @param string $message
+   *    Message string.
+   * @param int $entity_id
+   *    The entity id.
+   */
+  protected function saveLockWarning($message, $entity_id) {
+    if (empty($_SESSION['content_lock'])) {
+      $_SESSION['content_lock'] = '';
+    }
+    $data = unserialize($_SESSION['content_lock']);
+    if (!is_array($data)) {
+      $data = array();
+    }
+
+    if (array_key_exists($entity_id, $data)) {
+      return;
+    }
+
+    $data[$entity_id] = $message;
+    $_SESSION['content_lock'] = serialize($data);
+  }
+
+  /**
+   * Show warnings.
+   */
+  protected function showWarnings() {
+    $user = $this->currentUser;
+    if (empty($_SESSION['content_lock'])) {
+      return;
+    }
+    $data = unserialize($_SESSION['content_lock']);
+    if (!is_array($data) || count($data) == 0) {
+      return;
+    }
+    foreach ($data as $entity_id => $messsage) {
+      if ($this->stillLocked($user->id(), $entity_id)) {
+        drupal_set_message($messsage, 'warning', FALSE);
+      }
+    }
+    $_SESSION['content_lock'] = '';
+  }
+
+  /**
+   * Save locking into database.
+   *
+   * @param int $entity_id
+   *   The entity id.
+   * @param int $uid
+   *   The user uid.
+   * @param string $entity_type
+   *   The entity type.
+   *
+   * @return bool $result
+   *   The result of the merge query.
+   */
+  protected function lockingSave($entity_id, $uid, $entity_type = 'node') {
+    $result = $this->database->merge('content_lock')
+      ->key([
+        'entity_id' => $entity_id,
+        'entity_type' => $entity_type,
+      ])
+      ->fields([
+        'entity_id' => $entity_id,
+        'entity_type' => $entity_type,
+        'uid' => $uid,
+        'timestamp' => REQUEST_TIME,
+      ])
+      ->execute();
+
+    return $result;
+  }
+
+  /**
+   * Delete locking item from database.
+   *
+   * @param int $entity_id
+   *   The entity id.
+   * @param int $uid
+   *   The user uid.
+   * @param string $entity_type
+   *   The entity type.
+   *
+   * @return bool
+   *   The result of the delete query.
+   */
+  protected function lockingDelete($entity_id, $uid, $entity_type = 'node') {
+    $query = $this->database->delete('content_lock')
+      ->condition('entity_type', $entity_type)
+      ->condition('entity_id', $entity_id);
+    if (!empty($uid)) {
+      $query->condition('uid', $uid);
+    }
+
+    $result = $query->execute();
+
+    return $result;
+  }
+
+  /**
+   * Check if locking is verbose.
+   *
+   * @return bool
+   *   Return true if locking is verbose.
+   */
+  public function verbose() {
+    return $this->configFactory->get('content_lock.settings')->get('verbose');
+  }
+
+  /**
+   * Try to lock a document for editing.
+   *
+   * @param int $entity_id
+   *   The entity id.
+   * @param int $uid
+   *   The user id to lock the node for.
+   * @param string $entity_type
+   *   The entity type.
+   * @param bool $quiet
+   *   Suppress any normal user messages.
+   *
+   * @return bool
+   *   FALSE, if a document has already been locked by someone else.
+   */
+  public function locking($entity_id, $uid, $entity_type = 'node', $quiet = FALSE) {
+    // Check locking status.
+    $lock = $this->fetchLock($entity_id, $entity_type);
+
+    // No lock yet.
+    if ($lock === FALSE || !is_object($lock)) {
+      // Save locking into database.
+      $result = $this->lockingSave($entity_id, $uid, $entity_type);
+
+      if ($this->verbose() && !$quiet) {
+        drupal_set_message(t('This content is now locked against simultaneous editing.'), 'status', FALSE);
+      }
+      // Post locking hook.
+      $this->moduleHandler->invokeAll('content_lock_locked', [
+        $entity_id,
+        $uid,
+        $entity_type,
+      ]);
+
+      // Send success flag.
+      return TRUE;
+    }
+    else {
+      // Currently locking by other user.
+      if ($lock->uid != $uid) {
+        // Send message.
+        $message = $this->displayLockOwner($lock);
+        drupal_set_message($message, 'warning');
+
+        // Higher permission user can unblock.
+        if ($this->currentUser->hasPermission('break content lock')) {
+
+          $link = Link::createFromRoute(
+            t('Break lock'),
+            'content_lock.break_lock.' . $entity_type,
+            [$entity_type => $entity_id]
+          )->toString();
+
+          // Let user break lock.
+          drupal_set_message(t('Click here to @link', ['@link' => $link]), 'warning');
+        }
+
+        // Return FALSE flag.
+        return FALSE;
+      }
+      else {
+        // Save locking into database.
+        $this->lockingSave($entity_id, $uid, $entity_type);
+
+        // Locked by current user.
+        if ($this->verbose() && !$quiet) {
+          drupal_set_message(t('This content is now locked by you against simultaneous editing. This content will remain locked if you navigate away from this page without saving it.'), 'status', FALSE);
+        }
+
+        // Send success flag.
+        return TRUE;
+      }
+    }
+  }
+
+  /**
+   * Check whether a node is configured to be protected by content_lock.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface $entity
+   *   The entity to check.
+   *
+   * @return bool
+   *   TRUE is entity is lockable
+   */
+  public function isLockable(EntityInterface $entity) {
+    $entity_id = $entity->id();
+    $entity_type = $entity->getEntityTypeId();
+    $bundle = $entity->bundle();
+
+    $config = $this->configFactory->get('content_lock.settings')->get($entity_type);
+
+    $this->moduleHandler->invokeAll('content_lock_entity_lockable', [
+      $entity,
+      $entity_id,
+      $entity_type,
+      $bundle,
+      $config,
+    ]);
+
+    if (in_array($bundle, $config)) {
+      return TRUE;
+    }
+
+    // Always return FALSE.
+    return FALSE;
+  }
+
+}
diff --git a/src/Form/BreakLockBlockContentForm.php b/src/Form/BreakLockBlockContentForm.php
new file mode 100755
index 0000000..74f744a
--- /dev/null
+++ b/src/Form/BreakLockBlockContentForm.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\content_lock\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\block_content\Entity\BlockContent;
+
+/**
+ * Class BreakLockBlockContentForm.
+ *
+ * @package Drupal\content_lock\Form
+ */
+class BreakLockBlockContentForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'break_lock_block_content';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, BlockContent $block_content = NULL) {
+    $form['#title'] = t('Break Lock for Block @label', ['@label' => $block_content->label()]);
+    $form['entity_id'] = [
+      '#type' => 'hidden',
+      '#value' => $block_content->id(),
+    ];
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => t('Confirm break lock'),
+    ];
+    $input = $form_state->getUserInput();
+    $referrer = (isset($input['referrer']) && !empty($input['referrer'])) ? $input['referrer'] : $_SERVER['HTTP_REFERER'];
+    $form['referrer'] = [
+      '#type' => 'hidden',
+      '#value' => $referrer,
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $entity_id = $form_state->getValue('entity_id');
+    /** @var \Drupal\content_lock\ContentLock\ContentLock $lock_service */
+    $lock_service = \Drupal::service('content_lock');
+    $lock_service->release($entity_id, NULL, 'block_content');
+    drupal_set_message($this->t('Lock broken. You can now edit this content.'));
+    if ($referrer = $form_state->getValue('referrer')) {
+      $url = Url::fromUri($referrer);
+      $form_state->setRedirectUrl($url);
+    }
+    else {
+      $this->redirect('entity.block_content.edit_form', array('block_content' => $entity_id))->send();
+    }
+  }
+
+}
diff --git a/src/Form/BreakLockNodeForm.php b/src/Form/BreakLockNodeForm.php
new file mode 100755
index 0000000..d369ccd
--- /dev/null
+++ b/src/Form/BreakLockNodeForm.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Drupal\content_lock\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\node\Entity\Node;
+
+/**
+ * Class BreakLockNodeForm.
+ *
+ * @package Drupal\content_lock\Form
+ */
+class BreakLockNodeForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'break_lock_node';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, Node $node = NULL) {
+    $form['#title'] = t('Break Lock for content @label', ['@label' => $node->label()]);
+    $form['entity_id'] = [
+      '#type' => 'hidden',
+      '#value' => $node->id(),
+    ];
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => t('Confirm break lock'),
+    ];
+    $input = $form_state->getUserInput();
+    $referrer = (isset($input['referrer']) && !empty($input['referrer'])) ? $input['referrer'] : $_SERVER['HTTP_REFERER'];
+    $form['referrer'] = [
+      '#type' => 'hidden',
+      '#value' => $referrer,
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $entity_id = $form_state->getValue('entity_id');
+    /** @var \Drupal\content_lock\ContentLock\ContentLock $lock_service */
+    $lock_service = \Drupal::service('content_lock');
+    $lock_service->release($entity_id, NULL, 'node');
+    drupal_set_message($this->t('Lock broken. You can now edit this content.'));
+    if ($referrer = $form_state->getValue('referrer')) {
+      $url = Url::fromUri($referrer);
+      $form_state->setRedirectUrl($url);
+      /* return new RedirectResponse($referrer, 303);*/
+    }
+    else {
+      $this->redirect('entity.node.edit_form', array('node' => $entity_id))->send();
+    }
+  }
+
+}
diff --git a/src/Form/BreakLockTermForm.php b/src/Form/BreakLockTermForm.php
new file mode 100755
index 0000000..e6482a9
--- /dev/null
+++ b/src/Form/BreakLockTermForm.php
@@ -0,0 +1,64 @@
+<?php
+
+namespace Drupal\content_lock\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Url;
+use Drupal\taxonomy\Entity\Term;
+
+/**
+ * Class BreakLockTermForm.
+ *
+ * @package Drupal\content_lock\Form
+ */
+class BreakLockTermForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'break_lock_term';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state, Term $taxonomy_term = NULL) {
+    $form['#title'] = t('Break Lock for Taxonomy Term @label', ['@label' => $taxonomy_term->label()]);
+    $form['entity_id'] = [
+      '#type' => 'hidden',
+      '#value' => $taxonomy_term->id(),
+    ];
+    $form['submit'] = [
+      '#type' => 'submit',
+      '#value' => t('Confirm break lock'),
+    ];
+    $input = $form_state->getUserInput();
+    $referrer = (isset($input['referrer']) && !empty($input['referrer'])) ? $input['referrer'] : $_SERVER['HTTP_REFERER'];
+    $form['referrer'] = [
+      '#type' => 'hidden',
+      '#value' => $referrer,
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    $entity_id = $form_state->getValue('entity_id');
+    /** @var \Drupal\content_lock\ContentLock\ContentLock $lock_service */
+    $lock_service = \Drupal::service('content_lock');
+    $lock_service->release($entity_id, NULL, 'taxonomy_term');
+    drupal_set_message($this->t('Lock broken. You can now edit this content.'));
+    if ($referrer = $form_state->getValue('referrer')) {
+      $url = Url::fromUri($referrer);
+      $form_state->setRedirectUrl($url);
+    }
+    else {
+      $this->redirect('entity.taxonomy_term.edit_form', array('taxonomy_term' => $entity_id))->send();
+    }
+  }
+
+}
diff --git a/src/Form/ContentLockSettingsForm.php b/src/Form/ContentLockSettingsForm.php
new file mode 100644
index 0000000..28235b6
--- /dev/null
+++ b/src/Form/ContentLockSettingsForm.php
@@ -0,0 +1,193 @@
+<?php
+
+namespace Drupal\content_lock\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\node\Entity\NodeType;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Entity\EntityTypeManager;
+
+/**
+ * Class ContentLockSettingsForm.
+ *
+ * @package Drupal\content_lock\Form
+ */
+class ContentLockSettingsForm extends ConfigFormBase {
+
+  /**
+   * Drupal\Core\Entity\EntityTypeManager definition.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManager
+   */
+  protected $entityTypeManager;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(ConfigFactoryInterface $config_factory, EntityTypeManager $entityTypeManager) {
+    parent::__construct($config_factory);
+    $this->entityTypeManager = $entityTypeManager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('config.factory'),
+      $container->get('entity_type.manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getEditableConfigNames() {
+    return [
+      'content_lock.settings',
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'content_lock_settings_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $config = $this->config('content_lock.settings');
+
+    $form['general'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Verbose'),
+      '#open' => TRUE,
+      '#tree' => TRUE,
+      '#process' => [[get_class($this), 'formProcessMergeParent']],
+      '#weight' => 0,
+    ];
+    $form['general']['verbose'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Enable this option to display messages to editor when he locks a content by editing it.'),
+      '#description' => $this->t('Users trying to edit a content locked still see the content lock message.'),
+      '#default_value' => $config->get('verbose'),
+      '#return_value' => 1,
+      '#empty' => 0,
+    ];
+
+    $form['entities'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Entity type protected'),
+      '#open' => TRUE,
+      '#tree' => TRUE,
+      '#process' => [[get_class($this), 'formProcessMergeParent']],
+      '#weight' => 1,
+    ];
+
+    $node_types = $this->entityTypeManager->getStorage('node_type')->loadMultiple();
+    $options = [];
+    foreach ($node_types as $node_type) {
+      $options[$node_type->id()] = $node_type->label();
+    }
+    $form['entities']['node'] = [
+      '#type' => 'checkboxes',
+      '#title' => $this->t('Node'),
+      '#description' => $this->t('Select the bundles on which enable content lock'),
+      '#options' => $options,
+      '#default_value' => $config->get('node'),
+    ];
+
+    $block_content_types = $this->entityTypeManager->getStorage('block_content_type')->loadMultiple();
+    $options = [];
+    foreach ($block_content_types as $block_content_type) {
+      $options[$block_content_type->id()] = $block_content_type->label();
+    }
+    $form['entities']['block_content'] = [
+      '#type' => 'checkboxes',
+      '#title' => $this->t('Block content'),
+      '#description' => $this->t('Select the Block content types on which enable content lock'),
+      '#options' => $options,
+      '#default_value' => $config->get('block_content'),
+    ];
+
+    $vocabularies = $this->entityTypeManager->getStorage('taxonomy_vocabulary')->loadMultiple();
+    $options = [];
+    foreach ($vocabularies as $vocabulary) {
+      $options[$vocabulary->id()] = $vocabulary->label();
+    }
+    $form['entities']['taxonomy_term'] = [
+      '#type' => 'checkboxes',
+      '#title' => $this->t('Vocabularies'),
+      '#description' => $this->t('Select the vocabularies on which enable content lock'),
+      '#options' => $options,
+      '#default_value' => $config->get('taxonomy_term'),
+    ];
+
+
+
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, FormStateInterface $form_state) {
+    parent::validateForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    parent::submitForm($form, $form_state);
+
+    $this->config('content_lock.settings')
+      ->set('verbose', $form_state->getValue('verbose'))
+      ->set('node', $this->removeEmptyValue($form_state->getValue('node')))
+      ->set('block_content', $this->removeEmptyValue($form_state->getValue('block_content')))
+      ->set('taxonomy_term', $this->removeEmptyValue($form_state->getValue('taxonomy_term')))
+      ->save();
+  }
+
+  /**
+   * Helper function to filter empty value in an array.
+   *
+   * @param array $array
+   *   The array to check for empty values.
+   *
+   * @return array $array
+   *   The array without empty values.
+   */
+  protected function removeEmptyValue($array) {
+    return array_filter($array, function($value) {
+      return !empty($value);
+    });
+  }
+
+  /**
+   * Merge elements to the level up.
+   *
+   * Render API callback: Moves entity_reference specific Form API elements
+   * (i.e. 'handler_settings') up a level for easier processing values.
+   *
+   * @param array $element
+   *   The array to filter.
+   *
+   * @return array
+   *   The array filtered.
+   *
+   * @see _entity_reference_field_settings_process()
+   */
+  public static function formProcessMergeParent($element) {
+    $parents = $element['#parents'];
+    array_pop($parents);
+    $element['#parents'] = $parents;
+    return $element;
+  }
+
+}
diff --git a/src/Plugin/Views/Field/ContentLockBreak.php b/src/Plugin/Views/Field/ContentLockBreak.php
new file mode 100755
index 0000000..b0eaf6c
--- /dev/null
+++ b/src/Plugin/Views/Field/ContentLockBreak.php
@@ -0,0 +1,49 @@
+<?php
+
+namespace Drupal\content_lock\Plugin\Views\Field;
+
+use Drupal\Core\Link;
+use Drupal\Core\Url;
+use Drupal\views\Plugin\views\field\FieldPluginBase;
+use Drupal\views\ResultRow;
+
+/**
+ * Field handler to present a link to an entity.
+ *
+ * @group views_field_handlers
+ *
+ * @ViewsField("content_lock_break_link")
+ */
+class ContentLockBreak extends FieldPluginBase {
+
+  /**
+   * Prepares link to the file.
+   *
+   * @param string $data
+   *   The XSS safe string for the link text.
+   * @param \Drupal\views\ResultRow $values
+   *   The values retrieved from a single row of a view's query result.
+   *
+   * @return string
+   *   Returns a string for the link text.
+   */
+  protected function renderLink($data, ResultRow $values) {
+    $entity = $this->getEntity($values);
+    $url = Url::fromRoute(
+      'content_lock.break_lock.node',
+      ['node' => $entity->id()]
+    );
+
+    $break_link = Link::fromTextAndUrl('Break lock', $url);
+    return $break_link->toString();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function render(ResultRow $values) {
+    $value = $this->getValue($values);
+    return $this->renderLink($this->sanitizeValue($value), $values);
+  }
+
+}
diff --git a/src/Plugin/Views/Field/ContentLockField.php b/src/Plugin/Views/Field/ContentLockField.php
new file mode 100755
index 0000000..3495c71
--- /dev/null
+++ b/src/Plugin/Views/Field/ContentLockField.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Drupal\content_lock\Plugin\Views\Field;
+
+use Drupal\views\Plugin\views\display\DisplayPluginBase;
+use Drupal\views\Plugin\views\field\Boolean;
+use Drupal\views\ResultRow;
+use Drupal\views\ViewExecutable;
+
+/**
+ * A handler to provide proper displays for dates.
+ *
+ * @group views_field_handlers
+ *
+ * @ViewsField("content_lock_field")
+ */
+class ContentLockField extends Boolean {
+  /**
+   * Query.
+   */
+  public function query() {
+    $this->ensureMyTable();
+    $this->addAdditionalFields();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function render(ResultRow $values) {
+    $value = $values->content_lock_timestamp ? TRUE : FALSE;
+    if (!empty($this->options['not'])) {
+      $value = !$value;
+    }
+
+    switch ($this->options['type']) {
+      case 'true-false':
+        return $value ? t('True') : t('False');
+
+      case 'on-off':
+        return $value ? t('On') : t('Off');
+
+      case 'yes-no':
+      default:
+        return $value ? t('Yes') : t('No');
+    }
+  }
+}
diff --git a/src/Plugin/Views/Filter/ContentLockFilter.php b/src/Plugin/Views/Filter/ContentLockFilter.php
new file mode 100755
index 0000000..7188379
--- /dev/null
+++ b/src/Plugin/Views/Filter/ContentLockFilter.php
@@ -0,0 +1,26 @@
+<?php
+
+namespace Drupal\content_lock\Plugin\Views\Filter;
+
+use Drupal\views\Plugin\views\filter\BooleanOperator;
+
+/**
+ *
+ * @group views_filter_handlers
+ *
+ * @ViewsFilter("content_lock_filter")
+ */
+class ContentLockFilter extends BooleanOperator {
+  /**
+   * Query.
+   */
+  public function query() {
+    $this->ensureMyTable();
+    if (empty($this->value)) {
+      $this->query->addWhere($this->options['group'], $this->tableAlias . ".timestamp", "NULL", "=");
+    }
+    else {
+      $this->query->addWhere($this->options['group'], $this->tableAlias . ".timestamp", "NULL", "<>");
+    }
+  }
+}
diff --git a/src/Plugin/Views/Sort/ContentLockSort.php b/src/Plugin/Views/Sort/ContentLockSort.php
new file mode 100755
index 0000000..9e69186
--- /dev/null
+++ b/src/Plugin/Views/Sort/ContentLockSort.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Drupal\content_lock\Plugin\Views\Sort;
+
+use Drupal\views\Plugin\views\sort\Standard;
+
+/**
+ * Content lock sort.
+ *
+ * @ViewsSort("content_lock_sort")
+ */
+class ContentLockSort extends Standard {
+  /**
+   * Query.
+   */
+  public function query() {
+    $this->ensureMyTable();
+    $this->query->addOrderBy($this->table_alias, 'timestamp', $this->options['order']);
+  }
+}
diff --git a/src/Tests/ContentLockTestBase.php b/src/Tests/ContentLockTestBase.php
new file mode 100644
index 0000000..9abc657
--- /dev/null
+++ b/src/Tests/ContentLockTestBase.php
@@ -0,0 +1,396 @@
+<?php
+
+namespace Drupal\content_lock\Tests;
+
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
+use Drupal\Core\Entity\Entity\EntityFormDisplay;
+use Drupal\Core\Entity\Display\EntityFormDisplayInterface;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
+use Drupal\simpletest\WebTestBase;
+use Drupal\taxonomy\Tests\TaxonomyTestTrait;
+use Drupal\taxonomy\VocabularyInterface;
+use Drupal\user\Entity\User;
+use Drupal\block_content\Entity\BlockContent;
+use Drupal\block_content\Entity\BlockContentType;
+
+/**
+ * General setup and helper function for testing content_lock module.
+ *
+ * @group content_lock
+ */
+class ContentLockTestBase extends WebTestBase {
+
+  use TaxonomyTestTrait;
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = array(
+    'system',
+    'language',
+    'user',
+    'node',
+    'field',
+    'field_ui',
+    'taxonomy',
+    'block',
+    'block_content',
+    'content_lock',
+    'content_lock_timeout',
+  );
+
+  /**
+   * Array standard permissions for normal user.
+   *
+   * @var array
+   */
+  protected $permissions1;
+
+  /**
+   * Array standard permissions for user2.
+   *
+   * @var array
+   */
+  protected $permissions2;
+
+  /**
+   * User with permission to administer entites.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $adminUser;
+
+  /**
+   * Standard User.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $user1;
+
+  /**
+   * Standard User.
+   *
+   * @var \Drupal\user\Entity\User
+   */
+  protected $user2;
+
+  /**
+   * A node created.
+   *
+   * @var \Drupal\node\NodeInterface
+   */
+  protected $article1;
+
+  /**
+   * A vocabulary created.
+   *
+   * @var \Drupal\taxonomy\VocabularyInterface
+   */
+  protected $vocabulary;
+
+  /**
+   * A term created.
+   *
+   * @var \Drupal\taxonomy\TermInterface
+   */
+  protected $term1;
+
+  /**
+   * A Block created.
+   *
+   * @var \Drupal\block_content\BlockContentInterface
+   */
+  protected $block1;
+
+  /**
+   * Setup and Rebuild node access.
+   */
+  public function setUp() {
+    parent::setUp();
+
+    $this->drupalCreateContentType(['type' => 'article']);
+
+    $this->adminUser = $this->drupalCreateUser([
+      'edit any article content',
+      'delete any article content',
+      'administer nodes',
+      'administer content types',
+      'administer users',
+      'administer blocks',
+      'administer taxonomy',
+      'administer content lock',
+    ]);
+
+    $this->permissions1 = [
+      'create article content',
+      'edit any article content',
+      'delete any article content',
+      'access content',
+      'administer blocks',
+      'administer taxonomy',
+    ];
+
+    $this->permissions2 = [
+      'create article content',
+      'edit any article content',
+      'delete any article content',
+      'access content',
+      'administer blocks',
+      'administer taxonomy',
+      'break content lock',
+    ];
+
+    // Create articles nodes.
+    $this->article1 = $this->drupalCreateNode(['type' => 'article', 'title' => 'Article 1']);
+
+    // Create block.
+    $this->createBlockContentType('basic', TRUE);
+    $this->block1 = $this->createBlockContent('Block 1');
+
+    // Create vocabulary and terms.
+    $this->vocabulary = $this->createVocabulary();
+    $this->term1 = $this->createTerm($this->vocabulary);
+
+    $this->user1 = $this->drupalCreateUser($this->permissions1);
+    $this->user2 = $this->drupalCreateUser($this->permissions2);
+
+    node_access_rebuild();
+    $this->cronRun();
+
+  }
+
+  /**
+   * Creates a custom block.
+   *
+   * @param bool|string $title
+   *   (optional) Title of block. When no value is given uses a random name.
+   *   Defaults to FALSE.
+   * @param string $bundle
+   *   (optional) Bundle name. Defaults to 'basic'.
+   * @param bool $save
+   *   (optional) Whether to save the block. Defaults to TRUE.
+   *
+   * @return \Drupal\block_content\Entity\BlockContent
+   *   Created custom block.
+   */
+  protected function createBlockContent($title = FALSE, $bundle = 'basic', $save = TRUE) {
+    $title = $title ?: $this->randomMachineName();
+    $block_content = BlockContent::create(array(
+      'info' => $title,
+      'type' => $bundle,
+      'langcode' => 'en'
+    ));
+    if ($block_content && $save === TRUE) {
+      $block_content->save();
+    }
+    return $block_content;
+  }
+
+  /**
+   * Creates a custom block type (bundle).
+   *
+   * @param string $label
+   *   The block type label.
+   * @param bool $create_body
+   *   Whether or not to create the body field.
+   *
+   * @return \Drupal\block_content\Entity\BlockContentType
+   *   Created custom block type.
+   */
+  protected function createBlockContentType($label, $create_body = FALSE) {
+    $bundle = BlockContentType::create(array(
+      'id' => $label,
+      'label' => $label,
+      'revision' => FALSE,
+    ));
+    $bundle->save();
+    if ($create_body) {
+      block_content_add_body_field($bundle->id());
+    }
+    return $bundle;
+  }
+
+  /**
+   * Test simultaneous edit on content type article.
+   */
+  protected function testContentLockNode() {
+
+    // We protect the bundle created.
+    $this->drupalLogin($this->adminUser);
+    $edit = [
+      'node[article]' => 1,
+    ];
+    $this->drupalPostForm('admin/config/content_lock/contentlocksettings', $edit, t('Save configuration'));
+
+    // We lock article1.
+    $this->drupalLogin($this->user1);
+    // Edit a node without saving.
+    $this->drupalGet("node/{$this->article1->id()}/edit");
+    $this->assertText(t('This content is now locked against simultaneous editing.'));
+
+    // Other user can not edit article1.
+    $this->drupalLogin($this->user2);
+    $this->drupalGet("node/{$this->article1->id()}/edit");
+    $this->assertText(t('This content is being edited by the user @name and is therefore locked to prevent other users changes.', array(
+      '@name' => $this->user1->getDisplayName(),
+    )));
+    $this->assertLink(t('Break lock'));
+    $disabled_button = $this->xpath('//input[@id=:id and @disabled="disabled"]', array(':id' => 'edit-submit'));
+    $this->assertTrue($disabled_button, t('The form cannot be submitted.'));
+    $disabled_field = $this->xpath('//textarea[@id=:id and @disabled="disabled"]', array(':id' => 'edit-body-0-value'));
+    $this->assertTrue($disabled_field, t('The form cannot be submitted.'));
+
+    // We save article 1 and unlock it.
+    $this->drupalLogin($this->user1);
+    $this->drupalGet("node/{$this->article1->id()}/edit");
+    $this->assertText(t('This content is now locked by you against simultaneous editing.'));
+    $this->drupalPostForm('/node/' . $this->article1->id() . '/edit', [], t('Save'));
+
+    // We lock article1 with user2.
+    $this->drupalLogin($this->user2);
+    // Edit a node without saving.
+    $this->drupalGet("node/{$this->article1->id()}/edit");
+    $this->assertText(t('This content is now locked against simultaneous editing.'));
+
+    // Other user can not edit article1.
+    $this->drupalLogin($this->user1);
+    $this->drupalGet("node/{$this->article1->id()}/edit");
+    $this->assertText(t('This content is being edited by the user @name and is therefore locked to prevent other users changes.', array(
+      '@name' => $this->user2->getDisplayName(),
+    )));
+    $this->assertNoLink(t('Break lock'));
+    $disabled_button = $this->xpath('//input[@id=:id and @disabled="disabled"]', array(':id' => 'edit-submit'));
+    $this->assertTrue($disabled_button, t('The form cannot be submitted.'));
+
+    // We unlock article1 with user2.
+    $this->drupalLogin($this->user2);
+    // Edit a node without saving.
+    $this->drupalGet("node/{$this->article1->id()}/edit");
+    $this->assertText(t('This content is now locked by you against simultaneous editing.'));
+    $this->drupalPostForm('/node/' . $this->article1->id() . '/edit', [], t('Save'));
+    $this->assertText(t('updated.'));
+
+  }
+
+  /**
+   * Test simultaneous edit on block.
+   */
+  protected function testContentLockBlock() {
+
+    // We protect the bundle created.
+    $this->drupalLogin($this->adminUser);
+    $edit = [
+      'block_content[basic]' => 1,
+    ];
+    $this->drupalPostForm('admin/config/content_lock/contentlocksettings', $edit, t('Save configuration'));
+
+    // We lock block1.
+    $this->drupalLogin($this->user1);
+    // Edit a node without saving.
+    $this->drupalGet("block/{$this->block1->id()}");
+    $this->assertText(t('This content is now locked against simultaneous editing.'));
+
+    // Other user can not edit block1.
+    $this->drupalLogin($this->user2);
+    $this->drupalGet("block/{$this->block1->id()}");
+    $this->assertText(t('This content is being edited by the user @name and is therefore locked to prevent other users changes.', array(
+      '@name' => $this->user1->getDisplayName(),
+    )));
+    $this->assertLink(t('Break lock'));
+    $disabled_button = $this->xpath('//input[@id=:id and @disabled="disabled"]', array(':id' => 'edit-submit'));
+    $this->assertTrue($disabled_button, t('The form cannot be submitted.'));
+
+    // We save block1 and unlock it.
+    $this->drupalLogin($this->user1);
+    $this->drupalGet("block/{$this->block1->id()}");
+    $this->assertText(t('This content is now locked by you against simultaneous editing.'));
+    $this->drupalPostForm('/block/' . $this->block1->id(), [], t('Save'));
+
+    // We lock block1 with user2.
+    $this->drupalLogin($this->user2);
+    // Edit a node without saving.
+    $this->drupalGet("block/{$this->block1->id()}");
+    $this->assertText(t('This content is now locked against simultaneous editing.'));
+
+    // Other user can not edit block1.
+    $this->drupalLogin($this->user1);
+    $this->drupalGet("block/{$this->block1->id()}");
+    $this->assertText(t('This content is being edited by the user @name and is therefore locked to prevent other users changes.', array(
+      '@name' => $this->user2->getDisplayName(),
+    )));
+    $this->assertNoLink(t('Break lock'));
+    $disabled_button = $this->xpath('//input[@id=:id and @disabled="disabled"]', array(':id' => 'edit-submit'));
+    $this->assertTrue($disabled_button, t('The form cannot be submitted.'));
+
+    // We unlock block1 with user2.
+    $this->drupalLogin($this->user2);
+    // Edit a node without saving.
+    $this->drupalGet("block/{$this->block1->id()}");
+    $this->assertText(t('This content is now locked by you against simultaneous editing.'));
+    $this->drupalPostForm('/block/' . $this->block1->id(), [], t('Save'));
+    $this->assertText(t('has been updated.'));
+  }
+
+  /**
+   * Test simultaneous edit on block.
+   */
+  protected function testContentLockTerm() {
+
+    // We protect the bundle created.
+    $this->drupalLogin($this->adminUser);
+    $edit = [
+      'taxonomy_term[' . $this->term1->bundle() . ']' => 1,
+    ];
+    $this->drupalPostForm('admin/config/content_lock/contentlocksettings', $edit, t('Save configuration'));
+
+    // We lock term1.
+    $this->drupalLogin($this->user1);
+    // Edit a term without saving.
+    $this->drupalGet("taxonomy/term/{$this->term1->id()}/edit");
+    $this->assertText(t('This content is now locked against simultaneous editing.'));
+
+    // Other user can not edit term1.
+    $this->drupalLogin($this->user2);
+    $this->drupalGet("taxonomy/term/{$this->term1->id()}/edit");
+    $this->assertText(t('This content is being edited by the user @name and is therefore locked to prevent other users changes.', array(
+      '@name' => $this->user1->getDisplayName(),
+    )));
+    $this->assertLink(t('Break lock'));
+    $disabled_button = $this->xpath('//input[@id=:id and @disabled="disabled"]', array(':id' => 'edit-submit'));
+    $this->assertTrue($disabled_button, t('The form cannot be submitted.'));
+
+    // We save term1 and unlock it.
+    $this->drupalLogin($this->user1);
+    $this->drupalGet("taxonomy/term/{$this->term1->id()}/edit");
+    $this->assertText(t('This content is now locked by you against simultaneous editing.'));
+    $this->drupalPostForm('/taxonomy/term/' . $this->term1->id() . '/edit', [], t('Save'));
+
+    // We lock term1 with user2.
+    $this->drupalLogin($this->user2);
+    // Edit a node without saving.
+    $this->drupalGet("taxonomy/term/{$this->term1->id()}/edit");
+    $this->assertText(t('This content is now locked against simultaneous editing.'));
+
+    // Other user can not edit term1.
+    $this->drupalLogin($this->user1);
+    $this->drupalGet("taxonomy/term/{$this->term1->id()}/edit");
+    $this->assertText(t('This content is being edited by the user @name and is therefore locked to prevent other users changes.', array(
+      '@name' => $this->user2->getDisplayName(),
+    )));
+    $this->assertNoLink(t('Break lock'));
+    $disabled_button = $this->xpath('//input[@id=:id and @disabled="disabled"]', array(':id' => 'edit-submit'));
+    $this->assertTrue($disabled_button, t('The form cannot be submitted.'));
+
+    // We unlock term1 with user2.
+    $this->drupalLogin($this->user2);
+    // Edit a node without saving.
+    $this->drupalGet("taxonomy/term/{$this->term1->id()}/edit");
+    $this->assertText(t('This content is now locked by you against simultaneous editing.'));
+    $this->drupalPostForm('/taxonomy/term/' . $this->term1->id() . '/edit', [], t('Save'));
+
+  }
+
+}
