? .DS_Store
? multiple_reviews_01.patch
? reviews.patch
? css/.DS_Store
? images/.DS_Store
? includes/.DS_Store
? js/.DS_Store
Index: atr.api.php
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/atr/Attic/atr.api.php,v
retrieving revision 1.1.2.3
diff -u -p -r1.1.2.3 atr.api.php
--- atr.api.php	6 Jun 2009 01:14:39 -0000	1.1.2.3
+++ atr.api.php	30 Jul 2009 14:38:58 -0000
@@ -65,4 +65,14 @@ function atr_atr_method_callback() {
       ),
     ),
   );
+}
+
+/**
+ * Respond to review deletion.
+ *
+ * @param $review
+ *   The review that is being deleted.
+ */
+function hook_atr_review_delete($review) {
+  db_query("DELETE FROM {atr_review} WHERE rid = %d", $review->rid);
 }
\ No newline at end of file
Index: atr.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/atr/Attic/atr.install,v
retrieving revision 1.1.2.9
diff -u -p -r1.1.2.9 atr.install
--- atr.install	14 Jun 2009 16:59:42 -0000	1.1.2.9
+++ atr.install	30 Jul 2009 14:38:58 -0000
@@ -53,6 +53,51 @@ function atr_schema() {
         'pid' => array('pid'),
       ),
     ),
+    'atr_review' => array(
+      'description' => 'The base table for reviews.',
+      'fields' => array(
+        'rid' => array(
+          'description' => 'A unique ID for every review.',
+          'type' => 'serial',
+        ),
+        'timestamp' => array(
+          'description' => 'A Unix timestamp indicating when this review was run.',
+          'type' => 'int',
+          'not null' => TRUE,
+          'default' => 0
+        ),
+        'path' => array(
+          'description' => 'The path to the files that have been reviewed.',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+        ),
+      ),
+      'indexes' => array(
+        'rid' => array('rid'),
+      ),
+      'primary key' => array('rid'),
+    ),
+    'atr_review_method' => array(
+      'description' => 'The methods used for each {atr_review}.',
+      'fields' => array(
+        'rid' => array(
+          'description' => 'The {atr_review}.rid of the review.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+        ),
+        'method' => array(
+          'description' => 'A review method that is used with this profile.',
+          'type' => 'varchar',
+          'length' => 255,
+          'not null' => TRUE,
+        ),
+      ),
+      'indexes' => array(
+        'rid' => array('rid'),
+      ),
+    ),
     'atr_string' => array(
       'description' => 'The strings that are found in the files to review.',
       'fields' => array(
@@ -62,6 +107,12 @@ function atr_schema() {
           'unsigned' => TRUE,
           'not null' => TRUE,
         ),
+        'rid' => array(
+          'description' => 'The {atr_review}.rid of the review this string belongs to.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+        ),
         'string' => array(
           'description' => 'A string found in the files to review.',
           'type' => 'text',
@@ -80,6 +131,25 @@ function atr_schema() {
       ),
       'primary key' => array('sid'),
     ),
+    'atr_string_id' => array(
+      'description' => 'Generate IDs for all strings from a review.',
+      'fields' => array(
+        'sid' => array(
+          'description' => 'A unique ID for every string.',
+          'type' => 'serial',
+        ),
+        'rid' => array(
+          'description' => 'The {atr_review}.rid of the review this string ID belongs to.',
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+        ),
+      ),
+      'indexes' => array(
+        'rid' => array('rid'),
+      ),
+      'primary key' => array('sid'),
+    ),
     'atr_string_location' => array(
       'description' => 'Stores in which files and on which lines every {atr_string} is used.',
       'fields' => array(
Index: atr.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/atr/Attic/atr.module,v
retrieving revision 1.1.2.17
diff -u -p -r1.1.2.17 atr.module
--- atr.module	14 Jun 2009 23:29:21 -0000	1.1.2.17
+++ atr.module	30 Jul 2009 14:38:58 -0000
@@ -18,7 +18,7 @@ function atr_menu() {
       'file' => 'system.admin.inc',
       'file path' => drupal_get_path('module', 'system'),
     ),
-    'atr/review' => array(
+    'atr/new-review' => array(
       'title' => 'New review',
       'page callback' => 'drupal_get_form',
       'page arguments' => array('atr_form_review'),
@@ -71,19 +71,49 @@ function atr_menu() {
       'type' => MENU_CALLBACK,
       'weight' => 1,
     ),
-    'atr/blacklist' => array(
-      'title' => 'Blacklisted strings',
-      'page callback' => 'atr_results_blacklist',
+    'atr/review' => array(
+      'title' => 'Reviews',
+      'page callback' => 'atr_review_list',
+      'access arguments' => array('access text review results'),
+      'file' => 'atr.results.inc',
+      'file path' => drupal_get_path('module', 'atr') . '/includes',
+    ),
+    'atr/review/%atr_review' => array(
+      'title callback' => 'atr_review_title',
+      'title arguments' => array(2),
+      'page callback' => 'atr_review_result',
+      'page arguments' => array(2),
       'access arguments' => array('access text review results'),
       'file' => 'atr.results.inc',
       'file path' => drupal_get_path('module', 'atr') . '/includes',
+      'type' => MENU_CALLBACK,
     ),
-    'atr/similar' => array(
-      'title' => 'Similar strings',
-      'page callback' => 'atr_results_similar',
+    'atr/review/%atr_review/overview' => array(
+      'title' => 'Results',
+      'page callback' => 'atr_review_result',
+      'page arguments' => array(2),
       'access arguments' => array('access text review results'),
       'file' => 'atr.results.inc',
       'file path' => drupal_get_path('module', 'atr') . '/includes',
+      'type' => MENU_DEFAULT_LOCAL_TASK,
+    ),
+    'atr/review/%atr_review/delete' => array(
+      'title' => 'Delete review',
+      'page callback' => 'drupal_get_form',
+      'page arguments' => array('atr_form_review_delete', 2),
+      'access arguments' => array('delete text review results'),
+      'file' => 'atr.results.inc',
+      'file path' => drupal_get_path('module', 'atr') . '/includes',
+      'type' => MENU_LOCAL_TASK,
+      'weight' => 1,
+    ),
+    'atr/review/%atr_review/%' => array(
+      'page callback' => 'atr_review_method_result',
+      'page arguments' => array(2, 3),
+      'access arguments' => array('access text review results'),
+      'file' => 'atr.results.inc',
+      'file path' => drupal_get_path('module', 'atr') . '/includes',
+      'type' => MENU_CALLBACK,
     ),
   );
 }
@@ -148,10 +178,20 @@ function atr_atr_method_info() {
     'similarity' => array(
       '#title' => t('Similarity review'),
       '#description' => t('Compare all strings to eachother and list all that are similar. To reduce the amount of different strings, the ones that are similar may be merged.'),
+      '#result_callback' => array(
+        '#callback' => 'atr_results_similar',
+        '#module' => 'atr',
+        '#file' => 'includes/atr.results.inc',
+      ),
     ),
     'blacklist' => array(
       '#title' => t('Blacklist review'),
       '#description' => t('List all strings that contain blacklisted words.'),
+      '#result_callback' => array(
+        '#callback' => 'atr_results_blacklist',
+        '#module' => 'atr',
+        '#file' => 'includes/atr.results.inc',
+      ),
     ),
   );
 }
@@ -193,30 +233,49 @@ function atr_atr_method_callback() {
 }
 
 /**
- * Load a review method.
+ * Load a review method's information.
+ *
+ * @param $method
+ *   The method's machine name. Leave blank to return information for all
+ *   methods.
  *
- * @param $name
+ * @return
+ *   Array with information.
+ */
+function atr_method_info($method = NULL) {
+  static $method_info = NULL;
+
+  if(!$method_info) {
+    $method_info = module_invoke_all('atr_method_info');
+  }
+
+  return $method ? $method_info[$method] : $method_info;
+}
+
+/**
+ * Load a review method's callbacks.
+ *
+ * @param $method
  *   The method's machine name.
  * @param $language
  *   The language to get callback information for.
+ *
  * @return
- *   Array with method and callback information.
+ *   Array with callback information.
  */
-function atr_method_load($name, $language) {
-  static $method_info = NULL;
+function atr_method_callback($method, $language) {
   static $method_callback = NULL;
 
-  if(!$method_info) {
-    $method_info = module_invoke_all('atr_method_info');
+  if(!$method_callback) {
     $method_callback = module_invoke_all('atr_method_callback');
   }
 
   // Check for general and language-specific callback information.
-  if (isset($method_callback[$name]['all'])) {
-    $callbacks_all = $method_callback[$name]['all'];
+  if (isset($method_callback[$method]['all'])) {
+    $callbacks_all = $method_callback[$method]['all'];
   }
-  if (isset($method_callback[$name][$language]) && $language != 'all') {
-    $callbacks_language = $method_callback[$name][$language];
+  if (isset($method_callback[$method][$language]) && $language != 'all') {
+    $callbacks_language = $method_callback[$method][$language];
   }
 
   // Get the right callback information. Language-specific callbacks may
@@ -234,7 +293,7 @@ function atr_method_load($name, $languag
     $callbacks = $callbacks_language;
   }
 
-  return array_merge($method_info[$name], array('#callbacks' => $callbacks));
+  return $callbacks;
 }
 
 /**
@@ -250,10 +309,7 @@ function atr_profile_load($pid) {
   if ($profile = db_fetch_object(db_query("SELECT * FROM {atr_profile} WHERE pid = %d", $pid))) {
     $result = db_query("SELECT method FROM {atr_profile_method} WHERE pid = %d", $pid);
     while ($method_name = db_result($result)) {
-      $method = atr_method_load($method_name, $profile->language);
-      if ($method['#callbacks']) {
-        $profile->methods[$method_name] = $method;
-      }
+      $profile->methods[$method_name] = atr_method_callback($method_name, $profile->language);
     }
     return $profile;
   }
@@ -297,7 +353,7 @@ function atr_profile_save(&$profile) {
  *
  * @param $callback
  *   An associative array containing callback information as defined in
- *   hook_atr_method_info().
+ *   hook_atr_method_callback().
  * @param ...
  *   Additional arguments to pass on to the callback.
  */
@@ -310,84 +366,72 @@ function atr_callback_execute($callback)
 }
 
 /**
- * Load a string.
- *
- * @param $sid
- *   The SID of the string to load.
+ * Check if the operating system supports CLI tar.
  *
  * @return
- *   A string object or FALSE if no string with this SID exists.
+ *   Boolean.
  */
-function atr_string_load($sid) {
-  $string = atr_string_load_multiple(array($sid));
+function atr_tar_exists() {
+  $code;
+  $output;
+  exec('tar --help', $output, $code);
 
-  return count($string) ? $string[$sid] : FALSE;
+  return $code == 0;
 }
 
 /**
- * Load multiple strings by SID or as a range.
+ * Create a new review.
  *
- * @param $sids
- *   An array containing the SIDs of the strings to load.
- *
- * @return
- *   An array of string objects or FALSE if no strings could be loaded.
+ * @param $path
+ *   The path to the files to review.
+ * @param $methods
+ *   An array containing the review methods to use during this review.
+ * @param $timestamp
+ *   The time of execution. Only needed if the review will not be executed at
+ *   the time of instancing this class.
  */
-function atr_string_load_multiple($sids) {
-  static $strings = array();
+class ATRReview {
+  function __construct($path, $methods, $timestamp = NULL) {
+    $this->timestamp = $timestamp ? $timestamp : time();
+    $this->path = $path;
+    $this->methods = $methods;
 
-  // Only query for strings that haven't yet been loaded.
-  $sids_load = array_diff($sids, array_keys($strings));
-  $placeholders = db_placeholders($sids_load);
-  $result = db_query("SELECT * FROM {atr_string} WHERE sid IN (" . $placeholders . ")", $sids_load);
-  $strings = array();
-  while ($string = db_fetch_object($result)) {
-    $strings[$string->sid] = $string;
-  }
-
-  // Get all requested strings from the static cache.
-  $return = array();
-  foreach ($sids as $sid) {
-    $return[$sid] = $strings[$sid];
+    drupal_write_record('atr_review', $this);
+    $values = implode(',', array_fill(0, count($this->methods), "($this->rid, '%s')"));
+    db_query("INSERT INTO {atr_review_method} VALUES " . $values, $this->methods);
   }
-
-  return $return;
 }
 
 /**
- * Load a range of strings.
+ * Load a review.
  *
- * @param $from
- *   The first SID in the range.
- * @param $to
- *   The last SID in the range. 0 to load all strings from $from.
+ * @param $rid
+ *   The RID of the review to load.
  *
  * @return
- *   An array of string objects or FALSE if no strings could be loaded.
+ *   A review object or FALSE of the RID doesn't match any reviews.
  */
-function atr_string_load_range($from, $to = 0) {
-  $condition = $to ? " AND sid <= %d" : NULL;
-  $query = "SELECT * FROM {atr_string} WHERE sid >= %d" . $condition . " ORDER BY sid ASC";
-  $result = db_query($query, $from, $to);
-
-  $strings = array();
-  while ($string = db_fetch_object($result)) {
-    $strings[$string->sid] = $string;
+function atr_review_load($rid) {
+  if ($review = db_fetch_object(db_query("SELECT * FROM {atr_review} WHERE rid = %d", $rid))) {
+    $review->methods = array();
+    $result = db_query("SELECT method FROM {atr_review_method} WHERE rid = %d", $rid);
+    while ($method = db_result($result)) {
+      $review->methods[] = $method;
+    }
   }
 
-  return $strings;
+  return $review;
 }
 
 /**
- * Check if the operating system supports CLI tar.
+ * Create a page title for a review.
+ *
+ * @param $review
+ *   The review to create the title for.
  *
  * @return
- *   Boolean.
+ *   String.
  */
-function atr_tar_exists() {
-  $code;
-  $output;
-  exec('tar --help', $output, $code);
-
-  return $code == 0;
+function atr_review_title($review) {
+  return $review->path;
 }
\ No newline at end of file
Index: includes/atr.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/atr/includes/Attic/atr.admin.inc,v
retrieving revision 1.1.2.18
diff -u -p -r1.1.2.18 atr.admin.inc
--- includes/atr.admin.inc	14 Jun 2009 23:29:21 -0000	1.1.2.18
+++ includes/atr.admin.inc	30 Jul 2009 14:38:58 -0000
@@ -137,8 +137,10 @@ function atr_form_review_submit($form, &
 
   if ($path) {
     module_load_include('inc', 'atr', 'includes/atr.review');
-    atr_review($path, atr_profile_load((int) $values['pid']), (bool) $values['file_types']['po'], (bool) $values['file_types']['code'], $values['api_version']);
-    drupal_set_message(t('The texts have been reviewed.'));
+    $profile = atr_profile_load((int) $values['pid']);
+    $review = new ATRReview($path, array_keys($profile->methods));
+    atr_review($review, $profile, (bool) $values['file_types']['po'], (bool) $values['file_types']['code'], $values['api_version']);
+    drupal_set_message(t('The texts have been reviewed.') . ' ' . l(t('View results'), 'atr/review/' . $review->rid));
   }
   else {
     drupal_set_message(t('The texts could not be reviewed.'));
Index: includes/atr.extract.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/atr/includes/Attic/atr.extract.inc,v
retrieving revision 1.1.2.6
diff -u -p -r1.1.2.6 atr.extract.inc
--- includes/atr.extract.inc	14 Jun 2009 23:19:08 -0000	1.1.2.6
+++ includes/atr.extract.inc	30 Jul 2009 14:38:58 -0000
@@ -114,56 +114,58 @@ function atr_tmp_dir() {
  * @param $context
  *   A batch operation context.
  */
-function atr_extract($path, $po, $code, $api_version, &$context) {
+function atr_extract($review, $po, $code, $api_version, &$context) {
   global $atr_strings;
 
   $atr_strings = array();
 
   $context['message'] = t('Extracting strings.');
 
-  // Delete old strings.
-  db_query("DELETE FROM {atr_string}");
-  db_query("DELETE FROM {atr_string_location}");
-  db_query("DELETE FROM {atr_string_blacklist}");
-  db_query("DELETE FROM {atr_string_similar}");
-
   // Extract strings from code files.
   if ($code) {
     module_load_include('inc', 'potx', 'potx');
     potx_status('set', POTX_STATUS_SILENT);
-    $code_files = _potx_explore_dir($path);
+    $code_files = _potx_explore_dir($review->path);
     foreach ($code_files as $code_file) {
       _potx_process_file($code_file, 0, '_atr_potx_save_string', '_atr_potx_save_version', $api_version);
     }
   }
   // Extract strings from *.po files.
   if ($po) {
-    $po_files = atr_po_files($path);
+    $po_files = atr_po_files($review->path);
     foreach ($po_files as $po_file) {
       atr_po_file_process($po_file);
     }
   }
 
-  $path_length = strlen($path);
-  if (count($atr_strings)) {
-    $atr_string_arguments = array();
-    $atr_string_placeholders = array();
-    $atr_string_location_arguments = array();
-    $atr_string_location_placeholders = array();
+  $count = count($atr_strings);
+  $values = implode(',', array_fill(0, $count, "($review->rid)"));
+  db_query("INSERT INTO {atr_string_id} (rid) VALUES " . $values);
+  $result = db_query("SELECT sid FROM {atr_string_id} WHERE rid = %d ORDER BY sid ASC", $review->rid);
+  $ids = array();
+  while ($id = db_result($result)) {
+    $ids[] = $id;
+  }
+  db_query("DELETE FROM {atr_string_id} WHERE rid = %d", $review->rid);
+  
+  $path_length = strlen($review->path);
+  if ($count > 0) {
+    $atr_string_values = $atr_string_placeholders = $atr_string_location_values = $atr_string_location_placeholders = array();
     $i = 0;
     foreach ($atr_strings as $string => $locations) {
-      $atr_string_arguments[] = $string;
-      $atr_string_arguments[] = $string;
-      $atr_string_placeholders[] = '(' . $i . ", '%s', LOWER('%s'))";
+      $id = $ids[$i];
+      $atr_string_values[] = $string;
+      $atr_string_values[] = $string;
+      $atr_string_placeholders[] = "($id, $review->rid, '%s', LOWER('%s'))";
       foreach ($locations as $location) {
-        $atr_string_location_arguments[] = substr($location['file'], $path_length);
-        $atr_string_location_arguments[] = $location['line'];
-        $atr_string_location_placeholders[] = '(' . $i . ", '%s', '%s')";
+        $atr_string_location_values[] = substr($location['file'], $path_length);
+        $atr_string_location_values[] = $location['line'];
+        $atr_string_location_placeholders[] = "($id, '%s', '%s')";
       }
       $i++;
     }
-    db_query("INSERT INTO {atr_string} VALUES " . implode(',', $atr_string_placeholders), $atr_string_arguments);
-    db_query("INSERT INTO {atr_string_location} VALUES " . implode(',', $atr_string_location_placeholders), $atr_string_location_arguments);
+    db_query("INSERT INTO {atr_string} () VALUES " . implode(',', $atr_string_placeholders), $atr_string_values);
+    db_query("INSERT INTO {atr_string_location} VALUES " . implode(',', $atr_string_location_placeholders), $atr_string_location_values);
   }
 
   return count($atr_strings);
Index: includes/atr.results.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/atr/includes/Attic/atr.results.inc,v
retrieving revision 1.1.2.12
diff -u -p -r1.1.2.12 atr.results.inc
--- includes/atr.results.inc	14 Jun 2009 23:32:26 -0000	1.1.2.12
+++ includes/atr.results.inc	30 Jul 2009 14:38:59 -0000
@@ -7,40 +7,112 @@
  */
 
 /**
+ * Display a paged list of all reviews.
+ */
+function atr_review_list(){
+  $result = pager_query("SELECT * FROM {atr_review} ORDER BY timestamp DESC", 25);
+  $rows = array();
+  while ($review = db_fetch_object($result)) {
+    $rows[] = array(l($review->path, 'atr/review/' . $review->rid), format_date($review->timestamp));
+  }
+
+  if (count($rows)) {
+    $header = array(t('Review'), t('Date'));
+    $pager = theme('pager');
+    return $pager . theme('table', $header, $rows) . $pager;  
+  }
+  else {
+    return t('No reviews have yet been run.');
+  }
+}
+
+/**
+ * Display review results.
+ *
+ * @param $review
+ *   The review to display results for.
+ *
+ * @return
+ *   The result of the method's #review_callback.
+ */
+function atr_review_result($review) {
+  drupal_set_title(t('Review results for %review', array('%review' => $review->path)));
+
+  $rows = array();
+  $rows[] = array(
+    t('Path fo files'),
+    $review->path,
+  );
+  $rows[] = array(
+    t('Date of review'),
+    format_date($review->timestamp),
+  );
+  $method_info = atr_method_info();
+  $methods = array();
+  foreach ($review->methods as $method) {
+    $methods[] = l($method_info[$method]['#title'], 'atr/review/' . $review->rid . '/' . $method);
+  }
+  $rows[] = array(
+    t('Review methods'),
+    theme('item_list', $methods),
+  );
+
+  return theme('table', array(), $rows);
+}
+
+/**
+ * Display review results for a specific method.
+ *
+ * @param $review
+ *   The review to display results for.
+ * @param $method
+ *   The method to display results for.
+ */
+function atr_review_method_result($review, $method) {
+  $method_info = atr_method_info($method);
+  drupal_set_title(t('%method results for %review', array('%method' => $method_info['#title'], '%review' => $review->path)));
+  drupal_set_breadcrumb(array(
+    l(t('Home'), '<front>'),
+    l(t('Automated Text Review'), 'atr'),
+    l(t('Reviews'), 'atr/review'),
+    l($review->path, 'atr/review/' . $review->rid),
+  ));
+
+  return atr_callback_execute($method_info['#result_callback'], $review);
+}
+
+/**
  * Display an overview of blacklisted strings.
  */
-function atr_results_blacklist() {
+function atr_results_blacklist($review) {
   drupal_add_js(drupal_get_path('module', 'atr') . '/js/atr.js');
   drupal_add_css(drupal_get_path('module', 'atr') . '/css/atr.css');
+
   $header = array(
     array(
       'data' => t('String'),
-      'field' => 'string',
+      'field' => 's.string',
     ),
     array(
       'data' => t('Keyword'),
-      'field' => 'keyword',
+      'field' => 'sb.keyword',
     ),
     t('Comment'),
   );
-  $sids = array();
-  $strings_blacklist = array();
-  $result = pager_query("SELECT sb.sid, sb.keyword, b.comment FROM {atr_string_blacklist} sb LEFT JOIN {atr_string} s ON sb.sid = s.sid LEFT JOIN {atr_blacklist} b ON sb.keyword = b.keyword" . tablesort_sql($header), 25);
+
+  $rows = array();
+  $result = pager_query("SELECT s.sid, s.string, sb.keyword, b.comment FROM {atr_string_blacklist} sb LEFT JOIN {atr_string} s ON sb.sid = s.sid LEFT JOIN {atr_blacklist} b ON sb.keyword = b.keyword WHERE rid = %d" . tablesort_sql($header), 25, 0, NULL, $review->rid);
   while ($string_blacklist = db_fetch_object($result)) {
-    $strings_blacklist[] = (object) array(
-      'keyword' => $string_blacklist->keyword,
-      'comment' => $string_blacklist->comment,
+    $sid = $string_blacklist->sid;
+    $string = (object) array(
+      'sid' => $sid,
+      'rid' => $review->rid,
+      'string' => $string_blacklist->string,
     );
-    $sids[] = $string_blacklist->sid;
-  }
-  $strings = atr_string_load_multiple($sids);
-  $rows = array();
-  foreach ($sids as $i => $sid) {
-    $string = $strings[$sid];
     $rows[] = array(
-      atr_string_view($string, array($strings_blacklist[$i]->keyword)),
-      $strings_blacklist[$i]->keyword,
-      filter_xss($strings_blacklist[$i]->comment),
+      atr_string_view($string, array($string_blacklist->keyword)),
+      $string_blacklist->keyword,
+      filter_xss($string_blacklist->comment),
     );
   }
 
@@ -52,9 +124,10 @@ function atr_results_blacklist() {
 /**
  * Display an overview of similar strings.
  */
-function atr_results_similar() {
+function atr_results_similar($review) {
   drupal_add_js(drupal_get_path('module', 'atr') . '/js/atr.js');
   drupal_add_css(drupal_get_path('module', 'atr') . '/css/atr.css');
+
   $header = array(
     array(
       'data' => t('String'),
@@ -69,23 +142,26 @@ function atr_results_similar() {
       'field' => 'similarity',
     ),
   );
-  $strings_similar = array();
-  $sids = array();
-  $result = pager_query("SELECT ss.*
+
+  $rows = array();
+  $result = pager_query("SELECT ss.*, s_a.string AS string_a, s_b.string AS string_b
   FROM {atr_string_similar} ss
   JOIN {atr_string} s_a ON ss.sid_a = s_a.sid
   JOIN {atr_string} s_b ON ss.sid_b = s_b.sid" . tablesort_sql($header), 25);
   while ($string_similar = db_fetch_object($result)) {
-    $strings_similar[] = $string_similar;
-    $sids[] = $string_similar->sid_a;
-    $sids[] = $string_similar->sid_b;
-  }
-  $strings = atr_string_load_multiple($sids);
-  $rows = array();
-  foreach ($strings_similar as $string_similar) {
+    $string_a = (object) array(
+      'sid' => $string_similar->sid_a,
+      'rid' => $review->rid,
+      'string' => $string_similar->string_a,
+    );
+    $string_b = (object) array(
+      'sid' => $string_similar->sid_b,
+      'rid' => $review->rid,
+      'string' => $string_similar->string_b,
+    );
     $rows[] = array(
-      atr_string_view($strings[$string_similar->sid_a]),
-      atr_string_view($strings[$string_similar->sid_b]),
+      atr_string_view($string_a),
+      atr_string_view($string_b),
       $string_similar->similarity . '%',
     );
   }
@@ -168,4 +244,34 @@ function _atr_string_highlight($matches)
  */
 function theme_atr_string_highlight($string) {
   return '<span class="atr-highlight">' . $string . '</span>';
+}
+
+/**
+ * Form builder; delete a review.
+ */
+function atr_form_review_delete(&$form_state, $review = NULL) {
+  $form['#redirect'] = 'atr/review';
+  $form['review'] = array(
+    '#type' => 'value',
+    '#value' => $review,
+  );
+
+  return confirm_form($form, t('Are you sure you want to delete %review?', array('%review' => $review->path)), 'atr/review/' . $review->rid, NULL, t('Delete'));
+}
+
+/**
+ * Form submit handler for atr_form_review_delete().
+ */
+function atr_form_review_delete_submit($form, &$form_state) {
+  $review = $form_state['values']['review'];
+
+  module_invoke_all('atr_review_delete', $review);
+  db_query("DELETE FROM {atr_review} WHERE rid = %d", $review->rid);
+  db_query("DELETE FROM {atr_review_method} WHERE rid = %d", $review->rid);
+  db_query("DELETE FROM {atr_string_location} WHERE sid IN (SELECT sid FROM {atr_string} s WHERE rid = %d)", $review->rid);
+  db_query("DELETE FROM {atr_string_blacklist} WHERE sid IN (SELECT sid FROM {atr_string} s WHERE rid = %d)", $review->rid);
+  db_query("DELETE FROM {atr_string_similar} WHERE sid_a IN (SELECT sid FROM {atr_string} s WHERE rid = %d)", $review->rid);
+  db_query("DELETE FROM {atr_string} WHERE rid = %d", $review->rid);
+
+  drupal_set_message(t('%review has been deleted.', array('%review' => $review->path)));
 }
\ No newline at end of file
Index: includes/atr.review.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/atr/includes/Attic/atr.review.inc,v
retrieving revision 1.1.2.24
diff -u -p -r1.1.2.24 atr.review.inc
--- includes/atr.review.inc	14 Jun 2009 23:19:08 -0000	1.1.2.24
+++ includes/atr.review.inc	30 Jul 2009 14:38:59 -0000
@@ -14,7 +14,9 @@
  * @param $profile
  *   The settings profile used for this review.
  */
-function atr_review($path, $profile, $po, $code, $api_version = NULL) {
+function atr_review($review, $profile, $po, $code, $api_version = NULL) {
+  // Set up the batch process to execute the review. If this function is called
+  // from outside a form submit handler, the batch should be executed manually.
   $operations = array(
     array(
       'atr_callback_execute',
@@ -24,7 +26,7 @@ function atr_review($path, $profile, $po
           '#module' => 'atr',
           '#file' => 'includes/atr.extract.inc',
         ),
-        $path,
+        $review,
         $po,
         $code,
         $api_version
@@ -35,7 +37,8 @@ function atr_review($path, $profile, $po
     $operations[] = array(
       'atr_callback_execute',
       array(
-        $method['#callbacks']['process'],
+        $method['process'],
+        $review,
         $profile,
       ),
     );
@@ -55,9 +58,15 @@ function atr_review($path, $profile, $po
  * @param $context
  *   A batch operation context.
  */
-function atr_review_blacklist($profile, &$context) {
+function atr_review_blacklist($review, $profile, &$context) {
   $context['message'] = t('Checking for blacklisted words or phrases.');
-  $strings = atr_string_load_range(0);
+
+  $strings = array();
+  $result = db_query("SELECT sid, string FROM {atr_string} WHERE rid = %d", $review->rid);
+  while ($string = db_fetch_object($result)) {
+    $strings[] = $string;
+  }
+
   $values = array();
   $result = db_query("SELECT keyword FROM {atr_blacklist} WHERE pid = %d", $profile->pid);
   while ($keyword = db_result($result)) {
@@ -83,64 +92,70 @@ function atr_review_blacklist($profile, 
  * @param $context
  *   A batch operation context.
  */
-function atr_review_similar($profile, &$context) {
+function atr_review_similar($review, $profile, &$context) {
   // Get and set values necessary for this operation.
   if (!isset($context['sandbox']['from'])) {
     $context['sandbox']['from'] = 0;
-    $context['sandbox']['total'] = db_result(db_query("SELECT COUNT(*) FROM {atr_string}"));
+    $context['sandbox']['count'] = db_result(db_query("SELECT COUNT(*) FROM {atr_string} WHERE rid = %d", $review->rid));
   }
   $from = $context['sandbox']['from'];
-  $total = $context['sandbox']['total'];
+  $count = $context['sandbox']['count'];
 
-  $context['message'] = t('Checked @done of @total strings for similarity.', array('@done' => $from, '@total' => $total));
+  $context['message'] = t('Checked @done of @total strings for similarity.', array('@done' => $from, '@total' => $count));
 
-  // Calculate how many strings we're going to compare in this step.
-  // Try one string at least.
-  $max = $from + 1;
-  $step = 0;
-  // Do a maximum of 50,000 comparisons per step.
-  $delta_step = 50000;
-  while ($step < $delta_step && $max < $total) {
-    // Subtract 1 because strings aren't compared to themselves.
-    $delta = $total - $max - 1;
-    if ($step + $delta < $delta_step) {
-      $max++;
-      $step += $delta;
+  $strings = array();
+  $result = db_query_range("SELECT sid, string, string_lower FROM {atr_string} WHERE rid = %d", $review->rid, $from, $count);
+  while ($string = db_fetch_object($result)) {
+    $strings[] = $string;
+  }
+  // The string count of this run only.
+  $count_run = count($strings);
+
+  // Calculate how many strings we're going to compare during this run.
+  // Try the first string at least.
+  $i_max = 0;
+  // The amount of comparisons we're going to do during this run.
+  $comparisons = $count_run - 1;
+  // Do a maximum of 50,000 comparisons per run.
+  $comparisons_per_run = 50000;
+  // The last string has been compared to all previous strings already.
+  while ($i_max < $count_run - 2) {
+    // The amount of comparisons required for another string during this run.
+    $comparisons_next_string = $count_run - $i_max - 2;
+    if ($comparisons + $comparisons_next_string < $comparisons_per_run) {
+      $comparisons += $comparisons_next_string;
+      $i_max++;
     }
     else {
       break;
     }
   }
 
-  // Find similar strings.
-  $strings = atr_string_load_range($from);
-  $matches = 0;
   $values = array();
   $threshold = (int) variable_get('atr_profile_' . $profile->pid . '_similarity_threshold', 90);
-  for ($sid_a = $from; $sid_a < $max; $sid_a++) {
-    for ($sid_b = $sid_a + 1; $sid_b < $total; $sid_b++) {
-      $string_a = atr_callback_execute($profile->methods['similarity']['#callbacks']['prepare'], $strings[$sid_a], $profile->pid);
-      $string_b = atr_callback_execute($profile->methods['similarity']['#callbacks']['prepare'], $strings[$sid_b], $profile->pid);
-      $similarity = atr_callback_execute($profile->methods['similarity']['#callbacks']['review'], $strings[$sid_a]->string_lower, $strings[$sid_b]->string_lower);
+  for ($i = 0; $i <= $i_max; $i++) {
+    $string_a = atr_callback_execute($profile->methods['similarity']['prepare'], $strings[$i], $profile->pid);
+    for ($j = $i + 1; $j < $count_run; $j++) {
+      $string_b = atr_callback_execute($profile->methods['similarity']['prepare'], $strings[$j], $profile->pid);
+      $similarity = atr_callback_execute($profile->methods['similarity']['review'], $string_a, $string_b);
       if ($similarity > $threshold) {
-        $values[] = $sid_a;
-        $values[] = $sid_b;
+        $values[] = $strings[$i]->sid;
+        $values[] = $strings[$j]->sid;
         $values[] = $similarity;
-        $matches++;
       }
     }
   }
   // Save matches to the database.
-  if ($matches) {
+  if ($matches = count($values) / 3) {
     $placeholders = implode(',', array_fill(0, $matches, '(%d, %d, %d)'));
     db_query("INSERT INTO {atr_string_similar} VALUES " . $placeholders, $values);
   }
 
   // Inform Batch API we are not yet finished and provide an estimation of the
   // completion level we reached.
-  if ($max + 1 < $total) {
-    $context['sandbox']['from'] = $max;
-    $context['finished'] = $max / $total;
+  if ($i_max < $count_run - 2) {
+    $context['sandbox']['from'] = $from + $i_max + 1;
+    $context['finished'] = $from / $count;
   }
 }
 
