diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 96e2f9d..658de74 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -256,14 +256,6 @@ */ /** - * The maximum depth of a menu links tree - matches the number of p columns. - * - * @todo Move this constant to MenuLinkStorage along with all the tree - * functionality. - */ -const MENU_MAX_DEPTH = 9; - -/** * @section Rendering menus * Once you have created menus (that contain menu links), you want to render * them. Drupal provides a block (Drupal\system\Plugin\Block\SystemMenuBlock) to @@ -316,92 +308,6 @@ */ /** - * Localizes a menu link title using t() if possible. - * - * Translate the title and description to allow storage of English title - * strings in the database, yet display of them in the language required - * by the current user. - * - * @param $item - * A menu link entity. - */ -function _menu_item_localize(&$item) { - // Allow default menu links to be translated. - $item['localized_options'] = $item['options']; - // All 'class' attributes are assumed to be an array during rendering, but - // links stored in the database may use an old string value. - // @todo In order to remove this code we need to implement a database update - // including unserializing all existing link options and running this code - // on them, as well as adding validation to menu_link_save(). - if (isset($item['options']['attributes']['class']) && is_string($item['options']['attributes']['class'])) { - $item['localized_options']['attributes']['class'] = explode(' ', $item['options']['attributes']['class']); - } - // If the menu link is defined in code and not customized, we can use t(). - if (!empty($item['machine_name']) && !$item['customized']) { - // @todo Figure out a proper way to support translations of menu links, see - // https://drupal.org/node/2193777. - $item['title'] = t($item['link_title']); - } - else { - $item['title'] = $item['link_title']; - } -} - -/** - * Provides menu link unserializing, access control, and argument handling. - * - * @param array $item - * The passed in item has the following keys: - * - access: (optional) Becomes TRUE if the item is accessible, FALSE - * otherwise. If the key is not set, the access manager is used to - * determine the access. - * - options: (required) Is unserialized and copied to $item['localized_options']. - * - link_title: (required) The title of the menu link. - * - route_name: (required) The route name of the menu link. - * - route_parameters: (required) The unserialized route parameters of the menu link. - * The passed in item is changed by the following keys: - * - href: The actual path to the link. This path is generated from the - * link_path of the menu link entity. - * - title: The title of the link. This title is generated from the - * link_title of the menu link entity. - */ -function _menu_link_translate(&$item) { - if (!is_array($item['options'])) { - $item['options'] = (array) unserialize($item['options']); - } - $item['localized_options'] = $item['options']; - $item['title'] = $item['link_title']; - if ($item['external'] || empty($item['route_name'])) { - $item['access'] = 1; - $item['href'] = $item['link_path']; - $item['route_parameters'] = array(); - // Set to NULL so that drupal_pre_render_link() is certain to skip it. - $item['route_name'] = NULL; - } - else { - $item['href'] = NULL; - if (!is_array($item['route_parameters'])) { - $item['route_parameters'] = (array) unserialize($item['route_parameters']); - } - // menu_tree_check_access() may set this ahead of time for links to nodes. - if (!isset($item['access'])) { - $item['access'] = \Drupal::getContainer()->get('access_manager')->checkNamedRoute($item['route_name'], $item['route_parameters'], \Drupal::currentUser()); - } - // For performance, don't localize a link the user can't access. - if ($item['access']) { - _menu_item_localize($item); - } - } - - // Allow other customizations - e.g. adding a page-specific query string to the - // options array. For performance reasons we only invoke this hook if the link - // has the 'alter' flag set in the options array. - if (!empty($item['options']['alter'])) { - \Drupal::moduleHandler()->alter('translated_menu_link', $item, $map); - } -} - -/** * Implements template_preprocess_HOOK() for theme_menu_tree(). */ function template_preprocess_menu_tree(&$variables) { @@ -764,119 +670,6 @@ function theme_menu_local_tasks(&$variables) { } /** - * Sets (or gets) the active menu for the current page. - * - * The active menu for the page determines the active trail. - * - * @return - * An array of menu machine names, in order of preference. The - * 'system.menu:active_menus_default' config item may be used to assert a menu - * order different from the order of creation, or to prevent a particular menu - * from being used at all in the active trail. - */ -function menu_set_active_menu_names($menu_names = NULL) { - $active = &drupal_static(__FUNCTION__); - - if (isset($menu_names) && is_array($menu_names)) { - $active = $menu_names; - } - elseif (!isset($active)) { - $config = \Drupal::config('system.menu'); - $active = $config->get('active_menus_default') ?: array_keys(menu_list_system_menus()); - } - return $active; -} - -/** - * Gets the active menu for the current page. - */ -function menu_get_active_menu_names() { - return menu_set_active_menu_names(); -} - -/** - * Looks up the preferred menu link for a given system path. - * - * @param $path - * The path; for example, 'node/5'. The function will find the corresponding - * menu link ('node/5' if it exists, or fallback to 'node/%'). - * @param $selected_menu - * The name of a menu used to restrict the search for a preferred menu link. - * If not specified, all the menus returned by menu_get_active_menu_names() - * will be used. - * - * @return - * A fully translated menu link, or FALSE if no matching menu link was - * found. The most specific menu link ('node/5' preferred over 'node/%') in - * the most preferred menu (as defined by menu_get_active_menu_names()) is - * returned. - */ -function menu_link_get_preferred($path = NULL, $selected_menu = NULL) { - $preferred_links = &drupal_static(__FUNCTION__); - - if (!isset($path)) { - $path = current_path(); - } - - if (empty($selected_menu)) { - // Use an illegal menu name as the key for the preferred menu link. - $selected_menu = MENU_PREFERRED_LINK; - } - - if (!isset($preferred_links[$path])) { - // Look for the correct menu link by building a list of candidate paths, - // which are ordered by priority (translated hrefs are preferred over - // untranslated paths). Afterwards, the most relevant path is picked from - // the menus, ordered by menu preference. - $path_candidates = array(); - // 1. The current item href. - // @todo simplify this code and convert to using route names. - // @see https://drupal.org/node/2154949 - $path_candidates[$path] = $path; - - // Retrieve a list of menu names, ordered by preference. - $menu_names = menu_get_active_menu_names(); - // Put the selected menu at the front of the list. - array_unshift($menu_names, $selected_menu); - - $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => $path_candidates)); - - // Sort candidates by link path and menu name. - $candidates = array(); - foreach ($menu_links as $candidate) { - $candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate; - // Add any menus not already in the menu name search list. - if (!in_array($candidate['menu_name'], $menu_names)) { - $menu_names[] = $candidate['menu_name']; - } - } - - // Store the most specific link for each menu. Also save the most specific - // link of the most preferred menu in $preferred_link. - foreach ($path_candidates as $link_path) { - if (isset($candidates[$link_path])) { - foreach ($menu_names as $menu_name) { - if (empty($preferred_links[$path][$menu_name]) && isset($candidates[$link_path][$menu_name])) { - $candidate_item = $candidates[$link_path][$menu_name]; - $candidate_item['access'] = \Drupal::service('access_manager')->checkNamedRoute($candidate_item['route_name'], $candidate_item['route_parameters'], \Drupal::currentUser()); - if ($candidate_item['access']) { - _menu_item_localize($candidate_item); - $preferred_links[$path][$menu_name] = $candidate_item; - if (empty($preferred_links[$path][MENU_PREFERRED_LINK])) { - // Store the most specific link. - $preferred_links[$path][MENU_PREFERRED_LINK] = $candidate_item; - } - } - } - } - } - } - } - - return isset($preferred_links[$path][$selected_menu]) ? $preferred_links[$path][$selected_menu] : FALSE; -} - -/** * Clears all cached menu data. * * This should be called any time broad changes @@ -887,224 +680,5 @@ function menu_cache_clear_all() { } /** - * Saves menu links recursively for menu_links_rebuild_defaults(). - */ -function _menu_link_save_recursive($controller, $machine_name, &$children, &$links) { - $menu_link = $links[$machine_name]; - if ($menu_link->isNew() || !$menu_link->customized) { - if (!isset($menu_link->plid) && !empty($menu_link->parent) && !empty($links[$menu_link->parent])) { - $parent = $links[$menu_link->parent]; - - if (empty($menu_link->menu_name) || $parent->menu_name == $menu_link->menu_name) { - $menu_link->plid = $parent->id(); - $menu_link->menu_name = $parent->menu_name; - } - } - $controller->save($menu_link); - } - if (!empty($children[$machine_name])) { - foreach ($children[$machine_name] as $next_name) { - _menu_link_save_recursive($controller, $next_name, $children, $links); - } - } - // Remove processed link names so we can find stragglers. - unset($children[$machine_name]); -} - -/** - * Builds menu links for the items returned from the menu_link.static service. - */ -function menu_link_rebuild_defaults() { - // Ensure that all configuration used to build the menu items are loaded - // without overrides. - $old_state = \Drupal::configFactory()->getOverrideState(); - \Drupal::configFactory()->setOverrideState(FALSE); - $module_handler = \Drupal::moduleHandler(); - if (!$module_handler->moduleExists('menu_link')) { - // The Menu link module may not be available during install, so rebuild - // when possible. - return; - } - /** @var \Drupal\menu_link\MenuLinkStorageInterface $menu_link_storage */ - $menu_link_storage = \Drupal::entityManager() - ->getStorage('menu_link'); - $links = array(); - $children = array(); - $top_links = array(); - $all_links = \Drupal::service('menu_link.static')->getLinks(); - if ($all_links) { - foreach ($all_links as $machine_name => $link) { - // For performance reasons, do a straight query now and convert to a menu - // link entity later. - // @todo revisit before release. - $existing_item = db_select('menu_links') - ->fields('menu_links') - ->condition('machine_name', $machine_name) - ->execute()->fetchObject(); - if ($existing_item) { - $existing_item->options = unserialize($existing_item->options); - $existing_item->route_parameters = unserialize($existing_item->route_parameters); - $link['mlid'] = $existing_item->mlid; - $link['plid'] = $existing_item->plid; - $link['uuid'] = $existing_item->uuid; - $link['customized'] = $existing_item->customized; - $link['updated'] = $existing_item->updated; - $menu_link = $menu_link_storage->createFromDefaultLink($link); - // @todo Do not create a new entity in order to update it, see - // https://drupal.org/node/2241865 - $menu_link->setOriginalId($existing_item->mlid); - - // Convert the existing item to a typed object. - /** @var \Drupal\menu_link\MenuLinkInterface $existing_item */ - $existing_item = $menu_link_storage->create(get_object_vars($existing_item)); - - if (!$existing_item->customized) { - // A change in the default menu links may move the link to a - // different menu or parent. - if (!empty($link['menu_name']) && ($link['menu_name'] != $existing_item->menu_name)) { - $menu_link->plid = NULL; - $menu_link->menu_name = $link['menu_name']; - } - elseif (!empty($link['parent'])) { - $menu_link->plid = NULL; - } - - $menu_link->original = $existing_item; - } - } - else { - if (empty($link['route_name']) && empty($link['link_path'])) { - \Drupal::logger('menu_link')->error('Menu_link %machine_name does neither provide a route_name nor a link_path, so it got skipped.', array('%machine_name' => $machine_name)); - continue; - } - $menu_link = $menu_link_storage->createFromDefaultLink($link); - } - if (!empty($link['parent'])) { - $children[$link['parent']][$machine_name] = $machine_name; - $menu_link->parent = $link['parent']; - if (empty($link['menu_name'])) { - // Reset the default menu name so it is populated from the parent. - $menu_link->menu_name = NULL; - } - } - else { - // A top level link - we need them to root our tree. - $top_links[$machine_name] = $machine_name; - $menu_link->plid = 0; - } - $links[$machine_name] = $menu_link; - } - } - foreach ($top_links as $machine_name) { - _menu_link_save_recursive($menu_link_storage, $machine_name, $children, $links); - } - // Handle any children we didn't find starting from top-level links. - foreach ($children as $orphan_links) { - foreach ($orphan_links as $machine_name) { - // Force it to the top level. - $links[$machine_name]->plid = 0; - _menu_link_save_recursive($menu_link_storage, $machine_name, $children, $links); - } - } - - // Find any item whose default menu link no longer exists. - if ($all_links) { - $query = \Drupal::entityQuery('menu_link') - ->condition('machine_name', array_keys($all_links), 'NOT IN') - ->exists('machine_name') - ->condition('external', 0) - ->condition('updated', 0) - ->condition('customized', 0) - ->sort('depth', 'DESC'); - $result = $query->execute(); - } - else { - $result = array(); - } - - // Remove all such items. Starting from those with the greatest depth will - // minimize the amount of re-parenting done by the menu link controller. - if ($result) { - menu_link_delete_multiple($result, TRUE); - } - \Drupal::configFactory()->setOverrideState($old_state); -} - -/** - * Returns an array containing all links for a menu. - * - * @param $menu_name - * The name of the menu whose links should be returned. - * - * @return - * An array of menu links. - */ -function menu_load_links($menu_name) { - $links = array(); - - $query = \Drupal::entityQuery('menu_link') - ->condition('menu_name', $menu_name) - // Order by weight so as to be helpful for menus that are only one level - // deep. - ->sort('weight'); - $result = $query->execute(); - - if (!empty($result)) { - $links = menu_link_load_multiple($result); - } - - return $links; -} - -/** - * Deletes all links for a menu. - * - * @param $menu_name - * The name of the menu whose links will be deleted. - */ -function menu_delete_links($menu_name) { - $links = menu_load_links($menu_name); - menu_link_delete_multiple(array_keys($links), FALSE, TRUE); -} - -/** - * Updates the expanded menu item state at most twice per page load. - */ -function _menu_update_expanded_menus() { - $expanded_menus_updated = &drupal_static(__FUNCTION__, 0); - - // Update the expanded menu item state, but at most twice, including at - // the end of the page load when there are multiple links saved or deleted. - if ($expanded_menus_updated == 0) { - // Keep track of which menus have expanded items. - _menu_set_expanded_menus(); - $expanded_menus_updated = 1; - } - elseif ($expanded_menus_updated == 1) { - // Keep track of which menus have expanded items. - drupal_register_shutdown_function('_menu_set_expanded_menus'); - $expanded_menus_updated = 2; - } -} - -/** - * Updates a list of menus with expanded items. - */ -function _menu_set_expanded_menus() { - $names = array(); - $result = Drupal::entityQueryAggregate('menu_link') - ->condition('expanded', 0, '<>') - ->groupBy('menu_name') - ->execute(); - - // Flatten the resulting array. - foreach($result as $k => $v) { - $names[$k] = $v['menu_name']; - } - - \Drupal::state()->set('menu_expanded', $names); -} - -/** * @} End of "defgroup menu". */ diff --git a/core/modules/menu_link/menu_link.api.php b/core/modules/menu_link/menu_link.api.php deleted file mode 100644 index 4702436..0000000 --- a/core/modules/menu_link/menu_link.api.php +++ /dev/null @@ -1,42 +0,0 @@ - 1, - * @endcode - * - * @param \Drupal\menu_link\Entity\MenuLink $menu_link - * A menu link entity. - * - * @see hook_menu_link_alter() - */ -function hook_translated_menu_link_alter(\Drupal\menu_link\Entity\MenuLink &$menu_link, $map) { - if ($menu_link->href == 'devel/cache/clear') { - $menu_link->localized_options['query'] = drupal_get_destination(); - } -} - -/** - * @} End of "addtogroup hooks". - */ diff --git a/core/modules/menu_link/menu_link.install b/core/modules/menu_link/menu_link.install deleted file mode 100644 index c789d34..0000000 --- a/core/modules/menu_link/menu_link.install +++ /dev/null @@ -1,221 +0,0 @@ - 'Contains the individual links within a menu.', - 'fields' => array( - 'menu_name' => array( - 'description' => "The menu name. All links with the same menu name (such as 'tools') are part of the same menu.", - 'type' => 'varchar', - 'length' => 32, - 'not null' => TRUE, - 'default' => '', - ), - 'mlid' => array( - 'description' => 'The menu link ID (mlid) is the integer primary key.', - 'type' => 'serial', - 'unsigned' => TRUE, - 'not null' => TRUE, - ), - 'uuid' => array( - 'description' => 'Unique Key: Universally unique identifier for this entity.', - 'type' => 'varchar', - 'length' => 128, - 'not null' => FALSE, - ), - 'machine_name' => array( - 'description' => 'Unique machine name: Optional human-readable ID for this link.', - 'type' => 'varchar', - 'length' => 255, - 'not null' => FALSE, - ), - 'plid' => array( - 'description' => 'The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link is at the top level in its menu.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'link_path' => array( - 'description' => 'The Drupal path or external path this link points to.', - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - ), - 'langcode' => array( - 'description' => 'The {language}.langcode of this link.', - 'type' => 'varchar', - 'length' => 12, - 'not null' => TRUE, - 'default' => '', - ), - 'link_title' => array( - 'description' => 'The text displayed for the link.', - 'type' => 'varchar', - 'length' => 255, - 'not null' => TRUE, - 'default' => '', - ), - 'options' => array( - 'description' => 'A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.', - 'type' => 'blob', - 'not null' => FALSE, - 'serialize' => TRUE, - ), - 'module' => array( - 'description' => 'The name of the module that generated this link.', - 'type' => 'varchar', - 'length' => DRUPAL_EXTENSION_NAME_MAX_LENGTH, - 'not null' => TRUE, - 'default' => 'system', - ), - 'hidden' => array( - 'description' => 'A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link)', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'size' => 'small', - ), - 'external' => array( - 'description' => 'A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'size' => 'small', - ), - 'has_children' => array( - 'description' => 'Flag indicating whether any links have this link as a parent (1 = children exist, 0 = no children).', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'size' => 'small', - ), - 'expanded' => array( - 'description' => 'Flag for whether this link should be rendered as expanded in menus - expanded links always have their child links displayed, instead of only when the link is in the active trail (1 = expanded, 0 = not expanded)', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'size' => 'small', - ), - 'weight' => array( - 'description' => 'Link weight among links in the same menu at the same depth.', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - ), - 'depth' => array( - 'description' => 'The depth relative to the top level. A link with plid == 0 will have depth == 1.', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'size' => 'small', - ), - 'customized' => array( - 'description' => 'A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'size' => 'small', - ), - 'p1' => array( - 'description' => 'The first mlid in the materialized path. If N = depth, then pN must equal the mlid. If depth > 1 then p(N-1) must equal the plid. All pX where X > depth must equal zero. The columns p1 .. p9 are also called the parents.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'p2' => array( - 'description' => 'The second mlid in the materialized path. See p1.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'p3' => array( - 'description' => 'The third mlid in the materialized path. See p1.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'p4' => array( - 'description' => 'The fourth mlid in the materialized path. See p1.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'p5' => array( - 'description' => 'The fifth mlid in the materialized path. See p1.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'p6' => array( - 'description' => 'The sixth mlid in the materialized path. See p1.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'p7' => array( - 'description' => 'The seventh mlid in the materialized path. See p1.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'p8' => array( - 'description' => 'The eighth mlid in the materialized path. See p1.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'p9' => array( - 'description' => 'The ninth mlid in the materialized path. See p1.', - 'type' => 'int', - 'unsigned' => TRUE, - 'not null' => TRUE, - 'default' => 0, - ), - 'updated' => array( - 'description' => 'Flag that indicates that this link was generated during the update from Drupal 5.', - 'type' => 'int', - 'not null' => TRUE, - 'default' => 0, - 'size' => 'small', - ), - 'route_name' => array( - 'description' => 'The machine name of a defined Symfony Route this menu item represents.', - 'type' => 'varchar', - 'length' => 255, - ), - 'route_parameters' => array( - 'description' => 'Serialized array of route parameters of this menu link.', - 'type' => 'blob', - 'size' => 'big', - 'not null' => FALSE, - 'serialize' => TRUE, - ), - ), - 'indexes' => array( - 'path_menu' => array(array('link_path', 128), 'menu_name'), - 'menu_plid_expand_child' => array('menu_name', 'plid', 'expanded', 'has_children'), - 'menu_parents' => array('menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'), - ), - 'primary key' => array('mlid'), - ); - - return $schema; -} diff --git a/core/modules/menu_link/menu_link.module b/core/modules/menu_link/menu_link.module deleted file mode 100644 index 8a2ce38..0000000 --- a/core/modules/menu_link/menu_link.module +++ /dev/null @@ -1,225 +0,0 @@ -' . t('About') . ''; - $output .= '

' . t('The Menu Link module allows users to create menu links. It is required by the Menu UI module, which provides an interface for managing menus. See the Menu UI module help page for more information.', array('!menu-help' => \Drupal::url('help.page', array('name' => 'menu_ui')))) . '

'; - return $output; - } -} - -/** - * Entity URI callback. - * - * @param \Drupal\menu_link\Entity\MenuLink $menu_link - * A menu link entity. - */ -function menu_link_uri(MenuLink $menu_link) { - return new Url($menu_link->route_name, $menu_link->route_parameters); -} - -/** - * Loads a menu link entity. - * - * This function should never be called from within node_load() or any other - * function used as a menu object load function since an infinite recursion may - * occur. - * - * @param int $mlid - * The menu link ID. - * @param bool $reset - * (optional) Whether to reset the menu_link_load_multiple() cache. - * - * @return \Drupal\menu_link\Entity\MenuLink|null - * A menu link entity, or NULL if there is no entity with the given ID. - * - * @deprecated in Drupal 8.x, will be removed before Drupal 9.0. - * Use \Drupal\menu_link\Entity\MenuLink::load(). - */ -function menu_link_load($mlid = NULL, $reset = FALSE) { - if ($reset) { - \Drupal::entityManager()->getStorage('menu_link')->resetCache(array($mlid)); - } - return MenuLink::load($mlid); -} - -/** - * Loads menu link entities from the database. - * - * @param array $mlids - * (optional) An array of entity IDs. If omitted, all entities are loaded. - * @param bool $reset - * (optional) Whether to reset the internal cache. - * - * @return array<\Drupal\menu_link\Entity\MenuLink> - * An array of menu link entities indexed by entity IDs. - * - * @see menu_link_load() - * @see entity_load_multiple() - * - * @deprecated in Drupal 8.x, will be removed before Drupal 9.0. - * Use \Drupal\menu_link\Entity\MenuLink::loadMultiple(). - */ -function menu_link_load_multiple(array $mlids = NULL, $reset = FALSE) { - if ($reset) { - \Drupal::entityManager()->getStorage('menu_link')->resetCache($mlids); - } - return MenuLink::loadMultiple($mlids); -} - -/** - * Deletes a menu link. - * - * @param int $mlid - * The menu link ID. - * - * @see menu_link_delete_multiple() - */ -function menu_link_delete($mlid) { - menu_link_delete_multiple(array($mlid)); -} - -/** - * Deletes multiple menu links. - * - * @param array $mlids - * An array of menu link IDs. - * @param bool $force - * (optional) Forces deletion. Internal use only, setting to TRUE is - * discouraged. Defaults to FALSE. - * @param bool $prevent_reparenting - * (optional) Disables the re-parenting logic from the deletion process. - * Defaults to FALSE. - */ -function menu_link_delete_multiple(array $mlids, $force = FALSE, $prevent_reparenting = FALSE) { - if (!$mlids) { - // If no IDs or invalid IDs were passed, do nothing. - return; - } - - $controller = \Drupal::entityManager() - ->getStorage('menu_link'); - if (!$force) { - $entity_query = \Drupal::entityQuery('menu_link'); - $group = $entity_query->orConditionGroup() - ->condition('module', 'system', '<>') - ->condition('updated', 0, '<>'); - - $entity_query->condition('mlid', $mlids, 'IN'); - $entity_query->condition($group); - - $result = $entity_query->execute(); - $entities = $controller->loadMultiple($result); - } - else { - $entities = $controller->loadMultiple($mlids); - } - $controller->setPreventReparenting($prevent_reparenting); - $controller->delete($entities); -} - -/** - * Saves a menu link. - * - * After calling this function, rebuild the menu cache using - * menu_cache_clear_all(). - * - * @param \Drupal\menu_link\Entity\MenuLink $menu_link - * The menu link entity to be saved. - * - * @return int|bool - * Returns SAVED_NEW or SAVED_UPDATED if the save operation succeeded, or - * FALSE if it failed. - */ -function menu_link_save(MenuLink $menu_link) { - return $menu_link->save(); -} - -/** - * Inserts, updates, enables, disables, or deletes an uncustomized menu link. - * - * @param string $module - * The name of the module that owns the link. - * @param string $op - * Operation to perform: insert, update, enable, disable, or delete. - * @param string $link_path - * The path this link points to. - * @param string $link_title - * (optional) Title of the link to insert or new title to update the link to. - * Unused for delete. Defaults to NULL. - * - * @return integer|null - * The insert op returns the mlid of the new item. Others op return NULL. - */ -function menu_link_maintain($module, $op, $link_path, $link_title = NULL) { - $menu_link_controller = \Drupal::entityManager() - ->getStorage('menu_link'); - switch ($op) { - case 'insert': - $menu_link = entity_create('menu_link', array( - 'link_title' => $link_title, - 'link_path' => $link_path, - 'module' => $module,) - ); - return $menu_link->save(); - - case 'update': - $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => $link_path, 'module' => $module, 'customized' => 0)); - foreach ($menu_links as $menu_link) { - $menu_link->original = clone $menu_link; - if (isset($link_title)) { - $menu_link->link_title = $link_title; - } - $menu_link_controller->save($menu_link); - } - break; - - case 'enable': - case 'disable': - $menu_links = entity_load_multiple_by_properties('menu_link', array('link_path' => $link_path, 'module' => $module, 'customized' => 0)); - foreach ($menu_links as $menu_link) { - $menu_link->original = clone $menu_link; - $menu_link->hidden = ($op == 'disable' ? 1 : 0); - $menu_link->customized = 1; - if (isset($link_title)) { - $menu_link->link_title = $link_title; - } - $menu_link_controller->save($menu_link); - } - break; - - case 'delete': - $result = \Drupal::entityQuery('menu_link')->condition('link_path', $link_path)->execute(); - if (!empty($result)) { - menu_link_delete_multiple($result); - } - break; - } -} - -/** - * Implements hook_system_breadcrumb_alter(). - */ -function menu_link_system_breadcrumb_alter(array &$breadcrumb, RouteMatchInterface $route_match, array $context) { - // Custom breadcrumb behavior for editing menu links, we append a link to - // the menu in which the link is found. - if (($route_match->getRouteName() == 'menu_ui.link_edit') && $menu_link = $route_match->getParameter('menu_link')) { - if (($menu_link instanceof MenuLinkInterface) && !$menu_link->isNew()) { - // Add a link to the menu admin screen. - $menu = entity_load('menu', $menu_link->menu_name); - $breadcrumb[] = Drupal::l($menu->label(), 'menu_ui.menu_edit', array('menu' => $menu->id)); - } - } -} diff --git a/core/modules/menu_link/menu_link.services.yml b/core/modules/menu_link/menu_link.services.yml deleted file mode 100644 index 88f5037..0000000 --- a/core/modules/menu_link/menu_link.services.yml +++ /dev/null @@ -1,7 +0,0 @@ -services: - menu_link.tree: - class: Drupal\menu_link\MenuTree - arguments: ['@database', '@cache.data', '@language_manager', '@request_stack', '@entity.manager', '@entity.query', '@state'] - menu_link.static: - class: Drupal\menu_link\StaticMenuLinks - arguments: ['@module_handler'] diff --git a/core/modules/menu_link/src/Entity/MenuLink.php b/core/modules/menu_link/src/Entity/MenuLink.php deleted file mode 100644 index e2215fd..0000000 --- a/core/modules/menu_link/src/Entity/MenuLink.php +++ /dev/null @@ -1,680 +0,0 @@ -newRevision = $value; - } - /** - * {@inheritdoc} - */ - public function isNewRevision() { - return $this->newRevision || ($this->getEntityType()->hasKey('revision') && !$this->getRevisionId()); - } - - /** - * {@inheritdoc} - */ - public function getRevisionId() { - return NULL; - } - - /** - * {@inheritdoc} - */ - public function isTranslatable() { - // @todo Inject the entity manager and retrieve bundle info from it. - $bundles = entity_get_bundles($this->entityTypeId); - return !empty($bundles[$this->bundle()]['translatable']); - } - - /** - * {@inheritdoc} - */ - public function preSaveRevision(EntityStorageInterface $storage, \stdClass $record) { - } - - /** - * Overrides Entity::id(). - */ - public function id() { - return $this->mlid; - } - - /** - * {@inheritdoc} - */ - public function bundle() { - return $this->bundle; - } - - /** - * Overrides Entity::createDuplicate(). - */ - public function createDuplicate() { - $duplicate = parent::createDuplicate(); - $duplicate->plid = NULL; - return $duplicate; - } - - /** - * {@inheritdoc} - */ - public function getRoute() { - if (!$this->route_name) { - return NULL; - } - if (!($this->routeObject instanceof Route)) { - $route_provider = \Drupal::service('router.route_provider'); - $this->routeObject = $route_provider->getRouteByName($this->route_name); - } - return $this->routeObject; - } - - /** - * {@inheritdoc} - */ - public function setRouteObject(Route $route) { - $this->routeObject = $route; - } - - /** - * {@inheritdoc} - */ - public function reset() { - // To reset the link to its original values, we need to retrieve its - // definition from the menu_link.static service. Otherwise, for example, - // the link's menu would not be reset, because properties like the original - // 'menu_name' are not stored anywhere else. Since resetting a link happens - // rarely and this is a one-time operation, retrieving the full set of - // default menu links does little harm. - $all_links = \Drupal::service('menu_link.static')->getLinks(); - $original = $all_links[$this->machine_name]; - $original['machine_name'] = $this->machine_name; - /** @var \Drupal\menu_link\MenuLinkStorageInterface $storage */ - $storage = \Drupal::entityManager()->getStorage($this->entityTypeId); - // @todo Do not create a new entity in order to update it, see - // https://drupal.org/node/2241865 - $new_link = $storage->createFromDefaultLink($original); - $new_link->setOriginalId($this->id()); - // Allow the menu to be determined by the parent - if (!empty($new_link['parent']) && !empty($all_links[$new_link['parent']])) { - // Walk up the tree to find the menu name. - $parent = $all_links[$new_link['parent']]; - $existing_parent = db_select('menu_links') - ->fields('menu_links') - ->condition('machine_name', $parent['machine_name']) - ->execute()->fetchAssoc(); - if ($existing_parent) { - /** @var \Drupal\Core\Entity\EntityInterface $existing_parent */ - $existing_parent = $storage->create($existing_parent); - $new_link->menu_name = $existing_parent->menu_name; - $new_link->plid = $existing_parent->id(); - } - } - // Merge existing menu link's ID and 'has_children' property. - foreach (array('mlid', 'has_children') as $key) { - $new_link->{$key} = $this->{$key}; - } - $new_link->save(); - return $new_link; - } - - /** - * Implements ArrayAccess::offsetExists(). - */ - public function offsetExists($offset) { - return isset($this->{$offset}); - } - - /** - * Implements ArrayAccess::offsetGet(). - */ - public function &offsetGet($offset) { - return $this->{$offset}; - } - - /** - * Implements ArrayAccess::offsetSet(). - */ - public function offsetSet($offset, $value) { - $this->{$offset} = $value; - } - - /** - * Implements ArrayAccess::offsetUnset(). - */ - public function offsetUnset($offset) { - unset($this->{$offset}); - } - - /** - * {@inheritdoc} - */ - public static function preDelete(EntityStorageInterface $storage, array $entities) { - parent::preDelete($storage, $entities); - - // Nothing to do if we don't want to reparent children. - if ($storage->getPreventReparenting()) { - return; - } - - foreach ($entities as $entity) { - // Children get re-attached to the item's parent. - if ($entity->has_children) { - $children = $storage->loadByProperties(array('plid' => $entity->plid)); - foreach ($children as $child) { - $child->plid = $entity->plid; - $storage->save($child); - } - } - } - } - - /** - * {@inheritdoc} - */ - public static function postDelete(EntityStorageInterface $storage, array $entities) { - parent::postDelete($storage, $entities); - - // Update the has_children status of the parent. - foreach ($entities as $entity) { - if (!$storage->getPreventReparenting()) { - $storage->updateParentalStatus($entity); - } - } - - // Also clear the menu system static caches. - menu_reset_static_cache(); - _menu_update_expanded_menus(); - } - - /** - * {@inheritdoc} - */ - public function preSave(EntityStorageInterface $storage) { - parent::preSave($storage); - - // This is the easiest way to handle the unique internal path '', - // since a path marked as external does not need to match a route. - $this->external = (UrlHelper::isExternal($this->link_path) || $this->link_path == '') ? 1 : 0; - - // Try to find a parent link. If found, assign it and derive its menu. - $parent = $this->findParent($storage); - if ($parent) { - $this->plid = $parent->id(); - $this->menu_name = $parent->menu_name; - } - // If no corresponding parent link was found, move the link to the top-level. - else { - $this->plid = 0; - } - - // Directly fill parents for top-level links. - if ($this->plid == 0) { - $this->p1 = $this->id(); - for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) { - $parent_property = "p$i"; - $this->{$parent_property} = 0; - } - $this->depth = 1; - } - // Otherwise, ensure that this link's depth is not beyond the maximum depth - // and fill parents based on the parent link. - else { - if ($this->has_children && $this->original) { - $limit = MENU_MAX_DEPTH - $storage->findChildrenRelativeDepth($this->original) - 1; - } - else { - $limit = MENU_MAX_DEPTH - 1; - } - if ($parent->depth > $limit) { - return FALSE; - } - $this->depth = $parent->depth + 1; - $this->setParents($parent); - } - - // Need to check both plid and menu_name, since plid can be 0 in any menu. - if (isset($this->original) && ($this->plid != $this->original->plid || $this->menu_name != $this->original->menu_name)) { - $storage->moveChildren($this); - } - - // Find the route_name. - if (!$this->external && !isset($this->route_name)) { - $url = Url::createFromPath($this->link_path); - $this->route_name = $url->getRouteName(); - $this->route_parameters = $url->getRouteParameters(); - } - elseif (empty($this->link_path)) { - $this->link_path = \Drupal::urlGenerator()->getPathFromRoute($this->route_name, $this->route_parameters); - } - } - - /** - * {@inheritdoc} - */ - public function postSave(EntityStorageInterface $storage, $update = TRUE) { - parent::postSave($storage, $update); - - // Check the has_children status of the parent. - $storage->updateParentalStatus($this); - - - // Entity::postSave() calls Entity::invalidateTagsOnSave(), which only - // handles the regular cases. The MenuLink entity has two special cases. - $cache_tags = array(); - // Case 1: a newly created menu link is *also* added to a menu, so we must - // invalidate the associated menu's cache tag. - if (!$update) { - $cache_tags = $this->getCacheTag(); - } - // Case 2: a menu link may be moved from one menu to another; the original - // menu's cache tag must also be invalidated. - if (isset($this->original) && $this->menu_name != $this->original->menu_name) { - $cache_tags = NestedArray::mergeDeep($cache_tags, $this->original->getCacheTag()); - } - Cache::invalidateTags($cache_tags); - - // Also clear the menu system static caches. - menu_reset_static_cache(); - - // Track which menu items are expanded. - _menu_update_expanded_menus(); - } - - /** - * {@inheritdoc} - */ - public static function postLoad(EntityStorageInterface $storage, array &$entities) { - parent::postLoad($storage, $entities); - - $routes = array(); - foreach ($entities as $menu_link) { - $menu_link->options = unserialize($menu_link->options); - $menu_link->route_parameters = unserialize($menu_link->route_parameters); - - // By default use the menu_name as type. - $menu_link->bundle = $menu_link->menu_name; - - // For all links that have an associated route, load the route object now - // and save it on the object. That way we avoid a select N+1 problem later. - if ($menu_link->route_name) { - $routes[$menu_link->id()] = $menu_link->route_name; - } - } - - // Now mass-load any routes needed and associate them. - if ($routes) { - $route_objects = \Drupal::service('router.route_provider')->getRoutesByNames($routes); - foreach ($routes as $entity_id => $route) { - // Not all stored routes will be valid on load. - if (isset($route_objects[$route])) { - $entities[$entity_id]->setRouteObject($route_objects[$route]); - } - } - } - } - - /** - * {@inheritdoc} - */ - protected function setParents(MenuLinkInterface $parent) { - $i = 1; - while ($i < $this->depth) { - $p = 'p' . $i++; - $this->{$p} = $parent->{$p}; - } - $p = 'p' . $i++; - // The parent (p1 - p9) corresponding to the depth always equals the mlid. - $this->{$p} = $this->id(); - while ($i <= MENU_MAX_DEPTH) { - $p = 'p' . $i++; - $this->{$p} = 0; - } - } - - /** - * {@inheritdoc} - */ - protected function findParent(EntityStorageInterface $storage) { - $parent = FALSE; - - // This item is explicitly top-level, skip the rest of the parenting. - if (isset($this->plid) && empty($this->plid)) { - return $parent; - } - - // If we have a parent link ID, try to use that. - $candidates = array(); - if (isset($this->plid)) { - $candidates[] = $this->plid; - } - - // Else, if we have a link hierarchy try to find a valid parent in there. - if (!empty($this->depth) && $this->depth > 1) { - for ($depth = $this->depth - 1; $depth >= 1; $depth--) { - $parent_property = "p$depth"; - $candidates[] = $this->$parent_property; - } - } - - foreach ($candidates as $mlid) { - $parent = $storage->load($mlid); - if ($parent) { - break; - } - } - return $parent; - } - - /** - * Builds and returns the renderable array for this menu link. - * - * @return array - * A renderable array representing the content of the link. - */ - public function build() { - $build = array( - '#type' => 'link', - '#title' => $this->title, - '#href' => $this->href, - '#route_name' => $this->route_name ? $this->route_name : NULL, - '#route_parameters' => $this->route_parameters, - '#options' => !empty($this->localized_options) ? $this->localized_options : array(), - ); - return $build; - } - - /** - * {@inheritdoc} - */ - public function getCacheTag() { - return entity_load('menu', $this->menu_name)->getCacheTag(); - } - - /** - * {@inheritdoc} - */ - public function getListCacheTags() { - return entity_load('menu', $this->menu_name)->getListCacheTags(); - } - -} diff --git a/core/modules/menu_link/src/MenuLinkAccessController.php b/core/modules/menu_link/src/MenuLinkAccessController.php deleted file mode 100644 index c6e6b91..0000000 --- a/core/modules/menu_link/src/MenuLinkAccessController.php +++ /dev/null @@ -1,41 +0,0 @@ -hasPermission('administer menu'); - if ($access) { - switch ($operation) { - case 'reset': - // Reset allowed for items defined via hook_menu() and customized. - return !empty($entity->machine_name) && $entity->customized; - - case 'delete': - // Only items created by the Menu UI module can be deleted. - return $entity->module == 'menu_ui' || $entity->updated == 1; - - } - } - return $access; - } - -} diff --git a/core/modules/menu_link/src/MenuLinkForm.php b/core/modules/menu_link/src/MenuLinkForm.php deleted file mode 100644 index f5cbca1..0000000 --- a/core/modules/menu_link/src/MenuLinkForm.php +++ /dev/null @@ -1,311 +0,0 @@ -menuLinkStorage = $menu_link_storage; - $this->pathAliasManager = $path_alias_manager; - $this->urlGenerator = $url_generator; - } - - /** - * {@inheritdoc} - */ - public static function create(ContainerInterface $container) { - return new static( - $container->get('entity.manager')->getStorage('menu_link'), - $container->get('path.alias_manager'), - $container->get('url_generator') - ); - } - - /** - * Overrides EntityForm::form(). - */ - public function form(array $form, array &$form_state) { - $menu_link = $this->entity; - // Since menu_link_load() no longer returns a translated and access checked - // item, do it here instead. - _menu_link_translate($menu_link); - - $form['link_title'] = array( - '#type' => 'textfield', - '#title' => t('Menu link title'), - '#default_value' => $menu_link->link_title, - '#description' => t('The text to be used for this link in the menu.'), - '#required' => TRUE, - ); - foreach (array('link_path', 'mlid', 'module', 'has_children', 'options') as $key) { - $form[$key] = array('#type' => 'value', '#value' => $menu_link->{$key}); - } - // Any item created or edited via this interface is considered "customized". - $form['customized'] = array('#type' => 'value', '#value' => 1); - - // We are not using url() when constructing this path because it would add - // $base_path. - $path = $menu_link->link_path; - if (isset($menu_link->options['query'])) { - $path .= '?' . $this->urlGenerator->httpBuildQuery($menu_link->options['query']); - } - if (isset($menu_link->options['fragment'])) { - $path .= '#' . $menu_link->options['fragment']; - } - if ($menu_link->module == 'menu_ui') { - $form['link_path'] = array( - '#type' => 'textfield', - '#title' => t('Path'), - '#maxlength' => 255, - '#default_value' => $path, - '#description' => t('The path for this menu link. This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org')), - '#required' => TRUE, - ); - } - else { - $form['_path'] = array( - '#type' => 'item', - '#title' => t('Path'), - '#description' => l($menu_link->link_title, $menu_link->href, $menu_link->options), - ); - } - - $form['description'] = array( - '#type' => 'textarea', - '#title' => t('Description'), - '#default_value' => isset($menu_link->options['attributes']['title']) ? $menu_link->options['attributes']['title'] : '', - '#rows' => 1, - '#description' => t('Shown when hovering over the menu link.'), - ); - $form['enabled'] = array( - '#type' => 'checkbox', - '#title' => t('Enabled'), - '#default_value' => !$menu_link->hidden, - '#description' => t('Menu links that are not enabled will not be listed in any menu.'), - ); - $form['expanded'] = array( - '#type' => 'checkbox', - '#title' => t('Show as expanded'), - '#default_value' => $menu_link->expanded, - '#description' => t('If selected and this menu link has children, the menu will always appear expanded.'), - ); - - // Generate a list of possible parents (not including this link or descendants). - $options = menu_ui_parent_options(menu_ui_get_menus(), $menu_link); - $default = $menu_link->menu_name . ':' . $menu_link->plid; - if (!isset($options[$default])) { - $default = 'tools:0'; - } - $form['parent'] = array( - '#type' => 'select', - '#title' => t('Parent link'), - '#default_value' => $default, - '#options' => $options, - '#description' => t('The maximum depth for a link and all its children is fixed at !maxdepth. Some menu links may not be available as parents if selecting them would exceed this limit.', array('!maxdepth' => MENU_MAX_DEPTH)), - '#attributes' => array('class' => array('menu-title-select')), - ); - - // Get number of items in menu so the weight selector is sized appropriately. - $delta = $this->menuLinkStorage->countMenuLinks($menu_link->menu_name); - $form['weight'] = array( - '#type' => 'weight', - '#title' => t('Weight'), - // Old hardcoded value. - '#delta' => max($delta, 50), - '#default_value' => $menu_link->weight, - '#description' => t('Optional. In the menu, the heavier links will sink and the lighter links will be positioned nearer the top.'), - ); - - // Language module allows to configure the menu link language independently - // of the menu language. It also allows to optionally show the language - // selector on the menu link form so that the language of each menu link can - // be configured individually. - if ($this->moduleHandler->moduleExists('language')) { - $language_configuration = language_get_default_configuration('menu_link', $menu_link->bundle()); - $default_langcode = ($menu_link->isNew() ? $language_configuration['langcode'] : $menu_link->langcode); - $language_show = $language_configuration['language_show']; - } - // Without Language module menu links inherit the menu language and no - // language selector is shown. - else { - $default_langcode = ($menu_link->isNew() ? entity_load('menu', $menu_link->menu_name)->language()->getId() : $menu_link->langcode); - $language_show = FALSE; - } - - $form['langcode'] = array( - '#type' => 'language_select', - '#title' => t('Language'), - '#languages' => LanguageInterface::STATE_ALL, - '#default_value' => $default_langcode, - '#access' => $language_show, - ); - - return parent::form($form, $form_state, $menu_link); - } - - /** - * Overrides EntityForm::actions(). - */ - protected function actions(array $form, array &$form_state) { - $element = parent::actions($form, $form_state); - $element['submit']['#button_type'] = 'primary'; - return $element; - } - - /** - * Overrides EntityForm::validate(). - */ - public function validate(array $form, array &$form_state) { - $menu_link = $this->buildEntity($form, $form_state); - - $normal_path = $this->pathAliasManager->getPathByAlias($menu_link->link_path); - if ($menu_link->link_path != $normal_path) { - drupal_set_message(t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array('%link_path' => $menu_link->link_path, '%normal_path' => $normal_path))); - $menu_link->link_path = $normal_path; - $form_state['values']['link_path'] = $normal_path; - } - if (!UrlHelper::isExternal($menu_link->link_path)) { - $parsed_link = parse_url($menu_link->link_path); - if (isset($parsed_link['query'])) { - $menu_link->options['query'] = array(); - parse_str($parsed_link['query'], $menu_link->options['query']); - } - else { - // Use unset() rather than setting to empty string - // to avoid redundant serialized data being stored. - unset($menu_link->options['query']); - } - if (isset($parsed_link['fragment'])) { - $menu_link->options['fragment'] = $parsed_link['fragment']; - } - else { - unset($menu_link->options['fragment']); - } - if (isset($parsed_link['path']) && $menu_link->link_path != $parsed_link['path']) { - $menu_link->link_path = $parsed_link['path']; - } - } - if (!trim($menu_link->link_path) || !drupal_valid_path($menu_link->link_path, TRUE)) { - $this->setFormError('link_path', $form_state, $this->t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $menu_link->link_path))); - } - - parent::validate($form, $form_state); - } - - /** - * {@inheritdoc} - */ - public function buildEntity(array $form, array &$form_state) { - // @todo: Remove this when menu links are converted to content entities in - // http://drupal.org/node/1842858. - $entity = clone $this->entity; - // If you submit a form, the form state comes from caching, which forces - // the controller to be the one before caching. Ensure to have the - // controller of the current request. - $form_state['controller'] = $this; - - // Copy top-level form values to entity properties, without changing - // existing entity properties that are not being edited by - // this form. - foreach ($form_state['values'] as $key => $value) { - $entity->$key = $value; - } - - // Invoke all specified builders for copying form values to entity properties. - if (isset($form['#entity_builders'])) { - foreach ($form['#entity_builders'] as $function) { - call_user_func_array($function, array($entity->getEntityTypeId(), $entity, &$form, &$form_state)); - } - } - - return $entity; - } - - /** - * Overrides EntityForm::submit(). - */ - public function submit(array $form, array &$form_state) { - // Build the menu link object from the submitted values. - $menu_link = parent::submit($form, $form_state); - - // The value of "hidden" is the opposite of the value supplied by the - // "enabled" checkbox. - $menu_link->hidden = (int) !$menu_link->enabled; - unset($menu_link->enabled); - - $menu_link->options['attributes']['title'] = $menu_link->description; - list($menu_link->menu_name, $menu_link->plid) = explode(':', $menu_link->parent); - - return $menu_link; - } - - /** - * Overrides EntityForm::save(). - */ - public function save(array $form, array &$form_state) { - $menu_link = $this->entity; - - $saved = $menu_link->save(); - - if ($saved) { - drupal_set_message(t('The menu link has been saved.')); - $form_state['redirect_route'] = array( - 'route_name' => 'menu_ui.menu_edit', - 'route_parameters' => array( - 'menu' => $menu_link->menu_name, - ), - ); - } - else { - drupal_set_message(t('There was an error saving the menu link.'), 'error'); - $form_state['rebuild'] = TRUE; - } - } - -} diff --git a/core/modules/menu_link/src/MenuLinkInterface.php b/core/modules/menu_link/src/MenuLinkInterface.php deleted file mode 100644 index fdfab67..0000000 --- a/core/modules/menu_link/src/MenuLinkInterface.php +++ /dev/null @@ -1,45 +0,0 @@ -database->startTransaction(); - try { - // Load the stored entity, if any. - if (!$entity->isNew() && !isset($entity->original)) { - $id = $entity->id(); - if ($entity->getOriginalId() !== NULL) { - $id = $entity->getOriginalId(); - } - $entity->original = $this->loadUnchanged($id); - } - - if ($entity->isNew()) { - $entity->mlid = $this->database->insert($this->entityType->getBaseTable())->fields(array('menu_name' => $entity->menu_name))->execute(); - $entity->enforceIsNew(); - } - - // Unlike the save() method from EntityDatabaseStorage, we invoke the - // 'presave' hook first because we want to allow modules to alter the - // entity before all the logic from our preSave() method. - $this->invokeHook('presave', $entity); - $entity->preSave($this); - - // If every value in $entity->original is the same in the $entity, there - // is no reason to run the update queries or clear the caches. We use - // array_intersect_key() with the $entity as the first parameter because - // $entity may have additional keys left over from building a router entry. - // The intersect removes the extra keys, allowing a meaningful comparison. - if ($entity->isNew() || (array_intersect_key(get_object_vars($entity), get_object_vars($entity->original)) != get_object_vars($entity->original))) { - $return = drupal_write_record($this->entityType->getBaseTable(), $entity, $this->idKey); - - if ($return) { - if (!$entity->isNew()) { - $this->resetCache(array($entity->{$this->idKey})); - $entity->postSave($this, TRUE); - $this->invokeHook('update', $entity); - } - else { - $return = SAVED_NEW; - $this->resetCache(); - - $entity->enforceIsNew(FALSE); - $entity->postSave($this, FALSE); - $this->invokeHook('insert', $entity); - } - } - } - - // Ignore replica server temporarily. - db_ignore_replica(); - unset($entity->original); - - return $return; - } - catch (\Exception $e) { - $transaction->rollback(); - watchdog_exception($this->entityTypeId, $e); - throw new EntityStorageException($e->getMessage(), $e->getCode(), $e); - } - } - - /** - * {@inheritdoc} - */ - public function setPreventReparenting($value = FALSE) { - $this->preventReparenting = $value; - } - - /** - * {@inheritdoc} - */ - public function getPreventReparenting() { - return $this->preventReparenting; - } - - /** - * {@inheritdoc} - */ - public function loadUpdatedCustomized(array $router_paths) { - $query = parent::buildQuery(NULL); - $query - ->condition(db_or() - ->condition('updated', 1) - ->condition(db_and() - ->condition('router_path', $router_paths, 'NOT IN') - ->condition('external', 0) - ->condition('customized', 1) - ) - ); - $query_result = $query->execute(); - - // We provide the necessary arguments for PDO to create objects of the - // specified entity class. - // @see \Drupal\Core\Entity\EntityInterface::__construct() - $query_result->setFetchMode(\PDO::FETCH_CLASS, $this->entityClass, array(array(), $this->entityTypeId)); - - return $query_result->fetchAllAssoc($this->idKey); - } - - /** - * {@inheritdoc} - */ - public function loadModuleAdminTasks() { - // @todo - this code will move out of the menu link entity, so we are doing - // a straight SQL query for expediency. - $result = $this->database->select('menu_links'); - $result->condition('machine_name', 'system.admin'); - $result->addField('menu_links', 'mlid'); - $plid = $result->execute()->fetchField(); - - $query = $this->database->select('menu_links', 'base', array('fetch' => \PDO::FETCH_ASSOC)); - $query->fields('base'); - $query - ->condition('base.hidden', 0, '>=') - ->condition('base.module', '', '>') - ->condition('base.machine_name', '', '>') - ->condition('base.p1', $plid); - $entities = $query->execute()->fetchAll(); - - return $entities; - } - - /** - * {@inheritdoc} - */ - public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE) { - // If plid == 0, there is nothing to update. - if ($entity->plid) { - // Check if at least one visible child exists in the table. - $query = $this->getQuery(); - $query - ->condition('menu_name', $entity->menu_name) - ->condition('hidden', 0) - ->condition('plid', $entity->plid) - ->count(); - - if ($exclude) { - $query->condition('mlid', $entity->id(), '<>'); - } - - $parent_has_children = ((bool) $query->execute()) ? 1 : 0; - $this->database->update('menu_links') - ->fields(array('has_children' => $parent_has_children)) - ->condition('mlid', $entity->plid) - ->execute(); - } - } - - /** - * {@inheritdoc} - */ - public function findChildrenRelativeDepth(EntityInterface $entity) { - // @todo Since all we need is a specific field from the base table, does it - // make sense to convert to EFQ? - $query = $this->database->select('menu_links'); - $query->addField('menu_links', 'depth'); - $query->condition('menu_name', $entity->menu_name); - $query->orderBy('depth', 'DESC'); - $query->range(0, 1); - - $i = 1; - $p = 'p1'; - while ($i <= MENU_MAX_DEPTH && $entity->{$p}) { - $query->condition($p, $entity->{$p}); - $p = 'p' . ++$i; - } - - $max_depth = $query->execute()->fetchField(); - - return ($max_depth > $entity->depth) ? $max_depth - $entity->depth : 0; - } - - /** - * {@inheritdoc} - */ - public function moveChildren(EntityInterface $entity) { - $query = $this->database->update($this->entityType->getBaseTable()); - - $query->fields(array('menu_name' => $entity->menu_name)); - - $p = 'p1'; - $expressions = array(); - for ($i = 1; $i <= $entity->depth; $p = 'p' . ++$i) { - $expressions[] = array($p, ":p_$i", array(":p_$i" => $entity->{$p})); - } - $j = $entity->original->depth + 1; - while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) { - $expressions[] = array('p' . $i++, 'p' . $j++, array()); - } - while ($i <= MENU_MAX_DEPTH) { - $expressions[] = array('p' . $i++, 0, array()); - } - - $shift = $entity->depth - $entity->original->depth; - if ($shift > 0) { - // The order of expressions must be reversed so the new values don't - // overwrite the old ones before they can be used because "Single-table - // UPDATE assignments are generally evaluated from left to right" - // @see http://dev.mysql.com/doc/refman/5.0/en/update.html - $expressions = array_reverse($expressions); - } - foreach ($expressions as $expression) { - $query->expression($expression[0], $expression[1], $expression[2]); - } - - $query->expression('depth', 'depth + :depth', array(':depth' => $shift)); - $query->condition('menu_name', $entity->original->menu_name); - $p = 'p1'; - for ($i = 1; $i <= MENU_MAX_DEPTH && $entity->original->{$p}; $p = 'p' . ++$i) { - $query->condition($p, $entity->original->{$p}); - } - - $query->execute(); - - // Check the has_children status of the parent, while excluding this item. - $this->updateParentalStatus($entity->original, TRUE); - } - - /** - * {@inheritdoc} - */ - public function countMenuLinks($menu_name) { - $query = $this->getQuery(); - $query - ->condition('menu_name', $menu_name) - ->count(); - return $query->execute(); - } - - /** - * {@inheritdoc} - */ - public function getParentFromHierarchy(EntityInterface $entity) { - $parent_path = $entity->link_path; - do { - $parent = FALSE; - $parent_path = substr($parent_path, 0, strrpos($parent_path, '/')); - - $query = $this->getQuery(); - $query - ->condition('mlid', $entity->id(), '<>') - ->condition('module', 'system') - // We always respect the link's 'menu_name'; inheritance for router - // items is ensured in _menu_router_build(). - ->condition('menu_name', $entity->menu_name) - ->condition('link_path', $parent_path); - - $result = $query->execute(); - // Only valid if we get a unique result. - if (count($result) == 1) { - $parent = $this->load(reset($result)); - } - } while ($parent === FALSE && $parent_path); - - return $parent; - } - - /** - * {@inheritdoc} - */ - public function createFromDefaultLink(array $item) { - // Suggested items are disabled by default. - $item += array( - 'hidden' => 0, - 'options' => empty($item['description']) ? array() : array('attributes' => array('title' => $item['description'])), - ); - return $this->create($item); - } - -} diff --git a/core/modules/menu_link/src/MenuLinkStorageInterface.php b/core/modules/menu_link/src/MenuLinkStorageInterface.php deleted file mode 100644 index 3d3fd5d..0000000 --- a/core/modules/menu_link/src/MenuLinkStorageInterface.php +++ /dev/null @@ -1,110 +0,0 @@ -database = $database; - $this->cache = $cache_backend; - $this->languageManager = $language_manager; - $this->requestStack = $request_stack; - $this->menuLinkStorage = $entity_manager->getStorage('menu_link'); - $this->queryFactory = $entity_query_factory; - $this->state = $state; - } - - /** - * {@inheritdoc} - */ - public function buildAllData($menu_name, $link = NULL, $max_depth = NULL) { - $language_interface = $this->languageManager->getCurrentLanguage(); - - // Use $mlid as a flag for whether the data being loaded is for the whole - // tree. - $mlid = isset($link['mlid']) ? $link['mlid'] : 0; - // Generate a cache ID (cid) specific for this $menu_name, $link, $language, - // and depth. - $cid = 'links:' . $menu_name . ':all:' . $mlid . ':' . $language_interface->id . ':' . (int) $max_depth; - - if (!isset($this->menuFullTrees[$cid])) { - // If the static variable doesn't have the data, check {cache_menu}. - $cache = $this->cache->get($cid); - if ($cache && $cache->data) { - // If the cache entry exists, it contains the parameters for - // menu_build_tree(). - $tree_parameters = $cache->data; - } - // If the tree data was not in the cache, build $tree_parameters. - if (!isset($tree_parameters)) { - $tree_parameters = array( - 'min_depth' => 1, - 'max_depth' => $max_depth, - ); - if ($mlid) { - // The tree is for a single item, so we need to match the values in - // its p columns and 0 (the top level) with the plid values of other - // links. - $parents = array(0); - for ($i = 1; $i < MENU_MAX_DEPTH; $i++) { - if (!empty($link["p$i"])) { - $parents[] = $link["p$i"]; - } - } - $tree_parameters['expanded'] = $parents; - $tree_parameters['active_trail'] = $parents; - $tree_parameters['active_trail'][] = $mlid; - } - - // Cache the tree building parameters using the page-specific cid. - $this->cache->set($cid, $tree_parameters, Cache::PERMANENT, array('menu' => $menu_name)); - } - - // Build the tree using the parameters; the resulting tree will be cached - // by $this->doBuildTree()). - $this->menuFullTrees[$cid] = $this->buildTree($menu_name, $tree_parameters); - } - - return $this->menuFullTrees[$cid]; - } - - /** - * {@inheritdoc} - */ - public function buildPageData($menu_name, $max_depth = NULL, $only_active_trail = FALSE) { - $language_interface = $this->languageManager->getCurrentLanguage(); - - // Load the request corresponding to the current page. - $request = $this->requestStack->getCurrentRequest(); - $system_path = NULL; - if ($route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) { - // @todo https://drupal.org/node/2068471 is adding support so we can tell - // if this is called on a 404/403 page. - $system_path = $request->attributes->get('_system_path'); - $page_not_403 = 1; - } - if (isset($system_path)) { - if (isset($max_depth)) { - $max_depth = min($max_depth, MENU_MAX_DEPTH); - } - // Generate a cache ID (cid) specific for this page. - $cid = 'links:' . $menu_name . ':page:' . $system_path . ':' . $language_interface->id . ':' . $page_not_403 . ':' . (int) $max_depth; - // If we are asked for the active trail only, and $menu_name has not been - // built and cached for this page yet, then this likely means that it - // won't be built anymore, as this function is invoked from - // template_preprocess_page(). So in order to not build a giant menu tree - // that needs to be checked for access on all levels, we simply check - // whether we have the menu already in cache, or otherwise, build a - // minimum tree containing the active trail only. - if (!isset($this->menuPageTrees[$cid]) && $only_active_trail) { - $cid .= ':trail'; - } - - if (!isset($this->menuPageTrees[$cid])) { - // If the static variable doesn't have the data, check {cache_menu}. - $cache = $this->cache->get($cid); - if ($cache && $cache->data) { - // If the cache entry exists, it contains the parameters for - // menu_build_tree(). - $tree_parameters = $cache->data; - } - // If the tree data was not in the cache, build $tree_parameters. - if (!isset($tree_parameters)) { - $tree_parameters = array( - 'min_depth' => 1, - 'max_depth' => $max_depth, - ); - $active_trail = $this->getActiveTrailIds($menu_name); - - // If this page is accessible to the current user, build the tree - // parameters accordingly. - if ($page_not_403) { - // The active trail contains more than only array(0 => 0). - if (count($active_trail) > 1) { - // If we are asked to build links for the active trail only,skip - // the entire 'expanded' handling. - if ($only_active_trail) { - $tree_parameters['only_active_trail'] = TRUE; - } - } - $parents = $active_trail; - - $expanded = $this->state->get('menu_expanded'); - // Check whether the current menu has any links set to be expanded. - if (!$only_active_trail && $expanded && in_array($menu_name, $expanded)) { - // Collect all the links set to be expanded, and then add all of - // their children to the list as well. - do { - $query = $this->queryFactory->get('menu_link') - ->condition('menu_name', $menu_name) - ->condition('expanded', 1) - ->condition('has_children', 1) - ->condition('plid', $parents, 'IN') - ->condition('mlid', $parents, 'NOT IN'); - $result = $query->execute(); - $parents += $result; - } while (!empty($result)); - } - $tree_parameters['expanded'] = $parents; - $tree_parameters['active_trail'] = $active_trail; - } - // If access is denied, we only show top-level links in menus. - else { - $tree_parameters['expanded'] = $active_trail; - $tree_parameters['active_trail'] = $active_trail; - } - // Cache the tree building parameters using the page-specific cid. - $this->cache->set($cid, $tree_parameters, Cache::PERMANENT, array('menu' => $menu_name)); - } - - // Build the tree using the parameters; the resulting tree will be - // cached by $tihs->buildTree(). - $this->menuPageTrees[$cid] = $this->buildTree($menu_name, $tree_parameters); - } - return $this->menuPageTrees[$cid]; - } - - return array(); - } - - /** - * {@inheritdoc} - */ - public function getActiveTrailIds($menu_name) { - // Parent mlids; used both as key and value to ensure uniqueness. - // We always want all the top-level links with plid == 0. - $active_trail = array(0 => 0); - - $request = $this->requestStack->getCurrentRequest(); - - if ($route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME)) { - // @todo https://drupal.org/node/2068471 is adding support so we can tell - // if this is called on a 404/403 page. - // Check if the active trail has been overridden for this menu tree. - $active_path = $this->getPath($menu_name); - // Find a menu link corresponding to the current path. If - // $active_path is NULL, let menu_link_get_preferred() determine - // the path. - if ($active_link = $this->menuLinkGetPreferred($menu_name, $active_path)) { - if ($active_link['menu_name'] == $menu_name) { - // Use all the coordinates, except the last one because - // there can be no child beyond the last column. - for ($i = 1; $i < MENU_MAX_DEPTH; $i++) { - if ($active_link['p' . $i]) { - $active_trail[$active_link['p' . $i]] = $active_link['p' . $i]; - } - } - } - } - } - return $active_trail; - } - - /** - * {@inheritdoc} - */ - public function setPath($menu_name, $path = NULL) { - if (isset($path)) { - $this->trailPaths[$menu_name] = $path; - } - } - - /** - * {@inheritdoc} - */ - public function getPath($menu_name) { - return isset($this->trailPaths[$menu_name]) ? $this->trailPaths[$menu_name] : NULL; - } - - /** - * {@inheritdoc} - */ - public function renderMenu($menu_name) { - - if (!isset($this->menuOutput[$menu_name])) { - $tree = $this->buildPageData($menu_name); - $this->menuOutput[$menu_name] = $this->renderTree($tree); - } - return $this->menuOutput[$menu_name]; - } - - /** - * {@inheritdoc} - */ - public function renderTree($tree) { - $build = array(); - $items = array(); - $menu_name = $tree ? end($tree)['link']['menu_name'] : ''; - - // Pull out just the menu links we are going to render so that we - // get an accurate count for the first/last classes. - foreach ($tree as $data) { - if ($data['link']['access'] && !$data['link']['hidden']) { - $items[] = $data; - } - } - - foreach ($items as $data) { - $class = array(); - // Set a class for the
  • -tag. Since $data['below'] may contain local - // tasks, only set 'expanded' class if the link also has children within - // the current menu. - if ($data['link']['has_children'] && $data['below']) { - $class[] = 'expanded'; - } - elseif ($data['link']['has_children']) { - $class[] = 'collapsed'; - } - else { - $class[] = 'leaf'; - } - // Set a class if the link is in the active trail. - if ($data['link']['in_active_trail']) { - $class[] = 'active-trail'; - $data['link']['localized_options']['attributes']['class'][] = 'active-trail'; - } - - // Allow menu-specific theme overrides. - $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_'); - $element['#attributes']['class'] = $class; - $element['#title'] = $data['link']['title']; - // @todo Use route name and parameters to generate the link path, unless - // it is external. - $element['#href'] = $data['link']['link_path']; - $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array(); - $element['#below'] = $data['below'] ? $this->renderTree($data['below']) : $data['below']; - $element['#original_link'] = $data['link']; - // Index using the link's unique mlid. - $build[$data['link']['mlid']] = $element; - } - if ($build) { - // Make sure drupal_render() does not re-order the links. - $build['#sorted'] = TRUE; - // Add the theme wrapper for outer markup. - // Allow menu-specific theme overrides. - $build['#theme_wrappers'][] = 'menu_tree__' . strtr($menu_name, '-', '_'); - // Set cache tag. - $menu_name = $data['link']['menu_name']; - $build['#cache']['tags']['menu'][$menu_name] = $menu_name; - } - - return $build; - } - - /** - * {@inheritdoc} - */ - public function buildTree($menu_name, array $parameters = array()) { - // Build the menu tree. - $tree = $this->doBuildTree($menu_name, $parameters); - // Check access for the current user to each item in the tree. - $this->checkAccess($tree); - return $tree; - } - - /** - * Builds a menu tree. - * - * This function may be used build the data for a menu tree only, for example - * to further massage the data manually before further processing happens. - * MenuTree::checkAccess() needs to be invoked afterwards. - * - * @param string $menu_name - * The name of the menu. - * @param array $parameters - * The parameters passed into static::buildTree() - * - * @see static::buildTree() - */ - protected function doBuildTree($menu_name, array $parameters = array()) { - $language_interface = $this->languageManager->getCurrentLanguage(); - - // Build the cache id; sort parents to prevent duplicate storage and remove - // default parameter values. - if (isset($parameters['expanded'])) { - sort($parameters['expanded']); - } - $tree_cid = 'links:' . $menu_name . ':tree-data:' . $language_interface->id . ':' . hash('sha256', serialize($parameters)); - - // If we do not have this tree in the static cache, check {cache_menu}. - if (!isset($this->menuTree[$tree_cid])) { - $cache = $this->cache->get($tree_cid); - if ($cache && $cache->data) { - $this->menuTree[$tree_cid] = $cache->data; - } - } - - if (!isset($this->menuTree[$tree_cid])) { - $query = $this->queryFactory->get('menu_link'); - for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) { - $query->sort('p' . $i, 'ASC'); - } - $query->condition('menu_name', $menu_name); - if (!empty($parameters['expanded'])) { - $query->condition('plid', $parameters['expanded'], 'IN'); - } - elseif (!empty($parameters['only_active_trail'])) { - $query->condition('mlid', $parameters['active_trail'], 'IN'); - } - $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1); - if ($min_depth != 1) { - $query->condition('depth', $min_depth, '>='); - } - if (isset($parameters['max_depth'])) { - $query->condition('depth', $parameters['max_depth'], '<='); - } - // Add custom query conditions, if any were passed. - if (isset($parameters['conditions'])) { - foreach ($parameters['conditions'] as $column => $value) { - $query->condition($column, $value); - } - } - - // Build an ordered array of links using the query result object. - $links = array(); - if ($result = $query->execute()) { - $links = $this->menuLinkStorage->loadMultiple($result); - } - $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array()); - $tree = $this->doBuildTreeData($links, $active_trail, $min_depth); - - // Cache the data, if it is not already in the cache. - $this->cache->set($tree_cid, $tree, Cache::PERMANENT, array('menu' => $menu_name)); - $this->menuTree[$tree_cid] = $tree; - } - - return $this->menuTree[$tree_cid]; - } - - /** - * Sorts the menu tree and recursively checks access for each item. - * - * @param array $tree - * The menu tree you wish to operate on. - */ - protected function checkAccess(&$tree) { - $new_tree = array(); - foreach ($tree as $key => $v) { - $item = &$tree[$key]['link']; - $this->menuLinkTranslate($item); - if ($item['access'] || ($item['in_active_trail'] && strpos($item['href'], '%') !== FALSE)) { - if ($tree[$key]['below']) { - $this->checkAccess($tree[$key]['below']); - } - // The weights are made a uniform 5 digits by adding 50000 as an offset. - // After _menu_link_translate(), $item['title'] has the localized link - // title. Adding the mlid to the end of the index insures that it is - // unique. - $new_tree[(50000 + $item['weight']) . ' ' . $item['title'] . ' ' . $item['mlid']] = $tree[$key]; - } - } - // Sort siblings in the tree based on the weights and localized titles. - ksort($new_tree); - $tree = $new_tree; - } - - /** - * {@inheritdoc} - */ - public function buildTreeData(array $links, array $parents = array(), $depth = 1) { - $tree = $this->doBuildTreeData($links, $parents, $depth); - $this->checkAccess($tree); - return $tree; - } - - /** - * Prepares the data for calling $this->treeDataRecursive(). - */ - protected function doBuildTreeData(array $links, array $parents = array(), $depth = 1) { - // Reverse the array so we can use the more efficient array_pop() function. - $links = array_reverse($links); - return $this->treeDataRecursive($links, $parents, $depth); - } - - /** - * Builds the data representing a menu tree. - * - * The function is a bit complex because the rendering of a link depends on - * the next menu link. - * - * @param array $links - * A flat array of menu links that are part of the menu. Each array element - * is an associative array of information about the menu link, containing - * the fields from the {menu_links} table, and optionally additional - * information from the {menu_router} table, if the menu item appears in - * both tables. This array must be ordered depth-first. - * See _menu_build_tree() for a sample query. - * @param array $parents - * An array of the menu link ID values that are in the path from the current - * page to the root of the menu tree. - * @param int $depth - * The minimum depth to include in the returned menu tree. - * - * @return array - */ - protected function treeDataRecursive(&$links, $parents, $depth) { - $tree = array(); - while ($item = array_pop($links)) { - // We need to determine if we're on the path to root so we can later build - // the correct active trail. - $item['in_active_trail'] = in_array($item['mlid'], $parents); - // Add the current link to the tree. - $tree[$item['mlid']] = array( - 'link' => $item, - 'below' => array(), - ); - // Look ahead to the next link, but leave it on the array so it's - // available to other recursive function calls if we return or build a - // sub-tree. - $next = end($links); - // Check whether the next link is the first in a new sub-tree. - if ($next && $next['depth'] > $depth) { - // Recursively call doBuildTreeData to build the sub-tree. - $tree[$item['mlid']]['below'] = $this->treeDataRecursive($links, $parents, $next['depth']); - // Fetch next link after filling the sub-tree. - $next = end($links); - } - // Determine if we should exit the loop and return. - if (!$next || $next['depth'] < $depth) { - break; - } - } - return $tree; - } - - /** - * Wraps menu_link_get_preferred(). - */ - protected function menuLinkGetPreferred($menu_name, $active_path) { - return menu_link_get_preferred($active_path, $menu_name); - } - - /** - * Wraps _menu_link_translate(). - */ - protected function menuLinkTranslate(&$item) { - _menu_link_translate($item); - } - -} diff --git a/core/modules/menu_link/src/MenuTreeInterface.php b/core/modules/menu_link/src/MenuTreeInterface.php deleted file mode 100644 index 418f602..0000000 --- a/core/modules/menu_link/src/MenuTreeInterface.php +++ /dev/null @@ -1,182 +0,0 @@ -moduleHandler = $module_handler; - } - - /** - * Gets the menu links defined in YAML files. - * - * @return array - * An array of default menu links. - */ - public function getLinks() { - $discovery = $this->getDiscovery(); - foreach ($discovery->findAll() as $module => $menu_links) { - foreach ($menu_links as $machine_name => $menu_link) { - $all_links[$machine_name] = $menu_link; - $all_links[$machine_name]['machine_name'] = $machine_name; - $all_links[$machine_name]['module'] = $module; - } - } - - $this->moduleHandler->alter('menu_link_defaults', $all_links); - foreach ($all_links as $machine_name => $menu_link) { - // Set the machine_name to the menu links added dynamically. - if (!isset($menu_link['machine_name'])) { - $all_links[$machine_name]['machine_name'] = $machine_name; - } - // Change the key to match the DB column for now. - $all_links[$machine_name]['link_title'] = $all_links[$machine_name]['title']; - unset($all_links[$machine_name]['title']); - } - - return $all_links; - } - - /** - * Creates a YAML discovery for menu links. - * - * @return \Drupal\Component\Discovery\YamlDiscovery - * An YAML discovery instance. - */ - protected function getDiscovery() { - return new YamlDiscovery('links.menu', $this->moduleHandler->getModuleDirectories()); - } - -} - diff --git a/core/modules/menu_link/tests/src/MenuTreeTest.php b/core/modules/menu_link/tests/src/MenuTreeTest.php deleted file mode 100644 index 747335a..0000000 --- a/core/modules/menu_link/tests/src/MenuTreeTest.php +++ /dev/null @@ -1,539 +0,0 @@ - 'main-menu', - 'mlid' => 1, - 'title' => 'Example 1', - 'route_name' => 'example1', - 'link_path' => 'example1', - 'access' => 1, - 'hidden' => FALSE, - 'has_children' => FALSE, - 'in_active_trail' => TRUE, - 'localized_options' => array('attributes' => array('title' => '')), - 'weight' => 0, - ); - - /** - * {@inheritdoc} - */ - protected function setUp() { - $this->connection = $this->getMockBuilder('Drupal\Core\Database\Connection') - ->disableOriginalConstructor() - ->getMock(); - $this->cacheBackend = $this->getMock('Drupal\Core\Cache\CacheBackendInterface'); - $this->languageManager = $this->getMock('Drupal\Core\Language\LanguageManagerInterface'); - $this->requestStack = new RequestStack(); - $this->entityManager = $this->getMock('Drupal\Core\Entity\EntityManagerInterface'); - $this->entityQueryFactory = $this->getMockBuilder('Drupal\Core\Entity\Query\QueryFactory') - ->disableOriginalConstructor() - ->getMock(); - $this->state = $this->getMock('Drupal\Core\State\StateInterface'); - - $this->menuTree = new TestMenuTree($this->connection, $this->cacheBackend, $this->languageManager, $this->requestStack, $this->entityManager, $this->entityQueryFactory, $this->state); - } - - /** - * Tests active paths. - * - * @covers ::setPath - * @covers ::getPath - */ - public function testActivePaths() { - $this->assertNull($this->menuTree->getPath('test_menu1')); - - $this->menuTree->setPath('test_menu1', 'example_path1'); - $this->assertEquals('example_path1', $this->menuTree->getPath('test_menu1')); - $this->assertNull($this->menuTree->getPath('test_menu2')); - - $this->menuTree->setPath('test_menu2', 'example_path2'); - $this->assertEquals('example_path1', $this->menuTree->getPath('test_menu1')); - $this->assertEquals('example_path2', $this->menuTree->getPath('test_menu2')); - } - - /** - * Tests buildTreeData with a single level. - * - * @covers ::buildTreeData - * @covers ::doBuildTreeData - */ - public function testBuildTreeDataWithSingleLevel() { - $items = array(); - $items[] = array( - 'mlid' => 1, - 'depth' => 1, - 'weight' => 0, - 'title' => '', - 'route_name' => 'example1', - 'access' => TRUE, - ); - $items[] = array( - 'mlid' => 2, - 'depth' => 1, - 'weight' => 0, - 'title' => '', - 'route_name' => 'example2', - 'access' => TRUE, - ); - - $result = $this->menuTree->buildTreeData($items, array(), 1); - - $this->assertCount(2, $result); - $result1 = array_shift($result); - $this->assertEquals($items[0] + array('in_active_trail' => FALSE), $result1['link']); - $result2 = array_shift($result); - $this->assertEquals($items[1] + array('in_active_trail' => FALSE), $result2['link']); - } - - /** - * Tests buildTreeData with a single level and one item being active. - * - * @covers ::buildTreeData - * @covers ::doBuildTreeData - */ - public function testBuildTreeDataWithSingleLevelAndActiveItem() { - $items = array(); - $items[] = array( - 'mlid' => 1, - 'depth' => 1, - 'weight' => 0, - 'title' => '', - 'route_name' => 'example1', - 'access' => TRUE, - ); - $items[] = array( - 'mlid' => 2, - 'depth' => 1, - 'weight' => 0, - 'title' => '', - 'route_name' => 'example2', - 'access' => TRUE, - ); - - $result = $this->menuTree->buildTreeData($items, array(1), 1); - - $this->assertCount(2, $result); - $result1 = array_shift($result); - $this->assertEquals($items[0] + array('in_active_trail' => TRUE), $result1['link']); - $result2 = array_shift($result); - $this->assertEquals($items[1] + array('in_active_trail' => FALSE), $result2['link']); - } - - /** - * Tests buildTreeData with a single level and none item being active. - * - * @covers ::buildTreeData - * @covers ::doBuildTreeData - */ - public function testBuildTreeDataWithSingleLevelAndNoActiveItem() { - $items = array(); - $items[] = array( - 'mlid' => 1, - 'depth' => 1, - 'weight' => 0, - 'title' => '', - 'route_name' => 'example1', - 'access' => TRUE, - ); - $items[] = array( - 'mlid' => 2, - 'depth' => 1, - 'weight' => 0, - 'title' => '', - 'route_name' => 'example2', - 'access' => TRUE, - ); - - $result = $this->menuTree->buildTreeData($items, array(3), 1); - - $this->assertCount(2, $result); - $result1 = array_shift($result); - $this->assertEquals($items[0] + array('in_active_trail' => FALSE), $result1['link']); - $result2 = array_shift($result); - $this->assertEquals($items[1] + array('in_active_trail' => FALSE), $result2['link']); - } - - /** - * Tests buildTreeData with a more complex example. - * - * @covers ::buildTreeData - * @covers ::doBuildTreeData - */ - public function testBuildTreeWithComplexData() { - $items = array( - 1 => array('mlid' => 1, 'depth' => 1, 'route_name' => 'example1', 'access' => TRUE, 'weight' => 0, 'title' => ''), - 2 => array('mlid' => 2, 'depth' => 1, 'route_name' => 'example2', 'access' => TRUE, 'weight' => 0, 'title' => ''), - 3 => array('mlid' => 3, 'depth' => 2, 'route_name' => 'example3', 'access' => TRUE, 'weight' => 0, 'title' => ''), - 4 => array('mlid' => 4, 'depth' => 3, 'route_name' => 'example4', 'access' => TRUE, 'weight' => 0, 'title' => ''), - 5 => array('mlid' => 5, 'depth' => 1, 'route_name' => 'example5', 'access' => TRUE, 'weight' => 0, 'title' => ''), - ); - - $tree = $this->menuTree->buildTreeData($items); - - // Validate that parent items #1, #2, and #5 exist on the root level. - $this->assertEquals($items[1]['mlid'], $tree['50000 1']['link']['mlid']); - $this->assertEquals($items[2]['mlid'], $tree['50000 2']['link']['mlid']); - $this->assertEquals($items[5]['mlid'], $tree['50000 5']['link']['mlid']); - - // Validate that child item #4 exists at the correct location in the hierarchy. - $this->assertEquals($items[4]['mlid'], $tree['50000 2']['below']['50000 3']['below']['50000 4']['link']['mlid']); - } - - /** - * Tests getActiveTrailIds(). - * - * @covers ::getActiveTrailIds() - */ - public function testGetActiveTrailIds() { - $menu_link = array( - 'mlid' => 10, - 'route_name' => 'example1', - 'p1' => 3, - 'p2' => 2, - 'p3' => 1, - 'p4' => 4, - 'p5' => 9, - 'p6' => 5, - 'p7' => 6, - 'p8' => 7, - 'p9' => 8, - 'menu_name' => 'test_menu' - ); - $this->menuTree->setPreferredMenuLink('test_menu', 'test/path', $menu_link); - $request = (new Request()); - $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'test_route'); - $this->requestStack->push($request); - $this->menuTree->setPath('test_menu', 'test/path'); - - $trail = $this->menuTree->getActiveTrailIds('test_menu'); - $this->assertEquals(array(0 => 0, 3 => 3, 2 => 2, 1 => 1, 4 => 4, 9 => 9, 5 => 5, 6 => 6, 7 => 7), $trail); - } - - /** - * Tests getActiveTrailIds() without preferred link. - * - * @covers ::getActiveTrailIds() - */ - public function testGetActiveTrailIdsWithoutPreferredLink() { - $request = (new Request()); - $request->attributes->set(RouteObjectInterface::ROUTE_NAME, 'test_route'); - $this->requestStack->push($request); - $this->menuTree->setPath('test_menu', 'test/path'); - - $trail = $this->menuTree->getActiveTrailIds('test_menu'); - $this->assertEquals(array(0 => 0), $trail); - } - - - /** - * Tests buildTree with simple menu_name and no parameters. - */ - public function testBuildTreeWithoutParameters() { - $language = new Language(array('id' => 'en')); - $this->languageManager->expects($this->any()) - ->method('getCurrentLanguage') - ->will($this->returnValue($language)); - - // Setup query and the query result. - $query = $this->getMock('Drupal\Core\Entity\Query\QueryInterface'); - $this->entityQueryFactory->expects($this->once()) - ->method('get') - ->with('menu_link') - ->will($this->returnValue($query)); - $query->expects($this->once()) - ->method('condition') - ->with('menu_name', 'test_menu'); - $query->expects($this->once()) - ->method('execute') - ->will($this->returnValue(array(1, 2, 3))); - - $storage = $this->getMock('Drupal\Core\Entity\EntityStorageInterface'); - $base = array( - 'access' => TRUE, - 'weight' => 0, - 'title' => 'title', - ); - $menu_link = $base + array( - 'mlid' => 1, - 'p1' => 3, - 'p2' => 2, - 'p3' => 1, - ); - $links[1] = $menu_link; - $menu_link = $base + array( - 'mlid' => 3, - 'p1' => 3, - 'depth' => 1, - ); - $links[3] = $menu_link; - $menu_link = $base + array( - 'mlid' => 2, - 'p1' => 3, - 'p2' => 2, - 'depth' => 2, - ); - $links[2] = $menu_link; - $storage->expects($this->once()) - ->method('loadMultiple') - ->with(array(1, 2, 3)) - ->will($this->returnValue($links)); - $this->menuTree->setStorage($storage); - - // Ensure that static/non static caching works. - // First setup no working caching. - $this->cacheBackend->expects($this->at(0)) - ->method('get') - ->with('links:test_menu:tree-data:en:35786c7117b4e38d0f169239752ce71158266ae2f6e4aa230fbbb87bd699c0e3') - ->will($this->returnValue(FALSE)); - $this->cacheBackend->expects($this->at(1)) - ->method('set') - ->with('links:test_menu:tree-data:en:35786c7117b4e38d0f169239752ce71158266ae2f6e4aa230fbbb87bd699c0e3', $this->anything(), Cache::PERMANENT, array('menu' => 'test_menu')); - - // Ensure that the static caching triggered. - $this->cacheBackend->expects($this->exactly(1)) - ->method('get'); - - $this->menuTree->buildTree('test_menu'); - $this->menuTree->buildTree('test_menu'); - } - - /** - * Tests the output with a single level. - * - * @covers ::renderTree - */ - public function testOutputWithSingleLevel() { - $tree = array( - '1' => array( - 'link' => array('mlid' => 1) + $this->defaultMenuLink, - 'below' => array(), - ), - '2' => array( - 'link' => array('mlid' => 2) + $this->defaultMenuLink, - 'below' => array(), - ), - ); - - $output = $this->menuTree->renderTree($tree); - - // Validate that the - in main-menu is changed into an underscore - $this->assertEquals($output['1']['#theme'], 'menu_link__main_menu', 'Hyphen is changed to an underscore on menu_link'); - $this->assertEquals($output['2']['#theme'], 'menu_link__main_menu', 'Hyphen is changed to an underscore on menu_link'); - $this->assertEquals($output['#theme_wrappers'][0], 'menu_tree__main_menu', 'Hyphen is changed to an underscore on menu_tree wrapper'); - } - - /** - * Tests the output method with a complex example. - * - * @covers ::renderTree - */ - public function testOutputWithComplexData() { - $tree = array( - '1'=> array( - 'link' => array('mlid' => 1, 'has_children' => 1, 'title' => 'Item 1', 'link_path' => 'a') + $this->defaultMenuLink, - 'below' => array( - '2' => array('link' => array('mlid' => 2, 'title' => 'Item 2', 'link_path' => 'a/b') + $this->defaultMenuLink, - 'below' => array( - '3' => array('link' => array('mlid' => 3, 'title' => 'Item 3', 'in_active_trail' => 0, 'link_path' => 'a/b/c') + $this->defaultMenuLink, - 'below' => array()), - '4' => array('link' => array('mlid' => 4, 'title' => 'Item 4', 'in_active_trail' => 0, 'link_path' => 'a/b/d') + $this->defaultMenuLink, - 'below' => array()) - ) - ) - ) - ), - '5' => array('link' => array('mlid' => 5, 'hidden' => 1, 'title' => 'Item 5', 'link_path' => 'e') + $this->defaultMenuLink, 'below' => array()), - '6' => array('link' => array('mlid' => 6, 'title' => 'Item 6', 'in_active_trail' => 0, 'access' => 0, 'link_path' => 'f') + $this->defaultMenuLink, 'below' => array()), - '7' => array('link' => array('mlid' => 7, 'title' => 'Item 7', 'in_active_trail' => 0, 'link_path' => 'g') + $this->defaultMenuLink, 'below' => array()) - ); - - $output = $this->menuTree->renderTree($tree); - - // Looking for child items in the data - $this->assertEquals( $output['1']['#below']['2']['#href'], 'a/b', 'Checking the href on a child item'); - $this->assertTrue(in_array('active-trail', $output['1']['#below']['2']['#attributes']['class']), 'Checking the active trail class'); - // Validate that the hidden and no access items are missing - $this->assertFalse(isset($output['5']), 'Hidden item should be missing'); - $this->assertFalse(isset($output['6']), 'False access should be missing'); - // Item 7 is after a couple hidden items. Just to make sure that 5 and 6 are - // skipped and 7 still included. - $this->assertTrue(isset($output['7']), 'Item after hidden items is present'); - } - - /** - * Tests menu tree access check with a single level. - * - * @covers ::checkAccess - */ - public function testCheckAccessWithSingleLevel() { - $items = array( - array('mlid' => 1, 'route_name' => 'menu_test_1', 'depth' => 1, 'link_path' => 'menu_test/test_1', 'in_active_trail' => FALSE) + $this->defaultMenuLink, - array('mlid' => 2, 'route_name' => 'menu_test_2', 'depth' => 1, 'link_path' => 'menu_test/test_2', 'in_active_trail' => FALSE) + $this->defaultMenuLink, - ); - - // Register a menuLinkTranslate to mock the access. - $this->menuTree->menuLinkTranslateCallable = function(&$item) { - $item['access'] = $item['mlid'] == 1; - }; - - // Build the menu tree and check access for all of the items. - $tree = $this->menuTree->buildTreeData($items); - - $this->assertCount(1, $tree); - $item = reset($tree); - $this->assertEquals($items[0], $item['link']); - } - -} - -class TestMenuTree extends MenuTree { - - /** - * An alternative callable used for menuLinkTranslate. - * @var callable - */ - public $menuLinkTranslateCallable; - - /** - * Stores the preferred menu link per menu and path. - * - * @var array - */ - protected $preferredMenuLink; - - /** - * {@inheritdoc} - */ - protected function menuLinkTranslate(&$item) { - if (isset($this->menuLinkTranslateCallable)) { - call_user_func_array($this->menuLinkTranslateCallable, array(&$item)); - } - } - - /** - * {@inheritdoc} - */ - protected function menuLinkGetPreferred($menu_name, $active_path) { - return isset($this->preferredMenuLink[$menu_name][$active_path]) ? $this->preferredMenuLink[$menu_name][$active_path] : NULL; - } - - /** - * Set the storage. - * - * @param \Drupal\Core\Entity\EntityStorageInterface $storage - * The menu link storage. - */ - public function setStorage(EntityStorageInterface $storage) { - $this->menuLinkStorage = $storage; - } - - /** - * Sets the preferred menu link. - * - * @param string $menu_name - * The menu name. - * @param string $active_path - * The active path. - * @param array $menu_link - * The preferred menu link. - */ - public function setPreferredMenuLink($menu_name, $active_path, $menu_link) { - $this->preferredMenuLink[$menu_name][$active_path] = $menu_link; - } - -} - -} - -namespace { - if (!defined('MENU_MAX_DEPTH')) { - define('MENU_MAX_DEPTH', 9); - } -}