diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index d3e05dc..74f04d8 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -1367,3 +1367,19 @@ function _locale_rebuild_js($langcode = NULL) { return TRUE; } } +/** + * Form element callback: After build changes to the language update table. + * + * Adds labels to the languages and removes checkboxes from languages from which + * translation files could not be found. + */ +function locale_translation_language_table($form_element) { + // Remove checkboxes of languages without updates. + if ($form_element['#not_found']) { + foreach ($form_element['#not_found'] as $langcode) { + $form_element[$langcode] = array(); + } + } + return $form_element; +} + diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc index c35e33a..b07a0e3 100644 --- a/core/modules/locale/locale.pages.inc +++ b/core/modules/locale/locale.pages.inc @@ -38,234 +38,6 @@ function locale_translation_manual_status() { } /** - * Page callback: Display the current translation status. - * - * @see locale_menu() - * - * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. - * Use \Drupal\locale\Form\LocaleForm::status(). - */ -function locale_translation_status_form($form, &$form_state) { - module_load_include('translation.inc', 'locale'); - module_load_include('compare.inc', 'locale'); - $updates = $options = array(); - $languages_update = $languages_not_found = array(); - $projects_update = array(); - - // @todo Calling locale_translation_build_projects() is an expensive way to - // get a module name. In follow-up issue http://drupal.org/node/1842362 - // the project name will be stored to display use, like here. - $project_data = locale_translation_build_projects(); - $languages = locale_translatable_language_list(); - $status = locale_translation_get_status(); - - // Prepare information about projects which have available translation - // updates. - if ($languages && $status) { - foreach ($status as $project) { - foreach ($project as $langcode => $project_info) { - // No translation file found for this project-language combination. - if (empty($project_info->type)) { - $updates[$langcode]['not_found'][] = array( - 'name' => $project_info->name == 'drupal' ? t('Drupal core') : $project_data[$project_info->name]->info['name'], - 'version' => $project_info->version, - 'info' => _locale_translation_status_debug_info($project_info), - ); - $languages_not_found[$langcode] = $langcode; - } - // Translation update found for this project-language combination. - elseif ($project_info->type == LOCALE_TRANSLATION_LOCAL || $project_info->type == LOCALE_TRANSLATION_REMOTE ) { - $local = isset($project_info->files[LOCALE_TRANSLATION_LOCAL]) ? $project_info->files[LOCALE_TRANSLATION_LOCAL] : NULL; - $remote = isset($project_info->files[LOCALE_TRANSLATION_REMOTE]) ? $project_info->files[LOCALE_TRANSLATION_REMOTE] : NULL; - $recent = _locale_translation_source_compare($local, $remote) == LOCALE_TRANSLATION_SOURCE_COMPARE_LT ? $remote : $local; - $updates[$langcode]['updates'][] = array( - 'name' => $project_data[$project_info->name]->info['name'], - 'version' => $project_info->version, - 'timestamp' => $recent->timestamp, - ); - $languages_update[$langcode] = $langcode; - $projects_update[$project_info->name] = $project_info->name; - } - } - } - $languages_not_found = array_diff($languages_not_found, $languages_update); - - // Build data options for the select table. - foreach($updates as $langcode => $update) { - $title = String::checkPlain($languages[$langcode]->name); - $locale_translation_update_info = array('#theme' => 'locale_translation_update_info'); - foreach (array('updates', 'not_found') as $update_status) { - if (isset($update[$update_status])) { - $locale_translation_update_info['#' . $update_status] = $update[$update_status]; - } - } - $options[$langcode] = array( - 'title' => array( - 'class' => array('label'), - 'data' => array( - '#title' => $title, - '#markup' => $title - ), - ), - 'status' => array('class' => array('description', 'expand', 'priority-low'), 'data' => drupal_render($locale_translation_update_info)), - ); - } - // Sort the table data on language name. - uasort($options, function ($a, $b) { - return strcasecmp($a['title']['data']['#title'], $b['title']['data']['#title']); - }); - } - - $last_checked = \Drupal::state()->get('locale.translation_last_checked'); - $form['last_checked'] = array( - '#theme' => 'locale_translation_last_check', - '#last' => $last_checked, - ); - - $header = array( - 'title' => array( - 'data' => t('Language'), - 'class' => array('title'), - ), - 'status' => array( - 'data' => t('Status'), - 'class' => array('status', 'priority-low'), - ), - ); - - if (!$languages) { - $empty = t('No translatable languages available. Add a language first.', array('@add_language' => url('admin/config/regional/language'))); - } - elseif ($status) { - $empty = t('All translations up to date.'); - } - else { - $empty = t('No translation status available. Check manually.', array('@check' => url('admin/reports/translations/check'))); - } - - // The projects which require an update. Used by the _submit callback. - $form['projects_update'] = array( - '#type' => 'value', - '#value' => $projects_update, - ); - - $form['langcodes'] = array( - '#type' => 'tableselect', - '#header' => $header, - '#options' => $options, - '#default_value' => $languages_update, - '#empty' => $empty, - '#js_select' => TRUE, - '#multiple' => TRUE, - '#required' => TRUE, - '#not_found' => $languages_not_found, - '#after_build' => array('locale_translation_language_table'), - ); - - $form['#attached']['library'][] = 'locale/drupal.locale.admin'; - - $form['actions'] = array('#type' => 'actions'); - if ($languages_update) { - $form['actions']['submit'] = array( - '#type' => 'submit', - '#value' => t('Update translations'), - ); - } - - return $form; -} - -/** - * Form validation handler for locale_translation_status_form(). - */ -function locale_translation_status_form_validate($form, &$form_state) { - // Check if a language has been selected. 'tableselect' doesn't. - if (!array_filter($form_state['values']['langcodes'])) { - form_set_error('', $form_state, t('Select a language to update.')); - } -} - -/** - * Form submission handler for locale_translation_status_form(). - */ -function locale_translation_status_form_submit($form, &$form_state) { - module_load_include('fetch.inc', 'locale'); - $langcodes = array_filter($form_state['values']['langcodes']); - $projects = array_filter($form_state['values']['projects_update']); - - // Set the translation import options. This determines if existing - // translations will be overwritten by imported strings. - $options = _locale_translation_default_update_options(); - - // If the status was updated recently we can immediately start fetching the - // translation updates. If the status is expired we clear it an run a batch to - // update the status and then fetch the translation updates. - $last_checked = \Drupal::state()->get('locale.translation_last_checked'); - if ($last_checked < REQUEST_TIME - LOCALE_TRANSLATION_STATUS_TTL) { - locale_translation_clear_status(); - $batch = locale_translation_batch_update_build(array(), $langcodes, $options); - batch_set($batch); - } - else { - $batch = locale_translation_batch_fetch_build($projects, $langcodes, $options); - batch_set($batch); - } -} - -/** - * Form element callback: After build changes to the language update table. - * - * Adds labels to the languages and removes checkboxes from languages from which - * translation files could not be found. - */ -function locale_translation_language_table($form_element) { - // Remove checkboxes of languages without updates. - if ($form_element['#not_found']) { - foreach ($form_element['#not_found'] as $langcode) { - $form_element[$langcode] = array(); - } - } - return $form_element; -} - -/** - * Provides debug info for projects in case translation files are not found. - * - * Translations files are being fetched either from Drupal translation server - * and local files or only from the local filesystem depending on the - * "Translation source" setting at admin/config/regional/translate/settings. - * This method will produce debug information including the respective path(s) - * based on this setting. - * - * Translations for development versions are never fetched, so the debug info - * for that is a fixed message. - * - * @param array $source - * An array which is the project information of the source. - * - * @return string - * The string which contains debug information. - */ -function _locale_translation_status_debug_info($source) { - $remote_path = isset($source->files['remote']->uri) ? $source->files['remote']->uri : ''; - $local_path = isset($source->files['local']->uri) ? $source->files['local']->uri : ''; - - if (strpos($source->version, 'dev') !== FALSE) { - return t('No translation files are provided for development releases.'); - } - if (locale_translation_use_remote_source() && $remote_path && $local_path) { - return t('File not found at %remote_path nor at %local_path', array( - '%remote_path' => $remote_path, - '%local_path' => $local_path, - )); - } - elseif ($local_path) { - return t('File not found at %local_path', array('%local_path' => $local_path)); - } - return t('Translation file location could not be determined.'); -} - -/** * Returns HTML for translation edit form. * * @param array $variables diff --git a/core/modules/locale/locale.routing.yml b/core/modules/locale/locale.routing.yml index a5703d8..9c3936c 100644 --- a/core/modules/locale/locale.routing.yml +++ b/core/modules/locale/locale.routing.yml @@ -40,7 +40,7 @@ locale.translate_export: locale.translate_status: path: '/admin/reports/translations' defaults: - _content: '\Drupal\locale\Form\LocaleForm::status' + _form: '\Drupal\locale\Form\TranslationStatusForm' _title: 'Available translation updates' requirements: _permission: 'translate interface' diff --git a/core/modules/locale/src/Form/TranslationStatusForm.php b/core/modules/locale/src/Form/TranslationStatusForm.php new file mode 100644 index 0000000..74bff5c --- /dev/null +++ b/core/modules/locale/src/Form/TranslationStatusForm.php @@ -0,0 +1,301 @@ +get('module_handler'), + $container->get('state') + ); + } + + /** + * Constructs a TranslationStatusForm object. + * + * @param ModuleHandlerInterface $module_handler + * A module handler. + * @param \Drupal\Core\State\StateInterface $state + * The state service. + */ + public function __construct(ModuleHandlerInterface $module_handler, StateInterface $state) { + $this->moduleHandler = $module_handler; + $this->state = $state; + } + + /** + * {@inheritdoc} + */ + public function getFormID() { + return 'locale_translation_status_form'; + } + + /** + * Form builder for displaying the current translation status. + * + * @ingroup forms + */ + public function buildForm(array $form, array &$form_state) { + $languages = locale_translatable_language_list(); + $status = locale_translation_get_status(); + $options = array(); + $languages_update = array(); + $languages_not_found = array(); + $projects_update = array(); + // Prepare information about projects which have available translation + // updates. + if ($languages && $status) { + $updates = $this->prepareUpdateData($status); + + // Build data options for the select table. + foreach ($updates as $langcode => $update) { + $title = String::checkPlain($languages[$langcode]->name); + $locale_translation_update_info = array('#theme' => 'locale_translation_update_info'); + foreach (array('updates', 'not_found') as $update_status) { + if (isset($update[$update_status])) { + $locale_translation_update_info['#' . $update_status] = $update[$update_status]; + } + } + $options[$langcode] = array( + 'title' => array( + 'class' => array('label'), + 'data' => array( + '#title' => $title, + '#markup' => $title, + ), + ), + 'status' => array( + 'class' => array('description', 'expand', 'priority-low'), + 'data' => drupal_render($locale_translation_update_info), + ), + ); + if (!empty($update['not_found'])) { + $languages_not_found[$langcode] = $langcode; + } + elseif (!empty($update['updates'])) { + $languages_update[$langcode] = $langcode; + } + } + // Sort the table data on language name. + uasort($options, function ($a, $b) { + return strcasecmp($a['title']['data']['#title'], $b['title']['data']['#title']); + }); + $languages_not_found = array_diff($languages_not_found, $languages_update); + } + + $last_checked = $this->state->get('locale.translation_last_checked'); + $form['last_checked'] = array( + '#theme' => 'locale_translation_last_check', + '#last' => $last_checked, + ); + + $header = array( + 'title' => array( + 'data' => $this->t('Language'), + 'class' => array('title'), + ), + 'status' => array( + 'data' => $this->t('Status'), + 'class' => array('status', 'priority-low'), + ), + ); + + if (!$languages) { + $empty = $this->t('No translatable languages available. Add a language first.', array( + '@add_language' => $this->url('language.admin_overview'), + )); + } + elseif ($status) { + $empty = $this->t('All translations up to date.'); + } + else { + $empty = $this->t('No translation status available. Check manually.', array( + '@check' => $this->url('locale.check_translation'), + )); + } + + // The projects which require an update. Used by the _submit callback. + $form['projects_update'] = array( + '#type' => 'value', + '#value' => $projects_update, + ); + + $form['langcodes'] = array( + '#type' => 'tableselect', + '#header' => $header, + '#options' => $options, + '#default_value' => $languages_update, + '#empty' => $empty, + '#js_select' => TRUE, + '#multiple' => TRUE, + '#required' => TRUE, + '#not_found' => $languages_not_found, + '#after_build' => array('locale_translation_language_table'), + ); + + $form['#attached']['library'][] = 'locale/drupal.locale.admin'; + $form['#attached']['css'] = array(drupal_get_path('module', 'locale') . '/css/locale.admin.css'); + + $form['actions'] = array('#type' => 'actions'); + if ($languages_update) { + $form['actions']['submit'] = array( + '#type' => 'submit', + '#value' => $this->t('Update translations'), + ); + } + + return $form; + } + + /** + * Prepare information about projects with available translation updates. + * + * @param array $status + * Translation update status as an array keyed by Project ID and langcode. + * + * @return array + * Translation update status as an array keyed by language code and + * translation update status. + */ + protected function prepareUpdateData(array $status) { + $updates = array(); + + // @todo Calling locale_translation_build_projects() is an expensive way to + // get a module name. In follow-up issue http://drupal.org/node/1842362 + // the project name will be stored to display use, like here. + $this->moduleHandler->loadInclude('locale', 'compare.inc'); + $project_data = locale_translation_build_projects(); + + foreach ($status as $project_id => $project) { + foreach ($project as $langcode => $project_info) { + // No translation file found for this project-language combination. + if (empty($project_info->type)) { + $updates[$langcode]['not_found'][] = array( + 'name' => $project_info->name == 'drupal' ? $this->t('Drupal core') : $project_data[$project_info->name]->info['name'], + 'version' => $project_info->version, + 'info' => $this->createInfoString($project_info), + ); + } + // Translation update found for this project-language combination. + elseif ($project_info->type == LOCALE_TRANSLATION_LOCAL || $project_info->type == LOCALE_TRANSLATION_REMOTE) { + $local = isset($project_info->files[LOCALE_TRANSLATION_LOCAL]) ? $project_info->files[LOCALE_TRANSLATION_LOCAL] : NULL; + $remote = isset($project_info->files[LOCALE_TRANSLATION_REMOTE]) ? $project_info->files[LOCALE_TRANSLATION_REMOTE] : NULL; + $recent = _locale_translation_source_compare($local, $remote) == LOCALE_TRANSLATION_SOURCE_COMPARE_LT ? $remote : $local; + $updates[$langcode]['updates'][] = array( + 'name' => $project_data[$project_info->name]->info['name'], + 'version' => $project_info->version, + 'timestamp' => $recent->timestamp, + ); + } + } + } + return $updates; + } + + /** + * Provides debug info for projects in case translation files are not found. + * + * Translations files are being fetched either from Drupal translation server + * and local files or only from the local filesystem depending on the + * "Translation source" setting at admin/config/regional/translate/settings. + * This method will produce debug information including the respective path(s) + * based on this setting. + * + * Translations for development versions are never fetched, so the debug info + * for that is a fixed message. + * + * @param array $project_info + * An array which is the project information of the source. + * + * @return string + * The string which contains debug information. + */ + protected function createInfoString($project_info) { + $remote_path = isset($project_info->files['remote']->uri) ? $project_info->files['remote']->uri : FALSE; + $local_path = isset($project_info->files['local']->uri) ? $project_info->files['local']->uri : FALSE; + + if (strpos($project_info->version, 'dev') !== FALSE) { + return $this->t('No translation files are provided for development releases.'); + } + if (locale_translation_use_remote_source() && $remote_path && $local_path) { + return $this->t('File not found at %remote_path nor at %local_path', array( + '%remote_path' => $remote_path, + '%local_path' => $local_path, + )); + } + elseif ($local_path) { + return $this->t('File not found at %local_path', array('%local_path' => $local_path)); + } + return $this->t('Translation file location could not be determined.'); + } + + /** + * {@inheritdoc} + */ + public function validateForm(array &$form, array &$form_state) { + // Check if a language has been selected. 'tableselect' doesn't. + if (!array_filter($form_state['values']['langcodes'])) { + form_set_error('', $this->t('Select a language to update.')); + } + } + + /** + * {@inheritdoc} + */ + public function submitForm(array &$form, array &$form_state) { + $this->moduleHandler->loadInclude('locale', 'fetch.inc'); + $langcodes = array_filter($form_state['values']['langcodes']); + $projects = array_filter($form_state['values']['projects_update']); + + // Set the translation import options. This determines if existing + // translations will be overwritten by imported strings. + $options = _locale_translation_default_update_options(); + + // If the status was updated recently we can immediately start fetching the + // translation updates. If the status is expired we clear it an run a batch to + // update the status and then fetch the translation updates. + $last_checked = $this->state->get('locale.translation_last_checked'); + if ($last_checked < REQUEST_TIME - LOCALE_TRANSLATION_STATUS_TTL) { + locale_translation_clear_status(); + $batch = locale_translation_batch_update_build(array(), $langcodes, $options); + batch_set($batch); + } + else { + $batch = locale_translation_batch_fetch_build($projects, $langcodes, $options); + batch_set($batch); + } + } + +} diff --git a/core/modules/locale/src/Tests/LocaleUpdateTest.php b/core/modules/locale/src/Tests/LocaleUpdateTest.php index 268f72f..8e754f9 100644 --- a/core/modules/locale/src/Tests/LocaleUpdateTest.php +++ b/core/modules/locale/src/Tests/LocaleUpdateTest.php @@ -21,6 +21,9 @@ class LocaleUpdateTest extends LocaleUpdateBase { */ public static $modules = array('update', 'locale', 'locale_test'); + /** + * {@inheritdoc} + */ public static function getInfo() { return array( 'name' => 'Update translations', @@ -29,11 +32,20 @@ public static function getInfo() { ); } - function setUp() { + /** + * {@inheritdoc} + */ + public function setUp() { parent::setUp(); module_load_include('compare.inc', 'locale'); module_load_include('fetch.inc', 'locale'); - $admin_user = $this->drupalCreateUser(array('administer modules', 'administer site configuration', 'administer languages', 'access administration pages', 'translate interface')); + $admin_user = $this->drupalCreateUser(array( + 'administer modules', + 'administer site configuration', + 'administer languages', + 'access administration pages', + 'translate interface', + )); $this->drupalLogin($admin_user); // We use German as test language. This language must match the translation // file that come with the locale_test module (test.de.po) and can therefore @@ -44,7 +56,7 @@ function setUp() { /** * Checks if a list of translatable projects gets build. */ - function testUpdateProjects() { + public function testUpdateProjects() { module_load_include('compare.inc', 'locale'); // Make the test modules look like a normal custom module. i.e. make the @@ -57,7 +69,7 @@ function testUpdateProjects() { $projects = locale_translation_project_list(); $this->assertFalse(isset($projects['locale_test_translate']), 'Hidden module not found'); $this->assertEqual($projects['locale_test']['info']['interface translation server pattern'], 'core/modules/locale/test/test.%language.po', 'Interface translation parameter found in project info.'); - $this->assertEqual($projects['locale_test']['name'] , 'locale_test', format_string('%key found in project info.', array('%key' => 'interface translation project'))); + $this->assertEqual($projects['locale_test']['name'], 'locale_test', format_string('%key found in project info.', array('%key' => 'interface translation project'))); } /** @@ -74,7 +86,11 @@ function testUpdateProjects() { * the most recent files are selected in the different check scenarios: check * for local files only, check for both local and remote files. */ - function testUpdateCheckStatus() { + public function testUpdateCheckStatus() { + // Case when contributed modules are absent. + $this->drupalGet('admin/reports/translations'); + $this->assertText(t('Missing translations for one project')); + $config = \Drupal::config('locale.settings'); // Set a flag to let the locale_test module replace the project data with a // set of test projects. @@ -82,7 +98,8 @@ function testUpdateCheckStatus() { // Create local and remote translations files. $this->setTranslationFiles(); - $config->set('translation.default_filename', '%project-%version.%language._po')->save(); + $config->set('translation.default_filename', '%project-%version.%language._po') + ->save(); // Set the test conditions. $edit = array( @@ -126,12 +143,12 @@ function testUpdateCheckStatus() { * - Source: remote and local files * - Import overwrite: all existing translations */ - function testUpdateImportSourceRemote() { + public function testUpdateImportSourceRemote() { $config = \Drupal::config('locale.settings'); // Build the test environment. $this->setTranslationFiles(); - $this-> setCurrentTranslations(); + $this->setCurrentTranslations(); $config->set('translation.default_filename', '%project-%version.%language._po'); // Set the update conditions for this test. @@ -188,12 +205,12 @@ function testUpdateImportSourceRemote() { * - Source: local files only * - Import overwrite: all existing translations */ - function testUpdateImportSourceLocal() { + public function testUpdateImportSourceLocal() { $config = \Drupal::config('locale.settings'); // Build the test environment. $this->setTranslationFiles(); - $this-> setCurrentTranslations(); + $this->setCurrentTranslations(); $config->set('translation.default_filename', '%project-%version.%language._po'); // Set the update conditions for this test. @@ -242,12 +259,12 @@ function testUpdateImportSourceLocal() { * - Source: remote and local files * - Import overwrite: only overwrite non-customized translations */ - function testUpdateImportModeNonCustomized() { + public function testUpdateImportModeNonCustomized() { $config = \Drupal::config('locale.settings'); // Build the test environment. $this->setTranslationFiles(); - $this-> setCurrentTranslations(); + $this->setCurrentTranslations(); $config->set('translation.default_filename', '%project-%version.%language._po'); // Set the test conditions. @@ -278,12 +295,12 @@ function testUpdateImportModeNonCustomized() { * - Source: remote and local files * - Import overwrite: don't overwrite any existing translation */ - function testUpdateImportModeNone() { + public function testUpdateImportModeNone() { $config = \Drupal::config('locale.settings'); // Build the test environment. $this->setTranslationFiles(); - $this-> setCurrentTranslations(); + $this->setCurrentTranslations(); $config->set('translation.default_filename', '%project-%version.%language._po'); // Set the test conditions. @@ -310,7 +327,7 @@ function testUpdateImportModeNone() { /** * Tests automatic translation import when a module is enabled. */ - function testEnableUninstallModule() { + public function testEnableUninstallModule() { // Make the hidden test modules look like a normal custom module. \Drupal::state()->set('locale.test_system_info_alter', TRUE); @@ -325,7 +342,11 @@ function testEnableUninstallModule() { // Check if translations have been imported. $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', - array('%number' => 7, '%update' => 0, '%delete' => 0)), 'One translation file imported.'); + array( + '%number' => 7, + '%update' => 0, + '%delete' => 0, + )), 'One translation file imported.'); $this->assertTranslation('Tuesday', 'Dienstag', 'de'); $edit = array( @@ -348,7 +369,7 @@ function testEnableUninstallModule() { * enabled modules and will import them. When a language is removed the system * will remove all translations of that langugue from the database. */ - function testEnableLanguage() { + public function testEnableLanguage() { // Make the hidden test modules look like a normal custom module. \Drupal::state()->set('locale.test_system_info_alter', TRUE); @@ -370,7 +391,11 @@ function testEnableLanguage() { // Check if the right number of translations are added. $this->assertRaw(t('One translation file imported. %number translations were added, %update translations were updated and %delete translations were removed.', - array('%number' => 8, '%update' => 0, '%delete' => 0)), 'One language added.'); + array( + '%number' => 8, + '%update' => 0, + '%delete' => 0, + )), 'One language added.'); $this->assertTranslation('Extraday', 'extra dag', 'nl'); // Check if the language data is added to the database. @@ -392,7 +417,7 @@ function testEnableLanguage() { /** * Tests automatic translation import when a custom langauge is added. */ - function testEnableCustomLanguage() { + public function testEnableCustomLanguage() { // Make the hidden test modules look like a normal custom module. \Drupal::state()->set('locale.test_system_info_alter', TRUE);