l10n_update.admin.inc 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560
  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. $form = system_settings_form($form);
  187. $form['#submit'][] = 'l10n_update_admin_settings_form_submit';
  188. return $form;
  189. }
  190. /**
  191. * Additional validation handler for update settings.
  192. *
  193. * Check for existing files directory and creates one when required.
  194. */
  195. function l10n_update_admin_settings_form_validate($form, &$form_state) {
  196. $form_values = $form_state['values'];
  197. if (!empty($form_values['l10n_update_download_store'])) {
  198. if (!file_prepare_directory($form_values['l10n_update_download_store'], FILE_CREATE_DIRECTORY, 'l10n_update_download_store')) {
  199. 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'])));
  200. watchdog('file system', 'The directory %directory does not exist or is not writable.', array('%directory' => $form_values['l10n_update_download_store']), WATCHDOG_ERROR);
  201. }
  202. }
  203. }
  204. /**
  205. * Additional submit handler for update settings.
  206. */
  207. function l10n_update_admin_settings_form_submit($form, &$form_state) {
  208. // Add .htaccess file to the translations directory.
  209. l10n_update_ensure_htaccess();
  210. }
  211. /**
  212. * Get array of import options.
  213. *
  214. * The import options of the Locale module are used but the UI text is altered
  215. * to suit the Localization update cases.
  216. *
  217. * @return
  218. * Keyed array of import options.
  219. */
  220. function _l10n_update_admin_import_options() {
  221. return array(
  222. LOCALE_IMPORT_OVERWRITE => t('Translation updates replace existing ones, new ones are added'),
  223. LOCALE_UPDATE_OVERRIDE_DEFAULT => t('Edited translations are kept, only previously imported ones are overwritten and new translations are added'),
  224. LOCALE_IMPORT_KEEP => t('All existing translations are kept, only new translations are added.'),
  225. );
  226. }
  227. /**
  228. * Get array of check options.
  229. *
  230. * @return
  231. * Keyed array of source download options.
  232. */
  233. function _l10n_update_admin_check_options() {
  234. return array(
  235. L10N_UPDATE_CHECK_ALL => t('Local files and remote server.'),
  236. L10N_UPDATE_CHECK_LOCAL => t('Local files only.'),
  237. L10N_UPDATE_CHECK_REMOTE => t('Remote server only.'),
  238. );
  239. }
  240. /**
  241. * Format project update status.
  242. *
  243. * @param $variables
  244. * An associative array containing:
  245. * - projects: An array containing all enabled projects.
  246. * - languages: An array of all enabled languages.
  247. * - history: An array of the current translations per project.
  248. * - available: An array of translation sources per project.
  249. * - updates: An array of available translation updates per project.
  250. * Only recommended translations are listed.
  251. *
  252. * @return string
  253. * HTML output.
  254. */
  255. function theme_l10n_update_project_status($variables) {
  256. $header = $rows = array();
  257. // Get module and theme data for the project title.
  258. $projects = system_rebuild_module_data();
  259. $projects += system_rebuild_theme_data();
  260. foreach ($variables['projects'] as $name => $project) {
  261. if (isset($variables['history'][$name])) {
  262. if (isset($variables['updates'][$name])) {
  263. $project_status = 'updatable';
  264. $project_class = 'warning';
  265. }
  266. else {
  267. $project_status = 'uptodate';
  268. $project_class = 'ok';
  269. }
  270. }
  271. elseif (isset($variables['available'][$name])) {
  272. $project_status = 'available';
  273. $project_class = 'warning';
  274. }
  275. else {
  276. // Remote information not checked
  277. $project_status = 'unknown';
  278. $project_class = 'unknown';
  279. }
  280. // Get the project title and module version.
  281. $project->title = isset($projects[$name]->info['name']) ? $projects[$name]->info['name'] : '';
  282. $project->module_version = isset($projects[$name]->info['version']) ? $projects[$name]->info['version'] : $project->version;
  283. // Project with related language states.
  284. $row = theme('l10n_update_single_project_wrapper', array(
  285. 'project' => $project,
  286. 'project_status' => $project_status,
  287. 'languages' => $variables['languages'],
  288. 'available' => $variables['available'],
  289. 'history' => $variables['history'],
  290. 'updates' => $variables['updates'],
  291. ));
  292. $rows[$project->project_type][] = array(
  293. 'data' => array(
  294. array(
  295. 'data' => $row,
  296. 'class' => 'l10n-update-wrapper collapsed',
  297. ),
  298. ),
  299. 'class' => array($project_class),
  300. );
  301. }
  302. // Build tables of update states grouped by project type. Similar to the
  303. // status report by the Update module.
  304. $output = '';
  305. $project_types = array(
  306. 'core' => t('Drupal core'),
  307. 'module' => t('Modules'),
  308. 'theme' => t('Themes'),
  309. 'module-disabled' => t('Disabled modules'),
  310. 'theme-disabled' => t('Disabled themes'),
  311. );
  312. foreach ($project_types as $type_name => $type_label) {
  313. if (!empty($rows[$type_name])) {
  314. ksort($rows[$type_name]);
  315. $output .= "\n<h3>" . $type_label . "</h3>\n";
  316. $output .= theme('table', array('header' => $header, 'rows' => $rows[$type_name], 'attributes' => array('class' => array('update l10n-update'))));
  317. }
  318. }
  319. // We use the core update module CSS to re-use the color definitions.
  320. // Plus add our own css and js.
  321. drupal_add_css(drupal_get_path('module', 'update') . '/update.css');
  322. drupal_add_css(drupal_get_path('module', 'l10n_update') . '/css/l10n_update.admin.css');
  323. drupal_add_js('misc/collapse.js');
  324. drupal_add_js(drupal_get_path('module', 'l10n_update') . '/js/l10n_update.js');
  325. return $output;
  326. }
  327. /**
  328. * Format project translation state with states per language.
  329. *
  330. * @param $variables
  331. * An associative array containing:
  332. * - project: Project data object
  333. * - project_status: Project status
  334. * - languages: Available languages.
  335. * @return string
  336. * HTML output.
  337. */
  338. function theme_l10n_update_single_project_wrapper($variables) {
  339. $project = $variables['project'];
  340. $name = $project->name;
  341. $project_status = $variables['project_status'];
  342. $languages = $variables['languages'];
  343. $history = $variables['history'];
  344. $updates = $variables['updates'];
  345. $availables = $variables['available'];
  346. // Output project title and project summary status.
  347. $output = theme('l10n_update_single_project_status', array(
  348. 'project' => $project,
  349. 'server' => l10n_update_server($project->l10n_server),
  350. 'status' => $project_status,
  351. ));
  352. // Translation status per language is displayed in a table, one language per row.
  353. // For each language the current translation is listed. And optionally the
  354. // most recent update.
  355. $rows = array();
  356. foreach ($languages as $lang => $language) {
  357. // Determine current translation status and update status.
  358. $installed = isset($history[$name][$lang]) ? $history[$name][$lang] : NULL;
  359. $update = isset($updates[$name][$lang]) ? $updates[$name][$lang] : NULL;
  360. $available = isset($availables[$name][$lang]) ? $availables[$name][$lang] : NULL;
  361. if ($installed) {
  362. if ($update) {
  363. $status = 'updatable';
  364. $class = 'messages warning';
  365. }
  366. else {
  367. $status = 'uptodate';
  368. $class = 'ok';
  369. }
  370. }
  371. elseif ($available) {
  372. $status = 'available';
  373. $class = 'warning';
  374. }
  375. else {
  376. $status = 'unknown';
  377. $class = 'unknown';
  378. }
  379. // The current translation version.
  380. $row = theme('l10n_update_current_release', array('language' => $language, 'release' => $installed, 'status' => $status));
  381. // If an update is available, add it.
  382. if ($update) {
  383. $row .= theme('l10n_update_available_release', array('release' => $update));
  384. }
  385. $rows[] = array(
  386. 'data' => array($row),
  387. 'class' => array($class),
  388. );
  389. }
  390. // Output tables with translation status per language.
  391. $output .= '<div class="fieldset-wrapper">' . "\n";
  392. $output .= theme('table', array('header' => array(), 'rows' => $rows));
  393. $output .= "</div>\n";
  394. return $output;
  395. }
  396. /**
  397. * Format a single project translation state.
  398. *
  399. * @param $variables
  400. * An associative array containing:
  401. * - project: project data object.
  402. * - server: (optional) remote server data object.
  403. * - status: project summary status.
  404. * @return string
  405. * HTML output.
  406. */
  407. function theme_l10n_update_single_project_status($variables) {
  408. $project = $variables['project'];
  409. $server = $variables['server'];
  410. $title = $project->title ? $project->title : $project->name;
  411. $output = '<div class="project">';
  412. $output .= '<span class="project-title">' . check_plain($title) . '</span>' . ' ' . check_plain($project->module_version) ;
  413. if ($server = l10n_update_server($project->l10n_server)) {
  414. $output .= '<span class="project-server">' . t('(translation source: !server)', array('!server' => l($server['name'], $server['link']))) . '</span>';
  415. }
  416. $output .= theme('l10n_update_version_status', array('status' => $variables['status']));
  417. $output .= "</div>\n";
  418. return $output;
  419. }
  420. /**
  421. * Format current translation version.
  422. *
  423. * @param $variables
  424. * An associative array containing:
  425. * - language: Language name.
  426. * - release: Current file data.
  427. * - status: Release status.
  428. * @return string
  429. * HTML output.
  430. */
  431. function theme_l10n_update_current_release($variables) {
  432. if (isset($variables['release'])) {
  433. $date = $variables['release']->timestamp;
  434. $version = $variables['release']->version;
  435. $text = t('@language: @version (!date)', array('@language' => $variables['language'], '@version' => $version, '!date' => format_date($date, 'custom', 'Y-M-d')));
  436. }
  437. else {
  438. $text = t('@language: <em>No installed translation</em>', array('@language' => $variables['language']));
  439. }
  440. $output = '<div class="language">';
  441. $output .= $text;
  442. $output .= theme('l10n_update_version_status', $variables);
  443. $output .= "</div>\n";
  444. return $output;
  445. }
  446. /**
  447. * Format current translation version.
  448. *
  449. * @param object $release
  450. * Update file data.
  451. * @return string
  452. * HTML output.
  453. */
  454. function theme_l10n_update_available_release($variables) {
  455. $date = $variables['release']->timestamp;
  456. $version = $variables['release']->version;
  457. if (!empty($variables['release']->fileurl)) {
  458. // Remote file, straight link
  459. $link = l(t('Download'), $variables['release']->fileurl);
  460. }
  461. elseif (!empty($variables['release']->uri)) {
  462. // Local file, try something
  463. $link = l(t('Download'), $variables['release']->uri, array('absolute' => TRUE));
  464. }
  465. $output = '<div class="version version-recommended">';
  466. $output .= t('Recommended version: @version (!date)', array('@version' => $version, '!date' => format_date($date, 'custom', 'Y-M-d')));
  467. $output .= '<span class="version-links">' . $link . '</span>';
  468. $output .= "</div>\n";
  469. return $output;
  470. }
  471. /**
  472. * Format version status with icon.
  473. *
  474. * @param string $status
  475. * Version status: 'uptodate', 'updatable', 'available', 'unknown'.
  476. * @param string $type
  477. * Update type: 'download', 'localfile'.
  478. *
  479. * @return sting
  480. * HTML output.
  481. */
  482. function theme_l10n_update_version_status($variables) {
  483. $icon = '';
  484. $msg = '';
  485. switch ($variables['status']) {
  486. case 'uptodate':
  487. $icon = theme('image', array('path' => 'misc/watchdog-ok.png', 'alt' => t('ok'), 'title' => t('ok')));
  488. $msg = '<span class="current">' . t('Up to date') . '</span>';
  489. break;
  490. case 'updatable':
  491. $icon = theme('image', array('path' => 'misc/watchdog-warning.png', 'alt' => t('warning'), 'title' => t('warning')));
  492. $msg = '<span class="not-current">' . t('Update available') . '</span>';
  493. break;
  494. case 'available':
  495. $icon = theme('image', array('path' => 'misc/watchdog-warning.png', 'alt' => t('warning'), 'title' => t('warning')));
  496. $msg = '<span class="not-current">' . t('Uninstalled translation available') . '</span>';
  497. break;
  498. case 'unknown':
  499. $icon = theme('image', array('path' => 'misc/watchdog-warning.png', 'alt' => t('warning'), 'title' => t('warning')));
  500. $msg = '<span class="not-supported">' . t('No available translations found') . '</span>';
  501. break;
  502. }
  503. $output = '<div class="version-status">';
  504. $output .= $msg;
  505. $output .= '<span class="icon">' . $icon . '</span>';
  506. $output .= "</div>\n";
  507. return $output;
  508. }