Mercurial > hg > isophonics-drupal-site
comparison core/modules/responsive_image/src/Tests/ResponsiveImageFieldDisplayTest.php @ 0:4c8ae668cc8c
Initial import (non-working)
author | Chris Cannam |
---|---|
date | Wed, 29 Nov 2017 16:09:58 +0000 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:4c8ae668cc8c |
---|---|
1 <?php | |
2 | |
3 namespace Drupal\responsive_image\Tests; | |
4 | |
5 use Drupal\Component\Utility\Unicode; | |
6 use Drupal\image\Tests\ImageFieldTestBase; | |
7 use Drupal\image\Entity\ImageStyle; | |
8 use Drupal\node\Entity\Node; | |
9 use Drupal\file\Entity\File; | |
10 use Drupal\responsive_image\Plugin\Field\FieldFormatter\ResponsiveImageFormatter; | |
11 use Drupal\responsive_image\Entity\ResponsiveImageStyle; | |
12 use Drupal\user\RoleInterface; | |
13 | |
14 /** | |
15 * Tests responsive image display formatter. | |
16 * | |
17 * @group responsive_image | |
18 */ | |
19 class ResponsiveImageFieldDisplayTest extends ImageFieldTestBase { | |
20 | |
21 protected $dumpHeaders = TRUE; | |
22 | |
23 /** | |
24 * Responsive image style entity instance we test with. | |
25 * | |
26 * @var \Drupal\responsive_image\Entity\ResponsiveImageStyle | |
27 */ | |
28 protected $responsiveImgStyle; | |
29 | |
30 /** | |
31 * Modules to enable. | |
32 * | |
33 * @var array | |
34 */ | |
35 public static $modules = ['field_ui', 'responsive_image', 'responsive_image_test_module']; | |
36 | |
37 /** | |
38 * Drupal\simpletest\WebTestBase\setUp(). | |
39 */ | |
40 protected function setUp() { | |
41 parent::setUp(); | |
42 | |
43 // Create user. | |
44 $this->adminUser = $this->drupalCreateUser([ | |
45 'administer responsive images', | |
46 'access content', | |
47 'access administration pages', | |
48 'administer site configuration', | |
49 'administer content types', | |
50 'administer node display', | |
51 'administer nodes', | |
52 'create article content', | |
53 'edit any article content', | |
54 'delete any article content', | |
55 'administer image styles' | |
56 ]); | |
57 $this->drupalLogin($this->adminUser); | |
58 // Add responsive image style. | |
59 $this->responsiveImgStyle = ResponsiveImageStyle::create([ | |
60 'id' => 'style_one', | |
61 'label' => 'Style One', | |
62 'breakpoint_group' => 'responsive_image_test_module', | |
63 'fallback_image_style' => 'large', | |
64 ]); | |
65 } | |
66 | |
67 /** | |
68 * Tests responsive image formatters on node display for public files. | |
69 */ | |
70 public function testResponsiveImageFieldFormattersPublic() { | |
71 $this->addTestImageStyleMappings(); | |
72 $this->doTestResponsiveImageFieldFormatters('public'); | |
73 } | |
74 | |
75 /** | |
76 * Tests responsive image formatters on node display for private files. | |
77 */ | |
78 public function testResponsiveImageFieldFormattersPrivate() { | |
79 $this->addTestImageStyleMappings(); | |
80 // Remove access content permission from anonymous users. | |
81 user_role_change_permissions(RoleInterface::ANONYMOUS_ID, ['access content' => FALSE]); | |
82 $this->doTestResponsiveImageFieldFormatters('private'); | |
83 } | |
84 | |
85 /** | |
86 * Test responsive image formatters when image style is empty. | |
87 */ | |
88 public function testResponsiveImageFieldFormattersEmptyStyle() { | |
89 $this->addTestImageStyleMappings(TRUE); | |
90 $this->doTestResponsiveImageFieldFormatters('public', TRUE); | |
91 } | |
92 | |
93 /** | |
94 * Add image style mappings to the responsive image style entity. | |
95 * | |
96 * @param bool $empty_styles | |
97 * If true, the image style mappings will get empty image styles. | |
98 */ | |
99 protected function addTestImageStyleMappings($empty_styles = FALSE) { | |
100 if ($empty_styles) { | |
101 $this->responsiveImgStyle | |
102 ->addImageStyleMapping('responsive_image_test_module.mobile', '1x', [ | |
103 'image_mapping_type' => 'image_style', | |
104 'image_mapping' => '', | |
105 ]) | |
106 ->addImageStyleMapping('responsive_image_test_module.narrow', '1x', [ | |
107 'image_mapping_type' => 'sizes', | |
108 'image_mapping' => [ | |
109 'sizes' => '(min-width: 700px) 700px, 100vw', | |
110 'sizes_image_styles' => [], | |
111 ], | |
112 ]) | |
113 ->addImageStyleMapping('responsive_image_test_module.wide', '1x', [ | |
114 'image_mapping_type' => 'image_style', | |
115 'image_mapping' => '', | |
116 ]) | |
117 ->save(); | |
118 } | |
119 else { | |
120 $this->responsiveImgStyle | |
121 // Test the output of an empty image. | |
122 ->addImageStyleMapping('responsive_image_test_module.mobile', '1x', [ | |
123 'image_mapping_type' => 'image_style', | |
124 'image_mapping' => RESPONSIVE_IMAGE_EMPTY_IMAGE, | |
125 ]) | |
126 // Test the output with a 1.5x multiplier. | |
127 ->addImageStyleMapping('responsive_image_test_module.mobile', '1.5x', [ | |
128 'image_mapping_type' => 'image_style', | |
129 'image_mapping' => 'thumbnail', | |
130 ]) | |
131 // Test the output of the 'sizes' attribute. | |
132 ->addImageStyleMapping('responsive_image_test_module.narrow', '1x', [ | |
133 'image_mapping_type' => 'sizes', | |
134 'image_mapping' => [ | |
135 'sizes' => '(min-width: 700px) 700px, 100vw', | |
136 'sizes_image_styles' => [ | |
137 'large', | |
138 'medium', | |
139 ], | |
140 ], | |
141 ]) | |
142 // Test the normal output of mapping to an image style. | |
143 ->addImageStyleMapping('responsive_image_test_module.wide', '1x', [ | |
144 'image_mapping_type' => 'image_style', | |
145 'image_mapping' => 'large', | |
146 ]) | |
147 // Test the output of the original image. | |
148 ->addImageStyleMapping('responsive_image_test_module.wide', '3x', [ | |
149 'image_mapping_type' => 'image_style', | |
150 'image_mapping' => RESPONSIVE_IMAGE_ORIGINAL_IMAGE, | |
151 ]) | |
152 ->save(); | |
153 } | |
154 } | |
155 /** | |
156 * Test responsive image formatters on node display. | |
157 * | |
158 * If the empty styles param is set, then the function only tests for the | |
159 * fallback image style (large). | |
160 * | |
161 * @param string $scheme | |
162 * File scheme to use. | |
163 * @param bool $empty_styles | |
164 * If true, use an empty string for image style names. | |
165 * Defaults to false. | |
166 */ | |
167 protected function doTestResponsiveImageFieldFormatters($scheme, $empty_styles = FALSE) { | |
168 /** @var \Drupal\Core\Render\RendererInterface $renderer */ | |
169 $renderer = $this->container->get('renderer'); | |
170 $node_storage = $this->container->get('entity.manager')->getStorage('node'); | |
171 $field_name = Unicode::strtolower($this->randomMachineName()); | |
172 $this->createImageField($field_name, 'article', ['uri_scheme' => $scheme]); | |
173 // Create a new node with an image attached. Make sure we use a large image | |
174 // so the scale effects of the image styles always have an effect. | |
175 $test_image = current($this->drupalGetTestFiles('image', 39325)); | |
176 | |
177 // Create alt text for the image. | |
178 $alt = $this->randomMachineName(); | |
179 | |
180 $nid = $this->uploadNodeImage($test_image, $field_name, 'article', $alt); | |
181 $node_storage->resetCache([$nid]); | |
182 $node = $node_storage->load($nid); | |
183 | |
184 // Test that the default formatter is being used. | |
185 $image_uri = File::load($node->{$field_name}->target_id)->getFileUri(); | |
186 $image = [ | |
187 '#theme' => 'image', | |
188 '#uri' => $image_uri, | |
189 '#width' => 360, | |
190 '#height' => 240, | |
191 '#alt' => $alt, | |
192 ]; | |
193 $default_output = str_replace("\n", NULL, $renderer->renderRoot($image)); | |
194 $this->assertRaw($default_output, 'Default formatter displaying correctly on full node view.'); | |
195 | |
196 // Test field not being configured. This should not cause a fatal error. | |
197 $display_options = [ | |
198 'type' => 'responsive_image_test', | |
199 'settings' => ResponsiveImageFormatter::defaultSettings(), | |
200 ]; | |
201 $display = $this->container->get('entity.manager') | |
202 ->getStorage('entity_view_display') | |
203 ->load('node.article.default'); | |
204 if (!$display) { | |
205 $values = [ | |
206 'targetEntityType' => 'node', | |
207 'bundle' => 'article', | |
208 'mode' => 'default', | |
209 'status' => TRUE, | |
210 ]; | |
211 $display = $this->container->get('entity.manager')->getStorage('entity_view_display')->create($values); | |
212 } | |
213 $display->setComponent($field_name, $display_options)->save(); | |
214 | |
215 $this->drupalGet('node/' . $nid); | |
216 | |
217 // Test theme function for responsive image, but using the test formatter. | |
218 $display_options = [ | |
219 'type' => 'responsive_image_test', | |
220 'settings' => [ | |
221 'image_link' => 'file', | |
222 'responsive_image_style' => 'style_one', | |
223 ], | |
224 ]; | |
225 $display = entity_get_display('node', 'article', 'default'); | |
226 $display->setComponent($field_name, $display_options) | |
227 ->save(); | |
228 | |
229 $this->drupalGet('node/' . $nid); | |
230 | |
231 // Use the responsive image formatter linked to file formatter. | |
232 $display_options = [ | |
233 'type' => 'responsive_image', | |
234 'settings' => [ | |
235 'image_link' => 'file', | |
236 'responsive_image_style' => 'style_one', | |
237 ], | |
238 ]; | |
239 $display = entity_get_display('node', 'article', 'default'); | |
240 $display->setComponent($field_name, $display_options) | |
241 ->save(); | |
242 | |
243 $default_output = '<a href="' . file_url_transform_relative(file_create_url($image_uri)) . '"><picture'; | |
244 $this->drupalGet('node/' . $nid); | |
245 $cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags'); | |
246 $this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.'); | |
247 | |
248 $this->removeWhiteSpace(); | |
249 $this->assertRaw($default_output, 'Image linked to file formatter displaying correctly on full node view.'); | |
250 // Verify that the image can be downloaded. | |
251 $this->assertEqual(file_get_contents($test_image->uri), $this->drupalGet(file_create_url($image_uri)), 'File was downloaded successfully.'); | |
252 if ($scheme == 'private') { | |
253 // Only verify HTTP headers when using private scheme and the headers are | |
254 // sent by Drupal. | |
255 $this->assertEqual($this->drupalGetHeader('Content-Type'), 'image/png', 'Content-Type header was sent.'); | |
256 $this->assertTrue(strstr($this->drupalGetHeader('Cache-Control'), 'private') !== FALSE, 'Cache-Control header was sent.'); | |
257 | |
258 // Log out and try to access the file. | |
259 $this->drupalLogout(); | |
260 $this->drupalGet(file_create_url($image_uri)); | |
261 $this->assertResponse('403', 'Access denied to original image as anonymous user.'); | |
262 | |
263 // Log in again. | |
264 $this->drupalLogin($this->adminUser); | |
265 } | |
266 | |
267 // Use the responsive image formatter with a responsive image style. | |
268 $display_options['settings']['responsive_image_style'] = 'style_one'; | |
269 $display_options['settings']['image_link'] = ''; | |
270 $display->setComponent($field_name, $display_options) | |
271 ->save(); | |
272 | |
273 // Create a derivative so at least one MIME type will be known. | |
274 $large_style = ImageStyle::load('large'); | |
275 $large_style->createDerivative($image_uri, $large_style->buildUri($image_uri)); | |
276 | |
277 // Output should contain all image styles and all breakpoints. | |
278 $this->drupalGet('node/' . $nid); | |
279 if (!$empty_styles) { | |
280 $this->assertRaw('/styles/medium/'); | |
281 // Make sure the IE9 workaround is present. | |
282 $this->assertRaw('<!--[if IE 9]><video style="display: none;"><![endif]-->'); | |
283 $this->assertRaw('<!--[if IE 9]></video><![endif]-->'); | |
284 // Assert the empty image is present. | |
285 $this->assertRaw('data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='); | |
286 $thumbnail_style = ImageStyle::load('thumbnail'); | |
287 // Assert the output of the 'srcset' attribute (small multipliers first). | |
288 $this->assertRaw('data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== 1x, ' . file_url_transform_relative($thumbnail_style->buildUrl($image_uri)) . ' 1.5x'); | |
289 $this->assertRaw('/styles/medium/'); | |
290 // Assert the output of the original image. | |
291 $this->assertRaw(file_url_transform_relative(file_create_url($image_uri)) . ' 3x'); | |
292 // Assert the output of the breakpoints. | |
293 $this->assertRaw('media="(min-width: 0px)"'); | |
294 $this->assertRaw('media="(min-width: 560px)"'); | |
295 // Assert the output of the 'sizes' attribute. | |
296 $this->assertRaw('sizes="(min-width: 700px) 700px, 100vw"'); | |
297 $this->assertPattern('/media="\(min-width: 560px\)".+?sizes="\(min-width: 700px\) 700px, 100vw"/'); | |
298 // Assert the output of the 'srcset' attribute (small images first). | |
299 $medium_style = ImageStyle::load('medium'); | |
300 $this->assertRaw(file_url_transform_relative($medium_style->buildUrl($image_uri)) . ' 220w, ' . file_url_transform_relative($large_style->buildUrl($image_uri)) . ' 360w'); | |
301 $this->assertRaw('media="(min-width: 851px)"'); | |
302 } | |
303 $this->assertRaw('/styles/large/'); | |
304 $cache_tags = explode(' ', $this->drupalGetHeader('X-Drupal-Cache-Tags')); | |
305 $this->assertTrue(in_array('config:responsive_image.styles.style_one', $cache_tags)); | |
306 if (!$empty_styles) { | |
307 $this->assertTrue(in_array('config:image.style.medium', $cache_tags)); | |
308 $this->assertTrue(in_array('config:image.style.thumbnail', $cache_tags)); | |
309 $this->assertRaw('type="image/png"'); | |
310 } | |
311 $this->assertTrue(in_array('config:image.style.large', $cache_tags)); | |
312 | |
313 // Test the fallback image style. | |
314 $image = \Drupal::service('image.factory')->get($image_uri); | |
315 $fallback_image = [ | |
316 '#theme' => 'image', | |
317 '#alt' => $alt, | |
318 '#uri' => file_url_transform_relative($large_style->buildUrl($image->getSource())), | |
319 ]; | |
320 // The image.html.twig template has a newline after the <img> tag but | |
321 // responsive-image.html.twig doesn't have one after the fallback image, so | |
322 // we remove it here. | |
323 $default_output = trim($renderer->renderRoot($fallback_image)); | |
324 $this->assertRaw($default_output, 'Image style large formatter displaying correctly on full node view.'); | |
325 | |
326 if ($scheme == 'private') { | |
327 // Log out and try to access the file. | |
328 $this->drupalLogout(); | |
329 $this->drupalGet($large_style->buildUrl($image_uri)); | |
330 $this->assertResponse('403', 'Access denied to image style large as anonymous user.'); | |
331 $cache_tags_header = $this->drupalGetHeader('X-Drupal-Cache-Tags'); | |
332 $this->assertTrue(!preg_match('/ image_style\:/', $cache_tags_header), 'No image style cache tag found.'); | |
333 } | |
334 } | |
335 | |
336 /** | |
337 * Tests responsive image formatters on node display linked to the file. | |
338 */ | |
339 public function testResponsiveImageFieldFormattersLinkToFile() { | |
340 $this->addTestImageStyleMappings(); | |
341 $this->assertResponsiveImageFieldFormattersLink('file'); | |
342 } | |
343 | |
344 /** | |
345 * Tests responsive image formatters on node display linked to the node. | |
346 */ | |
347 public function testResponsiveImageFieldFormattersLinkToNode() { | |
348 $this->addTestImageStyleMappings(); | |
349 $this->assertResponsiveImageFieldFormattersLink('content'); | |
350 } | |
351 | |
352 /** | |
353 * Tests responsive image formatter on node display with an empty media query. | |
354 */ | |
355 public function testResponsiveImageFieldFormattersEmptyMediaQuery() { | |
356 $this->responsiveImgStyle | |
357 // Test the output of an empty media query. | |
358 ->addImageStyleMapping('responsive_image_test_module.empty', '1x', [ | |
359 'image_mapping_type' => 'image_style', | |
360 'image_mapping' => RESPONSIVE_IMAGE_EMPTY_IMAGE, | |
361 ]) | |
362 // Test the output with a 1.5x multiplier. | |
363 ->addImageStyleMapping('responsive_image_test_module.mobile', '1x', [ | |
364 'image_mapping_type' => 'image_style', | |
365 'image_mapping' => 'thumbnail', | |
366 ]) | |
367 ->save(); | |
368 $node_storage = $this->container->get('entity.manager')->getStorage('node'); | |
369 $field_name = Unicode::strtolower($this->randomMachineName()); | |
370 $this->createImageField($field_name, 'article', ['uri_scheme' => 'public']); | |
371 // Create a new node with an image attached. | |
372 $test_image = current($this->drupalGetTestFiles('image')); | |
373 $nid = $this->uploadNodeImage($test_image, $field_name, 'article', $this->randomMachineName()); | |
374 $node_storage->resetCache([$nid]); | |
375 | |
376 // Use the responsive image formatter linked to file formatter. | |
377 $display_options = [ | |
378 'type' => 'responsive_image', | |
379 'settings' => [ | |
380 'image_link' => '', | |
381 'responsive_image_style' => 'style_one', | |
382 ], | |
383 ]; | |
384 $display = entity_get_display('node', 'article', 'default'); | |
385 $display->setComponent($field_name, $display_options) | |
386 ->save(); | |
387 | |
388 // View the node. | |
389 $this->drupalGet('node/' . $nid); | |
390 | |
391 // Assert an empty media attribute is not output. | |
392 $this->assertNoPattern('@srcset="data:image/gif;base64,R0lGODlhAQABAIABAP///wAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== 1x".+?media=".+?/><source@'); | |
393 | |
394 // Assert the media attribute is present if it has a value. | |
395 $thumbnail_style = ImageStyle::load('thumbnail'); | |
396 $node = $node_storage->load($nid); | |
397 $image_uri = File::load($node->{$field_name}->target_id)->getFileUri(); | |
398 $this->assertPattern('/srcset="' . preg_quote(file_url_transform_relative($thumbnail_style->buildUrl($image_uri)), '/') . ' 1x".+?media="\(min-width: 0px\)"/'); | |
399 } | |
400 | |
401 /** | |
402 * Tests responsive image formatter on node display with one source. | |
403 */ | |
404 public function testResponsiveImageFieldFormattersOneSource() { | |
405 $this->responsiveImgStyle | |
406 // Test the output of an empty media query. | |
407 ->addImageStyleMapping('responsive_image_test_module.empty', '1x', [ | |
408 'image_mapping_type' => 'image_style', | |
409 'image_mapping' => 'medium', | |
410 ]) | |
411 ->addImageStyleMapping('responsive_image_test_module.empty', '2x', [ | |
412 'image_mapping_type' => 'image_style', | |
413 'image_mapping' => 'large', | |
414 ]) | |
415 ->save(); | |
416 $node_storage = $this->container->get('entity.manager')->getStorage('node'); | |
417 $field_name = Unicode::strtolower($this->randomMachineName()); | |
418 $this->createImageField($field_name, 'article', ['uri_scheme' => 'public']); | |
419 // Create a new node with an image attached. | |
420 $test_image = current($this->drupalGetTestFiles('image')); | |
421 $nid = $this->uploadNodeImage($test_image, $field_name, 'article', $this->randomMachineName()); | |
422 $node_storage->resetCache([$nid]); | |
423 | |
424 // Use the responsive image formatter linked to file formatter. | |
425 $display_options = [ | |
426 'type' => 'responsive_image', | |
427 'settings' => [ | |
428 'image_link' => '', | |
429 'responsive_image_style' => 'style_one', | |
430 ], | |
431 ]; | |
432 $display = entity_get_display('node', 'article', 'default'); | |
433 $display->setComponent($field_name, $display_options) | |
434 ->save(); | |
435 | |
436 // View the node. | |
437 $this->drupalGet('node/' . $nid); | |
438 | |
439 // Assert the media attribute is present if it has a value. | |
440 $large_style = ImageStyle::load('large'); | |
441 $medium_style = ImageStyle::load('medium'); | |
442 $node = $node_storage->load($nid); | |
443 $image_uri = File::load($node->{$field_name}->target_id)->getFileUri(); | |
444 $this->assertRaw('<img srcset="' . file_url_transform_relative($medium_style->buildUrl($image_uri)) . ' 1x, ' . file_url_transform_relative($large_style->buildUrl($image_uri)) . ' 2x"'); | |
445 } | |
446 | |
447 /** | |
448 * Tests responsive image formatters linked to the file or node. | |
449 * | |
450 * @param string $link_type | |
451 * The link type to test. Either 'file' or 'content'. | |
452 */ | |
453 private function assertResponsiveImageFieldFormattersLink($link_type) { | |
454 $field_name = Unicode::strtolower($this->randomMachineName()); | |
455 $field_settings = ['alt_field_required' => 0]; | |
456 $this->createImageField($field_name, 'article', ['uri_scheme' => 'public'], $field_settings); | |
457 // Create a new node with an image attached. | |
458 $test_image = current($this->drupalGetTestFiles('image')); | |
459 | |
460 // Test the image linked to file formatter. | |
461 $display_options = [ | |
462 'type' => 'responsive_image', | |
463 'settings' => [ | |
464 'image_link' => $link_type, | |
465 'responsive_image_style' => 'style_one', | |
466 ], | |
467 ]; | |
468 entity_get_display('node', 'article', 'default') | |
469 ->setComponent($field_name, $display_options) | |
470 ->save(); | |
471 // Ensure that preview works. | |
472 $this->previewNodeImage($test_image, $field_name, 'article'); | |
473 | |
474 // Look for a picture tag in the preview output | |
475 $this->assertPattern('/picture/'); | |
476 | |
477 $nid = $this->uploadNodeImage($test_image, $field_name, 'article'); | |
478 $this->container->get('entity.manager')->getStorage('node')->resetCache([$nid]); | |
479 $node = Node::load($nid); | |
480 | |
481 // Use the responsive image formatter linked to file formatter. | |
482 $display_options = [ | |
483 'type' => 'responsive_image', | |
484 'settings' => [ | |
485 'image_link' => $link_type, | |
486 'responsive_image_style' => 'style_one', | |
487 ], | |
488 ]; | |
489 entity_get_display('node', 'article', 'default') | |
490 ->setComponent($field_name, $display_options) | |
491 ->save(); | |
492 | |
493 // Create a derivative so at least one MIME type will be known. | |
494 $large_style = ImageStyle::load('large'); | |
495 $image_uri = File::load($node->{$field_name}->target_id)->getFileUri(); | |
496 $large_style->createDerivative($image_uri, $large_style->buildUri($image_uri)); | |
497 | |
498 // Output should contain all image styles and all breakpoints. | |
499 $this->drupalGet('node/' . $nid); | |
500 $this->removeWhiteSpace(); | |
501 switch ($link_type) { | |
502 case 'file': | |
503 // Make sure the link to the file is present. | |
504 $this->assertPattern('/<a(.*?)href="' . preg_quote(file_url_transform_relative(file_create_url($image_uri)), '/') . '"(.*?)><picture/'); | |
505 break; | |
506 | |
507 case 'content': | |
508 // Make sure the link to the node is present. | |
509 $this->assertPattern('/<a(.*?)href="' . preg_quote($node->url(), '/') . '"(.*?)><picture/'); | |
510 break; | |
511 } | |
512 } | |
513 | |
514 } |