Commit 0fa300a6 authored by Dries's avatar Dries

- Patch #332333 by sun: more fixes for the path API.

parent 0d2b7fe8
......@@ -1258,7 +1258,7 @@ function node_language_provider($languages) {
// We cannot use args now.
$path = explode('/', $path);
// Act only if we are in a node page.
if ($path[0] == 'node' && $nid = intval($path[1])) {
if (isset($path[0]) && isset($path[1]) && $path[0] == 'node' && $nid = intval($path[1])) {
// We cannot perform a node load here.
$result = db_query('SELECT n.language FROM {node} n WHERE n.nid = :nid', array(':nid' => $nid))->fetchAssoc();
return $result['language'];
......
......@@ -8,6 +8,7 @@
/**
* Return a listing of all defined URL aliases.
*
* When filter key passed, perform a standard search on the given key,
* and return the list of matching URL aliases.
*/
......@@ -156,11 +157,11 @@ function path_admin_form_validate($form, &$form_state) {
$language = isset($form_state['values']['language']) ? $form_state['values']['language'] : '';
$has_alias = db_query("SELECT COUNT(alias) FROM {url_alias} WHERE pid <> :pid AND alias = :alias AND language = :language", array(
':pid' => $pid,
':alias' => $alias,
':language' => $language,
))
->fetchField();
':pid' => $pid,
':alias' => $alias,
':language' => $language,
))
->fetchField();
if ($has_alias) {
form_set_error('alias', t('The alias %alias is already in use in this language.', array('%alias' => $alias)));
......@@ -175,17 +176,13 @@ function path_admin_form_validate($form, &$form_state) {
* Save a URL alias to the database.
*/
function path_admin_form_submit($form, &$form_state) {
$path = array();
foreach (array('source', 'alias', 'pid', 'language') as $key) {
if (isset($form_state['values'][$key])) {
$path[$key] = $form_state['values'][$key];
}
}
path_save($path);
// Remove unnecessary values.
form_state_values_clean($form_state);
path_save($form_state['values']);
drupal_set_message(t('The alias has been saved.'));
$form_state['redirect'] = 'admin/config/search/path';
return;
}
/**
......@@ -211,7 +208,6 @@ function path_admin_delete_confirm_submit($form, &$form_state) {
if ($form_state['values']['confirm']) {
path_delete($form_state['path']['pid']);
$form_state['redirect'] = 'admin/config/search/path';
return;
}
}
......
......@@ -5,7 +5,7 @@
Drupal.behaviors.pathFieldsetSummaries = {
attach: function (context) {
$('fieldset#edit-path', context).setSummary(function (context) {
var path = $('#edit-path-1').val();
var path = $('#edit-path-alias').val();
return path ?
Drupal.t('Alias: @alias', { '@alias': path }) :
......
......@@ -22,13 +22,31 @@ function path_help($path, $arg) {
$output .= '<p>' . t('This module also provides user-defined mass URL aliasing capabilities, which is useful if you wish to uniformly use URLs different from the default. For example, you may want to have your URLs presented in a different language. Access to the Drupal source code on the web server is required to set up mass URL aliasing.') . ' </p>';
$output .= '<p>' . t('For more information, see the online handbook entry for <a href="@path">Path module</a>.', array('@path' => 'http://drupal.org/handbook/modules/path/')) . '</p>';
return $output;
case 'admin/config/search/path':
return '<p>' . t("An alias defines a different name for an existing URL path - for example, the alias 'about' for the URL path 'node/1'. A URL path can have multiple aliases.") . '</p>';
case 'admin/config/search/path/add':
return '<p>' . t('Enter the path you wish to create the alias for, followed by the name of the new alias.') . '</p>';
}
}
/**
* Implement hook_permission().
*/
function path_permission() {
return array(
'administer url aliases' => array(
'title' => t('Administer URL aliases'),
'description' => t('Manage URL aliases across the entire website.'),
),
'create url aliases' => array(
'title' => t('Create URL aliases'),
'description' => t('Manage URL aliases on content.'),
),
);
}
/**
* Implement hook_menu().
*/
......@@ -75,26 +93,34 @@ function path_menu() {
/**
* Fetch a specific URL alias from the database.
*
* @param $criteria
* @param $conditions
* A string representing the source, a number representing the pid, or an
* array of criteria.
* array of query conditions.
*
* @return
* FALSE if no alias was found or an associative array containing the
* following keys:
* - source: The internal system path.
* - alias: The URL alias.
* - pid: Unique path alias identifier.
* - language: The language of the alias.
*/
function path_load($criteria) {
if (is_numeric($criteria)) {
$criteria = array('pid' => $criteria);
function path_load($conditions) {
if (is_numeric($conditions)) {
$conditions = array('pid' => $conditions);
}
else if (is_string($criteria)) {
$criteria = array('source' => $criteria);
elseif (is_string($conditions)) {
$conditions = array('source' => $conditions);
}
else if (!is_array($criteria)) {
elseif (!is_array($conditions)) {
return FALSE;
}
$select = db_select('url_alias');
foreach ($criteria as $field => $value) {
foreach ($conditions as $field => $value) {
$select->condition($field, $value);
}
return $select
->fields('url_alias', array('source', 'alias', 'language', 'pid'))
->fields('url_alias')
->execute()
->fetchAssoc();
}
......@@ -103,38 +129,28 @@ function path_load($criteria) {
* Save a path alias to the database.
*
* @param $path
* A path array containing the following keys:
* - source: the initial path.
* - alias: the aliased path.
* - language: the language of the alias.
* - pid: unique path alias identifier (optional).
* An associative array containing the following keys:
* - source: The internal system path.
* - alias: The URL alias.
* - pid: (optional) Unique path alias identifier.
* - language: (optional) The language of the alias.
*/
function path_save($path) {
$path += array('language' => '', 'pid' => 0);
$pid = empty($path['pid']) ? 0 : $path['pid'];
$new = (bool) $pid;
unset($path['pid']);
// Make sure that this combination of source, alias, language wasn't save before.
$loaded_path = path_load($path);
if ($loaded_path) {
return $loaded_path;
}
if ($pid) {
db_update('url_alias')
->fields($path)
->condition('pid', $pid)
->execute();
}
else {
$pid = db_insert('url_alias')
->fields($path)
->execute();
}
$path['pid'] = $pid;
module_invoke_all('path_' . ($new ? 'insert' : 'update'), $path);
function path_save(&$path) {
$path += array('pid' => NULL, 'language' => '');
drupal_clear_path_cache();
return $path;
// Insert or update the alias.
$status = drupal_write_record('url_alias', $path, (!empty($path['pid']) ? 'pid' : NULL));
// Verify that a record was written.
if (isset($status) && $status) {
if ($status === SAVED_NEW) {
module_invoke_all('path_insert', $path);
}
else {
module_invoke_all('path_update', $path);
}
drupal_clear_path_cache();
}
}
/**
......@@ -148,51 +164,98 @@ function path_delete($criteria) {
$criteria = array('pid' => $criteria);
}
$path = path_load($criteria);
$delete = db_delete('url_alias');
$query = db_delete('url_alias');
foreach ($criteria as $field => $value) {
$delete->condition($field, $value);
$query->condition($field, $value);
}
$delete->execute();
$query->execute();
module_invoke_all('path_delete', $path);
drupal_clear_path_cache();
}
/**
* Implement hook_node_validate().
* Implement hook_form_alter().
*/
function path_node_validate($node, $form) {
if (user_access('create url aliases') || user_access('administer url aliases')) {
if (isset($node->path)) {
if (!is_array($node->path)) {
$node->path = array('alias' => $node->path);
}
$select = db_select('url_alias')->condition('alias', trim($node->path['alias']));
$select->addExpression('COUNT(alias)');
if ($node->nid) {
$select->condition('source', 'node/' . $node->nid, '<>');
}
if (isset($node->language)) {
$select->condition('language', $node->language);
function path_form_alter(&$form, $form_state, $form_id) {
if (!empty($form['#node_edit_form'])) {
$path = array();
if (!empty($form['#node']->nid)) {
$conditions = array('source' => 'node/' . $form['#node']->nid);
if (!empty($form['#node']->language)) {
$conditions['language'] = $form['#node']->language;
}
if ($select->execute()->fetchField()) {
form_set_error('path', t('The path is already in use.'));
$path = path_load($conditions);
if ($path === FALSE) {
$path = array();
}
}
$path += array(
'pid' => NULL,
'source' => isset($form['#node']->nid) ? 'node/' . $form['#node']->nid : NULL,
'alias' => '',
'language' => isset($form['#node']->language) ? $form['#node']->language : '',
);
$form['path'] = array(
'#type' => 'fieldset',
'#title' => t('URL path settings'),
'#collapsible' => TRUE,
'#collapsed' => empty($path['alias']),
'#group' => 'additional_settings',
'#attached' => array(
'js' => array(drupal_get_path('module', 'path') . '/path.js'),
),
'#access' => user_access('create url aliases') || user_access('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,
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#description' => t('Optionally specify an alternative URL by which this node can be accessed. For example, type "about" when writing an about page. 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']['language'] = array('#type' => 'value', '#value' => $path['language']);
}
}
/**
* Implement hook_node_load().
* Form element validation handler for URL alias form element.
*/
function path_node_load($nodes, $types) {
foreach ($nodes as $node) {
$criteria = array('source' => 'node/' . $node->nid);
if (isset($node->language)) {
$criteria['language'] = $node->language;
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 (Locale module) 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']['language'])) {
form_set_value($element['language'], $form_state['values']['language'], $form_state);
}
$alias = path_load($criteria);
if ($alias) {
$node->path = $alias;
$path = $form_state['values']['path'];
// Ensure that the submitted alias does not exist yet.
$query = db_select('url_alias')
->condition('alias', $path['alias'])
->condition('language', $path['language']);
if (!empty($path['source'])) {
$query->condition('source', $path['source'], '<>');
}
$query->addExpression('1');
$query->range(0, 1);
if ($query->execute()->fetchField()) {
form_set_error('alias', t('The alias is already in use.'));
}
}
}
......@@ -201,16 +264,16 @@ function path_node_load($nodes, $types) {
* Implement hook_node_insert().
*/
function path_node_insert($node) {
if ((user_access('create url aliases') || user_access('administer url aliases')) && isset($node->path)) {
if (!is_array($node->path)) {
$node->path = array('alias' => $node->path);
if (isset($node->path)) {
$path = $node->path;
$path['alias'] = trim($path['alias']);
// Only save a non-empty alias.
if (!empty($path['alias'])) {
// Ensure fields for programmatic executions.
$path['source'] = 'node/' . $node->nid;
$path['language'] = isset($node->language) ? $node->language : '';
path_save($path);
}
$node->path += array(
'source' => 'node/' . $node->nid,
'language' => isset($node->language) ? $node->language : '',
);
$node->path = path_save($node->path);
}
}
......@@ -218,18 +281,20 @@ function path_node_insert($node) {
* Implement hook_node_update().
*/
function path_node_update($node) {
if ((user_access('create url aliases') || user_access('administer url aliases')) && isset($node->path)) {
if (!is_array($node->path)) {
$node->path = array('alias' => $node->path);
if (isset($node->path)) {
$path = $node->path;
$path['alias'] = trim($path['alias']);
// Delete old alias if user erased it.
if (!empty($path['pid']) && empty($path['alias'])) {
path_delete($path['pid']);
}
if (isset($node->pid)) {
$node->path['pid'] = $node->pid;
// Only save a non-empty alias.
if (!empty($path['alias'])) {
// Ensure fields for programmatic executions.
$path['source'] = 'node/' . $node->nid;
$path['language'] = isset($node->language) ? $node->language : '';
path_save($path);
}
$node->path += array(
'source' => 'node/' . $node->nid,
'language' => isset($node->language) ? $node->language : '',
);
path_save($node->path);
}
}
......@@ -237,64 +302,8 @@ function path_node_update($node) {
* Implement hook_node_delete().
*/
function path_node_delete($node) {
if (isset($node->path)) {
if (!is_array($node->path)) {
$node->path = path_load(array('alias' => $node->path));
}
path_delete($node->path['pid']);
unset($node->path);
}
}
/**
* Implement hook_taxonomy_term_delete().
*/
function path_taxonomy_term_delete($term) {
path_delete(path_load('taxonomy/term/' . $term->tid));
}
/**
* Implement hook_form_alter().
*/
function path_form_alter(&$form, $form_state, $form_id) {
if (!empty($form['#node_edit_form'])) {
$path = NULL;
if (isset($form['#node']->path)) {
if (is_array($form['#node']->path)) {
$path = $form['#node']->path['alias'];
}
else {
$path = $form['#node']->path;
}
}
$form['path'] = array(
'#type' => 'fieldset',
'#title' => t('URL path settings'),
'#collapsible' => TRUE,
'#collapsed' => empty($path),
'#group' => 'additional_settings',
'#attached' => array(
'js' => array(drupal_get_path('module', 'path') . '/path.js'),
),
'#access' => user_access('create url aliases'),
'#weight' => 30,
);
$form['path']['path'] = array(
'#type' => 'textfield',
'#title' => t('URL alias'),
'#default_value' => $path,
'#maxlength' => 255,
'#collapsible' => TRUE,
'#collapsed' => TRUE,
'#description' => t('Optionally specify an alternative URL by which this node can be accessed. For example, type "about" when writing an about page. Use a relative path and don\'t add a trailing slash or the URL alias won\'t work.'),
);
if ($path) {
$form['path']['pid'] = array(
'#type' => 'value',
'#value' => $form['#node']->path['pid'],
);
}
}
// Delete all aliases associated with this node.
path_delete(array('source' => 'node/' . $node->nid));
}
/**
......@@ -303,86 +312,78 @@ function path_form_alter(&$form, $form_state, $form_id) {
function path_form_taxonomy_form_term_alter(&$form, $form_state) {
// Make sure this does not show up on the delete confirmation form.
if (empty($form_state['confirm_delete'])) {
// After a new term is added, populate the path field if it was set.
if (!empty($form['#term']['path'])) {
$path = $form['#term']['path'];
if (!is_array($path)) {
$path = path_load(array('alias' => $path));
}
}
else {
$alias = path_load('taxonomy/term/' . $form['#term']['tid']);
// Since drupal_get_path_alias() can return the default path, check if we really have an alias.
if ($alias['alias'] != 'taxonomy/term/' . $form['#term']['tid']) {
$path = $alias;
}
else {
$path = NULL;
}
$path = (isset($form['#term']['tid']) ? path_load('taxonomy/term/' . $form['#term']['tid']) : array());
if ($path === FALSE) {
$path = array();
}
$form['#validate'][] = 'path_taxonomy_term_validate';
$form['#submit'][] = 'path_taxonomy_term_submit';
$path += array(
'pid' => NULL,
'source' => isset($form['#term']['tid']) ? 'taxonomy/term/' . $form['#term']['tid'] : NULL,
'alias' => '',
'language' => '',
);
$form['identification']['path'] = array(
'#access' => user_access('create url aliases') || user_access('administer url aliases'),
'#tree' => TRUE,
'#element_validate' => array('path_form_element_validate'),
);
$form['identification']['path']['alias'] = array(
'#type' => 'textfield',
'#title' => t('URL alias'),
'#default_value' => $path['alias'],
'#maxlength' => 255,
'#weight' => 0,
'#access' => (user_access('create url aliases') || user_access('administer url aliases')),
'#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 ($path) {
// Populate with pid so we can update existing path entry instead of creating a new one.
$form['identification']['path']['pid'] = array(
'#type' => 'value',
'#access' => (user_access('create url aliases') || user_access('administer url aliases')),
'#value' => db_query("SELECT pid FROM {url_alias} WHERE alias = :alias", array(':alias' => $path['alias']))->fetchField(),
);
}
$form['identification']['path']['pid'] = array('#type' => 'value', '#value' => $path['pid']);
$form['identification']['path']['source'] = array('#type' => 'value', '#value' => $path['source']);
$form['identification']['path']['language'] = array('#type' => 'value', '#value' => $path['language']);
}
}
/**
* Path validation callback for taxonomy_form_term.
* Implement hook_taxonomy_term_insert().
*/
function path_taxonomy_term_validate($form, &$form_state) {
$path = path_load(array('alias' => $form_state['values']['path']));
if ($path) {
// If the pid matches the one in use for this term then we are fine.
if (isset($form_state['values']['pid']) && $path['pid'] == $form_state['values']['pid']) {
return;
function path_taxonomy_term_insert($term) {
if (isset($term->path)) {
$path = $term->path;
$path['alias'] = trim($path['alias']);
// Only save a non-empty alias.
if (!empty($path['alias'])) {
// Ensure fields for programmatic executions.
$path['source'] = 'taxonomy/term/' . $term->tid;
$path['language'] = '';
path_save($path);
}
form_set_error('path', t('The URL alias is already in use.'));
}
}
/**
* Path submission callback for taxonomy_form_term.
* Implement hook_taxonomy_term_update().
*/
function path_taxonomy_term_submit($form, &$form_state) {
// Make sure this is not triggered on the delete confirmation form.
if (empty($form_state['confirm_delete'])) {
$path = array(
'source' => 'taxonomy/term/' . $form_state['tid'],
'alias' => isset($form_state['values']['path']) ? $form_state['values']['path'] : NULL,
'pid' => isset($form_state['values']['pid']) ? $form_state['values']['pid'] : NULL,
);
path_save($path);
function path_taxonomy_term_update($term) {
if (isset($term->path)) {
$path = $term->path;
$path['alias'] = trim($path['alias']);
// Delete old alias if user erased it.
if (!empty($path['pid']) && empty($path['alias'])) {
path_delete($path['pid']);
}
// Only save a non-empty alias.
if (!empty($path['alias'])) {
// Ensure fields for programmatic executions.
$path['source'] = 'taxonomy/term/' . $term->tid;
$path['language'] = '';
path_save($path);
}
}
}
/**
* Implement hook_permission().
* Implement hook_taxonomy_term_delete().
*/
function path_permission() {
return array(
'administer url aliases' => array(
'title' => t('Administer URL aliases'),
'description' => t('Manage URL aliases across the entire website.'),
),
'create url aliases' => array(
'title' => t('Create URL aliases'),
'description' => t('Manage URL aliases on content.'),
),
);
function path_taxonomy_term_delete($term) {
// Delete all aliases associated with this term.
path_delete(array('source' => 'taxonomy/term/' . $term->tid));
}
......@@ -15,9 +15,6 @@ class PathTestCase extends DrupalWebTestCase {
);
}
/**
* Create user, setup permissions, log user in, and create a node.
*/
function setUp() {
parent::setUp('path');
......@@ -113,20 +110,20 @@ class PathTestCase extends DrupalWebTestCase {
// Create alias.
$edit = array();
$edit['path'] = $this->randomName(8);
$edit['path[alias]'] = $this->randomName(8);
$this->drupalPost('node/' . $node1->nid . '/edit', $edit, t('Save'));