Issue #2054011 by amateescu, pwolanin, dawehner, YesCT, tim.plunkett, blueminds, Berdir, das-peter, effulgentsia: Implement built-in support for internal URLs.

8.0.x
Dries 2014-04-01 14:55:13 -04:00
parent 4e5b0480f4
commit bf4a582850
16 changed files with 567 additions and 152 deletions

View File

@ -309,7 +309,7 @@ services:
- [setContext, ['@?router.request_context']]
link_generator:
class: Drupal\Core\Utility\LinkGenerator
arguments: ['@url_generator', '@module_handler', '@path.alias_manager.cached']
arguments: ['@url_generator', '@module_handler']
router.dynamic:
class: Symfony\Cmf\Component\Routing\DynamicRouter
arguments: ['@router.request_context', '@router.matcher', '@url_generator']

View File

@ -83,7 +83,7 @@ interface UrlGeneratorInterface extends VersatileGeneratorInterface {
public function generateFromPath($path = NULL, $options = array());
/**
* Gets the internal path of a route.
* Gets the internal path (system path) of a route.
*
* @param string $name
* The route name.

View File

@ -169,7 +169,7 @@ class Url extends DependencySerialization {
$this->path = $this->routeName;
// Set empty route name and parameters.
$this->routeName = '';
$this->routeName = NULL;
$this->routeParameters = array();
return $this;
@ -188,8 +188,15 @@ class Url extends DependencySerialization {
* Returns the route name.
*
* @return string
*
* @throws \UnexpectedValueException.
* If this is an external URL with no corresponding route.
*/
public function getRouteName() {
if ($this->isExternal()) {
throw new \UnexpectedValueException('External URLs do not have an internal route name.');
}
return $this->routeName;
}
@ -197,8 +204,15 @@ class Url extends DependencySerialization {
* Returns the route parameters.
*
* @return array
*
* @throws \UnexpectedValueException.
* If this is an external URL with no corresponding route.
*/
public function getRouteParameters() {
if ($this->isExternal()) {
throw new \UnexpectedValueException('External URLs do not have internal route parameters.');
}
return $this->routeParameters;
}
@ -290,6 +304,25 @@ class Url extends DependencySerialization {
return $this;
}
/**
* Returns the external path of the URL.
*
* Only to be used if self::$external is TRUE.
*
* @return string
* The external path.
*
* @throws \UnexpectedValueException
* Thrown when the path was requested for an internal URL.
*/
public function getPath() {
if (!$this->isExternal()) {
throw new \UnexpectedValueException('Internal URLs do not have external paths.');
}
return $this->path;
}
/**
* Sets the absolute value for this Url.
*
@ -308,7 +341,7 @@ class Url extends DependencySerialization {
*/
public function toString() {
if ($this->isExternal()) {
return $this->urlGenerator()->generateFromPath($this->path, $this->getOptions());
return $this->urlGenerator()->generateFromPath($this->getPath(), $this->getOptions());
}
return $this->urlGenerator()->generateFromRoute($this->getRouteName(), $this->getRouteParameters(), $this->getOptions());
@ -321,11 +354,19 @@ class Url extends DependencySerialization {
* An associative array containing all the properties of the route.
*/
public function toArray() {
return array(
'route_name' => $this->getRouteName(),
'route_parameters' => $this->getRouteParameters(),
'options' => $this->getOptions(),
);
if ($this->isExternal()) {
return array(
'path' => $this->getPath(),
'options' => $this->getOptions(),
);
}
else {
return array(
'route_name' => $this->getRouteName(),
'route_parameters' => $this->getRouteParameters(),
'options' => $this->getOptions(),
);
}
}
/**
@ -335,24 +376,38 @@ class Url extends DependencySerialization {
* An associative array suitable for a render array.
*/
public function toRenderArray() {
return array(
'#route_name' => $this->getRouteName(),
'#route_parameters' => $this->getRouteParameters(),
'#options' => $this->getOptions(),
);
if ($this->isExternal()) {
return array(
'#href' => $this->getPath(),
'#options' => $this->getOptions(),
);
}
else {
return array(
'#route_name' => $this->getRouteName(),
'#route_parameters' => $this->getRouteParameters(),
'#options' => $this->getOptions(),
);
}
}
/**
* Returns the internal path for this route.
* Returns the internal path (system path) for this route.
*
* This path will not include any prefixes, fragments, or query strings.
*
* @return string
* The internal path for this route.
*
* @throws \UnexpectedValueException.
* If this is an external URL with no corresponding system path.
*
* @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0.
* System paths should not be used - use route names and parameters.
*/
public function getInternalPath() {
if ($this->isExternal()) {
throw new \Exception('External URLs do not have internal representations.');
throw new \UnexpectedValueException('External URLs do not have internal representations.');
}
return $this->urlGenerator()->getPathFromRoute($this->getRouteName(), $this->getRouteParameters());
}

View File

@ -34,13 +34,6 @@ class LinkGenerator implements LinkGeneratorInterface {
*/
protected $moduleHandler;
/**
* The path alias manager.
*
* @var \Drupal\Core\Path\AliasManagerInterface
*/
protected $aliasManager;
/**
* Constructs a LinkGenerator instance.
*
@ -48,13 +41,10 @@ class LinkGenerator implements LinkGeneratorInterface {
* The url generator.
* @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
* The module handler.
* @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
* The path alias manager.
*/
public function __construct(UrlGeneratorInterface $url_generator, ModuleHandlerInterface $module_handler, AliasManagerInterface $alias_manager) {
public function __construct(UrlGeneratorInterface $url_generator, ModuleHandlerInterface $module_handler) {
$this->urlGenerator = $url_generator;
$this->moduleHandler = $module_handler;
$this->aliasManager = $alias_manager;
}
/**
@ -107,8 +97,8 @@ class LinkGenerator implements LinkGeneratorInterface {
// Add a "data-drupal-link-system-path" attribute to let the
// drupal.active-link library know the path in a standardized manner.
if (!isset($variables['options']['attributes']['data-drupal-link-system-path'])) {
$path = $url->getInternalPath();
$variables['options']['attributes']['data-drupal-link-system-path'] = $this->aliasManager->getSystemPath($path);
// @todo System path is deprecated - use the route name and parameters.
$variables['options']['attributes']['data-drupal-link-system-path'] = $url->getInternalPath();
}
}

View File

@ -0,0 +1,40 @@
<?php
/**
* @file
* Contains \Drupal\link\LinkItemInterface.
*/
namespace Drupal\link;
use Drupal\Core\Field\FieldItemInterface;
/**
* Defines an interface for the link field item.
*/
interface LinkItemInterface extends FieldItemInterface {
/**
* Specifies whether the field supports only internal URLs.
*/
const LINK_INTERNAL = 0x01;
/**
* Specifies whether the field supports only external URLs.
*/
const LINK_EXTERNAL = 0x10;
/**
* Specifies whether the field supports both internal and external URLs.
*/
const LINK_GENERIC = 0x11;
/**
* Determines if a link is external.
*
* @return bool
* TRUE if the link is external, FALSE otherwise.
*/
public function isExternal();
}

View File

@ -7,11 +7,11 @@
namespace Drupal\link\Plugin\Field\FieldFormatter;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Component\Utility\String;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\FormatterBase;
use Drupal\Core\Url;
use Drupal\link\LinkItemInterface;
/**
* Plugin implementation of the 'link' formatter.
@ -128,7 +128,8 @@ class LinkFormatter extends FormatterBase {
foreach ($items as $delta => $item) {
// By default use the full URL as the link text.
$link_title = $item->url;
$url = $this->buildUrl($item);
$link_title = $url->toString();
// If the title field value is available, use it for the link text.
if (empty($settings['url_only']) && !empty($item->title)) {
@ -148,13 +149,18 @@ class LinkFormatter extends FormatterBase {
);
}
else {
$link = $this->buildLink($item);
$element[$delta] = array(
'#type' => 'link',
'#title' => $link_title,
'#href' => $link['path'],
'#options' => $link['options'],
'#options' => $url->getOptions(),
);
if ($url->isExternal()) {
$element[$delta]['#href'] = $url->getPath();
}
else {
$element[$delta]['#route_name'] = $url->getRouteName();
$element[$delta]['#route_parameters'] = $url->getRouteParameters();
}
}
}
@ -162,41 +168,36 @@ class LinkFormatter extends FormatterBase {
}
/**
* Builds the link information for a link field item.
* Builds the \Drupal\Core\Url object for a link field item.
*
* @param \Drupal\Core\Field\FieldItemInterface $item
* @param \Drupal\link\LinkItemInterface $item
* The link field item being rendered.
*
* @return array
* An array with the following key/value pairs:
* - 'path': a string suitable for the $path parameter in l().
* - 'options': an array suitable for the $options parameter in l().
* @return \Drupal\Core\Url
* An Url object.
*/
protected function buildLink(FieldItemInterface $item) {
protected function buildUrl(LinkItemInterface $item) {
$settings = $this->getSettings();
// Split out the link into the parts required for url(): path and options.
$parsed_url = UrlHelper::parse($item->url);
$result = array(
'path' => $parsed_url['path'],
'options' => array(
'query' => $parsed_url['query'],
'fragment' => $parsed_url['fragment'],
'attributes' => $item->attributes,
),
);
$options = $item->options;
// Add optional 'rel' attribute to link options.
if (!empty($settings['rel'])) {
$result['options']['attributes']['rel'] = $settings['rel'];
$options['attributes']['rel'] = $settings['rel'];
}
// Add optional 'target' attribute to link options.
if (!empty($settings['target'])) {
$result['options']['attributes']['target'] = $settings['target'];
$options['attributes']['target'] = $settings['target'];
}
return $result;
if ($item->isExternal()) {
$url = Url::createFromPath($item->url);
$url->setOptions($options);
}
else {
$url = new Url($item->route_name, (array) $item->route_parameters, (array) $options);
}
return $url;
}
}

View File

@ -48,7 +48,8 @@ class LinkSeparateFormatter extends LinkFormatter {
foreach ($items as $delta => $item) {
// By default use the full URL as the link text.
$link_title = $item->url;
$url = $this->buildUrl($item);
$link_title = $url->toString();
// If the link text field value is available, use it for the text.
if (empty($settings['url_only']) && !empty($item->title)) {
@ -64,19 +65,17 @@ class LinkSeparateFormatter extends LinkFormatter {
if (empty($item->title)) {
$link_title = NULL;
}
$url_title = $item->url;
$url_title = $url->toString();
if (!empty($settings['trim_length'])) {
$link_title = truncate_utf8($link_title, $settings['trim_length'], FALSE, TRUE);
$url_title = truncate_utf8($item->url, $settings['trim_length'], FALSE, TRUE);
$url_title = truncate_utf8($url_title, $settings['trim_length'], FALSE, TRUE);
}
$link = $this->buildLink($item);
$element[$delta] = array(
'#theme' => 'link_formatter_link_separate',
'#title' => $link_title,
'#url_title' => $url_title,
'#href' => $link['path'],
'#options' => $link['options'],
'#url' => $url,
);
}
return $element;

View File

@ -11,6 +11,7 @@ use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\TypedData\DataDefinition;
use Drupal\Core\TypedData\MapDataDefinition;
use Drupal\link\LinkItemInterface;
/**
* Plugin implementation of the 'link' field type.
@ -20,17 +21,19 @@ use Drupal\Core\TypedData\MapDataDefinition;
* label = @Translation("Link"),
* description = @Translation("Stores a URL string, optional varchar link text, and optional blob of attributes to assemble a link."),
* default_widget = "link_default",
* default_formatter = "link"
* default_formatter = "link",
* constraints = {"LinkType" = {}}
* )
*/
class LinkItem extends FieldItemBase {
class LinkItem extends FieldItemBase implements LinkItemInterface {
/**
* {@inheritdoc}
*/
public static function defaultInstanceSettings() {
return array(
'title' => 1,
'title' => DRUPAL_OPTIONAL,
'link_type' => LinkItemInterface::LINK_GENERIC
) + parent::defaultInstanceSettings();
}
@ -38,14 +41,20 @@ class LinkItem extends FieldItemBase {
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldDefinitionInterface $field_definition) {
$properties['url'] = DataDefinition::create('uri')
$properties['url'] = DataDefinition::create('string')
->setLabel(t('URL'));
$properties['title'] = DataDefinition::create('string')
->setLabel(t('Link text'));
$properties['attributes'] = MapDataDefinition::create()
->setLabel(t('Attributes'));
$properties['route_name'] = DataDefinition::create('string')
->setLabel(t('Route name'));
$properties['route_parameters'] = MapDataDefinition::create()
->setLabel(t('Route parameters'));
$properties['options'] = MapDataDefinition::create()
->setLabel(t('Options'));
return $properties;
}
@ -68,8 +77,21 @@ class LinkItem extends FieldItemBase {
'length' => 255,
'not null' => FALSE,
),
'attributes' => array(
'description' => 'Serialized array of attributes for the link.',
'route_name' => array(
'description' => 'The machine name of a defined Route this link represents.',
'type' => 'varchar',
'length' => 255,
'not null' => FALSE,
),
'route_parameters' => array(
'description' => 'Serialized array of route parameters of the link.',
'type' => 'blob',
'size' => 'big',
'not null' => FALSE,
'serialize' => TRUE,
),
'options' => array(
'description' => 'Serialized array of options for the link.',
'type' => 'blob',
'size' => 'big',
'not null' => FALSE,
@ -85,6 +107,17 @@ class LinkItem extends FieldItemBase {
public function instanceSettingsForm(array $form, array &$form_state) {
$element = array();
$element['link_type'] = array(
'#type' => 'radios',
'#title' => t('Allowed link type'),
'#default_value' => $this->getSetting('link_type'),
'#options' => array(
static::LINK_INTERNAL => t('Internal links only'),
static::LINK_EXTERNAL => t('External links only'),
static::LINK_GENERIC => t('Both internal and external links'),
),
);
$element['title'] = array(
'#type' => 'radios',
'#title' => t('Allow link text'),
@ -99,15 +132,6 @@ class LinkItem extends FieldItemBase {
return $element;
}
/**
* {@inheritdoc}
*/
public function preSave() {
// Trim any spaces around the URL and link text.
$this->url = trim($this->url);
$this->title = trim($this->title);
}
/**
* {@inheritdoc}
*/
@ -116,4 +140,11 @@ class LinkItem extends FieldItemBase {
return $value === NULL || $value === '';
}
/**
* {@inheritdoc}
*/
public function isExternal() {
// External links don't have a route_name value.
return empty($this->route_name);
}
}

View File

@ -7,8 +7,14 @@
namespace Drupal\link\Plugin\Field\FieldWidget;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Core\Routing\MatchingRouteNotFoundException;
use Drupal\Core\Url;
use Drupal\link\LinkItemInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Plugin implementation of the 'link' widget.
@ -37,17 +43,42 @@ class LinkWidget extends WidgetBase {
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, array &$form_state) {
$default_url_value = NULL;
if (isset($items[$delta]->url)) {
$url = Url::createFromPath($items[$delta]->url);
$url->setOptions($items[$delta]->options);
$default_url_value = ltrim($url->toString(), '/');
}
$element['url'] = array(
'#type' => 'url',
'#title' => t('URL'),
'#title' => $this->t('URL'),
'#placeholder' => $this->getSetting('placeholder_url'),
'#default_value' => isset($items[$delta]->url) ? $items[$delta]->url : NULL,
'#default_value' => $default_url_value,
'#maxlength' => 2048,
'#required' => $element['#required'],
);
// 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['url']['#type'] = 'textfield';
}
// If the field is configured to allow only internal links, add a useful
// element prefix.
if (!$this->supportsExternalLinks()) {
$element['url']['#field_prefix'] = \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['url']['#description'] = $this->t('This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '<front>', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org'));
}
$element['title'] = array(
'#type' => 'textfield',
'#title' => t('Link text'),
'#title' => $this->t('Link text'),
'#placeholder' => $this->getSetting('placeholder_title'),
'#default_value' => isset($items[$delta]->title) ? $items[$delta]->title : NULL,
'#maxlength' => 255,
@ -58,7 +89,7 @@ class LinkWidget extends WidgetBase {
// settings cannot be saved otherwise.
$is_field_edit_form = ($element['#entity'] === NULL);
if (!$is_field_edit_form && $this->getFieldSetting('title') == DRUPAL_REQUIRED) {
$element['#element_validate'] = array(array($this, 'validateTitle'));
$element['#element_validate'][] = array($this, 'validateTitle');
}
// Exposing the attributes array in the widget is left for alternate and more
@ -66,7 +97,7 @@ class LinkWidget extends WidgetBase {
$element['attributes'] = array(
'#type' => 'value',
'#tree' => TRUE,
'#value' => !empty($items[$delta]->attributes) ? $items[$delta]->attributes : array(),
'#value' => !empty($items[$delta]->options['attributes']) ? $items[$delta]->options['attributes'] : array(),
'#attributes' => array('class' => array('link-field-widget-attributes')),
);
@ -81,6 +112,30 @@ class LinkWidget extends WidgetBase {
return $element;
}
/**
* Indicates enabled support for link to routes.
*
* @return bool
* Returns TRUE if the LinkItem field is configured to support links to
* routes, otherwise FALSE.
*/
protected function supportsInternalLinks() {
$link_type = $this->getFieldSetting('link_type');
return (bool) ($link_type & LinkItemInterface::LINK_INTERNAL);
}
/**
* Indicates enabled support for link to external URLs.
*
* @return bool
* Returns TRUE if the LinkItem field is configured to support links to
* external URLs, otherwise FALSE.
*/
protected function supportsExternalLinks() {
$link_type = $this->getFieldSetting('link_type');
return (bool) ($link_type & LinkItemInterface::LINK_EXTERNAL);
}
/**
* {@inheritdoc}
*/
@ -89,15 +144,15 @@ class LinkWidget extends WidgetBase {
$elements['placeholder_url'] = array(
'#type' => 'textfield',
'#title' => t('Placeholder for URL'),
'#title' => $this->t('Placeholder for URL'),
'#default_value' => $this->getSetting('placeholder_url'),
'#description' => t('Text that will be shown inside the field until a value is entered. This hint is usually a sample value or a brief description of the expected format.'),
'#description' => $this->t('Text that will be shown inside the field until a value is entered. This hint is usually a sample value or a brief description of the expected format.'),
);
$elements['placeholder_title'] = array(
'#type' => 'textfield',
'#title' => t('Placeholder for link text'),
'#title' => $this->t('Placeholder for link text'),
'#default_value' => $this->getSetting('placeholder_title'),
'#description' => t('Text that will be shown inside the field until a value is entered. This hint is usually a sample value or a brief description of the expected format.'),
'#description' => $this->t('Text that will be shown inside the field until a value is entered. This hint is usually a sample value or a brief description of the expected format.'),
'#states' => array(
'invisible' => array(
':input[name="instance[settings][title]"]' => array('value' => DRUPAL_DISABLED),
@ -117,31 +172,62 @@ class LinkWidget extends WidgetBase {
$placeholder_title = $this->getSetting('placeholder_title');
$placeholder_url = $this->getSetting('placeholder_url');
if (empty($placeholder_title) && empty($placeholder_url)) {
$summary[] = t('No placeholders');
$summary[] = $this->t('No placeholders');
}
else {
if (!empty($placeholder_title)) {
$summary[] = t('Title placeholder: @placeholder_title', array('@placeholder_title' => $placeholder_title));
$summary[] = $this->t('Title placeholder: @placeholder_title', array('@placeholder_title' => $placeholder_title));
}
if (!empty($placeholder_url)) {
$summary[] = t('URL placeholder: @placeholder_url', array('@placeholder_url' => $placeholder_url));
$summary[] = $this->t('URL placeholder: @placeholder_url', array('@placeholder_url' => $placeholder_url));
}
}
return $summary;
}
/**
* Form element validation handler for link_field_widget_form().
* Form element validation handler; Validates the title property.
*
* Conditionally requires the link title if a URL value was filled in.
*/
function validateTitle(&$element, &$form_state, $form) {
public function validateTitle(&$element, &$form_state, $form) {
if ($element['url']['#value'] !== '' && $element['title']['#value'] === '') {
$element['title']['#required'] = TRUE;
form_error($element['title'], $form_state, t('!name field is required.', array('!name' => $element['title']['#title'])));
\Drupal::formBuilder()->setError($element['title'], $form_state, $this->t('!name field is required.', array('!name' => $element['title']['#title'])));
}
}
}
/**
* {@inheritdoc}
*/
public function massageFormValues(array $values, array $form, array &$form_state) {
foreach ($values as &$value) {
if (!empty($value['url'])) {
try {
$parsed_url = UrlHelper::parse($value['url']);
$url = Url::createFromPath($parsed_url['path']);
$url->setOption('query', $parsed_url['query']);
$url->setOption('fragment', $parsed_url['fragment']);
$url->setOption('attributes', $value['attributes']);
$value += $url->toArray();
// Reset the URL value to contain only the path.
$value['url'] = $parsed_url['path'];
}
catch (NotFoundHttpException $e) {
// Nothing to do here, LinkTypeConstraintValidator emits errors.
}
catch (MatchingRouteNotFoundException $e) {
// Nothing to do here, LinkTypeConstraintValidator emits errors.
}
catch (ParamNotConvertedException $e) {
// Nothing to do here, LinkTypeConstraintValidator emits errors.
}
}
}
return $values;
}
}

View File

@ -0,0 +1,93 @@
<?php
/**
* @file
* Contains \Drupal\link\Plugin\Validation\Constraint\LinkTypeConstraint.
*/
namespace Drupal\link\Plugin\Validation\Constraint;
use Drupal\link\LinkItemInterface;
use Drupal\Core\Url;
use Drupal\Core\Routing\MatchingRouteNotFoundException;
use Drupal\Core\ParamConverter\ParamNotConvertedException;
use Drupal\Component\Utility\UrlHelper;
use Symfony\Component\Validator\Constraint;
use Symfony\Component\Validator\ConstraintValidatorInterface;
use Symfony\Component\Validator\ExecutionContextInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Validation constraint for links receiving data allowed by its settings.
*
* @Plugin(
* id = "LinkType",
* label = @Translation("Link data valid for link type.", context = "Validation"),
* )
*/
class LinkTypeConstraint extends Constraint implements ConstraintValidatorInterface {
public $message = 'The URL %url is not valid.';
/**
* @var \Symfony\Component\Validator\ExecutionContextInterface
*/
protected $context;
/**
* {@inheritDoc}
*/
public function initialize(ExecutionContextInterface $context) {
$this->context = $context;
}
/**
* {@inheritdoc}
*/
public function validatedBy() {
return get_class($this);
}
/**
* {@inheritdoc}
*/
public function validate($value, Constraint $constraint) {
if (isset($value)) {
$url_is_valid = TRUE;
/** @var $link_item \Drupal\link\LinkItemInterface */
$link_item = $value;
$link_type = $link_item->getFieldDefinition()->getSetting('link_type');
$url_string = $link_item->url;
// Validate the url property.
if ($url_string !== '') {
try {
// @todo This shouldn't be needed, but massageFormValues() may not
// run.
$parsed_url = UrlHelper::parse($url_string);
$url = Url::createFromPath($parsed_url['path']);
if ($url->isExternal() && !UrlHelper::isValid($url_string, TRUE)) {
$url_is_valid = FALSE;
}
elseif ($url->isExternal() && !($link_type & LinkItemInterface::LINK_EXTERNAL)) {
$url_is_valid = FALSE;
}
}
catch (NotFoundHttpException $e) {
$url_is_valid = FALSE;
}
catch (MatchingRouteNotFoundException $e) {
$url_is_valid = FALSE;
}
catch (ParamNotConvertedException $e) {
$url_is_valid = FALSE;
}
}
if (!$url_is_valid) {
$this->context->addViolation($this->message, array('%url' => $url_string));
}
}
}
}

View File

@ -7,8 +7,9 @@
namespace Drupal\link\Tests;
use Drupal\simpletest\WebTestBase;
use Drupal\Component\Utility\String;
use Drupal\link\LinkItemInterface;
use Drupal\simpletest\WebTestBase;
/**
* Tests link field widgets and formatters.
@ -73,14 +74,16 @@ class LinkFieldTest extends WebTestBase {
'type' => 'link',
));
$this->field->save();
entity_create('field_instance_config', array(
$this->instance = entity_create('field_instance_config', array(
'field_name' => $field_name,
'entity_type' => 'entity_test',
'bundle' => 'entity_test',
'settings' => array(
'title' => DRUPAL_DISABLED,
'link_type' => LinkItemInterface::LINK_GENERIC,
),
))->save();
));
$this->instance->save();
entity_get_form_display('entity_test', 'entity_test', 'default')
->setComponent($field_name, array(
'type' => 'link_default',
@ -100,21 +103,16 @@ class LinkFieldTest extends WebTestBase {
$this->assertFieldByName("{$field_name}[0][url]", '', 'Link URL field is displayed');
$this->assertRaw('placeholder="http://example.com"');
// Verify that a valid URL can be submitted.
$value = 'http://www.example.com/';
$edit = array(
'user_id' => 1,
'name' => $this->randomName(),
"{$field_name}[0][url]" => $value,
// Define some valid URLs.
$valid_external_entries = array(
'http://www.example.com/',
);
$valid_internal_entries = array(
'entity_test/add',
);
$this->drupalPostForm(NULL, $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);
// Verify that invalid URLs cannot be submitted.
$wrong_entries = array(
// Define some invalid URLs.
$invalid_external_entries = array(
// Missing protcol
'not-an-url',
// Invalid protocol
@ -122,14 +120,66 @@ class LinkFieldTest extends WebTestBase {
// Missing host name
'http://',
);
$this->drupalGet('entity_test/add');
foreach ($wrong_entries as $invalid_value) {
$invalid_internal_entries = array(
'non/existing/path',
);
// Test external and internal URLs for 'link_type' = LinkItemInterface::LINK_GENERIC.
$this->assertValidEntries($field_name, $valid_external_entries + $valid_internal_entries);
$this->assertInvalidEntries($field_name, $invalid_external_entries + $invalid_internal_entries);
// Test external URLs for 'link_type' = LinkItemInterface::LINK_EXTERNAL.
$this->instance->settings['link_type'] = LinkItemInterface::LINK_EXTERNAL;
$this->instance->save();
$this->assertValidEntries($field_name, $valid_external_entries);
$this->assertInvalidEntries($field_name, $valid_internal_entries + $invalid_external_entries);
// Test external URLs for 'link_type' = LinkItemInterface::LINK_INTERNAL.
$this->instance->settings['link_type'] = LinkItemInterface::LINK_INTERNAL;
$this->instance->save();
$this->assertValidEntries($field_name, $valid_internal_entries);
$this->assertInvalidEntries($field_name, $valid_external_entries + $invalid_internal_entries);
}
/**
* Asserts that valid URLs can be submitted.
*
* @param string $field_name
* The field name.
* @param array $valid_entries
* An array of valid URL entries.
*/
protected function assertValidEntries($field_name, array $valid_entries) {
foreach ($valid_entries as $value) {
$edit = array(
'user_id' => 1,
'name' => $this->randomName(),
"{$field_name}[0][url]" => $value,
);
$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);
}
}
/**
* Asserts that invalid URLs cannot be submitted.
*
* @param string $field_name
* The field name.
* @param array $invalid_entries
* An array of invalid URL entries.
*/
protected function assertInvalidEntries($field_name, array $invalid_entries) {
foreach ($invalid_entries as $invalid_value) {
$edit = array(
'user_id' => 1,
'name' => $this->randomName(),
"{$field_name}[0][url]" => $invalid_value,
);
$this->drupalPostForm(NULL, $edit, t('Save'));
$this->drupalPostForm('entity_test/add', $edit, t('Save'));
$this->assertText(t('The URL @url is not valid.', array('@url' => $invalid_value)));
}
}
@ -153,6 +203,7 @@ class LinkFieldTest extends WebTestBase {
'label' => 'Read more about this entity',
'settings' => array(
'title' => DRUPAL_OPTIONAL,
'link_type' => LinkItemInterface::LINK_GENERIC,
),
));
$this->instance->save();
@ -272,6 +323,7 @@ class LinkFieldTest extends WebTestBase {
'bundle' => 'entity_test',
'settings' => array(
'title' => DRUPAL_OPTIONAL,
'link_type' => LinkItemInterface::LINK_GENERIC,
),
))->save();
entity_get_form_display('entity_test', 'entity_test', 'default')
@ -413,6 +465,7 @@ class LinkFieldTest extends WebTestBase {
'bundle' => 'entity_test',
'settings' => array(
'title' => DRUPAL_OPTIONAL,
'link_type' => LinkItemInterface::LINK_GENERIC,
),
))->save();
$display_options = array(

View File

@ -7,6 +7,7 @@
namespace Drupal\link\Tests;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\FieldItemInterface;
use Drupal\field\Tests\FieldUnitTestBase;
@ -53,12 +54,14 @@ class LinkItemTest extends FieldUnitTestBase {
public function testLinkItem() {
// Create entity.
$entity = entity_create('entity_test');
$url = 'http://www.drupal.org';
$url = 'http://www.drupal.org?test_param=test_value';
$parsed_url = UrlHelper::parse($url);
$title = $this->randomName();
$class = $this->randomName();
$entity->field_test->url = $url;
$entity->field_test->url = $parsed_url['path'];
$entity->field_test->title = $title;
$entity->field_test->first()->get('attributes')->set('class', $class);
$entity->field_test->first()->get('options')->set('query', $parsed_url['query']);
$entity->field_test->first()->get('options')->set('attributes', array('class' => $class));
$entity->name->value = $this->randomName();
$entity->save();
@ -67,11 +70,22 @@ class LinkItemTest extends FieldUnitTestBase {
$entity = entity_load('entity_test', $id);
$this->assertTrue($entity->field_test instanceof FieldItemListInterface, 'Field implements interface.');
$this->assertTrue($entity->field_test[0] instanceof FieldItemInterface, 'Field item implements interface.');
$this->assertEqual($entity->field_test->url, $url);
$this->assertEqual($entity->field_test[0]->url, $url);
$this->assertEqual($entity->field_test->url, $parsed_url['path']);
$this->assertEqual($entity->field_test[0]->url, $parsed_url['path']);
$this->assertEqual($entity->field_test->title, $title);
$this->assertEqual($entity->field_test[0]->title, $title);
$this->assertEqual($entity->field_test->attributes['class'], $class);
$this->assertEqual($entity->field_test->options['attributes']['class'], $class);
$this->assertEqual($entity->field_test->options['query'], $parsed_url['query']);
// Update only the entity name property to check if the link field data will
// remain intact.
$entity->name->value = $this->randomName();
$entity->save();
$id = $entity->id();
$entity = entity_load('entity_test', $id);
$this->assertEqual($entity->field_test->url, $parsed_url['path']);
$this->assertEqual($entity->field_test->options['attributes']['class'], $class);
$this->assertEqual($entity->field_test->options['query'], $parsed_url['query']);
// Verify changing the field value.
$new_url = 'http://drupal.org';
@ -79,17 +93,19 @@ class LinkItemTest extends FieldUnitTestBase {
$new_class = $this->randomName();
$entity->field_test->url = $new_url;
$entity->field_test->title = $new_title;
$entity->field_test->first()->get('attributes')->set('class', $new_class);
$entity->field_test->first()->get('options')->set('query', NULL);
$entity->field_test->first()->get('options')->set('attributes', array('class' => $new_class));
$this->assertEqual($entity->field_test->url, $new_url);
$this->assertEqual($entity->field_test->title, $new_title);
$this->assertEqual($entity->field_test->attributes['class'], $new_class);
$this->assertEqual($entity->field_test->options['attributes']['class'], $new_class);
$this->assertNull($entity->field_test->options['query']);
// Read changed entity and assert changed values.
$entity->save();
$entity = entity_load('entity_test', $id);
$this->assertEqual($entity->field_test->url, $new_url);
$this->assertEqual($entity->field_test->title, $new_title);
$this->assertEqual($entity->field_test->attributes['class'], $new_class);
$this->assertEqual($entity->field_test->options['attributes']['class'], $new_class);
}
}

View File

@ -15,7 +15,7 @@ function link_help($path, $arg) {
case 'admin/help#link':
$output = '';
$output .= '<h3>' . t('About') . '</h3>';
$output .= '<p>' . t('The Link module allows you to create fields that contain external URLs and optional link text. See the <a href="!field">Field module help</a> and the <a href="!field_ui">Field UI help</a> pages for general information on fields and how to create and manage them. For more information, see the <a href="!link_documentation">online documentation for the Link module</a>.', array('!field' => \Drupal::url('help.page', array('name' => 'field')), '!field_ui' => \Drupal::url('help.page', array('name' => 'field_ui')), '!link_documentation' => 'https://drupal.org/documentation/modules/link')) . '</p>';
$output .= '<p>' . t('The Link module allows you to create fields that contain internal or external URLs and optional link text. See the <a href="!field">Field module help</a> and the <a href="!field_ui">Field UI help</a> pages for general information on fields and how to create and manage them. For more information, see the <a href="!link_documentation">online documentation for the Link module</a>.', array('!field' => \Drupal::url('help.page', array('name' => 'field')), '!field_ui' => \Drupal::url('help.page', array('name' => 'field_ui')), '!link_documentation' => 'https://drupal.org/documentation/modules/link')) . '</p>';
$output .= '<h3>' . t('Uses') . '</h3>';
$output .= '<dl>';
$output .= '<dt>' . t('Managing and displaying link fields') . '</dt>';
@ -27,7 +27,7 @@ function link_help($path, $arg) {
$output .= '<dt>' . t('Adding attributes to links') . '</dt>';
$output .= '<dd>' . t('You can add attributes to links, by changing the <em>Format settings</em> in the <em>Manage display</em> page. Adding <em>rel="nofollow"</em> notifies search engines that links should not be followed.') . '</dd>';
$output .= '<dt>' . t('Validating URLs') . '</dt>';
$output .= '<dd>' . t('All links are validated after a link field is filled in. They can include anchors or query strings. Links need to start with either the scheme name <em>http</em> or <em>https</em>; other scheme names (for example ftp or git) are not supported.') . '</dd>';
$output .= '<dd>' . t('All links are validated after a link field is filled in. They can include anchors or query strings.') . '</dd>';
$output .= '</dl>';
return $output;
}
@ -39,7 +39,7 @@ function link_help($path, $arg) {
function link_theme() {
return array(
'link_formatter_link_separate' => array(
'variables' => array('title' => NULL, 'url_title' => NULL, 'href' => NULL, 'options' => array()),
'variables' => array('title' => NULL, 'url_title' => NULL, 'url' => NULL),
'template' => 'link-formatter-link-separate',
),
);
@ -57,12 +57,17 @@ function link_theme() {
* - title: (optional) A descriptive or alternate title for the link, which
* may be different than the actual link text.
* - url_title: The anchor text for the link.
* - href: The link URL.
* - options: (optional) An array of options to pass to l().
* - url: A \Drupal\Core\Url object.
*/
function template_preprocess_link_formatter_link_separate(&$variables) {
if (!empty($variables['title'])) {
$variables['title'] = String::checkPlain($variables['title']);
}
$variables['link'] = l($variables['url_title'], $variables['href'], $variables['options']);
if (!$variables['url']->isExternal()) {
$variables['link'] = \Drupal::linkGenerator()->generateFromUrl($variables['url_title'], $variables['url']);
}
else {
$variables['link'] = l($variables['url_title'], $variables['url']->getPath(), $variables['url']->getOptions());
}
}

View File

@ -142,8 +142,7 @@ class ExternalUrlTest extends UnitTestCase {
*/
public function testToArray(Url $url) {
$expected = array(
'route_name' => '',
'route_parameters' => array(),
'path' => $this->path,
'options' => array(),
);
$this->assertSame($expected, $url->toArray());
@ -154,10 +153,12 @@ class ExternalUrlTest extends UnitTestCase {
*
* @depends testCreateFromPath
*
* @expectedException \UnexpectedValueException
*
* @covers ::getRouteName()
*/
public function testGetRouteName(Url $url) {
$this->assertSame('', $url->getRouteName());
$url->getRouteName();
}
/**
@ -165,10 +166,12 @@ class ExternalUrlTest extends UnitTestCase {
*
* @depends testCreateFromPath
*
* @expectedException \UnexpectedValueException
*
* @covers ::getRouteParameters()
*/
public function testGetRouteParameters(Url $url) {
$this->assertSame(array(), $url->getRouteParameters());
$url->getRouteParameters();
}
/**
@ -184,6 +187,17 @@ class ExternalUrlTest extends UnitTestCase {
$this->assertNull($url->getInternalPath());
}
/**
* Tests the getPath() method.
*
* @depends testCreateFromPath
*
* @covers ::getPath()
*/
public function testGetPath(Url $url) {
$this->assertNotNull($url->getPath());
}
/**
* Tests the getOptions() method.
*

View File

@ -7,6 +7,7 @@
namespace Drupal\Tests\Core;
use Drupal\Component\Utility\UrlHelper;
use Drupal\Core\DependencyInjection\ContainerBuilder;
use Drupal\Core\Url;
use Drupal\Tests\UnitTestCase;
@ -201,6 +202,31 @@ class UrlTest extends UnitTestCase {
}
}
/**
* Tests the getPath() method for internal URLs.
*
* @depends testCreateFromPath
*
* @expectedException \UnexpectedValueException
*
* @covers ::getPath()
*/
public function testGetPathForInternalUrl($urls) {
foreach ($urls as $url) {
$url->getPath();
}
}
/**
* Tests the getPath() method for external URLs.
*
* @covers ::getPath
*/
public function testGetPathForExternalUrl() {
$url = Url::createFromPath('http://example.com/test');
$this->assertEquals('http://example.com/test', $url->getPath());
}
/**
* Tests the toString() method.
*
@ -255,6 +281,17 @@ class UrlTest extends UnitTestCase {
}
}
/**
* Tests the getRouteName() with an external URL.
*
* @covers ::getRouteName
* @expectedException \UnexpectedValueException
*/
public function testGetRouteNameWithExternalUrl() {
$url = Url::createFromPath('http://example.com');
$url->getRouteName();
}
/**
* Tests the getRouteParameters() method.
*
@ -271,6 +308,17 @@ class UrlTest extends UnitTestCase {
}
}
/**
* Tests the getRouteParameter() with an external URL.
*
* @covers ::getRouteParameter
* @expectedException \UnexpectedValueException
*/
public function testGetRouteParametersWithExternalUrl() {
$url = Url::createFromPath('http://example.com');
$url->getRouteParameters();
}
/**
* Tests the getOptions() method.
*

View File

@ -42,13 +42,6 @@ class LinkGeneratorTest extends UnitTestCase {
*/
protected $moduleHandler;
/**
* The mocked path alias manager.
*
* @var \Drupal\Core\Path\AliasManagerInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $aliasManager;
/**
* Contains the LinkGenerator default options.
*/
@ -80,9 +73,8 @@ class LinkGeneratorTest extends UnitTestCase {
$this->urlGenerator = $this->getMock('\Drupal\Core\Routing\UrlGenerator', array(), array(), '', FALSE);
$this->moduleHandler = $this->getMock('Drupal\Core\Extension\ModuleHandlerInterface');
$this->aliasManager = $this->getMock('\Drupal\Core\Path\AliasManagerInterface');
$this->linkGenerator = new LinkGenerator($this->urlGenerator, $this->moduleHandler, $this->aliasManager);
$this->linkGenerator = new LinkGenerator($this->urlGenerator, $this->moduleHandler);
}
/**
@ -344,14 +336,6 @@ class LinkGeneratorTest extends UnitTestCase {
array('test_route_4', array('object' => '1'), 'test-route-4/1'),
)));
$this->aliasManager->expects($this->exactly(7))
->method('getSystemPath')
->will($this->returnValueMap(array(
array('test-route-1', NULL, 'test-route-1'),
array('test-route-3', NULL, 'test-route-3'),
array('test-route-4/1', NULL, 'test-route-4/1'),
)));
$this->moduleHandler->expects($this->exactly(8))
->method('alter');