Chris@18
|
1 <?php
|
Chris@18
|
2
|
Chris@18
|
3 namespace Drupal\Tests\quickedit\FunctionalJavascript;
|
Chris@18
|
4
|
Chris@18
|
5 use Behat\Mink\Session;
|
Chris@18
|
6 use Drupal\block_content\Entity\BlockContent;
|
Chris@18
|
7 use Drupal\Core\Entity\Entity\EntityViewDisplay;
|
Chris@18
|
8 use Drupal\field\Entity\FieldConfig;
|
Chris@18
|
9 use Drupal\field\Entity\FieldStorageConfig;
|
Chris@18
|
10 use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
|
Chris@18
|
11 use Drupal\node\Entity\Node;
|
Chris@18
|
12 use Drupal\node\Entity\NodeType;
|
Chris@18
|
13 use Drupal\filter\Entity\FilterFormat;
|
Chris@18
|
14 use Drupal\Tests\contextual\FunctionalJavascript\ContextualLinkClickTrait;
|
Chris@18
|
15 use Drupal\Tests\TestFileCreationTrait;
|
Chris@18
|
16
|
Chris@18
|
17 /**
|
Chris@18
|
18 * Tests loading of in-place editing functionality and lazy loading of its
|
Chris@18
|
19 * in-place editors.
|
Chris@18
|
20 *
|
Chris@18
|
21 * @group quickedit
|
Chris@18
|
22 */
|
Chris@18
|
23 class QuickEditLoadingTest extends WebDriverTestBase {
|
Chris@18
|
24
|
Chris@18
|
25 use ContextualLinkClickTrait;
|
Chris@18
|
26
|
Chris@18
|
27 use TestFileCreationTrait {
|
Chris@18
|
28 getTestFiles as drupalGetTestFiles;
|
Chris@18
|
29 }
|
Chris@18
|
30
|
Chris@18
|
31 /**
|
Chris@18
|
32 * Modules to enable.
|
Chris@18
|
33 *
|
Chris@18
|
34 * @var array
|
Chris@18
|
35 */
|
Chris@18
|
36 public static $modules = [
|
Chris@18
|
37 'contextual',
|
Chris@18
|
38 'quickedit',
|
Chris@18
|
39 'filter',
|
Chris@18
|
40 'node',
|
Chris@18
|
41 'image',
|
Chris@18
|
42 ];
|
Chris@18
|
43
|
Chris@18
|
44 /**
|
Chris@18
|
45 * An user with permissions to create and edit articles.
|
Chris@18
|
46 *
|
Chris@18
|
47 * @var \Drupal\user\UserInterface
|
Chris@18
|
48 */
|
Chris@18
|
49 protected $authorUser;
|
Chris@18
|
50
|
Chris@18
|
51 /**
|
Chris@18
|
52 * A test node.
|
Chris@18
|
53 *
|
Chris@18
|
54 * @var \Drupal\node\NodeInterface
|
Chris@18
|
55 */
|
Chris@18
|
56 protected $testNode;
|
Chris@18
|
57
|
Chris@18
|
58 /**
|
Chris@18
|
59 * A author user with permissions to access in-place editor.
|
Chris@18
|
60 *
|
Chris@18
|
61 * @var \Drupal\user\UserInterface
|
Chris@18
|
62 */
|
Chris@18
|
63 protected $editorUser;
|
Chris@18
|
64
|
Chris@18
|
65 /**
|
Chris@18
|
66 * {@inheritdoc}
|
Chris@18
|
67 */
|
Chris@18
|
68 protected function setUp() {
|
Chris@18
|
69 parent::setUp();
|
Chris@18
|
70
|
Chris@18
|
71 // Create a text format.
|
Chris@18
|
72 $filtered_html_format = FilterFormat::create([
|
Chris@18
|
73 'format' => 'filtered_html',
|
Chris@18
|
74 'name' => 'Filtered HTML',
|
Chris@18
|
75 'weight' => 0,
|
Chris@18
|
76 'filters' => [],
|
Chris@18
|
77 ]);
|
Chris@18
|
78 $filtered_html_format->save();
|
Chris@18
|
79
|
Chris@18
|
80 // Create a node type.
|
Chris@18
|
81 $this->drupalCreateContentType([
|
Chris@18
|
82 'type' => 'article',
|
Chris@18
|
83 'name' => 'Article',
|
Chris@18
|
84 ]);
|
Chris@18
|
85
|
Chris@18
|
86 // Set the node type to initially not have revisions.
|
Chris@18
|
87 // Testing with revisions will be done later.
|
Chris@18
|
88 $node_type = NodeType::load('article');
|
Chris@18
|
89 $node_type->setNewRevision(FALSE);
|
Chris@18
|
90 $node_type->save();
|
Chris@18
|
91
|
Chris@18
|
92 // Create one node of the above node type using the above text format.
|
Chris@18
|
93 $this->testNode = $this->drupalCreateNode([
|
Chris@18
|
94 'type' => 'article',
|
Chris@18
|
95 'body' => [
|
Chris@18
|
96 0 => [
|
Chris@18
|
97 'value' => '<p>How are you?</p>',
|
Chris@18
|
98 'format' => 'filtered_html',
|
Chris@18
|
99 ],
|
Chris@18
|
100 ],
|
Chris@18
|
101 'revision_log' => $this->randomString(),
|
Chris@18
|
102 ]);
|
Chris@18
|
103
|
Chris@18
|
104 // Create 2 users, the only difference being the ability to use in-place
|
Chris@18
|
105 // editing
|
Chris@18
|
106 $basic_permissions = [
|
Chris@18
|
107 'access content',
|
Chris@18
|
108 'create article content',
|
Chris@18
|
109 'edit any article content',
|
Chris@18
|
110 'use text format filtered_html',
|
Chris@18
|
111 'access contextual links',
|
Chris@18
|
112 ];
|
Chris@18
|
113 $this->authorUser = $this->drupalCreateUser($basic_permissions);
|
Chris@18
|
114 $this->editorUser = $this->drupalCreateUser(array_merge($basic_permissions, ['access in-place editing']));
|
Chris@18
|
115 }
|
Chris@18
|
116
|
Chris@18
|
117 /**
|
Chris@18
|
118 * Test the loading of Quick Edit with different permissions.
|
Chris@18
|
119 */
|
Chris@18
|
120 public function testUserPermissions() {
|
Chris@18
|
121 $assert = $this->assertSession();
|
Chris@18
|
122 $this->drupalLogin($this->authorUser);
|
Chris@18
|
123 $this->drupalGet('node/1');
|
Chris@18
|
124
|
Chris@18
|
125 // Library and in-place editors.
|
Chris@18
|
126 $this->assertNoRaw('core/modules/quickedit/js/quickedit.js', 'Quick Edit library not loaded.');
|
Chris@18
|
127 $this->assertNoRaw('core/modules/quickedit/js/editors/formEditor.js', "'form' in-place editor not loaded.");
|
Chris@18
|
128
|
Chris@18
|
129 // HTML annotation and title class does not exist for users without
|
Chris@18
|
130 // permission to in-place edit.
|
Chris@18
|
131 $this->assertNoRaw('data-quickedit-entity-id="node/1"');
|
Chris@18
|
132 $this->assertNoRaw('data-quickedit-field-id="node/1/body/en/full"');
|
Chris@18
|
133 $this->assertNoFieldByXPath('//h1[contains(@class, "js-quickedit-page-title")]');
|
Chris@18
|
134 $assert->linkNotExists('Quick edit');
|
Chris@18
|
135
|
Chris@18
|
136 // Tests the loading of Quick Edit when a user does have access to it.
|
Chris@18
|
137 // Also ensures lazy loading of in-place editors works.
|
Chris@18
|
138 $nid = $this->testNode->id();
|
Chris@18
|
139 // There should be only one revision so far.
|
Chris@18
|
140 $node = Node::load($nid);
|
Chris@18
|
141 $vids = \Drupal::entityManager()->getStorage('node')->revisionIds($node);
|
Chris@18
|
142 $this->assertCount(1, $vids, 'The node has only one revision.');
|
Chris@18
|
143 $original_log = $node->revision_log->value;
|
Chris@18
|
144
|
Chris@18
|
145 $this->drupalLogin($this->editorUser);
|
Chris@18
|
146 $this->drupalGet('node/' . $nid);
|
Chris@18
|
147 $page = $this->getSession()->getPage();
|
Chris@18
|
148
|
Chris@18
|
149 // Wait "Quick edit" button for node.
|
Chris@18
|
150 $assert->waitForElement('css', '[data-quickedit-entity-id="node/' . $nid . '"] .contextual .quickedit');
|
Chris@18
|
151 // Click by "Quick edit".
|
Chris@18
|
152 $this->clickContextualLink('[data-quickedit-entity-id="node/' . $nid . '"]', 'Quick edit');
|
Chris@18
|
153 // Switch to body field.
|
Chris@18
|
154 $page->find('css', '[data-quickedit-field-id="node/' . $nid . '/body/en/full"]')->click();
|
Chris@18
|
155 $assert->assertWaitOnAjaxRequest();
|
Chris@18
|
156
|
Chris@18
|
157 // Wait and update body field.
|
Chris@18
|
158 $body_field_locator = '[name="body[0][value]"]';
|
Chris@18
|
159 $body_text = 'Fine thanks.';
|
Chris@18
|
160 $assert->waitForElementVisible('css', $body_field_locator)->setValue('<p>' . $body_text . '</p>');
|
Chris@18
|
161
|
Chris@18
|
162 // Wait and click by "Save" button after body field was changed.
|
Chris@18
|
163 $assert->waitForElementVisible('css', '.quickedit-toolgroup.ops [type="submit"][aria-hidden="false"]')->click();
|
Chris@18
|
164 $assert->assertWaitOnAjaxRequest();
|
Chris@18
|
165
|
Chris@18
|
166 $node = Node::load($nid);
|
Chris@18
|
167 $vids = \Drupal::entityManager()->getStorage('node')->revisionIds($node);
|
Chris@18
|
168 $this->assertCount(1, $vids, 'The node has only one revision.');
|
Chris@18
|
169 $this->assertSame($original_log, $node->revision_log->value, 'The revision log message is unchanged.');
|
Chris@18
|
170
|
Chris@18
|
171 // Ensure that the changes take effect.
|
Chris@18
|
172 $assert->responseMatches("|\s*$body_text\s*|");
|
Chris@18
|
173
|
Chris@18
|
174 // Reload the page and check for updated body.
|
Chris@18
|
175 $this->drupalGet('node/' . $nid);
|
Chris@18
|
176 $assert->pageTextContains($body_text);
|
Chris@18
|
177 }
|
Chris@18
|
178
|
Chris@18
|
179 /**
|
Chris@18
|
180 * Test Quick Edit does not appear for entities with pending revisions.
|
Chris@18
|
181 */
|
Chris@18
|
182 public function testWithPendingRevision() {
|
Chris@18
|
183 $this->drupalLogin($this->editorUser);
|
Chris@18
|
184
|
Chris@18
|
185 // Verify that the preview is loaded correctly.
|
Chris@18
|
186 $this->drupalPostForm('node/add/article', ['title[0][value]' => 'foo'], 'Preview');
|
Chris@18
|
187 // Verify that quickedit is not active on preview.
|
Chris@18
|
188 $this->assertNoRaw('data-quickedit-entity-id="node/' . $this->testNode->id() . '"');
|
Chris@18
|
189 $this->assertNoRaw('data-quickedit-field-id="node/' . $this->testNode->id() . '/title/' . $this->testNode->language()->getId() . '/full"');
|
Chris@18
|
190
|
Chris@18
|
191 $this->drupalGet('node/' . $this->testNode->id());
|
Chris@18
|
192 $this->assertRaw('data-quickedit-entity-id="node/' . $this->testNode->id() . '"');
|
Chris@18
|
193 $this->assertRaw('data-quickedit-field-id="node/' . $this->testNode->id() . '/title/' . $this->testNode->language()->getId() . '/full"');
|
Chris@18
|
194
|
Chris@18
|
195 // Wait for the page to completely load before making any changes to the
|
Chris@18
|
196 // node. This allows Quick Edit to fetch the metadata without causing
|
Chris@18
|
197 // database locks on SQLite.
|
Chris@18
|
198 $this->assertSession()->assertWaitOnAjaxRequest();
|
Chris@18
|
199 $this->testNode->title = 'Updated node';
|
Chris@18
|
200 $this->testNode->setNewRevision(TRUE);
|
Chris@18
|
201 $this->testNode->isDefaultRevision(FALSE);
|
Chris@18
|
202 $this->testNode->save();
|
Chris@18
|
203
|
Chris@18
|
204 $this->drupalGet('node/' . $this->testNode->id());
|
Chris@18
|
205 $this->assertNoRaw('data-quickedit-entity-id="node/' . $this->testNode->id() . '"');
|
Chris@18
|
206 $this->assertNoRaw('data-quickedit-field-id="node/' . $this->testNode->id() . '/title/' . $this->testNode->language()->getId() . '/full"');
|
Chris@18
|
207 }
|
Chris@18
|
208
|
Chris@18
|
209 /**
|
Chris@18
|
210 * Tests the loading of Quick Edit for the title base field.
|
Chris@18
|
211 */
|
Chris@18
|
212 public function testTitleBaseField() {
|
Chris@18
|
213 $page = $this->getSession()->getPage();
|
Chris@18
|
214 $assert = $this->assertSession();
|
Chris@18
|
215 $nid = $this->testNode->id();
|
Chris@18
|
216
|
Chris@18
|
217 $this->drupalLogin($this->editorUser);
|
Chris@18
|
218 $this->drupalGet('node/' . $nid);
|
Chris@18
|
219
|
Chris@18
|
220 // Wait "Quick edit" button for node.
|
Chris@18
|
221 $assert->waitForElement('css', '[data-quickedit-entity-id="node/' . $nid . '"] .contextual .quickedit');
|
Chris@18
|
222 // Click by "Quick edit".
|
Chris@18
|
223 $this->clickContextualLink('[data-quickedit-entity-id="node/' . $nid . '"]', 'Quick edit');
|
Chris@18
|
224 // Switch to title field.
|
Chris@18
|
225 $page->find('css', '[data-quickedit-field-id="node/' . $nid . '/title/en/full"]')->click();
|
Chris@18
|
226 $assert->assertWaitOnAjaxRequest();
|
Chris@18
|
227
|
Chris@18
|
228 // Wait and update title field.
|
Chris@18
|
229 $field_locator = '.field--name-title';
|
Chris@18
|
230 $text_new = 'Obligatory question';
|
Chris@18
|
231 $assert->waitForElementVisible('css', $field_locator)->setValue($text_new);
|
Chris@18
|
232
|
Chris@18
|
233 // Wait and click by "Save" button after title field was changed.
|
Chris@18
|
234 $this->assertSession()->waitForElementVisible('css', '.quickedit-toolgroup.ops [type="submit"][aria-hidden="false"]')->click();
|
Chris@18
|
235 $assert->assertWaitOnAjaxRequest();
|
Chris@18
|
236
|
Chris@18
|
237 // Ensure that the changes take effect.
|
Chris@18
|
238 $assert->responseMatches("|\s*$text_new\s*|");
|
Chris@18
|
239
|
Chris@18
|
240 // Reload the page and check for updated title.
|
Chris@18
|
241 $this->drupalGet('node/' . $nid);
|
Chris@18
|
242 $assert->pageTextContains($text_new);
|
Chris@18
|
243 }
|
Chris@18
|
244
|
Chris@18
|
245 /**
|
Chris@18
|
246 * Tests that Quick Edit doesn't make fields rendered with display options
|
Chris@18
|
247 * editable.
|
Chris@18
|
248 */
|
Chris@18
|
249 public function testDisplayOptions() {
|
Chris@18
|
250 $node = Node::load('1');
|
Chris@18
|
251 $display_settings = [
|
Chris@18
|
252 'label' => 'inline',
|
Chris@18
|
253 ];
|
Chris@18
|
254 $build = $node->body->view($display_settings);
|
Chris@18
|
255 $output = \Drupal::service('renderer')->renderRoot($build);
|
Chris@18
|
256 $this->assertFalse(strpos($output, 'data-quickedit-field-id'), 'data-quickedit-field-id attribute not added when rendering field using dynamic display options.');
|
Chris@18
|
257 }
|
Chris@18
|
258
|
Chris@18
|
259 /**
|
Chris@18
|
260 * Tests Quick Edit on a node that was concurrently edited on the full node
|
Chris@18
|
261 * form.
|
Chris@18
|
262 */
|
Chris@18
|
263 public function testConcurrentEdit() {
|
Chris@18
|
264 $nid = $this->testNode->id();
|
Chris@18
|
265 $this->drupalLogin($this->authorUser);
|
Chris@18
|
266
|
Chris@18
|
267 // Open the edit page in the default session.
|
Chris@18
|
268 $this->drupalGet('node/' . $nid . '/edit');
|
Chris@18
|
269
|
Chris@18
|
270 // Switch to a concurrent session and save a quick edit change.
|
Chris@18
|
271 // We need to do some bookkeeping to keep track of the logged in user.
|
Chris@18
|
272 $logged_in_user = $this->loggedInUser;
|
Chris@18
|
273 $this->loggedInUser = FALSE;
|
Chris@18
|
274 // Register a session to preform concurrent editing.
|
Chris@18
|
275 $driver = $this->getDefaultDriverInstance();
|
Chris@18
|
276 $session = new Session($driver);
|
Chris@18
|
277 $this->mink->registerSession('concurrent', $session);
|
Chris@18
|
278 $this->mink->setDefaultSessionName('concurrent');
|
Chris@18
|
279 $this->initFrontPage();
|
Chris@18
|
280 $this->drupalLogin($this->editorUser);
|
Chris@18
|
281 $this->drupalGet('node/' . $nid);
|
Chris@18
|
282
|
Chris@18
|
283 $assert = $this->assertSession();
|
Chris@18
|
284 $page = $this->getSession()->getPage();
|
Chris@18
|
285
|
Chris@18
|
286 // Wait "Quick edit" button for node.
|
Chris@18
|
287 $assert->waitForElement('css', '[data-quickedit-entity-id="node/' . $nid . '"] .contextual .quickedit');
|
Chris@18
|
288 // Click by "Quick edit".
|
Chris@18
|
289 $this->clickContextualLink('[data-quickedit-entity-id="node/' . $nid . '"]', 'Quick edit');
|
Chris@18
|
290 // Switch to body field.
|
Chris@18
|
291 $page->find('css', '[data-quickedit-field-id="node/' . $nid . '/body/en/full"]')->click();
|
Chris@18
|
292 $assert->assertWaitOnAjaxRequest();
|
Chris@18
|
293
|
Chris@18
|
294 // Wait and update body field.
|
Chris@18
|
295 $body_field_locator = '[name="body[0][value]"]';
|
Chris@18
|
296 $body_text = 'Fine thanks.';
|
Chris@18
|
297 $assert->waitForElementVisible('css', $body_field_locator)->setValue('<p>' . $body_text . '</p>');
|
Chris@18
|
298
|
Chris@18
|
299 // Wait and click by "Save" button after body field was changed.
|
Chris@18
|
300 $assert->waitForElementVisible('css', '.quickedit-toolgroup.ops [type="submit"][aria-hidden="false"]')->click();
|
Chris@18
|
301 $assert->assertWaitOnAjaxRequest();
|
Chris@18
|
302
|
Chris@18
|
303 // Ensure that the changes take effect.
|
Chris@18
|
304 $assert->responseMatches("|\s*$body_text\s*|");
|
Chris@18
|
305
|
Chris@18
|
306 // Switch back to the default session.
|
Chris@18
|
307 $this->mink->setDefaultSessionName('default');
|
Chris@18
|
308 $this->loggedInUser = $logged_in_user;
|
Chris@18
|
309 // Ensure different save timestamps for field editing.
|
Chris@18
|
310 sleep(2);
|
Chris@18
|
311 $this->drupalPostForm(NULL, ['body[0][value]' => '<p>Concurrent edit!</p>'], 'Save');
|
Chris@18
|
312
|
Chris@18
|
313 $this->getSession()->getPage()->hasContent('The content has either been modified by another user, or you have already submitted modifications. As a result, your changes cannot be saved.');
|
Chris@18
|
314 }
|
Chris@18
|
315
|
Chris@18
|
316 /**
|
Chris@18
|
317 * Tests that Quick Edit's data- attributes are present for content blocks.
|
Chris@18
|
318 */
|
Chris@18
|
319 public function testContentBlock() {
|
Chris@18
|
320 \Drupal::service('module_installer')->install(['block_content']);
|
Chris@18
|
321
|
Chris@18
|
322 // Create and place a content_block block.
|
Chris@18
|
323 $block = BlockContent::create([
|
Chris@18
|
324 'info' => $this->randomMachineName(),
|
Chris@18
|
325 'type' => 'basic',
|
Chris@18
|
326 'langcode' => 'en',
|
Chris@18
|
327 ]);
|
Chris@18
|
328 $block->save();
|
Chris@18
|
329 $this->drupalPlaceBlock('block_content:' . $block->uuid());
|
Chris@18
|
330
|
Chris@18
|
331 // Check that the data- attribute is present.
|
Chris@18
|
332 $this->drupalLogin($this->editorUser);
|
Chris@18
|
333 $this->drupalGet('');
|
Chris@18
|
334 $this->assertRaw('data-quickedit-entity-id="block_content/1"');
|
Chris@18
|
335 }
|
Chris@18
|
336
|
Chris@18
|
337 /**
|
Chris@18
|
338 * Tests that Quick Edit can handle an image field.
|
Chris@18
|
339 */
|
Chris@18
|
340 public function testImageField() {
|
Chris@18
|
341 $page = $this->getSession()->getPage();
|
Chris@18
|
342 $assert = $this->assertSession();
|
Chris@18
|
343
|
Chris@18
|
344 // Add an image field to the content type.
|
Chris@18
|
345 FieldStorageConfig::create([
|
Chris@18
|
346 'field_name' => 'field_image',
|
Chris@18
|
347 'type' => 'image',
|
Chris@18
|
348 'entity_type' => 'node',
|
Chris@18
|
349 ])->save();
|
Chris@18
|
350 FieldConfig::create([
|
Chris@18
|
351 'field_name' => 'field_image',
|
Chris@18
|
352 'field_type' => 'image',
|
Chris@18
|
353 'label' => t('Image'),
|
Chris@18
|
354 'entity_type' => 'node',
|
Chris@18
|
355 'bundle' => 'article',
|
Chris@18
|
356 ])->save();
|
Chris@18
|
357 entity_get_form_display('node', 'article', 'default')
|
Chris@18
|
358 ->setComponent('field_image', [
|
Chris@18
|
359 'type' => 'image_image',
|
Chris@18
|
360 ])
|
Chris@18
|
361 ->save();
|
Chris@18
|
362 $display = EntityViewDisplay::load('node.article.default');
|
Chris@18
|
363 $display->setComponent('field_image', [
|
Chris@18
|
364 'type' => 'image',
|
Chris@18
|
365 ])->save();
|
Chris@18
|
366
|
Chris@18
|
367 // Add an image to the node.
|
Chris@18
|
368 $this->drupalLogin($this->editorUser);
|
Chris@18
|
369 $this->drupalGet('node/1/edit');
|
Chris@18
|
370 $image = $this->drupalGetTestFiles('image')[0];
|
Chris@18
|
371 $image_path = $this->container->get('file_system')->realpath($image->uri);
|
Chris@18
|
372 $page->attachFileToField('files[field_image_0]', $image_path);
|
Chris@18
|
373 $alt_field = $assert->waitForField('field_image[0][alt]');
|
Chris@18
|
374 $this->assertNotEmpty($alt_field);
|
Chris@18
|
375 $this->drupalPostForm(NULL, [
|
Chris@18
|
376 'field_image[0][alt]' => 'Vivamus aliquet elit',
|
Chris@18
|
377 ], t('Save'));
|
Chris@18
|
378
|
Chris@18
|
379 // The image field form should load normally.
|
Chris@18
|
380 // Wait "Quick edit" button for node.
|
Chris@18
|
381 $assert->waitForElement('css', '[data-quickedit-entity-id="node/1"] .contextual .quickedit');
|
Chris@18
|
382 // Click by "Quick edit".
|
Chris@18
|
383 $this->clickContextualLink('[data-quickedit-entity-id="node/1"]', 'Quick edit');
|
Chris@18
|
384 // Switch to body field.
|
Chris@18
|
385 $assert->waitForElement('css', '[data-quickedit-field-id="node/1/field_image/en/full"]')->click();
|
Chris@18
|
386 $assert->assertWaitOnAjaxRequest();
|
Chris@18
|
387
|
Chris@18
|
388 $field_locator = '.field--name-field-image';
|
Chris@18
|
389 $assert->waitForElementVisible('css', $field_locator);
|
Chris@18
|
390 }
|
Chris@18
|
391
|
Chris@18
|
392 }
|