You are here

nodereferrer.module in NodeReferrer 5

Same filename and directory in other branches
  1. 6 nodereferrer.module
  2. 7 nodereferrer.module

Defines a field type for backlinking referencing nodes. @todo -clear content cache with nodeapi. -query nids for access on load/view..

File

nodereferrer.module
View source
<?php

/**
 * @file
 * Defines a field type for backlinking referencing nodes.
 * @todo 
 *    -clear content cache with nodeapi.
 *    -query nids for access on load/view..
 */

/**
 * Implementation of hook_help().
 */
function nodereferrer_help($section) {
  switch ($section) {
    case 'admin/modules#description':
      return t('<strong>CCK:</strong> Defines a field type for displaying referrers to a node. <em>Note: Requires content.module.</em>');
  }
}

/**
 * Implementation of hook_field_info().
 */
function nodereferrer_field_info() {
  return array(
    'nodereferrer' => array(
      'label' => t('Node Referrers'),
    ),
  );
}

/**
 * Implementation of hook_field_settings().
 */
function nodereferrer_field_settings($op, $field) {
  switch ($op) {
    case 'callbacks':
      return array(
        'view' => CONTENT_CALLBACK_CUSTOM,
      );
    case 'form':
      $form = array();
      $form['referrer_types'] = array(
        '#type' => 'checkboxes',
        '#title' => t('Referring Node Types'),
        '#multiple' => TRUE,
        '#default_value' => isset($field['referrer_types']) ? $field['referrer_types'] : array(),
        '#options' => node_get_types('names'),
      );
      $options = nodereferrer_nodereference_field_options();
      $form['referrer_fields'] = array(
        '#type' => 'checkboxes',
        '#title' => t('Referring Fields'),
        '#multiple' => TRUE,
        '#default_value' => isset($field['referrer_fields']) ? $field['referrer_fields'] : array(),
        '#options' => $options,
      );
      $form['referrer_nodes_per_page'] = array(
        '#type' => 'textfield',
        '#title' => t('Referrers Per Page'),
        '#description' => t('Referring nodes to display per page. 0 for no paging.'),
        '#default_value' => isset($field['referrer_nodes_per_page']) ? $field['referrer_nodes_per_page'] : 0,
      );
      $form['referrer_order'] = array(
        '#type' => 'select',
        '#title' => t('Refferer Sort Order'),
        '#options' => array(
          'CREATED_ASC' => t('Chronological Order'),
          'CREATED_DESC' => t('Reverse Chronological Order'),
          'TITLE_ASC' => t('Title Order'),
          'TITLE_DESC' => t('Reverse Title Order'),
        ),
        '#default_value' => isset($field['referrer_order']) ? $field['referrer_order'] : 'DESC',
      );
      return $form;
    case 'save':
      return array(
        'referrer_types',
        'referrer_fields',
        'referrer_nodes_per_page',
        'referrer_order',
      );
  }
}

/**
 * Implementation of hook_field().
 */
