You are here

field_redirection.module in Field Redirection 7.2

Same filename and directory in other branches
  1. 7 field_redirection.module

Provides a field formatter to redirect to another path.

File

field_redirection.module
View source
<?php

/**
 * @file
 * Provides a field formatter to redirect to another path.
 */

/**
 * Implements hook_permission().
 */
function field_redirection_permission() {
  return array(
    'bypass redirection' => array(
      'title' => t('Bypass Redirection'),
      'description' => t('Allow the user to see the page this field belongs to and <em>not</em> redirect to the given path; the user will instead be provided a link to the path.'),
    ),
  );
}

/**
 * Implements hook_field_formatter_info().
 */
function field_redirection_field_formatter_info() {
  return array(
    'field_redirection' => array(
      'label' => t('Redirect'),
      // Default settings.
      'settings' => array(
        'code' => '301',
        '404_if_empty' => FALSE,
        // Flag to indicate how to show blocks on pages:
        // 0 = Ignore the setting.
        // 1 = Redirect on all except listed pages.
        // 2 = Redirect only on listed pages.
        'page_restrictions' => 0,
        'pages' => '',
      ),
      // The supported field types.
      'field types' => array(
        'entityreference',
        'file',
        'image',
        'link_field',
        'node_reference',
        'taxonomy_term_reference',
        'url',
        'user_reference',
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_settings_from().
 */
function field_redirection_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {

  // Shortcuts to make the rest of the code simpler.
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  $codes = field_redirection_http_codes();

  // Load the current selection, default to "301".
  $code = 301;
  if (!empty($settings['code']) && isset($codes[$settings['code']])) {
    $code = $settings['code'];
  }

  // Build a new structure for this formatter settings form element.
  $element = array();

  // Choose the redirector.
  $element['code'] = array(
    '#title' => 'HTTP status code',
    '#type' => 'select',
    '#options' => field_redirection_http_codes(),
    '#default_value' => $code,
  );

  // 404 if the field value is empty.
  $element['404_if_empty'] = array(
    '#type' => 'checkbox',
    '#title' => t('404 if URL empty'),
    '#default_value' => !empty($settings['404_if_empty']),
    '#description' => t('Optionally display a 404 error page if the associated URL field is empty.'),
  );
  $element['note'] = array(
    '#markup' => t('Note: If the destination path is the same as the current path it will behave as if it is empty.'),
    '#prefix' => '<p>',
    '#suffix' => '</p>',
  );

  // Provide targeted URL rules to trigger this action.
  $element['page_restrictions'] = array(
    '#type' => 'radios',
    '#title' => t('Redirect page restrictions'),
    '#default_value' => empty($settings['page_restrictions']) ? 0 : $settings['page_restrictions'],
    '#options' => array(
      0 => t('Redirect on all pages.'),
      1 => t('Redirect only on the following pages.'),
      2 => t('Redirect on all pages except the following pages.'),
    ),
  );
  $element['pages'] = array(
    '#type' => 'textarea',
    '#title' => t('Paths'),
    '#default_value' => empty($settings['pages']) ? '' : $settings['pages'],
    '#description' => t("Enter one page per line as Drupal paths. The '@wildcard' character is a wildcard. Example paths are '@example_blog' for the blog page and '@example_all_personal_blogs' for every personal blog. '@frontpage' is the front page. You can also use tokens in this field, for example '@example_current_node' can be used to define the current node path.", array(
      '@wildcard' => '*',
      '@example_blog' => 'blog',
      '@example_all_personal_blogs' => 'blog/*',
      '@frontpage' => '<front>',
      '@example_current_node' => 'node/[node:nid]',
    )),
    '#states' => array(
      'invisible' => array(
        ':input[name~="page_restrictions"]' => array(
          array(
            'value' => '0',
          ),
        ),
      ),
    ),
  );

  // Show the token browser.
  $element['available_tokens'] = array(
    '#type' => 'container',
    '#weight' => 100,
    '#states' => $element['pages']['#states'],
  );
  $element['available_tokens']['tokens'] = array(
    '#value' => 'Browse available tokens',
    '#theme' => 'token_tree',
    '#token_types' => array(
      $instance['entity_type'],
    ),
    '#dialog' => TRUE,
  );
  return $element;
}

/**
 * Implements hook_field_formatter_settings_summary().
 */
function field_redirection_field_formatter_settings_summary($field, $instance, $view_mode) {
  $output = '';

  // Shortcuts to make the rest of the code simpler.
  $display = $instance['display'][$view_mode];
  $settings = $display['settings'];
  $codes = field_redirection_http_codes();

  // Display a "hair on fire" warning message if the view mode is not 'full'.
  if ($view_mode != 'full') {
    $bad_modes = array(
      'default' => 'Default',
      'teaser' => 'Teaser',
      'rss' => 'RSS',
      'search_index' => 'Search index',
      'search_result' => 'Search result',
      'diff_standard' => 'Revision comparison',
      'revision' => 'Revision',
      'token' => 'Token',
    );
    if (array_key_exists($view_mode, $bad_modes)) {
      $output .= '<h2>' . t('Danger, Will Robinson!') . '</h2>';
      $output .= '<p>' . t('The Redirect formatter should not be used with the ":mode" view mode, it works best when used with the ":full" view mode.', array(
        ':mode' => $bad_modes[$view_mode],
        ':full' => t('Full content'),
      )) . '</p>';
    }
    else {
      $output .= '<h3>' . t('Careful now!') . '</h3>';
      $output .= '<p>' . t('Be careful using the Redirect formatter with this view mode, it could lead to problems.') . '</p>';
    }
  }

  // Work out which redirection code was being used, default to "301".
  $code = 301;
  if (!empty($settings['code']) && isset($codes[$settings['code']])) {
    $code = $settings['code'];
  }
  $output .= t('Redirect method: @code', array(
    '@code' => $codes[$code],
  ));

  // Indicate if the option is used.
  if ($settings['404_if_empty']) {
    $output .= ' (404 if empty)';
  }

  // Show path rules if the page_restrictions option is enabled.
  if (!empty($settings['page_restrictions']) && !empty($settings['pages'])) {
    $output .= '<br />' . t('Redirect restricted to certain paths.');
  }
  return $output;
}

/**
 * The standard HTTP redirection codes that are supported.
 *
 * @return array
 *   The supported HTTP codes.
 */
function field_redirection_http_codes() {
  return array(
    '300' => t('300: Multiple Choices (rarely used)'),
    '301' => t('301: Moved Permanently (default)'),
    '302' => t('302: Found (rarely used)'),
    '303' => t('303: See Other (rarely used)'),
    '304' => t('304: Not Modified (rarely used)'),
    '305' => t('305: Use Proxy (rarely used)'),
    '307' => t('307: Temporary Redirect (temporarily moved)'),
  );
}

/**
 * Implements hook_field_formatter_view().
 *
 * If we have a node reference and we can redirect to it lets do it!
 */
function field_redirection_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {

  // Shortcuts to make the rest of the code simpler.
  $settings = $display['settings'];
  $codes = field_redirection_http_codes();

  // Optionally control the list of pages this works on.
  if (!empty($settings['page_restrictions']) && !empty($settings['pages'])) {

    // 1 = Redirect only on these pages, 2 = Redirect on all other pages.
    // Remove '1' from this value so it can be XOR'd later on.
    $page_restrictions = $settings['page_restrictions'] - 1;

    // Do raw token replacements.
    $pages = token_replace($settings['pages'], array(
      $entity_type => $entity,
    ), array(
      'clear' => 1,
      'sanitize' => 0,
    ));

    // Normalise all paths to lower case.
    $pages = drupal_strtolower($pages);
    $path_alias = drupal_strtolower(drupal_get_path_alias($_GET['q']));
    $page_match = drupal_match_path($path_alias, $pages);
    if ($path_alias != $_GET['q']) {
      $page_match = $page_match || drupal_match_path($_GET['q'], $pages);
    }

    // Stop processing if the page restrictions have matched.
    if (!($page_restrictions xor $page_match)) {
      return;
    }
  }

  // Don't execute if running via the CLI, e.g. Drush.
  if (constant('PHP_SAPI') == 'cli') {
    return;
  }
  elseif (strpos($_SERVER['PHP_SELF'], 'cron.php') !== FALSE) {
    return;
  }
  elseif ($_GET['q'] == 'admin/reports/status/run-cron') {
    return;
  }
  elseif (defined('MAINTENANCE_MODE') || variable_get('maintenance_mode', 0)) {
    return;
  }

  // Make some of the rest of the code simpler.
  $item = !empty($items[0]) ? $items[0] : array();
  $response_code = 301;
  if (!empty($settings['code']) && isset($codes[$settings['code']])) {
    $response_code = $settings['code'];
  }

  // Work out the destination path to redirect to. Each field type is handled
  // slightly differently, so identify that here.
  $path = '';
  $options = array();
  if (!empty($field['type'])) {
    switch ($field['type']) {

      // Entity reference field from the EntityReference module.
      case 'entityreference':
        if (!empty($item['target_id'])) {
          $referenced_entities = entity_load($field['settings']['target_type'], array(
            $item['target_id'],
          ));
          if (!empty($referenced_entities)) {
            $referenced_entity = reset($referenced_entities);
            if (!empty($referenced_entity)) {
              $uri = entity_uri($field['settings']['target_type'], $referenced_entity);
              if (!empty($uri['path'])) {
                $path = drupal_get_path_alias($uri['path']);
              }
            }
          }
        }
        break;

      // Fields from the core Image and File modules.
      case 'file':
      case 'image':
        if (!empty($item['uri'])) {
          $path = file_create_url($item['uri']);
        }
        break;

      // Link field from the Link module.
      case 'link_field':
        if (!empty($item['url'])) {

          // The path is the URL field itself.
          $path = $item['url'];

          // Cover for cases when a query string was provided.
          if (!empty($item['query'])) {
            $options['query'] = $item['query'];
          }

          // Optional fragment.
          if (!empty($item['fragment'])) {
            $options['fragment'] = $item['fragment'];
          }

          // Special handling for the front page.
          if ($path == '&lt;front&gt;') {
            $path = '<front>';
          }
        }
        break;

      // Node reference field from the node_reference module, part of the
      // References module.
      case 'node_reference':
      case 'node_reference_autocomplete':
        if (!empty($item['nid'])) {

          // Wrap the internal system path with its alias.
          $path = drupal_get_path_alias('node/' . $item['nid']);
        }
        break;

      // Term reference field from the core Taxonomy module.
      case 'taxonomy_term_reference':
      case 'taxonomy_term_reference_autocomplete':
        if (!empty($item['tid'])) {

          // Wrap the internal system path with its alias.
          $path = drupal_get_path_alias('taxonomy/term/' . $item['tid']);
        }
        break;

      // URL field from the URL module.
      case 'url':
        if (!empty($item['path'])) {
          $path = $item['path'];
          $options = $item['options'];
        }
        break;

      // User reference field from the user_reference module, part of the
      // References module.
      case 'user_reference':
      case 'user_reference_autocomplete':
        if (!empty($item['uid'])) {

          // Wrap the internal system path with its alias.
          $path = drupal_get_path_alias('user/' . $item['uid']);
        }
        break;
    }
  }

  // Don't redirect if the destination is the current page.
  if (!empty($path)) {

    // Simplest case.
    if ($_GET['q'] == $path) {
      $path = '';
    }
    else {
      $current_path = current_path();
      if ($path == $current_path || $path == url($current_path, array(
        'absolute' => TRUE,
      ))) {
        $path = '';
      }
      elseif ($path == '<front>' && drupal_is_front_page()) {
        $path = '';
      }
    }
  }

  // Only proceed if a path was identified.
  if (!empty($path)) {

    // Use default language to not prepend language prefix if path is absolute
    // without hostname.
    if ($path[0] == '/') {
      $path = substr($path, 1);
      $options['language'] = language_default();
    }

    // If the user has permission to bypass the page redirection, return a
    // message explaining where they would have been redirected to.
    if (user_access('bypass redirection')) {

      // "Listen very carefully, I shall say this only once." - 'Allo, 'Allo.
      $message = t('This page is set to redirect to <a href="!path">another URL</a>, but you have permission to see the page and not be automatically redirected.', array(
        '!path' => url($path, $options),
      ));
      if (empty($_SESSION['messages']['warning']) || !in_array($message, $_SESSION['messages']['warning'])) {
        drupal_set_message($message, 'warning');
      }
      $elements = array();
      $elements[] = array(
        '#theme' => 'link',
        '#text' => t('Redirected location'),
        '#path' => $path,
        '#options' => array(
          'attributes' => array(),
          'html' => FALSE,
        ),
      );
      return $elements;
    }
    else {

      // If caching this page, add 'Cache-Control' header before redirecting.
      $caching_enabled = variable_get('cache', 0);
      $page_cacheable = drupal_page_is_cacheable();
      $no_session_cookie = !isset($_COOKIE[session_name()]);
      if ($caching_enabled && $page_cacheable && $no_session_cookie) {

        // @see drupal_serve_page_from_cache().
        // If the client sent a session cookie, a cached copy will only be
        // served to that one particular client due to Vary: Cookie. Thus, do
        // not set max-age > 0, allowing the page to be cached by external
        // proxies, when a session cookie is present unless the Vary header has
        // been replaced or unset in hook_boot().
        $max_age = !isset($_COOKIE[session_name()]) || isset($hook_boot_headers['vary']) ? variable_get('page_cache_maximum_age', 0) : 0;
        drupal_add_http_header('Cache-Control', 'public, max-age=' . $max_age);
      }
      drupal_goto($path, $options, $response_code);
    }
  }
  elseif (!user_access('bypass redirection') && $display['settings']['404_if_empty']) {
    drupal_not_found();
  }
}