diff --git a/src/ConfigProvider/BundleConfigProvider.php b/src/ConfigProvider/BundleConfigProvider.php new file mode 100644 index 0000000..6511a2d --- /dev/null +++ b/src/ConfigProvider/BundleConfigProvider.php @@ -0,0 +1,103 @@ + + * @license MIT + */ + +namespace Terminal42\UrlRewriteBundle\ConfigProvider; + +use Terminal42\UrlRewriteBundle\RewriteConfig; +use Terminal42\UrlRewriteBundle\RewriteConfigInterface; + +class BundleConfigProvider implements ConfigProviderInterface +{ + /** + * @var array + */ + private $entries = []; + + /** + * BundleConfigProvider constructor. + * + * @param array $entries + */ + public function __construct(array $entries = []) + { + $this->entries = $entries; + } + + /** + * {@inheritdoc} + */ + public function find(string $id): ?RewriteConfigInterface + { + if (!array_key_exists($id, $this->entries)) { + return null; + } + + return $this->createConfig($id, $this->entries[$id]); + } + + /** + * {@inheritdoc} + */ + public function findAll(): array + { + if (count($this->entries) === 0) { + return []; + } + + $configs = []; + + foreach ($this->entries as $id => $entry) { + if (($config = $this->createConfig((string) $id, $entry)) !== null) { + $configs[] = $config; + } + } + + return $configs; + } + + /** + * Create the config. + * + * @param string $id + * @param array $data + * + * @return null|RewriteConfig + */ + private function createConfig(string $id, array $data): ?RewriteConfig + { + if (!isset($data['request']['path'], $data['response']['code'])) { + return null; + } + + $config = new RewriteConfig($id, $data['request']['path'], (int) $data['response']['code']); + + // Request hosts + if (isset($data['request']['hosts'])) { + $config->setRequestHosts($data['request']['hosts']); + } + + // Request condition + if (isset($data['request']['condition'])) { + $config->setRequestCondition($data['request']['condition']); + } + + // Request requirements + if (isset($data['request']['requirements'])) { + $config->setRequestRequirements($data['request']['requirements']); + } + + // Response URI + if (isset($data['response']['uri'])) { + $config->setResponseUri($data['response']['uri']); + } + + return $config; + } +} diff --git a/src/ConfigProvider/ChainConfigProvider.php b/src/ConfigProvider/ChainConfigProvider.php new file mode 100644 index 0000000..b9b479e --- /dev/null +++ b/src/ConfigProvider/ChainConfigProvider.php @@ -0,0 +1,82 @@ + + * @license MIT + */ + +namespace Terminal42\UrlRewriteBundle\ConfigProvider; + +use Terminal42\UrlRewriteBundle\RewriteConfigInterface; + +class ChainConfigProvider implements ConfigProviderInterface +{ + /** + * @var array + */ + private $providers = []; + + /** + * Add the config provider. + * + * @param ConfigProviderInterface $provider + */ + public function addProvider(ConfigProviderInterface $provider): void + { + $this->providers[] = $provider; + } + + /** + * {@inheritdoc} + */ + public function find(string $id): ?RewriteConfigInterface + { + list($class, $id) = explode(':', $id); + + /** @var ConfigProviderInterface $provider */ + foreach ($this->providers as $provider) { + if ($class === $this->getProviderIdentifier($provider) && ($config = $provider->find($id)) !== null) { + return $config; + } + } + + return null; + } + + /** + * {@inheritdoc} + */ + public function findAll(): array + { + $configs = []; + + /** @var ConfigProviderInterface $provider */ + foreach ($this->providers as $provider) { + $providerConfigs = $provider->findAll(); + + /** @var RewriteConfigInterface $config */ + foreach ($providerConfigs as $config) { + $config->setIdentifier($this->getProviderIdentifier($provider) . ':' . $config->getIdentifier()); + } + + $configs = array_merge($configs, $providerConfigs); + } + + return $configs; + } + + /** + * Get the provider identifier + * + * @param ConfigProviderInterface $provider + * + * @return string + */ + private function getProviderIdentifier(ConfigProviderInterface $provider): string + { + return get_class($provider); + } +} diff --git a/src/ConfigProvider/ConfigProviderInterface.php b/src/ConfigProvider/ConfigProviderInterface.php new file mode 100644 index 0000000..ec6b93b --- /dev/null +++ b/src/ConfigProvider/ConfigProviderInterface.php @@ -0,0 +1,32 @@ + + * @license MIT + */ + +namespace Terminal42\UrlRewriteBundle\ConfigProvider; + +use Terminal42\UrlRewriteBundle\RewriteConfigInterface; + +interface ConfigProviderInterface +{ + /** + * Find the config. + * + * @param string $id + * + * @return RewriteConfigInterface|null + */ + public function find(string $id): ?RewriteConfigInterface; + + /** + * Find all configs. + * + * @return array + */ + public function findAll(): array; +} diff --git a/src/ConfigProvider/DatabaseConfigProvider.php b/src/ConfigProvider/DatabaseConfigProvider.php new file mode 100644 index 0000000..b1a0bc3 --- /dev/null +++ b/src/ConfigProvider/DatabaseConfigProvider.php @@ -0,0 +1,130 @@ + + * @license MIT + */ + +namespace Terminal42\UrlRewriteBundle\ConfigProvider; + +use Contao\StringUtil; +use Doctrine\DBAL\Connection; +use Doctrine\DBAL\Exception\TableNotFoundException; +use Terminal42\UrlRewriteBundle\RewriteConfig; +use Terminal42\UrlRewriteBundle\RewriteConfigInterface; + +class DatabaseConfigProvider implements ConfigProviderInterface +{ + /** + * @var Connection + */ + private $connection; + + /** + * DatabaseConfigProvider constructor. + * + * @param Connection $connection + */ + public function __construct(Connection $connection) + { + $this->connection = $connection; + } + + /** + * {@inheritdoc} + */ + public function find(string $id): ?RewriteConfigInterface + { + try { + $data = $this->connection->fetchAssoc('SELECT * FROM tl_url_rewrite WHERE id=?', [$id]); + } catch (\PDOException | TableNotFoundException $e) { + return null; + } + + if (false === $data) { + return null; + } + + return $this->createConfig($data); + } + + /** + * {@inheritdoc} + */ + public function findAll(): array + { + try { + $records = $this->connection->fetchAll('SELECT * FROM tl_url_rewrite'); + } catch (\PDOException | TableNotFoundException $e) { + return []; + } + + if (count($records) === 0) { + return []; + } + + $configs = []; + + foreach ($records as $record) { + if (($config = $this->createConfig($record)) !== null) { + $configs[] = $config; + } + } + + return $configs; + } + + /** + * Create the config. + * + * @param array $data + * + * @return null|RewriteConfig + */ + private function createConfig(array $data): ?RewriteConfig + { + if (!isset($data['id'], $data['type'], $data['requestPath'], $data['responseCode'])) { + return null; + } + + $config = new RewriteConfig((string) $data['id'], $data['requestPath'], (int) $data['responseCode']); + + // Hosts + if (isset($data['requestHosts'])) { + $config->setRequestHosts(StringUtil::deserialize($data['requestHosts'], true)); + } + + // Response URI + if (isset($data['responseUri'])) { + $config->setResponseUri($data['responseUri']); + } + + switch ($data['type']) { + // Basic type + case 'basic': + if (isset($data['requestRequirements'])) { + $requirements = []; + + foreach (StringUtil::deserialize($data['requestRequirements'], true) as $requirement) { + if ($requirement['key'] !== '' && $requirement['value'] !== '') { + $requirements[$requirement['key']] = $requirement['value']; + } + } + + $config->setRequestRequirements($requirements); + } + break; + // Expert type + case 'expert': + $config->setRequestCondition($data['requestCondition']); + break; + default: + throw new \RuntimeException(sprintf('Unsupported database record config type: %s', $data['type'])); + } + + return $config; + } +} diff --git a/src/Controller/RewriteController.php b/src/Controller/RewriteController.php index 8db21d4..cff8138 100644 --- a/src/Controller/RewriteController.php +++ b/src/Controller/RewriteController.php @@ -12,18 +12,19 @@ use Contao\CoreBundle\Framework\ContaoFrameworkInterface; use Contao\InsertTags; -use Doctrine\DBAL\Connection; use Symfony\Component\HttpFoundation\RedirectResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\Routing\Exception\RouteNotFoundException; +use Terminal42\UrlRewriteBundle\ConfigProvider\ConfigProviderInterface; +use Terminal42\UrlRewriteBundle\RewriteConfigInterface; class RewriteController { /** - * @var Connection + * @var ConfigProviderInterface */ - private $db; + private $configProvider; /** * @var ContaoFrameworkInterface @@ -33,12 +34,12 @@ class RewriteController /** * RewriteController constructor. * - * @param Connection $db + * @param ConfigProviderInterface $configProvider * @param ContaoFrameworkInterface $framework */ - public function __construct(Connection $db, ContaoFrameworkInterface $framework) + public function __construct(ConfigProviderInterface $configProvider, ContaoFrameworkInterface $framework) { - $this->db = $db; + $this->configProvider = $configProvider; $this->framework = $framework; } @@ -53,17 +54,18 @@ public function __construct(Connection $db, ContaoFrameworkInterface $framework) */ public function indexAction(Request $request): Response { - if (!$request->attributes->has('_url_rewrite') || !($rewriteId = $request->attributes->getInt('_url_rewrite'))) { - throw new RouteNotFoundException('There _url_rewrite attribute is missing'); + if (!$request->attributes->has('_url_rewrite')) { + throw new RouteNotFoundException('The _url_rewrite attribute is missing'); } - $config = $this->db->fetchAssoc('SELECT * FROM tl_url_rewrite WHERE id=?', [$rewriteId]); + $rewriteId = $request->attributes->get('_url_rewrite'); + $config = $this->configProvider->find($rewriteId); - if (false === $config || !isset($config['responseCode'])) { + if (null === $config) { throw new RouteNotFoundException(sprintf('URL rewrite config ID %s does not exist', $rewriteId)); } - $responseCode = (int) $config['responseCode']; + $responseCode = $config->getResponseCode(); if (410 === $responseCode) { return new Response(Response::$statusTexts[$responseCode], $responseCode); @@ -77,20 +79,17 @@ public function indexAction(Request $request): Response /** * Generate the URI. * - * @param Request $request - * @param array $config + * @param Request $request + * @param RewriteConfigInterface $config * * @return string|null */ - private function generateUri(Request $request, array $config): ?string + private function generateUri(Request $request, RewriteConfigInterface $config): ?string { - if (!isset($config['responseUri'])) { + if (($uri = $config->getResponseUri()) === null) { return null; } - $uri = $config['responseUri']; - - // Parse the URI $uri = $this->replaceWildcards($request, $uri); $uri = $this->replaceInsertTags($uri); diff --git a/src/DependencyInjection/Compiler/ConfigProviderPass.php b/src/DependencyInjection/Compiler/ConfigProviderPass.php new file mode 100644 index 0000000..dfe90d0 --- /dev/null +++ b/src/DependencyInjection/Compiler/ConfigProviderPass.php @@ -0,0 +1,71 @@ + + * @license MIT + */ + +namespace Terminal42\UrlRewriteBundle\DependencyInjection\Compiler; + +use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; +use Symfony\Component\DependencyInjection\Compiler\PriorityTaggedServiceTrait; +use Symfony\Component\DependencyInjection\ContainerBuilder; + +class ConfigProviderPass implements CompilerPassInterface +{ + use PriorityTaggedServiceTrait; + + /** + * @var string + */ + private $alias; + + /** + * @var string + */ + private $chain; + + /** + * @var string + */ + private $tag; + + /** + * ConfigProviderPass constructor. + * + * @param string $alias + * @param string $chain + * @param $tag + */ + public function __construct($alias, $chain, $tag) + { + $this->alias = $alias; + $this->chain = $chain; + $this->tag = $tag; + } + + /** + * {@inheritdoc} + */ + public function process(ContainerBuilder $container) + { + $services = $this->findAndSortTaggedServices($this->tag, $container); + + // If there's only one service or chain service is not present alias the first service + if ((count($services) === 1 && count($services[0]) === 1) || !$container->hasDefinition($this->chain)) { + $container->setAlias($this->alias, (string) $services[0]); + + return; + } + + $definition = $container->findDefinition($this->chain); + + // Add providers to the chain + foreach ($services as $service) { + $definition->addMethodCall('addProvider', [$service]); + } + } +} diff --git a/src/DependencyInjection/Configuration.php b/src/DependencyInjection/Configuration.php new file mode 100644 index 0000000..eef4136 --- /dev/null +++ b/src/DependencyInjection/Configuration.php @@ -0,0 +1,79 @@ + + * @license MIT + */ + +namespace Terminal42\UrlRewriteBundle\DependencyInjection; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; +use Terminal42\UrlRewriteBundle\RewriteConfigInterface; + +class Configuration implements ConfigurationInterface +{ + /** + * {@inheritdoc} + */ + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder(); + + $rootNode = $treeBuilder->root('terminal42_url_rewrite'); + $rootNode + ->children() + ->booleanNode('backend_management') + ->info('Enable the rewrites management in Contao backend.') + ->defaultTrue() + ->end() + ->arrayNode('entries') + ->arrayPrototype() + ->children() + ->arrayNode('request') + ->children() + ->scalarNode('path') + ->info('The request path to match.') + ->isRequired() + ->cannotBeEmpty() + ->end() + ->arrayNode('hosts') + ->info('An array of hosts to match.') + ->scalarPrototype()->end() + ->end() + ->arrayNode('requirements') + ->info('Additional requirements to match.') + ->scalarPrototype()->end() + ->end() + ->scalarNode('condition') + ->info('Request condition in Symfony\'s Expression Language to match.') + ->end() + ->end() + ->end() + ->arrayNode('response') + ->children() + ->integerNode('code') + ->info('The response code.') + ->defaultValue(301) + ->validate() + ->ifNotInArray(RewriteConfigInterface::VALID_RESPONSE_CODES) + ->thenInvalid('Invalid response code %s.') + ->end() + ->end() + ->scalarNode('uri') + ->info('The response redirect URI. Irrelevant if response code is set to 410.') + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ->end() + ; + + return $treeBuilder; + } +} diff --git a/src/DependencyInjection/Terminal42UrlRewriteExtension.php b/src/DependencyInjection/Terminal42UrlRewriteExtension.php index 3fa4465..27b952f 100644 --- a/src/DependencyInjection/Terminal42UrlRewriteExtension.php +++ b/src/DependencyInjection/Terminal42UrlRewriteExtension.php @@ -14,18 +14,33 @@ use Symfony\Component\Config\FileLocator; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Extension\Extension; use Symfony\Component\DependencyInjection\Loader\YamlFileLoader; +use Symfony\Component\HttpKernel\DependencyInjection\ConfigurableExtension; -class Terminal42UrlRewriteExtension extends Extension +class Terminal42UrlRewriteExtension extends ConfigurableExtension { /** * {@inheritdoc} */ - public function load(array $configs, ContainerBuilder $container): void + protected function loadInternal(array $mergedConfig, ContainerBuilder $container) { $loader = new YamlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); $loader->load('listener.yml'); $loader->load('services.yml'); + + $hasBackendManagement = (bool) $mergedConfig['backend_management']; + + // Set the "backend management" parameter + $container->setParameter('terminal42_url_rewrite.backend_management', $hasBackendManagement); + + // Remove the database provider if backend management is not available + if (!$hasBackendManagement) { + $container->removeDefinition('terminal42_url_rewrite.provider.database'); + } + + // Set the entries as argument for bundle config provider + if (isset($mergedConfig['entries']) && $container->hasDefinition('terminal42_url_rewrite.provider.bundle')) { + $container->getDefinition('terminal42_url_rewrite.provider.bundle')->setArguments([$mergedConfig['entries']]); + } } } diff --git a/src/EventListener/RewriteContainerListener.php b/src/EventListener/RewriteContainerListener.php index 8c21bac..bf34e5b 100644 --- a/src/EventListener/RewriteContainerListener.php +++ b/src/EventListener/RewriteContainerListener.php @@ -13,6 +13,7 @@ use Symfony\Bundle\FrameworkBundle\Routing\Router; use Symfony\Component\Filesystem\Filesystem; use Symfony\Component\HttpFoundation\Response; +use Terminal42\UrlRewriteBundle\RewriteConfigInterface; class RewriteContainerListener { @@ -91,7 +92,7 @@ public function getResponseCodes(): array { $options = []; - foreach ([301, 302, 303, 307, 410] as $code) { + foreach (RewriteConfigInterface::VALID_RESPONSE_CODES as $code) { $options[$code] = $code.' '.Response::$statusTexts[$code]; } @@ -99,7 +100,7 @@ public function getResponseCodes(): array } /** - * Generate the examples + * Generate the examples. * * @return string */ diff --git a/src/Resources/config/services.yml b/src/Resources/config/services.yml index 4a00bc3..182e3e7 100644 --- a/src/Resources/config/services.yml +++ b/src/Resources/config/services.yml @@ -2,12 +2,33 @@ services: terminal42_url_rewrite.rewrite_controller: class: Terminal42\UrlRewriteBundle\Controller\RewriteController arguments: - - "@database_connection" + - "@terminal42_url_rewrite.provider" - "@contao.framework" terminal42_url_rewrite.rewrite_loader: class: Terminal42\UrlRewriteBundle\Routing\UrlRewriteLoader arguments: - - "@database_connection" + - "@terminal42_url_rewrite.provider" tags: - { name: routing.loader } + + terminal42_url_rewrite.provider: + alias: terminal42_url_rewrite.provider.chain + + terminal42_url_rewrite.provider.bundle: + class: Terminal42\UrlRewriteBundle\ConfigProvider\BundleConfigProvider + public: false + tags: + - { name: terminal42_url_rewrite.provider, priority: 64 } + + terminal42_url_rewrite.provider.chain: + class: Terminal42\UrlRewriteBundle\ConfigProvider\ChainConfigProvider + public: false + + terminal42_url_rewrite.provider.database: + class: Terminal42\UrlRewriteBundle\ConfigProvider\DatabaseConfigProvider + public: false + arguments: + - "@database_connection" + tags: + - { name: terminal42_url_rewrite.provider, priority: 32 } diff --git a/src/Resources/contao/config/config.php b/src/Resources/contao/config/config.php index 65e682e..ceafcca 100644 --- a/src/Resources/contao/config/config.php +++ b/src/Resources/contao/config/config.php @@ -8,9 +8,12 @@ * @license MIT */ -$GLOBALS['BE_MOD']['system']['url_rewrites'] = [ - 'tables' => ['tl_url_rewrite'], -]; +/** + * Add the backend module if allowed. + */ +if (\System::getContainer()->getParameter('terminal42_url_rewrite.backend_management')) { + $GLOBALS['BE_MOD']['system']['url_rewrites'] = ['tables' => ['tl_url_rewrite']]; +} /* * Hooks diff --git a/src/Resources/contao/dca/tl_url_rewrite.php b/src/Resources/contao/dca/tl_url_rewrite.php index 6cd8e2f..788132f 100644 --- a/src/Resources/contao/dca/tl_url_rewrite.php +++ b/src/Resources/contao/dca/tl_url_rewrite.php @@ -181,3 +181,10 @@ ], ], ]; + +/* + * Remove the DCA if not allowed + */ +if (!\System::getContainer()->getParameter('terminal42_url_rewrite.backend_management')) { + unset($GLOBALS['TL_DCA']['tl_url_rewrite']); +} diff --git a/src/Resources/contao/languages/de/tl_url_rewrite.php b/src/Resources/contao/languages/de/tl_url_rewrite.php index 73a440b..2c91d34 100644 --- a/src/Resources/contao/languages/de/tl_url_rewrite.php +++ b/src/Resources/contao/languages/de/tl_url_rewrite.php @@ -42,7 +42,7 @@ 'expert' => ['komplex', 'Erlaubt die Anfrage-Zuordnung auf Basis der Symfony Expression Language. Für weitere Informationen besuchen Sie bitte diese Seite.'], ]; -/** +/* * Examples */ $GLOBALS['TL_LANG']['tl_url_rewrite']['examples'] = [ diff --git a/src/Resources/contao/languages/en/tl_url_rewrite.php b/src/Resources/contao/languages/en/tl_url_rewrite.php index ac0c201..aae444c 100644 --- a/src/Resources/contao/languages/en/tl_url_rewrite.php +++ b/src/Resources/contao/languages/en/tl_url_rewrite.php @@ -42,7 +42,7 @@ 'expert' => ['Expert', 'Allows to define the request condition using the Expression Language. For more information please visit this link.'], ]; -/** +/* * Examples */ $GLOBALS['TL_LANG']['tl_url_rewrite']['examples'] = [ diff --git a/src/RewriteConfig.php b/src/RewriteConfig.php new file mode 100644 index 0000000..b03fd3c --- /dev/null +++ b/src/RewriteConfig.php @@ -0,0 +1,181 @@ + + * @license MIT + */ + +namespace Terminal42\UrlRewriteBundle; + +class RewriteConfig implements RewriteConfigInterface +{ + /** + * @var string + */ + private $identifier; + + /** + * @var string + */ + private $requestPath = ''; + + /** + * @var array + */ + private $requestHosts = []; + + /** + * @var array + */ + private $requestRequirements = []; + + /** + * @var string|null + */ + private $requestCondition; + + /** + * @var int + */ + private $responseCode = 301; + + /** + * @var string|null + */ + private $responseUri; + + /** + * RewriteConfig constructor. + * + * @param string $identifier + * @param string $requestPath + * @param int $responseCode + */ + public function __construct(string $identifier, string $requestPath, int $responseCode = 301) + { + $this->identifier = $identifier; + $this->setRequestPath($requestPath); + $this->setResponseCode($responseCode); + } + + /** + * @return string + */ + public function getIdentifier(): string + { + return $this->identifier; + } + + /** + * @inheritDoc + */ + public function setIdentifier(string $identifier): void + { + $this->identifier = $identifier; + } + + /** + * @return string + */ + public function getRequestPath(): string + { + return $this->requestPath; + } + + /** + * @param string $requestPath + */ + public function setRequestPath(string $requestPath): void + { + $this->requestPath = $requestPath; + } + + /** + * @return array + */ + public function getRequestHosts(): array + { + return $this->requestHosts; + } + + /** + * @param array $requestHosts + */ + public function setRequestHosts(array $requestHosts): void + { + $this->requestHosts = array_unique(array_filter($requestHosts)); + } + + /** + * @return array + */ + public function getRequestRequirements(): array + { + return $this->requestRequirements; + } + + /** + * @param array $requestRequirements + */ + public function setRequestRequirements(array $requestRequirements): void + { + $this->requestRequirements = $requestRequirements; + } + + /** + * @return null|string + */ + public function getRequestCondition(): ?string + { + return $this->requestCondition; + } + + /** + * @param null|string $requestCondition + */ + public function setRequestCondition(string $requestCondition): void + { + $this->requestCondition = $requestCondition; + } + + /** + * @return int + */ + public function getResponseCode(): int + { + return $this->responseCode; + } + + /** + * @param int $responseCode + * + * @throws \InvalidArgumentException + */ + public function setResponseCode(int $responseCode): void + { + if (!\in_array($responseCode, self::VALID_RESPONSE_CODES, true)) { + throw new \InvalidArgumentException(sprintf('Invalid response code: %s', $responseCode)); + } + + $this->responseCode = $responseCode; + } + + /** + * @return null|string + */ + public function getResponseUri(): ?string + { + return $this->responseUri; + } + + /** + * @param null|string $responseUri + */ + public function setResponseUri(string $responseUri): void + { + $this->responseUri = $responseUri; + } +} diff --git a/src/RewriteConfigInterface.php b/src/RewriteConfigInterface.php new file mode 100644 index 0000000..eaed98b --- /dev/null +++ b/src/RewriteConfigInterface.php @@ -0,0 +1,56 @@ + + * @license MIT + */ + +namespace Terminal42\UrlRewriteBundle; + +interface RewriteConfigInterface +{ + const VALID_RESPONSE_CODES = [301, 302, 303, 307, 410]; + + /** + * @return string + */ + public function getIdentifier(): string; + + /** + * @param string $identifier + */ + public function setIdentifier(string $identifier): void; + + /** + * @return string + */ + public function getRequestPath(): string; + + /** + * @return array + */ + public function getRequestHosts(): array; + + /** + * @return array + */ + public function getRequestRequirements(): array; + + /** + * @return null|string + */ + public function getRequestCondition(): ?string; + + /** + * @return int + */ + public function getResponseCode(): int; + + /** + * @return null|string + */ + public function getResponseUri(): ?string; +} diff --git a/src/Routing/UrlRewriteLoader.php b/src/Routing/UrlRewriteLoader.php index 0a2f587..8c83513 100644 --- a/src/Routing/UrlRewriteLoader.php +++ b/src/Routing/UrlRewriteLoader.php @@ -12,19 +12,18 @@ namespace Terminal42\UrlRewriteBundle\Routing; -use Contao\StringUtil; -use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Exception\TableNotFoundException; use Symfony\Component\Config\Loader\Loader; use Symfony\Component\Routing\Route; use Symfony\Component\Routing\RouteCollection; +use Terminal42\UrlRewriteBundle\ConfigProvider\ConfigProviderInterface; +use Terminal42\UrlRewriteBundle\RewriteConfigInterface; class UrlRewriteLoader extends Loader { /** - * @var Connection + * @var ConfigProviderInterface */ - private $db; + private $configProvider; /** * Has been already loaded? @@ -36,11 +35,11 @@ class UrlRewriteLoader extends Loader /** * UrlRewriteLoader constructor. * - * @param Connection $db + * @param ConfigProviderInterface $configProvider */ - public function __construct(Connection $db) + public function __construct(ConfigProviderInterface $configProvider) { - $this->db = $db; + $this->configProvider = $configProvider; } /** @@ -54,22 +53,18 @@ public function load($resource, $type = null): RouteCollection $this->loaded = true; $collection = new RouteCollection(); + $configs = $this->configProvider->findAll(); - try { - $rewrites = $this->db->fetchAll('SELECT * FROM tl_url_rewrite'); - } catch (\PDOException | TableNotFoundException $e) { - return $collection; - } - - if (0 === count($rewrites)) { + if (0 === count($configs)) { return $collection; } $count = 0; - foreach ($rewrites as $rewrite) { + /** @var RewriteConfigInterface $config */ + foreach ($configs as $config) { /** @var Route $route */ - foreach ($this->generateRoutes($rewrite) as $route) { + foreach ($this->generateRoutes($config) as $route) { if ($route !== null) { $collection->add('url_rewrite_'.$count++, $route); } @@ -90,19 +85,13 @@ public function supports($resource, $type = null): bool /** * Generate the routes. * - * @param array $config + * @param RewriteConfigInterface $config * * @return \Generator */ - private function generateRoutes(array $config): \Generator + private function generateRoutes(RewriteConfigInterface $config): \Generator { - $hosts = []; - - // Parse the hosts from config - if (isset($config['requestHosts'])) { - /** @var array $hosts */ - $hosts = array_unique(array_filter(StringUtil::deserialize($config['requestHosts'], true))); - } + $hosts = $config->getRequestHosts(); if (count($hosts) > 0) { foreach ($hosts as $host) { @@ -116,31 +105,25 @@ private function generateRoutes(array $config): \Generator /** * Create the route object. * - * @param array $config - * @param string|null $host + * @param RewriteConfigInterface $config + * @param string|null $host * * @return Route|null */ - private function createRoute(array $config, string $host = null): ?Route + private function createRoute(RewriteConfigInterface $config, string $host = null): ?Route { - if (!isset($config['id'], $config['type'], $config['requestPath'])) { - return null; - } + $route = new Route($config->getRequestPath()); + $route->setDefault('_controller', 'terminal42_url_rewrite.rewrite_controller:indexAction'); + $route->setDefault('_url_rewrite', $config->getIdentifier()); + $route->setRequirements($config->getRequestRequirements()); - switch ($config['type']) { - case 'basic': - $route = $this->createBasicRoute($config); - break; - case 'expert': - $route = $this->createExpertRoute($config); - break; - default: - throw new \InvalidArgumentException(sprintf('Unsupported database record config type: %s', $config['type'])); + // Set the condition + if (($condition = $config->getRequestCondition()) !== null) { + $route->setCondition($condition); + } else { + $route->setMethods('GET'); } - $route->setDefault('_controller', 'terminal42_url_rewrite.rewrite_controller:indexAction'); - $route->setDefault('_url_rewrite', $config['id']); - // Set the host if (null !== $host) { $route->setHost($host); @@ -148,49 +131,4 @@ private function createRoute(array $config, string $host = null): ?Route return $route; } - - /** - * Create the basic route. - * - * @param array $config - * - * @return Route - */ - private function createBasicRoute(array $config): Route - { - $route = new Route($config['requestPath']); - $route->setMethods('GET'); - - // Set the requirements - if (isset($config['requestRequirements'])) { - /** @var array $requirements */ - $requirements = StringUtil::deserialize($config['requestRequirements'], true); - $requirements = array_filter($requirements, function ($item) { - return $item['key'] !== '' && $item['value'] !== ''; - }); - - if (count($requirements) > 0) { - foreach ($requirements as $requirement) { - $route->setRequirement($requirement['key'], $requirement['value']); - } - } - } - - return $route; - } - - /** - * Create the expert route. - * - * @param array $config - * - * @return Route - */ - private function createExpertRoute(array $config): Route - { - $route = new Route($config['requestPath']); - $route->setCondition($config['requestCondition']); - - return $route; - } } diff --git a/src/Terminal42UrlRewriteBundle.php b/src/Terminal42UrlRewriteBundle.php index 139a01b..4d31768 100644 --- a/src/Terminal42UrlRewriteBundle.php +++ b/src/Terminal42UrlRewriteBundle.php @@ -14,6 +14,7 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\HttpKernel\Bundle\Bundle; +use Terminal42\UrlRewriteBundle\DependencyInjection\Compiler\ConfigProviderPass; class Terminal42UrlRewriteBundle extends Bundle { @@ -22,5 +23,10 @@ class Terminal42UrlRewriteBundle extends Bundle */ public function build(ContainerBuilder $container): void { + $container->addCompilerPass(new ConfigProviderPass( + 'terminal42_url_rewrite.provider', + 'terminal42_url_rewrite.provider.chain', + 'terminal42_url_rewrite.provider' + )); } } diff --git a/tests/DependencyInjection/ConfigurationTest.php b/tests/DependencyInjection/ConfigurationTest.php new file mode 100644 index 0000000..7e39a7d --- /dev/null +++ b/tests/DependencyInjection/ConfigurationTest.php @@ -0,0 +1,75 @@ +assertInstanceOf(Configuration::class, new Configuration()); + } + + public function testValidConfig() + { + $yaml = Yaml::parse(file_get_contents(__DIR__ . '/../Fixtures/config_valid.yml')); + $config = (new Processor())->processConfiguration(new Configuration(), $yaml); + + $expected = [ + 'backend_management' => true, + 'entries' => [ + [ + 'request' => [ + 'path' => 'find/{address}', + 'hosts' => [], + 'requirements' => [], + ], + 'response' => [ + 'code' => 303, + 'uri' => 'https://www.google.com/maps?q={address}', + ], + ], + [ + 'request' => [ + 'path' => 'news/{news}', + 'requirements' => ['news' => '\d+'], + 'hosts' => [], + ], + 'response' => [ + 'code' => 301, + 'uri' => '{{news_url::{news}|absolute}}' + ], + ], + [ + 'request' => [ + 'path' => 'home.php', + 'hosts' => ['localhost'], + 'condition' => "context.getMethod() == 'GET' and request.query.has('page')", + 'requirements' => [], + ], + 'response' => [ + 'uri' => '{{link_url::{page}|absolute}}', + 'code' => 301, + ], + ], + ], + ]; + + static::assertSame($expected, $config); + } + + public function testInvalidConfig() + { + $this->expectException(InvalidConfigurationException::class); + + $yaml = Yaml::parse(file_get_contents(__DIR__ . '/../Fixtures/config_invalid.yml')); + (new Processor())->processConfiguration(new Configuration(), $yaml); + } +} diff --git a/tests/Fixtures/config_invalid.yml b/tests/Fixtures/config_invalid.yml new file mode 100644 index 0000000..cea45df --- /dev/null +++ b/tests/Fixtures/config_invalid.yml @@ -0,0 +1,5 @@ +terminal42_url_rewrite: + entries: + - + request: { path: 'find/{address}' } + response: { code: 404, uri: 'https://www.google.com/maps?q={address}' } diff --git a/tests/Fixtures/config_valid.yml b/tests/Fixtures/config_valid.yml new file mode 100644 index 0000000..8f538e8 --- /dev/null +++ b/tests/Fixtures/config_valid.yml @@ -0,0 +1,18 @@ +terminal42_url_rewrite: + backend_management: true + entries: + - + request: { path: 'find/{address}' } + response: { code: 303, uri: 'https://www.google.com/maps?q={address}' } + + - + request: { path: 'news/{news}', requirements: {news: '\d+'} } + response: { code: 301, uri: '{{news_url::{news}|absolute}}' } + + - + request: + path: 'home.php' + hosts: ['localhost'] + condition: "context.getMethod() == 'GET' and request.query.has('page')" + response: + uri: '{{link_url::{page}|absolute}}'