From eda062740c676c456587dabfd10f6d672c0f52c9 Mon Sep 17 00:00:00 2001 From: Samuel Gfeller Date: Mon, 22 Jan 2024 18:53:24 +0100 Subject: [PATCH] Code style, dark mode, and navbar in own template [SLE-192] --- .cs.php | 2 +- README.md | 21 ++++--- config/container.php | 2 + config/functions.php | 4 +- public/assets/general/dark-mode/dark-mode.js | 15 +++-- public/assets/general/general-css/default.css | 1 - .../Ajax/NewPasswordResetSubmitAction.php | 2 +- .../PasswordForgottenEmailSubmitAction.php | 2 +- .../ErrorHandler/DefaultErrorHandler.php | 17 +++++- .../NonFatalErrorHandlerMiddleware.php | 4 +- src/Application/Renderer/RedirectHandler.php | 1 - src/Application/Renderer/TemplateRenderer.php | 2 +- .../Repository/UserRoleFinderRepository.php | 1 + .../Authentication/Service/LoginVerifier.php | 8 +-- src/Domain/Authorization/Privilege.php | 1 - .../ClientPermissionVerifier.php | 4 ++ src/Domain/Client/Service/ClientValidator.php | 2 +- .../Panel/UserFilterChipProvider.php | 4 +- src/Domain/Note/Service/NoteUpdater.php | 1 - .../Repository/EmailLogFinderRepository.php | 1 + .../Security/Service/EmailRequestFinder.php | 1 + .../AuthorizedUserRoleFilterer.php | 2 +- .../Authorization/UserPermissionVerifier.php | 19 +++--- src/Domain/User/Service/UserCreator.php | 2 +- src/Domain/User/Service/UserFinder.php | 8 +-- .../Console/SqlSchemaGenerator.php | 2 +- .../Service/LocaleConfigurator.php | 12 ++-- src/Infrastructure/Service/Mailer.php | 9 +-- templates/layout.html.php | 56 +----------------- templates/layout/navbar.html.php | 58 +++++++++++++++++++ templates/user/user-read.html.php | 1 - tests/Fixture/FixtureInterface.php | 1 + tests/Fixture/NoteFixture.php | 2 +- .../Authentication/LoginSecurityTest.php | 6 +- .../PasswordResetSubmitActionTest.php | 2 +- .../Client/ClientCreateActionTest.php | 6 +- .../Client/ClientListActionTest.php | 12 ++-- .../Client/ClientUpdateActionTest.php | 6 +- .../Integration/User/UserCreateActionTest.php | 1 - tests/Integration/User/UserListActionTest.php | 3 +- .../Integration/User/UserUpdateActionTest.php | 6 +- .../Provider/Client/ClientCreateProvider.php | 1 - tests/Provider/User/UserUpdateProvider.php | 1 - tests/Traits/FixtureTestTrait.php | 1 + .../Security/SecurityEmailCheckerTest.php | 3 + .../Security/SecurityLoginCheckerTest.php | 1 + 46 files changed, 178 insertions(+), 139 deletions(-) create mode 100644 templates/layout/navbar.html.php diff --git a/.cs.php b/.cs.php index 86a602bf..77cc33ec 100644 --- a/.cs.php +++ b/.cs.php @@ -21,7 +21,7 @@ 'array_syntax' => ['syntax' => 'short'], 'cast_spaces' => ['space' => 'none'], 'concat_space' => ['spacing' => 'one'], - 'compact_nullable_typehint' => true, + 'compact_nullable_type_declaration' => true, 'nullable_type_declaration' => true, 'nullable_type_declaration_for_default_null_value' => true, 'declare_equal_normalize' => ['space' => 'single'], diff --git a/README.md b/README.md index bd7235e7..402c3d39 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ This project showcases a real-world-example of a backend and frontend built usin [Slim](https://www.slimframework.com/) micro-framework. The primary goal is to provide a modern codebase with a scalable project structure and -a range of practical feature implementations. +the implementation of a range of practical features. These can serve as learning examples or be adapted for developing new applications. @@ -26,18 +26,18 @@ Project components: * Authentication (login) and authorization (permissions) * Account verification and password reset via email link and token * Protection against rapid fire brute force and password spraying attacks (time throttling and - captcha) - [docs](https://github.com/samuelgfeller/slim-example-project/blob/master/docs/security-concept.md) + captcha) * Localization — English, German and French * Flash messages * Request body and input validation * Template rendering with native PHP syntax * An intuitive method for editing values in the browser using "contenteditable" * Dark theme -* Custom error handler - [docs](https://github.com/samuelgfeller/slim-example-project/blob/master/docs/error-handling.md) -* Integration testing with fixtures and data providers [docs](https://github.com/samuelgfeller/slim-example-project/blob/master/docs/testing/testing-cheatsheet.md) -* Database migrations and seeding [docs](https://github.com/samuelgfeller/slim-example-project/blob/master/docs/cheatsheet.md#database-migrations) +* Custom error handler +* Integration testing with fixtures and data providers +* Database migrations and seeding -Application components demonstrating real-world features as examples: +Application components demonstrating examples for real-world features: * Users with 4 different roles and different permissions * User management for administrators * User activity history @@ -95,9 +95,8 @@ The database is reset every half-hour. **Frontend** * [Template rendering](https://github.com/samuelgfeller/slim-example-project/wiki/Template-rendering) -* [Dark mode - (coming soon)]() -* [JS Modules - (coming soon)]() -* [Ajax - (coming soon)](https://github.com/samuelgfeller/slim-example-project/wiki/Ajax) +* [JavaScript - (coming soon)](https://github.com/samuelgfeller/slim-example-project/wiki/JavaScript) +* [Dark mode](https://github.com/samuelgfeller/slim-example-project/wiki/Dark-Mode) **Other** * [Directory structure](https://github.com/samuelgfeller/slim-example-project/wiki/Directory-structure) @@ -157,6 +156,8 @@ so that it's long living and can be adapted to different needs and preferences. Basically, this is my take on what a modern and efficient web app could look like with today's tech. +## Credits + I worked closely with the software architect [Daniel Opitz](https://odan.github.io/about.html), who also reviewed this project. I learned a lot during @@ -165,8 +166,6 @@ and was inspired by his books, articles, tutorials and his slim [skeleton-project](https://github.com/odan/slim4-skeleton). I'm grateful for his support and the time he took to help me improve this project. -## Credits - Special thanks to [JetBrains](https://jb.gg/OpenSource) for supporting this project. ## Licence diff --git a/config/container.php b/config/container.php index 0f929994..2f08bd90 100644 --- a/config/container.php +++ b/config/container.php @@ -60,6 +60,7 @@ $rotatingFileHandler = new RotatingFileHandler($filename, 0, $level, true, 0777); // The last "true" here tells monolog to remove empty []'s $rotatingFileHandler->setFormatter(new LineFormatter(null, 'Y-m-d H:i:s', false, true)); + return $logger->pushHandler($rotatingFileHandler); }, @@ -153,6 +154,7 @@ SessionInterface::class => function (ContainerInterface $container) { $options = $container->get('settings')['session']; + return new PhpSession($options); }, diff --git a/config/functions.php b/config/functions.php index 2343138f..5cfb143e 100644 --- a/config/functions.php +++ b/config/functions.php @@ -19,13 +19,13 @@ function html(?string $text = null): string * If a context is provided, it is used to replace placeholders * in the translated string. * - * @param string $message The message to be translated. + * @param string $message the message to be translated * @param mixed ...$context Optional elements that should be inserted in the string with placeholders. * The function can be called like this: * __('The %s contains %d monkeys and %d birds.', 'tree', 5, 3); * With the argument unpacking operator ...$context, the arguments are accessible within the function as an array. * - * @return string The translated string. + * @return string the translated string */ function __(string $message, ...$context): string { diff --git a/public/assets/general/dark-mode/dark-mode.js b/public/assets/general/dark-mode/dark-mode.js index 07ed5d0f..0455be1b 100644 --- a/public/assets/general/dark-mode/dark-mode.js +++ b/public/assets/general/dark-mode/dark-mode.js @@ -1,16 +1,16 @@ -// Get the toggle switch element import {submitUpdate} from "../ajax/submit-update-data.js?v=0.4.0"; import {displayFlashMessage} from "../page-component/flash-message/flash-message.js?v=0.4.0"; +// Get the toggle switch element const toggleSwitch = document.querySelector('#dark-mode-toggle-checkbox'); -// Retrieve the current theme from localStorage -const currentTheme = localStorage.getItem('theme') ? localStorage.getItem('theme') : null; - if (toggleSwitch) { // Add event listener to the toggle switch for theme switching toggleSwitch.addEventListener('change', switchTheme, false); + // Retrieve the current theme from localStorage + const currentTheme = localStorage.getItem('theme') ? localStorage.getItem('theme') : null; + // Set the theme based on the stored value from localStorage if (currentTheme) { // Set the data-theme attribute on the html element @@ -45,10 +45,9 @@ function switchTheme(e) { let userId = document.getElementById('user-id').value; submitUpdate({theme: theme}, `users/${userId}`, true) .then(r => { - }) - .catch(r => { - displayFlashMessage('error', 'Failed to change the theme in the database.') - }); + }).catch(r => { + displayFlashMessage('error', 'Failed to change the theme in the database.') + }); } diff --git a/public/assets/general/general-css/default.css b/public/assets/general/general-css/default.css index 21b43b75..1a8955fa 100644 --- a/public/assets/general/general-css/default.css +++ b/public/assets/general/general-css/default.css @@ -10,7 +10,6 @@ --background-accent-3-color: #dcdcdc; --border-accent-2-color: #d5d5d5; --accent-color-when-dark: white; - --translucent-background: rgba(255, 255, 255, 0.8); /* Text */ --primary-text-color: #2e3e50; --secondary-text-color: rgba(46, 62, 80, 0.80); diff --git a/src/Application/Action/Authentication/Ajax/NewPasswordResetSubmitAction.php b/src/Application/Action/Authentication/Ajax/NewPasswordResetSubmitAction.php index b94fbef0..0ee7f2b7 100644 --- a/src/Application/Action/Authentication/Ajax/NewPasswordResetSubmitAction.php +++ b/src/Application/Action/Authentication/Ajax/NewPasswordResetSubmitAction.php @@ -29,9 +29,9 @@ public function __construct( * @param ServerRequest $request * @param Response $response * - * @return Response * @throws \Throwable * + * @return Response */ public function __invoke(ServerRequest $request, Response $response): Response { diff --git a/src/Application/Action/Authentication/Ajax/PasswordForgottenEmailSubmitAction.php b/src/Application/Action/Authentication/Ajax/PasswordForgottenEmailSubmitAction.php index e38b4d62..645d2880 100644 --- a/src/Application/Action/Authentication/Ajax/PasswordForgottenEmailSubmitAction.php +++ b/src/Application/Action/Authentication/Ajax/PasswordForgottenEmailSubmitAction.php @@ -29,9 +29,9 @@ public function __construct( * @param ServerRequest $request * @param Response $response * - * @return Response * @throws \Throwable * + * @return Response */ public function __invoke(ServerRequest $request, Response $response): Response { diff --git a/src/Application/ErrorHandler/DefaultErrorHandler.php b/src/Application/ErrorHandler/DefaultErrorHandler.php index 65498b39..3e79cfef 100644 --- a/src/Application/ErrorHandler/DefaultErrorHandler.php +++ b/src/Application/ErrorHandler/DefaultErrorHandler.php @@ -27,6 +27,12 @@ public function __construct( } /** + * @param ServerRequestInterface $request + * @param Throwable $exception + * @param bool $displayErrorDetails + * @param bool $logErrors + * @param bool $logErrorDetails + * * @throws Throwable * @throws \ErrorException */ @@ -91,6 +97,7 @@ public function __invoke( // The error-details template does not include the default layout, // so the base path to the project root folder is required to load assets $phpRendererAttributes['basePath'] = (new BasePathDetector($request->getServerParams()))->getBasePath(); + // Render template if the template path fails, the default webserver exception is shown return $this->phpRenderer->render($response, 'error/error-details.html.php', $phpRendererAttributes); } @@ -108,6 +115,7 @@ public function __invoke( * Determine http status code. * * @param Throwable $exception The exception + * * @return int The http code */ private function getHttpStatusCode(Throwable $exception): int @@ -136,6 +144,7 @@ private function getHttpStatusCode(Throwable $exception): int * Build the attribute array for the detailed error page. * * @param Throwable $exception + * * @return array */ private function getExceptionDetailsAttributes(Throwable $exception): array @@ -214,6 +223,7 @@ private function getExceptionDetailsAttributes(Throwable $exception): array * This function returns the argument as a string. * * @param mixed $argument + * * @return string */ private function getTraceArgumentAsString(mixed $argument): string @@ -237,6 +247,7 @@ private function getTraceArgumentAsString(mixed $argument): string $result[$key] = $value; } } + // Return the array converted to a string using var_export return var_export($result, true); } @@ -249,8 +260,9 @@ private function getTraceArgumentAsString(mixed $argument): string * Convert the given argument to a string not longer than 15 chars * except if it's a file or a class name. * - * @param mixed $argument The variable to be converted to a string. - * @return string The string representation of the variable. + * @param mixed $argument the variable to be converted to a string + * + * @return string the string representation of the variable */ private function getTraceArgumentAsTruncatedString(mixed $argument): string { @@ -288,6 +300,7 @@ private function getTraceArgumentAsTruncatedString(mixed $argument): string * If a string is 'App\Domain\Example\Class', this function returns 'Class'. * * @param string $string + * * @return string */ private function removeEverythingBeforeLastBackslash(string $string): string diff --git a/src/Application/Middleware/NonFatalErrorHandlerMiddleware.php b/src/Application/Middleware/NonFatalErrorHandlerMiddleware.php index 57cc3f92..6f5dbb4f 100644 --- a/src/Application/Middleware/NonFatalErrorHandlerMiddleware.php +++ b/src/Application/Middleware/NonFatalErrorHandlerMiddleware.php @@ -29,9 +29,9 @@ public function __construct(bool $displayErrorDetails, bool $logErrors, private * @param ServerRequestInterface $request The request * @param RequestHandlerInterface $handler The handler * - * @return ResponseInterface The response * @throws ErrorException * + * @return ResponseInterface The response */ public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface { @@ -59,9 +59,11 @@ function ($severity, $message, $file, $line) { throw new ErrorException($message, 0, $severity, $file, $line); } } + return true; } ); + return $handler->handle($request); } } diff --git a/src/Application/Renderer/RedirectHandler.php b/src/Application/Renderer/RedirectHandler.php index 550a0445..2bbf57df 100644 --- a/src/Application/Renderer/RedirectHandler.php +++ b/src/Application/Renderer/RedirectHandler.php @@ -7,7 +7,6 @@ readonly class RedirectHandler { - public function __construct(private RouteParserInterface $routeParser) { } diff --git a/src/Application/Renderer/TemplateRenderer.php b/src/Application/Renderer/TemplateRenderer.php index 7cf98131..24a44a8f 100644 --- a/src/Application/Renderer/TemplateRenderer.php +++ b/src/Application/Renderer/TemplateRenderer.php @@ -80,9 +80,9 @@ public function renderOnValidationError( * @param array|null $preloadValues * @param array $queryParams same query params passed to page to be added again to form after validation error * - * @return ResponseInterface * @throws \Throwable * + * @return ResponseInterface */ public function respondWithFormThrottle( ResponseInterface $response, diff --git a/src/Domain/Authentication/Repository/UserRoleFinderRepository.php b/src/Domain/Authentication/Repository/UserRoleFinderRepository.php index 86745663..6c7848c1 100644 --- a/src/Domain/Authentication/Repository/UserRoleFinderRepository.php +++ b/src/Domain/Authentication/Repository/UserRoleFinderRepository.php @@ -69,6 +69,7 @@ public function getRoleHierarchyByUserId(int $userId): int ->leftJoin('user_role', ['user.user_role_id = user_role.id']) ->where(['user.deleted_at IS' => null, 'user.id' => $userId]); $roleResultRow = $query->execute()->fetch('assoc'); + // If no role found, return highest hierarchy which means lowest privileged role return (int)($roleResultRow['hierarchy'] ?? 1000); } diff --git a/src/Domain/Authentication/Service/LoginVerifier.php b/src/Domain/Authentication/Service/LoginVerifier.php index 5630792d..cb754110 100644 --- a/src/Domain/Authentication/Service/LoginVerifier.php +++ b/src/Domain/Authentication/Service/LoginVerifier.php @@ -28,12 +28,12 @@ public function __construct( * * @param array $userLoginValues An associative array containing the user's login credentials. * Expected keys are 'email' and 'password' and optionally 'g-recaptcha-response'. - * @param array $queryParams An associative array containing any additional query parameters. + * @param array $queryParams an associative array containing any additional query parameters * - * @throws TransportExceptionInterface If an error occurs while sending an email to a non-active user. - * @throws InvalidCredentialsException If the user does not exist or the password is incorrect. + * @throws TransportExceptionInterface if an error occurs while sending an email to a non-active user + * @throws InvalidCredentialsException if the user does not exist or the password is incorrect * - * @return int The ID of the user if the login is successful. + * @return int the ID of the user if the login is successful */ public function verifyLoginAndGetUserId(array $userLoginValues, array $queryParams = []): int { diff --git a/src/Domain/Authorization/Privilege.php b/src/Domain/Authorization/Privilege.php index c2e2bcae..e3afd314 100644 --- a/src/Domain/Authorization/Privilege.php +++ b/src/Domain/Authorization/Privilege.php @@ -23,7 +23,6 @@ enum Privilege // Allowed to Read, Create, Update and Delete case CRUD; - // Initially, the Privilege Enum was the datatype in result objects that was passed to the PHP templates. // The case names were the name of the highest privilege (Read, Create, Update, Delete). // The values were the letters of the associated permissions meaning Delete was 'CRUD', Update was 'CRU' and so on. diff --git a/src/Domain/Client/Service/Authorization/ClientPermissionVerifier.php b/src/Domain/Client/Service/Authorization/ClientPermissionVerifier.php index 382ed17b..61e06f8b 100644 --- a/src/Domain/Client/Service/Authorization/ClientPermissionVerifier.php +++ b/src/Domain/Client/Service/Authorization/ClientPermissionVerifier.php @@ -41,6 +41,7 @@ public function isGrantedToCreate(?ClientData $client = null): bool 'loggedInUserId not set while isGrantedToCreate authorization check $client: ' . json_encode($client, JSON_PARTIAL_OUTPUT_ON_ERROR) ); + return false; } $authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId( @@ -89,6 +90,7 @@ public function isGrantedToAssignUserToClient( 'loggedInUserId not set while isGrantedToAssignUserToClient authorization check $assignedUserId: ' . $assignedUserId ); + return false; } @@ -136,6 +138,7 @@ public function isGrantedToUpdate(array $clientDataToUpdate, ?int $ownerId, bool 'loggedInUserId not set while isGrantedToUpdate authorization check $clientDataToUpdate: ' . json_encode($clientDataToUpdate, JSON_PARTIAL_OUTPUT_ON_ERROR) ); + return false; } $authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId( @@ -217,6 +220,7 @@ public function isGrantedToDelete(?int $ownerId, bool $log = true): bool { if (!$this->loggedInUserId) { $this->logger->error('loggedInUserId not set while isGrantedToDelete authorization check'); + return false; } $authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId( diff --git a/src/Domain/Client/Service/ClientValidator.php b/src/Domain/Client/Service/ClientValidator.php index 45e947f1..2e9199ee 100644 --- a/src/Domain/Client/Service/ClientValidator.php +++ b/src/Domain/Client/Service/ClientValidator.php @@ -120,4 +120,4 @@ public function validateClientValues(array $clientCreationValues, bool $isCreate throw new ValidationException($errors); } } -} \ No newline at end of file +} diff --git a/src/Domain/Dashboard/Panel/UserFilterChipProvider.php b/src/Domain/Dashboard/Panel/UserFilterChipProvider.php index c36c8c0a..f5fcd8ff 100644 --- a/src/Domain/Dashboard/Panel/UserFilterChipProvider.php +++ b/src/Domain/Dashboard/Panel/UserFilterChipProvider.php @@ -31,7 +31,7 @@ public function getUserFilterChipsHtml(): string $filters = $this->getActiveAndInactiveUserFilters(); $activeFilterChips = ''; foreach ($filters['active'] as $filterCategory => $filtersInCategory) { - /** @var \App\Domain\FilterSetting\Data\FilterData $filterData */ + /** @var FilterData $filterData */ foreach ($filtersInCategory as $filterId => $filterData) { $activeFilterChips .= "
\n $filtersInCategory) { $inactiveFilterChips .= "$filterCategory"; - /** @var \App\Domain\FilterSetting\Data\FilterData $filterData */ + /** @var FilterData $filterData */ foreach ($filtersInCategory as $filterId => $filterData) { $inactiveFilterChips .= "
userActivityLogger->logUserActivity(UserActivity::UPDATED, 'note', $noteId, $updateData); - return $updated; } diff --git a/src/Domain/Security/Repository/EmailLogFinderRepository.php b/src/Domain/Security/Repository/EmailLogFinderRepository.php index 89ff1d50..c6ed1150 100644 --- a/src/Domain/Security/Repository/EmailLogFinderRepository.php +++ b/src/Domain/Security/Repository/EmailLogFinderRepository.php @@ -19,6 +19,7 @@ public function __construct( * @param int $seconds * Throws PersistenceRecordNotFoundException if entry not found * @param int|null $userId + * * @return int */ public function getLoggedEmailCountInTimespan(string $email, int $seconds, ?int $userId): int diff --git a/src/Domain/Security/Service/EmailRequestFinder.php b/src/Domain/Security/Service/EmailRequestFinder.php index b3532234..06b02347 100644 --- a/src/Domain/Security/Service/EmailRequestFinder.php +++ b/src/Domain/Security/Service/EmailRequestFinder.php @@ -21,6 +21,7 @@ public function __construct( * * @param string $email * @param int|null $userId + * * @return int */ public function findEmailAmountInSetTimespan(string $email, ?int $userId): int diff --git a/src/Domain/User/Service/Authorization/AuthorizedUserRoleFilterer.php b/src/Domain/User/Service/Authorization/AuthorizedUserRoleFilterer.php index a0fc9227..b598d508 100644 --- a/src/Domain/User/Service/Authorization/AuthorizedUserRoleFilterer.php +++ b/src/Domain/User/Service/Authorization/AuthorizedUserRoleFilterer.php @@ -44,4 +44,4 @@ public function filterAuthorizedUserRoles(?int $attributedUserRoleId = null): ar return $grantedCreateUserRoles; } -} \ No newline at end of file +} diff --git a/src/Domain/User/Service/Authorization/UserPermissionVerifier.php b/src/Domain/User/Service/Authorization/UserPermissionVerifier.php index 3921c5ce..fbc260a1 100644 --- a/src/Domain/User/Service/Authorization/UserPermissionVerifier.php +++ b/src/Domain/User/Service/Authorization/UserPermissionVerifier.php @@ -39,6 +39,7 @@ public function isGrantedToCreate(array $userValues): bool 'loggedInUserId not set while authorization check isGrantedToCreate: ' . json_encode($userValues, JSON_PARTIAL_OUTPUT_ON_ERROR) ); + return false; } $authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId( @@ -53,11 +54,11 @@ public function isGrantedToCreate(array $userValues): bool if ($authenticatedUserRoleHierarchy <= $userRoleHierarchies[UserRole::MANAGING_ADVISOR->value]) { // Managing advisors can do everything with users except setting a role higher than advisor if ($this->userRoleIsGranted( - $userValues['user_role_id'] ?? null, - null, - $authenticatedUserRoleHierarchy, - $userRoleHierarchies - ) === true + $userValues['user_role_id'] ?? null, + null, + $authenticatedUserRoleHierarchy, + $userRoleHierarchies + ) === true ) { return true; } @@ -99,6 +100,7 @@ public function userRoleIsGranted( 'loggedInUserId not set while authorization check that user role is granted $userRoleIdOfUserToMutate: ' . $userRoleIdOfUserToMutate ); + return false; } // $authenticatedUserRoleData and $userRoleHierarchies passed as arguments if called inside this class @@ -157,6 +159,7 @@ public function isGrantedToUpdate(array $userDataToUpdate, string|int $userIdToU 'loggedInUserId not while user update authorization check' . json_encode($userDataToUpdate, JSON_PARTIAL_OUTPUT_ON_ERROR) ); + return false; } $grantedUpdateKeys = []; @@ -222,7 +225,6 @@ public function isGrantedToUpdate(array $userDataToUpdate, string|int $userIdToU // Owner user (profile edit) is not allowed to change its user role or status } - // Check that the data that the user wanted to update is in $grantedUpdateKeys array foreach ($userDataToUpdate as $key => $value) { // If at least one array key doesn't exist in $grantedUpdateKeys it means that user is not permitted @@ -237,6 +239,7 @@ public function isGrantedToUpdate(array $userDataToUpdate, string|int $userIdToU return false; } } + // All keys in $userDataToUpdate are in $grantedUpdateKeys return true; } @@ -258,6 +261,7 @@ public function isGrantedToDelete( 'loggedInUserId not set while authorization check isGrantedToDelete $userIdToDelete: ' . $userIdToDelete ); + return false; } $authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId( @@ -278,7 +282,6 @@ public function isGrantedToDelete( return true; } - if ($log === true) { $this->logger->notice( 'User ' . $this->loggedInUserId . ' tried to delete user but isn\'t allowed.' @@ -303,6 +306,7 @@ public function isGrantedToRead(?int $userIdToRead = null, bool $log = true): bo 'loggedInUserId not set while authorization check isGrantedToRead $userIdToRead: ' . $userIdToRead ); + return false; } $authenticatedUserRoleHierarchy = $this->userRoleFinderRepository->getRoleHierarchyByUserId( @@ -345,6 +349,7 @@ public function isGrantedToReadUserActivity( 'loggedInUserId not set while authorization check isGrantedToReadUserActivity $userIdToRead: ' . $userIdToRead ); + return false; } diff --git a/src/Domain/User/Service/UserCreator.php b/src/Domain/User/Service/UserCreator.php index 6f53d0ff..e500b2cc 100644 --- a/src/Domain/User/Service/UserCreator.php +++ b/src/Domain/User/Service/UserCreator.php @@ -36,9 +36,9 @@ public function __construct( * @param array $userValues * @param array $queryParams query params that should be added to email verification link (e.g. redirect) * - * @return int|bool insert id, false if user already exists * @throws TransportExceptionInterface|\JsonException|\Exception * + * @return int|bool insert id, false if user already exists */ public function createUser(array $userValues, array $queryParams = []): bool|int { diff --git a/src/Domain/User/Service/UserFinder.php b/src/Domain/User/Service/UserFinder.php index 9976b114..b96b9cf7 100644 --- a/src/Domain/User/Service/UserFinder.php +++ b/src/Domain/User/Service/UserFinder.php @@ -45,9 +45,9 @@ public function findAllUsersResultDataForList(): array (int)$userResultData->id, 'status', ); - // Personal info privilege like first name, email and so on no needed for list - // $userResultData->generalPrivilege = $this->userPermissionVerifier->getUpdatePrivilegeForUserColumn( - // 'personal_info', $userResultData->id ); + // Personal info privilege like first name, email and so on no needed for list + // $userResultData->generalPrivilege = $this->userPermissionVerifier->getUpdatePrivilegeForUserColumn( + // 'personal_info', $userResultData->id ); } else { unset($userResultArray[$key]); } @@ -72,9 +72,9 @@ public function findUserById(string|int|null $id): UserData * * @param int $id * - * @return UserResultData * @throws \Exception * + * @return UserResultData */ public function findUserReadResult(int $id): UserResultData { diff --git a/src/Infrastructure/Console/SqlSchemaGenerator.php b/src/Infrastructure/Console/SqlSchemaGenerator.php index 5b88edd1..87e5460b 100644 --- a/src/Infrastructure/Console/SqlSchemaGenerator.php +++ b/src/Infrastructure/Console/SqlSchemaGenerator.php @@ -60,9 +60,9 @@ public function generateSqlSchema(): int * * @param string $sql The sql * - * @return PDOStatement The statement * @throws UnexpectedValueException * + * @return PDOStatement The statement */ private function query(string $sql): PDOStatement { diff --git a/src/Infrastructure/Service/LocaleConfigurator.php b/src/Infrastructure/Service/LocaleConfigurator.php index e5f25f19..b9ca5ffd 100644 --- a/src/Infrastructure/Service/LocaleConfigurator.php +++ b/src/Infrastructure/Service/LocaleConfigurator.php @@ -16,13 +16,13 @@ public function __construct(Settings $settings) /** * Sets the locale and language settings for the application. * - * @param string|null|false $locale The locale or language code (e.g. 'en_US' or 'en'). + * @param string|false|null $locale The locale or language code (e.g. 'en_US' or 'en'). * If null or false, the default locale from the settings is used. - * @param string $domain The text domain (default 'messages') for gettext translations. + * @param string $domain the text domain (default 'messages') for gettext translations * - * @return false|string The new locale string, or false on failure. + * @throws \UnexpectedValueException if the locale is not 'en_US' and no translation file exists for the locale * - * @throws \UnexpectedValueException If the locale is not 'en_US' and no translation file exists for the locale. + * @return false|string the new locale string, or false on failure */ public function setLanguage(string|null|false $locale, string $domain = 'messages'): bool|string { @@ -81,6 +81,7 @@ public function getLanguageCodeForPath(): string * if the language is not available. * * @param false|string|null $locale + * * @return string */ private function getAvailableLocale(null|false|string $locale): string @@ -104,6 +105,7 @@ private function getAvailableLocale(null|false|string $locale): string } // Get the language code from the "target" locale $localeLanguageCode = $this->getLanguageCodeFromLocale($locale); + // Take the locale from the same language if available or the default one return $localesMappedByLanguage[$localeLanguageCode] ?? $this->localeSettings['default'] ?? 'en_US'; } @@ -112,6 +114,7 @@ private function getAvailableLocale(null|false|string $locale): string * Get the language code part of a locale. * * @param string|false|null $locale e.g. 'en_US' + * * @return string|null e.g. 'en' */ private function getLanguageCodeFromLocale(string|null|false $locale): ?string @@ -120,6 +123,7 @@ private function getLanguageCodeFromLocale(string|null|false $locale): ?string if ($locale && str_contains($locale, '-')) { $locale = str_replace('-', '_', $locale); } + // The language code is the first part of the locale string return $locale ? explode('_', $locale)[0] : null; } diff --git a/src/Infrastructure/Service/Mailer.php b/src/Infrastructure/Service/Mailer.php index f69d72d5..8eefe800 100644 --- a/src/Infrastructure/Service/Mailer.php +++ b/src/Infrastructure/Service/Mailer.php @@ -47,10 +47,11 @@ public function getContentFromTemplate(string $templatePath, array $templateData } /** - * Send and log email - * - * @param Email $email - * @return void + * Send and log email. + * + * @param Email $email + * + * @return void */ public function send(Email $email): void { diff --git a/templates/layout.html.php b/templates/layout.html.php index 0ec6b924..1cfa01fa 100644 --- a/templates/layout.html.php +++ b/templates/layout.html.php @@ -54,7 +54,7 @@ const theme = localStorage.getItem('theme') ? localStorage.getItem('theme') : null; // Get the theme provided from the server via query param (only after login) const themeParam = new URLSearchParams(window.location.search).get('theme'); - // Finally add the theme to the element + // Finally, add the theme to the element document.documentElement.setAttribute('data-theme', themeParam ?? theme ?? 'light'); // If a theme from the database is provided and not the same with localStorage, replace it if (themeParam && themeParam !== theme) { @@ -75,58 +75,8 @@ - - fetch('layout/navbar.html.php', []); } ?>
diff --git a/templates/layout/navbar.html.php b/templates/layout/navbar.html.php new file mode 100644 index 00000000..f1fa7d7c --- /dev/null +++ b/templates/layout/navbar.html.php @@ -0,0 +1,58 @@ + + \ No newline at end of file diff --git a/templates/user/user-read.html.php b/templates/user/user-read.html.php index f51b9e0e..f5a164b3 100644 --- a/templates/user/user-read.html.php +++ b/templates/user/user-read.html.php @@ -131,7 +131,6 @@ class="contenteditable-edit-icon cursor-pointer"
- sun
diff --git a/tests/Fixture/FixtureInterface.php b/tests/Fixture/FixtureInterface.php index 555c1902..2f643050 100644 --- a/tests/Fixture/FixtureInterface.php +++ b/tests/Fixture/FixtureInterface.php @@ -4,6 +4,7 @@ /** * Fixture classes contain the properties $table and $records. + * * @property string $table * @property array $records */ diff --git a/tests/Fixture/NoteFixture.php b/tests/Fixture/NoteFixture.php index 6ae7dc6c..c3d740dc 100644 --- a/tests/Fixture/NoteFixture.php +++ b/tests/Fixture/NoteFixture.php @@ -3,7 +3,7 @@ namespace App\Test\Fixture; /** - * Note values that can be inserted into the database + * Note values that can be inserted into the database. */ class NoteFixture implements FixtureInterface { diff --git a/tests/Integration/Authentication/LoginSecurityTest.php b/tests/Integration/Authentication/LoginSecurityTest.php index 636e339f..2559193a 100644 --- a/tests/Integration/Authentication/LoginSecurityTest.php +++ b/tests/Integration/Authentication/LoginSecurityTest.php @@ -28,10 +28,10 @@ class LoginSecurityTest extends TestCase * Test thresholds and according delays of login failures * If login request amount exceeds threshold, the user has to wait a certain delay. * - * @return void * @throws NotFoundExceptionInterface - * * @throws ContainerExceptionInterface + * + * @return void */ public function testLoginThrottlingWrongCredentials(): void { @@ -46,7 +46,7 @@ public function testLoginThrottlingWrongCredentials(): void $user = $this->insertFixtureWithAttributes(new UserFixture(), [ 'email' => $email, 'password_hash' => password_hash($password, PASSWORD_DEFAULT), - ],); + ], ); // Login request body with invalid credentials $loginRequestBody = ['email' => 'wrong@email.com', 'password' => 'wrong_password']; diff --git a/tests/Integration/Authentication/PasswordResetSubmitActionTest.php b/tests/Integration/Authentication/PasswordResetSubmitActionTest.php index ea8a9e9a..011fd4aa 100644 --- a/tests/Integration/Authentication/PasswordResetSubmitActionTest.php +++ b/tests/Integration/Authentication/PasswordResetSubmitActionTest.php @@ -42,7 +42,7 @@ public function testResetPasswordSubmit(UserVerificationData $verification, stri { $newPassword = 'new password'; // Insert user - $userRow = $this->insertFixtureWithAttributes(new UserFixture(), ['id' => $verification->userId],); + $userRow = $this->insertFixtureWithAttributes(new UserFixture(), ['id' => $verification->userId]); $this->insertFixture('user_verification', $verification->toArrayForDatabase()); diff --git a/tests/Integration/Client/ClientCreateActionTest.php b/tests/Integration/Client/ClientCreateActionTest.php index 45b4478d..50883c94 100644 --- a/tests/Integration/Client/ClientCreateActionTest.php +++ b/tests/Integration/Client/ClientCreateActionTest.php @@ -45,9 +45,9 @@ class ClientCreateActionTest extends TestCase * @param array $authenticatedUserRow authenticated user attributes containing the user_role_id * @param array $expectedResult HTTP status code, bool if db_entry_created and json_response * - * @return void * @throws \JsonException|ContainerExceptionInterface|NotFoundExceptionInterface * + * @return void */ public function testClientSubmitCreateActionAuthorization( ?array $userLinkedToClientRow, @@ -156,9 +156,9 @@ public function testClientSubmitCreateActionAuthorization( * @param array $requestBody * @param array $jsonResponse * - * @return void * @throws ContainerExceptionInterface|NotFoundExceptionInterface * + * @return void */ public function testClientSubmitCreateActionInvalid(array $requestBody, array $jsonResponse): void { @@ -210,9 +210,9 @@ public function testClientSubmitCreateActionInvalid(array $requestBody, array $j * * @param array $requestBody * - * @return void * @throws ContainerExceptionInterface|NotFoundExceptionInterface * + * @return void */ public function testClientSubmitCreateActionValid(array $requestBody): void { diff --git a/tests/Integration/Client/ClientListActionTest.php b/tests/Integration/Client/ClientListActionTest.php index 2cbce44d..ba3f3c99 100644 --- a/tests/Integration/Client/ClientListActionTest.php +++ b/tests/Integration/Client/ClientListActionTest.php @@ -52,10 +52,10 @@ class ClientListActionTest extends TestCase /** * Normal page action while having an active session. * - * @return void * @throws NotFoundExceptionInterface - * * @throws ContainerExceptionInterface + * + * @return void */ public function testClientListPageActionAuthorization(): void { @@ -105,10 +105,10 @@ public function testClientListPageActionUnauthenticated(): void * @param array $usersToInsert * @param array $clientStatusesToInsert * - * @return void * @throws NotFoundExceptionInterface - * * @throws ContainerExceptionInterface + * + * @return void */ public function testClientListWithFilterAction( array $filterQueryParamsArr, @@ -201,10 +201,10 @@ public function testClientListWithFilterAction( * @param array $queryParams Filter as GET paramets * @param array $expectedBody Expected response body * - * @return void * @throws NotFoundExceptionInterface - * * @throws ContainerExceptionInterface + * + * @return void */ public function testClientListActionInvalidFilters(array $queryParams, array $expectedBody): void { diff --git a/tests/Integration/Client/ClientUpdateActionTest.php b/tests/Integration/Client/ClientUpdateActionTest.php index 676d5824..3646b4b8 100644 --- a/tests/Integration/Client/ClientUpdateActionTest.php +++ b/tests/Integration/Client/ClientUpdateActionTest.php @@ -51,9 +51,9 @@ class ClientUpdateActionTest extends TestCase * @param array $requestData array of data for the request body * @param array $expectedResult HTTP status code, bool if db_entry_created and json_response * - * @return void * @throws \JsonException|ContainerExceptionInterface|NotFoundExceptionInterface * + * @return void */ public function testClientSubmitUpdateActionAuthorization( array $userLinkedToClientRow, @@ -162,9 +162,9 @@ public function testClientSubmitUpdateActionAuthorization( * @param array $requestBody * @param array $jsonResponse * - * @return void * @throws ContainerExceptionInterface|NotFoundExceptionInterface * + * @return void */ public function testClientSubmitUpdateActionInvalid(array $requestBody, array $jsonResponse): void { @@ -228,9 +228,9 @@ public function testClientSubmitUpdateActionUnauthenticated(): void * Test that if user makes update request but the content has not changed * compared to what's in the database, the response contains the warning. * - * @return void * @throws ContainerExceptionInterface|NotFoundExceptionInterface * + * @return void */ public function testClientSubmitUpdateActionUnchangedContent(): void { diff --git a/tests/Integration/User/UserCreateActionTest.php b/tests/Integration/User/UserCreateActionTest.php index 032b7e6f..495b37d0 100644 --- a/tests/Integration/User/UserCreateActionTest.php +++ b/tests/Integration/User/UserCreateActionTest.php @@ -172,7 +172,6 @@ public function testUserSubmitCreateInvalid(array $requestBody, array $jsonRespo // even if it's an empty string self::assertSame(StatusCodeInterface::STATUS_UNPROCESSABLE_ENTITY, $response->getStatusCode()); - // Database must be unchanged - only one row (authenticated user) expected in user table $this->assertTableRowCount(1, 'user'); $this->assertJsonData($jsonResponse, $response); diff --git a/tests/Integration/User/UserListActionTest.php b/tests/Integration/User/UserListActionTest.php index 32d18a44..ffb1a828 100644 --- a/tests/Integration/User/UserListActionTest.php +++ b/tests/Integration/User/UserListActionTest.php @@ -36,9 +36,10 @@ class UserListActionTest extends TestCase * @param array $authenticatedUserRow authenticated user attributes containing the user_role_id * @param array $expectedResult HTTP status code and privilege * - * @return void * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface + * + * @return void */ public function testUserListAuthorization( array $userRow, diff --git a/tests/Integration/User/UserUpdateActionTest.php b/tests/Integration/User/UserUpdateActionTest.php index 8780262d..a6071c5b 100644 --- a/tests/Integration/User/UserUpdateActionTest.php +++ b/tests/Integration/User/UserUpdateActionTest.php @@ -43,11 +43,11 @@ class UserUpdateActionTest extends TestCase * @param array $requestData array of data for the request body * @param array $expectedResult HTTP status code, bool if db_entry_created and json_response * - * @return void * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface - * * @throws \JsonException + * + * @return void */ public function testUserSubmitUpdateAuthorization( array $userToChangeRow, @@ -131,7 +131,7 @@ public function testUserSubmitUpdateInvalid(array $requestBody, array $jsonRespo { // Insert user that is allowed to change content (advisor owner) $userRow = $this->insertFixtureWithAttributes( - // Replace user_role_id enum case with database id with AuthorizationTestTrait function addUserRoleId() + // Replace user_role_id enum case with database id with AuthorizationTestTrait function addUserRoleId() new UserFixture(), $this->addUserRoleId(['user_role_id' => UserRole::ADVISOR]), ); diff --git a/tests/Provider/Client/ClientCreateProvider.php b/tests/Provider/Client/ClientCreateProvider.php index 49089fa8..9798c231 100644 --- a/tests/Provider/Client/ClientCreateProvider.php +++ b/tests/Provider/Client/ClientCreateProvider.php @@ -7,7 +7,6 @@ class ClientCreateProvider { - /** * Provides test cases for client creation dropdown options. * diff --git a/tests/Provider/User/UserUpdateProvider.php b/tests/Provider/User/UserUpdateProvider.php index d7abc3cf..d0c4748c 100644 --- a/tests/Provider/User/UserUpdateProvider.php +++ b/tests/Provider/User/UserUpdateProvider.php @@ -7,7 +7,6 @@ class UserUpdateProvider { - /** * @return array[] */ diff --git a/tests/Traits/FixtureTestTrait.php b/tests/Traits/FixtureTestTrait.php index 25f46634..efd52540 100644 --- a/tests/Traits/FixtureTestTrait.php +++ b/tests/Traits/FixtureTestTrait.php @@ -16,6 +16,7 @@ trait FixtureTestTrait * @param array $attributes attributes to override in the fixture * Format: ['field_name' => 'expected_value', 'other_field_name' => 'other expected value', ] -> one insert * alternatively [['field_name' => 'expected_value'], ['field_name' => 'expected_value'], ] -> two insets + * * @return array inserted row values */ protected function insertFixtureWithAttributes(FixtureInterface $fixture, array $attributes = []): array diff --git a/tests/Unit/Security/SecurityEmailCheckerTest.php b/tests/Unit/Security/SecurityEmailCheckerTest.php index c7757600..00086918 100644 --- a/tests/Unit/Security/SecurityEmailCheckerTest.php +++ b/tests/Unit/Security/SecurityEmailCheckerTest.php @@ -31,11 +31,13 @@ class SecurityEmailCheckerTest extends TestCase * * The Data Provider calls this function with all the different variation of email * request amounts where an exception must be thrown. + * * @dataProvider \App\Test\Provider\Security\EmailRequestProvider::individualEmailThrottlingTestCases() * * @param int|string $delay * @param int $emailLogAmountInTimeSpan * @param array $securitySettings + * * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ @@ -91,6 +93,7 @@ public function testPerformEmailAbuseCheckIndividual( * @param int $todayEmailAmount too many emails for today * @param int $thisMonthEmailAmount too many emails for this month * @param array $securitySettings + * * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */ diff --git a/tests/Unit/Security/SecurityLoginCheckerTest.php b/tests/Unit/Security/SecurityLoginCheckerTest.php index 1d88d542..21b915ca 100644 --- a/tests/Unit/Security/SecurityLoginCheckerTest.php +++ b/tests/Unit/Security/SecurityLoginCheckerTest.php @@ -50,6 +50,7 @@ class SecurityLoginCheckerTest extends TestCase * logins_by_ip: array{successes: int, failures: int}, * } $ipAndEmailLogSummary * @param array $securitySettings + * * @throws ContainerExceptionInterface * @throws NotFoundExceptionInterface */