You are here

l10n_update.module in Localization update 6

Same filename and directory in other branches
  1. 7.2 l10n_update.module
  2. 7 l10n_update.module

Download translations from remote localization server.

@todo Fetch information from info files.

File

l10n_update.module
View source
<?php

/**
 * @file
 *   Download translations from remote localization server.
 *
 * @todo Fetch information from info files.
 */

/**
 * Update mode: Remote server.
 */
define('L10N_UPDATE_CHECK_REMOTE', 1);

/**
 * Update mode: Local server.
 */
define('L10N_UPDATE_CHECK_LOCAL', 2);

/**
 * Update mode: both.
 */
define('L10N_UPDATE_CHECK_ALL', L10N_UPDATE_CHECK_REMOTE | L10N_UPDATE_CHECK_LOCAL);

/**
 * Translation import mode keeping translations which are edited after enabling
 * Locale Update module an only override default (un-edited) translations.
 */
define('LOCALE_UPDATE_OVERRIDE_DEFAULT', 2);

/**
 * The maximum number of projects which are checked for available translations each cron run.
 */
define('L10N_UPDATE_CRON_PROJECTS', 10);

/**
 * The maximum number of projects which are updated each cron run.
 */
define('L10N_UPDATE_CRON_UPDATES', 2);

/**
 * Implementation of hook_help().
 */
function l10n_update_help($path, $arg) {
  switch ($path) {
    case 'admin/build/translate/update':
      $output = '<p>' . t('List of latest imported translations and available updates for each enabled project and language.') . '</p>';
      $output .= '<p>' . t('If there are available updates you can click on Update for them to be downloaded and imported now or you can edit the configuration for them to be updated automatically on the <a href="@update-settings">Update settings page</a>', array(
        '@update-settings' => url('admin/settings/language/update'),
      )) . '</p>';
      return $output;
      break;
    case 'admin/settings/language/update':
      $output = '<p>' . t('These are the settings for the translation update system. To update your translations now, check out the <a href="@update-admin">Translation update administration page</a>.', array(
        '@update-admin' => url('admin/build/translate/update'),
      )) . '</p>';
      return $output;
      break;
  }
}

/**
 * Implementation of hook_menu().
 */
function l10n_update_menu() {
  $items['admin/build/translate/update'] = array(
    'title' => 'Update',
    'description' => 'Available updates',
    'page callback' => 'l10n_update_admin_overview',
    'access arguments' => array(
      'translate interface',
    ),
    'file' => 'l10n_update.admin.inc',
    'weight' => 20,
    'type' => MENU_LOCAL_TASK,
  );
  $items['admin/settings/language/update'] = array(
    'title' => 'Translation updates',
    'description' => 'Automatic update configuration',
    'page callback' => 'drupal_get_form',
    'page arguments' => array(
      'l10n_update_admin_settings_form',
    ),
    'access arguments' => array(
      'translate interface',
    ),
    'file' => 'l10n_update.admin.inc',
    'weight' => 20,
    'type' => MENU_LOCAL_TASK,
  );
  return $items;
}

/**
 * Implementation of hook_menu_alter().
 */
function l10n_update_menu_alter(&$menu) {

  // Redirect l10n_client AJAX callback path for strings.
  $menu['l10n_client/save']['page callback'] = 'l10n_update_client_save_string';
}

/**
 * Implementation of hook_cron().
 *
 * Check one project/language at a time, download and import if update available
 */
function l10n_update_cron() {
  if ($frequency = variable_get('l10n_update_check_frequency', 0)) {
    module_load_include('check.inc', 'l10n_update');
    list($checked, $updated) = l10n_update_check_translations(L10N_UPDATE_CRON_PROJECTS, time() - $frequency * 24 * 3600, L10N_UPDATE_CRON_UPDATES);
    watchdog('l10n_update', 'Automatically checked @checked translations, updated @updated.', array(
      '@checked' => count($checked),
      '@updated' => count($updated),
    ));
  }
}

/**
 * Implementation of hook_form_alter().
 */
