diff --git a/core/lib/Drupal/Core/Path/Path.php b/core/lib/Drupal/Core/Path/Path.php index a7fd55a..ed5aa31 100644 --- a/core/lib/Drupal/Core/Path/Path.php +++ b/core/lib/Drupal/Core/Path/Path.php @@ -130,7 +130,9 @@ public function load($conditions) { * An array of criteria. */ public function delete($conditions) { - $path = $this->load($conditions); + if (!$path = $this->load($conditions)) { + return; + } $query = $this->connection->delete('url_alias'); foreach ($conditions as $field => $value) { $query->condition($field, $value); diff --git a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php index c935c44..41d7d97 100644 --- a/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php +++ b/core/modules/field/lib/Drupal/field/Plugin/Type/Formatter/FormatterPluginManager.php @@ -76,16 +76,27 @@ public function getInstance(array $options) { $configuration = $this->prepareConfiguration($field['type'], $configuration); } - $plugin_id = $configuration['type']; + if (isset($configuration['type'])) { + $plugin_id = $configuration['type']; - // Switch back to default formatter if either: - // - $type_info doesn't exist (the widget type is unknown), - // - the field type is not allowed for the widget. - $definition = $this->getDefinition($configuration['type']); - if (!isset($definition['class']) || !in_array($field['type'], $definition['field_types'])) { - // Grab the default widget for the field type. - $field_type_definition = field_info_field_types($field['type']); - $plugin_id = $field_type_definition['default_formatter']; + // Switch back to default formatter if either: + // - $type_info doesn't exist (the formatter type is unknown), + // - the field type is not allowed for the formatter. + $definition = $this->getDefinition($configuration['type']); + if (!isset($definition['class']) || !in_array($field['type'], $definition['field_types'])) { + // Grab the default formatter for the field type. + $field_type_definition = field_info_field_types($field['type']); + if (!empty($field_type_definition['default_formatter'])) { + $plugin_id = $field_type_definition['default_formatter']; + } + } + } + + // If no formatter is configured for this field instance and the field type + // does not define a default_formatter, then this field does not have any + // associated formatters (and is not meant to be output). + if (empty($plugin_id)) { + return; } $configuration += array( @@ -115,11 +126,14 @@ public function prepareConfiguration($field_type, array $configuration) { // If no formatter is specified, use the default formatter. if (!isset($configuration['type'])) { $field_type = field_info_field_types($field_type); - $configuration['type'] = $field_type['default_formatter']; + if (!empty($field_type['default_formatter'])) { + $configuration['type'] = $field_type['default_formatter']; + } } // Fill in default settings values for the formatter. - $configuration['settings'] += field_info_formatter_settings($configuration['type']); - + if (isset($configuration['type'])) { + $configuration['settings'] += field_info_formatter_settings($configuration['type']); + } return $configuration; } diff --git a/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php b/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php index 12e6011..c579a24 100644 --- a/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php +++ b/core/modules/field_ui/lib/Drupal/field_ui/DisplayOverview.php @@ -107,7 +107,7 @@ public function build(array $form, array &$form_state) { '#region_callback' => 'field_ui_display_overview_row_region', '#js_settings' => array( 'rowHandler' => 'field', - 'defaultFormatter' => $field_types[$field['type']]['default_formatter'], + 'defaultFormatter' => isset($field_types[$field['type']]['default_formatter']) ? $field_types[$field['type']]['default_formatter'] : 'hidden', ), 'human_name' => array( '#markup' => check_plain($instance['label']), @@ -153,7 +153,7 @@ public function build(array $form, array &$form_state) { '#title' => t('Formatter for @title', array('@title' => $instance['label'])), '#title_display' => 'invisible', '#options' => $formatter_options, - '#default_value' => $display_options ? $display_options['type'] : 'hidden', + '#default_value' => isset($display_options['type']) ? $display_options['type'] : 'hidden', '#parents' => array('fields', $name, 'type'), '#attributes' => array('class' => array('field-formatter-type')), ), @@ -170,7 +170,7 @@ public function build(array $form, array &$form_state) { } // Get the corresponding formatter object. - if ($display_options && $display_options['type'] != 'hidden') { + if (isset($display_options['type']) && $display_options['type'] != 'hidden') { $formatter = drupal_container()->get('plugin.manager.field.formatter')->getInstance(array( 'instance' => $instance, 'view_mode' => $this->view_mode, diff --git a/core/modules/path/lib/Drupal/path/Plugin/field/widget/PathWidget.php b/core/modules/path/lib/Drupal/path/Plugin/field/widget/PathWidget.php new file mode 100644 index 0000000..dcd03bb --- /dev/null +++ b/core/modules/path/lib/Drupal/path/Plugin/field/widget/PathWidget.php @@ -0,0 +1,147 @@ +isNew()) { + $uri = $entity->uri(); + $conditions = array( + 'source' => $uri['path'], + ); + if ($langcode != LANGUAGE_NOT_SPECIFIED) { + $conditions['langcode'] = $langcode; + } + if ($path = drupal_container()->get('path.crud')->load($conditions)) { + // The field is supposed to store the actual user input. + $path['value'] = $path['alias']; + } + else { + $path = $conditions; + } + } + $path += array( + 'pid' => NULL, + 'source' => NULL, + 'value' => isset($items[$delta]['value']) ? $items[$delta]['value'] : NULL, + 'langcode' => $langcode, + ); + $language = language_load($langcode); + + $element += array( + '#type' => 'container', + '#attributes' => array( + 'class' => array('path-form'), + ), + '#attached' => array( + 'library' => array(array('path', 'drupal.path')), + ), + '#access' => user_access('create url aliases') || user_access('administer url aliases'), + '#tree' => TRUE, + '#path' => $path, + '#element_validate' => array(array($this, 'validatePath')), + ); + $element['pid'] = array( + '#type' => 'value', + '#value' => $path['pid'], + ); + // Prepare the site's base URL as field prefix, taking the currently edited + // entity language into account. If the language has no path prefix, the + // site URL ends with a slash; if there is a prefix, there is no trailing + // slash; ensure there is one in all cases. + $field_prefix = trim(url('', array('absolute' => TRUE, 'language' => $language)), '/') . '/'; + $element['value'] = array( + '#type' => 'textfield', + '#title' => $element['#title'], + '#field_prefix' => check_plain($field_prefix), + '#default_value' => $path['value'], + '#required' => $element['#required'], + '#maxlength' => 255, + '#description' => $element['#description'], + ); + // Remove the #description from $element, as it would be duplicated into the + // container otherwise. + unset($element['#description']); + + $element['source'] = array( + '#type' => 'value', + '#value' => $path['source'], + ); + $element['langcode'] = array( + '#type' => 'value', + '#value' => $path['langcode'], + ); + + // Integrate with advanced settings, if available. + if (isset($form['advanced'])) { + $element['#type'] = 'details'; + $element['#group'] = 'advanced'; + $element['#title'] = t('URL settings'); + } + + return $element; + } + + /** + * Form element validation handler for PathWidget. + */ + public function validatePath(&$element, &$form_state, $form) { + if ($element['value']['#value'] !== '') { + // Trim the submitted value. + $element['value']['#value'] = trim($element['value']['#value']); + form_set_value($element['value'], $element['value']['#value'], $form_state); + + // Entity language needs special care. Since the language of the URL alias + // depends on the entity language, and the entity language may be switched + // right within the same form, we need to conditionally overload the + // originally assigned URL alias language. + // @todo Remove this 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); + } + + // Ensure that the submitted alias does not exist yet. + $query = db_select('url_alias') + ->condition('alias', $element['value']['#value']) + ->condition('langcode', $element['langcode']['#value']); + if (!empty($element['source']['#value'])) { + $query->condition('source', $element['source']['#value'], '<>'); + } + $query->addExpression('1'); + $query->range(0, 1); + if ($query->execute()->fetchField()) { + form_error($element['value'], t('The alias is already in use.')); + } + } + } + +} diff --git a/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php b/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php index 23ff7f7..c79c1c1 100644 --- a/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php +++ b/core/modules/path/lib/Drupal/path/Tests/PathAliasTest.php @@ -12,13 +12,6 @@ */ class PathAliasTest extends PathTestBase { - /** - * Modules to enable. - * - * @var array - */ - public static $modules = array('path'); - public static function getInfo() { return array( 'name' => 'Path alias functionality', @@ -137,26 +130,27 @@ function testAdminAlias() { function testNodeAlias() { // Create test node. $node1 = $this->drupalCreateNode(); + $lang_code = LANGUAGE_NOT_SPECIFIED; // Create alias. $edit = array(); - $edit['path[alias]'] = $this->randomName(8); + $edit["path[$lang_code][0][value]"] = $this->randomName(8); $this->drupalPost('node/' . $node1->nid . '/edit', $edit, t('Save')); // Confirm that the alias works. - $this->drupalGet($edit['path[alias]']); + $this->drupalGet($edit["path[$lang_code][0][value]"]); $this->assertText($node1->label(), 'Alias works.'); $this->assertResponse(200); // Change alias to one containing "exotic" characters. - $previous = $edit['path[alias]']; - $edit['path[alias]'] = "- ._~!$'\"()*@[]?&+%#,;=:" . // "Special" ASCII characters. + $previous = $edit["path[$lang_code][0][value]"]; + $edit["path[$lang_code][0][value]"] = "- ._~!$'\"()*@[]?&+%#,;=:" . // "Special" ASCII characters. "%23%25%26%2B%2F%3F" . // Characters that look like a percent-escaped string. "éøïвβ中國書۞"; // Characters from various non-ASCII alphabets. $this->drupalPost('node/' . $node1->nid . '/edit', $edit, t('Save')); // Confirm that the alias works. - $this->drupalGet($edit['path[alias]']); + $this->drupalGet($edit["path[$lang_code][0][value]"]); $this->assertText($node1->label(), 'Changed alias works.'); $this->assertResponse(200); @@ -169,17 +163,17 @@ function testNodeAlias() { $node2 = $this->drupalCreateNode(); // Set alias to second test node. - // Leave $edit['path[alias]'] the same. + // Leave $edit['path[und][0][value]'] the same. $this->drupalPost('node/' . $node2->nid . '/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->drupalPost('node/' . $node1->nid . '/edit', array('path[alias]' => ''), t('Save')); + $this->drupalPost('node/' . $node1->nid . '/edit', array('path[und][0][value]' => ''), t('Save')); // Confirm that the alias no longer works. - $this->drupalGet($edit['path[alias]']); + $this->drupalGet($edit["path[$lang_code][0][value]"]); $this->assertNoText($node1->label(), 'Alias was successfully deleted.'); $this->assertResponse(404); } @@ -204,13 +198,14 @@ function testDuplicateNodeAlias() { // Create one node with a random alias. $node_one = $this->drupalCreateNode(); $edit = array(); - $edit['path[alias]'] = $this->randomName(); + $lang_code = LANGUAGE_NOT_SPECIFIED; + $edit["path[$lang_code][0][value]"] = $this->randomName(); $this->drupalPost('node/' . $node_one->nid . '/edit', $edit, t('Save')); // Now create another node and try to set the same alias. $node_two = $this->drupalCreateNode(); $this->drupalPost('node/' . $node_two->nid . '/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[$lang_code][0][value]' and contains(@class, 'error')]", $edit["path[$lang_code][0][value]"], 'Textfield exists and has the error class.'); } } diff --git a/core/modules/path/lib/Drupal/path/Tests/PathCustomUITest.php b/core/modules/path/lib/Drupal/path/Tests/PathCustomUITest.php new file mode 100644 index 0000000..6013a88 --- /dev/null +++ b/core/modules/path/lib/Drupal/path/Tests/PathCustomUITest.php @@ -0,0 +1,87 @@ + 'Custom URL alias fields', + 'description' => 'Tests custom URL alias fields.', + 'group' => 'Path', + ); + } + + /** + * Tests alias functionality through the admin interfaces. + */ + function testCustomUserAlias() { + $label = 'My URL alias'; + $field_name = 'url_alias'; + $langcode = LANGUAGE_NOT_SPECIFIED; + $edit_field_key = 'field_' . $field_name . '[' . $langcode . '][0][value]'; + + // Add a URL alias field to user accounts. + $this->drupalLogin($this->root_user); + $edit = array( + 'fields[_add_new_field][label]' => $label, + 'fields[_add_new_field][field_name]' => $field_name, + 'fields[_add_new_field][type]' => 'path', + 'fields[_add_new_field][widget_type]' => 'path_default', + ); + $this->drupalPost('admin/config/people/accounts/fields', $edit, t('Save')); + + // Verify that it appears as hidden in the display settings. + $this->drupalGet('admin/config/people/accounts/display'); + $this->assertFieldByName('fields[field_url_alias][type]', 'hidden'); + + // Create a user account and add a URL alias for it. + $account = $this->drupalCreateUser(); + $alias = 'members/foo'; + $edit = array( + $edit_field_key => $alias, + ); + $this->drupalPost("user/$account->uid/edit", $edit, t('Save')); + $this->assertText($label); + + // Verify that the URL alias works. + $this->drupalGet($alias); + $this->assertUrl($alias); + $this->assertResponse(200); + $this->drupalGet("user/$account->uid"); + $this->assertUrl("user/$account->uid"); + $this->assertResponse(200); + + // Remove the URL alias and verify that it was removed. + $edit = array( + $edit_field_key => '', + ); + $this->drupalPost("user/$account->uid/edit", $edit, t('Save')); + $this->drupalGet($alias); + $this->assertUrl($alias); + $this->assertResponse(404); + $this->drupalGet("user/$account->uid"); + $this->assertUrl("user/$account->uid"); + $this->assertResponse(200); + + // Verify that the user is not able to edit the URL alias field. + $this->drupalLogin($account); + $this->drupalGet("user/$account->uid/edit"); + $this->assertNoText($label); + } +} diff --git a/core/modules/path/lib/Drupal/path/Tests/PathFieldCRUDUnitTest.php b/core/modules/path/lib/Drupal/path/Tests/PathFieldCRUDUnitTest.php new file mode 100644 index 0000000..4f29248 --- /dev/null +++ b/core/modules/path/lib/Drupal/path/Tests/PathFieldCRUDUnitTest.php @@ -0,0 +1,144 @@ + 'Path field CRUD operations', + 'description' => 'Tests path field CRUD operations.', + 'group' => 'Path', + ); + } + + function setUp() { + parent::setUp(); + + $this->enableModules(array('field', 'node')); + $this->installSchema('system', 'url_alias'); + + $this->nodeType = (object) array( + 'type' => 'page', + 'name' => 'Basic page', + ); + node_type_save($this->nodeType); + + // Create a path field for the node type. + $this->field_name = drupal_strtolower($this->randomName()); + $this->langcode = LANGUAGE_NOT_SPECIFIED; + $this->field = array( + 'field_name' => $this->field_name, + 'type' => 'path', + ); + field_create_field($this->field); + $this->instance = array( + 'field_name' => $this->field_name, + 'entity_type' => 'node', + 'bundle' => $this->nodeType->type, + 'widget' => array( + 'type' => 'path_default', + ), + ); + field_create_instance($this->instance); + } + + /** + * Tests a basic CRUD flow for path fields. + */ + function testBasicCRUD() { + // Create and save an entity with an alias. + $entity = entity_create('node', array( + 'type' => $this->nodeType->type, + 'title' => $this->randomName(), + )); + $edit = array( + 'value' => 'test-alias', + ); + $entity->{$this->field_name}[$this->langcode][0] = $edit; + $entity->save(); + $uri = $entity->uri(); + + // Verify that field data and the URL alias was stored. + $data = $entity->{$this->field_name}[$this->langcode][0]; + $this->assertTrue($data['pid']); + $this->assertIdentical($data['value'], $edit['value']); + $this->assertIdentical($data['alias'], $edit['value']); + $path = $this->container->get('path.crud')->load(array('pid' => $data['pid'])); + $this->assertIdentical($path['source'], $uri['path']); + $this->assertIdentical($path['alias'], $edit['value']); + + // Edit the field value and update the entity. + $updated_value = 'updated-alias'; + $entity->{$this->field_name}[$this->langcode][0]['value'] = $updated_value; + $entity->save(); + + // Verify that field data and the URL alias was stored. + $data = $entity->{$this->field_name}[$this->langcode][0]; + $this->assertIdentical($data['pid'], $path['pid']); + $this->assertIdentical($data['value'], $updated_value); + $this->assertIdentical($data['alias'], $updated_value); + $path = $this->container->get('path.crud')->load(array('pid' => $data['pid'])); + $this->assertIdentical($path['source'], $uri['path']); + $this->assertIdentical($path['alias'], $updated_value); + + // Verify that there is only one alias. + $count = db_query('SELECT COUNT(*) FROM {url_alias} WHERE source = :source', array( + ':source' => $uri['path'], + ))->fetchField(); + $this->assertEqual($count, 1); + + // Delete the entity. + $entity->delete(); + + // Verify that the alias no longer exists. + $path = $this->container->get('path.crud')->load(array('pid' => $data['pid'])); + $this->assertFalse($path); + $count = db_query('SELECT COUNT(*) FROM {url_alias} WHERE source = :source', array( + ':source' => $uri['path'], + ))->fetchField(); + $this->assertEqual($count, 0); + } + + /** + * Tests updating of field data after deletion of aliases via URL alias API. + */ + function testPathDelete() { + // Create and save an entity with an alias. + $entity = entity_create('node', array( + 'type' => $this->nodeType->type, + 'title' => $this->randomName(), + )); + $edit = array( + 'value' => 'test-alias', + ); + $entity->{$this->field_name}[$this->langcode][0] = $edit; + $entity->save(); + $uri = $entity->uri(); + + // Delete the alias. + $this->container->get('path.crud')->delete(array('source' => $uri['path'])); + + // Reload the entity and verify that the alias no longer exists. + $entity = entity_load('node', $entity->id()); + $this->assertFalse($entity->{$this->field_name}); + } + +} diff --git a/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php b/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php index 455a3f6..93512e2 100644 --- a/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php +++ b/core/modules/path/lib/Drupal/path/Tests/PathLanguageTest.php @@ -17,7 +17,7 @@ class PathLanguageTest extends PathTestBase { * * @var array */ - public static $modules = array('path', 'locale', 'translation'); + public static $modules = array('locale', 'translation_entity'); public static function getInfo() { return array( @@ -30,19 +30,26 @@ public static function getInfo() { function setUp() { parent::setUp(); - // Create and login user. - $this->web_user = $this->drupalCreateUser(array('edit any page content', 'create page content', 'administer url aliases', 'create url aliases', 'administer languages', 'translate all content', 'access administration pages', 'administer content types')); - $this->drupalLogin($this->web_user); + // Permissions do not matter for this test. + $this->drupalLogin($this->root_user); // Enable French language. $edit = array(); $edit['predefined_langcode'] = 'fr'; - $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); // Enable URL language detection and selection. $edit = array('language_interface[enabled][language-url]' => 1); $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings')); + + // @todo Rewrite entity_translation module to provide proper APIs. + $edit = array( + 'entity_types[node]' => 'node', + 'settings[node][page][translatable]' => TRUE, + 'settings[node][page][fields][body]' => 'body', + 'settings[node][page][fields][path]' => 'path', + ); + $this->drupalPost('admin/config/regional/content-language', $edit, t('Save')); } /** @@ -55,42 +62,41 @@ function testAliasTranslation() { ); $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type')); $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), 'Basic page content type has been updated.'); - variable_set('node_type_language_translation_enabled_page', TRUE); - $english_node = $this->drupalCreateNode(array('type' => 'page')); + $node = $this->drupalCreateNode(array('type' => 'page')); $english_alias = $this->randomName(); // Edit the node to set language and path. $edit = array(); $edit['langcode'] = 'en'; - $edit['path[alias]'] = $english_alias; - $this->drupalPost('node/' . $english_node->nid . '/edit', $edit, t('Save')); + $lang_code = LANGUAGE_NOT_SPECIFIED; + $edit["path[$lang_code][0][value]"] = $english_alias; + $this->drupalPost('node/' . $node->nid . '/edit', $edit, t('Save and keep published')); // Confirm that the alias works. $this->drupalGet($english_alias); - $this->assertText($english_node->label(), 'Alias works.'); + $this->assertText($node->label(), 'Alias works.'); // Translate the node into French. - $this->drupalGet('node/' . $english_node->nid . '/translate'); - $this->clickLink(t('Add translation')); + $this->drupalGet('node/' . $node->nid . '/translations'); + $this->clickLink(t('Add')); $edit = array(); - $langcode = LANGUAGE_NOT_SPECIFIED; - $edit["title"] = $this->randomName(); - $edit["body[$langcode][0][value]"] = $this->randomName(); + $edit['title'] = $this->randomName(); + $edit['body[fr][0][value]'] = $this->randomName(); $french_alias = $this->randomName(); - $edit['path[alias]'] = $french_alias; - $this->drupalPost(NULL, $edit, t('Save')); + $edit['path[fr][0][value]'] = $french_alias; + $this->drupalPost(NULL, $edit, t('Save and keep published')); // Clear the path lookup cache. drupal_container()->get('path.alias_manager')->cacheClear(); // Ensure the node was created. - $french_node = $this->drupalGetNodeByTitle($edit["title"]); - $this->assertTrue(($french_node), 'Node found in database.'); + $node = $this->drupalGetNodeByTitle($edit["title"]); + $this->assertTrue($node, 'Node found in database.'); // Confirm that the alias works. - $this->drupalGet('fr/' . $edit['path[alias]']); - $this->assertText($french_node->label(), 'Alias for French translation works.'); + $this->drupalGet('fr/' . $edit['path[fr][0][value]']); + $this->assertText($node->label(), 'Alias for French translation works.'); // Confirm that the alias is returned by url(). Languages are cached on // many levels, and we need to clear those caches. @@ -98,8 +104,8 @@ function testAliasTranslation() { drupal_static_reset('language_url_outbound_alter'); drupal_static_reset('language_url_rewrite_url'); $languages = language_list(); - $url = url('node/' . $french_node->nid, array('language' => $languages[$french_node->langcode])); - $this->assertTrue(strpos($url, $edit['path[alias]']), 'URL contains the path alias.'); + $url = url('node/' . $node->nid, array('language' => language_load('fr'))); + $this->assertTrue(strpos($url, $edit['path[fr][0][value]']), '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. @@ -113,7 +119,7 @@ function testAliasTranslation() { // Change user language preference. $edit = array('preferred_langcode' => 'fr'); - $this->drupalPost("user/{$this->web_user->uid}/edit", $edit, t('Save')); + $this->drupalPost("user/{$this->root_user->uid}/edit", $edit, t('Save')); // Check that the English alias works. In this situation French is the // current UI and content language, while URL language is English (since we @@ -124,11 +130,11 @@ function testAliasTranslation() { // path alias for French matching the english alias. So the alias manager // needs to use the URL language to check whether the alias is valid. $this->drupalGet($english_alias); - $this->assertText($english_node->label(), 'Alias for English translation works.'); + $this->assertText($node->body['en'][0]['value'], 'Alias for English translation works.'); // Check that the French alias works. $this->drupalGet("fr/$french_alias"); - $this->assertText($french_node->label(), 'Alias for French translation works.'); + $this->assertText($node->body['fr'][0]['value'], 'Alias for French translation works.'); // Disable URL language negotiation. $edit = array('language_interface[enabled][language-url]' => FALSE); @@ -136,7 +142,7 @@ function testAliasTranslation() { // Check that the English alias still works. $this->drupalGet($english_alias); - $this->assertText($english_node->label(), 'Alias for English translation works.'); + $this->assertText($node->body['en'][0]['value'], 'Alias for English translation works.'); // Check that the French alias is not available. We check the unprefixed // alias because we disabled URL language negotiation above. In this @@ -148,17 +154,17 @@ function testAliasTranslation() { // The alias manager has an internal path lookup cache. Check to see that // it has the appropriate contents at this point. drupal_container()->get('path.alias_manager')->cacheClear(); - $french_node_path = drupal_container()->get('path.alias_manager')->getSystemPath($french_alias, $french_node->langcode); - $this->assertEqual($french_node_path, 'node/' . $french_node->nid, 'Normal path works.'); + $french_node_path = drupal_container()->get('path.alias_manager')->getSystemPath($french_alias, 'fr'); + $this->assertEqual($french_node_path, 'node/' . $node->nid, 'Normal path works.'); // Second call should return the same path. - $french_node_path = drupal_container()->get('path.alias_manager')->getSystemPath($french_alias, $french_node->langcode); - $this->assertEqual($french_node_path, 'node/' . $french_node->nid, 'Normal path is the same.'); + $french_node_path = drupal_container()->get('path.alias_manager')->getSystemPath($french_alias, 'fr'); + $this->assertEqual($french_node_path, 'node/' . $node->nid, 'Normal path is the same.'); // Confirm that the alias works. - $french_node_alias = drupal_container()->get('path.alias_manager')->getPathAlias('node/' . $french_node->nid, $french_node->langcode); + $french_node_alias = drupal_container()->get('path.alias_manager')->getPathAlias('node/' . $node->nid, 'fr'); $this->assertEqual($french_node_alias, $french_alias, 'Alias works.'); // Second call should return the same alias. - $french_node_alias = drupal_container()->get('path.alias_manager')->getPathAlias('node/' . $french_node->nid, $french_node->langcode); + $french_node_alias = drupal_container()->get('path.alias_manager')->getPathAlias('node/' . $node->nid, 'fr'); $this->assertEqual($french_node_alias, $french_alias, 'Alias is the same.'); } } diff --git a/core/modules/path/lib/Drupal/path/Tests/PathLanguageUiTest.php b/core/modules/path/lib/Drupal/path/Tests/PathLanguageUiTest.php index bd0d279..ab6a9bd 100644 --- a/core/modules/path/lib/Drupal/path/Tests/PathLanguageUiTest.php +++ b/core/modules/path/lib/Drupal/path/Tests/PathLanguageUiTest.php @@ -17,7 +17,7 @@ class PathLanguageUiTest extends PathTestBase { * * @var array */ - public static $modules = array('path', 'locale'); + public static $modules = array('locale'); public static function getInfo() { return array( diff --git a/core/modules/path/lib/Drupal/path/Tests/PathTaxonomyTermTest.php b/core/modules/path/lib/Drupal/path/Tests/PathTaxonomyTermTest.php index 2122b9a..a0912e6 100644 --- a/core/modules/path/lib/Drupal/path/Tests/PathTaxonomyTermTest.php +++ b/core/modules/path/lib/Drupal/path/Tests/PathTaxonomyTermTest.php @@ -30,12 +30,13 @@ public static function getInfo() { function setUp() { parent::setUp(); - // Create a Tags vocabulary for the Article node type. + // Create a Tags vocabulary for the Basic page node type. $vocabulary = entity_create('taxonomy_vocabulary', array( 'name' => t('Tags'), 'vid' => 'tags', )); $vocabulary->save(); + path_add_default_field_instance('taxonomy_term', 'tags'); // Create and login user. $web_user = $this->drupalCreateUser(array('administer url aliases', 'administer taxonomy', 'access administration pages')); @@ -52,36 +53,36 @@ function testTermAlias() { $edit = array( 'name' => $this->randomName(), 'description[value]' => $description, - 'path[alias]' => $this->randomName(), + 'path[und][0][value]' => $this->randomName(), ); $this->drupalPost('admin/structure/taxonomy/' . $vocabulary->id() . '/add', $edit, t('Save')); // Confirm that the alias works. - $this->drupalGet($edit['path[alias]']); + $this->drupalGet($edit['path[und][0][value]']); $this->assertText($description, 'Term can be accessed on URL alias.'); // Change the term's URL alias. $tid = db_query("SELECT tid FROM {taxonomy_term_data} WHERE name = :name", array(':name' => $edit['name']))->fetchField(); $edit2 = array(); - $edit2['path[alias]'] = $this->randomName(); + $edit2['path[und][0][value]'] = $this->randomName(); $this->drupalPost('taxonomy/term/' . $tid . '/edit', $edit2, t('Save')); // Confirm that the changed alias works. - $this->drupalGet($edit2['path[alias]']); + $this->drupalGet($edit2['path[und][0][value]']); $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[und][0][value]']); $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[und][0][value]'] = ''; $this->drupalPost('taxonomy/term/' . $tid . '/edit', $edit3, t('Save')); // Confirm that the alias no longer works. - $this->drupalGet($edit2['path[alias]']); + $this->drupalGet($edit2['path[und][0][value]']); $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/lib/Drupal/path/Tests/PathTestBase.php b/core/modules/path/lib/Drupal/path/Tests/PathTestBase.php index 97f3f81..9aafb56 100644 --- a/core/modules/path/lib/Drupal/path/Tests/PathTestBase.php +++ b/core/modules/path/lib/Drupal/path/Tests/PathTestBase.php @@ -24,10 +24,10 @@ function setUp() { parent::setUp(); - // Create Basic page and Article node types. + // Create Basic page node type. if ($this->profile != 'standard') { $this->drupalCreateContentType(array('type' => 'page', 'name' => 'Basic page')); - $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article')); + path_add_default_field_instance('node', 'page'); } } } diff --git a/core/modules/path/path.install b/core/modules/path/path.install new file mode 100644 index 0000000..eae337d --- /dev/null +++ b/core/modules/path/path.install @@ -0,0 +1,38 @@ + array( + 'pid' => array( + 'description' => 'Foreign Key: The path alias ID from {url_alias}.pid.', + 'type' => 'int', + 'not null' => FALSE, + ), + // This partially duplicates {url_alias}.alias, but contains the actual user + // input, which should be stored per field, to make it an inherent part of + // an entity's life-cycle; e.g., exposing changed values in field revisions. + 'value' => array( + 'description' => 'The user-customizable part of the URL alias.', + 'type' => 'varchar', + 'length' => 255, + 'not null' => FALSE, + ), + ), + 'foreign keys' => array( + 'path_url_alias' => array( + 'table' => 'url_alias', + 'columns' => array( + 'pid' => 'pid', + ), + ), + ), + ); +} diff --git a/core/modules/path/path.js b/core/modules/path/path.js index 7349d12..a568a3a 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 = $(context).find('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 7d3fadd..a4f1f53 100644 --- a/core/modules/path/path.module +++ b/core/modules/path/path.module @@ -5,9 +5,7 @@ * Enables users to rename URLs. */ -use Drupal\node\Plugin\Core\Entity\Node; - -use Drupal\taxonomy\Plugin\Core\Entity\Term; +use Drupal\Core\Entity\EntityInterface; /** * Implements hook_help(). @@ -20,8 +18,10 @@ function path_help($path, $arg) { $output .= '
' . t('The Path module allows you to specify an alias, or custom URL, for any existing internal system path. Aliases should not be confused with URL redirects, which allow you to forward a changed or inactive URL to a new URL. In addition to making URLs more readable, aliases also help search engines index content more effectively. Multiple aliases may be used for a single internal system path. To automate the aliasing of paths, you can install the contributed module Pathauto. For more information, see the online handbook entry for the Path module.', array('@path' => 'http://drupal.org/documentation/modules/path', '@pathauto' => 'http://drupal.org/project/pathauto')) . '
'; $output .= '