l10n_update.module 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761
  1. <?php
  2. /**
  3. * @file
  4. * Download translations from remote localization server.
  5. */
  6. /**
  7. * Translation update mode: Use local files only.
  8. *
  9. * When checking for available translation updates, only local files will be
  10. * used. Any remote translation file will be ignored. Also custom modules and
  11. * themes which have set a "server pattern" to use a remote translation server
  12. * will be ignored.
  13. */
  14. define('L10N_UPDATE_USE_SOURCE_LOCAL', 2);
  15. /**
  16. * Translation update mode: Use both remote and local files.
  17. *
  18. * When checking for available translation updates, both local and remote files
  19. * will be checked.
  20. */
  21. define('L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL', 3);
  22. /**
  23. * Default location of gettext file on the translation server.
  24. *
  25. * @see l10n_update_default_translation_server().
  26. */
  27. define('L10N_UPDATE_DEFAULT_SERVER_PATTERN', 'http://ftp.drupal.org/files/translations/%core/%project/%project-%release.%language.po');
  28. /**
  29. * Default gettext file name on the translation server.
  30. */
  31. define('L10N_UPDATE_DEFAULT_FILE_NAME', '%project-%release.%language.po');
  32. /**
  33. * Default gettext file name on the translation server.
  34. */
  35. define('L10N_UPDATE_DEFAULT_TRANSLATION_PATH', 'sites/all/translations');
  36. /**
  37. * The number of seconds that the translations status entry should be considered.
  38. */
  39. define('L10N_UPDATE_STATUS_TTL', 600);
  40. /**
  41. * UI option for override of existing translations. Only override non-customized
  42. * translations.
  43. */
  44. define('L10N_UPDATE_OVERWRITE_NON_CUSTOMIZED', 2);
  45. /**
  46. * Translation source is a remote file.
  47. */
  48. define('L10N_UPDATE_REMOTE', 'remote');
  49. /**
  50. * Translation source is a local file.
  51. */
  52. define('L10N_UPDATE_LOCAL', 'local');
  53. /**
  54. * Translation source is the current translation.
  55. */
  56. define('L10N_UPDATE_CURRENT', 'current');
  57. /**
  58. * The delimiter used to split plural strings.
  59. *
  60. * This is the ETX (End of text) character and is used as a minimal means to
  61. * separate singular and plural variants in source and translation text. It
  62. * was found to be the most compatible delimiter for the supported databases.
  63. */
  64. define('L10N_UPDATE_PLURAL_DELIMITER', "\03");
  65. /**
  66. * Flag for locally not customized interface translation.
  67. *
  68. * Such translations are imported from .po files downloaded from
  69. * localize.drupal.org for example.
  70. */
  71. define('L10N_UPDATE_NOT_CUSTOMIZED', 0);
  72. /**
  73. * Flag for locally customized interface translation.
  74. *
  75. * Strings are customized when translated or edited using the build in
  76. * string translation form. Strings can also be marked as customized when a po
  77. * file is imported.
  78. */
  79. define('L10N_UPDATE_STRING_CUSTOM', 1);
  80. /**
  81. * Flag for locally customized interface translation.
  82. *
  83. * Such translations are edited from their imported originals on the user
  84. * interface or are imported as customized.
  85. */
  86. define('L10N_UPDATE_CUSTOMIZED', 1);
  87. /**
  88. * Implements hook_help().
  89. */
  90. function l10n_update_help($path, $arg) {
  91. switch ($path) {
  92. case 'admin/config/regional/translate/update':
  93. $output = '<p>' . t('Status of interface translations for each of the enabled languages.') . '</p>';
  94. $output .= '<p>' . t('If there are available updates you can click on "Update translation" 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/config/regional/language/update'))) . '</p>';
  95. return $output;
  96. break;
  97. case 'admin/config/regional/language/update':
  98. $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/config/regional/translate/update'))) . '</p>';
  99. return $output;
  100. break;
  101. }
  102. }
  103. /**
  104. * Implements hook_menu().
  105. */
  106. function l10n_update_menu() {
  107. $items['admin/config/regional/translate/update'] = array(
  108. 'title' => 'Update',
  109. 'description' => 'Available updates',
  110. 'page callback' => 'drupal_get_form',
  111. 'page arguments' => array('l10n_update_status_form'),
  112. 'access arguments' => array('translate interface'),
  113. 'file' => 'l10n_update.admin.inc',
  114. 'weight' => 20,
  115. 'type' => MENU_LOCAL_TASK,
  116. );
  117. $items['admin/config/regional/translate/check'] = array(
  118. 'title' => 'Update',
  119. 'description' => 'Available updates',
  120. 'page callback' => 'l10n_update_manual_status',
  121. 'access arguments' => array('translate interface'),
  122. 'file' => 'l10n_update.admin.inc',
  123. 'weight' => 20,
  124. 'type' => MENU_CALLBACK,
  125. );
  126. $items['admin/config/regional/language/update'] = array(
  127. 'title' => 'Translation updates',
  128. 'description' => 'Automatic update configuration',
  129. 'page callback' => 'drupal_get_form',
  130. 'page arguments' => array('l10n_update_admin_settings_form'),
  131. 'access arguments' => array('translate interface'),
  132. 'file' => 'l10n_update.admin.inc',
  133. 'weight' => 20,
  134. 'type' => MENU_LOCAL_TASK,
  135. );
  136. return $items;
  137. }
  138. /**
  139. * Implements hook_theme().
  140. */
  141. function l10n_update_theme() {
  142. return array(
  143. 'l10n_update_last_check' => array(
  144. 'variables' => array('last' => NULL),
  145. 'file' => 'l10n_update.admin.inc',
  146. 'template' => 'l10n_update-translation-last-check',
  147. ),
  148. 'l10n_update_update_info' => array(
  149. 'variables' => array('updates' => array(), 'not_found' => array()),
  150. 'file' => 'l10n_update.admin.inc',
  151. 'template' => 'l10n_update-translation-update-info',
  152. ),
  153. );
  154. }
  155. /**
  156. * Implements hook_menu_alter().
  157. */
  158. function l10n_update_menu_alter(&$menu) {
  159. // Redirect l10n_client AJAX callback path for strings.
  160. if (module_exists('l10n_client')) {
  161. $menu['l10n_client/save']['page callback'] = 'l10n_update_client_save_string';
  162. }
  163. }
  164. /**
  165. * Implements hook_cron().
  166. *
  167. * Check one project/language at a time, download and import if update available
  168. */
  169. function l10n_update_cron() {
  170. // Update translations only when an update frequency was set by the admin
  171. // and a translatable language was set.
  172. // Update tasks are added to the queue here but processed by Drupal's cron
  173. // using the cron worker defined in l10n_update_queue_info().
  174. if ($frequency = variable_get('l10n_update_check_frequency', '0') && l10n_update_translatable_language_list()) {
  175. module_load_include('translation.inc', 'l10n_update');
  176. l10n_update_cron_fill_queue();
  177. }
  178. }
  179. /**
  180. * Implements hook_cron_queue_info().
  181. */
  182. function l10n_update_cron_queue_info() {
  183. $queues['l10n_update'] = array(
  184. 'worker callback' => 'l10n_update_worker',
  185. 'time' => 30,
  186. );
  187. return $queues;
  188. }
  189. /**
  190. * Callback: Executes interface translation queue tasks.
  191. *
  192. * The translation update functions executed here are batch operations which
  193. * are also used in translation update batches. The batch functions may need to
  194. * be executed multiple times to complete their task, typically this is the
  195. * translation import function. When a batch function is not finished, a new
  196. * queue task is created and added to the end of the queue. The batch context
  197. * data is needed to continue the batch task is stored in the queue with the
  198. * queue data.
  199. *
  200. * @param array $data
  201. * Queue data array containing:
  202. * - Function name.
  203. * - Array of function arguments. Optionally contains the batch context data.
  204. *
  205. * @see l10n_update_queue_info()
  206. */
  207. function l10n_update_worker($data) {
  208. module_load_include('batch.inc', 'l10n_update');
  209. list($function, $args) = $data;
  210. // We execute batch operation functions here to check, download and import the
  211. // translation files. Batch functions use a context variable as last argument
  212. // which is passed by reference. When a batch operation is called for the
  213. // first time a default batch context is created. When called iterative
  214. // (usually the batch import function) the batch context is passed through via
  215. // the queue and is part of the $data.
  216. $last = count($args) - 1;
  217. if (!is_array($args[$last]) || !isset($args[$last]['finished'])) {
  218. $batch_context = array(
  219. 'sandbox' => array(),
  220. 'results' => array(),
  221. 'finished' => 1,
  222. 'message' => '',
  223. );
  224. }
  225. else {
  226. $batch_context = $args[$last];
  227. unset ($args[$last]);
  228. }
  229. $args = array_merge($args, array(&$batch_context));
  230. // Call the batch operation function.
  231. call_user_func_array($function, $args);
  232. // If the batch operation is not finished we create a new queue task to
  233. // continue the task. This is typically the translation import task.
  234. if ($batch_context['finished'] < 1) {
  235. unset($batch_context['strings']);
  236. $queue = DrupalQueue::get('l10n_update', TRUE);
  237. $queue->createItem(array($function, $args));
  238. }
  239. }
  240. /**
  241. * Implements hook_stream_wrappers().
  242. */
  243. function l10n_update_stream_wrappers() {
  244. // Load the stream wrapper class if not automatically loaded. This happens
  245. // before update.php is executed.
  246. if (!class_exists('TranslationsStreamWrapper')) {
  247. require_once('includes/locale/TranslationsStreamWrapper.php');
  248. }
  249. $wrappers['translations'] = array(
  250. 'name' => t('Translation files'),
  251. 'class' => 'TranslationsStreamWrapper',
  252. 'description' => t('Translation files.'),
  253. 'type' => STREAM_WRAPPERS_LOCAL_HIDDEN,
  254. );
  255. return $wrappers;
  256. }
  257. /**
  258. * Implements hook_form_alter().
  259. */
  260. function l10n_update_form_alter(&$form, $form_state, $form_id) {
  261. switch ($form_id) {
  262. case 'locale_translate_edit_form':
  263. case 'i18n_string_locale_translate_edit_form':
  264. $form['#submit'][] = 'l10n_update_locale_translate_edit_form_submit';
  265. break;
  266. case 'locale_languages_predefined_form':
  267. case 'locale_languages_custom_form':
  268. $form['#submit'][] = 'l10n_update_languages_changed_submit';
  269. break;
  270. case 'locale_languages_delete_form':
  271. // A language is being deleted.
  272. $form['#submit'][] = 'l10n_update_languages_delete_submit';
  273. break;
  274. }
  275. }
  276. /**
  277. * Implements hook_modules_enabled().
  278. *
  279. * Refresh project translation status and get translations if required.
  280. */
  281. function l10n_update_modules_enabled($modules) {
  282. $components['module'] = $modules;
  283. l10n_update_system_update($components);
  284. }
  285. /**
  286. * Implements hook_modules_disabled().
  287. *
  288. * Set disabled modules to be ignored when updating translations.
  289. */
  290. function l10n_update_modules_disabled($modules) {
  291. if (!variable_get('l10n_update_check_disabled', FALSE)) {
  292. db_update('l10n_update_project')
  293. ->fields(array(
  294. 'status' => 0,
  295. ))
  296. ->condition('name', $modules)
  297. ->execute();
  298. }
  299. }
  300. /**
  301. * Implements hook_modules_uninstalled().
  302. *
  303. * Remove data of uninstalled modules from {l10n_update_file} table and
  304. * rebuild the projects cache.
  305. */
  306. function l10n_update_modules_uninstalled($modules) {
  307. $components['module'] = $modules;
  308. l10n_update_system_remove($components);
  309. }
  310. /**
  311. * Implements hook_themes_enabled().
  312. *
  313. * Refresh project translation status and get translations if required.
  314. */
  315. function l10n_update_themes_enabled($themes) {
  316. $components['theme'] = $themes;
  317. l10n_update_system_update($components);
  318. }
  319. /**
  320. * Additional submit handler for language forms
  321. *
  322. * We need to refresh status when a new language is enabled / disabled
  323. */
  324. function l10n_update_languages_changed_submit($form, $form_state) {
  325. if (variable_get('l10n_update_import_enabled', TRUE)) {
  326. if (empty($form_state['values']['predefined_langcode']) || $form_state['values']['predefined_langcode'] == 'custom') {
  327. $langcode = $form_state['values']['langcode'];
  328. }
  329. else {
  330. $langcode = $form_state['values']['predefined_langcode'];
  331. }
  332. // Download and import translations for the newly added language.
  333. module_load_include('fetch.inc', 'l10n_update');
  334. $options = _l10n_update_default_update_options();
  335. $batch = l10n_update_batch_update_build(array(), array($langcode), $options);
  336. batch_set($batch);
  337. }
  338. }
  339. /**
  340. * Additional submit handler for language deletion form.
  341. *
  342. * When a language is deleted, the file history of this language is cleared.
  343. */
  344. function l10n_update_languages_delete_submit($form, $form_state) {
  345. $langcode = $form_state['values']['langcode'];
  346. l10n_update_file_history_delete(array(), $langcode);
  347. }
  348. /**
  349. * Additional submit handler for locale and i18n_string translation edit form.
  350. *
  351. * Mark locally edited translations as customized.
  352. *
  353. * @see l10n_update_form_alter()
  354. */
  355. function l10n_update_locale_translate_edit_form_submit($form, &$form_state) {
  356. $lid = $form_state['values']['lid'];
  357. foreach ($form_state['values']['translations'] as $langcode => $value) {
  358. if (!empty($value) && $value != $form_state['complete form']['translations'][$langcode]['#default_value']) {
  359. // An update has been made, mark the string as customized.
  360. db_update('locales_target')
  361. ->fields(array('l10n_status' => L10N_UPDATE_STRING_CUSTOM))
  362. ->condition('lid', $lid)
  363. ->condition('language', $langcode)
  364. ->execute();
  365. }
  366. }
  367. }
  368. /**
  369. * Menu callback. Saves a string translation coming as POST data.
  370. */
  371. function l10n_update_client_save_string() {
  372. global $user, $language;
  373. if (l10n_client_access()) {
  374. if (isset($_POST['source']) && isset($_POST['target']) && !empty($_POST['textgroup']) && !empty($_POST['form_token']) && drupal_valid_token($_POST['form_token'], 'l10n_client_form')) {
  375. // Ensure we have this source string before we attempt to save it.
  376. // @todo: add actual context support.
  377. $lid = db_query("SELECT lid FROM {locales_source} WHERE source = :source AND context = :context AND textgroup = :textgroup", array(':source' => $_POST['source'], ':context' => '', ':textgroup' => $_POST['textgroup']))->fetchField();
  378. if (!empty($lid)) {
  379. module_load_include('translation.inc', 'l10n_update');
  380. $report = array('skips' => 0, 'additions' => 0, 'updates' => 0, 'deletes' => 0);
  381. // @todo: add actual context support.
  382. _l10n_update_locale_import_one_string_db($report, $language->language, '', $_POST['source'], $_POST['target'], $_POST['textgroup'], NULL, LOCALE_IMPORT_OVERWRITE, L10N_UPDATE_STRING_CUSTOM);
  383. cache_clear_all('locale:', 'cache', TRUE);
  384. _locale_invalidate_js($language->language);
  385. if (!empty($report['skips'])) {
  386. $message = theme('l10n_client_message', array('message' => t('Not saved locally due to invalid HTML content.')));
  387. }
  388. elseif (!empty($report['additions']) || !empty($report['updates'])) {
  389. $message = theme('l10n_client_message', array('message' => t('Translation saved locally.'), 'level' => WATCHDOG_INFO));
  390. }
  391. elseif (!empty($report['deletes'])) {
  392. $message = theme('l10n_client_message', array('message' => t('Translation successfuly removed locally.'), 'level' => WATCHDOG_INFO));
  393. }
  394. else {
  395. $message = theme('l10n_client_message', array('message' => t('Unknown error while saving translation locally.')));
  396. }
  397. // Submit to remote server if enabled.
  398. if (variable_get('l10n_client_use_server', FALSE) && user_access('submit translations to localization server') && ($_POST['textgroup'] == 'default')) {
  399. if (!empty($user->data['l10n_client_key'])) {
  400. $remote_result = l10n_client_submit_translation($language->language, $_POST['source'], $_POST['target'], $user->data['l10n_client_key'], l10n_client_user_token($user));
  401. $message .= theme('l10n_client_message', array('message' => $remote_result[1], 'level' => $remote_result[0] ? WATCHDOG_INFO : WATCHDOG_ERROR));
  402. }
  403. else {
  404. $server_url = variable_get('l10n_client_server', 'http://localize.drupal.org');
  405. $user_edit_url = url('user/' . $user->uid . '/edit', array('absolute' => TRUE));
  406. $message .= theme('l10n_client_message', array('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'))), 'level' => WATCHDOG_WARNING));
  407. }
  408. }
  409. }
  410. else {
  411. $message = theme('l10n_client_message', array('message' => t('Not saved due to source string missing.')));
  412. }
  413. }
  414. else {
  415. $message = theme('l10n_client_message', array('message' => t('Not saved due to missing form values.')));
  416. }
  417. }
  418. else {
  419. $message = theme('l10n_client_message', array('message' => t('Not saved due to insufficient permissions.')));
  420. }
  421. drupal_json_output($message);
  422. exit;
  423. }
  424. /**
  425. * Imports translations when new modules or themes are installed.
  426. *
  427. * This function will start a batch to import translations for the added
  428. * components.
  429. *
  430. * @param array $components
  431. * An array of arrays of component (theme and/or module) names to import
  432. * translations for, indexed by type.
  433. */
  434. function l10n_update_system_update(array $components) {
  435. $components += array('module' => array(), 'theme' => array());
  436. $list = array_merge($components['module'], $components['theme']);
  437. // Skip running the translation imports if in the installer,
  438. // because it would break out of the installer flow. We have
  439. // built-in support for translation imports in the installer.
  440. if (!drupal_installation_attempted() && l10n_update_translatable_language_list() && variable_get('l10n_update_import_enabled', TRUE)) {
  441. module_load_include('compare.inc', 'l10n_update');
  442. // Update the list of translatable projects and start the import batch.
  443. // Only when new projects are added the update batch will be triggered. Not
  444. // each enabled module will introduce a new project. E.g. sub modules.
  445. $projects = array_keys(l10n_update_build_projects());
  446. if ($list = array_intersect($list, $projects)) {
  447. module_load_include('fetch.inc', 'l10n_update');
  448. // Get translation status of the projects, download and update translations.
  449. $options = _l10n_update_default_update_options();
  450. $batch = l10n_update_batch_update_build($list, array(), $options);
  451. batch_set($batch);
  452. }
  453. }
  454. }
  455. /**
  456. * Delete translation history of modules and themes.
  457. *
  458. * Only the translation history is removed, not the source strings or
  459. * translations. This is not possible because strings are shared between
  460. * modules and we have no record of which string is used by which module.
  461. *
  462. * @param array $components
  463. * An array of arrays of component (theme and/or module) names to import
  464. * translations for, indexed by type.
  465. */
  466. function l10n_update_system_remove($components) {
  467. $components += array('module' => array(), 'theme' => array());
  468. $list = array_merge($components['module'], $components['theme']);
  469. if ($language_list = l10n_update_translatable_language_list()) {
  470. module_load_include('compare.inc', 'l10n_update');
  471. module_load_include('bulk.inc', 'l10n_update');
  472. // Only when projects are removed, the translation files and records will be
  473. // deleted. Not each disabled module will remove a project. E.g. sub modules.
  474. $projects = array_keys(l10n_update_get_projects());
  475. if ($list = array_intersect($list, $projects)) {
  476. l10n_update_file_history_delete($list);
  477. // Remove translation files.
  478. l10n_update_delete_translation_files($list, array());
  479. // Remove translatable projects.
  480. // Followup issue http://drupal.org/node/1842362 to replace the
  481. // {l10n_update_project} table. Then change this to a function call.
  482. db_delete('l10n_update_project')
  483. ->condition('name', $list)
  484. ->execute();
  485. // Clear the translation status.
  486. l10n_update_status_delete_projects($list);
  487. }
  488. }
  489. }
  490. /**
  491. * Gets current translation status from the {l10n_update_file} table.
  492. *
  493. * @return array
  494. * Array of translation file objects.
  495. */
  496. function l10n_update_get_file_history() {
  497. $history = &drupal_static(__FUNCTION__, array());
  498. if (empty($history)) {
  499. // Get file history from the database.
  500. $result = db_query('SELECT project, language, filename, version, uri, timestamp, last_checked FROM {l10n_update_file}');
  501. foreach ($result as $file) {
  502. $file->langcode = $file->language;
  503. $file->type = $file->timestamp ? L10N_UPDATE_CURRENT : '';
  504. $history[$file->project][$file->langcode] = $file;
  505. }
  506. }
  507. return $history;
  508. }
  509. /**
  510. * Updates the {locale_file} table.
  511. *
  512. * @param object $file
  513. * Object representing the file just imported.
  514. *
  515. * @return integer
  516. * FALSE on failure. Otherwise SAVED_NEW or SAVED_UPDATED.
  517. *
  518. * @see drupal_write_record()
  519. */
  520. function l10n_update_update_file_history($file) {
  521. // Update or write new record.
  522. if (db_query("SELECT project FROM {l10n_update_file} WHERE project = :project AND language = :langcode", array(':project' => $file->project, ':langcode' => $file->langcode))->fetchField()) {
  523. $update = array('project', 'language');
  524. }
  525. else {
  526. $update = array();
  527. }
  528. $file->language = $file->langcode;
  529. $result = drupal_write_record('l10n_update_file', $file, $update);
  530. // The file history has changed, flush the static cache now.
  531. // @todo Can we make this more fine grained?
  532. drupal_static_reset('l10n_update_get_file_history');
  533. return $result;
  534. }
  535. /**
  536. * Deletes the history of downloaded translations.
  537. *
  538. * @param array $projects
  539. * Project name(s) to be deleted from the file history. If both project(s) and
  540. * language code(s) are specified the conditions will be ANDed.
  541. * @param array $langcode
  542. * Language code(s) to be deleted from the file history.
  543. */
  544. function l10n_update_file_history_delete($projects = array(), $langcodes = array()) {
  545. $query = db_delete('l10n_update_file');
  546. if (!empty($projects)) {
  547. $query->condition('project', $projects);
  548. }
  549. if (!empty($langcodes)) {
  550. $query->condition('language', $langcodes);
  551. }
  552. $query->execute();
  553. }
  554. /**
  555. * Gets the current translation status.
  556. *
  557. * @todo What is 'translation status'?
  558. */
  559. function l10n_update_get_status($projects = NULL, $langcodes = NULL) {
  560. $result = array();
  561. $status = variable_get('l10n_update_translation_status', array());
  562. module_load_include('translation.inc', 'l10n_update');
  563. $projects = $projects ? $projects : array_keys(l10n_update_get_projects());
  564. $langcodes = $langcodes ? $langcodes : array_keys(l10n_update_translatable_language_list());
  565. // Get the translation status of each project-language combination. If no
  566. // status was stored, a new translation source is created.
  567. foreach ($projects as $project) {
  568. foreach ($langcodes as $langcode) {
  569. if (isset($status[$project][$langcode])) {
  570. $result[$project][$langcode] = $status[$project][$langcode];
  571. }
  572. else {
  573. $sources = l10n_update_build_sources(array($project), array($langcode));
  574. if (isset($sources[$project][$langcode])) {
  575. $result[$project][$langcode] = $sources[$project][$langcode];
  576. }
  577. }
  578. }
  579. }
  580. return $result;
  581. }
  582. /**
  583. * Saves the status of translation sources in static cache.
  584. *
  585. * @param string $project
  586. * Machine readable project name.
  587. * @param string $langcode
  588. * Language code.
  589. * @param string $type
  590. * Type of data to be stored.
  591. * @param array $data
  592. * File object also containing timestamp when the translation is last updated.
  593. */
  594. function l10n_update_status_save($project, $langcode, $type, $data) {
  595. // Followup issue: http://drupal.org/node/1842362
  596. // Split status storage per module/language and expire individually. This will
  597. // improve performance for large sites.
  598. // Load the translation status or build it if not already available.
  599. module_load_include('translation.inc', 'l10n_update');
  600. $status = l10n_update_get_status();
  601. if (empty($status)) {
  602. $projects = l10n_update_get_projects(array($project));
  603. if (isset($projects[$project])) {
  604. $status[$project][$langcode] = l10n_update_source_build($projects[$project], $langcode);
  605. }
  606. }
  607. // Merge the new status data with the existing status.
  608. if (isset($status[$project][$langcode])) {
  609. switch ($type) {
  610. case L10N_UPDATE_REMOTE:
  611. case L10N_UPDATE_LOCAL:
  612. // Add the source data to the status array.
  613. $status[$project][$langcode]->files[$type] = $data;
  614. // Check if this translation is the most recent one. Set timestamp and
  615. // data type of the most recent translation source.
  616. if (isset($data->timestamp) && $data->timestamp) {
  617. if ($data->timestamp > $status[$project][$langcode]->timestamp) {
  618. $status[$project][$langcode]->timestamp = $data->timestamp;
  619. $status[$project][$langcode]->last_checked = REQUEST_TIME;
  620. $status[$project][$langcode]->type = $type;
  621. }
  622. }
  623. break;
  624. case L10N_UPDATE_CURRENT:
  625. $data->last_checked = REQUEST_TIME;
  626. $status[$project][$langcode]->timestamp = $data->timestamp;
  627. $status[$project][$langcode]->last_checked = $data->last_checked;
  628. $status[$project][$langcode]->type = $type;
  629. l10n_update_update_file_history($data);
  630. break;
  631. }
  632. variable_set('l10n_update_translation_status', $status);
  633. variable_set('l10n_update_last_check', REQUEST_TIME);
  634. }
  635. }
  636. /**
  637. * Delete language entries from the status cache.
  638. *
  639. * @param array $langcodes
  640. * Language code(s) to be deleted from the cache.
  641. */
  642. function l10n_update_status_delete_languages($langcodes) {
  643. if ($status = l10n_update_get_status()) {
  644. foreach ($status as $project => $languages) {
  645. foreach ($languages as $langcode => $source) {
  646. if (in_array($langcode, $langcodes)) {
  647. unset($status[$project][$langcode]);
  648. }
  649. }
  650. }
  651. variable_set('l10n_update_translation_status', $status);
  652. }
  653. }
  654. /**
  655. * Delete project entries from the status cache.
  656. *
  657. * @param array $projects
  658. * Project name(s) to be deleted from the cache.
  659. */
  660. function l10n_update_status_delete_projects($projects) {
  661. $status = l10n_update_get_status();
  662. foreach ($status as $project => $languages) {
  663. if (in_array($project, $projects)) {
  664. unset($status[$project]);
  665. }
  666. }
  667. variable_set('l10n_update_translation_status', $status);
  668. }
  669. /**
  670. * Returns list of translatable languages.
  671. *
  672. * @return array
  673. * Array of enabled languages keyed by language name. English is omitted.
  674. */
  675. function l10n_update_translatable_language_list() {
  676. $languages = locale_language_list('name');
  677. unset($languages['en']);
  678. return $languages;
  679. }
  680. /**
  681. * Clear the translation status cache.
  682. */
  683. function l10n_update_clear_status() {
  684. variable_del('l10n_update_translation_status');
  685. variable_del('l10n_update_last_check');
  686. }
  687. /**
  688. * Checks whether remote translation sources are used.
  689. *
  690. * @return bool
  691. * Returns TRUE if remote translations sources should be taken into account
  692. * when checking or importing translation files, FALSE otherwise.
  693. */
  694. function l10n_update_use_remote_source() {
  695. return variable_get('l10n_update_check_mode', L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL) == L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL;
  696. }