From 6a0257c22cb382c6275abfde885503f6ec1026b6 Mon Sep 17 00:00:00 2001
From: Nathaniel Catchpole <catch@35733.no-reply.drupal.org>
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 000000000000..cf7ec2b13c5a
--- /dev/null
+++ b/core/modules/path/lib/Drupal/path/Plugin/Field/FieldType/PathFieldItemList.php
@@ -0,0 +1,28 @@
+<?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');
+  }
+
+}
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 1150c99957fd..ed44d8669cea 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 @@
  *   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);
     }
   }
 
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 000000000000..ac465d9a8cbe
--- /dev/null
+++ b/core/modules/path/lib/Drupal/path/Plugin/Field/FieldWidget/PathWidget.php
@@ -0,0 +1,116 @@
+<?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'];
+  }
+
+}
diff --git a/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php b/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php
index ab53370af0c6..f99ff786ff39 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 @@ 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.');
   }
 }
diff --git a/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php b/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php
index a2fcbb727326..c2c168b94ea1 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 @@ 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.
diff --git a/core/modules/path/lib/Drupal/path/Tests/PathTaxonomyTermTest.php b/core/modules/path/lib/Drupal/path/Tests/PathTaxonomyTermTest.php
index d2fd278a0cb4..6c9d0e693582 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 @@ 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.');
   }
diff --git a/core/modules/path/path.js b/core/modules/path/path.js
index f0a6687ced4e..a4d0b6cf8c0e 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 ee852a268d57..9be6cbd80f93 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,
-    );
-    $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;
   }
-- 
GitLab