You are here

computed_field_tools.module in Computed Field Tools 6

Same filename and directory in other branches
  1. 7 computed_field_tools.module

This module offers a quick way to re-compute computed fields when needed.

File

computed_field_tools.module
View source
<?php

/**
 * @file
 * This module offers a quick way to re-compute computed fields when needed.
 */

/**
 * Implementation of hook_help().
 */
function computed_field_tools_help($path, $arg) {
  switch ($path) {
    case 'admin/content/types/computed_field_recompute':
      return '<p>' . t('The computed field tools module offers a way to re-compute the CCK computed fields of existing nodes. It does so through the Batch API.
When using the Drupal module Computed Field (CCK) you sometimes make changes to the logic behind the value in the computed field. If you wish to avoid re-saving all nodes using the computing field, you can use this tool to re-compute all the values again. It is possible to choose which field (cross nodes) to re-compute and you can also choose which node types you whish to re-compute.
When the batch is running it does not save the entire node again, but it only saves the computed field.<br />
<br />
<em>Please note that when you re-compute the nodes, the node is fetched through node_load() which means that the format of some values might defer from when you submit the node through the node edit form. $node->taxonomy does this.</em>') . '</p> ';
      break;
  }
}

/**
 * Implementation of hook_menu().
 */
function computed_field_tools_menu() {
  $items = array();
  $items['admin/content/types/computed_field_recompute'] = array(
    'title' => 'Re-compute computed fields',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'computed_field_tools_recompute_form',
    ),
    'access arguments' => array(
      'recompute computed fields',
    ),
    'type' => MENU_LOCAL_TASK,
  );
  return $items;
}

/**
 * Implementation of hook_perm().
 */
function computed_field_tools_perm() {
  return array(
    'recompute computed fields',
  );
}

/**
 * Select which computed field to re-compute.
 */
function computed_field_tools_recompute_form($form_state) {
  $form = array();
  $computed_fields = computed_field_tools_get_computed_fields_as_options();
  $form['computed_field_to_recompute'] = array(
    '#type' => 'select',
    '#title' => t('Select computed field to re-compute'),
    '#options' => $computed_fields,
  );

  // Get content types with computed fields based on $computed_fields from above.
  $content_types_options = array();
  foreach (content_types() as $content_type) {
    if (!empty($content_type['fields'])) {
      foreach ($content_type['fields'] as $field_name => $field) {
        if (in_array($field_name, $computed_fields)) {
          $content_types_options[$content_type['type']] = $content_type['name'];
          break;
        }
      }
    }
  }
  $form['content_types'] = array(
    '#type' => 'select',
    '#title' => t('Optional: select content types to re-compute.'),
    '#options' => $content_types_options,
    '#multiple' => TRUE,
    '#size' => 5,
    '#description' => t('The list does not take into account which computed field is on which content types.<br />If no content types is selected, all the content types for the selected computed field are used.'),
  );
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => 'Re-compute',
  );
  return $form;
}

/**
 * Get an option friendly array of computed fields.
 *
 * @return array
 *   Array of computed field names, indexed by their machine names.
 */
function computed_field_tools_get_computed_fields_as_options() {
  $computed_fields = array();
  foreach (content_fields() as $field) {
    if ($field['type'] == 'computed') {
      $computed_fields[$field['field_name']] = $field['field_name'];
    }
  }
  return $computed_fields;
}

/**
 * Submit handler.
 * This prepares the computed field and node types and then it triggers the
 * batch run which does the actual re-computation.
 */
function computed_field_tools_recompute_form_submit($form, &$form_state) {
  if (empty($form_state['values']['computed_field_to_recompute'])) {
    drupal_set_message(t('No content field given.'), 'error');
    return;
  }
  $field = content_fields($form_state['values']['computed_field_to_recompute']);
  if (empty($field)) {
    drupal_set_message(t('Content field not found.'), 'error');
    return;
  }

  // Get node types to re-compute
  if (!empty($form_state['values']['content_types']) && is_array($form_state['values']['content_types'])) {
    $types = $form_state['values']['content_types'];
  }
  else {

    // This is partly stolen from content_fields_list() - cck.
    // Lets get content types for this field.
    $types = array();
    $result = db_query("SELECT nt.name, nt.type FROM {" . content_instance_tablename() . "} nfi " . "LEFT JOIN {node_type} nt ON nt.type = nfi.type_name " . "WHERE nfi.field_name = '%s' " . "AND nfi.widget_active = 1 " . "ORDER BY nt.name ASC", $field['field_name']);
    while ($type = db_fetch_array($result)) {
      $types[] = $type['type'];
    }
  }
  if (empty($types)) {
    drupal_set_message(t('No content types found for @content_type.', array(
      '@content_type' => $form_state['values']['computed_field_to_recompute'],
    )), 'error');
    return;
  }
  $db_info = content_database_info($field);
  $batch = array(
    'title' => 'Recomputing ' . $field['field_name'],
    'operations' => array(
      array(
        '_computed_field_tools_batch_recompute',
        array(
          $types,
          $field,
          $form_state,
        ),
      ),
    ),
    'title' => t('Re-computing @field_name', array(
      '@field_name' => $field['field_name'],
    )),
    'progress_message' => '',
    'finished' => '_computed_field_tools_batch_recompute_finished',
  );
  batch_set($batch);
}

/**
 * Batch job.
 * Re-compute all computed fields by field_name.
 * Code partly stolen from http://drupal.org/node/195013.
 */
function _computed_field_tools_batch_recompute($types, $field, $form_state, &$context) {

  // Make content aware connection to the computed fields query.
  $db_info = content_database_info($field);
  $db_table = $db_info['table'];
  $field_db_column_name = $db_info['columns']['value']['column'];
  $field_db_column_type = $db_info['columns']['value']['type'];

  // Batch initial properties.
  if (empty($context['sandbox'])) {
    $context['sandbox']['current_node_index'] = 0;
    $context['sandbox']['nodes_offset'] = 0;
    $context['sandbox']['nodes_per_run'] = 100;
    $context['results']['total_nids_touched'] = 0;
    $context['results']['start'] = microtime(TRUE);
    $context['results']['end'] = 0;

    // Lets examine how many nodes, we are dealing with.
    $result_total_count = db_query("SELECT COUNT(*) as total_count FROM {node} node " . "WHERE node.type IN (" . db_placeholders($types, 'text') . ") ", $types);
    $total_count = db_fetch_object($result_total_count);
    $context['results']['total_nid_count'] = $total_count->total_count;
  }

  // Query which fields to compute.
  $results = db_query_range("SELECT n.vid, cf." . $field_db_column_name . " AS existing_value FROM {node} n " . " LEFT JOIN {" . $db_table . "} cf ON cf.vid=n.vid " . "WHERE n.type IN (" . db_placeholders($types, 'text') . ") " . ' ORDER BY n.nid ASC', $types, $context['sandbox']['nodes_offset'], $context['sandbox']['nodes_per_run']);

  // Re-compute the given nodes.
  $counter = 0;
  while ($result = db_fetch_object($results)) {
    $node = node_load(array(
      'vid' => $result->vid,
    ));
    $node_field = isset($node->{$field}['field_name']) ? $node->{$field}['field_name'] : array(
      0 => array(
        'value' => NULL,
      ),
    );
    _computed_field_compute_value($node, $field, $node_field);

    // Only store changed values.
    if ($result->existing_value != $node_field[0]['value']) {
      db_query("UPDATE {" . $db_table . "} SET `" . $field_db_column_name . "` = " . db_type_placeholder($field_db_column_type) . " WHERE `vid` = %d", $node_field[0]['value'], $result->vid);
      if (db_affected_rows() < 1) {

        // Entry is not yet created, so lets insert.
        db_query("INSERT INTO {" . $db_table . "} SET `" . $field_db_column_name . "` = " . db_type_placeholder($field_db_column_type) . ", `vid` = %d, `nid` = %d", $node_field[0]['value'], $result->vid, $node->nid);
      }

      // Clear the content cache for the element so that the changes will be
      // visible at once.
      $cid = 'content:' . $node->nid . ':' . $node->vid;
      cache_clear_all($cid, content_cache_tablename());
    }
    $counter++;
  }
  $context['sandbox']['nodes_offset'] += $context['sandbox']['nodes_per_run'];
  if ($context['sandbox']['nodes_offset'] + 1 >= $context['results']['total_nid_count']) {
    $context['results']['end'] = microtime(TRUE);
    $context['finished'] = 1;
    return;
  }
  if ($context['sandbox']['nodes_offset']) {
    $time_spent = microtime(TRUE) - $context['results']['start'];
    $time_left = ceil($context['results']['total_nid_count'] * $time_spent / $context['sandbox']['nodes_offset'] - $time_spent);
    $time_left_formatted = t('@minutes minutes @seconds seconds', array(
      '@minutes' => floor($time_left / 60),
      '@seconds' => $time_left % 60,
    ));
  }
  else {
    $time_left_formatted = 'Estimating...';
  }
  $remaining_nodes_count = $context['results']['total_nid_count'] - $context['sandbox']['nodes_offset'];
  $context['message'] = '<p>' . t('Remaining %nodes_remaining of %nodes_total. Estimated time left: %time_left', array(
    '%nodes_remaining' => $remaining_nodes_count,
    '%nodes_total' => $context['results']['total_nid_count'],
    '%time_left' => $time_left_formatted,
  )) . '</p>';
  $context['finished'] = $context['sandbox']['nodes_offset'] / $context['results']['total_nid_count'];
}

/**
 * Batch operation finished.
 */
function _computed_field_tools_batch_recompute_finished($success, $results, $options) {
  $duration = $results['end'] - $results['start'];
  $average_time_pr_thousand = $duration * 1000 / $results['total_nid_count'];
  drupal_set_message(t('Re-computed %total_count fields in %duration seconds', array(
    '%total_count' => $results['total_nid_count'],
    '%duration' => $duration,
  )));
  drupal_set_message(t('It took an average of %average_time_pr_thousand seconds per 1000 nodes to compute.', array(
    '%average_time_pr_thousand' => $average_time_pr_thousand,
  )));
}

Functions

Namesort descending Description
computed_field_tools_get_computed_fields_as_options Get an option friendly array of computed fields.
computed_field_tools_help Implementation of hook_help().
computed_field_tools_menu Implementation of hook_menu().
computed_field_tools_perm Implementation of hook_perm().
computed_field_tools_recompute_form Select which computed field to re-compute.
computed_field_tools_recompute_form_submit Submit handler. This prepares the computed field and node types and then it triggers the batch run which does the actual re-computation.
_computed_field_tools_batch_recompute Batch job. Re-compute all computed fields by field_name. Code partly stolen from http://drupal.org/node/195013.
_computed_field_tools_batch_recompute_finished Batch operation finished.