delivery-8.x-1.x-dev/delivery.module
delivery.module
<?php
/**
* @file
* Drupal hooks and helper functions.
*/
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Database\Query\AlterableInterface;
use Drupal\Core\Database\Query\Select;
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\ContentEntityTypeInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityFormInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Entity\RevisionableInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\Url;
use Drupal\delivery\Entity\DeliveryItem;
use Drupal\delivery\Entity\MenuLinkContent;
use Drupal\delivery\Form\MenuPushForm;
use Drupal\delivery\MenuLinkContentStorage;
use Drupal\delivery\WorkspaceListBuilder;
use Drupal\Core\Form\FormStateInterface;
/**
* Implements hook_module_implements_alter().
*/
function delivery_module_implements_alter(&$implementations, $hook) {
if (in_array($hook, ['entity_bundle_info_alter', 'entity_base_field_info_alter', 'entity_type_build'])) {
$group = $implementations['delivery'];
unset($implementations['delivery']);
$implementations['delivery'] = $group;
}
}
/**
* Alter queries that are tagged as workspace sensitive.
*
* Implements hook_query_TAG_alter().
*/
function delivery_query_workspace_sensitive_alter(AlterableInterface $query) {
if (
$query instanceof Select &&
$query->getMetaData('entity_type') &&
$active_workspace_id = $query->getMetaData('active_workspace_id') &&
!$query->getMetaData('all_revisions')
) {
$entity_type_id = $query->getMetaData('entity_type');
$tables = &$query->getTables();
if (array_key_exists('workspace_association', $tables)) {
$tables['workspace_association']['join type'] = 'INNER';
$entityType = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
$query->innerJoin($entityType->getRevisionTable(), 'revision_table', 'workspace_association.target_entity_revision_id = revision_table.' . $entityType->getKey('revision') . ' AND revision_table.deleted = 0');
}
}
}
/**
* Implements hook_entity_access().
*/
function delivery_entity_access(EntityInterface $entity, $operation, AccountInterface $account) {
/** @var \Drupal\workspaces\WorkspaceManagerInterface $workspacesManager */
$workspacesManager = \Drupal::service('workspaces.manager');
/** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspaceAssociation */
$workspaceAssociation = \Drupal::service('workspaces.association');
if ($operation == 'deliver') {
if ($account->hasPermission('deliver items')) {
return AccessResult::allowed();
}
return AccessResult::forbidden();
}
if ($workspacesManager->isEntityTypeSupported($entity->getEntityType())) {
// Statically cache results in an array keyed by $account->id().
$trackingWorkspaces = &drupal_static(__FUNCTION__);
$key = $entity->getEntityTypeId() . ':' . $entity->id();
if (!isset($trackingWorkspaces[$key])) {
$trackingWorkspaces[$key] = $workspaceAssociation->getEntityTrackingWorkspaceIds($entity);
}
$activeWorkspace = $workspacesManager->getActiveWorkspace();
if ($activeWorkspace && !in_array($activeWorkspace->id(), $trackingWorkspaces[$key])) {
return AccessResult::forbidden();
}
}
if ($entity instanceof DeliveryItem) {
$storage = \Drupal::entityTypeManager()
->getStorage('delivery');
$deliveries = $storage
->getQuery()
->condition('items', $entity->id())->execute();
if ($delivery = $storage->load(array_pop($deliveries))) {
return $delivery->access($operation, $account, TRUE);
}
}
}
/**
* Implements hook_entity_type_build().
*
* @param \Drupal\Core\Entity\EntityTypeInterface[] $entity_types
*/
function delivery_entity_type_build(array &$entity_types) {
/** @var \Drupal\workspaces\WorkspaceManagerInterface $workspacesManager */
$workspacesManager = \Drupal::service('workspaces.manager');
$entity_types['menu_link_content']->setHandlerClass('storage', MenuLinkContentStorage::class);
$entity_types['menu_link_content']->setClass(MenuLinkContent::class);
// Override the list builder so we can inject our own deploy button.
$entity_types['workspace']->setHandlerClass('list_builder', WorkspaceListBuilder::class);
// Remove the workspace conflict constraint.
foreach ($entity_types as $entityType) {
$constraints = $entityType->getConstraints();
unset($constraints['EntityWorkspaceConflict']);
unset($constraints['EntityChanged']);
unset($constraints['MenuTreeHierarchy']);
// Mark file and crop entities internal. Questionable, I know.
if ($entityType->id() === 'file' || $entityType->id() === 'crop') {
$entityType->set('internal', TRUE);
}
// Allow to edit untranslatable fields.
if (
$entityType instanceof ContentEntityTypeInterface &&
$entityType->isRevisionable() &&
!$entityType->isInternal()
) {
unset($constraints['EntityUntranslatableFields']);
}
$entityType->setConstraints($constraints);
if ($workspacesManager->isEntityTypeSupported($entityType)) {
// Add the required entity key.
$entity_keys = $entityType->get('revision_metadata_keys');
$entity_keys['deleted'] = 'deleted';
$entityType->set('revision_metadata_keys', $entity_keys);
}
}
}
/**
* Implements hook_ENTITY_TYPE_view_alter() for deliveries.
*/
function delivery_delivery_view_alter(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display) {
$build['items'] = views_embed_view(
'delivery_status',
'embed',
$entity->id()
);
}
function delivery_form_field_config_edit_form_alter(&$form, FormStateInterface $formState) {
/** @var \Drupal\field\FieldConfigInterface $entity */
$entity = $formState->getFormObject()->getEntity();
$form['third_party_settings']['delivery'] = [
'#type' => 'details',
'#title' => t('Conflict resolution'),
'#open' => TRUE,
'blacklisted' => [
'#type' => 'checkbox',
'#title' => t('Blacklisted'),
'#description' => t('Exclude this field from conflict resolution.'),
'#default_value' => $entity->getThirdPartySetting('delivery', 'blacklisted', FALSE),
],
];
}
/**
* Implements hook_form_alter().
*/
function delivery_form_alter(&$form, FormStateInterface $formState, $formId) {
// Ensure the revision revert confirmation form works in all workspaces.
$formState->set('workspace_safe', TRUE);
$formObject = $formState->getFormObject();
// Set the default language to the current workspace primary language.
if (
$formObject instanceof EntityFormInterface &&
$formObject->getEntity() instanceof ContentEntityInterface &&
$formObject->getEntity()->isTranslatable() &&
array_key_exists('langcode', $form)
) {
$form['langcode']['#access'] = $formObject->getEntity()->isNew();
if ($formObject->getEntity()->isNew()) {
/** @var \Drupal\workspaces\WorkspaceManagerInterface $workspaceManager */
$workspaceManager = \Drupal::service('workspaces.manager');
$workspace = $workspaceManager->getActiveWorkspace();
if (!empty($workspace->primary_language) && $defaultLanguage = $workspace->primary_language->value) {
$form['langcode']['widget'][0]['value']['#default_value'] = $defaultLanguage;
}
if (!empty($workspace->secondary_languages) && !$workspace->secondary_languages->value) {
$form['langcode']['#access'] = FALSE;
}
}
}
}
/**
* Implements hook_form_FORM_ID_alter().
*/
function delivery_form_menu_edit_form_alter(&$form, FormStateInterface $form_state) {
/** @var \Drupal\workspaces\WorkspaceManagerInterface $manager */
$workspacesManager = \Drupal::service('workspaces.manager');
/** @var \Drupal\system\Entity\Menu $menu */
$menu = $form_state->getFormObject()->getEntity();
$currentUser = \Drupal::currentUser();
$activeWorkspace = $workspacesManager->getActiveWorkspace();
if (!$activeWorkspace->isDefaultWorkspace()) {
$form['actions']['publish'] = [
'#type' => 'link',
'#title' => new TranslatableMarkup('Publish'),
'#access' => $currentUser->hasPermission('deliver items'),
'#attributes' => [
'class' => ['button', 'button--primary'],
],
'#weight' => 6,
'#url' => Url::fromRoute(
'delivery.workspace_delivery_controller',
[
'workspace' => $activeWorkspace->id(),
],
[
'query' => [
'entity_type' => 'menu_link_content',
'bundle' => $menu->id(),
'hide_options_elements' => 1,
],
]
),
];
}
$menuId = $menu->id();
$messenger = \Drupal::messenger();
$differences = MenuPushForm::differences($menuId);
if ($differences !== FALSE) {
if ($differences) {
$messenger->addWarning(t('There are @count unpublished menu changes. <a href=":link">Publish them?</a>', [
'@count' => count($differences),
':link' => Url::fromRoute('delivery.menu_push', [
'menu' => $menuId,
])->toString(),
]));
}
else {
$messenger->addMessage(t('This menu is fully published.'));
}
}
}
/**
* Implements hook_entity_bundle_info_alter().
*/
function delivery_entity_bundle_info_alter(&$bundles) {
// Make sure moderation states are never translatable.
$bundles['content_moderation_state']['content_moderation_state']['translatable'] = FALSE;
}
/**
* Implements hook_entity_base_field_info_alter().
*/
function delivery_entity_base_field_info_alter(&$fields, EntityTypeInterface $entity_type) {
// Make sure all languages are always in the same moderation state.
if (isset($fields['moderation_state'])) {
$fields['moderation_state']->setTranslatable(FALSE);
}
// Make sure all languages are simultaneously published.
if (isset($fields['status'])) {
$fields['status']->setTranslatable(FALSE);
}
// Set menu tree related fields revisionable.
if ($entity_type->id() === 'menu_link_content') {
$revisionableFields = [
'weight',
'expanded',
'enabled',
'parent',
];
foreach ($revisionableFields as $revisionableField) {
$fields[$revisionableField]->setRevisionable(TRUE);
}
}
}
/**
* Implements hook_entity_base_field_info().
*
* @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
*/
function delivery_entity_base_field_info(EntityTypeInterface $entity_type) {
$fields = [];
if ($entity_type->id() === 'user') {
$fields['assigned_workspaces'] = BaseFieldDefinition::create('entity_reference')
->setLabel(t('Assigned workspaces'))
->setDescription(t('The list of workspaces that are assigned to this user.'))
->setSetting('target_type', 'workspace')
->setSetting('handler', 'default')
->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED)
->setDisplayOptions('view', array(
'label' => 'above',
'type' => 'entity_reference_label',
'weight' => -3,
))
->setDisplayOptions('form', array(
'type' => 'entity_reference_autocomplete',
'settings' => array(
'match_operator' => 'CONTAINS',
'size' => 60,
'autocomplete_type' => 'tags',
'placeholder' => '',
),
'weight' => -3,
))
->setDisplayConfigurable('form', TRUE)
->setDisplayConfigurable('view', TRUE);
}
if ($entity_type->id() === 'workspace') {
$fields['auto_push'] = BaseFieldDefinition::create('boolean')
->setLabel(new TranslatableMarkup('Auto push'))
->setDescription(new TranslatableMarkup('Automatically merge default revisions into the parent workspace'))
->setRevisionable(TRUE)
->setTranslatable(TRUE)
->setDefaultValue(FALSE)
->setDisplayOptions('form', [
'type' => 'boolean_checkbox',
'settings' => [
'display_label' => TRUE,
],
'weight' => 7,
])
->setDisplayConfigurable('form', TRUE);
}
/** @var \Drupal\workspaces\WorkspaceManagerInterface $workspaceManager */
$workspaceManager = \Drupal::service('workspaces.manager');
// Attach a revisionable "deleted" field.
if ($workspaceManager->isEntityTypeSupported($entity_type)) {
$fields['deleted'] = BaseFieldDefinition::create('timestamp')
->setLabel(t('Deleted'))
->setDescription(t('Time when the item got deleted'))
->setInternal(TRUE)
->setTranslatable(FALSE)
->setRevisionable(TRUE)
->setDefaultValue(0);
}
return $fields;
}
function delivery_entity_update(EntityInterface $entity) {
if ($entity instanceof ContentEntityInterface && !$entity->isSyncing()) {
$entityType = $entity->getEntityType();
/** @var \Drupal\workspaces\WorkspaceManagerInterface $workspaceManager */
$workspaceManager = \Drupal::service('workspaces.manager');
if (!$workspaceManager->isEntityTypeSupported($entityType)) {
return;
}
$activeWorkspace = $workspaceManager->getActiveWorkspace();
if (!$activeWorkspace->auto_push->value || !$activeWorkspace->parent->entity) {
return;
}
/** @var \Drupal\content_moderation\ModerationInformationInterface $content_moderation_info */
$content_moderation_info = \Drupal::service('content_moderation.moderation_information');
$workflow = $content_moderation_info->getWorkflowForEntity($entity);
if (!($workflow && $workflow->getTypePlugin()->hasState($entity->moderation_state->value))) {
return;
}
$current_state = $workflow->getTypePlugin()->getState($entity->moderation_state->value);
if (!$current_state->isDefaultRevisionState()) {
return;
}
/** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspaceAssiociaton */
$workspaceAssiociaton = \Drupal::service('workspaces.association');
/** @var \Drupal\workspaces\WorkspaceInterface $parentWorkspace */
$parentWorkspace = $activeWorkspace->parent->entity;
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
$storage = \Drupal::entityTypeManager()->getStorage($entity->getEntityTypeId());
$parentRevisions = $workspaceAssiociaton->getTrackedEntities(
$parentWorkspace->id(),
$entity->getEntityTypeId(),
[$entity->id()]
);
if (empty($parentRevisions)) {
$parentRevisions = array_keys($storage->getQuery()->allRevisions()
->notExists('workspace')
->condition($storage->getEntityType()->getKey('id'), $entity->id())
->execute());
}
else {
$parentRevisions = array_keys($parentRevisions[$entity->getEntityTypeId()]);
}
/** @var ContentEntityInterface $result */
$result = $storage->createRevision($entity);
$revisionParentField = $entityType->getRevisionMetadataKey('revision_parent');
$revisionMergeParentField = $entityType->getRevisionMetadataKey('revision_merge_parent');
$result->{$revisionMergeParentField}->target_revision_id = $entity->getRevisionId();
$result->{$revisionParentField}->target_revision_id = array_pop($parentRevisions);
$result->workspace = $parentWorkspace;
$result->setSyncing(TRUE);
$workspaceManager->unsafeExecuteInWorkspace($parentWorkspace->id(), function () use ($result) {
$result->save();
});
}
}
/**
* Implements hook_entity_load().
*/
function delivery_entity_load(array $entities, $entity_type_id) {
$entityType = \Drupal::entityTypeManager()->getDefinition($entity_type_id);
/** @var \Drupal\workspaces\WorkspaceManagerInterface $workspaceManager */
$workspaceManager = \Drupal::service('workspaces.manager');
if ($workspaceManager->isEntityTypeSupported($entityType)) {
/** @var \Drupal\workspaces\WorkspaceAssociationInterface $workspaceAssociation */
$workspaceAssociation = \Drupal::service('workspaces.association');
$active_workspace = $workspaceManager->getActiveWorkspace();
if (!$active_workspace) {
return;
}
$tracking = $workspaceAssociation->getTrackedEntities($active_workspace->id(), $entity_type_id, array_keys($entities));
if (empty($tracking)) {
return;
}
foreach ($entities as $entity) {
/** @var \Drupal\Core\Entity\ContentEntityInterface $entity */
$entity->isDefaultRevision(array_key_exists($entity->getRevisionId(), $tracking[$entity_type_id]));
}
}
}
/**
* Implements hook_entity_predelete().
*/
function delivery_entity_predelete(EntityInterface $entity) {
/** @var \Drupal\workspaces\WorkspaceManagerInterface $workspaceManager */
$workspaceManager = \Drupal::service('workspaces.manager');
$entityType = $entity->getEntityType();
if ($workspaceManager->isEntityTypeSupported($entityType) && $entity instanceof RevisionableInterface) {
/** @var \Drupal\Core\Entity\ContentEntityStorageInterface $storage */
$storage = \Drupal::entityTypeManager()->getStorage($entityType->id());
$deleted = $storage->createRevision($entity);
$deleted->deleted = \Drupal::time()->getRequestTime();
$deleted->save();
return EntityStorageInterface::ENTITY_SOFT_DELETE;
}
}
