b86ede1e9705f2ce7e64e9819f041268afc13382
[yaffs-website] / web / core / modules / rest / tests / src / Functional / Views / StyleSerializerTest.php
1 <?php
2
3 namespace Drupal\Tests\rest\Functional\Views;
4
5 use Drupal\Component\Serialization\Json;
6 use Drupal\Core\Cache\Cache;
7 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
8 use Drupal\Core\Field\FieldStorageDefinitionInterface;
9 use Drupal\entity_test\Entity\EntityTest;
10 use Drupal\field\Entity\FieldConfig;
11 use Drupal\field\Entity\FieldStorageConfig;
12 use Drupal\language\Entity\ConfigurableLanguage;
13 use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
14 use Drupal\Tests\views\Functional\ViewTestBase;
15 use Drupal\views\Entity\View;
16 use Drupal\views\Plugin\views\display\DisplayPluginBase;
17 use Drupal\views\Views;
18 use Drupal\views\Tests\ViewTestData;
19 use Symfony\Component\HttpFoundation\Request;
20
21 /**
22  * Tests the serializer style plugin.
23  *
24  * @group rest
25  * @see \Drupal\rest\Plugin\views\display\RestExport
26  * @see \Drupal\rest\Plugin\views\style\Serializer
27  * @see \Drupal\rest\Plugin\views\row\DataEntityRow
28  * @see \Drupal\rest\Plugin\views\row\DataFieldRow
29  */
30 class StyleSerializerTest extends ViewTestBase {
31
32   use AssertPageCacheContextsAndTagsTrait;
33
34   /**
35    * Modules to install.
36    *
37    * @var array
38    */
39   public static $modules = ['views_ui', 'entity_test', 'hal', 'rest_test_views', 'node', 'text', 'field', 'language', 'basic_auth'];
40
41   /**
42    * Views used by this test.
43    *
44    * @var array
45    */
46   public static $testViews = ['test_serializer_display_field', 'test_serializer_display_entity', 'test_serializer_display_entity_translated', 'test_serializer_node_display_field', 'test_serializer_node_exposed_filter', 'test_serializer_shared_path'];
47
48   /**
49    * A user with administrative privileges to look at test entity and configure views.
50    */
51   protected $adminUser;
52
53   protected function setUp($import_test_views = TRUE) {
54     parent::setUp($import_test_views);
55
56     ViewTestData::createTestViews(get_class($this), ['rest_test_views']);
57
58     $this->adminUser = $this->drupalCreateUser(['administer views', 'administer entity_test content', 'access user profiles', 'view test entity']);
59
60     // Save some entity_test entities.
61     for ($i = 1; $i <= 10; $i++) {
62       EntityTest::create(['name' => 'test_' . $i, 'user_id' => $this->adminUser->id()])->save();
63     }
64
65     $this->enableViewsTestModule();
66   }
67
68   /**
69    * Checks that the auth options restricts access to a REST views display.
70    */
71   public function testRestViewsAuthentication() {
72     // Assume the view is hidden behind a permission.
73     $this->drupalGet('test/serialize/auth_with_perm', ['query' => ['_format' => 'json']]);
74     $this->assertResponse(401);
75
76     // Not even logging in would make it possible to see the view, because then
77     // we are denied based on authentication method (cookie).
78     $this->drupalLogin($this->adminUser);
79     $this->drupalGet('test/serialize/auth_with_perm', ['query' => ['_format' => 'json']]);
80     $this->assertResponse(403);
81     $this->drupalLogout();
82
83     // But if we use the basic auth authentication strategy, we should be able
84     // to see the page.
85     $url = $this->buildUrl('test/serialize/auth_with_perm');
86     $response = \Drupal::httpClient()->get($url, [
87       'auth' => [$this->adminUser->getUsername(), $this->adminUser->pass_raw],
88       'query' => [
89         '_format' => 'json',
90       ],
91     ]);
92
93     // Ensure that any changes to variables in the other thread are picked up.
94     $this->refreshVariables();
95
96     $this->assertResponse(200);
97   }
98
99   /**
100    * Checks the behavior of the Serializer callback paths and row plugins.
101    */
102   public function testSerializerResponses() {
103     // Test the serialize callback.
104     $view = Views::getView('test_serializer_display_field');
105     $view->initDisplay();
106     $this->executeView($view);
107
108     $actual_json = $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]);
109     $this->assertResponse(200);
110     $this->assertCacheTags($view->getCacheTags());
111     $this->assertCacheContexts(['languages:language_interface', 'theme', 'request_format']);
112     // @todo Due to https://www.drupal.org/node/2352009 we can't yet test the
113     // propagation of cache max-age.
114
115     // Test the http Content-type.
116     $headers = $this->drupalGetHeaders();
117     $this->assertSame(['application/json'], $headers['Content-Type']);
118
119     $expected = [];
120     foreach ($view->result as $row) {
121       $expected_row = [];
122       foreach ($view->field as $id => $field) {
123         $expected_row[$id] = $field->render($row);
124       }
125       $expected[] = $expected_row;
126     }
127
128     $this->assertIdentical($actual_json, json_encode($expected), 'The expected JSON output was found.');
129
130     // Test that the rendered output and the preview output are the same.
131     $view->destroy();
132     $view->setDisplay('rest_export_1');
133     // Mock the request content type by setting it on the display handler.
134     $view->display_handler->setContentType('json');
135     $output = $view->preview();
136     $this->assertIdentical($actual_json, (string) drupal_render_root($output), 'The expected JSON preview output was found.');
137
138     // Test a 403 callback.
139     $this->drupalGet('test/serialize/denied', ['query' => ['_format' => 'json']]);
140     $this->assertResponse(403);
141
142     // Test the entity rows.
143     $view = Views::getView('test_serializer_display_entity');
144     $view->initDisplay();
145     $this->executeView($view);
146
147     // Get the serializer service.
148     $serializer = $this->container->get('serializer');
149
150     $entities = [];
151     foreach ($view->result as $row) {
152       $entities[] = $row->_entity;
153     }
154
155     $expected = $serializer->serialize($entities, 'json');
156
157     $actual_json = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]);
158     $this->assertResponse(200);
159     $this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
160     $expected_cache_tags = $view->getCacheTags();
161     $expected_cache_tags[] = 'entity_test_list';
162     /** @var \Drupal\Core\Entity\EntityInterface $entity */
163     foreach ($entities as $entity) {
164       $expected_cache_tags = Cache::mergeTags($expected_cache_tags, $entity->getCacheTags());
165     }
166     $this->assertCacheTags($expected_cache_tags);
167     $this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
168
169     $expected = $serializer->serialize($entities, 'hal_json');
170     $actual_json = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'hal_json']]);
171     $this->assertIdentical($actual_json, $expected, 'The expected HAL output was found.');
172     $this->assertCacheTags($expected_cache_tags);
173
174     // Change the format to xml.
175     $view->setDisplay('rest_export_1');
176     $view->getDisplay()->setOption('style', [
177       'type' => 'serializer',
178       'options' => [
179         'uses_fields' => FALSE,
180         'formats' => [
181           'xml' => 'xml',
182         ],
183       ],
184     ]);
185     $view->save();
186     $expected = $serializer->serialize($entities, 'xml');
187     $actual_xml = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'xml']]);
188     $this->assertSame(trim($expected), $actual_xml);
189     $this->assertCacheContexts(['languages:language_interface', 'theme', 'entity_test_view_grants', 'request_format']);
190
191     // Allow multiple formats.
192     $view->setDisplay('rest_export_1');
193     $view->getDisplay()->setOption('style', [
194       'type' => 'serializer',
195       'options' => [
196         'uses_fields' => FALSE,
197         'formats' => [
198           'xml' => 'xml',
199           'json' => 'json',
200         ],
201       ],
202     ]);
203     $view->save();
204     $expected = $serializer->serialize($entities, 'json');
205     $actual_json = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]);
206     $this->assertIdentical($actual_json, $expected, 'The expected JSON output was found.');
207     $expected = $serializer->serialize($entities, 'xml');
208     $actual_xml = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'xml']]);
209     $this->assertSame(trim($expected), $actual_xml);
210   }
211
212   /**
213    * Verifies REST export views work on the same path as a page display.
214    */
215   public function testSharedPagePath() {
216     // Test with no format as well as html explicitly.
217     $this->drupalGet('test/serialize/shared');
218     $this->assertResponse(200);
219     $this->assertHeader('content-type', 'text/html; charset=UTF-8');
220
221     $this->drupalGet('test/serialize/shared', ['query' => ['_format' => 'html']]);
222     $this->assertResponse(200);
223     $this->assertHeader('content-type', 'text/html; charset=UTF-8');
224
225     $this->drupalGet('test/serialize/shared', ['query' => ['_format' => 'json']]);
226     $this->assertResponse(200);
227     $this->assertHeader('content-type', 'application/json');
228
229     $this->drupalGet('test/serialize/shared', ['query' => ['_format' => 'xml']]);
230     $this->assertResponse(200);
231     $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
232   }
233
234   /**
235    * Verifies site maintenance mode functionality.
236    */
237   public function testSiteMaintenance() {
238     $view = Views::getView('test_serializer_display_field');
239     $view->initDisplay();
240     $this->executeView($view);
241
242     // Set the site to maintenance mode.
243     $this->container->get('state')->set('system.maintenance_mode', TRUE);
244
245     $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]);
246     // Verify that the endpoint is unavailable for anonymous users.
247     $this->assertResponse(503);
248   }
249
250   /**
251    * Sets up a request on the request stack with a specified format.
252    *
253    * @param string $format
254    *   The new request format.
255    */
256   protected function addRequestWithFormat($format) {
257     $request = \Drupal::request();
258     $request = clone $request;
259     $request->setRequestFormat($format);
260
261     \Drupal::requestStack()->push($request);
262   }
263
264   /**
265    * Tests REST export with views render caching enabled.
266    */
267   public function testRestRenderCaching() {
268     $this->drupalLogin($this->adminUser);
269     /** @var \Drupal\Core\Render\RenderCacheInterface $render_cache */
270     $render_cache = \Drupal::service('render_cache');
271
272     // Enable render caching for the views.
273     /** @var \Drupal\views\ViewEntityInterface $storage */
274     $storage = View::load('test_serializer_display_entity');
275     $options = &$storage->getDisplay('default');
276     $options['display_options']['cache'] = [
277       'type' => 'tag',
278     ];
279     $storage->save();
280
281     $original = DisplayPluginBase::buildBasicRenderable('test_serializer_display_entity', 'rest_export_1');
282
283     // Ensure that there is no corresponding render cache item yet.
284     $original['#cache'] += ['contexts' => []];
285     $original['#cache']['contexts'] = Cache::mergeContexts($original['#cache']['contexts'], $this->container->getParameter('renderer.config')['required_cache_contexts']);
286
287     $cache_tags = [
288       'config:views.view.test_serializer_display_entity',
289       'entity_test:1',
290       'entity_test:10',
291       'entity_test:2',
292       'entity_test:3',
293       'entity_test:4',
294       'entity_test:5',
295       'entity_test:6',
296       'entity_test:7',
297       'entity_test:8',
298       'entity_test:9',
299       'entity_test_list',
300     ];
301     $cache_contexts = [
302       'entity_test_view_grants',
303       'languages:language_interface',
304       'theme',
305       'request_format',
306     ];
307
308     $this->assertFalse($render_cache->get($original));
309
310     // Request the page, once in XML and once in JSON to ensure that the caching
311     // varies by it.
312     $result1 = Json::decode($this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]));
313     $this->addRequestWithFormat('json');
314     $this->assertHeader('content-type', 'application/json');
315     $this->assertCacheContexts($cache_contexts);
316     $this->assertCacheTags($cache_tags);
317     $this->assertTrue($render_cache->get($original));
318
319     $result_xml = $this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'xml']]);
320     $this->addRequestWithFormat('xml');
321     $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
322     $this->assertCacheContexts($cache_contexts);
323     $this->assertCacheTags($cache_tags);
324     $this->assertTrue($render_cache->get($original));
325
326     // Ensure that the XML output is different from the JSON one.
327     $this->assertNotEqual($result1, $result_xml);
328
329     // Ensure that the cached page works.
330     $result2 = Json::decode($this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]));
331     $this->addRequestWithFormat('json');
332     $this->assertHeader('content-type', 'application/json');
333     $this->assertEqual($result2, $result1);
334     $this->assertCacheContexts($cache_contexts);
335     $this->assertCacheTags($cache_tags);
336     $this->assertTrue($render_cache->get($original));
337
338     // Create a new entity and ensure that the cache tags are taken over.
339     EntityTest::create(['name' => 'test_11', 'user_id' => $this->adminUser->id()])->save();
340     $result3 = Json::decode($this->drupalGet('test/serialize/entity', ['query' => ['_format' => 'json']]));
341     $this->addRequestWithFormat('json');
342     $this->assertHeader('content-type', 'application/json');
343     $this->assertNotEqual($result3, $result2);
344
345     // Add the new entity cache tag and remove the first one, because we just
346     // show 10 items in total.
347     $cache_tags[] = 'entity_test:11';
348     unset($cache_tags[array_search('entity_test:1', $cache_tags)]);
349
350     $this->assertCacheContexts($cache_contexts);
351     $this->assertCacheTags($cache_tags);
352     $this->assertTrue($render_cache->get($original));
353   }
354
355   /**
356    * Tests the response format configuration.
357    */
358   public function testResponseFormatConfiguration() {
359     $this->drupalLogin($this->adminUser);
360
361     $style_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/style_options';
362
363     // Test with no format.
364     $this->drupalGet('test/serialize/field');
365     $this->assertHeader('content-type', 'text/html; charset=UTF-8');
366     $this->assertResponse(406, 'A 406 response was returned when no format was requested.');
367
368     // Select only 'xml' as an accepted format.
369     $this->drupalPostForm($style_options, ['style_options[formats][xml]' => 'xml'], t('Apply'));
370     $this->drupalPostForm(NULL, [], t('Save'));
371
372     // Should return a 406.
373     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]);
374     $this->assertHeader('content-type', 'application/json');
375     $this->assertResponse(406, 'A 406 response was returned when JSON was requested.');
376     // Should return a 200.
377     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'xml']]);
378     $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
379     $this->assertResponse(200, 'A 200 response was returned when XML was requested.');
380
381     // Add 'json' as an accepted format, so we have multiple.
382     $this->drupalPostForm($style_options, ['style_options[formats][json]' => 'json'], t('Apply'));
383     $this->drupalPostForm(NULL, [], t('Save'));
384
385     // Should return a 406. Emulates a sample Firefox header.
386     $this->drupalGet('test/serialize/field', [], ['Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8']);
387     $this->assertHeader('content-type', 'text/html; charset=UTF-8');
388     $this->assertResponse(406, 'A 406 response was returned when a browser accept header was requested.');
389
390     // Should return a 406.
391     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'html']]);
392     $this->assertHeader('content-type', 'text/html; charset=UTF-8');
393     $this->assertResponse(406, 'A 406 response was returned when HTML was requested.');
394
395     // Should return a 200.
396     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]);
397     $this->assertHeader('content-type', 'application/json');
398     $this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
399
400     // Should return a 200.
401     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'xml']]);
402     $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
403     $this->assertResponse(200, 'A 200 response was returned when XML was requested');
404
405     // Now configure no format, so both serialization formats should be allowed.
406     $this->drupalPostForm($style_options, ['style_options[formats][json]' => '0', 'style_options[formats][xml]' => '0'], t('Apply'));
407
408     // Should return a 200.
409     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]);
410     $this->assertHeader('content-type', 'application/json');
411     $this->assertResponse(200, 'A 200 response was returned when JSON was requested.');
412     // Should return a 200.
413     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'xml']]);
414     $this->assertHeader('content-type', 'text/xml; charset=UTF-8');
415     $this->assertResponse(200, 'A 200 response was returned when XML was requested');
416
417     // Should return a 406 for HTML still.
418     $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'html']]);
419     $this->assertHeader('content-type', 'text/html; charset=UTF-8');
420     $this->assertResponse(406, 'A 406 response was returned when HTML was requested.');
421   }
422
423   /**
424    * Test the field ID alias functionality of the DataFieldRow plugin.
425    */
426   public function testUIFieldAlias() {
427     $this->drupalLogin($this->adminUser);
428
429     // Test the UI settings for adding field ID aliases.
430     $this->drupalGet('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1');
431     $row_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options';
432     $this->assertLinkByHref($row_options);
433
434     // Test an empty string for an alias, this should not be used. This also
435     // tests that the form can be submitted with no aliases.
436     $this->drupalPostForm($row_options, ['row_options[field_options][name][alias]' => ''], t('Apply'));
437     $this->drupalPostForm(NULL, [], t('Save'));
438
439     $view = Views::getView('test_serializer_display_field');
440     $view->setDisplay('rest_export_1');
441     $this->executeView($view);
442
443     $expected = [];
444     foreach ($view->result as $row) {
445       $expected_row = [];
446       foreach ($view->field as $id => $field) {
447         $expected_row[$id] = $field->render($row);
448       }
449       $expected[] = $expected_row;
450     }
451
452     $this->assertIdentical(Json::decode($this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']])), $this->castSafeStrings($expected));
453
454     // Test a random aliases for fields, they should be replaced.
455     $alias_map = [
456       'name' => $this->randomMachineName(),
457       // Use # to produce an invalid character for the validation.
458       'nothing' => '#' . $this->randomMachineName(),
459       'created' => 'created',
460     ];
461
462     $edit = ['row_options[field_options][name][alias]' => $alias_map['name'], 'row_options[field_options][nothing][alias]' => $alias_map['nothing']];
463     $this->drupalPostForm($row_options, $edit, t('Apply'));
464     $this->assertText(t('The machine-readable name must contain only letters, numbers, dashes and underscores.'));
465
466     // Change the map alias value to a valid one.
467     $alias_map['nothing'] = $this->randomMachineName();
468
469     $edit = ['row_options[field_options][name][alias]' => $alias_map['name'], 'row_options[field_options][nothing][alias]' => $alias_map['nothing']];
470     $this->drupalPostForm($row_options, $edit, t('Apply'));
471
472     $this->drupalPostForm(NULL, [], t('Save'));
473
474     $view = Views::getView('test_serializer_display_field');
475     $view->setDisplay('rest_export_1');
476     $this->executeView($view);
477
478     $expected = [];
479     foreach ($view->result as $row) {
480       $expected_row = [];
481       foreach ($view->field as $id => $field) {
482         $expected_row[$alias_map[$id]] = $field->render($row);
483       }
484       $expected[] = $expected_row;
485     }
486
487     $this->assertIdentical(Json::decode($this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']])), $this->castSafeStrings($expected));
488   }
489
490   /**
491    * Tests the raw output options for row field rendering.
492    */
493   public function testFieldRawOutput() {
494     $this->drupalLogin($this->adminUser);
495
496     // Test the UI settings for adding field ID aliases.
497     $this->drupalGet('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1');
498     $row_options = 'admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options';
499     $this->assertLinkByHref($row_options);
500
501     // Test an empty string for an alias, this should not be used. This also
502     // tests that the form can be submitted with no aliases.
503     $values = [
504       'row_options[field_options][created][raw_output]' => '1',
505       'row_options[field_options][name][raw_output]' => '1',
506     ];
507     $this->drupalPostForm($row_options, $values, t('Apply'));
508     $this->drupalPostForm(NULL, [], t('Save'));
509
510     $view = Views::getView('test_serializer_display_field');
511     $view->setDisplay('rest_export_1');
512     $this->executeView($view);
513
514     $storage = $this->container->get('entity_type.manager')->getStorage('entity_test');
515
516     // Update the name for each to include a script tag.
517     foreach ($storage->loadMultiple() as $entity_test) {
518       $name = $entity_test->name->value;
519       $entity_test->set('name', "<script>$name</script>");
520       $entity_test->save();
521     }
522
523     // Just test the raw 'created' value against each row.
524     foreach (Json::decode($this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']])) as $index => $values) {
525       $this->assertIdentical($values['created'], $view->result[$index]->views_test_data_created, 'Expected raw created value found.');
526       $this->assertIdentical($values['name'], $view->result[$index]->views_test_data_name, 'Expected raw name value found.');
527     }
528
529     // Test result with an excluded field.
530     $view->setDisplay('rest_export_1');
531     $view->displayHandlers->get('rest_export_1')->overrideOption('fields', [
532       'name' => [
533         'id' => 'name',
534         'table' => 'views_test_data',
535         'field' => 'name',
536         'relationship' => 'none',
537       ],
538       'created' => [
539         'id' => 'created',
540         'exclude' => TRUE,
541         'table' => 'views_test_data',
542         'field' => 'created',
543         'relationship' => 'none',
544       ],
545     ]);
546     $view->save();
547     $this->executeView($view);
548     foreach (Json::decode($this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']])) as $index => $values) {
549       $this->assertTrue(!isset($values['created']), 'Excluded value not found.');
550     }
551     // Test that the excluded field is not shown in the row options.
552     $this->drupalGet('admin/structure/views/nojs/display/test_serializer_display_field/rest_export_1/row_options');
553     $this->assertNoText('created');
554   }
555
556   /**
557    * Tests the live preview output for json output.
558    */
559   public function testLivePreview() {
560     // We set up a request so it looks like an request in the live preview.
561     $request = new Request();
562     $request->query->add([MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_ajax']);
563     /** @var \Symfony\Component\HttpFoundation\RequestStack $request_stack */
564     $request_stack = \Drupal::service('request_stack');
565     $request_stack->push($request);
566
567     $view = Views::getView('test_serializer_display_entity');
568     $view->setDisplay('rest_export_1');
569     $this->executeView($view);
570
571     // Get the serializer service.
572     $serializer = $this->container->get('serializer');
573
574     $entities = [];
575     foreach ($view->result as $row) {
576       $entities[] = $row->_entity;
577     }
578
579     $expected = $serializer->serialize($entities, 'json');
580
581     $view->live_preview = TRUE;
582
583     $build = $view->preview();
584     $rendered_json = $build['#plain_text'];
585     $this->assertTrue(!isset($build['#markup']) && $rendered_json == $expected, 'Ensure the previewed json is escaped.');
586     $view->destroy();
587
588     $expected = $serializer->serialize($entities, 'xml');
589
590     // Change the request format to xml.
591     $view->setDisplay('rest_export_1');
592     $view->getDisplay()->setOption('style', [
593       'type' => 'serializer',
594       'options' => [
595         'uses_fields' => FALSE,
596         'formats' => [
597           'xml' => 'xml',
598         ],
599       ],
600     ]);
601
602     $this->executeView($view);
603     $build = $view->preview();
604     $rendered_xml = $build['#plain_text'];
605     $this->assertEqual($rendered_xml, $expected, 'Ensure we preview xml when we change the request format.');
606   }
607
608   /**
609    * Tests the views interface for REST export displays.
610    */
611   public function testSerializerViewsUI() {
612     $this->drupalLogin($this->adminUser);
613     // Click the "Update preview button".
614     $this->drupalPostForm('admin/structure/views/view/test_serializer_display_field/edit/rest_export_1', $edit = [], t('Update preview'));
615     $this->assertResponse(200);
616     // Check if we receive the expected result.
617     $result = $this->xpath('//div[@id="views-live-preview"]/pre');
618     $json_preview = $result[0]->getText();
619     $this->assertSame($json_preview, $this->drupalGet('test/serialize/field', ['query' => ['_format' => 'json']]), 'The expected JSON preview output was found.');
620   }
621
622   /**
623    * Tests the field row style using fieldapi fields.
624    */
625   public function testFieldapiField() {
626     $this->drupalCreateContentType(['type' => 'page']);
627     $node = $this->drupalCreateNode();
628
629     $result = Json::decode($this->drupalGet('test/serialize/node-field', ['query' => ['_format' => 'json']]));
630     $this->assertEqual($result[0]['nid'], $node->id());
631     $this->assertEqual($result[0]['body'], $node->body->processed);
632
633     // Make sure that serialized fields are not exposed to XSS.
634     $node = $this->drupalCreateNode();
635     $node->body = [
636       'value' => '<script type="text/javascript">alert("node-body");</script>' . $this->randomMachineName(32),
637       'format' => filter_default_format(),
638     ];
639     $node->save();
640     $result = Json::decode($this->drupalGet('test/serialize/node-field', ['query' => ['_format' => 'json']]));
641     $this->assertEqual($result[1]['nid'], $node->id());
642     $this->assertTrue(strpos($this->getSession()->getPage()->getContent(), "<script") === FALSE, "No script tag is present in the raw page contents.");
643
644     $this->drupalLogin($this->adminUser);
645
646     // Add an alias and make the output raw.
647     $row_options = 'admin/structure/views/nojs/display/test_serializer_node_display_field/rest_export_1/row_options';
648
649     // Test an empty string for an alias, this should not be used. This also
650     // tests that the form can be submitted with no aliases.
651     $this->drupalPostForm($row_options, ['row_options[field_options][title][raw_output]' => '1'], t('Apply'));
652     $this->drupalPostForm(NULL, [], t('Save'));
653
654     $view = Views::getView('test_serializer_node_display_field');
655     $view->setDisplay('rest_export_1');
656     $this->executeView($view);
657
658     // Test the raw 'created' value against each row.
659     foreach (Json::decode($this->drupalGet('test/serialize/node-field', ['query' => ['_format' => 'json']])) as $index => $values) {
660       $this->assertIdentical($values['title'], $view->result[$index]->_entity->title->value, 'Expected raw title value found.');
661     }
662
663     // Test that multiple raw body fields are shown.
664     // Set the body field to unlimited cardinality.
665     $storage_definition = $node->getFieldDefinition('body')->getFieldStorageDefinition();
666     $storage_definition->setCardinality(FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED);
667     $storage_definition->save();
668
669     $this->drupalPostForm($row_options, ['row_options[field_options][body][raw_output]' => '1'], t('Apply'));
670     $this->drupalPostForm(NULL, [], t('Save'));
671
672     $node = $this->drupalCreateNode();
673
674     $body = [
675       'value' => '<script type="text/javascript">alert("node-body");</script>' . $this->randomMachineName(32),
676       'format' => filter_default_format(),
677     ];
678     // Add two body items.
679     $node->body = [$body, $body];
680     $node->save();
681
682     $view = Views::getView('test_serializer_node_display_field');
683     $view->setDisplay('rest_export_1');
684     $this->executeView($view);
685
686     $result = Json::decode($this->drupalGet('test/serialize/node-field', ['query' => ['_format' => 'json']]));
687     $this->assertEqual(count($result[2]['body']), $node->body->count(), 'Expected count of values');
688     $this->assertEqual($result[2]['body'], array_map(function ($item) {
689       return $item['value'];
690     }, $node->body->getValue()), 'Expected raw body values found.');
691   }
692
693   /**
694    * Tests the "Grouped rows" functionality.
695    */
696   public function testGroupRows() {
697     /** @var \Drupal\Core\Render\RendererInterface $renderer */
698     $renderer = $this->container->get('renderer');
699     $this->drupalCreateContentType(['type' => 'page']);
700     // Create a text field with cardinality set to unlimited.
701     $field_name = 'field_group_rows';
702     $field_storage = FieldStorageConfig::create([
703       'field_name' => $field_name,
704       'entity_type' => 'node',
705       'type' => 'string',
706       'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
707     ]);
708     $field_storage->save();
709     // Create an instance of the text field on the content type.
710     $field = FieldConfig::create([
711       'field_storage' => $field_storage,
712       'bundle' => 'page',
713     ]);
714     $field->save();
715     $grouped_field_values = ['a', 'b', 'c'];
716     $edit = [
717       'title' => $this->randomMachineName(),
718       $field_name => $grouped_field_values,
719     ];
720     $this->drupalCreateNode($edit);
721     $view = Views::getView('test_serializer_node_display_field');
722     $view->setDisplay('rest_export_1');
723     // Override the view's fields to include the field_group_rows field, set the
724     // group_rows setting to true.
725     $fields = [
726       $field_name => [
727         'id' => $field_name,
728         'table' => 'node__' . $field_name,
729         'field' => $field_name,
730         'type' => 'string',
731         'group_rows' => TRUE,
732       ],
733     ];
734     $view->displayHandlers->get('default')->overrideOption('fields', $fields);
735     $build = $view->preview();
736     // Get the serializer service.
737     $serializer = $this->container->get('serializer');
738     // Check if the field_group_rows field is grouped.
739     $expected = [];
740     $expected[] = [$field_name => implode(', ', $grouped_field_values)];
741     $this->assertEqual($serializer->serialize($expected, 'json'), (string) $renderer->renderRoot($build));
742     // Set the group rows setting to false.
743     $view = Views::getView('test_serializer_node_display_field');
744     $view->setDisplay('rest_export_1');
745     $fields[$field_name]['group_rows'] = FALSE;
746     $view->displayHandlers->get('default')->overrideOption('fields', $fields);
747     $build = $view->preview();
748     // Check if the field_group_rows field is ungrouped and displayed per row.
749     $expected = [];
750     foreach ($grouped_field_values as $grouped_field_value) {
751       $expected[] = [$field_name => $grouped_field_value];
752     }
753     $this->assertEqual($serializer->serialize($expected, 'json'), (string) $renderer->renderRoot($build));
754   }
755
756   /**
757    * Tests the exposed filter works.
758    *
759    * There is an exposed filter on the title field which takes a title query
760    * parameter. This is set to filter nodes by those whose title starts with
761    * the value provided.
762    */
763   public function testRestViewExposedFilter() {
764     $this->drupalCreateContentType(['type' => 'page']);
765     $node0 = $this->drupalCreateNode(['title' => 'Node 1']);
766     $node1 = $this->drupalCreateNode(['title' => 'Node 11']);
767     $node2 = $this->drupalCreateNode(['title' => 'Node 111']);
768
769     // Test that no filter brings back all three nodes.
770     $result = Json::decode($this->drupalGet('test/serialize/node-exposed-filter', ['query' => ['_format' => 'json']]));
771
772     $expected = [
773       0 => [
774         'nid' => $node0->id(),
775         'body' => $node0->body->processed,
776       ],
777       1 => [
778         'nid' => $node1->id(),
779         'body' => $node1->body->processed,
780       ],
781       2 => [
782         'nid' => $node2->id(),
783         'body' => $node2->body->processed,
784       ],
785     ];
786
787     $this->assertEqual($result, $expected, 'Querying a view with no exposed filter returns all nodes.');
788
789     // Test that title starts with 'Node 11' query finds 2 of the 3 nodes.
790     $result = Json::decode($this->drupalGet('test/serialize/node-exposed-filter', ['query' => ['_format' => 'json', 'title' => 'Node 11']]));
791
792     $expected = [
793       0 => [
794         'nid' => $node1->id(),
795         'body' => $node1->body->processed,
796       ],
797       1 => [
798         'nid' => $node2->id(),
799         'body' => $node2->body->processed,
800       ],
801     ];
802
803     $cache_contexts = [
804       'languages:language_content',
805       'languages:language_interface',
806       'theme',
807       'request_format',
808       'user.node_grants:view',
809       'url',
810     ];
811
812     $this->assertEqual($result, $expected, 'Querying a view with a starts with exposed filter on the title returns nodes whose title starts with value provided.');
813     $this->assertCacheContexts($cache_contexts);
814   }
815
816   /**
817    * Test multilingual entity rows.
818    */
819   public function testMulEntityRows() {
820     // Create some languages.
821     ConfigurableLanguage::createFromLangcode('l1')->save();
822     ConfigurableLanguage::createFromLangcode('l2')->save();
823
824     // Create an entity with no translations.
825     $storage = \Drupal::entityTypeManager()->getStorage('entity_test_mul');
826     $storage->create(['langcode' => 'l1', 'name' => 'mul-none'])->save();
827
828     // Create some entities with translations.
829     $entity = $storage->create(['langcode' => 'l1', 'name' => 'mul-l1-orig']);
830     $entity->save();
831     $entity->addTranslation('l2', ['name' => 'mul-l1-l2'])->save();
832     $entity = $storage->create(['langcode' => 'l2', 'name' => 'mul-l2-orig']);
833     $entity->save();
834     $entity->addTranslation('l1', ['name' => 'mul-l2-l1'])->save();
835
836     // Get the names of the output.
837     $json = $this->drupalGet('test/serialize/translated_entity', ['query' => ['_format' => 'json']]);
838     $decoded = $this->container->get('serializer')->decode($json, 'hal_json');
839     $names = [];
840     foreach ($decoded as $item) {
841       $names[] = $item['name'][0]['value'];
842     }
843     sort($names);
844
845     // Check that the names are correct.
846     $expected = ['mul-l1-l2', 'mul-l1-orig', 'mul-l2-l1', 'mul-l2-orig', 'mul-none'];
847     $this->assertIdentical($names, $expected, 'The translated content was found in the JSON.');
848   }
849
850 }