l10n_update.admin.inc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  1. <?php
  2. /**
  3. * @file
  4. * Admin settings and update page.
  5. */
  6. /**
  7. * Page callback: Checks for translation updates and displays the status.
  8. *
  9. * Manually checks the translation status without the use of cron.
  10. */
  11. function l10n_update_manual_status() {
  12. module_load_include('compare.inc', 'l10n_update');
  13. // Check the translation status of all translatable projects in all languages.
  14. // First we clear the cached list of projects. Although not strictly
  15. // necessary, this is helpful in case the project list is out of sync.
  16. l10n_update_flush_projects();
  17. l10n_update_check_projects();
  18. // Execute a batch if required. A batch is only used when remote files
  19. // are checked.
  20. if (batch_get()) {
  21. batch_process('admin/config/regional/translate/update');
  22. }
  23. drupal_goto('admin/config/regional/translate/update');
  24. }
  25. /**
  26. * Page callback: Translation status page.
  27. */
  28. function l10n_update_status_form() {
  29. module_load_include('compare.inc', 'l10n_update');
  30. $updates = $options = array();
  31. $languages_update = $languages_not_found = array();
  32. $projects_update = array();
  33. // @todo Calling l10n_update_build_projects() is an expensive way to
  34. // get a module name. In follow-up issue http://drupal.org/node/1842362
  35. // the project name will be stored to display use, like here.
  36. $project_data = l10n_update_build_projects();
  37. $languages = l10n_update_translatable_language_list();
  38. $status = l10n_update_get_status();
  39. // Prepare information about projects which have available translation
  40. // updates.
  41. if ($languages && $status) {
  42. foreach ($status as $project) {
  43. foreach ($project as $langcode => $project_info) {
  44. if (isset($project_data[$project_info->name])) {
  45. // No translation file found for this project-language combination.
  46. if (empty($project_info->type)) {
  47. $updates[$langcode]['not_found'][] = array(
  48. 'name' => $project_info->name == 'drupal' ? t('Drupal core') : $project_data[$project_info->name]->info['name'],
  49. 'version' => $project_info->version,
  50. 'info' => _l10n_update_status_debug_info($project_info),
  51. );
  52. $languages_not_found[$langcode] = $langcode;
  53. }
  54. // Translation update found for this project-language combination.
  55. elseif ($project_info->type == L10N_UPDATE_LOCAL || $project_info->type == L10N_UPDATE_REMOTE ) {
  56. $local = isset($project_info->files[L10N_UPDATE_LOCAL]) ? $project_info->files[L10N_UPDATE_LOCAL] : NULL;
  57. $remote = isset($project_info->files[L10N_UPDATE_REMOTE]) ? $project_info->files[L10N_UPDATE_REMOTE] : NULL;
  58. $recent = _l10n_update_source_compare($local, $remote) == L10N_UPDATE_SOURCE_COMPARE_LT ? $remote : $local;
  59. $updates[$langcode]['updates'][] = array(
  60. 'name' => $project_info->name == 'drupal' ? t('Drupal core') : $project_data[$project_info->name]->info['name'],
  61. 'version' => $project_info->version,
  62. 'timestamp' => $recent->timestamp,
  63. );
  64. $languages_update[$langcode] = $langcode;
  65. $projects_update[$project_info->name] = $project_info->name;
  66. }
  67. }
  68. }
  69. }
  70. $languages_not_found = array_diff($languages_not_found, $languages_update);
  71. // Build data options for the select table.
  72. foreach($updates as $langcode => $update) {
  73. $title = check_plain($languages[$langcode]);
  74. $l10n_update_update_info = array('#theme' => 'l10n_update_update_info');
  75. foreach (array('updates', 'not_found') as $update_status) {
  76. if (isset($update[$update_status])) {
  77. $l10n_update_update_info['#' . $update_status] = $update[$update_status];
  78. }
  79. }
  80. $options[$langcode] = array(
  81. 'title' => array(
  82. 'class' => array('label'),
  83. 'data' => array(
  84. '#title' => $title,
  85. '#markup' => $title
  86. ),
  87. ),
  88. 'status' => array('class' => array('description', 'expand', 'priority-low'), 'data' => drupal_render($l10n_update_update_info)),
  89. );
  90. }
  91. // Sort the table data on language name.
  92. uasort($options, function ($a, $b) {
  93. return strcasecmp($a['title']['data']['#title'], $b['title']['data']['#title']);
  94. });
  95. }
  96. $last_checked = variable_get('l10n_update_last_check');
  97. $form['last_checked'] = array(
  98. '#theme' => 'l10n_update_last_check',
  99. '#last' => $last_checked,
  100. );
  101. $header = array(
  102. 'title' => array(
  103. 'data' => t('Language'),
  104. 'class' => array('title'),
  105. ),
  106. 'status' => array(
  107. 'data' => t('Status'),
  108. 'class' => array('status', 'priority-low'),
  109. ),
  110. );
  111. if (!$languages) {
  112. $empty = t('No translatable languages available. <a href="@add_language">Add a language</a> first.', array('@add_language' => url('admin/config/regional/language')));
  113. }
  114. elseif (empty($options)) {
  115. $empty = t('All translations up to date.');
  116. }
  117. else {
  118. $empty = t('No translation status available. <a href="@check">Check manually</a>.', array('@check' => url('admin/config/regional/translate/check')));
  119. }
  120. // The projects which require an update. Used by the _submit callback.
  121. $form['projects_update'] = array(
  122. '#type' => 'value',
  123. '#value' => $projects_update,
  124. );
  125. $form['langcodes'] = array(
  126. '#type' => 'tableselect',
  127. '#header' => $header,
  128. '#options' => $options,
  129. '#default_value' => $languages_update,
  130. '#empty' => $empty,
  131. '#js_select' => TRUE,
  132. '#multiple' => TRUE,
  133. '#required' => TRUE,
  134. '#not_found' => $languages_not_found,
  135. '#after_build' => array('l10n_update_language_table'),
  136. '#attributes' => array(),
  137. );
  138. $form['#attached'] = array(
  139. 'js' => array(
  140. drupal_get_path('module', 'l10n_update') . '/js/l10n_update.admin.js',
  141. ),
  142. 'css' => array(
  143. drupal_get_path('module', 'l10n_update') . '/css/l10n_update.admin.css',
  144. ),
  145. );
  146. if ($languages_update) {
  147. $form['actions'] = array(
  148. '#type' => 'actions',
  149. 'submit' => array(
  150. '#type' => 'submit',
  151. '#value' => t('Update translations'),
  152. ),
  153. '#attributes' => array(),
  154. );
  155. }
  156. return $form;
  157. }
  158. /**
  159. * Form validation handler for locale_translation_status_form().
  160. */
  161. function l10n_update_status_form_validate($form, &$form_state) {
  162. // Check if a language has been selected. 'tableselect' doesn't.
  163. if (!array_filter($form_state['values']['langcodes'])) {
  164. form_set_error('', t('Select a language to update.'));
  165. }
  166. }
  167. /**
  168. * Form submission handler for locale_translation_status_form().
  169. */
  170. function l10n_update_status_form_submit($form, $form_state) {
  171. module_load_include('fetch.inc', 'l10n_update');
  172. $langcodes = array_filter($form_state['values']['langcodes']);
  173. $projects = array_filter($form_state['values']['projects_update']);
  174. // Set the translation import options. This determines if existing
  175. // translations will be overwritten by imported strings.
  176. $options = _l10n_update_default_update_options();
  177. // If the status was updated recently we can immediately start fetching the
  178. // translation updates. If the status is expired we clear it an run a batch to
  179. // update the status and then fetch the translation updates.
  180. $last_checked = variable_get('l10n_update_last_check');
  181. if ($last_checked < REQUEST_TIME - L10N_UPDATE_STATUS_TTL) {
  182. l10n_update_clear_status();
  183. $batch = l10n_update_batch_update_build(array(), $langcodes, $options);
  184. batch_set($batch);
  185. }
  186. else {
  187. $batch = l10n_update_batch_fetch_build($projects, $langcodes, $options);
  188. batch_set($batch);
  189. }
  190. }
  191. /**
  192. * Page callback: Settings form.
  193. */
  194. function l10n_update_admin_settings_form($form, &$form_state) {
  195. $form['l10n_update_check_frequency'] = array(
  196. '#type' => 'radios',
  197. '#title' => t('Check for updates'),
  198. '#default_value' => variable_get('l10n_update_check_frequency', '0'),
  199. '#options' => array(
  200. '0' => t('Never (manually)'),
  201. '7' => t('Weekly'),
  202. '30' => t('Monthly'),
  203. ),
  204. '#description' => t('Select how frequently you want to check for new interface translations for your currently installed modules and themes. <a href="@url">Check updates now</a>.', array('@url' => url('admin/config/regional/translate/check'))),
  205. );
  206. $form['l10n_update_check_disabled'] = array(
  207. '#type' => 'checkbox',
  208. '#title' => t('Check for updates of disabled modules and themes'),
  209. '#default_value' => variable_get('l10n_update_check_disabled', FALSE),
  210. );
  211. $form['l10n_update_check_mode'] = array(
  212. '#type' => 'radios',
  213. '#title' => t('Translation source'),
  214. '#default_value' => variable_get('l10n_update_check_mode', L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL),
  215. '#options' => array(
  216. L10N_UPDATE_USE_SOURCE_REMOTE_AND_LOCAL => t('Drupal translation server and local files'),
  217. L10N_UPDATE_USE_SOURCE_LOCAL => t('Local files only'),
  218. ),
  219. '#description' => t('The source of translation files for automatic interface translation.'),
  220. );
  221. $form['l10n_update_download_store'] = array(
  222. '#title' => t('Translations directory'),
  223. '#type' => 'textfield',
  224. '#default_value' => variable_get('l10n_update_download_store', L10N_UPDATE_DEFAULT_TRANSLATION_PATH),
  225. '#required' => TRUE,
  226. '#description' => t('A path relative to the Drupal installation directory where translation files will be stored, e.g. sites/all/translations. Saved translation files can be reused by other installations.'),
  227. );
  228. $form['l10n_update_import_mode'] = array(
  229. '#type' => 'radios',
  230. '#title' => t('Import behaviour'),
  231. '#default_value' => variable_get('l10n_update_import_mode', LOCALE_IMPORT_KEEP),
  232. '#options' => array(
  233. LOCALE_IMPORT_KEEP => t("Don't overwrite existing translations."),
  234. L10N_UPDATE_OVERWRITE_NON_CUSTOMIZED => t('Only overwrite imported translations, customized translations are kept.'),
  235. LOCALE_IMPORT_OVERWRITE => t('Overwrite existing translations.'),
  236. ),
  237. '#description' => t('How to treat existing translations when automatically updating the interface translations.'),
  238. );
  239. $form['#submit'][] = 'l10n_update_admin_settings_form_submit';
  240. return system_settings_form($form);
  241. }
  242. /**
  243. * Validation handler for translation update settings.
  244. */
  245. function l10n_update_admin_settings_form_validate($form, &$form_state) {
  246. // Check for existing translations directory and create one if required.
  247. $directory = $form_state['values']['l10n_update_download_store'];
  248. if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
  249. form_set_error('l10n_update_download_store', t('The directory %directory does not exist or is not writable.', array('%directory' => $directory)));
  250. watchdog('file system', 'The directory %directory does not exist or is not writable.', array('%directory' => $directory), WATCHDOG_ERROR);
  251. }
  252. }
  253. /**
  254. * Submit handler for translation update settings.
  255. */
  256. function l10n_update_admin_settings_form_submit($form, $form_state) {
  257. // Invalidate the cached translation status when the configuration setting of
  258. // 'l10n_update_check_mode' or 'check_disabled' change.
  259. if ($form['l10n_update_check_mode']['#default_value'] != $form_state['values']['l10n_update_check_mode'] ||
  260. $form['l10n_update_check_disabled']['#default_value'] != $form_state['values']['l10n_update_check_disabled']) {
  261. l10n_update_clear_status();
  262. }
  263. }
  264. /**
  265. * Get array of import options.
  266. *
  267. * The import options of the Locale module are used but the UI text is altered
  268. * to suit the Localization update cases.
  269. *
  270. * @return
  271. * Keyed array of import options.
  272. */
  273. function _l10n_update_admin_import_options() {
  274. return array(
  275. LOCALE_IMPORT_OVERWRITE => t('Translation updates replace existing ones, new ones are added'),
  276. L10N_UPDATE_OVERWRITE_NON_CUSTOMIZED => t('Edited translations are kept, only previously imported ones are overwritten and new translations are added'),
  277. LOCALE_IMPORT_KEEP => t('All existing translations are kept, only new translations are added.'),
  278. );
  279. }
  280. /**
  281. * Provides debug info for projects in case translation files are not found.
  282. *
  283. * Translations files are being fetched either from Drupal translation server
  284. * and local files or only from the local filesystem depending on the
  285. * "Translation source" setting at admin/config/regional/language/update.
  286. * This method will produce debug information including the respective path(s)
  287. * based on this setting.
  288. *
  289. * Translations for development versions are never fetched, so the debug info
  290. * for that is a fixed message.
  291. *
  292. * @param array $source
  293. * An array which is the project information of the source.
  294. *
  295. * @return string
  296. * The string which contains debug information.
  297. */
  298. function _l10n_update_status_debug_info($source) {
  299. $remote_path = isset($source->files['remote']->uri) ? $source->files['remote']->uri : '';
  300. $local_path = isset($source->files['local']->uri) ? $source->files['local']->uri : '';
  301. if (strpos($source->version, 'dev') !== FALSE) {
  302. return t('No translation files are provided for development releases.');
  303. }
  304. if (l10n_update_use_remote_source() && $remote_path && $local_path) {
  305. return t('File not found at %remote_path nor at %local_path', array(
  306. '%remote_path' => $remote_path,
  307. '%local_path' => $local_path,
  308. ));
  309. }
  310. elseif ($local_path) {
  311. return t('File not found at %local_path', array('%local_path' => $local_path));
  312. }
  313. return t('Translation file location could not be determined.');
  314. }
  315. /**
  316. * Form element callback: After build changes to the language update table.
  317. *
  318. * Adds labels to the languages and removes checkboxes from languages from which
  319. * translation files could not be found.
  320. */
  321. function l10n_update_language_table($form_element) {
  322. // Remove checkboxes of languages without updates.
  323. if ($form_element['#not_found']) {
  324. foreach ($form_element['#not_found'] as $langcode) {
  325. $form_element[$langcode] = array();
  326. }
  327. }
  328. return $form_element;
  329. }
  330. /**
  331. * Returns HTML for translation edit form.
  332. *
  333. * @param array $variables
  334. * An associative array containing:
  335. * - form: The form that contains the language information.
  336. *
  337. * @see l10n_update_edit_form()
  338. * @ingroup themeable
  339. */
  340. function theme_l10n_update_edit_form_strings($variables) {
  341. $output = '';
  342. $form = $variables['form'];
  343. $header = array(
  344. t('Source string'),
  345. t('Translation for @language', array('@language' => $form['#language'])),
  346. );
  347. $rows = array();
  348. foreach (element_children($form) as $lid) {
  349. $string = $form[$lid];
  350. if ($string['plural']['#value']) {
  351. $source = drupal_render($string['original_singular']) . '<br />' . drupal_render($string['original_plural']);
  352. }
  353. else {
  354. $source = drupal_render($string['original']);
  355. }
  356. $source .= empty($string['context']) ? '' : '<br /><small>' . t('In Context') . ':&nbsp;' . $string['context']['#value'] . '</small>';
  357. $rows[] = array(
  358. array('data' => $source),
  359. array('data' => $string['translations']),
  360. );
  361. }
  362. $table = array(
  363. '#theme' => 'table',
  364. '#header' => $header,
  365. '#rows' => $rows,
  366. '#empty' => t('No strings available.'),
  367. '#attributes' => array('class' => array('locale-translate-edit-table')),
  368. );
  369. $output .= drupal_render($table);
  370. $pager = array('#theme' => 'pager');
  371. $output .= drupal_render($pager);
  372. return $output;
  373. }
  374. /**
  375. * Prepares variables for translation status information templates.
  376. *
  377. * Translation status information is displayed per language.
  378. *
  379. * Default template: l10n_update-translation-update-info.tpl.php.
  380. *
  381. * @param array $variables
  382. * An associative array containing:
  383. * - updates: The projects which have updates.
  384. * - not_found: The projects which updates are not found.
  385. *
  386. * @see l10n_update_status_form()
  387. */
  388. function template_preprocess_l10n_update_update_info(&$variables) {
  389. $details = array();
  390. $modules = array();
  391. // Default values
  392. $variables['modules'] = array();
  393. $variables['module_list'] = '';
  394. $details['available_updates_list'] = array();
  395. // Build output for available updates.
  396. if (isset($variables['updates'])) {
  397. $releases = array();
  398. if ($variables['updates']) {
  399. foreach ($variables['updates'] as $update) {
  400. $modules[] = $update['name'];
  401. $releases[] = t('@module (@date)', array('@module' => $update['name'], '@date' => format_date($update['timestamp'], 'html_date')));
  402. }
  403. $variables['modules'] = $modules;
  404. $variables['module_list'] = t('Updates for: @modules', array('@modules' => implode(', ', $modules)));
  405. }
  406. $details['available_updates_list'] = array(
  407. '#theme' => 'item_list',
  408. '#items' => $releases,
  409. );
  410. }
  411. // Build output for updates not found.
  412. if (isset($variables['not_found'])) {
  413. $releases = array();
  414. $variables['missing_updates_status'] = format_plural(count($variables['not_found']), 'Missing translations for one project', 'Missing translations for @count projects');
  415. if ($variables['not_found']) {
  416. foreach ($variables['not_found'] as $update) {
  417. $version = $update['version'] ? $update['version'] : t('no version');
  418. $releases[] = t('@module (@version).', array('@module' => $update['name'], '@version' => $version)) . ' ' . $update['info'];
  419. }
  420. }
  421. $details['missing_updates_list'] = array(
  422. '#theme' => 'item_list',
  423. '#items' => $releases,
  424. );
  425. // Prefix the missing updates list if there is an available updates lists
  426. // before it.
  427. if (!empty($details['missing_updates_list']['#items'])) {
  428. $details['missing_updates_list']['#prefix'] = t('Missing translations for:');
  429. }
  430. }
  431. $variables['details'] = $details;
  432. }
  433. /**
  434. * Prepares variables for most recent translation update templates.
  435. *
  436. * Displays the last time we checked for locale update data. In addition to
  437. * properly formatting the given timestamp, this function also provides a "Check
  438. * manually" link that refreshes the available update and redirects back to the
  439. * same page.
  440. *
  441. * Default template: l10n_update-translation-last-check.tpl.php.
  442. *
  443. * @param $variables
  444. * An associative array containing:
  445. * - last: The timestamp when the site last checked for available updates.
  446. *
  447. * @see l10n_update_status_form()
  448. */
  449. function template_preprocess_l10n_update_last_check(&$variables) {
  450. $last = $variables['last'];
  451. $variables['last_checked'] = $last ? t('Last checked: !time ago', array('!time' => format_interval(REQUEST_TIME - $last))) : t('Last checked: never');
  452. $variables['link'] = l(t('Check manually'), 'admin/config/regional/translate/check');
  453. }