function l10n_update_form_alter(&$form, $form_state, $form_id) {
  switch ($form_id) {
    case 'locale_translate_edit_form':

      // Replace the submit callback by our own customized version
      $form['#submit'] = array(
        'l10n_update_locale_translate_edit_form_submit',
      );
      break;
    case 'system_modules':
      $form['#submit'][] = 'l10n_update_system_modules_submit';
      break;
    case 'system_modules_uninstall':
      $form['#submit'][] = 'l10n_update_system_modules_uninstall_submit';
      break;
    case 'locale_languages_predefined_form':
    case 'locale_languages_custom_form':
      $form['#submit'][] = 'l10n_update_languages_changed_submit';
      break;
    case 'locale_languages_delete_form':

      // A language is being deleted.
      $form['#submit'][] = 'l10n_update_languages_delete_submit';
      break;
  }
}

/**
 * Additional submit hander for System Modules form.
 *
 * Rebuild project information, update status, etc. When this callback runs,
 * locale module has already run and update cache is refreshed.
 *
 * @see l10n_update_form_alter()
 */
function l10n_update_system_modules_submit($form, $form_state) {

  // Check we are not on the confirm form
  if (!isset($form_state['storage'])) {
    module_load_include('project.inc', 'l10n_update');
    l10n_update_project_refresh();
  }
}

/**
 * Additional submit hander for System Modules Uninstall form.
 *
 * Remove data of uninstalled modules from {l10n_update_file} table.
 *
 * @see l10n_update_form_alter()
 */
function l10n_update_system_modules_uninstall_submit($form, $form_state) {
  if (isset($form['#confirmed'])) {
    $uninstall = array_keys(array_filter($form_state['values']['uninstall']));
    db_query("DELETE FROM {l10n_update_file} WHERE project IN (" . db_placeholders($uninstall, 'text') . ")", $uninstall);
  }
}

/**
 * Aditional submit handler for language forms
 *
 * We need to refresh status when a new language is enabled / disabled
 */
function l10n_update_languages_changed_submit($form, $form_state) {
  module_load_include('check.inc', 'l10n_update');
  $langcode = $form_state['values']['langcode'];
  l10n_update_language_refresh(array(
    $langcode,
  ));
}

/**
 * Additional submit handler for language deletion form.
 *
 * When a language is deleted, the file history of this language is cleared.
 */
function l10n_update_languages_delete_submit($form, $form_state) {
  $langcode = $form_state['values']['langcode'];
  module_load_include('inc', 'l10n_update');
  l10n_update_delete_file_history($langcode);
}

/**
 * Replacement submit handler for translation edit form.
 *
 * Process string editing form submissions marking translations as customized.
 * Saves all translations of one string submitted from a form.
 *
 * @see l10n_update_form_alter()
 * @todo Just mark as customized when string changed.
 */
