Commit 203ead62 authored by alexpott's avatar alexpott
Browse files

Issue #2418017 by Wim Leers, YesCT, dawehner, webchick, jibran, amateescu,...

Issue #2418017 by Wim Leers, YesCT, dawehner, webchick, jibran, amateescu, hussainweb: Implement autocomplete UI for the link widget
parent e6967af9
......@@ -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'
......
......@@ -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(
......
......@@ -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.
'/' => '&lt;front&gt;',
'/?example=llama' => '&lt;front&gt;?example=llama',
'/#example' => '&lt;front&gt;#example',
// @todo '<front>' is valid input for BC reasons, may be removed by
// https://www.drupal.org/node/2421941
'<front>' => '&lt;front&gt;',
'<front>#example' => '&lt;front&gt;#example',
'<front>?example=llama' =>'&lt;front&gt;?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)));
}
}
......
......@@ -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();
......
......@@ -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]' => '',
......
......@@ -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.');
}
......
......@@ -187,7 +187,7 @@ function testBreadCrumbs() {
$menu = 'tools';