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