diff --git a/core/includes/menu.inc b/core/includes/menu.inc index 227dd3e..49e5ffd 100644 --- a/core/includes/menu.inc +++ b/core/includes/menu.inc @@ -635,14 +635,13 @@ function _menu_check_access(&$item, $map) { */ function _menu_item_localize(&$item, $map, $link_translate = FALSE) { $title_callback = $item['title_callback']; - $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']); + $item->options->attributes['class'] = explode(' ', $item['options']['attributes']['class']); } // If we are translating the title of a menu link, and its title is the same // as the corresponding router item, then we can use the title information @@ -670,7 +669,7 @@ function _menu_item_localize(&$item, $map, $link_translate = FALSE) { } // Avoid calling check_plain again on l() function. if ($title_callback == 'check_plain') { - $item['localized_options']['html'] = TRUE; + $item->options->html = TRUE; } } } @@ -704,11 +703,6 @@ function _menu_item_localize(&$item, $map, $link_translate = FALSE) { } } } - // If the title and description are the same, use the translated description - // as a localized title. - if ($link_translate && isset($original_description) && isset($item['options']['attributes']['title']) && $item['options']['attributes']['title'] == $original_description) { - $item['localized_options']['attributes']['title'] = $item['description']; - } } /** @@ -867,19 +861,14 @@ function menu_tail_load($arg, &$map, $index) { * $item['access'] becomes TRUE if the item is accessible, FALSE otherwise. * $item['href'] is generated from link_path, possibly by to_arg functions. * $item['title'] is generated from link_title, and may be localized. - * $item['options'] is unserialized; it is also changed within the call here * to $item['localized_options'] by _menu_item_localize(). */ function _menu_link_translate(&$item, $translate = FALSE) { - if (!is_array($item['options'])) { - $item['options'] = unserialize($item['options']); - } if ($item['external']) { $item['access'] = 1; $map = array(); $item['href'] = $item['link_path']; $item['title'] = $item['link_title']; - $item['localized_options'] = $item['options']; } else { // Complete the path of the menu link with elements from the current path, @@ -1105,24 +1094,26 @@ function menu_tree_output($tree) { $class[] = 'leaf'; } // Set a class if the link is in the active trail. + $localized_options = $data['link']['options']; if ($data['link']['in_active_trail']) { $class[] = 'active-trail'; - $data['link']['localized_options']['attributes']['class'][] = 'active-trail'; + $localized_options['attributes']['class'][] = 'active-trail'; } // Normally, l() compares the href of every link with the current path and // sets the active class accordingly. But local tasks do not appear in menu // trees, so if the current path is a local task, and this link is its // tab root, then we have to set the class manually. if ($data['link']['href'] == $router_item['tab_root_href'] && $data['link']['href'] != current_path()) { - $data['link']['localized_options']['attributes']['class'][] = 'active'; + $localized_options['attributes']['class'][] = 'active'; } + $data['link']['options'] = $localized_options; // Allow menu-specific theme overrides. $element['#theme'] = 'menu_link__' . strtr($data['link']['menu_name'], '-', '_'); $element['#attributes']['class'] = $class; $element['#title'] = $data['link']['title']; $element['#href'] = $data['link']['href']; - $element['#localized_options'] = !empty($data['link']['localized_options']) ? $data['link']['localized_options'] : array(); + $element['#options'] = !empty($data['link']['options']) ? $data['link']['options'] : array(); $element['#below'] = $data['below'] ? menu_tree_output($data['below']) : $data['below']; $element['#original_link'] = $data['link']; // Index using the link's unique mlid. @@ -1682,7 +1673,7 @@ function theme_menu_link(array $variables) { if ($element['#below']) { $sub_menu = drupal_render($element['#below']); } - $output = l($element['#title'], $element['#href'], $element['#localized_options']); + $output = l($element['#title'], $element['#href'], $element['#options']); return '' . $output . $sub_menu . "\n"; } @@ -1701,7 +1692,7 @@ function theme_menu_link(array $variables) { function theme_menu_local_task($variables) { $link = $variables['element']['#link']; $link += array( - 'localized_options' => array(), + 'options' => array(), ); $link_text = $link['title']; @@ -1711,14 +1702,14 @@ function theme_menu_local_task($variables) { // If the link does not contain HTML already, check_plain() it now. // After we set 'html'=TRUE the link will not be sanitized by l(). - if (empty($link['localized_options']['html'])) { + if (empty($link['options']['html'])) { $link['title'] = check_plain($link['title']); } - $link['localized_options']['html'] = TRUE; + $link['options']['html'] = TRUE; $link_text = t('!local-task-title!active', array('!local-task-title' => $link['title'], '!active' => $active)); } - return '' . l($link_text, $link['href'], $link['localized_options']) . ''; + return '' . l($link_text, $link['href'], $link['options']) . ''; } /** @@ -1736,13 +1727,13 @@ function theme_menu_local_action($variables) { $link = $variables['element']['#link']; $link += array( 'href' => '', - 'localized_options' => array(), + 'options' => array(), ); - $link['localized_options']['attributes']['class'][] = 'button'; - $link['localized_options']['attributes']['class'][] = 'button-action'; + $link['options']['attributes']['class'][] = 'button'; + $link['options']['attributes']['class'][] = 'button-action'; $output = '
  • '; - $output .= l($link['title'], $link['href'], $link['localized_options']); + $output .= l($link['title'], $link['href'], $link['options']); $output .= "
  • "; return $output; @@ -1906,7 +1897,7 @@ function menu_navigation_links($menu_name, $level = 0) { foreach ($tree as $item) { if (!$item['link']['hidden']) { $class = ''; - $l = $item['link']['localized_options']; + $l = $item['link']['options']; $l['href'] = $item['link']['href']; $l['title'] = $item['link']['title']; if ($item['link']['in_active_trail']) { @@ -2035,7 +2026,7 @@ function menu_local_tasks($level = 0) { // would not be marked as active, since l() only compares the href // with current_path(). if ($link['href'] != current_path()) { - $link['localized_options']['attributes']['class'][] = 'active'; + $link['options']['attributes']['class'][] = 'active'; } $tabs_current[$link['href']] = array( '#theme' => 'menu_local_task', @@ -2112,7 +2103,7 @@ function menu_local_tasks($level = 0) { // links to its parent, l() will not mark it as active, as it only // compares the link's href to current_path(). if ($link['href'] != current_path()) { - $link['localized_options']['attributes']['class'][] = 'active'; + $link['options']['attributes']['class'][] = 'active'; } $tabs_current[$link['href']] = array( '#theme' => 'menu_local_task', @@ -2458,7 +2449,7 @@ function menu_set_active_trail($new_trail = NULL) { 'title' => t('Home'), 'href' => '', 'link_path' => '', - 'localized_options' => array(), + 'options' => array(), 'type' => 0, ); @@ -2674,7 +2665,8 @@ function menu_get_active_breadcrumb() { } foreach ($active_trail as $parent) { - $breadcrumb[] = l($parent['title'], $parent['href'], $parent['localized_options']); + $l_options = $parent['options']; + $breadcrumb[] = l($parent['title'], $parent['href'], (is_array($l_options) && !empty($l_options)) ? $l_options : array('')); } } return $breadcrumb; @@ -2823,10 +2815,19 @@ function menu_router_build($save = FALSE) { * The translated path pattern from the route. */ function _menu_router_translate_route($route_name) { - $outline = Drupal::service('router.route_provider') - ->getRouteByName($route_name) - ->compile() - ->getPatternOutline(); + $route = Drupal::service('router.route_provider') + ->getRouteByName($route_name); + + $path = $route->getPattern(); + $defaults = $route->getDefaults(); + + // Remove placeholders with default values from the outline, so that they + // will still match. + $remove = array_map(function($a) { + return '/{' . $a . '}'; + }, array_keys($defaults)); + $stripped_path = str_replace($remove, '', $path); + $outline = preg_replace('#\{(\w+)\}#', '%$1', $stripped_path); return trim($outline, '/'); } @@ -2885,6 +2886,8 @@ function _menu_navigation_links_rebuild($menu) { array_multisort($sort, SORT_NUMERIC, $router_items); foreach ($router_items as $key => $router_item) { + // Add the path to the item. + $router_item['path'] = $key; // For performance reasons, do a straight query now and convert to a menu // link entity later. // @todo revisit before release. @@ -2892,9 +2895,8 @@ function _menu_navigation_links_rebuild($menu) { ->fields('menu_links') ->condition('link_path', $router_item['path']) ->condition('module', 'system') - ->execute()->fetchAll(); + ->execute()->fetch(); if ($existing_item) { - $existing_item = reset($existing_item); $existing_item->options = unserialize($existing_item->options); $router_item['mlid'] = $existing_item->mlid; @@ -2920,8 +2922,8 @@ function _menu_navigation_links_rebuild($menu) { $existing_item = NULL; } - if ($existing_item && $existing_item->customized) { - $parent_candidates[$existing_item->mlid] = $existing_item; + if ($existing_item && $existing_item->customized->value) { + $parent_candidates[$existing_item->id()] = $existing_item; } else { $menu_link = MenuLink::buildFromRouterItem($router_item); @@ -2938,14 +2940,14 @@ function _menu_navigation_links_rebuild($menu) { // Updated and customized items whose router paths are gone need new ones. $menu_links = $menu_link_controller->loadUpdatedCustomized($paths); foreach ($menu_links as $menu_link) { - $router_path = _menu_find_router_path($menu_link->link_path); - if (!empty($router_path) && ($router_path != $menu_link->router_path || $menu_link->updated)) { + $router_path = _menu_find_router_path($menu_link->link_path->value); + if (!empty($router_path) && ($router_path != $menu_link->router_path->value || $menu_link->updated->value)) { // If the router path and the link path matches, it's surely a working // item, so we clear the updated flag. - $updated = $menu_link->updated && $router_path != $menu_link->link_path; + $updated = $menu_link->updated->value && $router_path != $menu_link->link_path->value; - $menu_link->router_path = $router_path; - $menu_link->updated = (int) $updated; + $menu_link->router_path->value = $router_path; + $menu_link->updated->value = (int) $updated; $menu_link_controller->save($menu_link); } } diff --git a/core/includes/path.inc b/core/includes/path.inc index ba1da88..91a811d 100644 --- a/core/includes/path.inc +++ b/core/includes/path.inc @@ -204,7 +204,7 @@ function drupal_valid_path($path, $dynamic_allowed = FALSE) { $item['link_path'] = $form_item['link_path']; $item['link_title'] = $form_item['link_title']; $item['external'] = FALSE; - $item['options'] = ''; + $item['options'] = array(); _menu_link_translate($item); } } diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 5e12ff5..c75f848 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -1726,9 +1726,9 @@ function theme_links($variables) { // Handle title-only text items. else { // Merge in default array properties into $link. - $link += array( - 'html' => FALSE, - ); + if (!isset($link['html'])) { + $link['html'] = FALSE; + } $item = ($link['html'] ? $link['title'] : check_plain($link['title'])); if (isset($link['attributes'])) { $item = '' . $item . ''; diff --git a/core/lib/Drupal/Core/Entity/Plugin/DataType/MapItem.php b/core/lib/Drupal/Core/Entity/Plugin/DataType/MapItem.php new file mode 100644 index 0000000..dd835bd --- /dev/null +++ b/core/lib/Drupal/Core/Entity/Plugin/DataType/MapItem.php @@ -0,0 +1,88 @@ +properties as $name => $property) { + $definitions[$name] = $property->getDefinition(); + } + return $definitions; + } + + /** + * Overrides \Drupal\Core\TypedData\TypedData::setValue(). + * + * @param array|null $values + * An array of property values. + */ + public function setValue($values, $notify = TRUE) { + if (!isset($values)) { + $this->properties = array(); + return; + } + if (!is_array($values)) { + if ($values instanceof MapItem) { + $values = $values->getValue(); + } + else { + $values = unserialize($values); + } + } + $this->properties = array(); + foreach($values as $name => $value) { + $this->{$name} = $value; + } + } + + public function __get($name) { + if (!isset($this->properties[$name])) { + $this->{$name} = array(); + } + return $this->properties[$name]->getValue(); + } + + public function __set($name, $value) { + if (isset($value)) { + if (isset($this->properties[$name])) { + $this->set($name, $value); + } + else { + $def = array('type' => 'any'); + if ($value instanceof TypedData) { + $def = $value->getDefinition(); + $value = $value->getValue(); + } + $this->properties[$name] = \Drupal::typedData()->create($def, $value, $name); + } + } + else { + unset($this->properties[$name]); + } + } +} diff --git a/core/modules/book/book.module b/core/modules/book/book.module index 8ed85c4..9a5b6b7 100644 --- a/core/modules/book/book.module +++ b/core/modules/book/book.module @@ -58,7 +58,7 @@ function book_entity_bundle_info() { function book_menu_link_load($entities) { foreach ($entities as $entity) { // Change the bundle of menu links related to a book. - if (strpos($entity->menu_name, 'book-toc-') === 0) { + if (strpos($entity->menu_name->value, 'book-toc-') === 0) { $entity->bundle = 'book-toc'; } } diff --git a/core/modules/contextual/contextual.module b/core/modules/contextual/contextual.module index 70ed7ff..135d287 100644 --- a/core/modules/contextual/contextual.module +++ b/core/modules/contextual/contextual.module @@ -269,9 +269,9 @@ function contextual_pre_render_links($element) { 'href' => $item['href'], ); // @todo theme_links() should *really* use the same parameters as l(). - $item['localized_options'] += array('query' => array()); - $item['localized_options']['query'] += drupal_get_destination(); - $links[$class] += $item['localized_options']; + $item['options'] += array('query' => array()); + $item['options']['query'] += drupal_get_destination(); + $links[$class] += $item['options']; } $element['#links'] = $links; diff --git a/core/modules/menu/lib/Drupal/menu/MenuFormController.php b/core/modules/menu/lib/Drupal/menu/MenuFormController.php index 68bc6a4..b8e47f7 100644 --- a/core/modules/menu/lib/Drupal/menu/MenuFormController.php +++ b/core/modules/menu/lib/Drupal/menu/MenuFormController.php @@ -297,7 +297,7 @@ protected function buildOverviewTreeForm($tree, $delta) { $mlid = 'mlid:' . $item['mlid']; $form[$mlid]['#item'] = $item; $form[$mlid]['#attributes'] = $item['hidden'] ? array('class' => array('menu-disabled')) : array('class' => array('menu-enabled')); - $form[$mlid]['title']['#markup'] = l($item['title'], $item['href'], $item['localized_options']); + $form[$mlid]['title']['#markup'] = l($item['title'], $item['href'], $item['options']); if ($item['hidden']) { $form[$mlid]['title']['#markup'] .= ' (' . t('disabled') . ')'; } diff --git a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php index f7e0690..6601fb3 100644 --- a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php +++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php @@ -82,7 +82,7 @@ function testMenu() { $item = entity_load('menu_link', $item['mlid']); // Verify that a change to the description is saved. $description = $this->randomName(16); - $item['options']['attributes']['title'] = $description; + $item->options->attributes['title'] = $description; $return_value = menu_link_save($item); // Save the menu link again to test the return value of the procedural save // helper. @@ -448,7 +448,7 @@ public function testMenuBundles() { $unsaved_item = entity_create('menu_link', array('menu_name' => $menu->id(), 'link_title' => $this->randomName(16), 'link_path' => '')); $this->assertEqual($unsaved_item->bundle(), $menu->id(), 'Unsaved menu link bundle matches the menu'); - $this->assertEqual($unsaved_item->menu_name, $menu->id(), 'Unsaved menu link menu name matches the menu'); + $this->assertEqual($unsaved_item->menu_name->value, $menu->id(), 'Unsaved menu link menu name matches the menu'); } diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module index 038177a..317e2a8 100644 --- a/core/modules/menu/menu.module +++ b/core/modules/menu/menu.module @@ -188,7 +188,7 @@ function menu_enable() { $system_link = reset($system_link); $base_link = entity_create('menu_link', array( - 'menu_name' => $system_link->menu_name, + 'menu_name' => $system_link->menu_name->value, 'router_path' => 'admin/structure/menu/manage/%menu', 'module' => 'menu', )); @@ -198,11 +198,11 @@ function menu_enable() { $link = $base_link->createDuplicate(); $link->plid = $system_link->id(); $link->link_title = $menu->label(); - $link->link_path = 'admin/structure/menu/manage/' . $menu->id(); + $link->link_path = 'admin/structure/menu/manage/' . $menu->label(); $query = Drupal::entityQuery('menu_link') - ->condition('link_path', $link->link_path) - ->condition('plid', $link->plid); + ->condition('link_path', $link->link_path->value) + ->condition('plid', $link->plid->value); $result = $query->execute(); if (empty($result)) { @@ -601,7 +601,7 @@ function menu_form_node_form_alter(&$form, $form_state) { ); // Get number of items in menu so the weight selector is sized appropriately. - $delta = entity_get_controller('menu_link')->countMenuLinks($link->menu_name); + $delta = entity_get_controller('menu_link')->countMenuLinks($link->menu_name->value); if ($delta < 50) { // Old hardcoded value $delta = 50; 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 d6493f5..3607376 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 @@ -7,17 +7,16 @@ namespace Drupal\menu_link\Entity; -use Drupal\menu_link\MenuLinkInterface; -use Symfony\Component\Routing\Route; -use Symfony\Component\HttpFoundation\Request; - -use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Annotation\Translation; -use Drupal\Core\Entity\EntityStorageException; +use Drupal\Core\Entity\Annotation\EntityType; use Drupal\Core\Entity\EntityInterface; +use Drupal\Core\Entity\EntityNG; use Drupal\Core\Entity\EntityStorageControllerInterface; -use Drupal\Core\Entity\ContentEntityInterface; -use Drupal\Core\Entity\Entity; +use Drupal\Core\TypedData\TypedDataInterface; +use Drupal\menu_link\MenuLinkInterface; +use Drupal\menu_link\MenuLinkStorageControllerInterface; +use Symfony\Component\Routing\Route; +use Symfony\Component\HttpFoundation\Request; /** * Defines the menu link entity class. @@ -49,14 +48,7 @@ * } * ) */ -class MenuLink extends Entity implements \ArrayAccess, MenuLinkInterface { - - /** - * The link's menu name. - * - * @var string - */ - public $menu_name = 'tools'; +class MenuLink extends EntityNG implements \ArrayAccess, MenuLinkInterface { /** * The link's bundle. @@ -66,215 +58,40 @@ class MenuLink extends Entity implements \ArrayAccess, MenuLinkInterface { public $bundle = 'tools'; /** - * The menu link ID. - * - * @var int - */ - public $mlid; - - /** - * 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; - - /** - * For links corresponding to a Drupal path (external = 0), this connects the - * link to a {menu_router}.path for joins. - * - * @var string - */ - public $router_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'; - - /** - * 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. + * The route object associated with this menu link, if any. * - * @var int + * @var \Symfony\Component\Routing\Route */ - public $updated = 0; + protected $routeObject; /** - * The name of the route associated with this menu link, if any. + * Crap coming from the old routing system. * - * @var string - */ - public $route_name; - - /** - * The parameters of the route associated with this menu link, if any. + * @todo Remove when we rip out the old routing system. * * @var array */ - public $route_parameters; + protected $oldRouterItem = array(); /** * The route object associated with this menu link, if any. * * @var \Symfony\Component\Routing\Route */ - protected $routeObject; + protected $oldRoutingProperties = array( + 'path', 'load_functions', 'to_arg_functions', 'access_callback', + 'access_arguments', 'page_callback', 'page_arguments', 'fit', + 'number_parts', 'context', 'tab_parent', 'tab_root', 'title', + 'title_callback', 'title_arguments', 'theme_callback', 'theme_arguments', + 'type', 'description', 'description_callback', 'description_arguments', + 'position', 'include_file', 'route_name', + ); /** - * Overrides Entity::id(). + * {@inheritdoc} */ public function id() { - return $this->mlid; + return $this->get('mlid')->value; } /** @@ -289,7 +106,7 @@ public function bundle() { */ public function createDuplicate() { $duplicate = parent::createDuplicate(); - $duplicate->plid = NULL; + $duplicate->get('plid')->offsetGet(0)->set('value', NULL); return $duplicate; } @@ -297,12 +114,12 @@ public function createDuplicate() { * {@inheritdoc} */ public function getRoute() { - if (!$this->route_name) { + if (!$this->getRouteName()) { return NULL; } if (!($this->routeObject instanceof Route)) { $route_provider = \Drupal::service('router.route_provider'); - $this->routeObject = $route_provider->getRouteByName($this->route_name); + $this->routeObject = $route_provider->getRouteByName($this->getRouteName()); } return $this->routeObject; } @@ -324,11 +141,11 @@ public function reset() { // not stored anywhere else. Since resetting a link happens rarely and this // is a one-time operation, retrieving the full menu router does no harm. $menu = menu_get_router(); - $router_item = $menu[$this->router_path]; + $router_item = $menu[$this->getRouterPath()]; $new_link = self::buildFromRouterItem($router_item); // Merge existing menu link's ID and 'has_children' property. foreach (array('mlid', 'has_children') as $key) { - $new_link->{$key} = $this->{$key}; + $new_link->{$key}->value = $this->{$key}->value; } $new_link->save(); return $new_link; @@ -359,31 +176,90 @@ public static function buildFromRouterItem(array $item) { } /** - * Implements ArrayAccess::offsetExists(). + * {@inheritdoc} */ public function offsetExists($offset) { - return isset($this->{$offset}); + if (in_array($offset, $this->oldRoutingProperties)) { + return isset($this->oldRouterItem[$offset]); + } + return isset($this->{$offset}->value); } /** - * Implements ArrayAccess::offsetGet(). + * {@inheritdoc} */ - public function &offsetGet($offset) { - return $this->{$offset}; + public function offsetGet($offset) { + if (in_array($offset, $this->oldRoutingProperties)) { + return $this->oldRouterItem[$offset]; + } + elseif ($offset == 'options' || $offset == 'localized_options') { + return $this->options->getValue(); + } + elseif ($this->getPropertyDefinition($offset)) { + return $this->$offset->value; + } + else { + return $this->$offset; + } } /** - * Implements ArrayAccess::offsetSet(). + * {@inheritdoc} */ public function offsetSet($offset, $value) { - $this->{$offset} = $value; + if (in_array($offset, $this->oldRoutingProperties)) { + $this->oldRouterItem[$offset] = $value; + } + elseif ($offset == 'options' || $offset == 'localized_options') { + if (is_array($value)) { + foreach ($value as $delta => $value_item) { + if (!is_numeric($delta)) { + $this->options = array($value); + return; + } + } + } + /* if (!isset($value)) { + $this->options->value->setValue(array()); + } + elseif (is_array($value)) { + $this->options->value->setValue($value); + } + elseif ($value instanceof MapItem) { + $this->options->value->setValue($value->getValue()); + } + else { + $this->options = $value; + }*/ + } + elseif ($this->getPropertyDefinition($offset)) { + $this->{$offset} = $value; + } + else { + $this->{$offset} = $value; + } } /** - * Implements ArrayAccess::offsetUnset(). + * {@inheritdoc} */ public function offsetUnset($offset) { - unset($this->{$offset}); + if (in_array($offset, $this->oldRoutingProperties)) { + unset($this->oldRouterItem[$offset]); + } + else { + $this->{$offset}->value = NULL; + } +// unset($this->{$offset}); + } + + /** + * {@inheritdoc} + */ + public static function preCreate(EntityStorageControllerInterface $storage_controller, array &$values) { + if (empty($values['menu_name'])) { + $values['menu_name'] = $values['bundle'] = 'tools'; + } } /** @@ -397,10 +273,10 @@ public static function preDelete(EntityStorageControllerInterface $storage_contr foreach ($entities as $entity) { // Children get re-attached to the item's parent. - if ($entity->has_children) { - $children = $storage_controller->loadByProperties(array('plid' => $entity->plid)); + if ($entity->hasChildren()) { + $children = $storage_controller->loadByProperties(array('plid' => $entity->getParentLinkId())); foreach ($children as $child) { - $child->plid = $entity->plid; + $child->setParentLinkId($entity->getParentLinkId()); $storage_controller->save($child); } } @@ -419,8 +295,8 @@ public static function postDelete(EntityStorageControllerInterface $storage_cont } // Store all menu names for which we need to clear the cache. - if (!isset($affected_menus[$entity->menu_name])) { - $affected_menus[$entity->menu_name] = $entity->menu_name; + if (!isset($affected_menus[$entity->getMenuName()])) { + $affected_menus[$entity->getMenuName()] = $entity->getMenuName(); } } @@ -436,67 +312,69 @@ public static function postDelete(EntityStorageControllerInterface $storage_cont public function preSave(EntityStorageControllerInterface $storage_controller) { // This is the easiest way to handle the unique internal path '', // since a path marked as external does not need to match a router path. - $this->external = (url_is_external($this->link_path) || $this->link_path == '') ? 1 : 0; + $this->external->value = (url_is_external($this->getLinkPath()) || $this->getLinkPath() == '') ? 1 : 0; // Try to find a parent link. If found, assign it and derive its menu. $parent_candidates = !empty($this->parentCandidates) ? $this->parentCandidates : array(); $parent = $this->findParent($storage_controller, $parent_candidates); if ($parent) { - $this->plid = $parent->id(); - $this->menu_name = $parent->menu_name; + $this->setParentLinkId($parent->id()); + $this->setMenuName($parent->getMenuName()); } // If no corresponding parent link was found, move the link to the top-level. else { - $this->plid = 0; + $this->setParentLinkId(0); } // Directly fill parents for top-level links. - if ($this->plid == 0) { - $this->p1 = $this->id(); + if ($this->getParentLinkId() == 0) { + $this->p1->value = $this->id(); for ($i = 2; $i <= MENU_MAX_DEPTH; $i++) { $parent_property = "p$i"; - $this->{$parent_property} = 0; + $this->{$parent_property}->value = 0; } - $this->depth = 1; + $this->depth->value = 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) { + if ($this->hasChildren() && $this->original) { $limit = MENU_MAX_DEPTH - $storage_controller->findChildrenRelativeDepth($this->original) - 1; } else { $limit = MENU_MAX_DEPTH - 1; } - if ($parent->depth > $limit) { + if ($parent->getDepth() > $limit) { return FALSE; } - $this->depth = $parent->depth + 1; + $this->depth->value = $parent->getDepth() + 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)) { + if (isset($this->original) && ($this->getParentLinkId() != $this->original->getParentLinkId() || $this->getMenuName() != $this->original->getMenuName())) { $storage_controller->moveChildren($this, $this->original); } // Find the router_path. - if (empty($this->router_path) || empty($this->original) || (isset($this->original) && $this->original->link_path != $this->link_path)) { - if ($this->external) { - $this->router_path = ''; + if (!($this->getRouterPath()) || empty($this->original) || (isset($this->original) && $this->original->getLinkPath() != $this->getLinkPath())) { + if ($this->isExternal()) { + $this->setRouterPath(''); } else { // Find the router path which will serve this path. - $this->parts = explode('/', $this->link_path, MENU_MAX_PARTS); - $this->router_path = _menu_find_router_path($this->link_path); + // @todo Where do we need 'parts'? + $this->parts = explode('/', $this->getLinkPath(), MENU_MAX_PARTS); + $this->setRouterPath(_menu_find_router_path($this->getLinkPath())); } } // Find the route_name. - if (!isset($this->route_name)) { - if ($result = static::findRouteNameParameters($this->link_path)) { - list($this->route_name, $this->route_parameters) = $result; + if ($this->getRouteName()) { + if ($result = static::findRouteNameParameters($this->getLinkPath())) { + $this->setRouteName($result[0]); + $this->route_parameters = $result[1]; } else { - $this->route_name = ''; + $this->setRouteName(''); $this->route_parameters = array(); } } @@ -509,9 +387,9 @@ public function postSave(EntityStorageControllerInterface $storage_controller, $ // Check the has_children status of the parent. $storage_controller->updateParentalStatus($this); - menu_cache_clear($this->menu_name); - if (isset($this->original) && $this->menu_name != $this->original->menu_name) { - menu_cache_clear($this->original->menu_name); + menu_cache_clear($this->getMenuName()); + if (isset($this->original) && $this->getMenuName() != $this->original->getMenuName()) { + menu_cache_clear($this->original->getMenuName()); } // Now clear the cache. @@ -539,50 +417,50 @@ public static function findRouteNameParameters($link_path) { return array(); } } - + /** * {@inheritdoc} */ - public function setParents(EntityInterface $parent) { + public function setParents(MenuLinkInterface $parent) { $i = 1; - while ($i < $this->depth) { - $p = 'p' . $i++; - $this->{$p} = $parent->{$p}; + while ($i < $this->getDepth()) { + $p = 'p' . $i; + $this->{$p}->value = $parent->getMaterializedPathEntity($i); + $i++; } $p = 'p' . $i++; // The parent (p1 - p9) corresponding to the depth always equals the mlid. - $this->{$p} = $this->id(); + $this->{$p}->value = $this->id(); while ($i <= MENU_MAX_DEPTH) { $p = 'p' . $i++; - $this->{$p} = 0; + $this->{$p}->value = 0; } } /** * {@inheritdoc} */ - public function findParent(EntityStorageControllerInterface $storage_controller, array $parent_candidates = array()) { + public function findParent(MenuLinkStorageControllerInterface $storage_controller, array $parent_candidates = array()) { $parent = FALSE; + $plid = $this->getParentLinkId(); // This item is explicitely top-level, skip the rest of the parenting. - if (isset($this->plid) && empty($this->plid)) { + if ($plid === 0) { return $parent; } // If we have a parent link ID, try to use that. $candidates = array(); - if (isset($this->plid)) { - $candidates[] = $this->plid; + if ($plid) { + $candidates = array($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; + if ($this->getDepth() > 1) { + for ($depth = $this->getDepth() - 1; $depth >= 1; $depth--) { + $candidates[] = $this->getMaterializedPathEntity($depth); } } - foreach ($candidates as $mlid) { if (isset($parent_candidates[$mlid])) { $parent = $parent_candidates[$mlid]; @@ -598,12 +476,431 @@ public function findParent(EntityStorageControllerInterface $storage_controller, // If everything else failed, try to derive the parent from the path // hierarchy. This only makes sense for links derived from menu router // items (ie. from hook_menu()). - if ($this->module == 'system') { + if ($this->getModule() == 'system') { $parent = $storage_controller->getParentFromHierarchy($this); } return $parent; } + /** + * {@inheritdoc} + */ + public function getMenuName() { + return $this->get('menu_name')->value; + } + + /** + * {@inheritdoc} + */ + public function setMenuName($menu_name) { + $this->set('menu_name', $menu_name); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getParentLinkId() { + return $this->get('plid')->value; + } + + /** + * {@inheritdoc} + */ + public function setParentLinkId($plid) { + $this->set('plid', $plid); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getLinkPath() { + return $this->get('link_path')->value; + } + + /** + * {@inheritdoc} + */ + public function setLinkPath($link_path) { + $this->set('link_path', $link_path); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getLinkTitle() { + return $this->get('link_title')->value; + } + + /** + * {@inheritdoc} + */ + public function setLinkTitle($link_title) { + $this->set('link_title', $link_title); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getRouterPath() { + return $this->get('router_path')->value; + } + + /** + * {@inheritdoc} + */ + public function setRouterPath($router_path) { + $this->set('router_path', $router_path); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getOptions() { + return $this->get('options')->value; + } + + /** + * {@inheritdoc} + */ + public function setOptions(array $options) { + $this->set('options', $options); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getModule() { + return $this->get('module')->value; + } + + /** + * {@inheritdoc} + */ + public function setModule($module) { + $this->set('module', $module); + return $this; + } + + /** + * {@inheritdoc} + */ + public function setExpanded($expanded) { + $this->set('expanded', $expanded ? 1 : 0); + } + + /**v + */ + public function isExpanded() { + return (bool) $this->get('expanded')->value; + } + + /** + * {@inheritdoc} + */ + public function isExternal() { + return (bool) $this->get('external')->value; + } + + /** + * {@inheritdoc} + */ + public function setCustomized($customized) { + $this->set('customized', $customized); + return $this; + } + + /** + * {@inheritdoc} + */ + public function isCustomized() { + return (bool) $this->get('customized')->value; + } + + /** + * {@inheritdoc} + */ + public function setHidden($hidden) { + $this->set('hidden', $hidden ? 1 : 0); + return $this; + } + + /** + * {@inheritdoc} + */ + public function isHidden() { + return (bool)$this->get('hidden')->value; + } + + /** + * {@inheritdoc} + */ + public function hasChildren() { + return (bool)$this->get('has_children')->value; + } + + /** + * {@inheritdoc} + */ + public function getDepth() { + return $this->get('depth')->value; + } + + /** + * {@inheritdoc} + */ + public function getWeight() { + return $this->get('weight')->value; + } + + /** + * {@inheritdoc} + */ + public function setWeight($weight) { + $this->set('weight', $weight); + return $this; + } + + /** + * {@inheritdoc} + */ + public function getMaterializedPathEntity($depth) { + if ($depth < 1 || $depth > 9) { + return NULL; + } + return $this->get('p' . $depth)->value; + } + + /** + * {@inheritdoc} + */ + public function getChangedTime() { + return $this->get('updated')->value; + } + + /** + * {@inheritdoc} + */ + public function getRouteName() { + return $this->get('route_name')->value; + } + + /** + * {@inheritdoc} + */ + public function setRouteName($route_name) { + $this->set('route_name', $route_name); + return $this; + } + + /** + * {@inheritdoc} + */ + public static function baseFieldDefinitions($entity_type) { + $properties['menu_name'] = array( + 'label' => t('Menu name'), + 'description' => t('The menu name. All links with the same menu name (such as "tools") are part of the same menu.'), + 'type' => 'string_field', + 'settings' => array( + 'default_value' => 'tools', + ), + ); + $properties['mlid'] = array( + 'label' => t('Menu link ID'), + 'description' => t('The menu link ID.'), + 'type' => 'integer_field', + 'read-only' => TRUE, + ); + $properties['uuid'] = array( + 'label' => t('UUID'), + 'description' => t('The menu link UUID.'), + 'type' => 'uuid_field', + 'read-only' => TRUE, + ); + $properties['plid'] = array( + 'label' => t('Parent ID'), + 'description' => t('The parent menu link ID.'), + 'type' => 'entity_reference_field', + 'settings' => array('target_type' => 'menu_link'), + ); + $properties['link_path'] = array( + 'label' => t('Link path'), + 'description' => t('The Drupal path or external path this link points to.'), + 'type' => 'string_field', + ); + $properties['router_path'] = array( + 'label' => t('Router path'), + 'description' => t('For links corresponding to a Drupal path (external = 0), this connects the link to a {menu_router}.path.'), + 'type' => 'string_field', + ); + $properties['langcode'] = array( + 'label' => t('Language code'), + 'description' => t('The menu link language code.'), + 'type' => 'language_field', + ); + $properties['link_title'] = array( + 'label' => t('Title'), + 'description' => t('The text displayed for the link, which may be modified by a title callback stored in {menu_router}.'), + 'type' => 'string_field', + 'settings' => array( + 'default_value' => '', + ), + ); + $properties['options'] = array( + 'label' => t('Options'), + 'description' => t('A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.'), + 'type' => 'map_field', + ); + $properties['module'] = array( + 'label' => t('Module'), + 'description' => t('The name of the module that generated this link.'), + 'type' => 'string_field', + 'settings' => array( + 'default_value' => 'menu', + ), + ); + $properties['hidden'] = array( + 'label' => t('Hidden'), + 'description' => t('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' => 'boolean_field', + 'settings' => array( + 'default_value' => 0, + ), + ); + $properties['external'] = array( + 'label' => t('External'), + 'description' => t('A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).'), + 'type' => 'boolean_field', + 'settings' => array( + 'default_value' => 0, + ), + ); + $properties['has_children'] = array( + 'label' => t('Has children'), + 'description' => t('Flag indicating whether any links have this link as a parent (1 = children exist, 0 = no children).'), + 'type' => 'boolean_field', + 'settings' => array( + 'default_value' => 0, + ), + ); + $properties['expanded'] = array( + 'label' => t('Expanded'), + 'description' => t('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' => 'boolean_field', + 'settings' => array( + 'default_value' => 0, + ), + ); + $properties['weight'] = array( + 'label' => t('Weight'), + 'description' => t('Link weight among links in the same menu at the same depth.'), + 'type' => 'integer_field', + 'settings' => array( + 'default_value' => 0, + ), + ); + $properties['depth'] = array( + 'label' => t('Depth'), + 'description' => t('The depth relative to the top level. A link with plid == 0 will have depth == 1.'), + 'type' => 'integer_field', + ); + $properties['customized'] = array( + 'label' => t('Customized'), + 'description' => t('A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).'), + 'type' => 'boolean_field', + 'settings' => array( + 'default_value' => 0, + ), + ); + // @todo Declaring these pX properties as integer for the moment, we need to + // investigate if using 'entity_reference_field' cripples performance. + $properties['p1'] = array( + 'label' => t('Parent 1'), + 'description' => t('The first mlid in the materialized path.'), + 'type' => 'integer_field', + ); + $properties['p2'] = array( + 'label' => t('Parent 2'), + 'description' => t('The second mlid in the materialized path.'), + 'type' => 'integer_field', + ); + $properties['p3'] = array( + 'label' => t('Parent 3'), + 'description' => t('The third mlid in the materialized path.'), + 'type' => 'integer_field', + ); + $properties['p4'] = array( + 'label' => t('Parent 4'), + 'description' => t('The fourth mlid in the materialized path.'), + 'type' => 'integer_field', + ); + $properties['p5'] = array( + 'label' => t('Parent 5'), + 'description' => t('The fifth mlid in the materialized path.'), + 'type' => 'integer_field', + ); + $properties['p6'] = array( + 'label' => t('Parent 6'), + 'description' => t('The sixth mlid in the materialized path.'), + 'type' => 'integer_field', + ); + $properties['p7'] = array( + 'label' => t('Parent 7'), + 'description' => t('The seventh mlid in the materialized path.'), + 'type' => 'integer_field', + ); + $properties['p8'] = array( + 'label' => t('Parent 8'), + 'description' => t('The eighth mlid in the materialized path.'), + 'type' => 'integer_field', + ); + $properties['p9'] = array( + 'label' => t('Parent 9'), + 'description' => t('The ninth mlid in the materialized path.'), + 'type' => 'integer_field', + ); + $properties['updated'] = array( + 'label' => t('Updated'), + 'description' => t('Flag that indicates that this link was generated during the update from Drupal 5.'), + 'type' => 'boolean_field', + ); + $properties['route_name'] = array( + 'label' => t('Route name'), + 'description' => t('The machine name of a defined Symfony Route this menu item represents.'), + 'type' => 'string_field', + ); + $properties['route_parameters'] = array( + 'label' => t('Route parameters'), + 'description' => t('A serialized array of route parameters of this menu link.'), + 'type' => 'map_field', + ); + + // @todo Most of these should probably go away. + $properties['access'] = array( + 'label' => t('(old router) Access'), + 'description' => t(''), + 'type' => 'boolean_field', + 'computed' => TRUE, + ); + $properties['in_active_trail'] = array( + 'label' => t('In active trail'), + 'description' => t(''), + 'type' => 'boolean_field', + 'computed' => TRUE, + ); + $properties['localized_options'] = array( + 'label' => t('Localized options'), + 'description' => t(''), + 'type' => 'map_field', + 'computed' => TRUE, + ); + return $properties; + } } diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkAccessController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkAccessController.php index a1c2c69..e710775 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkAccessController.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkAccessController.php @@ -27,11 +27,11 @@ protected function checkAccess(EntityInterface $entity, $operation, $langcode, A switch ($operation) { case 'reset': // Reset allowed for items defined via hook_menu() and customized. - return $entity->module == 'system' && $entity->customized; + return $entity->module->value == 'system' && $entity->customized->value; case 'delete': // Only items created by the menu module can be deleted. - return $entity->module == 'menu' || $entity->updated == 1; + return $entity->module->value == 'menu' || $entity->updated->value == 1; } } diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php index 8aea21d..8060e58 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkFormController.php @@ -7,7 +7,8 @@ namespace Drupal\menu_link; -use Drupal\Core\Entity\EntityFormController; +use Drupal\Core\Entity\EntityControllerInterface; +use Drupal\Core\Entity\EntityFormControllerNG; use Drupal\Core\Language\Language; use Drupal\Core\Path\AliasManagerInterface; use Drupal\Core\Routing\UrlGenerator; @@ -17,7 +18,7 @@ /** * Form controller for the node edit forms. */ -class MenuLinkFormController extends EntityFormController { +class MenuLinkFormController extends EntityFormControllerNG { /** * The menu link storage controller. @@ -79,37 +80,37 @@ public function form(array $form, array &$form_state) { if (!$menu_link->isNew()) { // Get the human-readable menu title from the given menu name. $titles = menu_get_menus(); - $current_title = $titles[$menu_link->menu_name]; + $current_title = $titles[$menu_link->menu_name->value]; // Get the current breadcrumb and add a link to that menu's overview page. $breadcrumb = menu_get_active_breadcrumb(); - $breadcrumb[] = l($current_title, 'admin/structure/menu/manage/' . $menu_link->menu_name); + $breadcrumb[] = l($current_title, 'admin/structure/menu/manage/' . $menu_link->menu_name->value); drupal_set_breadcrumb($breadcrumb); } $form['link_title'] = array( '#type' => 'textfield', '#title' => t('Menu link title'), - '#default_value' => $menu_link->link_title, + '#default_value' => $menu_link->link_title->value, '#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}); + $form[$key] = array('#type' => 'value', '#value' => $menu_link->{$key}->getValue()); } // 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']); + $path = $menu_link->link_path->value; + 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 (isset($menu_link->options->fragment)) { + $path .= '#' . $menu_link->options->fragment; } - if ($menu_link->module == 'menu') { + if ($menu_link->module->value == 'menu') { $form['link_path'] = array( '#type' => 'textfield', '#title' => t('Path'), @@ -123,33 +124,33 @@ public function form(array $form, array &$form_state) { $form['_path'] = array( '#type' => 'item', '#title' => t('Path'), - '#description' => l($menu_link->link_title, $menu_link->href, $menu_link->options), + '#description' => l($menu_link->link_title->value, $menu_link->href, $menu_link->options->get('value')->getValue()), ); } $form['description'] = array( '#type' => 'textarea', '#title' => t('Description'), - '#default_value' => isset($menu_link->options['attributes']['title']) ? $menu_link->options['attributes']['title'] : '', + '#default_value' => isset($menu_link->options->value['attributes']['title']) ? $menu_link->options->value['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, + '#default_value' => !$menu_link->hidden->value, '#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, + '#default_value' => $menu_link->expanded->value, '#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_parent_options(menu_get_menus(), $menu_link); - $default = $menu_link->menu_name . ':' . $menu_link->plid; + $default = $menu_link->menu_name->value . ':' . $menu_link->plid->target_id; if (!isset($options[$default])) { $default = 'tools:0'; } @@ -163,13 +164,13 @@ public function form(array $form, array &$form_state) { ); // Get number of items in menu so the weight selector is sized appropriately. - $delta = $this->menuLinkStorageController->countMenuLinks($menu_link->menu_name); + $delta = $this->menuLinkStorageController->countMenuLinks($menu_link->menu_name->value); $form['weight'] = array( '#type' => 'weight', '#title' => t('Weight'), // Old hardcoded value. '#delta' => max($delta, 50), - '#default_value' => $menu_link->weight, + '#default_value' => $menu_link->weight->value, '#description' => t('Optional. In the menu, the heavier links will sink and the lighter links will be positioned nearer the top.'), ); @@ -179,13 +180,13 @@ public function form(array $form, array &$form_state) { // 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); + $default_langcode = ($menu_link->isNew() ? $language_configuration['langcode'] : $menu_link->langcode->value); $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)->langcode : $menu_link->langcode); + $default_langcode = ($menu_link->isNew() ? entity_load('menu', $menu_link->menu_name->value)->langcode : $menu_link->langcode->value); $language_show = FALSE; } @@ -212,74 +213,63 @@ protected function actions(array $form, array &$form_state) { } /** - * Overrides EntityFormController::validate(). + * {@inheritdoc} */ public function validate(array $form, array &$form_state) { $menu_link = $this->buildEntity($form, $form_state); - $normal_path = $this->pathAliasManager->getSystemPath($menu_link->link_path); - if ($menu_link->link_path != $normal_path) { + $normal_path = $this->pathAliasManager->getSystemPath($menu_link->link_path->value); + if ($menu_link->link_path->value != $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; + $menu_link->link_path->value = $normal_path; } - if (!url_is_external($menu_link->link_path)) { - $parsed_link = parse_url($menu_link->link_path); + if (!url_is_external($menu_link->link_path->value)) { + $parsed_link = parse_url($menu_link->link_path->value); if (isset($parsed_link['query'])) { - $menu_link->options['query'] = array(); - parse_str($parsed_link['query'], $menu_link->options['query']); + $query = array(); + parse_str($parsed_link['query'], $query); + $menu_link->options->query = $query; } else { - // Use unset() rather than setting to empty string - // to avoid redundant serialized data being stored. - unset($menu_link->options['query']); + $menu_link->options->query = ''; } if (isset($parsed_link['fragment'])) { - $menu_link->options['fragment'] = $parsed_link['fragment']; + $menu_link->options->fragment = $parsed_link['fragment']; } else { - unset($menu_link->options['fragment']); + $menu_link->options->fragment = ''; } - if (isset($parsed_link['path']) && $menu_link->link_path != $parsed_link['path']) { - $menu_link->link_path = $parsed_link['path']; + if (isset($parsed_link['path']) && $menu_link->link_path->value != $parsed_link['path']) { + $menu_link->link_path->value = $parsed_link['path']; } } - if (!trim($menu_link->link_path) || !drupal_valid_path($menu_link->link_path, TRUE)) { - form_set_error('link_path', t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $menu_link->link_path))); + if (!trim($menu_link->link_path->value) || !drupal_valid_path($menu_link->link_path->value, TRUE)) { + form_set_error('link_path', t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $menu_link->link_path->value))); } parent::validate($form, $form_state); } - /** - * Overrides EntityFormController::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); - + public function buildEntity(array $form, array &$form_state) { + $entity = parent::buildEntity($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; + $entity->hidden->value = (int) !$form_state['values']['enabled']; + list($entity->menu_name->value, $entity->plid->target_id) = explode(':', $form_state['values']['parent']); + $attributes = $entity->options->attributes; + $attributes['title'] = $form_state['values']['description']; + $entity->options->attributes = $attributes; + return $entity; } /** * Overrides EntityFormController::save(). */ public function save(array $form, array &$form_state) { - $menu_link = $this->entity; - - $saved = $menu_link->save(); - - if ($saved) { + if ($this->entity->save()) { drupal_set_message(t('The menu link has been saved.')); - $form_state['redirect'] = 'admin/structure/menu/manage/' . $menu_link->menu_name; + $form_state['redirect'] = 'admin/structure/menu/manage/' . $this->entity->menu_name->value; } else { drupal_set_message(t('There was an error saving the menu link.'), 'error'); @@ -291,7 +281,6 @@ public function save(array $form, array &$form_state) { * Overrides EntityFormController::delete(). */ public function delete(array $form, array &$form_state) { - $menu_link = $this->entity; - $form_state['redirect'] = 'admin/structure/menu/item/' . $menu_link->id() . '/delete'; + $form_state['redirect'] = 'admin/structure/menu/item/' . $this->entity->id() . '/delete'; } } diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php index b8e639e..acb4549 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkInterface.php @@ -10,7 +10,6 @@ use Symfony\Component\Routing\Route; use Drupal\Core\Entity\ContentEntityInterface; use Drupal\Core\Entity\EntityInterface; -use Drupal\Core\Entity\EntityStorageControllerInterface; /** * Provides an interface defining a menu link entity. @@ -70,10 +69,10 @@ public static function findRouteNameParameters($link_path); /** * Sets the p1 through p9 properties for a menu link entity being saved. * - * @param \Drupal\Core\Entity\EntityInterface $parent + * @param \Drupal\menu_link\MenuLinkInterface $parent * A menu link entity. */ - public function setParents(EntityInterface $parent); + public function setParents(MenuLinkInterface $parent); /** * Finds a possible parent for a given menu link entity. @@ -86,7 +85,7 @@ public function setParents(EntityInterface $parent); * - else, for system menu links (derived from hook_menu()), reparent * based on the path hierarchy. * - * @param \Drupal\Core\Entity\EntityStorageControllerInterface $storage_controller + * @param \Drupal\menu_link\MenuLinkStorageControllerInterface $storage_controller * Storage controller object. * @param array $parent_candidates * An array of menu link entities keyed by mlid. @@ -95,5 +94,282 @@ public function setParents(EntityInterface $parent); * A menu link entity structure of the possible parent or FALSE if no valid * parent has been found. */ - public function findParent(EntityStorageControllerInterface $storage_controller, array $parent_candidates = array()); + public function findParent(MenuLinkStorageControllerInterface $storage_controller, array $parent_candidates = array()); + + /** + * Returns the menu name of this menu link. + * + * @return string + * The name of the menu. + */ + public function getMenuName(); + + /** + * Sets the menu name of this menu link. + * + * @param string $menu_name + * The name of the menu. + * + * @return \Drupal\menu_link\MenuLinkInterface + * The called menu link entity. + */ + public function setMenuName($menu_name); + + /** + * Returns the parent menu link ID of this menu link. + * + * @return int + * The parent link ID of the menu. + */ + public function getParentLinkId(); + + /** + * Sets the parent menu link id of this menu link. + * + * @param int $plid + * The parent link ID of the menu. + * + * @return \Drupal\menu_link\MenuLinkInterface + * The called menu link entity. + */ + public function setParentLinkId($plid); + + /** + * Returns the Drupal path or external path this link points to. + * + * @return string + * The Drupal path or external path this link points to. + */ + public function getLinkPath(); + + /** + * Sets the Drupal path or external path this link points to. + * + * @param string $link_path + * The the Drupal path or external path this link points to. + * + * @return \Drupal\menu_link\MenuLinkInterface + * The called menu link entity. + */ + public function setLinkPath($link_path); + + /** + * Returns the title of this menu link. + * + * @return string + * The title of this menu link. + */ + public function getLinkTitle(); + + /** + * Sets the title of this menu link. + * + * @param string $link_title + * The title of this menu link. + * + * @return \Drupal\menu_link\MenuLinkInterface + * The called menu link entity. + */ + public function setLinkTitle($link_title); + + /** + * Returns the menu router path for this link. + * + * @return string + * The menu router path for this link. + */ + public function getRouterPath(); + + /** + * Sets the Drupal path or external path this link points to. + * + * @param string $router_path + * The menu router path for this link. + * + * @return \Drupal\menu_link\MenuLinkInterface + * The called menu link entity. + */ + public function setRouterPath($router_path); + + /** + * Returns the menu link options. + * + * @return array + * The menu link options, to be passed to to l() or url(). + */ + public function getOptions(); + + /** + * Sets the menu link options. + * + * @todo: Add a method to add options? + * + * @param array $options + * The menu link options. + * + * @return \Drupal\menu_link\MenuLinkInterface + * The called menu link entity. + */ + public function setOptions(array $options); + + /** + * Returns the name of the module that generated this link. + * + * @return string + * The name of the module that generated this link. + */ + public function getModule(); + + /** + * Sets the name of the module that generated this link. + * + * @param string $module + * The name of the module that generated this link. + * + * @return \Drupal\menu_link\MenuLinkInterface + * The called menu link entity. + */ + public function setModule($module); + + /** + * Sets whether the menu link is expanded. + * + * @param bool $expanded + * TRUE if the menu link is expanded, FALSE if not. + * + * @return \Drupal\menu_link\MenuLinkInterface + * The called menu link entity. + */ + public function setExpanded($expanded); + + /** + * Returns whether the menu link is expanded + * + * @return string + * TRUE if the menu link is expanded, FALSE if not. + */ + public function isExpanded(); + + /** + * Returns whether the menu link is external. + * + * @return string + * TRUE if the menu link is external, FALSE if not. + */ + public function isExternal(); + + /** + * Sets if the menu link is customized. + * + * @param bool $customized + * TRUE if the menu link is customized, FALSE if not. + * + * @return \Drupal\menu_link\MenuLinkInterface + * The called menu link entity. + */ + public function setCustomized($customized); + + /** + * Returns whether the menu link is customized. + * + * @return string + * TRUE if the menu link is customized, FALSE if not. + */ + public function isCustomized(); + + /** + * Sets whether the menu link is hidden. + * + * @param bool $hidden + * TRUE if the menu link is hidden, FALSE if not. + * + * @return \Drupal\menu_link\MenuLinkInterface + * The called menu link entity. + */ + public function setHidden($hidden); + + /** + * Returns whether the menu link is hidden. + * + * @return string + * TRUE if the menu link is hidden, FALSE if not. + */ + public function isHidden(); + + /** + * Returns whether the menu link is expanded + * + * @return string + * TRUE if the menu link has children, FALSE if not. + */ + public function hasChildren(); + + /** + * Returns the depth relative to the top level of this link. + * + * A link with parent ID 0 will have depth of 1. + * + * @return int + * The menu link depth. + */ + public function getDepth(); + + /** + * Returns the weight among menu links with the same depth. + * + * @return int + * The menu link weight. + */ + public function getWeight(); + + /** + * Sets the weight among menu links with the same depth. + * + *ยท@param int $weight + * The menu link weight. + * + * @return \Drupal\menu_link\MenuLinkInterface + * The called menu link entity. + */ + public function setWeight($weight); + + /** + * Returns the nth depth entity ID of the materialized path. + * + * @param int $depth + * Indicates the depth of entity ID of the materialized path that should be + * returned, between 1 and 9. + * + * @return int + * Nth Entity ID of the materialized path. + */ + public function getMaterializedPathEntity($depth); + + /** + * Returns the menu link modification timestamp. + * + * @return int + * Menu link modification timestamp. + */ + public function getChangedTime(); + + /** + * Returns the route name associated with this menu link, if any. + * + * @return string|null + * The route name of this menu link. + */ + public function getRouteName(); + + /** + * Sets the route name associated with this menu link. + * + * @param string|null $route_name + * The route name associated with this menu link. + * + * @return \Drupal\menu_link\MenuLinkInterface + * The called menu link entity. + */ + public function setRouteName($route_name); + } diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php index 7330147..6d9685c 100644 --- a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php +++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkStorageController.php @@ -7,7 +7,7 @@ namespace Drupal\menu_link; -use Drupal\Core\Entity\DatabaseStorageController; +use Drupal\Core\Entity\DatabaseStorageControllerNG; use Drupal\Core\Entity\EntityInterface; use Drupal\Core\Entity\EntityStorageException; use Drupal\Core\Database\Connection; @@ -16,12 +16,9 @@ use Symfony\Cmf\Component\Routing\RouteProviderInterface; /** - * Controller class for menu links. - * - * This extends the Drupal\entity\DatabaseStorageController class, adding - * required special handling for menu_link entities. + * Defines the storage controller class for menu links. */ -class MenuLinkStorageController extends DatabaseStorageController implements MenuLinkStorageControllerInterface { +class MenuLinkStorageController extends DatabaseStorageControllerNG implements MenuLinkStorageControllerInterface { /** * Indicates whether the delete operation should re-parent children items. @@ -94,7 +91,7 @@ public static function createInstance(ContainerInterface $container, $entity_typ } /** - * Overrides DatabaseStorageController::buildQuery(). + * {@inheritdoc} */ protected function buildQuery($ids, $revision_id = FALSE) { $query = parent::buildQuery($ids, $revision_id); @@ -105,51 +102,61 @@ protected function buildQuery($ids, $revision_id = FALSE) { } /** - * Overrides DatabaseStorageController::attachLoad(). - * - * @todo Don't call parent::attachLoad() at all because we want to be able to - * control the entity load hooks. + * {@inheritdoc} */ - protected function attachLoad(&$menu_links, $load_revision = FALSE) { + protected function attachLoad(&$queried_entities, $load_revision = FALSE) { $routes = array(); - foreach ($menu_links as &$menu_link) { - $menu_link->options = unserialize($menu_link->options); - $menu_link->route_parameters = unserialize($menu_link->route_parameters); + foreach ($queried_entities as &$record) { + $record->options = unserialize($record->options); + $record->route_parameters = unserialize($record->route_parameters); // Use the weight property from the menu link. - $menu_link->router_item['weight'] = $menu_link->weight; + $record->router_item['weight'] = $record->weight; // By default use the menu_name as type. - $menu_link->bundle = $menu_link->menu_name; + $record->bundle = $record->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; + if ($record->route_name) { + $routes[$record->{$this->idKey}] = $record->route_name; } } + parent::attachLoad($queried_entities, $load_revision); + // Now mass-load any routes needed and associate them. if ($routes) { $route_objects = $this->routeProvider->getRoutesByNames($routes); foreach ($routes as $entity_id => $route) { // Not all stored routes will be valid on load. if (isset($route_objects[$route])) { - $menu_links[$entity_id]->setRouteObject($route_objects[$route]); + $queried_entities[$entity_id]->setRouteObject($route_objects[$route]); } } } + } + + /** + * {@inheritdoc} + */ + protected function mapFromStorageRecords(array $records, $load_revision = FALSE) { + $entities = parent::mapFromStorageRecords($records, $load_revision); - parent::attachLoad($menu_links, $load_revision); + foreach ($entities as &$entity) { + foreach (static::$routerItemFields as $router_field) { + $entity->offsetSet($router_field, $records[$entity->id()]->{$router_field}); + } + } + + return $entities; } /** * Overrides DatabaseStorageController::save(). */ public function save(EntityInterface $entity) { - $entity_class = $this->entityInfo['class']; - // 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. @@ -157,13 +164,19 @@ public function save(EntityInterface $entity) { $transaction = $this->database->startTransaction(); try { + // Ensure we are dealing with the actual entity. + $entity = $entity->getNGEntity(); + + // Sync the changes made in the fields array to the internal values array. + $entity->updateOriginalValues(); + // Load the stored entity, if any. if (!$entity->isNew() && !isset($entity->original)) { $entity->original = entity_load_unchanged($this->entityType, $entity->id()); } if ($entity->isNew()) { - $entity->mlid = $this->database->insert($this->entityInfo['base_table'])->fields(array('menu_name' => 'tools'))->execute(); + $entity->mlid->value = $this->database->insert($this->entityInfo['base_table'])->fields(array('menu_name' => 'tools'))->execute(); $entity->enforceIsNew(); } @@ -179,20 +192,46 @@ public function save(EntityInterface $entity) { // 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->entityInfo['base_table'], $entity, $this->idKey); + if ($entity->isNew() || (array_intersect_key($entity->getPropertyValues(), $entity->original->getPropertyValues()) != $entity->original->getPropertyValues())) { + // Create the storage record to be saved. + $record = $this->mapToStorageRecord($entity); + $return = drupal_write_record($this->entityInfo['base_table'], $record, $this->idKey); if ($return) { if (!$entity->isNew()) { - $this->resetCache(array($entity->{$this->idKey})); + // @todo, should a different value be returned when saving an entity + // with $isDefaultRevision = FALSE? + if (!$entity->isDefaultRevision()) { + $return = FALSE; + } + + if ($this->revisionKey) { + $record->{$this->revisionKey} = $this->saveRevision($entity); + } + if ($this->dataTable) { + $this->savePropertyData($entity); + } + $this->resetCache(array($entity->id())); $entity->postSave($this, TRUE); $this->invokeFieldMethod('update', $entity); $this->saveFieldItems($entity, TRUE); $this->invokeHook('update', $entity); + if ($this->dataTable) { + $this->invokeTranslationHooks($entity); + } } else { $return = SAVED_NEW; - $this->resetCache(); + if ($this->revisionKey) { + $record->{$this->revisionKey} = $this->saveRevision($entity); + } + $entity->{$this->idKey}->value = $record->{$this->idKey}; + if ($this->dataTable) { + $this->savePropertyData($entity); + } + + // Reset general caches, but keep caches specific to certain entities. + $this->resetCache(array()); $entity->enforceIsNew(FALSE); $entity->postSave($this, FALSE); @@ -234,6 +273,10 @@ public function getPreventReparenting() { * {@inheritdoc} */ public function loadUpdatedCustomized(array $router_paths) { + $menu_links = array(); + + // @todo This doesn't really make sense anymore with EntityNG.. and EFQ got + // OR condition support in the meantime, so convert this query. $query = parent::buildQuery(NULL); $query ->condition(db_or() @@ -244,16 +287,12 @@ public function loadUpdatedCustomized(array $router_paths) { ->condition('customized', 1) ) ); - $query_result = $query->execute(); - if (!empty($this->entityInfo['class'])) { - // 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->entityInfo['class'], array(array(), $this->entityType)); + if ($ids = $query->execute()->fetchCol(1)) { + $menu_links = $this->load($ids); } - return $query_result->fetchAllAssoc($this->idKey); + return $menu_links; } /** @@ -277,13 +316,13 @@ public function loadModuleAdminTasks() { */ public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE) { // If plid == 0, there is nothing to update. - if ($entity->plid) { + if ($entity->plid->target_id) { // Check if at least one visible child exists in the table. $query = \Drupal::entityQuery($this->entityType); $query - ->condition('menu_name', $entity->menu_name) + ->condition('menu_name', $entity->menu_name->value) ->condition('hidden', 0) - ->condition('plid', $entity->plid) + ->condition('plid', $entity->plid->target_id) ->count(); if ($exclude) { @@ -293,7 +332,7 @@ public function updateParentalStatus(EntityInterface $entity, $exclude = FALSE) $parent_has_children = ((bool) $query->execute()) ? 1 : 0; $this->database->update('menu_links') ->fields(array('has_children' => $parent_has_children)) - ->condition('mlid', $entity->plid) + ->condition('mlid', $entity->plid->target_id) ->execute(); } } @@ -306,20 +345,20 @@ public function findChildrenRelativeDepth(EntityInterface $entity) { // 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->condition('menu_name', $entity->menu_name->value); $query->orderBy('depth', 'DESC'); $query->range(0, 1); $i = 1; $p = 'p1'; - while ($i <= MENU_MAX_DEPTH && $entity->{$p}) { - $query->condition($p, $entity->{$p}); + while ($i <= MENU_MAX_DEPTH && $entity->{$p}->value) { + $query->condition($p, $entity->{$p}->value); $p = 'p' . ++$i; } $max_depth = $query->execute()->fetchField(); - return ($max_depth > $entity->depth) ? $max_depth - $entity->depth : 0; + return ($max_depth > $entity->depth->value) ? $max_depth - $entity->depth->value : 0; } /** @@ -328,14 +367,14 @@ public function findChildrenRelativeDepth(EntityInterface $entity) { public function moveChildren(EntityInterface $entity) { $query = $this->database->update($this->entityInfo['base_table']); - $query->fields(array('menu_name' => $entity->menu_name)); + $query->fields(array('menu_name' => $entity->menu_name->value)); $p = 'p1'; $expressions = array(); - for ($i = 1; $i <= $entity->depth; $p = 'p' . ++$i) { - $expressions[] = array($p, ":p_$i", array(":p_$i" => $entity->{$p})); + for ($i = 1; $i <= $entity->depth->value; $p = 'p' . ++$i) { + $expressions[] = array($p, ":p_$i", array(":p_$i" => $entity->{$p}->value)); } - $j = $entity->original->depth + 1; + $j = $entity->original->depth->value + 1; while ($i <= MENU_MAX_DEPTH && $j <= MENU_MAX_DEPTH) { $expressions[] = array('p' . $i++, 'p' . $j++, array()); } @@ -343,7 +382,7 @@ public function moveChildren(EntityInterface $entity) { $expressions[] = array('p' . $i++, 0, array()); } - $shift = $entity->depth - $entity->original->depth; + $shift = $entity->depth->value - $entity->original->depth->value; 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 @@ -356,10 +395,10 @@ public function moveChildren(EntityInterface $entity) { } $query->expression('depth', 'depth + :depth', array(':depth' => $shift)); - $query->condition('menu_name', $entity->original->menu_name); + $query->condition('menu_name', $entity->original->menu_name->value); $p = 'p1'; - for ($i = 1; $i <= MENU_MAX_DEPTH && $entity->original->{$p}; $p = 'p' . ++$i) { - $query->condition($p, $entity->original->{$p}); + for ($i = 1; $i <= MENU_MAX_DEPTH && $entity->original->{$p}->value; $p = 'p' . ++$i) { + $query->condition($p, $entity->original->{$p}->value); } $query->execute(); @@ -383,7 +422,7 @@ public function countMenuLinks($menu_name) { * {@inheritdoc} */ public function getParentFromHierarchy(EntityInterface $entity) { - $parent_path = $entity->link_path; + $parent_path = $entity->link_path->value; do { $parent = FALSE; $parent_path = substr($parent_path, 0, strrpos($parent_path, '/')); @@ -394,7 +433,7 @@ public function getParentFromHierarchy(EntityInterface $entity) { ->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('menu_name', $entity->menu_name->value) ->condition('link_path', $parent_path); $result = $query->execute(); diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Form/SetCustomize.php b/core/modules/shortcut/lib/Drupal/shortcut/Form/SetCustomize.php index 29da4a8..97aafee 100644 --- a/core/modules/shortcut/lib/Drupal/shortcut/Form/SetCustomize.php +++ b/core/modules/shortcut/lib/Drupal/shortcut/Form/SetCustomize.php @@ -38,13 +38,13 @@ public function form(array $form, array &$form_state) { foreach ($this->entity->links as $link) { $mlid = $link->id(); $form['shortcuts']['links'][$mlid]['#attributes']['class'][] = 'draggable'; - $form['shortcuts']['links'][$mlid]['name']['#markup'] = l($link->link_title, $link->link_path); - $form['shortcuts']['links'][$mlid]['#weight'] = $link->weight; + $form['shortcuts']['links'][$mlid]['name']['#markup'] = l($link->link_title->value, $link->link_path->value); + $form['shortcuts']['links'][$mlid]['#weight'] = $link->weight->value; $form['shortcuts']['links'][$mlid]['weight'] = array( '#type' => 'weight', - '#title' => t('Weight for @title', array('@title' => $link->link_title)), + '#title' => t('Weight for @title', array('@title' => $link->link_title->value)), '#title_display' => 'invisible', - '#default_value' => $link->weight, + '#default_value' => $link->weight->value, '#attributes' => array('class' => array('shortcut-weight')), ); @@ -88,7 +88,7 @@ protected function actions(array $form, array &$form_state) { */ public function save(array $form, array &$form_state) { foreach ($this->entity->links as $link) { - $link->weight = $form_state['values']['shortcuts']['links'][$link->mlid]['weight']; + $link->weight = $form_state['values']['shortcuts']['links'][$link->id()]['weight']; $link->save(); } drupal_set_message(t('The shortcut set has been updated.')); diff --git a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php index 06088ef..2fcfa5b 100644 --- a/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php +++ b/core/modules/shortcut/lib/Drupal/shortcut/Tests/ShortcutTestBase.php @@ -129,7 +129,7 @@ function generateShortcutLink($path, $title = '') { function getShortcutInformation($set, $key) { $info = array(); foreach ($set->links as $uuid => $link) { - $info[] = $link->{$key}; + $info[] = $link->{$key}->value; } return $info; } diff --git a/core/modules/shortcut/shortcut.module b/core/modules/shortcut/shortcut.module index 27f11d7..2d59d03 100644 --- a/core/modules/shortcut/shortcut.module +++ b/core/modules/shortcut/shortcut.module @@ -56,7 +56,7 @@ function shortcut_entity_bundle_info() { function shortcut_menu_link_load($entities) { foreach ($entities as $entity) { // Change the bundle of menu links related to a shortcut. - if (strpos($entity->menu_name, 'shortcut-') === 0) { + if (strpos($entity->menu_name->value, 'shortcut-') === 0) { $entity->bundle = 'shortcut'; } } @@ -235,9 +235,9 @@ function shortcut_link_access($menu_link) { */ function shortcut_menu_link_delete($menu_link) { // If the deleted menu link was in a shortcut set, remove it. - if (strpos($menu_link->menu_name, 'shortcut-') === 0) { - $shortcut = entity_load('shortcut_set', str_replace('shortcut-', '', $menu_link->menu_name)); - unset($shortcut->links[$menu_link->uuid]); + if (strpos($menu_link->menu_name->value, 'shortcut-') === 0) { + $shortcut = entity_load('shortcut_set', str_replace('shortcut-', '', $menu_link->menu_name->value)); + unset($shortcut->links[$menu_link->uuid()]); $shortcut->save(); } } diff --git a/core/modules/system/lib/Drupal/system/Controller/SystemController.php b/core/modules/system/lib/Drupal/system/Controller/SystemController.php index 470b5bb..ee46955 100644 --- a/core/modules/system/lib/Drupal/system/Controller/SystemController.php +++ b/core/modules/system/lib/Drupal/system/Controller/SystemController.php @@ -78,7 +78,7 @@ public function overview() { $system_link = reset($system_link); $query = $this->queryFactory->get('menu_link') ->condition('link_path', 'admin/help', '<>') - ->condition('menu_name', $system_link->menu_name) + ->condition('menu_name', $system_link->getMenuName()) ->condition('plid', $system_link->id()) ->condition('hidden', 0); $result = $query->execute(); @@ -95,7 +95,11 @@ public function overview() { $item['description'] = $item['localized_options']['attributes']['title']; unset($item['localized_options']['attributes']['title']); } - $block = $item; + $block = array( + 'title' => $item->getLinkTitle(), + 'description' => $item['description'], + 'position' => $item['position'], + ); $block['content'] = array( '#theme' => 'admin_block_content', '#content' => $this->systemManager->getAdminBlock($item), diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php index e9ece26..a075606 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuRouterTest.php @@ -365,7 +365,7 @@ function testMenuHidden() { $links = array(); foreach ($menu_links as $menu_link) { - $links[$menu_link->router_path] = $menu_link; + $links[$menu_link->router_path->value] = $menu_link; } $parent = $links['menu-test/hidden/menu']; @@ -417,7 +417,7 @@ function testMenuHidden() { $links = array(); foreach ($menu_links as $menu_link) { - $links[$menu_link->router_path] = $menu_link; + $links[$menu_link->router_path->value] = $menu_link; } $parent = $links['menu-test/hidden/block']; diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php index 263e2fc..6714e13 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeAccessTest.php @@ -20,6 +20,13 @@ class TreeAccessTest extends DrupalUnitTestBase { /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('menu_link'); + + /** * A list of menu links used for this test. * * @var array @@ -93,8 +100,8 @@ public function testRouteItemMenuLinksAccess() { // Setup the links with the route items. $this->links = array( - new MenuLink(array('mlid' => 1, 'route_name' => 'menu_test_1', 'depth' => 1, 'link_path' => 'menu_test/test_1'), 'menu_link'), - new MenuLink(array('mlid' => 2, 'route_name' => 'menu_test_2', 'depth' => 1, 'link_path' => 'menu_test/test_2'), 'menu_link'), + entity_create('menu_link', array('mlid' => 1, 'route_name' => 'menu_test_1', 'depth' => 1, 'link_path' => 'menu_test/test_1')), + entity_create('menu_link', array('mlid' => 2, 'route_name' => 'menu_test_2', 'depth' => 1, 'link_path' => 'menu_test/test_2')), ); // Build the menu tree and check access for all of the items. diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php index 8b6c4a1..e771e4f 100644 --- a/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php +++ b/core/modules/system/lib/Drupal/system/Tests/Menu/TreeDataUnitTest.php @@ -8,12 +8,20 @@ namespace Drupal\system\Tests\Menu; use Drupal\menu_link\Entity\MenuLink; -use Drupal\simpletest\UnitTestBase; +use Drupal\simpletest\DrupalUnitTestBase; /** * Menu tree data related tests. */ -class TreeDataUnitTest extends UnitTestBase { +class TreeDataUnitTest extends DrupalUnitTestBase { + + /** + * Modules to enable. + * + * @var array + */ + public static $modules = array('menu_link'); + /** * Dummy link structure acceptable for menu_tree_data(). */ @@ -32,11 +40,11 @@ public static function getInfo() { */ public function testMenuTreeData() { $this->links = array( - 1 => new MenuLink(array('mlid' => 1, 'depth' => 1), 'menu_link'), - 2 => new MenuLink(array('mlid' => 2, 'depth' => 1), 'menu_link'), - 3 => new MenuLink(array('mlid' => 3, 'depth' => 2), 'menu_link'), - 4 => new MenuLink(array('mlid' => 4, 'depth' => 3), 'menu_link'), - 5 => new MenuLink(array('mlid' => 5, 'depth' => 1), 'menu_link'), + 1 => entity_create('menu_link', array('mlid' => 1, 'depth' => 1)), + 2 => entity_create('menu_link', array('mlid' => 2, 'depth' => 1)), + 3 => entity_create('menu_link', array('mlid' => 3, 'depth' => 2)), + 4 => entity_create('menu_link', array('mlid' => 4, 'depth' => 3)), + 5 => entity_create('menu_link', array('mlid' => 5, 'depth' => 1)), ); $tree = menu_tree_data($this->links); diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc index cefffd1..b5d5816 100644 --- a/core/modules/system/system.admin.inc +++ b/core/modules/system/system.admin.inc @@ -334,7 +334,7 @@ function theme_admin_block_content($variables) { } $output .= '
    '; foreach ($content as $item) { - $output .= '
    ' . l($item['title'], $item['href'], $item['localized_options']) . '
    '; + $output .= '
    ' . l($item['link_title'], $item['link_path'], is_object($item['localized_options']) ? $item['localized_options']->getValue() : $item['localized_options']) . '
    '; if (!$compact && isset($item['description'])) { $output .= '
    ' . filter_xss_admin($item['description']) . '
    '; } diff --git a/core/modules/system/system.install b/core/modules/system/system.install index 5222f25..b4089ca 100644 --- a/core/modules/system/system.install +++ b/core/modules/system/system.install @@ -2263,6 +2263,7 @@ function system_update_8060() { 'size' => 'big', 'not null' => FALSE, 'serialize' => TRUE, + 'initial' => 'a:0:{}', ); db_add_field('menu_links', 'route_parameters', $spec); diff --git a/core/modules/toolbar/toolbar.module b/core/modules/toolbar/toolbar.module index 43ad735..fd9e386 100644 --- a/core/modules/toolbar/toolbar.module +++ b/core/modules/toolbar/toolbar.module @@ -507,7 +507,8 @@ function toolbar_menu_navigation_links(&$tree) { } // Make sure we have a path specific ID in place, so we can attach icons // and behaviors to the items. - $tree[$key]['link']['localized_options']['attributes'] = array( + $l_options = $tree[$key]['link']['localized_options']; + $l_options['attributes'] = array( 'id' => 'toolbar-link-' . str_replace(array('/', '<', '>'), array('-', '', ''), $item['link']['link_path']), 'class' => array( 'toolbar-icon', @@ -515,6 +516,7 @@ function toolbar_menu_navigation_links(&$tree) { ), 'title' => check_plain($item['link']['description']), ); + $tree[$key]['link']['localized_options'] = $l_options; } } diff --git a/core/modules/user/user.module b/core/modules/user/user.module index 7037793..0b32697 100644 --- a/core/modules/user/user.module +++ b/core/modules/user/user.module @@ -885,13 +885,13 @@ function user_menu_link_presave(MenuLink $menu_link) { // for authenticated users. Authenticated users should see "My account", but // anonymous users should not see it at all. Therefore, invoke // user_menu_link_load() to conditionally hide the link. - if ($menu_link->link_path == 'user' && $menu_link->module == 'system') { - $menu_link->options['alter'] = TRUE; + if ($menu_link->getLinkPath() == 'user' && $menu_link->getModule() == 'system') { + //$menu_link->options['alter'] = TRUE; } // Force the Logout link to appear on the top-level of 'account' menu by // default (i.e., unless it has been customized). - if ($menu_link->link_path == 'user/logout' && $menu_link->module == 'system' && empty($menu_link->customized)) { + if ($menu_link->getLinkPath() == 'user/logout' && $menu_link->getModule() == 'system' && !$menu_link->isCustomized()) { $menu_link->plid = 0; } } @@ -913,8 +913,8 @@ function user_menu_breadcrumb_alter(&$active_trail, $item) { function user_menu_link_load($menu_links) { // Hide the "User account" link for anonymous users. foreach ($menu_links as $link) { - if ($link['link_path'] == 'user' && $link['module'] == 'system' && !$GLOBALS['user']->id()) { - $link['hidden'] = 1; + if ($link->getLinkPath() == 'user' && $link->getModule() == 'system' && $GLOBALS['user']->isAnonymous()) { + $link->hidden = 1; } } } diff --git a/core/themes/seven/seven.theme b/core/themes/seven/seven.theme index bfc87e2..6ca7f5e 100644 --- a/core/themes/seven/seven.theme +++ b/core/themes/seven/seven.theme @@ -120,7 +120,7 @@ function seven_admin_block_content($variables) { foreach ($content as $item) { $output .= '
  • '; $content = '' . filter_xss_admin($item['title']) . ''; - $options = $item['localized_options']; + $options = $item['localized_options']->getValue(); $options['html'] = TRUE; if (isset($item['description']) && !system_admin_compact_mode()) { $content .= '
    ' . filter_xss_admin($item['description']) . '
    ';