You are here

nodesymlinks.module in NodeSymlinks 6

Same filename and directory in other branches
  1. 7 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 Robots meta tag http://www.robotstxt.org/meta.html

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

@example <META NAME="ROBOTS" CONTENT="NOINDEX, FOLLOW"> <META NAME="ROBOTS" CONTENT="INDEX, NOFOLLOW"> <META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW">

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
 * Robots meta tag
 * http://www.robotstxt.org/meta.html
 *
 * NOTE: This module has weight 2, so it is launched after Pathauto (if present)
 *
 * @example
 * <META NAME="ROBOTS" CONTENT="NOINDEX, FOLLOW">
 * <META NAME="ROBOTS" CONTENT="INDEX, NOFOLLOW">
 * <META NAME="ROBOTS" CONTENT="NOINDEX, NOFOLLOW">
 *
 * 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/
 *
 */

/**
 * Implementation of hook_menu().
 */
function nodesymlinks_menu() {
  $items = array();
  $items['node/%node/mid/%'] = array(
    'title' => 'Content Duplicate',
    'page callback' => 'nodesymlinks_page',
    'page arguments' => array(
      1,
      3,
    ),
    'title callback' => 'nodesymlinks_menu_title',
    'title arguments' => array(
      1,
    ),
    'access callback' => 'node_access',
    'access arguments' => array(
      'view',
      1,
    ),
    'type' => MENU_CALLBACK,
  );
  $items['admin/settings/nodesymlinks'] = array(
    'title' => 'NodeSymlinks',
    'page callback' => 'nodesymlinks_admin',
    'access arguments' => array(
      'access administration pages',
    ),
    'type' => MENU_NORMAL_ITEM,
    'file' => 'nodesymlinks.admin.inc',
  );
  $items['admin/settings/nodesymlinks/list'] = array(
    'title' => 'List',
    'type' => MENU_DEFAULT_LOCAL_TASK,
    'weight' => -10,
  );
  $items['admin/settings/nodesymlinks/settings'] = array(
    'title' => 'Settings',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'nodesymlinks_settings',
    ),
    'access arguments' => array(
      'access administration pages',
    ),
    'type' => MENU_LOCAL_TASK,
    'file' => 'nodesymlinks.admin.inc',
  );
  $items['nodesymlinks/ahah'] = array(
    'page callback' => 'nodesymlinks_ahah_handler',
    'access arguments' => array(
      'administer nodesymlinks',
    ),
    'type' => MENU_CALLBACK,
    'file' => 'nodesymlinks.inc',
  );
  return $items;
}

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

/**
 * Implementation of hook_perm().
 */
function nodesymlinks_perm() {
  return array(
    'administer nodesymlinks',
  );
}

/**
 * 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.
 *
 * @param node $node
 * @param mlid $mid
 *
 * @return HTML
 */
function nodesymlinks_page($node, $mid) {

  // don't allow duplicates to be indexed
  if (!drupal_is_front_page()) {
    drupal_set_html_head('<meta name="robots" content="noindex, follow">');
  }
  $output = '';
  if (node_access('update', $node)) {
    $output .= theme('nodesymlinks_edit_links', $node->nid);
  }

  // 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.
    include_once drupal_get_path('module', 'page_manager') . '/plugins/tasks/node_view.inc';
    $output .= page_manager_node_view($node);
  }
  else {

    // Use the drupal core handler for a node view.
    $output .= node_view($node, FALSE, TRUE, FALSE);
  }

  // Create breadcrumb based on the menu path.
  // NOTE: Setting breadcrumb *after* node_view() prevents our breadcrumbs to
  // be changed in some other module.
  drupal_set_breadcrumb(nodesymlinks_get_breadcrumbs());
  drupal_set_title($node->title);
  return $output;
}

/**
 * Generate breadcrumbs for current active menu trail. Return array of
 * breadcrumb links. Partly taken from function menutrails_get_breadcrumbs().
 *
 * @return array
 */
