67b066614c9ee8127e33aa1f98f0ca30c14597bc
[yaffs-website] / web / modules / contrib / redirect / src / Tests / RedirectUITest.php
1 <?php
2
3 namespace Drupal\redirect\Tests;
4
5 use Drupal\Component\Utility\SafeMarkup;
6 use Drupal\Core\Language\Language;
7 use Drupal\Core\Logger\RfcLogLevel;
8 use Drupal\Core\Url;
9 use Drupal\simpletest\WebTestBase;
10
11 /**
12  * UI tests for redirect module.
13  *
14  * @group redirect
15  */
16 class RedirectUITest extends WebTestBase {
17
18   use AssertRedirectTrait;
19
20   /**
21    * @var \Drupal\Core\Session\AccountInterface
22    */
23   protected $adminUser;
24
25   /**
26    * @var \Drupal\redirect\RedirectRepository
27    */
28   protected $repository;
29
30   /**
31    * @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage
32    */
33    protected $storage;
34
35   /**
36    * {@inheritdoc}
37    */
38   public static $modules = ['redirect', 'node', 'path', 'dblog', 'views', 'taxonomy'];
39
40   /**
41    * {@inheritdoc}
42    */
43   protected function setUp() {
44     parent::setUp();
45
46     $this->drupalCreateContentType(array('type' => 'article', 'name' => 'Article'));
47     $this->adminUser = $this->drupalCreateUser(array(
48       'administer redirects',
49       'administer redirect settings',
50       'access content',
51       'bypass node access',
52       'create url aliases',
53       'administer taxonomy',
54       'administer url aliases',
55     ));
56
57     $this->repository = \Drupal::service('redirect.repository');
58
59     $this->storage = $this->container->get('entity.manager')->getStorage('redirect');
60   }
61
62   /**
63    * Test the redirect UI.
64    */
65   public function testRedirectUI() {
66     $this->drupalLogin($this->adminUser);
67
68     // Test populating the redirect form with predefined values.
69     $this->drupalGet('admin/config/search/redirect/add', array('query' => array(
70       'source' => 'non-existing',
71       'source_query' => array('key' => 'val', 'key1' => 'val1'),
72       'redirect' => 'node',
73       'redirect_options' => array('query' => array('key' => 'val', 'key1' => 'val1')),
74     )));
75     $this->assertFieldByName('redirect_source[0][path]', 'non-existing?key=val&key1=val1');
76     $this->assertFieldByName('redirect_redirect[0][uri]', '/node?key=val&key1=val1');
77
78     // Test creating a new redirect via UI.
79     $this->drupalPostForm('admin/config/search/redirect/add', array(
80       'redirect_source[0][path]' => 'non-existing',
81       'redirect_redirect[0][uri]' => '/node',
82     ), t('Save'));
83
84     // Try to find the redirect we just created.
85     $redirect = $this->repository->findMatchingRedirect('non-existing');
86     $this->assertEqual($redirect->getSourceUrl(), Url::fromUri('base:non-existing')->toString());
87     $this->assertEqual($redirect->getRedirectUrl()->toString(), Url::fromUri('base:node')->toString());
88
89     // After adding the redirect we should end up in the list. Check if the
90     // redirect is listed.
91     $this->assertUrl('admin/config/search/redirect');
92     $this->assertText('non-existing');
93     $this->assertLink(Url::fromUri('base:node')->toString());
94     $this->assertText(t('Not specified'));
95
96     // Test the edit form and update action.
97     $this->clickLink(t('Edit'));
98     $this->assertFieldByName('redirect_source[0][path]', 'non-existing');
99     $this->assertFieldByName('redirect_redirect[0][uri]', '/node');
100     $this->assertFieldByName('status_code', $redirect->getStatusCode());
101
102     // Append a query string to see if we handle query data properly.
103     $this->drupalPostForm(NULL, array(
104       'redirect_source[0][path]' => 'non-existing?key=value',
105     ), t('Save'));
106
107     // Check the location after update and check if the value has been updated
108     // in the list.
109     $this->assertUrl('admin/config/search/redirect');
110     $this->assertText('non-existing?key=value');
111
112     // The path field should not contain the query string and therefore we
113     // should be able to load the redirect using only the url part without
114     // query.
115     $this->storage->resetCache();
116     $redirects = $this->repository->findBySourcePath('non-existing');
117     $redirect = array_shift($redirects);
118     $this->assertEqual($redirect->getSourceUrl(), Url::fromUri('base:non-existing', ['query' => ['key' => 'value']])->toString());
119
120     // Test the source url hints.
121     // The hint about an existing base path.
122     $this->drupalPostAjaxForm('admin/config/search/redirect/add', array(
123       'redirect_source[0][path]' => 'non-existing?key=value',
124     ), 'redirect_source[0][path]');
125     $this->assertRaw(t('The base source path %source is already being redirected. Do you want to <a href="@edit-page">edit the existing redirect</a>?',
126       array('%source' => 'non-existing?key=value', '@edit-page' => $redirect->url('edit-form'))));
127
128     // The hint about a valid path.
129     $this->drupalPostAjaxForm('admin/config/search/redirect/add', array(
130       'redirect_source[0][path]' => 'node',
131     ), 'redirect_source[0][path]');
132     $this->assertRaw(t('The source path %path is likely a valid path. It is preferred to <a href="@url-alias">create URL aliases</a> for existing paths rather than redirects.',
133       array('%path' => 'node', '@url-alias' => Url::fromRoute('path.admin_add')->toString())));
134
135     // Test validation.
136     // Duplicate redirect.
137     $this->drupalPostForm('admin/config/search/redirect/add', array(
138       'redirect_source[0][path]' => 'non-existing?key=value',
139       'redirect_redirect[0][uri]' => '/node',
140     ), t('Save'));
141     $this->assertRaw(t('The source path %source is already being redirected. Do you want to <a href="@edit-page">edit the existing redirect</a>?',
142       array('%source' => 'non-existing?key=value', '@edit-page' => $redirect->url('edit-form'))));
143
144     // Redirecting to itself.
145     $this->drupalPostForm('admin/config/search/redirect/add', array(
146       'redirect_source[0][path]' => 'node',
147       'redirect_redirect[0][uri]' => '/node',
148     ), t('Save'));
149     $this->assertRaw(t('You are attempting to redirect the page to itself. This will result in an infinite loop.'));
150
151     // Redirecting the front page.
152     $this->drupalPostForm('admin/config/search/redirect/add', array(
153       'redirect_source[0][path]' => '<front>',
154       'redirect_redirect[0][uri]' => '/node',
155     ), t('Save'));
156     $this->assertRaw(t('It is not allowed to create a redirect from the front page.'));
157
158     // Redirecting a url with fragment.
159     $this->drupalPostForm('admin/config/search/redirect/add', array(
160       'redirect_source[0][path]' => 'page-to-redirect#content',
161       'redirect_redirect[0][uri]' => '/node',
162     ), t('Save'));
163     $this->assertRaw(t('The anchor fragments are not allowed.'));
164
165     // Adding path that starts with /
166     $this->drupalPostForm('admin/config/search/redirect/add', array(
167       'redirect_source[0][path]' => '/page-to-redirect',
168       'redirect_redirect[0][uri]' => '/node',
169     ), t('Save'));
170     $this->assertRaw(t('The url to redirect from should not start with a forward slash (/).'));
171
172     // Test filters.
173     // Add a new redirect.
174     $this->drupalPostForm('admin/config/search/redirect/add', array(
175       'redirect_source[0][path]' => 'test27',
176       'redirect_redirect[0][uri]' => '/node',
177     ), t('Save'));
178
179     // Filter  with non existing value.
180     $this->drupalGet('admin/config/search/redirect', array(
181       'query' => array(
182         'status_code' => '3',
183       ),
184     ));
185
186     $rows = $this->xpath('//tbody/tr');
187     // Check if the list has no rows.
188     $this->assertTrue(count($rows) == 0);
189
190     // Filter with existing values.
191     $this->drupalGet('admin/config/search/redirect', array(
192       'query' => array(
193         'redirect_source__path' => 'test',
194         'status_code' => '2',
195       ),
196     ));
197
198     $rows = $this->xpath('//tbody/tr');
199     // Check if the list has 1 row.
200     $this->assertTrue(count($rows) == 1);
201
202     $this->drupalGet('admin/config/search/redirect', array(
203       'query' => array(
204         'redirect_redirect__uri' => 'nod',
205       ),
206     ));
207
208     $rows = $this->xpath('//tbody/tr');
209     // Check if the list has 2 rows.
210     $this->assertTrue(count($rows) == 2);
211
212     // Test the plural form of the bulk delete action.
213     $this->drupalGet('admin/config/search/redirect');
214     $edit = [
215       'redirect_bulk_form[0]' => TRUE,
216       'redirect_bulk_form[1]' => TRUE,
217     ];
218     $this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
219     $this->assertText('Are you sure you want to delete these redirects?');
220     $this->clickLink('Cancel');
221
222     // Test the delete action.
223     $this->clickLink(t('Delete'));
224     $this->assertRaw(t('Are you sure you want to delete the URL redirect from %source to %redirect?',
225       array('%source' => Url::fromUri('base:non-existing', ['query' => ['key' => 'value']])->toString(), '%redirect' => Url::fromUri('base:node')->toString())));
226     $this->drupalPostForm(NULL, array(), t('Delete'));
227     $this->assertUrl('admin/config/search/redirect');
228
229     // Test the bulk delete action.
230     $this->drupalPostForm(NULL, ['redirect_bulk_form[0]' => TRUE], t('Apply to selected items'));
231     $this->assertText('Are you sure you want to delete this redirect?');
232     $this->assertText('test27');
233     $this->drupalPostForm(NULL, [], t('Delete'));
234
235     $this->assertText(t('There is no redirect yet.'));
236   }
237
238   /**
239    * Tests redirects being automatically created upon path alias change.
240    */
241   public function testAutomaticRedirects() {
242     $this->drupalLogin($this->adminUser);
243
244     // Create a node and update its path alias which should result in a redirect
245     // being automatically created from the old alias to the new one.
246     $node = $this->drupalCreateNode(array(
247       'type' => 'article',
248       'langcode' => Language::LANGCODE_NOT_SPECIFIED,
249       'path' => array('alias' => '/node_test_alias'),
250     ));
251     $this->drupalGet('node/' . $node->id() . '/edit');
252     $this->assertText(t('No URL redirects available.'));
253     $this->drupalPostForm('node/' . $node->id() . '/edit', array('path[0][alias]' => '/node_test_alias_updated'), t('Save'));
254
255     $redirect = $this->repository->findMatchingRedirect('node_test_alias', array(), Language::LANGCODE_NOT_SPECIFIED);
256     $this->assertEqual($redirect->getRedirectUrl()->toString(), Url::fromUri('base:node_test_alias_updated')->toString());
257     // Test if the automatically created redirect works.
258     $this->assertRedirect('node_test_alias', 'node_test_alias_updated');
259
260     // Test that changing the path back deletes the first redirect, creates
261     // a new one and does not result in a loop.
262     $this->drupalPostForm('node/' . $node->id() . '/edit', array('path[0][alias]' => '/node_test_alias'), t('Save'));
263     $redirect = $this->repository->findMatchingRedirect('node_test_alias', array(), Language::LANGCODE_NOT_SPECIFIED);
264     $this->assertTrue(empty($redirect));
265
266     \Drupal::service('path.alias_manager')->cacheClear();
267     $redirect = $this->repository->findMatchingRedirect('node_test_alias_updated', array(), Language::LANGCODE_NOT_SPECIFIED);
268
269     $this->drupalGet('node/' . $node->id() . '/edit');
270     $this->assertText($redirect->getSourcePathWithQuery());
271     $this->assertLinkByHref(Url::fromRoute('entity.redirect.edit_form', ['redirect' => $redirect->id()])->toString());
272     $this->assertLinkByHref(Url::fromRoute('entity.redirect.delete_form', ['redirect' => $redirect->id()])->toString());
273
274     $this->assertEqual($redirect->getRedirectUrl()->toString(), Url::fromUri('base:node_test_alias')->toString());
275     // Test if the automatically created redirect works.
276     $this->assertRedirect('node_test_alias_updated', 'node_test_alias');
277
278     // Test that the redirect will be deleted upon node deletion.
279     $this->drupalPostForm('node/' . $node->id() . '/delete', array(), t('Delete'));
280     $redirect = $this->repository->findMatchingRedirect('node_test_alias_updated', array(), Language::LANGCODE_NOT_SPECIFIED);
281     $this->assertTrue(empty($redirect));
282
283     // Create a term and update its path alias and check if we have a redirect
284     // from the previous path alias to the new one.
285     $term = $this->createTerm($this->createVocabulary());
286     $this->drupalPostForm('taxonomy/term/' . $term->id() . '/edit', array('path[0][alias]' => '/term_test_alias_updated'), t('Save'));
287     $redirect = $this->repository->findMatchingRedirect('term_test_alias');
288     $this->assertEqual($redirect->getRedirectUrl()->toString(), Url::fromUri('base:term_test_alias_updated')->toString());
289     // Test if the automatically created redirect works.
290     $this->assertRedirect('term_test_alias', 'term_test_alias_updated');
291
292     // Test the path alias update via the admin path form.
293     $this->drupalPostForm('admin/config/search/path/add', array(
294       'source' => '/node',
295       'alias' => '/aaa_path_alias',
296     ), t('Save'));
297     // Note that here we rely on fact that we land on the path alias list page
298     // and the default sort is by the alias, which implies that the first edit
299     // link leads to the edit page of the aaa_path_alias.
300     $this->clickLink(t('Edit'));
301     $this->drupalPostForm(NULL, array('alias' => '/aaa_path_alias_updated'), t('Save'));
302     $redirect = $this->repository->findMatchingRedirect('aaa_path_alias', array(), 'en');
303     $this->assertEqual($redirect->getRedirectUrl()->toString(), Url::fromUri('base:aaa_path_alias_updated')->toString());
304     // Test if the automatically created redirect works.
305     $this->assertRedirect('aaa_path_alias', 'aaa_path_alias_updated');
306
307     // Test the automatically created redirect shows up in the form correctly.
308     $this->drupalGet('admin/config/search/redirect/edit/' . $redirect->id());
309     $this->assertFieldByName('redirect_source[0][path]', 'aaa_path_alias');
310     $this->assertFieldByName('redirect_redirect[0][uri]', '/node');
311   }
312
313   /**
314    * Test the redirect loop protection and logging.
315    */
316   function testRedirectLoop() {
317     // Redirect loop redirection only works when page caching is disabled.
318     \Drupal::service('module_installer')->uninstall(['page_cache']);
319
320     /** @var \Drupal\redirect\Entity\Redirect $redirect1 */
321     $redirect1 = $this->storage->create();
322     $redirect1->setSource('node');
323     $redirect1->setRedirect('admin');
324     $redirect1->setStatusCode(301);
325     $redirect1->save();
326
327     /** @var \Drupal\redirect\Entity\Redirect $redirect2 */
328     $redirect2 = $this->storage->create();
329     $redirect2->setSource('admin');
330     $redirect2->setRedirect('node');
331     $redirect2->setStatusCode(301);
332     $redirect2->save();
333
334     $this->maximumRedirects = 10;
335     $this->drupalGet('node');
336     $this->assertText('Service unavailable');
337     $this->assertResponse(503);
338
339     $log = db_select('watchdog')->fields('watchdog')->condition('type', 'redirect')->execute()->fetchAll();
340     if (count($log) == 0) {
341       $this->fail('Redirect loop has not been logged');
342     }
343     else {
344       $log = reset($log);
345       $this->assertEqual($log->severity, RfcLogLevel::WARNING);
346       $this->assertEqual(SafeMarkup::format($log->message, unserialize($log->variables)),
347         SafeMarkup::format('Redirect loop identified at %path for redirect %id', array('%path' => '/node', '%id' => $redirect1->id())));
348     }
349   }
350
351   /**
352    * Returns a new vocabulary with random properties.
353    */
354   function createVocabulary() {
355     // Create a vocabulary.
356     $vocabulary = entity_create('taxonomy_vocabulary', array(
357       'name' => $this->randomMachineName(),
358       'description' => $this->randomMachineName(),
359       'vid' => mb_strtolower($this->randomMachineName()),
360       'langcode' => Language::LANGCODE_NOT_SPECIFIED,
361       'weight' => mt_rand(0, 10),
362     ));
363     $vocabulary->save();
364     return $vocabulary;
365   }
366
367   /**
368    * Returns a new term with random properties in vocabulary $vid.
369    */
370   function createTerm($vocabulary) {
371     $filter_formats = filter_formats();
372     $format = array_pop($filter_formats);
373     $term = entity_create('taxonomy_term', array(
374       'name' => $this->randomMachineName(),
375       'description' => array(
376         'value' => $this->randomMachineName(),
377         // Use the first available text format.
378         'format' => $format->id(),
379       ),
380       'vid' => $vocabulary->id(),
381       'langcode' => Language::LANGCODE_NOT_SPECIFIED,
382       'path' => array('alias' => '/term_test_alias'),
383     ));
384     $term->save();
385     return $term;
386   }
387
388   /**
389    * Test cache tags.
390    *
391    * @todo Not sure this belongs in a UI test, but a full web test is needed.
392    */
393   public function testCacheTags() {
394     /** @var \Drupal\redirect\Entity\Redirect $redirect1 */
395     $redirect1 = $this->storage->create();
396     $redirect1->setSource('test-redirect');
397     $redirect1->setRedirect('node');
398     $redirect1->setStatusCode(301);
399     $redirect1->save();
400
401     $this->assertRedirect('test-redirect', 'node');
402     $headers = $this->drupalGetHeaders(TRUE);
403     // Note, self::assertCacheTag() cannot be used here since it only looks at
404     // the final set of headers.
405     $expected = 'http_response ' . implode(' ', $redirect1->getCacheTags());
406     $this->assertEqual($expected, $headers[0]['x-drupal-cache-tags'], 'Redirect cache tags properly set.');
407
408     // First request should be a cache MISS.
409     $this->assertEqual($headers[0]['x-drupal-cache'], 'MISS', 'First request to the redirect was not cached.');
410
411     // Second request should be cached.
412     $this->assertRedirect('test-redirect', 'node');
413     $headers = $this->drupalGetHeaders(TRUE);
414     $this->assertEqual($headers[0]['x-drupal-cache'], 'HIT', 'The second request to the redirect was cached.');
415
416     // Ensure that the redirect has been cleared from cache when deleted.
417     $redirect1->delete();
418     $this->drupalGet('test-redirect');
419     $this->assertResponse(404, 'Deleted redirect properly clears the internal page cache.');
420   }
421
422   /**
423    * Test external destinations.
424    */
425   public function testExternal() {
426     $redirect = $this->storage->create();
427     $redirect->setSource('a-path');
428     // @todo Redirect::setRedirect() assumes that all redirects are internal.
429     $redirect->redirect_redirect->set(0, ['uri' => 'https://www.example.org']);
430     $redirect->setStatusCode(301);
431     $redirect->save();
432     $this->assertRedirect('a-path', 'https://www.example.org');
433     $this->drupalLogin($this->adminUser);
434   }
435
436 }