diff --git a/module/VuFind/config/module.config.php b/module/VuFind/config/module.config.php index 9b5b320f89f..067601115e1 100644 --- a/module/VuFind/config/module.config.php +++ b/module/VuFind/config/module.config.php @@ -502,6 +502,7 @@ 'VuFind\Http\CachingDownloader' => 'VuFind\Http\CachingDownloaderFactory', 'VuFind\Http\GuzzleService' => 'VuFind\Http\GuzzleServiceFactory', 'VuFind\Http\PhpEnvironment\Request' => 'Laminas\ServiceManager\Factory\InvokableFactory', + 'VuFind\Http\RouteHelper' => 'VuFind\Http\RouteHelperFactory', 'VuFind\Http\ServerUrlHelper' => 'VuFind\Http\ServerUrlHelperFactory', 'VuFind\I18n\Locale\LocaleSettings' => 'VuFind\Service\ServiceWithConfigIniFactory', 'VuFind\I18n\Sorter' => 'VuFind\I18n\SorterFactory', diff --git a/module/VuFind/src/VuFind/Http/RouteHelper.php b/module/VuFind/src/VuFind/Http/RouteHelper.php new file mode 100644 index 00000000000..3d0724d134d --- /dev/null +++ b/module/VuFind/src/VuFind/Http/RouteHelper.php @@ -0,0 +1,85 @@ +. + * + * @category VuFind + * @package Http + * @author Demian Katz + * @author Maccabee Levine + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org + */ + +namespace VuFind\Http; + +use Closure; + +/** + * Route Helper class. Wrapper around Laminas UrlHelper. + * + * @category VuFind + * @package Http + * @author Demian Katz + * @author Maccabee Levine + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org + */ +class RouteHelper +{ + /** + * Constructor. + * + * @param Closure $urlHelper URL helper function + * + * @return void + */ + public function __construct(protected Closure $urlHelper) + { + } + + /** + * Generates a url given the name of a route. + * + * @param string $name Name of the route + * @param array $routeParams Path parameters + * @param array $queryParams Query parameters + * + * @see \Laminas\Router\RouteInterface::assemble() + * + * @throws \Laminas\View\Exception\RuntimeException If no RouteStackInterface was provided + * @throws \Laminas\View\Exception\RuntimeException If no RouteMatch was provided + * @throws \Laminas\View\Exception\RuntimeException If RouteMatch didn't contain a matched + * route name + * @throws \Laminas\View\Exception\InvalidArgumentException If the params object was not an + * array or Traversable object. + * + * @return self|string Url For the link href attribute + */ + public function getUrlFromRoute( + string $name, + array $routeParams = [], + array $queryParams = [] + ): string { + // Path normalization can cause problems with IDs containing escaped slashes, so let's always disable it: + $routeOptions = ['normalize_path' => false] + ($queryParams ? ['query' => $queryParams] : []); + return ($this->urlHelper)($name, $routeParams, $routeOptions); + } +} diff --git a/module/VuFind/src/VuFind/Http/RouteHelperFactory.php b/module/VuFind/src/VuFind/Http/RouteHelperFactory.php new file mode 100644 index 00000000000..f3e20b026fb --- /dev/null +++ b/module/VuFind/src/VuFind/Http/RouteHelperFactory.php @@ -0,0 +1,78 @@ +. + * + * @category VuFind + * @package Http + * @author Maccabee Levine + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ + +namespace VuFind\Http; + +use Closure; +use Laminas\ServiceManager\Exception\ServiceNotCreatedException; +use Laminas\ServiceManager\Exception\ServiceNotFoundException; +use Laminas\ServiceManager\Factory\FactoryInterface; +use Psr\Container\ContainerExceptionInterface as ContainerException; +use Psr\Container\ContainerInterface; + +/** + * Route Helper factory. + * + * @category VuFind + * @package Http + * @author Maccabee Levine + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development Wiki + */ +class RouteHelperFactory implements FactoryInterface +{ + /** + * Create an object + * + * @param ContainerInterface $container Service manager + * @param string $requestedName Service being created + * @param null|array $options Extra options (optional) + * + * @return object + * + * @throws ServiceNotFoundException if unable to resolve the service. + * @throws ServiceNotCreatedException if an exception is raised when + * creating a service. + * @throws ContainerException&\Throwable if any other error occurs + */ + public function __invoke( + ContainerInterface $container, + $requestedName, + ?array $options = null + ) { + if (!empty($options)) { + throw new \Exception('Unexpected options passed to factory.'); + } + + $viewRenderer = $container->get('ViewRenderer'); + return new $requestedName( + Closure::fromCallable($viewRenderer->plugin('url')) + ); + } +} diff --git a/module/VuFind/tests/unit-tests/src/VuFindTest/Http/RouteHelperTest.php b/module/VuFind/tests/unit-tests/src/VuFindTest/Http/RouteHelperTest.php new file mode 100644 index 00000000000..3bd4193dded --- /dev/null +++ b/module/VuFind/tests/unit-tests/src/VuFindTest/Http/RouteHelperTest.php @@ -0,0 +1,71 @@ +. + * + * @category VuFind + * @package Tests + * @author Maccabee Levine + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ + +namespace VuFindTest\Http; + +use Closure; +use Laminas\View\Helper\Url as UrlHelper; +use VuFind\Http\RouteHelper; + +/** + * RouteHelper Test Class + * + * @category VuFind + * @package Tests + * @author Maccabee Levine + * @license http://opensource.org/licenses/gpl-2.0.php GNU General Public License + * @link https://vufind.org/wiki/development:testing:unit_tests Wiki + */ +class RouteHelperTest extends \PHPUnit\Framework\TestCase +{ + /** + * Shallowly test helper's getUrlFromRoute method + * + * @return void + */ + public function testShallowGetUrlFromRoute(): void + { + $routeName = 'some-route'; + $routeParams = ['record' => 1]; + $queryParams = ['foo' => 'bar']; + $routeResult = '/some/route/1?foo=bar'; + + $urlHelper = $this->createMock(UrlHelper::class); + $urlHelper->expects($this->once()) + ->method('__invoke') + ->with($routeName, $routeParams, ['query' => $queryParams, 'normalize_path' => false]) + ->willReturn($routeResult); + $routeHelper = new RouteHelper( + Closure::fromCallable($urlHelper) + ); + + $url = $routeHelper->getUrlFromRoute($routeName, $routeParams, $queryParams); + $this->assertSame($routeResult, $url); + } +}