diff --git a/config/autoload/local.php.dist b/config/autoload/local.php.dist
index c4b22a83..b2215f09 100644
--- a/config/autoload/local.php.dist
+++ b/config/autoload/local.php.dist
@@ -45,12 +45,20 @@ return [
],
],
'contact' => [
- 'notification_receivers' => [],
- 'message_receivers' => [
- 'to' => [
+ 'subject' => 'Dotkernel Contact',
+ 'message_sender' => [
+ 'from_name' => '',
+ 'from_email' => '',
+ ],
+ 'message_recipients' => [
+ 'name' => 'Dotkernel Team',
+ 'to' => [
+ 'tech@example.com',
+ ],
+ 'cc' => [
'tech@example.com',
],
- 'cc' => [
+ 'bcc' => [
'tech@example.com',
],
],
diff --git a/config/autoload/navigation.global.php b/config/autoload/navigation.global.php
new file mode 100644
index 00000000..3c4517f2
--- /dev/null
+++ b/config/autoload/navigation.global.php
@@ -0,0 +1,169 @@
+ [
+ //enable menu item active if any child is active
+ 'active_recursion' => true,
+ 'containers' => [
+ 'left_menu' => [
+ 'type' => 'ArrayProvider',
+ 'options' => [
+ 'items' => [
+ [
+ 'options' => [
+ 'label' => 'Pages',
+ 'route' => [],
+ ],
+ 'attributes' => [
+ 'class' => 'nav-link dropdown-toggle',
+ 'href' => '#',
+ ],
+ 'pages' => [
+ [
+ 'options' => [
+ 'label' => 'Home',
+ 'uri' => '/home',
+ ],
+ 'attributes' => [
+ 'class' => 'dropdown-item',
+ ],
+ ],
+ [
+ 'options' => [
+ 'label' => 'About Us',
+ 'uri' => '/page/about-us',
+ ],
+ 'attributes' => [
+ 'class' => 'dropdown-item',
+ ],
+ ],
+ [
+ 'options' => [
+ 'label' => 'Who We Are',
+ 'uri' => '/page/who-we-are',
+ ],
+ 'attributes' => [
+ 'class' => 'dropdown-item',
+ ],
+ ],
+ [
+ 'options' => [
+ 'label' => 'Premium content',
+ 'uri' => '/page/premium-content',
+ ],
+ 'attributes' => [
+ 'class' => 'dropdown-item',
+ ],
+ ],
+ ],
+ ],
+ [
+ 'options' => [
+ 'label' => 'Contribute',
+ 'uri' => 'https://github.com/dotkernel',
+ ],
+ 'attributes' => [
+ 'class' => 'nav-link',
+ 'target' => '_blank',
+ ],
+ ],
+ [
+ 'options' => [
+ 'label' => 'Contact Us',
+ 'uri' => '/contact/form',
+ ],
+ 'attributes' => [
+ 'class' => 'nav-link',
+ ],
+ ],
+ [
+ 'options' => [
+ 'label' => 'Disabled',
+ 'uri' => '/',
+ ],
+ 'attributes' => [
+ 'class' => 'nav-link disabled',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'guest_menu' => [
+ 'type' => 'ArrayProvider',
+ 'options' => [
+ 'items' => [
+ [
+ 'options' => [
+ 'label' => 'Log in',
+ 'uri' => '/user/login',
+ ],
+ 'attributes' => [
+ 'class' => 'nav-link',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'user_menu' => [
+ 'type' => 'ArrayProvider',
+ 'options' => [
+ 'items' => [
+ [
+ 'options' => [
+ 'label' => 'Profile',
+ 'uri' => '/account/details',
+ ],
+ 'attributes' => [
+ 'class' => 'nav-link',
+ ],
+ ],
+ [
+ 'options' => [
+ 'label' => 'Log out',
+ 'uri' => '/user/logout',
+ ],
+ 'attributes' => [
+ 'class' => 'nav-link',
+ ],
+ ],
+ ],
+ ],
+ ],
+ 'user_profile_menu' => [
+ 'type' => 'ArrayProvider',
+ 'options' => [
+ 'items' => [
+ [
+ 'options' => [
+ 'label' => 'Avatar',
+ 'uri' => '/account/avatar',
+ ],
+ ],
+ [
+ 'options' => [
+ 'label' => 'Details',
+ 'uri' => '/account/details',
+ ],
+ ],
+ [
+ 'options' => [
+ 'label' => 'Change password',
+ 'uri' => '/account/change-password',
+ ],
+ ],
+ [
+ 'options' => [
+ 'label' => 'Delete account',
+ 'uri' => '/account/delete-account',
+ ],
+ ],
+ ],
+ ],
+ ],
+ ],
+ //register custom providers here
+ 'provider_manager' => [],
+ ],
+];
diff --git a/config/autoload/templates.global.php b/config/autoload/templates.global.php
index 56308edf..7ee92fac 100644
--- a/config/autoload/templates.global.php
+++ b/config/autoload/templates.global.php
@@ -3,6 +3,7 @@
declare(strict_types=1);
use Dot\Twig\Extension\DateExtension;
+use Frontend\App\Twig\Extension\RouteExtension;
use Laminas\ServiceManager\Factory\InvokableFactory;
use Mezzio\Template\TemplateRendererInterface;
use Mezzio\Twig\TwigEnvironmentFactory;
@@ -29,12 +30,13 @@
'cache_dir' => 'data/cache/twig',
'extensions' => [
DateExtension::class,
+ RouteExtension::class,
],
'optimizations' => -1,
'runtime_loaders' => [],
//'timezone' => '',
'globals' => [
- 'appName' => $app['name'],
+ 'appName' => $app['name'] ?? '',
],
],
];
diff --git a/config/config.php b/config/config.php
index 6686a4ce..fc5dda8a 100644
--- a/config/config.php
+++ b/config/config.php
@@ -45,6 +45,8 @@ class_exists(\Mezzio\Swoole\ConfigProvider::class)
\Dot\ResponseHeader\ConfigProvider::class,
\Dot\DataFixtures\ConfigProvider::class,
\Dot\Cache\ConfigProvider::class,
+ \Dot\Helpers\ConfigProvider::class,
+ \Dot\Navigation\ConfigProvider::class,
// Default App module config
\Frontend\App\ConfigProvider::class,
diff --git a/config/pipeline.php b/config/pipeline.php
index 3915570f..c3859ece 100644
--- a/config/pipeline.php
+++ b/config/pipeline.php
@@ -3,6 +3,7 @@
declare(strict_types=1);
use Dot\ErrorHandler\ErrorHandlerInterface;
+use Dot\Navigation\NavigationMiddleware;
use Dot\Rbac\Guard\Middleware\ForbiddenHandler;
use Dot\Rbac\Guard\Middleware\RbacGuardMiddleware;
use Dot\ResponseHeader\Middleware\ResponseHeaderMiddleware;
@@ -77,6 +78,7 @@
$app->pipe(AuthMiddleware::class);
$app->pipe(ForbiddenHandler::class);
$app->pipe(RbacGuardMiddleware::class);
+ $app->pipe(NavigationMiddleware::class);
// Register the dispatch middleware in the middleware pipeline
$app->pipe(DispatchMiddleware::class);
diff --git a/src/App/src/ConfigProvider.php b/src/App/src/ConfigProvider.php
index de61c488..193f20f1 100644
--- a/src/App/src/ConfigProvider.php
+++ b/src/App/src/ConfigProvider.php
@@ -36,6 +36,7 @@ public function getDependencies(): array
RecaptchaService::class => AttributedServiceFactory::class,
CookieService::class => AttributedServiceFactory::class,
RememberMeMiddleware::class => AttributedServiceFactory::class,
+ Twig\Extension\RouteExtension::class => AttributedServiceFactory::class,
],
'aliases' => [
EntityManager::class => 'doctrine.entity_manager.orm_default',
diff --git a/src/App/src/Twig/Extension/RouteExtension.php b/src/App/src/Twig/Extension/RouteExtension.php
new file mode 100644
index 00000000..a7b35848
--- /dev/null
+++ b/src/App/src/Twig/Extension/RouteExtension.php
@@ -0,0 +1,46 @@
+urlHelper->getRequest()?->getUri()?->getPath();
+ }
+
+ public function isRoute(?string $route): bool
+ {
+ if (null === $route) {
+ return false;
+ }
+
+ $currentRoute = $this->getCurrentRoute();
+ if (null === $currentRoute) {
+ return false;
+ }
+
+ return $currentRoute === $route;
+ }
+}
diff --git a/src/App/templates/layout/default.html.twig b/src/App/templates/layout/default.html.twig
index 6ae7dbf4..d0e0e7d0 100644
--- a/src/App/templates/layout/default.html.twig
+++ b/src/App/templates/layout/default.html.twig
@@ -42,37 +42,14 @@
diff --git a/src/App/templates/partial/menu.html.twig b/src/App/templates/partial/menu.html.twig
new file mode 100644
index 00000000..6235e29d
--- /dev/null
+++ b/src/App/templates/partial/menu.html.twig
@@ -0,0 +1,27 @@
+{% set extraAttributes = '' %}
+{% if page is defined %}
+
+{% endif %}
diff --git a/src/App/templates/partial/user_profile_menu.html.twig b/src/App/templates/partial/user_profile_menu.html.twig
new file mode 100644
index 00000000..42057267
--- /dev/null
+++ b/src/App/templates/partial/user_profile_menu.html.twig
@@ -0,0 +1,8 @@
+{% set extraAttributes = '' %}
+{% for page in container %}
+ {% if navigation.isAllowed(page) %}
+
+ {{ page.getOption('label') }}
+
+ {% endif %}
+{% endfor %}
diff --git a/src/Contact/src/Controller/ContactController.php b/src/Contact/src/Controller/ContactController.php
index 83b9f2a2..444f7f8d 100644
--- a/src/Contact/src/Controller/ContactController.php
+++ b/src/Contact/src/Controller/ContactController.php
@@ -65,7 +65,7 @@ public function formAction(): ResponseInterface
$this->messenger->addError('Missing recaptcha');
return new RedirectResponse($request->getUri(), 303);
}
- $data['subject'] = $data['subject'] ?: $this->config['application']['name'] . ' Contact';
+
$form->setData($data);
if ($form->isValid()) {
/** @var array $dataForm */
diff --git a/src/Contact/src/Form/ContactForm.php b/src/Contact/src/Form/ContactForm.php
index e3528362..a084e684 100644
--- a/src/Contact/src/Form/ContactForm.php
+++ b/src/Contact/src/Form/ContactForm.php
@@ -8,7 +8,6 @@
use Frontend\Contact\InputFilter\ContactInputFilter;
use Laminas\Form\Element\Csrf;
use Laminas\Form\Element\Email;
-use Laminas\Form\Element\Hidden;
use Laminas\Form\Element\Text;
use Laminas\Form\Element\Textarea;
use Laminas\Form\Form;
@@ -62,18 +61,6 @@ public function init(): void
'type' => Text::class,
]);
- $this->add([
- 'name' => 'subject',
- 'options' => [
- 'label' => 'Subject',
- ],
- 'attributes' => [
- 'placeholder' => 'Subject...',
- 'class' => 'form-control',
- ],
- 'type' => Hidden::class,
- ]);
-
$this->add([
'name' => 'message',
'options' => [
diff --git a/src/Contact/src/InputFilter/ContactInputFilter.php b/src/Contact/src/InputFilter/ContactInputFilter.php
index 6b7c76d8..73a1283f 100644
--- a/src/Contact/src/InputFilter/ContactInputFilter.php
+++ b/src/Contact/src/InputFilter/ContactInputFilter.php
@@ -53,20 +53,6 @@ public function init(): void
], true);
$this->add($name);
- $subject = new Input('subject');
- $subject->setRequired(false);
- $subject->getFilterChain()
- ->attachByName(StringTrim::class)
- ->attachByName(StripTags::class);
- $subject->getValidatorChain()
- ->attachByName(NotEmpty::class, [
- 'message' => 'Subject is required and cannot be empty',
- ], true)
- ->attachByName(StringLength::class, [
- 'max' => 500,
- ], true);
- $this->add($subject);
-
$message = new Input('message');
$message->setRequired(true);
$message->getFilterChain()
diff --git a/src/Contact/src/Service/MessageService.php b/src/Contact/src/Service/MessageService.php
index 33a8b89b..b9b5847c 100644
--- a/src/Contact/src/Service/MessageService.php
+++ b/src/Contact/src/Service/MessageService.php
@@ -34,10 +34,13 @@ public function getRepository(): MessageRepository
public function processMessage(array $data): bool
{
+ $subject = $this->config['contact']['subject'] ?? $this->config['dot_mail']['message_options']['subject']
+ ?: $this->config['application']['name'] . ' Contact';
+
$message = new Message(
$data['email'],
$data['name'],
- $data['subject'],
+ $subject,
$data['message'],
Message::PLATFORM_WEBSITE
);
@@ -57,18 +60,28 @@ public function sendContactMail(Message $message): bool
);
$this->mailService->setSubject($message->getSubject());
- $this->mailService->getMessage()->addFrom(
- $this->config['dot_mail']['default']['message_options']['from'],
- $this->config['dot_mail']['default']['message_options']['from_name']
+
+ $messageConfig = $this->config['dot_mail']['default']['message_options'];
+ $contactSender = $this->config['contact']['message_sender'];
+ $contactRecipients = $this->config['contact']['message_recipients'];
+
+ $this->mailService->getMessage()->setFrom(
+ $contactSender['from_email'] ?: $messageConfig['from'],
+ $contactSender['from_name'] ?: $messageConfig['from_name']
);
- $this->mailService->getMessage()->addTo(
- $this->config['contact']['message_receivers']['to'],
- 'Dotkernel Team'
+
+ $this->mailService->getMessage()->setTo(
+ $contactRecipients['to'] ?: $messageConfig['to'],
+ $contactRecipients['name'] ?: null
);
- $this->mailService->getMessage()->addCC(
- $this->config['contact']['message_receivers']['cc'],
- 'Dotkernel Team'
+
+ $this->mailService->getMessage()->setCc(
+ $contactRecipients['cc'] ?: $messageConfig['cc'],
+ $contactRecipients['name'] ?: null
);
+
+ $this->mailService->getMessage()->setBcc($contactRecipients['bcc'] ?: $messageConfig['bcc']);
+
$this->mailService->getMessage()->setReplyTo($message->getEmail(), $message->getName());
return $this->mailService->send()->isValid();
diff --git a/src/Contact/templates/contact/contact-form.html.twig b/src/Contact/templates/contact/contact-form.html.twig
index 2ffb1a0c..e4a6c376 100644
--- a/src/Contact/templates/contact/contact-form.html.twig
+++ b/src/Contact/templates/contact/contact-form.html.twig
@@ -37,12 +37,6 @@
-
-
- {{ formElement(form.get('subject')) }}
-
-
-
{{ formElement(form.get('message')) }}
diff --git a/src/User/templates/user/profile.html.twig b/src/User/templates/user/profile.html.twig
index d2ad81ab..53dcdf6d 100644
--- a/src/User/templates/user/profile.html.twig
+++ b/src/User/templates/user/profile.html.twig
@@ -22,10 +22,7 @@
diff --git a/test/Unit/App/Twig/Extension/RouteExtensionTest.php b/test/Unit/App/Twig/Extension/RouteExtensionTest.php
new file mode 100644
index 00000000..3f195d96
--- /dev/null
+++ b/test/Unit/App/Twig/Extension/RouteExtensionTest.php
@@ -0,0 +1,78 @@
+createMock(UrlHelper::class);
+ $routeExtension = new RouteExtension($urlHelper);
+ $this->assertInstanceOf(RouteExtension::class, $routeExtension);
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function testWillAddExistingFunctions(): void
+ {
+ $routeExtension = new RouteExtension(
+ $this->createMock(UrlHelper::class)
+ );
+
+ $functions = $routeExtension->getFunctions();
+ $this->assertCount(2, $functions);
+
+ $twigFunction = $functions[0];
+ $this->assertInstanceOf(TwigFunction::class, $twigFunction);
+
+ $callable = $twigFunction->getCallable();
+ $this->assertIsArray($callable);
+ $this->assertCount(2, $callable);
+ $this->assertInstanceOf(RouteExtension::class, $callable[0]);
+ $this->assertTrue(method_exists($routeExtension, $callable[1]));
+ $this->assertSame($twigFunction->getName(), $callable[1]);
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function testWillGetCurrentRoute(): void
+ {
+ $router = $this->createMock(RouterInterface::class);
+ $request = new ServerRequest(uri: new Uri('/test'));
+ $urlHelper = new UrlHelper($router);
+ $urlHelper->setRequest($request);
+ $routeExtension = new RouteExtension($urlHelper);
+ $this->assertSame('/test', $routeExtension->getCurrentRoute());
+ }
+
+ /**
+ * @throws Exception
+ */
+ public function testIsRoute(): void
+ {
+ $router = $this->createMock(RouterInterface::class);
+ $request = new ServerRequest(uri: new Uri('/test'));
+ $urlHelper = new UrlHelper($router);
+ $urlHelper->setRequest($request);
+ $routeExtension = new RouteExtension($urlHelper);
+ $this->assertSame(true, $routeExtension->isRoute('/test'));
+ }
+}
diff --git a/test/Unit/Contact/Form/ContactFormTest.php b/test/Unit/Contact/Form/ContactFormTest.php
index 8d2b251e..4c373029 100644
--- a/test/Unit/Contact/Form/ContactFormTest.php
+++ b/test/Unit/Contact/Form/ContactFormTest.php
@@ -22,7 +22,6 @@ public function testFormHasElements(): void
$this->formHasElements(new ContactForm(), [
'email',
'name',
- 'subject',
'message',
'contactCsrf',
]);
@@ -33,7 +32,6 @@ public function testFormHasInputFilter(): void
$this->formHasInputFilter((new ContactForm())->getInputFilter(), [
'email',
'name',
- 'subject',
'message',
'contactCsrf',
]);
diff --git a/test/Unit/Contact/Service/MessageServiceTest.php b/test/Unit/Contact/Service/MessageServiceTest.php
index e9f7844d..819c2c9a 100644
--- a/test/Unit/Contact/Service/MessageServiceTest.php
+++ b/test/Unit/Contact/Service/MessageServiceTest.php
@@ -44,7 +44,7 @@ public function testProcessMessage(): void
$result = $this->createMock(ResultInterface::class);
$mail = $this->createMock(Email::class);
- $mail->expects($this->once())->method('addFrom')->willReturn($mail);
+ $mail->expects($this->once())->method('setFrom')->willReturn($mail);
$result->expects($this->once())->method('isValid')->willReturn(true);
$mailService->expects($this->once())->method('send')->willReturn($result);
$mailService->expects($this->any())->method('getMessage')->willReturn($mail);
@@ -54,9 +54,16 @@ public function testProcessMessage(): void
$template,
[
'contact' => [
- 'message_receivers' => [
- 'to' => 'test@dotkernel.com',
- 'cc' => 'bcc@dotkernel.com',
+ 'subject' => 'test',
+ 'message_sender' => [
+ 'from_name' => 'Contact',
+ 'from_email' => 'dotkernelContact@test.com',
+ ],
+ 'message_recipients' => [
+ 'name' => 'Dotkernel Team',
+ 'to' => 'test@dotkernel.com',
+ 'cc' => 'cc@dotkernel.com',
+ 'bcc' => 'bcc@dotkernel.com',
],
],
'dot_mail' => [