cbf4d4a55ea47dc48804ba0cbd90a2217806439c
[yaffs-website] / web / core / modules / locale / src / Form / TranslationStatusForm.php
1 <?php
2
3 namespace Drupal\locale\Form;
4
5 use Drupal\Core\Extension\ModuleHandlerInterface;
6 use Drupal\Core\Form\FormBase;
7 use Drupal\Core\Form\FormStateInterface;
8 use Drupal\Core\State\StateInterface;
9 use Symfony\Component\DependencyInjection\ContainerInterface;
10
11 /**
12  * Provides a translation status form.
13  */
14 class TranslationStatusForm extends FormBase {
15
16   /**
17    * The module handler service.
18    *
19    * @var \Drupal\Core\Extension\ModuleHandlerInterface
20    */
21   protected $moduleHandler;
22
23   /**
24    * The Drupal state storage service.
25    *
26    * @var \Drupal\Core\State\StateInterface
27    */
28   protected $state;
29
30   /**
31    * {@inheritdoc}
32    */
33   public static function create(ContainerInterface $container) {
34     return new static(
35       $container->get('module_handler'),
36       $container->get('state')
37     );
38   }
39
40   /**
41    * Constructs a TranslationStatusForm object.
42    *
43    * @param ModuleHandlerInterface $module_handler
44    *   A module handler.
45    * @param \Drupal\Core\State\StateInterface $state
46    *   The state service.
47    */
48   public function __construct(ModuleHandlerInterface $module_handler, StateInterface $state) {
49     $this->moduleHandler = $module_handler;
50     $this->state = $state;
51   }
52
53   /**
54    * {@inheritdoc}
55    */
56   public function getFormId() {
57     return 'locale_translation_status_form';
58   }
59
60   /**
61    * Form builder for displaying the current translation status.
62    *
63    * @ingroup forms
64    */
65   public function buildForm(array $form, FormStateInterface $form_state) {
66     $languages = locale_translatable_language_list();
67     $status = locale_translation_get_status();
68     $options = [];
69     $languages_update = [];
70     $languages_not_found = [];
71     $projects_update = [];
72     // Prepare information about projects which have available translation
73     // updates.
74     if ($languages && $status) {
75       $updates = $this->prepareUpdateData($status);
76
77       // Build data options for the select table.
78       foreach ($updates as $langcode => $update) {
79         $title = $languages[$langcode]->getName();
80         $locale_translation_update_info = ['#theme' => 'locale_translation_update_info'];
81         foreach (['updates', 'not_found'] as $update_status) {
82           if (isset($update[$update_status])) {
83             $locale_translation_update_info['#' . $update_status] = $update[$update_status];
84           }
85         }
86         $options[$langcode] = [
87           'title' => [
88             'data' => [
89               '#title' => $title,
90               '#plain_text' => $title,
91             ],
92           ],
93           'status' => [
94             'class' => ['description', 'priority-low'],
95             'data' => $locale_translation_update_info,
96           ],
97         ];
98         if (!empty($update['not_found'])) {
99           $languages_not_found[$langcode] = $langcode;
100         }
101         if (!empty($update['updates'])) {
102           $languages_update[$langcode] = $langcode;
103         }
104       }
105       // Sort the table data on language name.
106       uasort($options, function ($a, $b) {
107         return strcasecmp($a['title']['data']['#title'], $b['title']['data']['#title']);
108       });
109       $languages_not_found = array_diff($languages_not_found, $languages_update);
110     }
111
112     $last_checked = $this->state->get('locale.translation_last_checked');
113     $form['last_checked'] = [
114       '#theme' => 'locale_translation_last_check',
115       '#last' => $last_checked,
116     ];
117
118     $header = [
119       'title' => [
120         'data' => $this->t('Language'),
121         'class' => ['title'],
122       ],
123       'status' => [
124         'data' => $this->t('Status'),
125         'class' => ['status', 'priority-low'],
126       ],
127     ];
128
129     if (!$languages) {
130       $empty = $this->t('No translatable languages available. <a href=":add_language">Add a language</a> first.', [
131         ':add_language' => $this->url('entity.configurable_language.collection'),
132       ]);
133     }
134     elseif ($status) {
135       $empty = $this->t('All translations up to date.');
136     }
137     else {
138       $empty = $this->t('No translation status available. <a href=":check">Check manually</a>.', [
139         ':check' => $this->url('locale.check_translation'),
140       ]);
141     }
142
143     // The projects which require an update. Used by the _submit callback.
144     $form['projects_update'] = [
145       '#type' => 'value',
146       '#value' => $projects_update,
147     ];
148
149     $form['langcodes'] = [
150       '#type' => 'tableselect',
151       '#header' => $header,
152       '#options' => $options,
153       '#default_value' => $languages_update,
154       '#empty' => $empty,
155       '#js_select' => TRUE,
156       '#multiple' => TRUE,
157       '#required' => TRUE,
158       '#not_found' => $languages_not_found,
159       '#after_build' => ['locale_translation_language_table'],
160     ];
161
162     $form['#attached']['library'][] = 'locale/drupal.locale.admin';
163
164     $form['actions'] = ['#type' => 'actions'];
165     if ($languages_update) {
166       $form['actions']['submit'] = [
167         '#type' => 'submit',
168         '#value' => $this->t('Update translations'),
169       ];
170     }
171
172     return $form;
173   }
174
175   /**
176    * Prepare information about projects with available translation updates.
177    *
178    * @param array $status
179    *   Translation update status as an array keyed by Project ID and langcode.
180    *
181    * @return array
182    *   Translation update status as an array keyed by language code and
183    *   translation update status.
184    */
185   protected function prepareUpdateData(array $status) {
186     $updates = [];
187
188     // @todo Calling locale_translation_build_projects() is an expensive way to
189     //   get a module name. In follow-up issue
190     //   https://www.drupal.org/node/1842362 the project name will be stored to
191     //   display use, like here.
192     $this->moduleHandler->loadInclude('locale', 'compare.inc');
193     $project_data = locale_translation_build_projects();
194
195     foreach ($status as $project_id => $project) {
196       foreach ($project as $langcode => $project_info) {
197         // No translation file found for this project-language combination.
198         if (empty($project_info->type)) {
199           $updates[$langcode]['not_found'][] = [
200             'name' => $project_info->name == 'drupal' ? $this->t('Drupal core') : $project_data[$project_info->name]->info['name'],
201             'version' => $project_info->version,
202             'info' => $this->createInfoString($project_info),
203           ];
204         }
205         // Translation update found for this project-language combination.
206         elseif ($project_info->type == LOCALE_TRANSLATION_LOCAL || $project_info->type == LOCALE_TRANSLATION_REMOTE) {
207           $local = isset($project_info->files[LOCALE_TRANSLATION_LOCAL]) ? $project_info->files[LOCALE_TRANSLATION_LOCAL] : NULL;
208           $remote = isset($project_info->files[LOCALE_TRANSLATION_REMOTE]) ? $project_info->files[LOCALE_TRANSLATION_REMOTE] : NULL;
209           $recent = _locale_translation_source_compare($local, $remote) == LOCALE_TRANSLATION_SOURCE_COMPARE_LT ? $remote : $local;
210           $updates[$langcode]['updates'][] = [
211             'name' => $project_info->name == 'drupal' ? $this->t('Drupal core') : $project_data[$project_info->name]->info['name'],
212             'version' => $project_info->version,
213             'timestamp' => $recent->timestamp,
214           ];
215         }
216       }
217     }
218     return $updates;
219   }
220
221   /**
222    * Provides debug info for projects in case translation files are not found.
223    *
224    * Translations files are being fetched either from Drupal translation server
225    * and local files or only from the local filesystem depending on the
226    * "Translation source" setting at admin/config/regional/translate/settings.
227    * This method will produce debug information including the respective path(s)
228    * based on this setting.
229    *
230    * @param array $project_info
231    *   An array which is the project information of the source.
232    *
233    * @return string
234    *   The string which contains debug information.
235    */
236   protected function createInfoString($project_info) {
237     $remote_path = isset($project_info->files['remote']->uri) ? $project_info->files['remote']->uri : FALSE;
238     $local_path = isset($project_info->files['local']->uri) ? $project_info->files['local']->uri : FALSE;
239
240     if (locale_translation_use_remote_source() && $remote_path && $local_path) {
241       return $this->t('File not found at %remote_path nor at %local_path', [
242         '%remote_path' => $remote_path,
243         '%local_path' => $local_path,
244       ]);
245     }
246     elseif ($local_path) {
247       return $this->t('File not found at %local_path', ['%local_path' => $local_path]);
248     }
249     return $this->t('Translation file location could not be determined.');
250   }
251
252   /**
253    * {@inheritdoc}
254    */
255   public function validateForm(array &$form, FormStateInterface $form_state) {
256     // Check if a language has been selected. 'tableselect' doesn't.
257     if (!array_filter($form_state->getValue('langcodes'))) {
258       $form_state->setErrorByName('', $this->t('Select a language to update.'));
259     }
260   }
261
262   /**
263    * {@inheritdoc}
264    */
265   public function submitForm(array &$form, FormStateInterface $form_state) {
266     $this->moduleHandler->loadInclude('locale', 'fetch.inc');
267     $this->moduleHandler->loadInclude('locale', 'bulk.inc');
268
269     $langcodes = array_filter($form_state->getValue('langcodes'));
270     $projects = array_filter($form_state->getValue('projects_update'));
271
272     // Set the translation import options. This determines if existing
273     // translations will be overwritten by imported strings.
274     $options = _locale_translation_default_update_options();
275
276     // If the status was updated recently we can immediately start fetching the
277     // translation updates. If the status is expired we clear it an run a batch to
278     // update the status and then fetch the translation updates.
279     $last_checked = $this->state->get('locale.translation_last_checked');
280     if ($last_checked < REQUEST_TIME - LOCALE_TRANSLATION_STATUS_TTL) {
281       locale_translation_clear_status();
282       $batch = locale_translation_batch_update_build([], $langcodes, $options);
283       batch_set($batch);
284     }
285     else {
286       // Set a batch to download and import translations.
287       $batch = locale_translation_batch_fetch_build($projects, $langcodes, $options);
288       batch_set($batch);
289       // Set a batch to update configuration as well.
290       if ($batch = locale_config_batch_update_components($options, $langcodes)) {
291         batch_set($batch);
292       }
293     }
294   }
295
296 }