You are here

nodesymlinks.module in NodeSymlinks 7

Same filename and directory in other branches
  1. 6 nodesymlinks.module

Node Symlinks allows creating duplicate menu links with unique id to all nodes. As a result all these duplicates have unique menu trails and breadcrumbs.

@author Vojtech Kusy <wojtha@gmail.com>, http://vojtechkusy.com

Google answer about duplicates http://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=66359

NOTE: This module has weight 2, so it is launched after Pathauto (if present)

CREDITS: -- Backup table, admin page and some other fixes by Gordon Luk <gordon.luk@gmail.com> http://getluky.net/2009/05/29/drupal-module-node-multi-parenting/

File

nodesymlinks.module
View source
<?php

/**
 * @file
 * Node Symlinks allows creating duplicate menu links with unique id to all
 * nodes. As a result all these duplicates have unique menu trails and
 * breadcrumbs.
 *
 * @author Vojtech Kusy <wojtha@gmail.com>, http://vojtechkusy.com
 *
 * Google answer about duplicates
 * http://www.google.com/support/webmasters/bin/answer.py?hl=en&answer=66359
 *
 * NOTE: This module has weight 2, so it is launched after Pathauto (if present)
 *
 * CREDITS:
 * -- Backup table, admin page and some other fixes by Gordon Luk
 *    <gordon.luk@gmail.com>
 *    http://getluky.net/2009/05/29/drupal-module-node-multi-parenting/
 */

/**
 * Implements hook_menu().
 */
