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