View source
<?php
namespace Drupal\content_lock\ContentLock;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Extension\ModuleHandler;
use Drupal\Core\DependencyInjection\ServiceProviderBase;
use Drupal\Core\Language\LanguageInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Link;
use Drupal\Core\Datetime\DateFormatter;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Url;
use Symfony\Component\HttpFoundation\RequestStack;
class ContentLock extends ServiceProviderBase {
use StringTranslationTrait;
const FORM_OP_MODE_DISABLED = 0;
const FORM_OP_MODE_WHITELIST = 1;
const FORM_OP_MODE_BLACKLIST = 2;
protected $database;
protected $moduleHandler;
protected $dateFormatter;
protected $currentUser;
protected $config;
protected $currentRequest;
protected $entityTypeManager;
protected $messenger;
public function __construct(Connection $database, ModuleHandler $moduleHandler, DateFormatter $dateFormatter, AccountProxyInterface $currentUser, ConfigFactory $configFactory, RequestStack $requestStack, EntityTypeManagerInterface $entityTypeManager, MessengerInterface $messenger, TimeInterface $time) {
$this->database = $database;
$this->moduleHandler = $moduleHandler;
$this->dateFormatter = $dateFormatter;
$this->currentUser = $currentUser;
$this->config = $configFactory
->get('content_lock.settings');
$this->currentRequest = $requestStack
->getCurrentRequest();
$this->entityTypeManager = $entityTypeManager;
$this->messenger = $messenger;
$this->time = $time;
}
public function fetchLock($entity_id, $langcode, $form_op = NULL, $entity_type = 'node') {
if (!$this
->isTranslationLockEnabled($entity_type)) {
$langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
}
if (!$this
->isFormOperationLockEnabled($entity_type)) {
$form_op = '*';
}
$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)
->condition('c.langcode', $langcode);
if (isset($form_op)) {
$query
->condition('c.form_op', $form_op);
}
return $query
->execute()
->fetchObject();
}
public function displayLockOwner($lock, $translation_lock) {
$username = $this->entityTypeManager
->getStorage('user')
->load($lock->uid);
$date = $this->dateFormatter
->formatInterval($this->time
->getRequestTime() - $lock->timestamp);
if ($translation_lock) {
$message = $this
->t('This content translation is being edited by the user @name and is therefore locked to prevent other users changes. This lock is in place since @date.', [
'@name' => $username
->getDisplayName(),
'@date' => $date,
]);
}
else {
$message = $this
->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.', [
'@name' => $username
->getDisplayName(),
'@date' => $date,
]);
}
return $message;
}
public function isLockedBy($entity_id, $langcode, $form_op, $uid, $entity_type = 'node') {
if (!$this
->isTranslationLockEnabled($entity_type)) {
$langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
}
if (!$this
->isFormOperationLockEnabled($entity_type)) {
$form_op = '*';
}
$query = $this->database
->select('content_lock', 'c')
->fields('c')
->condition('entity_id', $entity_id)
->condition('uid', $uid)
->condition('entity_type', $entity_type)
->condition('langcode', $langcode)
->condition('form_op', $form_op);
$num_rows = $query
->countQuery()
->execute()
->fetchField();
return (bool) $num_rows;
}
public function release($entity_id, $langcode, $form_op = NULL, $uid = NULL, $entity_type = 'node') {
if (!$this
->isTranslationLockEnabled($entity_type)) {
$langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
}
if (!$this
->isFormOperationLockEnabled($entity_type)) {
$form_op = '*';
}
$this
->lockingDelete($entity_id, $langcode, $form_op, $uid, $entity_type);
$this->moduleHandler
->invokeAll('content_lock_release', [
$entity_id,
$langcode,
$form_op,
$entity_type,
]);
}
public function releaseAllUserLocks($uid) {
$this->database
->delete('content_lock')
->condition('uid', $uid)
->execute();
}
protected function lockingSave($entity_id, $langcode, $form_op, $uid, $entity_type = 'node') {
if (!$this
->isTranslationLockEnabled($entity_type)) {
$langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
}
if (!$this
->isFormOperationLockEnabled($entity_type)) {
$form_op = '*';
}
$result = $this->database
->merge('content_lock')
->key([
'entity_id' => $entity_id,
'entity_type' => $entity_type,
'langcode' => $langcode,
'form_op' => $form_op,
])
->fields([
'entity_id' => $entity_id,
'entity_type' => $entity_type,
'langcode' => $langcode,
'form_op' => $form_op,
'uid' => $uid,
'timestamp' => $this->time
->getRequestTime(),
])
->execute();
return $result;
}
protected function lockingDelete($entity_id, $langcode, $form_op = NULL, $uid = NULL, $entity_type = 'node') {
if (!$this
->isTranslationLockEnabled($entity_type)) {
$langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
}
if (!$this
->isFormOperationLockEnabled($entity_type)) {
$form_op = '*';
}
$query = $this->database
->delete('content_lock')
->condition('entity_type', $entity_type)
->condition('entity_id', $entity_id)
->condition('langcode', $langcode);
if (isset($form_op)) {
$query
->condition('form_op', $form_op);
}
if (!empty($uid)) {
$query
->condition('uid', $uid);
}
$result = $query
->execute();
return $result;
}
public function verbose() {
return $this->config
->get('verbose');
}
public function locking($entity_id, $langcode, $form_op, $uid, $entity_type = 'node', $quiet = FALSE, $destination = NULL) {
$translation_lock = $this
->isTranslationLockEnabled($entity_type);
if (!$translation_lock) {
$langcode = LanguageInterface::LANGCODE_NOT_SPECIFIED;
}
if (!$this
->isFormOperationLockEnabled($entity_type)) {
$form_op = '*';
}
$lock = $this
->fetchLock($entity_id, $langcode, $form_op, $entity_type);
if ($lock === FALSE || !is_object($lock)) {
$this
->lockingSave($entity_id, $langcode, $form_op, $uid, $entity_type);
if ($this
->verbose() && !$quiet) {
if ($translation_lock) {
$this->messenger
->addStatus($this
->t('This content translation is now locked against simultaneous editing. This content translation will remain locked if you navigate away from this page without saving or unlocking it.'));
}
else {
$this->messenger
->addStatus($this
->t('This content is now locked against simultaneous editing. This content will remain locked if you navigate away from this page without saving or unlocking it.'));
}
}
$this->moduleHandler
->invokeAll('content_lock_locked', [
$entity_id,
$langcode,
$form_op,
$uid,
$entity_type,
]);
return TRUE;
}
else {
if ($lock->uid != $uid) {
$message = $this
->displayLockOwner($lock, $translation_lock);
$this->messenger
->addWarning($message);
if ($this->currentUser
->hasPermission('break content lock')) {
$link = Link::createFromRoute($this
->t('Break lock'), 'content_lock.break_lock.' . $entity_type, [
'entity' => $entity_id,
'langcode' => $langcode,
'form_op' => $form_op,
], [
'query' => [
'destination' => isset($destination) ? $destination : $this->currentRequest
->getRequestUri(),
],
])
->toString();
$this->messenger
->addWarning($this
->t('Click here to @link', [
'@link' => $link,
]));
}
return FALSE;
}
else {
$this
->lockingSave($entity_id, $langcode, $form_op, $uid, $entity_type);
if ($this
->verbose() && !$quiet) {
if ($translation_lock) {
$this->messenger
->addStatus($this
->t('This content translation is now locked by you against simultaneous editing. This content translation will remain locked if you navigate away from this page without saving or unlocking it.'));
}
else {
$this->messenger
->addStatus($this
->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 or unlocking it.'));
}
}
return TRUE;
}
}
}
public function isLockable(EntityInterface $entity, $form_op = NULL) {
$entity_id = $entity
->id();
$entity_type = $entity
->getEntityTypeId();
$bundle = $entity
->bundle();
$config = $this->config
->get("types.{$entity_type}");
$this->moduleHandler
->invokeAll('content_lock_entity_lockable', [
$entity,
$entity_id,
$entity
->language()
->getId(),
$form_op,
$entity_type,
$bundle,
$config,
]);
if (is_array($config) && (in_array($bundle, $config) || in_array('*', $config))) {
if (isset($form_op) && $this
->isFormOperationLockEnabled($entity_type)) {
$mode = $this->config
->get("form_op_lock.{$entity_type}.mode");
$values = $this->config
->get("form_op_lock.{$entity_type}.values");
if ($mode == self::FORM_OP_MODE_BLACKLIST) {
return !in_array($form_op, $values);
}
elseif ($mode == self::FORM_OP_MODE_WHITELIST) {
return in_array($form_op, $values);
}
}
return TRUE;
}
return FALSE;
}
public function isJsLock($entity_type_id) {
return in_array($entity_type_id, $this->config
->get("types_js_lock") ?: []);
}
public function unlockButton($entity_type, $entity_id, $langcode, $form_op, $destination) {
$unlock_url_options = [];
if ($destination) {
$unlock_url_options['query'] = [
'destination' => $destination,
];
}
$route_parameters = [
'entity' => $entity_id,
'langcode' => $this
->isTranslationLockEnabled($entity_type) ? $langcode : LanguageInterface::LANGCODE_NOT_SPECIFIED,
'form_op' => $this
->isFormOperationLockEnabled($entity_type) ? $form_op : '*',
];
return [
'#type' => 'link',
'#title' => $this
->t('Unlock'),
'#access' => TRUE,
'#attributes' => [
'class' => [
'button',
],
],
'#url' => Url::fromRoute('content_lock.break_lock.' . $entity_type, $route_parameters, $unlock_url_options),
'#weight' => 99,
];
}
public function isTranslationLockEnabled($entity_type_id) {
return $this->moduleHandler
->moduleExists('conflict') && in_array($entity_type_id, $this->config
->get("types_translation_lock"));
}
public function isFormOperationLockEnabled($entity_type_id) {
return $this->config
->get("form_op_lock.{$entity_type_id}.mode") != self::FORM_OP_MODE_DISABLED;
}
}