conflict.module in Conflict 8.2
Same filename and directory in other branches
The module that makes concurrent editing possible.
File
conflict.moduleView source
<?php
/**
* @file
* The module that makes concurrent editing possible.
*/
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\conflict\Entity\ContentEntityConflictHandler;
use Drupal\conflict\Entity\EntityConflictHandlerInterface;
/**
* Implements hook_module_implements_alter().
*/
function conflict_module_implements_alter(&$implementations, $hook) {
// Move the hooks conflict_form_alter(), conflict_entity_load() and
// conflict_entity_type_alter() to the end of the list.
if (in_array($hook, [
'form_alter',
'entity_load',
'entity_type_alter',
])) {
$group = $implementations['conflict'];
unset($implementations['conflict']);
$implementations['conflict'] = $group;
}
}
/**
* Implements hook_entity_type_alter().
*
* @see \Drupal\Core\Entity\Annotation\EntityType
*/
function conflict_entity_type_alter(array &$entity_types) {
// Provide defaults for translation info.
/** @var $entity_types \Drupal\Core\Entity\EntityTypeInterface[] */
foreach ($entity_types as $entity_type) {
if ($entity_type instanceof ContentEntityTypeInterface) {
if (!$entity_type
->hasHandlerClass('conflict.resolution_handler')) {
$entity_type
->setHandlerClass('conflict.resolution_handler', ContentEntityConflictHandler::class);
}
if (is_null($entity_type
->get('conflict_ui_merge_supported'))) {
$entity_type
->set('conflict_ui_merge_supported', TRUE);
}
}
else {
// @todo add support for config entities.
}
}
}
/**
* Implements hook_entity_load().
*
* Attaches a clone of the loaded entity to the currently loaded entity, which
* will be used if any conflicts are detected on an entity form submission in
* order to determine the changes made by the user in case the entity has been
* saved meanwhile.
*/
function conflict_entity_load(array $entities, $entity_type_id) {
// @todo decide whether this is the right place for storing a clone of the
// loaded entity. Another possible place would be the form state for the main
// entity and the field state for inline entities. The problem with the
// current solution is that even entities loaded e.g. for a non inline entity
// form widget will be cloned.
$route = \Drupal::routeMatch()
->getRouteObject();
// The route object will not be present if the entity is being loaded before
// the routing has completed. This happens e.g. in
// Drupal\Core\ParamConverter\EntityConverter::convert(), therefore we have
// to check if the route object is not present that we are still in the
// browser. This is not a perfect solution as there will be cases where we
// will clone unnecessary the entity, but currently the most simple solution.
if (is_null($route) && php_sapi_name() != 'cli' || $route && ($route_defaults = $route
->getDefaults()) && isset($route_defaults['_entity_form'])) {
foreach ($entities as $entity) {
if ($entity instanceof ContentEntityInterface) {
$clone = clone $entity;
$entity->{EntityConflictHandlerInterface::CONFLICT_ENTITY_ORIGINAL} = $clone;
$serialized = serialize($clone);
$hash = $entity_type_id . '_' . $entity
->id() . sha1($serialized);
$entity->{EntityConflictHandlerInterface::CONFLICT_ENTITY_ORIGINAL_HASH} = $hash;
\Drupal::keyValueExpirable('conflict_original_entity')
->setWithExpireIfNotExists($hash, $serialized, 86400);
}
}
}
}
/**
* Implements hook_entity_prepare_form().
*
* Loads the original entity into the form for not yet cached entity forms. This
* is required as forms are not cached on GET, but only on POST requests.
* Therefore until there was some AJAX interactions with the form it will remain
* uncached. However if the form was submitted with changes without being cached
* before and in the meanwhile the entity has been saved in another session then
* the currently rebuilt form for submitting it will be using the newer version
* of the entity instead of the one used for generating it. For proper conflict
* handling we however need that the form is built with the originally used
* entity.
*
* Note: when the referenced drupal.org issue is solved we would not need to
* store the entity in the key value store anymore and exchange it in the form.
*
* @see https://www.drupal.org/project/drupal/issues/2824293
*/
function conflict_entity_prepare_form(EntityInterface $entity, $operation, FormStateInterface $form_state) {
if (!$entity
->isNew() && !$form_state
->isCached()) {
$conflict_entity_original_hash = $form_state
->getUserInput()['conflict_entity_original_hash'] ?? NULL;
if ($conflict_entity_original_hash) {
$original_entity = \Drupal::keyValueExpirable('conflict_original_entity')
->get($conflict_entity_original_hash);
if ($original_entity) {
$original_entity = unserialize($original_entity);
/** @var \Drupal\Core\Entity\EntityFormInterface $form_object */
$form_object = $form_state
->getFormObject();
$form_object
->setEntity($original_entity);
$form_state
->set('conflict-exchanged-entity', TRUE);
}
}
}
}
/**
* Implements hook_form_alter().
*/
function conflict_form_alter(&$form, FormStateInterface $form_state, $form_id) {
$form_object = $form_state
->getFormObject();
if (!$form_object instanceof EntityFormInterface) {
return;
}
conflict_prepare_entity_form($form, $form_state, $form_object
->getEntity());
}
/**
* Helper method for preparing entity forms for conflict resolution.
*
* The entity is present as a parameter to support inline entity forms.
*/
function conflict_prepare_entity_form(&$form, FormStateInterface $form_state, EntityInterface $entity, $inline_entity_form = FALSE) {
$conflict_supported = $form_state
->get('conflict.supported');
if ($conflict_supported === FALSE) {
return;
}
elseif (is_null($conflict_supported)) {
$route = \Drupal::routeMatch()
->getRouteObject();
if (!($route && ($route_defaults = $route
->getDefaults()) && isset($route_defaults['_entity_form']))) {
$form_state
->set('conflict.supported', FALSE);
return;
}
elseif ($entity instanceof RevisionableInterface && (!$entity
->isDefaultRevision() && !(bool) $form_state
->get('conflict-exchanged-entity'))) {
$form_state
->set('conflict.supported', FALSE);
return;
}
// Flags the form that on it a conflict resolution is supported.
$form_state
->set('conflict.supported', TRUE);
}
$entity_type_id = $entity
->getEntityTypeId();
$bundle = $entity
->bundle();
$entity_type_manager = \Drupal::entityTypeManager();
if ($entity_type_manager
->hasHandler($entity_type_id, 'conflict.resolution_handler')) {
if (!$inline_entity_form) {
/** @var \Drupal\Core\Entity\ContentEntityFormInterface $form_object */
$form_object = $form_state
->getFormObject();
$entity->{EntityConflictHandlerInterface::CONFLICT_FORM_DISPLAY} = $form_object
->getFormDisplay($form_state);
}
/** @var \Drupal\conflict\Entity\EntityConflictHandlerInterface $entity_conflict_resolution_handler */
$entity_conflict_resolution_handler = $entity_type_manager
->getHandler($entity_type_id, 'conflict.resolution_handler');
$entity_conflict_resolution_handler
->entityFormAlter($form, $form_state, $entity, $inline_entity_form);
// Retrieve the resolution strategy from the settings and if none selected
// default to inline.
$settings = \Drupal::configFactory()
->get('conflict.settings');
$strategy = $settings
->get("resolution_type.{$entity_type_id}.{$bundle}") ?? $settings
->get("resolution_type.{$entity_type_id}.default") ?? $settings
->get("resolution_type.default.default") ?? 'inline';
if ($strategy === 'dialog') {
// Add the dialog conflict resolution overview only to the main entity
// form and not to the nested entity forms.
if (!$inline_entity_form && ($form_state
->get('conflict.build_conflict_resolution_form') || $form_state
->get('conflict.processing'))) {
\Drupal::service('conflict.resolution_form.builder')
->processForm($form, $form_state);
}
}
else {
if ($form_state
->get('conflict.build_conflict_resolution_form')) {
\Drupal::service('conflict.resolution_inline_form.builder')
->processForm($form, $form_state, $entity);
}
}
}
}
Functions
Name | Description |
---|---|
conflict_entity_load | Implements hook_entity_load(). |
conflict_entity_prepare_form | Implements hook_entity_prepare_form(). |
conflict_entity_type_alter | Implements hook_entity_type_alter(). |
conflict_form_alter | Implements hook_form_alter(). |
conflict_module_implements_alter | Implements hook_module_implements_alter(). |
conflict_prepare_entity_form | Helper method for preparing entity forms for conflict resolution. |