diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module index 76da041..72f1d0b 100644 --- a/core/modules/menu/menu.module +++ b/core/modules/menu/menu.module @@ -440,7 +440,9 @@ function menu_node_prepare_form(NodeInterface $node, $operation, array &$form_st * Find the depth limit for items in the parent select. */ function _menu_parent_depth_limit($item) { - return MENU_MAX_DEPTH - 1 - (($item['mlid'] && $item['has_children']) ? entity_get_controller('menu_link')->findChildrenRelativeDepth($item) : 0); + /** @var \Drupal\menu_link\MenuTreeStorageInterface $menu_tree_storage */ + $menu_tree_storage = \Drupal::service('menu_link.tree_storage'); + return MENU_MAX_DEPTH - 1 - (($item['mlid'] && $item['has_children']) ? $menu_tree_storage->findChildrenRelativeDepth($item) : 0); } /** diff --git a/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php b/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php index ebb28bf..e851067 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/Entity/MenuLink.php @@ -427,7 +427,9 @@ public static function preDelete(EntityStorageInterface $storage, array $entitie parent::preDelete($storage, $entities); // Nothing to do if we don't want to reparent children. - if ($storage->getPreventReparenting()) { + /** @var \Drupal\menu_link\MenuTreeStorageInterface $menu_tree_storage */ + $menu_tree_storage = \Drupal::service('menu_link.tree_storage'); + if ($menu_tree_storage->getPreventReparenting()) { return; } @@ -451,9 +453,11 @@ public static function postDelete(EntityStorageInterface $storage, array $entiti $affected_menus = array(); // Update the has_children status of the parent. + /** @var \Drupal\menu_link\MenuTreeStorageInterface $menu_tree_storage */ + $menu_tree_storage = \Drupal::service('menu_link.tree_storage'); foreach ($entities as $entity) { - if (!$storage->getPreventReparenting()) { - $storage->updateParentalStatus($entity); + if (!$menu_tree_storage->getPreventReparenting()) { + $menu_tree_storage->updateParentalStatus($entity); } // Store all menu names for which we need to clear the cache. @@ -478,46 +482,9 @@ public function preSave(EntityStorageInterface $storage) { // since a path marked as external does not need to match a route. $this->external = (url_is_external($this->link_path) || $this->link_path == '') ? 1 : 0; - // Try to find a parent link. If found, assign it and derive its menu. - $parent = $this->findParent($storage); - if ($parent) { - $this->plid = $parent->id(); - $this->menu_name = $parent->menu_name; - } - // If no corresponding parent link was found, move the link to the top-level. - else { - $this->plid = 0; - } - - // Directly fill parents for top-level links. - if ($this->plid == 0) { - $this->p1 = $this->id(); - for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) { - $parent_property = "p$i"; - $this->{$parent_property} = 0; - } - $this->depth = 1; - } - // Otherwise, ensure that this link's depth is not beyond the maximum depth - // and fill parents based on the parent link. - else { - if ($this->has_children && $this->original) { - $limit = MENU_MAX_DEPTH - $storage->findChildrenRelativeDepth($this->original) - 1; - } - else { - $limit = MENU_MAX_DEPTH - 1; - } - if ($parent->depth > $limit) { - return FALSE; - } - $this->depth = $parent->depth + 1; - $this->setParents($parent); - } - - // Need to check both plid and menu_name, since plid can be 0 in any menu. - if (isset($this->original) && ($this->plid != $this->original->plid || $this->menu_name != $this->original->menu_name)) { - $storage->moveChildren($this); - } + /** @var \Drupal\menu_link\MenuTreeStorageInterface $menu_tree_storage */ + $menu_tree_storage = \Drupal::service('menu_link.tree_storage'); + $menu_tree_storage->preSaveMenuLink($this); // Find the route_name. if (!isset($this->route_name)) { @@ -537,7 +504,9 @@ public function postSave(EntityStorageInterface $storage, $update = TRUE) { parent::postSave($storage, $update); // Check the has_children status of the parent. - $storage->updateParentalStatus($this); + /** @var \Drupal\menu_link\MenuTreeStorageInterface $menu_tree_storage */ + $menu_tree_storage = \Drupal::service('menu_link.tree_storage'); + $menu_tree_storage->updateParentalStatus($this); Cache::invalidateTags(array('menu' => $this->menu_name)); if (isset($this->original) && $this->menu_name != $this->original->menu_name) { @@ -584,58 +553,6 @@ public static function postLoad(EntityStorageInterface $storage, array &$entitie } /** - * {@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 diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorage.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorage.php index ea05968..831959f 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorage.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorage.php @@ -19,12 +19,6 @@ */ class MenuLinkStorage extends EntityDatabaseStorage implements MenuLinkStorageInterface { - /** - * Indicates whether the delete operation should re-parent children items. - * - * @var bool - */ - protected $preventReparenting = FALSE; /** * {@inheritdoc} @@ -108,19 +102,6 @@ public function save(EntityInterface $entity) { } } - /** - * {@inheritdoc} - */ - public function setPreventReparenting($value = FALSE) { - $this->preventReparenting = $value; - } - - /** - * {@inheritdoc} - */ - public function getPreventReparenting() { - return $this->preventReparenting; - } /** * {@inheritdoc} @@ -197,75 +178,6 @@ public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE) } } - /** - * {@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} diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php index 3d3fd5d..c7c7097 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php @@ -16,24 +16,6 @@ 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 @@ -42,39 +24,6 @@ public function getPreventReparenting(); 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 diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php index f12cae8..23f3098 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuTree.php @@ -23,14 +23,6 @@ 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 @@ -59,13 +51,6 @@ class MenuTree implements MenuTreeInterface { protected $menuLinkStorage; /** - * The entity query factory. - * - * @var \Drupal\Core\Entity\Query\QueryFactory - */ - protected $queryFactory; - - /** * The state. * * @var \Drupal\Core\KeyValueStore\StateInterface @@ -117,10 +102,15 @@ class MenuTree implements MenuTreeInterface { protected $menuPageTrees; /** + * The menu tree storage. + * + * @var \Drupal\menu_link\MenuTreeStorageInterface + */ + protected $menuTreeStorage; + + /** * 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 @@ -129,19 +119,18 @@ class MenuTree implements MenuTreeInterface { * 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\KeyValueStore\StateInterface $state * The state. + * @param \Drupal\menu_link\MenuTreeStorageInterface $menu_tree_storage + * The menu tree storage. */ - 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; + public function __construct(CacheBackendInterface $cache_backend, LanguageManagerInterface $language_manager, RequestStack $request_stack, EntityManagerInterface $entity_manager, StateInterface $state, MenuTreeStorageInterface $menu_tree_storage) { $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; + $this->menuTreeStorage = $menu_tree_storage; } /** @@ -285,13 +274,7 @@ public function buildPageData($menu_name, $max_depth = NULL, $only_active_trail // 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(); + $result = $this->menuTreeStorage->getExpandedParents($parents, $menu_name); $parents += $result; } while (!empty($result)); } @@ -452,37 +435,13 @@ protected function doBuildTree($menu_name, array $parameters = array()) { } 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()) { + if ($result = $this->menuTreeStorage->findNodes($parameters, $menu_name)) { $links = $this->menuLinkStorage->loadMultiple($result); } $active_trail = (isset($parameters['active_trail']) ? $parameters['active_trail'] : array()); + $min_depth = (isset($parameters['min_depth']) ? $parameters['min_depth'] : 1); $tree = $this->doBuildTreeData($links, $active_trail, $min_depth); // Cache the data, if it is not already in the cache. diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeStorage.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeStorage.php new file mode 100644 index 0000000..ab70524 --- /dev/null +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeStorage.php @@ -0,0 +1,282 @@ +database = $database; + } + + /** + * {@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->database->select('menu_tree'); + $query + ->fields('menu_tree', array('mlid')) + ->condition('menu_name', $entity->menu_name) +// ->condition('hidden', 0) + ->condition('plid', $entity->plid) + ->countQuery(); + + if ($exclude) { + $query->condition('mlid', $entity->id(), '<>'); + } + + $parent_has_children = ((bool) $query->execute()) ? 1 : 0; + $this->database->update('menu_tree') + ->fields(array('has_children' => $parent_has_children)) + ->condition('mlid', $entity->plid) + ->execute(); + } + } + + /** + * {@inheritdoc} + */ + protected function setParents(MenuLinkInterface $menu_link, MenuLinkInterface $parent) { + $i = 1; + while ($i < $menu_link->depth) { + $p = 'p' . $i++; + $menu_link->{$p} = $parent->{$p}; + } + $p = 'p' . $i++; + // The parent (p1 - p9) corresponding to the depth always equals the mlid. + $menu_link->{$p} = $menu_link->id(); + while ($i <= MENU_MAX_DEPTH) { + $p = 'p' . $i++; + $menu_link->{$p} = 0; + } + } + + + /** + * {@inheritdoc} + */ + protected function findParent(MenuLinkInterface $menu_link, EntityStorageInterface $storage) { + $parent = FALSE; + + // This item is explicitly top-level, skip the rest of the parenting. + if (isset($menu_link->plid) && empty($menu_link->plid)) { + return $parent; + } + + // If we have a parent link ID, try to use that. + $candidates = array(); + if (isset($menu_link->plid)) { + $candidates[] = $menu_link->plid; + } + + // Else, if we have a link hierarchy try to find a valid parent in there. + if (!empty($menu_link->depth) && $menu_link->depth > 1) { + for ($depth = $menu_link->depth - 1; $depth >= 1; $depth--) { + $parent_property = "p$depth"; + $candidates[] = $menu_link->$parent_property; + } + } + + foreach ($candidates as $mlid) { + $parent = $storage->load($mlid); + if ($parent) { + break; + } + } + return $parent; + } + + /** + * {@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('menu_tree'); + + $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 preSaveMenuLink(MenuLinkInterface $menu_link) { + $storage = \Drupal::entityManager()->getStorage('menu_link'); + // Try to find a parent link. If found, assign it and derive its menu. + $parent = $this->findParent($menu_link, $storage); + if ($parent) { + $menu_link->plid = $parent->id(); + $menu_link->menu_name = $parent->menu_name; + } + // If no corresponding parent link was found, move the link to the top-level. + else { + $menu_link->plid = 0; + } + + // Directly fill parents for top-level links. + if ($menu_link->plid == 0) { + $menu_link->p1 = $menu_link->id(); + for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) { + $parent_property = "p$i"; + $menu_link->{$parent_property} = 0; + } + $menu_link->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 ($menu_link->has_children && $menu_link->original) { + $limit = MENU_MAX_DEPTH - $this->findChildrenRelativeDepth($menu_link->original) - 1; + } + else { + $limit = MENU_MAX_DEPTH - 1; + } + if ($parent->depth > $limit) { + return FALSE; + } + $menu_link->depth = $parent->depth + 1; + $this->setParents($menu_link, $parent); + } + + // Need to check both plid and menu_name, since plid can be 0 in any menu. + if (isset($menu_link->original) && ($menu_link->plid != $menu_link->original->plid || $menu_link->menu_name != $menu_link->original->menu_name)) { + $this->moveChildren($menu_link); + } + } + + /** + * {@inheritdoc} + */ + public function setPreventReparenting($value = FALSE) { + $this->preventReparenting = $value; + } + + /** + * {@inheritdoc} + */ + public function getPreventReparenting() { + return $this->preventReparenting; + } + + public function getExpandedParents(array $parents, $menu_name) { + $result = $this->database->select('menu_tree') + ->fields('menu_tree', array('mlid')) + ->condition('menu_name', $menu_name) + ->condition('expanded', 1) + ->condition('has_children', 1) + ->condition('plid', $parents, 'IN') + ->condition('mlid', $parents, 'NOT IN') + ->execute() + ->fetchAllKeyed(); + + return $result; + } + + public function findNodes($parameters, $menu_name) { + $query = $this->database->select('menu_link'); + $query->addField('menu_link', 'mlid'); + for ($i = 1; $i <= MENU_MAX_DEPTH; $i++) { + $query->orderBy('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); + } + } + return $query->execute()->fetchAllKeyed(); + } + +} + diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeStorageInterface.php similarity index 55% copy from core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php copy to core/modules/menu_link/lib/Drupal/menu_link/MenuTreeStorageInterface.php index 3d3fd5d..10af93a 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageInterface.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuTreeStorageInterface.php @@ -2,50 +2,22 @@ /** * @file - * Contains \Drupal\menu_link\MenuLinkStorageInterface. -*/ + * Contains \Drupal\menu_link\MenuTreeStorageInterface. + */ 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(); +interface MenuTreeStorageInterface { /** * Checks and updates the 'has_children' property for the parent of a link. * * @param \Drupal\Core\Entity\EntityInterface $entity * A menu link entity. + * @param bool $exclude + * @return */ public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE); @@ -75,36 +47,33 @@ public function findChildrenRelativeDepth(EntityInterface $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. + * React an saving a menu link. + * @param MenuLinkInterface $menu_link + * @return */ - public function countMenuLinks($menu_name); + public function preSaveMenuLink(MenuLinkInterface $menu_link); /** - * Tries to derive menu link's parent from the path hierarchy. - * - * @param \Drupal\Core\Entity\EntityInterface $entity - * A menu link entity. + * Sets an internal flag that allows us to prevent the reparenting operations + * executed during deletion. * - * @return \Drupal\Core\Entity\EntityInterface|false - * A menu link entity or FALSE if not valid parent was found. + * @param bool $value + * TRUE if reparenting should be allowed, FALSE if it should be prevented. */ - public function getParentFromHierarchy(EntityInterface $entity); + public function setPreventReparenting($value = FALSE); /** - * 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. + * Gets value of internal flag that allows/prevents reparenting operations + * executed during deletion. * - * @return \Drupal\menu_link\MenuLinkInterface - * A menu link entity. + * @return bool + * TRUE if reparenting is allowed, FALSE if it is prevented. */ - public function createFromDefaultLink(array $item); + public function getPreventReparenting(); + + public function getExpandedParents(array $parents, $menu_name); + + public function findNodes($parameters, $menu_name); } + diff --git a/core/modules/menu_link/menu_link.install b/core/modules/menu_link/menu_link.install index c789d34..b62bc67 100644 --- a/core/modules/menu_link/menu_link.install +++ b/core/modules/menu_link/menu_link.install @@ -217,5 +217,118 @@ function menu_link_schema() { 'primary key' => array('mlid'), ); + $schema['menu_tree'] = array( + 'description' => 'Contains the menu tree hierarchy.', + '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, + ), + '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, + ), + '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', + ), + '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', + ), + '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, + ), + ), + 'indexes' => array( + 'menu_parents' => array('menu_name', 'p1', 'p2', 'p3', 'p4', 'p5', 'p6', 'p7', 'p8', 'p9'), + ), + 'primary key' => array('mlid'), + ); + return $schema; } diff --git a/core/modules/menu_link/menu_link.module b/core/modules/menu_link/menu_link.module index ea3bb1f..7cd6156 100644 --- a/core/modules/menu_link/menu_link.module +++ b/core/modules/menu_link/menu_link.module @@ -114,7 +114,10 @@ function menu_link_delete_multiple(array $mlids, $force = FALSE, $prevent_repare else { $entities = $controller->loadMultiple($mlids); } - $controller->setPreventReparenting($prevent_reparenting); + /** @var \Drupal\menu_link\MenuTreeStorageInterface $menu_tree_storage */ + $menu_tree_storage = \Drupal::service('menu_link.tree_storage'); + $menu_tree_storage->setPreventReparenting($prevent_reparenting); + $controller->delete($entities); } diff --git a/core/modules/menu_link/menu_link.services.yml b/core/modules/menu_link/menu_link.services.yml index 88f5037..00009fe 100644 --- a/core/modules/menu_link/menu_link.services.yml +++ b/core/modules/menu_link/menu_link.services.yml @@ -1,7 +1,10 @@ services: menu_link.tree: class: Drupal\menu_link\MenuTree - arguments: ['@database', '@cache.data', '@language_manager', '@request_stack', '@entity.manager', '@entity.query', '@state'] + arguments: ['@cache.data', '@language_manager', '@request_stack', '@entity.manager', '@state', '@menu_link.tree_storage'] menu_link.static: class: Drupal\menu_link\StaticMenuLinks arguments: ['@module_handler'] + menu_link.tree_storage: + class: Drupal\menu_link\MenuTreeStorage + arguments: ['@database'] diff --git a/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php b/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php index 3b44b08..f96d00a 100644 --- a/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php +++ b/core/modules/menu_link/tests/Drupal/menu_link/Tests/MenuTreeTest.php @@ -97,6 +97,13 @@ class MenuTreeTest extends UnitTestCase { ); /** + * The mocked menu tree storage. + * + * @var \Drupal\menu_link\MenuTreeStorageInterface|\PHPUnit_Framework_MockObject_MockObject + */ + protected $menuTreeStorage; + + /** * {@inheritdoc} */ public static function getInfo() { @@ -111,19 +118,14 @@ public static function getInfo() { * {@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\KeyValueStore\StateInterface'); + $this->menuTreeStorage = $this->getMock('Drupal\menu_link\MenuTreeStorageInterface'); - $this->menuTree = new TestMenuTree($this->connection, $this->cacheBackend, $this->languageManager, $this->requestStack, $this->entityManager, $this->entityQueryFactory, $this->state); + $this->menuTree = new TestMenuTree($this->cacheBackend, $this->languageManager, $this->requestStack, $this->entityManager, $this->state, $this->menuTreeStorage); } /**