From 6a0257c22cb382c6275abfde885503f6ec1026b6 Mon Sep 17 00:00:00 2001 From: Nathaniel Catchpole Date: Thu, 1 May 2014 12:43:49 +0100 Subject: [PATCH] Issue #2201051 by andypost, sun, Berdir: Convert path.module form alters to a field widget. --- .../Field/FieldType/PathFieldItemList.php | 28 ++++ .../path/Plugin/Field/FieldType/PathItem.php | 18 +-- .../Plugin/Field/FieldWidget/PathWidget.php | 116 +++++++++++++++ .../lib/Drupal/path/Tests/PathAliasTest.php | 22 +-- .../Drupal/path/Tests/PathLanguageTest.php | 14 +- .../path/Tests/PathTaxonomyTermTest.php | 16 +-- core/modules/path/path.js | 2 +- core/modules/path/path.module | 134 +++--------------- 8 files changed, 201 insertions(+), 149 deletions(-) create mode 100644 core/modules/path/lib/Drupal/path/Plugin/Field/FieldType/PathFieldItemList.php create mode 100644 core/modules/path/lib/Drupal/path/Plugin/Field/FieldWidget/PathWidget.php diff --git a/core/modules/path/lib/Drupal/path/Plugin/Field/FieldType/PathFieldItemList.php b/core/modules/path/lib/Drupal/path/Plugin/Field/FieldType/PathFieldItemList.php new file mode 100644 index 00000000000..cf7ec2b13c5 --- /dev/null +++ b/core/modules/path/lib/Drupal/path/Plugin/Field/FieldType/PathFieldItemList.php @@ -0,0 +1,28 @@ +hasPermission('create url aliases') || $account->hasPermission('administer url aliases'); + } + +} diff --git a/core/modules/path/lib/Drupal/path/Plugin/Field/FieldType/PathItem.php b/core/modules/path/lib/Drupal/path/Plugin/Field/FieldType/PathItem.php index 1150c99957f..ed44d8669ce 100644 --- a/core/modules/path/lib/Drupal/path/Plugin/Field/FieldType/PathItem.php +++ b/core/modules/path/lib/Drupal/path/Plugin/Field/FieldType/PathItem.php @@ -18,7 +18,9 @@ use Drupal\Core\TypedData\DataDefinition; * 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 @@ class PathItem extends FieldItemBase { 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 @@ class PathItem extends FieldItemBase { 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); } } diff --git a/core/modules/path/lib/Drupal/path/Plugin/Field/FieldWidget/PathWidget.php b/core/modules/path/lib/Drupal/path/Plugin/Field/FieldWidget/PathWidget.php new file mode 100644 index 00000000000..ac465d9a8cb --- /dev/null +++ b/core/modules/path/lib/Drupal/path/Plugin/Field/FieldWidget/PathWidget.php @@ -0,0 +1,116 @@ +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']; + } + +} diff --git a/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php b/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php index ab53370af0c..f99ff786ff3 100644 --- a/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php +++ b/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php @@ -146,29 +146,29 @@ class PathAliasTest extends PathTestBase { // 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 @@ class PathAliasTest extends PathTestBase { $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 @@ class PathAliasTest extends PathTestBase { // 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.'); } } diff --git a/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php b/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php index a2fcbb72732..c2c168b94ea 100644 --- a/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php +++ b/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php @@ -61,7 +61,11 @@ class PathLanguageTest extends PathTestBase { // 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 @@ class PathLanguageTest extends PathTestBase { // 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 @@ class PathLanguageTest extends PathTestBase { $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 @@ class PathLanguageTest extends PathTestBase { // 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 @@ class PathLanguageTest extends PathTestBase { $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. diff --git a/core/modules/path/lib/Drupal/path/Tests/PathTaxonomyTermTest.php b/core/modules/path/lib/Drupal/path/Tests/PathTaxonomyTermTest.php index d2fd278a0cb..6c9d0e69358 100644 --- a/core/modules/path/lib/Drupal/path/Tests/PathTaxonomyTermTest.php +++ b/core/modules/path/lib/Drupal/path/Tests/PathTaxonomyTermTest.php @@ -52,42 +52,42 @@ class PathTaxonomyTermTest extends PathTestBase { $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.'); } diff --git a/core/modules/path/path.js b/core/modules/path/path.js index f0a6687ced4..a4d0b6cf8c0 100644 --- a/core/modules/path/path.js +++ b/core/modules/path/path.js @@ -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 }) : diff --git a/core/modules/path/path.module b/core/modules/path/path.module index ee852a268d5..9be6cbd80f9 100644 --- a/core/modules/path/path.module +++ b/core/modules/path/path.module @@ -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, + 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'] = 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."), - ); - $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; }