function nodereferrer_field($op, &$node, $field, &$items, $teaser, $page) {
  static $element = 3;
  switch ($op) {
    case 'view':
      $pager = '';
      if ($field['referrer_nodes_per_page']) {

        // Unique Element for the pager.
        $element++;
        $limit = $field['referrer_nodes_per_page'];

        // Fake the values set by pager query...
        global $pager_page_array, $pager_total, $pager_total_items;
        $page = isset($_GET['page']) ? $_GET['page'] : '';

        // Convert comma-separated $page to an array, used by other functions.
        $pager_page_array = explode(',', $page);

        // We calculate the total of pages as ceil(items / limit).
        $pager_total_items[$element] = count($items);
        $pager_total[$element] = ceil($pager_total_items[$element] / $limit);
        $pager_page_array[$element] = max(0, min((int) $pager_page_array[$element], (int) $pager_total[$element] - 1));

        // only display the select elements.
        if (is_array($items)) {
          $items = array_slice($items, $pager_page_array[$element] * $limit, $limit);
        }
        $pager = theme('pager', array(), $limit, $element);
      }

      // get the context
      $context = $teaser ? 'teaser' : 'full';

      // get the formatter
      $formatter = isset($field['display_settings'][$context]['format']) ? $field['display_settings'][$context]['format'] : 'default';
      foreach ($items as $delta => $item) {
        $access_node = node_load($item['nid']);
        if ($access_node && node_access('view', $access_node)) {
          $items[$delta]['view'] = content_format($field, $item, $formatter);
        }
      }
      return theme('field', $node, $field, $items, $teaser, $page) . $pager;
    case 'load':
      $types = array_values(array_filter($field['referrer_types']));
      $fields = array_values(array_filter($field['referrer_fields']));
      $order = $field['referrer_order'];
      $values = nodereferrer_referrers($node->nid, $fields, $types, $order);

      //$node_field = array(); -- do I really need to initialize... do I get node_field

      // even though this module doesn't have any db fields.
      $items = array();

      // Pass referring node objects into CCK content_load() cache. 24/08/2006 sun
      foreach ($values as $nid => $rnode) {
        $items[] = $rnode;
      }
      return array(
        $field['field_name'] => $items,
      );
    case 'delete':
    case 'update':

      // clear cache on nodes that refer to me.
      $types = array_values(array_filter($field['referrer_types']));
      $fields = array_values(array_filter($field['referrer_fields']));

      // clear any modules referring to me as my title or other data may change.
      // and nodereference doesn't clear the cache yet.
      foreach (nodereferrer_referrers($node->nid, $fields, $types) as $delta => $item) {
        $cid = 'content:' . $item['nid'] . ':' . $item['vid'];
        cache_clear_all($cid, 'cache_page');
      }
      return;
  }
}

/**
 * Implementation of hook_field_formatter_info().
 */
function nodereferrer_field_formatter_info() {
  return array(
    'default' => array(
      'label' => 'Node Title Link (Default)',
      'field types' => array(
        'nodereferrer',
      ),
    ),
    'plain' => array(
      'label' => 'Node Title Plain Text',
      'field types' => array(
        'nodereferrer',
      ),
    ),
    'teaser' => array(
      'label' => 'Node Teaser',
      'field types' => array(
        'nodereferrer',
      ),
    ),
    'full' => array(
      'label' => 'Node Body',
      'field types' => array(
        'nodereferrer',
      ),
    ),
  );
}

/**
 * Implementation of hook_field_formatter().
 */
function nodereferrer_field_formatter($field, $item, $formatter, $node) {
  switch ($formatter) {
    case 'plain':
      return strip_tags($item['title']);
    case 'teaser':
      return node_view(node_load($item['nid']), TRUE);
    case 'full':
      return node_view(node_load($item['nid']));
    default:
      return l($item['title'], 'node/' . $item['nid']);
  }
}

/**
 * Implementation of hook_widget_info().
 */
function nodereferrer_widget_info() {
  return array(
    'nodereferrer_list' => array(
      'label' => t('Read-Only List'),
      'field types' => array(
        'nodereferrer',
      ),
    ),
  );
}

/**
 * Implementation of hook_widget().
 */
function nodereferrer_widget($op, &$node, $field, &$node_field) {

  // for some reason widgets must implement a form for them to work...
  // I'm not sure that this is ideal for a set and forget service
  // automated field, something in CCK in content_admin.inc:content_admin_field_overview_form
  // need to be re-thunk I think.
  // token module tries to use the form input.. grr... so lets structure it like
  // an item...
  switch ($op) {
    case 'form':
      $form[$field['field_name']] = array(
        '#tree' => TRUE,
        '0' => array(),
      );
      return $form;
  }

  // I do nothing. I'm a read only thing... not too into the widgets.
}

/**
 * Get an array of referrer nids, by node.type & field.type
 * @param nid
 *     the nid we want to find referres for
 * @param fieldnames 
 *     array of fieldnames to be checked for referrers
 * @param nodetypes
 *     array of node types to be checked for referrers
 */
function nodereferrer_referrers($nid, $fieldnames = array(), $nodetypes = array(), $order = 'DESC') {
  if ($nodetypes) {
    $filter_nodetypes = "AND n.type IN ('" . implode("', '", $nodetypes) . "')";
  }
  $fields = content_fields();

  // Set default values of fieldnames..
  if (!count($fieldnames)) {
    $fieldnames = array_keys($fields);
  }
  switch ($order) {
    case 'TITLE_ASC':
      $order = 'n.title ASC';
      break;
    case 'TITLE_DESC':
      $order = 'n.title DESC';
      break;
    case 'ASC':
    case 'CREATED_ASC':
      $order = 'n.created ASC';
      break;
    default:
    case 'DESC':
    case 'CREATED_DESC':
      $order = 'n.created DESC';
      break;
  }
  $values = array();
  foreach ($fieldnames as $fieldname) {
    if ($fields[$fieldname]['type'] == 'nodereference') {
      $db_info = content_database_info($fields[$fieldname]);
      $query = "SELECT       n.nid, n.vid, n.title\n                FROM         {" . $db_info['table'] . "} nr\n                INNER JOIN   {node} n ON n.vid = nr.vid AND n.status = 1 " . $filter_nodetypes . "\n                WHERE        nr." . $db_info['columns']['nid']['column'] . " = %d\n                ORDER BY     " . $order;
      $query = db_rewrite_sql($query);
      $result = db_query($query, $nid);
      while ($value = db_fetch_array($result)) {

        // avoid duplicate referrers by using nid as key
        $values[$value['nid']] = $value;
      }
    }
  }
  return $values;
}

/** 
 * Helper function to create an options list of nodereference fields.
 */
function nodereferrer_nodereference_field_options() {
  $options = array();
  $types = content_fields();
  foreach ($types as $type) {
    if ($type['type'] == 'nodereference') {
      $options[$type['field_name']] = $type['field_name'] . ' (' . $type['widget']['label'] . ')';
    }
  }
  return $options;
}

/**
 * Implementation of hook_nodeapi
 */
function nodereferrer_nodeapi($node, $op) {
  switch ($op) {
    case 'insert':
    case 'update':
    case 'delete':

      // Clear content cache to help maintain proper display of nodes.
      $nids = array();
      $type = content_types($node->type);
      foreach ($type['fields'] as $field) {

        // Add referenced nodes to nids. This will clean up nodereferrer fields
        // when the referencing node is updated.
        if ($field['type'] == 'nodereference') {
          $node_field = isset($node->{$field}['field_name']) ? $node->{$field}['field_name'] : array();
          foreach ($node_field as $delta => $item) {
            $nids[$item['nid']] = $item['nid'];
          }
        }
      }

      // Clear Content cache for nodes that reference the node that is being updated.
      // This will keep nodereference fields up to date when referred nodes are
      // updated. @note this currenlty doesn't work all that well since nodereference
      // doesn't respect publishing states or access control.
      $referrers = nodereferrer_referrers($node->nid);
      $referrer_nids = array_keys($referrers);
      $nids = array_merge($nids, $referrer_nids);
      foreach ($nids as $nid) {
        $cid = "content:{$nid}:";

        // define a table to delete from or else this complains
        cache_clear_all($cid, 'cache_content', TRUE);
      }
  }
}

/**
 * Implementation of hook_views_tables
 */
function nodereferrer_views_tables() {
  $tables['nodereferrer'] = array(
    'filters' => array(
      'nodereferrer_filter_field' => array(
        'name' => t('NodeReferrer: by field'),
        'field' => 'nid',
        'operator' => 'nodereferrer_view_handler_operator_innotin',
        'handler' => 'nodereferrer_view_handler_filter_field',
        'option' => array(
          '#type' => 'select',
          '#options' => nodereferrer_nodereference_field_options(),
        ),
        'help' => t('Allows views to query the node\'s references by a certain node reference field.'),
      ),
      'nodereferrer_filter_type' => array(
        'name' => t('NodeReferrer: by node'),
        'field' => 'nid',
        'operator' => 'nodereferrer_view_handler_operator_innotin',
        'handler' => 'nodereferrer_view_handler_filter_type',
        'option' => array(
          '#type' => 'select',
          '#options' => nodereferrer_nodereference_field_options(),
        ),
        'help' => t('Allows views to query the node\'s references by a certain node type.'),
      ),
    ),
  );
  return $tables;
}
function nodereferrer_view_handler_operator_innotin() {
  return array(
    'in' => t('IN'),
    'not in' => t('NOT IN'),
  );
}
function nodereferrer_view_handler_filter_field($op, $filter, $filterinfo, &$query) {
  $nid = (int) $filter['value'];
  $field = $filter['options'];
  $fields = empty($field) ? array_keys(nodereferrer_nodereference_field_options()) : array(
    $field,
  );
  $referrers = nodereferrer_referrers($nid, $fields);
  $query
    ->add_where('node.nid ' . $filter['operator'] . ' (' . implode(',', array_keys($referrers)) . ') ');
}
function nodereferrer_view_handler_filter_node_type($op, $filter, $filterinfo, &$query) {
  $nid = $filter['value'];
  $fields = array_keys(nodereferrer_nodereference_field_options());
  $node_type = $filter['options'];
  $referrers = nodereferrer_referrers($nid, $fields, array(
    $node_type,
  ));
  $query
    ->add_where('node.nid ' . $filter['operator'] . ' (' . implode(',', array_keys($referrers)) . ') ');
}

/**
 * Implementation of hook_views_arguments().
 */
function nodereferrer_views_arguments() {
  $arguments = array(
    'nodereferrer_by_field' => array(
      'name' => t('NodeReference Field References: ID'),
      'handler' => t('nodereferrer_view_handler_argument_field'),
      'option' => array(
        '#type' => 'select',
        '#options' => nodereferrer_nodereference_field_options(),
      ),
      'help' => t('This argument allows views to query the nodes that refer a certain node reference by field.
        Specify referencing field In the options.'),
    ),
    'nodereferrer_by_type' => array(
      'name' => t('Node Type References: ID'),
      'handler' => t('nodereferrer_view_handler_argument_type'),
      'option' => array(
        '#type' => 'select',
        '#options' => node_get_types('names'),
      ),
      'help' => t('This argument allows views to query the nodes that refer to a certain node reference by node type.
        Specify node type in the options field.'),
    ),
  );
  return $arguments;
}

// delegate argument handling to op specific callbacks.
function nodereferrer_view_handler_argument_field($op, &$query, $a1, $a2 = NULL) {
  $function = 'nodereferrer_view_handler_argument_field_' . $op;
  if (function_exists($function)) {
    $function($query, $a1, $a2);
  }
}
function nodereferrer_view_handler_argument_field_filter(&$query, $argtype, $arg = null) {
  $nid = (int) $arg;
  $field = $argtype['options'];
  $fields = empty($field) ? array_keys(nodereferrer_nodereference_field_options()) : array(
    $field,
  );
  $referrers = nodereferrer_referrers($nid, $fields);
  if (empty($referrers)) {
    $referrers = array(
      0 => 0,
    );
  }
  $query
    ->add_where('node.nid in (' . implode(',', array_keys($referrers)) . ') ');
}

// delegate argument handling to op specific callbacks.
function nodereferrer_view_handler_argument_type($op, &$query, $a1, $a2 = NULL) {
  $function = 'nodereferrer_view_handler_argument_type_' . $op;
  if (function_exists($function)) {
    $function($query, $a1, $a2);
  }
}
function nodereferrer_view_handler_argument_type_filter(&$query, $argtype, $arg = null) {
  $nid = (int) $arg;
  $fields = array_keys(nodereferrer_nodereference_field_options());
  $node_type = $argtype['options'];
  $referrers = nodereferrer_referrers($nid, $fields, array(
    $node_type,
  ));
  if (empty($referrers)) {
    $referrers = array(
      0 => 0,
    );
  }
  $query
    ->add_where('node.nid in (' . implode(',', array_keys($referrers)) . ') ');
}