You are here

content_lock_timeout.module in Content locking (anti-concurrent editing) 8.2

Allowed time-based automatic unlocking of nodes.

File

modules/content_lock_timeout/content_lock_timeout.module
View source
<?php

/**
 * @file
 * Allowed time-based automatic unlocking of nodes.
 */
use Drupal\Core\Session\SessionManager;
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 = Drupal::time()
    ->getCurrentTime() - 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->langcode, $obj->form_op, $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 = Drupal::time()
      ->getCurrentTime() - 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
      ->language()
      ->getId(), $operation, $entity
      ->getEntityTypeId())) && $lock->uid != $user
      ->id() && $lock->timestamp < $last_valid_time && $user
      ->hasPermission('break content lock') && $user
      ->id() > 0) {
      $lock_service
        ->release($entity
        ->id(), $entity
        ->language()
        ->getId(), $operation, $lock->uid, $entity
        ->getEntityTypeId());
      if ($lock_service
        ->verbose()) {
        $username = User::load($lock->uid)
          ->getDisplayName();
        $date = \Drupal::service('date.formatter')
          ->formatInterval(\Drupal::time()
          ->getRequestTime() - $lock->timestamp);
        $stale_time = \Drupal::service('date.formatter')
          ->formatInterval($last_valid_time - $lock->timestamp);
        \Drupal::messenger()
          ->addStatus(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.)', [
          '@name' => $username,
          '@date' => $date,
          '@stale_time' => $stale_time,
        ]));
      }
    }
  }
}

/**
 * Implements hook_user_logout().
 */
function content_lock_timeout_user_logout($account) {

  /** @var \Drupal\Core\Session\AccountInterface $account */

  // Only remove locks if there is a timeout given.
  $config = \Drupal::config('content_lock_timeout.settings');
  $timeout_minutes = $config
    ->get('content_lock_timeout_minutes');
  if ($timeout_minutes == 0) {
    return;
  }

  // Only do the database check if the original drupal session manager is used.
  // Otherwise its not sure if sessions table has correct data. As it would be
  // possible to extend the Class, instanceof is not used here!
  if (get_class(Drupal::service('session_manager')) == SessionManager::class) {
    $query = \Drupal::database()
      ->select('sessions');
    $query
      ->condition('uid', $account
      ->id());
    $query = $query
      ->countQuery();
    $session_count = (int) $query
      ->execute()
      ->fetchField();
  }
  else {
    $session_count = FALSE;
  }

  // Only remove all locks of user if its the last session of the user.
  if ($session_count === 1) {

    /** @var \Drupal\content_lock\ContentLock\ContentLock $lock_service */
    $lock_service = \Drupal::service('content_lock');
    $lock_service
      ->releaseAllUserLocks($account
      ->id());
  }
}