function l10n_update_locale_translate_edit_form_submit($form, &$form_state) {
  module_load_include('inc', 'l10n_update');
  $lid = $form_state['values']['lid'];
  foreach ($form_state['values']['translations'] as $key => $value) {
    $translation = db_result(db_query("SELECT translation FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $key));
    if (!empty($value)) {

      // Only update or insert if we have a value to use.
      if (!empty($translation)) {
        db_query("UPDATE {locales_target} SET translation = '%s', l10n_status = %d WHERE lid = %d AND language = '%s'", $value, L10N_UPDATE_STRING_CUSTOM, $lid, $key);
      }
      else {
        db_query("INSERT INTO {locales_target} (lid, translation, language, l10n_status) VALUES (%d, '%s', '%s', %d)", $lid, $value, $key, L10N_UPDATE_STRING_CUSTOM);
      }
    }
    elseif (!empty($translation)) {

      // Empty translation entered: remove existing entry from database.
      db_query("DELETE FROM {locales_target} WHERE lid = %d AND language = '%s'", $lid, $key);
    }

    // Force JavaScript translation file recreation for this language.
    _locale_invalidate_js($key);
  }
  drupal_set_message(t('The string has been saved.'));

  // Clear locale cache.
  _locale_invalidate_js();
  cache_clear_all('locale:', 'cache', TRUE);
  $form_state['redirect'] = 'admin/build/translate/search';
  return;
}

/**
 * Menu callback. Saves a string translation coming as POST data.
 */
function l10n_update_client_save_string() {
  global $user, $language;
  if (l10n_client_access()) {
    if (isset($_POST['source']) && isset($_POST['target']) && !empty($_POST['textgroup']) && !empty($_POST['form_token']) && drupal_valid_token($_POST['form_token'], 'l10n_client_form')) {

      // Ensure we have this source string before we attempt to save it.
      $lid = db_result(db_query("SELECT lid FROM {locales_source} WHERE source = '%s' AND textgroup = '%s'", $_POST['source'], $_POST['textgroup']));
      if (!empty($lid)) {
        module_load_include('inc', 'l10n_update');
        $report = array(
          'skips' => 0,
          'additions' => 0,
          'updates' => 0,
          'deletes' => 0,
        );
        _l10n_update_locale_import_one_string_db($report, $language->language, $_POST['source'], $_POST['target'], $_POST['textgroup'], NULL, LOCALE_IMPORT_OVERWRITE, L10N_UPDATE_STRING_CUSTOM);
        cache_clear_all('locale:', 'cache', TRUE);
        _locale_invalidate_js($language->language);
        if (!empty($report['skips'])) {
          $message = theme('l10n_client_message', t('Not saved locally due to invalid HTML content.'));
        }
        elseif (!empty($report['additions']) || !empty($report['updates'])) {
          $message = theme('l10n_client_message', t('Translation saved locally.'), WATCHDOG_INFO);
        }
        elseif (!empty($report['deletes'])) {
          $message = theme('l10n_client_message', t('Translation successfuly removed locally.'), WATCHDOG_INFO);
        }
        else {
          $message = theme('l10n_client_message', t('Unknown error while saving translation locally.'));
        }

        // Submit to remote server if enabled.
        if (variable_get('l10n_client_use_server', FALSE) && user_access('submit translations to localization server') && $_POST['textgroup'] == 'default') {
          if (!empty($user->l10n_client_key)) {
            $remote_result = l10n_client_submit_translation($language->language, $_POST['source'], $_POST['target'], $user->l10n_client_key, l10n_client_user_token($user));
            $message .= theme('l10n_client_message', $remote_result[1], $remote_result[0] ? WATCHDOG_INFO : WATCHDOG_ERROR);
          }
          else {
            $server_url = variable_get('l10n_client_server', 'http://localize.drupal.org');
            $user_edit_url = url('user/' . $user->uid . '/edit', array(
              'absolute' => TRUE,
            ));
            $message .= theme('l10n_client_message', t('You could share your work with !l10n_server if you set your API key at !user_link.', array(
              '!l10n_server' => l($server_url, $server_url),
              '!user_link' => l($user_edit_url, 'user/' . $user->uid . '/edit'),
            )), WATCHDOG_WARNING);
          }
        }
      }
      else {
        $message = theme('l10n_client_message', t('Not saved due to source string missing.'));
      }
    }
    else {
      $message = theme('l10n_client_message', t('Not saved due to missing form values.'));
    }
  }
  else {
    $message = theme('l10n_client_message', t('Not saved due to insufficient permissions.'));
  }
  drupal_json(array(
    'message' => $message,
  ));
  exit;
}

/**
 * Get stored list of projects
 *
 * @param boolean $refresh
 *   TRUE = refresh the project data.
 * @param boolean $disabled
 *   TRUE = get enabled AND disabled projects.
 *   FALSE = get enabled projects only.
 *
 * @return array
 *   Array of project objects keyed by project name.
 */
function l10n_update_get_projects($refresh = FALSE, $disabled = FALSE) {
  static $projects, $enabled;
  if (!isset($projects) || $refresh) {
    if (variable_get('l10n_update_rebuild_projects', 0)) {
      module_load_include('project.inc', 'l10n_update');
      variable_del('l10n_update_rebuild_projects');
      l10n_update_build_projects();
    }
    $projects = $enabled = array();
    $result = db_query('SELECT * FROM {l10n_update_project}');
    while ($project = db_fetch_object($result)) {
      $projects[$project->name] = $project;
      if ($project->status) {
        $enabled[$project->name] = $project;
      }
    }
  }
  return $disabled ? $projects : $enabled;
}

/**
 * Get server information, that can come from different sources.
 *
 * - From server list provided by modules. They can provide full server information or just the url
 * - From server_url in a project, we'll fetch latest data from the server itself
 *
 * @param string $name
 *   Server name e.g. localize.drupal.org
 * @param string $url
 *   Server url
 * @param boolean $refresh
 *   TRUE = refresh the server data.
 *
 * @return array
 *   Array of server data.
 */
function l10n_update_server($name = NULL, $url = NULL, $refresh = FALSE) {
  static $info, $server_list;

  // Retrieve server list from modules
  if (!isset($server_list) || $refresh) {
    $server_list = module_invoke_all('l10n_servers');
  }

  // We need at least the server url to fetch all the information
  if (!$url && $name && isset($server_list[$name])) {
    $url = $server_list[$name]['server_url'];
  }

  // If we still don't have an url, cannot find this server, return false
  if (!$url) {
    return FALSE;
  }

  // Cache server information based on the url, refresh if asked
  $cid = 'l10n_update_server:' . $url;
  if ($refresh) {
    unset($info);
    cache_clear_all($cid, 'cache_l10n_update');
  }
  if (!isset($info[$url])) {
    if ($cache = cache_get($cid, 'cache_l10n_update')) {
      $info[$url] = $cache->data;
    }
    else {
      require_once 'l10n_update.parser.inc';
      if ($name && !empty($server_list[$name])) {

        // The name is in our list, it can be full data or just an url
        $server = $server_list[$name];
      }
      else {

        // This may be a new server provided by a module / package
        $server = array(
          'name' => $name,
          'server_url' => $url,
        );

        // If searching by name, store the name => url mapping
        if ($name) {
          $server_list[$name] = $server;
        }
      }

      // Now fetch server meta information form the server itself
      if ($server = l10n_update_get_server($server)) {
        cache_set($cid, $server, 'cache_l10n_update');
        $info[$url] = $server;
      }
      else {

        // If no server information, this will be FALSE. We won't search a server twice
        $info[$url] = FALSE;
      }
    }
  }
  return $info[$url];
}

/**
 * Implementation of hook_l10n_servers().
 *
 * @return array
 *   Array of server data:
 *     'name'       => server name
 *     'server_url' => server url
 *     'update_url' => update url
 */
function l10n_update_l10n_servers() {
  module_load_include('inc', 'l10n_update');
  $server = l10n_update_default_server();
  return array(
    $server['name'] => $server,
  );
}

/**
 * Get update history.
 *
 * @param boolean $refresh
 *   TRUE = refresh the history data.
 * @return
 *   An array of translation files indexed by project and language.
 */
function l10n_update_get_history($refresh = NULL) {
  static $status;
  if ($refresh || !isset($status)) {

    // Now add downloads history to projects
    $result = db_query("SELECT * FROM {l10n_update_file}");
    while ($update = db_fetch_object($result)) {
      $status[$update->project][$update->language] = $update;
    }
  }
  return $status;
}

/**
 * Get language list.
 *
 * @return array
 *   Array of installed language names. English is the source language and
 *   is therefore not included.
 */
function l10n_update_language_list() {
  $languages = locale_language_list('name');

  // Skip English language
  if (isset($languages['en'])) {
    unset($languages['en']);
  }
  return $languages;
}

/**
 * Implementation of the hook_theme() registry.
 */
function l10n_update_theme() {
  return array(
    'l10n_update_project_status' => array(
      'arguments' => array(
        'projects' => NULL,
        'languages' => NULL,
        'history' => NULL,
        'available' => NULL,
        'updates' => NULL,
      ),
      'file' => 'l10n_update.admin.inc',
    ),
    'l10n_update_release' => array(
      'arguments' => array(
        'tag' => NULL,
        'date' => NULL,
      ),
      'file' => 'l10n_update.admin.inc',
    ),
    'l10n_update_version_status' => array(
      'arguments' => array(
        'status' => NULL,
      ),
      'file' => 'l10n_update.admin.inc',
    ),
  );
}

/**
 * Build the warning message for when there is no data about available updates.
 *
 * @return sting
 *   Message text with links.
 */
function _l10n_update_no_data() {
  $destination = drupal_get_destination();
  return t('No information is available about potential new and updated translations for currently installed modules and themes. To check for updates, you may need to <a href="@run_cron">run cron</a> or you can <a href="@check_manually">check manually</a>. Please note that checking for available updates can take a long time, so please be patient.', array(
    '@run_cron' => url('admin/reports/status/run-cron', array(
      'query' => $destination,
    )),
    '@check_manually' => url('admin/build/translate/update', array(
      'query' => $destination,
    )),
  ));
}

/**
 * Get available updates.
 *
 * @param boolean $refresh
 *   TRUE = refresh the history data.
 *
 * @return array
 *   Array of all projects for which updates are available. For each project
 *   an array of update objects, one per language.
 */
function l10n_update_available_updates($refresh = NULL) {
  module_load_include('check.inc', 'l10n_update');
  if ($available = l10n_update_available_releases($refresh)) {
    $history = l10n_update_get_history();
    return l10n_update_build_updates($history, $available);
  }
}

/**
 * Implementation of hook_flush_caches().
 *
 * Called from update.php (among others) to flush the caches.
 */
function l10n_update_flush_caches() {
  if (defined('MAINTENANCE_MODE') && MAINTENANCE_MODE == 'update') {
    cache_clear_all('*', 'cache_l10n_update', TRUE);
    variable_set('l10n_update_rebuild_projects', 1);
  }
  return array();
}

/**
 * Creates a .htaccess file in the translations directory if it is missing.
 */
function l10n_update_ensure_htaccess() {
  $directory = variable_get('l10n_update_download_store', '');
  if ($directory) {
    file_create_htaccess($directory, FALSE);
  }
}

Functions

Namesort descending Description
l10n_update_available_updates Get available updates.
l10n_update_client_save_string Menu callback. Saves a string translation coming as POST data.
l10n_update_cron Implementation of hook_cron().
l10n_update_ensure_htaccess Creates a .htaccess file in the translations directory if it is missing.
l10n_update_flush_caches Implementation of hook_flush_caches().
l10n_update_form_alter Implementation of hook_form_alter().
l10n_update_get_history Get update history.
l10n_update_get_projects Get stored list of projects
l10n_update_help Implementation of hook_help().
l10n_update_l10n_servers Implementation of hook_l10n_servers().
l10n_update_languages_changed_submit Aditional submit handler for language forms
l10n_update_languages_delete_submit Additional submit handler for language deletion form.
l10n_update_language_list Get language list.
l10n_update_locale_translate_edit_form_submit Replacement submit handler for translation edit form.
l10n_update_menu Implementation of hook_menu().
l10n_update_menu_alter Implementation of hook_menu_alter().
l10n_update_server Get server information, that can come from different sources.
l10n_update_system_modules_submit Additional submit hander for System Modules form.
l10n_update_system_modules_uninstall_submit Additional submit hander for System Modules Uninstall form.
l10n_update_theme Implementation of the hook_theme() registry.
_l10n_update_no_data Build the warning message for when there is no data about available updates.

Constants

Namesort descending Description
L10N_UPDATE_CHECK_ALL Update mode: both.
L10N_UPDATE_CHECK_LOCAL Update mode: Local server.
L10N_UPDATE_CHECK_REMOTE Update mode: Remote server.
L10N_UPDATE_CRON_PROJECTS The maximum number of projects which are checked for available translations each cron run.
L10N_UPDATE_CRON_UPDATES The maximum number of projects which are updated each cron run.
LOCALE_UPDATE_OVERRIDE_DEFAULT Translation import mode keeping translations which are edited after enabling Locale Update module an only override default (un-edited) translations.