atr.module

<?php
// $Id: atr.module,v 1.1.2.36 2009/09/27 11:13:38 xano Exp $

/**
 * @file
 *   Hook implementations and general funcions.
 */

/**
 * Implementation of hook_menu().
 */
function atr_menu() {
  return array(
    'admin/settings/atr' => array(
      'title' => 'Automated Text Review',
      'page callback' => 'drupal_get_form',
      'page arguments' => array('atr_form_settings'),
      'access arguments' => array('administer automated text review'),
      'file' => 'atr.admin.inc',
      'file path' => drupal_get_path('module', 'atr') . '/includes',
    ),
    'atr' => array(
      'title' => 'Automated Text Review',
      'page callback' => 'system_admin_menu_block_page',
      'access arguments' => array('access automated text review'),
      'file' => 'system.admin.inc',
      'file path' => drupal_get_path('module', 'system'),
    ),
    'atr/new-review' => array(
      'title' => 'New review',
      'page callback' => 'drupal_get_form',
      'page arguments' => array('atr_form_review'),
      'access arguments' => array('execute text reviews'),
      'file' => 'atr.admin.inc',
      'file path' => drupal_get_path('module', 'atr') . '/includes',
    ),
    'atr/profile' => array(
      'title' => 'Settings profiles',
      'page callback' => 'atr_profile_list',
      'access arguments' => array('administer automated text review'),
      'file' => 'atr.admin.inc',
      'file path' => drupal_get_path('module', 'atr') . '/includes',
    ),
    'atr/profile/list' => array(
      'title' => 'List',
      'page callback' => 'atr_profile_list',
      'access arguments' => array('administer automated text review'),
      'file' => 'atr.admin.inc',
      'file path' => drupal_get_path('module', 'atr') . '/includes',
      'type' => MENU_DEFAULT_LOCAL_TASK,
    ),
    'atr/profile/add' => array(
      'title' => 'Add settings profile',
      'page callback' => 'drupal_get_form',
      'page arguments' => array('atr_form_profile_add'),
      'access arguments' => array('administer automated text review'),
      'file' => 'atr.admin.inc',
      'file path' => drupal_get_path('module', 'atr') . '/includes',
      'type' => MENU_LOCAL_TASK,
      'weight' => 1,
    ),
    'atr/profile/%atr_profile/edit' => array(
      'title' => 'Edit settings profile',
      'page callback' => 'drupal_get_form',
      'page arguments' => array('atr_form_profile', 2),
      'access arguments' => array('administer automated text review'),
      'file' => 'atr.admin.inc',
      'file path' => drupal_get_path('module', 'atr') . '/includes',
      'type' => MENU_CALLBACK,
      'weight' => 1,
    ),
    'atr/profile/%atr_profile/delete' => array(
      'title' => 'Delete settings profile',
      'page callback' => 'drupal_get_form',
      'page arguments' => array('atr_form_profile_delete', 2),
      'access arguments' => array('administer automated text review'),
      'file' => 'atr.admin.inc',
      'file path' => drupal_get_path('module', 'atr') . '/includes',
      'type' => MENU_CALLBACK,
      'weight' => 1,
    ),
    'atr/review' => array(
      'title' => 'Reviews',
      'page callback' => 'atr_review_list',
      'access arguments' => array('access text review results'),
      'file' => 'atr.result.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.result.inc',
      'file path' => drupal_get_path('module', 'atr') . '/includes',
      'type' => MENU_CALLBACK,
    ),
    '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.result.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.result.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.result.inc',
      'file path' => drupal_get_path('module', 'atr') . '/includes',
      'type' => MENU_CALLBACK,
    ),
  );
}

/**
 * Implementation of hook_perm().
 */
function atr_perm() {
  return array('administer automated text review', 'access automated text review', 'execute text reviews', 'access text review results');
}

/**
 * Implementation of hook_theme().
 */
function atr_theme() {
  return array(
    'atr_string' => array(
      'arguments' => array(
        'string' => new stdclass(),
      ),
    ),
    'atr_string_highlight' => array(
      'arguments' => array(
        'string' => '',
      ),
    ),
    'atr_profile' => array(
      'arguments' => array(
        'profile' => new stdclass(),
      ),
    ),
  );
}

/**
 * Implementation of hook_elements().
 */
function atr_elements() {
  return array(
    'atr_file' => array(
      '#input' => TRUE,
      '#process' => array('atr_file_process'),
    ),
  );
}

/**
 * Load a review method's information.
 *
 * @param $method
 *   The method's machine name. Leave blank to return information for all
 *   methods.
 *
 * @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');
    drupal_alter('atr_method_info', $method_info);
  }

  return $method ? $method_info[$method] : $method_info;
}

/**
 * Check if a review method is available for a certain language.
 *
 * @param $method
 *   The method to check.
 * @param $language
 *   The language as per ISO 639.
 *
 * @return
 *   Method information as defined in hook_atr_method_info(), but with one
 *   review callback or FALSE if the method is not available for the given
 *   language.
 */
function atr_method_availability($method, $language) {
  $method_info = atr_method_info($method);
  $languages = array($language, 'zxx');
  foreach ($languages as $possible_language) {
    if (isset($method_info['#review_callback'][$possible_language])) {
      $method_info['#review_callback'] = $method_info['#review_callback'][$possible_language];
      return $method_info;
    }
  }
  return FALSE;
}

/**
 * Load a settings profile.
 *
 * @param $pid
 *   The profile's ID.
 *
 * @return
 *   A profile object or FALSE if no profile matched the provided $pid.
 */
function atr_profile_load($pid) {
  if ($profile = db_fetch_object(db_query("SELECT * FROM {atr_profile} WHERE pid = %d", $pid))) {
    $profile->methods = array();
    $result = db_query("SELECT method FROM {atr_profile_method} WHERE pid = %d", $pid);
    while ($method = db_result($result)) {
      $profile->methods[] = $method;
    }
    return $profile;
  }
  return FALSE;
}

/**
 * Save a settings profile.
 *
 * @param $profile
 *   The profile object to save.
 *
 * @return
 *   The value returned by drupal_write_record() for saving the profile.
 */
function atr_profile_save(&$profile) {
  if (isset($profile->pid)) {
    $update = 'pid';
    db_query("DELETE FROM {atr_profile_method} WHERE pid = %d", $profile->pid);
  }
  else {
    $update = array();
  }
  $return = drupal_write_record('atr_profile', $profile, $update);
  if (count($profile->methods)) {
    $values = array();
    $placeholders = array();
    foreach ($profile->methods as $method) {
      $values[] = $profile->pid;
      $values[] = $method;
      $placeholders[] = "(%d, '%s')";
    }
    db_query("INSERT INTO {atr_profile_method} VALUES " . implode(',', $placeholders), $values);
  }

  return $return;
}

/**
 * Execute a review method callback.
 *
 * @param $callback
 *   An associative array containing callback information as defined in
 *   hook_atr_method_info().
 * @param ...
 *   Additional arguments to pass on to the callback.
 */
function atr_callback_execute($callback) {
  require_once('./'. drupal_get_path('module', $callback['#module']) . '/' . $callback['#file']);
  $args = func_get_args();
  array_shift($args);

  return call_user_func_array($callback['#callback'], $args);
}

/**
 * Check if the operating system has an application installed.
 *
 * @return
 *   Boolean.
 */
function atr_app_exists($app) {
  $code;
  $output;
  exec("which $app", $output, $code);

  return $code == 0;
}

/**
 * Set or get a statically cached item.
 *
 * This function provides a general static cache similar to drupal_static() in
 * Drupal 7, which allows multiple functions to access the same static cache.
 *
 * @param $name
 *   The name of the cache item to get or set.
 * @param $value
 *   The value of a cache item when setting it. NULL when reading the item.
 * @param $reset
 *   TRUE to reset an item. Internal use only, use atr_cache_static_del() instead.
 *
 * @return
 *   The value of the cached item.
 */
function &atr_cache_static($name, $value = NULL, $del = FALSE) {
  static $cache = array();

  if ($del) {
    unset($cache_name);
    return;
  }
  elseif ($value) {
    $cache[$name] = $value;
  }
  elseif (!isset($cache[$name])) {
    $cache[$name] = NULL;
  }
  return $cache[$name];
}

/**
 * Delete an item from the static cache.
 *
 * @param $name
 *   The name of the cache item to delete.
 */
function atr_cache_static_del($name) {
  atr_cache_static($name, NULL, TRUE);
}


/**
 * Create a new review.
 *
 * @param $title
 *   The human-readable title of this review.
 * @param $language
 *   The text language.
 * @param $methods
 *   An array containing the review methods of the profile applied to this
 *   review.
 * @param $string_count
 *   The amount of strings reviewed.
 * @param $word_count
 *   The amount of words reviewed.
 * @param $timestamp
 *   The time of execution. Defaults to the current time.
 * @param $rid
 *   The RID of this review if it already exists.
 */
class ATRReview {
  function __construct($title, $language, $methods, $string_count = 0, $word_count = 0, $timestamp = NULL, $rid = NULL) {
    $this->title = $title;
    $this->language = $language;
    $this->methods = array();
    foreach ($methods as $method) {
      $method_info = atr_method_info($method);
      $languages = array($language, 'zxx');
      foreach ($languages as $possible_language) {
        if (isset($method_info['#review_callback'][$possible_language])) {
          $method_info['#review_callback'] = $method_info['#review_callback'][$possible_language];
          $this->methods[$method] = $method_info;
          break;
        }
      }
    }
    $this->string_count = $string_count;
    $this->word_count = $word_count;
    $this->timestamp = $timestamp ? $timestamp : time();
    if ($rid) {
      $this->rid = $rid;
    }
  }
}

/**
 * Save an existing review.
 *
 * @param $review
 *   The ATRReview object to save.
 */
function atr_review_save($review) {
  $update = isset($review->rid) ? 'rid' : array();
  $return = drupal_write_record('atr_review', $review, $update);
  db_query("DELETE FROM {atr_review_method} WHERE rid = %d", $review->rid);
  $values = implode(',', array_fill(0, count($review->methods), "($review->rid, '%s')"));
  db_query("INSERT INTO {atr_review_method} VALUES " . $values, array_keys($review->methods));

  module_invoke_all('atr_review_save', $review);
  atr_cache_static('review_' . $review->rid, $review);

  return $return;
}

/**
 * Load a review.
 *
 * @param $rid
 *   The RID of the review to load.
 *
 * @return
 *   An ATRReview object or FALSE if the RID doesn't match any reviews.
 */
function atr_review_load($rid) {
  $reviews = atr_review_load_multiple(array($rid));

  return isset($reviews[$rid]) ? $reviews[$rid] : FALSE;
}

/**
 * Load multiple reviews.
 *
 * @param $rids
 *   An array with the RIDs of the reviews to load.
 *
 * @return
 *   An array with ATRReview objects.
 */
function atr_review_load_multiple($rids) {
  // Get previously loaded reviews from the static cache.
  $reviews_cache = array();
  foreach ($rids as $i => $rid) {
    if($review = atr_cache_static("review_$rid")) {
      $reviews_cache[$rid] = $review;
      unset($rids[$i]);
    }
  }

  // Get the remaining reviews from the DB.
  $reviews_db = array();
  if ($rids) {
    $placeholders = db_placeholders($rids);
    $methods = array();
    $result = db_query("SELECT method FROM {atr_review_method} WHERE rid in ($placeholders)", $rids);
    while ($method = db_result($result)) {
      $methods[] = $method;
    }
    $result = db_query("SELECT * FROM {atr_review} WHERE rid in ($placeholders)", $rids);
    while ($review_data = db_fetch_object($result)) {
      $rid = $review_data->rid;
      $review = new ATRReview($review_data->title, $review_data->language, $methods, $review_data->string_count, $review_data->word_count, $review_data->timestamp, $rid);
      $reviews_db[$rid] = $review;
      atr_cache_static("review_$rid", $review);
    }
    foreach (module_implements('atr_review_load') as $module) {
      call_user_func($module . '_atr_review_load', $reviews_db);
    }
  }

  return $reviews_cache + $reviews_db;
}

/**
 * Delete a review.
 *
 * @param $rid
 *   The RID of the review to delete.
 */
function atr_review_delete($rid) {
  atr_review_delete_multiple(array($rid));
}

/**
 * Delete multiple reviews.
 *
 * @param $rids
 *   An array with the RIDs of the reviews to delete.
 */
function atr_review_delete_multiple($rids) {
  module_invoke_all('atr_review_delete', $rids);
  foreach ($rids as $rid) {
    atr_cache_static_del('review_' . $rid);
  }
  $placeholders = db_placeholders($rids);
  db_query("DELETE FROM {atr_review} WHERE rid IN ($placeholders)", $rids);
  db_query("DELETE FROM {atr_review_method} WHERE rid IN ($placeholders)", $rids);
  db_query("DELETE FROM {atr_string_location} WHERE sid IN (SELECT sid FROM {atr_string} s WHERE rid IN ($placeholders))", $rids);
  db_query("DELETE FROM {atr_string} WHERE rid IN ($placeholders)", $rids);
}

/**
 * Create a page title for a review.
 *
 * @param $review
 *   The ATRReview object of the review to create the title for.
 *
 * @return
 *   String.
 */
function atr_review_title($review) {
  return $review->title;
}

/**
 * Undo a string of all insignificant characters.
 *
 * @param $string
 *
 * @return
 *   String.
 */
function atr_string_strip($string) {
	// HTML tags cannot be considered words.
  $string = strip_tags($string);
  // Strip everything but words, whitespace and placeholder prefixes.
  $string = preg_replace('#[^@!%a-z\s]#i', '', $string);

  return $string;
}

/**
 * Create a temporary directory.
 *
 * @result
 *   The path to the temporary directory or FALSE upon failure.
 */
function atr_tmp_dir() {
  $dir = '/' . trim(sys_get_temp_dir(), '/') . '/atr/' . time() . mt_rand(100, 999);
  if (@mkdir($dir, 0766, TRUE)) {
    return $dir;
  }
  else {
    watchdog('atr', "Automated Text Review has insufficient permissions to write to your system's temporary directory (%directory)", array('%directory' => sys_get_temp_dir()), WATCHDOG_ERROR);
    return FALSE;
  }
}

/**
 * Form process handler for 'atr_file'.
 */
function atr_file_process($element, $form_state) {
  $tar = atr_app_exists('tar') ? ' ' . t('!tar files are supported.', array('!tar' => '<code>*.tar.gz</code>')) : NULL;
  $element[$element['#name'] . '_online'] = array(
    '#type' => 'textfield',
    '#title' => t('Online file'),
    '#description' => t('The directory or file that needs to be reviewed. Files may be located on another website.') . $tar,
    // We trigger validation for the entire 'atr_file' element through this
    // textfield.
    '#element_validate' => array('atr_file_validate'),
  );
  $element[$element['#name'] . '_upload'] = array(
    '#type' => 'file',
    '#title' => t('File upload'),
    '#description' => $tar,
  );
  // Unset the wrapper element's type. Otherwise the child elements won't be rendered.
  unset($element['#type']);

  return $element;
}

/**
 * Form validate handler for 'atr_file'.
 */
function atr_file_validate($element, &$form_state) {
  $paths = array();
  $parents = array_reverse($element['#array_parents']);
  $parent = $parents[1];
  // Process the online file.
  // TODO: Throw an error if the path cannot be read.
  if ($element['#value']) {
    // Make sure the path doesn't start with a slash, but only ends with one.
    $paths['online'] = trim($element['#value'], ' /');
    // Check if the source is a directory.
    if (strpos($paths['online'], '.', strrpos($paths['online'], '/')) === FALSE) {
      $paths['online'] .= '/';
    }
    else {
      module_load_include('inc', 'atr', 'includes/atr.extract');
      atr_file_get($paths['online'], basename($paths['online']));
    }
  }
  // Process the uploaded file.
  if ($path_upload = $_FILES['files']['tmp_name'][$parent . '_upload']) {
    $paths['upload'] = $path_upload;
    module_load_include('inc', 'atr', 'includes/atr.extract');
    atr_file_get($paths['upload'], $_FILES['files']['name'][$parent . '_upload']);
  }

  // Save the paths as the 'atr_file' element's value.
  $atr_file_element = array(
    '#parents' => array($parent),
  );
  form_set_value($atr_file_element, $paths, $form_state);
}