diff --git a/core/modules/link/link.install b/core/modules/link/link.install new file mode 100644 index 0000000..0153aed --- /dev/null +++ b/core/modules/link/link.install @@ -0,0 +1,69 @@ +getDefinitions(); + $sandbox['max'] = 0; + foreach ($entity_types as $entity_type) { + if ($entity_type->isSubclassOf('\Drupal\Core\Entity\FieldableEntityInterface')) { + // Throw all entities in the queue. + $sandbox['current_entity'] = 0; + $sandbox['current_entity_index'] = 0; + $query = \Drupal::entityQuery($entity_type->id()); + $sandbox['entities'][] = [ + 'entity' => $entity_type->id(), + 'ids' => $query->execute(), + ]; + } + } + } + $page_size = 20; + $entities_to_process = array_slice($sandbox['entities'][$sandbox['current_entity']]['ids'], $sandbox['current_entity_index'], $page_size); + $entity_type_name = $sandbox['entities'][$sandbox['current_entity']]['entity']; + $entities_loaded = entity_load_multiple($entity_type_name, $entities_to_process); + /** @var \Drupal\Core\Entity\FieldableEntityInterface $entity */ + foreach ($entities_loaded as $entity) { + // See if it has a link field. + $fields = $entity->getFields(FALSE); + /** @var \Drupal\Core\Field\FieldItemListInterface $field */ + foreach ($fields as $field) { + $item_definition = $field->getItemDefinition(); + if ($item_definition->getDataType() == 'field_item:link') { + // Try to run the validation. + /** @var \Drupal\Core\Field\FieldItemListInterface $a */ + foreach ($entity->get($field->getName())->getValue() as $value) { + if (!LinkWidget::urlWillProcessAsUserEntered(LinkWidget::getUriAsDisplayableString($value['uri']))) { + drupal_set_message(t('The @type entity with the id @id has a field that will be displayed differently than the user input suggests. If this is unintended, please verify your settings for this entity.', [ + '@type' => $entity_type_name, + '@id' => $entity->id(), + ]), 'error'); + } + } + } + } + + } + //print_r($sandbox); + $sandbox['current_entity_index'] += $page_size; + if ($sandbox['current_entity_index'] > count($sandbox['entities'][$sandbox['current_entity']])) { + $sandbox['current_entity_index'] = 0; + $sandbox['current_entity']++; + } + $sandbox['#finished'] = $sandbox['current_entity'] / count($sandbox['entities']); + if ($sandbox['#finished'] >= 1) { + return t('Links validated. Please see output for results.'); + } +} \ No newline at end of file diff --git a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php index 0f88f5e..b7ed540 100644 --- a/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php +++ b/core/modules/link/src/Plugin/Field/FieldWidget/LinkWidget.php @@ -50,7 +50,7 @@ public static function defaultSettings() { * * @see static::getUserEnteredStringAsUri() */ - protected static function getUriAsDisplayableString($uri) { + public static function getUriAsDisplayableString($uri) { $scheme = parse_url($uri, PHP_URL_SCHEME); // By default, the displayable string is the URI. @@ -126,6 +126,76 @@ protected static function getUserEnteredStringAsUri($string) { } /** + * Checks if a user entered link will end up being processed as entered. + * + * One example is a user entering /en on a multilingual page and expecting the + * link to resolve to the English front page, while it in practice will + * resolve to the front page in the current language. + * + * @param string $user_link + * A user entered string, like the one entered in the widget. + * + * @return bool + * Whether the URL will resolve as the user entered it or not. + */ + public static function urlWillProcessAsUserEntered($user_link) { + $language_manager = \Drupal::languageManager(); + // Create a URL object from that. + $user_url = \Drupal::pathValidator()->getUrlIfValid($user_link); + if ($user_url) { + // If the user entered the special case "" we must allow this + // to not correspond to what Drupal thinks the URL is. + $base_path = \Drupal::service('request_stack') + ->getCurrentRequest() + ->getBasePath(); + if (strpos($user_link, '') === 0) { + // Need to append the '/' for comparison. + $user_link = str_replace('', $base_path . '/', $user_link); + } + $user_link = $base_path . $user_link; + // Compare the generated link with what Drupal thinks this is. We want + // to do this without language prefixes, if applicable. We also strip + // the fragments and query parameters, because those can confuse path + // resolving, and make for false errors. + $user_url->setOptions(['language' => $language_manager->getDefaultLanguage()]); + // Also, we want to allow the user to enter an aliased or non-aliased + // path. + $display_url = $user_url->toString(); + // On update.php this will end up being prefixed with update.php. + if (strpos($display_url, '/update.php') === 0) { + $display_url = str_replace('/update.php', '', $display_url); + } + $non_aliased_path_search = $display_url; + // Remove the base path to search for the alias. And for displaying to + // the user, if it comes to that. + if ($base_path) { + $display_url = substr_replace($display_url, '', 0, strlen($base_path)); + $non_aliased_path_search = substr_replace($non_aliased_path_search, '', 0, strlen($base_path)); + } + // Prepend back the base path after resolving the alias. + $non_aliased_path = $base_path . \Drupal::service('path.alias_manager') + ->getPathByAlias($non_aliased_path_search); + // Some times, for example in our tests, the front page can be set to + // something else than "/". In that case, our validator will tell the + // user that the link is not valid because the link resolves to + // something else than "/". So, make sure "/" is fine, if it resolves + // to the front page. + if (parse_url($user_link, PHP_URL_PATH) == $base_path . '/' || parse_url($user_link, PHP_URL_PATH) == $base_path . '') { + // Compare to front page config. + $front = \Drupal::config('system.site')->get('page.front'); + if ($user_url->toString() == $base_path . $front) { + // Just change the comparison user link. This will be allowed. + $user_link = $user_url->toString(); + } + } + if (strpos($user_link, $user_url->toString()) !== 0 && strpos($user_link, $non_aliased_path) !== 0) { + return FALSE; + } + } + return TRUE; + } + + /** * Form element validation handler for the 'uri' element. * * Disallows saving inaccessible or untrusted URLs. @@ -143,65 +213,17 @@ public static function validateUriElement($element, FormStateInterface $form_sta $form_state->setError($element, t('Manually entered paths should start with /, ? or #.')); return; } + $user_link = $element['#value']; // If the URL is internal, we want to check that the link will resolve in // a way the user expects. - $language_manager = \Drupal::languageManager(); - $language = $language_manager->getLanguage($form_state->getValue('langcode')[0]['value']); - $link_url = Url::fromUri($uri, [ - 'language' => $language, - ]); // Find the user input and validate that no URL negotiator will end up // changing this URL to something else. For example if a user is // entering a URL with a language prefix. - $user_link = $element['#value']; - // Create a URL object from that. - $user_url = \Drupal::pathValidator()->getUrlIfValid($user_link); - if ($user_url) { - // If the user entered the special case "" we must allow this - // to not correspond to what Drupal thinks the URL is. - $base_path = \Drupal::service('request_stack') - ->getCurrentRequest() - ->getBasePath(); - if (strpos($user_link, '') === 0) { - // Need to append the '/' for comparison. - $user_link = str_replace('', $base_path . '/', $user_link); - } - $user_link = $base_path . $user_link; - // Compare the generated link with what Drupal thinks this is. We want - // to do this without language prefixes, if applicable. We also strip - // the fragments and query parameters, because those can confuse path - // resolving, and make for false errors. - $user_url->setOptions(['language' => $language_manager->getDefaultLanguage()]); - // Also, we want to allow the user to enter an aliased or non-aliased - // path. - $display_url = $user_url->toString(); - $non_aliased_path_search = $user_url->toString(); - // Remove the base path to search for the alias. And for displaying to - // the user, if it comes to that. - if ($base_path) { - $display_url = substr_replace($display_url, '', 0, strlen($base_path)); - $non_aliased_path_search = substr_replace($non_aliased_path_search, '', 0, strlen($base_path)); - } - // Prepend back the base path after resolving the alias. - $non_aliased_path = $base_path . \Drupal::service('path.alias_manager')->getPathByAlias($non_aliased_path_search); - // Some times, for example in our tests, the front page can be set to - // something else than "/". In that case, our validator will tell the - // user that the link is not valid because the link resolves to - // something else than "/". So, make sure "/" is fine, if it resolves - // to the front page. - if (parse_url($user_link, PHP_URL_PATH) == $base_path . '/' || parse_url($user_link, PHP_URL_PATH) == $base_path . '') { - // Compare to front page config. - $front = \Drupal::config('system.site')->get('page.front'); - if ($user_url->toString() == $base_path . $front) { - // Just change the comparison user link. This will be allowed. - $user_link = $user_url->toString(); - } - } - if (strpos($user_link, $user_url->toString()) !== 0 && strpos($user_link, $non_aliased_path) !== 0) { - $form_state->setError($element, t('The link you entered is not valid. One reason can be that you entered a link with a language prefix. Did you for example mean to link to @url?', [ - '@url' => $display_url, - ])); - } + if (!static::urlWillProcessAsUserEntered($user_link)) { + $display_url = \Drupal::pathValidator()->getUrlIfValid($user_link)->toString(); + $form_state->setError($element, t('The link you entered is not valid. One reason can be that you entered a link with a language prefix. Did you for example mean to link to @url?', [ + '@url' => $display_url, + ])); } } } @@ -257,13 +279,19 @@ public function formElement(FieldItemListInterface $items, $delta, array $elemen // If the field is configured to allow only internal links, add a useful // element prefix. + $language_manager = \Drupal::languageManager(); if (!$this->supportsExternalLinks()) { $element['uri']['#field_prefix'] = rtrim(\Drupal::url('', array(), array('absolute' => TRUE)), '/'); + // If the site is multilingual, warn the user that language prefixes will + // be stripped off, and the link will not be saved. + if ($language_manager->isMultilingual()) { + $element['uri']['#description'] = t('Be aware that it is not possible to save internal links with language prefixes.'); + } } // 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('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' => '', '%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. Be aware that it is not possible to save internal links with language prefixes.', array('%front' => '', '%add-node' => '/node/add', '%url' => 'http://example.com')); } // If the field is configured to allow only external links, show a useful // description.