function nodesymlinks_menu() {
  $items = array();
  $items['node/%node/mid/%'] = array(
    'title' => 'Content Duplicate',
    'title callback' => 'nodesymlinks_menu_title',
    'title arguments' => array(
      1,
    ),
    'page callback' => 'nodesymlinks_page',
    'page arguments' => array(
      1,
      3,
    ),
    'access callback' => 'node_access',
    'access arguments' => array(
      'view',
      1,
    ),
    'type' => MENU_CALLBACK,
  );
  $items['node/%node/mid/%/view'] = array(
    'title' => 'NodeSymlink',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['node/%node/mid/%/original'] = array(
    'title' => 'Original',
    'page callback' => 'nodesymlinks_original',
    'page arguments' => array(
      1,
    ),
    'access callback' => 'node_access',
    'access arguments' => array(
      'update',
      1,
    ),
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'weight' => 1,
  );
  $items['node/%node/mid/%/edit'] = array(
    'title' => 'Edit',
    'page callback' => 'nodesymlinks_original',
    'page arguments' => array(
      1,
      'edit',
    ),
    'access callback' => 'node_access',
    'access arguments' => array(
      'update',
      1,
    ),
    'type' => MENU_LOCAL_TASK,
    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
    'weight' => 5,
  );
  $items['admin/structure/nodesymlinks'] = array(
    'title' => 'NodeSymlinks',
    'page callback' => 'nodesymlinks_admin',
    'access arguments' => array(
      'administer nodesymlinks',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'nodesymlinks.admin.inc',
  );
  $items['admin/structure/nodesymlinks/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/structure/nodesymlinks/settings'] = array(
    'title' => 'Settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'nodesymlinks_settings',
    ),
    'access arguments' => array(
      'administer nodesymlinks',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'nodesymlinks.admin.inc',
  );
  return $items;
}

/**
 * Title callback for the menu overview page and links.
 */
function nodesymlinks_menu_title($node) {
  return $node->title;
}

/**
 * Implements hook_permission().
 */
function nodesymlinks_permission() {
  return array(
    'administer nodesymlinks' => array(
      'title' => t('administer nodesymlinks'),
      'description' => t('Administer and configure NodeSymlinks'),
    ),
  );
}

/**
 * Implements hook_menu_breadcrumb_alter().
 */
function nodesymlinks_menu_breadcrumb_alter(&$active_trail, $item) {
  if (!drupal_is_front_page() && (!empty($item['page_callback']) && $item['page_callback'] == 'nodesymlinks_page')) {
    $last_crumb = variable_get('nodesymlinks_crumbs_lastcrumb', 'parent');
    switch ($last_crumb) {
      case 'current_link':
        $active_trail[] = $item;
        break;
      case 'current_plain':
        $active_trail[] = $item->title;
        break;
      case 'parent':
      default:
    }
  }
}

/**
 * Main callback for displaying the node.
 *
 * Menu callback which loads and displays the content from the node wrapped
 * within the different menu and breadcrumbs. It also sets the robots META tag
 * to prevent duplicate search engine indexing.
 */
function nodesymlinks_page($node, $mid) {

  // View as a full page.
  if (module_exists('page_manager')) {

    // If there exists another handler for the node view use it, otherwise
    // fallback on the drupal core node view.
    module_load_include('inc', 'page_manager', '/plugins/tasks/node_view');
    $output = page_manager_node_view_page($node);
  }
  else {

    // If display suite’s 'View mode per node' is used,
    // select the correct view mode.
    $viewmode = empty($node->ds_switch) ? 'full' : $node->ds_switch;

    // For markup consistency with other pages, use node_view_multiple()
    // rather than node_view().
    $output = node_view_multiple(array(
      $node->nid => $node,
    ), $viewmode);

    // Update the history table, stating that this user viewed this node.
    node_tag_new($node);
  }

  // Without this, title of the page would be same as the link title
  // @See http://drupal.org/node/1226012
  // @TODO: Is there a better way?
  drupal_set_title($node->title);

  // Set the canonical URL to be that of the original node.
  $uri = entity_uri('node', $node);
  drupal_add_html_head_link(array(
    'rel' => 'canonical',
    'href' => url($uri['path'], $uri['options']),
  ), TRUE);

  // Set the shortlink URL to be the unaliased version of the symlink path.
  drupal_add_html_head_link(array(
    'rel' => 'shortlink',
    'href' => url('node/' . $node->nid . '/mid/' . $mid, array(
      'alias' => TRUE,
    )),
  ), TRUE);
  return $output;
}

/**
 * Redirect to original node.
 */
function nodesymlinks_original($node, $op = '') {
  drupal_goto('node/' . $node->nid . '/' . $op);
}

/**
 * Include nodesymlinks file.
 */
function _nodesymlinks_include($name = '') {
  $name = $name ? 'nodesymlinks.' . $name : 'nodesymlinks';
  module_load_include('inc', 'nodesymlinks', $name);
}

/**
 * Implements hook_pathauto().
 */
function nodesymlinks_pathauto($op) {
  switch ($op) {
    case 'settings':
      $settings = array(
        'module' => 'nodesymlinks',
        'token_type' => 'nodesymlink',
        'groupheader' => t('Nodesymlinks paths'),
        'patterndescr' => t('Default path pattern (applies to all node types with blank patterns below)'),
        'patterndefault' => '[nodesymlink:menu-link:parents:join-path]/[nodesymlink:menu-link:title]',
        'bulkname' => t('Bulk generate aliases for nodesymlinks that are not aliased'),
        'bulkdescr' => t('Generate aliases for all existing nodesymlinks which do not already have aliases.'),
        'batch_update_callback' => '_nodesymlinks_pathauto_bulkupdate',
        'batch_file' => drupal_get_path('module', 'nodesymlinks') . '/nodesymlinks.inc',
      );
      $languages = array();
      if (module_exists('locale')) {
        $languages = array(
          LANGUAGE_NONE => t('language neutral'),
        ) + locale_language_list('name');
      }
      foreach (node_type_get_names() as $node_type => $node_name) {
        if (count($languages) && variable_get('language_content_type_' . $node_type, 0)) {
          $settings['patternitems'][$node_type] = t('Default path pattern for @node_type (applies to all @node_type content types with blank patterns below)', array(
            '@node_type' => $node_name,
          ));
          foreach ($languages as $lang_code => $lang_name) {
            $settings['patternitems'][$node_type . '_' . $lang_code] = t('Pattern for all @language @node_type paths', array(
              '@node_type' => $node_name,
              '@language' => $lang_name,
            ));
          }
        }
        else {
          $settings['patternitems'][$node_type] = t('Pattern for all @node_type paths', array(
            '@node_type' => $node_name,
          ));
        }
      }
      return (object) $settings;
  }
}

/**
 * Implements hook_token_info().
 */
function nodesymlinks_token_info() {
  $type = array(
    'name' => t('Nodesymlinks'),
    'description' => t('Tokens related to nodesymlinks.'),
    'needs-data' => 'node',
  );

  // Chained node tokens.
  $nodesymlink['node'] = array(
    'name' => t('Node'),
    'description' => t('Node of the nodesymlink.'),
    'type' => 'node',
  );

  // Chained menu tokens.
  $nodesymlink['menu-link'] = array(
    'name' => t('Menu link'),
    'description' => t("The menu link for this node."),
    'type' => 'menu-link',
  );
  return array(
    'types' => array(
      'nodesymlink' => $type,
    ),
    'tokens' => array(
      'nodesymlink' => $nodesymlink,
    ),
  );
}

/**
 * Implements hook_tokens().
 */
function nodesymlinks_tokens($type, $tokens, array $data = array(), array $options = array()) {
  $replacements = array();
  $url_options = array(
    'absolute' => TRUE,
  );
  if (isset($options['language'])) {
    $url_options['language'] = $options['language'];
    $language_code = $options['language']->language;
  }
  else {
    $language_code = NULL;
  }
  $sanitize = !empty($options['sanitize']);
  if ($type == 'nodesymlink' && !empty($data['node'])) {
    foreach ($tokens as $name => $original) {
      switch ($name) {

        // Default values for the chained tokens handled below.
        case 'node':
          $node = $data['node'];
          $replacements[$original] = $sanitize ? check_plain($node->title) : $node->title;
          break;
      }
    }
    if ($node_tokens = token_find_with_prefix($tokens, 'node')) {
      $replacements += token_generate('node', $node_tokens, $data, $options);
    }
  }
  if ($type == 'nodesymlink' && !empty($data['menu-link'])) {
    foreach ($tokens as $name => $original) {
      switch ($name) {

        // Default values for the chained tokens handled below.
        case 'menu-link':
          $replacements[$original] = $sanitize ? check_plain($node->title) : $node->title;
          break;
      }
    }
    if ($menu_tokens = token_find_with_prefix($tokens, 'menu-link')) {
      $replacements += token_generate('menu-link', $menu_tokens, $data, $options);
    }
  }
  return $replacements;
}

/**
 * Implements hook_node_validate().
 */
function nodesymlinks_node_validate($node, $form) {
  if (isset($node->menu['nodesymlinks'])) {
    _nodesymlinks_include();
    _nodesymlinks_nodeapi_validate($node, 'validate');
  }
}

/**
 * Implements hook_node_insert().
 */
function nodesymlinks_node_insert($node) {
  if (isset($node->menu['nodesymlinks'])) {
    _nodesymlinks_include();
    _nodesymlinks_nodeapi_insert_update($node, 'insert');
  }
}

/**
 * Implements hook_node_update().
 */
function nodesymlinks_node_update($node) {
  if (isset($node->menu['nodesymlinks'])) {
    _nodesymlinks_include();
    _nodesymlinks_nodeapi_insert_update($node, 'update');
  }
}

/**
 * Implements hook_node_delete().
 */
function nodesymlinks_node_delete($node) {
  _nodesymlinks_include();
  _nodesymlinks_nodeapi_delete($node, 'delete');
}

/**
 * Implements hook_node_prepare().
 */
function nodesymlinks_node_prepare($node) {
  if (empty($node->nodesymlinks)) {
    _nodesymlinks_include();
    _nodesymlinks_nodeapi_prepare_presave($node, 'prepare');
  }
}

/**
 * Implements hook_node_presave().
 */
function nodesymlinks_node_presave($node) {
  if (empty($node->nodesymlinks)) {
    _nodesymlinks_include();
    _nodesymlinks_nodeapi_prepare_presave($node, 'presave');
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Adds nodesymlinks item fields to the node form.
 */
function nodesymlinks_form_node_form_alter(&$form, &$form_state, $form_id) {

  // Check for node form and also check if 'menu' exists - some modules like
  // Menuless Nodetype or Content Type Menu are able to unset menu at certain
  // node types.
  if (isset($form['#node']) && isset($form['menu'])) {
    _nodesymlinks_include();
    _nodesymlinks_node_form_alter($form, $form_state, $form_id);
  }
}

/**
 * Implements hook_theme().
 */
function nodesymlinks_theme() {
  return array(
    'nodesymlinks_form_items' => array(
      'render element' => 'form',
      'file' => 'nodesymlinks.inc',
    ),
  );
}