Commit 6a0257c2 authored by catch's avatar catch
Browse files

Issue #2201051 by andypost, sun, Berdir: Convert path.module form alters to a field widget.

parent 44b38852
<?php
/**
* @file
* Contains \Drupal\path\Plugin\Field\FieldType\PathFieldItemList.
*/
namespace Drupal\path\Plugin\Field\FieldType;
use Drupal\Core\Field\FieldItemList;
use Drupal\Core\Session\AccountInterface;
/**
* Represents a configurable entity path field.
*/
class PathFieldItemList extends FieldItemList {
/**
* {@inheritdoc}
*/
public function defaultAccess($operation = 'view', AccountInterface $account = NULL) {
if ($operation == 'view') {
return TRUE;
}
return $account->hasPermission('create url aliases') || $account->hasPermission('administer url aliases');
}
}
......@@ -18,7 +18,9 @@
* id = "path",
* label = @Translation("Path"),
* description = @Translation("An entity field containing a path alias and related data."),
* no_ui = TRUE
* no_ui = TRUE,
* default_widget = "path",
* list_class = "\Drupal\path\Plugin\Field\FieldType\PathFieldItemList"
* )
*/
class PathItem extends FieldItemBase {
......@@ -28,9 +30,9 @@ class PathItem extends FieldItemBase {
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
$properties['alias'] = DataDefinition::create('string')
->setLabel(t('Path alias'));
->setLabel(t('Path alias'));
$properties['pid'] = DataDefinition::create('string')
->setLabel(t('Path id'));
->setLabel(t('Path id'));
return $properties;
}
......@@ -55,10 +57,7 @@ public function insert() {
if ($this->alias) {
$entity = $this->getEntity();
// Ensure fields for programmatic executions.
$langcode = $entity->language()->id;
if ($path = \Drupal::service('path.alias_storage')->save($entity->getSystemPath(), $this->alias, $langcode)) {
if ($path = \Drupal::service('path.alias_storage')->save($entity->getSystemPath(), $this->alias, $this->getLangcode())) {
$this->pid = $path['pid'];
}
}
......@@ -76,10 +75,7 @@ public function update() {
elseif ($this->alias) {
$entity = $this->getEntity();
// Ensure fields for programmatic executions.
$langcode = $entity->language()->id;
\Drupal::service('path.alias_storage')->save($entity->getSystemPath(), $this->alias, $langcode, $this->pid);
\Drupal::service('path.alias_storage')->save($entity->getSystemPath(), $this->alias, $this->getLangcode(), $this->pid);
}
}
......
<?php
/**
* @file
* Contains \Drupal\path\Plugin\Field\FieldWidget\PathWidget.
*/
namespace Drupal\path\Plugin\Field\FieldWidget;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Language\Language;
use Symfony\Component\Validator\ConstraintViolationInterface;
/**
* Plugin implementation of the 'path' widget.
*
* @FieldWidget(
* id = "path",
* label = @Translation("URL alias"),
* field_types = {
* "path"
* }
* )
*/
class PathWidget extends WidgetBase {
/**
* {@inheritdoc}
*/
public function formElement(FieldItemListInterface $items, $delta, array $element, array &$form, array &$form_state) {
$entity = $items->getEntity();
$path = array();
if (!$entity->isNew()) {
$conditions = array('source' => $entity->getSystemPath());
if ($items->getLangcode() != Language::LANGCODE_NOT_SPECIFIED) {
$conditions['langcode'] = $items->getLangcode();
}
$path = \Drupal::service('path.alias_storage')->load($conditions);
if ($path === FALSE) {
$path = array();
}
}
$path += array(
'pid' => NULL,
'source' => !$entity->isNew() ? $entity->getSystemPath() : NULL,
'alias' => '',
'langcode' => $items->getLangcode(),
);
$element += array(
'#element_validate' => array(array(get_class($this), 'validateFormElement')),
);
$element['alias'] = array(
'#type' => 'textfield',
'#title' => $element['#title'],
'#default_value' => $path['alias'],
'#required' => $element['#required'],
'#maxlength' => 255,
'#description' => t('The alternative URL for this content. Use a relative path without a trailing slash. For example, enter "about" for the about page.'),
);
$element['pid'] = array(
'#type' => 'value',
'#value' => $path['pid'],
);
$element['source'] = array(
'#type' => 'value',
'#value' => $path['source'],
);
$element['langcode'] = array(
'#type' => 'value',
'#value' => $path['langcode'],
);
return $element;
}
/**
* Form element validation handler for URL alias form element.
*
* @param array $element
* The form element.
* @param array $form_state
* The form state.
*/
public static function validateFormElement(array &$element, array &$form_state) {
if (!empty($element['alias']['#value'])) {
// Trim the submitted value.
$alias = trim($element['alias']['#value']);
$form_builder = \Drupal::formBuilder();
$form_builder->setValue($element['alias'], $alias, $form_state);
// Entity language needs special care. Since the language of the URL alias
// depends on the entity language, and the entity language can be switched
// right within the same form, we need to conditionally overload the
// originally assigned URL alias language.
// @see \Drupal\content_translation\ContentTranslationController::entityFormAlter()
if (isset($form_state['values']['langcode'])) {
$form_builder->setValue($element['langcode'], $form_state['values']['langcode'], $form_state);
}
// Validate that the submitted alias does not exist yet.
$is_exists = \Drupal::service('path.alias_storage')->aliasExists($alias, $element['langcode']['#value'], $element['source']['#value']);
if ($is_exists) {
$form_builder->setError($element, $form_state, t('The alias is already in use.'));
}
}
}
/**
* {@inheritdoc}
*/
public function errorElement(array $element, ConstraintViolationInterface $violation, array $form, array &$form_state) {
return $element['alias'];
}
}
......@@ -146,29 +146,29 @@ function testNodeAlias() {
// Create alias.
$edit = array();
$edit['path[alias]'] = $this->randomName(8);
$edit['path[0][alias]'] = $this->randomName(8);
$this->drupalPostForm('node/' . $node1->id() . '/edit', $edit, t('Save'));
// Confirm that the alias works.
$this->drupalGet($edit['path[alias]']);
$this->drupalGet($edit['path[0][alias]']);
$this->assertText($node1->label(), 'Alias works.');
$this->assertResponse(200);
// Confirm the 'canonical' and 'shortlink' URLs.
$elements = $this->xpath("//link[contains(@rel, 'canonical') and contains(@href, '" . $edit['path[alias]'] . "')]");
$elements = $this->xpath("//link[contains(@rel, 'canonical') and contains(@href, '" . $edit['path[0][alias]'] . "')]");
$this->assertTrue(!empty($elements), 'Page contains canonical link URL.');
$elements = $this->xpath("//link[contains(@rel, 'shortlink') and contains(@href, 'node/" . $node1->id() . "')]");
$this->assertTrue(!empty($elements), 'Page contains shortlink URL.');
// Change alias to one containing "exotic" characters.
$previous = $edit['path[alias]'];
$edit['path[alias]'] = "- ._~!$'\"()*@[]?&+%#,;=:" . // "Special" ASCII characters.
$previous = $edit['path[0][alias]'];
$edit['path[0][alias]'] = "- ._~!$'\"()*@[]?&+%#,;=:" . // "Special" ASCII characters.
"%23%25%26%2B%2F%3F" . // Characters that look like a percent-escaped string.
"éøïвβ中國書۞"; // Characters from various non-ASCII alphabets.
$this->drupalPostForm('node/' . $node1->id() . '/edit', $edit, t('Save'));
// Confirm that the alias works.
$this->drupalGet($edit['path[alias]']);
$this->drupalGet($edit['path[0][alias]']);
$this->assertText($node1->label(), 'Changed alias works.');
$this->assertResponse(200);
......@@ -181,17 +181,17 @@ function testNodeAlias() {
$node2 = $this->drupalCreateNode();
// Set alias to second test node.
// Leave $edit['path[alias]'] the same.
// Leave $edit['path[0][alias]'] the same.
$this->drupalPostForm('node/' . $node2->id() . '/edit', $edit, t('Save'));
// Confirm that the alias didn't make a duplicate.
$this->assertText(t('The alias is already in use.'), 'Attempt to moved alias was rejected.');
// Delete alias.
$this->drupalPostForm('node/' . $node1->id() . '/edit', array('path[alias]' => ''), t('Save'));
$this->drupalPostForm('node/' . $node1->id() . '/edit', array('path[0][alias]' => ''), t('Save'));
// Confirm that the alias no longer works.
$this->drupalGet($edit['path[alias]']);
$this->drupalGet($edit['path[0][alias]']);
$this->assertNoText($node1->label(), 'Alias was successfully deleted.');
$this->assertResponse(404);
}
......@@ -216,13 +216,13 @@ function testDuplicateNodeAlias() {
// Create one node with a random alias.
$node_one = $this->drupalCreateNode();
$edit = array();
$edit['path[alias]'] = $this->randomName();
$edit['path[0][alias]'] = $this->randomName();
$this->drupalPostForm('node/' . $node_one->id() . '/edit', $edit, t('Save'));
// Now create another node and try to set the same alias.
$node_two = $this->drupalCreateNode();
$this->drupalPostForm('node/' . $node_two->id() . '/edit', $edit, t('Save'));
$this->assertText(t('The alias is already in use.'));
$this->assertFieldByXPath("//input[@name='path[alias]' and contains(@class, 'error')]", $edit['path[alias]'], 'Textfield exists and has the error class.');
$this->assertFieldByXPath("//input[@name='path[0][alias]' and contains(@class, 'error')]", $edit['path[0][alias]'], 'Textfield exists and has the error class.');
}
}
......@@ -61,7 +61,11 @@ function setUp() {
// Enable translation for page node.
$edit = array(
'entity_types[node]' => 1,
'settings[node][article][translatable]' => 1,
'settings[node][article][fields][path]' => 1,
'settings[node][article][fields][body]' => 1,
'settings[node][page][translatable]' => 1,
'settings[node][page][fields][path]' => 1,
'settings[node][page][fields][body]' => 1,
'settings[node][page][settings][language][language_show]' => 1,
);
......@@ -83,7 +87,7 @@ function testAliasTranslation() {
// Edit the node to set language and path.
$edit = array();
$edit['path[alias]'] = $english_alias;
$edit['path[0][alias]'] = $english_alias;
$this->drupalPostForm('node/' . $english_node->id() . '/edit', $edit, t('Save'));
// Confirm that the alias works.
......@@ -98,7 +102,7 @@ function testAliasTranslation() {
$edit['title[0][value]'] = $this->randomName();
$edit['body[0][value]'] = $this->randomName();
$french_alias = $this->randomName();
$edit['path[alias]'] = $french_alias;
$edit['path[0][alias]'] = $french_alias;
$this->drupalPostForm(NULL, $edit, t('Save') . ' ' . ($translatable ? t('(this translation)') : t('(all translations)')));
// Clear the path lookup cache.
......@@ -112,10 +116,10 @@ function testAliasTranslation() {
// Ensure the node was created.
$english_node = node_load($english_node->id(), TRUE);
$french_node = $english_node->getTranslation('fr');
$this->assertTrue(($french_node), 'Node found in database.');
$this->assertTrue($english_node->hasTranslation('fr'), 'Node found in database.');
// Confirm that the alias works.
$this->drupalGet('fr/' . $edit['path[alias]']);
$this->drupalGet('fr/' . $edit['path[0][alias]']);
$this->assertText($french_node->body->value, 'Alias for French translation works.');
// Confirm that the alias is returned by url(). Languages are cached on
......@@ -124,7 +128,7 @@ function testAliasTranslation() {
$languages = $this->container->get('language_manager')->getLanguages();
$url = $this->container->get('url_generator')->generateFromPath('node/' . $french_node->id(), array('language' => $languages['fr']));
$this->assertTrue(strpos($url, $edit['path[alias]']), 'URL contains the path alias.');
$this->assertTrue(strpos($url, $edit['path[0][alias]']), 'URL contains the path alias.');
// Confirm that the alias works even when changing language negotiation
// options. Enable User language detection and selection over URL one.
......
......@@ -52,42 +52,42 @@ function testTermAlias() {
$edit = array(
'name[0][value]' => $this->randomName(),
'description[0][value]' => $description,
'path[alias]' => $this->randomName(),
'path[0][alias]' => $this->randomName(),
);
$this->drupalPostForm('admin/structure/taxonomy/manage/' . $vocabulary->id() . '/add', $edit, t('Save'));
$tid = db_query("SELECT tid FROM {taxonomy_term_data} WHERE name = :name", array(':name' => $edit['name[0][value]']))->fetchField();
// Confirm that the alias works.
$this->drupalGet($edit['path[alias]']);
$this->drupalGet($edit['path[0][alias]']);
$this->assertText($description, 'Term can be accessed on URL alias.');
// Confirm the 'canonical' and 'shortlink' URLs.
$elements = $this->xpath("//link[contains(@rel, 'canonical') and contains(@href, '" . $edit['path[alias]'] . "')]");
$elements = $this->xpath("//link[contains(@rel, 'canonical') and contains(@href, '" . $edit['path[0][alias]'] . "')]");
$this->assertTrue(!empty($elements), 'Term page contains canonical link URL.');
$elements = $this->xpath("//link[contains(@rel, 'shortlink') and contains(@href, 'taxonomy/term/" . $tid . "')]");
$this->assertTrue(!empty($elements), 'Term page contains shortlink URL.');
// Change the term's URL alias.
$edit2 = array();
$edit2['path[alias]'] = $this->randomName();
$edit2['path[0][alias]'] = $this->randomName();
$this->drupalPostForm('taxonomy/term/' . $tid . '/edit', $edit2, t('Save'));
// Confirm that the changed alias works.
$this->drupalGet($edit2['path[alias]']);
$this->drupalGet($edit2['path[0][alias]']);
$this->assertText($description, 'Term can be accessed on changed URL alias.');
// Confirm that the old alias no longer works.
$this->drupalGet($edit['path[alias]']);
$this->drupalGet($edit['path[0][alias]']);
$this->assertNoText($description, 'Old URL alias has been removed after altering.');
$this->assertResponse(404, 'Old URL alias returns 404.');
// Remove the term's URL alias.
$edit3 = array();
$edit3['path[alias]'] = '';
$edit3['path[0][alias]'] = '';
$this->drupalPostForm('taxonomy/term/' . $tid . '/edit', $edit3, t('Save'));
// Confirm that the alias no longer works.
$this->drupalGet($edit2['path[alias]']);
$this->drupalGet($edit2['path[0][alias]']);
$this->assertNoText($description, 'Old URL alias has been removed after altering.');
$this->assertResponse(404, 'Old URL alias returns 404.');
}
......
......@@ -9,7 +9,7 @@
Drupal.behaviors.pathDetailsSummaries = {
attach: function (context) {
$(context).find('.path-form').drupalSetSummary(function (context) {
var path = $('.form-item-path-alias input').val();
var path = $('.form-item-path-0-alias input').val();
return path ?
Drupal.t('Alias: @alias', { '@alias': path }) :
......
......@@ -56,121 +56,24 @@ function path_permission() {
/**
* Implements hook_form_BASE_FORM_ID_alter() for node_form().
*
* @see path_form_element_validate()
*/
function path_form_node_form_alter(&$form, $form_state) {
$node = $form_state['controller']->getEntity();
$path = array();
if (!$node->isNew()) {
$conditions = array('source' => 'node/' . $node->id());
if ($node->language()->id != Language::LANGCODE_NOT_SPECIFIED) {
$conditions['langcode'] = $node->language()->id;
}
$path = \Drupal::service('path.alias_storage')->load($conditions);
if ($path === FALSE) {
$path = array();
}
}
$path += array(
'pid' => NULL,
'source' => $node->id() ? 'node/' . $node->id() : NULL,
'alias' => '',
'langcode' => $node->language()->id,
);
$account = \Drupal::currentUser();
$form['path'] = array(
'#type' => 'details',
'#title' => t('URL path settings'),
'#open' => !empty($path['alias']),
'#group' => 'advanced',
'#attributes' => array(
'class' => array('path-form'),
),
'#attached' => array(
'library' => array('path/drupal.path'),
),
'#access' => $account->hasPermission('create url aliases') || $account->hasPermission('administer url aliases'),
'#weight' => 30,
'#tree' => TRUE,
'#element_validate' => array('path_form_element_validate'),
);
$form['path']['alias'] = array(
'#type' => 'textfield',
'#title' => t('URL alias'),
'#default_value' => $path['alias'],
'#maxlength' => 255,
'#description' => t('The alternative URL for this content. Use a relative path without a trailing slash. For example, enter "about" for the about page.'),
);
$form['path']['pid'] = array('#type' => 'value', '#value' => $path['pid']);
$form['path']['source'] = array('#type' => 'value', '#value' => $path['source']);
$form['path']['langcode'] = array('#type' => 'value', '#value' => $path['langcode']);
}
/**
* Form element validation handler for URL alias form element.
*
* @see path_form_node_form_alter()
*/
function path_form_element_validate($element, &$form_state, $complete_form) {
if (!empty($form_state['values']['path']['alias'])) {
// Trim the submitted value.
$alias = trim($form_state['values']['path']['alias']);
form_set_value($element['alias'], $alias, $form_state);
// Node language needs special care. Since the language of the URL alias
// depends on the node language, and the node language can be switched
// right within the same form, we need to conditionally overload the
// originally assigned URL alias language.
// @todo Remove this after converting Path module to a field, and, after
// stopping Locale module from abusing the content language system.
if (isset($form_state['values']['langcode'])) {
form_set_value($element['langcode'], $form_state['values']['langcode'], $form_state);
}
$path = $form_state['values']['path'];
// Ensure that the submitted alias does not exist yet.
if (\Drupal::service('path.alias_storage')->aliasExists($path['alias'], $path['langcode'], $path['source'])) {
form_error($element, $form_state, t('The alias is already in use.'));
}
}
}
/**
* Implements hook_form_FORM_ID_alter() for taxonomy_term_form().
*/
function path_form_taxonomy_term_form_alter(&$form, $form_state) {
$account = \Drupal::currentUser();
// Make sure this does not show up on the delete confirmation form.
if (empty($form_state['confirm_delete'])) {
$term = $form_state['controller']->getEntity();
$path = ($term->id() ? \Drupal::service('path.alias_storage')->load(array('source' => 'taxonomy/term/' . $term->id())) : array());
if ($path === FALSE) {
$path = array();
}
$path += array(
'pid' => NULL,
'source' => $term->id() ? 'taxonomy/term/' . $term->id() : NULL,
'alias' => '',
'langcode' => Language::LANGCODE_NOT_SPECIFIED,
);
$form['path'] = array(
'#access' => $account->hasPermission('create url aliases') || $account->hasPermission('administer url aliases'),
'#tree' => TRUE,
'#element_validate' => array('path_form_element_validate'),
);
$form['path']['alias'] = array(
'#type' => 'textfield',
'#title' => t('URL alias'),
'#default_value' => $path['alias'],
'#maxlength' => 255,
'#weight' => 0,
'#description' => t("Optionally specify an alternative URL by which this term can be accessed. Use a relative path and don't add a trailing slash or the URL alias won't work."),
if ($node->hasField('path') && $node->get('path')->access('edit')) {
$form['path_settings'] = array(
'#type' => 'details',
'#title' => t('URL path settings'),
'#open' => !empty($form['path']['widget'][0]['alias']['#value']),
'#group' => 'advanced',
'#attributes' => array(
'class' => array('path-form'),
),
'#attached' => array(
'library' => array('path/drupal.path'),
),
'#weight' => 30,
);
$form['path']['pid'] = array('#type' => 'value', '#value' => $path['pid']);
$form['path']['source'] = array('#type' => 'value', '#value' => $path['source']);
$form['path']['langcode'] = array('#type' => 'value', '#value' => $path['langcode']);
$form['path']['#group'] = 'path_settings';
}
}
......@@ -180,8 +83,13 @@ function path_form_taxonomy_term_form_alter(&$form, $form_state) {
function path_entity_base_field_info(EntityTypeInterface $entity_type) {
if ($entity_type->id() === 'taxonomy_term' || $entity_type->id() === 'node') {
$fields['path'] = FieldDefinition::create('path')
->setLabel(t('The path alias'))
->setComputed(TRUE);
->setLabel(t('URL alias'))
->setTranslatable(TRUE)
->setDisplayOptions('form', array(
'type' => 'path',
'weight' => 30,
))
->setDisplayConfigurable('form', TRUE);
return $fields;
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment