8d20db5c3f1d220d6eab596eb46439516f915a4e
[yaffs-website] / web / core / tests / Drupal / Tests / Core / Form / FormBuilderTest.php
1 <?php
2
3 /**
4  * @file
5  * Contains \Drupal\Tests\Core\Form\FormBuilderTest.
6  */
7
8 namespace Drupal\Tests\Core\Form;
9
10 use Drupal\Component\Utility\Html;
11 use Drupal\Component\Utility\NestedArray;
12 use Drupal\Core\Access\AccessResult;
13 use Drupal\Core\Cache\Context\CacheContextsManager;
14 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
15 use Drupal\Core\Form\EnforcedResponseException;
16 use Drupal\Core\Form\Exception\BrokenPostRequestException;
17 use Drupal\Core\Form\FormBuilder;
18 use Drupal\Core\Form\FormBuilderInterface;
19 use Drupal\Core\Form\FormInterface;
20 use Drupal\Core\Form\FormState;
21 use Drupal\Core\Form\FormStateInterface;
22 use Drupal\Core\Session\AccountInterface;
23 use Drupal\Core\Session\AccountProxyInterface;
24 use Symfony\Component\DependencyInjection\ContainerBuilder;
25 use Symfony\Component\DependencyInjection\ContainerInterface;
26 use Symfony\Component\HttpFoundation\Request;
27 use Symfony\Component\HttpFoundation\RequestStack;
28
29 /**
30  * @coversDefaultClass \Drupal\Core\Form\FormBuilder
31  * @group Form
32  */
33 class FormBuilderTest extends FormTestBase {
34
35   /**
36    * The dependency injection container.
37    *
38    * @var \Symfony\Component\DependencyInjection\ContainerBuilder
39    */
40   protected $container;
41
42   /**
43    * {@inheritdoc}
44    */
45   protected function setUp() {
46     parent::setUp();
47
48     $this->container = new ContainerBuilder();
49     $cache_contexts_manager = $this->prophesize(CacheContextsManager::class)->reveal();
50     $this->container->set('cache_contexts_manager', $cache_contexts_manager);
51     \Drupal::setContainer($this->container);
52   }
53
54   /**
55    * Tests the getFormId() method with a string based form ID.
56    */
57   public function testGetFormIdWithString() {
58     $form_arg = 'foo';
59     $form_state = new FormState();
60     $this->setExpectedException(\InvalidArgumentException::class, 'The form argument foo is not a valid form.');
61     $this->formBuilder->getFormId($form_arg, $form_state);
62   }
63
64   /**
65    * Tests the getFormId() method with a class name form ID.
66    */
67   public function testGetFormIdWithClassName() {
68     $form_arg = 'Drupal\Tests\Core\Form\TestForm';
69
70     $form_state = new FormState();
71     $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
72
73     $this->assertSame('test_form', $form_id);
74     $this->assertSame($form_arg, get_class($form_state->getFormObject()));
75   }
76
77   /**
78    * Tests the getFormId() method with an injected class name form ID.
79    */
80   public function testGetFormIdWithInjectedClassName() {
81     $container = $this->getMock('Symfony\Component\DependencyInjection\ContainerInterface');
82     \Drupal::setContainer($container);
83
84     $form_arg = 'Drupal\Tests\Core\Form\TestFormInjected';
85
86     $form_state = new FormState();
87     $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
88
89     $this->assertSame('test_form', $form_id);
90     $this->assertSame($form_arg, get_class($form_state->getFormObject()));
91   }
92
93   /**
94    * Tests the getFormId() method with a form object.
95    */
96   public function testGetFormIdWithObject() {
97     $expected_form_id = 'my_module_form_id';
98
99     $form_arg = $this->getMockForm($expected_form_id);
100
101     $form_state = new FormState();
102     $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
103
104     $this->assertSame($expected_form_id, $form_id);
105     $this->assertSame($form_arg, $form_state->getFormObject());
106   }
107
108   /**
109    * Tests the getFormId() method with a base form object.
110    */
111   public function testGetFormIdWithBaseForm() {
112     $expected_form_id = 'my_module_form_id';
113     $base_form_id = 'my_module';
114
115     $form_arg = $this->getMock('Drupal\Core\Form\BaseFormIdInterface');
116     $form_arg->expects($this->once())
117       ->method('getFormId')
118       ->will($this->returnValue($expected_form_id));
119     $form_arg->expects($this->once())
120       ->method('getBaseFormId')
121       ->will($this->returnValue($base_form_id));
122
123     $form_state = new FormState();
124     $form_id = $this->formBuilder->getFormId($form_arg, $form_state);
125
126     $this->assertSame($expected_form_id, $form_id);
127     $this->assertSame($form_arg, $form_state->getFormObject());
128     $this->assertSame($base_form_id, $form_state->getBuildInfo()['base_form_id']);
129   }
130
131   /**
132    * Tests the handling of FormStateInterface::$response.
133    *
134    * @dataProvider formStateResponseProvider
135    */
136   public function testHandleFormStateResponse($class, $form_state_key) {
137     $form_id = 'test_form_id';
138     $expected_form = $form_id();
139
140     $response = $this->getMockBuilder($class)
141       ->disableOriginalConstructor()
142       ->getMock();
143
144     $form_arg = $this->getMockForm($form_id, $expected_form);
145     $form_arg->expects($this->any())
146       ->method('submitForm')
147       ->will($this->returnCallback(function ($form, FormStateInterface $form_state) use ($response, $form_state_key) {
148         $form_state->setFormState([$form_state_key => $response]);
149       }));
150
151     $form_state = new FormState();
152     try {
153       $input['form_id'] = $form_id;
154       $form_state->setUserInput($input);
155       $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
156       $this->fail('EnforcedResponseException was not thrown.');
157     }
158     catch (EnforcedResponseException $e) {
159       $this->assertSame($response, $e->getResponse());
160     }
161     $this->assertSame($response, $form_state->getResponse());
162   }
163
164   /**
165    * Provides test data for testHandleFormStateResponse().
166    */
167   public function formStateResponseProvider() {
168     return [
169       ['Symfony\Component\HttpFoundation\Response', 'response'],
170       ['Symfony\Component\HttpFoundation\RedirectResponse', 'redirect'],
171     ];
172   }
173
174   /**
175    * Tests the handling of a redirect when FormStateInterface::$response exists.
176    */
177   public function testHandleRedirectWithResponse() {
178     $form_id = 'test_form_id';
179     $expected_form = $form_id();
180
181     // Set up a response that will be used.
182     $response = $this->getMockBuilder('Symfony\Component\HttpFoundation\Response')
183       ->disableOriginalConstructor()
184       ->getMock();
185
186     // Set up a redirect that will not be called.
187     $redirect = $this->getMockBuilder('Symfony\Component\HttpFoundation\RedirectResponse')
188       ->disableOriginalConstructor()
189       ->getMock();
190
191     $form_arg = $this->getMockForm($form_id, $expected_form);
192     $form_arg->expects($this->any())
193       ->method('submitForm')
194       ->will($this->returnCallback(function ($form, FormStateInterface $form_state) use ($response, $redirect) {
195         // Set both the response and the redirect.
196         $form_state->setResponse($response);
197         $form_state->set('redirect', $redirect);
198       }));
199
200     $form_state = new FormState();
201     try {
202       $input['form_id'] = $form_id;
203       $form_state->setUserInput($input);
204       $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
205       $this->fail('EnforcedResponseException was not thrown.');
206     }
207     catch (EnforcedResponseException $e) {
208       $this->assertSame($response, $e->getResponse());
209     }
210     $this->assertSame($response, $form_state->getResponse());
211   }
212
213   /**
214    * Tests the getForm() method with a string based form ID.
215    */
216   public function testGetFormWithString() {
217     $form_id = 'test_form_id';
218     $this->setExpectedException(\InvalidArgumentException::class, 'The form argument test_form_id is not a valid form.');
219     $this->formBuilder->getForm($form_id);
220   }
221
222   /**
223    * Tests the getForm() method with a form object.
224    */
225   public function testGetFormWithObject() {
226     $form_id = 'test_form_id';
227     $expected_form = $form_id();
228
229     $form_arg = $this->getMockForm($form_id, $expected_form);
230
231     $form = $this->formBuilder->getForm($form_arg);
232     $this->assertFormElement($expected_form, $form, 'test');
233     $this->assertArrayHasKey('#id', $form);
234   }
235
236   /**
237    * Tests the getForm() method with a class name based form ID.
238    */
239   public function testGetFormWithClassString() {
240     $form_id = '\Drupal\Tests\Core\Form\TestForm';
241     $object = new TestForm();
242     $form = [];
243     $form_state = new FormState();
244     $expected_form = $object->buildForm($form, $form_state);
245
246     $form = $this->formBuilder->getForm($form_id);
247     $this->assertFormElement($expected_form, $form, 'test');
248     $this->assertSame('test-form', $form['#id']);
249   }
250
251   /**
252    * Tests the buildForm() method with a string based form ID.
253    */
254   public function testBuildFormWithString() {
255     $form_id = 'test_form_id';
256     $this->setExpectedException(\InvalidArgumentException::class, 'The form argument test_form_id is not a valid form.');
257     $this->formBuilder->getForm($form_id);
258   }
259
260   /**
261    * Tests the buildForm() method with a class name based form ID.
262    */
263   public function testBuildFormWithClassString() {
264     $form_id = '\Drupal\Tests\Core\Form\TestForm';
265     $object = new TestForm();
266     $form = [];
267     $form_state = new FormState();
268     $expected_form = $object->buildForm($form, $form_state);
269
270     $form = $this->formBuilder->buildForm($form_id, $form_state);
271     $this->assertFormElement($expected_form, $form, 'test');
272     $this->assertSame('test-form', $form['#id']);
273   }
274
275   /**
276    * Tests the buildForm() method with a form object.
277    */
278   public function testBuildFormWithObject() {
279     $form_id = 'test_form_id';
280     $expected_form = $form_id();
281
282     $form_arg = $this->getMockForm($form_id, $expected_form);
283
284     $form_state = new FormState();
285     $form = $this->formBuilder->buildForm($form_arg, $form_state);
286     $this->assertFormElement($expected_form, $form, 'test');
287     $this->assertSame($form_id, $form_state->getBuildInfo()['form_id']);
288     $this->assertArrayHasKey('#id', $form);
289   }
290
291   /**
292    * Tests whether the triggering element is properly identified.
293    *
294    * @param string $element_value
295    *   The input element "#value" value.
296    * @param string $input_value
297    *   The corresponding submitted input value.
298    *
299    * @covers ::buildForm
300    *
301    * @dataProvider providerTestBuildFormWithTriggeringElement
302    */
303   public function testBuildFormWithTriggeringElement($element_value, $input_value) {
304     $form_id = 'test_form_id';
305     $expected_form = $form_id();
306
307     $expected_form['actions']['other_submit'] = [
308       '#type' => 'submit',
309       '#value' => $element_value,
310     ];
311
312     $form_arg = $this->getMockForm($form_id, $expected_form, 2);
313     $form_state = new FormState();
314     $form_state->setProcessInput();
315     $form_state->setUserInput(['form_id' => $form_id, 'op' => $input_value]);
316     $this->request->setMethod('POST');
317     $this->formBuilder->buildForm($form_arg, $form_state);
318
319     $this->assertEquals($expected_form['actions']['other_submit']['#value'], $form_state->getTriggeringElement()['#value']);
320   }
321
322   /**
323    * Data provider for ::testBuildFormWithTriggeringElement().
324    */
325   public function providerTestBuildFormWithTriggeringElement() {
326     $plain_text = 'Other submit value';
327     $markup = 'Other submit <input> value';
328     return [
329       'plain-text' => [$plain_text, $plain_text],
330       'markup' => [$markup, $markup],
331       // Note: The input is always decoded, see
332       // \Drupal\Core\Form\FormBuilder::buttonWasClicked, so we do not need to
333       // escape the input.
334       'escaped-markup' => [Html::escape($markup), $markup],
335     ];
336   }
337
338   /**
339    * Tests the rebuildForm() method for a POST submission.
340    */
341   public function testRebuildForm() {
342     $form_id = 'test_form_id';
343     $expected_form = $form_id();
344
345     // The form will be built four times.
346     $form_arg = $this->getMock('Drupal\Core\Form\FormInterface');
347     $form_arg->expects($this->exactly(2))
348       ->method('getFormId')
349       ->will($this->returnValue($form_id));
350     $form_arg->expects($this->exactly(4))
351       ->method('buildForm')
352       ->will($this->returnValue($expected_form));
353
354     // Do an initial build of the form and track the build ID.
355     $form_state = new FormState();
356     $form = $this->formBuilder->buildForm($form_arg, $form_state);
357     $original_build_id = $form['#build_id'];
358
359     $this->request->setMethod('POST');
360     $form_state->setRequestMethod('POST');
361
362     // Rebuild the form, and assert that the build ID has not changed.
363     $form_state->setRebuild();
364     $input['form_id'] = $form_id;
365     $form_state->setUserInput($input);
366     $form_state->addRebuildInfo('copy', ['#build_id' => TRUE]);
367     $this->formBuilder->processForm($form_id, $form, $form_state);
368     $this->assertSame($original_build_id, $form['#build_id']);
369     $this->assertTrue($form_state->isCached());
370
371     // Rebuild the form again, and assert that there is a new build ID.
372     $form_state->setRebuildInfo([]);
373     $form = $this->formBuilder->buildForm($form_arg, $form_state);
374     $this->assertNotSame($original_build_id, $form['#build_id']);
375     $this->assertTrue($form_state->isCached());
376   }
377
378   /**
379    * Tests the rebuildForm() method for a GET submission.
380    */
381   public function testRebuildFormOnGetRequest() {
382     $form_id = 'test_form_id';
383     $expected_form = $form_id();
384
385     // The form will be built four times.
386     $form_arg = $this->getMock('Drupal\Core\Form\FormInterface');
387     $form_arg->expects($this->exactly(2))
388       ->method('getFormId')
389       ->will($this->returnValue($form_id));
390     $form_arg->expects($this->exactly(4))
391       ->method('buildForm')
392       ->will($this->returnValue($expected_form));
393
394     // Do an initial build of the form and track the build ID.
395     $form_state = new FormState();
396     $form_state->setMethod('GET');
397     $form = $this->formBuilder->buildForm($form_arg, $form_state);
398     $original_build_id = $form['#build_id'];
399
400     // Rebuild the form, and assert that the build ID has not changed.
401     $form_state->setRebuild();
402     $input['form_id'] = $form_id;
403     $form_state->setUserInput($input);
404     $form_state->addRebuildInfo('copy', ['#build_id' => TRUE]);
405     $this->formBuilder->processForm($form_id, $form, $form_state);
406     $this->assertSame($original_build_id, $form['#build_id']);
407     $this->assertFalse($form_state->isCached());
408
409     // Rebuild the form again, and assert that there is a new build ID.
410     $form_state->setRebuildInfo([]);
411     $form = $this->formBuilder->buildForm($form_arg, $form_state);
412     $this->assertNotSame($original_build_id, $form['#build_id']);
413     $this->assertFalse($form_state->isCached());
414   }
415
416   /**
417    * Tests the getCache() method.
418    */
419   public function testGetCache() {
420     $form_id = 'test_form_id';
421     $expected_form = $form_id();
422     $expected_form['#token'] = FALSE;
423
424     // FormBuilder::buildForm() will be called twice, but the form object will
425     // only be called once due to caching.
426     $form_arg = $this->getMock('Drupal\Core\Form\FormInterface');
427     $form_arg->expects($this->exactly(2))
428       ->method('getFormId')
429       ->will($this->returnValue($form_id));
430     $form_arg->expects($this->once())
431       ->method('buildForm')
432       ->will($this->returnValue($expected_form));
433
434     // Do an initial build of the form and track the build ID.
435     $form_state = (new FormState())
436       ->addBuildInfo('files', [['module' => 'node', 'type' => 'pages.inc']])
437       ->setRequestMethod('POST')
438       ->setCached();
439     $form = $this->formBuilder->buildForm($form_arg, $form_state);
440
441     $cached_form = $form;
442     $cached_form['#cache_token'] = 'csrf_token';
443     // The form cache, form_state cache, and CSRF token validation will only be
444     // called on the cached form.
445     $this->formCache->expects($this->once())
446       ->method('getCache')
447       ->willReturn($form);
448
449     // The final form build will not trigger any actual form building, but will
450     // use the form cache.
451     $form_state->setExecuted();
452     $input['form_id'] = $form_id;
453     $input['form_build_id'] = $form['#build_id'];
454     $form_state->setUserInput($input);
455     $this->formBuilder->buildForm($form_arg, $form_state);
456     $this->assertEmpty($form_state->getErrors());
457   }
458
459   /**
460    * Tests that HTML IDs are unique when rebuilding a form with errors.
461    */
462   public function testUniqueHtmlId() {
463     $form_id = 'test_form_id';
464     $expected_form = $form_id();
465     $expected_form['test']['#required'] = TRUE;
466
467     // Mock a form object that will be built two times.
468     $form_arg = $this->getMock('Drupal\Core\Form\FormInterface');
469     $form_arg->expects($this->exactly(2))
470       ->method('getFormId')
471       ->will($this->returnValue($form_id));
472     $form_arg->expects($this->exactly(2))
473       ->method('buildForm')
474       ->will($this->returnValue($expected_form));
475
476     $form_state = new FormState();
477     $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
478     $this->assertSame('test-form-id', $form['#id']);
479
480     $form_state = new FormState();
481     $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
482     $this->assertSame('test-form-id--2', $form['#id']);
483   }
484
485   /**
486    * Tests that a cached form is deleted after submit.
487    */
488   public function testFormCacheDeletionCached() {
489     $form_id = 'test_form_id';
490     $form_build_id = $this->randomMachineName();
491
492     $expected_form = $form_id();
493     $expected_form['#build_id'] = $form_build_id;
494     $form_arg = $this->getMockForm($form_id, $expected_form);
495     $form_arg->expects($this->once())
496       ->method('submitForm')
497       ->willReturnCallback(function (array &$form, FormStateInterface $form_state) {
498         // Mimic EntityForm by cleaning the $form_state upon submit.
499         $form_state->cleanValues();
500       });
501
502     $this->formCache->expects($this->once())
503       ->method('deleteCache')
504       ->with($form_build_id);
505
506     $form_state = new FormState();
507     $form_state->setRequestMethod('POST');
508     $form_state->setCached();
509     $this->simulateFormSubmission($form_id, $form_arg, $form_state);
510   }
511
512   /**
513    * Tests that an uncached form does not trigger cache set or delete.
514    */
515   public function testFormCacheDeletionUncached() {
516     $form_id = 'test_form_id';
517     $form_build_id = $this->randomMachineName();
518
519     $expected_form = $form_id();
520     $expected_form['#build_id'] = $form_build_id;
521     $form_arg = $this->getMockForm($form_id, $expected_form);
522
523     $this->formCache->expects($this->never())
524       ->method('deleteCache');
525
526     $form_state = new FormState();
527     $this->simulateFormSubmission($form_id, $form_arg, $form_state);
528   }
529
530   /**
531    * @covers ::buildForm
532    */
533   public function testExceededFileSize() {
534     $request = new Request([FormBuilderInterface::AJAX_FORM_REQUEST => TRUE]);
535     $request_stack = new RequestStack();
536     $request_stack->push($request);
537     $this->formBuilder = $this->getMockBuilder('\Drupal\Core\Form\FormBuilder')
538       ->setConstructorArgs([$this->formValidator, $this->formSubmitter, $this->formCache, $this->moduleHandler, $this->eventDispatcher, $request_stack, $this->classResolver, $this->elementInfo, $this->themeManager, $this->csrfToken])
539       ->setMethods(['getFileUploadMaxSize'])
540       ->getMock();
541     $this->formBuilder->expects($this->once())
542       ->method('getFileUploadMaxSize')
543       ->willReturn(33554432);
544
545     $form_arg = $this->getMockForm('test_form_id');
546     $form_state = new FormState();
547
548     $this->setExpectedException(BrokenPostRequestException::class);
549     $this->formBuilder->buildForm($form_arg, $form_state);
550   }
551
552   /**
553    * @covers ::buildForm
554    */
555   public function testGetPostAjaxRequest() {
556     $request = new Request([FormBuilderInterface::AJAX_FORM_REQUEST => TRUE], ['form_id' => 'different_form_id']);
557     $request->setMethod('POST');
558     $this->requestStack->push($request);
559
560     $form_state = (new FormState())
561       ->setUserInput([FormBuilderInterface::AJAX_FORM_REQUEST => TRUE])
562       ->setMethod('get')
563       ->setAlwaysProcess()
564       ->disableRedirect()
565       ->set('ajax', TRUE);
566
567     $form_id = '\Drupal\Tests\Core\Form\TestForm';
568     $expected_form = (new TestForm())->buildForm([], $form_state);
569
570     $form = $this->formBuilder->buildForm($form_id, $form_state);
571     $this->assertFormElement($expected_form, $form, 'test');
572     $this->assertSame('test-form', $form['#id']);
573   }
574
575   /**
576    * @covers ::buildForm
577    *
578    * @dataProvider providerTestChildAccessInheritance
579    */
580   public function testChildAccessInheritance($element, $access_checks) {
581     $form_arg = new TestFormWithPredefinedForm();
582     $form_arg->setForm($element);
583
584     $form_state = new FormState();
585
586     $form = $this->formBuilder->buildForm($form_arg, $form_state);
587
588     $actual_access_structure = [];
589     $expected_access_structure = [];
590
591     // Ensure that the expected access checks are set.
592     foreach ($access_checks as $access_check) {
593       $parents = $access_check[0];
594       $parents[] = '#access';
595
596       $actual_access = NestedArray::getValue($form, $parents);
597       $actual_access_structure[] = [$parents, $actual_access];
598       $expected_access_structure[] = [$parents, $access_check[1]];
599     }
600
601     $this->assertEquals($expected_access_structure, $actual_access_structure);
602   }
603
604   /**
605    * Data provider for testChildAccessInheritance.
606    *
607    * @return array
608    */
609   public function providerTestChildAccessInheritance() {
610     $data = [];
611
612     $element = [
613       'child0' => [
614         '#type' => 'checkbox',
615       ],
616       'child1' => [
617         '#type' => 'checkbox',
618       ],
619       'child2' => [
620         '#type' => 'fieldset',
621         'child2.0' => [
622           '#type' => 'checkbox',
623         ],
624         'child2.1' => [
625           '#type' => 'checkbox',
626         ],
627         'child2.2' => [
628           '#type' => 'checkbox',
629         ],
630       ],
631     ];
632
633     // Sets access FALSE on the root level, this should be inherited completely.
634     $clone = $element;
635     $clone['#access'] = FALSE;
636
637     $expected_access = [];
638     $expected_access[] = [[], FALSE];
639     $expected_access[] = [['child0'], FALSE];
640     $expected_access[] = [['child1'], FALSE];
641     $expected_access[] = [['child2'], FALSE];
642     $expected_access[] = [['child2', 'child2.0'], FALSE];
643     $expected_access[] = [['child2', 'child2.1'], FALSE];
644     $expected_access[] = [['child2', 'child2.2'], FALSE];
645
646     $data['access-false-root'] = [$clone, $expected_access];
647
648     $clone = $element;
649     $access_result = AccessResult::forbidden();
650     $clone['#access'] = $access_result;
651
652     $expected_access = [];
653     $expected_access[] = [[], $access_result];
654     $expected_access[] = [['child0'], $access_result];
655     $expected_access[] = [['child1'], $access_result];
656     $expected_access[] = [['child2'], $access_result];
657     $expected_access[] = [['child2', 'child2.0'], $access_result];
658     $expected_access[] = [['child2', 'child2.1'], $access_result];
659     $expected_access[] = [['child2', 'child2.2'], $access_result];
660
661     $data['access-forbidden-root'] = [$clone, $expected_access];
662
663     // Allow access on the most outer level but set FALSE otherwise.
664     $clone = $element;
665     $clone['#access'] = TRUE;
666     $clone['child0']['#access'] = FALSE;
667
668     $expected_access = [];
669     $expected_access[] = [[], TRUE];
670     $expected_access[] = [['child0'], FALSE];
671     $expected_access[] = [['child1'], NULL];
672     $expected_access[] = [['child2'], NULL];
673     $expected_access[] = [['child2', 'child2.0'], NULL];
674     $expected_access[] = [['child2', 'child2.1'], NULL];
675     $expected_access[] = [['child2', 'child2.2'], NULL];
676
677     $data['access-true-root'] = [$clone, $expected_access];
678
679     // Allow access on the most outer level but forbid otherwise.
680     $clone = $element;
681     $access_result_allowed = AccessResult::allowed();
682     $clone['#access'] = $access_result_allowed;
683     $access_result_forbidden = AccessResult::forbidden();
684     $clone['child0']['#access'] = $access_result_forbidden;
685
686     $expected_access = [];
687     $expected_access[] = [[], $access_result_allowed];
688     $expected_access[] = [['child0'], $access_result_forbidden];
689     $expected_access[] = [['child1'], NULL];
690     $expected_access[] = [['child2'], NULL];
691     $expected_access[] = [['child2', 'child2.0'], NULL];
692     $expected_access[] = [['child2', 'child2.1'], NULL];
693     $expected_access[] = [['child2', 'child2.2'], NULL];
694
695     $data['access-allowed-root'] = [$clone, $expected_access];
696
697     // Allow access on the most outer level, deny access on a parent, and allow
698     // on a child. The denying should be inherited.
699     $clone = $element;
700     $clone['#access'] = TRUE;
701     $clone['child2']['#access'] = FALSE;
702     $clone['child2.0']['#access'] = TRUE;
703     $clone['child2.1']['#access'] = TRUE;
704     $clone['child2.2']['#access'] = TRUE;
705
706     $expected_access = [];
707     $expected_access[] = [[], TRUE];
708     $expected_access[] = [['child0'], NULL];
709     $expected_access[] = [['child1'], NULL];
710     $expected_access[] = [['child2'], FALSE];
711     $expected_access[] = [['child2', 'child2.0'], FALSE];
712     $expected_access[] = [['child2', 'child2.1'], FALSE];
713     $expected_access[] = [['child2', 'child2.2'], FALSE];
714
715     $data['access-mixed-parents'] = [$clone, $expected_access];
716
717     $clone = $element;
718     $clone['#access'] = $access_result_allowed;
719     $clone['child2']['#access'] = $access_result_forbidden;
720     $clone['child2.0']['#access'] = $access_result_allowed;
721     $clone['child2.1']['#access'] = $access_result_allowed;
722     $clone['child2.2']['#access'] = $access_result_allowed;
723
724     $expected_access = [];
725     $expected_access[] = [[], $access_result_allowed];
726     $expected_access[] = [['child0'], NULL];
727     $expected_access[] = [['child1'], NULL];
728     $expected_access[] = [['child2'], $access_result_forbidden];
729     $expected_access[] = [['child2', 'child2.0'], $access_result_forbidden];
730     $expected_access[] = [['child2', 'child2.1'], $access_result_forbidden];
731     $expected_access[] = [['child2', 'child2.2'], $access_result_forbidden];
732
733     $data['access-mixed-parents-object'] = [$clone, $expected_access];
734
735     return $data;
736   }
737
738   /**
739    * @covers ::valueCallableIsSafe
740    *
741    * @dataProvider providerTestValueCallableIsSafe
742    */
743   public function testValueCallableIsSafe($callback, $expected) {
744     $method = new \ReflectionMethod(FormBuilder::class, 'valueCallableIsSafe');
745     $method->setAccessible(TRUE);
746     $is_safe = $method->invoke($this->formBuilder, $callback);
747     $this->assertSame($expected, $is_safe);
748   }
749
750   public function providerTestValueCallableIsSafe() {
751     $data = [];
752     $data['string_no_slash'] = [
753       'Drupal\Core\Render\Element\Token::valueCallback',
754       TRUE,
755     ];
756     $data['string_with_slash'] = [
757       '\Drupal\Core\Render\Element\Token::valueCallback',
758       TRUE,
759     ];
760     $data['array_no_slash'] = [
761       ['Drupal\Core\Render\Element\Token', 'valueCallback'],
762       TRUE,
763     ];
764     $data['array_with_slash'] = [
765       ['\Drupal\Core\Render\Element\Token', 'valueCallback'],
766       TRUE,
767     ];
768     $data['closure'] = [
769       function () {},
770       FALSE,
771     ];
772     return $data;
773   }
774
775   /**
776    * @covers ::doBuildForm
777    *
778    * @dataProvider providerTestInvalidToken
779    */
780   public function testInvalidToken($expected, $valid_token, $user_is_authenticated) {
781     $form_token = 'the_form_token';
782     $form_id = 'test_form_id';
783
784     if (is_bool($valid_token)) {
785       $this->csrfToken->expects($this->any())
786         ->method('get')
787         ->willReturnArgument(0);
788       $this->csrfToken->expects($this->atLeastOnce())
789         ->method('validate')
790         ->willReturn($valid_token);
791     }
792
793     $current_user = $this->prophesize(AccountInterface::class);
794     $current_user->isAuthenticated()->willReturn($user_is_authenticated);
795     $property = new \ReflectionProperty(FormBuilder::class, 'currentUser');
796     $property->setAccessible(TRUE);
797     $property->setValue($this->formBuilder, $current_user->reveal());
798
799     $expected_form = $form_id();
800     $form_arg = $this->getMockForm($form_id, $expected_form);
801
802     $form_state = new FormState();
803     $input['form_id'] = $form_id;
804     $input['form_token'] = $form_token;
805     $form_state->setUserInput($input);
806     $this->simulateFormSubmission($form_id, $form_arg, $form_state, FALSE);
807     $this->assertSame($expected, $form_state->hasInvalidToken());
808   }
809
810   public function providerTestInvalidToken() {
811     $data = [];
812     $data['authenticated_invalid'] = [TRUE, FALSE, TRUE];
813     $data['authenticated_valid'] = [FALSE, TRUE, TRUE];
814     // If the user is not authenticated, we will not have a token.
815     $data['anonymous'] = [FALSE, NULL, FALSE];
816     return $data;
817   }
818
819   /**
820    * @covers ::prepareForm
821    *
822    * @dataProvider providerTestFormTokenCacheability
823    */
824   public function testFormTokenCacheability($token, $is_authenticated, $expected_form_cacheability, $expected_token_cacheability, $method) {
825     $user = $this->prophesize(AccountProxyInterface::class);
826     $user->isAuthenticated()
827       ->willReturn($is_authenticated);
828     $this->container->set('current_user', $user->reveal());
829     \Drupal::setContainer($this->container);
830
831     $form_id = 'test_form_id';
832     $form = $form_id();
833     $form['#method'] = $method;
834
835     if (isset($token)) {
836       $form['#token'] = $token;
837     }
838
839     $form_arg = $this->getMock('Drupal\Core\Form\FormInterface');
840     $form_arg->expects($this->once())
841       ->method('getFormId')
842       ->will($this->returnValue($form_id));
843     $form_arg->expects($this->once())
844       ->method('buildForm')
845       ->will($this->returnValue($form));
846
847     $form_state = new FormState();
848     $built_form = $this->formBuilder->buildForm($form_arg, $form_state);
849     if (!isset($expected_form_cacheability) || ($method == 'get' && !is_string($token))) {
850       $this->assertFalse(isset($built_form['#cache']));
851     }
852     else {
853       $this->assertTrue(isset($built_form['#cache']));
854       $this->assertEquals($expected_form_cacheability, $built_form['#cache']);
855     }
856     if (!isset($expected_token_cacheability)) {
857       $this->assertFalse(isset($built_form['form_token']));
858     }
859     else {
860       $this->assertTrue(isset($built_form['form_token']));
861       $this->assertEquals($expected_token_cacheability, $built_form['form_token']['#cache']);
862     }
863   }
864
865   /**
866    * Data provider for testFormTokenCacheability.
867    *
868    * @return array
869    */
870   public function providerTestFormTokenCacheability() {
871     return [
872       'token:none,authenticated:true' => [NULL, TRUE, ['contexts' => ['user.roles:authenticated']], ['max-age' => 0], 'post'],
873       'token:none,authenticated:false' => [NULL, FALSE, ['contexts' => ['user.roles:authenticated']], NULL, 'post'],
874       'token:false,authenticated:false' => [FALSE, FALSE, NULL, NULL, 'post'],
875       'token:false,authenticated:true' => [FALSE, TRUE, NULL, NULL, 'post'],
876       'token:none,authenticated:false,method:get' => [NULL, FALSE, ['contexts' => ['user.roles:authenticated']], NULL, 'get'],
877       'token:test_form_id,authenticated:false,method:get' => ['test_form_id', TRUE, ['contexts' => ['user.roles:authenticated']], ['max-age' => 0], 'get'],
878     ];
879   }
880
881 }
882
883 class TestForm implements FormInterface {
884
885   public function getFormId() {
886     return 'test_form';
887   }
888
889   public function buildForm(array $form, FormStateInterface $form_state) {
890     return test_form_id();
891   }
892
893   public function validateForm(array &$form, FormStateInterface $form_state) {}
894
895   public function submitForm(array &$form, FormStateInterface $form_state) {}
896
897 }
898 class TestFormInjected extends TestForm implements ContainerInjectionInterface {
899
900   public static function create(ContainerInterface $container) {
901     return new static();
902   }
903
904 }
905
906
907 class TestFormWithPredefinedForm extends TestForm {
908
909   /**
910    * @var array
911    */
912   protected $form;
913
914   public function setForm($form) {
915     $this->form = $form;
916   }
917
918   public function buildForm(array $form, FormStateInterface $form_state) {
919     return $this->form;
920   }
921
922 }