Commit cd7b8f09 authored by webchick's avatar webchick

#320331 by Dave Reid, dww, John Morahan, cwgordon7, moshe weitzman, c960657,...

#320331 by Dave Reid, dww, John Morahan, cwgordon7, moshe weitzman, c960657, and smoothify: Turn custom_url_rewrite_inbound() and custom_url_rewrite_outbound() into hooks.
parent dec6514c
......@@ -96,8 +96,16 @@ function authorize_access_allowed() {
// Load the code that drives the authorize process.
require_once DRUPAL_ROOT . '/includes/authorize.inc';
// Initialize the URL path, but not via raising our bootstrap level.
drupal_path_initialize();
// For the sake of Batch API and a few other low-level functions, we need to
// initialize the URL path into $_GET['q']. However, we do not want to raise
// our bootstrap level, nor do we want to call drupal_initialize_path(),
// since that is assuming that modules are loaded and invoking hooks.
// However, all we really care is if we're in the middle of a batch, in which
// case $_GET['q'] will already be set, we just initialize it to an empty
// string if it's not already defined.
if (!isset($_GET['q'])) {
$_GET['q'] = '';
}
if (isset($_SESSION['authorize_operation']['page_title'])) {
drupal_set_title(check_plain($_SESSION['authorize_operation']['page_title']));
......
......@@ -126,16 +126,11 @@
*/
define('DRUPAL_BOOTSTRAP_LANGUAGE', 6);
/**
* Eighth bootstrap phase: set $_GET['q'] to Drupal path of request.
*/
define('DRUPAL_BOOTSTRAP_PATH', 7);
/**
* Final bootstrap phase: Drupal is fully loaded; validate and fix
* input data.
*/
define('DRUPAL_BOOTSTRAP_FULL', 8);
define('DRUPAL_BOOTSTRAP_FULL', 7);
/**
* Role ID for anonymous users; should match what's in the "role" table.
......@@ -1405,7 +1400,6 @@ function drupal_bootstrap($phase = NULL, $new_phase = TRUE) {
DRUPAL_BOOTSTRAP_SESSION,
DRUPAL_BOOTSTRAP_PAGE_HEADER,
DRUPAL_BOOTSTRAP_LANGUAGE,
DRUPAL_BOOTSTRAP_PATH,
DRUPAL_BOOTSTRAP_FULL,
));
$completed_phase = &drupal_static(__FUNCTION__ . '_completed_phase', -1);
......@@ -1541,12 +1535,6 @@ function _drupal_bootstrap($phase) {
drupal_language_initialize();
break;
case DRUPAL_BOOTSTRAP_PATH:
require_once DRUPAL_ROOT . '/includes/path.inc';
// Initialize $_GET['q'] prior to loading modules and invoking hook_init().
drupal_path_initialize();
break;
case DRUPAL_BOOTSTRAP_FULL:
require_once DRUPAL_ROOT . '/includes/common.inc';
_drupal_bootstrap_full();
......
......@@ -2379,6 +2379,7 @@ function url($path = NULL, array $options = array()) {
'https' => FALSE,
'prefix' => ''
);
if (!isset($options['external'])) {
// Return an external link if $path contains an allowed absolute URL.
// Only call the slow filter_xss_bad_protocol if $path contains a ':' before
......@@ -2387,10 +2388,12 @@ function url($path = NULL, array $options = array()) {
$options['external'] = ($colonpos !== FALSE && !preg_match('![/?#]!', substr($path, 0, $colonpos)) && filter_xss_bad_protocol($path, FALSE) == check_plain($path));
}
// May need language dependent rewriting if language.inc is present.
if (function_exists('language_url_rewrite')) {
language_url_rewrite($path, $options);
}
// Preserve the original path before altering or aliasing.
$original_path = $path;
// Allow other modules to alter the outbound URL and options.
drupal_alter('url_outbound', $path, $options, $original_path);
if ($options['fragment']) {
$options['fragment'] = '#' . $options['fragment'];
}
......@@ -2439,21 +2442,16 @@ function url($path = NULL, array $options = array()) {
}
}
// Preserve the original path before aliasing.
$original_path = $path;
// The special path '<front>' links to the default front page.
if ($path == '<front>') {
$path = '';
}
elseif (!empty($path) && !$options['alias']) {
$language = isset($options['language']) && isset($options['language']->language) ? $options['language']->language : '';
$path = drupal_get_path_alias($path, $language);
}
if (function_exists('custom_url_rewrite_outbound')) {
// Modules may alter outbound links by reference.
custom_url_rewrite_outbound($path, $options, $original_path);
$alias = drupal_get_path_alias($original_path, $language);
if ($alias != $original_path) {
$path = $alias;
}
}
$base = $options['absolute'] ? $options['base_url'] . '/' : base_path();
......@@ -4223,6 +4221,7 @@ function _drupal_bootstrap_full() {
return;
}
$called = 1;
require_once DRUPAL_ROOT . '/includes/path.inc';
require_once DRUPAL_ROOT . '/includes/theme.inc';
require_once DRUPAL_ROOT . '/includes/pager.inc';
require_once DRUPAL_ROOT . '/includes/menu.inc';
......@@ -4256,6 +4255,8 @@ function _drupal_bootstrap_full() {
ini_set('log_errors', 1);
ini_set('error_log', file_directory_path() . '/error.log');
}
// Initialize $_GET['q'] prior to invoking hook_init().
drupal_path_initialize();
// Set a custom theme for the current page, if there is one. We need to run
// this before invoking hook_init(), since any modules which initialize the
// theme system will prevent a custom theme from being correctly set later.
......@@ -6115,4 +6116,3 @@ function drupal_get_updaters() {
}
return $updaters;
}
......@@ -324,47 +324,6 @@ function language_from_default() {
return language_default()->language;
}
/**
* Rewrite URLs allowing modules to hook in.
*
* @param $path
* The path to rewrite.
* @param $options
* An associative array of additional options as in url().
*/
function language_url_rewrite(&$path, &$options) {
// Only modify relative (insite) URLs.
if (!$options['external']) {
static $callbacks;
if (!isset($callbacks)) {
$callbacks = array();
foreach (language_types_configurable() as $type) {
// Get url rewriter callbacks only from enabled language providers.
$negotiation = variable_get("language_negotiation_$type", array());
foreach ($negotiation as $id => $provider) {
if (isset($provider['file'])) {
require_once DRUPAL_ROOT . '/' . $provider['file'];
}
// Avoid duplicate callback entries.
if (isset($provider['callbacks']['url_rewrite'])) {
$callbacks[$provider['callbacks']['url_rewrite']] = NULL;
}
}
}
$callbacks = array_keys($callbacks);
}
foreach ($callbacks as $callback) {
$callback($path, $options);
}
}
}
/**
* Split the given path into prefix and actual path.
*
......
......@@ -6,8 +6,8 @@
* Functions to handle paths in Drupal, including path aliasing.
*
* These functions are not loaded for cached pages, but modules that need
* to use them in hook_init() or hook exit() can make them available, by
* executing "drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH);".
* to use them in hook_boot() or hook exit() can make them available, by
* executing "drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);".
*/
/**
......@@ -204,15 +204,22 @@ function drupal_get_path_alias($path = NULL, $path_language = '') {
* internal path was found.
*/
function drupal_get_normal_path($path, $path_language = '') {
$result = $path;
$original_path = $path;
// Lookup the path alias first.
if ($source = drupal_lookup_path('source', $path, $path_language)) {
$result = $source;
$path = $source;
}
if (function_exists('custom_url_rewrite_inbound')) {
// Modules may alter the inbound request path by reference.
custom_url_rewrite_inbound($result, $path, $path_language);
// Allow other modules to alter the inbound URL. We cannot use drupal_alter()
// here because we need to run hook_url_inbound_alter() in the reverse order
// of hook_url_outbound_alter().
foreach (array_reverse(module_implements('url_inbound_alter')) as $module) {
$function = $module . '_url_inbound_alter';
$function($path, $original_path, $path_language);
}
return $result;
return $path;
}
/**
......@@ -347,7 +354,7 @@ function drupal_match_path($path, $patterns) {
* This function is not available in hook_boot() so use $_GET['q'] instead.
* However, be careful when doing that because in the case of Example #3
* $_GET['q'] will contain "path/alias". If "node/306" is needed, calling
* drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH) makes this function available.
* drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL) makes this function available.
*
* @return
* The current Drupal URL path.
......
......@@ -115,12 +115,6 @@ function forum_menu() {
'parent' => 'admin/structure/forum',
'file' => 'forum.admin.inc',
);
$items['admin/structure/forum/edit/%taxonomy_term'] = array(
'page callback' => 'forum_form_main',
'access arguments' => array('administer forums'),
'type' => MENU_CALLBACK,
'file' => 'forum.admin.inc',
);
$items['admin/structure/forum/edit/container/%taxonomy_term'] = array(
'title' => 'Edit container',
'page callback' => 'forum_form_main',
......@@ -610,7 +604,7 @@ function forum_block_view_pre_render($elements) {
*/
function forum_form($node, $form_state) {
$type = node_type_get_type($node);
if (!empty($node->nid)) {
$forum_terms = $node->taxonomy_forums;
// If editing, give option to leave shadows
......@@ -626,10 +620,15 @@ function forum_form($node, $form_state) {
}
/**
* Implement hook_term_path().
* Implement hook_url_outbound_alter().
*/
function forum_term_path($term) {
return 'forum/' . $term->tid;
function forum_url_outbound_alter(&$path, &$options, $original_path) {
if (preg_match('!^taxonomy/term/(\d+)!', $path, $matches)) {
$term = taxonomy_term_load($matches[1]);
if ($term && $term->vocabulary_machine_name == 'forums') {
$path = 'forum/' . $matches[1];
}
}
}
/**
......
......@@ -211,7 +211,7 @@ class ForumTestCase extends DrupalWebTestCase {
function deleteForum($tid) {
// Delete the forum.
$this->drupalPost('admin/structure/forum/edit/forum/' . $tid, array(), t('Delete'));
$this->drupalPost(NULL, NULL, t('Delete'));
$this->drupalPost(NULL, array(), t('Delete'));
// Assert that the forum no longer exists.
$this->drupalGet('forum/' . $tid);
......
......@@ -1067,3 +1067,41 @@ function locale_date_format_reset_form_submit($form, &$form_state) {
$form_state['redirect'] = 'admin/config/regional/date-time/locale';
}
/**
* Implement hook_url_outbound_alter().
*
* Rewrite outbound URLs with language based prefixes.
*/
function locale_url_outbound_alter(&$path, &$options, $original_path) {
// Only modify internal URLs.
if (!$options['external']) {
static $callbacks;
if (!isset($callbacks)) {
$callbacks = array();
include_once DRUPAL_ROOT . '/includes/language.inc';
foreach (language_types_configurable() as $type) {
// Get url rewriter callbacks only from enabled language providers.
$negotiation = variable_get("language_negotiation_$type", array());
foreach ($negotiation as $id => $provider) {
if (isset($provider['file'])) {
require_once DRUPAL_ROOT . '/' . $provider['file'];
}
// Avoid duplicate callback entries.
if (isset($provider['callbacks']['url_rewrite'])) {
$callbacks[$provider['callbacks']['url_rewrite']] = NULL;
}
}
}
$callbacks = array_keys($callbacks);
}
foreach ($callbacks as $callback) {
$callback($path, $options);
}
}
}
......@@ -105,7 +105,7 @@ function path_admin_form($form, &$form_state, $path = array('source' => '', 'ali
'#default_value' => $path['source'],
'#maxlength' => 255,
'#size' => 45,
'#description' => t('Specify the existing path you wish to alias. For example: node/28, forum/1, taxonomy/term/1+2.'),
'#description' => t('Specify the existing path you wish to alias. For example: node/28, forum/1, taxonomy/term/1.'),
'#field_prefix' => url(NULL, array('absolute' => TRUE)) . (variable_get('clean_url', 0) ? '' : '?q='),
'#required' => TRUE,
);
......@@ -150,7 +150,8 @@ function path_admin_form($form, &$form_state, $path = array('source' => '', 'ali
* Verify that a URL alias is valid
*/
function path_admin_form_validate($form, &$form_state) {
$source = $form_state['values']['source'];
$source = &$form_state['values']['source'];
$source = drupal_get_normal_path($source);
$alias = $form_state['values']['alias'];
$pid = isset($form_state['values']['pid']) ? $form_state['values']['pid'] : 0;
// Language is only set if locale module is enabled, otherwise save for all languages.
......
......@@ -126,3 +126,112 @@ class DrupalMatchPathTestCase extends DrupalWebTestCase {
);
}
}
/**
* Tests hook_url_alter functions.
*/
class UrlAlterFunctionalTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => t('URL altering'),
'description' => t('Tests hook_url_inbound_alter() and hook_url_outbound_alter().'),
'group' => t('Path API'),
);
}
function setUp() {
parent::setUp('path', 'forum', 'url_alter_test');
}
/**
* Test that URL altering works and that it occurs in the correct order.
*/
function testUrlAlter() {
$account = $this->drupalCreateUser(array('administer url aliases'));
$this->drupalLogin($account);
$uid = $account->uid;
$name = $account->name;
// Test a single altered path.
$this->assertUrlInboundAlter("user/$name", "user/$uid");
$this->assertUrlOutboundAlter("user/$uid", "user/$name");
// Test that a path always uses its alias.
$path = array('source' => "user/$uid/test1", 'alias' => 'alias/test1');
path_save($path);
$this->assertUrlInboundAlter('alias/test1', "user/$uid/test1");
$this->assertUrlOutboundAlter("user/$uid/test1", 'alias/test1');
// Test that alias source paths are normalized in the interface.
$edit = array('source' => "user/$name/edit", 'alias' => 'alias/test2');
$this->drupalPost('admin/config/search/path/add', $edit, t('Create new alias'));
$this->assertText(t('The alias has been saved.'));
// Test that a path always uses its alias.
$this->assertUrlInboundAlter('alias/test2', "user/$uid/edit");
$this->assertUrlOutboundAlter("user/$uid/edit", 'alias/test2');
// Test a non-existant user is not altered.
$uid++;
$this->assertUrlInboundAlter("user/$uid", "user/$uid");
$this->assertUrlOutboundAlter("user/$uid", "user/$uid");
// Test that 'forum' is altered to 'community' correctly.
$this->assertUrlInboundAlter('community', 'forum');
$this->assertUrlOutboundAlter('forum', 'community');
// Add a forum to test url altering.
$forum_vid = db_query("SELECT vid FROM {taxonomy_vocabulary} WHERE module = 'forum'")->fetchField();
$tid = db_insert('taxonomy_term_data')
->fields(array(
'name' => $this->randomName(),
'vid' => $forum_vid,
))
->execute();
// Test that a existing forum URL is altered.
$this->assertUrlInboundAlter("community/$tid", "forum/$tid");
$this->assertUrlOutboundAlter("taxonomy/term/$tid", "community/$tid");
// Test that a non-existant forum URL is not altered.
$tid++;
$this->assertUrlInboundAlter("taxonomy/term/$tid", "taxonomy/term/$tid");
$this->assertUrlOutboundAlter("taxonomy/term/$tid", "taxonomy/term/$tid");
}
/**
* Assert that an outbound path is altered to an expected value.
*
* @param $original
* A string with the original path that is run through url().
* @param $final
* A string with the expected result after url().
* @return
* TRUE if $original was correctly altered to $final, FALSE otherwise.
*/
protected function assertUrlOutboundAlter($original, $final) {
// Test outbound altering.
$result = url($original);
$base_path = base_path() . (variable_get('clean_url', '0') ? '' : '?q=');
$result = substr($result, strlen($base_path));
$this->assertIdentical($result, $final, t('Altered outbound URL %original, expected %final, and got %result.', array('%original' => $original, '%final' => $final, '%result' => $result)));
}
/**
* Assert that a inbound path is altered to an expected value.
*
* @param $original
* A string with the aliased or un-normal path that is run through
* drupal_get_normal_path().
* @param $final
* A string with the expected result after url().
* @return
* TRUE if $original was correctly altered to $final, FALSE otherwise.
*/
protected function assertUrlInboundAlter($original, $final) {
// Test inbound altering.
$result = drupal_get_normal_path($original);
$this->assertIdentical($result, $final, t('Altered inbound URL %original, expected %final, and got %result.', array('%original' => $original, '%final' => $final, '%result' => $result)));
}
}
; $Id$
name = Url_alter tests
description = A support modules for url_alter hook testing.
core = 7.x
package = Testing
version = VERSION
files[] = url_alter_test.module
files[] = url_alter_test.install
hidden = TRUE
<?php
// $Id$
/**
* Impelement hook_install().
*/
function url_alter_test_install() {
// Set the weight of this module to one higher than forum.module.
db_update('system')
->fields(array('weight' => 2))
->condition('name', 'url_alter_test')
->execute();
}
<?php
// $Id$
/**
* @file
* Module to help test hook_url_inbound_alter() and hook_url_outbound_alter().
*/
/**
* Implement hook_url_inbound_alter().
*/
function url_alter_test_url_inbound_alter(&$path, $original_path, $path_language) {
// Rewrite user/username to user/uid.
if (preg_match('!^user/([^/]+)(/.*)?!', $path, $matches)) {
if ($account = user_load_by_name($matches[1])) {
$matches += array(2 => '');
$path = 'user/' . $account->uid . $matches[2];
}
}
// Rewrite community/ to forum/.
if ($path == 'community' || strpos($path, 'community/') === 0) {
$path = 'forum' . substr($path, 9);
}
}
/**
* Implement hook_url_outbound_alter().
*/
function url_alter_test_url_outbound_alter(&$path, &$options, $original_path) {
// Rewrite user/uid to user/username.
if (preg_match('!^user/([0-9]+)(/.*)?!', $path, $matches)) {
if ($account = user_load($matches[1])) {
$matches += array(2 => '');
$path = 'user/' . $account->name . $matches[2];
}
}
// Rewrite forum/ to community/.
if ($path == 'forum' || strpos($path, 'forum/') === 0) {
$path = 'community' . substr($path, 5);
}
}
......@@ -45,7 +45,7 @@ function statistics_help($path, $arg) {
function statistics_exit() {
global $user;
drupal_bootstrap(DRUPAL_BOOTSTRAP_PATH);
drupal_bootstrap(DRUPAL_BOOTSTRAP_FULL);
if (variable_get('statistics_count_content_views', 0)) {
// We are counting content views.
......
......@@ -2666,6 +2666,62 @@ function hook_page_delivery_callback_alter(&$callback) {
}
}
/**
* Alters inbound URL requests.
*
* @param $path
* The path being constructed, which, if a path alias, has been resolved to a
* Drupal path by the database, and which also may have been altered by other
* modules before this one.
* @param $original_path
* The original path, before being checked for path aliases or altered by any
* modules.
* @param $path_language
* The language of the path.
*
* @see drupal_get_normal_path()
*/
function hook_url_inbound_alter(&$path, $original_path, $path_language) {
// Create the path user/me/edit, which allows a user to edit their account.
if (preg_match('|^user/me/edit(/.*)?|', $path, $matches)) {
global $user;
$path = 'user/' . $user->uid . '/edit' . $matches[1];
}
}
/**
* Alters outbound URLs.
*
* @param $path
* The outbound path to alter, not adjusted for path aliases yet. It won't be
* adjusted for path aliases until all modules are finished altering it, thus
* being consistent with hook_url_alter_inbound(), which adjusts for all path
* aliases before allowing modules to alter it. This may have been altered by
* other modules before this one.
* @param $options
* A set of URL options for the URL so elements such as a fragment or a query
* string can be added to the URL.
* @param $original_path
* The original path, before being altered by any modules.
*
* @see url()
*/
function hook_url_outbound_alter(&$path, &$options, $original_path) {
// Use an external RSS feed rather than the Drupal one.
if ($path == 'rss.xml') {
$path = 'http://example.com/rss.xml';
$options['external'] = TRUE;
}
// Instead of pointing to user/[uid]/edit, point to user/me/edit.
if (preg_match('|^user/([0-9]*)/edit(/.*)?|', $path, $matches)) {
global $user;
if ($user->uid == $matches[1]) {
$path = 'user/me/edit' . $matches[2];
}
}
}
/**
* @} End of "addtogroup hooks".
*/
......@@ -163,23 +163,6 @@ function taxonomy_theme() {
);
}
/**
* For vocabularies not maintained by taxonomy.module, give the maintaining
* module a chance to provide a path for terms in that vocabulary.
*
* @param $term
* A term object.
* @return
* An internal Drupal path.
*/
function taxonomy_term_path($term) {
$vocabulary = taxonomy_vocabulary_load($term->vid);
if ($vocabulary->module != 'taxonomy' && $path = module_invoke($vocabulary->module, 'term_path', $term)) {
return $path;
}
return 'taxonomy/term/' . $term->tid;
}
/**
* Implement hook_menu().
*/
......@@ -1103,7 +1086,7 @@ function taxonomy_field_formatter_info() {
function theme_field_formatter_taxonomy_term_link($variables) {
$term = $variables['element']['#item']['taxonomy_term'];
$attributes = empty($variables['link_options']) ? array() : $variables['link_options'];
return l($term->name, taxonomy_term_path($term), $attributes);
return l($term->name, 'taxonomy/term/' . $term->tid, $attributes);
}
/**
......
......@@ -22,7 +22,7 @@ function taxonomy_term_page($term) {
$breadcrumb = array();
while ($parents = taxonomy_get_parents($current->tid)) {
$current = array_shift($parents);
$breadcrumb[] = l($current->name, taxonomy_term_path($current));
$breadcrumb[] = l($current->name, 'taxonomy/term/' . $current->tid);
}
$breadcrumb[] = l(t('Home'), NULL);
$breadcrumb = array_reverse($breadcrumb);
......
......@@ -119,7 +119,7 @@ function taxonomy_tokens($type, $tokens, array $data = array(), array $options =
break;
case 'url':
$replacements[$original] = url(taxonomy_term_path($term), array('absolute' => TRUE));
$replacements[$original] = url('taxonomy/term/' . $term, array('absolute' => TRUE));
break;
case 'node-count':
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment