You are here

nodereference_count.module in Nodereference Count 7

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

Defines a field type for counting the references to a node.

File

nodereference_count.module
View source
<?php

/**
 * @file
 * Defines a field type for counting the references to a node.
 */

/**
 * Implements hook_field_info().
 */
function nodereference_count_field_info() {
  return array(
    'nodereference_count' => array(
      'label' => t('Node reference count'),
      'description' => t('Store node reference count data in the database.'),
      'instance_settings' => array(
        'counted_reference_fields' => array(),
        'count_only_published' => TRUE,
      ),
      'default_widget' => 'nodereference_count_widget',
      'default_formatter' => 'nodereference_count_formatter_default',
    ),
  );
}

/**
 * Generate a list of available node reference fields to count.
 *
 * @param $bundle
 *   The field instance bundle.
 * @return
 *   An array of nodereference fields that are available to count.
 */
function nodereference_count_field_options($bundle) {
  $field_types = field_info_fields();
  $field_options = array();
  foreach ($field_types as $field_type) {
    if ($field_type['type'] == 'node_reference' && isset($field_type['settings']['referenceable_types'][$bundle]) && $field_type['settings']['referenceable_types'][$bundle] === $bundle) {
      $field_options[$field_type['field_name']] = check_plain($field_type['field_name']);
    }
  }
  return $field_options;
}

/**
 * Implements hook_field_instance_settings_form().
 */
function nodereference_count_field_instance_settings_form($field, $instance) {
  $settings = $instance['settings'];
  $options = nodereference_count_field_options($instance['bundle']);
  $form = array();
  if (empty($options)) {
    $form['counted_reference_fields_empty'] = array(
      '#prefix' => '<p>',
      '#markup' => t('There are no node reference fields to count. If you wish to count the number of references to a node of this type, add a node reference field to another content type, allowing it to reference this content type.'),
      '#suffix' => '</p>',
    );
  }
  else {
    $form['counted_reference_fields'] = array(
      '#type' => 'checkboxes',
      '#title' => t('Nodereference fields that may be counted'),
      '#description' => t('Select the node refence fields that you would like to count.'),
      '#multiple' => TRUE,
      '#default_value' => $settings['counted_reference_fields'],
      '#options' => $options,
    );
    $form['count_only_published'] = array(
      '#type' => 'checkbox',
      '#title' => t('Do not count references from unpublished nodes.'),
      '#default_value' => $settings['count_only_published'],
    );
  }
  return $form;
}

/**
 * Implements hook_field_is_empty().
 */
function nodereference_count_field_is_empty($item, $field) {
  return is_null($item['count']);
}

/**
 * Implements hook_field_widget_info().
 */
function nodereference_count_field_widget_info() {
  return array(
    'nodereference_count_widget' => array(
      'label' => t('default'),
      'description' => t('The count is calculated, so there is no data to enter.'),
      'field types' => array(
        'nodereference_count',
      ),
      'behaviors' => array(
        'default_value' => FIELD_BEHAVIOR_NONE,
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_info().
 */
function nodereference_count_field_formatter_info() {
  return array(
    'nodereference_count_default' => array(
      'label' => t('Default'),
      'field types' => array(
        'nodereference_count',
      ),
    ),
    'nodereference_count_nonzero' => array(
      'label' => t('Non-zero'),
      'field types' => array(
        'nodereference_count',
      ),
    ),
  );
}

/**
 * Implements hook_field_formatter_view().
 */
function nodereference_count_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
  $element = array();
  switch ($display['type']) {
    case 'nodereference_count_default':
      foreach ($items as $delta => $item) {
        $element[$delta] = array(
          '#markup' => $item['count'],
        );
      }
      break;
    case 'nodereference_count_nonzero':
      foreach ($items as $delta => $item) {
        if ($item['count'] > 0) {
          $element[$delta] = array(
            '#markup' => $item['count'],
          );
        }
      }
      break;
  }
  return $element;
}

/**
 * Implements hook_field_presave().
 */
function nodereference_count_field_presave($entity_type, $entity, $field, $instance, $langcode, &$items) {
  if ($field['type'] == 'nodereference_count' && $entity_type == 'node') {
    $items[0]['count'] = nodereference_count_get_count($instance['settings'], $entity->nid);
  }
}

/**
 * Get the db tables and columns for an array of field names.
 *
 * @param $field_names
 *   An array of field names.
 * @return
 *   An indexed array of table and column names.
 */
function nodereference_count_get_fields_db($field_names) {
  $db = array();
  if (!empty($field_names)) {
    foreach ($field_names as $field_name) {
      $field = field_info_field($field_name);

      // Make sure we are dealing with SQL storage.
      if ($field['storage']['type'] == 'field_sql_storage') {
        $db_info = $field['storage']['details']['sql']['FIELD_LOAD_CURRENT'];
        $tables = array_keys($db_info);
        $table = array_pop($tables);
        $db[] = array(
          'table' => $table,
          'column' => array_pop($db_info[$table]),
        );
      }
    }
  }
  return $db;
}

/**
 * Get the count of node references to a particular node.
 *
 * @param $settings
 *   The settings for this field instance.
 * @param $nid
 *   The nid of the node being referenced.
 * @return
 *   A count of the number of references to the node.
 */
function nodereference_count_get_count($settings, $nid) {

  // Get the db info for the node reference fields.
  $db = nodereference_count_get_fields_db($settings['counted_reference_fields']);
  if (!empty($db)) {

    // Use the first field for the initial query.
    $query = db_select('node', 'n');
    $query
      ->fields('n', array(
      'nid',
    ));
    $alias = $query
      ->innerJoin($db[0]['table'], 'nr', '%alias.entity_id = n.nid');
    $query
      ->condition("{$alias}.{$db[0]['column']}", $nid);
    if ($settings['count_only_published']) {
      $query
        ->condition('n.status', 1);
    }
    unset($db[0]);

    // Add each additional field to the query via a UNION ALL.
    foreach ($db as $d) {
      $select = db_select('node', 'n');
      $select
        ->fields('n', array(
        'nid',
      ));
      $alias = $select
        ->innerJoin($d['table'], 'nr', '%alias.entity_id = n.nid');
      $select
        ->condition("{$alias}.{$d['column']}", $nid);
      if ($settings['count_only_published']) {
        $select
          ->condition('n.status', 1);
      }
      $query
        ->union($select, 'UNION ALL');
    }
    $query
      ->addTag('nodereference_count');
    return $query
      ->countQuery()
      ->execute()
      ->fetchField();
  }
  return 0;
}

/**
 * Implements hook_node_insert().
 */
function nodereference_count_node_insert($node) {
  nodereference_count_references_update($node);
}

/**
 * Implements hook_node_update().
 */
function nodereference_count_node_update($node) {
  nodereference_count_references_update($node);
}

/**
 * Implements hook_node_delete().
 *
 * hook_node_delete() runs before database queries are executed, so
 * we cannot just update the counts here or it will reflect the count
 * before anything has actually been deleted.
 *
 * The workaround is an ugly hack. We add a delay flag to
 * nodereference_count_references_update(). This allows us to add nids
 * to a statically cached array instead of counting them immediately.
 * The cached array can then be processed via hook_exit() after the
 * database updates are done so that we get a correct count.
 *
 * @see nodereference_count_references_update().
 * @see nodereference_count_delayed_nids().
 * @see nodereference_count_exit().
 */
function nodereference_count_node_delete($node) {
  nodereference_count_references_update($node, TRUE);
}

/**
 * Get an array of node reference fields for a particular node bundle.
 *
 * @param $bundle
 *   The content type for which we want a list of node reference fields.
 * @return
 *   An array of fields.
 */
function nodereference_count_get_nodereference_fields($bundle) {
  $nodereference_fields = array();
  $fields = field_info_fields();
  foreach ($fields as $field) {
    if ($field['type'] == 'node_reference' && isset($field['bundles']['node']) && in_array($bundle, $field['bundles']['node'])) {
      $nodereference_fields[$field['field_name']] = $field;
    }
  }
  return $nodereference_fields;
}

/**
 * From a set of node reference fields get those that are counted by a nodereference count field.
 *
 * @param $node_references
 *   An array of node reference fields.
 * @return
 *   An array of field names.
 */
function nodereference_count_get_counted_nodereference_fields($node_references) {
  $counted_fields = array();
  $bundles = field_info_instances('node');
  foreach ($bundles as $bundle) {
    foreach ($bundle as $instance) {
      if (isset($instance['settings']['counted_reference_fields'])) {
        foreach ($node_references as $node_reference) {
          if (in_array($node_reference['field_name'], $instance['settings']['counted_reference_fields'], TRUE)) {
            $counted_fields[$node_reference['field_name']] = $node_reference['field_name'];
          }
        }
      }
    }
  }
  return $counted_fields;
}

/**
 * From a set of node reference fields get all the nids that need to be updated.
 *
 * @param $node
 *   The node object.
 * @param $counted_fields
 *   An array of node reference fields.
 * @return
 *   An array of nids.
 */
function nodereference_count_get_referenced_nids($node, $counted_fields) {
  $nids = array();
  foreach ($counted_fields as $counted_field) {

    // Get all the updated nids.
    $updated_nodereferences = $node->{$counted_field};
    foreach ($updated_nodereferences as $language => $deltas) {
      foreach ($deltas as $delta => $nodereference) {
        $nids[$nodereference['nid']] = $nodereference['nid'];
      }
    }

    // Get all the original nids.
    if (isset($node->original)) {
      $original_nodereferences = $node->original->{$counted_field};
      foreach ($original_nodereferences as $language => $deltas) {
        foreach ($deltas as $delta => $nodereference) {
          $nids[$nodereference['nid']] = $nodereference['nid'];
        }
      }
    }
  }
  return $nids;
}

/**
 * Identify counted node references on a node and trigger an update of the referenced nodes.
 *
 * @param $node
 *   The node object.
 * @param $delay
 *   Whether the actual count update should be delayed. See the hook_node_delete
 *   implementation above for more info.
 */
function nodereference_count_references_update($node, $delay = FALSE) {

  // Get all the node reference fields for this content type.
  $nodereference_fields = nodereference_count_get_nodereference_fields($node->type);

  // If there are no node references for this content type then there is nothing to count.
  if (empty($nodereference_fields)) {
    return;
  }

  // Get all the node reference fields for this content type that are counted by a nodereference count field.
  $counted_fields = nodereference_count_get_counted_nodereference_fields($nodereference_fields);

  // If there are no node references being counted for this content type then there is nothing to count.
  if (empty($counted_fields)) {
    return;
  }

  // Get all the nids that need to be updated.
  $nids = nodereference_count_get_referenced_nids($node, $counted_fields);

  // Update the counts on the referenced nodes.
  foreach ($nids as $nid) {

    // Wait to update the count for this nid.
    if ($delay) {
      nodereference_count_delayed_nids($nid);
    }
    else {
      nodereference_count_update_count($nid);
    }
  }
}

/**
 * Trigger an update of the fields on a particular node.
 *
 * @param $nid
 *   The nid of the node being referenced.
 */
function nodereference_count_update_count($nid) {
  $node = node_load($nid);
  field_attach_presave('node', $node);
  field_attach_update('node', $node);
}

/**
 * Statically cache any nids that should have their count update delayed.
 *
 * @param $nid
 *   A nid that should be added to the cache.
 * @return
 *   An array of nids.
 */
function nodereference_count_delayed_nids($nid = NULL) {
  $nids =& drupal_static(__FUNCTION__, array());
  if (!is_null($nid) && !isset($nids[$nid])) {
    $nids[$nid] = $nid;
  }
  return $nids;
}

/**
 * Implements hook_exit().
 */
function nodereference_count_exit() {
  $nids = nodereference_count_delayed_nids();
  if (!empty($nids)) {
    foreach ($nids as $nid) {
      nodereference_count_update_count($nid);
    }
  }
}

Functions

Namesort descending Description
nodereference_count_delayed_nids Statically cache any nids that should have their count update delayed.
nodereference_count_exit Implements hook_exit().
nodereference_count_field_formatter_info Implements hook_field_formatter_info().
nodereference_count_field_formatter_view Implements hook_field_formatter_view().
nodereference_count_field_info Implements hook_field_info().
nodereference_count_field_instance_settings_form Implements hook_field_instance_settings_form().
nodereference_count_field_is_empty Implements hook_field_is_empty().
nodereference_count_field_options Generate a list of available node reference fields to count.
nodereference_count_field_presave Implements hook_field_presave().
nodereference_count_field_widget_info Implements hook_field_widget_info().
nodereference_count_get_count Get the count of node references to a particular node.
nodereference_count_get_counted_nodereference_fields From a set of node reference fields get those that are counted by a nodereference count field.
nodereference_count_get_fields_db Get the db tables and columns for an array of field names.
nodereference_count_get_nodereference_fields Get an array of node reference fields for a particular node bundle.
nodereference_count_get_referenced_nids From a set of node reference fields get all the nids that need to be updated.
nodereference_count_node_delete Implements hook_node_delete().
nodereference_count_node_insert Implements hook_node_insert().
nodereference_count_node_update Implements hook_node_update().
nodereference_count_references_update Identify counted node references on a node and trigger an update of the referenced nodes.
nodereference_count_update_count Trigger an update of the fields on a particular node.