diff --git a/core/modules/views/src/Access/ArgumentValidatorAccessCheck.php b/core/modules/views/src/Access/ArgumentValidatorAccessCheck.php new file mode 100644 index 0000000000..807c0e53e0 --- /dev/null +++ b/core/modules/views/src/Access/ArgumentValidatorAccessCheck.php @@ -0,0 +1,82 @@ +views_plugin_manager = $views_plugin_manager; + $this->current_route_match = $current_route_match; + } + + /** + * Checks access result for a view based on argument validation. + * + * @param \Symfony\Component\Routing\Route $route + * The route to check against. + * @param \Drupal\Core\Routing\RouteMatchInterface $route_match + * The parameterized route. + * + * @return \Drupal\Core\Access\AccessResultInterface + * The access result. + */ + public function access(Route $route, RouteMatchInterface $route_match) { + $requirement = unserialize($route->getRequirement('_argument_validator_access')); + + if ($this->current_route_match->getRouteName() === $route_match->getRouteName()) { + // The route is the current route so let the view handle it. + return AccessResult::allowed(); + } + + // We're on a different route. The result can affect whether menu links are + // displayed or not. + + $allowed = TRUE; + + // Check validation for each argument by using its plugin. + foreach ($requirement['arguments'] as $argument) { + $plugin = $this->views_plugin_manager->createInstance($argument['plugin_id']); + $plugin->options = $argument['plugin_options']; + $parameter_value = $route_match->getRawParameter($argument['parameter_name']); + $allowed = $allowed && $plugin->validateArgument($parameter_value); + if (!$allowed) { + break; + } + } + + return $allowed ? AccessResult::allowed() : AccessResult::neutral(); + } +} diff --git a/core/modules/views/src/Plugin/views/display/PathPluginBase.php b/core/modules/views/src/Plugin/views/display/PathPluginBase.php index 17d8663ed1..1023feeb0b 100644 --- a/core/modules/views/src/Plugin/views/display/PathPluginBase.php +++ b/core/modules/views/src/Plugin/views/display/PathPluginBase.php @@ -135,7 +135,6 @@ protected function getRoute($view_id, $display_id) { '_view_display_show_admin_links' => $this->getOption('show_admin_links'), ]; - // @todo How do we apply argument validation? $bits = explode('/', $this->getOption('path')); // @todo Figure out validation/argument loading. // Replace % with %views_arg for menu autoloading and add to the @@ -198,6 +197,39 @@ protected function getRoute($view_id, $display_id) { } $access_plugin->alterRouteDefinition($route); + $arguments = $this->getOption('arguments'); + // Some views return null arguments rather than [] + if ($arguments === NULL) { + $arguments = []; + } + + $validate_arguments = []; + $idx = -1; + + // Collect arguments which have validation and have a fail behavior of + // "page not found" or "access denied". + foreach ($arguments as $argument) { + if (!empty($argument['specify_validation']) && !empty($argument['validate']) + && in_array($argument['validate']['fail'], ['not found', 'access denied']) ) { + $validate_arguments[] = [ + 'plugin_id' => $argument['validate']['type'], + 'plugin_options' => $argument['validate_options'], + 'parameter_name' => $argument_map['arg_' . ++$idx], + ]; + } + } + + if (!empty($validate_arguments)) { + // Add route requirement to enforce access based on argument validation. + // This is primarily used to avoid displaying broken menu links that will + // fail validation. + $route->addRequirements([ + '_argument_validator_access' => serialize([ + 'arguments' => $validate_arguments, + ]), + ]); + } + // Set the argument map, in order to support named parameters. $route->setOption('_view_argument_map', $argument_map); $route->setOption('_view_display_plugin_id', $this->getPluginId()); diff --git a/core/modules/views/views.services.yml b/core/modules/views/views.services.yml index 28f8d0d333..d835902c2c 100644 --- a/core/modules/views/views.services.yml +++ b/core/modules/views/views.services.yml @@ -80,3 +80,8 @@ services: arguments: ['@entity.manager'] tags: - { name: 'event_subscriber' } + access_check.views.argument_validator: + class: Drupal\views\Access\ArgumentValidatorAccessCheck + arguments: ['@plugin.manager.views.argument_validator', '@current_route_match'] + tags: + - { name: access_check, applies_to: _argument_validator_access }