diff --git a/core/core.services.yml b/core/core.services.yml index be51f03..7d77e54 100644 --- a/core/core.services.yml +++ b/core/core.services.yml @@ -396,6 +396,10 @@ services: path.alias_storage: class: Drupal\Core\Path\AliasStorage arguments: ['@database', '@module_handler'] + path.matcher: + class: Drupal\Core\Path\PathMatcher + arguments: ['@config.factory'] + # The argument to the hashing service defined in services.yml, to the # constructor of PhpassHashedPassword is the log2 number of iterations for # password stretching. diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc index b00d23b..7108d24 100644 --- a/core/includes/install.core.inc +++ b/core/includes/install.core.inc @@ -322,6 +322,9 @@ function install_begin_request(&$install_state) { $container ->register('string_translation', 'Drupal\Core\StringTranslation\TranslationManager') ->addArgument(new Reference('language_manager')); + $container + ->register('path.matcher', 'Drupal\Core\Path\PathMatcher') + ->addArgument(new Reference('config.factory')); \Drupal::setContainer($container); diff --git a/core/includes/path.inc b/core/includes/path.inc index 6043749..02ab498 100644 --- a/core/includes/path.inc +++ b/core/includes/path.inc @@ -41,27 +41,12 @@ function drupal_is_front_page() { * * @return * Boolean value: TRUE if the path matches a pattern, FALSE otherwise. + * + * @deprecated as of Drupal 8.0. Use + * \Drupal\Core\Path\PathMatcherInterface::matchPath() instead. */ function drupal_match_path($path, $patterns) { - $regexps = &drupal_static(__FUNCTION__); - - if (!isset($regexps[$patterns])) { - // Convert path settings to a regular expression. - // Therefore replace newlines with a logical or, /* with asterisks and the with the frontpage. - $to_replace = array( - '/(\r\n?|\n)/', // newlines - '/\\\\\*/', // asterisks - '/(^|\|)\\\\($|\|)/' // - ); - $replacements = array( - '|', - '.*', - '\1' . preg_quote(\Drupal::config('system.site')->get('page.front'), '/') . '\2' - ); - $patterns_quoted = preg_quote($patterns, '/'); - $regexps[$patterns] = '/^(' . preg_replace($to_replace, $replacements, $patterns_quoted) . ')$/'; - } - return (bool)preg_match($regexps[$patterns], $path); + return \Drupal::service('path.matcher')->matchPath($path, $patterns); } /** diff --git a/core/lib/Drupal/Core/Path/PathMatcher.php b/core/lib/Drupal/Core/Path/PathMatcher.php new file mode 100644 index 0000000..449ba2b --- /dev/null +++ b/core/lib/Drupal/Core/Path/PathMatcher.php @@ -0,0 +1,77 @@ +configFactory = $config_factory; + } + + /** + * {@inheritdoc} + */ + public function matchPath($path, $patterns) { + + if (!isset($this->regexes[$patterns])) { + // Lazy-load front page config. + if (!isset($this->frontPage)) { + $this->frontPage = $this->configFactory->get('system.site')->get('page.front'); + } + // Convert path settings to a regular expression. + $to_replace = array( + // Replace newlines with a logical 'or'. + '/(\r\n?|\n)/', + // Quote asterisks. + '/\\\\\*/', + // Quote keyword. + '/(^|\|)\\\\($|\|)/', + ); + $replacements = array( + '|', + '.*', + '\1' . preg_quote($this->frontPage, '/') . '\2', + ); + $patterns_quoted = preg_quote($patterns, '/'); + $this->regexes[$patterns] = '/^(' . preg_replace($to_replace, $replacements, $patterns_quoted) . ')$/'; + } + return (bool) preg_match($this->regexes[$patterns], $path); + } +} diff --git a/core/lib/Drupal/Core/Path/PathMatcherInterface.php b/core/lib/Drupal/Core/Path/PathMatcherInterface.php new file mode 100644 index 0000000..8c0b800 --- /dev/null +++ b/core/lib/Drupal/Core/Path/PathMatcherInterface.php @@ -0,0 +1,28 @@ + 'Drupal match path', - 'description' => 'Tests the drupal_match_path() function to make sure it works properly.', - 'group' => 'Path API', - ); - } - - function setUp() { - // Set up the database and testing environment. - parent::setUp(); - - // Set up a random site front page to test the '' placeholder. - $this->front = $this->randomName(); - \Drupal::config('system.site')->set('page.front', $this->front)->save(); - // Refresh our static variables from the database. - $this->refreshVariables(); - } - - /** - * Run through our test cases, making sure each one works as expected. - */ - function testDrupalMatchPath() { - // Set up our test cases. - $tests = $this->drupalMatchPathTests(); - foreach ($tests as $patterns => $cases) { - foreach ($cases as $path => $expected_result) { - $actual_result = drupal_match_path($path, $patterns); - $this->assertIdentical($actual_result, $expected_result, format_string('Tried matching the path @path to the pattern
@patterns
- expected @expected, got @actual.', array('@path' => $path, '@patterns' => $patterns, '@expected' => var_export($expected_result, TRUE), '@actual' => var_export($actual_result, TRUE)))); - } - } - } - - /** - * Helper function for testDrupalMatchPath(): set up an array of test cases. - * - * @return - * An array of test cases to cycle through. - */ - private function drupalMatchPathTests() { - return array( - // Single absolute paths. - 'example/1' => array( - 'example/1' => TRUE, - 'example/2' => FALSE, - 'test' => FALSE, - ), - // Single paths with wildcards. - 'example/*' => array( - 'example/1' => TRUE, - 'example/2' => TRUE, - 'example/3/edit' => TRUE, - 'example/' => TRUE, - 'example' => FALSE, - 'test' => FALSE, - ), - // Single paths with multiple wildcards. - 'node/*/revisions/*' => array( - 'node/1/revisions/3' => TRUE, - 'node/345/revisions/test' => TRUE, - 'node/23/edit' => FALSE, - 'test' => FALSE, - ), - // Single paths with ''. - '' => array( - $this->front => TRUE, - "$this->front/" => FALSE, - "$this->front/edit" => FALSE, - 'node' => FALSE, - '' => FALSE, - ), - // Paths with both '' and wildcards (should not work). - '/*' => array( - $this->front => FALSE, - "$this->front/" => FALSE, - "$this->front/edit" => FALSE, - 'node/12' => FALSE, - '' => FALSE, - ), - // Multiple paths with the \n delimiter. - "node/*\nnode/*/edit" => array( - 'node/1' => TRUE, - 'node/view' => TRUE, - 'node/32/edit' => TRUE, - 'node/delete/edit' => TRUE, - 'node/50/delete' => TRUE, - 'test/example' => FALSE, - ), - // Multiple paths with the \r delimiter. - "user/*\rexample/*" => array( - 'user/1' => TRUE, - 'example/1' => TRUE, - 'user/1/example/1' => TRUE, - 'user/example' => TRUE, - 'test/example' => FALSE, - 'user' => FALSE, - 'example' => FALSE, - ), - // Multiple paths with the \r\n delimiter. - "test\r\n" => array( - 'test' => TRUE, - $this->front => TRUE, - 'example' => FALSE, - ), - // Test existing regular expressions (should be escaped). - '[^/]+?/[0-9]' => array( - 'test/1' => FALSE, - '[^/]+?/[0-9]' => TRUE, - ), - ); - } -} diff --git a/core/tests/Drupal/Tests/Core/Path/PathMatcherTest.php b/core/tests/Drupal/Tests/Core/Path/PathMatcherTest.php new file mode 100644 index 0000000..6c43478 --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Path/PathMatcherTest.php @@ -0,0 +1,176 @@ + 'Path Matcher tests', + 'description' => 'Tests that path matching is working properly.', + 'group' => 'Path', + ); + } + + /** + * {@inheritdoc} + */ + public function setUp() { + // Create a stub config factory with all config settings that will be + // checked during this test. + $config_factory_stub = $this->getConfigFactoryStub( + array( + 'system.site' => array( + 'page.front' => 'dummy', + ), + ) + ); + $this->pathMatcher = new PathMatcher($config_factory_stub); + } + + /** + * Test that standard paths works with multiple patterns. + * + * @dataProvider getMatchPathData + */ + public function testMatchPath($patterns, $paths) { + foreach ($paths as $path => $expected_result) { + $actual_result = $this->pathMatcher->matchPath($path, $patterns); + $this->assertEquals($actual_result, $expected_result, String::format('Tried matching the path @path to the pattern
@patterns
- expected @expected, got @actual.', array( + '@path' => $path, + '@patterns' => $patterns, + '@expected' => var_export($expected_result, TRUE), + '@actual' => var_export($actual_result, TRUE), + ))); + } + } + + /** + * Provides test path data. + * + * @return array + * A nested array of pattern arrays and path arrays. + */ + public function getMatchPathData() { + return array( + array( + // Single absolute paths. + 'example/1', + array( + 'example/1' => TRUE, + 'example/2' => FALSE, + 'test' => FALSE, + ), + ), + array( + // Single paths with wildcards. + 'example/*', + array( + 'example/1' => TRUE, + 'example/2' => TRUE, + 'example/3/edit' => TRUE, + 'example/' => TRUE, + 'example' => FALSE, + 'test' => FALSE, + ), + ), + array( + // Single paths with multiple wildcards. + 'node/*/revisions/*', + array( + 'node/1/revisions/3' => TRUE, + 'node/345/revisions/test' => TRUE, + 'node/23/edit' => FALSE, + 'test' => FALSE, + ), + ), + array( + // Single paths with ''. + "", + array( + 'dummy' => TRUE, + "dummy/" => FALSE, + "dummy/edit" => FALSE, + 'node' => FALSE, + '' => FALSE, + ), + ), + array( + // Paths with both '' and wildcards (should not work). + "/*", + array( + 'dummy' => FALSE, + 'dummy/' => FALSE, + 'dummy/edit' => FALSE, + 'node/12' => FALSE, + '' => FALSE, + ), + ), + array( + // Multiple paths with the \n delimiter. + "node/*\nnode/*/edit", + array( + 'node/1' => TRUE, + 'node/view' => TRUE, + 'node/32/edit' => TRUE, + 'node/delete/edit' => TRUE, + 'node/50/delete' => TRUE, + 'test/example' => FALSE, + ), + ), + array( + // Multiple paths with the \r delimiter. + "user/*\rexample/*", + array( + 'user/1' => TRUE, + 'example/1' => TRUE, + 'user/1/example/1' => TRUE, + 'user/example' => TRUE, + 'test/example' => FALSE, + 'user' => FALSE, + 'example' => FALSE, + ), + ), + array( + // Multiple paths with the \r\n delimiter. + "test\r\n", + array( + 'test' => TRUE, + 'dummy' => TRUE, + 'example' => FALSE, + ), + ), + array( + // Test existing regular expressions (should be escaped). + '[^/]+?/[0-9]', + array( + 'test/1' => FALSE, + '[^/]+?/[0-9]' => TRUE, + ), + ), + ); + } +}