Issue #2301319 by pwolanin, effulgentsia: MenuLinkNG part5: Remove dead code and party!
parent
718a47d755
commit
c77851c473
|
@ -255,14 +255,6 @@ use Drupal\Core\Template\Attribute;
|
|||
* documentation here.
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
@ -315,92 +307,6 @@ const MENU_MAX_DEPTH = 9;
|
|||
* @endcode
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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().
|
||||
*/
|
||||
|
@ -764,119 +670,6 @@ function theme_menu_local_tasks(&$variables) {
|
|||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
|
@ -887,225 +680,6 @@ function menu_cache_clear_all() {
|
|||
\Drupal::cache('menu')->invalidateAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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".
|
||||
*/
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Hooks provided by the Menu link module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @addtogroup hooks
|
||||
* @{
|
||||
*/
|
||||
|
||||
/**
|
||||
* Alter a menu link after it has been translated and before it is rendered.
|
||||
*
|
||||
* This hook is invoked from _menu_link_translate() after a menu link has been
|
||||
* translated; i.e., after the user access to the link's target page has
|
||||
* been checked. It is only invoked if $menu_link['options']['alter'] has been
|
||||
* set to a non-empty value (e.g. TRUE). This flag should be set using
|
||||
* hook_ENTITY_TYPE_presave() for entity 'menu_link'.
|
||||
*
|
||||
* Implementations of this hook are able to alter any property of the menu link.
|
||||
* For example, this hook may be used to add a page-specific query string to all
|
||||
* menu links, or hide a certain link by setting:
|
||||
* @code
|
||||
* 'hidden' => 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".
|
||||
*/
|
|
@ -1,221 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Install, update and uninstall functions for the menu_link module.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Implements hook_schema().
|
||||
*/
|
||||
function menu_link_schema() {
|
||||
$schema['menu_links'] = array(
|
||||
'description' => '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;
|
||||
}
|
|
@ -1,225 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Enables users to create menu links.
|
||||
*/
|
||||
|
||||
use Drupal\Core\Routing\RouteMatchInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\menu_link\Entity\MenuLink;
|
||||
use Drupal\menu_link\MenuLinkInterface;
|
||||
|
||||
function menu_link_help($route_name, RouteMatchInterface $route_match) {
|
||||
switch ($route_name) {
|
||||
case 'help.page.menu_link':
|
||||
$output = '';
|
||||
$output .= '<h3>' . t('About') . '</h3>';
|
||||
$output .= '<p>' . 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 <a href="!menu-help">Menu UI module help page</a> for more information.', array('!menu-help' => \Drupal::url('help.page', array('name' => 'menu_ui')))) . '</p>';
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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']
|
|
@ -1,680 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\menu_link\Entity\MenuLink.
|
||||
*/
|
||||
|
||||
namespace Drupal\menu_link\Entity;
|
||||
|
||||
use Drupal\Component\Utility\NestedArray;
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Entity\Entity;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Url;
|
||||
use Drupal\menu_link\MenuLinkInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Defines the menu link entity class.
|
||||
*
|
||||
* @EntityType(
|
||||
* id = "menu_link",
|
||||
* label = @Translation("Menu link"),
|
||||
* controllers = {
|
||||
* "storage" = "Drupal\menu_link\MenuLinkStorage",
|
||||
* "access" = "Drupal\menu_link\MenuLinkAccessController",
|
||||
* "form" = {
|
||||
* "default" = "Drupal\menu_link\MenuLinkForm"
|
||||
* }
|
||||
* },
|
||||
* admin_permission = "administer menu",
|
||||
* static_cache = FALSE,
|
||||
* base_table = "menu_links",
|
||||
* uri_callback = "menu_link_uri",
|
||||
* translatable = TRUE,
|
||||
* entity_keys = {
|
||||
* "id" = "mlid",
|
||||
* "label" = "link_title",
|
||||
* "uuid" = "uuid",
|
||||
* "bundle" = "bundle"
|
||||
* },
|
||||
* )
|
||||
*/
|
||||
class MenuLink extends Entity implements \ArrayAccess, MenuLinkInterface {
|
||||
|
||||
/**
|
||||
* The link's menu name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $menu_name = 'tools';
|
||||
|
||||
/**
|
||||
* The link's bundle.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $bundle = 'tools';
|
||||
|
||||
/**
|
||||
* The menu link ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $mlid;
|
||||
|
||||
/**
|
||||
* An optional machine name if defined via the menu_link.static service.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $machine_name;
|
||||
|
||||
/**
|
||||
* The menu link UUID.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $uuid;
|
||||
|
||||
/**
|
||||
* The parent link ID.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $plid;
|
||||
|
||||
/**
|
||||
* The Drupal path or external path this link points to.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $link_path;
|
||||
|
||||
/**
|
||||
* The entity label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $link_title = '';
|
||||
|
||||
/**
|
||||
* A serialized array of options to be passed to the url() or l() function,
|
||||
* such as a query string or HTML attributes.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $options = array();
|
||||
|
||||
/**
|
||||
* The name of the module that generated this link.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $module = 'menu_ui';
|
||||
|
||||
/**
|
||||
* A flag for whether the link should be rendered in menus.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $hidden = 0;
|
||||
|
||||
/**
|
||||
* A flag to indicate if the link points to a full URL starting with a
|
||||
* protocol, like http:// (1 = external, 0 = internal).
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $external;
|
||||
|
||||
/**
|
||||
* Flag indicating whether any links have this link as a parent.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $has_children = 0;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $expanded = 0;
|
||||
|
||||
/**
|
||||
* Link weight among links in the same menu at the same depth.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $weight = 0;
|
||||
|
||||
/**
|
||||
* The depth relative to the top level. A link with plid == 0 will have
|
||||
* depth == 1.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $depth;
|
||||
|
||||
/**
|
||||
* A flag to indicate that the user has manually created or edited the link.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $customized = 0;
|
||||
|
||||
/**
|
||||
* The first entity ID in the materialized path.
|
||||
*
|
||||
* @var int
|
||||
*
|
||||
* @todo Investigate whether the p1, p2, .. pX properties can be moved to a
|
||||
* single array property.
|
||||
*/
|
||||
public $p1;
|
||||
|
||||
/**
|
||||
* The second entity ID in the materialized path.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $p2;
|
||||
|
||||
/**
|
||||
* The third entity ID in the materialized path.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $p3;
|
||||
|
||||
/**
|
||||
* The fourth entity ID in the materialized path.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $p4;
|
||||
|
||||
/**
|
||||
* The fifth entity ID in the materialized path.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $p5;
|
||||
|
||||
/**
|
||||
* The sixth entity ID in the materialized path.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $p6;
|
||||
|
||||
/**
|
||||
* The seventh entity ID in the materialized path.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $p7;
|
||||
|
||||
/**
|
||||
* The eighth entity ID in the materialized path.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $p8;
|
||||
|
||||
/**
|
||||
* The ninth entity ID in the materialized path.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $p9;
|
||||
|
||||
/**
|
||||
* The menu link modification timestamp.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
public $updated = 0;
|
||||
|
||||
/**
|
||||
* The name of the route associated with this menu link, if any.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $route_name;
|
||||
|
||||
/**
|
||||
* The parameters of the route associated with this menu link, if any.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $route_parameters = array();
|
||||
|
||||
/**
|
||||
* The route object associated with this menu link, if any.
|
||||
*
|
||||
* @var \Symfony\Component\Routing\Route
|
||||
*/
|
||||
protected $routeObject;
|
||||
|
||||
/**
|
||||
* Boolean indicating whether a new revision should be created on save.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $newRevision = FALSE;
|
||||
|
||||
/**
|
||||
* Indicates whether this is the default revision.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $isDefaultRevision = TRUE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function setNewRevision($value = TRUE) {
|
||||
$this->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 '<front>',
|
||||
// since a path marked as external does not need to match a route.
|
||||
$this->external = (UrlHelper::isExternal($this->link_path) || $this->link_path == '<front>') ? 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\menu_link\MenuLinkAccessController.
|
||||
*/
|
||||
|
||||
namespace Drupal\menu_link;
|
||||
|
||||
use Drupal\Core\Entity\EntityAccessController;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Session\AccountInterface;
|
||||
|
||||
/**
|
||||
* Defines an access controller for the menu link entity.
|
||||
*
|
||||
* @see \Drupal\menu_link\Entity\MenuLink
|
||||
*/
|
||||
class MenuLinkAccessController extends EntityAccessController {
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function checkAccess(EntityInterface $entity, $operation, $langcode, AccountInterface $account) {
|
||||
$access = $account->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;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,311 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\menu_link\MenuLinkForm.
|
||||
*/
|
||||
|
||||
namespace Drupal\menu_link;
|
||||
|
||||
use Drupal\Component\Utility\UrlHelper;
|
||||
use Drupal\Core\Entity\EntityForm;
|
||||
use Drupal\Core\Language\LanguageInterface;
|
||||
use Drupal\Core\Path\AliasManagerInterface;
|
||||
use Drupal\Core\Routing\UrlGenerator;
|
||||
use Symfony\Component\DependencyInjection\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Form controller for the node edit forms.
|
||||
*/
|
||||
class MenuLinkForm extends EntityForm {
|
||||
|
||||
/**
|
||||
* The menu link storage.
|
||||
*
|
||||
* @var \Drupal\menu_link\MenuLinkStorageInterface
|
||||
*/
|
||||
protected $menuLinkStorage;
|
||||
|
||||
/**
|
||||
* The path alias manager.
|
||||
*
|
||||
* @var \Drupal\Core\Path\AliasManagerInterface
|
||||
*/
|
||||
protected $pathAliasManager;
|
||||
|
||||
/**
|
||||
* The URL generator.
|
||||
*
|
||||
* @var \Drupal\Core\Routing\UrlGenerator
|
||||
*/
|
||||
protected $urlGenerator;
|
||||
|
||||
/**
|
||||
* Constructs a new MenuLinkForm object.
|
||||
*
|
||||
* @param \Drupal\menu_link\MenuLinkStorageInterface $menu_link_storage
|
||||
* The menu link storage.
|
||||
* @param \Drupal\Core\Path\AliasManagerInterface $path_alias_manager
|
||||
* The path alias manager.
|
||||
* @param \Drupal\Core\Routing\UrlGenerator $url_generator
|
||||
* The URL generator.
|
||||
*/
|
||||
public function __construct(MenuLinkStorageInterface $menu_link_storage, AliasManagerInterface $path_alias_manager, UrlGenerator $url_generator) {
|
||||
$this->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' => '<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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\menu_link\MenuLinkInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\menu_link;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Symfony\Component\Routing\Route;
|
||||
|
||||
/**
|
||||
* Provides an interface defining a menu link entity.
|
||||
*/
|
||||
interface MenuLinkInterface extends EntityInterface {
|
||||
|
||||
/**
|
||||
* Returns the Route object associated with this link, if any.
|
||||
*
|
||||
* @return \Symfony\Component\Routing\Route|null
|
||||
* The route object for this menu link, or NULL if there isn't one.
|
||||
*/
|
||||
public function getRoute();
|
||||
|
||||
/**
|
||||
* Sets the route object for this link.
|
||||
*
|
||||
* This should only be called by MenuLinkStorage when loading
|
||||
* the link object. Calling it at other times could result in unpredictable
|
||||
* behavior.
|
||||
*
|
||||
* @param \Symfony\Component\Routing\Route $route
|
||||
*/
|
||||
public function setRouteObject(Route $route);
|
||||
|
||||
/**
|
||||
* Resets a system-defined menu link.
|
||||
*
|
||||
* @return \Drupal\menu_link\MenuLinkInterface
|
||||
* A menu link entity.
|
||||
*/
|
||||
public function reset();
|
||||
|
||||
}
|
|
@ -1,319 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\menu_link\MenuLinkStorage.
|
||||
*/
|
||||
|
||||
namespace Drupal\menu_link;
|
||||
|
||||
use Drupal\Core\Entity\EntityDatabaseStorage;
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageException;
|
||||
|
||||
/**
|
||||
* Controller class for menu links.
|
||||
*
|
||||
* This extends the Drupal\entity\EntityDatabaseStorage class, adding
|
||||
* required special handling for menu_link entities.
|
||||
*/
|
||||
class MenuLinkStorage extends EntityDatabaseStorage implements MenuLinkStorageInterface {
|
||||
|
||||
/**
|
||||
* Indicates whether the delete operation should re-parent children items.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected $preventReparenting = FALSE;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function create(array $values = array()) {
|
||||
// The bundle of menu links being the menu name is not enforced but is the
|
||||
// default behavior if no bundle is set.
|
||||
if (!isset($values['bundle']) && isset($values['menu_name'])) {
|
||||
$values['bundle'] = $values['menu_name'];
|
||||
}
|
||||
return parent::create($values);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function save(EntityInterface $entity) {
|
||||
|
||||
// We return SAVED_UPDATED by default because the logic below might not
|
||||
// update the entity if its values haven't changed, so returning FALSE
|
||||
// would be confusing in that situation.
|
||||
$return = SAVED_UPDATED;
|
||||
|
||||
$transaction = $this->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);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\menu_link\MenuLinkStorageInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\menu_link;
|
||||
|
||||
use Drupal\Core\Entity\EntityInterface;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
|
||||
/**
|
||||
* Defines a common interface for menu link entity controller classes.
|
||||
*/
|
||||
interface MenuLinkStorageInterface extends EntityStorageInterface {
|
||||
|
||||
/**
|
||||
* Sets an internal flag that allows us to prevent the reparenting operations
|
||||
* executed during deletion.
|
||||
*
|
||||
* @param bool $value
|
||||
* TRUE if reparenting should be allowed, FALSE if it should be prevented.
|
||||
*/
|
||||
public function setPreventReparenting($value = FALSE);
|
||||
|
||||
/**
|
||||
* Gets value of internal flag that allows/prevents reparenting operations
|
||||
* executed during deletion.
|
||||
*
|
||||
* @return bool
|
||||
* TRUE if reparenting is allowed, FALSE if it is prevented.
|
||||
*/
|
||||
public function getPreventReparenting();
|
||||
|
||||
/**
|
||||
* Loads system menu link as needed by system_get_module_admin_tasks().
|
||||
*
|
||||
* @return array
|
||||
* An array of menu link entities indexed by their IDs.
|
||||
*/
|
||||
public function loadModuleAdminTasks();
|
||||
|
||||
/**
|
||||
* Checks and updates the 'has_children' property for the parent of a link.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* A menu link entity.
|
||||
*/
|
||||
public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE);
|
||||
|
||||
/**
|
||||
* Finds the depth of an item's children relative to its depth.
|
||||
*
|
||||
* For example, if the item has a depth of 2 and the maximum of any child in
|
||||
* the menu link tree is 5, the relative depth is 3.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* A menu link entity.
|
||||
*
|
||||
* @return int
|
||||
* The relative depth, or zero.
|
||||
*/
|
||||
public function findChildrenRelativeDepth(EntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Updates the children of a menu link that is being moved.
|
||||
*
|
||||
* The menu name, parents (p1 - p6), and depth are updated for all children of
|
||||
* the link, and the has_children status of the previous parent is updated.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* A menu link entity.
|
||||
*/
|
||||
public function moveChildren(EntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Returns the number of menu links from a menu.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The unique name of a menu.
|
||||
*/
|
||||
public function countMenuLinks($menu_name);
|
||||
|
||||
/**
|
||||
* Tries to derive menu link's parent from the path hierarchy.
|
||||
*
|
||||
* @param \Drupal\Core\Entity\EntityInterface $entity
|
||||
* A menu link entity.
|
||||
*
|
||||
* @return \Drupal\Core\Entity\EntityInterface|false
|
||||
* A menu link entity or FALSE if not valid parent was found.
|
||||
*/
|
||||
public function getParentFromHierarchy(EntityInterface $entity);
|
||||
|
||||
/**
|
||||
* Builds a menu link entity from a default item.
|
||||
*
|
||||
* This function should only be called for link data from
|
||||
* the menu_link.static service.
|
||||
*
|
||||
* @param array $item
|
||||
* An item returned from the menu_link.static service.
|
||||
*
|
||||
* @return \Drupal\menu_link\MenuLinkInterface
|
||||
* A menu link entity.
|
||||
*/
|
||||
public function createFromDefaultLink(array $item);
|
||||
|
||||
}
|
|
@ -1,619 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\menu_link\MenuTree.
|
||||
*/
|
||||
|
||||
namespace Drupal\menu_link;
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Cache\CacheBackendInterface;
|
||||
use Drupal\Core\Database\Connection;
|
||||
use Drupal\Core\Entity\EntityManagerInterface;
|
||||
use Drupal\Core\Entity\Query\QueryFactory;
|
||||
use Drupal\Core\State\StateInterface;
|
||||
use Drupal\Core\Language\LanguageManagerInterface;
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
/**
|
||||
* Provides the default implementation of a menu tree.
|
||||
*/
|
||||
class MenuTree implements MenuTreeInterface {
|
||||
|
||||
/**
|
||||
* The database connection.
|
||||
*
|
||||
* @var \Drupal\Core\Database\Connection
|
||||
* The database connection.
|
||||
*/
|
||||
protected $database;
|
||||
|
||||
/**
|
||||
* The cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface
|
||||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* The language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* The menu link storage.
|
||||
*
|
||||
* @var \Drupal\menu_link\MenuLinkStorageInterface
|
||||
*/
|
||||
protected $menuLinkStorage;
|
||||
|
||||
/**
|
||||
* The entity query factory.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryFactory
|
||||
*/
|
||||
protected $queryFactory;
|
||||
|
||||
/**
|
||||
* The state.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* A list of active trail paths keyed by $menu_name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $trailPaths;
|
||||
|
||||
/**
|
||||
* Stores the rendered menu output keyed by $menu_name.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $menuOutput;
|
||||
|
||||
/**
|
||||
* Stores the menu tree used by the doBuildTree method, keyed by a cache ID.
|
||||
*
|
||||
* This cache ID is built using the $menu_name, the current language and
|
||||
* some parameters passed into an entity query.
|
||||
*/
|
||||
protected $menuTree;
|
||||
|
||||
/**
|
||||
* Stores the full menu tree data keyed by a cache ID.
|
||||
*
|
||||
* This variable distinct from static::$menuTree by having also items without
|
||||
* access by the current user.
|
||||
*
|
||||
* This cache ID is built with the menu name, a passed in root link ID, the
|
||||
* current language as well as the maximum depth.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $menuFullTrees;
|
||||
|
||||
/**
|
||||
* Stores the menu tree data on the current page keyed by a cache ID.
|
||||
*
|
||||
* This contains less information than a tree built with buildAllData.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $menuPageTrees;
|
||||
|
||||
/**
|
||||
* Constructs a new MenuTree.
|
||||
*
|
||||
* @param \Drupal\Core\Database\Connection $database
|
||||
* The database connection.
|
||||
* @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
|
||||
* The cache backend.
|
||||
* @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
|
||||
* The language manager.
|
||||
* @param \Symfony\Component\HttpFoundation\RequestStack $request_stack
|
||||
* The request stack.
|
||||
* @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
|
||||
* The entity manager.
|
||||
* @param \Drupal\Core\Entity\Query\QueryFactory $entity_query_factory
|
||||
* The entity query factory.
|
||||
* @param \Drupal\Core\State\StateInterface $state
|
||||
* The state.
|
||||
*/
|
||||
public function __construct(Connection $database, CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, RequestStack $request_stack, EntityManagerInterface $entity_manager, QueryFactory $entity_query_factory, StateInterface $state) {
|
||||
$this->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 <li>-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);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,182 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\menu_link\MenuTreeInterface.
|
||||
*/
|
||||
|
||||
namespace Drupal\menu_link;
|
||||
|
||||
/**
|
||||
* Defines an interface for trees out of menu links.
|
||||
*/
|
||||
interface MenuTreeInterface {
|
||||
|
||||
/**
|
||||
* Returns a rendered menu tree.
|
||||
*
|
||||
* The menu item's LI element is given one of the following classes:
|
||||
* - expanded: The menu item is showing its submenu.
|
||||
* - collapsed: The menu item has a submenu which is not shown.
|
||||
* - leaf: The menu item has no submenu.
|
||||
*
|
||||
* @param array $tree
|
||||
* A data structure representing the tree as returned from menu_tree_data.
|
||||
*
|
||||
* @return array
|
||||
* A structured array to be rendered by drupal_render().
|
||||
*/
|
||||
public function renderTree($tree);
|
||||
|
||||
/**
|
||||
* Sets the path for determining the active trail of the specified menu tree.
|
||||
*
|
||||
* This path will also affect the breadcrumbs under some circumstances.
|
||||
* Breadcrumbs are built using the preferred link returned by
|
||||
* menu_link_get_preferred(). If the preferred link is inside one of the menus
|
||||
* specified in calls to static::setPath(), the preferred link will be
|
||||
* overridden by the corresponding path returned by static::getPath().
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The name of the affected menu tree.
|
||||
* @param string $path
|
||||
* The path to use when finding the active trail.
|
||||
*/
|
||||
public function setPath($menu_name, $path = NULL);
|
||||
|
||||
/**
|
||||
* Gets the path for determining the active trail of the specified menu tree.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The menu name of the requested tree.
|
||||
*
|
||||
* @return string
|
||||
* A string containing the path. If no path has been specified with
|
||||
* static::setPath(), NULL is returned.
|
||||
*/
|
||||
public function getPath($menu_name);
|
||||
|
||||
/**
|
||||
* Gets the active trail IDs of the specified menu tree.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The menu name of the requested tree.
|
||||
*
|
||||
* @return array
|
||||
* An array containing the active trail: a list of mlids.
|
||||
*/
|
||||
public function getActiveTrailIds($menu_name);
|
||||
|
||||
/**
|
||||
* Sorts and returns the built data representing a menu tree.
|
||||
*
|
||||
* @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
|
||||
* An array of menu links in the form of a tree. Each item in the tree is an
|
||||
* associative array containing:
|
||||
* - link: The menu link item from $links, with additional element
|
||||
* 'in_active_trail' (TRUE if the link ID was in $parents).
|
||||
* - below: An array containing the sub-tree of this item, where each
|
||||
* element is a tree item array with 'link' and 'below' elements. This
|
||||
* array will be empty if the menu item has no items in its sub-tree
|
||||
* having a depth greater than or equal to $depth.
|
||||
*/
|
||||
public function buildTreeData(array $links, array $parents = array(), $depth = 1);
|
||||
|
||||
/**
|
||||
* Gets the data structure for a named menu tree, based on the current page.
|
||||
*
|
||||
* The tree order is maintained by storing each parent in an individual
|
||||
* field, see http://drupal.org/node/141866 for more.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The named menu links to return.
|
||||
* @param int $max_depth
|
||||
* (optional) The maximum depth of links to retrieve.
|
||||
* @param bool $only_active_trail
|
||||
* (optional) Whether to only return the links in the active trail (TRUE)
|
||||
* instead of all links on every level of the menu link tree (FALSE).
|
||||
* Defaults to FALSE.
|
||||
*
|
||||
* @return array
|
||||
* An array of menu links, in the order they should be rendered. The array
|
||||
* is a list of associative arrays -- these have two keys, link and below.
|
||||
* link is a menu item, ready for theming as a link. Below represents the
|
||||
* submenu below the link if there is one, and it is a subtree that has the
|
||||
* same structure described for the top-level array.
|
||||
*/
|
||||
public function buildPageData($menu_name, $max_depth = NULL, $only_active_trail = FALSE);
|
||||
|
||||
/**
|
||||
* Gets the data structure representing a named menu tree.
|
||||
*
|
||||
* Since this can be the full tree including hidden items, the data returned
|
||||
* may be used for generating an an admin interface or a select.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The named menu links to return
|
||||
* @param array $link
|
||||
* A fully loaded menu link, or NULL. If a link is supplied, only the
|
||||
* path to root will be included in the returned tree - as if this link
|
||||
* represented the current page in a visible menu.
|
||||
* @param int $max_depth
|
||||
* Optional maximum depth of links to retrieve. Typically useful if only one
|
||||
* or two levels of a sub tree are needed in conjunction with a non-NULL
|
||||
* $link, in which case $max_depth should be greater than $link['depth'].
|
||||
*
|
||||
* @return array
|
||||
* An tree of menu links in an array, in the order they should be rendered.
|
||||
*/
|
||||
public function buildAllData($menu_name, $link = NULL, $max_depth = NULL);
|
||||
|
||||
/**
|
||||
* Renders a menu tree based on the current path.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The name of the menu.
|
||||
*
|
||||
* @return array
|
||||
* A structured array representing the specified menu on the current page,
|
||||
* to be rendered by drupal_render().
|
||||
*/
|
||||
public function renderMenu($menu_name);
|
||||
|
||||
/**
|
||||
* Builds a menu tree, translates links, and checks access.
|
||||
*
|
||||
* @param string $menu_name
|
||||
* The name of the menu.
|
||||
* @param array $parameters
|
||||
* (optional) An associative array of build parameters. Possible keys:
|
||||
* - expanded: An array of parent link ids to return only menu links that
|
||||
* are children of one of the plids in this list. If empty, the whole menu
|
||||
* tree is built, unless 'only_active_trail' is TRUE.
|
||||
* - active_trail: An array of mlids, representing the coordinates of the
|
||||
* currently active menu link.
|
||||
* - only_active_trail: Whether to only return links that are in the active
|
||||
* trail. This option is ignored, if 'expanded' is non-empty.
|
||||
* - min_depth: The minimum depth of menu links in the resulting tree.
|
||||
* Defaults to 1, which is the default to build a whole tree for a menu
|
||||
* (excluding menu container itself).
|
||||
* - max_depth: The maximum depth of menu links in the resulting tree.
|
||||
* - conditions: An associative array of custom database select query
|
||||
* condition key/value pairs; see _menu_build_tree() for the actual query.
|
||||
*
|
||||
* @return array
|
||||
* A fully built menu tree.
|
||||
*/
|
||||
public function buildTree($menu_name, array $parameters = array());
|
||||
|
||||
}
|
|
@ -1,76 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\Core\Menu\StaticMenuLinks.
|
||||
*/
|
||||
|
||||
namespace Drupal\menu_link;
|
||||
|
||||
use Drupal\Component\Discovery\YamlDiscovery;
|
||||
use Drupal\Core\Extension\ModuleHandlerInterface;
|
||||
|
||||
/**
|
||||
* Provides a service which finds and alters default menu links in yml files.
|
||||
*/
|
||||
class StaticMenuLinks {
|
||||
|
||||
/**
|
||||
* The module handler.
|
||||
*
|
||||
* @var \Drupal\Core\Extension\ModuleHandlerInterface
|
||||
*/
|
||||
protected $moduleHandler;
|
||||
|
||||
/**
|
||||
* Constructs a new StaticMenuLinks.
|
||||
*
|
||||
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
|
||||
* The module handler.
|
||||
*/
|
||||
public function __construct(ModuleHandlerInterface $module_handler) {
|
||||
$this->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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,539 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Contains \Drupal\menu_link\Tests\MenuTreeTest.
|
||||
*/
|
||||
|
||||
namespace Drupal\menu_link\Tests {
|
||||
|
||||
use Drupal\Core\Cache\Cache;
|
||||
use Drupal\Core\Entity\EntityStorageInterface;
|
||||
use Drupal\Core\Language\Language;
|
||||
use Drupal\menu_link\MenuTree;
|
||||
use Drupal\Tests\UnitTestCase;
|
||||
use Symfony\Cmf\Component\Routing\RouteObjectInterface;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\RequestStack;
|
||||
|
||||
if (!defined('MENU_MAX_DEPTH')) {
|
||||
define('MENU_MAX_DEPTH', 9);
|
||||
}
|
||||
|
||||
/**
|
||||
* @coversDefaultClass \Drupal\menu_link\MenuTree
|
||||
* @group menu_link
|
||||
*/
|
||||
class MenuTreeTest extends UnitTestCase {
|
||||
|
||||
/**
|
||||
* The tested menu tree.
|
||||
*
|
||||
* @var \Drupal\menu_link\MenuTree|\Drupal\menu_link\Tests\TestMenuTree
|
||||
*/
|
||||
protected $menuTree;
|
||||
|
||||
/**
|
||||
* The mocked database connection.
|
||||
*
|
||||
* @var \Drupal\Core\DatabaseConnection|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $connection;
|
||||
|
||||
/**
|
||||
* The mocked cache backend.
|
||||
*
|
||||
* @var \Drupal\Core\Cache\CacheBackendInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $cacheBackend;
|
||||
|
||||
/**
|
||||
* The mocked language manager.
|
||||
*
|
||||
* @var \Drupal\Core\Language\LanguageManagerInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $languageManager;
|
||||
|
||||
/**
|
||||
* The test request stack.
|
||||
*
|
||||
* @var \Symfony\Component\HttpFoundation\RequestStack.
|
||||
*/
|
||||
protected $requestStack;
|
||||
|
||||
/**
|
||||
* The mocked entity manager.
|
||||
*
|
||||
* @var \Drupal\Core\Entity\EntityManagerInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $entityManager;
|
||||
|
||||
/**
|
||||
* The mocked entity query factor.y
|
||||
*
|
||||
* @var \Drupal\Core\Entity\Query\QueryFactory|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $entityQueryFactory;
|
||||
|
||||
/**
|
||||
* The mocked state.
|
||||
*
|
||||
* @var \Drupal\Core\State\StateInterface|\PHPUnit_Framework_MockObject_MockObject
|
||||
*/
|
||||
protected $state;
|
||||
|
||||
/**
|
||||
* Stores some default values for a menu link.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultMenuLink = array(
|
||||
'menu_name' => '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);
|
||||
}
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* @file
|
||||
* Definition of Drupal\views\Plugin\views\sort\MenuHierarchy.
|
||||
*/
|
||||
|
||||
namespace Drupal\views\Plugin\views\sort;
|
||||
|
||||
use Drupal\views\Views;
|
||||
|
||||
/**
|
||||
* Sort in menu hierarchy order.
|
||||
*
|
||||
* Given a field name of 'p' this produces an ORDER BY on p1, p2, ..., p9;
|
||||
* and optionally injects multiple joins to {menu_links} to sort by weight
|
||||
* and title as well.
|
||||
*
|
||||
* This is only really useful for the {menu_links} table.
|
||||
*
|
||||
* @ViewsSort("menu_hierarchy")
|
||||
*/
|
||||
class MenuHierarchy extends SortPluginBase {
|
||||
|
||||
protected function defineOptions() {
|
||||
$options = parent::defineOptions();
|
||||
$options['sort_within_level'] = array('default' => FALSE);
|
||||
return $options;
|
||||
}
|
||||
|
||||
public function buildOptionsForm(&$form, &$form_state) {
|
||||
parent::buildOptionsForm($form, $form_state);
|
||||
$form['sort_within_level'] = array(
|
||||
'#type' => 'checkbox',
|
||||
'#title' => t('Sort within each hierarchy level'),
|
||||
'#description' => t('Enable this to sort the items within each level of the hierarchy by weight and title. Warning: this may produce a slow query.'),
|
||||
'#default_value' => $this->options['sort_within_level'],
|
||||
);
|
||||
}
|
||||
|
||||
public function query() {
|
||||
$this->ensureMyTable();
|
||||
$max_depth = isset($this->definition['max depth']) ? $this->definition['max depth'] : MENU_MAX_DEPTH;
|
||||
for ($i = 1; $i <= $max_depth; ++$i) {
|
||||
if ($this->options['sort_within_level']) {
|
||||
$definition = array(
|
||||
'table' => 'menu_links',
|
||||
'field' => 'mlid',
|
||||
'left_table' => $this->tableAlias,
|
||||
'left_field' => $this->field . $i
|
||||
);
|
||||
$join = Views::pluginManager('join')->createInstance('standard', $definition);
|
||||
|
||||
$menu_links = $this->query->addTable('menu_links', NULL, $join);
|
||||
$this->query->addOrderBy($menu_links, 'weight', $this->options['order']);
|
||||
$this->query->addOrderBy($menu_links, 'link_title', $this->options['order']);
|
||||
}
|
||||
|
||||
// We need this even when also sorting by weight and title, to make sure
|
||||
// that children of two parents with the same weight and title are
|
||||
// correctly separated.
|
||||
$this->query->addOrderBy($this->tableAlias, $this->field . $i, $this->options['order']);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue