<?php
// $Id: l10n_localpacks.module,v 1.1.2.9.2.6 2009/09/18 18:03:18 goba Exp $

/**
 * @file
 *   Localization community for local packages.
 *
 *   Extracts translateable strings from local package files.
 */

// = Core hooks ================================================================

/**
 * Implementation of hook_help().
 */
function l10n_localpacks_help($path, $arg) {
  switch ($path) {
    case 'admin/l10n_server/l10n_localpacks':
      return '<p>'. t('The local package connector for localization community looks at a local directory and handles packages you upload there, making them available for localization on the web interface. Set up the properties of that directory here. The <a href="@scan_link">Scan tab</a> allows you to initiate a manual scan on files in the directory, but it is advised to set up cron to check for new packages periodically.', array('@scan_link' => url('admin/l10n_server/l10n_localpacks/scan'))) .'</p>';
  }
}

/**
 * Implementation of hook_menu().
 */
function l10n_localpacks_menu() {
  $items = array();
  $items['admin/l10n_server/l10n_localpacks'] = array(
    'title' => 'Local package connector',
    'description' => 'Configure where does the server look for files locally.',
    'page callback' => 'drupal_get_form',
    'page arguments' => array('l10n_localpacks_settings_form'),
    'access arguments' => array('administer localization community for local packages'),
  );
  $items['admin/l10n_server/l10n_localpacks/configure'] = array(
    'title' => 'Configure',
    'type' => MENU_DEFAULT_LOCAL_TASK,
  );
  $items['admin/l10n_server/l10n_localpacks/scan'] = array(
    'title' => 'Scan',
    'page callback' => 'l10n_localpacks_scan',
    'access arguments' => array('administer localization community for local packages'),
    'type' => MENU_LOCAL_TASK
  );
  return $items;
}

/**
 * Implementation of hook_perm().
 */
function l10n_localpacks_perm() {
  return array('administer localization community for local packages');
}

/**
 * Implementation of hook_cron().
 */
function l10n_localpacks_cron() {
  if (variable_get('l10n_localpacks_cron', FALSE)) {
    l10n_localpacks_scan(TRUE);
  }
}

// = Settings ==================================================================

/**
 * Settings form callback.
 *
 * @see l10n_localpacks_settings_form_validate().
 */
function l10n_localpacks_settings_form() {
  $form = array(
    '#validate' => array('l10n_localpacks_settings_form_validate'),
  );
  $form['l10n_localpacks_directory'] = array(
    '#title' => t('Local packages directory'),
    '#description' => t('The directory on the local file system to be scanned for packages. Either relative to the Drupal site root or an absolute path on your file system. Drupal should have read access to the files and directories found there. Projects are identified based on subdirectory names or the first part of filenames if put directly in the root directory. Releases are identified based on the second part of package filenames. Examples: Drupal/drupal-6.0.tar.gz is from project "Drupal" in version 6.0, while og-5.x-2.0-alpha1 is project "og" in version 5.x-2.0-alpha1.'),
    '#type' => 'textfield',
    '#required' => TRUE,
    '#default_value' => variable_get('l10n_localpacks_directory', ''),
  );
  $form['l10n_localpacks_limit'] = array(
    '#title' => t('Number of projects to scan at once'),
    '#description' => t('The number of projects to scan on a manual or cron run. Scanning is synchronous, so you need to wait for Drupal to extract and parse the tarball content.'),
    '#type' => 'select',
    '#options' => drupal_map_assoc(array(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16, 18, 20)),
    '#default_value' => variable_get('l10n_localpacks_limit', 1),
  );
  $form['l10n_localpacks_cron'] = array(
    '#title' => t('Scan new projects on every cron run'),
    '#type' => 'checkbox',
    '#default_value' => variable_get('l10n_localpacks_cron', 0),
    '#description' => t('It is advised to set up a regular cron run to parse new package files, instead of hitting the Scan tab manually.'),
  );
  $form['l10n_localpacks_home_link'] = array(
    '#title' => t('Assign drupal.org home links to packages'),
    '#type' => 'checkbox',
    '#default_value' => variable_get('l10n_localpacks_home_link', 0),
    '#description' => t('Assigns http://drupal.org/project/<em>project</em> type home links to projects. These home links are used to guide users to the home pages of the projects. The setting only affects newly parsed packages.'),
  );
  return system_settings_form($form);
}

/**
 * Validation callback for l10n_localpacks_settings_form().
 *
 * @see l10n_localpacks_settings_form().
 */
function l10n_localpacks_settings_form_validate($form, &$form_state) {
  if (!is_dir($form_state['values']['l10n_localpacks_directory'])) {
    form_set_error('l10n_localpacks_directory', t('The directory specified to be used for scanning is not found.'));
  }
}

// = Project synchronization ===================================================

/**
 * Scans files of a project and release picked.
 *
 * @param $automated
 *   TRUE if the execution was automated, so user feedback should
 *   not be provided. FALSE otherwise.
 */
function l10n_localpacks_scan($automated = FALSE) {

  // We look for projects in the working directory.
  $workdir = variable_get('l10n_localpacks_directory', '');

  if (!is_dir($workdir)) {
    drupal_set_message(t('The configured local packages directory (%workdir) cannot be found. <a href="@configure">Check your configuration</a>.', array('%workdir' => $workdir, '@configure' => url('admin/l10n_server/l10n_localpacks/configure'))));
  }
  else {
    // Packages are always .tar.gz files.
    $files = file_scan_directory($workdir, '.tar.gz$');
    if (count($files)) {
      foreach ($files as $path => $file) {

        if (!l10n_community_is_supported_version($path)) {
          // Skip files for unsupported versions.
          continue;
        }

        // Get rid of $workdir prefix on file names, eg.
        // files/Drupal/drupal-4.6.7.tar.gz or
        // files/Ubercart/ubercart-5.x-1.0-alpha8.tar.gz.
        $path = $package = trim(preg_replace('!(^'. preg_quote($workdir, '!') .')(.+)\.tar\.gz!', '\2', $path), '/');

        $project_title = '';
        if (strpos($path, '/')) {
          // We have a slash, so this package is in a subfolder.
          // Eg. Drupal/drupal-4.6.7 or Ubercart/ubercart-5.x-1.0-alpha8.
          // Grab the directory name as project title.
          list($project_title, $package) = explode('/', $path);
        }
        if (strpos($package, '-')) {
          // Only remaining are the project uri and release,
          // eg. drupal-4.6.7 or ubercart-5.x-1.0-alpha8.
          list($project_uri, $release_version) = explode('-', $package, 2);

          l10n_localpacks_save_data($project_uri, ($project_title ? $project_title : $project_uri), $release_version, $path .'.tar.gz', filemtime($file->filename));
        }
        else {
          // File name not formatted properly.
          $result['error'] = t('File name should have project codename and version number included separated with hyphen, such as drupal-5.2.tar.gz.');
        }
      }
    }
  }

  $user_feedback = FALSE;
  $results = db_query_range("SELECT * FROM {l10n_community_release} WHERE pid IN (SELECT pid FROM {l10n_community_project} WHERE connector_module = 'l10n_localpacks' AND status = 1) ORDER BY last_parsed ASC", 0, variable_get('l10n_localpacks_limit', 1));
  while ($release = db_fetch_object($results)) {

    // Only parse file if something changed since we last parsed it.
    $file_name = $workdir .'/'. $release->download_link;

    if (file_exists($file_name)) {
      if (filemtime($file_name) > $release->last_parsed) {
        include_once drupal_get_path('module', 'l10n_community') .'/extractor.inc';
        $result = l10n_community_parse_package($file_name, $release);

        // User feedback, if not automated. Log messages are already done.
        if (isset($result['error']) && !$automated) {
          $user_feedback = TRUE;
          drupal_set_message($result['error'], 'error');
        }
        elseif (isset($result['message']) && !$automated) {
          $user_feedback = TRUE;
          drupal_set_message($result['message']);
        }
      }
      else {
        if (!$automated) {
          $user_feedback = TRUE;
          drupal_set_message(t('@release was already parsed, no need to scan again.', array('@release' => $release->download_link)));
        }
      }
    }
    // Hackish update of last parsed time so other tarballs will get into the queue too.
    // @todo: work on something better for this.
    db_query("UPDATE {l10n_community_release} SET last_parsed = %d WHERE rid = %d", time(), $release->rid);
  }
  if (!$automated && !$user_feedback) {
    drupal_set_message(t('No (new) local packages found to scan in %workdir.', array('%workdir' => $workdir)));
  }

  // Ensure that a Drupal page will be displayed with the messages.
  return '';
}