function nodesymlinks_get_breadcrumbs() {
  $crumbs = array();
  if (variable_get('nodesymlinks_crumbs_include_home', 1)) {
    $crumbs[] = l(t('Home'), '<front>');
  }
  $trail = _nodesymlinks_menu_get_active_trail();
  $depth = count($trail) - 1;
  for ($i = 0; $i < $depth; ++$i) {
    $crumbs[] = l($trail[$i]['title'], $trail[$i]['href']);
  }

  // Add current item as last crumb if display type is set so.
  $last_crumb_type = variable_get('nodesymlinks_crumbs_lastcrumb', 'parent');
  if ($last_crumb_type == 'current_plain') {
    $crumbs[] = $trail[$depth]['title'];
  }
  elseif ($last_crumb_type == 'current_link') {
    $crumbs[] = l($trail[$depth]['title'], $trail[$depth]['href']);
  }
  return $crumbs;
}

/**
 * Gets the active trail (path to root menu root) of the current page.
 *
 * @return
 *   Path to menu root of the current page, as an array of menu link items,
 *   starting with the site's home page. Each link item is an associative array
 *   with the following components:
 *   - title: Title of the item.
 *   - href: Drupal path of the item.
 *   - localized_options: Options for passing into the l() function.
 *   - type: A menu type constant, such as MENU_DEFAULT_LOCAL_TASK, or 0 to
 *     indicate it's not really in the menu (used for the home page item).
 *   If $new_trail is supplied, the value is saved in a static variable and
 *   returned. If $new_trail is not supplied, and there is a saved value from
 *   a previous call, the saved value is returned. If $new_trail is not supplied
 *   and there is no saved value, the path to the current page is calculated,
 *   saved as the static value, and returned.
 *
 * @see menu_set_active_trail()
 */
function _nodesymlinks_menu_get_active_trail() {
  static $trail;
  if (!isset($trail)) {
    $trail = array();
    $item = menu_get_item();
    $tree = menu_tree_page_data(menu_get_active_menu_name());
    list($key, $curr) = each($tree);
    while ($curr) {

      // Terminate the loop when we find the current path in the active trail.
      if ($curr['link']['href'] == $item['href']) {
        $trail[] = $curr['link'];
        $curr = FALSE;
      }
      else {

        // Add the link if it's in the active trail, then move to the link
        // below.
        if ($curr['link']['in_active_trail']) {
          $trail[] = $curr['link'];
          $tree = $curr['below'] ? $curr['below'] : array();
        }
        list($key, $curr) = each($tree);
      }
    }

    // Make sure the current page is in the trail (needed for the page title),
    // but exclude tabs and the front page.
    $last = count($trail) - 1;
    if ($trail[$last]['href'] != $item['href'] && !(bool) ($item['type'] & MENU_IS_LOCAL_TASK) && !drupal_is_front_page()) {
      $trail[] = $item;
    }
  }
  return $trail;
}
function _nodesymlinks_include($name = '') {
  $name = $name ? 'nodesymlinks.' . $name : 'nodesymlinks';
  module_load_include('inc', 'nodesymlinks', $name);
}

/**
 * Implementation of hook_pathauto().
 */
function nodesymlinks_pathauto($op) {
  switch ($op) {
    case 'settings':
      $settings = array();
      $settings['module'] = 'nodesymlinks';
      $settings['token_type'] = 'nodesymlinks';
      $settings['groupheader'] = t('Nodesymlinks paths');
      $settings['patterndescr'] = t('Default path pattern (applies to all node types with blank patterns below)');
      $settings['patterndefault'] = '[nodesymlinks-menupath-raw]';
      $settings['bulkname'] = t('Bulk generate aliases for nodesymlinks that are not aliased');
      $settings['bulkdescr'] = t('Generate aliases for all existing nodesymlinks which do not already have aliases.');
      $node_settings = node_pathauto($op);
      $settings['placeholders'] = $node_settings->placeholders;

      // Next two settings is supported from Pathauto 2+
      // Pathauto 1.x simply calls $function = $module . '_pathauto_bulkupdate';
      $settings['batch_update_callback'] = '_nodesymlinks_pathauto_bulkupdate';
      $settings['batch_file'] = drupal_get_path('module', 'nodesymlinks') . '/nodesymlinks.pathauto.inc';
      return (object) $settings;
    default:
      break;
  }
}

