*/
- protected array $_defaultConfig = [
- 'activeClass' => 'active',
- ];
- /**
+ protected array $_defaultConfig = [
+ 'activeClass' => 'active',
+ ];
+
+ /**
* List of helpers used by this helper
*
* @var string[]
*/
- protected array $helpers = [
- 'Html',
- 'Url',
- ];
+ protected array $helpers = [
+ 'Html',
+ 'Url',
+ ];
- /**
+ /**
* @param array|string $title
* @param array|string|null $url
* @param array $options
*
* @return string
*/
- public function link(array|string $title, array|string|null $url = null, array $options = []): string
- {
- $currentUrl = Router::parseRequest($this->getView()->getRequest());
- if (!array_key_exists('target', $options) || !$currentUrl) {
- return $this->Html->link($title, $url, $options);
- }
- $target = $options['target'];
+ public function link(array|string $title, array|string|null $url = null, array $options = []): string {
+ $currentUrl = $options['current'] ?? Router::parseRequest($this->getView()->getRequest());
+ if (!array_key_exists('target', $options) || !$currentUrl) {
+ return $this->Html->link($title, $url, $options);
+ }
+ $target = $options['target'];
+ $activeClass = $options['activeClass'] ?? $this->getConfig('activeClass');
+
unset($options['target']);
- if (is_string($target)) {
- return $this->_linkFromStringTarget($currentUrl, $target, $title, $url, $options);
- }
- if (!is_array($target)) {
- return $this->Html->link($title, $url, $options);
- }
+ unset($options['activeClass']);
+
+ if (is_string($target)) {
+ return $this->_linkFromStringTarget($currentUrl, $target, $title, $url, $activeClass, $options);
+ }
+ if (!is_array($target)) {
+ return $this->Html->link($title, $url, $options);
+ }
- if (!array_key_exists('plugin', $currentUrl)) {
- $currentUrl['plugin'] = false;
- }
- if (!array_key_exists('prefix', $currentUrl)) {
- $currentUrl['prefix'] = false;
- }
- if (isset($target['or']) && $target['or']) {
- foreach ($target['or'] as $singleTargetToMatch) {
- if ($this->_matchesUrlFromArrayTarget($currentUrl, $singleTargetToMatch)) {
- $options['class'] = $this->_addClass($options);
+ if (!array_key_exists('plugin', $currentUrl)) {
+ $currentUrl['plugin'] = false;
+ }
+ if (!array_key_exists('prefix', $currentUrl)) {
+ $currentUrl['prefix'] = false;
+ }
+ if (isset($target['or']) && $target['or']) {
+ foreach ($target['or'] as $singleTargetToMatch) {
+ if ($this->_matchesUrlFromArrayTarget($currentUrl, $singleTargetToMatch)) {
+ $options['class'] = $this->_addClass($options, $activeClass);
- return $this->Html->link($title, $url, $options);
- }
- }
- return $this->Html->link($title, $url, $options);
- }
+ return $this->Html->link($title, $url, $options);
+ }
+ }
- if (!$this->_matchesUrlFromArrayTarget($currentUrl, $target)) {
- return $this->Html->link($title, $url, $options);
- }
+ return $this->Html->link($title, $url, $options);
+ }
- $options['class'] = $this->_addClass($options);
+ if (!$this->doesUrlMatchTarget($target, $currentUrl)) {
+ return $this->Html->link($title, $url, $options);
+ }
- return $this->Html->link($title, $url, $options);
- }
+ $options['class'] = $this->_addClass($options, $activeClass);
+
+ return $this->Html->link($title, $url, $options);
+ }
- /**
+ /**
+ * @param array|string $targetUrl
+ * @param array|null $current |null current url
+ *
+ * @return bool
+ */
+ public function doesUrlMatchTarget(array|string $targetUrl, array|null $current = null) {
+ if (!isset($current)) {
+ $current = Router::parseRequest($this->getView()->getRequest());
+ }
+ if (is_string($targetUrl) && Router::normalize($current) == Router::normalize($targetUrl)) {
+ return true;
+ }
+ if (is_array($targetUrl)) {
+ if (isset($targetUrl['or']) && $targetUrl['or']) {
+ foreach ($targetUrl['or'] as $singleTargetToMatch) {
+ $matched = $this->_matchesUrlFromArrayTarget($current, $singleTargetToMatch);
+ if ($matched) {
+ return true;
+ }
+ }
+ }
+
+ return $this->_matchesUrlFromArrayTarget($current, $targetUrl);
+ }
+
+ return false;
+ }
+
+ /**
* @param array $providedOptions
*
* @return string
*/
- protected function _addClass(array $providedOptions): string
- {
- $activeClass = array_key_exists('activeClass', $providedOptions) ? $providedOptions['activeClass'] : $this->getConfig('activeClass');
-
- return array_key_exists('class', $providedOptions) ? $providedOptions['class'] . ' ' . $activeClass : $activeClass;
- }
+ protected function _addClass(array $providedOptions, string $toAdd): string {
+ return array_key_exists('class', $providedOptions) ? $providedOptions['class'] . ' ' . $toAdd : $toAdd;
+ }
+
+ /**
+ * @param array $current
+ * @param string $targetString
+ * @param string $title
+ * @param array|string|null $url
+ * @param array $options
+ *
+ * @return string
+ */
+ protected function _linkFromStringTarget(array $current, string $targetString, string $title, array|string|null $url, string $activeClass, array $options) {
+ if (Router::normalize($current) == Router::normalize($targetString)) {
+ $options['class'] = $this->_addClass($options, $activeClass);
- protected function _linkFromStringTarget(array $current, string $targetString, string $title, array|string|null $url, array $options)
- {
- if (Router::normalize($current) == Router::normalize($targetString)) {
- $options['class'] = $this->_addClass($options);
+ return $this->Html->link($title, $url, $options);
+ }
- return $this->Html->link($title, $url, $options);
- }
+ return $this->Html->link($title, $url, $options);
+ }
+
+ /**
+ * @param array $current
+ * @param array $targetUrl
+ *
+ * @return bool
+ */
+ protected function _matchesUrlFromArrayTarget(array $current, array $targetUrl) {
+ foreach ($targetUrl as $targetKey => $targetValue) {
+ if (is_array($targetValue)) {
+ if (!in_array($current[$targetKey], $targetValue)) {
+ return false;
+ }
- return $this->Html->link($title, $url, $options);
- }
+ continue;
+ }
+ if (!array_key_exists($targetKey, $current) || $targetValue != $current[$targetKey]) {
+ return false;
+ }
+ }
- protected function _matchesUrlFromArrayTarget(array $current, array $targetUrl)
- {
- foreach ($targetUrl as $targetKey => $targetValue) {
- if (is_array($targetValue)) {
- if (!in_array($current[$targetKey], $targetValue)) {
- return false;
- }
+ return true;
+ }
- continue;
- }
- if (!array_key_exists($targetKey, $current) || $targetValue != $current[$targetKey]) {
- return false;
- }
- }
-
- return true;
- }
}
diff --git a/tests/TestCase/View/Helper/ActiveLinkHelperTest.php b/tests/TestCase/View/Helper/ActiveLinkHelperTest.php
new file mode 100644
index 0000000..7481dca
--- /dev/null
+++ b/tests/TestCase/View/Helper/ActiveLinkHelperTest.php
@@ -0,0 +1,118 @@
+loadRoutes();
+ $routeBuilder = Router::createRouteBuilder('/');
+ $routeBuilder->scope('/', function (RouteBuilder $routes) {
+ $routes->setRouteClass(DashedRoute::class);
+ $routes->get(
+ '/',
+ ['controller' => 'Tests', 'action' => 'index'],
+ );
+ $routes->get(
+ '/admin/users',
+ ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'index'],
+ );
+ $routes->get(
+ '/admin/users/view',
+ ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'view'],
+ );
+ });
+
+ }
+
+ /**
+ * @return void
+ */
+ public function testLinkMatchesString(): void {
+ $request = new ServerRequest(['url' => '/']);
+ $view = new View($request);
+ $helper = new ActiveLinkHelper($view);
+
+ $result = $helper->link('goto', '/', [
+ 'target' => '/',
+ 'activeClass' => 'awesome-active-class',
+ ]);
+ $this->assertStringContainsString('awesome-active-class', $result);
+ }
+
+ /**
+ * @return void
+ */
+ public function testLinkMatchesArrayFromCurrent(): void {
+ $request = new ServerRequest();
+ $view = new View($request);
+ $helper = new ActiveLinkHelper($view);
+
+ // matches specific controller action
+ $specificActionResult = $helper->link('goto', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'index'], [
+ 'target' => ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'index'],
+ 'current' => ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'index'],
+ 'activeClass' => 'awesome-active-class',
+ ]);
+ $this->assertStringContainsString('awesome-active-class', $specificActionResult);
+
+ // match whole controller
+ $wholeControllerResult = $helper->link('goto', [
+ 'plugin' => null,
+ 'prefix' => 'Admin',
+ 'controller' => 'Users',
+ 'action' => 'index',
+ ], [
+ 'current' => ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'index'],
+ 'target' => ['prefix' => 'Admin', 'controller' => 'Users'],
+ 'activeClass' => 'awesome-active-class',
+ ]);
+ $this->assertStringContainsString('awesome-active-class', $wholeControllerResult);
+
+ // match whole prefix
+ $wholePrefixResult = $helper->link('goto', [
+ 'prefix' => 'Admin',
+ 'controller' => 'Users',
+ 'action' => 'index',
+ ], [
+ 'current' => ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'index'],
+ 'target' => ['prefix' => 'Admin'],
+ 'activeClass' => 'awesome-active-class',
+ ]);
+ $this->assertStringContainsString('awesome-active-class', $wholePrefixResult);
+ }
+
+ /**
+ * @return void
+ */
+ public function testLinkDoesNotMatchArray(): void {
+ $request = new ServerRequest();
+ $request = $request->withParam('prefix', 'Admin');
+ $request = $request->withParam('controller', 'Users');
+ $request = $request->withParam('action', 'index');
+ $view = new View($request);
+ $helper = new ActiveLinkHelper($view);
+
+ // matches specific controller action
+ $specificActionResult = $helper->link('goto', ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'index'], [
+ 'target' => ['controller' => 'Events', 'action' => 'index'],
+ 'current' => ['prefix' => 'Admin', 'controller' => 'Users', 'action' => 'index'],
+ 'activeClass' => 'awesome-active-class',
+ 'class' => 'link-class',
+ ]);
+ $this->assertStringNotContainsString('awesome-active-class', $specificActionResult);
+ }
+
+}
diff --git a/tests/bootstrap.php b/tests/bootstrap.php
index d9ec65c..9776e6a 100644
--- a/tests/bootstrap.php
+++ b/tests/bootstrap.php
@@ -1,6 +1,27 @@
'TestApp',
+ 'encoding' => 'UTF-8',
+ 'paths' => [
+ 'testWebroot' => PLUGIN_ROOT . DS . 'tests' . DS . 'test_app' . DS . 'webroot' . DS,
+ 'webroot' => PLUGIN_ROOT . DS . 'webroot' . DS,
+ 'templates' => [
+ PLUGIN_ROOT . DS . 'tests' . DS . 'test_app' . DS . 'templates' . DS,
+ ],
+ ],
+]);
/**
* Load schema from a SQL dump file.
*
@@ -49,7 +80,6 @@ if (file_exists($root . '/config/bootstrap.php')) {
* using migrations to provide schema for your plugin,
* and using \Migrations\TestSuite\Migrator to load schema.
*/
-use Cake\TestSuite\Fixture\SchemaLoader;
// Load a schema dump file.
-(new SchemaLoader())->loadSqlFiles('tests/schema.sql', 'test');
+//(new SchemaLoader())->loadSqlFiles('tests/schema.sql', 'test');
diff --git a/tests/test_app/config/bootstrap.php b/tests/test_app/config/bootstrap.php
new file mode 100644
index 0000000..b3d9bbc
--- /dev/null
+++ b/tests/test_app/config/bootstrap.php
@@ -0,0 +1 @@
+setRouteClass(DashedRoute::class);
+
+ $routes->scope('/', function (RouteBuilder $builder): void {
+ /*
+ * Here, we are connecting '/' (base path) to a controller called 'Pages',
+ * its action called 'display', and we pass a param to select the view file
+ * to use (in this case, templates/Pages/home.php)...
+ */
+ $builder->connect('/', ['controller' => 'Pages', 'action' => 'display', 'home']);
+
+ $builder->prefix('Admin', function (RouteBuilder $adminRouteBuilder): void {
+ $adminRouteBuilder->connect('/users/view', ['controller' => 'Pages', 'action' => 'view']);
+ $adminRouteBuilder->connect('/users/index', ['controller' => 'Pages', 'action' => 'index']);
+ });
+
+ /*
+ * Connect catchall routes for all controllers.
+ *
+ * The `fallbacks` method is a shortcut for
+ *
+ * ```
+ * $builder->connect('/{controller}', ['action' => 'index']);
+ * $builder->connect('/{controller}/{action}/*', []);
+ * ```
+ *
+ * You can remove these routes once you've connected the
+ * routes you want in your application.
+ */
+ $builder->fallbacks();
+ });
+ /*
+ * If you need a different set of middleware or none at all,
+ * open new scope and define routes there.
+ *
+ * ```
+ * $routes->scope('/api', function (RouteBuilder $builder): void {
+ * // No $builder->applyMiddleware() here.
+ *
+ * // Parse specified extensions from URLs
+ * // $builder->setExtensions(['json', 'xml']);
+ *
+ * // Connect API actions here.
+ * });
+ * ```
+ */
+};
diff --git a/tests/test_app/src/Controller/AppController.php b/tests/test_app/src/Controller/AppController.php
new file mode 100644
index 0000000..7387589
--- /dev/null
+++ b/tests/test_app/src/Controller/AppController.php
@@ -0,0 +1,16 @@
+loadComponent('Flash');
+ }
+}
diff --git a/tests/test_app/src/View/AppView.php b/tests/test_app/src/View/AppView.php
new file mode 100644
index 0000000..e885cc1
--- /dev/null
+++ b/tests/test_app/src/View/AppView.php
@@ -0,0 +1,11 @@
+layout = 'error';
+
+if (Configure::read('debug')):
+ $this->layout = 'dev_error';
+
+ $this->assign('title', $message);
+ $this->assign('templateName', 'error500.ctp');
+
+ $this->start('file');
+?>
+queryString)) : ?>
+
+ SQL Query:
+ = h($error->queryString) ?>
+
+
+params)) : ?>
+ SQL Query Params:
+ params) ?>
+
+
+ Error in:
+ = sprintf('%s, line %s', str_replace(ROOT, 'ROOT', $error->getFile()), $error->getLine()) ?>
+
+element('auto_table_warning');
+
+ if (extension_loaded('xdebug')):
+ xdebug_print_function_stack();
+ endif;
+
+ $this->end();
+endif;
+?>
+= __d('cake', 'An Internal Error Has Occurred') ?>
+
+ = __d('cake', 'Error') ?>:
+ = h($message) ?>
+
diff --git a/tests/test_app/templates/layout/ajax.php b/tests/test_app/templates/layout/ajax.php
new file mode 100644
index 0000000..13b3dea
--- /dev/null
+++ b/tests/test_app/templates/layout/ajax.php
@@ -0,0 +1,6 @@
+
+= $this->fetch('content') ?>
diff --git a/tests/test_app/templates/layout/default.php b/tests/test_app/templates/layout/default.php
new file mode 100644
index 0000000..13b3dea
--- /dev/null
+++ b/tests/test_app/templates/layout/default.php
@@ -0,0 +1,6 @@
+
+= $this->fetch('content') ?>
diff --git a/tests/test_app/webroot/images/cake_icon.png b/tests/test_app/webroot/images/cake_icon.png
new file mode 100644
index 0000000..394fa42
Binary files /dev/null and b/tests/test_app/webroot/images/cake_icon.png differ