uuid_redirect.module in UUID Redirect 7
Redirects entity URLs to the correct place on this or other sites, based on UUID.
File
uuid_redirect.moduleView source
<?php
/**
* @file
* Redirects entity URLs to the correct place on this or other sites, based on UUID.
*/
/**
* Implements hook_permission().
*/
function uuid_redirect_permission() {
$permissions['administer uuid redirects'] = array(
'title' => t('Administer UUID redirects'),
);
return $permissions;
}
/**
* Implements hook_menu().
*/
function uuid_redirect_menu() {
$items['admin/config/search/uuid_redirect'] = array(
'title' => 'URL redirects using universally unique identifiers',
'description' => 'Redirect users from URLs on this site to corresponding URLs on another site, using universally unique identifiers to match them.',
'page callback' => 'drupal_get_form',
'page arguments' => array(
'uuid_redirect_settings_form',
),
'access arguments' => array(
'administer uuid redirects',
),
'file' => 'uuid_redirect.admin.inc',
);
return $items;
}
/**
* Implements hook_module_implements_alter().
*/
function uuid_redirect_module_implements_alter(&$implementations, $hook) {
// Our hook_menu_alter() implementation needs to run after any others, since
// we are trying to completely take over certain URLs and redirect them to
// another site.
if ($hook == 'menu_alter') {
$group = $implementations['uuid_redirect'];
unset($implementations['uuid_redirect']);
$implementations['uuid_redirect'] = $group;
}
}
/**
* Implements hook_menu_alter().
*
* Alter any menu callbacks that need to be redirected to an external URL.
*/
function uuid_redirect_menu_alter(&$items) {
if (variable_get('uuid_redirect_external_base_url') && ($paths = variable_get('uuid_redirect_menu_paths'))) {
$altered_paths = array();
foreach (preg_split('/\\s/', $paths) as $path) {
// Go through each component of the path and look for patterns like
// "%node" or "%node--article". Extract the information we need, and
// replace the path components like "%node--article" with the actual
// component we expect to find in the menu item (e.g., "%node").
$args = arg(NULL, $path);
$wildcard_info = array();
foreach ($args as $index => &$arg) {
if (preg_match('/^(%(.*?))(?:--(.+))?$/', $arg, $matches)) {
$arg = $matches[1];
// We need to cheat a little bit to support node revisions. Those use
// a named wildcard ("%node") followed by an unnamed one ("%") in
// which the revision is stored. So we modify the wildcard info for
// the latter accordingly in that case.
$wildcard_name = $matches[2];
if (empty($wildcard_name) && ($previous_wildcard_info = end($wildcard_info))) {
$load_function = $previous_wildcard_info['load function'];
$revision = TRUE;
}
else {
$load_function = $wildcard_name . '_load';
$revision = FALSE;
}
$wildcard_info[$index] = array(
'load function' => $load_function,
'bundle restrictions' => isset($matches[3]) ? array(
$matches[3],
) : array(),
'revision' => $revision,
);
}
}
$path = implode('/', $args);
// Now modify the corresponding menu item (if there is one) to replace
// its page callback with one that will perform the redirect. (This is
// done in a page callback so that only users with access to the URL
// actually experience the redirect.)
if (isset($items[$path]) && isset($items[$path]['page callback'])) {
// If it's the first time we're altering this path, we append two items
// to the end of the 'page arguments' array. The first stores the
// original page callback (which we use as a fallback in case the
// redirect shouldn't happen), and the second stores our wildcard info
// for this path. (Note that above we skipped modifying the menu item
// altogether if it did not have an existing page callback; i.e., if it
// was a default local task. We do not need to support redirecting
// default local tasks anyway, since the main path can be configured to
// redirect instead and the default local task will inherit it.)
if (empty($altered_paths[$path])) {
$items[$path]['page arguments'][] = $items[$path]['page callback'];
$items[$path]['page arguments'][] = $wildcard_info;
}
else {
$page_argument_keys = array_keys($items[$path]['page arguments']);
$wildcard_info_key = array_pop($page_argument_keys);
foreach ($wildcard_info as $index => $info) {
$current_bundle_info =& $items[$path]['page arguments'][$wildcard_info_key][$index]['bundle restrictions'];
// If at any point the path is declared to work for all bundles
// (e.g., with "%node"), that trumps all bundle restrictions.
$current_bundle_info = empty($info['bundle restrictions']) || empty($current_bundle_info) ? array() : array_merge($current_bundle_info, $info['bundle restrictions']);
}
}
// Define the custom page callback in which the redirect will occur.
$items[$path]['page callback'] = 'uuid_redirect_to_external_site';
$altered_paths[$path] = TRUE;
}
}
}
}
/**
* Page callback: Redirects to an external URL, after replacing IDs with UUIDs.
*/
function uuid_redirect_to_external_site() {
$page_arguments = func_get_args();
$wildcard_info = array_pop($page_arguments);
$original_page_callback = array_pop($page_arguments);
// Replace any entity IDs in the current path with UUIDs, and redirect to the
// external URL.
$args = arg();
$item = menu_get_item();
$skip_redirect = FALSE;
foreach ($wildcard_info as $index => $info) {
if (isset($item['map'][$index])) {
// We need to cheat a little bit to support node revisions. If an entity
// was not loaded from the wildcard (for example, if the wildcard is "%"
// rather than a named wildcard), we just keep using the previous loaded
// entity from earlier in the path.
if (is_object($item['map'][$index])) {
$entity = $item['map'][$index];
}
$entity_types_by_load_function = _uuid_redirect_entity_types_by_load_function();
if (isset($entity_types_by_load_function[$info['load function']])) {
// Allow modules to skip the redirect for this entity; see, for
// example, uuid_redirect_uuid_redirect_skip_redirect(). For
// consistency we always invoke the hook for all entities in the path,
// but if any module indicates that the redirect should be skipped, we
// honor that regardless of what happens later.
$entity_type = $entity_types_by_load_function[$info['load function']];
$responses = module_invoke_all('uuid_redirect_skip_redirect', $entity_type, $entity, $info);
$skip_redirect = $skip_redirect || in_array(TRUE, $responses, TRUE);
// Perform the appropriate UUID replacement.
$uuid_key_type = $info['revision'] ? 'revision uuid' : 'uuid';
$entity_info = entity_get_info($entity_type);
if (isset($entity_info['entity keys'][$uuid_key_type])) {
$uuid_key = $entity_info['entity keys'][$uuid_key_type];
if (isset($entity->{$uuid_key})) {
$args[$index] = $entity->{$uuid_key};
}
}
}
}
}
// Only redirect if we have a URL, and if we didn't already decide to skip
// redirecting above.
if (!$skip_redirect && ($base_url = variable_get('uuid_redirect_external_base_url'))) {
$new_path = implode('/', $args);
$new_url = $base_url . '/' . $new_path;
$redirect_function = module_exists('overlay') && overlay_get_mode() == 'child' ? 'overlay_close_dialog' : 'drupal_goto';
unset($_GET['destination']);
$redirect_function($new_url, array(
'query' => drupal_get_query_parameters(),
'external' => TRUE,
));
}
else {
return call_user_func_array($original_page_callback, $page_arguments);
}
}
/**
* Implements hook_uuid_redirect_skip_redirect().
*/
function uuid_redirect_uuid_redirect_skip_redirect($entity_type, $entity, $menu_info) {
// Do not redirect if we are only redirecting certain bundles and this isn't
// one of them.
if (!empty($menu_info['bundle restrictions'])) {
$entity_info = entity_get_info($entity_type);
if (isset($entity_info['entity keys']['bundle'])) {
$bundle_key = $entity_info['entity keys']['bundle'];
if (isset($entity->{$bundle_key}) && !in_array($entity->{$bundle_key}, $menu_info['bundle restrictions'])) {
return TRUE;
}
}
}
}
/**
* Implements hook_menu_get_item_alter().
*
* Replace any UUIDs in the current path with entity IDs, and, if any
* replacements were made, redirect to the new path on this site.
*
* We need to perform the redirects from inside this hook implementation
* because it is the only place where we always have access to the menu item
* corresponding to the current requested URL (even in cases where the request
* is for a path that would normally give a 404 error).
*/
function uuid_redirect_menu_get_item_alter(&$router_item, $path, $original_map) {
// If the current path's menu item has wildcards in it which are used to load
// an entity by its ID (e.g., node/%node/edit), check if a UUID was provided
// for the wildcard instead, and if so, substitute in the ID and redirect to
// the new URL.
if ($path == $_GET['q'] && !empty($router_item['load_functions'])) {
// We can tell which type of entity (if any) the wildcard is supposed to
// correspond to by looking at the menu item's load functions (e.g.,
// 'node_load') and seeing if it matches one defined in hook_entity_info().
$load_functions = unserialize($router_item['load_functions']);
$entity_types_by_load_function = _uuid_redirect_entity_types_by_load_function();
$replacements = array();
foreach ($load_functions as $arg => $load_function) {
$candidate_args = array(
$arg,
);
// If there were load arguments passed (e.g., in the case of node
// revisions) we should check those for UUIDs also.
if (is_array($load_function)) {
$load_arguments = reset($load_function);
$candidate_args = array_merge($candidate_args, $load_arguments);
$load_function = key($load_function);
}
if (isset($entity_types_by_load_function[$load_function])) {
$entity_type = $entity_types_by_load_function[$load_function];
foreach ($candidate_args as $candidate_arg) {
// Only perform the replacement if a valid UUID was provided for the
// wildcard in the URL. Since there's no easy way to distinguish
// revisions from non-revisions, we just check both each time by
// calling entity_get_id_by_uuid() twice.
$candidate_uuid = arg($candidate_arg, $path);
if (uuid_is_valid($candidate_uuid) && (($ids = entity_get_id_by_uuid($entity_type, array(
$candidate_uuid,
), FALSE)) || ($ids = entity_get_id_by_uuid($entity_type, array(
$candidate_uuid,
), TRUE)))) {
$replacements[$candidate_arg] = reset($ids);
}
}
}
}
if (!empty($replacements)) {
$args = arg(NULL, $path);
foreach ($replacements as $arg => $id) {
$args[$arg] = $id;
}
$new_path = implode('/', $args);
unset($_GET['destination']);
drupal_goto($new_path, array(
'query' => drupal_get_query_parameters(),
));
}
}
}
/**
* Returns an array of entity types keyed by their load functions.
*/
function _uuid_redirect_entity_types_by_load_function() {
$entity_types = array();
foreach (entity_get_info() as $type => $info) {
$entity_types[$info['load hook']] = $type;
}
// Special case for taxonomy terms, since they have an additional menu load
// function which is not found in hook_entity_info().
if (isset($entity_types['taxonomy_vocabulary_load']) && !isset($entity_types['taxonomy_vocabulary_machine_name_load'])) {
$entity_types['taxonomy_vocabulary_machine_name_load'] = $entity_types['taxonomy_vocabulary_load'];
}
return $entity_types;
}
Functions
Name![]() |
Description |
---|---|
uuid_redirect_menu | Implements hook_menu(). |
uuid_redirect_menu_alter | Implements hook_menu_alter(). |
uuid_redirect_menu_get_item_alter | Implements hook_menu_get_item_alter(). |
uuid_redirect_module_implements_alter | Implements hook_module_implements_alter(). |
uuid_redirect_permission | Implements hook_permission(). |
uuid_redirect_to_external_site | Page callback: Redirects to an external URL, after replacing IDs with UUIDs. |
uuid_redirect_uuid_redirect_skip_redirect | Implements hook_uuid_redirect_skip_redirect(). |
_uuid_redirect_entity_types_by_load_function | Returns an array of entity types keyed by their load functions. |