commit 227dee99214981f2d34e2b86b3993b1000d7d059 Author: Erik Stielstra Date: Tue Oct 7 15:07:47 2014 +0200 #44 WIP diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index 88a9171..0bf1f9e 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -1643,31 +1643,32 @@ function _install_prepare_import($langcodes, $server_pattern) { foreach ($langcodes as $langcode) { // Get the translation files located in the translations directory. $files = locale_translate_get_interface_translation_files(array('drupal'), array($langcode)); - // We pick the first file which matches the installation language. + // Pick the first file which matches the language, if any. $file = reset($files); - $filename = $file->filename; - preg_match('/drupal-([0-9a-z\.-]+)\.' . $langcode . '\.po/', $filename, $matches); - // Get the version information. - if ($version = $matches[1]) { - $info = _install_get_version_info($version); - // Picking the first file does not necessarily result in the right file. So - // we check if at least the major version number is available. - if ($info['major']) { - $core = $info['major'] . '.x'; - $data = array( - 'id' => 'drupal', - 'name' => 'Drupal core', - 'project_type' => 'module', - 'core' => $core, - 'version' => $version, - 'server_pattern' => $install_state['server_pattern'], - 'status' => 1, - ); - $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'])); + if (is_object($file)) { + $filename = $file->filename; + preg_match('/drupal-([0-9a-z\.-]+)\.' . $langcode . '\.po/', $filename, $matches); + // Get the version information. + if ($version = $matches[1]) { + $info = _install_get_version_info($version); + // Picking the first file does not necessarily result in the right file. So + // we check if at least the major version number is available. + if ($info['major']) { + $core = $info['major'] . '.x'; + $data = array( + 'name' => 'drupal', + 'project_type' => 'module', + 'core' => $core, + 'version' => $version, + 'server_pattern' => $server_pattern, + 'status' => 1, + ); + $project = new \Drupal\locale\TranslatableProject($data); + $project->setLangcode($langcode); + \Drupal::service('locale.project')->set($project); + module_load_include('compare.inc', 'locale'); + locale_translation_check_projects_local(array('drupal'), array($langcode)); + } } } } diff --git a/core/modules/locale/locale.batch.inc b/core/modules/locale/locale.batch.inc index ec8ce6d..bc27ce6 100644 --- a/core/modules/locale/locale.batch.inc +++ b/core/modules/locale/locale.batch.inc @@ -6,7 +6,7 @@ */ use GuzzleHttp\Exception\RequestException; -use Drupal\locale\LocaleTranslatableProject; +use Drupal\locale\TranslatableProject; /** * Load the common translation API. @@ -43,32 +43,27 @@ function locale_translation_batch_status_check($project_id, $langcode, $options 'use_remote' => TRUE, ); - /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + /** @var \Drupal\locale\TranslatableProjectInterface $project */ $project = \Drupal::service('locale.project')->get($project_id); if ($project) { - $project->setLangcode($langcode); - + $project->getStateByLanguage($langcode); // Check the status of local translation files. - if ($source = $project->getLocalSource()) { - if ($local_file = locale_translation_source_check_file($source)) { - $project->setLocalSource($local_file); - } + if ($project->getLocalSource()->isAvailable()) { + locale_translation_source_check_file($project->getLocalSource()); $checked = 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)) { + if ($options['use_remote'] && $project->getRemoteSource()->isAvailable() && $remote_source = $project->getRemoteSource()) { + if ($result = locale_translation_http_check($remote_source->getUri())) { // 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->setRemoteSource($remote_file); + if (isset($result['location'])) { + $remote_source->setUri($result['location']); + } + $remote_source->setTimestamp($result['last_modified']); } - // @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 { @@ -144,19 +139,20 @@ function locale_translation_batch_status_finished($success, $results) { * @see locale_translation_batch_fetch_import() */ function locale_translation_batch_fetch_download($project_id, $langcode, &$context) { - /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + /** @var \Drupal\locale\TranslatableProjectInterface $project */ $project = \Drupal::service('locale.project')->get($project_id); if ($project) { - $project->setLangcode($langcode); + $project->getStateByLanguage($langcode); - if ($project->remoteIsAvailable() && $remote = $project->getRemoteSource()) { - if ($result = locale_translation_download_source($remote, 'translations://')) { + $remote_source = $project->getRemoteSource(); + $local_source = $project->getLocalSource(); + if ($remote_source && $remote_source->isAvailable() && $project->remoteIsLatest()) { + if (locale_translation_download_source($remote_source, $local_source, 'translations://')) { $context['message'] = t('Downloaded translation for %project.', array('%project' => $project->getName())); - $project->setLocalSource($result); \Drupal::service('locale.project')->set($project); } else { - $context['results']['failed_files'][] = $remote; + $context['results']['failed_files'][] = $remote_source; } } } @@ -181,31 +177,33 @@ function locale_translation_batch_fetch_download($project_id, $langcode, &$conte * @see locale_translation_batch_fetch_download() */ function locale_translation_batch_fetch_import($project_id, $langcode, $options, &$context) { - /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + /** @var \Drupal\locale\TranslatableProjectInterface $project */ $project = \Drupal::service('locale.project')->get($project_id); if ($project) { - $project->setLangcode($langcode); + $project->getStateByLanguage($langcode); - if ($project->localIsLatest() && $local = $project->getLocalSource()) { - $local->langcode = $langcode; + /** @var \Drupal\locale\ProjectSourceInterface $local_source */ + if ($project->localIsLatest() && $local_source = $project->getLocalSource()) { 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); + $file = (object) $local_source->toArray(); + locale_translate_batch_import($file, $options, $context); // The import is finished. if (isset($context['finished']) && $context['finished'] == 1) { // The import is successful. - if (isset($context['results']['files'][$local->uri])) { + if (isset($context['results']['files'][$local_source->getUri()])) { $context['message'] = t('Imported translation for %project.', array('%project' => $project->getName())); - // Update the current translation timestamp and translation history. - $project->setTimestamp($local->timestamp); + // Update the translation timestamps and translation history. + $local_source->setTimestamp($file->timestamp); + $project->setTimestamp($file->timestamp); \Drupal::service('locale.project')->set($project); - locale_translation_update_file_history($local); + locale_translation_update_file_history($file); } } } @@ -225,7 +223,7 @@ function locale_translation_batch_fetch_finished($success, $results) { if ($success) { \Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME); } - return locale_translate_batch_finished($success, $results); + locale_translate_batch_finished($success, $results); } /** @@ -276,28 +274,24 @@ function locale_translation_http_check($uri) { /** * Downloads a translation file from a remote server. * - * @param object $source_file - * Source file object with at least: - * - "uri": uri to download the file from. - * - "project": Project name. - * - "langcode": Translation language. - * - "version": Project version. - * - "filename": File name. + * @param \Drupal\locale\ProjectSourceInterface $source + * Remote translation source object. + * @param \Drupal\locale\ProjectSourceInterface $destination + * Local translation source object. * @param string $directory * Directory where the downloaded file will be saved. Defaults to the * temporary file path. * - * @return object - * File object if download was successful. FALSE on failure. + * @return bool + * TRUE 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, FALSE, FILE_EXISTS_REPLACE)) { - $file = clone($source_file); - $file->uri = $uri; - $file->directory = $directory; - $file->timestamp = filemtime($uri); - return $file; +function locale_translation_download_source(\Drupal\locale\ProjectSourceInterface $source, \Drupal\locale\ProjectSourceInterface $destination, $directory = 'temporary://') { + if ($uri = system_retrieve_file($source->getUri(), $directory, FALSE, FILE_EXISTS_REPLACE)) { + $destination->setUri($uri); + $destination->setDirectory = $directory; + $destination->setTimestamp(filemtime($uri)); + return TRUE; } - \Drupal::logger('locale')->error('Unable to download translation file @uri.', array('@uri' => $source_file->uri)); + \Drupal::logger('locale')->error('Unable to download translation file @uri.', array('@uri' => $source->getUri())); return FALSE; } diff --git a/core/modules/locale/locale.compare.inc b/core/modules/locale/locale.compare.inc index a8de91f..f3580a6 100644 --- a/core/modules/locale/locale.compare.inc +++ b/core/modules/locale/locale.compare.inc @@ -33,7 +33,7 @@ function locale_translation_flush_projects() { * array. * * @return array - * Array of translatable projects \Drupal\locale\LocaleTranslatableProject + * Array of translatable projects \Drupal\locale\TranslatableProject */ function locale_translation_build_projects() { // This function depends on Update module. We degrade gracefully. @@ -93,13 +93,13 @@ function locale_translation_build_projects() { // Create a new translatable project or update an existing. if (!isset($existing_projects[$name])) { - $project = new \Drupal\locale\LocaleTranslatableProject($project_data); + $project = new \Drupal\locale\TranslatableProject($project_data); $project->initLangcodeMultiple(array_keys(locale_translatable_language_list())); \Drupal::service('locale.project')->set($project); $projects[$name] = $project; } else { - /** @var \Drupal\locale\LocaleTranslatableProject $project */ + /** @var \Drupal\locale\TranslatableProject $project */ $project = $existing_projects[$name]; $project->update($project_data); \Drupal::service('locale.project')->set($project); @@ -330,14 +330,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 */ + /** @var \Drupal\locale\TranslatableProject $project */ foreach ($projects as $name => $project) { foreach ($langcodes as $langcode) { - $project->setLangcode($langcode); + $project->getStateByLanguage($langcode); $local = $project->getLocalSource(); if ($local) { - $file = locale_translation_source_check_file($local); - $project->setLocalSource($file); + locale_translation_source_check_file($local); \Drupal::service('locale.project')->set($project); } } diff --git a/core/modules/locale/locale.install b/core/modules/locale/locale.install index 657dd6d..28ea319 100644 --- a/core/modules/locale/locale.install +++ b/core/modules/locale/locale.install @@ -246,11 +246,11 @@ function locale_requirements($phase) { // Determine the status of the translation updates per language. $projects = \Drupal::service('locale.project')->getAll(); if ($projects) { - /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + /** @var \Drupal\locale\TranslatableProjectInterface $project */ foreach ($projects as $project) { /** @var \Drupal\Core\Language\Language $language */ foreach ($languages as $langcode => $language) { - $project->setLangcode($langcode); + $project->getStateByLanguage($langcode); if ($project->hasNoTranslation()) { $untranslated[$langcode] = $language->getName(); } diff --git a/core/modules/locale/locale.module b/core/modules/locale/locale.module index db5f08c..b98d237 100644 --- a/core/modules/locale/locale.module +++ b/core/modules/locale/locale.module @@ -22,7 +22,7 @@ use Drupal\Core\Language\LanguageInterface; use Drupal\language\Entity\ConfigurableLanguage; use Drupal\Component\Utility\Crypt; -use Drupal\locale\LocaleTranslatableProject; +use Drupal\locale\TranslatableProject; /** * Regular expression pattern used to localize JavaScript strings. diff --git a/core/modules/locale/locale.pages.inc b/core/modules/locale/locale.pages.inc index 0e296c8..8e366f3 100644 --- a/core/modules/locale/locale.pages.inc +++ b/core/modules/locale/locale.pages.inc @@ -12,7 +12,7 @@ use Drupal\locale\TranslationString; use Drupal\Component\Utility\String; use Symfony\Component\HttpFoundation\RedirectResponse; -use Drupal\locale\LocaleTranslatableProject; +use Drupal\locale\TranslatableProject; /** * Page callback: Checks for translation updates and displays the status. @@ -117,7 +117,7 @@ function template_preprocess_locale_translation_update_info(&$variables) { } // Build output for updates not found. - if (isset($variables['not_found'])) { + if (isset($variables['not_found']) && $variables['not_found']) { $releases = array(); $variables['missing_updates_status'] = format_plural(count($variables['not_found']), 'Missing translations for one project', 'Missing translations for @count projects'); if ($variables['not_found']) { diff --git a/core/modules/locale/locale.translation.inc b/core/modules/locale/locale.translation.inc index e10a791..7652148 100644 --- a/core/modules/locale/locale.translation.inc +++ b/core/modules/locale/locale.translation.inc @@ -21,7 +21,7 @@ * Array of names of the projects to get. * * @return array - * Array of translatable project classes for translation update. + * Array of translatable project objects for translation update. * * @see locale_translation_build_projects() */ @@ -64,60 +64,31 @@ function locale_translation_clear_cache_projects() { * to the "translations://" stream wrapper path. The directory may contain any * valid stream wrapper. * - * The "local" files property of the source object contains the definition of a - * po file we are looking for. The file name defaults to - * %project-%version.%language.po. Per project this value can be overridden - * using the server_pattern directive in the module's .info.yml file or by using - * hook_locale_translation_projects_alter(). + * The source object contains the definition of a po file we are looking for. + * The file name defaults to %project-%version.%language.po. Per project this + * value can be overridden using the server_pattern directive in the module's + * .info.yml file or by using hook_locale_translation_projects_alter(). * - * @param object $source + * @param \Drupal\locale\ProjectSourceInterface $source * Translation source object. * - * @return object - * Source file object of the po file, updated with: - * - "uri": File name and path. - * - "timestamp": Last updated time of the po file. - * FALSE if the file is not found. + * @return bool + * TRUE if the file was found, FALSE otherwise. */ -function locale_translation_source_check_file($source_file) { - $directory = $source_file->directory; - $filename = '/' . preg_quote($source_file->filename) . '$/'; +function locale_translation_source_check_file(\Drupal\locale\ProjectSourceInterface $source) { + $directory = $source->getDirectory(); + $filename = '/' . preg_quote($source->getFilename()) . '$/'; 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; + $source->setUri($file->uri); + $source->setTimestamp(filemtime($file->uri)); + return TRUE; } return FALSE; } /** - * Build path to translation source, out of a server path replacement pattern. - * - * @param object $project - * Project object containing data to be inserted in the template. - * @param string $template - * String containing placeholders. Available placeholders: - * - "%project": Project name. - * - "%version": Project version. - * - "%core": Project core version. - * - "%language": Language code. - * - * @return string - * String with replaced placeholders. - */ -function locale_translation_build_server_pattern($project, $template) { - $variables = array( - '%project' => $project->name, - '%version' => $project->version, - '%core' => $project->core, - '%language' => isset($project->langcode) ? $project->langcode : '%language', - ); - return strtr($template, $variables); -} - -/** * Populate a queue with project to check for translation updates. */ function locale_cron_fill_queue() { @@ -164,23 +135,6 @@ function locale_cron_fill_queue() { } /** - * Determine if a file is a remote file. - * - * @param string $uri - * The URI or URI pattern of the file. - * - * @return bool - * TRUE if the $uri is a remote file. - */ -function _locale_translation_file_is_remote($uri) { - $scheme = file_uri_scheme($uri); - if ($scheme) { - return !drupal_realpath($scheme . '://'); - } - return FALSE; -} - -/** * Returns default import options for translation update. * * @return array diff --git a/core/modules/locale/src/AbstractProjectSource.php b/core/modules/locale/src/AbstractProjectSource.php deleted file mode 100644 index 8d3acb5..0000000 --- a/core/modules/locale/src/AbstractProjectSource.php +++ /dev/null @@ -1,61 +0,0 @@ -latestSource = ''; - $this->latestSourceTimestamp = 0; - } - - /** - * 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. - */ - public static function buildServerPattern($project, $langcode) { - $variables = array( - '%project' => $project->id, - '%version' => $project->projectVersion, - '%core' => $project->projectCore, - '%language' => $langcode, - ); - - return strtr($project->serverPattern, $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. - */ - public static function fileIsRemote($uri) { - - $scheme = file_uri_scheme($uri); - if ($scheme) { - return !drupal_realpath($scheme . '://'); - } - - return FALSE; - } -} \ No newline at end of file diff --git a/core/modules/locale/src/Form/TranslationStatusForm.php b/core/modules/locale/src/Form/TranslationStatusForm.php index 38d196b..42c80a3 100644 --- a/core/modules/locale/src/Form/TranslationStatusForm.php +++ b/core/modules/locale/src/Form/TranslationStatusForm.php @@ -63,6 +63,10 @@ public function getFormID() { return 'locale_translation_status_form'; } + function set_timestamp($source, $timestamp) { + $source->setTimestamp($timestamp); + } + /** * Form builder for displaying the current translation status. * @@ -196,11 +200,11 @@ public function buildForm(array $form, FormStateInterface $form_state) { protected function prepareUpdateData(array $projects, array $languages) { $updates = array(); - /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + /** @var \Drupal\locale\TranslatableProjectInterface $project */ foreach ($projects as $project_id => $project) { /** @var \Drupal\Core\Language\Language $language */ foreach ($languages as $langcode => $language) { - $project->setLangcode($langcode); + $project->getStateByLanguage($langcode); // No translation file found for this project-language combination. if ($project->hasNoTranslation()) { $updates[$langcode]['not_found'][] = array( @@ -234,17 +238,17 @@ protected function prepareUpdateData(array $projects, array $languages) { * Translations for development versions are never fetched, so the debug info * for that is a fixed message. * - * @param \Drupal\locale\LocaleTranslatableProjectInterface $project + * @param \Drupal\locale\TranslatableProjectInterface $project * The translatable project object. * * @return string * The string which contains debug information. */ - protected function createInfoString(\Drupal\locale\LocaleTranslatableProjectInterface $project) { + protected function createInfoString(\Drupal\locale\TranslatableProjectInterface $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; + $remote_path = $remote_source->getUri(); + $local_path = $local_source->getUri(); if (strpos($project->getVersion(), 'dev') !== FALSE) { return $this->t('No translation files are provided for development releases.'); diff --git a/core/modules/locale/src/LocalProjectSource.php b/core/modules/locale/src/LocalProjectSource.php index 05b8f26..fb4f420 100644 --- a/core/modules/locale/src/LocalProjectSource.php +++ b/core/modules/locale/src/LocalProjectSource.php @@ -1,4 +1,5 @@ project = $project; + $filename = \Drupal::config('locale.settings')->get('translation.default_filename'); - $source = new \stdClass(); $server_pattern = $project->getServerPattern(); - if (self::fileIsRemote($server_pattern)) { - $source->filename = self::buildServerPattern($filename, $langcode); - $source->directory = 'translations://'; - $source->uri = $source->directory . $source->filename; + + if ($this->fileIsRemote($server_pattern)) { + $this->filename = $this->buildServerPattern($filename, $this->project, $langcode); + $this->directory = 'translations://'; + $this->uri = $this->directory . $this->filename; } else { - $source->filename = self::buildServerPattern(basename($server_pattern), $langcode); - $source->directory = self::buildServerPattern(drupal_dirname($server_pattern), $langcode); - $source->uri = $source->directory . '/' . $source->filename; + $this->filename = $this->buildServerPattern(basename($server_pattern), $this->project, $langcode); + $this->directory = $this->buildServerPattern(drupal_dirname($server_pattern), $this->project, $langcode); + $this->uri = $this->directory . '/' . $this->filename; } + } - return $source; + /** + * @inheritdoc + */ + public function setTimestamp($timestamp) { + parent::setTimestamp($timestamp); + $this->project->setLocalTimestamp($timestamp); } } \ No newline at end of file diff --git a/core/modules/locale/src/LocaleProjectStorage.php b/core/modules/locale/src/LocaleProjectStorage.php index 7cbf8cf..ebe5588 100644 --- a/core/modules/locale/src/LocaleProjectStorage.php +++ b/core/modules/locale/src/LocaleProjectStorage.php @@ -78,7 +78,7 @@ 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] = $this->buildProject($loaded_values[$key]); + $values[$key] = unserialize($loaded_values[$key]); $this->cache[$key] = $values[$key]; } else { @@ -93,7 +93,7 @@ public function getMultiple(array $keys) { /** * {@inheritdoc} */ - public function set(\Drupal\locale\LocaleTranslatableProjectInterface $project) { + public function set(\Drupal\locale\TranslatableProjectInterface $project) { $this->setMultiple(array($project->id() => $project)); } @@ -103,7 +103,7 @@ public function set(\Drupal\locale\LocaleTranslatableProjectInterface $project) public function setMultiple(array $data) { foreach ($data as $key => $project) { $this->cache[$key] = $project; - $data[$key] = $project->toArray(); + $data[$key] = serialize($project); } $this->keyValueStore->setMultiple($data); } @@ -169,22 +169,11 @@ public function countProjects() { public function getAll() { if (!static::$all) { foreach ($this->keyValueStore->getAll() as $key => $value) { - $this->cache[$key] = $this->buildProject($value); + $this->cache[$key] = unserialize($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 d75910d..792e495 100644 --- a/core/modules/locale/src/LocaleProjectStorageInterface.php +++ b/core/modules/locale/src/LocaleProjectStorageInterface.php @@ -20,7 +20,7 @@ * @param mixed $default * The default value to use if the key is not found. * - * @return \Drupal\locale\LocaleTranslatableProjectInterface|NULL + * @return \Drupal\locale\TranslatableProjectInterface|NULL * The stored value, or the default value if no value exists. */ public function get($key, $default = NULL); @@ -39,10 +39,10 @@ public function getMultiple(array $keys); /** * Creates or updates the project record. * - * @param \Drupal\locale\LocaleTranslatableProjectInterface $project + * @param \Drupal\locale\TranslatableProjectInterface $project * The project to store. */ - public function set(\Drupal\locale\LocaleTranslatableProjectInterface $project); + public function set(\Drupal\locale\TranslatableProjectInterface $project); /** * Creates or updates multiple project records. diff --git a/core/modules/locale/src/LocaleProjectTranslationState.php b/core/modules/locale/src/LocaleProjectTranslationState.php deleted file mode 100644 index 71ba541..0000000 --- a/core/modules/locale/src/LocaleProjectTranslationState.php +++ /dev/null @@ -1,205 +0,0 @@ - $property) { - $state->{$property_name} = $property; - } - - return $state; - } - - /** - * Returns the timestamp of the latest source. - * @return int - */ - public function getLatestSourceTimestamp() { - return $this->latestSourceTimestamp; - } - - /** - * Returns true if the latest source is remote. - * @return bool - */ - public function remoteIsLatest() { - return $this->latestSource == 'remote'; - } - - /** - * @inheritdoc - */ - public function remoteIsValid() { - $this->remoteSource->isValid(); - } - - /** - * @inheritdoc - */ - public function remoteIsAvailable() { - $this->remoteSource->isAvailable(); - } - - /** - * Returns true if the latest source is local. - * @return bool - */ - public function localIsLatest() { - return $this->latestSource == 'local'; - } - - /** - * Clears translation states for each local and remote source. - * @return void - */ - public function clearTranslationStates() { - if ($this->latestSource == 'local' || $this->latestSource == 'remote') { - $this->localSource->clearTranslationState(); - $this->remoteSource->clearTranslationState(); - } - } - - /** - * Set a new local source from the data array. - * @param $data - * An array containing the data for the local source. - */ - public function setLocalSource($data) { - if (isset($data->timestamp)) { - $local_source = $this->localSource; - $local_source->filename = $data->filename; - $local_source->directory = $data->directory; - $local_source->uri = $data->uri; - $local_source->timestamp = $data->timestamp; - - // 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; - } - } - - $this->localSource->lastChecked = REQUEST_TIME; - } - - /** - * Set a new remote source from the data array. - * @param $data - * An array containing the data for the local source. - */ - public function setRemoteSource($data) { - if (isset($data->timestamp)) { - $remote_source = $this->remoteSource; - $remote_source->filename = $data->filename; - $remote_source->uri = $data->uri; - $remote_source->timestamp = $data->timestamp; - - // 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; - } - } - - $this->remoteSource->lastChecked = REQUEST_TIME; - } - - /** - * Sets the time when the translation state was last checked. - * @param $timestamp - * The timestamp when the state was last checked. - */ - public function setTimestamp($timestamp) { - $this->timestamp = $timestamp; - $this->lastChecked = REQUEST_TIME; - - $this->latestSource = 'current'; - $this->latestSourceTimestamp = $timestamp; - } - - /** - * Returns true if the project is translated. - * @return bool - */ - public function isTranslated() { - return $this->timestamp > 0; - } - - /** - * Returns true if the latest source has updates. - * @return bool - */ - public function hasUpdates() { - return $this->latestSource == 'local'|| $this->latestSource == 'remote'; - } - - /** - * Returns true if we don't have translations available. - * @return bool - */ - public function hasNoTranslation() { - return $this->latestSource == ''; - } - - /** - * Returns true if the translations are up to date. - * @return bool - */ - public function isUpToDate() { - return $this->latestSource == 'current'; - } -} \ No newline at end of file diff --git a/core/modules/locale/src/LocaleProjectTranslationStateInterface.php b/core/modules/locale/src/LocaleProjectTranslationStateInterface.php deleted file mode 100644 index 16bba74..0000000 --- a/core/modules/locale/src/LocaleProjectTranslationStateInterface.php +++ /dev/null @@ -1,86 +0,0 @@ -importData($data); - } - - /** - * @inheritdoc - */ - public function id() { - - return $this->id; - } - - /** - * @inheritdoc - */ - public function getName() { - - return $this->name; - } - - /** - * @inheritdoc - */ - public function update($data) { - - $old_version = $this->projectVersion; - $old_server_pattern = $this->serverPattern; - - unset($data['id']); - $this->importData($data); - - // Rebuild local and remote source and clear pending update states only - // if version or server pattern has changed. - if ((isset($data['version']) && $old_version != $this->projectVersion) - || (isset($data['server_pattern']) && $old_server_pattern != $this->serverPattern)) { - foreach ($this->stateByLanguage as $langcode => $state) { - $state->localSource = ProjectSource::create($this, $langcode); - $state->remoteSource = RemoteProjectSource::create($this, $langcode); - $state->clearTranslationStates(); - } - } - } - - /** - * @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); - } - - /** - * @inheritdoc - */ - public function initLangcode($langcode) { - - $this->initLangcodeMultiple(array($langcode)); - } - - /** - * @inheritdoc - */ - public function initLangcodeMultiple(array $langcodes) { - - foreach ($langcodes as $langcode) { - if (empty($this->stateByLanguage[$langcode])) { - $this->initState($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 getLocalSource() { - $source = NULL; - - if (!empty($this->stateByLanguage[$this->langcode]->localSource)) { - $source = $this->stateByLanguage[$this->langcode]->localSource; - } - - return $source; - } - - /** - * @inheritdoc - */ - public function getRemoteSource() { - $source = NULL; - - if (!empty($this->stateByLanguage[$this->langcode]->remoteSource)) { - $source = $this->stateByLanguage[$this->langcode]->localSource; - } - - return $source; - } - - /** - * @inheritdoc - */ - public function getLatestSourceTimestamp() { - return $this->getCurrentState()->getLatestSourceTimestamp; - } - - /** - * @inheritdoc - */ - public function getVersion() { - - return $this->projectVersion; - } - - /** - * @inheritdoc - */ - public function setVersion($version) { - - $this->projectVersion = $version; - } - - /** - * @inheritdoc - */ - public function remoteIsLatest() { - return $this->getCurrentState()->remoteIsLatest(); - } - - /** - * @inheritdoc - */ - public function remoteIsValid() { - return $this->getCurrentState()->remoteIsValid(); - } - - /** - * @inheritdoc - */ - public function remoteIsAvailable() { - return $this->getCurrentState()->remoteIsAvailable(); - } - - /** - * @inheritdoc - */ - public function localIsLatest() { - return $this->getCurrentState()->localIsLatest(); - } - - /** - * @inheritdoc - */ - public function setLocalSource($data) { - $this->getCurrentState()->setLocalSource($data); - - } - - /** - * @inheritdoc - */ - public function setRemoteSource($data) { - $this->getCurrentState()->setRemoteSource($data); - } - - /** - * @inheritdoc - */ - public function setTimestamp($timestamp) { - $this->getCurrentState()->setTimestamp($timestamp); - } - - /** - * @inheritdoc - */ - public function isTranslated() { - return $this->getCurrentState()->isTranslated(); - } - - /** - * @inheritdoc - */ - public function hasUpdates() { - return $this->getCurrentState()->hasUpdates(); - } - - /** - * @inheritdoc - */ - public function hasNoTranslation() { - return $this->getCurrentState()->hasNoTranslation(); - } - - /** - * @inheritdoc - */ - public function isUpToDate() { - return $this->getCurrentState()->isUpToDate(); - } - - /** - * Returns the server pattern. - * @return string - */ - public function getServerPattern() { - return $this->serverPattern; - } - - /** - * Import data into the class from array data structure. - * - * @param array $data - * Array of project data. - */ - protected function importData($data) { - - foreach ($this->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 (e.g. 'Action'). - if ($this->id == 'drupal') { - $this->name = 'Drupal core'; - } - } - - /* - * 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 (!$this->getCurrentState()) { - $data = array( - 'timestamp' => 0, - 'lastChecked' => 0, - 'latestSource' => '', - 'latestSourceTimestamp' => 0, - 'localSource' => LocalProjectSource::create($this, $langcode), - 'remoteSource' => RemoteProjectSource::create($this, $langcode), - ); - $this->setCurrentState(LocaleProjectTranslationState::create($data), $langcode); - } - } - - /** - * Returns mapping for array to properties. - * - * @return array - * Key value array of mapping values. Key: storage array key; value: class - * property name. - */ - protected function getMapping() { - - // A limited set of properties are mapped. Only those that are stored. - return array( - 'id' => 'id', - 'name' => 'name', - 'version' => 'projectVersion', - 'core' => 'projectCore', - 'status' => 'status', - 'server_pattern' => 'serverPattern', - 'state_by_language' => 'stateByLanguage', - ); - } - - /**r - * Returns the current LocalProjectState depending on the language. - * @return LocaleProjectTranslationState - */ - protected function getCurrentState() { - return isset($this->stateByLanguage[$this->langcode]) ? $this->stateByLanguage[$this->langcode] : null; - } - - /** - * @param LocaleProjectTranslationState $state - * The project translation state. - * @param string $langcode - * The language code. - */ - protected function setCurrentState($state, $langcode) { - $this->stateByLanguage[$langcode] = $state; - } -} diff --git a/core/modules/locale/src/LocaleTranslatableProjectInterface.php b/core/modules/locale/src/LocaleTranslatableProjectInterface.php deleted file mode 100644 index 83a5054..0000000 --- a/core/modules/locale/src/LocaleTranslatableProjectInterface.php +++ /dev/null @@ -1,148 +0,0 @@ -filename; + } + + /** + * @inheritdoc + */ + public function getDirectory() { + return $this->directory; + } + + /** + * @inheritdoc + */ + public function getUri() { + return $this->uri; + } + + /** + * @inheritdoc + */ + public function getTimestamp() { + return $this->timestamp; + } + + /** + * @inheritdoc + */ + public function getLastChecked() { + return $this->lastChecked; + } + + /** + * @inheritdoc + */ + public function setFilename($name) { + return $this->filename = $name; + } + + /** + * @inheritdoc + */ + public function setDirectory($directory) { + return $this->directory = $directory; + } + + /** + * @inheritdoc + */ + public function setUri($uri) { + $this->uri = $uri; + } + + /** + * @inheritdoc + */ + public function setTimestamp($timestamp) { + $this->timestamp = $timestamp; + $this->lastChecked = REQUEST_TIME; + } + + /** + * @inheritdoc + */ + public function setProject($project) { + $this->project = $project; + } + + /** + * @inheritdoc + */ + public function toArray() { + return array( + 'uri' => $this->uri, + 'filename' => $this->filename, + 'timestamp' => $this->timestamp, + 'lastChecked' => $this->lastChecked, + 'langcode' => $this->project->getLangcode(), + 'project' => $this->project->id(), + 'version' => $this->project->getVersion(), + ); + } + + /** + * @inheritdoc + */ + public function isAvailable() { + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function serialize() { + $source = clone $this; + // Don't serialize the parent project object. + unset($source->project); + return serialize(get_object_vars($source)); + + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) { + $data = unserialize($serialized); + foreach ($data as $key => $value) { + $this->{$key} = $value; + } + // TranslatableProject::unserialize takes care of restoring $this->project. + } + + /** + * 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 \Drupal\locale\TranslatableProjectInterface $project + * Translatable project. + * @param string $langcode + * Language code of the translation file. + * + * @return string + * File uri with replaced placeholders. + */ + protected function buildServerPattern($template, \Drupal\locale\TranslatableProjectInterface $project, $langcode) { + $variables = array( + '%project' => $project->id(), + '%version' => $project->getVersion(), + '%core' => $project->getCoreVersion(), + '%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; + } +} \ No newline at end of file diff --git a/core/modules/locale/src/ProjectSourceInterface.php b/core/modules/locale/src/ProjectSourceInterface.php index ca36145..26b0cb9 100644 --- a/core/modules/locale/src/ProjectSourceInterface.php +++ b/core/modules/locale/src/ProjectSourceInterface.php @@ -1,45 +1,92 @@ project = $project; + $server_pattern = $project->getServerPattern(); - if (self::fileIsRemote($server_pattern)) { - $source->filename = self::buildServerPattern(basename($server_pattern), $langcode); - $source->uri = self::buildServerPattern($server_pattern, $langcode); + if ($this->fileIsRemote($server_pattern)) { + $this->filename = self::buildServerPattern(basename($server_pattern), $this->project, $langcode); + $this->uri = self::buildServerPattern($server_pattern, $this->project, $langcode); } - - return $source; } /** - * Returns TRUE if is valid. - * Development release translations are not supported for remote sources. - * @return bool + * @inheritdoc */ - public function isValid() { - return empty($this->projectVersion) || strpos('dev', $this->projectVersion) === FALSE; + public function setTimestamp($timestamp) { + parent::setTimestamp($timestamp); + $this->project->setRemoteTimestamp($timestamp); } /** - * Return TRUE if remote translation is available. - * @return bool + * @inheritdoc */ public function isAvailable() { - return !$this->projectVersion || strpos('dev', $this->projectVersion) === FALSE; + // Development release translations are not supported for remote sources. + if (strpos('dev', $this->project->getVersion()) !== FALSE) { + return FALSE; + } + + return TRUE; } } \ No newline at end of file diff --git a/core/modules/locale/src/Tests/LocaleUpdateBase.php b/core/modules/locale/src/Tests/LocaleUpdateBase.php index b27433f..8551c95 100644 --- a/core/modules/locale/src/Tests/LocaleUpdateBase.php +++ b/core/modules/locale/src/Tests/LocaleUpdateBase.php @@ -296,7 +296,7 @@ protected function setCurrentTranslations() { 'latestSourceTimestamp' => $this->timestampMedium, )), ); - $project = new \Drupal\locale\LocaleTranslatableProject($project_data); + $project = new \Drupal\locale\TranslatableProject($project_data); $project->clearTranslationStates(); \Drupal::service('locale.project')->set($project); } diff --git a/core/modules/locale/src/Tests/LocaleUpdateInterfaceTest.php b/core/modules/locale/src/Tests/LocaleUpdateInterfaceTest.php index afe015a..bc45be0 100644 --- a/core/modules/locale/src/Tests/LocaleUpdateInterfaceTest.php +++ b/core/modules/locale/src/Tests/LocaleUpdateInterfaceTest.php @@ -7,6 +7,8 @@ namespace Drupal\locale\Tests; +use Drupal\locale\LocalProjectSource; + /** * Tests for the user interface of project interface translations. * @@ -49,12 +51,15 @@ public function testInterface() { $this->addLanguage('de'); // Override Drupal core translation status as 'up-to-date'. - /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + /** @var \Drupal\locale\TranslatableProjectInterface $project */ $project = \Drupal::service('locale.project')->get('drupal'); - $project->setLangcode('de'); - $project->setTimestamp(REQUEST_TIME); + $project->getStateByLanguage('de')->setTimestamp(REQUEST_TIME); \Drupal::service('locale.project')->set($project); + // Reset the project cache to make sure we get fresh data. + \Drupal::service('locale.project')->resetCache(); +//debug(\Drupal::service('locale.project')->getAll()); + // One language added, all translations up to date. $this->drupalGet('admin/reports/status'); $this->assertText(t('Translation update status'), 'Status message'); @@ -64,13 +69,7 @@ public function testInterface() { // Set locale_test_translate module to have a local translation available. $project = \Drupal::service('locale.project')->get('locale_test_translate'); - $project->setLangcode('de'); - $project->setLocalSource((object) array( - 'timestamp' => REQUEST_TIME + 100, - 'filename' => 'TEST', - 'directory' => 'TEST', - 'uri' => 'TEST', - )); + $project->getStateByLanguage('de')->getLocalSource()->setTimestamp(REQUEST_TIME + 100); \Drupal::service('locale.project')->set($project); // Check if updates are available for German. @@ -82,8 +81,7 @@ public function testInterface() { // Set locale_test_translate module to have a dev release and no // translation found. - $project = \Drupal::service('locale.project')->get('locale_test_translate'); - $project->setLangcode('de'); + $project = \Drupal::service('locale.project')->get('locale_test_translate')->getStateByLanguage('de'); $project->setVersion('1.3-dev'); $project->clearTranslationStates(); \Drupal::service('locale.project')->set($project); @@ -98,11 +96,11 @@ public function testInterface() { $this->assertText(t('No translation files are provided for development releases.'), 'Release info'); // Override Drupal core translation status as 'no translations found'. - $status = locale_translation_get_status(); - $status['drupal']['de']->type = ''; - $status['drupal']['de']->timestamp = 0; - $status['drupal']['de']->version = '8.1.1'; - \Drupal::state()->set('locale.translation_status', $status); + $project = \Drupal::service('locale.project')->get('drupal')->getStateByLanguage('de'); + $project->setVersion('8.1.1'); + $project->clearTranslationStates(); + \Drupal::service('locale.project')->set($project); +//debug($project); // Check if Drupal core is not translated. $this->drupalGet('admin/reports/translations'); @@ -110,11 +108,10 @@ public function testInterface() { $this->assertText(t('@module (@version).', array('@module' => t('Drupal core'), '@version' => '8.1.1')), 'Release details'); // Override Drupal core translation status as 'translations available'. - $status = locale_translation_get_status(); - $status['drupal']['de']->type = 'local'; - $status['drupal']['de']->files['local']->timestamp = REQUEST_TIME; - $status['drupal']['de']->files['local']->info['version'] = '8.1.1'; - \Drupal::state()->set('locale.translation_status', $status); + $project = \Drupal::service('locale.project')->get('drupal')->getStateByLanguage('de'); + $project->getLocalSource()->setTimestamp(REQUEST_TIME); + \Drupal::service('locale.project')->set($project); +//debug($project); // Check if translations are available for Drupal core. $this->drupalGet('admin/reports/translations'); diff --git a/core/modules/locale/src/Tests/LocaleUpdateTest.php b/core/modules/locale/src/Tests/LocaleUpdateTest.php index b53a902..f1f53df 100644 --- a/core/modules/locale/src/Tests/LocaleUpdateTest.php +++ b/core/modules/locale/src/Tests/LocaleUpdateTest.php @@ -86,23 +86,23 @@ public function testUpdateCheckStatus() { // Get status of translation sources at local file system. $this->drupalGet('admin/reports/translations/check'); - /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + /** @var \Drupal\locale\TranslatableProjectInterface $project */ $project = \Drupal::service('locale.project')->get('contrib_module_one'); - $project->setLangcode('de'); + $project->getStateByLanguage('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'); + $project->getStateByLanguage('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'); + $project->getStateByLanguage('de'); $this->assertTrue($project->localIsLatest(), 'Translation of locale_test found'); $project = \Drupal::service('locale.project')->get('custom_module_one'); - $project->setLangcode('de'); + $project->getStateByLanguage('de'); $this->assertTrue($project->localIsLatest(), 'Translation of custom_module_one found'); // Set the test conditions. @@ -117,28 +117,28 @@ public function testUpdateCheckStatus() { // Reset the project cache to make sure we get fresh data. \Drupal::service('locale.project')->resetCache(); - /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + /** @var \Drupal\locale\TranslatableProjectInterface $project */ $project = \Drupal::service('locale.project')->get('contrib_module_one'); - $project->setLangcode('de'); + $project->getStateByLanguage('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'); + $project->getStateByLanguage('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'); + $project->getStateByLanguage('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'); + $project->getStateByLanguage('de'); $this->assertTrue($project->localIsLatest(), 'Translation of locale_test found'); $project = \Drupal::service('locale.project')->get('custom_module_one'); - $project->setLangcode('de'); + $project->getStateByLanguage('de'); $this->assertTrue($project->localIsLatest(), 'Translation of custom_module_one found'); } @@ -177,17 +177,17 @@ public function testUpdateImportSourceRemote() { $this->drupalPostForm('admin/reports/translations', array(), t('Update translations')); // Check if the translation has been updated, using the status cache. - /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + /** @var \Drupal\locale\TranslatableProjectInterface $project */ $project = \Drupal::service('locale.project')->get('contrib_module_one'); - $project->setLangcode('de'); + $project->getStateByLanguage('de'); $this->assertTrue($project->isUpToDate(), 'Translation of contrib_module_one found'); $project = \Drupal::service('locale.project')->get('contrib_module_two'); - $project->setLangcode('de'); + $project->getStateByLanguage('de'); $this->assertTrue($project->isUpToDate(), 'Translation of contrib_module_two found'); $project = \Drupal::service('locale.project')->get('contrib_module_three'); - $project->setLangcode('de'); + $project->getStateByLanguage('de'); $this->assertTrue($project->isUpToDate(), 'Translation of contrib_module_three found'); // Check the new translation status. @@ -239,17 +239,17 @@ public function testUpdateImportSourceLocal() { $this->drupalPostForm('admin/reports/translations', array(), t('Update translations')); // Check if the translation has been updated, using the status cache. - /** @var \Drupal\locale\LocaleTranslatableProjectInterface $project */ + /** @var \Drupal\locale\TranslatableProjectInterface $project */ $project = \Drupal::service('locale.project')->get('contrib_module_one'); - $project->setLangcode('de'); + $project->getStateByLanguage('de'); $this->assertTrue($project->isUpToDate(), 'Translation of contrib_module_one found'); $project = \Drupal::service('locale.project')->get('contrib_module_two'); - $project->setLangcode('de'); + $project->getStateByLanguage('de'); $this->assertTrue($project->isUpToDate(), 'Translation of contrib_module_two found'); $project = \Drupal::service('locale.project')->get('contrib_module_three'); - $project->setLangcode('de'); + $project->getStateByLanguage('de'); $this->assertTrue($project->isUpToDate(), 'Translation of contrib_module_three found'); // Check the new translation status. diff --git a/core/modules/locale/src/TranslatableProject.php b/core/modules/locale/src/TranslatableProject.php new file mode 100644 index 0000000..2bd21e4 --- /dev/null +++ b/core/modules/locale/src/TranslatableProject.php @@ -0,0 +1,470 @@ +importData($data); + } + + /** + * @inheritdoc + */ + public function id() { + return $this->id; + } + + /** + * @inheritdoc + */ + public function getName() { + return $this->name; + } + + /** + * @inheritdoc + */ + public function update($data) { + $old_version = $this->projectVersion; + $old_server_pattern = $this->serverPattern; + + unset($data['id']); + $this->importData($data); + + // Rebuild local and remote source and clear pending update states when + // version or server pattern have changed. + if ((isset($data['version']) && $old_version != $this->projectVersion) + || (isset($data['server_pattern']) && $old_server_pattern != $this->serverPattern)) { + foreach (array_keys($this->stateByLanguage) as $langcode) { + $this->stateByLanguage[$langcode]->localSource = new LocalProjectSource($this, $langcode); + $this->stateByLanguage[$langcode]->remoteSource = new RemoteProjectSource($this, $langcode); + } + $this->clearTranslationStates(); + } + } + + /** + * {@inheritdoc} + */ + public function serialize() { + $project = clone $this; + // Don't serialize the parent project object. + unset($project->langcode); + return serialize(get_object_vars($project)); + + } + + /** + * {@inheritdoc} + */ + public function unserialize($serialized) { + $data = unserialize($serialized); + foreach ($data as $key => $value) { + $this->{$key} = $value; + } + foreach (array_keys($this->stateByLanguage) as $langcode) { + $this->stateByLanguage[$langcode]->localSource->setProject($this); + $this->stateByLanguage[$langcode]->remoteSource->setProject($this); + } + } + + /** + * @inheritdoc + */ + public function getStateByLanguage($langcode) { + $this->langcode = $langcode; + if (!isset($this->stateByLanguage[$langcode])) { + $this->initLangcode($langcode); + } + + return $this; + } + + /** + * @inheritdoc + */ + public function initLangcode($langcode) { + $this->initLangcodeMultiple(array($langcode)); + } + + /** + * @inheritdoc + */ + public function initLangcodeMultiple(array $langcodes) { + foreach ($langcodes as $langcode) { + if (!isset($this->stateByLanguage[$langcode])) { + $this->initState($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) { + if ($this->stateByLanguage[$langcode]->latestSource == $this::LOCAL_SOURCE + || $this->stateByLanguage[$langcode]->latestSource == $this::REMOTE_SOURCE) { + $this->stateByLanguage[$langcode]->latestSource = ''; + $this->stateByLanguage[$langcode]->latestSourceTimestamp = 0; + } + } + } + + /** + * @inheritdoc + */ + public function getLocalSource() { + if (empty($this->getState()->localSource)) { + new LocalProjectSource($this, $this->langcode); + } + return $this->getState()->localSource; + } + + /** + * @inheritdoc + */ + public function getRemoteSource() { + if (empty($this->getState()->remoteSource)) { + new RemoteProjectSource($this, $this->langcode); + } + return $this->getState()->remoteSource; + } + + /** + * @inheritdoc + */ + public function getLatestSourceTimestamp() { + return $this->getState()->latestSourceTimestamp; + } + + /** + * @inheritdoc + */ + public function getVersion() { + return $this->projectVersion; + } + + /** + * @inheritdoc + */ + public function setVersion($version) { + $this->projectVersion = $version; + } + + /** + * @inheritdoc + */ + public function getCoreVersion() { + return $this->projectCore; + } + + /** + * @inheritdoc + */ + public function getLangcode() { + return $this->langcode; + } + + /** + * @inheritdoc + */ + public function remoteIsLatest() { + return $this->getState()->latestSource == $this::REMOTE_SOURCE; + } + + /** + * @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->getState()->latestSource == $this::REMOTE_SOURCE; + } + + /** + * @inheritdoc + */ + public function localIsLatest() { + return $this->getState()->latestSource == $this::LOCAL_SOURCE; + } + + /** + * @inheritdoc + */ + public function setLocalTimestamp($timestamp) { + + if ($timestamp) { + $this->checkLangcode(); + + // If the local translation is the most recent, we update the + // latestSource status. Otherwise mark the current translation as + // checked. + if ($timestamp > $this->getState()->latestSourceTimestamp + || ($timestamp >= $this->getState()->latestSourceTimestamp && $this->getState()->latestSource == $this::REMOTE_SOURCE)) { + $this->stateByLanguage[$this->langcode]->latestSource = $this::LOCAL_SOURCE; + $this->stateByLanguage[$this->langcode]->latestSourceTimestamp = $timestamp; + } + else { + $this->stateByLanguage[$this->langcode]->lastChecked = REQUEST_TIME; + } + } + } + + + /** + * @inheritdoc + */ + public function setRemoteTimestamp($timestamp) { + + if ($timestamp) { + $this->checkLangcode(); + + // If the remote translation is the most recent, we update the + // latestSource status. Otherwise mark the current translation as + // checked. + if ($timestamp > $this->getState()->latestSourceTimestamp) { + $this->stateByLanguage[$this->langcode]->latestSource = $this::REMOTE_SOURCE; + $this->stateByLanguage[$this->langcode]->latestSourceTimestamp = $timestamp; + } + else { + $this->stateByLanguage[$this->langcode]->lastChecked = REQUEST_TIME; + } + } +} + + /** + * @inheritdoc + */ + public function setTimestamp($timestamp) { + if ($timestamp) { + $this->checkLangcode(); + + // The current translation has been updated. Mark the current translation + // as checked and store the timestamp. + $this->stateByLanguage[$this->langcode]->timestamp = $timestamp; + $this->stateByLanguage[$this->langcode]->lastChecked = REQUEST_TIME; + $this->stateByLanguage[$this->langcode]->latestSource = $this::CURRENT_SOURCE; + $this->stateByLanguage[$this->langcode]->latestSourceTimestamp = $timestamp; + } + } + + /** + * @inheritdoc + */ + public function isTranslated() { + return $this->getState()->timestamp > 0; + } + + /** + * @inheritdoc + */ + public function hasUpdates() { + return $this->getState()->latestSource == $this::LOCAL_SOURCE || $this->getState()->latestSource == $this::REMOTE_SOURCE; + } + + /** + * @inheritdoc + */ + public function hasNoTranslation() { + return $this->getState()->latestSource == ''; + } + + /** + * @inheritdoc + */ + public function isUpToDate() { + return $this->getState()->latestSource == $this::CURRENT_SOURCE; + } + + /** + * Returns the server pattern. + * @return string + */ + public function getServerPattern() { + return $this->serverPattern; + } + + /** + * Import data into the class from array data structure. + * + * @param array $data + * Array of project data. + */ + protected function importData($data) { + foreach ($this->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 (e.g. 'Action'). + if ($this->id == 'drupal') { + $this->name = 'Drupal core'; + } + } + + /* + * 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 (!$this->getState()) { + $state = new \stdClass(); + $state->timestamp = 0; + $state->lastChecked = 0; + $state->latestSource = ''; + $state->latestSourceTimestamp = 0; + $state->localSource = new LocalProjectSource($this, $langcode); + $state->remoteSource = new RemoteProjectSource($this, $langcode); + + $this->stateByLanguage[$langcode] = $state; +// } + } + + /** + * Returns mapping for array to properties. + * + * @return array + * Key value array of mapping values. Key: storage array key; value: class + * property name. + */ + protected function getMapping() { + // A limited set of properties are mapped. Only those that are stored. + return array( + 'id' => 'id', + 'name' => 'name', + 'version' => 'projectVersion', + 'core' => 'projectCore', + 'status' => 'status', + 'server_pattern' => 'serverPattern', + 'state_by_language' => 'stateByLanguage', + ); + } + + /** + * @todo + * + * @throws \Exception when the required language code was not set. + */ + protected function checkLangcode() { + if (empty($this->langcode)) { + throw new \Exception('Required language code was not set.'); + } + } + + /** + * Returns the current translations state of the currently set language. + * + * @return object + */ + protected function getState() { + $this->checkLangcode(); + return $this->stateByLanguage[$this->langcode]; + } + + /** + * Set the current translations state. + * + * @param \Drupal\locale\TranslatableProjectInterface $state + * The project translation state. + * + * @param string $langcode + * The language code. + */ + protected function setState($state, $langcode) { + $this->stateByLanguage[$langcode] = $state; + } +} diff --git a/core/modules/locale/src/TranslatableProjectInterface.php b/core/modules/locale/src/TranslatableProjectInterface.php new file mode 100644 index 0000000..9c34163 --- /dev/null +++ b/core/modules/locale/src/TranslatableProjectInterface.php @@ -0,0 +1,260 @@ +