Commit 186e226e authored by webchick's avatar webchick

Issue #1814916 by andypost, tim.plunkett, sun, yoroy: Convert menus into entities.

parent 159f3ce9
......@@ -429,6 +429,10 @@ function drupal_install_system() {
module_list_reset();
module_implements_reset();
// To ensure that the system module can be found by the plugin system, warm
// the module list cache.
// @todo Remove this in http://drupal.org/node/1798732.
module_list();
config_install_default_config('module', 'system');
module_invoke('system', 'install');
......
......@@ -2641,7 +2641,6 @@ function menu_reset_static_cache() {
drupal_static_reset('menu_tree');
drupal_static_reset('menu_tree_all_data');
drupal_static_reset('menu_tree_page_data');
drupal_static_reset('menu_load_all');
drupal_static_reset('menu_link_get_preferred');
}
......
......@@ -662,7 +662,7 @@ function block_menu_delete($menu) {
$block_configs = config_get_storage_names_with_prefix('plugin.core.block');
foreach ($block_configs as $config_id) {
$config = config($config_id);
if ($config->get('id') == 'menu_menu_block:' . $menu['menu_name']) {
if ($config->get('id') == 'menu_menu_block:' . $menu->id()) {
$config->delete();
}
}
......
......@@ -76,8 +76,8 @@ function field_ui_menu() {
// Extract path information from the bundle.
$path = $bundle_info['admin']['path'];
// Different bundles can appear on the same path (e.g. %node_type and
// %comment_node_type). To allow field_ui_menu_load() to extract the
// actual bundle object from the translated menu router path
// %comment_node_type). To allow field_ui_instance_load() to extract
// the actual bundle object from the translated menu router path
// arguments, we need to identify the argument position of the bundle
// name string ('bundle argument') and pass that position to the menu
// loader. The position needs to be casted into a string; otherwise it
......@@ -90,7 +90,7 @@ function field_ui_menu() {
$bundle_arg = $bundle_name;
$bundle_pos = '0';
}
// This is the position of the %field_ui_menu placeholder in the
// This is the position of the %field_ui_instance placeholder in the
// items below.
$field_position = count(explode('/', $path)) + 1;
......@@ -109,15 +109,15 @@ function field_ui_menu() {
'weight' => 1,
'file' => 'field_ui.admin.inc',
) + $access;
$items["$path/fields/%field_ui_menu"] = array(
$items["$path/fields/%field_ui_instance"] = array(
'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
'title callback' => 'field_ui_menu_title',
'title callback' => 'field_ui_instance_title',
'title arguments' => array($field_position),
'page callback' => 'drupal_get_form',
'page arguments' => array('field_ui_field_edit_form', $field_position),
'file' => 'field_ui.admin.inc',
) + $access;
$items["$path/fields/%field_ui_menu/edit"] = array(
$items["$path/fields/%field_ui_instance/edit"] = array(
'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
'title' => 'Edit',
'page callback' => 'drupal_get_form',
......@@ -125,7 +125,7 @@ function field_ui_menu() {
'type' => MENU_DEFAULT_LOCAL_TASK,
'file' => 'field_ui.admin.inc',
) + $access;
$items["$path/fields/%field_ui_menu/field-settings"] = array(
$items["$path/fields/%field_ui_instance/field-settings"] = array(
'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
'title' => 'Field settings',
'page callback' => 'drupal_get_form',
......@@ -133,7 +133,7 @@ function field_ui_menu() {
'type' => MENU_LOCAL_TASK,
'file' => 'field_ui.admin.inc',
) + $access;
$items["$path/fields/%field_ui_menu/widget-type"] = array(
$items["$path/fields/%field_ui_instance/widget-type"] = array(
'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
'title' => 'Widget type',
'page callback' => 'drupal_get_form',
......@@ -141,7 +141,7 @@ function field_ui_menu() {
'type' => MENU_LOCAL_TASK,
'file' => 'field_ui.admin.inc',
) + $access;
$items["$path/fields/%field_ui_menu/delete"] = array(
$items["$path/fields/%field_ui_instance/delete"] = array(
'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
'title' => 'Delete',
'page callback' => 'drupal_get_form',
......@@ -211,12 +211,12 @@ function field_ui_menu() {
*
* @ingroup field
*/
function field_ui_menu_load($field_name, $entity_type, $bundle_name, $bundle_pos, $map) {
function field_ui_instance_load($field_name, $entity_type, $bundle_name, $bundle_pos, $map) {
// Extract the actual bundle name from the translated argument map.
// The menu router path to manage fields of an entity can be shared among
// multiple bundles. For example:
// - admin/structure/types/manage/%node_type/fields/%field_ui_menu
// - admin/structure/types/manage/%comment_node_type/fields/%field_ui_menu
// - admin/structure/types/manage/%node_type/fields/%field_ui_instance
// - admin/structure/types/manage/%comment_node_type/fields/%field_ui_instance
// The menu system will automatically load the correct bundle depending on the
// actual path arguments, but this menu loader function only receives the node
// type string as $bundle_name, which is not the bundle name for comments.
......@@ -242,7 +242,7 @@ function field_ui_menu_load($field_name, $entity_type, $bundle_name, $bundle_pos
*
* @see field_ui_menu()
*/
function field_ui_menu_title($instance) {
function field_ui_instance_title($instance) {
return $instance['label'];
}
......
id: account
label: User account menu
description: Links related to the user account.
id: admin
label: Administration
description: Contains links to administrative tasks.
id: footer
label: Footer
description: Use this for linking to site information.
id: main
label: Main navigation
description: Use this for linking to the main site sections.
id: tools
label: Tools
description: Contains links for site visitors. Some modules add their links here.
<?php
/**
* @file
* Contains Drupal\menu\MenuFormController.
*/
namespace Drupal\menu;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityFormController;
/**
* Base form controller for menu edit forms.
*/
class MenuFormController extends EntityFormController {
/**
* Overrides Drupal\Core\Entity\EntityFormController::form().
*/
public function form(array $form, array &$form_state, EntityInterface $menu) {
$form = parent::form($form, $form_state, $menu);
$system_menus = menu_list_system_menus();
$form['label'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => $menu->label(),
'#required' => TRUE,
// The title of a system menu cannot be altered.
'#access' => !isset($system_menus[$menu->id()]),
);
$form['id'] = array(
'#type' => 'machine_name',
'#title' => t('Menu name'),
'#default_value' => $menu->id(),
'#maxlength' => MENU_MAX_MENU_NAME_LENGTH_UI,
'#description' => t('A unique name to construct the URL for the menu. It must only contain lowercase letters, numbers and hyphens.'),
'#machine_name' => array(
'exists' => 'menu_edit_menu_name_exists',
'source' => array('label'),
'replace_pattern' => '[^a-z0-9-]+',
'replace' => '-',
),
// A menu's machine name cannot be changed.
'#disabled' => !$menu->isNew() || isset($system_menus[$menu->id()]),
);
$form['description'] = array(
'#type' => 'textarea',
'#title' => t('Description'),
'#default_value' => $menu->description,
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array(
'#type' => 'submit',
'#value' => t('Save'),
'#button_type' => 'primary',
);
// Only custom menus may be deleted.
$form['actions']['delete'] = array(
'#type' => 'submit',
'#value' => t('Delete'),
'#access' => !$menu->isNew() && !isset($system_menus[$menu->id()]),
);
return $form;
}
/**
* Overrides Drupal\Core\Entity\EntityFormController::save().
*/
public function save(array $form, array &$form_state) {
$menu = $this->getEntity($form_state);
if ($menu->isNew()) {
// Add 'menu-' to the menu name to help avoid name-space conflicts.
$menu->set('id', 'menu-' . $menu->id());
}
$status = $menu->save();
$uri = $menu->uri();
if ($status == SAVED_UPDATED) {
drupal_set_message(t('Menu %label has been updated.', array('%label' => $menu->label())));
watchdog('menu', 'Menu %label has been updated.', array('%label' => $menu->label()), WATCHDOG_NOTICE, l(t('Edit'), $uri['path'] . '/edit'));
}
else {
drupal_set_message(t('Menu %label has been added.', array('%label' => $menu->label())));
watchdog('menu', 'Menu %label has been added.', array('%label' => $menu->label()), WATCHDOG_NOTICE, l(t('Edit'), $uri['path'] . '/edit'));
}
$form_state['redirect'] = 'admin/structure/menu/manage/' . $menu->id();
}
/**
* Overrides Drupal\Core\Entity\EntityFormController::delete().
*/
public function delete(array $form, array &$form_state) {
$menu = $this->getEntity($form_state);
$form_state['redirect'] = 'admin/structure/menu/manage/' . $menu->id() . '/delete';
}
}
<?php
/**
* Contains \Drupal\menu\MenuListController.
*/
namespace Drupal\menu;
use Drupal\Core\Config\Entity\ConfigEntityListController;
use Drupal\Core\Entity\EntityInterface;
/**
* Provides a listing of contact categories.
*/
class MenuListController extends ConfigEntityListController {
/**
* Overrides \Drupal\Core\Entity\EntityListController::buildHeader().
*/
public function buildHeader() {
$row['title'] = t('Title');
$row['description'] = array(
'data' => t('Description'),
'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
);
$row['operations'] = t('Operations');
return $row;
}
/**
* Overrides \Drupal\Core\Entity\EntityListController::buildRow().
*/
public function buildRow(EntityInterface $entity) {
$row['title'] = array(
'data' => check_plain($entity->label()),
'class' => array('menu-label'),
);
$row['description'] = filter_xss_admin($entity->description);
$row['operations']['data'] = $this->buildOperations($entity);
return $row;
}
/**
* Overrides \Drupal\Core\Entity\EntityListController::getOperations();
*/
public function getOperations(EntityInterface $entity) {
$operations = parent::getOperations($entity);
$uri = $entity->uri();
$operations['list'] = array(
'title' => t('list links'),
'href' => $uri['path'],
'options' => $uri['options'],
'weight' => 0,
);
$operations['edit']['title'] = t('edit menu');
$operations['add'] = array(
'title' => t('add link'),
'href' => $uri['path'] . '/add',
'options' => $uri['options'],
'weight' => 20,
);
// System menus could not be deleted.
$system_menus = menu_list_system_menus();
if (isset($system_menus[$entity->id()])) {
unset($operations['delete']);
}
else {
$operations['delete']['title'] = t('delete menu');
}
return $operations;
}
/**
* Overrides \Drupal\Core\Entity\EntityListController::render();
*/
public function render() {
$build = parent::render();
$build['#attached']['css'][] = drupal_get_path('module', 'menu') . '/menu.admin.css';
return $build;
}
}
......@@ -102,8 +102,8 @@ function doStandardMenuTests() {
*/
function doCustomMenuTests() {
$this->menu = $this->addCustomMenu();
$this->doMenuTests($this->menu['menu_name']);
$this->addInvalidMenuLink($this->menu['menu_name']);
$this->doMenuTests($this->menu->id());
$this->addInvalidMenuLink($this->menu->id());
$this->addCustomMenuCRUD();
}
......@@ -113,25 +113,25 @@ function doCustomMenuTests() {
function addCustomMenuCRUD() {
// Add a new custom menu.
$menu_name = substr(hash('sha256', $this->randomName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI);
$title = $this->randomName(16);
$label = $this->randomName(16);
$menu = array(
'menu_name' => $menu_name,
'title' => $title,
$menu = entity_create('menu', array(
'id' => $menu_name,
'label' => $label,
'description' => 'Description text',
);
menu_save($menu);
));
$menu->save();
// Assert the new menu.
$this->drupalGet('admin/structure/menu/manage/' . $menu_name . '/edit');
$this->assertRaw($title, 'Custom menu was added.');
$this->assertRaw($label, 'Custom menu was added.');
// Edit the menu.
$new_title = $this->randomName(16);
$menu['title'] = $new_title;
menu_save($menu);
$new_label = $this->randomName(16);
$menu->set('label', $new_label);
$menu->save();
$this->drupalGet('admin/structure/menu/manage/' . $menu_name . '/edit');
$this->assertRaw($new_title, 'Custom menu was edited.');
$this->assertRaw($new_label, 'Custom menu was edited.');
}
/**
......@@ -142,11 +142,11 @@ function addCustomMenu() {
// Try adding a menu using a menu_name that is too long.
$this->drupalGet('admin/structure/menu/add');
$menu_name = substr(hash('sha256', $this->randomName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI + 1);
$title = $this->randomName(16);
$label = $this->randomName(16);
$edit = array(
'menu_name' => $menu_name,
'id' => $menu_name,
'description' => '',
'title' => $title,
'label' => $label,
);
$this->drupalPost('admin/structure/menu/add', $edit, t('Save'));
......@@ -159,7 +159,7 @@ function addCustomMenu() {
// Change the menu_name so it no longer exceeds the maximum length.
$menu_name = substr(hash('sha256', $this->randomName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI);
$edit['menu_name'] = $menu_name;
$edit['id'] = $menu_name;
$this->drupalPost('admin/structure/menu/add', $edit, t('Save'));
// Verify that no validation error is given for menu_name length.
......@@ -168,16 +168,16 @@ function addCustomMenu() {
'%max' => MENU_MAX_MENU_NAME_LENGTH_UI,
'%length' => drupal_strlen($menu_name),
)));
// Unlike most other modules, there is no confirmation message displayed.
// Verify that confirmation message displayed.
$this->assertRaw(t('Menu %label has been added.', array('%label' => $label)));
$this->drupalGet('admin/structure/menu');
$this->assertText($title, 'Menu created');
$this->assertText($label, 'Menu created');
// Enable the custom menu block.
$menu_name = 'menu-' . $menu_name; // Drupal prepends the name with 'menu-'.
// Confirm that the custom menu block is available.
$this->drupalGet('admin/structure/block/list/block_plugin_ui:' . variable_get('theme_default', 'stark') . '/add');
$this->assertText($title);
$this->assertText($label);
// Enable the block.
$this->drupalPlaceBlock('menu_menu_block:' . $menu_name);
......@@ -190,13 +190,13 @@ function addCustomMenu() {
* @param string $menu_name Custom menu name.
*/
function deleteCustomMenu($menu) {
$menu_name = $this->menu['menu_name'];
$title = $this->menu['title'];
$menu_name = $this->menu->id();
$label = $this->menu->label();
// Delete custom menu.
$this->drupalPost("admin/structure/menu/manage/$menu_name/delete", array(), t('Delete'));
$this->assertResponse(200);
$this->assertRaw(t('The custom menu %title has been deleted.', array('%title' => $title)), 'Custom menu was deleted');
$this->assertRaw(t('The custom menu %title has been deleted.', array('%title' => $label)), 'Custom menu was deleted');
$this->assertFalse(menu_load($menu_name), 'Custom menu was deleted');
// Test if all menu links associated to the menu were removed from database.
$result = db_query("SELECT menu_name FROM {menu_links} WHERE menu_name = :menu_name", array(':menu_name' => $menu_name))->fetchField();
......
.menu-operations {
width: 100px;
}
.menu-enabled {
width: 70px;
}
.menu-label {
font-weight: bold;
}
This diff is collapsed.
......@@ -17,11 +17,8 @@
* Contributed modules may use the information to perform actions based on the
* information entered into the menu system.
*
* @param $menu
* An array representing a custom menu:
* - menu_name: The unique name of the custom menu.
* - title: The human readable menu title.
* - description: The custom menu description.
* @param \Drupal\system\Plugin\Core\Entity\Menu $menu
* A menu entity.
*
* @see hook_menu_update()
* @see hook_menu_delete()
......@@ -29,7 +26,7 @@
function hook_menu_insert($menu) {
// For example, we track available menus in a variable.
$my_menus = variable_get('my_module_menus', array());
$my_menus[$menu['menu_name']] = $menu['menu_name'];
$my_menus[$menu->id()] = $menu->id();
variable_set('my_module_menus', $my_menus);
}
......@@ -40,13 +37,8 @@ function hook_menu_insert($menu) {
* Contributed modules may use the information to perform actions based on the
* information entered into the menu system.
*
* @param $menu
* An array representing a custom menu:
* - menu_name: The unique name of the custom menu.
* - title: The human readable menu title.
* - description: The custom menu description.
* - old_name: The current 'menu_name'. Note that internal menu names cannot
* be changed after initial creation.
* @param \Drupal\system\Plugin\Core\Entity\Menu $menu
* A menu entity.
*
* @see hook_menu_insert()
* @see hook_menu_delete()
......@@ -54,7 +46,7 @@ function hook_menu_insert($menu) {
function hook_menu_update($menu) {
// For example, we track available menus in a variable.
$my_menus = variable_get('my_module_menus', array());
$my_menus[$menu['menu_name']] = $menu['menu_name'];
$my_menus[$menu->id()] = $menu->id();
variable_set('my_module_menus', $my_menus);
}
......@@ -66,11 +58,8 @@ function hook_menu_update($menu) {
* information to perform actions based on the information entered into the menu
* system.
*
* @param $link
* An array representing a custom menu:
* - menu_name: The unique name of the custom menu.
* - title: The human readable menu title.
* - description: The custom menu description.
* @param \Drupal\system\Plugin\Core\Entity\Menu $menu
* A menu entity.
*
* @see hook_menu_insert()
* @see hook_menu_update()
......@@ -78,7 +67,7 @@ function hook_menu_update($menu) {
function hook_menu_delete($menu) {
// Delete the record from our variable.
$my_menus = variable_get('my_module_menus', array());
unset($my_menus[$menu['menu_name']]);
unset($my_menus[$menu->id()]);
variable_set('my_module_menus', $my_menus);
}
......
......@@ -5,63 +5,7 @@
* Install, update and uninstall functions for the menu module.
*/
/**
* Implements hook_schema().
*/
function menu_schema() {
$schema['menu_custom'] = array(
'description' => 'Holds definitions for top-level custom menus (for example, Main navigation menu).',
'fields' => array(
'menu_name' => array(
'type' => 'varchar',
'length' => 32,
'not null' => TRUE,
'default' => '',
'description' => 'Primary Key: Unique key for menu. This is used as a block delta so length is 32.',
),
'title' => array(
'type' => 'varchar',
'length' => 255,
'not null' => TRUE,
'default' => '',
'description' => 'Menu title; displayed at top of block.',
'translatable' => TRUE,
),
'description' => array(
'type' => 'text',
'not null' => FALSE,
'description' => 'Menu description.',
'translatable' => TRUE,
),
),
'primary key' => array('menu_name'),
);
return $schema;
}
/**
* Implements hook_install().
*/
function menu_install() {
$system_menus = menu_list_system_menus();
$t = get_t();
$descriptions = array(
'tools' => $t('Contains links for site visitors. Some modules add their links here.'),
'account' => $t('Links related to the user account.'),
'admin' => $t('Contains links to administrative tasks.'),
'main' => $t('Use this for linking to the main site sections.'),
'footer' => $t('Use this for linking to site information.'),
);
foreach ($system_menus as $menu_name => $title) {
$menu = array(
'menu_name' => $menu_name,
'title' => $t($title),
'description' => $descriptions[$menu_name],
);
menu_save($menu);
}
}
use Drupal\Component\Uuid\Uuid;
/**
* Implements hook_uninstall().
......@@ -126,3 +70,22 @@ function menu_update_8003() {
));
}
/**
* Migrate menus into configuration.
*
* @ingroup config_upgrade
*/
function menu_update_8004() {
$uuid = new Uuid();
$result = db_query('SELECT * FROM {menu_custom}');
foreach ($result as $menu) {
// Save the config object.
config('menu.menu.' . $menu->menu_name)
->set('id', $menu->menu_name)
->set('uuid', $uuid->generate())
->set('label', $menu->title)
->set('description', $menu->description)
->save();
update_config_manifest_add('menu.menu', array($menu->menu_name));
}
}
......@@ -12,6 +12,7 @@
*/
use Drupal\node\Plugin\Core\Entity\Node;
use Drupal\system\Plugin\Core\Entity\Menu;
use Drupal\system\Plugin\block\block\SystemMenuBlock;
use Symfony\Component\HttpFoundation\JsonResponse;
......@@ -82,8 +83,7 @@ function menu_menu() {
);
$items['admin/structure/menu/add'] = array(
'title' => 'Add menu',
'page callback' => 'drupal_get_form',
'page arguments' => array('menu_edit_menu', 'add'),
'page callback' => 'menu_menu_add',
'access arguments' => array('administer menu'),
'type' => MENU_LOCAL_ACTION,
'file' => 'menu.admin.inc',
......@@ -101,7 +101,7 @@ function menu_menu() {
'title' => 'Customize menu',
'page callback' => 'drupal_get_form',
'page arguments' => array('menu_overview_form', 4),
'title callback' => 'menu_overview_title',
'title callback' => 'entity_page_label',
'title arguments' => array(4),
'access arguments' => array('administer menu'),
'file' => 'menu.admin.inc',
......@@ -122,8 +122,8 @@ function menu_menu() {
);
$items['admin/structure/menu/manage/%menu/edit'] = array(
'title' => 'Edit menu',
'page callback' => 'drupal_get_form',
'page arguments' => array('menu_edit_menu', 'edit', 4),
'page callback' => 'menu_menu_edit',
'page arguments' => array(4),
'access arguments' => array('administer menu'),
'type' => MENU_LOCAL_TASK,
'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
......@@ -160,6 +160,29 @@ function menu_menu() {
return $items;
}
/**
* Implements hook_entity_info_alter().
*/
function menu_entity_info_alter(&$entity_info) {
$entity_info['menu']['list_controller_class'] = 'Drupal\menu\MenuListController';
$entity_info['menu']['uri_callback'] = 'menu_uri';
$entity_info['menu']['form_controller_class'] = array(
'default' => 'Drupal\menu\MenuFormController',
);
}
/**
* Entity URI callback.
*
* @param \Drupal\system\Plugin\Core\Entity\Menu $menu
* A Menu entity.
*/
function menu_uri(Menu $menu) {
return array(
'path' => 'admin/structure/menu/manage/' . $menu->id(),
);
}
/**
* Implements hook_theme().
*/
......@@ -169,10 +192,6 @@ function menu_theme() {
'file' => 'menu.admin.inc',
'render element' => 'form',
),
'menu_admin_overview' => array(
'file' => 'menu.admin.inc',
'variables' => array('title' => NULL, 'name' => NULL, 'description' => NULL),
),
);
}
......@@ -186,13 +205,13 @@ function menu_enable() {
$base_link = db_query("SELECT mlid AS plid, menu_name FROM {menu_links} WHERE link_path = 'admin/structure/menu' AND module = 'system'")->fetchAssoc();
$base_link['router_path'] = 'admin/structure/menu/manage/%';
$base_link['module'] = 'menu';
$result = db_query("SELECT * FROM {menu_custom}", array(), array('fetch' => PDO::FETCH_ASSOC));
foreach ($result as $menu) {
$menus = entity_load_multiple('menu');
foreach ($menus as $menu) {
// $link is passed by reference to menu_link_save(), so we make a copy of $base_link.
$link = $base_link;
$link['mlid'] = 0;
$link['link_title'] = $menu['title'];
$link['link_path'] = 'admin/structure/menu/manage/' . $menu['menu_name'];
$link['link_title'] = $menu->label();
$link['link_path'] = 'admin/structure/menu/manage/' . $menu->id();
$menu_link = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :path AND plid = :plid", array(
':path' => $link['link_path'],
':plid' => $link['plid']
......@@ -205,13 +224,6 @@ function menu_enable() {
menu_cache_clear_all();
}
/**
* Title callback for the menu overview page and links.
*/
function menu_overview_title($menu) {
return $menu['title'];
}
/**
* Load the data for a single custom menu.
*
......@@ -221,129 +233,69 @@ function menu_overview_title($menu) {
* Array defining the custom menu, or FALSE if the menu doesn't exist.
*/
function menu_load($menu_name) {
$all_menus = menu_load_all();