diff --git a/core/includes/install.inc b/core/includes/install.inc index aada754..9223b9c 100644 --- a/core/includes/install.inc +++ b/core/includes/install.inc @@ -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'); diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 7d419ac..e6810b8 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -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'); } diff --git a/core/modules/block/block.module b/core/modules/block/block.module index a31feec..d9155c3 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -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(); } } diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module index a04c0dd..dd2a1cc 100644 --- a/core/modules/field_ui/field_ui.module +++ b/core/modules/field_ui/field_ui.module @@ -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']; } diff --git a/core/modules/menu/config/menu.menu.account.yml b/core/modules/menu/config/menu.menu.account.yml new file mode 100644 index 0000000..6be2706 --- /dev/null +++ b/core/modules/menu/config/menu.menu.account.yml @@ -0,0 +1,3 @@ +id: account +label: User account menu +description: Links related to the user account. diff --git a/core/modules/menu/config/menu.menu.admin.yml b/core/modules/menu/config/menu.menu.admin.yml new file mode 100644 index 0000000..5435da5 --- /dev/null +++ b/core/modules/menu/config/menu.menu.admin.yml @@ -0,0 +1,3 @@ +id: admin +label: Administration +description: Contains links to administrative tasks. diff --git a/core/modules/menu/config/menu.menu.footer.yml b/core/modules/menu/config/menu.menu.footer.yml new file mode 100644 index 0000000..9dda784 --- /dev/null +++ b/core/modules/menu/config/menu.menu.footer.yml @@ -0,0 +1,3 @@ +id: footer +label: Footer +description: Use this for linking to site information. diff --git a/core/modules/menu/config/menu.menu.main.yml b/core/modules/menu/config/menu.menu.main.yml new file mode 100644 index 0000000..3dfe975 --- /dev/null +++ b/core/modules/menu/config/menu.menu.main.yml @@ -0,0 +1,3 @@ +id: main +label: Main navigation +description: Use this for linking to the main site sections. diff --git a/core/modules/menu/config/menu.menu.tools.yml b/core/modules/menu/config/menu.menu.tools.yml new file mode 100644 index 0000000..3f15287 --- /dev/null +++ b/core/modules/menu/config/menu.menu.tools.yml @@ -0,0 +1,3 @@ +id: tools +label: Tools +description: Contains links for site visitors. Some modules add their links here. diff --git a/core/modules/menu/lib/Drupal/menu/MenuFormController.php b/core/modules/menu/lib/Drupal/menu/MenuFormController.php new file mode 100644 index 0000000..41e5696 --- /dev/null +++ b/core/modules/menu/lib/Drupal/menu/MenuFormController.php @@ -0,0 +1,103 @@ + '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'; + } + +} diff --git a/core/modules/menu/lib/Drupal/menu/MenuListController.php b/core/modules/menu/lib/Drupal/menu/MenuListController.php new file mode 100644 index 0000000..a833dbc --- /dev/null +++ b/core/modules/menu/lib/Drupal/menu/MenuListController.php @@ -0,0 +1,83 @@ + 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; + } + +} diff --git a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php index caf27e6..a0d8d28 100644 --- a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php +++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php @@ -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(); diff --git a/core/modules/menu/menu.admin.css b/core/modules/menu/menu.admin.css index 8717aca..efbfe75 100644 --- a/core/modules/menu/menu.admin.css +++ b/core/modules/menu/menu.admin.css @@ -1,6 +1,6 @@ -.menu-operations { - width: 100px; -} .menu-enabled { width: 70px; } +.menu-label { + font-weight: bold; +} diff --git a/core/modules/menu/menu.admin.inc b/core/modules/menu/menu.admin.inc index 4a78495..1962ce3 100644 --- a/core/modules/menu/menu.admin.inc +++ b/core/modules/menu/menu.admin.inc @@ -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 .= '
' . filter_xss_admin($variables['description']) . '
'; +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. Add link.', array('@link' => url('admin/structure/menu/manage/'. $form['#menu']['menu_name'] .'/add'))); + $form['#empty_text'] = t('There are no menu links yet. Add link.', 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. @@ -478,85 +463,12 @@ function menu_edit_item_submit($form, &$form_state) { } /** - * 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' => '-', - ), - // A menu's machine name cannot be changed. - '#disabled' => !empty($menu['old_name']) || isset($system_menus[$menu['menu_name']]), - ); - - $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' => $type == 'edit' && !isset($system_menus[$menu['menu_name']]), - '#submit' => array('menu_custom_delete_submit'), - ); - - return $form; -} - -/** - * Submit function for the 'Delete' button on the menu editing form. - */ -function menu_custom_delete_submit($form, &$form_state) { - $form_state['redirect'] = 'admin/structure/menu/manage/' . $form_state['values']['menu_name'] . '/delete'; -} - -/** * Menu callback; check access and get a confirm form for deletion of a custom menu. */ function menu_delete_menu_page($menu) { // System-defined menus may not be deleted. $system_menus = menu_list_system_menus(); - if (isset($system_menus[$menu['menu_name']])) { + if (isset($system_menus[$menu->id()])) { throw new AccessDeniedHttpException(); } return drupal_get_form('menu_delete_menu_confirm', $menu); @@ -565,15 +477,15 @@ function menu_delete_menu_page($menu) { /** * Build a confirm form for deletion of a custom menu. */ -function menu_delete_menu_confirm($form, &$form_state, $menu) { +function menu_delete_menu_confirm($form, &$form_state, Menu $menu) { $form['#menu'] = $menu; $caption = ''; - $num_links = db_query("SELECT COUNT(*) FROM {menu_links} WHERE menu_name = :menu", array(':menu' => $menu['menu_name']))->fetchField(); + $num_links = db_query("SELECT COUNT(*) FROM {menu_links} WHERE menu_name = :menu", array(':menu' => $menu->id()))->fetchField(); if ($num_links) { - $caption .= '

' . format_plural($num_links, 'Warning: There is currently 1 menu link in %title. It will be deleted (system-defined items will be reset).', 'Warning: There are currently @count menu links in %title. They will be deleted (system-defined links will be reset).', array('%title' => $menu['title'])) . '

'; + $caption .= '

' . format_plural($num_links, 'Warning: There is currently 1 menu link in %title. It will be deleted (system-defined items will be reset).', 'Warning: There are currently @count menu links in %title. They will be deleted (system-defined links will be reset).', array('%title' => $menu->label())) . '

'; } $caption .= '

' . t('This action cannot be undone.') . '

'; - return confirm_form($form, t('Are you sure you want to delete the custom menu %title?', array('%title' => $menu['title'])), 'admin/structure/menu/manage/' . $menu['menu_name'], $caption, t('Delete')); + return confirm_form($form, t('Are you sure you want to delete the custom menu %title?', array('%title' => $menu->label())), 'admin/structure/menu/manage/' . $menu->id(), $caption, t('Delete')); } /** @@ -585,26 +497,26 @@ function menu_delete_menu_confirm_submit($form, &$form_state) { // System-defined menus may not be deleted - only menus defined by this module. $system_menus = menu_list_system_menus(); - if (isset($system_menus[$menu['menu_name']]) || !(db_query("SELECT 1 FROM {menu_custom} WHERE menu_name = :menu", array(':menu' => $menu['menu_name']))->fetchField())) { + if (isset($system_menus[$menu->id()])) { return; } // Reset all the menu links defined by the system via hook_menu(). - $result = db_query("SELECT * FROM {menu_links} ml INNER JOIN {menu_router} m ON ml.router_path = m.path WHERE ml.menu_name = :menu AND ml.module = 'system' ORDER BY m.number_parts ASC", array(':menu' => $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC)); + $result = db_query("SELECT * FROM {menu_links} ml INNER JOIN {menu_router} m ON ml.router_path = m.path WHERE ml.menu_name = :menu AND ml.module = 'system' ORDER BY m.number_parts ASC", array(':menu' => $menu->id()), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $link) { menu_reset_item($link); } // Delete all links to the overview page for this menu. - $result = db_query("SELECT mlid FROM {menu_links} ml WHERE ml.link_path = :link", array(':link' => 'admin/structure/menu/manage/' . $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC)); + $result = db_query("SELECT mlid FROM {menu_links} ml WHERE ml.link_path = :link", array(':link' => 'admin/structure/menu/manage/' . $menu->id()), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $link) { menu_link_delete($link['mlid']); } // Delete the custom menu and all its menu links. - menu_delete($menu); + $menu->delete(); - $t_args = array('%title' => $menu['title']); + $t_args = array('%title' => $menu->label()); drupal_set_message(t('The custom menu %title has been deleted.', $t_args)); watchdog('menu', 'Deleted custom menu %title and all its menu links.', $t_args, WATCHDOG_NOTICE); } @@ -616,9 +528,9 @@ function menu_delete_menu_confirm_submit($form, &$form_state) { * @see form_validate_machine_name() */ function menu_edit_menu_name_exists($value) { + $custom_exists = entity_load('menu', $value); // 'menu-' is added to the menu name to avoid name-space conflicts. $value = 'menu-' . $value; - $custom_exists = db_query_range('SELECT 1 FROM {menu_custom} WHERE menu_name = :menu', 0, 1, array(':menu' => $value))->fetchField(); $link_exists = db_query_range("SELECT 1 FROM {menu_links} WHERE menu_name = :menu", 0, 1, array(':menu' => $value))->fetchField(); return $custom_exists || $link_exists; @@ -632,9 +544,9 @@ function menu_edit_menu_submit($form, &$form_state) { $path = 'admin/structure/menu/manage/'; if ($form['#insert']) { // Add 'menu-' to the menu name to help avoid name-space conflicts. - $menu['menu_name'] = 'menu-' . $menu['menu_name']; - $link['link_title'] = $menu['title']; - $link['link_path'] = $path . $menu['menu_name']; + $menu['id'] = 'menu-' . $menu['id']; + $link['link_title'] = $menu['label']; + $link['link_path'] = $path . $menu['id']; $link['router_path'] = $path . '%'; $link['module'] = 'menu'; $link['plid'] = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :link AND module = :module", array( @@ -648,15 +560,15 @@ function menu_edit_menu_submit($form, &$form_state) { } else { menu_save($menu); - $result = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :path", array(':path' => $path . $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC)); + $result = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :path", array(':path' => $path . $menu['id']), array('fetch' => PDO::FETCH_ASSOC)); foreach ($result as $m) { $link = menu_link_load($m['mlid']); - $link['link_title'] = $menu['title']; + $link['link_title'] = $menu['label']; menu_link_save($link); } } drupal_set_message(t('Your configuration has been saved.')); - $form_state['redirect'] = $path . $menu['menu_name']; + $form_state['redirect'] = $path . $menu['id']; } /** diff --git a/core/modules/menu/menu.api.php b/core/modules/menu/menu.api.php index 3f3818e..95926a6 100644 --- a/core/modules/menu/menu.api.php +++ b/core/modules/menu/menu.api.php @@ -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); } diff --git a/core/modules/menu/menu.install b/core/modules/menu/menu.install index f1a7b05..a76207b 100644 --- a/core/modules/menu/menu.install +++ b/core/modules/menu/menu.install @@ -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)); + } +} diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module index ff24720..3d4a360 100644 --- a/core/modules/menu/menu.module +++ b/core/modules/menu/menu.module @@ -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, @@ -161,6 +161,29 @@ function menu_menu() { } /** + * 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(). */ function menu_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'] @@ -206,13 +225,6 @@ function menu_enable() { } /** - * 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. * * @param $menu_name @@ -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(); - return isset($all_menus[$menu_name]) ? $all_menus[$menu_name] : FALSE; + return entity_load('menu', $menu_name); } /** - * Load all custom menu data. - * - * @return - * Array of custom menu data. + * Implements hook_menu_insert() */ -function menu_load_all() { - $custom_menus = &drupal_static(__FUNCTION__); - if (!isset($custom_menus)) { - if ($cached = cache('menu')->get('menu_custom')) { - $custom_menus = $cached->data; - } - else { - $custom_menus = db_query('SELECT * FROM {menu_custom}')->fetchAllAssoc('menu_name', PDO::FETCH_ASSOC); - cache('menu')->set('menu_custom', $custom_menus); - } +function menu_menu_insert(Menu $menu) { + menu_cache_clear_all(); + // Invalidate the block cache to update menu-based derivatives. + if (module_exists('block')) { + drupal_container()->get('plugin.manager.block')->clearCachedDefinitions(); + } + // Make sure the menu is present in the active menus variable so that its + // items may appear in the menu active trail. + // See menu_set_active_menu_names(). + $config = config('system.menu'); + + $active_menus = $config->get('active_menus_default') ?: array_keys(menu_get_menus()); + if (!in_array($menu->id(), $active_menus)) { + $active_menus[] = $menu->id(); + $config + ->set('active_menus_default', $active_menus) + ->save(); } - return $custom_menus; } /** - * Save a custom menu. - * - * @param $menu - * An array representing a custom menu: - * - menu_name: The unique name of the custom menu (composed of lowercase - * letters, numbers, and hyphens). - * - title: The human readable menu title. - * - description: The custom menu description. - * - * Modules should always pass a fully populated $menu when saving a custom - * menu, so other modules are able to output proper status or watchdog messages. - * - * @see menu_load() - */ -function menu_save($menu) { - $status = db_merge('menu_custom') - ->key(array('menu_name' => $menu['menu_name'])) - ->fields(array( - 'title' => $menu['title'], - 'description' => $menu['description'], - )) - ->execute(); + * Implements hook_menu_update(). + */ +function menu_menu_update(Menu $menu) { menu_cache_clear_all(); // Invalidate the block cache to update menu-based derivatives. if (module_exists('block')) { drupal_container()->get('plugin.manager.block')->clearCachedDefinitions(); } - - switch ($status) { - case SAVED_NEW: - // Make sure the menu is present in the active menus variable so that its - // items may appear in the menu active trail. - // See menu_set_active_menu_names(). - $config = config('system.menu'); - - $active_menus = $config->get('active_menus_default') ?: array_keys(menu_get_menus()); - if (!in_array($menu['menu_name'], $active_menus)) { - $active_menus[] = $menu['menu_name']; - $config->set('active_menus_default', $active_menus); - } - - module_invoke_all('menu_insert', $menu); - break; - - case SAVED_UPDATED: - module_invoke_all('menu_update', $menu); - break; - } } /** - * Delete a custom menu and all contained links. - * - * Note that this function deletes all menu links in a custom menu. While menu - * links derived from router paths may be restored by rebuilding the menu, all - * customized and custom links will be irreversibly gone. Therefore, this - * function should usually be called from a user interface (form submit) handler - * only, which allows the user to confirm the action. - * - * @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. - * - * Modules should always pass a fully populated $menu when deleting a custom - * menu, so other modules are able to output proper status or watchdog messages. - * - * @see menu_load() - * - * menu_delete_links() will take care of clearing the page cache. Other modules - * should take care of their menu-related data by implementing - * hook_menu_delete(). + * Implements hook_menu_predelete(). */ -function menu_delete($menu) { +function menu_menu_predelete(Menu $menu) { // Delete all links from the menu. - menu_delete_links($menu['menu_name']); + menu_delete_links($menu->id()); // Remove menu from active menus variable. $active_menus = variable_get('menu_default_active_menus', array_keys(menu_get_menus())); foreach ($active_menus as $i => $menu_name) { - if ($menu['menu_name'] == $menu_name) { + if ($menu->id() == $menu_name) { unset($active_menus[$i]); variable_set('menu_default_active_menus', $active_menus); } } +} - // Delete the custom menu. - db_delete('menu_custom') - ->condition('menu_name', $menu['menu_name']) - ->execute(); - +/** + * Implements hook_menu_delete(). + */ +function menu_menu_delete(Menu $menu) { menu_cache_clear_all(); // Invalidate the block cache to update menu-based derivatives. if (module_exists('block')) { drupal_container()->get('plugin.manager.block')->clearCachedDefinitions(); } - module_invoke_all('menu_delete', $menu); } /** @@ -778,12 +730,12 @@ function menu_form_node_type_form_alter(&$form, $form_state) { * titles as the values. */ function menu_get_menus($all = TRUE) { - if ($custom_menus = menu_load_all()) { + if ($custom_menus = entity_load_multiple('menu')) { if (!$all) { $custom_menus = array_diff_key($custom_menus, menu_list_system_menus()); } foreach ($custom_menus as $menu_name => $menu) { - $custom_menus[$menu_name] = t($menu['title']); + $custom_menus[$menu_name] = $menu->label(); } asort($custom_menus); } diff --git a/core/modules/system/lib/Drupal/system/Plugin/Core/Entity/Menu.php b/core/modules/system/lib/Drupal/system/Plugin/Core/Entity/Menu.php new file mode 100644 index 0000000..7e3ceb0 --- /dev/null +++ b/core/modules/system/lib/Drupal/system/Plugin/Core/Entity/Menu.php @@ -0,0 +1,60 @@ +