Commit c056f6df authored by webchick's avatar webchick

Issue #2241827 by killua99, dawehner, djevans | Crell: Using a numeric...

Issue #2241827 by killua99, dawehner, djevans | Crell: Using a numeric placeholder in paths in Views UI causes fatal error.
parent 519e01c7
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
namespace Drupal\rest\Plugin\views\display; namespace Drupal\rest\Plugin\views\display;
use Drupal\Core\Form\FormBuilderInterface;
use Drupal\Core\State\StateInterface; use Drupal\Core\State\StateInterface;
use Drupal\Core\Routing\RouteProviderInterface; use Drupal\Core\Routing\RouteProviderInterface;
use Drupal\Core\ContentNegotiation; use Drupal\Core\ContentNegotiation;
...@@ -99,13 +99,15 @@ class RestExport extends PathPluginBase { ...@@ -99,13 +99,15 @@ class RestExport extends PathPluginBase {
* The route provider * The route provider
* @param \Drupal\Core\State\StateInterface $state * @param \Drupal\Core\State\StateInterface $state
* The state key value store. * The state key value store.
* @param \Drupal\Core\Form\FormBuilderInterface $form_error
* The form builder.
* @param \Drupal\Core\ContentNegotiation $content_negotiation * @param \Drupal\Core\ContentNegotiation $content_negotiation
* The content negotiation library. * The content negotiation library.
* @param \Symfony\Component\HttpFoundation\Request $request * @param \Symfony\Component\HttpFoundation\Request $request
* The request object. * The request object.
*/ */
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, StateInterface $state, ContentNegotiation $content_negotiation, Request $request) { public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, StateInterface $state, FormBuilderInterface $form_error, ContentNegotiation $content_negotiation, Request $request) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $route_provider, $state); parent::__construct($configuration, $plugin_id, $plugin_definition, $route_provider, $state, $form_error);
$this->contentNegotiation = $content_negotiation; $this->contentNegotiation = $content_negotiation;
$this->request = $request; $this->request = $request;
} }
...@@ -120,6 +122,7 @@ public static function create(ContainerInterface $container, array $configuratio ...@@ -120,6 +122,7 @@ public static function create(ContainerInterface $container, array $configuratio
$plugin_definition, $plugin_definition,
$container->get('router.route_provider'), $container->get('router.route_provider'),
$container->get('state'), $container->get('state'),
$container->get('form_builder'),
$container->get('content_negotiation'), $container->get('content_negotiation'),
$container->get('request') $container->get('request')
); );
......
...@@ -90,6 +90,9 @@ protected function setUp() { ...@@ -90,6 +90,9 @@ protected function setUp() {
->getMock(); ->getMock();
$container->set('plugin.manager.views.style', $style_manager); $container->set('plugin.manager.views.style', $style_manager);
$form_builder = $this->getMock('Drupal\Core\Form\FormBuilderInterface');
$container->set('form_builder', $form_builder);
\Drupal::setContainer($container); \Drupal::setContainer($container);
$this->restExport = RestExport::create($container, array(), "test_routes", array()); $this->restExport = RestExport::create($container, array(), "test_routes", array());
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
namespace Drupal\views\Plugin\views\display; namespace Drupal\views\Plugin\views\display;
use Drupal\Core\Form\FormErrorInterface;
use Drupal\Core\State\StateInterface; use Drupal\Core\State\StateInterface;
use Drupal\Core\Routing\RouteCompiler; use Drupal\Core\Routing\RouteCompiler;
use Drupal\Core\Routing\RouteProviderInterface; use Drupal\Core\Routing\RouteProviderInterface;
...@@ -39,6 +40,13 @@ abstract class PathPluginBase extends DisplayPluginBase implements DisplayRouter ...@@ -39,6 +40,13 @@ abstract class PathPluginBase extends DisplayPluginBase implements DisplayRouter
*/ */
protected $state; protected $state;
/**
* The form error helper.
*
* @var \Drupal\Core\Form\FormErrorInterface
*/
protected $formError;
/** /**
* Constructs a PathPluginBase object. * Constructs a PathPluginBase object.
* *
...@@ -52,12 +60,15 @@ abstract class PathPluginBase extends DisplayPluginBase implements DisplayRouter ...@@ -52,12 +60,15 @@ abstract class PathPluginBase extends DisplayPluginBase implements DisplayRouter
* The route provider. * The route provider.
* @param \Drupal\Core\State\StateInterface $state * @param \Drupal\Core\State\StateInterface $state
* The state key value store. * The state key value store.
* @param \Drupal\Core\Form\FormErrorInterface $form_error
* The form error helper.
*/ */
public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, StateInterface $state) { public function __construct(array $configuration, $plugin_id, $plugin_definition, RouteProviderInterface $route_provider, StateInterface $state, FormErrorInterface $form_error) {
parent::__construct($configuration, $plugin_id, $plugin_definition); parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->routeProvider = $route_provider; $this->routeProvider = $route_provider;
$this->state = $state; $this->state = $state;
$this->formError = $form_error;
} }
/** /**
...@@ -69,7 +80,8 @@ public static function create(ContainerInterface $container, array $configuratio ...@@ -69,7 +80,8 @@ public static function create(ContainerInterface $container, array $configuratio
$plugin_id, $plugin_id,
$plugin_definition, $plugin_definition,
$container->get('router.route_provider'), $container->get('router.route_provider'),
$container->get('state') $container->get('state'),
$container->get('form_builder')
); );
} }
...@@ -395,8 +407,9 @@ public function validateOptionsForm(&$form, &$form_state) { ...@@ -395,8 +407,9 @@ public function validateOptionsForm(&$form, &$form_state) {
parent::validateOptionsForm($form, $form_state); parent::validateOptionsForm($form, $form_state);
if ($form_state['section'] == 'path') { if ($form_state['section'] == 'path') {
if (strpos($form_state['values']['path'], '%') === 0) { $errors = $this->validatePath($form_state['values']['path']);
form_error($form['path'], $form_state, t('"%" may not be used for the first segment of a path.')); foreach ($errors as $error) {
$this->formError->setError($form['path'], $form_state, $error);
} }
// Automatically remove '/' and trailing whitespace from path. // Automatically remove '/' and trailing whitespace from path.
...@@ -415,4 +428,44 @@ public function submitOptionsForm(&$form, &$form_state) { ...@@ -415,4 +428,44 @@ public function submitOptionsForm(&$form, &$form_state) {
} }
} }
/**
* Validates the path of the display.
*
* @param string $path
* The path to validate.
*
* @return array
* A list of error strings.
*/
protected function validatePath($path) {
$errors = array();
if (strpos($path, '%') === 0) {
$errors[] = $this->t('"%" may not be used for the first segment of a path.');
}
$path_sections = explode('/', $path);
// Symfony routing does not allow to use numeric placeholders.
// @see \Symfony\Component\Routing\RouteCompiler
$numeric_placeholders = array_filter($path_sections, function ($section) {
return (preg_match('/^%(.*)/', $section, $matches)
&& is_numeric($matches[1]));
});
if (!empty($numeric_placeholders)) {
$errors[] = $this->t("Numeric placeholders may not be used. Please use plain placeholders (%).");
}
return $errors;
}
/**
* {@inheritdoc}
*/
public function validate() {
$errors = parent::validate();
$errors += $this->validatePath($this->getOption('path'));
return $errors;
}
} }
...@@ -47,6 +47,13 @@ class PathPluginBaseTest extends UnitTestCase { ...@@ -47,6 +47,13 @@ class PathPluginBaseTest extends UnitTestCase {
*/ */
protected $state; protected $state;
/**
* The mocked form error.
*
* @var \Drupal\Core\Form\FormErrorInterface|\PHPUnit_Framework_MockObject_MockObject
*/
protected $formError;
public static function getInfo() { public static function getInfo() {
return array( return array(
'name' => 'Display: Path plugin base.', 'name' => 'Display: Path plugin base.',
...@@ -63,8 +70,9 @@ protected function setUp() { ...@@ -63,8 +70,9 @@ protected function setUp() {
$this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); $this->routeProvider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
$this->state = $this->getMock('\Drupal\Core\State\StateInterface'); $this->state = $this->getMock('\Drupal\Core\State\StateInterface');
$this->formError = $this->getMock('Drupal\Core\Form\FormErrorInterface');
$this->pathPlugin = $this->getMockBuilder('Drupal\views\Plugin\views\display\PathPluginBase') $this->pathPlugin = $this->getMockBuilder('Drupal\views\Plugin\views\display\PathPluginBase')
->setConstructorArgs(array(array(), 'path_base', array(), $this->routeProvider, $this->state)) ->setConstructorArgs(array(array(), 'path_base', array(), $this->routeProvider, $this->state, $this->formError))
->setMethods(NULL) ->setMethods(NULL)
->getMock(); ->getMock();
$this->setupContainer(); $this->setupContainer();
......
...@@ -30,6 +30,14 @@ public static function getInfo() { ...@@ -30,6 +30,14 @@ public static function getInfo() {
} }
public function testPathUI() { public function testPathUI() {
$this->doBasicPathUITest();
$this->doAdvancedPathsValidationTest();
}
/**
* Tests basic functionality in configuring a view.
*/
protected function doBasicPathUITest() {
$this->drupalGet('admin/structure/views/view/test_view'); $this->drupalGet('admin/structure/views/view/test_view');
// Add a new page display and check the appearing text. // Add a new page display and check the appearing text.
...@@ -44,6 +52,21 @@ public function testPathUI() { ...@@ -44,6 +52,21 @@ public function testPathUI() {
$this->assertLink(t('View @display', array('@display' => 'Page')), 0, 'view page link found on the page.'); $this->assertLink(t('View @display', array('@display' => 'Page')), 0, 'view page link found on the page.');
} }
/**
* Tests a couple of invalid path patterns.
*/
protected function doAdvancedPathsValidationTest() {
$url = 'admin/structure/views/nojs/display/test_view/page_1/path';
$this->drupalPostForm($url, array('path' => '%/magrathea'), t('Apply'));
$this->assertUrl($url);
$this->assertText('"%" may not be used for the first segment of a path.');
$this->drupalPostForm($url, array('path' => 'user/%1/example'), t('Apply'));
$this->assertUrl($url);
$this->assertText("Numeric placeholders may not be used. Please use plain placeholders (%).");
}
/** /**
* Tests deleting a page display that has no path. * Tests deleting a page display that has no path.
*/ */
......
...@@ -79,9 +79,10 @@ public function testBuildRowEntityList() { ...@@ -79,9 +79,10 @@ public function testBuildRowEntityList() {
); );
$route_provider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface'); $route_provider = $this->getMock('Drupal\Core\Routing\RouteProviderInterface');
$state = $this->getMock('\Drupal\Core\State\StateInterface'); $state = $this->getMock('\Drupal\Core\State\StateInterface');
$form_builder = $this->getMock('Drupal\Core\Form\FormBuilderInterface');
$page_display = $this->getMock('Drupal\views\Plugin\views\display\Page', $page_display = $this->getMock('Drupal\views\Plugin\views\display\Page',
array('initDisplay', 'getPath'), array('initDisplay', 'getPath'),
array(array(), 'default', $display_manager->getDefinition('page'), $route_provider, $state) array(array(), 'default', $display_manager->getDefinition('page'), $route_provider, $state, $form_builder)
); );
$page_display->expects($this->any()) $page_display->expects($this->any())
->method('getPath') ->method('getPath')
......
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