UrlTest.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875
  1. <?php
  2. /**
  3. * @file
  4. * Contains \Drupal\Tests\Core\UrlTest.
  5. */
  6. namespace Drupal\Tests\Core;
  7. use Drupal\Component\Utility\UrlHelper;
  8. use Drupal\Core\Access\AccessManagerInterface;
  9. use Drupal\Core\DependencyInjection\ContainerBuilder;
  10. use Drupal\Core\GeneratedUrl;
  11. use Drupal\Core\Routing\RouteMatch;
  12. use Drupal\Core\Url;
  13. use Drupal\Tests\UnitTestCase;
  14. use Symfony\Cmf\Component\Routing\RouteObjectInterface;
  15. use Symfony\Component\HttpFoundation\ParameterBag;
  16. use Symfony\Component\HttpFoundation\Request;
  17. use Symfony\Component\Routing\Exception\InvalidParameterException;
  18. use Symfony\Component\Routing\Exception\ResourceNotFoundException;
  19. use Symfony\Component\Routing\Route;
  20. /**
  21. * @coversDefaultClass \Drupal\Core\Url
  22. * @group UrlTest
  23. */
  24. class UrlTest extends UnitTestCase {
  25. /**
  26. * @var \Symfony\Component\DependencyInjection\ContainerInterface
  27. */
  28. protected $container;
  29. /**
  30. * The URL generator
  31. *
  32. * @var \Drupal\Core\Routing\UrlGeneratorInterface|\PHPUnit\Framework\MockObject\MockObject
  33. */
  34. protected $urlGenerator;
  35. /**
  36. * The path alias manager.
  37. *
  38. * @var \Drupal\path_alias\AliasManagerInterface|\PHPUnit\Framework\MockObject\MockObject
  39. */
  40. protected $pathAliasManager;
  41. /**
  42. * The router.
  43. *
  44. * @var \Drupal\Tests\Core\Routing\TestRouterInterface|\PHPUnit\Framework\MockObject\MockObject
  45. */
  46. protected $router;
  47. /**
  48. * An array of values to use for the test.
  49. *
  50. * @var array
  51. */
  52. protected $map;
  53. /**
  54. * The mocked path validator.
  55. *
  56. * @var \Drupal\Core\Path\PathValidatorInterface|\PHPUnit\Framework\MockObject\MockObject
  57. */
  58. protected $pathValidator;
  59. /**
  60. * {@inheritdoc}
  61. */
  62. protected function setUp() {
  63. parent::setUp();
  64. $map = [];
  65. $map[] = ['view.frontpage.page_1', [], [], FALSE, '/node'];
  66. $map[] = ['node_view', ['node' => '1'], [], FALSE, '/node/1'];
  67. $map[] = ['node_edit', ['node' => '2'], [], FALSE, '/node/2/edit'];
  68. $this->map = $map;
  69. $alias_map = [
  70. // Set up one proper alias that can be resolved to a system path.
  71. ['node-alias-test', NULL, FALSE, 'node'],
  72. // Passing in anything else should return the same string.
  73. ['node', NULL, FALSE, 'node'],
  74. ['node/1', NULL, FALSE, 'node/1'],
  75. ['node/2/edit', NULL, FALSE, 'node/2/edit'],
  76. ['non-existent', NULL, FALSE, 'non-existent'],
  77. ];
  78. // $this->map has $collect_bubbleable_metadata = FALSE; also generate the
  79. // $collect_bubbleable_metadata = TRUE case for ::generateFromRoute().
  80. $generate_from_route_map = [];
  81. foreach ($this->map as $values) {
  82. $generate_from_route_map[] = $values;
  83. $generate_from_route_map[] = [$values[0], $values[1], $values[2], TRUE, (new GeneratedUrl())->setGeneratedUrl($values[4])];
  84. }
  85. $this->urlGenerator = $this->createMock('Drupal\Core\Routing\UrlGeneratorInterface');
  86. $this->urlGenerator->expects($this->any())
  87. ->method('generateFromRoute')
  88. ->will($this->returnValueMap($generate_from_route_map));
  89. $this->pathAliasManager = $this->createMock('Drupal\path_alias\AliasManagerInterface');
  90. $this->pathAliasManager->expects($this->any())
  91. ->method('getPathByAlias')
  92. ->will($this->returnValueMap($alias_map));
  93. $this->router = $this->createMock('Drupal\Tests\Core\Routing\TestRouterInterface');
  94. $this->pathValidator = $this->createMock('Drupal\Core\Path\PathValidatorInterface');
  95. $this->container = new ContainerBuilder();
  96. $this->container->set('router.no_access_checks', $this->router);
  97. $this->container->set('url_generator', $this->urlGenerator);
  98. $this->container->set('path_alias.manager', $this->pathAliasManager);
  99. $this->container->set('path.validator', $this->pathValidator);
  100. \Drupal::setContainer($this->container);
  101. }
  102. /**
  103. * Tests creating a Url from a request.
  104. */
  105. public function testUrlFromRequest() {
  106. $this->router->expects($this->at(0))
  107. ->method('matchRequest')
  108. ->with($this->getRequestConstraint('/node'))
  109. ->willReturn([
  110. RouteObjectInterface::ROUTE_NAME => 'view.frontpage.page_1',
  111. '_raw_variables' => new ParameterBag(),
  112. ]);
  113. $this->router->expects($this->at(1))
  114. ->method('matchRequest')
  115. ->with($this->getRequestConstraint('/node/1'))
  116. ->willReturn([
  117. RouteObjectInterface::ROUTE_NAME => 'node_view',
  118. '_raw_variables' => new ParameterBag(['node' => '1']),
  119. ]);
  120. $this->router->expects($this->at(2))
  121. ->method('matchRequest')
  122. ->with($this->getRequestConstraint('/node/2/edit'))
  123. ->willReturn([
  124. RouteObjectInterface::ROUTE_NAME => 'node_edit',
  125. '_raw_variables' => new ParameterBag(['node' => '2']),
  126. ]);
  127. $urls = [];
  128. foreach ($this->map as $index => $values) {
  129. $path = array_pop($values);
  130. $url = Url::createFromRequest(Request::create("$path"));
  131. $expected = Url::fromRoute($values[0], $values[1], $values[2]);
  132. $this->assertEquals($expected, $url);
  133. $urls[$index] = $url;
  134. }
  135. return $urls;
  136. }
  137. /**
  138. * This constraint checks whether a Request object has the right path.
  139. *
  140. * @param string $path
  141. * The path.
  142. *
  143. * @return \PHPUnit\Framework\Constraint\Callback
  144. * The constraint checks whether a Request object has the right path.
  145. */
  146. protected function getRequestConstraint($path) {
  147. return $this->callback(function (Request $request) use ($path) {
  148. return $request->getPathInfo() == $path;
  149. });
  150. }
  151. /**
  152. * Tests the fromRoute() method with the special <front> path.
  153. *
  154. * @covers ::fromRoute
  155. */
  156. public function testFromRouteFront() {
  157. $url = Url::fromRoute('<front>');
  158. $this->assertSame('<front>', $url->getRouteName());
  159. }
  160. /**
  161. * Tests the fromUserInput method with valid paths.
  162. *
  163. * @covers ::fromUserInput
  164. * @dataProvider providerFromValidInternalUri
  165. */
  166. public function testFromUserInput($path) {
  167. $url = Url::fromUserInput($path);
  168. $uri = $url->getUri();
  169. $this->assertInstanceOf('Drupal\Core\Url', $url);
  170. $this->assertFalse($url->isRouted());
  171. $this->assertStringStartsWith('base:', $uri);
  172. $parts = UrlHelper::parse($path);
  173. $options = $url->getOptions();
  174. if (!empty($parts['fragment'])) {
  175. $this->assertSame($parts['fragment'], $options['fragment']);
  176. }
  177. else {
  178. $this->assertArrayNotHasKey('fragment', $options);
  179. }
  180. if (!empty($parts['query'])) {
  181. $this->assertEquals($parts['query'], $options['query']);
  182. }
  183. else {
  184. $this->assertArrayNotHasKey('query', $options);
  185. }
  186. }
  187. /**
  188. * Tests the fromUserInput method with invalid paths.
  189. *
  190. * @covers ::fromUserInput
  191. * @dataProvider providerFromInvalidInternalUri
  192. */
  193. public function testFromInvalidUserInput($path) {
  194. $this->expectException(\InvalidArgumentException::class);
  195. $url = Url::fromUserInput($path);
  196. }
  197. /**
  198. * Tests fromUri() method with a user-entered path not matching any route.
  199. *
  200. * @covers ::fromUri
  201. */
  202. public function testFromRoutedPathWithInvalidRoute() {
  203. $this->pathValidator->expects($this->once())
  204. ->method('getUrlIfValidWithoutAccessCheck')
  205. ->with('invalid-path')
  206. ->willReturn(FALSE);
  207. $url = Url::fromUri('internal:/invalid-path');
  208. $this->assertSame(FALSE, $url->isRouted());
  209. $this->assertSame('base:invalid-path', $url->getUri());
  210. }
  211. /**
  212. * Tests fromUri() method with user-entered path matching a valid route.
  213. *
  214. * @covers ::fromUri
  215. */
  216. public function testFromRoutedPathWithValidRoute() {
  217. $url = Url::fromRoute('test_route');
  218. $this->pathValidator->expects($this->once())
  219. ->method('getUrlIfValidWithoutAccessCheck')
  220. ->with('valid-path')
  221. ->willReturn($url);
  222. $result_url = Url::fromUri('internal:/valid-path');
  223. $this->assertSame($url, $result_url);
  224. }
  225. /**
  226. * Tests the createFromRequest method.
  227. *
  228. * @covers ::createFromRequest
  229. */
  230. public function testCreateFromRequest() {
  231. $attributes = [
  232. '_raw_variables' => new ParameterBag([
  233. 'color' => 'chartreuse',
  234. ]),
  235. RouteObjectInterface::ROUTE_NAME => 'the_route_name',
  236. ];
  237. $request = new Request([], [], $attributes);
  238. $this->router->expects($this->once())
  239. ->method('matchRequest')
  240. ->with($request)
  241. ->will($this->returnValue($attributes));
  242. $url = Url::createFromRequest($request);
  243. $expected = new Url('the_route_name', ['color' => 'chartreuse']);
  244. $this->assertEquals($expected, $url);
  245. }
  246. /**
  247. * Tests that an invalid request will thrown an exception.
  248. *
  249. * @covers ::createFromRequest
  250. */
  251. public function testUrlFromRequestInvalid() {
  252. $request = Request::create('/test-path');
  253. $this->router->expects($this->once())
  254. ->method('matchRequest')
  255. ->with($request)
  256. ->will($this->throwException(new ResourceNotFoundException()));
  257. $this->expectException(ResourceNotFoundException::class);
  258. Url::createFromRequest($request);
  259. }
  260. /**
  261. * Tests the isExternal() method.
  262. *
  263. * @depends testUrlFromRequest
  264. *
  265. * @covers ::isExternal
  266. */
  267. public function testIsExternal($urls) {
  268. foreach ($urls as $url) {
  269. $this->assertFalse($url->isExternal());
  270. }
  271. }
  272. /**
  273. * Tests the getUri() method for internal URLs.
  274. *
  275. * @param \Drupal\Core\Url[] $urls
  276. * Array of URL objects.
  277. *
  278. * @depends testUrlFromRequest
  279. *
  280. * @covers ::getUri
  281. */
  282. public function testGetUriForInternalUrl($urls) {
  283. $this->expectException(\UnexpectedValueException::class);
  284. foreach ($urls as $url) {
  285. $url->getUri();
  286. }
  287. }
  288. /**
  289. * Tests the getUri() method for external URLs.
  290. *
  291. * @covers ::getUri
  292. */
  293. public function testGetUriForExternalUrl() {
  294. $url = Url::fromUri('http://example.com/test');
  295. $this->assertEquals('http://example.com/test', $url->getUri());
  296. }
  297. /**
  298. * Tests the getUri() and isExternal() methods for protocol-relative URLs.
  299. *
  300. * @covers ::getUri
  301. * @covers ::isExternal
  302. */
  303. public function testGetUriForProtocolRelativeUrl() {
  304. $url = Url::fromUri('//example.com/test');
  305. $this->assertEquals('//example.com/test', $url->getUri());
  306. $this->assertTrue($url->isExternal());
  307. }
  308. /**
  309. * Tests the getInternalPath method().
  310. *
  311. * @param \Drupal\Core\Url[] $urls
  312. * Array of URL objects.
  313. *
  314. * @covers ::getInternalPath
  315. *
  316. * @depends testUrlFromRequest
  317. */
  318. public function testGetInternalPath($urls) {
  319. $map = [];
  320. $map[] = ['view.frontpage.page_1', [], '/node'];
  321. $map[] = ['node_view', ['node' => '1'], '/node/1'];
  322. $map[] = ['node_edit', ['node' => '2'], '/node/2/edit'];
  323. foreach ($urls as $index => $url) {
  324. // Clone the url so that there is no leak of internal state into the
  325. // other ones.
  326. $url = clone $url;
  327. $url_generator = $this->createMock('Drupal\Core\Routing\UrlGeneratorInterface');
  328. $url_generator->expects($this->once())
  329. ->method('getPathFromRoute')
  330. ->will($this->returnValueMap($map, $index));
  331. $url->setUrlGenerator($url_generator);
  332. $url->getInternalPath();
  333. $url->getInternalPath();
  334. }
  335. }
  336. /**
  337. * Tests the toString() method.
  338. *
  339. * @param \Drupal\Core\Url[] $urls
  340. * An array of Url objects.
  341. *
  342. * @depends testUrlFromRequest
  343. *
  344. * @covers ::toString
  345. */
  346. public function testToString($urls) {
  347. foreach ($urls as $index => $url) {
  348. $path = array_pop($this->map[$index]);
  349. $this->assertSame($path, $url->toString());
  350. $generated_url = $url->toString(TRUE);
  351. $this->assertSame($path, $generated_url->getGeneratedUrl());
  352. $this->assertInstanceOf('\Drupal\Core\Render\BubbleableMetadata', $generated_url);
  353. }
  354. }
  355. /**
  356. * Tests the getRouteName() method.
  357. *
  358. * @param \Drupal\Core\Url[] $urls
  359. * An array of Url objects.
  360. *
  361. * @depends testUrlFromRequest
  362. *
  363. * @covers ::getRouteName
  364. */
  365. public function testGetRouteName($urls) {
  366. foreach ($urls as $index => $url) {
  367. $this->assertSame($this->map[$index][0], $url->getRouteName());
  368. }
  369. }
  370. /**
  371. * Tests the getRouteName() with an external URL.
  372. *
  373. * @covers ::getRouteName
  374. */
  375. public function testGetRouteNameWithExternalUrl() {
  376. $url = Url::fromUri('http://example.com');
  377. $this->expectException(\UnexpectedValueException::class);
  378. $url->getRouteName();
  379. }
  380. /**
  381. * Tests the getRouteParameters() method.
  382. *
  383. * @param \Drupal\Core\Url[] $urls
  384. * An array of Url objects.
  385. *
  386. * @depends testUrlFromRequest
  387. *
  388. * @covers ::getRouteParameters
  389. */
  390. public function testGetRouteParameters($urls) {
  391. foreach ($urls as $index => $url) {
  392. $this->assertSame($this->map[$index][1], $url->getRouteParameters());
  393. }
  394. }
  395. /**
  396. * Tests the getRouteParameters() with an external URL.
  397. *
  398. * @covers ::getRouteParameters
  399. */
  400. public function testGetRouteParametersWithExternalUrl() {
  401. $url = Url::fromUri('http://example.com');
  402. $this->expectException(\UnexpectedValueException::class);
  403. $url->getRouteParameters();
  404. }
  405. /**
  406. * Tests the getOptions() method.
  407. *
  408. * @param \Drupal\Core\Url[] $urls
  409. * An array of Url objects.
  410. *
  411. * @depends testUrlFromRequest
  412. *
  413. * @covers ::getOptions
  414. */
  415. public function testGetOptions($urls) {
  416. foreach ($urls as $index => $url) {
  417. $this->assertSame($this->map[$index][2], $url->getOptions());
  418. }
  419. }
  420. /**
  421. * Tests the setOptions() method.
  422. *
  423. * @covers ::setOptions
  424. */
  425. public function testSetOptions() {
  426. $url = Url::fromRoute('test_route', []);
  427. $this->assertEquals([], $url->getOptions());
  428. $url->setOptions(['foo' => 'bar']);
  429. $this->assertEquals(['foo' => 'bar'], $url->getOptions());
  430. $url->setOptions([]);
  431. $this->assertEquals([], $url->getOptions());
  432. }
  433. /**
  434. * Tests the mergeOptions() method.
  435. *
  436. * @covers ::mergeOptions
  437. */
  438. public function testMergeOptions() {
  439. $url = Url::fromRoute('test_route', [], ['foo' => 'bar', 'bar' => ['key' => 'value']]);
  440. $url->mergeOptions(['bar' => ['key' => 'value1', 'key2' => 'value2']]);
  441. $this->assertEquals(['foo' => 'bar', 'bar' => ['key' => 'value1', 'key2' => 'value2']], $url->getOptions());
  442. }
  443. /**
  444. * Tests the access() method for routed URLs.
  445. *
  446. * @param bool $access
  447. *
  448. * @covers ::access
  449. * @covers ::accessManager
  450. * @dataProvider accessProvider
  451. */
  452. public function testAccessRouted($access) {
  453. $account = $this->createMock('Drupal\Core\Session\AccountInterface');
  454. $url = new TestUrl('entity.node.canonical', ['node' => 3]);
  455. $url->setAccessManager($this->getMockAccessManager($access, $account));
  456. $this->assertEquals($access, $url->access($account));
  457. }
  458. /**
  459. * Tests the access() method for unrouted URLs (they always have access).
  460. *
  461. * @covers ::access
  462. */
  463. public function testAccessUnrouted() {
  464. $account = $this->createMock('Drupal\Core\Session\AccountInterface');
  465. $url = TestUrl::fromUri('base:kittens');
  466. $access_manager = $this->createMock('Drupal\Core\Access\AccessManagerInterface');
  467. $access_manager->expects($this->never())
  468. ->method('checkNamedRoute');
  469. $url->setAccessManager($access_manager);
  470. $this->assertTrue($url->access($account));
  471. }
  472. /**
  473. * Tests the renderAccess() method.
  474. *
  475. * @param bool $access
  476. *
  477. * @covers ::renderAccess
  478. * @dataProvider accessProvider
  479. */
  480. public function testRenderAccess($access) {
  481. $element = [
  482. '#url' => Url::fromRoute('entity.node.canonical', ['node' => 3]),
  483. ];
  484. $this->container->set('current_user', $this->createMock('Drupal\Core\Session\AccountInterface'));
  485. $this->container->set('access_manager', $this->getMockAccessManager($access));
  486. $this->assertEquals($access, TestUrl::renderAccess($element));
  487. }
  488. /**
  489. * Tests the fromRouteMatch() method.
  490. */
  491. public function testFromRouteMatch() {
  492. $route = new Route('/test-route/{foo}');
  493. $route_match = new RouteMatch('test_route', $route, ['foo' => (object) [1]], ['foo' => 1]);
  494. $url = Url::fromRouteMatch($route_match);
  495. $this->assertSame('test_route', $url->getRouteName());
  496. $this->assertEquals(['foo' => '1'], $url->getRouteParameters());
  497. }
  498. /**
  499. * Data provider for testing entity URIs
  500. */
  501. public function providerTestEntityUris() {
  502. return [
  503. [
  504. 'entity:test_entity/1',
  505. [],
  506. 'entity.test_entity.canonical',
  507. ['test_entity' => '1'],
  508. NULL,
  509. NULL,
  510. ],
  511. [
  512. // Ensure a fragment of #0 is handled correctly.
  513. 'entity:test_entity/1#0',
  514. [],
  515. 'entity.test_entity.canonical',
  516. ['test_entity' => '1'],
  517. NULL,
  518. '0',
  519. ],
  520. // Ensure an empty fragment of # is in options discarded as expected.
  521. [
  522. 'entity:test_entity/1',
  523. ['fragment' => ''],
  524. 'entity.test_entity.canonical',
  525. ['test_entity' => '1'],
  526. NULL,
  527. NULL,
  528. ],
  529. // Ensure an empty fragment of # in the URI is discarded as expected.
  530. [
  531. 'entity:test_entity/1#',
  532. [],
  533. 'entity.test_entity.canonical',
  534. ['test_entity' => '1'],
  535. NULL,
  536. NULL,
  537. ],
  538. [
  539. 'entity:test_entity/2?page=1&foo=bar#bottom',
  540. [], 'entity.test_entity.canonical',
  541. ['test_entity' => '2'],
  542. ['page' => '1', 'foo' => 'bar'],
  543. 'bottom',
  544. ],
  545. [
  546. 'entity:test_entity/2?page=1&foo=bar#bottom',
  547. ['fragment' => 'top', 'query' => ['foo' => 'yes', 'focus' => 'no']],
  548. 'entity.test_entity.canonical',
  549. ['test_entity' => '2'],
  550. ['page' => '1', 'foo' => 'yes', 'focus' => 'no'],
  551. 'top',
  552. ],
  553. ];
  554. }
  555. /**
  556. * Tests the fromUri() method with an entity: URI.
  557. *
  558. * @covers ::fromUri
  559. *
  560. * @dataProvider providerTestEntityUris
  561. */
  562. public function testEntityUris($uri, $options, $route_name, $route_parameters, $query, $fragment) {
  563. $url = Url::fromUri($uri, $options);
  564. $this->assertSame($route_name, $url->getRouteName());
  565. $this->assertEquals($route_parameters, $url->getRouteParameters());
  566. $this->assertEquals($url->getOption('query'), $query);
  567. $this->assertSame($url->getOption('fragment'), $fragment);
  568. }
  569. /**
  570. * Tests the fromUri() method with an invalid entity: URI.
  571. *
  572. * @covers ::fromUri
  573. */
  574. public function testInvalidEntityUriParameter() {
  575. // Make the mocked URL generator behave like the actual one.
  576. $this->urlGenerator->expects($this->once())
  577. ->method('generateFromRoute')
  578. ->with('entity.test_entity.canonical', ['test_entity' => '1/blah'])
  579. ->willThrowException(new InvalidParameterException('Parameter "test_entity" for route "/test_entity/{test_entity}" must match "[^/]++" ("1/blah" given) to generate a corresponding URL..'));
  580. $this->expectException(InvalidParameterException::class);
  581. Url::fromUri('entity:test_entity/1/blah')->toString();
  582. }
  583. /**
  584. * Tests the toUriString() method with entity: URIs.
  585. *
  586. * @covers ::toUriString
  587. *
  588. * @dataProvider providerTestToUriStringForEntity
  589. */
  590. public function testToUriStringForEntity($uri, $options, $uri_string) {
  591. $url = Url::fromUri($uri, $options);
  592. $this->assertSame($url->toUriString(), $uri_string);
  593. }
  594. /**
  595. * Data provider for testing string entity URIs
  596. */
  597. public function providerTestToUriStringForEntity() {
  598. return [
  599. ['entity:test_entity/1', [], 'route:entity.test_entity.canonical;test_entity=1'],
  600. ['entity:test_entity/1', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
  601. ['entity:test_entity/1?page=2#top', [], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
  602. ];
  603. }
  604. /**
  605. * Tests the toUriString() method with internal: URIs.
  606. *
  607. * @covers ::toUriString
  608. *
  609. * @dataProvider providerTestToUriStringForInternal
  610. */
  611. public function testToUriStringForInternal($uri, $options, $uri_string) {
  612. $url = Url::fromRoute('entity.test_entity.canonical', ['test_entity' => '1']);
  613. $this->pathValidator->expects($this->any())
  614. ->method('getUrlIfValidWithoutAccessCheck')
  615. ->willReturnMap([
  616. ['test-entity/1', $url],
  617. ['<front>', Url::fromRoute('<front>')],
  618. ['<none>', Url::fromRoute('<none>')],
  619. ]);
  620. $url = Url::fromUri($uri, $options);
  621. $this->assertSame($url->toUriString(), $uri_string);
  622. }
  623. /**
  624. * Data provider for testing internal URIs.
  625. */
  626. public function providerTestToUriStringForInternal() {
  627. return [
  628. // The four permutations of a regular path.
  629. ['internal:/test-entity/1', [], 'route:entity.test_entity.canonical;test_entity=1'],
  630. ['internal:/test-entity/1', ['fragment' => 'top'], 'route:entity.test_entity.canonical;test_entity=1#top'],
  631. ['internal:/test-entity/1', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
  632. ['internal:/test-entity/1?page=2#top', [], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
  633. // The four permutations of the special '<front>' path.
  634. ['internal:/', [], 'route:<front>'],
  635. ['internal:/', ['fragment' => 'top'], 'route:<front>#top'],
  636. ['internal:/', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:<front>?page=2#top'],
  637. ['internal:/?page=2#top', [], 'route:<front>?page=2#top'],
  638. // The four permutations of the special '<none>' path.
  639. ['internal:', [], 'route:<none>'],
  640. ['internal:', ['fragment' => 'top'], 'route:<none>#top'],
  641. ['internal:', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:<none>?page=2#top'],
  642. ['internal:?page=2#top', [], 'route:<none>?page=2#top'],
  643. ];
  644. }
  645. /**
  646. * Tests the fromUri() method with a valid internal: URI.
  647. *
  648. * @covers ::fromUri
  649. * @dataProvider providerFromValidInternalUri
  650. */
  651. public function testFromValidInternalUri($path) {
  652. $url = Url::fromUri('internal:' . $path);
  653. $this->assertInstanceOf('Drupal\Core\Url', $url);
  654. }
  655. /**
  656. * Data provider for testFromValidInternalUri().
  657. */
  658. public function providerFromValidInternalUri() {
  659. return [
  660. // Normal paths with a leading slash.
  661. ['/kittens'],
  662. ['/kittens/bengal'],
  663. // Fragments with and without leading slashes.
  664. ['/#about-our-kittens'],
  665. ['/kittens#feeding'],
  666. ['#feeding'],
  667. // Query strings with and without leading slashes.
  668. ['/kittens?page=1000'],
  669. ['/?page=1000'],
  670. ['?page=1000'],
  671. ['?breed=bengal&page=1000'],
  672. ['?referrer=https://kittenfacts'],
  673. // Paths with various token formats but no leading slash.
  674. ['/[duckies]'],
  675. ['/%bunnies'],
  676. ['/{{ puppies }}'],
  677. // Disallowed characters in the authority (host name) that are valid
  678. // elsewhere in the path.
  679. ['/(:;2&+h^'],
  680. ['/AKI@&hO@'],
  681. ];
  682. }
  683. /**
  684. * Tests the fromUri() method with an invalid internal: URI.
  685. *
  686. * @covers ::fromUri
  687. * @dataProvider providerFromInvalidInternalUri
  688. */
  689. public function testFromInvalidInternalUri($path) {
  690. $this->expectException(\InvalidArgumentException::class);
  691. Url::fromUri('internal:' . $path);
  692. }
  693. /**
  694. * Data provider for testFromInvalidInternalUri().
  695. */
  696. public function providerFromInvalidInternalUri() {
  697. return [
  698. // Normal paths without a leading slash.
  699. 'normal_path0' => ['kittens'],
  700. 'normal_path1' => ['kittens/bengal'],
  701. // Path without a leading slash containing a fragment.
  702. 'fragment' => ['kittens#feeding'],
  703. // Path without a leading slash containing a query string.
  704. 'without_leading_slash_query' => ['kittens?page=1000'],
  705. // Paths with various token formats but no leading slash.
  706. 'path_with_tokens0' => ['[duckies]'],
  707. 'path_with_tokens1' => ['%bunnies'],
  708. 'path_with_tokens2' => ['{{ puppies }}'],
  709. // Disallowed characters in the authority (host name) that are valid
  710. // elsewhere in the path.
  711. 'disallowed_hostname_chars0' => ['(:;2&+h^'],
  712. 'disallowed_hostname_chars1' => ['AKI@&hO@'],
  713. // Leading slash with a domain.
  714. 'leading_slash_with_domain' => ['/http://example.com'],
  715. ];
  716. }
  717. /**
  718. * Tests the fromUri() method with a base: URI starting with a number.
  719. *
  720. * @covers ::fromUri
  721. */
  722. public function testFromUriNumber() {
  723. $url = Url::fromUri('base:2015/10/06');
  724. $this->assertSame($url->toUriString(), 'base:/2015/10/06');
  725. }
  726. /**
  727. * Tests the toUriString() method with route: URIs.
  728. *
  729. * @covers ::toUriString
  730. *
  731. * @dataProvider providerTestToUriStringForRoute
  732. */
  733. public function testToUriStringForRoute($uri, $options, $uri_string) {
  734. $url = Url::fromUri($uri, $options);
  735. $this->assertSame($url->toUriString(), $uri_string);
  736. }
  737. /**
  738. * Data provider for testing route: URIs.
  739. */
  740. public function providerTestToUriStringForRoute() {
  741. return [
  742. ['route:entity.test_entity.canonical;test_entity=1', [], 'route:entity.test_entity.canonical;test_entity=1'],
  743. ['route:entity.test_entity.canonical;test_entity=1', ['fragment' => 'top', 'query' => ['page' => '2']], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
  744. ['route:entity.test_entity.canonical;test_entity=1?page=2#top', [], 'route:entity.test_entity.canonical;test_entity=1?page=2#top'],
  745. // Check that an empty fragment is discarded.
  746. ['route:entity.test_entity.canonical;test_entity=1?page=2#', [], 'route:entity.test_entity.canonical;test_entity=1?page=2'],
  747. // Check that an empty fragment is discarded.
  748. ['route:entity.test_entity.canonical;test_entity=1?page=2', ['fragment' => ''], 'route:entity.test_entity.canonical;test_entity=1?page=2'],
  749. // Check that a fragment of #0 is preserved.
  750. ['route:entity.test_entity.canonical;test_entity=1?page=2#0', [], 'route:entity.test_entity.canonical;test_entity=1?page=2#0'],
  751. ];
  752. }
  753. /**
  754. * @covers ::fromUri
  755. */
  756. public function testFromRouteUriWithMissingRouteName() {
  757. $this->expectException(\InvalidArgumentException::class);
  758. $this->expectExceptionMessage("The route URI 'route:' is invalid.");
  759. Url::fromUri('route:');
  760. }
  761. /**
  762. * Creates a mock access manager for the access tests.
  763. *
  764. * @param bool $access
  765. * @param \Drupal\Core\Session\AccountInterface|null $account
  766. *
  767. * @return \Drupal\Core\Access\AccessManagerInterface|\PHPUnit\Framework\MockObject\MockObject
  768. */
  769. protected function getMockAccessManager($access, $account = NULL) {
  770. $access_manager = $this->createMock('Drupal\Core\Access\AccessManagerInterface');
  771. $access_manager->expects($this->once())
  772. ->method('checkNamedRoute')
  773. ->with('entity.node.canonical', ['node' => 3], $account)
  774. ->willReturn($access);
  775. return $access_manager;
  776. }
  777. /**
  778. * Data provider for the access test methods.
  779. */
  780. public function accessProvider() {
  781. return [
  782. [TRUE],
  783. [FALSE],
  784. ];
  785. }
  786. }
  787. class TestUrl extends Url {
  788. /**
  789. * Sets the access manager.
  790. *
  791. * @param \Drupal\Core\Access\AccessManagerInterface $access_manager
  792. */
  793. public function setAccessManager(AccessManagerInterface $access_manager) {
  794. $this->accessManager = $access_manager;
  795. }
  796. }