diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache index 15d634d..54d94c8 100644 --- a/.php-cs-fixer.cache +++ b/.php-cs-fixer.cache @@ -1 +1 @@ -{"php":"8.3.6","version":"3.89.1:v3.89.1#f34967da2866ace090a2b447de1f357356474573","indent":" ","lineEnding":"\n","rules":{"binary_operator_spaces":{"default":"at_least_single_space"},"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"braces_position":{"allow_single_line_empty_anonymous_classes":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_type_declaration":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"modifier_keywords":true,"new_with_parentheses":{"anonymous_class":true},"no_blank_lines_after_class_opening":true,"no_extra_blank_lines":{"tokens":["use"]},"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"none"},"return_type_declaration":true,"short_scalar_cast":true,"single_import_per_statement":{"group_to_single_imports":false},"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","const_import","do","else","elseif","final","finally","for","foreach","function","function_import","if","insteadof","interface","namespace","new","private","protected","public","static","switch","trait","try","use","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"unary_operator_spaces":{"only_dec_inc":true},"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":{"closure_fn_spacing":"one"},"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"after_heredoc":false,"attribute_placement":"ignore","on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true,"strict_param":true,"declare_strict_types":true,"no_unused_imports":true,"single_quote":true},"hashes":{"templates\/header.php":"f4b64c4ecb4dadec166cb7935309096a","templates\/footer.php":"3111b4701ea698ba11c3423260657e28","public\/login\/oidc.php":"8ec86d6f3af33f64d586109ec17f817d","public\/login\/config.php":"5b7b3e2937b349c76a2fd239c3ae06f8","public\/login\/magic.php":"54ef6b7ef80e608905e64e4fa8539846","public\/login\/index.php":"063d7b997bf8292d2b3f8c34dae3252f","public\/route.php":"f35dadd27dbdea162b67c42002519be9","public\/oidc\/callback.php":"793ff84451299c9984ac4742f02ca842","public\/oidc\/start.php":"87ddb61a0ef796d7303709ffa741c9c7","public\/oidc\/me.php":"d0439342011bb0e58ef8738b3b81cc2f","public\/index.php":"73a917520ea547ae8a122bd90098bf46","src\/Infrastructure\/DbAdapter.php":"3899a835130c146e2d30dbcca88d8f33","src\/Infrastructure\/Database.php":"6f2848ed70b29d9c2e2d259be611b9b0","src\/Infrastructure\/Session.php":"3538a1147cc81678c470d45ea8574a95","src\/Domain\/User.php":"02213454f7edf43f4afae3f2f81aaf01","src\/Http\/Csrf.php":"55631812cab4b1192f8e30c5d35fd5eb","src\/FileManager.php":"a51dda44f293f238aea295fd56b2fa99","src\/PostManager.php":"25f0179c4d96e9aa04218d54bf45a029","src\/helpers.php":"3a83a4872b1e3e3c58898b54f51e72b4","src\/db.php":"8888b7fbc9740eb3c60dd2374d0cb5d6","src\/auth.php":"d237017d90091f5fb75a133d08d5e544","src\/ConfigRepo.php":"c2dcee160a272d27725d480a90e76dcf","src\/Parsedown.php":"85da2b47eca1a703fdfe44753bf912df","src\/Service\/MailQueue.php":"7e040056aec64cfd780e3c9e7d04748a","src\/Service\/Validator.php":"7c267b8b9f3f1bac0f2520dd10364831","src\/Service\/UiFormRenderer.php":"065617191c6d680ce97588f4fa159688","src\/Service\/AuthService.php":"51c714164c6bd453154c024d9aa814e9","src\/Service\/MailService.php":"e1ef847a70551ae8887b86b3fb4167d0","src\/mailer.php":"17e6b19103c880cc9a6c6634486506c2","src\/Repository\/DictionnaryRepository.php":"f937e98cf0f27b59ae00e430b52a586d","src\/Repository\/ProfileRepository.php":"b1cd483652500ee4e2aaaa9e0330ff1d","src\/Repository\/UserRepository.php":"d75fed70910d0e6450a5b6c8ce9608c2","versions.php":"51a72261e1a507d3435b4a24e5f5fc09","config\/config.php":"a8b7698b01ab9b40eea655e8fcc194fc","templates\/post_form.php":"32765f286dc2fc2f9d9790fa3a94bef5","templates\/post_view.php":"64094ca90fd3d03bb9615bb3772bc189","templates\/layout.php":"d15f6cfe941b8ee69b2add5af6d3ffab"}} \ No newline at end of file +{"php":"8.3.6","version":"3.89.1:v3.89.1#f34967da2866ace090a2b447de1f357356474573","indent":" ","lineEnding":"\n","rules":{"binary_operator_spaces":{"default":"at_least_single_space"},"blank_line_after_opening_tag":true,"blank_line_between_import_groups":true,"blank_lines_before_namespace":true,"braces_position":{"allow_single_line_empty_anonymous_classes":true},"class_definition":{"inline_constructor_arguments":false,"space_before_parenthesis":true},"compact_nullable_type_declaration":true,"declare_equal_normalize":true,"lowercase_cast":true,"lowercase_static_reference":true,"modifier_keywords":true,"new_with_parentheses":{"anonymous_class":true},"no_blank_lines_after_class_opening":true,"no_extra_blank_lines":{"tokens":["use"]},"no_leading_import_slash":true,"no_whitespace_in_blank_line":true,"ordered_class_elements":{"order":["use_trait"]},"ordered_imports":{"imports_order":["class","function","const"],"sort_algorithm":"none"},"return_type_declaration":true,"short_scalar_cast":true,"single_import_per_statement":{"group_to_single_imports":false},"single_space_around_construct":{"constructs_followed_by_a_single_space":["abstract","as","case","catch","class","const_import","do","else","elseif","final","finally","for","foreach","function","function_import","if","insteadof","interface","namespace","new","private","protected","public","static","switch","trait","try","use","use_lambda","while"],"constructs_preceded_by_a_single_space":["as","else","elseif","use_lambda"]},"single_trait_insert_per_statement":true,"ternary_operator_spaces":true,"unary_operator_spaces":{"only_dec_inc":true},"blank_line_after_namespace":true,"constant_case":true,"control_structure_braces":true,"control_structure_continuation_position":true,"elseif":true,"function_declaration":{"closure_fn_spacing":"one"},"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"after_heredoc":false,"attribute_placement":"ignore","on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_multiple_statements_per_line":true,"no_space_around_double_colon":true,"no_spaces_after_function_name":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_line_after_imports":true,"spaces_inside_parentheses":true,"statement_indentation":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"encoding":true,"full_opening_tag":true,"strict_param":true,"declare_strict_types":true,"no_unused_imports":true,"single_quote":true},"hashes":{"templates\/header.php":"f4b64c4ecb4dadec166cb7935309096a","templates\/footer.php":"3111b4701ea698ba11c3423260657e28","public\/login\/oidc.php":"8ec86d6f3af33f64d586109ec17f817d","public\/login\/config.php":"5b7b3e2937b349c76a2fd239c3ae06f8","public\/login\/magic.php":"54ef6b7ef80e608905e64e4fa8539846","public\/login\/index.php":"063d7b997bf8292d2b3f8c34dae3252f","public\/route.php":"f35dadd27dbdea162b67c42002519be9","public\/oidc\/callback.php":"793ff84451299c9984ac4742f02ca842","public\/oidc\/start.php":"87ddb61a0ef796d7303709ffa741c9c7","public\/oidc\/me.php":"d0439342011bb0e58ef8738b3b81cc2f","public\/index.php":"73a917520ea547ae8a122bd90098bf46","src\/Infrastructure\/DbAdapter.php":"3899a835130c146e2d30dbcca88d8f33","src\/Infrastructure\/Database.php":"6f2848ed70b29d9c2e2d259be611b9b0","src\/Infrastructure\/Session.php":"3538a1147cc81678c470d45ea8574a95","src\/Domain\/User.php":"02213454f7edf43f4afae3f2f81aaf01","src\/Http\/Csrf.php":"55631812cab4b1192f8e30c5d35fd5eb","src\/FileManager.php":"a51dda44f293f238aea295fd56b2fa99","src\/PostManager.php":"25f0179c4d96e9aa04218d54bf45a029","src\/helpers.php":"3a83a4872b1e3e3c58898b54f51e72b4","src\/db.php":"8888b7fbc9740eb3c60dd2374d0cb5d6","src\/ConfigRepo.php":"c2dcee160a272d27725d480a90e76dcf","src\/Parsedown.php":"85da2b47eca1a703fdfe44753bf912df","src\/Service\/Validator.php":"7c267b8b9f3f1bac0f2520dd10364831","src\/Service\/UiFormRenderer.php":"065617191c6d680ce97588f4fa159688","src\/mailer.php":"17e6b19103c880cc9a6c6634486506c2","src\/Repository\/DictionnaryRepository.php":"f937e98cf0f27b59ae00e430b52a586d","src\/Repository\/ProfileRepository.php":"b1cd483652500ee4e2aaaa9e0330ff1d","versions.php":"51a72261e1a507d3435b4a24e5f5fc09","config\/config.php":"a8b7698b01ab9b40eea655e8fcc194fc","templates\/post_form.php":"32765f286dc2fc2f9d9790fa3a94bef5","templates\/post_view.php":"64094ca90fd3d03bb9615bb3772bc189","templates\/layout.php":"d15f6cfe941b8ee69b2add5af6d3ffab","templates\/post_list.php":"864963b27f0361ad40a9f0f3245fa897","phpstan-bootstrap.php":"d74864c2f107b740523f070d077d715e","src\/Service\/MailQueue.php":"20db418b83dcf426b7c6ad6787644cde","src\/Service\/AuthService.php":"f95a9ab097dcfc4ac6cbcf908cf4cd90","src\/Repository\/UserRepository.php":"d0ccc80374b54a5c4f20cb04c00fb083","src\/auth.php":"c3dc272b004694a6783c90fd7e31c962","src\/Service\/MailService.php":"7bff5df8cac3274a1a4ab8fe137514f2"}} \ No newline at end of file diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index c35645e..659ec56 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -4,68 +4,3 @@ parameters: message: "#^Unreachable statement \\- code above always terminates\\.$#" count: 1 path: src/Repository/ProfileRepository.php - - - - message: "#^Call to method getCode\\(\\) on an unknown class App\\\\Repository\\\\PDOException\\.$#" - count: 1 - path: src/Repository/UserRepository.php - - - - message: "#^Caught class App\\\\Repository\\\\PDOException not found\\.$#" - count: 1 - path: src/Repository/UserRepository.php - - - - message: "#^Instantiated class App\\\\Repository\\\\InvalidArgumentException not found\\.$#" - count: 1 - path: src/Repository/UserRepository.php - - - - message: "#^Method App\\\\Repository\\\\UserRepository\\:\\:nullIfEmpty\\(\\) is unused\\.$#" - count: 1 - path: src/Repository/UserRepository.php - - - - message: "#^Throwing object of an unknown class App\\\\Repository\\\\InvalidArgumentException\\.$#" - count: 1 - path: src/Repository/UserRepository.php - - - - message: "#^Throwing object of an unknown class App\\\\Repository\\\\PDOException\\.$#" - count: 1 - path: src/Repository/UserRepository.php - - - - message: "#^Class App\\\\Repository\\\\UserRepository constructor invoked with 0 parameters, 1 required\\.$#" - count: 1 - path: src/Service/AuthService.php - - - - message: "#^Call to an undefined method App\\\\Infrastructure\\\\Database\\:\\:getConnection\\(\\)\\.$#" - count: 1 - path: src/Service/MailQueue.php - - - - message: "#^Call to an undefined method App\\\\Infrastructure\\\\Database\\:\\:getConnection\\(\\)\\.$#" - count: 1 - path: src/Service/MailService.php - - - - message: "#^Comparison operation \"\\>\" between 200 and 0 is always true\\.$#" - count: 1 - path: src/Service/MailService.php - - - - message: "#^Constant BASE_PATH not found\\.$#" - count: 1 - path: src/auth.php - - - - message: "#^Parameter \\#1 \\$scope of method Jumbojett\\\\OpenIDConnectClient\\:\\:addScope\\(\\) expects array, string given\\.$#" - count: 1 - path: src/auth.php - - - - message: "#^Constant BASE_PATH not found\\.$#" - count: 1 - path: src/db.php diff --git a/phpstan-bootstrap.php b/phpstan-bootstrap.php new file mode 100644 index 0000000..647c135 --- /dev/null +++ b/phpstan-bootstrap.php @@ -0,0 +1,5 @@ + $randomHash, ]); return (string)$st->fetchColumn(); - } catch (PDOException $e) { + } catch (\PDOException $e) { // Unique violation sur email (23505) → on relit l’id (race condition) if ($e->getCode() === '23505') { $st = $this->pdo->prepare('SELECT id FROM users WHERE email = :email LIMIT 1'); @@ -67,12 +67,6 @@ SQL; } } - private function nullIfEmpty(?string $v): ?string - { - $v = trim((string)$v); - return $v === '' ? null : $v; - } - public function findByEmail(string $email): ?User { $sql = 'SELECT id, email, password_hash, is_active FROM users WHERE email = :email LIMIT 1'; diff --git a/src/Service/AuthService.php b/src/Service/AuthService.php index 509aa74..4169fe7 100644 --- a/src/Service/AuthService.php +++ b/src/Service/AuthService.php @@ -76,7 +76,7 @@ final class AuthService // Mettre à jour le hash $newHash = password_hash($newPassword, PASSWORD_ARGON2ID); - (new \App\Repository\UserRepository())->updatePassword($row['id'], $newHash); + (new \App\Repository\UserRepository(\App\Infrastructure\Database::get()))->updatePassword($row['id'], $newHash); // (Optionnel) rotation session \App\Infrastructure\Session::regenerate(); diff --git a/src/Service/MailQueue.php b/src/Service/MailQueue.php index b891849..42fdc58 100644 --- a/src/Service/MailQueue.php +++ b/src/Service/MailQueue.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Service; -use App\Infrastructure\Database; use PDO; use Throwable; use DateTimeImmutable; @@ -33,9 +32,9 @@ final class MailQueue private PDO $pdo; private MailService $mailService; - public function __construct(Database $db, MailService $mailService) + public function __construct(\PDO $pdo, MailService $mailService) { - $this->pdo = $db->getConnection(); + $this->pdo = $pdo; $this->mailService = $mailService; } diff --git a/src/Service/MailService.php b/src/Service/MailService.php index ab59493..e554280 100644 --- a/src/Service/MailService.php +++ b/src/Service/MailService.php @@ -4,7 +4,6 @@ declare(strict_types=1); namespace App\Service; -use App\Infrastructure\Database; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception as MailException; use PDO; @@ -38,7 +37,7 @@ final class MailService private array $smtpConfig; /** - * @param Database $db Retourne un PDO connecté (PostgreSQL recommandé) + * @param \PDO $pdo PDO connecté (PostgreSQL recommandé) * @param array $smtpConfig [ * 'host' => 'smtp.example.tld', * 'port' => 587, @@ -52,9 +51,9 @@ final class MailService * 'smtp_options' => [...] (optionnel, cf. PHPMailer::SMTPOptions) * ] */ - public function __construct(Database $db, array $smtpConfig) + public function __construct(\PDO $pdo, array $smtpConfig) { - $this->pdo = $db->getConnection(); + $this->pdo = $pdo; $this->smtpConfig = $smtpConfig; $this->mailer = new PHPMailer(true); @@ -204,19 +203,17 @@ final class MailService return false; } - // 3) Garde-fou global / heure (optionnel) - if (self::MAX_GLOBAL_PER_HOUR > 0) { - $sql3 = <<= (NOW() AT TIME ZONE 'UTC') - INTERVAL '1 hour' - AND status = 'sent' - SQL; - $stmt3 = $this->pdo->query($sql3); - $global1h = (int) $stmt3->fetchColumn(); - if ($global1h >= self::MAX_GLOBAL_PER_HOUR) { - return false; - } + // 3) Garde-fou global / heure + $sql3 = <<= (NOW() AT TIME ZONE 'UTC') - INTERVAL '1 hour' + AND status = 'sent' + SQL; + $stmt3 = $this->pdo->query($sql3); + $global1h = (int) $stmt3->fetchColumn(); + if ($global1h >= self::MAX_GLOBAL_PER_HOUR) { + return false; } return true; diff --git a/src/auth.php b/src/auth.php index e37c40c..bd8f633 100644 --- a/src/auth.php +++ b/src/auth.php @@ -23,6 +23,6 @@ function get_oidc_client(): OpenIDConnectClient 'varlog-client-secret' ); $oidc->setRedirectURL('http://varlog.acegrp.lan/auth/callback.php'); - $oidc->addScope('openid email profile'); + $oidc->addScope(['openid', 'email', 'profile']); return $oidc; } diff --git a/templates/post_list.php b/templates/post_list.php index 37f3c58..7b2be8b 100644 --- a/templates/post_list.php +++ b/templates/post_list.php @@ -2,17 +2,28 @@ require_once BASE_PATH . '/src/Parsedown.php'; $Parsedown = new Parsedown(); +$coverGradients = [ + 'linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 100%)', + 'linear-gradient(135deg, #d1fae5 0%, #a7f3d0 100%)', + 'linear-gradient(135deg, #fce7f3 0%, #fbcfe8 100%)', + 'linear-gradient(135deg, #fef3c7 0%, #fde68a 100%)', + 'linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%)', + 'linear-gradient(135deg, #ede9fe 0%, #ddd6fe 100%)', +]; + ob_start(); ?>
text($post['content']); - $preview = mb_strimwidth(strip_tags($html), 0, 160, '…'); + $html = $Parsedown->text($post['content']); + $preview = mb_strimwidth(strip_tags($html), 0, 120, '…'); + $gradient = $coverGradients[$post['id'] % count($coverGradients)]; ?>