Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 /**
|
Chris@0
|
4 * @file
|
Chris@0
|
5 * The API for comparing project translation status with available translation.
|
Chris@0
|
6 */
|
Chris@0
|
7
|
Chris@0
|
8 use Drupal\Core\Utility\ProjectInfo;
|
Chris@0
|
9
|
Chris@0
|
10 /**
|
Chris@0
|
11 * Load common APIs.
|
Chris@0
|
12 */
|
Chris@0
|
13 // @todo Combine functions differently in files to avoid unnecessary includes.
|
Chris@0
|
14 // Follow-up issue: https://www.drupal.org/node/1834298.
|
Chris@0
|
15 require_once __DIR__ . '/locale.translation.inc';
|
Chris@0
|
16
|
Chris@0
|
17 /**
|
Chris@0
|
18 * Clear the project data table.
|
Chris@0
|
19 */
|
Chris@0
|
20 function locale_translation_flush_projects() {
|
Chris@0
|
21 \Drupal::service('locale.project')->deleteAll();
|
Chris@0
|
22 }
|
Chris@0
|
23
|
Chris@0
|
24 /**
|
Chris@0
|
25 * Builds list of projects and stores the result in the database.
|
Chris@0
|
26 *
|
Chris@0
|
27 * The project data is based on the project list supplied by the Update module.
|
Chris@0
|
28 * Only the properties required by Locale module is included and additional
|
Chris@0
|
29 * (custom) modules and translation server data is added.
|
Chris@0
|
30 *
|
Chris@0
|
31 * In case the Update module is disabled this function will return an empty
|
Chris@0
|
32 * array.
|
Chris@0
|
33 *
|
Chris@0
|
34 * @return array
|
Chris@0
|
35 * Array of project data:
|
Chris@0
|
36 * - "name": Project system name.
|
Chris@0
|
37 * - "project_type": Project type, e.g. 'module', 'theme'.
|
Chris@0
|
38 * - "core": Core release version, e.g. 8.x
|
Chris@0
|
39 * - "version": Project release version, e.g. 8.x-1.0
|
Chris@0
|
40 * 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
|
41 * for how the version strings are created.
|
Chris@0
|
42 * - "server_pattern": Translation server po file pattern.
|
Chris@0
|
43 * - "status": Project status, 1 = enabled.
|
Chris@0
|
44 */
|
Chris@0
|
45 function locale_translation_build_projects() {
|
Chris@0
|
46 // Get the project list based on .info.yml files.
|
Chris@0
|
47 $projects = locale_translation_project_list();
|
Chris@0
|
48
|
Chris@0
|
49 // Mark all previous projects as disabled and store new project data.
|
Chris@0
|
50 \Drupal::service('locale.project')->disableAll();
|
Chris@0
|
51
|
Chris@0
|
52 $default_server = locale_translation_default_translation_server();
|
Chris@0
|
53
|
Chris@0
|
54 foreach ($projects as $name => $data) {
|
Chris@0
|
55 // For dev releases, remove the '-dev' part and trust the translation server
|
Chris@0
|
56 // to fall back to the latest stable release for that branch.
|
Chris@0
|
57 if (isset($data['info']['version']) && strpos($data['info']['version'], '-dev')) {
|
Chris@0
|
58 if (preg_match("/^(\d+\.x-\d+\.).*$/", $data['info']['version'], $matches)) {
|
Chris@0
|
59 // Example matches: 8.x-1.x-dev, 8.x-1.0-alpha1+5-dev => 8.x-1.x
|
Chris@0
|
60 $data['info']['version'] = $matches[1] . 'x';
|
Chris@0
|
61 }
|
Chris@0
|
62 elseif (preg_match("/^(\d+\.\d+\.).*$/", $data['info']['version'], $matches)) {
|
Chris@0
|
63 // Example match: 8.0.0-dev => 8.0.x (Drupal core)
|
Chris@0
|
64 $data['info']['version'] = $matches[1] . 'x';
|
Chris@0
|
65 }
|
Chris@0
|
66 }
|
Chris@0
|
67
|
Chris@0
|
68 // For every project store information.
|
Chris@0
|
69 $data += [
|
Chris@0
|
70 'name' => $name,
|
Chris@0
|
71 'version' => isset($data['info']['version']) ? $data['info']['version'] : '',
|
Chris@0
|
72 'core' => isset($data['info']['core']) ? $data['info']['core'] : \Drupal::CORE_COMPATIBILITY,
|
Chris@0
|
73 // A project can provide the path and filename pattern to download the
|
Chris@0
|
74 // gettext file. Use the default if not.
|
Chris@0
|
75 '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
|
76 'status' => !empty($data['project_status']) ? 1 : 0,
|
Chris@0
|
77 ];
|
Chris@0
|
78
|
Chris@0
|
79 $project = (object) $data;
|
Chris@0
|
80 $projects[$name] = $project;
|
Chris@0
|
81
|
Chris@0
|
82 // Create or update the project record.
|
Chris@0
|
83 \Drupal::service('locale.project')->set($project->name, $data);
|
Chris@0
|
84
|
Chris@0
|
85 // Invalidate the cache of translatable projects.
|
Chris@0
|
86 locale_translation_clear_cache_projects();
|
Chris@0
|
87 }
|
Chris@0
|
88 return $projects;
|
Chris@0
|
89 }
|
Chris@0
|
90
|
Chris@0
|
91 /**
|
Chris@0
|
92 * Fetch an array of projects for translation update.
|
Chris@0
|
93 *
|
Chris@0
|
94 * @return array
|
Chris@0
|
95 * Array of project data including .info.yml file data.
|
Chris@0
|
96 */
|
Chris@0
|
97 function locale_translation_project_list() {
|
Chris@0
|
98 $projects = &drupal_static(__FUNCTION__, []);
|
Chris@0
|
99 if (empty($projects)) {
|
Chris@0
|
100 $projects = [];
|
Chris@0
|
101
|
Chris@0
|
102 $additional_whitelist = [
|
Chris@0
|
103 'interface translation project',
|
Chris@0
|
104 'interface translation server pattern',
|
Chris@0
|
105 ];
|
Chris@0
|
106 $module_data = _locale_translation_prepare_project_list(system_rebuild_module_data(), 'module');
|
Chris@0
|
107 $theme_data = _locale_translation_prepare_project_list(\Drupal::service('theme_handler')->rebuildThemeData(), 'theme');
|
Chris@0
|
108 $project_info = new ProjectInfo();
|
Chris@0
|
109 $project_info->processInfoList($projects, $module_data, 'module', TRUE, $additional_whitelist);
|
Chris@0
|
110 $project_info->processInfoList($projects, $theme_data, 'theme', TRUE, $additional_whitelist);
|
Chris@0
|
111
|
Chris@0
|
112 // Allow other modules to alter projects before fetching and comparing.
|
Chris@0
|
113 \Drupal::moduleHandler()->alter('locale_translation_projects', $projects);
|
Chris@0
|
114 }
|
Chris@0
|
115 return $projects;
|
Chris@0
|
116 }
|
Chris@0
|
117
|
Chris@0
|
118 /**
|
Chris@0
|
119 * Prepare module and theme data.
|
Chris@0
|
120 *
|
Chris@0
|
121 * Modify .info.yml file data before it is processed by
|
Chris@0
|
122 * \Drupal\Core\Utility\ProjectInfo->processInfoList(). In order for
|
Chris@0
|
123 * \Drupal\Core\Utility\ProjectInfo->processInfoList() to recognize a project,
|
Chris@0
|
124 * it requires the 'project' parameter in the .info.yml file data.
|
Chris@0
|
125 *
|
Chris@0
|
126 * Custom modules or themes can bring their own gettext translation file. To
|
Chris@0
|
127 * enable import of this file the module or theme defines "interface translation
|
Chris@0
|
128 * project = myproject" in its .info.yml file. This function will add a project
|
Chris@0
|
129 * "myproject" to the info data.
|
Chris@0
|
130 *
|
Chris@0
|
131 * @param \Drupal\Core\Extension\Extension[] $data
|
Chris@0
|
132 * Array of .info.yml file data.
|
Chris@0
|
133 * @param string $type
|
Chris@0
|
134 * The project type. i.e. module, theme.
|
Chris@0
|
135 *
|
Chris@0
|
136 * @return array
|
Chris@0
|
137 * Array of .info.yml file data.
|
Chris@0
|
138 */
|
Chris@0
|
139 function _locale_translation_prepare_project_list($data, $type) {
|
Chris@0
|
140 foreach ($data as $name => $file) {
|
Chris@0
|
141 // Include interface translation projects. To allow
|
Chris@0
|
142 // \Drupal\Core\Utility\ProjectInfo->processInfoList() to identify this as
|
Chris@0
|
143 // a project the 'project' property is filled with the
|
Chris@0
|
144 // 'interface translation project' value.
|
Chris@0
|
145 if (isset($file->info['interface translation project'])) {
|
Chris@0
|
146 $data[$name]->info['project'] = $file->info['interface translation project'];
|
Chris@0
|
147 }
|
Chris@0
|
148 }
|
Chris@0
|
149 return $data;
|
Chris@0
|
150 }
|
Chris@0
|
151
|
Chris@0
|
152 /**
|
Chris@0
|
153 * Retrieve data for default server.
|
Chris@0
|
154 *
|
Chris@0
|
155 * @return array
|
Chris@0
|
156 * Array of server parameters:
|
Chris@0
|
157 * - "pattern": URI containing po file pattern.
|
Chris@0
|
158 */
|
Chris@0
|
159 function locale_translation_default_translation_server() {
|
Chris@0
|
160 $pattern = \Drupal::config('locale.settings')->get('translation.default_server_pattern');
|
Chris@0
|
161 // An additional check is required here. During the upgrade process
|
Chris@0
|
162 // \Drupal::config()->get() returns NULL. We use the defined value as
|
Chris@0
|
163 // fallback.
|
Chris@17
|
164 $pattern = $pattern ? $pattern : LOCALE_TRANSLATION_DEFAULT_SERVER_PATTERN;
|
Chris@0
|
165
|
Chris@0
|
166 return [
|
Chris@0
|
167 'pattern' => $pattern,
|
Chris@0
|
168 ];
|
Chris@0
|
169 }
|
Chris@0
|
170
|
Chris@0
|
171 /**
|
Chris@0
|
172 * Check for the latest release of project translations.
|
Chris@0
|
173 *
|
Chris@0
|
174 * @param array $projects
|
Chris@0
|
175 * Array of project names to check. Defaults to all translatable projects.
|
Chris@0
|
176 * @param string $langcodes
|
Chris@0
|
177 * Array of language codes. Defaults to all translatable languages.
|
Chris@0
|
178 *
|
Chris@0
|
179 * @return array
|
Chris@0
|
180 * Available sources indexed by project and language.
|
Chris@0
|
181 *
|
Chris@0
|
182 * @todo Return batch or NULL.
|
Chris@0
|
183 */
|
Chris@0
|
184 function locale_translation_check_projects($projects = [], $langcodes = []) {
|
Chris@0
|
185 if (locale_translation_use_remote_source()) {
|
Chris@0
|
186 // Retrieve the status of both remote and local translation sources by
|
Chris@0
|
187 // using a batch process.
|
Chris@0
|
188 locale_translation_check_projects_batch($projects, $langcodes);
|
Chris@0
|
189 }
|
Chris@0
|
190 else {
|
Chris@0
|
191 // Retrieve and save the status of local translations only.
|
Chris@0
|
192 locale_translation_check_projects_local($projects, $langcodes);
|
Chris@0
|
193 \Drupal::state()->set('locale.translation_last_checked', REQUEST_TIME);
|
Chris@0
|
194 }
|
Chris@0
|
195 }
|
Chris@0
|
196
|
Chris@0
|
197 /**
|
Chris@0
|
198 * Gets and stores the status and timestamp of remote po files.
|
Chris@0
|
199 *
|
Chris@0
|
200 * A batch process is used to check for po files at remote locations and (when
|
Chris@0
|
201 * configured) to check for po files in the local file system. The most recent
|
Chris@0
|
202 * translation source states are stored in the state variable
|
Chris@0
|
203 * 'locale.translation_status'.
|
Chris@0
|
204 *
|
Chris@0
|
205 * @param array $projects
|
Chris@0
|
206 * Array of project names to check. Defaults to all translatable projects.
|
Chris@0
|
207 * @param string $langcodes
|
Chris@0
|
208 * Array of language codes. Defaults to all translatable languages.
|
Chris@0
|
209 */
|
Chris@0
|
210 function locale_translation_check_projects_batch($projects = [], $langcodes = []) {
|
Chris@0
|
211 // Build and set the batch process.
|
Chris@0
|
212 $batch = locale_translation_batch_status_build($projects, $langcodes);
|
Chris@0
|
213 batch_set($batch);
|
Chris@0
|
214 }
|
Chris@0
|
215
|
Chris@0
|
216 /**
|
Chris@0
|
217 * Builds a batch to get the status of remote and local translation files.
|
Chris@0
|
218 *
|
Chris@0
|
219 * The batch process fetches the state of both local and (if configured) remote
|
Chris@0
|
220 * translation files. The data of the most recent translation is stored per
|
Chris@0
|
221 * per project and per language. This data is stored in a state variable
|
Chris@0
|
222 * 'locale.translation_status'. The timestamp it was last updated is stored
|
Chris@0
|
223 * in the state variable 'locale.translation_last_checked'.
|
Chris@0
|
224 *
|
Chris@0
|
225 * @param array $projects
|
Chris@0
|
226 * Array of project names for which to check the state of translation files.
|
Chris@0
|
227 * Defaults to all translatable projects.
|
Chris@0
|
228 * @param array $langcodes
|
Chris@0
|
229 * Array of language codes. Defaults to all translatable languages.
|
Chris@0
|
230 *
|
Chris@0
|
231 * @return array
|
Chris@0
|
232 * Batch definition array.
|
Chris@0
|
233 */
|
Chris@0
|
234 function locale_translation_batch_status_build($projects = [], $langcodes = []) {
|
Chris@0
|
235 $projects = $projects ? $projects : array_keys(locale_translation_get_projects());
|
Chris@0
|
236 $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
|
Chris@0
|
237 $options = _locale_translation_default_update_options();
|
Chris@0
|
238
|
Chris@0
|
239 $operations = _locale_translation_batch_status_operations($projects, $langcodes, $options);
|
Chris@0
|
240
|
Chris@0
|
241 $batch = [
|
Chris@0
|
242 'operations' => $operations,
|
Chris@0
|
243 'title' => t('Checking translations'),
|
Chris@0
|
244 'progress_message' => '',
|
Chris@0
|
245 'finished' => 'locale_translation_batch_status_finished',
|
Chris@0
|
246 'error_message' => t('Error checking translation updates.'),
|
Chris@0
|
247 'file' => drupal_get_path('module', 'locale') . '/locale.batch.inc',
|
Chris@0
|
248 ];
|
Chris@0
|
249 return $batch;
|
Chris@0
|
250 }
|
Chris@0
|
251
|
Chris@0
|
252 /**
|
Chris@0
|
253 * Helper function to construct batch operations checking remote translation
|
Chris@0
|
254 * status.
|
Chris@0
|
255 *
|
Chris@0
|
256 * @param array $projects
|
Chris@0
|
257 * Array of project names to be processed.
|
Chris@0
|
258 * @param array $langcodes
|
Chris@0
|
259 * Array of language codes.
|
Chris@0
|
260 * @param array $options
|
Chris@0
|
261 * Batch processing options.
|
Chris@0
|
262 *
|
Chris@0
|
263 * @return array
|
Chris@0
|
264 * Array of batch operations.
|
Chris@0
|
265 */
|
Chris@0
|
266 function _locale_translation_batch_status_operations($projects, $langcodes, $options = []) {
|
Chris@0
|
267 $operations = [];
|
Chris@0
|
268
|
Chris@0
|
269 foreach ($projects as $project) {
|
Chris@0
|
270 foreach ($langcodes as $langcode) {
|
Chris@0
|
271 // Check status of local and remote translation sources.
|
Chris@0
|
272 $operations[] = ['locale_translation_batch_status_check', [$project, $langcode, $options]];
|
Chris@0
|
273 }
|
Chris@0
|
274 }
|
Chris@0
|
275
|
Chris@0
|
276 return $operations;
|
Chris@0
|
277 }
|
Chris@0
|
278
|
Chris@0
|
279 /**
|
Chris@0
|
280 * Check and store the status and timestamp of local po files.
|
Chris@0
|
281 *
|
Chris@0
|
282 * Only po files in the local file system are checked. Any remote translation
|
Chris@0
|
283 * files will be ignored.
|
Chris@0
|
284 *
|
Chris@0
|
285 * Projects may contain a server_pattern option containing a pattern of the
|
Chris@0
|
286 * path to the po source files. If no server_pattern is defined the default
|
Chris@0
|
287 * translation directory is checked for the po file. When a server_pattern is
|
Chris@0
|
288 * defined the specified location is checked. The server_pattern can be set in
|
Chris@0
|
289 * the module's .info.yml file or by using
|
Chris@0
|
290 * hook_locale_translation_projects_alter().
|
Chris@0
|
291 *
|
Chris@0
|
292 * @param array $projects
|
Chris@0
|
293 * Array of project names for which to check the state of translation files.
|
Chris@0
|
294 * Defaults to all translatable projects.
|
Chris@0
|
295 * @param array $langcodes
|
Chris@0
|
296 * Array of language codes. Defaults to all translatable languages.
|
Chris@0
|
297 */
|
Chris@0
|
298 function locale_translation_check_projects_local($projects = [], $langcodes = []) {
|
Chris@0
|
299 $projects = locale_translation_get_projects($projects);
|
Chris@0
|
300 $langcodes = $langcodes ? $langcodes : array_keys(locale_translatable_language_list());
|
Chris@0
|
301
|
Chris@0
|
302 // For each project and each language we check if a local po file is
|
Chris@0
|
303 // available. When found the source object is updated with the appropriate
|
Chris@0
|
304 // type and timestamp of the po file.
|
Chris@0
|
305 foreach ($projects as $name => $project) {
|
Chris@0
|
306 foreach ($langcodes as $langcode) {
|
Chris@0
|
307 $source = locale_translation_source_build($project, $langcode);
|
Chris@0
|
308 $file = locale_translation_source_check_file($source);
|
Chris@0
|
309 locale_translation_status_save($name, $langcode, LOCALE_TRANSLATION_LOCAL, $file);
|
Chris@0
|
310 }
|
Chris@0
|
311 }
|
Chris@0
|
312 }
|