diff --git a/core/modules/system/config/schema/system.data_types.schema.yml b/core/modules/system/config/schema/system.data_types.schema.yml index ab375b6..6ada1c4 100644 --- a/core/modules/system/config/schema/system.data_types.schema.yml +++ b/core/modules/system/config/schema/system.data_types.schema.yml @@ -84,3 +84,18 @@ filter: status: type: boolean label: 'Enabled' + +# Array of routes with route_name and route_params keys. +route: + type: mapping + label: 'Route' + mapping: + route_name: + type: text + label: 'Route Name' + route_params: + type: sequence + label: 'Route Params' + sequence: + - type: string + label: 'Param' diff --git a/core/modules/tour/config/schema/tour.schema.yml b/core/modules/tour/config/schema/tour.schema.yml index b1a0c28..9ac1082 100644 --- a/core/modules/tour/config/schema/tour.schema.yml +++ b/core/modules/tour/config/schema/tour.schema.yml @@ -22,12 +22,12 @@ tour.tour.*: langcode: type: string label: 'Default language' - paths: + routes: type: sequence - label: 'Path settings' + label: 'Route settings' sequence: - - type: path - label: 'Path' + - type: route + label: 'Route' tips: type: sequence label: 'Tips' @@ -68,5 +68,3 @@ tour.tip.text: body: type: text label: 'Body' - - diff --git a/core/modules/tour/lib/Drupal/tour/Entity/Tour.php b/core/modules/tour/lib/Drupal/tour/Entity/Tour.php index 8cefe16..2821995 100644 --- a/core/modules/tour/lib/Drupal/tour/Entity/Tour.php +++ b/core/modules/tour/lib/Drupal/tour/Entity/Tour.php @@ -53,11 +53,18 @@ class Tour extends ConfigEntityBase implements TourInterface { public $label; /** - * The paths in which this tip can be displayed. + * The routes on which this tour should be displayed. * * @var array */ - protected $paths = array(); + protected $routes = array(); + + /** + * The routes on which this tour should be displayed, keyed by route id. + * + * @var array + */ + protected $keyedRoutes; /** * Holds the collection of tips that are attached to this tour. @@ -85,10 +92,12 @@ public function __construct(array $values, $entity_type) { /** * {@inheritdoc} */ - public function getPaths() { - return $this->paths; + public function getRoutes() { + return $this->routes; } + + /** * {@inheritdoc} */ @@ -121,7 +130,7 @@ public function getTips() { public function getExportProperties() { $properties = parent::getExportProperties(); $names = array( - 'paths', + 'routes', 'tips', ); foreach ($names as $name) { @@ -130,4 +139,38 @@ public function getExportProperties() { return $properties; } + /** + * {@inheritdoc} + */ + public function hasMatchingRoute($route_name, $route_params) { + if (!isset($this->keyedRoutes)) { + $this->keyedRoutes = array(); + foreach ($this->getRoutes() as $route) { + $this->keyedRoutes[$route['route_name']] = isset($route['route_params']) ? $route['route_params'] : array(); + } + } + if (!isset($this->keyedRoutes[$route_name])) { + // We don't know about this route. + return FALSE; + } + if (empty($this->keyedRoutes[$route_name])) { + // We don't need to worry about route params, the route name is enough. + return TRUE; + } + foreach ($this->keyedRoutes[$route_name] as $key => $value) { + // If a required param is missing or doesn't match, return FALSE. + if (empty($route_params[$key]) || $route_params[$key] !== $value) { + return FALSE; + } + } + return TRUE; + } + + /** + * {@inheritdoc} + */ + public function resetKeyedRoutes() { + unset($this->keyedRoutes); + } + } diff --git a/core/modules/tour/lib/Drupal/tour/Tests/TourTest.php b/core/modules/tour/lib/Drupal/tour/Tests/TourTest.php index 75d196c..7a26ef4 100644 --- a/core/modules/tour/lib/Drupal/tour/Tests/TourTest.php +++ b/core/modules/tour/lib/Drupal/tour/Tests/TourTest.php @@ -129,8 +129,8 @@ public function testTourFunctionality() { 'id' => 'tour-entity-create-test-en', 'label' => 'Tour test english', 'langcode' => 'en', - 'paths' => array( - 'tour-test-1', + 'routes' => array( + array('route_name' => 'tour_test.1'), ), 'tips' => array( 'tour-test-1' => array( @@ -173,5 +173,25 @@ public function testTourFunctionality() { // Test hook_tour_alter(). $this->assertText('Altered by hook_tour_tips_alter'); + + // Navigate to tour-test-3 and verify the tour_test_1 tip is found with + // appropriate classes. + $this->drupalGet('tour-test-3/foo'); + $elements = $this->xpath('//li[@data-id=:data_id and @class=:classes and ./h2[contains(., :text)]]', array( + ':classes' => 'tip-module-tour-test tip-type-text tip-tour-test-1', + ':data_id' => 'tour-test-1', + ':text' => 'The first tip', + )); + $this->assertEqual(count($elements), 1, 'Found English variant of tip 1.'); + + // Navigate to tour-test-3 and verify the tour_test_1 tip is not found with + // appropriate classes. + $this->drupalGet('tour-test-3/bar'); + $elements = $this->xpath('//li[@data-id=:data_id and @class=:classes and ./h2[contains(., :text)]]', array( + ':classes' => 'tip-module-tour-test tip-type-text tip-tour-test-1', + ':data_id' => 'tour-test-1', + ':text' => 'The first tip', + )); + $this->assertEqual(count($elements), 0, 'Found English variant of tip 1.'); } } diff --git a/core/modules/tour/lib/Drupal/tour/TourInterface.php b/core/modules/tour/lib/Drupal/tour/TourInterface.php index d5e4135..2aa1459 100644 --- a/core/modules/tour/lib/Drupal/tour/TourInterface.php +++ b/core/modules/tour/lib/Drupal/tour/TourInterface.php @@ -15,12 +15,25 @@ interface TourInterface extends ConfigEntityInterface { /** - * The paths that this tour will appear on. + * The routes that this tour will appear on. * * @return array - * Returns array of paths for the tour. + * Returns array of routes for the tour. */ - public function getPaths(); + public function getRoutes(); + + /** + * Whether the tour matches a given set of route parameters. + * + * @param string $route_name + * The route name the parameters are for. + * @param array $route_params + * Associative array of raw route params. + * + * @return bool + * TRUE if the tour matches the route parameters. + */ + public function hasMatchingRoute($route_name, $route_params); /** * Returns tip plugin. @@ -41,4 +54,9 @@ public function getTip($id); */ public function getTips(); + /** + * Resets the statically cached keyed routes. + */ + public function resetKeyedRoutes(); + } diff --git a/core/modules/tour/tests/Drupal/tour/Tests/Entity/TourTest.php b/core/modules/tour/tests/Drupal/tour/Tests/Entity/TourTest.php new file mode 100644 index 0000000..d62ecc4 --- /dev/null +++ b/core/modules/tour/tests/Drupal/tour/Tests/Entity/TourTest.php @@ -0,0 +1,143 @@ + 'Tour entity tests', + 'description' => 'Test \Drupal\tour\Entity\Tour.', + 'group' => 'Tour', + ); + } + + /** + * Tests \Drupal\tour\Entity\Tour::hasMatchingRoute(). + * + * @param array $routes + * Array of routes as per the Tour::routes property. + * @param string $route_name + * The route name to match. + * @param array $route_params + * Array of route params. + * @param bool $result + * Expected result. + * + * @covers \Drupal\tour\Entity\Tour::hasMatchingRoute(). + * @group Tour + * @dataProvider routeProvider + */ + public function testHasMatchingRoute($routes, $route_name, $route_params, $result) { + $tour = $this->getMockBuilder('\Drupal\tour\Entity\Tour') + ->disableOriginalConstructor() + ->setMethods(array('getRoutes')) + ->getMock(); + + $tour->expects($this->any()) + ->method('getRoutes') + ->will($this->returnValue($routes)); + + $this->assertEquals($result, $tour->hasMatchingRoute($route_name, $route_params)); + + $tour->resetKeyedRoutes(); + } + + /* + * Provides sample routes for testing. + */ + public function routeProvider() { + return array( + // Simple match. + array( + array( + array('route_name' => 'some.route') + ), + 'some.route', + array(), + TRUE + ), + // Empty params. + array( + array( + array( + 'route_name' => 'some.route', + 'route_params' => array('foo' => 'bar'), + ), + ), + 'some.route', + array(), + FALSE + ), + // Match on params. + array( + array( + array( + 'route_name' => 'some.route', + 'route_params' => array('foo' => 'bar'), + ), + ), + 'some.route', + array('foo' => 'bar'), + TRUE + ), + // Non-matching params. + array( + array( + array( + 'route_name' => 'some.route', + 'route_params' => array('foo' => 'bar'), + ), + ), + 'some.route', + array('bar' => 'foo'), + FALSE + ), + // One matching, one not. + array( + array( + array( + 'route_name' => 'some.route', + 'route_params' => array('foo' => 'bar'), + ), + array( + 'route_name' => 'some.route', + 'route_params' => array('bar' => 'foo'), + ), + ), + 'some.route', + array('bar' => 'foo'), + TRUE + ), + // One matching, one not. + array( + array( + array( + 'route_name' => 'some.route', + 'route_params' => array('foo' => 'bar'), + ), + array( + 'route_name' => 'some.route', + 'route_params' => array('foo' => 'baz'), + ), + ), + 'some.route', + array('foo' => 'baz'), + TRUE + ), + ); + } + +} diff --git a/core/modules/tour/tests/tour_test/config/tour.tour.tour-test-2.yml b/core/modules/tour/tests/tour_test/config/tour.tour.tour-test-2.yml index 62e5ae8..32dc788 100644 --- a/core/modules/tour/tests/tour_test/config/tour.tour.tour-test-2.yml +++ b/core/modules/tour/tests/tour_test/config/tour.tour.tour-test-2.yml @@ -2,8 +2,8 @@ id: tour-test-2 module: tour_test label: Tour test english langcode: en -paths: - - tour-test-2/* +routes: + - route_name: tour_test.2 tips: tour-test-2: id: tour-test-2 diff --git a/core/modules/tour/tests/tour_test/config/tour.tour.tour-test.yml b/core/modules/tour/tests/tour_test/config/tour.tour.tour-test.yml index 3fa6d5c..6452fab 100644 --- a/core/modules/tour/tests/tour_test/config/tour.tour.tour-test.yml +++ b/core/modules/tour/tests/tour_test/config/tour.tour.tour-test.yml @@ -2,8 +2,11 @@ id: tour-test module: tour_test label: Tour test english langcode: en -paths: - - tour-test-1 +routes: + - route_name: tour_test.1 + - route_name: tour_test.3 + route_params: + locale: foo tips: tour-test-1: id: tour-test-1 diff --git a/core/modules/tour/tests/tour_test/lib/Drupal/tour_test/Controller/TourTestController.php b/core/modules/tour/tests/tour_test/lib/Drupal/tour_test/Controller/TourTestController.php index 126be87..30e1602 100644 --- a/core/modules/tour/tests/tour_test/lib/Drupal/tour_test/Controller/TourTestController.php +++ b/core/modules/tour/tests/tour_test/lib/Drupal/tour_test/Controller/TourTestController.php @@ -23,8 +23,15 @@ public static function create(ContainerInterface $container) { /** * Outputs some content for testing tours. + * + * @param string $locale + * (optional) Dummy locale variable for testing routing parameters. Defaults + * to 'foo'. + * + * @return array + * Array of markup. */ - public function tourTest1() { + public function tourTest1($locale = 'foo') { return array( 'tip-1' => array( '#type' => 'container', diff --git a/core/modules/tour/tests/tour_test/tour_test.routing.yml b/core/modules/tour/tests/tour_test/tour_test.routing.yml index 32f934d..5e4d254 100644 --- a/core/modules/tour/tests/tour_test/tour_test.routing.yml +++ b/core/modules/tour/tests/tour_test/tour_test.routing.yml @@ -18,3 +18,12 @@ tour_test.2: _content: '\Drupal\tour_test\Controller\TourTestController::tourTest2' requirements: _access: 'TRUE' + +tour_test.3: + path: '/tour-test-3/{locale}' + defaults: + locale: 'foo' + _content: '\Drupal\tour_test\Controller\TourTestController::tourTest1' + requirements: + _access: 'TRUE' + diff --git a/core/modules/tour/tour.module b/core/modules/tour/tour.module index 8bd9ec1..16fe5f0 100644 --- a/core/modules/tour/tour.module +++ b/core/modules/tour/tour.module @@ -5,6 +5,7 @@ * Main functions of the module. */ use Drupal\Core\Cache\CacheBackendInterface; +use Symfony\Cmf\Component\Routing\RouteObjectInterface; /** * Implements hook_permission(). @@ -108,23 +109,23 @@ function tour_preprocess_page(&$variables) { return; } - // @todo replace this with http://drupal.org/node/1918768 once it is committed. - $path = current_path(); - // Load all of the items and match on path. - $tours = entity_load_multiple('tour'); - - $path_alias = drupal_strtolower(\Drupal::service('path.alias_manager')->getPathAlias($path)); - foreach ($tours as $tour_id => $tour) { - // @todo Replace this with an entity query that does path matching when - // http://drupal.org/node/1918768 lands. - $pages = implode("\n", $tour->getPaths()); - if (!drupal_match_path($path_alias, $pages) && (($path == $path_alias) || drupal_match_path($path, $pages))) { - unset($tours[$tour_id]); + // Load all of the items and match on route name. + $request = \Drupal::request(); + $route_name = $request->attributes->get(RouteObjectInterface::ROUTE_NAME); + + $results = \Drupal::entityQuery('tour') + ->condition('routes.*.route_name', $route_name) + ->execute(); + if (!empty($results) && $tours = entity_load_multiple('tour', array_keys($results))) { + foreach ($tours as $id => $tour) { + // Match on params. + if (!$tour->hasMatchingRoute($route_name, $request->attributes->get('_raw_variables')->all())) { + unset($tours[$id]); + } + } + if (!empty($tours)) { + $variables['page']['help']['tour'] = entity_view_multiple($tours, 'full'); } - } - - if ($tours) { - $variables['page']['help']['tour'] = entity_view_multiple($tours, 'full'); } } diff --git a/core/modules/views_ui/config/tour.tour.views-ui.yml b/core/modules/views_ui/config/tour.tour.views-ui.yml index a7e6fa9..645fb5c 100644 --- a/core/modules/views_ui/config/tour.tour.views-ui.yml +++ b/core/modules/views_ui/config/tour.tour.views-ui.yml @@ -3,8 +3,9 @@ uuid: 261db4f0-603c-440d-8211-17a095614851 module: views_ui label: 'Views ui' langcode: en -paths: - - 'admin/structure/views/view/*' +routes: + - route_name: views_ui.edit + - route_name: views_ui.edit_display tips: views-ui-active-display: id: views-ui-active-display