/**
 * Implementation of hook_pathauto_bulkupdate().
 *
 * @deprecated since Pathauto 2.x
 *
 * For Pathauto 1.x
 * @see pathauto.api.inc, pathauto.admin.inc:260
 */
function nodesymlinks_pathauto_bulkupdate() {
  _nodesymlinks_include('pathauto');
  _nodesymlinks_pathauto_bulkupdate();
}

/**
 * Implementation of hook_token_list().
 */
function nodesymlinks_token_list($type = 'all') {
  $tokens = array();
  if ($type == 'nodesymlinks' || $type == 'all') {
    $tokens['nodesymlinks']['nodesymlinks-nodepath'] = t("The URL path of the node the nodesymlink belongs to.");
    $tokens['nodesymlinks']['nodesymlinks-menu'] = t("The name of the menu the node belongs to.");
    $tokens['nodesymlinks']['nodesymlinks-menu-raw'] = t("The name of the menu the node belongs to.");
    $tokens['nodesymlinks']['nodesymlinks-menupath'] = t("The menu path (as reflected in the breadcrumb), not including Home or [menu]. Separated by /.");
    $tokens['nodesymlinks']['nodesymlinks-menupath-raw'] = t("The unfiltered menu path (as reflected in the breadcrumb), not including Home or [menu]. Separated by /.");
    $tokens['nodesymlinks']['nodesymlinks-menu-link-title'] = t("The text used in the menu as link text for this item.");
    $tokens['nodesymlinks']['nodesymlinks-menu-link-title-raw'] = t("The unfiltered text used in the menu as link text for this item.");
    $tokens['nodesymlinks']['nodesymlinks-menu-link-mlid'] = t("The unique ID of the node's menu link.");
    $tokens['nodesymlinks']['nodesymlinks-menu-link-plid'] = t("The unique ID of the node's menu link parent.");

    // Access also node tokens
    if (variable_get('nodesymlinks_node_tokens', 0) && $type == 'nodesymlinks') {
      $tokens += token_get_list('node');
    }
  }
  return $tokens;
}

/**
 * Implementation of hook_token_values().
 *
 * @see node_token_values() in token_node.inc
 */
function nodesymlinks_token_values($type, $object = NULL, $options = array()) {
  $values = array();
  if ($type == 'nodesymlinks') {
    $values = array(
      'nodesymlinks-menupath' => '',
      'nodesymlinks-menupath-raw' => '',
      'nodesymlinks-menu' => '',
      'nodesymlinks-menu-raw' => '',
      'nodesymlinks-menu-link-title' => '',
      'nodesymlinks-menu-link-title-raw' => '',
      'nodesymlinks-menu-link-mlid' => '',
      'nodesymlinks-menu-link-plid' => '',
    );

    // Now get the menu related information.
    if (!empty($object['mlid'])) {

      // TODO: refactor this
      $mlid = $object['mlid'];
      $node = $object['node'];
      $menu_link = menu_link_load($mlid);
      $menus = menu_get_menus();
      $menu = isset($menus[$menu_link['menu_name']]) ? $menus[$menu_link['menu_name']] : '';
      $trail_raw = _menu_titles($menu_link, $node->nid);
      $trail = array();
      foreach ($trail_raw as $title) {
        $trail[] = check_plain($title);
      }
      $values['nodesymlinks-nodepath'] = !empty($node->path) ? $node->path : 'node/' . $node->nid;
      $values['nodesymlinks-menupath'] = !empty($options['pathauto']) ? $trail : implode('/', $trail);
      $values['nodesymlinks-menupath-raw'] = !empty($options['pathauto']) ? $trail_raw : implode('/', $trail_raw);
      $values['nodesymlinks-menu'] = check_plain($menu);
      $values['nodesymlinks-menu-raw'] = $menu;
      $values['nodesymlinks-menu-link-title'] = check_plain($menu_link['title']);
      $values['nodesymlinks-menu-link-title-raw'] = $menu_link['link_title'];
      $values['nodesymlinks-menu-link-mlid'] = $menu_link['mlid'];
      $values['nodesymlinks-menu-link-plid'] = $menu_link['plid'];
    }
  }
  return $values;
}

