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