l10n_update.admin.inc 18 KB

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