Chris@0: deleteAll(); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Builds list of projects and stores the result in the database. Chris@0: * Chris@0: * The project data is based on the project list supplied by the Update module. Chris@0: * Only the properties required by Locale module is included and additional Chris@0: * (custom) modules and translation server data is added. Chris@0: * Chris@0: * In case the Update module is disabled this function will return an empty Chris@0: * array. Chris@0: * Chris@0: * @return array Chris@0: * Array of project data: Chris@0: * - "name": Project system name. Chris@0: * - "project_type": Project type, e.g. 'module', 'theme'. Chris@0: * - "core": Core release version, e.g. 8.x Chris@0: * - "version": Project release version, e.g. 8.x-1.0 Chris@0: * See http://drupalcode.org/project/drupalorg.git/blob/refs/heads/7.x-3.x:/drupalorg_project/plugins/release_packager/DrupalorgProjectPackageRelease.class.php#l219 Chris@0: * for how the version strings are created. Chris@0: * - "server_pattern": Translation server po file pattern. Chris@0: * - "status": Project status, 1 = enabled. Chris@0: */ Chris@0: function locale_translation_build_projects() { Chris@0: // Get the project list based on .info.yml files. Chris@0: $projects = locale_translation_project_list(); Chris@0: Chris@0: // Mark all previous projects as disabled and store new project data. Chris@0: \Drupal::service('locale.project')->disableAll(); Chris@0: Chris@0: $default_server = locale_translation_default_translation_server(); Chris@0: Chris@0: foreach ($projects as $name => $data) { Chris@0: // For dev releases, remove the '-dev' part and trust the translation server Chris@0: // to fall back to the latest stable release for that branch. Chris@0: if (isset($data['info']['version']) && strpos($data['info']['version'], '-dev')) { Chris@0: if (preg_match("/^(\d+\.x-\d+\.).*$/", $data['info']['version'], $matches)) { Chris@0: // Example matches: 8.x-1.x-dev, 8.x-1.0-alpha1+5-dev => 8.x-1.x Chris@0: $data['info']['version'] = $matches[1] . 'x'; Chris@0: } Chris@0: elseif (preg_match("/^(\d+\.\d+\.).*$/", $data['info']['version'], $matches)) { Chris@0: // Example match: 8.0.0-dev => 8.0.x (Drupal core) Chris@0: $data['info']['version'] = $matches[1] . 'x'; Chris@0: } Chris@0: } Chris@0: Chris@0: // For every project store information. Chris@0: $data += [ Chris@0: 'name' => $name, Chris@0: 'version' => isset($data['info']['version']) ? $data['info']['version'] : '', Chris@0: 'core' => isset($data['info']['core']) ? $data['info']['core'] : \Drupal::CORE_COMPATIBILITY, Chris@0: // A project can provide the path and filename pattern to download the Chris@0: // gettext file. Use the default if not. Chris@0: 'server_pattern' => isset($data['info']['interface translation server pattern']) && $data['info']['interface translation server pattern'] ? $data['info']['interface translation server pattern'] : $default_server['pattern'], Chris@0: 'status' => !empty($data['project_status']) ? 1 : 0, Chris@0: ]; Chris@0: Chris@0: $project = (object) $data; Chris@0: $projects[$name] = $project; Chris@0: Chris@0: // Create or update the project record. Chris@0: \Drupal::service('locale.project')->set($project->name, $data); Chris@0: Chris@0: // Invalidate the cache of translatable projects. Chris@0: locale_translation_clear_cache_projects(); Chris@0: } Chris@0: return $projects; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Fetch an array of projects for translation update. Chris@0: * Chris@0: * @return array Chris@0: * Array of project data including .info.yml file data. Chris@0: */ Chris@0: function locale_translation_project_list() { Chris@0: $projects = &drupal_static(__FUNCTION__, []); Chris@0: if (empty($projects)) { Chris@0: $projects = []; Chris@0: Chris@0: $additional_whitelist = [ Chris@0: 'interface translation project', Chris@0: 'interface translation server pattern', Chris@0: ]; Chris@0: $module_data = _locale_translation_prepare_project_list(system_rebuild_module_data(), 'module'); Chris@0: $theme_data = _locale_translation_prepare_project_list(\Drupal::service('theme_handler')->rebuildThemeData(), 'theme'); Chris@0: $project_info = new ProjectInfo(); Chris@0: $project_info->processInfoList($projects, $module_data, 'module', TRUE, $additional_whitelist); Chris@0: $project_info->processInfoList($projects, $theme_data, 'theme', TRUE, $additional_whitelist); Chris@0: Chris@0: // Allow other modules to alter projects before fetching and comparing. Chris@0: \Drupal::moduleHandler()->alter('locale_translation_projects', $projects); Chris@0: } Chris@0: return $projects; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Prepare module and theme data. Chris@0: * Chris@0: * Modify .info.yml file data before it is processed by Chris@0: * \Drupal\Core\Utility\ProjectInfo->processInfoList(). In order for Chris@0: * \Drupal\Core\Utility\ProjectInfo->processInfoList() to recognize a project, Chris@0: * it requires the 'project' parameter in the .info.yml file data. Chris@0: * Chris@0: * Custom modules or themes can bring their own gettext translation file. To Chris@0: * enable import of this file the module or theme defines "interface translation Chris@0: * project = myproject" in its .info.yml file. This function will add a project Chris@0: * "myproject" to the info data. Chris@0: * Chris@0: * @param \Drupal\Core\Extension\Extension[] $data Chris@0: * Array of .info.yml file data. Chris@0: * @param string $type Chris@0: * The project type. i.e. module, theme. Chris@0: * Chris@0: * @return array Chris@0: * Array of .info.yml file data. Chris@0: */ Chris@0: function _locale_translation_prepare_project_list($data, $type) { Chris@0: foreach ($data as $name => $file) { Chris@0: // Include interface translation projects. To allow Chris@0: // \Drupal\Core\Utility\ProjectInfo->processInfoList() to identify this as Chris@0: // a project the 'project' property is filled with the Chris@0: // 'interface translation project' value. Chris@0: if (isset($file->info['interface translation project'])) { Chris@0: $data[$name]->info['project'] = $file->info['interface translation project']; Chris@0: } Chris@0: } Chris@0: return $data; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Retrieve data for default server. Chris@0: * Chris@0: * @return array Chris@0: * Array of server parameters: Chris@0: * - "pattern": URI containing po file pattern. Chris@0: */ Chris@0: function locale_translation_default_translation_server() { Chris@0: $pattern = \Drupal::config('locale.settings')->get('translation.default_server_pattern'); Chris@0: // An additional check is required here. During the upgrade process Chris@0: // \Drupal::config()->get() returns NULL. We use the defined value as Chris@0: // fallback. Chris@17: $pattern = $pattern ? $pattern : LOCALE_TRANSLATION_DEFAULT_SERVER_PATTERN; Chris@0: Chris@0: return [ Chris@0: 'pattern' => $pattern, Chris@0: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Check for the latest release of project translations. Chris@0: * Chris@0: * @param array $projects Chris@0: * Array of project names to check. Defaults to all translatable projects. Chris@0: * @param string $langcodes Chris@0: * Array of language codes. Defaults to all translatable languages. Chris@0: * Chris@0: * @return array Chris@0: * Available sources indexed by project and language. Chris@0: * Chris@0: * @todo Return batch or NULL. Chris@0: */ Chris@0: function locale_translation_check_projects($projects = [], $langcodes = []) { Chris@0: if (locale_translation_use_remote_source()) { Chris@0: // Retrieve the status of both remote and local translation sources by Chris@0: // using a batch process. Chris@0: locale_translation_check_projects_batch($projects, $langcodes); Chris@0: } Chris@0: else { Chris@0: // Retrieve and save the status of local translations only. Chris@0: locale_translation_check_projects_local($projects, $langcodes); Chris@0: \Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets and stores the status and timestamp of remote po files. Chris@0: * Chris@0: * A batch process is used to check for po files at remote locations and (when Chris@0: * configured) to check for po files in the local file system. The most recent Chris@0: * translation source states are stored in the state variable Chris@0: * 'locale.translation_status'. Chris@0: * Chris@0: * @param array $projects Chris@0: * Array of project names to check. Defaults to all translatable projects. Chris@0: * @param string $langcodes Chris@0: * Array of language codes. Defaults to all translatable languages. Chris@0: */ Chris@0: function locale_translation_check_projects_batch($projects = [], $langcodes = []) { Chris@0: // Build and set the batch process. Chris@0: $batch = locale_translation_batch_status_build($projects, $langcodes); Chris@0: batch_set($batch); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Builds a batch to get the status of remote and local translation files. Chris@0: * Chris@0: * The batch process fetches the state of both local and (if configured) remote Chris@0: * translation files. The data of the most recent translation is stored per Chris@0: * per project and per language. This data is stored in a state variable Chris@0: * 'locale.translation_status'. The timestamp it was last updated is stored Chris@0: * in the state variable 'locale.translation_last_checked'. Chris@0: * Chris@0: * @param array $projects Chris@0: * Array of project names for which to check the state of translation files. Chris@0: * Defaults to all translatable projects. Chris@0: * @param array $langcodes Chris@0: * Array of language codes. Defaults to all translatable languages. Chris@0: * Chris@0: * @return array Chris@0: * Batch definition array. Chris@0: */ Chris@0: function locale_translation_batch_status_build($projects = [], $langcodes = []) { Chris@0: $projects = $projects ? $projects : array_keys(locale_translation_get_projects()); Chris@0: $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list()); Chris@0: $options = _locale_translation_default_update_options(); Chris@0: Chris@0: $operations = _locale_translation_batch_status_operations($projects, $langcodes, $options); Chris@0: Chris@0: $batch = [ Chris@0: 'operations' => $operations, Chris@0: 'title' => t('Checking translations'), Chris@0: 'progress_message' => '', Chris@0: 'finished' => 'locale_translation_batch_status_finished', Chris@0: 'error_message' => t('Error checking translation updates.'), Chris@0: 'file' => drupal_get_path('module', 'locale') . '/locale.batch.inc', Chris@0: ]; Chris@0: return $batch; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Helper function to construct batch operations checking remote translation Chris@0: * status. Chris@0: * Chris@0: * @param array $projects Chris@0: * Array of project names to be processed. Chris@0: * @param array $langcodes Chris@0: * Array of language codes. Chris@0: * @param array $options Chris@0: * Batch processing options. Chris@0: * Chris@0: * @return array Chris@0: * Array of batch operations. Chris@0: */ Chris@0: function _locale_translation_batch_status_operations($projects, $langcodes, $options = []) { Chris@0: $operations = []; Chris@0: Chris@0: foreach ($projects as $project) { Chris@0: foreach ($langcodes as $langcode) { Chris@0: // Check status of local and remote translation sources. Chris@0: $operations[] = ['locale_translation_batch_status_check', [$project, $langcode, $options]]; Chris@0: } Chris@0: } Chris@0: Chris@0: return $operations; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Check and store the status and timestamp of local po files. Chris@0: * Chris@0: * Only po files in the local file system are checked. Any remote translation Chris@0: * files will be ignored. Chris@0: * Chris@0: * Projects may contain a server_pattern option containing a pattern of the Chris@0: * path to the po source files. If no server_pattern is defined the default Chris@0: * translation directory is checked for the po file. When a server_pattern is Chris@0: * defined the specified location is checked. The server_pattern can be set in Chris@0: * the module's .info.yml file or by using Chris@0: * hook_locale_translation_projects_alter(). Chris@0: * Chris@0: * @param array $projects Chris@0: * Array of project names for which to check the state of translation files. Chris@0: * Defaults to all translatable projects. Chris@0: * @param array $langcodes Chris@0: * Array of language codes. Defaults to all translatable languages. Chris@0: */ Chris@0: function locale_translation_check_projects_local($projects = [], $langcodes = []) { Chris@0: $projects = locale_translation_get_projects($projects); Chris@0: $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list()); Chris@0: Chris@0: // For each project and each language we check if a local po file is Chris@0: // available. When found the source object is updated with the appropriate Chris@0: // type and timestamp of the po file. Chris@0: foreach ($projects as $name => $project) { Chris@0: foreach ($langcodes as $langcode) { Chris@0: $source = locale_translation_source_build($project, $langcode); Chris@0: $file = locale_translation_source_check_file($source); Chris@0: locale_translation_status_save($name, $langcode, LOCALE_TRANSLATION_LOCAL, $file); Chris@0: } Chris@0: } Chris@0: }