diff --git a/core/modules/locale/lib/Drupal/locale/ProjectTranslationState.php b/core/modules/locale/lib/Drupal/locale/ProjectTranslationState.php new file mode 100644 index 0000000..894e4c5 --- /dev/null +++ b/core/modules/locale/lib/Drupal/locale/ProjectTranslationState.php @@ -0,0 +1,405 @@ +projectName = $project_name; + } + + public function __destruct() { + foreach ($this->stateByLanguage as $langcode => $state) { + // @todo Need to check if changed? + $this->saveState($langcode); + } + } + + /** + * Returns the machine name of the project. + */ + public function getName() { + return $this->projectName; + } + + /** + * Returns the human readable name of the project. + */ + public function getHumanName() { + return $this->projectHumanName; + } + + /** + * Sets the language this project is translated to. + * + * @param $langcode + */ + public function setLangcode($langcode) { + // @todo Need to check if language is translatable? + $this->langcode = $langcode; + if (!isset($this->stateByLanguage[$langcode])) { + $this->initState(); + } + $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; + } + + /** + * Deletes stored data of specified language. + */ + public function deleteLangcode($langcode) { + \Drupal::state()->delete('locale.translation_status.' . $this->projectName . '.' . $langcode); + } + + /** + * Returns the local source state. + */ + public function getLocalSource() { + if (!empty($this->localSource)) { + return $this->localSource; + } + return FALSE; + } + + /** + * Returns the remote source state. + */ + public function getRemoteSource() { + if (!empty($this->remoteSource)) { + return $this->remoteSource; + } + return FALSE; + } + + public function getLatestSourceTimestamp() { + return $this->latestSourceTimestamp; + } + + public function getVersion() { + return $this->projectVersion; + } + + /** + * Whether the remote source is valid and can be checked or downloaded. + * + * @return bool + * Returns FALSE if the project version is a dev release. No translations + * are available for dev releases. + */ + public function remoteIsValid() { + // @todo Return FALSE if file was not found (404) less than x time ago. + if ($this->projectVersion && strpos('dev', $this->projectVersion) !== FALSE) { + return FALSE; + } + return TRUE; + } + + /** + * Whether a remote source file is available and can be downloaded. + * + * @return bool + * Returns FALSE if the project version is a dev release. No translations + * are available for dev releases. + */ + public function remoteIsAvailable() { + return $this->remoteIsValid() && $this->latestSource == 'remote'; + } + + /** + * Whether the local source is available and can be imported. + * + * @return bool + * Returns FALSE if the project version is a dev release. No translations + * are available for dev releases. + */ + public function localIsAvailable() { + return $this->latestSource == 'local'; + } + + public function remoteIsLatest() { + return $this->latestSource == 'remote'; + } + + public function localIsLatest() { + return $this->latestSource == 'local'; + } + + /** + * Updates the translation state with local source data. + * + * @param $data + */ + 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; + } + } + + /** + * Updates the translation state with remote source data. + * + * @param $data + */ + 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; + } + } + + /** + * Updates the translation state with the timestamp of a newly imported translation. + * + * @param $timestamp + */ + public function updateTimestamp($timestamp) { + $this->timestamp = $timestamp; + $this->lastChecked = REQUEST_TIME; + + $this->latestSource = 'current'; + $this->latestSourceTimestamp = $timestamp; + } + + /** + * Whether this project is translated in language $langcode. + * + * @return bool + * Returns TRUE if this project is translated. + */ + public function isTranslated() { + return $this->timestamp > 0; + } + + /** + * Whether translation updates are available for this language. + * + * @return bool + * Returns TRUE if a local or remote translation update is available. + */ + public function hasUpdates() { + return $this->latestSource == 'local' || $this->latestSource == 'remote' ; + } + + /* + * Initializes the translation state by loading from cache or building a new one. + */ + protected function initState() { + if (empty($this->projectCore)) { + $this->loadProject(); + } + $this->loadState(); + if (empty($this->stateByLanguage[$this->langcode])) { + $this->stateByLanguage[$this->langcode] = $this->buildState(); + } + } + + /** + * Loads the project data. + */ + protected function loadProject() { + module_load_include('translation.inc', 'locale'); + // @todo Make a singular function based on locale_translation_get_projects() + //$project = locale_translation_get_project($this->projectName); + // @todo Follow-up issue to convert project into an object, so we don't have + // to call a procedural function here. https://drupal.org/node/1832946 + $project = locale_translation_get_projects(array($this->projectName)); + $project = $project[$this->projectName]; + if (empty($project)) { + // @todo Throw exception. + } + $this->projectHumanName = $this->projectName . ' (TODO)'; + $this->projectVersion = $project->version; + $this->projectCore = $project->core; + $this->serverPattern = $project->server_pattern; + } + + /** + * Builds a new translation state. + */ + protected function buildState() { + $state = new \stdClass(); + $state->timestamp = 0; + $state->lastChecked = 0; + $state->latestSource = ''; + $state->latestSourceTimestamp = 0; + $state->localSource = $this->buildLocalSource(); + $state->remoteSource = $this->buildRemoteSource(); + + return $state; + } + + /** + * Builds a local translation source object. + */ + protected function buildLocalSource() { + $filename = config('locale.settings')->get('translation.default_filename'); + $source = new \stdClass(); + if ($this->fileIsRemote($this->serverPattern)) { + $source->filename = $this->buildServerPattern($filename); + $source->directory = 'translations://'; + $source->uri = $source->directory . $source->filename; + } + else { + $source->filename = $this->buildServerPattern(basename($this->serverPattern)); + $source->directory = $this->buildServerPattern(drupal_dirname($this->serverPattern)); + $source->uri = $source->directory . '/' . $source->filename; + } + return $source; + } + + /** + * Builds a local translation source object. + */ + protected function buildRemoteSource() { + $source = new \stdClass(); + if ($this->fileIsRemote($this->serverPattern)) { + $source->filename = $this->buildServerPattern(basename($this->serverPattern)); + $source->uri = $this->buildServerPattern($this->serverPattern); + } + 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. + * + * @return string + * File uri with replaced placeholders. + */ + protected function buildServerPattern($template) { + $variables = array( + '%project' => $this->projectName, + '%version' => $this->projectVersion, + '%core' => $this->projectCore, + '%language' => $this->langcode, + ); + return strtr($template, $variables); + } + + /** + * Loads the translation state. + */ + protected function loadState() { + return $this->stateByLanguage[$this->langcode] = \Drupal::state()->get('locale.translation_status.' . $this->projectName . '.' . $this->langcode, NULL); + } + + /** + * Saves the translation state. + * + * @param $langcode + */ + protected function saveState() { + // @todo Need to check if data changed? (hash of lastChecked timestamps). + \Drupal::state()->set('locale.translation_status.' . $this->projectName . '.' . $this->langcode, $this->stateByLanguage[$this->langcode]); + } + + protected function fileIsRemote($uri) { + $scheme = file_uri_scheme($uri); + if ($scheme) { + return !drupal_realpath($scheme . '://'); + } + return FALSE; + } +} diff --git a/core/modules/locale/locale.batch.inc b/core/modules/locale/locale.batch.inc index 5085a2b..fee0b93 100644 --- a/core/modules/locale/locale.batch.inc +++ b/core/modules/locale/locale.batch.inc @@ -5,6 +5,7 @@ * Batch process to check the availability of remote or local po files. */ +use Drupal\locale\ProjectTranslationState; use Guzzle\Http\Exception\BadResponseException; use Guzzle\Http\Exception\RequestException; @@ -40,27 +41,27 @@ function locale_translation_batch_status_check($project, $langcode, $options = a 'finish_feedback' => TRUE, 'use_remote' => TRUE, ); - $source = locale_translation_get_status(array($project), array($langcode)); - $source = $source[$project][$langcode]; + // @todo Class ProjectTranslationState must be overridable. + $project_state = new ProjectTranslationState($project); + $project_state->setLangcode($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); + if ($source = $project_state->getLocalSource()) { + if ($local_file = locale_translation_source_check_file($source)) { + $project_state->updateLocalSource($local_file); } $checked = TRUE; } // 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 ($options['use_remote'] && $project_state->remoteIsValid() && $remote_file = $project_state->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']; - locale_translation_status_save($source->name, $source->langcode, LOCALE_TRANSLATION_REMOTE, $remote_file); + $project_state->updateRemoteSource($remote_file); } // @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 @@ -75,12 +76,12 @@ function locale_translation_batch_status_check($project, $langcode, $options = a // 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; + $context['results']['files'][] = $project_state->getName(); } if ($failure && !$checked) { - $context['results']['failed_files'][] = $source->name; + $context['results']['failed_files'][] = $project_state->getName(); } - $context['message'] = t('Checked translation for %project.', array('%project' => $source->project)); + $context['message'] = t('Checked translation for %project.', array('%project' => $project_state->getHumanName())); } /** @@ -135,17 +136,17 @@ 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); - } - else { - $context['results']['failed_files'][] = $source->files[LOCALE_TRANSLATION_REMOTE]; - } + $project_state = new ProjectTranslationState($project); + $project_state->setLangcode($langcode); + + if ($project_state->remoteIsAvailable() && $remote = $project_state->getRemoteSource()) { + $t = get_t(); + if ($result = locale_translation_download_source($remote, 'translations://')) { + $context['message'] = $t('Downloaded translation for %project.', array('%project' => $project_state->getHumanName())); + $project_state->updateLocalSource($result); + } + else { + $context['results']['failed_files'][] = $remote; } } } @@ -169,31 +170,28 @@ function locale_translation_batch_fetch_download($project, $langcode, &$context) * @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); + $project_state = new ProjectTranslationState($project); + $project_state->setLangcode($langcode); - // 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)); + if ($project_state->localIsAvailable() && $local = $project_state->getLocalSource()) { + $local->langcode = $langcode; + $t = get_t(); + module_load_include('bulk.inc', 'locale'); + $options += array( + 'message' => $t('Importing translation for %project.', array('%project' => $project_state->getHumanName())), + ); + // 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); - // 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]); - } - } + // The import is finished. + if (isset($context['finished']) && $context['finished'] == 1) { + // The import is successfull. + if (isset($context['results']['files'][$local->uri])) { + $context['message'] = $t('Imported translation for %project.', array('%project' => $project_state->getHumanName())); + + // Update the current translation timestamp. + $project_state->updateTimestamp($local->timestamp); } } } @@ -284,9 +282,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 dad67a8..a89f417 100644 --- a/core/modules/locale/locale.compare.inc +++ b/core/modules/locale/locale.compare.inc @@ -335,10 +335,13 @@ function locale_translation_check_projects_local($projects = array(), $langcodes // available. When found the source object is updated with the appropriate // type and timestamp of the po file. foreach ($projects as $name => $project) { + // @todo ProjectTranslationState must be overridable. + $project_state = new ProjectTranslationState($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_state->setLangcode($langcode); + $local = $project_state->getLocal(); + $file = locale_translation_source_check_file($local); + $project_state->updateLocalSource($file); } } } diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index 7c3f570..b32ed1f 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -14,6 +14,7 @@ use Drupal\Core\Cache\Cache; use Drupal\Core\Language\Language; use Drupal\Component\Utility\Crypt; +use Drupal\locale\ProjectTranslationState; /** * Regular expression pattern used to localize JavaScript strings. @@ -114,21 +115,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($path, $arg) { @@ -911,7 +897,6 @@ function locale_translation_file_history_delete($projects = array(), $langcodes */ 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()); @@ -920,14 +905,9 @@ function locale_translation_get_status($projects = NULL, $langcodes = NULL) { // 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]; - } + // @todo ProjectTranslationState object needs a method to clear its own data. + if ($status = Drupal::state()->get('locale.translation_status.' . $project . '.' . $langcode, NULL)) { + $result[$project][$langcode] = $status; } } } @@ -935,80 +915,19 @@ function locale_translation_get_status($projects = NULL, $langcodes = NULL) { } /** - * Saves the status of translation sources in static cache. - * - * @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. - */ -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; - } - - \Drupal::state()->set('locale.translation_status', $status); - \Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME); - } -} - -/** * Delete language entries from the status cache. * * @param array $langcodes * 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 = locale_translation_get_projects(); + foreach (array_keys($projects) as $project) { + foreach ($langcodes as $langcode) { + // @todo ProjectTranslationState object needs a method to clear its own data. + Drupal::state() + ->delete('locale.translation_status.' . $project . '.' . $langcode); } - \Drupal::state()->set('locale.translation_status', $status); } } @@ -1033,7 +952,14 @@ function locale_translation_status_delete_projects($projects) { * Clear the translation status cache. */ function locale_translation_clear_status() { - \Drupal::state()->delete('locale.translation_status'); + $projects = locale_translation_get_projects(); + $langcodes = locale_translatable_language_list(); + foreach (array_keys($projects) as $project) { + foreach (array_keys($langcodes) as $langcode) { + Drupal::state() + ->delete('locale.translation_status.' . $project . '.' . $langcode); + } + } \Drupal::state()->delete('locale.translation_last_checked'); } diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc index 98f4db9..e4588d9 100644 --- a/core/modules/locale/locale.pages.inc +++ b/core/modules/locale/locale.pages.inc @@ -6,11 +6,8 @@ */ use Drupal\Component\Utility\String; -use Drupal\Core\Language\Language; -use Drupal\locale\SourceString; -use Drupal\locale\TranslationString; use Symfony\Component\HttpFoundation\RedirectResponse; -use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; +use Drupal\locale\ProjectTranslationState; /** * Page callback: Checks for translation updates and displays the status. @@ -48,7 +45,7 @@ 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(); + $languages_update = $languages_not_found = $languages_translated = array(); $projects_update = array(); // @todo Calling locale_translation_build_projects() is an expensive way to @@ -56,41 +53,39 @@ function locale_translation_status_form($form, &$form_state) { // 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(); + $projects = locale_translation_get_projects(); // 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; + if ($languages) { + foreach (array_keys($projects) as $project) { + $project_status = new ProjectTranslationState($project); + foreach (array_keys($languages) as $langcode) { + $project_status->setLangcode($langcode); + + if ($project_status->hasUpdates()) { $updates[$langcode]['updates'][] = array( - 'name' => $project_data[$project_info->name]->info['name'], - 'version' => $project_info->version, - 'timestamp' => $recent->timestamp, + 'name' => $project == 'drupal' ? t('Drupal core') : $project_data[$project]->info['name'], + 'version' => $project_status->getVersion(), + 'timestamp' => $project_status->getLatestSourceTimestamp(), ); $languages_update[$langcode] = $langcode; - $projects_update[$project_info->name] = $project_info->name; + $projects_update[$project] = $project; + } + elseif (!$project_status->isTranslated()) { + $updates[$langcode]['not_found'][] = array( + 'name' => $project == 'drupal' ? t('Drupal core') : $project_data[$project]->info['name'], + 'version' => $project_status->getVersion(), + 'info' => _locale_translation_status_debug_info($project_status), + ); + $languages_not_found[$langcode] = $langcode; } } } $languages_not_found = array_diff($languages_not_found, $languages_update); // Build data options for the select table. - foreach($updates as $langcode => $update) { + 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) { @@ -106,7 +101,14 @@ function locale_translation_status_form($form, &$form_state) { '#markup' => $title ), ), - 'status' => array('class' => array('description', 'expand', 'priority-low'), 'data' => drupal_render($locale_translation_update_info)), + 'status' => array( + 'class' => array( + 'description', + 'expand', + 'priority-low' + ), + 'data' => drupal_render($locale_translation_update_info) + ), ); } // Sort the table data on language name. @@ -135,12 +137,12 @@ function locale_translation_status_form($form, &$form_state) { 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 { + elseif (empty($updates)) { $empty = t('No translation status available. Check manually.', array('@check' => url('admin/reports/translations/check'))); } + else { + $empty = t('All translations up to date.'); + } // The projects which require an update. Used by the _submit callback. $form['projects_update'] = array( @@ -246,11 +248,13 @@ function locale_translation_language_table($form_element) { * @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 : ''; +function _locale_translation_status_debug_info(ProjectTranslationState $project_status) { + $local_source = $project_status->getLocalSource(); + $remote_source = $project_status->getRemoteSource(); + $remote_path = isset($remote_source->uri) ? $remote_source->uri : ''; + $local_path = isset($local_source->uri) ? $local_source->uri : ''; - if (strpos($source->version, 'dev') !== FALSE) { + if (strpos($project_status->getVersion(), 'dev') !== FALSE) { return 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/locale.translation.inc b/core/modules/locale/locale.translation.inc index fab2a21..713ce46 100644 --- a/core/modules/locale/locale.translation.inc +++ b/core/modules/locale/locale.translation.inc @@ -6,30 +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 @@ -86,63 +62,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 @@ -163,136 +82,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 @@ -379,45 +183,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 integer - * - "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