Security update for Core, with self-updated composer
[yaffs-website] / web / core / modules / simpletest / src / Form / SimpletestTestForm.php
1 <?php
2
3 namespace Drupal\simpletest\Form;
4
5 use Drupal\Core\Form\FormBase;
6 use Drupal\Core\Form\FormStateInterface;
7 use Drupal\Core\Render\RendererInterface;
8 use Drupal\simpletest\TestDiscovery;
9 use Symfony\Component\DependencyInjection\ContainerInterface;
10
11 /**
12  * List tests arranged in groups that can be selected and run.
13  */
14 class SimpletestTestForm extends FormBase {
15
16   /**
17    * The renderer.
18    *
19    * @var \Drupal\Core\Render\RendererInterface
20    */
21   protected $renderer;
22
23   /**
24    * The test discovery service.
25    *
26    * @var \Drupal\simpletest\TestDiscovery
27    */
28   protected $testDiscovery;
29
30   /**
31    * {@inheritdoc}
32    */
33   public static function create(ContainerInterface $container) {
34     return new static(
35       $container->get('renderer'),
36       $container->get('test_discovery')
37     );
38   }
39
40   /**
41    * Constructs a new SimpletestTestForm.
42    *
43    * @param \Drupal\Core\Render\RendererInterface $renderer
44    *   The renderer.
45    * @param \Drupal\simpletest\TestDiscovery $test_discovery
46    *   The test discovery service.
47    */
48   public function __construct(RendererInterface $renderer, TestDiscovery $test_discovery) {
49     $this->renderer = $renderer;
50     $this->testDiscovery = $test_discovery;
51   }
52
53   /**
54    * {@inheritdoc}
55    */
56   public function getFormId() {
57     return 'simpletest_test_form';
58   }
59
60   /**
61    * {@inheritdoc}
62    */
63   public function buildForm(array $form, FormStateInterface $form_state) {
64     $form['actions'] = ['#type' => 'actions'];
65     $form['actions']['submit'] = [
66       '#type' => 'submit',
67       '#value' => $this->t('Run tests'),
68       '#tableselect' => TRUE,
69       '#button_type' => 'primary',
70     ];
71     $form['clean'] = [
72       '#type' => 'fieldset',
73       '#title' => $this->t('Clean test environment'),
74       '#description' => $this->t('Remove tables with the prefix "test" followed by digits and temporary directories that are left over from tests that crashed. This is intended for developers when creating tests.'),
75       '#weight' => 200,
76     ];
77     $form['clean']['op'] = [
78       '#type' => 'submit',
79       '#value' => $this->t('Clean environment'),
80       '#submit' => ['simpletest_clean_environment'],
81     ];
82
83     // Do not needlessly re-execute a full test discovery if the user input
84     // already contains an explicit list of test classes to run.
85     $user_input = $form_state->getUserInput();
86     if (!empty($user_input['tests'])) {
87       return $form;
88     }
89
90     // JavaScript-only table filters.
91     $form['filters'] = [
92       '#type' => 'container',
93       '#attributes' => [
94         'class' => ['table-filter', 'js-show'],
95       ],
96     ];
97     $form['filters']['text'] = [
98       '#type' => 'search',
99       '#title' => $this->t('Search'),
100       '#size' => 30,
101       '#placeholder' => $this->t('Enter test nameā€¦'),
102       '#attributes' => [
103         'class' => ['table-filter-text'],
104         'data-table' => '#simpletest-test-form',
105         'autocomplete' => 'off',
106         'title' => $this->t('Enter at least 3 characters of the test name or description to filter by.'),
107       ],
108     ];
109
110     $form['tests'] = [
111       '#type' => 'table',
112       '#id' => 'simpletest-form-table',
113       '#tableselect' => TRUE,
114       '#header' => [
115         ['data' => $this->t('Test'), 'class' => ['simpletest-test-label']],
116         ['data' => $this->t('Description'), 'class' => ['simpletest-test-description']],
117       ],
118       '#empty' => $this->t('No tests to display.'),
119       '#attached' => [
120         'library' => [
121           'simpletest/drupal.simpletest',
122         ],
123       ],
124     ];
125
126     // Define the images used to expand/collapse the test groups.
127     $image_collapsed = [
128       '#theme' => 'image',
129       '#uri' => 'core/misc/menu-collapsed.png',
130       '#width' => '7',
131       '#height' => '7',
132       '#alt' => $this->t('Expand'),
133       '#title' => $this->t('Expand'),
134       '#suffix' => '<a href="#" class="simpletest-collapse">(' . $this->t('Expand') . ')</a>',
135     ];
136     $image_extended = [
137       '#theme' => 'image',
138       '#uri' => 'core/misc/menu-expanded.png',
139       '#width' => '7',
140       '#height' => '7',
141       '#alt' => $this->t('Collapse'),
142       '#title' => $this->t('Collapse'),
143       '#suffix' => '<a href="#" class="simpletest-collapse">(' . $this->t('Collapse') . ')</a>',
144     ];
145     $form['tests']['#attached']['drupalSettings']['simpleTest']['images'] = [
146       (string) $this->renderer->renderPlain($image_collapsed),
147       (string) $this->renderer->renderPlain($image_extended),
148     ];
149
150     // Generate the list of tests arranged by group.
151     $groups = $this->testDiscovery->getTestClasses();
152     foreach ($groups as $group => $tests) {
153       $form['tests'][$group] = [
154         '#attributes' => ['class' => ['simpletest-group']],
155       ];
156
157       // Make the class name safe for output on the page by replacing all
158       // non-word/decimal characters with a dash (-).
159       $group_class = 'module-' . strtolower(trim(preg_replace("/[^\w\d]/", "-", $group)));
160
161       // Override tableselect column with custom selector for this group.
162       // This group-select-all checkbox is injected via JavaScript.
163       $form['tests'][$group]['select'] = [
164         '#wrapper_attributes' => [
165           'id' => $group_class,
166           'class' => ['simpletest-group-select-all'],
167         ],
168       ];
169       $form['tests'][$group]['title'] = [
170         // Expand/collapse image.
171         '#prefix' => '<div class="simpletest-image" id="simpletest-test-group-' . $group_class . '"></div>',
172         '#markup' => '<label for="' . $group_class . '-group-select-all">' . $group . '</label>',
173         '#wrapper_attributes' => [
174           'class' => ['simpletest-group-label'],
175         ],
176       ];
177       $form['tests'][$group]['description'] = [
178         '#markup' => '&nbsp;',
179         '#wrapper_attributes' => [
180           'class' => ['simpletest-group-description'],
181         ],
182       ];
183
184       // Cycle through each test within the current group.
185       foreach ($tests as $class => $info) {
186         $form['tests'][$class] = [
187           '#attributes' => ['class' => [$group_class . '-test', 'js-hide']],
188         ];
189         $form['tests'][$class]['title'] = [
190           '#type' => 'label',
191           '#title' => '\\' . $info['name'],
192           '#wrapper_attributes' => [
193             'class' => ['simpletest-test-label', 'table-filter-text-source'],
194           ],
195         ];
196         $form['tests'][$class]['description'] = [
197           '#prefix' => '<div class="description">',
198           '#plain_text' => $info['description'],
199           '#suffix' => '</div>',
200           '#wrapper_attributes' => [
201             'class' => ['simpletest-test-description', 'table-filter-text-source'],
202           ],
203         ];
204       }
205     }
206
207     return $form;
208   }
209
210   /**
211    * {@inheritdoc}
212    */
213   public function submitForm(array &$form, FormStateInterface $form_state) {
214     // Test discovery does not run upon form submission.
215     $this->testDiscovery->registerTestNamespaces();
216
217     // This form accepts arbitrary user input for 'tests'.
218     // An invalid value will cause the $class_name lookup below to die with a
219     // fatal error. Regular user access mechanisms to this form are intact.
220     // The only validation effectively being skipped is the validation of
221     // available checkboxes vs. submitted checkboxes.
222     // @todo Refactor Form API to allow to POST values without constructing the
223     //   entire form more easily, BUT retaining routing access security and
224     //   retaining Form API CSRF #token security validation, and without having
225     //   to rely on form caching.
226     $user_input = $form_state->getUserInput();
227     if ($form_state->isValueEmpty('tests') && !empty($user_input['tests'])) {
228       $form_state->setValue('tests', $user_input['tests']);
229     }
230
231     $tests_list = array_filter($form_state->getValue('tests'));
232     if (!empty($tests_list)) {
233       $test_id = simpletest_run_tests($tests_list, 'drupal');
234       $form_state->setRedirect(
235         'simpletest.result_form',
236         ['test_id' => $test_id]
237       );
238     }
239   }
240
241 }