Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Tests\link\Functional;
|
Chris@0
|
4
|
Chris@0
|
5 use Drupal\Component\Utility\Html;
|
Chris@0
|
6 use Drupal\Component\Utility\Unicode;
|
Chris@0
|
7 use Drupal\Core\Url;
|
Chris@0
|
8 use Drupal\entity_test\Entity\EntityTest;
|
Chris@0
|
9 use Drupal\field\Entity\FieldConfig;
|
Chris@0
|
10 use Drupal\link\LinkItemInterface;
|
Chris@0
|
11 use Drupal\node\NodeInterface;
|
Chris@0
|
12 use Drupal\Tests\BrowserTestBase;
|
Chris@0
|
13 use Drupal\field\Entity\FieldStorageConfig;
|
Chris@0
|
14
|
Chris@0
|
15 /**
|
Chris@0
|
16 * Tests link field widgets and formatters.
|
Chris@0
|
17 *
|
Chris@0
|
18 * @group link
|
Chris@0
|
19 */
|
Chris@0
|
20 class LinkFieldTest extends BrowserTestBase {
|
Chris@0
|
21
|
Chris@0
|
22 /**
|
Chris@0
|
23 * Modules to enable.
|
Chris@0
|
24 *
|
Chris@0
|
25 * @var array
|
Chris@0
|
26 */
|
Chris@14
|
27 public static $modules = [
|
Chris@14
|
28 'entity_test',
|
Chris@14
|
29 'link',
|
Chris@14
|
30 'node',
|
Chris@14
|
31 'link_test_base_field',
|
Chris@14
|
32 ];
|
Chris@0
|
33
|
Chris@0
|
34 /**
|
Chris@0
|
35 * A field to use in this test class.
|
Chris@0
|
36 *
|
Chris@0
|
37 * @var \Drupal\field\Entity\FieldStorageConfig
|
Chris@0
|
38 */
|
Chris@0
|
39 protected $fieldStorage;
|
Chris@0
|
40
|
Chris@0
|
41 /**
|
Chris@0
|
42 * The instance used in this test class.
|
Chris@0
|
43 *
|
Chris@0
|
44 * @var \Drupal\field\Entity\FieldConfig
|
Chris@0
|
45 */
|
Chris@0
|
46 protected $field;
|
Chris@0
|
47
|
Chris@0
|
48 protected function setUp() {
|
Chris@0
|
49 parent::setUp();
|
Chris@0
|
50
|
Chris@0
|
51 $this->drupalLogin($this->drupalCreateUser([
|
Chris@0
|
52 'view test entity',
|
Chris@0
|
53 'administer entity_test content',
|
Chris@0
|
54 'link to any page',
|
Chris@0
|
55 ]));
|
Chris@0
|
56 }
|
Chris@0
|
57
|
Chris@0
|
58 /**
|
Chris@0
|
59 * Tests link field URL validation.
|
Chris@0
|
60 */
|
Chris@0
|
61 public function testURLValidation() {
|
Chris@17
|
62 $field_name = mb_strtolower($this->randomMachineName());
|
Chris@0
|
63 // Create a field with settings to validate.
|
Chris@0
|
64 $this->fieldStorage = FieldStorageConfig::create([
|
Chris@0
|
65 'field_name' => $field_name,
|
Chris@0
|
66 'entity_type' => 'entity_test',
|
Chris@0
|
67 'type' => 'link',
|
Chris@0
|
68 ]);
|
Chris@0
|
69 $this->fieldStorage->save();
|
Chris@0
|
70 $this->field = FieldConfig::create([
|
Chris@0
|
71 'field_storage' => $this->fieldStorage,
|
Chris@0
|
72 'bundle' => 'entity_test',
|
Chris@0
|
73 'settings' => [
|
Chris@0
|
74 'title' => DRUPAL_DISABLED,
|
Chris@0
|
75 'link_type' => LinkItemInterface::LINK_GENERIC,
|
Chris@0
|
76 ],
|
Chris@0
|
77 ]);
|
Chris@0
|
78 $this->field->save();
|
Chris@0
|
79 entity_get_form_display('entity_test', 'entity_test', 'default')
|
Chris@0
|
80 ->setComponent($field_name, [
|
Chris@0
|
81 'type' => 'link_default',
|
Chris@0
|
82 'settings' => [
|
Chris@0
|
83 'placeholder_url' => 'http://example.com',
|
Chris@0
|
84 ],
|
Chris@0
|
85 ])
|
Chris@0
|
86 ->save();
|
Chris@0
|
87 entity_get_display('entity_test', 'entity_test', 'full')
|
Chris@0
|
88 ->setComponent($field_name, [
|
Chris@0
|
89 'type' => 'link',
|
Chris@0
|
90 ])
|
Chris@0
|
91 ->save();
|
Chris@0
|
92
|
Chris@0
|
93 // Display creation form.
|
Chris@0
|
94 $this->drupalGet('entity_test/add');
|
Chris@0
|
95 $this->assertFieldByName("{$field_name}[0][uri]", '', 'Link URL field is displayed');
|
Chris@0
|
96 $this->assertRaw('placeholder="http://example.com"');
|
Chris@0
|
97
|
Chris@0
|
98 // Create a path alias.
|
Chris@0
|
99 \Drupal::service('path.alias_storage')->save('/admin', '/a/path/alias');
|
Chris@0
|
100
|
Chris@0
|
101 // Create a node to test the link widget.
|
Chris@0
|
102 $node = $this->drupalCreateNode();
|
Chris@0
|
103
|
Chris@0
|
104 $restricted_node = $this->drupalCreateNode(['status' => NodeInterface::NOT_PUBLISHED]);
|
Chris@0
|
105
|
Chris@0
|
106 // Define some valid URLs (keys are the entered values, values are the
|
Chris@0
|
107 // strings displayed to the user).
|
Chris@0
|
108 $valid_external_entries = [
|
Chris@0
|
109 'http://www.example.com/' => 'http://www.example.com/',
|
Chris@0
|
110 // Strings within parenthesis without leading space char.
|
Chris@0
|
111 'http://www.example.com/strings_(string_within_parenthesis)' => 'http://www.example.com/strings_(string_within_parenthesis)',
|
Chris@0
|
112 // Numbers within parenthesis without leading space char.
|
Chris@0
|
113 'http://www.example.com/numbers_(9999)' => 'http://www.example.com/numbers_(9999)',
|
Chris@0
|
114 ];
|
Chris@0
|
115 $valid_internal_entries = [
|
Chris@0
|
116 '/entity_test/add' => '/entity_test/add',
|
Chris@0
|
117 '/a/path/alias' => '/a/path/alias',
|
Chris@0
|
118
|
Chris@0
|
119 // Front page, with query string and fragment.
|
Chris@0
|
120 '/' => '<front>',
|
Chris@0
|
121 '/?example=llama' => '<front>?example=llama',
|
Chris@0
|
122 '/#example' => '<front>#example',
|
Chris@0
|
123
|
Chris@16
|
124 // Trailing spaces should be ignored.
|
Chris@16
|
125 '/ ' => '<front>',
|
Chris@16
|
126 '/path with spaces ' => '/path with spaces',
|
Chris@16
|
127
|
Chris@0
|
128 // @todo '<front>' is valid input for BC reasons, may be removed by
|
Chris@0
|
129 // https://www.drupal.org/node/2421941
|
Chris@0
|
130 '<front>' => '<front>',
|
Chris@0
|
131 '<front>#example' => '<front>#example',
|
Chris@0
|
132 '<front>?example=llama' => '<front>?example=llama',
|
Chris@0
|
133
|
Chris@0
|
134 // Query string and fragment.
|
Chris@0
|
135 '?example=llama' => '?example=llama',
|
Chris@0
|
136 '#example' => '#example',
|
Chris@0
|
137
|
Chris@0
|
138 // Entity reference autocomplete value.
|
Chris@0
|
139 $node->label() . ' (1)' => $node->label() . ' (1)',
|
Chris@0
|
140 // Entity URI displayed as ER autocomplete value when displayed in a form.
|
Chris@0
|
141 'entity:node/1' => $node->label() . ' (1)',
|
Chris@0
|
142 // URI for an entity that exists, but is not accessible by the user.
|
Chris@0
|
143 'entity:node/' . $restricted_node->id() => '- Restricted access - (' . $restricted_node->id() . ')',
|
Chris@0
|
144 // URI for an entity that doesn't exist, but with a valid ID.
|
Chris@0
|
145 'entity:user/999999' => 'entity:user/999999',
|
Chris@0
|
146 ];
|
Chris@0
|
147
|
Chris@0
|
148 // Define some invalid URLs.
|
Chris@0
|
149 $validation_error_1 = "The path '@link_path' is invalid.";
|
Chris@18
|
150 $validation_error_2 = 'Manually entered paths should start with one of the following characters: / ? #';
|
Chris@0
|
151 $validation_error_3 = "The path '@link_path' is inaccessible.";
|
Chris@0
|
152 $invalid_external_entries = [
|
Chris@0
|
153 // Invalid protocol
|
Chris@0
|
154 'invalid://not-a-valid-protocol' => $validation_error_1,
|
Chris@0
|
155 // Missing host name
|
Chris@0
|
156 'http://' => $validation_error_1,
|
Chris@0
|
157 ];
|
Chris@0
|
158 $invalid_internal_entries = [
|
Chris@0
|
159 'no-leading-slash' => $validation_error_2,
|
Chris@0
|
160 'entity:non_existing_entity_type/yar' => $validation_error_1,
|
Chris@0
|
161 // URI for an entity that doesn't exist, with an invalid ID.
|
Chris@0
|
162 'entity:user/invalid-parameter' => $validation_error_1,
|
Chris@0
|
163 ];
|
Chris@0
|
164
|
Chris@0
|
165 // Test external and internal URLs for 'link_type' = LinkItemInterface::LINK_GENERIC.
|
Chris@0
|
166 $this->assertValidEntries($field_name, $valid_external_entries + $valid_internal_entries);
|
Chris@0
|
167 $this->assertInvalidEntries($field_name, $invalid_external_entries + $invalid_internal_entries);
|
Chris@0
|
168
|
Chris@0
|
169 // Test external URLs for 'link_type' = LinkItemInterface::LINK_EXTERNAL.
|
Chris@0
|
170 $this->field->setSetting('link_type', LinkItemInterface::LINK_EXTERNAL);
|
Chris@0
|
171 $this->field->save();
|
Chris@0
|
172 $this->assertValidEntries($field_name, $valid_external_entries);
|
Chris@0
|
173 $this->assertInvalidEntries($field_name, $valid_internal_entries + $invalid_external_entries);
|
Chris@0
|
174
|
Chris@0
|
175 // Test external URLs for 'link_type' = LinkItemInterface::LINK_INTERNAL.
|
Chris@0
|
176 $this->field->setSetting('link_type', LinkItemInterface::LINK_INTERNAL);
|
Chris@0
|
177 $this->field->save();
|
Chris@0
|
178 $this->assertValidEntries($field_name, $valid_internal_entries);
|
Chris@0
|
179 $this->assertInvalidEntries($field_name, $valid_external_entries + $invalid_internal_entries);
|
Chris@0
|
180
|
Chris@0
|
181 // Ensure that users with 'link to any page', don't apply access checking.
|
Chris@0
|
182 $this->drupalLogin($this->drupalCreateUser([
|
Chris@0
|
183 'view test entity',
|
Chris@0
|
184 'administer entity_test content',
|
Chris@0
|
185 ]));
|
Chris@0
|
186 $this->assertValidEntries($field_name, ['/entity_test/add' => '/entity_test/add']);
|
Chris@0
|
187 $this->assertInValidEntries($field_name, ['/admin' => $validation_error_3]);
|
Chris@0
|
188 }
|
Chris@0
|
189
|
Chris@0
|
190 /**
|
Chris@0
|
191 * Asserts that valid URLs can be submitted.
|
Chris@0
|
192 *
|
Chris@0
|
193 * @param string $field_name
|
Chris@0
|
194 * The field name.
|
Chris@0
|
195 * @param array $valid_entries
|
Chris@0
|
196 * An array of valid URL entries.
|
Chris@0
|
197 */
|
Chris@0
|
198 protected function assertValidEntries($field_name, array $valid_entries) {
|
Chris@0
|
199 foreach ($valid_entries as $uri => $string) {
|
Chris@0
|
200 $edit = [
|
Chris@0
|
201 "{$field_name}[0][uri]" => $uri,
|
Chris@0
|
202 ];
|
Chris@0
|
203 $this->drupalPostForm('entity_test/add', $edit, t('Save'));
|
Chris@0
|
204 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
Chris@0
|
205 $id = $match[1];
|
Chris@0
|
206 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
|
Chris@16
|
207 $this->assertRaw('"' . $string . '"');
|
Chris@0
|
208 }
|
Chris@0
|
209 }
|
Chris@0
|
210
|
Chris@0
|
211 /**
|
Chris@0
|
212 * Asserts that invalid URLs cannot be submitted.
|
Chris@0
|
213 *
|
Chris@0
|
214 * @param string $field_name
|
Chris@0
|
215 * The field name.
|
Chris@0
|
216 * @param array $invalid_entries
|
Chris@0
|
217 * An array of invalid URL entries.
|
Chris@0
|
218 */
|
Chris@0
|
219 protected function assertInvalidEntries($field_name, array $invalid_entries) {
|
Chris@0
|
220 foreach ($invalid_entries as $invalid_value => $error_message) {
|
Chris@0
|
221 $edit = [
|
Chris@0
|
222 "{$field_name}[0][uri]" => $invalid_value,
|
Chris@0
|
223 ];
|
Chris@0
|
224 $this->drupalPostForm('entity_test/add', $edit, t('Save'));
|
Chris@0
|
225 $this->assertText(t($error_message, ['@link_path' => $invalid_value]));
|
Chris@0
|
226 }
|
Chris@0
|
227 }
|
Chris@0
|
228
|
Chris@0
|
229 /**
|
Chris@0
|
230 * Tests the link title settings of a link field.
|
Chris@0
|
231 */
|
Chris@0
|
232 public function testLinkTitle() {
|
Chris@17
|
233 $field_name = mb_strtolower($this->randomMachineName());
|
Chris@0
|
234 // Create a field with settings to validate.
|
Chris@0
|
235 $this->fieldStorage = FieldStorageConfig::create([
|
Chris@0
|
236 'field_name' => $field_name,
|
Chris@0
|
237 'entity_type' => 'entity_test',
|
Chris@0
|
238 'type' => 'link',
|
Chris@0
|
239 ]);
|
Chris@0
|
240 $this->fieldStorage->save();
|
Chris@0
|
241 $this->field = FieldConfig::create([
|
Chris@0
|
242 'field_storage' => $this->fieldStorage,
|
Chris@0
|
243 'bundle' => 'entity_test',
|
Chris@0
|
244 'label' => 'Read more about this entity',
|
Chris@0
|
245 'settings' => [
|
Chris@0
|
246 'title' => DRUPAL_OPTIONAL,
|
Chris@0
|
247 'link_type' => LinkItemInterface::LINK_GENERIC,
|
Chris@0
|
248 ],
|
Chris@0
|
249 ]);
|
Chris@0
|
250 $this->field->save();
|
Chris@0
|
251 entity_get_form_display('entity_test', 'entity_test', 'default')
|
Chris@0
|
252 ->setComponent($field_name, [
|
Chris@0
|
253 'type' => 'link_default',
|
Chris@0
|
254 'settings' => [
|
Chris@0
|
255 'placeholder_url' => 'http://example.com',
|
Chris@0
|
256 'placeholder_title' => 'Enter the text for this link',
|
Chris@0
|
257 ],
|
Chris@0
|
258 ])
|
Chris@0
|
259 ->save();
|
Chris@0
|
260 entity_get_display('entity_test', 'entity_test', 'full')
|
Chris@0
|
261 ->setComponent($field_name, [
|
Chris@0
|
262 'type' => 'link',
|
Chris@0
|
263 'label' => 'hidden',
|
Chris@0
|
264 ])
|
Chris@0
|
265 ->save();
|
Chris@0
|
266
|
Chris@0
|
267 // Verify that the link text field works according to the field setting.
|
Chris@0
|
268 foreach ([DRUPAL_DISABLED, DRUPAL_REQUIRED, DRUPAL_OPTIONAL] as $title_setting) {
|
Chris@0
|
269 // Update the link title field setting.
|
Chris@0
|
270 $this->field->setSetting('title', $title_setting);
|
Chris@0
|
271 $this->field->save();
|
Chris@0
|
272
|
Chris@0
|
273 // Display creation form.
|
Chris@0
|
274 $this->drupalGet('entity_test/add');
|
Chris@0
|
275 // Assert label is shown.
|
Chris@0
|
276 $this->assertText('Read more about this entity');
|
Chris@0
|
277 $this->assertFieldByName("{$field_name}[0][uri]", '', 'URL field found.');
|
Chris@0
|
278 $this->assertRaw('placeholder="http://example.com"');
|
Chris@0
|
279
|
Chris@0
|
280 if ($title_setting === DRUPAL_DISABLED) {
|
Chris@0
|
281 $this->assertNoFieldByName("{$field_name}[0][title]", '', 'Link text field not found.');
|
Chris@0
|
282 $this->assertNoRaw('placeholder="Enter the text for this link"');
|
Chris@0
|
283 }
|
Chris@0
|
284 else {
|
Chris@0
|
285 $this->assertRaw('placeholder="Enter the text for this link"');
|
Chris@0
|
286
|
Chris@0
|
287 $this->assertFieldByName("{$field_name}[0][title]", '', 'Link text field found.');
|
Chris@16
|
288 if ($title_setting === DRUPAL_OPTIONAL) {
|
Chris@16
|
289 // Verify that the URL is required, if the link text is non-empty.
|
Chris@16
|
290 $edit = [
|
Chris@16
|
291 "{$field_name}[0][title]" => 'Example',
|
Chris@16
|
292 ];
|
Chris@16
|
293 $this->drupalPostForm(NULL, $edit, t('Save'));
|
Chris@16
|
294 $this->assertText(t('The URL field is required when the @title field is specified.', ['@title' => t('Link text')]));
|
Chris@16
|
295 }
|
Chris@0
|
296 if ($title_setting === DRUPAL_REQUIRED) {
|
Chris@0
|
297 // Verify that the link text is required, if the URL is non-empty.
|
Chris@0
|
298 $edit = [
|
Chris@0
|
299 "{$field_name}[0][uri]" => 'http://www.example.com',
|
Chris@0
|
300 ];
|
Chris@0
|
301 $this->drupalPostForm(NULL, $edit, t('Save'));
|
Chris@14
|
302 $this->assertText(t('@title field is required if there is @uri input.', ['@title' => t('Link text'), '@uri' => t('URL')]));
|
Chris@0
|
303
|
Chris@0
|
304 // Verify that the link text is not required, if the URL is empty.
|
Chris@0
|
305 $edit = [
|
Chris@0
|
306 "{$field_name}[0][uri]" => '',
|
Chris@0
|
307 ];
|
Chris@0
|
308 $this->drupalPostForm(NULL, $edit, t('Save'));
|
Chris@0
|
309 $this->assertNoText(t('@name field is required.', ['@name' => t('Link text')]));
|
Chris@0
|
310
|
Chris@0
|
311 // Verify that a URL and link text meets requirements.
|
Chris@0
|
312 $this->drupalGet('entity_test/add');
|
Chris@0
|
313 $edit = [
|
Chris@0
|
314 "{$field_name}[0][uri]" => 'http://www.example.com',
|
Chris@0
|
315 "{$field_name}[0][title]" => 'Example',
|
Chris@0
|
316 ];
|
Chris@0
|
317 $this->drupalPostForm(NULL, $edit, t('Save'));
|
Chris@0
|
318 $this->assertNoText(t('@name field is required.', ['@name' => t('Link text')]));
|
Chris@0
|
319 }
|
Chris@0
|
320 }
|
Chris@0
|
321 }
|
Chris@0
|
322
|
Chris@0
|
323 // Verify that a link without link text is rendered using the URL as text.
|
Chris@0
|
324 $value = 'http://www.example.com/';
|
Chris@0
|
325 $edit = [
|
Chris@0
|
326 "{$field_name}[0][uri]" => $value,
|
Chris@0
|
327 "{$field_name}[0][title]" => '',
|
Chris@0
|
328 ];
|
Chris@0
|
329 $this->drupalPostForm(NULL, $edit, t('Save'));
|
Chris@0
|
330 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
Chris@0
|
331 $id = $match[1];
|
Chris@0
|
332 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
|
Chris@0
|
333
|
Chris@0
|
334 $output = $this->renderTestEntity($id);
|
Chris@0
|
335 $expected_link = (string) \Drupal::l($value, Url::fromUri($value));
|
Chris@0
|
336 $this->assertContains($expected_link, $output);
|
Chris@0
|
337
|
Chris@0
|
338 // Verify that a link with text is rendered using the link text.
|
Chris@0
|
339 $title = $this->randomMachineName();
|
Chris@0
|
340 $edit = [
|
Chris@0
|
341 "{$field_name}[0][title]" => $title,
|
Chris@0
|
342 ];
|
Chris@0
|
343 $this->drupalPostForm("entity_test/manage/$id/edit", $edit, t('Save'));
|
Chris@0
|
344 $this->assertText(t('entity_test @id has been updated.', ['@id' => $id]));
|
Chris@0
|
345
|
Chris@0
|
346 $output = $this->renderTestEntity($id);
|
Chris@0
|
347 $expected_link = (string) \Drupal::l($title, Url::fromUri($value));
|
Chris@0
|
348 $this->assertContains($expected_link, $output);
|
Chris@0
|
349 }
|
Chris@0
|
350
|
Chris@0
|
351 /**
|
Chris@0
|
352 * Tests the default 'link' formatter.
|
Chris@0
|
353 */
|
Chris@0
|
354 public function testLinkFormatter() {
|
Chris@17
|
355 $field_name = mb_strtolower($this->randomMachineName());
|
Chris@0
|
356 // Create a field with settings to validate.
|
Chris@0
|
357 $this->fieldStorage = FieldStorageConfig::create([
|
Chris@0
|
358 'field_name' => $field_name,
|
Chris@0
|
359 'entity_type' => 'entity_test',
|
Chris@0
|
360 'type' => 'link',
|
Chris@0
|
361 'cardinality' => 3,
|
Chris@0
|
362 ]);
|
Chris@0
|
363 $this->fieldStorage->save();
|
Chris@0
|
364 FieldConfig::create([
|
Chris@0
|
365 'field_storage' => $this->fieldStorage,
|
Chris@0
|
366 'label' => 'Read more about this entity',
|
Chris@0
|
367 'bundle' => 'entity_test',
|
Chris@0
|
368 'settings' => [
|
Chris@0
|
369 'title' => DRUPAL_OPTIONAL,
|
Chris@0
|
370 'link_type' => LinkItemInterface::LINK_GENERIC,
|
Chris@0
|
371 ],
|
Chris@0
|
372 ])->save();
|
Chris@0
|
373 entity_get_form_display('entity_test', 'entity_test', 'default')
|
Chris@0
|
374 ->setComponent($field_name, [
|
Chris@0
|
375 'type' => 'link_default',
|
Chris@0
|
376 ])
|
Chris@0
|
377 ->save();
|
Chris@0
|
378 $display_options = [
|
Chris@0
|
379 'type' => 'link',
|
Chris@0
|
380 'label' => 'hidden',
|
Chris@0
|
381 ];
|
Chris@0
|
382 entity_get_display('entity_test', 'entity_test', 'full')
|
Chris@0
|
383 ->setComponent($field_name, $display_options)
|
Chris@0
|
384 ->save();
|
Chris@0
|
385
|
Chris@0
|
386 // Create an entity with three link field values:
|
Chris@0
|
387 // - The first field item uses a URL only.
|
Chris@0
|
388 // - The second field item uses a URL and link text.
|
Chris@0
|
389 // - The third field item uses a fragment-only URL with text.
|
Chris@0
|
390 // For consistency in assertion code below, the URL is assigned to the title
|
Chris@0
|
391 // variable for the first field.
|
Chris@0
|
392 $this->drupalGet('entity_test/add');
|
Chris@0
|
393 $url1 = 'http://www.example.com/content/articles/archive?author=John&year=2012#com';
|
Chris@0
|
394 $url2 = 'http://www.example.org/content/articles/archive?author=John&year=2012#org';
|
Chris@0
|
395 $url3 = '#net';
|
Chris@0
|
396 $title1 = $url1;
|
Chris@0
|
397 // Intentionally contains an ampersand that needs sanitization on output.
|
Chris@0
|
398 $title2 = 'A very long & strange example title that could break the nice layout of the site';
|
Chris@0
|
399 $title3 = 'Fragment only';
|
Chris@0
|
400 $edit = [
|
Chris@0
|
401 "{$field_name}[0][uri]" => $url1,
|
Chris@0
|
402 // Note that $title1 is not submitted.
|
Chris@0
|
403 "{$field_name}[0][title]" => '',
|
Chris@0
|
404 "{$field_name}[1][uri]" => $url2,
|
Chris@0
|
405 "{$field_name}[1][title]" => $title2,
|
Chris@0
|
406 "{$field_name}[2][uri]" => $url3,
|
Chris@0
|
407 "{$field_name}[2][title]" => $title3,
|
Chris@0
|
408 ];
|
Chris@0
|
409 // Assert label is shown.
|
Chris@0
|
410 $this->assertText('Read more about this entity');
|
Chris@0
|
411 $this->drupalPostForm(NULL, $edit, t('Save'));
|
Chris@0
|
412 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
Chris@0
|
413 $id = $match[1];
|
Chris@0
|
414 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
|
Chris@0
|
415
|
Chris@0
|
416 // Verify that the link is output according to the formatter settings.
|
Chris@0
|
417 // Not using generatePermutations(), since that leads to 32 cases, which
|
Chris@0
|
418 // would not test actual link field formatter functionality but rather
|
Chris@0
|
419 // the link generator and options/attributes. Only 'url_plain' has a
|
Chris@0
|
420 // dependency on 'url_only', so we have a total of ~10 cases.
|
Chris@0
|
421 $options = [
|
Chris@0
|
422 'trim_length' => [NULL, 6],
|
Chris@0
|
423 'rel' => [NULL, 'nofollow'],
|
Chris@0
|
424 'target' => [NULL, '_blank'],
|
Chris@0
|
425 'url_only' => [
|
Chris@0
|
426 ['url_only' => FALSE],
|
Chris@0
|
427 ['url_only' => FALSE, 'url_plain' => TRUE],
|
Chris@0
|
428 ['url_only' => TRUE],
|
Chris@0
|
429 ['url_only' => TRUE, 'url_plain' => TRUE],
|
Chris@0
|
430 ],
|
Chris@0
|
431 ];
|
Chris@0
|
432 foreach ($options as $setting => $values) {
|
Chris@0
|
433 foreach ($values as $new_value) {
|
Chris@0
|
434 // Update the field formatter settings.
|
Chris@0
|
435 if (!is_array($new_value)) {
|
Chris@0
|
436 $display_options['settings'] = [$setting => $new_value];
|
Chris@0
|
437 }
|
Chris@0
|
438 else {
|
Chris@0
|
439 $display_options['settings'] = $new_value;
|
Chris@0
|
440 }
|
Chris@0
|
441 entity_get_display('entity_test', 'entity_test', 'full')
|
Chris@0
|
442 ->setComponent($field_name, $display_options)
|
Chris@0
|
443 ->save();
|
Chris@0
|
444
|
Chris@0
|
445 $output = $this->renderTestEntity($id);
|
Chris@0
|
446 switch ($setting) {
|
Chris@0
|
447 case 'trim_length':
|
Chris@0
|
448 $url = $url1;
|
Chris@0
|
449 $title = isset($new_value) ? Unicode::truncate($title1, $new_value, FALSE, TRUE) : $title1;
|
Chris@0
|
450 $this->assertContains('<a href="' . Html::escape($url) . '">' . Html::escape($title) . '</a>', $output);
|
Chris@0
|
451
|
Chris@0
|
452 $url = $url2;
|
Chris@0
|
453 $title = isset($new_value) ? Unicode::truncate($title2, $new_value, FALSE, TRUE) : $title2;
|
Chris@0
|
454 $this->assertContains('<a href="' . Html::escape($url) . '">' . Html::escape($title) . '</a>', $output);
|
Chris@0
|
455
|
Chris@0
|
456 $url = $url3;
|
Chris@0
|
457 $title = isset($new_value) ? Unicode::truncate($title3, $new_value, FALSE, TRUE) : $title3;
|
Chris@0
|
458 $this->assertContains('<a href="' . Html::escape($url) . '">' . Html::escape($title) . '</a>', $output);
|
Chris@0
|
459 break;
|
Chris@0
|
460
|
Chris@0
|
461 case 'rel':
|
Chris@0
|
462 $rel = isset($new_value) ? ' rel="' . $new_value . '"' : '';
|
Chris@0
|
463 $this->assertContains('<a href="' . Html::escape($url1) . '"' . $rel . '>' . Html::escape($title1) . '</a>', $output);
|
Chris@0
|
464 $this->assertContains('<a href="' . Html::escape($url2) . '"' . $rel . '>' . Html::escape($title2) . '</a>', $output);
|
Chris@0
|
465 $this->assertContains('<a href="' . Html::escape($url3) . '"' . $rel . '>' . Html::escape($title3) . '</a>', $output);
|
Chris@0
|
466 break;
|
Chris@0
|
467
|
Chris@0
|
468 case 'target':
|
Chris@0
|
469 $target = isset($new_value) ? ' target="' . $new_value . '"' : '';
|
Chris@0
|
470 $this->assertContains('<a href="' . Html::escape($url1) . '"' . $target . '>' . Html::escape($title1) . '</a>', $output);
|
Chris@0
|
471 $this->assertContains('<a href="' . Html::escape($url2) . '"' . $target . '>' . Html::escape($title2) . '</a>', $output);
|
Chris@0
|
472 $this->assertContains('<a href="' . Html::escape($url3) . '"' . $target . '>' . Html::escape($title3) . '</a>', $output);
|
Chris@0
|
473 break;
|
Chris@0
|
474
|
Chris@0
|
475 case 'url_only':
|
Chris@0
|
476 // In this case, $new_value is an array.
|
Chris@0
|
477 if (!$new_value['url_only']) {
|
Chris@0
|
478 $this->assertContains('<a href="' . Html::escape($url1) . '">' . Html::escape($title1) . '</a>', $output);
|
Chris@0
|
479 $this->assertContains('<a href="' . Html::escape($url2) . '">' . Html::escape($title2) . '</a>', $output);
|
Chris@0
|
480 $this->assertContains('<a href="' . Html::escape($url3) . '">' . Html::escape($title3) . '</a>', $output);
|
Chris@0
|
481 }
|
Chris@0
|
482 else {
|
Chris@0
|
483 if (empty($new_value['url_plain'])) {
|
Chris@0
|
484 $this->assertContains('<a href="' . Html::escape($url1) . '">' . Html::escape($url1) . '</a>', $output);
|
Chris@0
|
485 $this->assertContains('<a href="' . Html::escape($url2) . '">' . Html::escape($url2) . '</a>', $output);
|
Chris@0
|
486 $this->assertContains('<a href="' . Html::escape($url3) . '">' . Html::escape($url3) . '</a>', $output);
|
Chris@0
|
487 }
|
Chris@0
|
488 else {
|
Chris@0
|
489 $this->assertNotContains('<a href="' . Html::escape($url1) . '">' . Html::escape($url1) . '</a>', $output);
|
Chris@0
|
490 $this->assertNotContains('<a href="' . Html::escape($url2) . '">' . Html::escape($url2) . '</a>', $output);
|
Chris@0
|
491 $this->assertNotContains('<a href="' . Html::escape($url3) . '">' . Html::escape($url3) . '</a>', $output);
|
Chris@0
|
492 $this->assertContains(Html::escape($url1), $output);
|
Chris@0
|
493 $this->assertContains(Html::escape($url2), $output);
|
Chris@0
|
494 $this->assertContains(Html::escape($url3), $output);
|
Chris@0
|
495 }
|
Chris@0
|
496 }
|
Chris@0
|
497 break;
|
Chris@0
|
498 }
|
Chris@0
|
499 }
|
Chris@0
|
500 }
|
Chris@0
|
501 }
|
Chris@0
|
502
|
Chris@0
|
503 /**
|
Chris@0
|
504 * Tests the 'link_separate' formatter.
|
Chris@0
|
505 *
|
Chris@0
|
506 * This test is mostly the same as testLinkFormatter(), but they cannot be
|
Chris@0
|
507 * merged, since they involve different configuration and output.
|
Chris@0
|
508 */
|
Chris@0
|
509 public function testLinkSeparateFormatter() {
|
Chris@17
|
510 $field_name = mb_strtolower($this->randomMachineName());
|
Chris@0
|
511 // Create a field with settings to validate.
|
Chris@0
|
512 $this->fieldStorage = FieldStorageConfig::create([
|
Chris@0
|
513 'field_name' => $field_name,
|
Chris@0
|
514 'entity_type' => 'entity_test',
|
Chris@0
|
515 'type' => 'link',
|
Chris@0
|
516 'cardinality' => 3,
|
Chris@0
|
517 ]);
|
Chris@0
|
518 $this->fieldStorage->save();
|
Chris@0
|
519 FieldConfig::create([
|
Chris@0
|
520 'field_storage' => $this->fieldStorage,
|
Chris@0
|
521 'bundle' => 'entity_test',
|
Chris@0
|
522 'settings' => [
|
Chris@0
|
523 'title' => DRUPAL_OPTIONAL,
|
Chris@0
|
524 'link_type' => LinkItemInterface::LINK_GENERIC,
|
Chris@0
|
525 ],
|
Chris@0
|
526 ])->save();
|
Chris@0
|
527 $display_options = [
|
Chris@0
|
528 'type' => 'link_separate',
|
Chris@0
|
529 'label' => 'hidden',
|
Chris@0
|
530 ];
|
Chris@0
|
531 entity_get_form_display('entity_test', 'entity_test', 'default')
|
Chris@0
|
532 ->setComponent($field_name, [
|
Chris@0
|
533 'type' => 'link_default',
|
Chris@0
|
534 ])
|
Chris@0
|
535 ->save();
|
Chris@0
|
536 entity_get_display('entity_test', 'entity_test', 'full')
|
Chris@0
|
537 ->setComponent($field_name, $display_options)
|
Chris@0
|
538 ->save();
|
Chris@0
|
539
|
Chris@0
|
540 // Create an entity with three link field values:
|
Chris@0
|
541 // - The first field item uses a URL only.
|
Chris@0
|
542 // - The second field item uses a URL and link text.
|
Chris@0
|
543 // - The third field item uses a fragment-only URL with text.
|
Chris@0
|
544 // For consistency in assertion code below, the URL is assigned to the title
|
Chris@0
|
545 // variable for the first field.
|
Chris@0
|
546 $this->drupalGet('entity_test/add');
|
Chris@0
|
547 $url1 = 'http://www.example.com/content/articles/archive?author=John&year=2012#com';
|
Chris@0
|
548 $url2 = 'http://www.example.org/content/articles/archive?author=John&year=2012#org';
|
Chris@0
|
549 $url3 = '#net';
|
Chris@0
|
550 // Intentionally contains an ampersand that needs sanitization on output.
|
Chris@0
|
551 $title2 = 'A very long & strange example title that could break the nice layout of the site';
|
Chris@0
|
552 $title3 = 'Fragment only';
|
Chris@0
|
553 $edit = [
|
Chris@0
|
554 "{$field_name}[0][uri]" => $url1,
|
Chris@0
|
555 "{$field_name}[1][uri]" => $url2,
|
Chris@0
|
556 "{$field_name}[1][title]" => $title2,
|
Chris@0
|
557 "{$field_name}[2][uri]" => $url3,
|
Chris@0
|
558 "{$field_name}[2][title]" => $title3,
|
Chris@0
|
559 ];
|
Chris@0
|
560 $this->drupalPostForm(NULL, $edit, t('Save'));
|
Chris@0
|
561 preg_match('|entity_test/manage/(\d+)|', $this->getUrl(), $match);
|
Chris@0
|
562 $id = $match[1];
|
Chris@0
|
563 $this->assertText(t('entity_test @id has been created.', ['@id' => $id]));
|
Chris@0
|
564
|
Chris@0
|
565 // Verify that the link is output according to the formatter settings.
|
Chris@0
|
566 $options = [
|
Chris@0
|
567 'trim_length' => [NULL, 6],
|
Chris@0
|
568 'rel' => [NULL, 'nofollow'],
|
Chris@0
|
569 'target' => [NULL, '_blank'],
|
Chris@0
|
570 ];
|
Chris@0
|
571 foreach ($options as $setting => $values) {
|
Chris@0
|
572 foreach ($values as $new_value) {
|
Chris@0
|
573 // Update the field formatter settings.
|
Chris@0
|
574 $display_options['settings'] = [$setting => $new_value];
|
Chris@0
|
575 entity_get_display('entity_test', 'entity_test', 'full')
|
Chris@0
|
576 ->setComponent($field_name, $display_options)
|
Chris@0
|
577 ->save();
|
Chris@0
|
578
|
Chris@0
|
579 $output = $this->renderTestEntity($id);
|
Chris@0
|
580 switch ($setting) {
|
Chris@0
|
581 case 'trim_length':
|
Chris@0
|
582 $url = $url1;
|
Chris@0
|
583 $url_title = isset($new_value) ? Unicode::truncate($url, $new_value, FALSE, TRUE) : $url;
|
Chris@0
|
584 $expected = '<div class="link-item">';
|
Chris@0
|
585 $expected .= '<div class="link-url"><a href="' . Html::escape($url) . '">' . Html::escape($url_title) . '</a></div>';
|
Chris@0
|
586 $expected .= '</div>';
|
Chris@0
|
587 $this->assertContains($expected, $output);
|
Chris@0
|
588
|
Chris@0
|
589 $url = $url2;
|
Chris@0
|
590 $url_title = isset($new_value) ? Unicode::truncate($url, $new_value, FALSE, TRUE) : $url;
|
Chris@0
|
591 $title = isset($new_value) ? Unicode::truncate($title2, $new_value, FALSE, TRUE) : $title2;
|
Chris@0
|
592 $expected = '<div class="link-item">';
|
Chris@0
|
593 $expected .= '<div class="link-title">' . Html::escape($title) . '</div>';
|
Chris@0
|
594 $expected .= '<div class="link-url"><a href="' . Html::escape($url) . '">' . Html::escape($url_title) . '</a></div>';
|
Chris@0
|
595 $expected .= '</div>';
|
Chris@0
|
596 $this->assertContains($expected, $output);
|
Chris@0
|
597
|
Chris@0
|
598 $url = $url3;
|
Chris@0
|
599 $url_title = isset($new_value) ? Unicode::truncate($url, $new_value, FALSE, TRUE) : $url;
|
Chris@0
|
600 $title = isset($new_value) ? Unicode::truncate($title3, $new_value, FALSE, TRUE) : $title3;
|
Chris@0
|
601 $expected = '<div class="link-item">';
|
Chris@0
|
602 $expected .= '<div class="link-title">' . Html::escape($title) . '</div>';
|
Chris@0
|
603 $expected .= '<div class="link-url"><a href="' . Html::escape($url) . '">' . Html::escape($url_title) . '</a></div>';
|
Chris@0
|
604 $expected .= '</div>';
|
Chris@0
|
605 $this->assertContains($expected, $output);
|
Chris@0
|
606 break;
|
Chris@0
|
607
|
Chris@0
|
608 case 'rel':
|
Chris@0
|
609 $rel = isset($new_value) ? ' rel="' . $new_value . '"' : '';
|
Chris@0
|
610 $this->assertContains('<div class="link-url"><a href="' . Html::escape($url1) . '"' . $rel . '>' . Html::escape($url1) . '</a></div>', $output);
|
Chris@0
|
611 $this->assertContains('<div class="link-url"><a href="' . Html::escape($url2) . '"' . $rel . '>' . Html::escape($url2) . '</a></div>', $output);
|
Chris@0
|
612 $this->assertContains('<div class="link-url"><a href="' . Html::escape($url3) . '"' . $rel . '>' . Html::escape($url3) . '</a></div>', $output);
|
Chris@0
|
613 break;
|
Chris@0
|
614
|
Chris@0
|
615 case 'target':
|
Chris@0
|
616 $target = isset($new_value) ? ' target="' . $new_value . '"' : '';
|
Chris@0
|
617 $this->assertContains('<div class="link-url"><a href="' . Html::escape($url1) . '"' . $target . '>' . Html::escape($url1) . '</a></div>', $output);
|
Chris@0
|
618 $this->assertContains('<div class="link-url"><a href="' . Html::escape($url2) . '"' . $target . '>' . Html::escape($url2) . '</a></div>', $output);
|
Chris@0
|
619 $this->assertContains('<div class="link-url"><a href="' . Html::escape($url3) . '"' . $target . '>' . Html::escape($url3) . '</a></div>', $output);
|
Chris@0
|
620 break;
|
Chris@0
|
621 }
|
Chris@0
|
622 }
|
Chris@0
|
623 }
|
Chris@0
|
624 }
|
Chris@0
|
625
|
Chris@0
|
626 /**
|
Chris@0
|
627 * Test '#link_type' property exists on 'link_default' widget.
|
Chris@0
|
628 *
|
Chris@0
|
629 * Make sure the 'link_default' widget exposes a '#link_type' property on
|
Chris@0
|
630 * its element. Modules can use it to understand if a text form element is
|
Chris@0
|
631 * a link and also which LinkItemInterface::LINK_* is (EXTERNAL, GENERIC,
|
Chris@0
|
632 * INTERNAL).
|
Chris@0
|
633 */
|
Chris@0
|
634 public function testLinkTypeOnLinkWidget() {
|
Chris@0
|
635
|
Chris@0
|
636 $link_type = LinkItemInterface::LINK_EXTERNAL;
|
Chris@17
|
637 $field_name = mb_strtolower($this->randomMachineName());
|
Chris@0
|
638
|
Chris@0
|
639 // Create a field with settings to validate.
|
Chris@0
|
640 $this->fieldStorage = FieldStorageConfig::create([
|
Chris@0
|
641 'field_name' => $field_name,
|
Chris@0
|
642 'entity_type' => 'entity_test',
|
Chris@0
|
643 'type' => 'link',
|
Chris@0
|
644 'cardinality' => 1,
|
Chris@0
|
645 ]);
|
Chris@0
|
646 $this->fieldStorage->save();
|
Chris@0
|
647 FieldConfig::create([
|
Chris@0
|
648 'field_storage' => $this->fieldStorage,
|
Chris@0
|
649 'label' => 'Read more about this entity',
|
Chris@0
|
650 'bundle' => 'entity_test',
|
Chris@0
|
651 'settings' => [
|
Chris@0
|
652 'title' => DRUPAL_OPTIONAL,
|
Chris@0
|
653 'link_type' => $link_type,
|
Chris@0
|
654 ],
|
Chris@0
|
655 ])->save();
|
Chris@0
|
656
|
Chris@0
|
657 $this->container->get('entity.manager')
|
Chris@0
|
658 ->getStorage('entity_form_display')
|
Chris@0
|
659 ->load('entity_test.entity_test.default')
|
Chris@0
|
660 ->setComponent($field_name, [
|
Chris@0
|
661 'type' => 'link_default',
|
Chris@0
|
662 ])
|
Chris@0
|
663 ->save();
|
Chris@0
|
664
|
Chris@0
|
665 $form = \Drupal::service('entity.form_builder')->getForm(EntityTest::create());
|
Chris@0
|
666 $this->assertEqual($form[$field_name]['widget'][0]['uri']['#link_type'], $link_type);
|
Chris@0
|
667 }
|
Chris@0
|
668
|
Chris@0
|
669 /**
|
Chris@0
|
670 * Tests editing a link to a non-node entity.
|
Chris@0
|
671 */
|
Chris@0
|
672 public function testEditNonNodeEntityLink() {
|
Chris@0
|
673
|
Chris@0
|
674 $entity_type_manager = \Drupal::entityTypeManager();
|
Chris@0
|
675 $entity_test_storage = $entity_type_manager->getStorage('entity_test');
|
Chris@0
|
676
|
Chris@0
|
677 // Create a field with settings to validate.
|
Chris@0
|
678 $this->fieldStorage = FieldStorageConfig::create([
|
Chris@0
|
679 'field_name' => 'field_link',
|
Chris@0
|
680 'entity_type' => 'entity_test',
|
Chris@0
|
681 'type' => 'link',
|
Chris@0
|
682 'cardinality' => 1,
|
Chris@0
|
683 ]);
|
Chris@0
|
684 $this->fieldStorage->save();
|
Chris@0
|
685 FieldConfig::create([
|
Chris@0
|
686 'field_storage' => $this->fieldStorage,
|
Chris@0
|
687 'label' => 'Read more about this entity',
|
Chris@0
|
688 'bundle' => 'entity_test',
|
Chris@0
|
689 'settings' => [
|
Chris@0
|
690 'title' => DRUPAL_OPTIONAL,
|
Chris@0
|
691 ],
|
Chris@0
|
692 ])->save();
|
Chris@0
|
693
|
Chris@0
|
694 $entity_type_manager
|
Chris@0
|
695 ->getStorage('entity_form_display')
|
Chris@0
|
696 ->load('entity_test.entity_test.default')
|
Chris@0
|
697 ->setComponent('field_link', [
|
Chris@0
|
698 'type' => 'link_default',
|
Chris@0
|
699 ])
|
Chris@0
|
700 ->save();
|
Chris@0
|
701
|
Chris@0
|
702 // Create a node and a test entity to have a possibly valid reference for
|
Chris@0
|
703 // both. Create another test entity that references the first test entity.
|
Chris@0
|
704 $entity_test_link = $entity_test_storage->create(['name' => 'correct link target']);
|
Chris@0
|
705 $entity_test_link->save();
|
Chris@0
|
706
|
Chris@0
|
707 $node = $this->drupalCreateNode(['wrong link target']);
|
Chris@0
|
708
|
Chris@0
|
709 $correct_link = 'entity:entity_test/' . $entity_test_link->id();
|
Chris@0
|
710 $entity_test = $entity_test_storage->create([
|
Chris@0
|
711 'name' => 'correct link target',
|
Chris@0
|
712 'field_link' => $correct_link,
|
Chris@0
|
713 ]);
|
Chris@0
|
714 $entity_test->save();
|
Chris@0
|
715
|
Chris@0
|
716 // Edit the entity and save it, verify the correct link is kept and not
|
Chris@0
|
717 // changed to point to a node. Currently, widget does not support non-node
|
Chris@0
|
718 // autocomplete and therefore must show the link unaltered.
|
Chris@0
|
719 $this->drupalGet($entity_test->toUrl('edit-form'));
|
Chris@0
|
720 $this->assertSession()->fieldValueEquals('field_link[0][uri]', $correct_link);
|
Chris@0
|
721 $this->drupalPostForm(NULL, [], 'Save');
|
Chris@0
|
722
|
Chris@0
|
723 $entity_test_storage->resetCache();
|
Chris@0
|
724 $entity_test = $entity_test_storage->load($entity_test->id());
|
Chris@0
|
725
|
Chris@0
|
726 $this->assertEquals($correct_link, $entity_test->get('field_link')->uri);
|
Chris@0
|
727 }
|
Chris@0
|
728
|
Chris@0
|
729 /**
|
Chris@0
|
730 * Renders a test_entity and returns the output.
|
Chris@0
|
731 *
|
Chris@0
|
732 * @param int $id
|
Chris@0
|
733 * The test_entity ID to render.
|
Chris@0
|
734 * @param string $view_mode
|
Chris@0
|
735 * (optional) The view mode to use for rendering.
|
Chris@0
|
736 * @param bool $reset
|
Chris@0
|
737 * (optional) Whether to reset the entity_test storage cache. Defaults to
|
Chris@0
|
738 * TRUE to simplify testing.
|
Chris@0
|
739 *
|
Chris@0
|
740 * @return string
|
Chris@0
|
741 * The rendered HTML output.
|
Chris@0
|
742 */
|
Chris@0
|
743 protected function renderTestEntity($id, $view_mode = 'full', $reset = TRUE) {
|
Chris@0
|
744 if ($reset) {
|
Chris@0
|
745 $this->container->get('entity.manager')->getStorage('entity_test')->resetCache([$id]);
|
Chris@0
|
746 }
|
Chris@0
|
747 $entity = EntityTest::load($id);
|
Chris@0
|
748 $display = entity_get_display($entity->getEntityTypeId(), $entity->bundle(), $view_mode);
|
Chris@0
|
749 $content = $display->build($entity);
|
Chris@0
|
750 $output = \Drupal::service('renderer')->renderRoot($content);
|
Chris@0
|
751 $output = (string) $output;
|
Chris@0
|
752 $this->verbose($output);
|
Chris@0
|
753 return $output;
|
Chris@0
|
754 }
|
Chris@0
|
755
|
Chris@0
|
756 }
|