diff --git a/core/misc/autocomplete.js b/core/misc/autocomplete.js index 70ccf0be5170fbe392026fd3b4b280be7d42f907..639f9dc18e5939cde9587efdc7ad1318322e870d 100644 --- a/core/misc/autocomplete.js +++ b/core/misc/autocomplete.js @@ -59,9 +59,14 @@ * @return {Boolean} */ function searchHandler(event) { - // Only search when the term is two characters or larger. + var options = autocomplete.options; var term = autocomplete.extractLastTerm(event.target.value); - return term.length >= autocomplete.minLength; + // Abort search if the first character is in firstCharacterBlacklist. + if (term.length > 0 && options.firstCharacterBlacklist.indexOf(term[0]) !== -1) { + return false; + } + // Only search when the term is at least the minimum length. + return term.length >= options.minLength; } /** @@ -174,6 +179,11 @@ // Act on textfields with the "form-autocomplete" class. var $autocomplete = $(context).find('input.form-autocomplete').once('autocomplete'); if ($autocomplete.length) { + // Allow options to be overriden per instance. + var blacklist = $autocomplete.attr('data-autocomplete-first-character-blacklist'); + $.extend(autocomplete.options, { + firstCharacterBlacklist: (blacklist) ? blacklist : '' + }); // Use jQuery UI Autocomplete on the textfield. $autocomplete.autocomplete(autocomplete.options) .data("ui-autocomplete") @@ -194,8 +204,7 @@ */ autocomplete = { cache: {}, - // Exposes methods to allow overriding by contrib. - minLength: 1, + // Exposes options to allow overriding by contrib. splitValues: autocompleteSplitValues, extractLastTerm: extractLastTerm, // jQuery UI autocomplete options. @@ -204,7 +213,10 @@ focus: focusHandler, search: searchHandler, select: selectHandler, - renderItem: renderItem + renderItem: renderItem, + minLength: 1, + // Custom options, used by Drupal.autocomplete. + firstCharacterBlacklist: '' }, ajax: { dataType: 'json' diff --git a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php index 2a25702502f51b2cdeaaa11dfd8310144bf82c7d..169204a4af6f7951c1b021f50f0f5e25c84079ab 100644 --- a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php +++ b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php @@ -9,11 +9,13 @@ use Drupal\Component\Utility\Unicode; use Drupal\Component\Utility\UrlHelper; +use Drupal\Core\Entity\Element\EntityAutocomplete; use Drupal\Core\Field\FieldItemListInterface; use Drupal\Core\Field\WidgetBase; use Drupal\Core\Form\FormStateInterface; use Drupal\Core\Url; use Drupal\link\LinkItemInterface; +use Symfony\Component\Routing\Exception\RouteNotFoundException; use Symfony\Component\Validator\ConstraintViolation; use Symfony\Component\Validator\ConstraintViolationInterface; use Symfony\Component\Validator\ConstraintViolationListInterface; @@ -42,83 +44,101 @@ public static function defaultSettings() { } /** - * Gets the URI without the 'user-path:' scheme, for display while editing. + * Gets the URI without the 'user-path:' or 'entity:' scheme. + * + * The following two forms of URIs are transformed: + * - 'entity:' URIs: to entity autocomplete ("label (entity id)") strings; + * - 'user-path:' URIs: the scheme is stripped. + * + * This method is the inverse of ::getUserEnteredStringAsUri(). * * @param string $uri * The URI to get the displayable string for. * * @return string + * + * @see static::getUserEnteredStringAsUri() */ protected static function getUriAsDisplayableString($uri) { $scheme = parse_url($uri, PHP_URL_SCHEME); + + // By default, the displayable string is the URI. + $displayable_string = $uri; + + // A different displayable string may be chosen in case of the 'user-path:' + // or 'entity:' built-in schemes. if ($scheme === 'user-path') { $uri_reference = explode(':', $uri, 2)[1]; - // @todo Present the leading slash to the user and hence delete the next - // block in https://www.drupal.org/node/2418017. There, we will also - // remove the ability to enter '<front>' or '<none>', we'll expect '/' - // and '' instead respectively. + + // @todo '<front>' is valid input for BC reasons, may be removed by + // https://www.drupal.org/node/2421941 $path = parse_url($uri, PHP_URL_PATH); if ($path === '/') { $uri_reference = '<front>' . substr($uri_reference, 1); } - elseif (empty($path)) { - $uri_reference = '<none>' . $uri_reference; - } - else { - $uri_reference = ltrim($uri_reference, '/'); - } + + $displayable_string = $uri_reference; } - else { - $uri_reference = $uri; + elseif ($scheme === 'entity') { + list($entity_type, $entity_id) = explode('/', substr($uri, 7), 2); + // Show the 'entity:' URI as the entity autocomplete would, but only if: + // - the entity could be loaded, and; + // - the current user is allowed to view the entity (otherwise we have a + // information disclosure security problem). + $entity_manager = \Drupal::entityManager(); + if ($entity_manager->getDefinition($entity_type, FALSE)) { + $entity = \Drupal::entityManager()->getStorage($entity_type)->load($entity_id); + if ($entity) { + $label = ($entity->access('view')) ? $entity->label() : t('- Restricted access -'); + $displayable_string = $label . ' (' . $entity_id . ')'; + } + } } - return $uri_reference; + + return $displayable_string; } /** * Gets the user-entered string as a URI. * - * Schemeless URIs are treated as 'user-path:' URIs. + * The following two forms of input are mapped to URIs: + * - entity autocomplete ("label (entity id)") strings: to 'entity:' URIs; + * - strings without a detectable scheme: to 'user-path:' URIs. + * + * This method is the inverse of ::getUriAsDisplayableString(). * * @param string $string * The user-entered string. * * @return string - * The URI, if a non-empty $string was passed. + * The URI, if a non-empty $uri was passed. + * + * @see static::getUriAsDisplayableString() */ protected static function getUserEnteredStringAsUri($string) { - if (!empty($string)) { - // Users can enter relative URLs, but we need a valid URI, so add an - // explicit scheme when necessary. - if (parse_url($string, PHP_URL_SCHEME) === NULL) { - // @todo Present the leading slash to the user and hence delete the next - // block in https://www.drupal.org/node/2418017. There, we will also - // remove the ability to enter '<front>' or '<none>', we'll expect '/' - // and '' instead respectively. - // Users can enter paths that don't start with a leading slash, we - // want to normalize them to have a leading slash. However, we don't - // want to add a leading slash if it already starts with one, or if it - // contains only a querystring or a fragment. Examples: - // - 'foo' -> '/foo' - // - '?foo=bar' -> '/?foo=bar' - // - '#foo' -> '/#foo' - // - '<front>' -> '/' - // - '<front>#foo' -> '/#foo' - // - '<none>' -> '' - // - '<none>#foo' -> '#foo' - if (strpos($string, '<front>') === 0) { - $string = '/' . substr($string, strlen('<front>')); - } - elseif (strpos($string, '<none>') === 0) { - $string = substr($string, strlen('<none>')); - } - elseif (!in_array($string[0], ['/', '?', '#'])) { - $string = '/' . $string; - } - - return 'user-path:' . $string; + // By default, assume the entered string is an URI. + $uri = $string; + + // Detect entity autocomplete string, map to 'entity:' URI. + $entity_id = EntityAutocomplete::extractEntityIdFromAutocompleteInput($string); + if ($entity_id !== NULL) { + // @todo Support entity types other than 'node'. Will be fixed in + // https://www.drupal.org/node/2423093. + $uri = 'entity:node/' . $entity_id; + } + // Detect a schemeless string, map to 'user-path:' URI. + elseif (!empty($string) && parse_url($string, PHP_URL_SCHEME) === NULL) { + // @todo '<front>' is valid input for BC reasons, may be removed by + // https://www.drupal.org/node/2421941 + // - '<front>' -> '/' + // - '<front>#foo' -> '/#foo' + if (strpos($string, '<front>') === 0) { + $string = '/' . substr($string, strlen('<front>')); } + $uri = 'user-path:' . $string; } - return $string; + + return $uri; } /** @@ -126,6 +146,15 @@ protected static function getUserEnteredStringAsUri($string) { */ public static function validateUriElement($element, FormStateInterface $form_state, $form) { $uri = static::getUserEnteredStringAsUri($element['#value']); + $form_state->setValueForElement($element, $uri); + + // If getUserEnteredStringAsUri() mapped the entered value is mapped to a + // 'user-path:' URI , ensure the raw value begins with '/', '?' or '#'. + // @todo '<front>' is valid input for BC reasons, may be removed by + // https://www.drupal.org/node/2421941 + if (parse_url($uri, PHP_URL_SCHEME) === 'user-path' && !in_array($element['#value'][0], ['/', '?', '#'], TRUE) && substr($element['#value'], 0, 7) !== '<front>') { + $form_state->setError($element, t('Manually entered paths should start with /, ? or #.')); + } // If the URI is empty or not well-formed, the link field type's validation // constraint will detect it. @@ -140,6 +169,15 @@ public static function validateUriElement($element, FormStateInterface $form_sta $disallowed = $disallowed || (!\Drupal::currentUser()->hasPermission('link to any page') && !$url->access()); // Disallow external URLs using untrusted protocols. $disallowed = $disallowed || ($url->isExternal() && !in_array(parse_url($uri, PHP_URL_SCHEME), UrlHelper::getAllowedProtocols())); + // Disallow routed URLs that don't exist. + if (!$disallowed && $url->isRouted()) { + try { + $url->toString(); + } + catch (RouteNotFoundException $e) { + $disallowed = TRUE; + } + } if ($disallowed) { $form_state->setError($element, t("The path '@link_path' is either invalid or you do not have access to it.", ['@link_path' => static::getUriAsDisplayableString($uri)])); @@ -170,18 +208,23 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen // If the field is configured to support internal links, it cannot use the // 'url' form element and we have to do the validation ourselves. if ($this->supportsInternalLinks()) { - $element['uri']['#type'] = 'textfield'; + $element['uri']['#type'] = 'entity_autocomplete'; + // @todo The user should be able to select an entity type. Will be fixed + // in https://www.drupal.org/node/2423093. + $element['uri']['#target_type'] = 'node'; + // Disable autocompletion when the first character is '/', '#' or '?'. + $element['uri']['#attributes']['data-autocomplete-first-character-blacklist'] = '/#?'; } // If the field is configured to allow only internal links, add a useful // element prefix. if (!$this->supportsExternalLinks()) { - $element['uri']['#field_prefix'] = \Drupal::url('<front>', array(), array('absolute' => TRUE)); + $element['uri']['#field_prefix'] = rtrim(\Drupal::url('<front>', array(), array('absolute' => TRUE)), '/'); } // If the field is configured to allow both internal and external links, // show a useful description. elseif ($this->supportsExternalLinks() && $this->supportsInternalLinks()) { - $element['uri']['#description'] = $this->t('This can be an internal path such as %add-node or an external URL such as %url. Enter %front to link to the front page.', array('%front' => '<front>', '%add-node' => 'node/add', '%url' => 'http://example.com')); + $element['uri']['#description'] = $this->t('Start typing the title of a piece of content to select it. You can also enter an internal path such as %add-node or an external URL such as %url. Enter %front to link to the front page.', array('%front' => '<front>', '%add-node' => '/node/add', '%url' => 'http://example.com')); } $element['title'] = array( diff --git a/core/modules/link/src/Tests/LinkFieldTest.php b/core/modules/link/src/Tests/LinkFieldTest.php index 05a427e4747a0810d58267e6271daca46e195e2f..42ee4a3fd83719f3efe4f463feaa2b077195f3b1 100644 --- a/core/modules/link/src/Tests/LinkFieldTest.php +++ b/core/modules/link/src/Tests/LinkFieldTest.php @@ -25,7 +25,7 @@ class LinkFieldTest extends WebTestBase { * * @var array */ - public static $modules = ['entity_test', 'link']; + public static $modules = ['entity_test', 'link', 'node']; /** * A field to use in this test class. @@ -93,27 +93,61 @@ function testURLValidation() { // Create a path alias. \Drupal::service('path.alias_storage')->save('admin', 'a/path/alias'); - // Define some valid URLs. + + // Create a node to test the link widget. + $node = $this->drupalCreateNode(); + + // Define some valid URLs (keys are the entered values, values are the + // strings displayed to the user). $valid_external_entries = array( - 'http://www.example.com/', + 'http://www.example.com/' => 'http://www.example.com/', ); $valid_internal_entries = array( - 'entity_test/add', - 'a/path/alias', - 'entity:user/1', + '/entity_test/add' => '/entity_test/add', + '/a/path/alias' => '/a/path/alias', + + // Front page, with query string and fragment. + '/' => '<front>', + '/?example=llama' => '<front>?example=llama', + '/#example' => '<front>#example', + + // @todo '<front>' is valid input for BC reasons, may be removed by + // https://www.drupal.org/node/2421941 + '<front>' => '<front>', + '<front>#example' => '<front>#example', + '<front>?example=llama' =>'<front>?example=llama', + + // Query string and fragment. + '?example=llama' => '?example=llama', + '#example' => '#example', + + // Entity reference autocomplete value. + $node->label() . ' (1)' => $node->label() . ' (1)', + // Entity URI displayed as ER autocomplete value when displayed in a form. + 'entity:node/1' => $node->label() . ' (1)', + // URI for an entity that exists, but is not accessible by the user. + 'entity:user/1' => '- Restricted access - (1)', + // URI for an entity that doesn't exist, but with a valid ID. + 'entity:user/999999' => 'entity:user/999999', + // URI for an entity that doesn't exist, with an invalid ID. + 'entity:user/invalid-parameter' => 'entity:user/invalid-parameter', ); // Define some invalid URLs. + $validation_error_1 = "The path '@link_path' is either invalid or you do not have access to it."; + $validation_error_2 = 'Manually entered paths should start with /, ? or #.'; $invalid_external_entries = array( // Missing protcol - 'not-an-url', + 'not-an-url' => $validation_error_2, // Invalid protocol - 'invalid://not-a-valid-protocol', + 'invalid://not-a-valid-protocol' => $validation_error_1, // Missing host name - 'http://', + 'http://' => $validation_error_1, ); $invalid_internal_entries = array( - 'non/existing/path', + '/non/existing/path' => $validation_error_1, + 'no-leading-slash' => $validation_error_2, + 'entity:non_existing_entity_type/yar' => $validation_error_1, ); // Test external and internal URLs for 'link_type' = LinkItemInterface::LINK_GENERIC. @@ -142,15 +176,15 @@ function testURLValidation() { * An array of valid URL entries. */ protected function assertValidEntries($field_name, array $valid_entries) { - foreach ($valid_entries as $value) { + foreach ($valid_entries as $uri => $string) { $edit = array( - "{$field_name}[0][uri]" => $value, + "{$field_name}[0][uri]" => $uri, ); $this->drupalPostForm('entity_test/add', $edit, t('Save')); preg_match('|entity_test/manage/(\d+)|', $this->url, $match); $id = $match[1]; $this->assertText(t('entity_test @id has been created.', array('@id' => $id))); - $this->assertRaw($value); + $this->assertRaw($string); } } @@ -163,12 +197,12 @@ protected function assertValidEntries($field_name, array $valid_entries) { * An array of invalid URL entries. */ protected function assertInvalidEntries($field_name, array $invalid_entries) { - foreach ($invalid_entries as $invalid_value) { + foreach ($invalid_entries as $invalid_value => $error_message) { $edit = array( "{$field_name}[0][uri]" => $invalid_value, ); $this->drupalPostForm('entity_test/add', $edit, t('Save')); - $this->assertText(t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $invalid_value))); + $this->assertText(t($error_message, array('@link_path' => $invalid_value))); } } diff --git a/core/modules/menu_ui/src/Tests/MenuLanguageTest.php b/core/modules/menu_ui/src/Tests/MenuLanguageTest.php index 4d386185768a819363ef9b31226f40bc0aa70f93..59b045871aa390b0e9453eeb944f922498ff0ed1 100644 --- a/core/modules/menu_ui/src/Tests/MenuLanguageTest.php +++ b/core/modules/menu_ui/src/Tests/MenuLanguageTest.php @@ -65,7 +65,7 @@ function testMenuLanguage() { $this->assertOptionSelected('edit-langcode', $edit['langcode'], 'The menu language was correctly selected.'); // Test menu link language. - $link_path = '<front>'; + $link_path = '/'; // Add a menu link. $link_title = $this->randomString(); diff --git a/core/modules/menu_ui/src/Tests/MenuTest.php b/core/modules/menu_ui/src/Tests/MenuTest.php index a994bb4771a66a737cfdbbc146a9403f35aa3b59..d3897cdb0c729be831ea279d6f925f021aad69a0 100644 --- a/core/modules/menu_ui/src/Tests/MenuTest.php +++ b/core/modules/menu_ui/src/Tests/MenuTest.php @@ -266,7 +266,7 @@ function doMenuTests() { $this->clickLink(t('Add link')); $link_title = $this->randomString(); - $this->drupalPostForm(NULL, array('link[0][uri]' => '<front>', 'title[0][value]' => $link_title), t('Save')); + $this->drupalPostForm(NULL, array('link[0][uri]' => '/', 'title[0][value]' => $link_title), t('Save')); $this->assertUrl(Url::fromRoute('entity.menu.edit_form', ['menu' => $menu_name])); // Test the 'Edit' operation. $this->clickLink(t('Edit')); @@ -301,9 +301,9 @@ function doMenuTests() { $this->doMenuLinkFormDefaultsTest(); // Add menu links. - $item1 = $this->addMenuLink('', 'node/' . $node1->id(), $menu_name, TRUE); - $item2 = $this->addMenuLink($item1->getPluginId(), 'node/' . $node2->id(), $menu_name, FALSE); - $item3 = $this->addMenuLink($item2->getPluginId(), 'node/' . $node3->id(), $menu_name); + $item1 = $this->addMenuLink('', '/node/' . $node1->id(), $menu_name, TRUE); + $item2 = $this->addMenuLink($item1->getPluginId(), '/node/' . $node2->id(), $menu_name, FALSE); + $item3 = $this->addMenuLink($item2->getPluginId(), '/node/' . $node3->id(), $menu_name); // Hierarchy // <$menu_name> @@ -337,10 +337,10 @@ function doMenuTests() { $this->verifyMenuLink($item3, $node3, $item2, $node2); // Add more menu links. - $item4 = $this->addMenuLink('', 'node/' . $node4->id(), $menu_name); - $item5 = $this->addMenuLink($item4->getPluginId(), 'node/' . $node5->id(), $menu_name); + $item4 = $this->addMenuLink('', '/node/' . $node4->id(), $menu_name); + $item5 = $this->addMenuLink($item4->getPluginId(), '/node/' . $node5->id(), $menu_name); // Create a menu link pointing to an alias. - $item6 = $this->addMenuLink($item4->getPluginId(), 'node5', $menu_name, TRUE, '0'); + $item6 = $this->addMenuLink($item4->getPluginId(), '/node5', $menu_name, TRUE, '0'); // Hierarchy // <$menu_name> @@ -427,7 +427,7 @@ function doMenuTests() { // item's weight doesn't get changed because of the old hardcoded delta=50. $items = array(); for ($i = -50; $i <= 51; $i++) { - $items[$i] = $this->addMenuLink('', 'node/' . $node1->id(), $menu_name, TRUE, strval($i)); + $items[$i] = $this->addMenuLink('', '/node/' . $node1->id(), $menu_name, TRUE, strval($i)); } $this->assertMenuLink($items[51]->getPluginId(), array('weight' => '51')); @@ -454,7 +454,7 @@ function doMenuTests() { $this->assertMenuLink($item7->getPluginId(), array('url' => 'http://drupal.org')); // Add <front> menu item. - $item8 = $this->addMenuLink('', '<front>', $menu_name); + $item8 = $this->addMenuLink('', '/', $menu_name); $this->assertMenuLink($item8->getPluginId(), array('route_name' => '<front>')); $this->drupalGet(''); $this->assertResponse(200); @@ -494,20 +494,19 @@ function testMenuQueryAndFragment() { $this->drupalLogin($this->adminUser); // Make a path with query and fragment on. - $path = 'test-page?arg1=value1&arg2=value2'; + $path = '/test-page?arg1=value1&arg2=value2'; $item = $this->addMenuLink('', $path); $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit'); $this->assertFieldByName('link[0][uri]', $path, 'Path is found with both query and fragment.'); // Now change the path to something without query and fragment. - $path = 'test-page'; + $path = '/test-page'; $this->drupalPostForm('admin/structure/menu/item/' . $item->id() . '/edit', array('link[0][uri]' => $path), t('Save')); $this->drupalGet('admin/structure/menu/item/' . $item->id() . '/edit'); $this->assertFieldByName('link[0][uri]', $path, 'Path no longer has query or fragment.'); - // Use <front>#fragment and ensure that saving it does not loose its - // content. + // Use <front>#fragment and ensure that saving it does not lose its content. $path = '<front>?arg1=value#fragment'; $item = $this->addMenuLink('', $path); @@ -547,7 +546,7 @@ function testUnpublishedNodeMenuItem() { 'status' => NODE_NOT_PUBLISHED, )); - $item = $this->addMenuLink('', 'node/' . $node->id()); + $item = $this->addMenuLink('', '/node/' . $node->id()); $this->modifyMenuLink($item); // Test that a user with 'administer menu' but without 'bypass node access' @@ -564,7 +563,7 @@ function testUnpublishedNodeMenuItem() { public function testBlockContextualLinks() { $this->drupalLogin($this->drupalCreateUser(array('administer menu', 'access contextual links', 'administer blocks'))); $custom_menu = $this->addCustomMenu(); - $this->addMenuLink('', '<front>', $custom_menu->id()); + $this->addMenuLink('', '/', $custom_menu->id()); $block = $this->drupalPlaceBlock('system_menu_block:' . $custom_menu->id(), array('label' => 'Custom menu', 'provider' => 'system')); $this->drupalGet('test-page'); @@ -606,7 +605,7 @@ public function testBlockContextualLinks() { * @return \Drupal\menu_link_content\Entity\MenuLinkContent * A menu link entity. */ - function addMenuLink($parent = '', $path = '<front>', $menu_name = 'tools', $expanded = FALSE, $weight = '0') { + function addMenuLink($parent = '', $path = '/', $menu_name = 'tools', $expanded = FALSE, $weight = '0') { // View add menu link page. $this->drupalGet("admin/structure/menu/manage/$menu_name/add"); $this->assertResponse(200); @@ -640,7 +639,7 @@ function addMenuLink($parent = '', $path = '<front>', $menu_name = 'tools', $exp * Attempts to add menu link with invalid path or no access permission. */ function addInvalidMenuLink() { - foreach (array('-&-', 'admin/people/permissions') as $link_path) { + foreach (array('/-&-', '/admin/people/permissions') as $link_path) { $edit = array( 'link[0][uri]' => $link_path, 'title[0][value]' => 'title', @@ -666,7 +665,7 @@ function checkInvalidParentMenuLinks() { $parent = $last_link ? 'tools:' . $last_link->getPluginId() : 'tools:'; $title = 'title' . $i; $edit = array( - 'link[0][uri]' => '<front>', + 'link[0][uri]' => '/', 'title[0][value]' => $title, 'menu_parent' => $parent, 'description[0][value]' => '', diff --git a/core/modules/shortcut/src/Tests/ShortcutLinksTest.php b/core/modules/shortcut/src/Tests/ShortcutLinksTest.php index 677e471d57b2b333359067d48d0d96a73c4482e1..47fbe8131861ea8520efad5933f5f698a4e7632a 100644 --- a/core/modules/shortcut/src/Tests/ShortcutLinksTest.php +++ b/core/modules/shortcut/src/Tests/ShortcutLinksTest.php @@ -41,18 +41,18 @@ public function testShortcutLinkAdd() { // Create some paths to test. $test_cases = [ - '<front>', - 'admin', - 'admin/config/system/site-information', - 'node/' . $this->node->id() . '/edit', - $path['alias'], - 'router_test/test2', - 'router_test/test3/value', + '/', + '/admin', + '/admin/config/system/site-information', + '/node/' . $this->node->id() . '/edit', + '/' . $path['alias'], + '/router_test/test2', + '/router_test/test3/value', ]; $test_cases_non_access = [ - 'admin', - 'admin/config/system/site-information', + '/admin', + '/admin/config/system/site-information', ]; // Check that each new shortcut links where it should. @@ -66,7 +66,7 @@ public function testShortcutLinkAdd() { $this->assertResponse(200); $saved_set = ShortcutSet::load($set->id()); $paths = $this->getShortcutInformation($saved_set, 'link'); - $this->assertTrue(in_array('user-path:/' . ($test_path == '<front>' ? '' : $test_path), $paths), 'Shortcut created: ' . $test_path); + $this->assertTrue(in_array('user-path:' . $test_path, $paths), 'Shortcut created: ' . $test_path); if (in_array($test_path, $test_cases_non_access)) { $this->assertNoLink($title, String::format('Shortcut link %url not accessible on the page.', ['%url' => $test_path])); @@ -92,15 +92,15 @@ public function testShortcutLinkAdd() { $title = $this->randomMachineName(); $form_data = [ 'title[0][value]' => $title, - 'link[0][uri]' => 'admin', + 'link[0][uri]' => '/admin', ]; $this->drupalPostForm('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link', $form_data, t('Save')); $this->assertResponse(200); - $this->assertRaw(t("The path '@link_path' is either invalid or you do not have access to it.", ['@link_path' => 'admin'])); + $this->assertRaw(t("The path '@link_path' is either invalid or you do not have access to it.", ['@link_path' => '/admin'])); $form_data = [ 'title[0][value]' => $title, - 'link[0][uri]' => 'node', + 'link[0][uri]' => '/node', ]; $this->drupalPostForm('admin/config/user-interface/shortcut/manage/' . $set->id() . '/add-link', $form_data, t('Save')); $this->assertLink($title, 0, 'Shortcut link found on the page.'); @@ -147,7 +147,7 @@ public function testShortcutLinkRename() { $shortcuts = $set->getShortcuts(); $shortcut = reset($shortcuts); - $this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $shortcut->id(), array('title[0][value]' => $new_link_name, 'link[0][uri]' => $shortcut->link->uri), t('Save')); + $this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $shortcut->id(), array('title[0][value]' => $new_link_name), t('Save')); $saved_set = ShortcutSet::load($set->id()); $titles = $this->getShortcutInformation($saved_set, 'title'); $this->assertTrue(in_array($new_link_name, $titles), 'Shortcut renamed: ' . $new_link_name); @@ -161,14 +161,14 @@ public function testShortcutLinkChangePath() { $set = $this->set; // Tests changing a shortcut path. - $new_link_path = 'admin/config'; + $new_link_path = '/admin/config'; $shortcuts = $set->getShortcuts(); $shortcut = reset($shortcuts); $this->drupalPostForm('admin/config/user-interface/shortcut/link/' . $shortcut->id(), array('title[0][value]' => $shortcut->getTitle(), 'link[0][uri]' => $new_link_path), t('Save')); $saved_set = ShortcutSet::load($set->id()); $paths = $this->getShortcutInformation($saved_set, 'link'); - $this->assertTrue(in_array('user-path:/' . $new_link_path, $paths), 'Shortcut path changed: ' . $new_link_path); + $this->assertTrue(in_array('user-path:' . $new_link_path, $paths), 'Shortcut path changed: ' . $new_link_path); $this->assertLinkByHref($new_link_path, 0, 'Shortcut with new path appears on the page.'); } diff --git a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php index ab06c99f87330cd3ca2c7364e16a57260e2483ad..175509197b9a77f419c039b4795e0207e11a882f 100644 --- a/core/modules/system/src/Tests/Menu/BreadcrumbTest.php +++ b/core/modules/system/src/Tests/Menu/BreadcrumbTest.php @@ -187,7 +187,7 @@ function testBreadCrumbs() { $menu = 'tools'; $edit = array( 'title[0][value]' => 'Root', - 'link[0][uri]' => 'node', + 'link[0][uri]' => '/node', ); $this->drupalPostForm("admin/structure/menu/manage/$menu/add", $edit, t('Save')); $menu_links = entity_load_multiple_by_properties('menu_link_content', array('title' => 'Root')); @@ -240,7 +240,7 @@ function testBreadCrumbs() { $term = $data['term']; $edit = array( 'title[0][value]' => "$name link", - 'link[0][uri]' => "taxonomy/term/{$term->id()}", + 'link[0][uri]' => "/taxonomy/term/{$term->id()}", 'menu_parent' => "$menu:{$parent_mlid}", 'enabled[value]' => 1, ); diff --git a/core/modules/system/system.routing.yml b/core/modules/system/system.routing.yml index 7c87bb92ccb501ea8d16371f6b4628b83969f21c..fecfd249fa207d1d1232cefa2d980dc2774b6040 100644 --- a/core/modules/system/system.routing.yml +++ b/core/modules/system/system.routing.yml @@ -382,7 +382,8 @@ system.theme_settings_theme: path: '' options: _only_fragment: TRUE - + requirements: + _access: 'TRUE' '<current>': path: '<current>'