Commit 821d3bb0 authored by Dries's avatar Dries

Issue #2110845 by dawehner: Allow overriding views to override paths with parameters.

parent 503284a9
......@@ -214,6 +214,8 @@ public function alterRoutes(RouteCollection $collection) {
// Ensure that we don't override a route which is already controlled by
// views.
if (!$route->hasDefault('view_id') && ('/' . $view_path == $route_path)) {
$parameters = $route->compile()->getPathVariables();
// @todo Figure out whether we need to merge some settings (like
// requirements).
......@@ -223,6 +225,24 @@ public function alterRoutes(RouteCollection $collection) {
$view_id = $this->view->storage->id();
$display_id = $this->display['id'];
$route = $this->getRoute($view_id, $display_id);
$path = $route->getPath();
// Load the argument IDs from the view executable.
$view_arguments = (array) $this->view->argument;
$argument_ids = array_keys($view_arguments);
// Replace the path with the original parameter names and add a mapping.
$argument_map = array();
// We assume that the numeric ids of the parameters match the one from
// the view argument handlers.
foreach ($parameters as $position => $parameter_name) {
$path = str_replace('arg_' . $argument_ids[$position], $parameter_name, $path);
$argument_map['arg_' . $argument_ids[$position]] = $parameter_name;
}
// Set the corrected path and the mapping to the route object.
$route->setDefault('_view_argument_map', $argument_map);
$route->setPath($path);
$collection->add($name, $route);
$view_route_names[$view_id . '.' . $display_id] = $name;
}
......
......@@ -7,6 +7,7 @@
namespace Drupal\views\Routing;
use Drupal\Component\Utility\String;
use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
use Drupal\Core\Entity\EntityStorageControllerInterface;
use Drupal\views\ViewExecutableFactory;
......@@ -65,15 +66,37 @@ public function handle(Request $request) {
$entity = $this->storageController->load($view_id);
if (empty($entity)) {
throw new NotFoundHttpException(format_string('Page controller for view %id requested, but view was not found.', array('%id' => $view_id)));
throw new NotFoundHttpException(String::format('Page controller for view %id requested, but view was not found.', array('%id' => $view_id)));
}
$view = $this->executableFactory->get($entity);
$view->setDisplay($display_id);
$view->initHandlers();
$args = array();
$map = $request->attributes->get('_view_argument_map', array());
foreach (array_keys((array) $view->argument) as $argument_id) {
$arg = $request->attributes->get('arg_' . $argument_id);
// Allow parameters be pulled from the request.
// The map stores the actual name of the parameter in the request. Views
// which override existing controller, use for example 'node' instead of
// arg_nid as name.
$attribute = 'arg_' . $argument_id;
if (isset($map[$attribute])) {
$attribute = $map[$attribute];
// First try to get from the original values then on the not converted
// ones.
if ($request->attributes->has('_raw_variables')) {
$arg = $request->attributes->get('_raw_variables')->get($attribute);
}
else {
$arg = $request->attributes->get($attribute);
}
}
else {
$arg = $request->attributes->get($attribute);
}
if (isset($arg)) {
$args[] = $arg;
}
......
<?php
/**
* @file
* Contains \Drupal\views\Tests\ViewPageControllerTest.
*/
namespace Drupal\views\Tests;
use Drupal\views\Routing\ViewPageController;
use Drupal\views\ViewExecutableFactory;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* Tests the page controller but not the actualy execution/rendering of a view.
*
* @see \Drupal\views\Routing\ViewPageController
*/
class ViewPageControllerTest extends ViewUnitTestBase {
/**
* Modules to enable.
*
* @var array
*/
public static $modules = array('user');
/**
* Views used by this test.
*
* @var array
*/
public static $testViews = array('test_page_view');
/**
* The page controller of views.
*
* @var \Drupal\views\Routing\ViewPageController
*/
public $pageController;
public static function getInfo() {
return array(
'name' => 'View page controller test',
'description' => 'Tests views page controller.',
'group' => 'Views'
);
}
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->installSchema('system', array('router', 'menu_router'));
$this->pageController = new ViewPageController($this->container->get('entity.manager')->getStorageController('view'), new ViewExecutableFactory());
}
/**
* Tests the page controller.
*/
public function testPageController() {
$this->assertTrue($this->pageController instanceof ViewPageController, 'Ensure the right class is stored in the container');
// Pass in a non existent view.
$random_view_id = $this->randomName();
$request = new Request();
$request->attributes->set('view_id', $random_view_id);
$request->attributes->set('display_id', 'default');
try {
$this->pageController->handle($request);
$this->fail('No exception thrown on non-existing view.');
}
catch (NotFoundHttpException $e) {
$this->pass('Exception thrown when view was not found');
}
$request->attributes->set('view_id', 'test_page_view');
$output = $this->pageController->handle($request);
$this->assertTrue(is_array($output));
$this->assertEqual($output['#view']->storage->id, 'test_page_view', 'The right view was executed.');
$request->attributes->set('display_id', 'page_1');
$output = $this->pageController->handle($request);
$this->assertTrue($output instanceof Response, 'Ensure the page display returns a response object.');
}
}
......@@ -114,8 +114,8 @@ public function testCollectRoutes() {
*/
public function testAlterRoute() {
$collection = new RouteCollection();
$collection->add('test_route', new Route('test_route', array('_controller' => 'Drupal\Tests\Core\Controller\TestController')));
$route_2 = new Route('test_route/example', array('_controller' => 'Drupal\Tests\Core\Controller\TestController'));
$collection->add('test_route', new Route('test_route', array('_controller' => 'Drupal\Tests\Core\Controller\TestController::content')));
$route_2 = new Route('test_route/example', array('_controller' => 'Drupal\Tests\Core\Controller\TestController::content'));
$collection->add('test_route_2', $route_2);
list($view) = $this->setupViewExecutableAccessPlugin();
......@@ -145,6 +145,42 @@ public function testAlterRoute() {
$this->assertSame($collection->get('test_route_2'), $route_2);
}
/**
* Tests alter routes with parameters in the overriding route.
*/
public function testAlterRoutesWithParameters() {
$collection = new RouteCollection();
$collection->add('test_route', new Route('test_route/{parameter}', array('_controller' => 'Drupal\Tests\Core\Controller\TestController::content')));
list($view) = $this->setupViewExecutableAccessPlugin();
// Manually setup an argument handler.
$argument = $this->getMockBuilder('Drupal\views\Plugin\views\argument\ArgumentPluginBase')
->disableOriginalConstructor()
->getMock();
$view->argument['test_id'] = $argument;
$display = array();
$display['display_plugin'] = 'page';
$display['id'] = 'page_1';
$display['display_options'] = array(
'path' => 'test_route/%',
);
$this->pathPlugin->initDisplay($view, $display);
$view_route_names = $this->pathPlugin->alterRoutes($collection);
$this->assertEquals(array('test_id.page_1' => 'test_route'), $view_route_names);
// Ensure that the test_route is overridden.
$route = $collection->get('test_route');
$this->assertInstanceOf('\Symfony\Component\Routing\Route', $route);
$this->assertEquals('test_id', $route->getDefault('view_id'));
$this->assertEquals('page_1', $route->getDefault('display_id'));
// Ensure that the path did not changed and placeholders are respected.
$this->assertEquals('/test_route/{parameter}', $route->getPath());
$this->assertEquals(array('arg_test_id' => 'parameter'), $route->getDefault('_view_argument_map'));
}
/**
* Returns some mocked view entity, view executable, and access plugin.
*/
......
<?php
/**
* @file
* Contains \Drupal\views\Tests\Routing\ViewPageControllerTest.
*/
namespace Drupal\views\Tests\Routing;
use Drupal\Tests\UnitTestCase;
use Drupal\views\Routing\ViewPageController;
use Symfony\Component\HttpFoundation\ParameterBag;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Attribute\AttributeBag;
/**
* Tests the page controller but not the actual execution/rendering of a view.
*
* @group Drupal
* @group Views
*
* @see \Drupal\views\Routing\ViewPageController
*/
class ViewPageControllerTest extends UnitTestCase {
/**
* The page controller of views.
*
* @var \Drupal\views\Routing\ViewPageController
*/
public $pageController;
/**
* The mocked view storage controller.
*
* @var \Drupal\views\ViewStorageController|\PHPUnit_Framework_MockObject_MockObject
*/
protected $storageController;
/**
* The mocked view executable factory.
*
* @var \Drupal\views\ViewExecutableFactory|\PHPUnit_Framework_MockObject_MockObject
*/
protected $executableFactory;
public static function getInfo() {
return array(
'name' => 'View page controller test',
'description' => 'Tests views page controller.',
'group' => 'Views'
);
}
protected function setUp() {
$this->storageController = $this->getMockBuilder('Drupal\views\ViewStorageController')
->disableOriginalConstructor()
->getMock();
$this->executableFactory = $this->getMockBuilder('Drupal\views\ViewExecutableFactory')
->disableOriginalConstructor()
->getMock();
$this->pageController = new ViewPageController($this->storageController, $this->executableFactory);
}
/**
* Tests the page controller.
*/
public function testPageController() {
$view = $this->getMock('Drupal\views\ViewStorageInterface');
$this->storageController->expects($this->once())
->method('load')
->with('test_page_view')
->will($this->returnValue($view));
$executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
->disableOriginalConstructor()
->getMock();
$executable->expects($this->once())
->method('setDisplay')
->with('default');
$executable->expects($this->once())
->method('initHandlers');
$executable->expects($this->once())
->method('executeDisplay')
->with('default', array())
->will($this->returnValue(array('#markup' => 'example output')));
$this->executableFactory->staticExpects($this->any())
->method('get')
->with($view)
->will($this->returnValue($executable));
$request = new Request();
$request->attributes->set('view_id', 'test_page_view');
$request->attributes->set('display_id', 'default');
$output = $this->pageController->handle($request);
$this->assertInternalType('array', $output);
$this->assertEquals(array('#markup' => 'example output'), $output);
}
/**
* Tests the page controller with arguments on a non overridden page view.
*/
public function testHandleWithArgumentsWithoutOverridden() {
$view = $this->getMock('Drupal\views\ViewStorageInterface');
$this->storageController->expects($this->once())
->method('load')
->with('test_page_view')
->will($this->returnValue($view));
$executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
->disableOriginalConstructor()
->getMock();
$executable->expects($this->once())
->method('setDisplay')
->with('page_1');
$executable->expects($this->once())
->method('initHandlers');
// Manually setup a argument handler.
$argument = $this->getMockBuilder('Drupal\views\Plugin\views\argument\ArgumentPluginBase')
->disableOriginalConstructor()
->getMock();
$executable->argument['test_id'] = $argument;
$executable->expects($this->once())
->method('executeDisplay')
->with('page_1', array('test-argument'));
$this->executableFactory->staticExpects($this->any())
->method('get')
->with($view)
->will($this->returnValue($executable));
$request = new Request();
$request->attributes->set('view_id', 'test_page_view');
$request->attributes->set('display_id', 'page_1');
// Add the argument to the request.
$request->attributes->set('arg_test_id', 'test-argument');
$this->pageController->handle($request);
}
/**
* Tests the page controller with arguments of a overridden page view.
*
* Note: This test does not care about upcasting for now.
*/
public function testHandleWithArgumentsOnOveriddenRoute() {
$view = $this->getMock('Drupal\views\ViewStorageInterface');
$this->storageController->expects($this->once())
->method('load')
->with('test_page_view')
->will($this->returnValue($view));
$executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
->disableOriginalConstructor()
->getMock();
$executable->expects($this->once())
->method('setDisplay')
->with('page_1');
$executable->expects($this->once())
->method('initHandlers');
// Manually setup a argument handler.
$argument = $this->getMockBuilder('Drupal\views\Plugin\views\argument\ArgumentPluginBase')
->disableOriginalConstructor()
->getMock();
$executable->argument['test_id'] = $argument;
$executable->expects($this->once())
->method('executeDisplay')
->with('page_1', array('test-argument'));
$this->executableFactory->staticExpects($this->any())
->method('get')
->with($view)
->will($this->returnValue($executable));
$request = new Request();
$request->attributes->set('view_id', 'test_page_view');
$request->attributes->set('display_id', 'page_1');
// Add the argument to the request.
$request->attributes->set('parameter', 'test-argument');
$request->attributes->set('_view_argument_map', array(
'arg_test_id' => 'parameter',
));
$this->pageController->handle($request);
}
/**
* Tests the page controller with arguments of a overridden page view.
*
* This test care about upcasted values and ensures that the raw variables
* are pulled in.
*/
public function testHandleWithArgumentsOnOveriddenRouteWithUpcasting() {
$view = $this->getMock('Drupal\views\ViewStorageInterface');
$this->storageController->expects($this->once())
->method('load')
->with('test_page_view')
->will($this->returnValue($view));
$executable = $this->getMockBuilder('Drupal\views\ViewExecutable')
->disableOriginalConstructor()
->getMock();
$executable->expects($this->once())
->method('setDisplay')
->with('page_1');
$executable->expects($this->once())
->method('initHandlers');
// Manually setup a argument handler.
$argument = $this->getMockBuilder('Drupal\views\Plugin\views\argument\ArgumentPluginBase')
->disableOriginalConstructor()
->getMock();
$executable->argument['test_id'] = $argument;
$executable->expects($this->once())
->method('executeDisplay')
->with('page_1', array('example_id'));
$this->executableFactory->staticExpects($this->any())
->method('get')
->with($view)
->will($this->returnValue($executable));
$request = new Request();
$request->attributes->set('view_id', 'test_page_view');
$request->attributes->set('display_id', 'page_1');
// Add the argument to the request.
$request->attributes->set('test_entity', $this->getMock('Drupal\Core\Entity\EntityInterface'));
$raw_variables = new ParameterBag(array('test_entity' => 'example_id'));
$request->attributes->set('_raw_variables', $raw_variables);
$request->attributes->set('_view_argument_map', array(
'arg_test_id' => 'test_entity',
));
$this->pageController->handle($request);
}
/**
* Tests handle with a non existing view.
*
* @expectedException \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
public function testHandleWithNotExistingView() {
// Pass in a non existent view.
$random_view_id = $this->randomName();
$request = new Request();
$request->attributes->set('view_id', $random_view_id);
$request->attributes->set('display_id', 'default');
$this->pageController->handle($request);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment