Commit 186e226e authored by webchick's avatar webchick
Browse files

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;
}
......@@ -6,57 +6,42 @@
*/
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Drupal\system\Plugin\Core\Entity\Menu;
/**
* Menu callback which shows an overview page of all the custom menus and their descriptions.
*/
function menu_overview_page() {
$result = db_query("SELECT * FROM {menu_custom} ORDER BY title", array(), array('fetch' => PDO::FETCH_ASSOC));
$header = array(t('Title'), t('Operations'));
$rows = array();
foreach ($result as $menu) {
$row = array();
$row[] = theme('menu_admin_overview', array('title' => $menu['title'], 'name' => $menu['menu_name'], 'description' => $menu['description']));
$links = array();
$links['list'] = array(
'title' => t('list links'),
'href' => 'admin/structure/menu/manage/' . $menu['menu_name'],
);
$links['edit'] = array(
'title' => t('edit menu'),
'href' => 'admin/structure/menu/manage/' . $menu['menu_name'] . '/edit',
);
$links['add'] = array(
'title' => t('add link'),
'href' => 'admin/structure/menu/manage/' . $menu['menu_name'] . '/add',
);
$row[] = array(
'data' => array(
'#type' => 'operations',
'#links' => $links,
),
);
$rows[] = $row;
}
return theme('table', array('header' => $header, 'rows' => $rows));
return entity_list_controller('menu')->render();
}
/**
* Returns HTML for a menu title and description for the menu overview page.
* Page callback: Presents the menu creation form.
*
* @param $variables
* An associative array containing:
* - title: The menu's title.
* - description: The menu's description.
* @return array
* A form array as expected by drupal_render().
*
* @ingroup themeable
* @see menu_menu()
*/
function theme_menu_admin_overview($variables) {
$output = check_plain($variables['title']);
$output .= '<div class="description">' . filter_xss_admin($variables['description']) . '</div>';
function menu_menu_add() {
$menu = entity_create('menu', array());
return entity_get_form($menu);
}
return $output;
/**
* Page callback: Presents the menu edit form.
*
* @param \Drupal\system\Plugin\Core\Entity\Menu $menu
* The menu to edit.
*
* @return array
* A form array as expected by drupal_render().
*
* @see menu_menu()
*/
function menu_menu_edit(Menu $menu) {
drupal_set_title(t('Edit menu %label', array('%label' => $menu->label())), PASS_THROUGH);
return entity_get_form($menu);
}
/**
......@@ -73,7 +58,7 @@ function menu_overview_form($form, &$form_state, $menu) {
FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
WHERE ml.menu_name = :menu
ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC";
$result = db_query($sql, array(':menu' => $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
$result = db_query($sql, array(':menu' => $menu->id()), array('fetch' => PDO::FETCH_ASSOC));
$links = array();
foreach ($result as $item) {
$links[] = $item;
......@@ -99,7 +84,7 @@ function menu_overview_form($form, &$form_state, $menu) {
);
}
else {
$form['#empty_text'] = t('There are no menu links yet. <a href="@link">Add link</a>.', array('@link' => url('admin/structure/menu/manage/'. $form['#menu']['menu_name'] .'/add')));
$form['#empty_text'] = t('There are no menu links yet. <a href="@link">Add link</a>.', array('@link' => url('admin/structure/menu/manage/'. $form['#menu']->id() .'/add')));
}
return $form;
}
......@@ -296,7 +281,7 @@ function theme_menu_overview_form($variables) {
function menu_edit_item($form, &$form_state, $type, $item, $menu) {
if ($type == 'add' || empty($item)) {
// This is an add form, initialize the menu link.
$item = array('link_title' => '', 'mlid' => 0, 'plid' => 0, 'menu_name' => $menu['menu_name'], 'weight' => 0, 'link_path' => '', 'options' => array(), 'module' => 'menu', 'expanded' => 0, 'hidden' => 0, 'has_children' => 0);
$item = array('link_title' => '', 'mlid' => 0, 'plid' => 0, 'menu_name' => $menu->id(), 'weight' => 0, 'link_path' => '', 'options' => array(), 'module' => 'menu', 'expanded' => 0, 'hidden' => 0, 'has_children' => 0);
}
else {
// Get the human-readable menu title from the given menu name.
......@@ -477,86 +462,13 @@ function menu_edit_item_submit($form, &$form_state) {
$form_state['redirect'] = 'admin/structure/menu/manage/' . $item['menu_name'];
}
/**
* Menu callback; Build the form that handles the adding/editing of a custom menu.
*/
function menu_edit_menu($form, &$form_state, $type, $menu = array()) {
$system_menus = menu_list_system_menus();
$menu += array(
'menu_name' => '',
'old_name' => !empty($menu['menu_name']) ? $menu['menu_name'] : '',
'title' => '',
'description' => '',
);
// Allow menu_edit_menu_submit() and other form submit handlers to determine
// whether the menu already exists.
$form['#insert'] = empty($menu['old_name']);
$form['old_name'] = array(
'#type' => 'value',
'#value' => $menu['old_name'],
);
$form['title'] = array(
'#type' => 'textfield',
'#title' => t('Title'),
'#default_value' => $menu['title'],
'#required' => TRUE,
// The title of a system menu cannot be altered.
'#access' => !isset($system_menus[$menu['menu_name']]),
);
$form['menu_name'] = array(
'#type' => 'machine_name',
'#title' => t('Menu name'),
'#default_value' => $menu['menu_name'],
'#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('title'),
'replace_pattern' => '[^a-z0-9-]+',
'replace' => '-',