diff --git a/config/packages/knp.yaml b/config/packages/knp.yaml
new file mode 100644
index 0000000..894d10d
--- /dev/null
+++ b/config/packages/knp.yaml
@@ -0,0 +1,11 @@
+knp_paginator:
+ page_range: 4
+ default_options:
+ page_name: page # page query parameter name
+ sort_field_name: sort # sort field query parameter name
+ sort_direction_name: direction # sort direction query parameter name
+ distinct: true # ensure distinct results, useful when ORM queries are using GROUP BY statements
+ template:
+ pagination: '@Core/pager/sliding.html.twig' # sliding pagination controls template
+ sortable: '@KnpPaginator/Pagination/sortable_link.html.twig' # sort link template
+ filtration: '@KnpPaginator/Pagination/filtration.html.twig' # filters template
diff --git a/core/Cache/SymfonyCacheManager.php b/core/Cache/SymfonyCacheManager.php
new file mode 100644
index 0000000..da72bad
--- /dev/null
+++ b/core/Cache/SymfonyCacheManager.php
@@ -0,0 +1,51 @@
+
+ */
+class SymfonyCacheManager
+{
+ protected KernelInterface $kernel;
+
+ public function __construct(KernelInterface $kernel)
+ {
+ $this->kernel = $kernel;
+ }
+
+ public function cleanRouting()
+ {
+ $finder = new Finder();
+ $finder
+ ->in($this->kernel->getCacheDir())
+ ->depth('== 0')
+ ->name('url_*.php*')
+ ;
+
+ foreach ($finder as $file) {
+ unlink((string) $file->getPathname());
+ }
+ }
+
+ public function cleanAll()
+ {
+ $application = new Application($this->kernel);
+ $application->setAutoExit(false);
+
+ $input = new ArrayInput([
+ 'command' => 'cache:clear',
+ ]);
+
+ $output = new BufferedOutput();
+ $application->run($input, $output);
+ }
+}
diff --git a/core/Resources/views/form/bootstrap_4_form_theme.html.twig b/core/Resources/views/form/bootstrap_4_form_theme.html.twig
index cb8652f..d309a9a 100644
--- a/core/Resources/views/form/bootstrap_4_form_theme.html.twig
+++ b/core/Resources/views/form/bootstrap_4_form_theme.html.twig
@@ -6,7 +6,7 @@
{% set value = form.vars.data %}
{% if value %}
- {% if value and value.extension in ['jpg', 'gif', 'png'] %}
+ {% if value and value.extension in ['jpeg', 'jpg', 'gif', 'png'] %}
diff --git a/core/Resources/views/pager/sliding.html.twig b/core/Resources/views/pager/sliding.html.twig
new file mode 100644
index 0000000..409417d
--- /dev/null
+++ b/core/Resources/views/pager/sliding.html.twig
@@ -0,0 +1,76 @@
+{% if pageCount > 1 %}
+
+{% endif %}
diff --git a/core/Twig/Extension/RoutingExtension.php b/core/Twig/Extension/RoutingExtension.php
new file mode 100644
index 0000000..287ea93
--- /dev/null
+++ b/core/Twig/Extension/RoutingExtension.php
@@ -0,0 +1,125 @@
+generator = $generator;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFunctions(): array
+ {
+ return [
+ new TwigFunction('node_url', [$this, 'getNodeUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]),
+ new TwigFunction('node_path', [$this, 'getNodePath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]),
+ new TwigFunction('safe_node_url', [$this, 'getSafeNodeUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]),
+ new TwigFunction('safe_node_path', [$this, 'getSafeNodePath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]),
+ new TwigFunction('safe_url', [$this, 'getSafeUrl'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]),
+ new TwigFunction('safe_path', [$this, 'getSafePath'], ['is_safe_callback' => [$this, 'isUrlGenerationSafe']]),
+ ];
+ }
+
+ public function getSafePath(string $route, array $parameters = [], bool $relative = false): ?string
+ {
+ try {
+ return $this->generator->generate(
+ $route,
+ $parameters,
+ $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH
+ );
+ } catch (\Exception $e) {
+ return null;
+ }
+ }
+
+ public function getSafeUrl(string $route, array $parameters = [], bool $schemeRelative = false): ?string
+ {
+ try {
+ return $this->generator->generate(
+ $route,
+ $parameters,
+ $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL
+ );
+ } catch (\Exception $e) {
+ return null;
+ }
+ }
+
+ public function getNodePath(Node $node, array $parameters = [], bool $relative = false): ?string
+ {
+ if ($node->hasExternalUrl()) {
+ return $node->getUrl();
+ }
+
+ return $this->generator->generate(
+ $node->getRouteName(),
+ $parameters,
+ $relative ? UrlGeneratorInterface::RELATIVE_PATH : UrlGeneratorInterface::ABSOLUTE_PATH
+ );
+ }
+
+ public function getNodeUrl(Node $node, array $parameters = [], bool $schemeRelative = false): ?string
+ {
+ if ($node->hasExternalUrl()) {
+ return $node->getUrl();
+ }
+
+ return $this->generator->generate(
+ $node->getRouteName(),
+ $parameters,
+ $schemeRelative ? UrlGeneratorInterface::NETWORK_PATH : UrlGeneratorInterface::ABSOLUTE_URL
+ );
+ }
+
+ public function getSafeNodePath(Node $node, array $parameters = [], bool $relative = false): ?string
+ {
+ try {
+ return $this->getNodePath($node, $parameters, $relative);
+ } catch (\Exception $e) {
+ return null;
+ }
+ }
+
+ public function getSafeNodeUrl(Node $node, array $parameters = [], bool $schemeRelative = false): ?string
+ {
+ try {
+ return $this->getNodeUrl($node, $parameters, $schemeRelative);
+ } catch (\Exception $e) {
+ return null;
+ }
+ }
+
+ /**
+ * @see Symfony\Bridge\Twig\Extension\RoutingExtension::isUrlGenerationSafe
+ */
+ public function isUrlGenerationSafe(TwigNode $argsNode): array
+ {
+ // support named arguments
+ $paramsNode = $argsNode->hasNode('parameters') ? $argsNode->getNode('parameters') : (
+ $argsNode->hasNode(1) ? $argsNode->getNode(1) : null
+ );
+
+ if (null === $paramsNode || $paramsNode instanceof ArrayExpression && \count($paramsNode) <= 2 &&
+ (!$paramsNode->hasNode(1) || $paramsNode->getNode(1) instanceof ConstantExpression)
+ ) {
+ return ['html'];
+ }
+
+ return [];
+ }
+}