diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 27e8c5d..2606240 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1609,14 +1609,17 @@ function _install_prepare_import($langcode) { if ($info['major']) { $core = $info['major'] . '.x'; $data = array( - 'name' => 'drupal', + 'id' => 'drupal', + 'name' => 'Drupal core', 'project_type' => 'module', 'core' => $core, 'version' => $version, 'server_pattern' => $install_state['server_pattern'], 'status' => 1, ); - \Drupal::service('locale.project')->set($data['name'], $data); + $project = new \Drupal\locale\LocaleTranslatableProject($data); + $project->setLangcode($langcode); + \Drupal::service('locale.project')->set($project); module_load_include('compare.inc', 'locale'); locale_translation_check_projects_local(array('drupal'), array($install_state['parameters']['langcode'])); } diff --git a/core/modules/locale/locale.batch.inc b/core/modules/locale/locale.batch.inc index b174074..a76b5e9 100644 --- a/core/modules/locale/locale.batch.inc +++ b/core/modules/locale/locale.batch.inc @@ -6,6 +6,7 @@ */ use GuzzleHttp\Exception\RequestException; +use Drupal\locale\LocaleTranslatableProject; /** * Load the common translation API. @@ -20,7 +21,7 @@ * Checks the presence and creation time po translation files in located at * remote server location and local file system. * - * @param string $project + * @param string $project_id * Machine name of the project for which to check the translation status. * @param string $langcode * Language code of the language for which to check the translation. @@ -33,53 +34,59 @@ * @param array $context * The batch context. */ -function locale_translation_batch_status_check($project, $langcode, $options = array(), &$context) { +function locale_translation_batch_status_check($project_id, $langcode, $options = array(), &$context) { + $failure = $checked = FALSE; $options += array( 'finish_feedback' => TRUE, 'use_remote' => TRUE, ); - $source = locale_translation_get_status(array($project), array($langcode)); - $source = $source[$project][$langcode]; - // Check the status of local translation files. - if (isset($source->files[LOCALE_TRANSLATION_LOCAL])) { - if ($file = locale_translation_source_check_file($source)) { - locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_LOCAL, $file); - } - $checked = TRUE; - } + /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + $project = \Drupal::service('locale.project')->get($project_id); + if ($project) { + $project->setLangcode($langcode); - // Check the status of remote translation files. - if ($options['use_remote'] && isset($source->files[LOCALE_TRANSLATION_REMOTE])) { - $remote_file = $source->files[LOCALE_TRANSLATION_REMOTE]; - if ($result = locale_translation_http_check($remote_file->uri)) { - // Update the file object with the result data. In case of a redirect we - // store the resulting uri. - if (isset($result['last_modified'])) { - $remote_file->uri = isset($result['location']) ? $result['location'] : $remote_file->uri; - $remote_file->timestamp = $result['last_modified']; - locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_REMOTE, $remote_file); + // Check the status of local translation files. + if ($source = $project->getLocalSource()) { + if ($local_file = locale_translation_source_check_file($source)) { + $project->updateLocalSource($local_file); + \Drupal::service('locale.project')->set($project); } - // @todo What to do with when the file is not found (404)? To prevent - // re-checking within the TTL (1day, 1week) we can set a last_checked - // timestamp or cache the result. $checked = TRUE; } - else { - $failure = TRUE; + + // Check the status of remote translation files. + if ($options['use_remote'] && $project->remoteIsValid() && $remote_file = $project->getRemoteSource()) { + if ($result = locale_translation_http_check($remote_file->uri)) { + // Update the file object with the result data. In case of a redirect we + // store the resulting uri. + if (isset($result['last_modified'])) { + $remote_file->uri = isset($result['location']) ? $result['location'] : $remote_file->uri; + $remote_file->timestamp = $result['last_modified']; + $project->updateRemoteSource($remote_file); + \Drupal::service('locale.project')->set($project); + } + // @todo What to do with when the file is not found (404)? To prevent + // re-checking within the TTL (1day, 1week) we can set a last_checked + // timestamp or cache the result. + $checked = TRUE; + } + else { + $failure = TRUE; + } } - } - // Provide user feedback and record success or failure for reporting at the - // end of the batch. - if ($options['finish_feedback'] && $checked) { - $context['results']['files'][] = $source->name; - } - if ($failure && !$checked) { - $context['results']['failed_files'][] = $source->name; + // Provide user feedback and record success or failure for reporting at the + // end of the batch. + if ($options['finish_feedback'] && $checked) { + $context['results']['files'][] = $project->id(); + } + if ($failure && !$checked) { + $context['results']['failed_files'][] = $project->id(); + } + $context['message'] = t('Checked translation for %project.', array('%project' => $project->getName())); } - $context['message'] = t('Checked translation for %project.', array('%project' => $source->project)); } /** @@ -124,8 +131,8 @@ function locale_translation_batch_status_finished($success, $results) { * Downloads a remote gettext file into the translations directory. When * successfully the translation status is updated. * - * @param object $project - * Source object of the translatable project. + * @param string $project_id + * The id of the translatable project. * @param string $langcode * Language code. * @param array $context @@ -133,17 +140,20 @@ function locale_translation_batch_status_finished($success, $results) { * * @see locale_translation_batch_fetch_import() */ -function locale_translation_batch_fetch_download($project, $langcode, &$context) { - $sources = locale_translation_get_status(array($project), array($langcode)); - if (isset($sources[$project][$langcode])) { - $source = $sources[$project][$langcode]; - if (isset($source->type) && $source->type == LOCALE_TRANSLATION_REMOTE) { - if ($file = locale_translation_download_source($source->files[LOCALE_TRANSLATION_REMOTE], 'translations://')) { - $context['message'] = t('Downloaded translation for %project.', array('%project' => $source->project)); - locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_LOCAL, $file); +function locale_translation_batch_fetch_download($project_id, $langcode, &$context) { + /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + $project = \Drupal::service('locale.project')->get($project_id); + if ($project) { + $project->setLangcode($langcode); + + if ($project->remoteIsAvailable() && $remote = $project->getRemoteSource()) { + if ($result = locale_translation_download_source($remote, 'translations://')) { + $context['message'] = t('Downloaded translation for %project.', array('%project' => $project->getName())); + $project->updateLocalSource($result); + \Drupal::service('locale.project')->set($project); } else { - $context['results']['failed_files'][] = $source->files[LOCALE_TRANSLATION_REMOTE]; + $context['results']['failed_files'][] = $remote; } } } @@ -155,8 +165,8 @@ function locale_translation_batch_fetch_download($project, $langcode, &$context) * Imports a gettext file from the translation directory. When successfully the * translation status is updated. * - * @param object $project - * Source object of the translatable project. + * @param string $project_id + * The id of the translatable project. * @param string $langcode * Language code. * @param array $options @@ -167,31 +177,31 @@ function locale_translation_batch_fetch_download($project, $langcode, &$context) * @see locale_translate_batch_import_files() * @see locale_translation_batch_fetch_download() */ -function locale_translation_batch_fetch_import($project, $langcode, $options, &$context) { - $sources = locale_translation_get_status(array($project), array($langcode)); - if (isset($sources[$project][$langcode])) { - $source = $sources[$project][$langcode]; - if (isset($source->type)) { - if ($source->type == LOCALE_TRANSLATION_REMOTE || $source->type == LOCALE_TRANSLATION_LOCAL) { - $file = $source->files[LOCALE_TRANSLATION_LOCAL]; - module_load_include('bulk.inc', 'locale'); - $options += array( - 'message' => t('Importing translation for %project.', array('%project' => $source->project)), - ); - // Import the translation file. For large files the batch operations is - // progressive and will be called repeatedly until finished. - locale_translate_batch_import($file, $options, $context); +function locale_translation_batch_fetch_import($project_id, $langcode, $options, &$context) { + /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + $project = \Drupal::service('locale.project')->get($project_id); + if ($project) { + $project->setLangcode($langcode); + + if ($project->localIsLatest() && $local = $project->getLocalSource()) { + $local->langcode = $langcode; + module_load_include('bulk.inc', 'locale'); + $options += array( + 'message' => t('Importing translation for %project.', array('%project' => $project->getName())), + ); + // Import the translation file. For large files the batch operations is + // progressive and will be called repeatedly until finished. + locale_translate_batch_import($local, $options, $context); - // The import is finished. - if (isset($context['finished']) && $context['finished'] == 1) { - // The import is successful. - if (isset($context['results']['files'][$file->uri])) { - $context['message'] = t('Imported translation for %project.', array('%project' => $source->project)); + // The import is finished. + if (isset($context['finished']) && $context['finished'] == 1) { + // The import is successful. + if (isset($context['results']['files'][$local->uri])) { + $context['message'] = t('Imported translation for %project.', array('%project' => $project->getName())); - // Save the data of imported source into the {locale_file} table and - // update the current translation status. - locale_translation_status_save($project, $langcode, LOCALE_TRANSLATION_CURRENT, $source->files[LOCALE_TRANSLATION_LOCAL]); - } + // Update the current translation timestamp. + $project->updateTimestamp($local->timestamp); + \Drupal::service('locale.project')->set($project); } } } @@ -277,9 +287,8 @@ function locale_translation_http_check($uri) { * File object if download was successful. FALSE on failure. */ function locale_translation_download_source($source_file, $directory = 'temporary://') { - if ($uri = system_retrieve_file($source_file->uri, $directory)) { + if ($uri = system_retrieve_file($source_file->uri, $directory, FALSE, FILE_EXISTS_REPLACE)) { $file = clone($source_file); - $file->type = LOCALE_TRANSLATION_LOCAL; $file->uri = $uri; $file->directory = $directory; $file->timestamp = filemtime($uri); diff --git a/core/modules/locale/locale.compare.inc b/core/modules/locale/locale.compare.inc index 26f3c9b..a3e8422 100644 --- a/core/modules/locale/locale.compare.inc +++ b/core/modules/locale/locale.compare.inc @@ -85,7 +85,8 @@ function locale_translation_build_projects() { // For every project store information. $data += array( - 'name' => $name, + 'id' => $name, + 'name' => $data['info']['name'], 'version' => isset($data['info']['version']) ? $data['info']['version'] : '', 'core' => isset($data['info']['core']) ? $data['info']['core'] : \Drupal::CORE_COMPATIBILITY, // A project can provide the path and filename pattern to download the @@ -93,11 +94,11 @@ function locale_translation_build_projects() { 'server_pattern' => isset($data['info']['interface translation server pattern']) && $data['info']['interface translation server pattern'] ? $data['info']['interface translation server pattern'] : $default_server['pattern'], 'status' => !empty($data['project_status']) ? 1 : 0, ); - $project = (object) $data; - $projects[$name] = $project; - // Create or update the project record. - \Drupal::service('locale.project')->set($project->name, $data); + $project = new \Drupal\locale\LocaleTranslatableProject($data); + $projects[$name] = $project; + $project->initLangcodeMultiple(array_keys(locale_translatable_language_list())); + \Drupal::service('locale.project')->set($project); // Invalidate the cache of translatable projects. locale_translation_clear_cache_projects(); @@ -321,11 +322,13 @@ function locale_translation_check_projects_local($projects = array(), $langcodes // For each project and each language we check if a local po file is // available. When found the source object is updated with the appropriate // type and timestamp of the po file. + /** @var \Drupal\locale\LocaleTranslatableProject $project */ foreach ($projects as $name => $project) { foreach ($langcodes as $langcode) { - $source = locale_translation_source_build($project, $langcode); - $file = locale_translation_source_check_file($source); - locale_translation_status_save($name, $langcode, LOCALE_TRANSLATION_LOCAL, $file); + $project->setLangcode($langcode); + $local = $project->getLocalSource(); + $file = locale_translation_source_check_file($local); + $project->updateLocalSource($file); } } } diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install index 197eded..8211a1a 100644 --- a/core/modules/locale/locale.install +++ b/core/modules/locale/locale.install @@ -243,15 +243,18 @@ function locale_requirements($phase) { if ($languages) { // Determine the status of the translation updates per language. - $status = locale_translation_get_status(); - if ($status) { - foreach ($status as $project) { - foreach ($project as $langcode => $project_info) { - if (empty($project_info->type)) { - $untranslated[$langcode] = $languages[$langcode]->name; + $projects = \Drupal::service('locale.project')->getAll(); + if ($projects) { + /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + foreach ($projects as $project) { + /** @var \Drupal\Core\Language\Language $language */ + foreach ($languages as $langcode => $language) { + $project->setLangcode($langcode); + if (!$project->isTranslated()) { + $untranslated[$langcode] = $language->getName(); } - elseif ($project_info->type == LOCALE_TRANSLATION_LOCAL || $project_info->type == LOCALE_TRANSLATION_REMOTE) { - $available_updates[$langcode] = $languages[$langcode]->name; + elseif ($project->hasUpdates()) { + $available_updates[$langcode] = $language->getName(); } } } diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 3122f59..90e3d18 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -21,6 +21,7 @@ use Drupal\Core\Language\LanguageInterface; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\Component\Utility\Crypt; +use Drupal\locale\LocaleTranslatableProject; /** * Regular expression pattern used to localize JavaScript strings. @@ -121,21 +122,6 @@ const LOCALE_TRANSLATION_OVERWRITE_NONE = 'none'; /** - * Translation source is a remote file. - */ -const LOCALE_TRANSLATION_REMOTE = 'remote'; - -/** - * Translation source is a local file. - */ -const LOCALE_TRANSLATION_LOCAL = 'local'; - -/** - * Translation source is the current translation. - */ -const LOCALE_TRANSLATION_CURRENT = 'current'; - -/** * Implements hook_help(). */ function locale_help($route_name, RouteMatchInterface $route_match) { @@ -477,11 +463,12 @@ function locale_system_update(array $components) { // built-in support for translation imports in the installer. if (!drupal_installation_attempted() && locale_translatable_language_list() && \Drupal::config('locale.settings')->get('translation.import_enabled')) { module_load_include('compare.inc', 'locale'); + locale_translation_build_projects(); // Update the list of translatable projects and start the import batch. // Only when new projects are added the update batch will be triggered. Not // each enabled module will introduce a new project. E.g. sub modules. - $projects = array_keys(locale_translation_build_projects()); + $projects = array_keys(locale_translation_get_projects()); if ($list = array_intersect($list, $projects)) { module_load_include('fetch.inc', 'locale'); // Get translation status of the projects, download and update @@ -528,12 +515,7 @@ function locale_system_remove($components) { locale_translate_delete_translation_files($list, array()); // Remove translatable projects. - // Followup issue http://drupal.org/node/1842362 to replace the - // {locale_project} table. Then change this to a function call. \Drupal::service('locale.project')->deleteMultiple($list); - - // Clear the translation status. - locale_translation_status_delete_projects($list); } } @@ -845,7 +827,7 @@ function locale_translation_get_file_history() { // Get file history from the database. $result = db_query('SELECT project, langcode, filename, version, uri, timestamp, last_checked FROM {locale_file}'); foreach ($result as $file) { - $file->type = $file->timestamp ? LOCALE_TRANSLATION_CURRENT : ''; + $file->type = $file->timestamp ? 'current' : ''; $history[$file->project][$file->langcode] = $file; } } @@ -874,7 +856,6 @@ function locale_translation_update_file_history($file) { )) ->execute(); // The file history has changed, flush the static cache now. - // @todo Can we make this more fine grained? drupal_static_reset('locale_translation_get_file_history'); return $status; } @@ -897,95 +878,32 @@ function locale_translation_file_history_delete($projects = array(), $langcodes $query->condition('langcode', $langcodes); } $query->execute(); + + // The file history has changed, flush the static cache now. + drupal_static_reset('locale_translation_get_file_history'); } /** * Gets the current translation status. * - * @todo What is 'translation status'? - */ -function locale_translation_get_status($projects = NULL, $langcodes = NULL) { - $result = array(); - $status = \Drupal::state()->get('locale.translation_status'); - module_load_include('translation.inc', 'locale'); - $projects = $projects ? $projects : array_keys(locale_translation_get_projects()); - $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list()); - - // Get the translation status of each project-language combination. If no - // status was stored, a new translation source is created. - foreach ($projects as $project) { - foreach ($langcodes as $langcode) { - if (isset($status[$project][$langcode])) { - $result[$project][$langcode] = $status[$project][$langcode]; - } - else { - $sources = locale_translation_build_sources(array($project), array($langcode)); - if (isset($sources[$project][$langcode])) { - $result[$project][$langcode] = $sources[$project][$langcode]; - } - } - } - } - return $result; -} - -/** - * Saves the status of translation sources in static cache. + * @param array|NULL $project_names + * Array of project machine names for which to get the translation status. + * Defaults to all projects. + * @param array|NULL $langcodes + * Array of language codes for which to get the translation status. Defaults + * to all languages. * - * @param string $project - * Machine readable project name. - * @param string $langcode - * Language code. - * @param string $type - * Type of data to be stored. - * @param array $data - * File object also containing timestamp when the translation is last updated. + * @return array + * Array of translatable project objects. */ -function locale_translation_status_save($project, $langcode, $type, $data) { - // Followup issue: http://drupal.org/node/1842362 - // Split status storage per module/language and expire individually. This will - // improve performance for large sites. - - // Load the translation status or build it if not already available. - module_load_include('translation.inc', 'locale'); - $status = locale_translation_get_status(); - if (empty($status)) { - $projects = locale_translation_get_projects(array($project)); - if (isset($projects[$project])) { - $status[$project][$langcode] = locale_translation_source_build($projects[$project], $langcode); - } - } - - // Merge the new status data with the existing status. - if (isset($status[$project][$langcode])) { - switch ($type) { - case LOCALE_TRANSLATION_REMOTE: - case LOCALE_TRANSLATION_LOCAL: - // Add the source data to the status array. - $status[$project][$langcode]->files[$type] = $data; - - // Check if this translation is the most recent one. Set timestamp and - // data type of the most recent translation source. - if (isset($data->timestamp) && $data->timestamp) { - if ($data->timestamp > $status[$project][$langcode]->timestamp) { - $status[$project][$langcode]->timestamp = $data->timestamp; - $status[$project][$langcode]->last_checked = REQUEST_TIME; - $status[$project][$langcode]->type = $type; - } - } - break; - - case LOCALE_TRANSLATION_CURRENT: - $data->last_checked = REQUEST_TIME; - $status[$project][$langcode]->timestamp = $data->timestamp; - $status[$project][$langcode]->last_checked = $data->last_checked; - $status[$project][$langcode]->type = $type; - locale_translation_update_file_history($data); - break; - } +// @todo deprecate ? +function locale_translation_get_status($project_names = NULL, $langcodes = NULL) { - \Drupal::state()->set('locale.translation_status', $status); - \Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME); + if ($project_names) { + return \Drupal::service('locale.project')->getMultiple($project_names); + } + else { + return \Drupal::service('locale.project')->getAll(); } } @@ -996,16 +914,14 @@ function locale_translation_status_save($project, $langcode, $type, $data) { * Language code(s) to be deleted from the cache. */ function locale_translation_status_delete_languages($langcodes) { - if ($status = locale_translation_get_status()) { - foreach ($status as $project => $languages) { - foreach ($languages as $langcode => $source) { - if (in_array($langcode, $langcodes)) { - unset($status[$project][$langcode]); - } - } + + $projects = \Drupal::service('locale.project')->getAll(); + foreach (array_keys($projects) as $name) { + foreach ($langcodes as $langcode) { + $projects[$name]->removeLangcode($langcode); } - \Drupal::state()->set('locale.translation_status', $status); } + \Drupal::service('locale.project')->setMultiple($projects); } /** @@ -1014,22 +930,22 @@ function locale_translation_status_delete_languages($langcodes) { * @param array $projects * Project name(s) to be deleted from the cache. */ +// @todo Deprecate ? function locale_translation_status_delete_projects($projects) { - $status = locale_translation_get_status(); - foreach ($status as $project => $languages) { - if (in_array($project, $projects)) { - unset($status[$project]); - } - } - \Drupal::state()->set('locale.translation_status', $status); + \Drupal::service('locale.project')->deleteMultiple($projects); } /** * Clear the translation status cache. */ function locale_translation_clear_status() { - \Drupal::state()->delete('locale.translation_status'); + + $projects = \Drupal::service('locale.project')->getAll(); + foreach (array_keys($projects) as $name) { + $projects[$name]->clearTranslationStates(); + } + \Drupal::service('locale.project')->setMultiple($projects); \Drupal::state()->delete('locale.translation_last_checked'); } diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc index dc6043b..a6178ca 100644 --- a/core/modules/locale/locale.pages.inc +++ b/core/modules/locale/locale.pages.inc @@ -9,8 +9,9 @@ use Drupal\Core\Render\Element; use Drupal\locale\SourceString; use Drupal\locale\TranslationString; +use Drupal\Component\Utility\String; use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Drupal\locale\LocaleTranslatableProject; /** * Page callback: Checks for translation updates and displays the status. diff --git a/core/modules/locale/locale.translation.inc b/core/modules/locale/locale.translation.inc index 2208b1d..48a8b26 100644 --- a/core/modules/locale/locale.translation.inc +++ b/core/modules/locale/locale.translation.inc @@ -6,33 +6,6 @@ */ /** - * Comparison result of source files timestamps. - * - * Timestamp of source 1 is less than the timestamp of source 2. - * - * @see _locale_translation_source_compare() - */ -const LOCALE_TRANSLATION_SOURCE_COMPARE_LT = -1; - -/** - * Comparison result of source files timestamps. - * - * Timestamp of source 1 is equal to the timestamp of source 2. - * - * @see _locale_translation_source_compare() - */ -const LOCALE_TRANSLATION_SOURCE_COMPARE_EQ = 0; - -/** - * Comparison result of source files timestamps. - * - * Timestamp of source 1 is greater than the timestamp of source 2. - * - * @see _locale_translation_source_compare() - */ -const LOCALE_TRANSLATION_SOURCE_COMPARE_GT = 1; - -/** * Get array of projects which are available for interface translation. * * This project data contains all projects which will be checked for available @@ -48,7 +21,7 @@ * Array of names of the projects to get. * * @return array - * Array of project data for translation update. + * Array of translatable project classes for translation update. * * @see locale_translation_build_projects() */ @@ -56,7 +29,7 @@ function locale_translation_get_projects($project_names = array()) { $projects = &drupal_static(__FUNCTION__, array()); if (empty($projects)) { - // Get project data from the database. + // Get project data from the key value store. $row_count = \Drupal::service('locale.project')->countProjects(); // http://drupal.org/node/1777106 is a follow-up issue to make the check for // possible out-of-date project information more robust. @@ -67,9 +40,6 @@ function locale_translation_get_projects($project_names = array()) { locale_translation_build_projects(); } $projects = \Drupal::service('locale.project')->getAll(); - array_walk($projects, function(&$project) { - $project = (object) $project; - }); } // Return the requested project names or all projects. @@ -87,63 +57,6 @@ function locale_translation_clear_cache_projects() { } /** - * Loads cached translation sources containing current translation status. - * - * @param array $projects - * Array of project names. Defaults to all translatable projects. - * @param array $langcodes - * Array of language codes. Defaults to all translatable languages. - * - * @return array - * Array of source objects. Keyed with :. - * - * @see locale_translation_source_build() - */ -function locale_translation_load_sources($projects = NULL, $langcodes = NULL) { - $sources = array(); - $projects = $projects ? $projects : array_keys(locale_translation_get_projects()); - $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list()); - - // Load source data from locale_translation_status cache. - $status = locale_translation_get_status(); - - // Use only the selected projects and languages for update. - foreach ($projects as $project) { - foreach ($langcodes as $langcode) { - $sources[$project][$langcode] = isset($status[$project][$langcode]) ? $status[$project][$langcode] : NULL; - } - } - return $sources; -} - -/** - * Build translation sources. - * - * @param array $projects - * Array of project names. Defaults to all translatable projects. - * @param array $langcodes - * Array of language codes. Defaults to all translatable languages. - * - * @return array - * Array of source objects. Keyed by project name and language code. - * - * @see locale_translation_source_build() - */ -function locale_translation_build_sources($projects = array(), $langcodes = array()) { - $sources = array(); - $projects = locale_translation_get_projects($projects); - $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list()); - - foreach ($projects as $project) { - foreach ($langcodes as $langcode) { - $source = locale_translation_source_build($project, $langcode); - $sources[$source->name][$source->langcode] = $source; - } - } - return $sources; -} - -/** * Checks whether a po file exists in the local filesystem. * * It will search in the directory set in the translation source. Which defaults @@ -164,136 +77,21 @@ function locale_translation_build_sources($projects = array(), $langcodes = arra * - "uri": File name and path. * - "timestamp": Last updated time of the po file. * FALSE if the file is not found. - * - * @see locale_translation_source_build() */ -function locale_translation_source_check_file($source) { - if (isset($source->files[LOCALE_TRANSLATION_LOCAL])) { - $source_file = $source->files[LOCALE_TRANSLATION_LOCAL]; - $directory = $source_file->directory; - $filename = '/' . preg_quote($source_file->filename) . '$/'; +function locale_translation_source_check_file($source_file) { + $directory = $source_file->directory; + $filename = '/' . preg_quote($source_file->filename) . '$/'; - if ($files = file_scan_directory($directory, $filename, array('key' => 'name', 'recurse' => FALSE))) { - $file = current($files); - $source_file->uri = $file->uri; - $source_file->timestamp = filemtime($file->uri); - return $source_file; - } + if ($files = file_scan_directory($directory, $filename, array('key' => 'name', 'recurse' => FALSE))) { + $file = current($files); + $source_file->uri = $file->uri; + $source_file->timestamp = filemtime($file->uri); + return $source_file; } return FALSE; } /** - * Builds abstract translation source. - * - * @param object $project - * Project object. - * @param string $langcode - * Language code. - * @param string $filename - * File name of translation file. May contain placeholders. - * - * @return object - * Source object: - * - "project": Project name. - * - "name": Project name (inherited from project). - * - "language": Language code. - * - "core": Core version (inherited from project). - * - "version": Project version (inherited from project). - * - "project_type": Project type (inherited from project). - * - "files": Array of file objects containing properties of local and remote - * translation files. - * Other processes can add the following properties: - * - "type": Most recent translation source found. LOCALE_TRANSLATION_REMOTE - * and LOCALE_TRANSLATION_LOCAL indicate available new translations, - * LOCALE_TRANSLATION_CURRENT indicate that the current translation is them - * most recent. "type" sorresponds with a key of the "files" array. - * - "timestamp": The creation time of the "type" translation (file). - * - "last_checked": The time when the "type" translation was last checked. - * The "files" array can hold file objects of type: - * LOCALE_TRANSLATION_LOCAL, LOCALE_TRANSLATION_REMOTE and - * LOCALE_TRANSLATION_CURRENT. Each contains following properties: - * - "type": The object type (LOCALE_TRANSLATION_LOCAL, - * LOCALE_TRANSLATION_REMOTE, etc. see above). - * - "project": Project name. - * - "langcode": Language code. - * - "version": Project version. - * - "uri": Local or remote file path. - * - "directory": Directory of the local po file. - * - "filename": File name. - * - "timestamp": Timestamp of the file. - * - "keep": TRUE to keep the downloaded file. - */ -function locale_translation_source_build($project, $langcode, $filename = NULL) { - // Followup issue: http://drupal.org/node/1842380 - // Convert $source object to a TranslatableProject class and use a typed class - // for $source-file. - - // Create a source object with data of the project object. - $source = clone $project; - $source->project = $project->name; - $source->langcode = $langcode; - $source->type = ''; - $source->timestamp = 0; - $source->last_checked = 0; - - $filename = $filename ? $filename : \Drupal::config('locale.settings')->get('translation.default_filename'); - - // If the server_pattern contains a remote file path we will check for a - // remote file. The local version of this file will only be checked if a - // translations directory has been defined. If the server_pattern is a local - // file path we will only check for a file in the local file system. - $files = array(); - if (_locale_translation_file_is_remote($source->server_pattern)) { - $files[LOCALE_TRANSLATION_REMOTE] = (object) array( - 'project' => $project->name, - 'langcode' => $langcode, - 'version' => $project->version, - 'type' => LOCALE_TRANSLATION_REMOTE, - 'filename' => locale_translation_build_server_pattern($source, basename($source->server_pattern)), - 'uri' => locale_translation_build_server_pattern($source, $source->server_pattern), - ); - $files[LOCALE_TRANSLATION_LOCAL] = (object) array( - 'project' => $project->name, - 'langcode' => $langcode, - 'version' => $project->version, - 'type' => LOCALE_TRANSLATION_LOCAL, - 'filename' => locale_translation_build_server_pattern($source, $filename), - 'directory' => 'translations://', - ); - $files[LOCALE_TRANSLATION_LOCAL]->uri = $files[LOCALE_TRANSLATION_LOCAL]->directory . $files[LOCALE_TRANSLATION_LOCAL]->filename; - } - else { - $files[LOCALE_TRANSLATION_LOCAL] = (object) array( - 'project' => $project->name, - 'langcode' => $langcode, - 'version' => $project->version, - 'type' => LOCALE_TRANSLATION_LOCAL, - 'filename' => locale_translation_build_server_pattern($source, basename($source->server_pattern)), - 'directory' => locale_translation_build_server_pattern($source, drupal_dirname($source->server_pattern)), - ); - $files[LOCALE_TRANSLATION_LOCAL]->uri = $files[LOCALE_TRANSLATION_LOCAL]->directory . '/' . $files[LOCALE_TRANSLATION_LOCAL]->filename; - } - $source->files = $files; - - // If this project+language is already translated, we add its status and - // update the current translation timestamp and last_updated time. If the - // project+language is not translated before, create a new record. - $history = locale_translation_get_file_history(); - if (isset($history[$project->name][$langcode]) && $history[$project->name][$langcode]->timestamp) { - $source->files[LOCALE_TRANSLATION_CURRENT] = $history[$project->name][$langcode]; - $source->type = LOCALE_TRANSLATION_CURRENT; - $source->timestamp = $history[$project->name][$langcode]->timestamp; - $source->last_checked = $history[$project->name][$langcode]->last_checked; - } - else { - locale_translation_update_file_history($source); - } - - return $source; -} - -/** * Build path to translation source, out of a server path replacement pattern. * * @param object $project @@ -329,7 +127,7 @@ function locale_cron_fill_queue() { $last = REQUEST_TIME - $config->get('translation.update_interval_days') * 3600 * 24; $projects = \Drupal::service('locale.project')->getAll(); $projects = array_filter($projects, function($project) { - return $project['status'] == 1; + return $project->getStatus(); }); $files = db_select('locale_file', 'f') ->condition('f.project', array_keys($projects)) @@ -382,45 +180,6 @@ function _locale_translation_file_is_remote($uri) { } /** - * Compare two update sources, looking for the newer one. - * - * The timestamp property of the source objects are used to determine which is - * the newer one. - * - * @param object $source1 - * Source object of the first translation source. - * @param object $source2 - * Source object of available update. - * - * @return int - * - "LOCALE_TRANSLATION_SOURCE_COMPARE_LT": $source1 < $source2 OR $source1 - * is missing. - * - "LOCALE_TRANSLATION_SOURCE_COMPARE_EQ": $source1 == $source2 OR both - * $source1 and $source2 are missing. - * - "LOCALE_TRANSLATION_SOURCE_COMPARE_GT": $source1 > $source2 OR $source2 - * is missing. - */ -function _locale_translation_source_compare($source1, $source2) { - if (isset($source1->timestamp) && isset($source2->timestamp)) { - if ($source1->timestamp == $source2->timestamp) { - return LOCALE_TRANSLATION_SOURCE_COMPARE_EQ; - } - else { - return $source1->timestamp > $source2->timestamp ? LOCALE_TRANSLATION_SOURCE_COMPARE_GT : LOCALE_TRANSLATION_SOURCE_COMPARE_LT; - } - } - elseif (isset($source1->timestamp) && !isset($source2->timestamp)) { - return LOCALE_TRANSLATION_SOURCE_COMPARE_GT; - } - elseif (!isset($source1->timestamp) && isset($source2->timestamp)) { - return LOCALE_TRANSLATION_SOURCE_COMPARE_LT; - } - else { - return LOCALE_TRANSLATION_SOURCE_COMPARE_EQ; - } -} - -/** * Returns default import options for translation update. * * @return array diff --git a/core/modules/locale/src/Form/TranslationStatusForm.php b/core/modules/locale/src/Form/TranslationStatusForm.php index df43b62..725f063 100644 --- a/core/modules/locale/src/Form/TranslationStatusForm.php +++ b/core/modules/locale/src/Form/TranslationStatusForm.php @@ -4,7 +4,6 @@ * @file * Contains \Drupal\locale\Form\TranslationStatusForm. */ - namespace Drupal\locale\Form; use Drupal\Component\Utility\String; @@ -70,15 +69,16 @@ public function getFormID() { */ public function buildForm(array $form, FormStateInterface $form_state) { $languages = locale_translatable_language_list(); - $status = locale_translation_get_status(); + $projects = \Drupal::service('locale.project')->getAll(); $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); + if ($languages && $projects) { + $updates = $this->prepareUpdateData($projects, $languages); // Build data options for the select table. foreach ($updates as $langcode => $update) { @@ -105,7 +105,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { if (!empty($update['not_found'])) { $languages_not_found[$langcode] = $langcode; } - elseif (!empty($update['updates'])) { + if (!empty($update['updates'])) { $languages_update[$langcode] = $langcode; } } @@ -138,7 +138,7 @@ public function buildForm(array $form, FormStateInterface $form_state) { '@add_language' => $this->url('language.admin_overview'), )); } - elseif ($status) { + elseif ($projects) { $empty = $this->t('All translations up to date.'); } else { @@ -183,41 +183,37 @@ public function buildForm(array $form, FormStateInterface $form_state) { /** * Prepare information about projects with available translation updates. * - * @param array $status - * Translation update status as an array keyed by Project ID and langcode. + * @param array $projects + * Translatable projects as an array keyed by project ID. + * @param array $languages + * Translatable languages as an array keyed by langcode. * * @return array * Translation update status as an array keyed by language code and * translation update status. */ - protected function prepareUpdateData(array $status) { + protected function prepareUpdateData(array $projects, array $languages) { $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) { + /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + foreach ($projects as $project_id => $project) { + /** @var \Drupal\Core\Language\Language $language */ + foreach ($languages as $langcode => $language) { + $project->setLangcode($langcode); // No translation file found for this project-language combination. - if (empty($project_info->type)) { + if ($project->hasNoTranslation()) { $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), + 'name' => $project->getName(), + 'version' => $project->getVersion(), + 'info' => $this->createInfoString($project), ); } // 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; + elseif ($project->hasUpdates()) { $updates[$langcode]['updates'][] = array( - 'name' => $project_data[$project_info->name]->info['name'], - 'version' => $project_info->version, - 'timestamp' => $recent->timestamp, + 'name' => $project->getName(), + 'version' => $project->getVersion(), + 'timestamp' => $project->getLatestSourceTimestamp(), ); } } @@ -237,17 +233,19 @@ protected function prepareUpdateData(array $status) { * 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. + * @param \Drupal\locale\LocaleTranslatableProjectInterface $project + * The translatable project object. * * @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; + protected function createInfoString(\Drupal\locale\LocaleTranslatableProjectInterface $project) { + $remote_source = $project->getRemoteSource(); + $local_source = $project->getLocalSource(); + $remote_path = isset($remote_source->uri) ? $remote_source->uri : FALSE; + $local_path = isset($local_source->uri) ? $local_source->uri : FALSE; - if (strpos($project_info->version, 'dev') !== FALSE) { + if (strpos($project->getVersion(), 'dev') !== FALSE) { return $this->t('No translation files are provided for development releases.'); } if (locale_translation_use_remote_source() && $remote_path && $local_path) { diff --git a/core/modules/locale/src/LocaleProjectStorage.php b/core/modules/locale/src/LocaleProjectStorage.php index 65c83fc..7cbf8cf 100644 --- a/core/modules/locale/src/LocaleProjectStorage.php +++ b/core/modules/locale/src/LocaleProjectStorage.php @@ -25,6 +25,7 @@ class LocaleProjectStorage implements LocaleProjectStorageInterface { * Static state cache. * * @var array + * Array of translatable project classes. */ protected $cache = array(); @@ -59,6 +60,7 @@ public function get($key, $default = NULL) { public function getMultiple(array $keys) { $values = array(); $load = array(); + foreach ($keys as $key) { // Check if we have a value in the cache. if (isset($this->cache[$key])) { @@ -76,8 +78,8 @@ public function getMultiple(array $keys) { // If we find a value, even one that is NULL, add it to the cache and // return it. if (isset($loaded_values[$key])) { - $values[$key] = $loaded_values[$key]; - $this->cache[$key] = $loaded_values[$key]; + $values[$key] = $this->buildProject($loaded_values[$key]); + $this->cache[$key] = $values[$key]; } else { $this->cache[$key] = NULL; @@ -91,16 +93,17 @@ public function getMultiple(array $keys) { /** * {@inheritdoc} */ - public function set($key, $value) { - $this->setMultiple(array($key => $value)); + public function set(\Drupal\locale\LocaleTranslatableProjectInterface $project) { + $this->setMultiple(array($project->id() => $project)); } /** * {@inheritdoc} */ public function setMultiple(array $data) { - foreach ($data as $key => $value) { - $this->cache[$key] = $value; + foreach ($data as $key => $project) { + $this->cache[$key] = $project; + $data[$key] = $project->toArray(); } $this->keyValueStore->setMultiple($data); } @@ -142,15 +145,15 @@ public function deleteAll() { * {@inheritdoc} */ public function disableAll() { - $projects = $this->keyValueStore->getAll(); + $projects = $this->getAll(); foreach (array_keys($projects) as $key) { - $projects[$key]['status'] = 0; + $projects[$key]->setStatus(FALSE); if (isset($cache[$key])) { $cache[$key] = $projects[$key]; } } - $this->keyValueStore->setMultiple($projects); + $this->setMultiple($projects); } /** @@ -165,9 +168,23 @@ public function countProjects() { */ public function getAll() { if (!static::$all) { - $this->cache = $this->keyValueStore->getAll(); + foreach ($this->keyValueStore->getAll() as $key => $value) { + $this->cache[$key] = $this->buildProject($value); + } static::$all = TRUE; } return $this->cache; } + + /** + * Builds translatable project object. + * + * @param array $data + * Project data. + * + * @return LocaleTranslatableProject + */ + protected function buildProject($data) { + return new \Drupal\locale\LocaleTranslatableProject($data); + } } diff --git a/core/modules/locale/src/LocaleProjectStorageInterface.php b/core/modules/locale/src/LocaleProjectStorageInterface.php index 876f05a..d75910d 100644 --- a/core/modules/locale/src/LocaleProjectStorageInterface.php +++ b/core/modules/locale/src/LocaleProjectStorageInterface.php @@ -2,7 +2,7 @@ /** * @file - * Contains Drupal\locale\LocalProjectStorageInterface. + * Contains Drupal\locale\LocaleProjectStorageInterface. */ namespace Drupal\locale; @@ -20,7 +20,7 @@ * @param mixed $default * The default value to use if the key is not found. * - * @return mixed + * @return \Drupal\locale\LocaleTranslatableProjectInterface|NULL * The stored value, or the default value if no value exists. */ public function get($key, $default = NULL); @@ -39,12 +39,10 @@ public function getMultiple(array $keys); /** * Creates or updates the project record. * - * @param string $key - * The key of the data to store. - * @param mixed $value - * The data to store. + * @param \Drupal\locale\LocaleTranslatableProjectInterface $project + * The project to store. */ - public function set($key, $value); + public function set(\Drupal\locale\LocaleTranslatableProjectInterface $project); /** * Creates or updates multiple project records. diff --git a/core/modules/locale/src/LocaleTranslatableProject.php b/core/modules/locale/src/LocaleTranslatableProject.php new file mode 100644 index 0000000..6b2a08b --- /dev/null +++ b/core/modules/locale/src/LocaleTranslatableProject.php @@ -0,0 +1,517 @@ +getMapping() as $key => $property) { + if (isset($data[$key])) { + $this->$property = $data[$key]; + } + } + + // Override the module name of drupal core. The default name is the name + // of the first extension ('Action'). + if ($this->id == 'drupal') { + $this->name = 'Drupal core'; + } + } + + /** + * @inheritdoc + */ + public function id() { + + return $this->id; + } + + /** + * @inheritdoc + */ + public function getName() { + + return $this->name; + } + + /** + * @inheritdoc + */ + public function toArray() { + + $result = array(); + + foreach ($this->getMapping() as $key => $property) { + $result[$key] = $this->$property; + } + + return $result; + } + + /** + * @inheritdoc + */ + public function setLangcode($langcode) { + + $this->langcode = $langcode; + $this->initLangcode($langcode); + $this->timestamp = &$this->stateByLanguage[$langcode]->timestamp; + $this->lastChecked = &$this->stateByLanguage[$langcode]->lastChecked; + $this->latestSource = &$this->stateByLanguage[$langcode]->latestSource; + $this->latestSourceTimestamp = &$this->stateByLanguage[$langcode]->latestSourceTimestamp; + $this->localSource = &$this->stateByLanguage[$langcode]->localSource; + $this->remoteSource = &$this->stateByLanguage[$langcode]->remoteSource; + } + + /** + * @inheritdoc + */ + public function initLangcode($langcode) { + + if (empty($this->stateByLanguage[$langcode])) { + $this->initState($langcode); + } + } + + /** + * @inheritdoc + */ + public function initLangcodeMultiple(array $langcodes) { + + foreach ($langcodes as $langcode) { + $this->initLangcode($langcode); + } + } + + /** + * @inheritdoc + */ + public function removeLangcode($langcode) { + + unset($this->stateByLanguage[$langcode]); + } + + /** + * @inheritdoc + */ + public function getStatus() { + + return $this->status; + } + + /** + * @inheritdoc + */ + public function setStatus($status) { + + $this->status = (bool) $status; + } + + /** + * @inheritdoc + */ + public function clearTranslationStates() { + + foreach (array_keys($this->stateByLanguage) as $langcode) { + $this->stateByLanguage[$langcode]->localSource = NULL; + $this->stateByLanguage[$langcode]->remoteSource = NULL; + } + } + + /** + * @inheritdoc + */ + public function getLocalSource() { + $source = NULL; + + if (!empty($this->localSource)) { + $source = $this->localSource; + } + + return $source; + } + + /** + * @inheritdoc + */ + public function getRemoteSource() { + $source = NULL; + + if (!empty($this->remoteSource)) { + $source = $this->remoteSource; + } + + return $source; + } + + /** + * @inheritdoc + */ + public function getLatestSourceTimestamp() { + + return $this->latestSourceTimestamp; + } + + /** + * @inheritdoc + */ + public function getVersion() { + + return $this->projectVersion; + } + + /** + * @inheritdoc + */ + public function setVersion($version) { + + $this->projectVersion = $version; + } + + /** + * @inheritdoc + */ + public function remoteIsValid() { + + // Development release translations are not supported for remote sources. + if ($this->projectVersion && strpos('dev', $this->projectVersion) !== FALSE) { + return FALSE; + } + + return TRUE; + } + + /** + * @inheritdoc + */ + public function remoteIsAvailable() { + + return $this->remoteIsValid() && $this->latestSource == 'remote'; + } + + /** + * @inheritdoc + */ + public function remoteIsLatest() { + + return $this->latestSource == 'remote'; + } + + /** + * @inheritdoc + */ + public function localIsLatest() { + + return $this->latestSource == 'local'; + } + + /** + * @inheritdoc + */ + public function updateLocalSource($data) { + + $this->localSource = $data; + $this->localSource->lastChecked = REQUEST_TIME; + + // If the local translation is the most recent, we update the latestSource + // status. Otherwise only mark the current translation as checked. If remote + // and local have the same timestamp, the local source is chosen. + if ($data->timestamp > $this->latestSourceTimestamp || ($data->timestamp >= $this->latestSourceTimestamp && $this->latestSource == 'remote')) { + $this->latestSource = 'local'; + $this->latestSourceTimestamp = $data->timestamp; + } + else { + $this->lastChecked = REQUEST_TIME; + } + } + + /** + * @inheritdoc + */ + public function updateRemoteSource($data) { + + $this->remoteSource = $data; + $this->remoteSource->lastChecked = REQUEST_TIME; + + // If the remote translation is the most recent, we update the latestSource + // status. Otherwise mark the current translation as checked. + if ($data->timestamp > $this->latestSourceTimestamp) { + $this->latestSource = 'remote'; + $this->latestSourceTimestamp = $data->timestamp; + } + else { + $this->lastChecked = REQUEST_TIME; + } + } + + /** + * @inheritdoc + */ + public function updateTimestamp($timestamp) { + + $this->timestamp = $timestamp; + $this->lastChecked = REQUEST_TIME; + + $this->latestSource = 'current'; + $this->latestSourceTimestamp = $timestamp; + } + + /** + * @inheritdoc + */ + public function isTranslated() { + + return $this->timestamp > 0; + } + + /** + * @inheritdoc + */ + public function hasUpdates() { + + return $this->latestSource == 'local' || $this->latestSource == 'remote'; + } + + /** + * @inheritdoc + */ + public function hasNoTranslation() { + + return $this->latestSource == ''; + } + + /** + * @inheritdoc + */ + public function isUpToDate() { + + return $this->latestSource == 'current'; + } + + /* + * Initializes the translation state by loading from cache or building a new one. + * + * @param string $langcode + * Language code of the translation sources. + */ + protected function initState($langcode) { + + if (!isset($this->stateByLanguage[$langcode])) { + $state = new \stdClass(); + $state->timestamp = 0; + $state->lastChecked = 0; + $state->latestSource = ''; + $state->latestSourceTimestamp = 0; + $state->localSource = $this->buildLocalSource($langcode); + $state->remoteSource = $this->buildRemoteSource($langcode); + + $this->stateByLanguage[$langcode] = $state; + } + } + + /** + * Returns mapping for array to properties. + * + * @return array + * Key value array of mapping values. Keyed by array key, the values are the + * class property names. + */ + protected function getMapping() { + + // All properties are mapped except: + // - langcode + // - timestamp + // - lastChecked + // - latestSource + // - latestSourceTimestamp + // - localSource + // - remoteSource + return array( + 'id' => 'id', + 'name' => 'name', + 'version' => 'projectVersion', + 'core' => 'projectCore', + 'status' => 'status', + 'server_pattern' => 'serverPattern', + 'state_by_language' => 'stateByLanguage', + ); + } + + /** + * Builds a local translation source object. + * + * @param string $langcode + * Language code of the translation source. + * + * @return \stdClass + * Local source object. + */ + protected function buildLocalSource($langcode) { + + $filename = \Drupal::config('locale.settings')->get('translation.default_filename'); + $source = new \stdClass(); + if ($this->fileIsRemote($this->serverPattern)) { + $source->filename = $this->buildServerPattern($filename, $langcode); + $source->directory = 'translations://'; + $source->uri = $source->directory . $source->filename; + } + else { + $source->filename = $this->buildServerPattern(basename($this->serverPattern), $langcode); + $source->directory = $this->buildServerPattern(drupal_dirname($this->serverPattern), $langcode); + $source->uri = $source->directory . '/' . $source->filename; + } + + return $source; + } + + /** + * Builds a local translation source object. + * + * @param string $langcode + * Language code of the translation source. + * + * @return \stdClass + * Remote source object. + */ + protected function buildRemoteSource($langcode) { + + $source = new \stdClass(); + if ($this->fileIsRemote($this->serverPattern)) { + $source->filename = $this->buildServerPattern(basename($this->serverPattern), $langcode); + $source->uri = $this->buildServerPattern($this->serverPattern, $langcode); + } + + return $source; + } + + /** + * Builds uri of a translation file out of a server path replacement pattern. + * + * @param string $template + * Uri template string containing placeholders. Available placeholders: + * - "%project": Project name. + * - "%version": Project version. + * - "%core": Project core version. + * - "%language": Language code. + * @param string $langcode + * Language code of the translation file. + * + * @return string + * File uri with replaced placeholders. + */ + protected function buildServerPattern($template, $langcode) { + + $variables = array( + '%project' => $this->id, + '%version' => $this->projectVersion, + '%core' => $this->projectCore, + '%language' => $langcode, + ); + + return strtr($template, $variables); + } + + /** + * Returns whether the file URI is at a remote translation source. + * + * @param $uri + * File URI. May include a scheme. + * + * @return bool + * Returns TRUE if the file scheme indicates a remote file. FALSE if no + * scheme is supplied or files are stored at the local file system. + */ + protected function fileIsRemote($uri) { + + $scheme = file_uri_scheme($uri); + if ($scheme) { + return !drupal_realpath($scheme . '://'); + } + + return FALSE; + } +} diff --git a/core/modules/locale/src/LocaleTranslatableProjectInterface.php b/core/modules/locale/src/LocaleTranslatableProjectInterface.php new file mode 100644 index 0000000..b4bb214 --- /dev/null +++ b/core/modules/locale/src/LocaleTranslatableProjectInterface.php @@ -0,0 +1,243 @@ +type = 'current'; - \Drupal::state()->set('locale.translation_status', $status); + /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + $project = \Drupal::service('locale.project')->get('drupal'); + $project->setLangcode('de'); + $project->updateTimestamp(REQUEST_TIME); + \Drupal::service('locale.project')->set($project); // One language added, all translations up to date. $this->drupalGet('admin/reports/status'); @@ -65,9 +67,10 @@ public function testInterface() { $this->assertText(t('All translations up to date.'), 'Translations up to date'); // Set locale_test_translate module to have a local translation available. - $status = locale_translation_get_status(); - $status['locale_test_translate']['de']->type = 'local'; - \Drupal::state()->set('locale.translation_status', $status); + $project = \Drupal::service('locale.project')->get('drupal'); + $project->setLangcode('de'); + $project->updateLocalSource((object) array('timestamp' => REQUEST_TIME)); + \Drupal::service('locale.project')->set($project); // Check if updates are available for German. $this->drupalGet('admin/reports/status'); @@ -78,10 +81,11 @@ public function testInterface() { // Set locale_test_translate module to have a dev release and no // translation found. - $status = locale_translation_get_status(); - $status['locale_test_translate']['de']->version = '1.3-dev'; - $status['locale_test_translate']['de']->type = ''; - \Drupal::state()->set('locale.translation_status', $status); + $project = \Drupal::service('locale.project')->get('drupal'); + $project->setLangcode('de'); + $project->setVersion('1.3-dev'); + $project->clearTranslationStates(); + \Drupal::service('locale.project')->set($project); // Check if no updates were found. $this->drupalGet('admin/reports/status'); diff --git a/core/modules/locale/src/Tests/LocaleUpdateTest.php b/core/modules/locale/src/Tests/LocaleUpdateTest.php index f564652..9b6df18 100644 --- a/core/modules/locale/src/Tests/LocaleUpdateTest.php +++ b/core/modules/locale/src/Tests/LocaleUpdateTest.php @@ -94,13 +94,24 @@ public function testUpdateCheckStatus() { // Get status of translation sources at local file system. $this->drupalGet('admin/reports/translations/check'); - $result = locale_translation_get_status(); - $this->assertEqual($result['contrib_module_one']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of contrib_module_one found'); - $this->assertEqual($result['contrib_module_one']['de']->timestamp, $this->timestampOld, 'Translation timestamp found'); - $this->assertEqual($result['contrib_module_two']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of contrib_module_two found'); - $this->assertEqual($result['contrib_module_two']['de']->timestamp, $this->timestampNew, 'Translation timestamp found'); - $this->assertEqual($result['locale_test']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of locale_test found'); - $this->assertEqual($result['custom_module_one']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of custom_module_one found'); + /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + $project = \Drupal::service('locale.project')->get('contrib_module_one'); + $project->setLangcode('de'); + $this->assertTrue($project->localIsLatest(), 'Translation of contrib_module_one found'); + $this->assertEqual($project->getLatestSourceTimestamp(), $this->timestampOld, 'Translation timestamp found'); + + $project = \Drupal::service('locale.project')->get('contrib_module_two'); + $project->setLangcode('de'); + $this->assertTrue($project->localIsLatest(), 'Translation of contrib_module_two found'); + $this->assertEqual($project->getLatestSourceTimestamp(), $this->timestampNew, 'Translation timestamp found'); + + $project = \Drupal::service('locale.project')->get('locale_test'); + $project->setLangcode('de'); + $this->assertTrue($project->localIsLatest(), 'Translation of locale_test found'); + + $project = \Drupal::service('locale.project')->get('custom_module_one'); + $project->setLangcode('de'); + $this->assertTrue($project->localIsLatest(), 'Translation of custom_module_one found'); // Set the test conditions. $edit = array( @@ -110,15 +121,29 @@ public function testUpdateCheckStatus() { // Get status of translation sources at both local and remote locations. $this->drupalGet('admin/reports/translations/check'); - $result = locale_translation_get_status(); - $this->assertEqual($result['contrib_module_one']['de']->type, LOCALE_TRANSLATION_REMOTE, 'Translation of contrib_module_one found'); - $this->assertEqual($result['contrib_module_one']['de']->timestamp, $this->timestampNew, 'Translation timestamp found'); - $this->assertEqual($result['contrib_module_two']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of contrib_module_two found'); - $this->assertEqual($result['contrib_module_two']['de']->timestamp, $this->timestampNew, 'Translation timestamp found'); - $this->assertEqual($result['contrib_module_three']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of contrib_module_three found'); - $this->assertEqual($result['contrib_module_three']['de']->timestamp, $this->timestampOld, 'Translation timestamp found'); - $this->assertEqual($result['locale_test']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of locale_test found'); - $this->assertEqual($result['custom_module_one']['de']->type, LOCALE_TRANSLATION_LOCAL, 'Translation of custom_module_one found'); + /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + $project = \Drupal::service('locale.project')->get('contrib_module_one'); + $project->setLangcode('de'); + $this->assertTrue($project->remoteIsLatest(), 'Translation of contrib_module_one found'); + $this->assertEqual($project->getLatestSourceTimestamp(), $this->timestampNew, 'Translation timestamp found'); + + $project = \Drupal::service('locale.project')->get('contrib_module_two'); + $project->setLangcode('de'); + $this->assertTrue($project->localIsLatest(), 'Translation of contrib_module_two found'); + $this->assertEqual($project->getLatestSourceTimestamp(), $this->timestampNew, 'Translation timestamp found'); + + $project = \Drupal::service('locale.project')->get('contrib_module_three'); + $project->setLangcode('de'); + $this->assertTrue($project->localIsLatest(), 'Translation of contrib_module_three found'); + $this->assertEqual($project->getLatestSourceTimestamp(), $this->timestampOld, 'Translation timestamp found'); + + $project = \Drupal::service('locale.project')->get('locale_test'); + $project->setLangcode('de'); + $this->assertTrue($project->localIsLatest(), 'Translation of locale_test found'); + + $project = \Drupal::service('locale.project')->get('custom_module_one'); + $project->setLangcode('de'); + $this->assertTrue($project->localIsLatest(), 'Translation of custom_module_one found'); } /** @@ -156,10 +181,18 @@ public function testUpdateImportSourceRemote() { $this->drupalPostForm('admin/reports/translations', array(), t('Update translations')); // Check if the translation has been updated, using the status cache. - $status = locale_translation_get_status(); - $this->assertEqual($status['contrib_module_one']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_one found'); - $this->assertEqual($status['contrib_module_two']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_two found'); - $this->assertEqual($status['contrib_module_three']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_three found'); + /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + $project = \Drupal::service('locale.project')->get('contrib_module_one'); + $project->setLangcode('de'); + $this->assertTrue($project->isUpToDate(), 'Translation of contrib_module_one found'); + + $project = \Drupal::service('locale.project')->get('contrib_module_two'); + $project->setLangcode('de'); + $this->assertTrue($project->isUpToDate(), 'Translation of contrib_module_two found'); + + $project = \Drupal::service('locale.project')->get('contrib_module_three'); + $project->setLangcode('de'); + $this->assertTrue($project->isUpToDate(), 'Translation of contrib_module_three found'); // Check the new translation status. // The static cache needs to be flushed first to get the most recent data @@ -210,10 +243,18 @@ public function testUpdateImportSourceLocal() { $this->drupalPostForm('admin/reports/translations', array(), t('Update translations')); // Check if the translation has been updated, using the status cache. - $status = locale_translation_get_status(); - $this->assertEqual($status['contrib_module_one']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_one found'); - $this->assertEqual($status['contrib_module_two']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_two found'); - $this->assertEqual($status['contrib_module_three']['de']->type, LOCALE_TRANSLATION_CURRENT, 'Translation of contrib_module_three found'); + /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + $project = \Drupal::service('locale.project')->get('contrib_module_one'); + $project->setLangcode('de'); + $this->assertTrue($project->isUpToDate(), 'Translation of contrib_module_one found'); + + $project = \Drupal::service('locale.project')->get('contrib_module_two'); + $project->setLangcode('de'); + $this->assertTrue($project->isUpToDate(), 'Translation of contrib_module_two found'); + + $project = \Drupal::service('locale.project')->get('contrib_module_three'); + $project->setLangcode('de'); + $this->assertTrue($project->isUpToDate(), 'Translation of contrib_module_three found'); // Check the new translation status. // The static cache needs to be flushed first to get the most recent data diff --git a/core/modules/locale/tests/modules/locale_test/locale_test.module b/core/modules/locale/tests/modules/locale_test/locale_test.module index f06a24a..416b2dd 100644 --- a/core/modules/locale/tests/modules/locale_test/locale_test.module +++ b/core/modules/locale/tests/modules/locale_test/locale_test.module @@ -20,7 +20,7 @@ function locale_test_system_info_alter(&$info, Extension $file, $type) { // To test the module detection process by locale_project_list() the // test modules should mimic a custom module. I.e. be non-hidden. if (\Drupal::state()->get('locale.test_system_info_alter')) { - if ($file->getName() == 'locale_test' || $file->getName() == 'locale_test_translate') { + if ($file->id() == 'locale_test' || $file->id() == 'locale_test_translate') { // Don't hide the module. $info['hidden'] = FALSE; } diff --git a/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module b/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module index e55d4dc..0bc13e9 100644 --- a/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module +++ b/core/modules/locale/tests/modules/locale_test_translate/locale_test_translate.module @@ -15,7 +15,7 @@ * setting the hidden status to FALSE. */ function locale_test_translate_system_info_alter(&$info, Extension $file, $type) { - if ($file->getName() == 'locale_test_translate') { + if ($file->id() == 'locale_test_translate') { // Don't hide the module. $info['hidden'] = FALSE; }