l10n_update.admin.inc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
  1. <?php
  2. /**
  3. * @file
  4. * Admin settings and update page.
  5. */
  6. /**
  7. * Project has a new release available.
  8. */
  9. define('L10N_UPDATE_NOT_CURRENT', 4);
  10. /**
  11. * Project is up to date.
  12. */
  13. define('L10N_UPDATE_CURRENT', 5);
  14. /**
  15. * Project's status cannot be checked.
  16. */
  17. define('L10N_UPDATE_NOT_CHECKED', -1);
  18. /**
  19. * No available update data was found for project.
  20. */
  21. define('L10N_UPDATE_UNKNOWN', -2);
  22. /**
  23. * There was a failure fetching available update data for this project.
  24. */
  25. define('L10N_UPDATE_NOT_FETCHED', -3);
  26. // Include l10n_update API
  27. module_load_include('check.inc', 'l10n_update');
  28. // And project api
  29. module_load_include('project.inc', 'l10n_update');
  30. /**
  31. * Page callback: Admin overview page.
  32. */
  33. function l10n_update_admin_overview() {
  34. // For now we get package information provided by modules.
  35. $projects = l10n_update_get_projects();
  36. $languages = l10n_update_language_list('name');
  37. $build = array();
  38. if ($languages) {
  39. $history = l10n_update_get_history();
  40. $available = l10n_update_available_releases();
  41. $updates = l10n_update_build_updates($history, $available);
  42. $build['project_status'] = array(
  43. '#theme' => 'l10n_update_project_status',
  44. '#projects' => $projects,
  45. '#languages' => $languages,
  46. '#history' => $history,
  47. '#available' => $available,
  48. '#updates' => $updates,
  49. );
  50. $build['admin_import_form'] = drupal_get_form('l10n_update_admin_import_form', $projects, $updates);
  51. }
  52. else {
  53. $build['no_projects'] = array('#markup' => t('No projects or languages to update.'));
  54. }
  55. return $build;
  56. }
  57. /**
  58. * Translation update form.
  59. *
  60. * @todo selectable packages
  61. * @todo check language support in server
  62. * @todo check file update dates
  63. *
  64. * @param $form_state
  65. * Form states array.
  66. * @param $projects
  67. * @todo $projects are not used in the form.
  68. * @param $updates
  69. * Updates to be displayed in the form.
  70. */
  71. function l10n_update_admin_import_form($form, $form_state, $projects, $updates) {
  72. //module_load_include('inc', 'l10n_update');
  73. // For now we get package information provided by modules
  74. $projects = l10n_update_get_projects();
  75. $languages = l10n_update_language_list('name');
  76. // Absence of projects is an error and only occurs if the database table
  77. // was truncated. In this case we rebuild the project data.
  78. if (!$projects) {
  79. l10n_update_build_projects();
  80. $projects = l10n_update_get_projects();
  81. }
  82. if ($projects && $languages) {
  83. $form['updates'] = array(
  84. '#type' => 'value',
  85. '#value' => $updates,
  86. );
  87. // @todo Only show this language fieldset if we have more than 1 language.
  88. $form['lang'] = array(
  89. '#type' => 'fieldset',
  90. '#title' => t('Languages'),
  91. '#collapsible' => TRUE,
  92. '#collapsed' => TRUE,
  93. '#description' => t('Select one or more languages to download and update. If you select none, all of them will be updated.'),
  94. );
  95. $form['lang']['languages'] = array(
  96. '#type' => 'checkboxes',
  97. '#options' => $languages,
  98. );
  99. $form['mode'] = array(
  100. '#type' => 'radios',
  101. '#title' => t('Update mode'),
  102. '#default_value' => variable_get('l10n_update_import_mode', LOCALE_IMPORT_KEEP),
  103. '#options' => _l10n_update_admin_import_options(),
  104. );
  105. $form['buttons']['download'] = array(
  106. '#type' => 'submit',
  107. '#value' => t('Update translations'),
  108. );
  109. }
  110. $form['buttons']['refresh'] = array(
  111. '#type' => 'submit',
  112. '#value' => t('Refresh information'),
  113. );
  114. return $form;
  115. }
  116. /**
  117. * Submit handler for Update form.
  118. *
  119. * Handles both submit buttons to update translations and to update the
  120. * form information.
  121. */
  122. function l10n_update_admin_import_form_submit($form, $form_state) {
  123. $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : '';
  124. $projects = l10n_update_get_projects();
  125. if ($op == t('Update translations')) {
  126. $languages = array_filter($form_state['values']['languages']);
  127. $updates = $form_state['values']['updates'];
  128. $mode = $form_state['values']['mode'];
  129. if ($projects && $updates) {
  130. module_load_include('batch.inc', 'l10n_update');
  131. // Filter out updates in other languages. If no languages, all of them will be updated
  132. $updates = _l10n_update_prepare_updates($updates, NULL, $languages);
  133. $batch = l10n_update_batch_multiple($updates, $mode);
  134. batch_set($batch);
  135. }
  136. else {
  137. drupal_set_message(t('Cannot find any translation updates.'), 'error');
  138. }
  139. }
  140. elseif ($op == t('Refresh information')) {
  141. // Get current version of projects.
  142. l10n_update_build_projects();
  143. // Get available translation updates and update file history.
  144. if ($available = l10n_update_available_releases(TRUE)) {
  145. l10n_update_flag_history($available);
  146. drupal_set_message(t('Fetched information about available updates from the server'));
  147. }
  148. else {
  149. drupal_set_message(t('Failed to fetch information about available updates from the server.'), 'error');
  150. }
  151. }
  152. }
  153. /**
  154. * Page callback: Settings form.
  155. */
  156. function l10n_update_admin_settings_form($form, &$form_state) {
  157. $form['l10n_update_check_mode'] = array(
  158. '#type' => 'radios',
  159. '#title' => t('Update source'),
  160. '#default_value' => variable_get('l10n_update_check_mode', L10N_UPDATE_CHECK_ALL),
  161. '#options' => _l10n_update_admin_check_options(),
  162. );
  163. $form['l10n_update_import_mode'] = array(
  164. '#type' => 'radios',
  165. '#title' => t('Update mode'),
  166. '#default_value' => variable_get('l10n_update_import_mode', LOCALE_IMPORT_KEEP),
  167. '#options' => _l10n_update_admin_import_options(),
  168. );
  169. $form['l10n_update_check_frequency'] = array(
  170. '#type' => 'radios',
  171. '#title' => t('Check for updates'),
  172. '#default_value' => variable_get('l10n_update_check_frequency', 0),
  173. '#options' => array(
  174. 0 => t('Never (manually)'),
  175. 1 => t('Daily'),
  176. 7 => t('Weekly'),
  177. ),
  178. '#description' => t('Select how frequently you want to automatically check for updated translations for installed modules and themes.'),
  179. );
  180. $form['l10n_update_check_disabled'] = array(
  181. '#type' => 'checkbox',
  182. '#title' => t('Check for updates of disabled modules and themes'),
  183. '#default_value' => variable_get('l10n_update_check_disabled', 0),
  184. '#description' => t('Note that this comes with a performance penalty, so it is not recommended.'),
  185. );
  186. $form['l10n_update_download_store'] = array(
  187. '#title' => t('Store downloaded files'),
  188. '#type' => 'textfield',
  189. '#default_value' => variable_get('l10n_update_download_store', ''),
  190. '#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. If left empty the downloaded translation will not be saved.'),
  191. );
  192. return system_settings_form($form);
  193. }
  194. /**
  195. * Additional validation handler for update settings.
  196. *
  197. * Check for existing files directory and creates one when required.
  198. */
  199. function l10n_update_admin_settings_form_validate($form, &$form_state) {
  200. $form_values = $form_state['values'];
  201. if (!empty($form_values['l10n_update_download_store'])) {
  202. if (!file_prepare_directory($form_values['l10n_update_download_store'], FILE_CREATE_DIRECTORY, 'l10n_update_download_store')) {
  203. form_set_error('l10n_update_download_store', t('The directory %directory does not exist or is not writable.', array('%directory' => $form_values['l10n_update_download_store'])));
  204. watchdog('file system', 'The directory %directory does not exist or is not writable.', array('%directory' => $form_values['l10n_update_download_store']), WATCHDOG_ERROR);
  205. }
  206. }
  207. }
  208. /**
  209. * Get array of import options.
  210. *
  211. * The import options of the Locale module are used but the UI text is altered
  212. * to suit the Localization update cases.
  213. *
  214. * @return
  215. * Keyed array of import options.
  216. */
  217. function _l10n_update_admin_import_options() {
  218. return array(
  219. LOCALE_IMPORT_OVERWRITE => t('Translation updates replace existing ones, new ones are added'),
  220. LOCALE_UPDATE_OVERRIDE_DEFAULT => t('Edited translations are kept, only previously imported ones are overwritten and new translations are added'),
  221. LOCALE_IMPORT_KEEP => t('All existing translations are kept, only new translations are added.'),
  222. );
  223. }
  224. /**
  225. * Get array of check options.
  226. *
  227. * @return
  228. * Keyed array of source download options.
  229. */
  230. function _l10n_update_admin_check_options() {
  231. return array(
  232. L10N_UPDATE_CHECK_ALL => t('Local files and remote server.'),
  233. L10N_UPDATE_CHECK_LOCAL => t('Local files only.'),
  234. L10N_UPDATE_CHECK_REMOTE => t('Remote server only.'),
  235. );
  236. }
  237. /**
  238. * Format project update status.
  239. *
  240. * @param $variables
  241. * An associative array containing:
  242. * - projects: An array containing all enabled projects.
  243. * - languages: An array of all enabled languages.
  244. * - history: An array of the current translations per project.
  245. * - available: An array of translation sources per project.
  246. * - updates: An array of available translation updates per project.
  247. * Only recommended translations are listed.
  248. *
  249. * @return string
  250. * HTML output.
  251. */
  252. function theme_l10n_update_project_status($variables) {
  253. $header = $rows = array();
  254. // Get module and theme data for the project title.
  255. $projects = system_rebuild_module_data();
  256. $projects += system_rebuild_theme_data();
  257. foreach ($variables['projects'] as $name => $project) {
  258. if (isset($variables['history'][$name])) {
  259. if (isset($variables['updates'][$name])) {
  260. $project_status = 'updatable';
  261. $project_class = 'warning';
  262. }
  263. else {
  264. $project_status = 'uptodate';
  265. $project_class = 'ok';
  266. }
  267. }
  268. elseif (isset($variables['available'][$name])) {
  269. $project_status = 'available';
  270. $project_class = 'warning';
  271. }
  272. else {
  273. // Remote information not checked
  274. $project_status = 'unknown';
  275. $project_class = 'unknown';
  276. }
  277. // Get the project title and module version.
  278. $project->title = isset($projects[$name]->info['name']) ? $projects[$name]->info['name'] : '';
  279. $project->module_version = isset($projects[$name]->info['version']) ? $projects[$name]->info['version'] : $project->version;
  280. // Project with related language states.
  281. $row = theme('l10n_update_single_project_wrapper', array(
  282. 'project' => $project,
  283. 'project_status' => $project_status,
  284. 'languages' => $variables['languages'],
  285. 'available' => $variables['available'],
  286. 'history' => $variables['history'],
  287. 'updates' => $variables['updates'],
  288. ));
  289. $rows[$project->project_type][] = array(
  290. 'data' => array(
  291. array(
  292. 'data' => $row,
  293. 'class' => 'l10n-update-wrapper collapsed',
  294. ),
  295. ),
  296. 'class' => array($project_class),
  297. );
  298. }
  299. // Build tables of update states grouped by project type. Similar to the
  300. // status report by the Update module.
  301. $output = '';
  302. $project_types = array(
  303. 'core' => t('Drupal core'),
  304. 'module' => t('Modules'),
  305. 'theme' => t('Themes'),
  306. 'module-disabled' => t('Disabled modules'),
  307. 'theme-disabled' => t('Disabled themes'),
  308. );
  309. foreach ($project_types as $type_name => $type_label) {
  310. if (!empty($rows[$type_name])) {
  311. ksort($rows[$type_name]);
  312. $output .= "\n<h3>" . $type_label . "</h3>\n";
  313. $output .= theme('table', array('header' => $header, 'rows' => $rows[$type_name], 'attributes' => array('class' => array('update l10n-update'))));
  314. }
  315. }
  316. // We use the core update module CSS to re-use the color definitions.
  317. // Plus add our own css and js.
  318. drupal_add_css(drupal_get_path('module', 'update') . '/update.css');
  319. drupal_add_css(drupal_get_path('module', 'l10n_update') . '/css/l10n_update.admin.css');
  320. drupal_add_js(drupal_get_path('module', 'l10n_update') . '/js/l10n_update.js');
  321. return $output;
  322. }
  323. /**
  324. * Format project translation state with states per language.
  325. *
  326. * @param $variables
  327. * An associative array containing:
  328. * - project: Project data object
  329. * - project_status: Project status
  330. * - languages: Available languages.
  331. * @return string
  332. * HTML output.
  333. */
  334. function theme_l10n_update_single_project_wrapper($variables) {
  335. $project = $variables['project'];
  336. $name = $project->name;
  337. $project_status = $variables['project_status'];
  338. $languages = $variables['languages'];
  339. $history = $variables['history'];
  340. $updates = $variables['updates'];
  341. $availables = $variables['available'];
  342. // Output project title and project summary status.
  343. $output = theme('l10n_update_single_project_status', array(
  344. 'project' => $project,
  345. 'server' => l10n_update_server($project->l10n_server),
  346. 'status' => $project_status,
  347. ));
  348. // Translation status per language is displayed in a table, one language per row.
  349. // For each language the current translation is listed. And optionally the
  350. // most recent update.
  351. $rows = array();
  352. foreach ($languages as $lang => $language) {
  353. // Determine current translation status and update status.
  354. $installed = isset($history[$name][$lang]) ? $history[$name][$lang] : NULL;
  355. $update = isset($updates[$name][$lang]) ? $updates[$name][$lang] : NULL;
  356. $available = isset($availables[$name][$lang]) ? $availables[$name][$lang] : NULL;
  357. if ($installed) {
  358. if ($update) {
  359. $status = 'updatable';
  360. $class = 'messages warning';
  361. }
  362. else {
  363. $status = 'uptodate';
  364. $class = 'ok';
  365. }
  366. }
  367. elseif ($available) {
  368. $status = 'available';
  369. $class = 'warning';
  370. }
  371. else {
  372. $status = 'unknown';
  373. $class = 'unknown';
  374. }
  375. // The current translation version.
  376. $row = theme('l10n_update_current_release', array('language' => $language, 'release' => $installed, 'status' => $status));
  377. // If an update is available, add it.
  378. if ($update) {
  379. $row .= theme('l10n_update_available_release', array('release' => $update));
  380. }
  381. $rows[] = array(
  382. 'data' => array($row),
  383. 'class' => array($class),
  384. );
  385. }
  386. // Output tables with translation status per language.
  387. $output .= '<div class="fieldset-wrapper">' . "\n";
  388. $output .= theme('table', array('header' => array(), 'rows' => $rows));
  389. $output .= "</div>\n";
  390. return $output;
  391. }
  392. /**
  393. * Format a single project translation state.
  394. *
  395. * @param $variables
  396. * An associative array containing:
  397. * - project: project data object.
  398. * - server: (optional) remote server data object.
  399. * - status: project summary status.
  400. * @return string
  401. * HTML output.
  402. */
  403. function theme_l10n_update_single_project_status($variables) {
  404. $project = $variables['project'];
  405. $server = $variables['server'];
  406. $title = $project->title ? $project->title : $project->name;
  407. $output = '<div class="project">';
  408. $output .= '<span class="project-title">' . check_plain($title) . '</span>' . ' ' . check_plain($project->module_version) ;
  409. if ($server = l10n_update_server($project->l10n_server)) {
  410. $output .= '<span class="project-server">' . t('(translation source: !server)', array('!server' => l($server['name'], $server['link']))) . '</span>';
  411. }
  412. $output .= theme('l10n_update_version_status', array('status' => $variables['status']));
  413. $output .= "</div>\n";
  414. return $output;
  415. }
  416. /**
  417. * Format current translation version.
  418. *
  419. * @param $variables
  420. * An associative array containing:
  421. * - language: Language name.
  422. * - release: Current file data.
  423. * - status: Release status.
  424. * @return string
  425. * HTML output.
  426. */
  427. function theme_l10n_update_current_release($variables) {
  428. if (isset($variables['release'])) {
  429. $date = $variables['release']->timestamp;
  430. $version = $variables['release']->version;
  431. $text = t('@language: @version (!date)', array('@language' => $variables['language'], '@version' => $version, '!date' => format_date($date, 'custom', 'Y-M-d')));
  432. }
  433. else {
  434. $text = t('@language: <em>No installed translation</em>', array('@language' => $variables['language']));
  435. }
  436. $output = '<div class="language">';
  437. $output .= $text;
  438. $output .= theme('l10n_update_version_status', $variables);
  439. $output .= "</div>\n";
  440. return $output;
  441. }
  442. /**
  443. * Format current translation version.
  444. *
  445. * @param object $release
  446. * Update file data.
  447. * @return string
  448. * HTML output.
  449. */
  450. function theme_l10n_update_available_release($variables) {
  451. $date = $variables['release']->timestamp;
  452. $version = $variables['release']->version;
  453. if (!empty($variables['release']->fileurl)) {
  454. // Remote file, straight link
  455. $link = l(t('Download'), $variables['release']->fileurl);
  456. }
  457. elseif (!empty($variables['release']->uri)) {
  458. // Local file, try something
  459. $link = l(t('Download'), $variables['release']->uri, array('absolute' => TRUE));
  460. }
  461. $output = '<div class="version version-recommended">';
  462. $output .= t('Recommended version: @version (!date)', array('@version' => $version, '!date' => format_date($date, 'custom', 'Y-M-d')));
  463. $output .= '<span class="version-links">' . $link . '</span>';
  464. $output .= "</div>\n";
  465. return $output;
  466. }
  467. /**
  468. * Format version status with icon.
  469. *
  470. * @param string $status
  471. * Version status: 'uptodate', 'updatable', 'available', 'unknown'.
  472. * @param string $type
  473. * Update type: 'download', 'localfile'.
  474. *
  475. * @return sting
  476. * HTML output.
  477. */
  478. function theme_l10n_update_version_status($variables) {
  479. $icon = '';
  480. $msg = '';
  481. switch ($variables['status']) {
  482. case 'uptodate':
  483. $icon = theme('image', array('path' => 'misc/watchdog-ok.png', 'alt' => t('ok'), 'title' => t('ok')));
  484. $msg = '<span class="current">' . t('Up to date') . '</span>';
  485. break;
  486. case 'updatable':
  487. $icon = theme('image', array('path' => 'misc/watchdog-warning.png', 'alt' => t('warning'), 'title' => t('warning')));
  488. $msg = '<span class="not-current">' . t('Update available') . '</span>';
  489. break;
  490. case 'available':
  491. $icon = theme('image', array('path' => 'misc/watchdog-warning.png', 'alt' => t('warning'), 'title' => t('warning')));
  492. $msg = '<span class="not-current">' . t('Uninstalled translation available') . '</span>';
  493. break;
  494. case 'unknown':
  495. $icon = theme('image', array('path' => 'misc/watchdog-warning.png', 'alt' => t('warning'), 'title' => t('warning')));
  496. $msg = '<span class="not-supported">' . t('No available translations found') . '</span>';
  497. break;
  498. }
  499. $output = '<div class="version-status">';
  500. $output .= $msg;
  501. $output .= '<span class="icon">' . $icon . '</span>';
  502. $output .= "</div>\n";
  503. return $output;
  504. }