locale.compare.inc 12 KB

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