Hello,
any chance this is going to be ported in Drupal 7?

Any workaround on how to recompute a computed field in D7?

Thanks

Comments

frakke’s picture

Assigned: Unassigned » frakke

The version for Drupal 7 is in the works. I can't promise when it will be done, but soon I guess :-)

I will update this issue when I have a running BETA.

enxox’s picture

I was trying to update it, but i'm not good with the code.

Here is my code, seem working except for the database calls that I'm not able to update.


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

/**
 * Implements hook_help().
().
 */
function computed_field_tools_help($path, $arg) {
  switch ($path) {
    case 'admin/structure/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;
  }
}

/**
 * Implements hook_menu().
().
 */
function computed_field_tools_menu() {
  $items = array();

  $items['admin/structure/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;
}

/**
 * Implements hook_permission().
().
 */
function computed_field_tools_permission() {
  return array(
    'recompute computed fields' => array(
      'title' => t('recompute computed fields'),
      'description' => t('TODO Add a description for \'recompute computed fields\''),
    ),
  );
}

/**
 * Select which computed field to re-compute.
 */
function computed_field_tools_recompute_form($form, $form_state) {
  $form = array();

  $computed_fields = array();
  foreach (field_info_fields() as $field) {
    if ($field['type'] == 'computed') {
      $computed_fields[$field['field_name']] = $field['field_name'];
    }
  }

  $form['computed_field_to_recompute'] = array(
    '#type' => 'select',
    '#title' => t('Select computed field to re-compute'),
    '#options' => $computed_fields,
    '#description' => t('Please set site ind maintenance mode before re-computing fields.'),
  );

  // Get content types with computed fields based on $computed_fields from above.
  $content_types_options = array();
 foreach (field_info_instances() 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;
}

/**
 * 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 = field_info_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();
    // TODO Please convert this statement to the D7 database API syntax.
    $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' " .
    // Keep disabled modules out of table.
    "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.
    // TODO Please convert this statement to the D7 database API syntax.
    $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.
  // TODO Please convert this statement to the D7 database API syntax.
  $results = db_query_range(
    "SELECT node.vid FROM {node} node "
    . "WHERE node.type IN (" . db_placeholders($types, 'text') . ") "
    . ' ORDER BY node.nid ASC'
    , $types
    );

  // Re-compute the given nodes.
  $counter = 0;
  while ($result = db_fetch_object($results)) {
    // TODO node_load_multiple returns an array of nodes, rather than a single node
    $node = node_load_multiple(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);

    // TODO Please convert this statement to the D7 database API syntax.
    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.
      // TODO Please convert this statement to the D7 database API syntax.
      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);
    }
    $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;
  }

  $context['message'] = '<p>' . t('Processed %nodes of %nodes_total', array('%nodes' => $context['sandbox']['nodes_offset'], '%nodes_total' => $context['results']['total_nid_count'])) . '</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)));
}
frakke’s picture

Hi enxox,

I am a bit further than that. I am trying to make it fit the idea of using fields on varioues types of entities (node and taxonomy is running fine so far, and the user entity type is to come).
Right now I am figuring out a way to handle the counter bit of entities on each run. Not that I am struckling with it. I have just reached that part of the module :-)

It won't be too long before I can have an alpha or beta ready depending on what state the code ends up in for the first release.

enxox’s picture

I'm ready to test it :)

frakke’s picture

Hi enxox.

Great!

I have added a D7 branch. It contains a D7 DEV version of the module. It's still a bit messy though and contains a few debugging lines. Fell free to test it and report back :-)

I would not recommend using this on a live site yet, though. I am still completely new to Drupal 7, so there might be a few bad lines in there.

I will continue to clean the code and the documentation.

enxox’s picture

well, I'll test it in the next few days, thanks!

enxox’s picture

where the d7 module is?

frakke’s picture

Updated the code.

Nodes, users and terms (vocabularies) should now be fully supported. I have also added a time left estimate while processing the entities.

the latest dev can be found here:
http://drupal.org/node/1079758/release

Feel free to test it and report any errors :-)

(Please note, that when the entity is saved through the edit form, it will have one format. When it is re-computed it will be thrown in as a loaded entity so there might be differences in the entity parameter.)

bennos’s picture

subscribing

wizonesolutions’s picture

bennos: Use the Follow button at the top-right. Don't post subscribe comments.

colan’s picture

As the new maintainer of Computed Field, I'd rather see #1262820: Merge Computed Field Tools into Computed Field resolved than have a separate release for this module.

colan’s picture

Status: Active » Needs review
colan’s picture

Version: 6.x-1.0 » 7.x-1.x-dev
Status: Needs review » Closed (duplicate)

Actually, it makes more sense to mark this one as a duplicate.