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' => [