Issue #2880152 by amateescu, blazey, plach, larowlan, vijaycs85: Convert custom menu links to be revisionable
parent
1e0ad95a0b
commit
5ad8f598e5
|
|
@ -29,6 +29,16 @@ function menu_link_content_help($route_name, RouteMatchInterface $route_match) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implements hook_entity_type_alter().
|
||||||
|
*/
|
||||||
|
function menu_link_content_entity_type_alter(array &$entity_types) {
|
||||||
|
// @todo Moderation is disabled for custom menu links until when we have an UI
|
||||||
|
// for them.
|
||||||
|
// @see https://www.drupal.org/project/drupal/issues/2350939
|
||||||
|
$entity_types['menu_link_content']->setHandlerClass('moderation', '');
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements hook_menu_delete().
|
* Implements hook_menu_delete().
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,103 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @file
|
||||||
|
* Post update functions for the Menu link content module.
|
||||||
|
*/
|
||||||
|
|
||||||
|
use Drupal\Core\Field\BaseFieldDefinition;
|
||||||
|
use Drupal\Core\StringTranslation\TranslatableMarkup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update custom menu links to be revisionable.
|
||||||
|
*/
|
||||||
|
function menu_link_content_post_update_make_menu_link_content_revisionable(&$sandbox) {
|
||||||
|
$definition_update_manager = \Drupal::entityDefinitionUpdateManager();
|
||||||
|
/** @var \Drupal\Core\Entity\EntityLastInstalledSchemaRepositoryInterface $last_installed_schema_repository */
|
||||||
|
$last_installed_schema_repository = \Drupal::service('entity.last_installed_schema.repository');
|
||||||
|
|
||||||
|
$entity_type = $definition_update_manager->getEntityType('menu_link_content');
|
||||||
|
$field_storage_definitions = $last_installed_schema_repository->getLastInstalledFieldStorageDefinitions('menu_link_content');
|
||||||
|
|
||||||
|
// Update the entity type definition.
|
||||||
|
$entity_keys = $entity_type->getKeys();
|
||||||
|
$entity_keys['revision'] = 'revision_id';
|
||||||
|
$entity_keys['revision_translation_affected'] = 'revision_translation_affected';
|
||||||
|
$entity_type->set('entity_keys', $entity_keys);
|
||||||
|
$entity_type->set('revision_table', 'menu_link_content_revision');
|
||||||
|
$entity_type->set('revision_data_table', 'menu_link_content_field_revision');
|
||||||
|
$revision_metadata_keys = [
|
||||||
|
'revision_default' => 'revision_default',
|
||||||
|
'revision_user' => 'revision_user',
|
||||||
|
'revision_created' => 'revision_created',
|
||||||
|
'revision_log_message' => 'revision_log_message',
|
||||||
|
];
|
||||||
|
$entity_type->set('revision_metadata_keys', $revision_metadata_keys);
|
||||||
|
|
||||||
|
// Update the field storage definitions and add the new ones required by a
|
||||||
|
// revisionable entity type.
|
||||||
|
$field_storage_definitions['langcode']->setRevisionable(TRUE);
|
||||||
|
$field_storage_definitions['title']->setRevisionable(TRUE);
|
||||||
|
$field_storage_definitions['description']->setRevisionable(TRUE);
|
||||||
|
$field_storage_definitions['link']->setRevisionable(TRUE);
|
||||||
|
$field_storage_definitions['external']->setRevisionable(TRUE);
|
||||||
|
$field_storage_definitions['enabled']->setRevisionable(TRUE);
|
||||||
|
$field_storage_definitions['changed']->setRevisionable(TRUE);
|
||||||
|
|
||||||
|
$field_storage_definitions['revision_id'] = BaseFieldDefinition::create('integer')
|
||||||
|
->setName('revision_id')
|
||||||
|
->setTargetEntityTypeId('menu_link_content')
|
||||||
|
->setTargetBundle(NULL)
|
||||||
|
->setLabel(new TranslatableMarkup('Revision ID'))
|
||||||
|
->setReadOnly(TRUE)
|
||||||
|
->setSetting('unsigned', TRUE);
|
||||||
|
|
||||||
|
$field_storage_definitions['revision_default'] = BaseFieldDefinition::create('boolean')
|
||||||
|
->setName('revision_default')
|
||||||
|
->setTargetEntityTypeId('menu_link_content')
|
||||||
|
->setTargetBundle(NULL)
|
||||||
|
->setLabel(new TranslatableMarkup('Default revision'))
|
||||||
|
->setDescription(new TranslatableMarkup('A flag indicating whether this was a default revision when it was saved.'))
|
||||||
|
->setStorageRequired(TRUE)
|
||||||
|
->setInternal(TRUE)
|
||||||
|
->setTranslatable(FALSE)
|
||||||
|
->setRevisionable(TRUE);
|
||||||
|
|
||||||
|
$field_storage_definitions['revision_translation_affected'] = BaseFieldDefinition::create('boolean')
|
||||||
|
->setName('revision_translation_affected')
|
||||||
|
->setTargetEntityTypeId('menu_link_content')
|
||||||
|
->setTargetBundle(NULL)
|
||||||
|
->setLabel(new TranslatableMarkup('Revision translation affected'))
|
||||||
|
->setDescription(new TranslatableMarkup('Indicates if the last edit of a translation belongs to current revision.'))
|
||||||
|
->setReadOnly(TRUE)
|
||||||
|
->setRevisionable(TRUE)
|
||||||
|
->setTranslatable(TRUE);
|
||||||
|
|
||||||
|
$field_storage_definitions['revision_created'] = BaseFieldDefinition::create('created')
|
||||||
|
->setName('revision_created')
|
||||||
|
->setTargetEntityTypeId('menu_link_content')
|
||||||
|
->setTargetBundle(NULL)
|
||||||
|
->setLabel(new TranslatableMarkup('Revision create time'))
|
||||||
|
->setDescription(new TranslatableMarkup('The time that the current revision was created.'))
|
||||||
|
->setRevisionable(TRUE);
|
||||||
|
$field_storage_definitions['revision_user'] = BaseFieldDefinition::create('entity_reference')
|
||||||
|
->setName('revision_user')
|
||||||
|
->setTargetEntityTypeId('menu_link_content')
|
||||||
|
->setTargetBundle(NULL)
|
||||||
|
->setLabel(new TranslatableMarkup('Revision user'))
|
||||||
|
->setDescription(new TranslatableMarkup('The user ID of the author of the current revision.'))
|
||||||
|
->setSetting('target_type', 'user')
|
||||||
|
->setRevisionable(TRUE);
|
||||||
|
$field_storage_definitions['revision_log_message'] = BaseFieldDefinition::create('string_long')
|
||||||
|
->setName('revision_log_message')
|
||||||
|
->setTargetEntityTypeId('menu_link_content')
|
||||||
|
->setTargetBundle(NULL)
|
||||||
|
->setLabel(new TranslatableMarkup('Revision log message'))
|
||||||
|
->setDescription(new TranslatableMarkup('Briefly describe the changes you have made.'))
|
||||||
|
->setRevisionable(TRUE)
|
||||||
|
->setDefaultValue('');
|
||||||
|
|
||||||
|
$definition_update_manager->updateFieldableEntityType($entity_type, $field_storage_definitions, $sandbox);
|
||||||
|
|
||||||
|
return t('Custom menu links have been converted to be revisionable.');
|
||||||
|
}
|
||||||
|
|
@ -2,9 +2,7 @@
|
||||||
|
|
||||||
namespace Drupal\menu_link_content\Entity;
|
namespace Drupal\menu_link_content\Entity;
|
||||||
|
|
||||||
use Drupal\Core\Entity\ContentEntityBase;
|
use Drupal\Core\Entity\EditorialContentEntityBase;
|
||||||
use Drupal\Core\Entity\EntityChangedTrait;
|
|
||||||
use Drupal\Core\Entity\EntityPublishedTrait;
|
|
||||||
use Drupal\Core\Entity\EntityStorageInterface;
|
use Drupal\Core\Entity\EntityStorageInterface;
|
||||||
use Drupal\Core\Entity\EntityTypeInterface;
|
use Drupal\Core\Entity\EntityTypeInterface;
|
||||||
use Drupal\Core\Field\BaseFieldDefinition;
|
use Drupal\Core\Field\BaseFieldDefinition;
|
||||||
|
|
@ -28,7 +26,7 @@ use Drupal\menu_link_content\MenuLinkContentInterface;
|
||||||
* plural = "@count custom menu links",
|
* plural = "@count custom menu links",
|
||||||
* ),
|
* ),
|
||||||
* handlers = {
|
* handlers = {
|
||||||
* "storage" = "Drupal\Core\Entity\Sql\SqlContentEntityStorage",
|
* "storage" = "\Drupal\menu_link_content\MenuLinkContentStorage",
|
||||||
* "storage_schema" = "Drupal\menu_link_content\MenuLinkContentStorageSchema",
|
* "storage_schema" = "Drupal\menu_link_content\MenuLinkContentStorageSchema",
|
||||||
* "access" = "Drupal\menu_link_content\MenuLinkContentAccessControlHandler",
|
* "access" = "Drupal\menu_link_content\MenuLinkContentAccessControlHandler",
|
||||||
* "form" = {
|
* "form" = {
|
||||||
|
|
@ -39,26 +37,34 @@ use Drupal\menu_link_content\MenuLinkContentInterface;
|
||||||
* admin_permission = "administer menu",
|
* admin_permission = "administer menu",
|
||||||
* base_table = "menu_link_content",
|
* base_table = "menu_link_content",
|
||||||
* data_table = "menu_link_content_data",
|
* data_table = "menu_link_content_data",
|
||||||
|
* revision_table = "menu_link_content_revision",
|
||||||
|
* revision_data_table = "menu_link_content_field_revision",
|
||||||
* translatable = TRUE,
|
* translatable = TRUE,
|
||||||
* entity_keys = {
|
* entity_keys = {
|
||||||
* "id" = "id",
|
* "id" = "id",
|
||||||
|
* "revision" = "revision_id",
|
||||||
* "label" = "title",
|
* "label" = "title",
|
||||||
* "langcode" = "langcode",
|
* "langcode" = "langcode",
|
||||||
* "uuid" = "uuid",
|
* "uuid" = "uuid",
|
||||||
* "bundle" = "bundle",
|
* "bundle" = "bundle",
|
||||||
* "published" = "enabled",
|
* "published" = "enabled",
|
||||||
* },
|
* },
|
||||||
|
* revision_metadata_keys = {
|
||||||
|
* "revision_user" = "revision_user",
|
||||||
|
* "revision_created" = "revision_created",
|
||||||
|
* "revision_log_message" = "revision_log_message",
|
||||||
|
* },
|
||||||
* links = {
|
* links = {
|
||||||
* "canonical" = "/admin/structure/menu/item/{menu_link_content}/edit",
|
* "canonical" = "/admin/structure/menu/item/{menu_link_content}/edit",
|
||||||
* "edit-form" = "/admin/structure/menu/item/{menu_link_content}/edit",
|
* "edit-form" = "/admin/structure/menu/item/{menu_link_content}/edit",
|
||||||
* "delete-form" = "/admin/structure/menu/item/{menu_link_content}/delete",
|
* "delete-form" = "/admin/structure/menu/item/{menu_link_content}/delete",
|
||||||
* }
|
* },
|
||||||
|
* constraints = {
|
||||||
|
* "MenuTreeHierarchy" = {}
|
||||||
|
* },
|
||||||
* )
|
* )
|
||||||
*/
|
*/
|
||||||
class MenuLinkContent extends ContentEntityBase implements MenuLinkContentInterface {
|
class MenuLinkContent extends EditorialContentEntityBase implements MenuLinkContentInterface {
|
||||||
|
|
||||||
use EntityChangedTrait;
|
|
||||||
use EntityPublishedTrait;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A flag for whether this entity is wrapped in a plugin instance.
|
* A flag for whether this entity is wrapped in a plugin instance.
|
||||||
|
|
@ -203,6 +209,11 @@ class MenuLinkContent extends ContentEntityBase implements MenuLinkContentInterf
|
||||||
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
|
public function postSave(EntityStorageInterface $storage, $update = TRUE) {
|
||||||
parent::postSave($storage, $update);
|
parent::postSave($storage, $update);
|
||||||
|
|
||||||
|
// Don't update the menu tree if a pending revision was saved.
|
||||||
|
if (!$this->isDefaultRevision()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
/** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
|
/** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
|
||||||
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
|
$menu_link_manager = \Drupal::service('plugin.manager.menu.link');
|
||||||
|
|
||||||
|
|
@ -277,6 +288,7 @@ class MenuLinkContent extends ContentEntityBase implements MenuLinkContentInterf
|
||||||
->setDescription(t('The text to be used for this link in the menu.'))
|
->setDescription(t('The text to be used for this link in the menu.'))
|
||||||
->setRequired(TRUE)
|
->setRequired(TRUE)
|
||||||
->setTranslatable(TRUE)
|
->setTranslatable(TRUE)
|
||||||
|
->setRevisionable(TRUE)
|
||||||
->setSetting('max_length', 255)
|
->setSetting('max_length', 255)
|
||||||
->setDisplayOptions('view', [
|
->setDisplayOptions('view', [
|
||||||
'label' => 'hidden',
|
'label' => 'hidden',
|
||||||
|
|
@ -293,6 +305,7 @@ class MenuLinkContent extends ContentEntityBase implements MenuLinkContentInterf
|
||||||
->setLabel(t('Description'))
|
->setLabel(t('Description'))
|
||||||
->setDescription(t('Shown when hovering over the menu link.'))
|
->setDescription(t('Shown when hovering over the menu link.'))
|
||||||
->setTranslatable(TRUE)
|
->setTranslatable(TRUE)
|
||||||
|
->setRevisionable(TRUE)
|
||||||
->setSetting('max_length', 255)
|
->setSetting('max_length', 255)
|
||||||
->setDisplayOptions('view', [
|
->setDisplayOptions('view', [
|
||||||
'label' => 'hidden',
|
'label' => 'hidden',
|
||||||
|
|
@ -313,6 +326,7 @@ class MenuLinkContent extends ContentEntityBase implements MenuLinkContentInterf
|
||||||
$fields['link'] = BaseFieldDefinition::create('link')
|
$fields['link'] = BaseFieldDefinition::create('link')
|
||||||
->setLabel(t('Link'))
|
->setLabel(t('Link'))
|
||||||
->setDescription(t('The location this menu link points to.'))
|
->setDescription(t('The location this menu link points to.'))
|
||||||
|
->setRevisionable(TRUE)
|
||||||
->setRequired(TRUE)
|
->setRequired(TRUE)
|
||||||
->setSettings([
|
->setSettings([
|
||||||
'link_type' => LinkItemInterface::LINK_GENERIC,
|
'link_type' => LinkItemInterface::LINK_GENERIC,
|
||||||
|
|
@ -326,7 +340,8 @@ class MenuLinkContent extends ContentEntityBase implements MenuLinkContentInterf
|
||||||
$fields['external'] = BaseFieldDefinition::create('boolean')
|
$fields['external'] = BaseFieldDefinition::create('boolean')
|
||||||
->setLabel(t('External'))
|
->setLabel(t('External'))
|
||||||
->setDescription(t('A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).'))
|
->setDescription(t('A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).'))
|
||||||
->setDefaultValue(FALSE);
|
->setDefaultValue(FALSE)
|
||||||
|
->setRevisionable(TRUE);
|
||||||
|
|
||||||
$fields['rediscover'] = BaseFieldDefinition::create('boolean')
|
$fields['rediscover'] = BaseFieldDefinition::create('boolean')
|
||||||
->setLabel(t('Indicates whether the menu link should be rediscovered'))
|
->setLabel(t('Indicates whether the menu link should be rediscovered'))
|
||||||
|
|
@ -365,7 +380,6 @@ class MenuLinkContent extends ContentEntityBase implements MenuLinkContentInterf
|
||||||
$fields['enabled']->setLabel(t('Enabled'));
|
$fields['enabled']->setLabel(t('Enabled'));
|
||||||
$fields['enabled']->setDescription(t('A flag for whether the link should be enabled in menus or hidden.'));
|
$fields['enabled']->setDescription(t('A flag for whether the link should be enabled in menus or hidden.'));
|
||||||
$fields['enabled']->setTranslatable(FALSE);
|
$fields['enabled']->setTranslatable(FALSE);
|
||||||
$fields['enabled']->setRevisionable(FALSE);
|
|
||||||
$fields['enabled']->setDisplayOptions('view', [
|
$fields['enabled']->setDisplayOptions('view', [
|
||||||
'label' => 'hidden',
|
'label' => 'hidden',
|
||||||
'type' => 'boolean',
|
'type' => 'boolean',
|
||||||
|
|
@ -383,7 +397,14 @@ class MenuLinkContent extends ContentEntityBase implements MenuLinkContentInterf
|
||||||
$fields['changed'] = BaseFieldDefinition::create('changed')
|
$fields['changed'] = BaseFieldDefinition::create('changed')
|
||||||
->setLabel(t('Changed'))
|
->setLabel(t('Changed'))
|
||||||
->setDescription(t('The time that the menu link was last edited.'))
|
->setDescription(t('The time that the menu link was last edited.'))
|
||||||
->setTranslatable(TRUE);
|
->setTranslatable(TRUE)
|
||||||
|
->setRevisionable(TRUE);
|
||||||
|
|
||||||
|
// @todo Keep this field hidden until we have a revision UI for menu links.
|
||||||
|
// @see https://www.drupal.org/project/drupal/issues/2350939
|
||||||
|
$fields['revision_log_message']->setDisplayOptions('form', [
|
||||||
|
'region' => 'hidden',
|
||||||
|
]);
|
||||||
|
|
||||||
return $fields;
|
return $fields;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -127,19 +127,11 @@ class MenuLinkContentForm extends ContentEntityForm {
|
||||||
public function save(array $form, FormStateInterface $form_state) {
|
public function save(array $form, FormStateInterface $form_state) {
|
||||||
// The entity is rebuilt in parent::submit().
|
// The entity is rebuilt in parent::submit().
|
||||||
$menu_link = $this->entity;
|
$menu_link = $this->entity;
|
||||||
$saved = $menu_link->save();
|
$menu_link->save();
|
||||||
|
|
||||||
if ($saved) {
|
$this->messenger()->addStatus($this->t('The menu link has been saved.'));
|
||||||
$this->messenger()->addStatus($this->t('The menu link has been saved.'));
|
|
||||||
$form_state->setRedirect(
|
$form_state->setRedirectUrl($menu_link->toUrl('canonical'));
|
||||||
'entity.menu_link_content.canonical',
|
|
||||||
['menu_link_content' => $menu_link->id()]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$this->messenger()->addError($this->t('There was an error saving the menu link.'));
|
|
||||||
$form_state->setRebuild();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,12 @@ namespace Drupal\menu_link_content;
|
||||||
use Drupal\Core\Entity\EntityChangedInterface;
|
use Drupal\Core\Entity\EntityChangedInterface;
|
||||||
use Drupal\Core\Entity\ContentEntityInterface;
|
use Drupal\Core\Entity\ContentEntityInterface;
|
||||||
use Drupal\Core\Entity\EntityPublishedInterface;
|
use Drupal\Core\Entity\EntityPublishedInterface;
|
||||||
|
use Drupal\Core\Entity\RevisionLogInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines an interface for custom menu links.
|
* Defines an interface for custom menu links.
|
||||||
*/
|
*/
|
||||||
interface MenuLinkContentInterface extends ContentEntityInterface, EntityChangedInterface, EntityPublishedInterface {
|
interface MenuLinkContentInterface extends ContentEntityInterface, EntityChangedInterface, EntityPublishedInterface, RevisionLogInterface {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flags this instance as being wrapped in a menu link plugin instance.
|
* Flags this instance as being wrapped in a menu link plugin instance.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\menu_link_content;
|
||||||
|
|
||||||
|
use Drupal\Core\Entity\Sql\SqlContentEntityStorage;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Storage handler for menu_link_content entities.
|
||||||
|
*/
|
||||||
|
class MenuLinkContentStorage extends SqlContentEntityStorage implements MenuLinkContentStorageInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function getMenuLinkIdsWithPendingRevisions() {
|
||||||
|
$table_mapping = $this->getTableMapping();
|
||||||
|
$id_field = $table_mapping->getColumnNames($this->entityType->getKey('id'))['value'];
|
||||||
|
$revision_field = $table_mapping->getColumnNames($this->entityType->getKey('revision'))['value'];
|
||||||
|
$rta_field = $table_mapping->getColumnNames($this->entityType->getKey('revision_translation_affected'))['value'];
|
||||||
|
$langcode_field = $table_mapping->getColumnNames($this->entityType->getKey('langcode'))['value'];
|
||||||
|
$revision_default_field = $table_mapping->getColumnNames($this->entityType->getRevisionMetadataKey('revision_default'))['value'];
|
||||||
|
|
||||||
|
$query = $this->database->select($this->getRevisionDataTable(), 'mlfr');
|
||||||
|
$query->fields('mlfr', [$id_field]);
|
||||||
|
$query->addExpression("MAX(mlfr.$revision_field)", $revision_field);
|
||||||
|
|
||||||
|
$query->join($this->getRevisionTable(), 'mlr', "mlfr.$revision_field = mlr.$revision_field AND mlr.$revision_default_field = 0");
|
||||||
|
|
||||||
|
$inner_select = $this->database->select($this->getRevisionDataTable(), 't');
|
||||||
|
$inner_select->condition("t.$rta_field", '1');
|
||||||
|
$inner_select->fields('t', [$id_field, $langcode_field]);
|
||||||
|
$inner_select->addExpression("MAX(t.$revision_field)", $revision_field);
|
||||||
|
$inner_select
|
||||||
|
->groupBy("t.$id_field")
|
||||||
|
->groupBy("t.$langcode_field");
|
||||||
|
|
||||||
|
$query->join($inner_select, 'mr', "mlfr.$revision_field = mr.$revision_field AND mlfr.$langcode_field = mr.$langcode_field");
|
||||||
|
|
||||||
|
$query->groupBy("mlfr.$id_field");
|
||||||
|
|
||||||
|
return $query->execute()->fetchAllKeyed(1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\menu_link_content;
|
||||||
|
|
||||||
|
use Drupal\Core\Entity\ContentEntityStorageInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines an interface for menu_link_content entity storage classes.
|
||||||
|
*/
|
||||||
|
interface MenuLinkContentStorageInterface extends ContentEntityStorageInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a list of menu link IDs with pending revisions.
|
||||||
|
*
|
||||||
|
* @return int[]
|
||||||
|
* An array of menu link IDs which have pending revisions, keyed by their
|
||||||
|
* revision IDs.
|
||||||
|
*
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
public function getMenuLinkIdsWithPendingRevisions();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\menu_link_content\Plugin\Validation\Constraint;
|
||||||
|
|
||||||
|
use Drupal\Core\Entity\Plugin\Validation\Constraint\CompositeConstraintBase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validation constraint for changing the menu hierarchy in pending revisions.
|
||||||
|
*
|
||||||
|
* @Constraint(
|
||||||
|
* id = "MenuTreeHierarchy",
|
||||||
|
* label = @Translation("Menu tree hierarchy.", context = "Validation"),
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
class MenuTreeHierarchyConstraint extends CompositeConstraintBase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default violation message.
|
||||||
|
*
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
public $message = 'You can only change the hierarchy for the <em>published</em> version of this menu link.';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function coversFields() {
|
||||||
|
return ['parent', 'weight'];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Drupal\menu_link_content\Plugin\Validation\Constraint;
|
||||||
|
|
||||||
|
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
|
||||||
|
use Drupal\Core\Entity\EntityTypeManagerInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
use Symfony\Component\Validator\Constraint;
|
||||||
|
use Symfony\Component\Validator\ConstraintValidator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constraint validator for changing menu link parents in pending revisions.
|
||||||
|
*/
|
||||||
|
class MenuTreeHierarchyConstraintValidator extends ConstraintValidator implements ContainerInjectionInterface {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The entity type manager.
|
||||||
|
*
|
||||||
|
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
|
||||||
|
*/
|
||||||
|
private $entityTypeManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new MenuTreeHierarchyConstraintValidator instance.
|
||||||
|
*
|
||||||
|
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
|
||||||
|
* The entity type manager.
|
||||||
|
*/
|
||||||
|
public function __construct(EntityTypeManagerInterface $entity_type_manager) {
|
||||||
|
$this->entityTypeManager = $entity_type_manager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public static function create(ContainerInterface $container) {
|
||||||
|
return new static(
|
||||||
|
$container->get('entity_type.manager')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritdoc}
|
||||||
|
*/
|
||||||
|
public function validate($entity, Constraint $constraint) {
|
||||||
|
if ($entity && !$entity->isNew() && !$entity->isDefaultRevision()) {
|
||||||
|
$original = $this->entityTypeManager->getStorage($entity->getEntityTypeId())->loadUnchanged($entity->id());
|
||||||
|
|
||||||
|
// Ensure that empty items do not affect the comparison checks below.
|
||||||
|
// @todo Remove this filtering when
|
||||||
|
// https://www.drupal.org/project/drupal/issues/3039031 is fixed.
|
||||||
|
$entity->parent->filterEmptyItems();
|
||||||
|
if (($entity->parent->isEmpty() !== $original->parent->isEmpty()) || !$entity->parent->equals($original->parent)) {
|
||||||
|
$this->context->buildViolation($constraint->message)
|
||||||
|
->atPath('menu_parent')
|
||||||
|
->addViolation();
|
||||||
|
}
|
||||||
|
if (!$entity->weight->equals($original->weight)) {
|
||||||
|
$this->context->buildViolation($constraint->message)
|
||||||
|
->atPath('weight')
|
||||||
|
->addViolation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -120,6 +120,11 @@ abstract class MenuLinkContentResourceTestBase extends EntityResourceTestBase {
|
||||||
'value' => 1,
|
'value' => 1,
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
'revision_id' => [
|
||||||
|
[
|
||||||
|
'value' => 1,
|
||||||
|
],
|
||||||
|
],
|
||||||
'title' => [
|
'title' => [
|
||||||
[
|
[
|
||||||
'value' => 'Llama Gabilondo',
|
'value' => 'Llama Gabilondo',
|
||||||
|
|
@ -191,6 +196,16 @@ abstract class MenuLinkContentResourceTestBase extends EntityResourceTestBase {
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
'parent' => [],
|
'parent' => [],
|
||||||
|
'revision_created' => [
|
||||||
|
$this->formatExpectedTimestampItemValues((int) $this->entity->getRevisionCreationTime()),
|
||||||
|
],
|
||||||
|
'revision_user' => [],
|
||||||
|
'revision_log_message' => [],
|
||||||
|
'revision_translation_affected' => [
|
||||||
|
[
|
||||||
|
'value' => TRUE,
|
||||||
|
],
|
||||||
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,6 +59,49 @@ class MenuLinkContentUpdateTest extends UpdatePathTestBase {
|
||||||
$this->assertTrue($menu_link->isPublished());
|
$this->assertTrue($menu_link->isPublished());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests the conversion of custom menu links to be revisionable.
|
||||||
|
*
|
||||||
|
* @see menu_link_content_post_update_make_menu_link_content_revisionable()
|
||||||
|
*/
|
||||||
|
public function testConversionToRevisionable() {
|
||||||
|
$entity_type = \Drupal::entityDefinitionUpdateManager()->getEntityType('menu_link_content');
|
||||||
|
$this->assertFalse($entity_type->isRevisionable());
|
||||||
|
|
||||||
|
$this->runUpdates();
|
||||||
|
|
||||||
|
$entity_type = \Drupal::entityDefinitionUpdateManager()->getEntityType('menu_link_content');
|
||||||
|
$this->assertTrue($entity_type->isRevisionable());
|
||||||
|
|
||||||
|
// Log in as user 1.
|
||||||
|
$account = User::load(1);
|
||||||
|
$account->passRaw = 'drupal';
|
||||||
|
$this->drupalLogin($account);
|
||||||
|
|
||||||
|
// Make sure our custom menu link exists.
|
||||||
|
$assert_session = $this->assertSession();
|
||||||
|
$this->drupalGet('admin/structure/menu/item/1/edit');
|
||||||
|
$assert_session->checkboxChecked('edit-enabled-value');
|
||||||
|
|
||||||
|
// Check that custom menu links can be created, saved and then loaded.
|
||||||
|
$storage = \Drupal::entityTypeManager()->getStorage('menu_link_content');
|
||||||
|
/** @var \Drupal\menu_link_content\Entity\MenuLinkContent $menu_link */
|
||||||
|
$menu_link = $storage->create([
|
||||||
|
'menu_name' => 'main',
|
||||||
|
'link' => 'route:user.page',
|
||||||
|
'title' => 'Pineapple',
|
||||||
|
]);
|
||||||
|
$menu_link->save();
|
||||||
|
|
||||||
|
$storage->resetCache();
|
||||||
|
$menu_link = $storage->loadRevision($menu_link->getRevisionId());
|
||||||
|
|
||||||
|
$this->assertEquals('main', $menu_link->getMenuName());
|
||||||
|
$this->assertEquals('Pineapple', $menu_link->label());
|
||||||
|
$this->assertEquals('route:user.page', $menu_link->link->uri);
|
||||||
|
$this->assertTrue($menu_link->isPublished());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class MenuLinkContentDeriverTest extends KernelTestBase {
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public static $modules = ['menu_link_content', 'link', 'system', 'menu_link_content_dynamic_route'];
|
public static $modules = ['menu_link_content', 'link', 'system', 'menu_link_content_dynamic_route', 'user'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
|
|
@ -26,6 +26,7 @@ class MenuLinkContentDeriverTest extends KernelTestBase {
|
||||||
protected function setUp() {
|
protected function setUp() {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->installEntitySchema('user');
|
||||||
$this->installEntitySchema('menu_link_content');
|
$this->installEntitySchema('menu_link_content');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -310,4 +310,114 @@ class MenuLinksTest extends KernelTestBase {
|
||||||
$this->assertEqual(count($menu_links), 0);
|
$this->assertEqual(count($menu_links), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests handling of pending revisions.
|
||||||
|
*
|
||||||
|
* @coversDefaultClass \Drupal\menu_link_content\Plugin\Validation\Constraint\MenuTreeHierarchyConstraintValidator
|
||||||
|
*/
|
||||||
|
public function testPendingRevisions() {
|
||||||
|
/** @var \Drupal\Core\Entity\RevisionableStorageInterface $storage */
|
||||||
|
$storage = \Drupal::entityTypeManager()->getStorage('menu_link_content');
|
||||||
|
|
||||||
|
// Add new menu items in a hierarchy.
|
||||||
|
$default_root_1_title = $this->randomMachineName(8);
|
||||||
|
$root_1 = $storage->create([
|
||||||
|
'title' => $default_root_1_title,
|
||||||
|
'link' => [['uri' => 'internal:/#root_1']],
|
||||||
|
'menu_name' => 'menu_test',
|
||||||
|
]);
|
||||||
|
$root_1->save();
|
||||||
|
$default_child1_title = $this->randomMachineName(8);
|
||||||
|
$child1 = $storage->create([
|
||||||
|
'title' => $default_child1_title,
|
||||||
|
'link' => [['uri' => 'internal:/#child1']],
|
||||||
|
'menu_name' => 'menu_test',
|
||||||
|
'parent' => 'menu_link_content:' . $root_1->uuid(),
|
||||||
|
]);
|
||||||
|
$child1->save();
|
||||||
|
$default_child2_title = $this->randomMachineName(8);
|
||||||
|
$child2 = $storage->create([
|
||||||
|
'title' => $default_child2_title,
|
||||||
|
'link' => [['uri' => 'internal:/#child2']],
|
||||||
|
'menu_name' => 'menu_test',
|
||||||
|
'parent' => 'menu_link_content:' . $child1->uuid(),
|
||||||
|
]);
|
||||||
|
$child2->save();
|
||||||
|
$default_root_2_title = $this->randomMachineName(8);
|
||||||
|
$root_2 = $storage->create([
|
||||||
|
'title' => $default_root_2_title,
|
||||||
|
'link' => [['uri' => 'internal:/#root_2']],
|
||||||
|
'menu_name' => 'menu_test',
|
||||||
|
]);
|
||||||
|
$root_2->save();
|
||||||
|
|
||||||
|
// Check that changing the title and the link in a pending revision is
|
||||||
|
// allowed.
|
||||||
|
$pending_child1_title = $this->randomMachineName(8);
|
||||||
|
$child1_pending_revision = $storage->createRevision($child1, FALSE);
|
||||||
|
$child1_pending_revision->set('title', $pending_child1_title);
|
||||||
|
$child1_pending_revision->set('link', [['uri' => 'internal:/#test']]);
|
||||||
|
|
||||||
|
$violations = $child1_pending_revision->validate();
|
||||||
|
$this->assertEmpty($violations);
|
||||||
|
$child1_pending_revision->save();
|
||||||
|
|
||||||
|
$storage->resetCache();
|
||||||
|
$child1_pending_revision = $storage->loadRevision($child1_pending_revision->getRevisionId());
|
||||||
|
$this->assertFalse($child1_pending_revision->isDefaultRevision());
|
||||||
|
$this->assertEquals($pending_child1_title, $child1_pending_revision->getTitle());
|
||||||
|
$this->assertEquals('/#test', $child1_pending_revision->getUrlObject()->toString());
|
||||||
|
|
||||||
|
// Check that saving a pending revision does not affect the menu tree.
|
||||||
|
$menu_tree = \Drupal::menuTree()->load('menu_test', new MenuTreeParameters());
|
||||||
|
$parent_link = reset($menu_tree);
|
||||||
|
$this->assertEquals($default_root_1_title, $parent_link->link->getTitle());
|
||||||
|
$this->assertEquals('/#root_1', $parent_link->link->getUrlObject()->toString());
|
||||||
|
|
||||||
|
$child1_link = reset($parent_link->subtree);
|
||||||
|
$this->assertEquals($default_child1_title, $child1_link->link->getTitle());
|
||||||
|
$this->assertEquals('/#child1', $child1_link->link->getUrlObject()->toString());
|
||||||
|
|
||||||
|
$child2_link = reset($child1_link->subtree);
|
||||||
|
$this->assertEquals($default_child2_title, $child2_link->link->getTitle());
|
||||||
|
$this->assertEquals('/#child2', $child2_link->link->getUrlObject()->toString());
|
||||||
|
|
||||||
|
// Check that changing the parent in a pending revision is not allowed.
|
||||||
|
$child2_pending_revision = $storage->createRevision($child2, FALSE);
|
||||||
|
$child2_pending_revision->set('parent', $child1->id());
|
||||||
|
$violations = $child2_pending_revision->validate();
|
||||||
|
$this->assertCount(1, $violations);
|
||||||
|
$this->assertEquals('You can only change the hierarchy for the <em>published</em> version of this menu link.', $violations[0]->getMessage());
|
||||||
|
$this->assertEquals('menu_parent', $violations[0]->getPropertyPath());
|
||||||
|
|
||||||
|
// Check that changing the weight in a pending revision is not allowed.
|
||||||
|
$child2_pending_revision = $storage->createRevision($child2, FALSE);
|
||||||
|
$child2_pending_revision->set('weight', 500);
|
||||||
|
$violations = $child2_pending_revision->validate();
|
||||||
|
$this->assertCount(1, $violations);
|
||||||
|
$this->assertEquals('You can only change the hierarchy for the <em>published</em> version of this menu link.', $violations[0]->getMessage());
|
||||||
|
$this->assertEquals('weight', $violations[0]->getPropertyPath());
|
||||||
|
|
||||||
|
// Check that changing both the parent and the weight in a pending revision
|
||||||
|
// is not allowed.
|
||||||
|
$child2_pending_revision = $storage->createRevision($child2, FALSE);
|
||||||
|
$child2_pending_revision->set('parent', $child1->id());
|
||||||
|
$child2_pending_revision->set('weight', 500);
|
||||||
|
$violations = $child2_pending_revision->validate();
|
||||||
|
$this->assertCount(2, $violations);
|
||||||
|
$this->assertEquals('You can only change the hierarchy for the <em>published</em> version of this menu link.', $violations[0]->getMessage());
|
||||||
|
$this->assertEquals('You can only change the hierarchy for the <em>published</em> version of this menu link.', $violations[1]->getMessage());
|
||||||
|
$this->assertEquals('menu_parent', $violations[0]->getPropertyPath());
|
||||||
|
$this->assertEquals('weight', $violations[1]->getPropertyPath());
|
||||||
|
|
||||||
|
// Check that changing the parent of a term which didn't have a parent
|
||||||
|
// initially is not allowed in a pending revision.
|
||||||
|
$root_2_pending_revision = $storage->createRevision($root_2, FALSE);
|
||||||
|
$root_2_pending_revision->set('parent', $root_1->id());
|
||||||
|
$violations = $root_2_pending_revision->validate();
|
||||||
|
$this->assertCount(1, $violations);
|
||||||
|
$this->assertEquals('You can only change the hierarchy for the <em>published</em> version of this menu link.', $violations[0]->getMessage());
|
||||||
|
$this->assertEquals('menu_parent', $violations[0]->getPropertyPath());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ class PathAliasMenuLinkContentTest extends KernelTestBase {
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
*/
|
*/
|
||||||
public static $modules = ['menu_link_content', 'system', 'link', 'test_page_test'];
|
public static $modules = ['menu_link_content', 'system', 'link', 'test_page_test', 'user'];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritdoc}
|
* {@inheritdoc}
|
||||||
|
|
@ -26,6 +26,7 @@ class PathAliasMenuLinkContentTest extends KernelTestBase {
|
||||||
protected function setUp() {
|
protected function setUp() {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->installEntitySchema('user');
|
||||||
$this->installEntitySchema('menu_link_content');
|
$this->installEntitySchema('menu_link_content');
|
||||||
|
|
||||||
// Ensure that the weight of module_link_content is higher than system.
|
// Ensure that the weight of module_link_content is higher than system.
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,8 @@ use Drupal\Core\Menu\MenuTreeParameters;
|
||||||
use Drupal\Core\Render\Element;
|
use Drupal\Core\Render\Element;
|
||||||
use Drupal\Core\Url;
|
use Drupal\Core\Url;
|
||||||
use Drupal\Core\Utility\LinkGeneratorInterface;
|
use Drupal\Core\Utility\LinkGeneratorInterface;
|
||||||
|
use Drupal\menu_link_content\MenuLinkContentStorageInterface;
|
||||||
|
use Drupal\menu_link_content\Plugin\Menu\MenuLinkContent;
|
||||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -45,6 +47,13 @@ class MenuForm extends EntityForm {
|
||||||
*/
|
*/
|
||||||
protected $linkGenerator;
|
protected $linkGenerator;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The menu_link_content storage handler.
|
||||||
|
*
|
||||||
|
* @var \Drupal\menu_link_content\MenuLinkContentStorageInterface
|
||||||
|
*/
|
||||||
|
protected $menuLinkContentStorage;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The overview tree form.
|
* The overview tree form.
|
||||||
*
|
*
|
||||||
|
|
@ -61,11 +70,14 @@ class MenuForm extends EntityForm {
|
||||||
* The menu tree service.
|
* The menu tree service.
|
||||||
* @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator
|
* @param \Drupal\Core\Utility\LinkGeneratorInterface $link_generator
|
||||||
* The link generator.
|
* The link generator.
|
||||||
|
* @param \Drupal\menu_link_content\MenuLinkContentStorageInterface $menu_link_content_storage
|
||||||
|
* The menu link content storage handler.
|
||||||
*/
|
*/
|
||||||
public function __construct(MenuLinkManagerInterface $menu_link_manager, MenuLinkTreeInterface $menu_tree, LinkGeneratorInterface $link_generator) {
|
public function __construct(MenuLinkManagerInterface $menu_link_manager, MenuLinkTreeInterface $menu_tree, LinkGeneratorInterface $link_generator, MenuLinkContentStorageInterface $menu_link_content_storage) {
|
||||||
$this->menuLinkManager = $menu_link_manager;
|
$this->menuLinkManager = $menu_link_manager;
|
||||||
$this->menuTree = $menu_tree;
|
$this->menuTree = $menu_tree;
|
||||||
$this->linkGenerator = $link_generator;
|
$this->linkGenerator = $link_generator;
|
||||||
|
$this->menuLinkContentStorage = $menu_link_content_storage;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -75,7 +87,8 @@ class MenuForm extends EntityForm {
|
||||||
return new static(
|
return new static(
|
||||||
$container->get('plugin.manager.menu.link'),
|
$container->get('plugin.manager.menu.link'),
|
||||||
$container->get('menu.link_tree'),
|
$container->get('menu.link_tree'),
|
||||||
$container->get('link_generator')
|
$container->get('link_generator'),
|
||||||
|
$container->get('entity_type.manager')->getStorage('menu_link_content')
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,16 +286,52 @@ class MenuForm extends EntityForm {
|
||||||
]),
|
]),
|
||||||
]);
|
]);
|
||||||
$links = $this->buildOverviewTreeForm($tree, $delta);
|
$links = $this->buildOverviewTreeForm($tree, $delta);
|
||||||
|
|
||||||
|
// Get the menu links which have pending revisions, and disable the
|
||||||
|
// tabledrag if there are any.
|
||||||
|
$edited_ids = array_filter(array_map(function ($element) {
|
||||||
|
return is_array($element) && isset($element['#item']) && $element['#item']->link instanceof MenuLinkContent ? $element['#item']->link->getMetaData()['entity_id'] : NULL;
|
||||||
|
}, $links));
|
||||||
|
$pending_menu_link_ids = array_intersect($this->menuLinkContentStorage->getMenuLinkIdsWithPendingRevisions(), $edited_ids);
|
||||||
|
if ($pending_menu_link_ids) {
|
||||||
|
$form['help'] = [
|
||||||
|
'#type' => 'container',
|
||||||
|
'message' => [
|
||||||
|
'#markup' => $this->formatPlural(
|
||||||
|
count($pending_menu_link_ids),
|
||||||
|
'%capital_name contains 1 menu link with pending revisions. Manipulation of a menu tree having links with pending revisions is not supported, but you can re-enable manipulation by getting each menu link to a published state.',
|
||||||
|
'%capital_name contains @count menu links with pending revisions. Manipulation of a menu tree having links with pending revisions is not supported, but you can re-enable manipulation by getting each menu link to a published state.',
|
||||||
|
[
|
||||||
|
'%capital_name' => $this->entity->label(),
|
||||||
|
]
|
||||||
|
),
|
||||||
|
],
|
||||||
|
'#attributes' => ['class' => ['messages', 'messages--warning']],
|
||||||
|
'#weight' => -10,
|
||||||
|
];
|
||||||
|
|
||||||
|
unset($form['links']['#tabledrag']);
|
||||||
|
unset($form['links']['#header'][2]);
|
||||||
|
}
|
||||||
|
|
||||||
foreach (Element::children($links) as $id) {
|
foreach (Element::children($links) as $id) {
|
||||||
if (isset($links[$id]['#item'])) {
|
if (isset($links[$id]['#item'])) {
|
||||||
$element = $links[$id];
|
$element = $links[$id];
|
||||||
|
|
||||||
|
$is_pending_menu_link = isset($element['#item']->link->getMetaData()['entity_id'])
|
||||||
|
&& in_array($element['#item']->link->getMetaData()['entity_id'], $pending_menu_link_ids);
|
||||||
|
|
||||||
$form['links'][$id]['#item'] = $element['#item'];
|
$form['links'][$id]['#item'] = $element['#item'];
|
||||||
|
|
||||||
// TableDrag: Mark the table row as draggable.
|
// TableDrag: Mark the table row as draggable.
|
||||||
$form['links'][$id]['#attributes'] = $element['#attributes'];
|
$form['links'][$id]['#attributes'] = $element['#attributes'];
|
||||||
$form['links'][$id]['#attributes']['class'][] = 'draggable';
|
$form['links'][$id]['#attributes']['class'][] = 'draggable';
|
||||||
|
|
||||||
|
if ($is_pending_menu_link) {
|
||||||
|
$form['links'][$id]['#attributes']['class'][] = 'color-warning';
|
||||||
|
$form['links'][$id]['#attributes']['class'][] = 'menu-link-content--pending-revision';
|
||||||
|
}
|
||||||
|
|
||||||
// TableDrag: Sort the table row according to its existing/configured weight.
|
// TableDrag: Sort the table row according to its existing/configured weight.
|
||||||
$form['links'][$id]['#weight'] = $element['#item']->link->getWeight();
|
$form['links'][$id]['#weight'] = $element['#item']->link->getWeight();
|
||||||
|
|
||||||
|
|
@ -301,7 +350,14 @@ class MenuForm extends EntityForm {
|
||||||
$form['links'][$id]['enabled'] = $element['enabled'];
|
$form['links'][$id]['enabled'] = $element['enabled'];
|
||||||
$form['links'][$id]['enabled']['#wrapper_attributes']['class'] = ['checkbox', 'menu-enabled'];
|
$form['links'][$id]['enabled']['#wrapper_attributes']['class'] = ['checkbox', 'menu-enabled'];
|
||||||
|
|
||||||
$form['links'][$id]['weight'] = $element['weight'];
|
// Disallow changing the publishing status of a pending revision.
|
||||||
|
if ($is_pending_menu_link) {
|
||||||
|
$form['links'][$id]['enabled']['#access'] = FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$pending_menu_link_ids) {
|
||||||
|
$form['links'][$id]['weight'] = $element['weight'];
|
||||||
|
}
|
||||||
|
|
||||||
// Operations (dropbutton) column.
|
// Operations (dropbutton) column.
|
||||||
$form['links'][$id]['operations'] = $element['operations'];
|
$form['links'][$id]['operations'] = $element['operations'];
|
||||||
|
|
@ -463,7 +519,7 @@ class MenuForm extends EntityForm {
|
||||||
$updated_values = [];
|
$updated_values = [];
|
||||||
// Update any fields that have changed in this menu item.
|
// Update any fields that have changed in this menu item.
|
||||||
foreach ($fields as $field) {
|
foreach ($fields as $field) {
|
||||||
if ($element[$field]['#value'] != $element[$field]['#default_value']) {
|
if (isset($element[$field]['#value']) && $element[$field]['#value'] != $element[$field]['#default_value']) {
|
||||||
$updated_values[$field] = $element[$field]['#value'];
|
$updated_values[$field] = $element[$field]['#value'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -987,4 +987,48 @@ class MenuUiTest extends BrowserTestBase {
|
||||||
$block->save();
|
$block->save();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that menu links with pending revisions can not be re-parented.
|
||||||
|
*/
|
||||||
|
public function testMenuUiWithPendingRevisions() {
|
||||||
|
$this->drupalLogin($this->adminUser);
|
||||||
|
$assert_session = $this->assertSession();
|
||||||
|
|
||||||
|
// Add four menu links in two separate menus.
|
||||||
|
$menu_1 = $this->addCustomMenu();
|
||||||
|
$root_1 = $this->addMenuLink('', '/', $menu_1->id());
|
||||||
|
$this->addMenuLink($root_1->getPluginId(), '/', $menu_1->id());
|
||||||
|
|
||||||
|
$menu_2 = $this->addCustomMenu();
|
||||||
|
$root_2 = $this->addMenuLink('', '/', $menu_2->id());
|
||||||
|
$child_2 = $this->addMenuLink($root_2->getPluginId(), '/', $menu_2->id());
|
||||||
|
|
||||||
|
$this->drupalGet('admin/structure/menu/manage/' . $menu_2->id());
|
||||||
|
$assert_session->pageTextNotContains($menu_2->label() . ' contains 1 menu link with pending revisions. Manipulation of a menu tree having links with pending revisions is not supported, but you can re-enable manipulation by getting each menu link to a published state.');
|
||||||
|
|
||||||
|
$this->drupalGet('admin/structure/menu/manage/' . $menu_1->id());
|
||||||
|
$assert_session->pageTextNotContains($menu_1->label() . ' contains 1 menu link with pending revisions. Manipulation of a menu tree having links with pending revisions is not supported, but you can re-enable manipulation by getting each menu link to a published state.');
|
||||||
|
|
||||||
|
// Create a pending revision for one of the menu links and check that it can
|
||||||
|
// no longer be re-parented in the UI. We can not create pending revisions
|
||||||
|
// through the UI yet so we have to use API calls.
|
||||||
|
\Drupal::entityTypeManager()->getStorage('menu_link_content')->createRevision($child_2, FALSE)->save();
|
||||||
|
|
||||||
|
$this->drupalGet('admin/structure/menu/manage/' . $menu_2->id());
|
||||||
|
$assert_session->pageTextContains($menu_2->label() . ' contains 1 menu link with pending revisions. Manipulation of a menu tree having links with pending revisions is not supported, but you can re-enable manipulation by getting each menu link to a published state.');
|
||||||
|
|
||||||
|
// Check that the 'Enabled' checkbox is hidden for a pending revision.
|
||||||
|
$this->assertNotEmpty($this->cssSelect('input[name="links[menu_plugin_id:' . $root_2->getPluginId() . '][enabled]"]'), 'The publishing status of a default revision can be changed.');
|
||||||
|
$this->assertEmpty($this->cssSelect('input[name="links[menu_plugin_id:' . $child_2->getPluginId() . '][enabled]"]'), 'The publishing status of a pending revision can not be changed.');
|
||||||
|
|
||||||
|
$this->drupalGet('admin/structure/menu/manage/' . $menu_1->id());
|
||||||
|
$assert_session->pageTextNotContains($menu_1->label() . ' contains 1 menu link with pending revisions. Manipulation of a menu tree having links with pending revisions is not supported, but you can re-enable manipulation by getting each menu link to a published state.');
|
||||||
|
|
||||||
|
// Check that the menu overview form can be saved without errors when there
|
||||||
|
// are pending revisions.
|
||||||
|
$this->drupalPostForm('admin/structure/menu/manage/' . $menu_2->id(), [], 'Save');
|
||||||
|
$errors = $this->xpath('//div[contains(@class, "messages--error")]');
|
||||||
|
$this->assertFalse($errors, 'Menu overview form saved without errors.');
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -239,7 +239,7 @@ EOS;
|
||||||
*/
|
*/
|
||||||
public function testEnableModulesFixedList() {
|
public function testEnableModulesFixedList() {
|
||||||
// Install system module.
|
// Install system module.
|
||||||
$this->container->get('module_installer')->install(['system', 'menu_link_content']);
|
$this->container->get('module_installer')->install(['system', 'user', 'menu_link_content']);
|
||||||
$entity_manager = \Drupal::entityManager();
|
$entity_manager = \Drupal::entityManager();
|
||||||
|
|
||||||
// entity_test is loaded via $modules; its entity type should exist.
|
// entity_test is loaded via $modules; its entity type should exist.
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,8 @@ class DbDumpTest extends KernelTestBase {
|
||||||
'key_value_expire',
|
'key_value_expire',
|
||||||
'menu_link_content',
|
'menu_link_content',
|
||||||
'menu_link_content_data',
|
'menu_link_content_data',
|
||||||
|
'menu_link_content_revision',
|
||||||
|
'menu_link_content_field_revision',
|
||||||
'sequences',
|
'sequences',
|
||||||
'sessions',
|
'sessions',
|
||||||
'url_alias',
|
'url_alias',
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ class MenuLinkTreeTest extends KernelTestBase {
|
||||||
'menu_link_content',
|
'menu_link_content',
|
||||||
'field',
|
'field',
|
||||||
'link',
|
'link',
|
||||||
|
'user',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -49,6 +50,7 @@ class MenuLinkTreeTest extends KernelTestBase {
|
||||||
protected function setUp() {
|
protected function setUp() {
|
||||||
parent::setUp();
|
parent::setUp();
|
||||||
\Drupal::service('router.builder')->rebuild();
|
\Drupal::service('router.builder')->rebuild();
|
||||||
|
$this->installEntitySchema('user');
|
||||||
$this->installEntitySchema('menu_link_content');
|
$this->installEntitySchema('menu_link_content');
|
||||||
|
|
||||||
$this->linkTree = $this->container->get('menu.link_tree');
|
$this->linkTree = $this->container->get('menu.link_tree');
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue