diff --git a/core/composer.json b/core/composer.json
index 9e68eaa..2f885d1 100644
--- a/core/composer.json
+++ b/core/composer.json
@@ -99,6 +99,7 @@
"drupal/locale": "self.version",
"drupal/minimal": "self.version",
"drupal/menu_link_content": "self.version",
+ "drupal/menu_link": "self.version",
"drupal/menu_ui": "self.version",
"drupal/migrate": "self.version",
"drupal/migrate_drupal": "self.version",
diff --git a/core/modules/menu_link/config/schema/menu_link.schema.yml b/core/modules/menu_link/config/schema/menu_link.schema.yml
new file mode 100644
index 0000000..54b7a45
--- /dev/null
+++ b/core/modules/menu_link/config/schema/menu_link.schema.yml
@@ -0,0 +1,26 @@
+field.field_settings.menu_link:
+ type: mapping
+ label: 'Menu link settings'
+ mapping:
+ available_menus:
+ type: sequence
+ label: 'Available menus'
+ sequence:
+ - type: string
+ default_menu_parent:
+ type: string
+ label: 'Default menu parent'
+
+field.value.menu_link:
+ type: mapping
+ label: 'Default value'
+ mapping:
+ menu_name:
+ type: string
+ label: 'Value'
+ parent:
+ type: string
+ label: 'Text format'
+ weight:
+ type: integer
+ label: 'Weight'
diff --git a/core/modules/menu_ui/menu_ui.js b/core/modules/menu_link/js/menu_link.js
similarity index 96%
rename from core/modules/menu_ui/menu_ui.js
rename to core/modules/menu_link/js/menu_link.js
index 5eb10e0..f26b11d 100644
--- a/core/modules/menu_ui/menu_ui.js
+++ b/core/modules/menu_link/js/menu_link.js
@@ -15,7 +15,7 @@
* @prop {Drupal~behaviorAttach} attach
* Find the form and call `drupalSetSummary` on it.
*/
- Drupal.behaviors.menuUiDetailsSummaries = {
+ Drupal.behaviors.menuLinkDetailsSummaries = {
attach: function (context) {
$(context).find('.menu-link-form').drupalSetSummary(function (context) {
var $context = $(context);
@@ -38,7 +38,7 @@
* Attaches change and keyup behavior for automatically filling out menu
* link titles.
*/
- Drupal.behaviors.menuUiLinkAutomaticTitle = {
+ Drupal.behaviors.menuLinkLinkAutomaticTitle = {
attach: function (context) {
var $context = $(context);
$context.find('.menu-link-form').each(function () {
diff --git a/core/modules/menu_link/menu_link.info.yml b/core/modules/menu_link/menu_link.info.yml
new file mode 100644
index 0000000..2c90e90
--- /dev/null
+++ b/core/modules/menu_link/menu_link.info.yml
@@ -0,0 +1,8 @@
+name: Menu link
+type: module
+description: 'Defines a menu link field type'
+package: Field types
+version: VERSION
+core: 8.x
+dependencies:
+ - field
diff --git a/core/modules/menu_link/menu_link.libraries.yml b/core/modules/menu_link/menu_link.libraries.yml
new file mode 100644
index 0000000..6610a24
--- /dev/null
+++ b/core/modules/menu_link/menu_link.libraries.yml
@@ -0,0 +1,8 @@
+menu_link.form:
+ version: VERSION
+ js:
+ js/menu_link.js: {}
+ dependencies:
+ - core/jquery
+ - core/drupal
+ - core/drupal.form
diff --git a/core/modules/menu_link/menu_link.module b/core/modules/menu_link/menu_link.module
new file mode 100644
index 0000000..55e8bec
--- /dev/null
+++ b/core/modules/menu_link/menu_link.module
@@ -0,0 +1,21 @@
+getType() == 'menu_link') {
+ // We only support posting one menu link at a time.
+ // @todo - this is a hack and should be managed by the field definition.
+ // https://www.drupal.org/node/2403703
+ $form['field_storage']['cardinality_container']['cardinality']['#default_value'] = 1;
+ $form['field_storage']['cardinality_container']['#access'] = FALSE;
+ }
+}
diff --git a/core/modules/menu_link/src/Plugin/Field/FieldType/MenuLinkItem.php b/core/modules/menu_link/src/Plugin/Field/FieldType/MenuLinkItem.php
new file mode 100644
index 0000000..bb6aa9b
--- /dev/null
+++ b/core/modules/menu_link/src/Plugin/Field/FieldType/MenuLinkItem.php
@@ -0,0 +1,283 @@
+menuPluginManager = \Drupal::service('plugin.manager.menu.link');
+ $this->menuParentFormSelector = \Drupal::service('menu.parent_form_selector');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function defaultFieldSettings() {
+ $settings = parent::defaultFieldSettings();
+
+ $settings['available_menus'] = ['main'];
+ $settings['default_menu_parent'] = 'main:';
+ return $settings;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function fieldSettingsForm(array $form, FormStateInterface $form_state) {
+ $form = parent::fieldSettingsForm($form, $form_state);
+
+ $menu_options = $this->getMenuNames();
+
+ $form['available_menus'] = [
+ '#type' => 'checkboxes',
+ '#title' => $this->t('Available menus'),
+ '#default_value' => $this->getSetting('available_menus'),
+ '#options' => $menu_options,
+ '#description' => $this->t('The menus available to place links in for this kind of entity.'),
+ '#required' => TRUE,
+ ];
+
+ $parent_options = [];
+ // Make sure the setting is normalized to an associative array.
+ $available_menus = array_filter($this->getSetting('available_menus'));
+ $available_menus = array_combine($available_menus, $available_menus);
+ foreach ($available_menus as $name) {
+ if (isset($menu_options[$name])) {
+ $parent_options["$name:"] = $menu_options[$name];
+ }
+ }
+ $form['default_menu_parent'] = [
+ '#type' => 'select',
+ '#title' => $this->t('Default menu for new links'),
+ '#default_value' => $this->getSetting('default_menu_parent'),
+ '#options' => $parent_options,
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
+ $definitions = [];
+
+ $definitions['menu_name'] = DataDefinition::create('string')
+ ->setLabel(t('Menu'));
+ $definitions['title'] = DataDefinition::create('string')
+ ->setLabel(t('Menu link title'));
+ $definitions['description'] = DataDefinition::create('string')
+ ->setLabel(t('Menu link description'));
+ $definitions['parent'] = DataDefinition::create('string')
+ ->setLabel(t('Menu link parent'));
+ $definitions['weight'] = DataDefinition::create('integer')
+ ->setLabel(t('Menu link weight'));
+
+ return $definitions;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function schema(FieldStorageDefinitionInterface $field_definition) {
+ $schema = [];
+
+ $schema['columns']['menu_name'] = [
+ 'description' => 'The menu of the link',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => FALSE,
+ ];
+
+ $schema['columns']['title'] = [
+ 'description' => 'The menu link text.',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => FALSE,
+ ];
+
+ $schema['columns']['description'] = [
+ 'description' => 'The description of the menu link.',
+ 'type' => 'blob',
+ 'size' => 'big',
+ 'not null' => FALSE,
+ ];
+
+ $schema['columns']['parent'] = [
+ 'description' => 'The parent of the menu link',
+ 'type' => 'varchar',
+ 'length' => 255,
+ 'not null' => FALSE,
+ ];
+
+ $schema['columns']['weight'] = [
+ 'description' => 'The weight of the menu link',
+ 'type' => 'int',
+ ];
+
+ return $schema;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function insert() {
+ parent::insert();
+
+ $this->doSave();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function update() {
+ parent::update();
+
+ $this->doSave();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function delete() {
+ parent::delete();
+
+ $plugin_id = $this->getMenuPluginId();
+ if ($this->menuPluginManager->hasDefinition($plugin_id)) {
+ $this->menuPluginManager->removeDefinition($plugin_id, FALSE);
+ }
+ }
+
+ /**
+ * Saves the plugin definition.
+ */
+ protected function doSave() {
+ // We only update the menu link definition when working with the original
+ // language value of the field, otherwise, we can never properly update or
+ // remove the menu link.
+ // @todo - use the FieldTranslationSynchronizer
+ // https://www.drupal.org/node/2403455
+ if ($this->getLangcode() != $this->getEntity()->getUntranslated()->language()->getId()) {
+ return;
+ }
+ $plugin_id = $this->getMenuPluginId();
+
+ // When the entity is saved via a plugin instance, we should not call the
+ // menu tree manager to update the definition a second time.
+ if ($menu_plugin_definition = $this->getMenuPluginDefinition()) {
+ if (!$this->menuPluginManager->hasDefinition($plugin_id)) {
+ $this->menuPluginManager->addDefinition($plugin_id, $this->getMenuPluginDefinition());
+ }
+ else {
+ $this->menuPluginManager->updateDefinition($plugin_id, $this->getMenuPluginDefinition(), FALSE);
+ }
+ }
+ else {
+ $this->menuPluginManager->removeDefinition($plugin_id, FALSE);
+ }
+ }
+
+ /**
+ * Generates the plugin ID for the associated menu link.
+ *
+ * @return string
+ * The Plugin ID.
+ */
+ public function getMenuPluginId() {
+ $field_name = $this->definition->getFieldDefinition()->getName();
+ $entity_type_id = $this->getEntity()->getEntityTypeId();
+ return 'menu_link_field:' . "{$entity_type_id}_{$field_name}_{$this->getEntity()->uuid()}";
+ }
+
+ /**
+ * Generates the plugin definition of the associated menu link.
+ *
+ * @return array|FALSE
+ * The menu plugin definition, otherwise FALSE.
+ */
+ protected function getMenuPluginDefinition() {
+ $menu_definition = [];
+
+ // If there is no menu name selected, you don't have a valid menu plugin.
+ if (!$this->values['menu_name']) {
+ return FALSE;
+ }
+
+ $entity = $this->getEntity();
+ $menu_definition['id'] = $this->getPluginId();
+ $menu_definition['title'] = $this->values['title'] ?: $entity->label();
+ $menu_definition['title_max_length'] = $this->getFieldDefinition()->getSetting('max_length');
+ $menu_definition['description'] = isset($this->values['description']) ? $this->values['description'] : '';
+ $menu_definition['menu_name'] = $this->values['menu_name'];
+ $menu_definition['parent'] = $this->values['parent'];
+ $menu_definition['weight'] = isset($this->values['weight']) ? $this->values['weight'] : 0;
+ $menu_definition['class'] = '\Drupal\menu_link\Plugin\Menu\MenuLinkField';
+ $menu_definition['form_class'] = '\Drupal\menu_link\Plugin\Menu\Form\MenuLinkFieldForm';
+ $menu_definition['metadata']['entity_id'] = $entity->id();
+ $menu_definition['metadata']['entity_type_id'] = $entity->getEntityTypeId();
+ $menu_definition['metadata']['field_name'] = $this->definition->getFieldDefinition()->getName();
+ $menu_definition['metadata']['translatable'] = $entity->getEntityType()->isTranslatable();
+
+ $url = $entity->urlInfo();
+ $menu_definition['route_name'] = $url->getRouteName();
+ $menu_definition['route_parameters'] = $url->getRouteParameters();
+
+ return $menu_definition;
+ }
+
+ /**
+ * Returns available menu names.
+ *
+ * @return string[]
+ * Returns menu labels, keyed by menu ID.
+ */
+ protected function getMenuNames() {
+ if ($custom_menus = Menu::loadMultiple()) {
+ foreach ($custom_menus as $menu_name => $menu) {
+ $custom_menus[$menu_name] = $menu->label();
+ }
+ asort($custom_menus);
+ }
+
+ return $custom_menus;
+ }
+
+}
diff --git a/core/modules/menu_link/src/Plugin/Field/FieldWidget/MenuLinkWidget.php b/core/modules/menu_link/src/Plugin/Field/FieldWidget/MenuLinkWidget.php
new file mode 100644
index 0000000..f36b966
--- /dev/null
+++ b/core/modules/menu_link/src/Plugin/Field/FieldWidget/MenuLinkWidget.php
@@ -0,0 +1,219 @@
+menuParentSelector = $menu_parent_selector;
+ $this->account = $account;
+ $this->menuLinkManager = $menu_link_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $plugin_id,
+ $plugin_definition,
+ $configuration['field_definition'],
+ $configuration['settings'],
+ $configuration['third_party_settings'],
+ $container->get('menu.parent_form_selector'),
+ $container->get('current_user'),
+ $container->get('plugin.manager.menu.link')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, FormStateInterface $form_state) {
+ $default_weight = isset($items[$delta]->weight) ? $items[$delta]->weight : 0;
+
+ $available_menus = array_filter($items->getSetting('available_menus'));
+ $available_menus = array_combine($available_menus, $available_menus);
+ $menu_names = array_keys($available_menus);
+
+ if (empty($items[$delta]->menu_name)) {
+ $default_menu_parent = $items->getSetting('default_menu_parent');
+ if (empty($available_menus[rtrim($default_menu_parent, ':')])) {
+ $default_menu_parent = reset($menu_names) . ':';
+ }
+ }
+ else {
+ $menu = $items[$delta]->menu_name;
+ $parent = !empty($items[$delta]->parent) ? $items[$delta]->parent : '';
+ $default_menu_parent = "$menu:$parent";
+ }
+ $default_title = isset($items[$delta]->title) ? $items[$delta]->title : NULL;
+ $default_description = isset($items[$delta]->description) ? $items[$delta]->description : NULL;
+ // The widget form may be used to define default values, so make sure the
+ // form object is an entity form, rather than a configuration form.
+ $form_object = $form_state->getFormObject();
+ $in_translation_form = is_subclass_of($form_object, '\Drupal\Core\Entity\ContentEntityFormInterface') && !$form_object->isDefaultFormLangcode($form_state);
+
+ /** @var \Drupal\Core\Entity\EntityInterface $entity */
+ $entity = $items->getParent()->getValue();
+ $element['#pre_render'][] = [get_class($this), 'preRenderMenuDetails'];
+
+ $element['#attached']['library'][] = 'menu_link/menu_link.form';
+
+ $element['title'] = [
+ '#type' => 'textfield',
+ '#title' => $this->t('Menu link title'),
+ '#default_value' => $default_title,
+ '#attributes' => ['class' => ['menu-link-title']],
+ ];
+
+ $element['description'] = [
+ '#type' => 'textarea',
+ '#title' => $this->t('Description'),
+ '#default_value' => $default_description,
+ '#rows' => 1,
+ '#description' => $this->t('Shown when hovering over the menu link.'),
+ ];
+
+ $plugin_id = $items[$delta]->getMenuPluginId();
+ $has_plugin = $plugin_id && $this->menuLinkManager->hasDefinition($plugin_id);
+ $element['enabled'] = [
+ '#type' => 'checkbox',
+ '#title' => $this->t('Provide a menu link'),
+ '#default_value' => (int) (bool) $has_plugin,
+ '#attributes' => ['class' => ['menu-link-enabled']],
+ '#multilingual' => FALSE,
+ '#disabled' => $in_translation_form,
+ ];
+ $element['menu'] = [
+ '#type' => 'details',
+ '#title' => $this->t('Menu settings'),
+ '#open' => (bool) $has_plugin,
+ '#tree' => FALSE,
+ '#weight' => $entity->getEntityTypeId() == 'node' ? -2 : 0,
+ '#group' => $entity->getEntityTypeId() == 'node' ? 'advanced' : NULL,
+ '#attributes' => ['class' => ['menu-link-form']],
+ '#attached' => [
+ 'library' => ['menu_ui/drupal.menu_ui'],
+ ],
+ ];
+
+ $plugin_id = $items[$delta]->getMenuPluginId();
+ $parent_element = $this->menuParentSelector->parentSelectElement($default_menu_parent, $plugin_id, $available_menus);
+
+ $element['menu_parent'] = $parent_element;
+ $element['menu_parent']['#title'] = $this->t('Parent item');
+ $element['menu_parent']['#attributes']['class'][] = 'menu-parent-select';
+ $element['menu_parent']['#multilingual'] = FALSE;
+ $element['menu_parent']['#disabled'] = $in_translation_form;
+
+ $element['weight'] = [
+ '#type' => 'number',
+ '#title' => $this->t('Weight'),
+ '#default_value' => $default_weight,
+ '#description' => $this->t('Menu links with lower weights are displayed before links with higher weights.'),
+ ];
+
+ return $element;
+ }
+
+ /**
+ * Pre-render callback: Builds a renderable array for the menu link widget.
+ *
+ * @param array $element
+ * A renderable array.
+ *
+ * @return array
+ * A renderable array.
+ */
+ public static function preRenderMenuDetails($element) {
+ $element['menu']['enabled'] = $element['enabled'];
+ $element['menu']['title'] = $element['title'];
+ $element['menu']['description'] = $element['description'];
+ $element['menu']['menu_parent'] = $element['menu_parent'];
+ $element['menu']['weight'] = $element['weight'];
+
+ // Hide the elements when enabled is disabled.
+ foreach (['title', 'description', 'menu_parent', 'weight'] as $form_element) {
+ $element['menu'][$form_element]['#states'] = [
+ 'invisible' => [
+ 'input[name="' . $element['menu']['enabled']['#name'] . '"]' => ['checked' => FALSE],
+ ],
+ ];
+ }
+
+ unset($element['enabled'], $element['title'], $element['description'], $element['menu_parent'], $element['weight']);
+
+ return $element;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function extractFormValues(FieldItemListInterface $items, array $form, FormStateInterface $form_state) {
+ parent::extractFormValues($items, $form, $form_state);
+
+ // Extract menu and parent menu link from single select element.
+ foreach ($items as $delta => $item) {
+ if (!empty($item->enabled) && !empty($item->menu_parent) && $item->menu_parent != ':') {
+ list($item->menu_name, $item->parent) = explode(':', $item->menu_parent, 2);
+ }
+ else {
+ $item->title = '';
+ $item->description = '';
+ $item->menu_name = '';
+ $item->parent = '';
+ }
+ unset($item->enabled);
+ unset($item->menu_parent);
+ }
+ }
+
+}
diff --git a/core/modules/menu_link/src/Plugin/Field/MenuLinkItemList.php b/core/modules/menu_link/src/Plugin/Field/MenuLinkItemList.php
new file mode 100644
index 0000000..ee68c6d
--- /dev/null
+++ b/core/modules/menu_link/src/Plugin/Field/MenuLinkItemList.php
@@ -0,0 +1,32 @@
+menuLink->getEntity();
+
+ $form['info'] = [
+ '#type' => 'item',
+ '#title' => $this->t('This link is provided by the %type: @label. The path cannot be edited.', [
+ '%type' => $entity->getEntityType()->getLabel(),
+ '@url' => $entity->url(),
+ '@label' => $entity->label(),
+ ]),
+ ];
+
+ return $form;
+ }
+
+}
diff --git a/core/modules/menu_link/src/Plugin/Menu/MenuLinkField.php b/core/modules/menu_link/src/Plugin/Menu/MenuLinkField.php
new file mode 100644
index 0000000..5a84504
--- /dev/null
+++ b/core/modules/menu_link/src/Plugin/Menu/MenuLinkField.php
@@ -0,0 +1,202 @@
+entityTypeManager = $entity_manager;
+ $this->entityRepository = $entity_repository;
+ $this->languageManager = $language_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+ return new static(
+ $configuration,
+ $plugin_id,
+ $plugin_definition,
+ $container->get('entity_type.manager'),
+ $container->get('entity.repository'),
+ $container->get('language_manager')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getTitle() {
+ return $this->getProperty('title');
+ }
+
+ /**
+ * Gets a specific property.
+ *
+ * In case the underlying entity is translatable, we watch the translated
+ * value.
+ *
+ * @param string $property
+ * Gets a specific property from the field, like title or weight.
+ *
+ * @return mixed
+ * The Property.
+ */
+ protected function getProperty($property) {
+ // We only need to get the property from the actual entity if it may be a
+ // translation based on the current language context. This can only happen
+ // if the site is configured to be multilingual.
+ if (!empty($this->pluginDefinition['metadata']['translatable']) && $this->languageManager->isMultilingual()) {
+ /** @var \Drupal\Core\TypedData\TranslatableInterface|\Drupal\Core\Entity\EntityInterface $entity */
+ $entity = $this->entityRepository->getTranslationFromContext($this->getEntity());
+ $field_name = $this->getFieldName();
+ if ($property_value = $entity->$field_name->$property) {
+ return $property_value;
+ }
+ return $entity->label();
+ }
+ return $this->pluginDefinition[$property];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescription() {
+ return $this->getProperty('description');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getWeight() {
+ return $this->getProperty('weight');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function updateLink(array $new_definition_values, $persist) {
+ $field_name = $this->getFieldName();
+
+ $this->pluginDefinition = $new_definition_values + $this->getPluginDefinition();
+ if ($persist) {
+ $updated = [];
+ foreach ($new_definition_values as $key => $value) {
+ $field = $this->getEntity()->{$field_name};
+ if (isset($field->{$key})) {
+ $field->{$key} = $value;
+ $updated[] = $key;
+ }
+ }
+ if ($updated) {
+ $this->getEntity()->save();
+ }
+ }
+
+ return $this->pluginDefinition;
+ }
+
+ /**
+ * Loads the entity the field was attached to.
+ *
+ * @return \Drupal\Core\Entity\EntityInterface
+ * Returns the entity, if exists.
+ */
+ public function getEntity() {
+ if (empty($this->entity)) {
+ $entity_type_id = $this->pluginDefinition['metadata']['entity_type_id'];
+ $entity_id = $this->pluginDefinition['metadata']['entity_id'];
+ $this->entity = $this->entityTypeManager->getStorage($entity_type_id)->load($entity_id);
+ }
+ return $this->entity;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function isDeletable() {
+ return TRUE;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function deleteLink() {
+ $entity = $this->getEntity();
+ $field_name = $this->getFieldName();
+
+ $entity->$field_name->title = '';
+ $entity->$field_name->description = '';
+ $entity->$field_name->menu_name = '';
+ $entity->$field_name->parent = '';
+ $this->entity->save();
+ }
+
+ /**
+ * Returns the field name.
+ *
+ * @return string
+ * The Field name.
+ */
+ protected function getFieldName() {
+ return $this->getPluginDefinition()['metadata']['field_name'];
+ }
+
+}
diff --git a/core/modules/menu_link/src/Tests/MenuLinkFieldStandardTest.php b/core/modules/menu_link/src/Tests/MenuLinkFieldStandardTest.php
new file mode 100644
index 0000000..f2c0a3d
--- /dev/null
+++ b/core/modules/menu_link/src/Tests/MenuLinkFieldStandardTest.php
@@ -0,0 +1,89 @@
+getPermissions());
+ $this->adminUser = $this->drupalCreateUser($perms);
+ $this->drupalLogin($this->adminUser);
+ }
+
+ /**
+ * Tests field CRUD on the node form and field configurations.
+ */
+ public function testLinkEdit() {
+ // Ensure that the field_menu link got created.
+ $node = $this->drupalCreateNode(array(
+ 'type' => 'article',
+ 'title' => 'Foobar',
+ 'promote' => 1,
+ 'status' => 1,
+ ));
+ $this->drupalGet("node/{$node->id()}/edit");
+ $this->assertNoFieldChecked('edit-field-menu-0-enabled');
+ $title = $this->randomString();
+ $edit = $this->translatePostValues([
+ 'field_menu' => [
+ 0 => [
+ 'enabled' => TRUE,
+ 'title' => $title,
+ ],
+ ],
+ ]);
+ $this->drupalPostForm("node/{$node->id()}/edit", $edit, 'Save and keep published');
+ $this->drupalGet("node/{$node->id()}/edit");
+ $this->assertFieldChecked('edit-field-menu-0-enabled');
+ $this->assertOptionSelected('edit-field-menu-0-menu-parent', 'main:');
+ // Enable another menu.
+ $edit = $this->translatePostValues([
+ 'field' => [
+ 'settings' => [
+ 'available_menus' => [
+ 'footer' => TRUE,
+ 'main' => TRUE,
+ ],
+ ],
+ ],
+ ]);
+ $this->drupalPostForm('admin/structure/types/manage/article/fields/node.article.field_menu', $edit, 'Save settings');
+ $this->drupalGet("node/{$node->id()}/edit");
+ $this->assertOptionSelected('edit-field-menu-0-menu-parent', 'main:');
+ $edit = $this->translatePostValues([
+ 'field_menu' => [
+ 0 => [
+ 'menu_parent' => 'footer:',
+ ],
+ ],
+ ]);
+ $this->drupalPostForm("node/{$node->id()}/edit", $edit, 'Save and keep published');
+ $this->drupalGet("node/{$node->id()}/edit");
+ $this->assertOptionSelected('edit-field-menu-0-menu-parent', 'footer:');
+ }
+
+}
diff --git a/core/modules/menu_link/src/Tests/MenuLinkFieldTranslateUITest.php b/core/modules/menu_link/src/Tests/MenuLinkFieldTranslateUITest.php
new file mode 100644
index 0000000..e23f067
--- /dev/null
+++ b/core/modules/menu_link/src/Tests/MenuLinkFieldTranslateUITest.php
@@ -0,0 +1,61 @@
+bundle content"];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getTranslatorPermissions() {
+ return array_merge(parent::getTranslatorPermissions(), ['administer menu', 'administer nodes', "edit any $this->bundle content"]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getAdministratorPermissions() {
+ return array_merge(parent::getAdministratorPermissions(), ['administer menu', 'access administration pages', 'administer content types', 'administer node fields', 'access content overview', 'bypass node access', 'administer languages', 'administer themes', 'view the administration theme']);
+ }
+
+}
diff --git a/core/modules/menu_ui/config/schema/menu_ui.schema.yml b/core/modules/menu_ui/config/schema/menu_ui.schema.yml
index 69af435..8bbc714 100644
--- a/core/modules/menu_ui/config/schema/menu_ui.schema.yml
+++ b/core/modules/menu_ui/config/schema/menu_ui.schema.yml
@@ -7,17 +7,3 @@ menu_ui.settings:
override_parent_selector:
type: boolean
label: 'Override parent selector'
-
-node.type.*.third_party.menu_ui:
- type: mapping
- label: 'Per-content type menu settings'
- mapping:
- available_menus:
- type: sequence
- label: 'Available menus'
- sequence:
- type: string
- label: 'Menu machine name'
- parent:
- type: string
- label: 'Parent'
diff --git a/core/modules/menu_ui/menu_ui.libraries.yml b/core/modules/menu_ui/menu_ui.libraries.yml
index c5f9594..31f34d2 100644
--- a/core/modules/menu_ui/menu_ui.libraries.yml
+++ b/core/modules/menu_ui/menu_ui.libraries.yml
@@ -1,12 +1,3 @@
-drupal.menu_ui:
- version: VERSION
- js:
- menu_ui.js: {}
- dependencies:
- - core/jquery
- - core/drupal
- - core/drupal.form
-
drupal.menu_ui.admin:
version: VERSION
js:
diff --git a/core/modules/menu_ui/menu_ui.module b/core/modules/menu_ui/menu_ui.module
index f03d4af..3336567 100644
--- a/core/modules/menu_ui/menu_ui.module
+++ b/core/modules/menu_ui/menu_ui.module
@@ -127,344 +127,6 @@ function menu_ui_block_view_system_menu_block_alter(array &$build, BlockPluginIn
}
/**
- * Helper function to create or update a menu link for a node.
- *
- * @param \Drupal\node\NodeInterface $node
- * Node entity.
- * @param array $values
- * Values for the menu link.
- */
-function _menu_ui_node_save(NodeInterface $node, array $values) {
- /** @var \Drupal\menu_link_content\MenuLinkContentInterface $entity */
- if (!empty($values['entity_id'])) {
- $entity = MenuLinkContent::load($values['entity_id']);
- if ($entity->isTranslatable()) {
- if (!$entity->hasTranslation($node->language()->getId())) {
- $entity = $entity->addTranslation($node->language()->getId(), $entity->toArray());
- }
- else {
- $entity = $entity->getTranslation($node->language()->getId());
- }
- }
- }
- else {
- // Create a new menu_link_content entity.
- $entity = MenuLinkContent::create(array(
- 'link' => ['uri' => 'entity:node/' . $node->id()],
- 'langcode' => $node->language()->getId(),
- ));
- $entity->enabled->value = 1;
- }
- $entity->title->value = trim($values['title']);
- $entity->description->value = trim($values['description']);
- $entity->menu_name->value = $values['menu_name'];
- $entity->parent->value = $values['parent'];
- $entity->weight->value = isset($values['weight']) ? $values['weight'] : 0;
- $entity->save();
-}
-
-/**
- * Implements hook_ENTITY_TYPE_predelete() for node entities.
- */
-function menu_ui_node_predelete(EntityInterface $node) {
- // Delete all MenuLinkContent links that point to this node.
- /** @var \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager */
- $menu_link_manager = \Drupal::service('plugin.manager.menu.link');
- $result = $menu_link_manager->loadLinksByRoute('entity.node.canonical', array('node' => $node->id()));
-
- if (!empty($result)) {
- foreach ($result as $id => $instance) {
- if ($instance->isDeletable() && strpos($id, 'menu_link_content:') === 0) {
- $instance->deleteLink();
- }
- }
- }
-}
-
-/**
- * Returns the definition for a menu link for the given node.
- *
- * @param \Drupal\node\NodeInterface $node
- * The node entity.
- *
- * @return array
- * An array that contains default values for the menu link form.
- */
-function menu_ui_get_menu_link_defaults(NodeInterface $node) {
- // Prepare the definition for the edit form.
- /** @var \Drupal\node\NodeTypeInterface $node_type */
- $node_type = $node->type->entity;
- $menu_name = strtok($node_type->getThirdPartySetting('menu_ui', 'parent', 'main:'), ':');
- $defaults = FALSE;
- if ($node->id()) {
- $id = FALSE;
- // Give priority to the default menu
- $type_menus = $node_type->getThirdPartySetting('menu_ui', 'available_menus', array('main'));
- if (in_array($menu_name, $type_menus)) {
- $query = \Drupal::entityQuery('menu_link_content')
- ->condition('link.uri', 'node/' . $node->id())
- ->condition('menu_name', $menu_name)
- ->sort('id', 'ASC')
- ->range(0, 1);
- $result = $query->execute();
-
- $id = (!empty($result)) ? reset($result) : FALSE;
- }
- // Check all allowed menus if a link does not exist in the default menu.
- if (!$id && !empty($type_menus)) {
- $query = \Drupal::entityQuery('menu_link_content')
- ->condition('link.uri', 'entity:node/' . $node->id())
- ->condition('menu_name', array_values($type_menus), 'IN')
- ->sort('id', 'ASC')
- ->range(0, 1);
- $result = $query->execute();
-
- $id = (!empty($result)) ? reset($result) : FALSE;
- }
- if ($id) {
- $menu_link = MenuLinkContent::load($id);
- $menu_link = \Drupal::service('entity.repository')->getTranslationFromContext($menu_link);
- $defaults = array(
- 'entity_id' => $menu_link->id(),
- 'id' => $menu_link->getPluginId(),
- 'title' => $menu_link->getTitle(),
- 'title_max_length' => $menu_link->getFieldDefinitions()['title']->getSetting('max_length'),
- 'description' => $menu_link->getDescription(),
- 'menu_name' => $menu_link->getMenuName(),
- 'parent' => $menu_link->getParentId(),
- 'weight' => $menu_link->getWeight(),
- );
- }
- }
-
- if (!$defaults) {
- // Get the default max_length of a menu link title from the base field
- // definition.
- $field_definitions = \Drupal::entityManager()->getBaseFieldDefinitions('menu_link_content');
- $max_length = $field_definitions['title']->getSetting('max_length');
- $defaults = array(
- 'entity_id' => 0,
- 'id' => '',
- 'title' => '',
- 'title_max_length' => $max_length,
- 'description' => '',
- 'menu_name' => $menu_name,
- 'parent' => '',
- 'weight' => 0,
- );
- }
- return $defaults;
-}
-
-/**
- * Implements hook_form_BASE_FORM_ID_alter() for \Drupal\node\NodeForm.
- *
- * Adds menu item fields to the node form.
- *
- * @see menu_ui_form_node_form_submit()
- */
-function menu_ui_form_node_form_alter(&$form, FormStateInterface $form_state) {
- // Generate a list of possible parents (not including this link or descendants).
- // @todo This must be handled in a #process handler.
- $node = $form_state->getFormObject()->getEntity();
- $defaults = menu_ui_get_menu_link_defaults($node);
- /** @var \Drupal\node\NodeTypeInterface $node_type */
- $node_type = $node->type->entity;
- /** @var \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector */
- $menu_parent_selector = \Drupal::service('menu.parent_form_selector');
- $menu_names = menu_ui_get_menus();
- $type_menus = $node_type->getThirdPartySetting('menu_ui', 'available_menus', array('main'));
- $available_menus = array();
- foreach ($type_menus as $menu) {
- $available_menus[$menu] = $menu_names[$menu];
- }
- if ($defaults['id']) {
- $default = $defaults['menu_name'] . ':' . $defaults['parent'];
- }
- else {
- $default = $node_type->getThirdPartySetting('menu_ui', 'parent', 'main:');
- }
- $parent_element = $menu_parent_selector->parentSelectElement($default, $defaults['id'], $available_menus);
- // If no possible parent menu items were found, there is nothing to display.
- if (empty($parent_element)) {
- return;
- }
-
- $form['menu'] = array(
- '#type' => 'details',
- '#title' => t('Menu settings'),
- '#access' => \Drupal::currentUser()->hasPermission('administer menu'),
- '#open' => (bool) $defaults['id'],
- '#group' => 'advanced',
- '#attached' => array(
- 'library' => array('menu_ui/drupal.menu_ui'),
- ),
- '#tree' => TRUE,
- '#weight' => -2,
- '#attributes' => array('class' => array('menu-link-form')),
- );
- $form['menu']['enabled'] = array(
- '#type' => 'checkbox',
- '#title' => t('Provide a menu link'),
- '#default_value' => (int) (bool) $defaults['id'],
- );
- $form['menu']['link'] = array(
- '#type' => 'container',
- '#parents' => array('menu'),
- '#states' => array(
- 'invisible' => array(
- 'input[name="menu[enabled]"]' => array('checked' => FALSE),
- ),
- ),
- );
-
- // Populate the element with the link data.
- foreach (array('id', 'entity_id') as $key) {
- $form['menu']['link'][$key] = array('#type' => 'value', '#value' => $defaults[$key]);
- }
-
- $form['menu']['link']['title'] = array(
- '#type' => 'textfield',
- '#title' => t('Menu link title'),
- '#default_value' => $defaults['title'],
- '#maxlength' => $defaults['title_max_length'],
- );
-
- $form['menu']['link']['description'] = array(
- '#type' => 'textarea',
- '#title' => t('Description'),
- '#default_value' => $defaults['description'],
- '#rows' => 1,
- '#description' => t('Shown when hovering over the menu link.'),
- );
-
- $form['menu']['link']['menu_parent'] = $parent_element;
- $form['menu']['link']['menu_parent']['#title'] = t('Parent item');
- $form['menu']['link']['menu_parent']['#attributes']['class'][] = 'menu-parent-select';
-
- $form['menu']['link']['weight'] = array(
- '#type' => 'number',
- '#title' => t('Weight'),
- '#default_value' => $defaults['weight'],
- '#description' => t('Menu links with lower weights are displayed before links with higher weights.'),
- );
-
- foreach (array_keys($form['actions']) as $action) {
- if ($action != 'preview' && isset($form['actions'][$action]['#type']) && $form['actions'][$action]['#type'] === 'submit') {
- $form['actions'][$action]['#submit'][] = 'menu_ui_form_node_form_submit';
- }
- }
-}
-
-/**
- * Form submission handler for menu item field on the node form.
- *
- * @see menu_ui_form_node_form_alter()
- */
-function menu_ui_form_node_form_submit($form, FormStateInterface $form_state) {
- $node = $form_state->getFormObject()->getEntity();
- if (!$form_state->isValueEmpty('menu')) {
- $values = $form_state->getValue('menu');
- if (empty($values['enabled'])) {
- if ($values['entity_id']) {
- $entity = MenuLinkContent::load($values['entity_id']);
- $entity->delete();
- }
- }
- elseif (trim($values['title'])) {
- // Decompose the selected menu parent option into 'menu_name' and 'parent',
- // if the form used the default parent selection widget.
- if (!empty($values['menu_parent'])) {
- list($menu_name, $parent) = explode(':', $values['menu_parent'], 2);
- $values['menu_name'] = $menu_name;
- $values['parent'] = $parent;
- }
- _menu_ui_node_save($node, $values);
- }
- }
-}
-
-/**
- * Implements hook_form_FORM_ID_alter() for \Drupal\node\NodeTypeForm.
- *
- * Adds menu options to the node type form.
- *
- * @see NodeTypeForm::form().
- * @see menu_ui_form_node_type_form_submit().
- */
-function menu_ui_form_node_type_form_alter(&$form, FormStateInterface $form_state) {
- /** @var \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector */
- $menu_parent_selector = \Drupal::service('menu.parent_form_selector');
- $menu_options = menu_ui_get_menus();
- /** @var \Drupal\node\NodeTypeInterface $type */
- $type = $form_state->getFormObject()->getEntity();
- $form['menu'] = array(
- '#type' => 'details',
- '#title' => t('Menu settings'),
- '#attached' => array(
- 'library' => array('menu_ui/drupal.menu_ui.admin'),
- ),
- '#group' => 'additional_settings',
- );
- $form['menu']['menu_options'] = array(
- '#type' => 'checkboxes',
- '#title' => t('Available menus'),
- '#default_value' => $type->getThirdPartySetting('menu_ui', 'available_menus', array('main')),
- '#options' => $menu_options,
- '#description' => t('The menus available to place links in for this content type.'),
- );
- // @todo See if we can avoid pre-loading all options by changing the form or
- // using a #process callback. https://www.drupal.org/node/2310319
- // To avoid an 'illegal option' error after saving the form we have to load
- // all available menu parents. Otherwise, it is not possible to dynamically
- // add options to the list using ajax.
- $options_cacheability = new CacheableMetadata();
- $options = $menu_parent_selector->getParentSelectOptions('', NULL, $options_cacheability);
- $form['menu']['menu_parent'] = array(
- '#type' => 'select',
- '#title' => t('Default parent item'),
- '#default_value' => $type->getThirdPartySetting('menu_ui', 'parent', 'main:'),
- '#options' => $options,
- '#description' => t('Choose the menu item to be the default parent for a new link in the content authoring form.'),
- '#attributes' => array('class' => array('menu-title-select')),
- );
- $options_cacheability->applyTo($form['menu']['menu_parent']);
-
- $form['#validate'][] = 'menu_ui_form_node_type_form_validate';
- $form['#entity_builders'][] = 'menu_ui_form_node_type_form_builder';
-}
-
-/**
- * Submit handler for forms with menu options.
- *
- * @see menu_ui_form_node_type_form_alter().
- */
-function menu_ui_form_node_type_form_validate(&$form, FormStateInterface $form_state) {
- $available_menus = array_filter($form_state->getValue('menu_options'));
- // If there is at least one menu allowed, the selected item should be in
- // one of them.
- if (count($available_menus)) {
- $menu_item_id_parts = explode(':', $form_state->getValue('menu_parent'));
- if (!in_array($menu_item_id_parts[0], $available_menus)) {
- $form_state->setErrorByName('menu_parent', t('The selected menu item is not under one of the selected menus.'));
- }
- }
- else {
- $form_state->setValue('menu_parent', '');
- }
-}
-
-/**
- * Entity builder for the node type form with menu options.
- *
- * @see menu_ui_form_node_type_form_alter().
- */
-function menu_ui_form_node_type_form_builder($entity_type, NodeTypeInterface $type, &$form, FormStateInterface $form_state) {
- $type->setThirdPartySetting('menu_ui', 'available_menus', array_values(array_filter($form_state->getValue('menu_options'))));
- $type->setThirdPartySetting('menu_ui', 'parent', $form_state->getValue('menu_parent'));
-}
-
-/**
* Return an associative array of the custom menus names.
*
* @param bool $all
diff --git a/core/modules/menu_ui/menu_ui.routing.yml b/core/modules/menu_ui/menu_ui.routing.yml
index 7f94a6d..9e1312f 100644
--- a/core/modules/menu_ui/menu_ui.routing.yml
+++ b/core/modules/menu_ui/menu_ui.routing.yml
@@ -38,6 +38,19 @@ menu_ui.link_reset:
_permission: 'administer menu'
_custom_access: '\Drupal\menu_ui\Form\MenuLinkResetForm::linkIsResettable'
+menu_ui.link_delete:
+ path: '/admin/structure/menu/link/{menu_link_plugin}/delete'
+ defaults:
+ _form: 'Drupal\menu_ui\Form\MenuLinkDeleteForm'
+ _title: 'Delete menu link'
+ options:
+ parameters:
+ menu_link_plugin:
+ type: menu_link_plugin
+ requirements:
+ _permission: 'administer menu'
+ _custom_access: '\Drupal\menu_ui\Form\MenuLinkDeleteForm::linkIsDeletable'
+
entity.menu.add_form:
path: '/admin/structure/menu/add'
defaults:
diff --git a/core/modules/menu_ui/src/Form/MenuLinkDeleteForm.php b/core/modules/menu_ui/src/Form/MenuLinkDeleteForm.php
new file mode 100644
index 0000000..19e8540
--- /dev/null
+++ b/core/modules/menu_ui/src/Form/MenuLinkDeleteForm.php
@@ -0,0 +1,123 @@
+menuLinkManager = $menu_link_manager;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function create(ContainerInterface $container) {
+ return new static(
+ $container->get('plugin.manager.menu.link')
+ );
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFormId() {
+ return 'menu_link_delete_confirm';
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getQuestion() {
+ return $this->t('Are you sure you want to delete the link %item?', array('%item' => $this->link->getTitle()));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getCancelUrl() {
+ if ($this->link) {
+ return new Url('entity.menu.edit_form', array(
+ 'menu' => $this->link->getMenuName(),
+ ));
+ }
+ return new Url('');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescription() {
+ return $this->t('The link will be deleted. This action cannot be undone.');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getConfirmText() {
+ return $this->t('Delete');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function buildForm(array $form, FormStateInterface $form_state, MenuLinkInterface $menu_link_plugin = NULL) {
+ $this->link = $menu_link_plugin;
+
+ $form = parent::buildForm($form, $form_state);
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $this->link = $this->menuLinkManager->removeDefinition($this->link->getPluginId());
+ drupal_set_message($this->t('The menu link was deleted.'));
+ $form_state->setRedirectUrl($this->getCancelUrl());
+ }
+
+ /**
+ * Checks access based on whether the link can be deleted.
+ *
+ * @param \Drupal\Core\Menu\MenuLinkInterface $menu_link_plugin
+ * The menu link plugin being checked.
+ *
+ * @return \Drupal\Core\Access\AccessResultInterface
+ * The access result.
+ */
+ public function linkIsDeletable(MenuLinkInterface $menu_link_plugin) {
+ return AccessResult::allowedIf($menu_link_plugin->isDeletable())->setCacheable(FALSE);
+ }
+
+}
diff --git a/core/modules/menu_ui/src/MenuForm.php b/core/modules/menu_ui/src/MenuForm.php
index c23e514..505423e 100644
--- a/core/modules/menu_ui/src/MenuForm.php
+++ b/core/modules/menu_ui/src/MenuForm.php
@@ -405,9 +405,17 @@ protected function buildOverviewTreeForm($tree, $delta) {
'url' => Url::fromRoute('menu_ui.link_reset', ['menu_link_plugin' => $link->getPluginId()]),
);
}
- elseif ($delete_link = $link->getDeleteRoute()) {
- $operations['delete']['url'] = $delete_link;
- $operations['delete']['query'] = $this->getDestinationArray();
+ elseif ($link->isDeletable()) {
+ // Allow for a custom delete link per plugin.
+ $delete_route = $link->getDeleteRoute();
+ if ($delete_route) {
+ $operations['delete']['url'] = $delete_route;
+ }
+ else {
+ // Fall back to the standard delete link.
+ $operations['delete']['url'] = Url::fromRoute('menu_ui.link_delete', ['menu_link_plugin' => $link->getPluginId()]);
+ }
+ $operations['delete']['query']['destination'] = $this->entity->url();
$operations['delete']['title'] = $this->t('Delete');
}
if ($link->isTranslatable()) {
diff --git a/core/modules/menu_ui/src/Tests/MenuNodeTest.php b/core/modules/menu_ui/src/Tests/MenuNodeTest.php
index ce47fb1..5c46bc0 100644
--- a/core/modules/menu_ui/src/Tests/MenuNodeTest.php
+++ b/core/modules/menu_ui/src/Tests/MenuNodeTest.php
@@ -2,6 +2,8 @@
namespace Drupal\menu_ui\Tests;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
use Drupal\simpletest\WebTestBase;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\menu_link_content\Entity\MenuLinkContent;
@@ -26,7 +28,22 @@ class MenuNodeTest extends WebTestBase {
*
* @var array
*/
- public static $modules = array('menu_ui', 'test_page_test', 'node', 'block', 'locale', 'language', 'content_translation');
+ public static $modules = array('menu_ui', 'test_page_test', 'node', 'block', 'menu_link', 'locale', 'language', 'content_translation');
+
+ /**
+ * @var \Drupal\field\Entity\FieldConfig
+ */
+ protected $fieldConfig;
+
+ /**
+ * @var \Drupal\field\Entity\FieldStorageConfig
+ */
+ protected $fieldStorageConfig;
+
+ /**
+ * @var \Drupal\field\Entity\FieldStorageConfig
+ */
+ protected $fieldConfigArticle;
protected function setUp() {
parent::setUp();
@@ -35,6 +52,25 @@ protected function setUp() {
$this->drupalPlaceBlock('page_title_block');
$this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page'));
+ $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
+
+ $this->fieldStorageConfig = FieldStorageConfig::create(['field_name' => 'menu', 'entity_type' => 'node', 'type' => 'menu_link']);
+ $this->fieldStorageConfig->save();
+ $this->fieldConfig = FieldConfig::create(['entity_type' => 'node', 'bundle' => 'page', 'field_name' => 'menu']);
+ $this->fieldConfig->save();
+ entity_get_form_display('node', 'page', 'default')
+ ->setComponent('menu', [
+ 'type' => 'menu_link_default',
+ ])
+ ->save();
+
+ $this->fieldConfigArticle = FieldConfig::create(['entity_type' => 'node', 'bundle' => 'article', 'field_name' => 'menu']);
+ $this->fieldConfigArticle->save();
+ entity_get_form_display('node', 'article', 'default')
+ ->setComponent('menu', [
+ 'type' => 'menu_link_default',
+ ])
+ ->save();
$this->editor = $this->drupalCreateUser(array(
'access administration pages',
@@ -68,15 +104,12 @@ function testMenuNodeFormWidget() {
$this->assertPattern('//', 'Menu link title field has correct maxlength in node add form.');
// Disable the default main menu, so that no menus are enabled.
- $edit = array(
- 'menu_options[main]' => FALSE,
- );
- $this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
+ $this->fieldConfig->settings['available_menus']['menu'] = FALSE;
+ $this->fieldConfig->save();
// Verify that no menu settings are displayed and nodes can be created.
$this->drupalGet('node/add/page');
$this->assertText(t('Create Basic page'));
- $this->assertNoText(t('Menu settings'));
$node_title = $this->randomMachineName();
$edit = array(
'title[0][value]' => $node_title,
@@ -88,22 +121,15 @@ function testMenuNodeFormWidget() {
// Test that we cannot set a menu item from a menu that is not set as
// available.
- $edit = array(
- 'menu_options[tools]' => 1,
- 'menu_parent' => 'main:',
- );
- $this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
- $this->assertText(t('The selected menu item is not under one of the selected menus.'));
- $this->assertNoRaw(t('The content type %name has been updated.', array('%name' => 'Basic page')));
+ $this->fieldConfig->settings['available_menus']['tools'] = 'tools';
+ $this->fieldConfig->settings['parent'] = 'main:';
+ $this->fieldConfig->save();
// Enable Tools menu as available menu.
- $edit = array(
- 'menu_options[main]' => 1,
- 'menu_options[tools]' => 1,
- 'menu_parent' => 'main:',
- );
- $this->drupalPostForm('admin/structure/types/manage/page', $edit, t('Save content type'));
- $this->assertRaw(t('The content type %name has been updated.', array('%name' => 'Basic page')));
+ $this->fieldConfig->settings['available_menus']['tools'] = 'tools';
+ $this->fieldConfig->settings['available_menus']['main'] = 'main';
+ $this->fieldConfig->settings['parent'] = 'main:';
+ $this->fieldConfig->save();
// Test that we can preview a node that will create a menu item.
$edit = array(
@@ -126,13 +152,14 @@ function testMenuNodeFormWidget() {
$this->assertNoLink($node_title);
// Edit the node, enable the menu link setting, but skip the link title.
+ // We still expect to have a link to the entity label of the node.
$edit = array(
- 'menu[enabled]' => 1,
+ 'menu[0][enabled]' => 1,
);
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
- // Assert that there is no link for the node.
+ // Assert that there is a link for the node.
$this->drupalGet('test-page');
- $this->assertNoLink($node_title);
+ $this->assertLink($node->label());
// Use not only the save button, but also the two special buttons:
// 'Save and publish' as well as 'Save and keep published'.
@@ -166,9 +193,8 @@ function testMenuNodeFormWidget() {
$this->drupalLogin($this->editor);
// Edit the node and create a menu link.
$edit = array(
- 'menu[enabled]' => 1,
- 'menu[title]' => $node_title,
- 'menu[weight]' => 17,
+ 'menu[0][enabled]' => 1,
+ 'menu[0][weight]' => 17,
);
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
// Assert that the link exists.
@@ -176,9 +202,12 @@ function testMenuNodeFormWidget() {
$this->assertLink($node_title);
$this->drupalGet('node/' . $node->id() . '/edit');
- $this->assertFieldById('edit-menu-weight', 17, 'Menu weight correct in edit form');
+ $this->assertFieldById('edit-menu-0-weight', 17, 'Menu weight correct in edit form');
$this->assertPattern('//', 'Menu link title field has correct maxlength in node edit form.');
+ // Ensure that the menu link field is opened up by default.
+ $this->assertFieldById('edit-menu-0-enabled', 1, 'Provide a menu link checkbox is checked.');
+
// Disable the menu link, then edit the node--the link should stay disabled.
$link_id = menu_ui_get_menu_link_defaults($node)['entity_id'];
/** @var \Drupal\menu_link_content\Entity\MenuLinkContent $link */
@@ -191,13 +220,17 @@ function testMenuNodeFormWidget() {
// Edit the node and remove the menu link.
$edit = array(
- 'menu[enabled]' => FALSE,
+ 'menu[0][enabled]' => FALSE,
);
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
// Assert that there is no link for the node.
$this->drupalGet('test-page');
$this->assertNoLink($node_title);
+ // Ensure that the menu link is also no longer stored.
+ $plugin_id = $node->menu->first()->getMenuPluginId();
+ $this->assertFalse(\Drupal::service('plugin.manager.menu.link')->hasDefinition($plugin_id));
+
// Add a menu link to the Administration menu.
$item = MenuLinkContent::create(array(
'link' => [['uri' => 'entity:node/' . $node->id()]],
@@ -212,12 +245,13 @@ function testMenuNodeFormWidget() {
$this->assertText('Provide a menu link', 'Link in not allowed menu not shown in node edit form');
// Assert that the link is still in the Administration menu after save.
$this->drupalPostForm('node/' . $node->id() . '/edit', $edit, t('Save'));
- $link = MenuLinkContent::load($item->id());
+ $link = \Drupal::service('plugin.manager.menu.link')->getDefinition($node->menu->first()->getMenuPluginId());
$this->assertTrue($link, 'Link in not allowed menu still exists after saving node');
// Move the menu link back to the Tools menu.
- $item->menu_name->value = 'tools';
- $item->save();
+ $node->menu->menu_name = 'tools';
+ $node->save();
+
// Create a second node.
$child_node = $this->drupalCreateNode(array('type' => 'article'));
// Assign a menu link to the second node, being a child of the first one.
@@ -231,10 +265,10 @@ function testMenuNodeFormWidget() {
// Edit the first node.
$this->drupalGet('node/' . $node->id() . '/edit');
// Assert that it is not possible to set the parent of the first node to itself or the second node.
- $this->assertNoOption('edit-menu-menu-parent', 'tools:' . $item->getPluginId());
- $this->assertNoOption('edit-menu-menu-parent', 'tools:' . $child_item->getPluginId());
+ $this->assertNoOption('edit-menu-0-menu-parent', 'tools:'. $node->menu->first()->getMenuPluginId());
+ $this->assertNoOption('edit-menu-0-menu-parent', 'tools:'. $child_node->menu->first()->getMenuPluginId());
// Assert that unallowed Administration menu is not available in options.
- $this->assertNoOption('edit-menu-menu-parent', 'admin:');
+ $this->assertNoOption('edit-menu-0-menu-parent', 'admin:');
}
/**
diff --git a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php
index f756300..53c9119 100644
--- a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php
+++ b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php
@@ -3,8 +3,9 @@
namespace Drupal\system\Tests\Menu;
use Drupal\Core\Url;
-use Drupal\node\Entity\NodeType;
use Drupal\user\RoleInterface;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\field\Entity\FieldStorageConfig;
/**
* Tests breadcrumbs functionality.
@@ -18,7 +19,7 @@ class BreadcrumbTest extends MenuTestBase {
*
* @var array
*/
- public static $modules = array('menu_test', 'block');
+ public static $modules = array('menu_test', 'block', 'menu_link');
/**
* An administrative user.
@@ -166,11 +167,19 @@ function testBreadCrumbs() {
// @todo Also test all themes? Manually testing led to the suspicion that
// breadcrumbs may differ, possibly due to theme overrides.
$menus = array('main', 'tools');
- // Alter node type menu settings.
- $node_type = NodeType::load($type);
- $node_type->setThirdPartySetting('menu_ui', 'available_menus', $menus);
- $node_type->setThirdPartySetting('menu_ui', 'parent', 'tools:');
- $node_type->save();
+ // Add a menu field to the node type.
+ $field_storage_config = FieldStorageConfig::create(['field_name' => 'menu', 'entity_type' => 'node', 'type' => 'menu_link']);
+ $field_storage_config->save();
+ $field_config = FieldConfig::create(['entity_type' => 'node', 'bundle' => $type, 'field_name' => 'menu']);
+ $field_config->settings['available_menus'] = array_combine($menus, $menus);
+ $field_config->settings['default_menu_parent'] = 'tools:';
+ $field_config->save();
+
+ entity_get_form_display('node', $type, 'default')
+ ->setComponent('menu', [
+ 'type' => 'menu_link_default',
+ ])
+ ->save();
foreach ($menus as $menu) {
// Create a parent node in the current menu.
@@ -178,14 +187,11 @@ function testBreadCrumbs() {
$node2 = $this->drupalCreateNode(array(
'type' => $type,
'title' => $title,
- 'menu' => array(
- 'enabled' => 1,
- 'title' => 'Parent ' . $title,
- 'description' => '',
- 'menu_name' => $menu,
- 'parent' => '',
- ),
));
+ $node2->menu->title = 'Parent ' . $title;
+ $node2->menu->description = '';
+ $node2->menu->menu_name = $menu;
+ $node2->menu->parent = '';
if ($menu == 'tools') {
$parent = $node2;
@@ -204,7 +210,7 @@ function testBreadCrumbs() {
$link = reset($menu_links);
$edit = array(
- 'menu[menu_parent]' => $link->getMenuName() . ':' . $link->getPluginId(),
+ 'menu[0][menu_parent]' => $link->getMenuName() . ':' . $link->getPluginId(),
);
$this->drupalPostForm('node/' . $parent->id() . '/edit', $edit, t('Save and keep published'));
$expected = array(
@@ -212,10 +218,10 @@ function testBreadCrumbs() {
);
$trail = $home + $expected;
$tree = $expected + array(
- 'node/' . $parent->id() => $parent->menu['title'],
+ 'node/' . $parent->id() => $parent->menu->title,
);
$trail += array(
- 'node/' . $parent->id() => $parent->menu['title'],
+ 'node/' . $parent->id() => $parent->menu->title,
);
// Add a taxonomy term/tag to last node, and add a link for that term to the
diff --git a/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml b/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml
index 79156b2..26d29ea 100644
--- a/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml
+++ b/core/profiles/standard/config/install/core.entity_form_display.node.article.default.yml
@@ -80,4 +80,9 @@ content:
size: 60
placeholder: ''
third_party_settings: { }
+ field_menu:
+ weight: 32
+ settings: { }
+ third_party_settings: { }
+ type: menu_link_default
hidden: { }
diff --git a/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml b/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml
index 1fef06d..a04c484 100644
--- a/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml
+++ b/core/profiles/standard/config/install/core.entity_form_display.node.page.default.yml
@@ -3,10 +3,12 @@ status: true
dependencies:
config:
- field.field.node.page.body
+ - field.field.node.page.field_menu
- node.type.page
module:
- path
- text
+ - menu_link
id: node.page.default
targetEntityType: node
bundle: page
@@ -57,4 +59,9 @@ content:
size: 60
placeholder: ''
third_party_settings: { }
+ field_menu:
+ weight: 32
+ settings: { }
+ third_party_settings: { }
+ type: menu_link_default
hidden: { }
diff --git a/core/profiles/standard/config/install/field.field.node.article.field_menu.yml b/core/profiles/standard/config/install/field.field.node.article.field_menu.yml
new file mode 100644
index 0000000..178853b
--- /dev/null
+++ b/core/profiles/standard/config/install/field.field.node.article.field_menu.yml
@@ -0,0 +1,24 @@
+langcode: en
+status: true
+dependencies:
+ config:
+ - field.storage.node.field_menu
+ - node.type.article
+ module:
+ - menu_link
+id: node.article.field_menu
+field_name: field_menu
+entity_type: node
+bundle: article
+label: Menu link
+description: ''
+required: false
+translatable: true
+default_value: { }
+default_value_callback: ''
+settings:
+ available_menus:
+ - main
+ default_menu_parent: 'main:'
+third_party_settings: { }
+field_type: menu_link
diff --git a/core/profiles/standard/config/install/field.field.node.page.field_menu.yml b/core/profiles/standard/config/install/field.field.node.page.field_menu.yml
new file mode 100644
index 0000000..c8445a1
--- /dev/null
+++ b/core/profiles/standard/config/install/field.field.node.page.field_menu.yml
@@ -0,0 +1,24 @@
+langcode: en
+status: true
+dependencies:
+ config:
+ - field.storage.node.field_menu
+ - node.type.page
+ module:
+ - menu_link
+id: node.page.field_menu
+field_name: field_menu
+entity_type: node
+bundle: page
+label: Menu link
+description: ''
+required: false
+translatable: true
+default_value: { }
+default_value_callback: ''
+settings:
+ available_menus:
+ - main
+ default_menu_parent: 'main:'
+third_party_settings: { }
+field_type: menu_link
diff --git a/core/profiles/standard/config/install/field.storage.node.field_menu.yml b/core/profiles/standard/config/install/field.storage.node.field_menu.yml
new file mode 100644
index 0000000..cb69efc
--- /dev/null
+++ b/core/profiles/standard/config/install/field.storage.node.field_menu.yml
@@ -0,0 +1,17 @@
+langcode: en
+status: true
+dependencies:
+ module:
+ - menu_link
+ - node
+id: node.field_menu
+field_name: field_menu
+entity_type: node
+type: menu_link
+settings: { }
+module: menu_link
+locked: false
+cardinality: 1
+translatable: true
+indexes: { }
+persist_with_no_fields: false
diff --git a/core/profiles/standard/src/Tests/StandardTest.php b/core/profiles/standard/src/Tests/StandardTest.php
index 6be002b..5e898fa 100644
--- a/core/profiles/standard/src/Tests/StandardTest.php
+++ b/core/profiles/standard/src/Tests/StandardTest.php
@@ -44,6 +44,8 @@ function testStandard() {
'skip comment approval',
'create article content',
'create page content',
+ 'edit own page content',
+ 'administer menu',
));
$this->drupalLogin($this->adminUser);
// Configure the block.
@@ -100,6 +102,16 @@ function testStandard() {
$this->assertText('Foobar');
$this->assertNoText('Then she picked out two somebodies, Sally and me');
+ // Ensure that the field_menu link got created.
+ $node = $this->drupalCreateNode(array(
+ 'type' => 'page',
+ 'title' => 'Foobar',
+ 'promote' => 1,
+ 'status' => 1,
+ ));
+ $this->drupalGet("node/{$node->id()}/edit");
+ $this->assertFieldById('edit-field-menu-0-enabled', FALSE);
+
// Ensure block body exists.
$this->drupalGet('block/add');
$this->assertFieldByName('body[0][value]');
diff --git a/core/profiles/standard/standard.info.yml b/core/profiles/standard/standard.info.yml
index 82af32e..9c77190 100644
--- a/core/profiles/standard/standard.info.yml
+++ b/core/profiles/standard/standard.info.yml
@@ -15,6 +15,7 @@ dependencies:
- contextual
- contact
- menu_link_content
+ - menu_link
- datetime
- block_content
- quickedit