/**
 * Implementation of hook_nodeapi().
 *
 * NOTE: This module has weight 2, because it needs to be launched after
 * Pathauto module (if present).
 */
function nodesymlinks_nodeapi(&$node, $op) {
  switch ($op) {
    case 'validate':
      if (isset($node->menu['nodesymlinks'])) {
        _nodesymlinks_include();
        _nodesymlinks_nodeapi_validate($node, $op);
      }
      break;
    case 'insert':
    case 'update':
      if (isset($node->menu['nodesymlinks'])) {
        _nodesymlinks_include();
        _nodesymlinks_nodeapi_insert_update($node, $op);
      }
      break;
    case 'delete':
      _nodesymlinks_include();
      _nodesymlinks_nodeapi_delete($node, $op);
      break;
    case 'prepare':
    case 'presave':
      if (empty($node->nodesymlinks)) {
        _nodesymlinks_include();
        _nodesymlinks_nodeapi_prepare_presave($node, $op);
      }
      break;
  }
}

/**
 * Implementation of hook_form_alter(). Adds nodesymlinks item fields to the
 * node form.
 */
function nodesymlinks_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']) && $form['#node']->type . '_node_form' == $form_id && isset($form['menu'])) {
    _nodesymlinks_include();
    _nodesymlinks_form_alter($form, $form_state, $form_id);
  }
}

/**
 * Implementation of hook_theme()
 */
function nodesymlinks_theme() {
  return array(
    'nodesymlinks_form_items' => array(
      'arguments' => array(
        'form' => array(),
      ),
      'file' => 'nodesymlinks.inc',
    ),
    'nodesymlinks_edit_links' => array(
      'arguments' => array(
        'nid' => NULL,
      ),
    ),
  );
}

/**
 * Theme view/edit links for original node.
 *
 * @param $nid
 *   The node id of the original node.
 * @return
 *   The themed HTML output.
 */
function theme_nodesymlinks_edit_links($nid) {
  $text = t('Original node: !view | !edit', array(
    '!view' => l(t('View'), 'node/' . $nid),
    '!edit' => l(t('Edit'), 'node/' . $nid . '/edit'),
  ));
  return '<div class="nodesymlinks_edit_links">' . $text . "</div>\n";
}

Functions

Namesort descending Description
nodesymlinks_form_alter Implementation of hook_form_alter(). Adds nodesymlinks item fields to the node form.
nodesymlinks_get_breadcrumbs Generate breadcrumbs for current active menu trail. Return array of breadcrumb links. Partly taken from function menutrails_get_breadcrumbs().
nodesymlinks_menu Implementation of hook_menu().
nodesymlinks_menu_title Title callback for the menu overview page and links.
nodesymlinks_nodeapi Implementation of hook_nodeapi().
nodesymlinks_page 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.
nodesymlinks_pathauto Implementation of hook_pathauto().
nodesymlinks_pathauto_bulkupdate Deprecated Implementation of hook_pathauto_bulkupdate().
nodesymlinks_perm Implementation of hook_perm().
nodesymlinks_theme Implementation of hook_theme()
nodesymlinks_token_list Implementation of hook_token_list().
nodesymlinks_token_values Implementation of hook_token_values().
theme_nodesymlinks_edit_links Theme view/edit links for original node.
_nodesymlinks_include
_nodesymlinks_menu_get_active_trail Gets the active trail (path to root menu root) of the current page.