You are here

rabbit_hole.module in Rabbit Hole 7

Main module file for Rabbit Hole.

This is a module that will prevent users from viewing the full node. This is configurable per content type or node.

File

rabbit_hole.module
View source
<?php

/**
 * @file
 * Main module file for Rabbit Hole.
 *
 * This is a module that will prevent users from viewing the full node. This is
 * configurable per content type or node.
 */
define('RABBIT_HOLE_USE_DEFAULT', -1);
define('RABBIT_HOLE_DISPLAY_CONTENT', 0);
define('RABBIT_HOLE_ACCESS_DENIED', 1);
define('RABBIT_HOLE_PAGE_NOT_FOUND', 2);
define('RABBIT_HOLE_PAGE_REDIRECT', 3);
define('RABBIT_HOLE_PAGE_REDIRECT_RESPONSE_DEFAULT', 301);

/**
 * Implements hook_permission().
 */
function rabbit_hole_permission() {
  return array(
    'administer rabbit hole' => array(
      'title' => t('Administer Rabbit Hole'),
      'description' => t('Configure Rabbit Hole per content type and node.'),
    ),
    'bypass rabbit hole' => array(
      'title' => t('Bypass Rabbit Hole'),
      'description' => t('Ignore the Rabbit Hole configuration and view any node in a regular way.'),
    ),
  );
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * This will add Rabbit Hole options to the node type form. These settings will
 * be used as default for every node of this node type.
 */
function rabbit_hole_form_node_type_form_alter(&$form, $form_state) {
  if (!user_access('administer rabbit hole')) {

    // The user doesn't have access.
    return;
  }
  if (isset($form['type'])) {
    $form['rabbit_hole'] = array(
      '#type' => 'fieldset',
      '#title' => t('Rabbit Hole settings'),
      '#collapsed' => TRUE,
      '#collapsible' => TRUE,
      '#group' => 'additional_settings',
      '#attributes' => array(
        'class' => array(
          'rabbit-hole-settings-form',
        ),
      ),
      '#attached' => array(
        'js' => array(
          drupal_get_path('module', 'rabbit_hole') . '/rabbit-hole.js',
          array(
            'data' => array(
              'rabbitHole' => array(
                'redirectValue' => RABBIT_HOLE_PAGE_REDIRECT,
              ),
            ),
            'type' => 'setting',
          ),
        ),
      ),
    );
    $form['rabbit_hole']['rabbit_hole_action'] = array(
      '#type' => 'radios',
      '#title' => t('Behavior'),
      '#options' => array(
        RABBIT_HOLE_DISPLAY_CONTENT => t('Display the content (regular behavior)'),
        RABBIT_HOLE_ACCESS_DENIED => t('Access denied'),
        RABBIT_HOLE_PAGE_NOT_FOUND => t('Page not found'),
        RABBIT_HOLE_PAGE_REDIRECT => t('Page redirect'),
      ),
      '#default_value' => variable_get('rabbit_hole_action_' . $form['#node_type']->type, RABBIT_HOLE_DISPLAY_CONTENT),
      '#description' => t('What should happen when someone tries to visit the node page?'),
    );
    $form['rabbit_hole']['redirect'] = array(
      '#type' => 'fieldset',
      '#title' => t('Redirect settings'),
      '#attributes' => array(
        'class' => array(
          'rabbit-hole-redirect-options',
        ),
      ),
    );
    $form['rabbit_hole']['redirect']['rabbit_hole_redirect'] = array(
      '#type' => 'textfield',
      '#title' => t('Redirect path'),
      '#size' => 40,
      '#default_value' => variable_get('rabbit_hole_redirect_' . $form['#node_type']->type, ''),
      '#description' => t('The relative path to were the user should be redirected. Leave this empty, or use %front to redirect to the front page. You may enter tokens in this field.', array(
        '%front' => '<front>',
      )),
    );

    // Display a list of tokens if the Token module is enabled.
    if (module_exists('token')) {
      $form['rabbit_hole']['redirect']['token_info'] = array(
        '#theme' => 'token_tree',
        '#token_types' => array(
          'node',
        ),
      );
    }
    $form['rabbit_hole']['redirect']['rabbit_hole_redirect_response'] = array(
      '#type' => 'select',
      '#title' => t('Response code'),
      '#options' => array(
        301 => t('301 (Moved Permanently)'),
        302 => t('302 (Found)'),
        303 => t('303 (See other)'),
        304 => t('304 (Not modified)'),
        305 => t('305 (Use proxy)'),
        307 => t('307 (Temporary redirect)'),
      ),
      '#default_value' => variable_get('rabbit_hole_redirect_response_' . $form['#node_type']->type, RABBIT_HOLE_PAGE_REDIRECT_RESPONSE_DEFAULT),
      '#description' => t('The response code that should be sent to the users browser. Follow !link for more information on response codes.', array(
        '!link' => l(t('this link'), 'http://api.drupal.org/api/drupal/includes--common.inc/function/drupal_goto/7'),
      )),
    );
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * This will add Rabbit Hole options to the node form. The user will be able to
 * override the default Rabbit Hole options.
 */
function rabbit_hole_form_node_form_alter(&$form, $form_state) {
  if (!user_access('administer rabbit hole')) {

    // The user doesn't have access.
    return;
  }
  $node = $form['#node'];
  $form['rabbit_hole'] = array(
    '#type' => 'fieldset',
    '#title' => t('Rabbit Hole settings'),
    '#collapsed' => TRUE,
    '#collapsible' => TRUE,
    '#group' => 'additional_settings',
    '#attributes' => array(
      'class' => array(
        'rabbit-hole-settings-form',
      ),
    ),
    '#attached' => array(
      'js' => array(
        drupal_get_path('module', 'rabbit_hole') . '/rabbit-hole.js',
        array(
          'data' => array(
            'rabbitHole' => array(
              'redirectValue' => RABBIT_HOLE_PAGE_REDIRECT,
            ),
          ),
          'type' => 'setting',
        ),
      ),
    ),
  );
  $form['rabbit_hole']['rabbit_hole_action'] = array(
    '#type' => 'radios',
    '#title' => t('Behavior'),
    '#options' => array(
      RABBIT_HOLE_USE_DEFAULT => t('Content type default'),
      RABBIT_HOLE_DISPLAY_CONTENT => t('Display the content (regular behavior)'),
      RABBIT_HOLE_ACCESS_DENIED => t('Access denied'),
      RABBIT_HOLE_PAGE_NOT_FOUND => t('Page not found'),
      RABBIT_HOLE_PAGE_REDIRECT => t('Page redirect'),
    ),
    '#default_value' => isset($node->rabbit_hole_action) ? $node->rabbit_hole_action : RABBIT_HOLE_USE_DEFAULT,
    '#description' => t('What should happen when someone tries to visit the node page?'),
  );
  $form['rabbit_hole']['redirect'] = array(
    '#type' => 'fieldset',
    '#title' => t('Redirect settings'),
    '#attributes' => array(
      'class' => array(
        'rabbit-hole-redirect-options',
      ),
    ),
  );
  $form['rabbit_hole']['redirect']['rabbit_hole_redirect'] = array(
    '#type' => 'textfield',
    '#title' => t('Redirect path'),
    '#size' => 40,
    '#default_value' => isset($node->rabbit_hole_redirect) ? $node->rabbit_hole_redirect : variable_get('rabbit_hole_redirect_' . $node->type, ''),
    '#description' => t('The relative path to were the user should be redirected. Leave this empty, or use %front to redirect to the front page.', array(
      '%front' => '<front>',
    )),
  );

  // Display a list of tokens if the Token module is enabled.
  if (module_exists('token')) {
    $form['rabbit_hole']['redirect']['token_info'] = array(
      '#theme' => 'token_tree',
      '#token_types' => array(
        'node',
      ),
    );
  }
  $form['rabbit_hole']['redirect']['rabbit_hole_redirect_response'] = array(
    '#type' => 'select',
    '#title' => t('Response code'),
    '#options' => array(
      301 => t('301 (Moved Permanently)'),
      302 => t('302 (Found)'),
      303 => t('303 (See other)'),
      304 => t('304 (Not modified)'),
      305 => t('305 (Use proxy)'),
      307 => t('307 (Temporary redirect)'),
    ),
    '#default_value' => isset($node->rabbit_hole_redirect_response) ? $node->rabbit_hole_redirect_response : variable_get('rabbit_hole_redirect_response_' . $node->type, RABBIT_HOLE_PAGE_REDIRECT_RESPONSE_DEFAULT),
    '#description' => t('The response code that should be sent to the users browser, e.g. 301. Follow !link for more information on response codes.', array(
      '!link' => l(t('this link'), 'http://api.drupal.org/api/drupal/includes--common.inc/function/drupal_goto/7'),
    )),
  );

  // Add a custom submit function. This is used to disable the redirect to
  // node/123 if Rabbit Hole is enabled.
  $form['actions']['submit']['#submit'][] = 'rabbit_hole_node_form_submit';
}

/**
 * Custom submit function for the node form.
 *
 * This will fire after the regular submit function, and it's purpose is to make
 * sure that the user doesn't get redirected to node/123 after saving the node,
 * if any Rabbit Hole action is enabled. This works by redirecting the user to
 * node/123/edit, if a destination parameter hasn't been set.
 *
 * @see node_form_submit().
 */
function rabbit_hole_node_form_submit($form, &$form_state) {

  // Get the action. Either the one specified for this node, or the default
  // value for the content type.
  $action = $form_state['values']['rabbit_hole_action'] != RABBIT_HOLE_USE_DEFAULT ? $form_state['values']['rabbit_hole_action'] : variable_get('rabbit_hole_action_' . $form_state['values']['type']);

  // If the action says anything else than to display the content, make sure
  // that the user doesn't land on the node view page. We'll check if a custom
  // redirect path has been set, otherwise, we'll redirect the user to the edit
  // page again.
  if ($action != RABBIT_HOLE_DISPLAY_CONTENT && $form_state['redirect'] == 'node/' . $form_state['values']['nid']) {
    $form_state['redirect'] = 'node/' . $form_state['values']['nid'] . '/edit';
  }
}

/**
 * Implements hook_node_view().
 */
function rabbit_hole_node_view($node, $view_mode, $langcode) {
  if ($view_mode != 'full' || !preg_match('/node\\/' . $node->nid . '(\\/view|)$/', current_path()) || user_access('bypass rabbit hole')) {

    // The node is not being viewed at it's own page, or the user is able to
    // bypass Rabbit Hole, exit early.
    return;
  }

  // Get the action. Use the one specified for this node, or fallback to the
  // default value for the content type.
  $action = isset($node->rabbit_hole_action) ? $node->rabbit_hole_action != RABBIT_HOLE_USE_DEFAULT ? $node->rabbit_hole_action : variable_get('rabbit_hole_action_' . $node->type) : variable_get('rabbit_hole_action_' . $node->type);

  // If we should perform a redirect, we will also get the path and response.
  if ($action == RABBIT_HOLE_PAGE_REDIRECT) {
    if (isset($node->rabbit_hole_action) && $node->rabbit_hole_action != RABBIT_HOLE_USE_DEFAULT) {

      // Get the redirect path and response from the node.
      $redirect_path = token_replace($node->rabbit_hole_redirect, array(
        'node' => $node,
      ));
      $redirect_response = $node->rabbit_hole_redirect_response;
    }
    else {

      // Get the redirect path and response from the content type.
      $redirect_path = token_replace(variable_get('rabbit_hole_redirect_' . $node->type, '<front>'), array(
        'node' => $node,
      ));
      $redirect_response = variable_get('rabbit_hole_redirect_response_' . $node->type, RABBIT_HOLE_PAGE_REDIRECT_RESPONSE_DEFAULT);
    }
  }

  // Now, let's see what we should do.
  switch ($action) {
    case RABBIT_HOLE_ACCESS_DENIED:

      // TODO: Is this the proper way to deliver an access denied page?
      drupal_access_denied();
      exit;
    case RABBIT_HOLE_PAGE_NOT_FOUND:

      // TODO: Is this the proper way to deliver a not found page?
      drupal_not_found();
      exit;
    case RABBIT_HOLE_PAGE_REDIRECT:

      // Redirect the user to the specified path.
      drupal_goto($redirect_path, array(), $redirect_response);
  }
}

/**
 * Implements hook_node_type_delete().
 */
function rabbit_hole_node_type_delete($info) {

  // Delete variables connected to this content type.
  variable_del('rabbit_hole_action_' . $info->type);
  variable_del('rabbit_hole_redirect_' . $info->type);
  variable_del('rabbit_hole_redirect_response_' . $info->type);
}

/**
 * Implements hook_menu_local_tasks_alter().
 */
function rabbit_hole_menu_local_tasks_alter(&$data, $router_item, $root_path) {
  $primary =& $data['tabs'][0];
  if (!is_array($primary['output'])) {

    // There are no tabs present, exit early.
    return;
  }

  // Iterate through the primary tabs, and look for the View tab for nodes.
  foreach ($primary['output'] as $delta => $element) {
    if ($element['#link']['path'] == 'node/%/view') {

      // Found the View tab, get the Rabbit Hole action for this node, and
      // remove the tab if any Rabbit Hole action has been set.
      $node = menu_get_object('node', 1, $router_item['tab_root_href']);
      $action = isset($node->rabbit_hole_action) ? $node->rabbit_hole_action != RABBIT_HOLE_USE_DEFAULT ? $node->rabbit_hole_action : variable_get('rabbit_hole_action_' . $node->type) : variable_get('rabbit_hole_action_' . $node->type);
      if ($action != RABBIT_HOLE_DISPLAY_CONTENT && !user_access('bypass rabbit hole')) {
        unset($primary['output'][$delta]);
      }
    }
  }

  // Reset the count and keys for the existing tabs.
  $primary['output'] = array_values($primary['output']);
  $primary['count'] = count($primary['output']);
}

/**
 * Implements template_preprocess_search_results().
 *
 * TODO: Alter the search results in another way. If there is another way.
 */
function rabbit_hole_preprocess_search_results(&$variables) {
  $results =& $variables['results'];

  // Iterate through the search results, and remove the nodes where Rabbit Hole
  // is activated.
  foreach ($results as $delta => $item) {
    if (isset($item['node'])) {
      $node = $item['node'];
      $action = isset($node->rabbit_hole_action) ? $node->rabbit_hole_action != RABBIT_HOLE_USE_DEFAULT ? $node->rabbit_hole_action : variable_get('rabbit_hole_action_' . $node->type) : variable_get('rabbit_hole_action_' . $node->type);
      if ($action != RABBIT_HOLE_DISPLAY_CONTENT) {

        // This node shouldn't be displayed, remove it from the resuts.
        unset($results[$delta]);
      }
    }
  }

  // Reset the keys for the results.
  $results = array_values($results);

  // Pass the variables through template_preprocess_search_results() once again.
  // This is done because the Search module needs to process the new results
  // array. This is not the ideal solution, since this could interfere with
  // other modules also implementing template_preprocess_search_results().
  $variables['theme_hook_suggestions'] = array();
  template_preprocess_search_results($variables);
}