/**
 * Data save callback for local project and release mappings.
 *
 * @param $project_uri
 *   Short project URI, the first part of file names, eg. 'drupal', 'og'.
 * @param $project_title
 *   Project title, effectively the directory name under which this release
 *   was found. Characters allowed in directory names are allowed here.
 * @param $release_version
 *   Version identified string, such as '5.2' or '5.x-1.x-dev'.
 * @param $filepath
 *   Path to file (without the working directory name).
 * @param filedate
 *   Modification date of the package file.
 */
function l10n_localpacks_save_data($project_uri, $project_title, $release_version, $filepath, $filedate) {
  // Save project information first.
  if ($existing_project = db_fetch_object(db_query("SELECT * FROM {l10n_community_project} WHERE uri = '%s'", $project_uri))) {
    if ($existing_project->connector_module == 'l10n_localpacks') {
      if ($existing_project->status == 0) {
        // Skip this project if it is disabled.
        return;
      }
      // No need to update home_link here, as uri is not changed, and we base home_link on the uri.
      db_query("UPDATE {l10n_community_project} SET title = '%s' WHERE uri = '%s' AND connector_module = 'l10n_localpacks'", $project_title, $project_uri);
    }
    else {
      // Log error on existing project with another connector and skip the rest of this function.
      $t_args = array('%uri' => $uri, '%other_connector' => $existing_project->connector_module, '%this_connector' => 'l10n_localpacks');
      watchdog('l10n_localpacks', 'An existing project under the URI %uri is already handled by the %other_connector module. Not possible to add it with %this_connector.', $t_args);
      drupal_set_message(t('An existing project under the URI %uri is already handled by the %other_connector module. Not possible to add it with %this_connector.', $t_args), 'error');
      return;
    }
  }
  else {
    db_query(
      "INSERT INTO {l10n_community_project} (uri, title, last_parsed, home_link, connector_module, status) VALUES ('%s', '%s', %d, '%s', '%s', %d)",
      $project_uri, $project_title, 0, (variable_get('l10n_localpacks_home_link', 0) ? ('http://drupal.org/project/'. $project_uri) : ''), 'l10n_localpacks', 1
    );
  }

  // Grab updated project data and save release information.
  $project = db_fetch_object(db_query("SELECT * FROM {l10n_community_project} WHERE uri = '%s' AND connector_module = 'l10n_localpacks'", $project_uri));
  if ($existing_release = db_fetch_object(db_query("SELECT * FROM {l10n_community_release} WHERE pid = %d AND title = '%s'", $project->pid, $release_version))) {
    if ($filedate != $existing_release->file_date) {
      // New file with possibly new date for the same release (dev snapshot
      // probably), so update data, set parsed date to past.
      db_query("UPDATE {l10n_community_release} SET download_link = '%s', file_date = %d, last_parsed = 0 WHERE rid = %d", $filepath, $filedate, $existing_release->rid);
    }
  }
  else {
    // No such release recorded yet.
    db_query("INSERT INTO {l10n_community_release} (pid, title, download_link, file_date, last_parsed) VALUES (%d, '%s', '%s', %d, 0)", $project->pid, $release_version, $filepath, $filedate);
  }
}
