From 70304d3b31fccecaff6ac3bdb8f2403a0ff40b3e Mon Sep 17 00:00:00 2001 From: Cedric Abonnel Date: Fri, 8 May 2026 13:18:00 +0200 Subject: [PATCH] =?UTF-8?q?S=C3=A9curit=C3=A9=20et=20qualit=C3=A9=20:=20he?= =?UTF-8?q?aders=20HTTP,=20permissions=20.env,=20lint=20PHPStan=20+=20PHP-?= =?UTF-8?q?CS-Fixer,=20r=C3=A9organisation=20dossiers,=20scripts=20de=20d?= =?UTF-8?q?=C3=A9ploiement?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- .php-cs-fixer.cache | 1 + CHANGELOG.md | 23 + config/config.php | 9 +- .../tables_create.sql | 0 .../architecture-notes.md | 0 notes.md => docs/notes-dev.md | 0 phpstan-baseline.neon | 71 +++ phpstan.neon | 9 + public/create.php | 71 --- public/index.php | 2 + public/login/config.php | 20 +- public/login/index.php | 38 +- public/login/magic.php | 41 +- public/login/oidc.php | 2 + public/oidc/callback.php | 30 +- public/oidc/me.php | 43 +- public/oidc/start.php | 24 +- public/route.php | 106 ++-- {includes => src}/ConfigRepo.php | 77 ++- src/Domain/User.php | 4 +- src/FileManager.php | 24 +- src/Http/Csrf.php | 1 + src/Infrastructure/Database.php | 15 +- src/Infrastructure/DbAdapter.php | 9 +- src/Infrastructure/Session.php | 5 +- src/Parsedown.php | 546 ++++++------------ src/PostManager.php | 20 +- src/Repository/DictionnaryRepository.php | 29 +- src/Repository/ProfileRepository.php | 35 +- src/Repository/UserRepository.php | 5 +- src/Service/AuthService.php | 27 +- src/Service/MailQueue.php | 2 +- src/Service/MailService.php | 1 - src/Service/UiFormRenderer.php | 24 +- src/Service/Validator.php | 28 +- src/auth.php | 8 +- src/db.php | 2 + src/helpers.php | 7 +- {includes => src}/mailer.php | 63 +- {includes => templates}/footer.php | 0 {includes => templates}/header.php | 8 +- templates/post_list.php | 8 +- templates/post_view.php | 6 +- 44 files changed, 776 insertions(+), 670 deletions(-) create mode 100644 .php-cs-fixer.cache create mode 100644 CHANGELOG.md rename tables_create.sql => database/tables_create.sql (100%) rename fichier non enregistré 1 => docs/architecture-notes.md (100%) rename notes.md => docs/notes-dev.md (100%) create mode 100644 phpstan-baseline.neon create mode 100644 phpstan.neon delete mode 100644 public/create.php rename {includes => src}/ConfigRepo.php (57%) rename {includes => src}/mailer.php (77%) rename {includes => templates}/footer.php (100%) rename {includes => templates}/header.php (98%) diff --git a/.gitignore b/.gitignore index 55ac089..5344962 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -# Credentials & config sensible +# Credentials .env # Composer dependencies diff --git a/.php-cs-fixer.cache b/.php-cs-fixer.cache new file mode 100644 index 0000000..46c47d2 --- /dev/null +++ b/.php-cs-fixer.cache @@ -0,0 +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\/post_view.php":"3c83bdaa22dc0c01bb0b043e0c07784d","templates\/header.php":"f4b64c4ecb4dadec166cb7935309096a","templates\/post_list.php":"191d1349cc7b0972a0ebbb99149590d3","templates\/layout.php":"7fbb10d6a3693b543efb83eee52b049c","templates\/footer.php":"3111b4701ea698ba11c3423260657e28","templates\/post_form.php":"07c1208cb64a796bd8f24d7f2fd62ccd","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"}} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..a253a29 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,23 @@ +# Changelog + +Toutes les modifications notables sont documentées ici. +Format : [Keep a Changelog](https://keepachangelog.com/fr/1.0.0/) + +## [Unreleased] + +### Added +- Initialisation du dépôt git sur le serveur +- Réorganisation des dossiers (`includes/` → `src/` et `templates/`, `docs/`, `database/`) +- Scripts de déploiement (`sync.sh`, `commit.sh`, `deploy.sh`, `sync-server-config.sh`) +- Copie locale des configs serveur Apache et PHP-FPM + +--- + + diff --git a/config/config.php b/config/config.php index 1033061..4fe5c5b 100644 --- a/config/config.php +++ b/config/config.php @@ -1,4 +1,6 @@ load(); if (!$_ENV['APP_URL']) { http_response_code(500); - echo "Configuration manquante : définis APP_URL ou APP_URL dans le .env"; + echo 'Configuration manquante : définis APP_URL ou APP_URL dans le .env'; exit; } @@ -26,12 +28,11 @@ $_ENV['APP_URL'] = APP_URL; * url('ressources/user/login.php') * url('api/items', ['page'=>2]) */ -function url(string $path = '', array $qs = []): string { +function url(string $path = '', array $qs = []): string +{ $u = APP_URL . ltrim($path, '/'); if ($qs) { $u .= (str_contains($u, '?') ? '&' : '?') . http_build_query($qs); } return $u; } - - diff --git a/tables_create.sql b/database/tables_create.sql similarity index 100% rename from tables_create.sql rename to database/tables_create.sql diff --git a/fichier non enregistré 1 b/docs/architecture-notes.md similarity index 100% rename from fichier non enregistré 1 rename to docs/architecture-notes.md diff --git a/notes.md b/docs/notes-dev.md similarity index 100% rename from notes.md rename to docs/notes-dev.md diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 0000000..c35645e --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,71 @@ +parameters: + ignoreErrors: + - + 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.neon b/phpstan.neon new file mode 100644 index 0000000..61e12e1 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,9 @@ +includes: + - phpstan-baseline.neon + +parameters: + level: 5 + paths: + - src + excludePaths: + - src/Parsedown.php diff --git a/public/create.php b/public/create.php deleted file mode 100644 index 0ba2a26..0000000 --- a/public/create.php +++ /dev/null @@ -1,71 +0,0 @@ -create($title, $content, $published); - header("Location: index.php"); - exit; - } -} -?> - - - - - - Nouveau post - - - -
-

Ajouter un nouveau post

- - -
-
    - -
  • - -
-
- - -
-
- - -
- -
- - -
- -
- > - -
- - - Annuler -
-
- - diff --git a/public/index.php b/public/index.php index 1366ef5..d4f026e 100644 --- a/public/index.php +++ b/public/index.php @@ -1,4 +1,6 @@ trim((string)($_POST['oidc_issuer'] ?? '')), 'oidc_name' => trim((string)($_POST['oidc_name'] ?? '')), 'oidc_client_id' => trim((string)($_POST['oidc_client_id'] ?? '')), - 'oidc_client_secret'=> trim((string)($_POST['oidc_client_secret'] ?? '')), + 'oidc_client_secret' => trim((string)($_POST['oidc_client_secret'] ?? '')), 'oidc_redirect_uri' => trim((string)($_POST['oidc_redirect_uri'] ?? '')), ]; // validations simples if ($in['allow_oidc']) { if ($in['oidc_issuer'] === '' || $in['oidc_client_id'] === '' || $in['oidc_client_secret'] === '' || $in['oidc_redirect_uri'] === '') { - $err = "OIDC activé mais champs incomplets."; + $err = 'OIDC activé mais champs incomplets.'; } } @@ -48,7 +54,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { env_set_pairs(BASE_PATH.'/.env', $envPairs); $cfg = config_repo_get(); - $msg = "Configuration enregistrée."; + $msg = 'Configuration enregistrée.'; } } ?> diff --git a/public/login/index.php b/public/login/index.php index e202d6b..534e6b6 100644 --- a/public/login/index.php +++ b/public/login/index.php @@ -8,18 +8,27 @@ use App\Http\Csrf; // --- Helpers AVANT tout usage --- if (!function_exists('env')) { - function env(string $key, ?string $default = null): ?string { - if (array_key_exists($key, $_ENV) && $_ENV[$key] !== '') return (string)$_ENV[$key]; + function env(string $key, ?string $default = null): ?string + { + if (array_key_exists($key, $_ENV) && $_ENV[$key] !== '') { + return (string)$_ENV[$key]; + } $v = getenv($key); - if ($v !== false && $v !== '') return (string)$v; + if ($v !== false && $v !== '') { + return (string)$v; + } return $default; } } if (!function_exists('db')) { - function db(): \PDO { return \App\Infrastructure\Database::get(); } + function db(): \PDO + { + return \App\Infrastructure\Database::get(); + } } if (!function_exists('url')) { - function url(string $path = '/'): string { + function url(string $path = '/'): string + { $scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') ? 'https' : 'http'; $host = $_SERVER['HTTP_HOST'] ?? 'localhost'; return $scheme . '://' . $host . $path; @@ -40,7 +49,9 @@ $maxPerWin = (int) env('MAGIC_MAX_PER_WINDOW', '5'); $defaultReturn = '/'; $sanitize = static function (string $url) use ($defaultReturn): string { $url = trim($url); - if ($url === '' || !str_starts_with($url, '/')) return $defaultReturn; + if ($url === '' || !str_starts_with($url, '/')) { + return $defaultReturn; + } return $url; }; $returnTo = $sanitize((string)($_GET['return_to'] ?? ($_SERVER['HTTP_REFERER'] ?? $defaultReturn))); @@ -49,7 +60,10 @@ $returnTo = $sanitize((string)($_GET['return_to'] ?? ($_SERVER['HTTP_REFERER'] ? $oidcEnabled = (bool) (env('OIDC_ISSUER') && env('OIDC_CLIENT_ID')); $oidcLoginUrl = '/login/oidc' . ($returnTo ? ('?return_to=' . urlencode($returnTo)) : ''); $oidcAuto = (isset($_GET['sso']) && $_GET['sso'] === '1') || (env('OIDC_AUTO', '0') === '1'); -if ($oidcEnabled && $oidcAuto) { header('Location: ' . $oidcLoginUrl, true, 302); exit; } +if ($oidcEnabled && $oidcAuto) { + header('Location: ' . $oidcLoginUrl, true, 302); + exit; +} // --- form: demande de lien magique --- $errors = []; @@ -65,13 +79,15 @@ if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') { } else { // rate limit simple par email et IP $ip = $_SERVER['HTTP_X_FORWARDED_FOR'] ?? ($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'); - if (strpos($ip, ',') !== false) $ip = trim(explode(',', $ip, 2)[0]); + if (strpos($ip, ',') !== false) { + $ip = trim(explode(',', $ip, 2)[0]); + } $pdo = db(); $pdo->beginTransaction(); try { // purge expirés / consommés - $pdo->prepare("DELETE FROM auth_magic_links WHERE email = :e AND (expires_at < NOW() OR consumed_at IS NOT NULL)") + $pdo->prepare('DELETE FROM auth_magic_links WHERE email = :e AND (expires_at < NOW() OR consumed_at IS NOT NULL)') ->execute([':e' => $email]); // 1) cooldown: refuser si un envoi récent < coolMin @@ -130,7 +146,9 @@ if (($_SERVER['REQUEST_METHOD'] ?? 'GET') === 'POST') { $okMsg = "Un lien vient d'être envoyé. Vérifiez votre boîte de réception et le dossier spam/indésirables."; } catch (\Throwable $ex) { - if ($pdo->inTransaction()) $pdo->rollBack(); + if ($pdo->inTransaction()) { + $pdo->rollBack(); + } $errors[] = $ex->getMessage(); } } diff --git a/public/login/magic.php b/public/login/magic.php index 374f58b..f23b0b4 100644 --- a/public/login/magic.php +++ b/public/login/magic.php @@ -1,4 +1,5 @@ beginTransaction(); try { // récupère lien non consommé et non expiré - $sql = "SELECT id, email, token, created_at, expires_at, consumed_at, return_to + $sql = 'SELECT id, email, token, created_at, expires_at, consumed_at, return_to FROM auth_magic_links WHERE token = :t - FOR UPDATE"; + FOR UPDATE'; $stmt = $pdo->prepare($sql); $stmt->execute([':t' => $token]); $row = $stmt->fetch(PDO::FETCH_ASSOC); - if (!$row) throw new RuntimeException('Lien inconnu.'); - if ($row['consumed_at'] !== null) throw new RuntimeException('Lien déjà utilisé.'); - if (strtotime((string)$row['expires_at']) < time()) throw new RuntimeException('Lien expiré.'); + if (!$row) { + throw new RuntimeException('Lien inconnu.'); + } + if ($row['consumed_at'] !== null) { + throw new RuntimeException('Lien déjà utilisé.'); + } + if (strtotime((string)$row['expires_at']) < time()) { + throw new RuntimeException('Lien expiré.'); + } // consomme le lien - $pdo->prepare("UPDATE auth_magic_links SET consumed_at = NOW() WHERE id = :id")->execute([':id' => $row['id']]); + $pdo->prepare('UPDATE auth_magic_links SET consumed_at = NOW() WHERE id = :id')->execute([':id' => $row['id']]); $pdo->commit(); // ouvre une session applicative « anonyme authentifiée par email » - if (session_status() !== PHP_SESSION_ACTIVE) session_start(); + if (session_status() !== PHP_SESSION_ACTIVE) { + session_start(); + } $_SESSION['auth'] = [ 'method' => 'magic', 'email' => (string)$row['email'], @@ -58,11 +71,15 @@ try { $dest = $row['return_to'] ?? '/'; // sécurité: ne renvoyer que des chemins relatifs - if (!is_string($dest) || !str_starts_with($dest, '/')) $dest = '/'; + if (!is_string($dest) || !str_starts_with($dest, '/')) { + $dest = '/'; + } header('Location: ' . $dest, true, 303); exit; } catch (\Throwable $e) { - if ($pdo->inTransaction()) $pdo->rollBack(); + if ($pdo->inTransaction()) { + $pdo->rollBack(); + } http_response_code(400); echo htmlspecialchars($e->getMessage(), ENT_QUOTES); } diff --git a/public/login/oidc.php b/public/login/oidc.php index 422bec6..61b7d1d 100644 --- a/public/login/oidc.php +++ b/public/login/oidc.php @@ -1,4 +1,6 @@ $OIDC_CLIENT_ID, 'code_verifier' => $codeVerifier, ]; -if ($OIDC_CLIENT_SECRET !== '') $post['client_secret'] = $OIDC_CLIENT_SECRET; +if ($OIDC_CLIENT_SECRET !== '') { + $post['client_secret'] = $OIDC_CLIENT_SECRET; +} $ch = curl_init($tokenEndpoint); curl_setopt_array($ch, [ @@ -127,7 +137,9 @@ if (!$email && $idToken && substr_count($idToken, '.') === 2) { [, $p, ] = explode('.', $idToken, 3); $payloadJson = base64_decode(strtr($p, '-_', '+/'), true); $payload = $payloadJson ? json_decode($payloadJson, true) : null; - if (is_array($payload) && !empty($payload['email'])) $email = $payload['email']; + if (is_array($payload) && !empty($payload['email'])) { + $email = $payload['email']; + } } if (!$email) { @@ -159,7 +171,9 @@ if ($flow === 'login' && $existingId) { ]; $target = $_SESSION['oidc_return_to'] ?? '/'; unset($_SESSION['oidc_return_to'], $_SESSION['oidc_flow']); - if (!is_string($target) || $target === '' || $target[0] !== '/') $target = '/'; + if (!is_string($target) || $target === '' || $target[0] !== '/') { + $target = '/'; + } header('Location: ' . $target, true, 303); exit; } @@ -176,4 +190,4 @@ $_SESSION['pending_oidc'] = [ unset($_SESSION['oidc_flow']); header('Location: ' . url('register/from-oidc'), true, 303); -exit; \ No newline at end of file +exit; diff --git a/public/oidc/me.php b/public/oidc/me.php index 1ba8ff2..ad8a791 100644 --- a/public/oidc/me.php +++ b/public/oidc/me.php @@ -12,31 +12,48 @@ require_once dirname(__DIR__, 2) . '/vendor/autoload.php'; require_once dirname(__DIR__, 2) . '/app/bootstrap.php'; require_once dirname(__DIR__, 2) . '/config/config.php'; -function maskToken(?string $t): string { - if (!$t) return ''; +function maskToken(?string $t): string +{ + if (!$t) { + return ''; + } $len = strlen($t); - if ($len <= 12) return str_repeat('•', $len); + if ($len <= 12) { + return str_repeat('•', $len); + } return substr($t, 0, 6) . str_repeat('•', max(0, $len - 12)) . substr($t, -6); } -function b64url_decode_str(string $s): string|false { +function b64url_decode_str(string $s): string|false +{ $s = strtr($s, '-_', '+/'); $pad = strlen($s) % 4; - if ($pad) $s .= str_repeat('=', 4 - $pad); + if ($pad) { + $s .= str_repeat('=', 4 - $pad); + } return base64_decode($s, true); } -function decode_jwt(string $jwt): array { - if (substr_count($jwt, '.') !== 2) return []; +function decode_jwt(string $jwt): array +{ + if (substr_count($jwt, '.') !== 2) { + return []; + } [, $payload, ] = explode('.', $jwt, 3); $json = b64url_decode_str($payload); - if ($json === false) return []; + if ($json === false) { + return []; + } $arr = json_decode($json, true); return is_array($arr) ? $arr : []; } -$env = static function(string $k, ?string $d = null): ?string { - if (array_key_exists($k, $_ENV) && $_ENV[$k] !== '') return (string)$_ENV[$k]; +$env = static function (string $k, ?string $d = null): ?string { + if (array_key_exists($k, $_ENV) && $_ENV[$k] !== '') { + return (string)$_ENV[$k]; + } $v = getenv($k); - if ($v !== false && $v !== '') return (string)$v; + if ($v !== false && $v !== '') { + return (string)$v; + } return $d; }; @@ -72,7 +89,9 @@ if ($debugEnabled && $claims === [] && $accTok && $issuer) { curl_close($ch); if ($resp !== false && $code === 200) { $tmp = json_decode((string)$resp, true); - if (is_array($tmp)) $claims = $tmp; + if (is_array($tmp)) { + $claims = $tmp; + } } } diff --git a/public/oidc/start.php b/public/oidc/start.php index f87419d..7256736 100644 --- a/public/oidc/start.php +++ b/public/oidc/start.php @@ -1,23 +1,33 @@ get($id); if (!$post) { - echo "Post introuvable."; + echo 'Post introuvable.'; exit; } @@ -79,61 +81,61 @@ switch ($action) { if ($id) { $postManager->delete($id); } - header("Location: route.php"); + header('Location: route.php'); exit; - - case 'edit': - if (!$id) { - echo "ID manquant."; - exit; + + case 'edit': + if (!$id) { + echo 'ID manquant.'; + exit; + } + + $post = $postManager->get($id); + if (!$post) { + echo 'Post introuvable.'; + exit; + } + + $title = $_POST['title'] ?? $post['title']; + $content = $_POST['content'] ?? $post['content']; + $published_at = $_POST['published_at'] ?? date('Y-m-d\TH:i', strtotime($post['created_at'])); + $published = isset($_POST['published']) ? true : $post['is_published']; + $errors = []; + + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (trim($title) === '') { + $errors[] = 'Le titre est obligatoire.'; } - - $post = $postManager->get($id); - if (!$post) { - echo "Post introuvable."; - exit; - } - - $title = $_POST['title'] ?? $post['title']; - $content = $_POST['content'] ?? $post['content']; - $published_at = $_POST['published_at'] ?? date('Y-m-d\TH:i', strtotime($post['created_at'])); - $published = isset($_POST['published']) ? true : $post['is_published']; - $errors = []; - - if ($_SERVER['REQUEST_METHOD'] === 'POST') { - if (trim($title) === '') { - $errors[] = 'Le titre est obligatoire.'; - } - - if (empty($errors)) { - $published_at_sql = str_replace('T', ' ', $_POST['published_at']); - $postManager->update($id, $title, $content, $published_at_sql, $published); - - if (!empty($_FILES['files']['name'][0])) { - foreach ($_FILES['files']['tmp_name'] as $i => $tmpName) { - if ($_FILES['files']['error'][$i] === UPLOAD_ERR_OK) { - $file = [ - 'name' => $_FILES['files']['name'][$i], - 'type' => $_FILES['files']['type'][$i], - 'tmp_name' => $_FILES['files']['tmp_name'][$i], - 'error' => $_FILES['files']['error'][$i], - 'size' => $_FILES['files']['size'][$i], - ]; - $fileManager->upload($id, $file); - } + + if (empty($errors)) { + $published_at_sql = str_replace('T', ' ', $_POST['published_at']); + $postManager->update($id, $title, $content, $published_at_sql, $published); + + if (!empty($_FILES['files']['name'][0])) { + foreach ($_FILES['files']['tmp_name'] as $i => $tmpName) { + if ($_FILES['files']['error'][$i] === UPLOAD_ERR_OK) { + $file = [ + 'name' => $_FILES['files']['name'][$i], + 'type' => $_FILES['files']['type'][$i], + 'tmp_name' => $_FILES['files']['tmp_name'][$i], + 'error' => $_FILES['files']['error'][$i], + 'size' => $_FILES['files']['size'][$i], + ]; + $fileManager->upload($id, $file); } } - - header("Location: route.php?action=view&id=$id"); - exit; } + + header("Location: route.php?action=view&id=$id"); + exit; } - - $formAction = "route.php?action=edit&id=$id"; - $action = 'edit'; - include BASE_PATH . '/templates/post_form.php'; - break; - + } + + $formAction = "route.php?action=edit&id=$id"; + $action = 'edit'; + include BASE_PATH . '/templates/post_form.php'; + break; + case 'list': default: $posts = $postManager->getAll(); diff --git a/includes/ConfigRepo.php b/src/ConfigRepo.php similarity index 57% rename from includes/ConfigRepo.php rename to src/ConfigRepo.php index c8a64b8..bace310 100644 --- a/includes/ConfigRepo.php +++ b/src/ConfigRepo.php @@ -1,25 +1,31 @@ -query("SELECT * FROM app_config WHERE id=1")->fetch(PDO::FETCH_ASSOC); - if (!$row) { return [ - 'allow_password'=>true,'allow_oidc'=>false,'registrations_open'=>true, - 'oidc_issuer'=>null,'oidc_name'=>null,'oidc_client_id'=>null,'oidc_client_secret'=>null,'oidc_redirect_uri'=>null - ]; } + $row = $pdo->query('SELECT * FROM app_config WHERE id=1')->fetch(PDO::FETCH_ASSOC); + if (!$row) { + return [ + 'allow_password' => true,'allow_oidc' => false,'registrations_open' => true, + 'oidc_issuer' => null,'oidc_name' => null,'oidc_client_id' => null,'oidc_client_secret' => null,'oidc_redirect_uri' => null + ]; + } return $row; } -function config_repo_save(array $in): void { +function config_repo_save(array $in): void +{ $pdo = db(); - $sql = "INSERT INTO app_config + $sql = 'INSERT INTO app_config (id, allow_password, allow_oidc, registrations_open, oidc_issuer, oidc_name, oidc_client_id, oidc_client_secret, oidc_redirect_uri, updated_at) VALUES (1,:pw,:oidc,:open,:iss,:name,:cid,:sec,:redir, now()) ON CONFLICT (id) DO UPDATE SET allow_password=:pw, allow_oidc=:oidc, registrations_open=:open, oidc_issuer=:iss, oidc_name=:name, oidc_client_id=:cid, oidc_client_secret=:sec, oidc_redirect_uri=:redir, - updated_at=now()"; + updated_at=now()'; $stmt = $pdo->prepare($sql); $stmt->execute([ ':pw' => (bool)$in['allow_password'], @@ -29,7 +35,7 @@ function config_repo_save(array $in): void { ':name' => trim((string)($in['oidc_name'] ?? '')) ?: null, ':cid' => trim((string)($in['oidc_client_id'] ?? '')) ?: null, ':sec' => trim((string)($in['oidc_client_secret'] ?? '')) ?: null, - ':redir'=> trim((string)($in['oidc_redirect_uri'] ?? '')) ?: null, + ':redir' => trim((string)($in['oidc_redirect_uri'] ?? '')) ?: null, ]); } @@ -37,36 +43,59 @@ function config_repo_save(array $in): void { * Met à jour le fichier .env en conservant les autres lignes. * $pairs = ['KEY'=>'value', ...] ; value null => supprime la clé. */ -function env_set_pairs(string $envPath, array $pairs): void { - if (!is_file($envPath)) { file_put_contents($envPath, ""); } +function env_set_pairs(string $envPath, array $pairs): void +{ + if (!is_file($envPath)) { + file_put_contents($envPath, ''); + } $lines = file($envPath, FILE_IGNORE_NEW_LINES); $map = []; foreach ($lines as $i => $line) { - if (preg_match('/^\s*#/', $line) || trim($line)==='') { $map[$i] = $line; continue; } - if (!str_contains($line, '=')) { $map[$i] = $line; continue; } + if (preg_match('/^\s*#/', $line) || trim($line) === '') { + $map[$i] = $line; + continue; + } + if (!str_contains($line, '=')) { + $map[$i] = $line; + continue; + } [$k,$v] = explode('=', $line, 2); $k = trim($k); - if ($k==='') { $map[$i] = $line; continue; } + if ($k === '') { + $map[$i] = $line; + continue; + } if (array_key_exists($k, $pairs)) { - if ($pairs[$k] === null) { $map[$i] = null; } // supprimé - else { $map[$i] = $k.'='.env_quote((string)$pairs[$k]); } + if ($pairs[$k] === null) { + $map[$i] = null; + } // supprimé + else { + $map[$i] = $k.'='.env_quote((string)$pairs[$k]); + } unset($pairs[$k]); } else { $map[$i] = $line; } } // append keys restantes - foreach ($pairs as $k=>$v) { - if ($v === null) continue; + foreach ($pairs as $k => $v) { + if ($v === null) { + continue; + } $map[] = $k.'='.env_quote((string)$v); } // re-écriture $out = []; - foreach ($map as $line) { if ($line === null) continue; $out[] = $line; } + foreach ($map as $line) { + if ($line === null) { + continue; + } $out[] = $line; + } file_put_contents($envPath, implode(PHP_EOL, $out).PHP_EOL); } -function env_quote(string $v): string { +function env_quote(string $v): string +{ if ($v === '' || preg_match('/\s|[#"\'=]/', $v)) { // met entre guillemets et échappe $v = str_replace(['\\','"'], ['\\\\','\\"'], $v); @@ -75,11 +104,11 @@ function env_quote(string $v): string { return $v; } -function ensure_admin(): void { +function ensure_admin(): void +{ // adapte à ton système if (empty($_SESSION['user']['is_admin'])) { http_response_code(403); exit('Forbidden'); } } - diff --git a/src/Domain/User.php b/src/Domain/User.php index 434e213..da5e745 100644 --- a/src/Domain/User.php +++ b/src/Domain/User.php @@ -1,4 +1,5 @@ db->prepare(" + $stmt = $this->db->prepare(' INSERT INTO post_files (post_id, file_type, file_path, original_name) VALUES (:post_id, :file_type, :file_path, :original_name) - "); + '); $stmt->execute([ 'post_id' => $postId, 'file_type' => $type, @@ -43,14 +45,14 @@ class FileManager public function getFilesForPost(int $postId): array { - $stmt = $this->db->prepare("SELECT * FROM post_files WHERE post_id = :post_id ORDER BY uploaded_at"); + $stmt = $this->db->prepare('SELECT * FROM post_files WHERE post_id = :post_id ORDER BY uploaded_at'); $stmt->execute(['post_id' => $postId]); return $stmt->fetchAll(PDO::FETCH_ASSOC); } public function delete(int $fileId): bool { - $stmt = $this->db->prepare("SELECT file_path FROM post_files WHERE id = :id"); + $stmt = $this->db->prepare('SELECT file_path FROM post_files WHERE id = :id'); $stmt->execute(['id' => $fileId]); $file = $stmt->fetch(PDO::FETCH_ASSOC); @@ -63,15 +65,21 @@ class FileManager unlink($fullPath); } - $stmt = $this->db->prepare("DELETE FROM post_files WHERE id = :id"); + $stmt = $this->db->prepare('DELETE FROM post_files WHERE id = :id'); return $stmt->execute(['id' => $fileId]); } private function guessType(string $mime): string { - if (str_starts_with($mime, 'image/')) return 'image'; - if (str_starts_with($mime, 'video/')) return 'video'; - if (str_starts_with($mime, 'audio/')) return 'audio'; + if (str_starts_with($mime, 'image/')) { + return 'image'; + } + if (str_starts_with($mime, 'video/')) { + return 'video'; + } + if (str_starts_with($mime, 'audio/')) { + return 'audio'; + } return 'file'; } } diff --git a/src/Http/Csrf.php b/src/Http/Csrf.php index 833ce9f..ecf3fbf 100644 --- a/src/Http/Csrf.php +++ b/src/Http/Csrf.php @@ -1,4 +1,5 @@ commit(); return $ret; } catch (\Throwable $e) { - if ($pdo->inTransaction()) $pdo->rollBack(); + if ($pdo->inTransaction()) { + $pdo->rollBack(); + } throw $e; } } diff --git a/src/Infrastructure/DbAdapter.php b/src/Infrastructure/DbAdapter.php index d229e6b..4230c86 100644 --- a/src/Infrastructure/DbAdapter.php +++ b/src/Infrastructure/DbAdapter.php @@ -1,4 +1,5 @@ PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, diff --git a/src/Infrastructure/Session.php b/src/Infrastructure/Session.php index 4873680..86a6443 100644 --- a/src/Infrastructure/Session.php +++ b/src/Infrastructure/Session.php @@ -1,4 +1,5 @@ textElements($text); @@ -56,7 +58,7 @@ class Parsedown # Setters # - function setBreaksEnabled($breaksEnabled) + public function setBreaksEnabled($breaksEnabled) { $this->breaksEnabled = $breaksEnabled; @@ -65,7 +67,7 @@ class Parsedown protected $breaksEnabled; - function setMarkupEscaped($markupEscaped) + public function setMarkupEscaped($markupEscaped) { $this->markupEscaped = $markupEscaped; @@ -74,7 +76,7 @@ class Parsedown protected $markupEscaped; - function setUrlsLinked($urlsLinked) + public function setUrlsLinked($urlsLinked) { $this->urlsLinked = $urlsLinked; @@ -83,7 +85,7 @@ class Parsedown protected $urlsLinked = true; - function setSafeMode($safeMode) + public function setSafeMode($safeMode) { $this->safeMode = (bool) $safeMode; @@ -92,7 +94,7 @@ class Parsedown protected $safeMode; - function setStrictMode($strictMode) + public function setStrictMode($strictMode) { $this->strictMode = (bool) $strictMode; @@ -169,13 +171,11 @@ class Parsedown $Elements = array(); $CurrentBlock = null; - foreach ($lines as $line) - { - if (chop($line) === '') - { - if (isset($CurrentBlock)) - { - $CurrentBlock['interrupted'] = (isset($CurrentBlock['interrupted']) + foreach ($lines as $line) { + if (chop($line) === '') { + if (isset($CurrentBlock)) { + $CurrentBlock['interrupted'] = ( + isset($CurrentBlock['interrupted']) ? $CurrentBlock['interrupted'] + 1 : 1 ); } @@ -183,8 +183,7 @@ class Parsedown continue; } - while (($beforeTab = strstr($line, "\t", true)) !== false) - { + while (($beforeTab = strstr($line, "\t", true)) !== false) { $shortage = 4 - mb_strlen($beforeTab, 'utf-8') % 4; $line = $beforeTab @@ -203,21 +202,16 @@ class Parsedown # ~ - if (isset($CurrentBlock['continuable'])) - { + if (isset($CurrentBlock['continuable'])) { $methodName = 'block' . $CurrentBlock['type'] . 'Continue'; $Block = $this->$methodName($Line, $CurrentBlock); - if (isset($Block)) - { + if (isset($Block)) { $CurrentBlock = $Block; continue; - } - else - { - if ($this->isBlockCompletable($CurrentBlock['type'])) - { + } else { + if ($this->isBlockCompletable($CurrentBlock['type'])) { $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; $CurrentBlock = $this->$methodName($CurrentBlock); } @@ -232,37 +226,30 @@ class Parsedown $blockTypes = $this->unmarkedBlockTypes; - if (isset($this->BlockTypes[$marker])) - { - foreach ($this->BlockTypes[$marker] as $blockType) - { - $blockTypes []= $blockType; + if (isset($this->BlockTypes[$marker])) { + foreach ($this->BlockTypes[$marker] as $blockType) { + $blockTypes [] = $blockType; } } # # ~ - foreach ($blockTypes as $blockType) - { + foreach ($blockTypes as $blockType) { $Block = $this->{"block$blockType"}($Line, $CurrentBlock); - if (isset($Block)) - { + if (isset($Block)) { $Block['type'] = $blockType; - if ( ! isset($Block['identified'])) - { - if (isset($CurrentBlock)) - { + if (! isset($Block['identified'])) { + if (isset($CurrentBlock)) { $Elements[] = $this->extractElement($CurrentBlock); } $Block['identified'] = true; } - if ($this->isBlockContinuable($blockType)) - { + if ($this->isBlockContinuable($blockType)) { $Block['continuable'] = true; } @@ -274,19 +261,14 @@ class Parsedown # ~ - if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph') - { + if (isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph') { $Block = $this->paragraphContinue($Line, $CurrentBlock); } - if (isset($Block)) - { + if (isset($Block)) { $CurrentBlock = $Block; - } - else - { - if (isset($CurrentBlock)) - { + } else { + if (isset($CurrentBlock)) { $Elements[] = $this->extractElement($CurrentBlock); } @@ -298,16 +280,14 @@ class Parsedown # ~ - if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) - { + if (isset($CurrentBlock['continuable']) and $this->isBlockCompletable($CurrentBlock['type'])) { $methodName = 'block' . $CurrentBlock['type'] . 'Complete'; $CurrentBlock = $this->$methodName($CurrentBlock); } # ~ - if (isset($CurrentBlock)) - { + if (isset($CurrentBlock)) { $Elements[] = $this->extractElement($CurrentBlock); } @@ -318,14 +298,10 @@ class Parsedown protected function extractElement(array $Component) { - if ( ! isset($Component['element'])) - { - if (isset($Component['markup'])) - { + if (! isset($Component['element'])) { + if (isset($Component['markup'])) { $Component['element'] = array('rawHtml' => $Component['markup']); - } - elseif (isset($Component['hidden'])) - { + } elseif (isset($Component['hidden'])) { $Component['element'] = array(); } } @@ -348,13 +324,11 @@ class Parsedown protected function blockCode($Line, $Block = null) { - if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted'])) - { + if (isset($Block) and $Block['type'] === 'Paragraph' and ! isset($Block['interrupted'])) { return; } - if ($Line['indent'] >= 4) - { + if ($Line['indent'] >= 4) { $text = substr($Line['body'], 4); $Block = array( @@ -373,10 +347,8 @@ class Parsedown protected function blockCodeContinue($Line, $Block) { - if ($Line['indent'] >= 4) - { - if (isset($Block['interrupted'])) - { + if ($Line['indent'] >= 4) { + if (isset($Block['interrupted'])) { $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']); unset($Block['interrupted']); @@ -402,13 +374,11 @@ class Parsedown protected function blockComment($Line) { - if ($this->markupEscaped or $this->safeMode) - { + if ($this->markupEscaped or $this->safeMode) { return; } - if (strpos($Line['text'], '') !== false) - { + if (strpos($Line['text'], '-->') !== false) { $Block['closed'] = true; } @@ -427,15 +396,13 @@ class Parsedown protected function blockCommentContinue($Line, array $Block) { - if (isset($Block['closed'])) - { + if (isset($Block['closed'])) { return; } $Block['element']['rawHtml'] .= "\n" . $Line['body']; - if (strpos($Line['text'], '-->') !== false) - { + if (strpos($Line['text'], '-->') !== false) { $Block['closed'] = true; } @@ -451,15 +418,13 @@ class Parsedown $openerLength = strspn($Line['text'], $marker); - if ($openerLength < 3) - { + if ($openerLength < 3) { return; } $infostring = trim(substr($Line['text'], $openerLength), "\t "); - if (strpos($infostring, '`') !== false) - { + if (strpos($infostring, '`') !== false) { return; } @@ -468,8 +433,7 @@ class Parsedown 'text' => '', ); - if ($infostring !== '') - { + if ($infostring !== '') { /** * https://www.w3.org/TR/2011/WD-html5-20110525/elements.html#classes * Every HTML element may have a class attribute specified. @@ -501,13 +465,11 @@ class Parsedown protected function blockFencedCodeContinue($Line, $Block) { - if (isset($Block['complete'])) - { + if (isset($Block['complete'])) { return; } - if (isset($Block['interrupted'])) - { + if (isset($Block['interrupted'])) { $Block['element']['element']['text'] .= str_repeat("\n", $Block['interrupted']); unset($Block['interrupted']); @@ -540,15 +502,13 @@ class Parsedown { $level = strspn($Line['text'], '#'); - if ($level > 6) - { + if ($level > 6) { return; } $text = trim($Line['text'], '#'); - if ($this->strictMode and isset($text[0]) and $text[0] !== ' ') - { + if ($this->strictMode and isset($text[0]) and $text[0] !== ' ') { return; } @@ -575,18 +535,14 @@ class Parsedown { list($name, $pattern) = $Line['text'][0] <= '-' ? array('ul', '[*+-]') : array('ol', '[0-9]{1,9}+[.\)]'); - if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches)) - { + if (preg_match('/^('.$pattern.'([ ]++|$))(.*+)/', $Line['text'], $matches)) { $contentIndent = strlen($matches[2]); - if ($contentIndent >= 5) - { + if ($contentIndent >= 5) { $contentIndent -= 1; $matches[1] = substr($matches[1], 0, -$contentIndent); $matches[3] = str_repeat(' ', $contentIndent) . $matches[3]; - } - elseif ($contentIndent === 0) - { + } elseif ($contentIndent === 0) { $matches[1] .= ' '; } @@ -607,12 +563,10 @@ class Parsedown ); $Block['data']['markerTypeRegex'] = preg_quote($Block['data']['markerType'], '/'); - if ($name === 'ol') - { + if ($name === 'ol') { $listStart = ltrim(strstr($matches[1], $Block['data']['markerType'], true), '0') ?: '0'; - if ($listStart !== '1') - { + if ($listStart !== '1') { if ( isset($CurrentBlock) and $CurrentBlock['type'] === 'Paragraph' @@ -634,7 +588,7 @@ class Parsedown ) ); - $Block['element']['elements'] []= & $Block['li']; + $Block['element']['elements'] [] = & $Block['li']; return $Block; } @@ -642,8 +596,7 @@ class Parsedown protected function blockListContinue($Line, array $Block) { - if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument'])) - { + if (isset($Block['interrupted']) and empty($Block['li']['handler']['argument'])) { return null; } @@ -660,9 +613,8 @@ class Parsedown ) ) ) { - if (isset($Block['interrupted'])) - { - $Block['li']['handler']['argument'] []= ''; + if (isset($Block['interrupted'])) { + $Block['li']['handler']['argument'] [] = ''; $Block['loose'] = true; @@ -684,25 +636,20 @@ class Parsedown ) ); - $Block['element']['elements'] []= & $Block['li']; + $Block['element']['elements'] [] = & $Block['li']; return $Block; - } - elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line)) - { + } elseif ($Line['indent'] < $requiredIndent and $this->blockList($Line)) { return null; } - if ($Line['text'][0] === '[' and $this->blockReference($Line)) - { + if ($Line['text'][0] === '[' and $this->blockReference($Line)) { return $Block; } - if ($Line['indent'] >= $requiredIndent) - { - if (isset($Block['interrupted'])) - { - $Block['li']['handler']['argument'] []= ''; + if ($Line['indent'] >= $requiredIndent) { + if (isset($Block['interrupted'])) { + $Block['li']['handler']['argument'] [] = ''; $Block['loose'] = true; @@ -711,16 +658,15 @@ class Parsedown $text = substr($Line['body'], $requiredIndent); - $Block['li']['handler']['argument'] []= $text; + $Block['li']['handler']['argument'] [] = $text; return $Block; } - if ( ! isset($Block['interrupted'])) - { + if (! isset($Block['interrupted'])) { $text = preg_replace('/^[ ]{0,'.$requiredIndent.'}+/', '', $Line['body']); - $Block['li']['handler']['argument'] []= $text; + $Block['li']['handler']['argument'] [] = $text; return $Block; } @@ -728,13 +674,10 @@ class Parsedown protected function blockListComplete(array $Block) { - if (isset($Block['loose'])) - { - foreach ($Block['element']['elements'] as &$li) - { - if (end($li['handler']['argument']) !== '') - { - $li['handler']['argument'] []= ''; + if (isset($Block['loose'])) { + foreach ($Block['element']['elements'] as &$li) { + if (end($li['handler']['argument']) !== '') { + $li['handler']['argument'] [] = ''; } } } @@ -747,8 +690,7 @@ class Parsedown protected function blockQuote($Line) { - if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) - { + if (preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) { $Block = array( 'element' => array( 'name' => 'blockquote', @@ -766,21 +708,18 @@ class Parsedown protected function blockQuoteContinue($Line, array $Block) { - if (isset($Block['interrupted'])) - { + if (isset($Block['interrupted'])) { return; } - if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) - { - $Block['element']['handler']['argument'] []= $matches[1]; + if ($Line['text'][0] === '>' and preg_match('/^>[ ]?+(.*+)/', $Line['text'], $matches)) { + $Block['element']['handler']['argument'] [] = $matches[1]; return $Block; } - if ( ! isset($Block['interrupted'])) - { - $Block['element']['handler']['argument'] []= $Line['text']; + if (! isset($Block['interrupted'])) { + $Block['element']['handler']['argument'] [] = $Line['text']; return $Block; } @@ -793,8 +732,7 @@ class Parsedown { $marker = $Line['text'][0]; - if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '') - { + if (substr_count($Line['text'], $marker) >= 3 and chop($Line['text'], " $marker") === '') { $Block = array( 'element' => array( 'name' => 'hr', @@ -810,13 +748,11 @@ class Parsedown protected function blockSetextHeader($Line, ?array $Block = null) { - if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) - { + if (! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) { return; } - if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '') - { + if ($Line['indent'] < 4 and chop(chop($Line['text'], ' '), $Line['text'][0]) === '') { $Block['element']['name'] = $Line['text'][0] === '=' ? 'h1' : 'h2'; return $Block; @@ -828,17 +764,14 @@ class Parsedown protected function blockMarkup($Line) { - if ($this->markupEscaped or $this->safeMode) - { + if ($this->markupEscaped or $this->safeMode) { return; } - if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches)) - { + if (preg_match('/^<[\/]?+(\w*)(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+(\/)?>/', $Line['text'], $matches)) { $element = strtolower($matches[1]); - if (in_array($element, $this->textLevelElements)) - { + if (in_array($element, $this->textLevelElements, true)) { return; } @@ -856,8 +789,7 @@ class Parsedown protected function blockMarkupContinue($Line, array $Block) { - if (isset($Block['closed']) or isset($Block['interrupted'])) - { + if (isset($Block['closed']) or isset($Block['interrupted'])) { return; } @@ -896,8 +828,7 @@ class Parsedown protected function blockTable($Line, ?array $Block = null) { - if ( ! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) - { + if (! isset($Block) or $Block['type'] !== 'Paragraph' or isset($Block['interrupted'])) { return; } @@ -910,8 +841,7 @@ class Parsedown return; } - if (chop($Line['text'], ' -:|') !== '') - { + if (chop($Line['text'], ' -:|') !== '') { return; } @@ -924,28 +854,24 @@ class Parsedown $dividerCells = explode('|', $divider); - foreach ($dividerCells as $dividerCell) - { + foreach ($dividerCells as $dividerCell) { $dividerCell = trim($dividerCell); - if ($dividerCell === '') - { + if ($dividerCell === '') { return; } $alignment = null; - if ($dividerCell[0] === ':') - { + if ($dividerCell[0] === ':') { $alignment = 'left'; } - if (substr($dividerCell, - 1) === ':') - { + if (substr($dividerCell, - 1) === ':') { $alignment = $alignment === 'left' ? 'center' : 'right'; } - $alignments []= $alignment; + $alignments [] = $alignment; } # ~ @@ -959,13 +885,11 @@ class Parsedown $headerCells = explode('|', $header); - if (count($headerCells) !== count($alignments)) - { + if (count($headerCells) !== count($alignments)) { return; } - foreach ($headerCells as $index => $headerCell) - { + foreach ($headerCells as $index => $headerCell) { $headerCell = trim($headerCell); $HeaderElement = array( @@ -977,8 +901,7 @@ class Parsedown ) ); - if (isset($alignments[$index])) - { + if (isset($alignments[$index])) { $alignment = $alignments[$index]; $HeaderElement['attributes'] = array( @@ -986,7 +909,7 @@ class Parsedown ); } - $HeaderElements []= $HeaderElement; + $HeaderElements [] = $HeaderElement; } # ~ @@ -1000,16 +923,16 @@ class Parsedown ), ); - $Block['element']['elements'] []= array( + $Block['element']['elements'] [] = array( 'name' => 'thead', ); - $Block['element']['elements'] []= array( + $Block['element']['elements'] [] = array( 'name' => 'tbody', 'elements' => array(), ); - $Block['element']['elements'][0]['elements'] []= array( + $Block['element']['elements'][0]['elements'] [] = array( 'name' => 'tr', 'elements' => $HeaderElements, ); @@ -1019,13 +942,11 @@ class Parsedown protected function blockTableContinue($Line, array $Block) { - if (isset($Block['interrupted'])) - { + if (isset($Block['interrupted'])) { return; } - if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|')) - { + if (count($Block['alignments']) === 1 or $Line['text'][0] === '|' or strpos($Line['text'], '|')) { $Elements = array(); $row = $Line['text']; @@ -1037,8 +958,7 @@ class Parsedown $cells = array_slice($matches[0], 0, count($Block['alignments'])); - foreach ($cells as $index => $cell) - { + foreach ($cells as $index => $cell) { $cell = trim($cell); $Element = array( @@ -1050,14 +970,13 @@ class Parsedown ) ); - if (isset($Block['alignments'][$index])) - { + if (isset($Block['alignments'][$index])) { $Element['attributes'] = array( 'style' => 'text-align: ' . $Block['alignments'][$index] . ';', ); } - $Elements []= $Element; + $Elements [] = $Element; } $Element = array( @@ -1065,7 +984,7 @@ class Parsedown 'elements' => $Elements, ); - $Block['element']['elements'][1]['elements'] []= $Element; + $Block['element']['elements'][1]['elements'] [] = $Element; return $Block; } @@ -1092,8 +1011,7 @@ class Parsedown protected function paragraphContinue($Line, array $Block) { - if (isset($Block['interrupted'])) - { + if (isset($Block['interrupted'])) { return; } @@ -1139,48 +1057,43 @@ class Parsedown $Elements = array(); - $nonNestables = (empty($nonNestables) + $nonNestables = ( + empty($nonNestables) ? array() : array_combine($nonNestables, $nonNestables) ); # $excerpt is based on the first occurrence of a marker - while ($excerpt = strpbrk($text, $this->inlineMarkerList)) - { + while ($excerpt = strpbrk($text, $this->inlineMarkerList)) { $marker = $excerpt[0]; $markerPosition = strlen($text) - strlen($excerpt); $Excerpt = array('text' => $excerpt, 'context' => $text); - foreach ($this->InlineTypes[$marker] as $inlineType) - { + foreach ($this->InlineTypes[$marker] as $inlineType) { # check to see if the current inline type is nestable in the current context - if (isset($nonNestables[$inlineType])) - { + if (isset($nonNestables[$inlineType])) { continue; } $Inline = $this->{"inline$inlineType"}($Excerpt); - if ( ! isset($Inline)) - { + if (! isset($Inline)) { continue; } # makes sure that the inline belongs to "our" marker - if (isset($Inline['position']) and $Inline['position'] > $markerPosition) - { + if (isset($Inline['position']) and $Inline['position'] > $markerPosition) { continue; } # sets a default inline position - if ( ! isset($Inline['position'])) - { + if (! isset($Inline['position'])) { $Inline['position'] = $markerPosition; } @@ -1221,10 +1134,8 @@ class Parsedown $InlineText = $this->inlineText($text); $Elements[] = $InlineText['element']; - foreach ($Elements as &$Element) - { - if ( ! isset($Element['autobreak'])) - { + foreach ($Elements as &$Element) { + if (! isset($Element['autobreak'])) { $Element['autobreak'] = false; } } @@ -1259,8 +1170,7 @@ class Parsedown { $marker = $Excerpt['text'][0]; - if (preg_match('/^(['.$marker.']++)[ ]*+(.+?)[ ]*+(?') !== false and preg_match("/^<((mailto:)?$commonMarkEmail)>/i", $Excerpt['text'], $matches) - ){ + ) { $url = $matches[1]; - if ( ! isset($matches[2])) - { + if (! isset($matches[2])) { $url = "mailto:$url"; } @@ -1306,23 +1215,17 @@ class Parsedown protected function inlineEmphasis($Excerpt) { - if ( ! isset($Excerpt['text'][1])) - { + if (! isset($Excerpt['text'][1])) { return; } $marker = $Excerpt['text'][0]; - if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) - { + if ($Excerpt['text'][1] === $marker and preg_match($this->StrongRegex[$marker], $Excerpt['text'], $matches)) { $emphasis = 'strong'; - } - elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) - { + } elseif (preg_match($this->EmRegex[$marker], $Excerpt['text'], $matches)) { $emphasis = 'em'; - } - else - { + } else { return; } @@ -1341,8 +1244,7 @@ class Parsedown protected function inlineEscapeSequence($Excerpt) { - if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters)) - { + if (isset($Excerpt['text'][1]) and in_array($Excerpt['text'][1], $this->specialCharacters, true)) { return array( 'element' => array('rawHtml' => $Excerpt['text'][1]), 'extent' => 2, @@ -1352,17 +1254,15 @@ class Parsedown protected function inlineImage($Excerpt) { - if ( ! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') - { + if (! isset($Excerpt['text'][1]) or $Excerpt['text'][1] !== '[') { return; } - $Excerpt['text']= substr($Excerpt['text'], 1); + $Excerpt['text'] = substr($Excerpt['text'], 1); $Link = $this->inlineLink($Excerpt); - if ($Link === null) - { + if ($Link === null) { return; } @@ -1405,46 +1305,35 @@ class Parsedown $remainder = $Excerpt['text']; - if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) - { + if (preg_match('/\[((?:[^][]++|(?R))*+)\]/', $remainder, $matches)) { $Element['handler']['argument'] = $matches[1]; $extent += strlen($matches[0]); $remainder = substr($remainder, $extent); - } - else - { + } else { return; } - if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches)) - { + if (preg_match('/^[(]\s*+((?:[^ ()]++|[(][^ )]+[)])++)(?:[ ]+("[^"]*+"|\'[^\']*+\'))?\s*+[)]/', $remainder, $matches)) { $Element['attributes']['href'] = $matches[1]; - if (isset($matches[2])) - { + if (isset($matches[2])) { $Element['attributes']['title'] = substr($matches[2], 1, - 1); } $extent += strlen($matches[0]); - } - else - { - if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) - { + } else { + if (preg_match('/^\s*\[(.*?)\]/', $remainder, $matches)) { $definition = strlen($matches[1]) ? $matches[1] : $Element['handler']['argument']; $definition = strtolower($definition); $extent += strlen($matches[0]); - } - else - { + } else { $definition = strtolower($Element['handler']['argument']); } - if ( ! isset($this->DefinitionData['Reference'][$definition])) - { + if (! isset($this->DefinitionData['Reference'][$definition])) { return; } @@ -1462,29 +1351,25 @@ class Parsedown protected function inlineMarkup($Excerpt) { - if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false) - { + if ($this->markupEscaped or $this->safeMode or strpos($Excerpt['text'], '>') === false) { return; } - if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches)) - { + if ($Excerpt['text'][1] === '/' and preg_match('/^<\/\w[\w-]*+[ ]*+>/s', $Excerpt['text'], $matches)) { return array( 'element' => array('rawHtml' => $matches[0]), 'extent' => strlen($matches[0]), ); } - if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) - { + if ($Excerpt['text'][1] === '!' and preg_match('/^/s', $Excerpt['text'], $matches)) { return array( 'element' => array('rawHtml' => $matches[0]), 'extent' => strlen($matches[0]), ); } - if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches)) - { + if ($Excerpt['text'][1] !== ' ' and preg_match('/^<\w[\w-]*+(?:[ ]*+'.$this->regexHtmlAttribute.')*+[ ]*+\/?>/s', $Excerpt['text'], $matches)) { return array( 'element' => array('rawHtml' => $matches[0]), 'extent' => strlen($matches[0]), @@ -1508,13 +1393,11 @@ class Parsedown protected function inlineStrikethrough($Excerpt) { - if ( ! isset($Excerpt['text'][1])) - { + if (! isset($Excerpt['text'][1])) { return; } - if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) - { + if ($Excerpt['text'][1] === '~' and preg_match('/^~~(?=\S)(.+?)(?<=\S)~~/', $Excerpt['text'], $matches)) { return array( 'extent' => strlen($matches[0]), 'element' => array( @@ -1531,8 +1414,7 @@ class Parsedown protected function inlineUrl($Excerpt) { - if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') - { + if ($this->urlsLinked !== true or ! isset($Excerpt['text'][2]) or $Excerpt['text'][2] !== '/') { return; } @@ -1559,8 +1441,7 @@ class Parsedown protected function inlineUrlTag($Excerpt) { - if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches)) - { + if (strpos($Excerpt['text'], '>') !== false and preg_match('/^<(\w++:\/{2}[^ >]++)>/i', $Excerpt['text'], $matches)) { $url = $matches[1]; return array( @@ -1590,22 +1471,17 @@ class Parsedown protected function handle(array $Element) { - if (isset($Element['handler'])) - { - if (!isset($Element['nonNestables'])) - { + if (isset($Element['handler'])) { + if (!isset($Element['nonNestables'])) { $Element['nonNestables'] = array(); } - if (is_string($Element['handler'])) - { + if (is_string($Element['handler'])) { $function = $Element['handler']; $argument = $Element['text']; unset($Element['text']); $destination = 'rawHtml'; - } - else - { + } else { $function = $Element['handler']['function']; $argument = $Element['handler']['argument']; $destination = $Element['handler']['destination']; @@ -1613,8 +1489,7 @@ class Parsedown $Element[$destination] = $this->{$function}($argument, $Element['nonNestables']); - if ($destination === 'handler') - { + if ($destination === 'handler') { $Element = $this->handle($Element); } @@ -1638,12 +1513,9 @@ class Parsedown { $Element = call_user_func($closure, $Element); - if (isset($Element['elements'])) - { + if (isset($Element['elements'])) { $Element['elements'] = $this->elementsApplyRecursive($closure, $Element['elements']); - } - elseif (isset($Element['element'])) - { + } elseif (isset($Element['element'])) { $Element['element'] = $this->elementApplyRecursive($closure, $Element['element']); } @@ -1652,12 +1524,9 @@ class Parsedown protected function elementApplyRecursiveDepthFirst($closure, array $Element) { - if (isset($Element['elements'])) - { + if (isset($Element['elements'])) { $Element['elements'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['elements']); - } - elseif (isset($Element['element'])) - { + } elseif (isset($Element['element'])) { $Element['element'] = $this->elementsApplyRecursiveDepthFirst($closure, $Element['element']); } @@ -1668,8 +1537,7 @@ class Parsedown protected function elementsApplyRecursive($closure, array $Elements) { - foreach ($Elements as &$Element) - { + foreach ($Elements as &$Element) { $Element = $this->elementApplyRecursive($closure, $Element); } @@ -1678,8 +1546,7 @@ class Parsedown protected function elementsApplyRecursiveDepthFirst($closure, array $Elements) { - foreach ($Elements as &$Element) - { + foreach ($Elements as &$Element) { $Element = $this->elementApplyRecursiveDepthFirst($closure, $Element); } @@ -1688,8 +1555,7 @@ class Parsedown protected function element(array $Element) { - if ($this->safeMode) - { + if ($this->safeMode) { $Element = $this->sanitiseElement($Element); } @@ -1700,16 +1566,12 @@ class Parsedown $markup = ''; - if ($hasName) - { + if ($hasName) { $markup .= '<' . $Element['name']; - if (isset($Element['attributes'])) - { - foreach ($Element['attributes'] as $name => $value) - { - if ($value === null) - { + if (isset($Element['attributes'])) { + foreach ($Element['attributes'] as $name => $value) { + if ($value === null) { continue; } @@ -1720,14 +1582,12 @@ class Parsedown $permitRawHtml = false; - if (isset($Element['text'])) - { + if (isset($Element['text'])) { $text = $Element['text']; } // very strongly consider an alternative if you're writing an // extension - elseif (isset($Element['rawHtml'])) - { + elseif (isset($Element['rawHtml'])) { $text = $Element['rawHtml']; $allowRawHtmlInSafeMode = isset($Element['allowRawHtmlInSafeMode']) && $Element['allowRawHtmlInSafeMode']; @@ -1736,34 +1596,23 @@ class Parsedown $hasContent = isset($text) || isset($Element['element']) || isset($Element['elements']); - if ($hasContent) - { + if ($hasContent) { $markup .= $hasName ? '>' : ''; - if (isset($Element['elements'])) - { + if (isset($Element['elements'])) { $markup .= $this->elements($Element['elements']); - } - elseif (isset($Element['element'])) - { + } elseif (isset($Element['element'])) { $markup .= $this->element($Element['element']); - } - else - { - if (!$permitRawHtml) - { + } else { + if (!$permitRawHtml) { $markup .= self::escape($text, true); - } - else - { + } else { $markup .= $text; } } $markup .= $hasName ? '' : ''; - } - elseif ($hasName) - { + } elseif ($hasName) { $markup .= ' />'; } @@ -1776,14 +1625,13 @@ class Parsedown $autoBreak = true; - foreach ($Elements as $Element) - { - if (empty($Element)) - { + foreach ($Elements as $Element) { + if (empty($Element)) { continue; } - $autoBreakNext = (isset($Element['autobreak']) + $autoBreakNext = ( + isset($Element['autobreak']) ? $Element['autobreak'] : isset($Element['name']) ); // (autobreak === false) covers both sides of an element @@ -1804,7 +1652,7 @@ class Parsedown { $Elements = $this->linesElements($lines); - if ( ! in_array('', $lines) + if (! in_array('', $lines, true) and isset($Elements[0]) and isset($Elements[0]['name']) and $Elements[0]['name'] === 'p' ) { @@ -1826,16 +1674,14 @@ class Parsedown { $newElements = array(); - while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE)) - { + while (preg_match($regexp, $text, $matches, PREG_OFFSET_CAPTURE)) { $offset = $matches[0][1]; $before = substr($text, 0, $offset); $after = substr($text, $offset + strlen($matches[0][0])); $newElements[] = array('text' => $before); - foreach ($Elements as $Element) - { + foreach ($Elements as $Element) { $newElements[] = $Element; } @@ -1851,7 +1697,7 @@ class Parsedown # Deprecated Methods # - function parse($text) + public function parse($text) { $markup = $this->text($text); @@ -1866,29 +1712,23 @@ class Parsedown 'img' => 'src', ); - if ( ! isset($Element['name'])) - { + if (! isset($Element['name'])) { unset($Element['attributes']); return $Element; } - if (isset($safeUrlNameToAtt[$Element['name']])) - { + if (isset($safeUrlNameToAtt[$Element['name']])) { $Element = $this->filterUnsafeUrlInAttribute($Element, $safeUrlNameToAtt[$Element['name']]); } - if ( ! empty($Element['attributes'])) - { - foreach ($Element['attributes'] as $att => $val) - { + if (! empty($Element['attributes'])) { + foreach ($Element['attributes'] as $att => $val) { # filter out badly parsed attribute - if ( ! preg_match($goodAttribute, $att)) - { + if (! preg_match($goodAttribute, $att)) { unset($Element['attributes'][$att]); } # dump onevent attribute - elseif (self::striAtStart($att, 'on')) - { + elseif (self::striAtStart($att, 'on')) { unset($Element['attributes'][$att]); } } @@ -1899,10 +1739,8 @@ class Parsedown protected function filterUnsafeUrlInAttribute(array $Element, $attribute) { - foreach ($this->safeLinksWhitelist as $scheme) - { - if (self::striAtStart($Element['attributes'][$attribute], $scheme)) - { + foreach ($this->safeLinksWhitelist as $scheme) { + if (self::striAtStart($Element['attributes'][$attribute], $scheme)) { return $Element; } } @@ -1925,20 +1763,16 @@ class Parsedown { $len = strlen($needle); - if ($len > strlen($string)) - { + if ($len > strlen($string)) { return false; - } - else - { + } else { return strtolower(substr($string, 0, $len)) === strtolower($needle); } } - static function instance($name = 'default') + public static function instance($name = 'default') { - if (isset(self::$instances[$name])) - { + if (isset(self::$instances[$name])) { return self::$instances[$name]; } diff --git a/src/PostManager.php b/src/PostManager.php index 701b87f..4050dfd 100644 --- a/src/PostManager.php +++ b/src/PostManager.php @@ -1,5 +1,7 @@ db->query("SELECT * FROM posts ORDER BY created_at DESC"); + $stmt = $this->db->query('SELECT * FROM posts ORDER BY created_at DESC'); return $stmt->fetchAll(PDO::FETCH_ASSOC); } public function get(int $id): ?array { - $stmt = $this->db->prepare("SELECT * FROM posts WHERE id = :id"); + $stmt = $this->db->prepare('SELECT * FROM posts WHERE id = :id'); $stmt->execute(['id' => $id]); $post = $stmt->fetch(PDO::FETCH_ASSOC); return $post ?: null; @@ -25,10 +27,10 @@ class PostManager public function create(string $title, string $content, string $published_at): int { - $stmt = $this->db->prepare(" + $stmt = $this->db->prepare(' INSERT INTO posts (title, content, created_at, is_published) VALUES (:title, :content, :published_at, true) - "); + '); $stmt->execute([ 'title' => $title, 'content' => $content, @@ -36,11 +38,11 @@ class PostManager ]); return (int)$this->db->lastInsertId(); } - + public function update(int $id, string $title, string $content, string $published_at, bool $published): bool { - $stmt = $this->db->prepare(" + $stmt = $this->db->prepare(' UPDATE posts SET title = :title, content = :content, @@ -48,7 +50,7 @@ class PostManager is_published = :published, updated_at = NOW() WHERE id = :id - "); + '); return $stmt->execute([ 'id' => $id, 'title' => $title, @@ -57,11 +59,11 @@ class PostManager 'published' => $published, ]); } - + public function delete(int $id): bool { - $stmt = $this->db->prepare("DELETE FROM posts WHERE id = :id"); + $stmt = $this->db->prepare('DELETE FROM posts WHERE id = :id'); return $stmt->execute(['id' => $id]); } } diff --git a/src/Repository/DictionnaryRepository.php b/src/Repository/DictionnaryRepository.php index 04ce5b0..262aa69 100644 --- a/src/Repository/DictionnaryRepository.php +++ b/src/Repository/DictionnaryRepository.php @@ -1,4 +1,5 @@ pdo->prepare('SELECT * FROM dd_entities WHERE code = :c AND is_active IS TRUE'); - $st->execute([':c'=>$code]); + $st->execute([':c' => $code]); $e = $st->fetch(PDO::FETCH_ASSOC); - if (!$e) return null; + if (!$e) { + return null; + } $e['fields'] = $this->getFields((int)$e['id']); $e['rules'] = $this->getRules((int)$e['id']); return $e; } - public function getFields(int $entityId): array { + public function getFields(int $entityId): array + { $st = $this->pdo->prepare('SELECT * FROM dd_fields WHERE entity_id = :id ORDER BY ui_order NULLS LAST, id'); - $st->execute([':id'=>$entityId]); + $st->execute([':id' => $entityId]); return $st->fetchAll(PDO::FETCH_ASSOC); } - public function getRules(int $entityId): array { + public function getRules(int $entityId): array + { $st = $this->pdo->prepare('SELECT * FROM dd_rules WHERE entity_id = :id AND active IS TRUE'); - $st->execute([':id'=>$entityId]); + $st->execute([':id' => $entityId]); return $st->fetchAll(PDO::FETCH_ASSOC); } - public function getEnum(string $name): array { + public function getEnum(string $name): array + { $st = $this->pdo->prepare(' SELECT ev.code, ev.label FROM dd_enums e JOIN dd_enum_values ev ON ev.enum_id = e.id WHERE e.name = :n AND ev.active IS TRUE ORDER BY ev.sort_order, ev.id '); - $st->execute([':n'=>$name]); + $st->execute([':n' => $name]); return $st->fetchAll(PDO::FETCH_ASSOC); } } diff --git a/src/Repository/ProfileRepository.php b/src/Repository/ProfileRepository.php index f98beba..94c3abb 100644 --- a/src/Repository/ProfileRepository.php +++ b/src/Repository/ProfileRepository.php @@ -1,4 +1,5 @@ pdo = $pdo; return; } + if ($pdo instanceof PDO) { + $this->pdo = $pdo; + return; + } // 1) App\Infrastructure\Database (si elle expose quelque chose) if (class_exists(Database::class)) { if (method_exists(Database::class, 'pdo')) { $try = Database::pdo(); - if ($try instanceof PDO) { $this->pdo = $try; return; } + if ($try instanceof PDO) { + $this->pdo = $try; + return; + } } if (method_exists(Database::class, 'getPdo')) { $try = Database::getPdo(); - if ($try instanceof PDO) { $this->pdo = $try; return; } + if ($try instanceof PDO) { + $this->pdo = $try; + return; + } } if (method_exists(Database::class, 'getInstance')) { $db = Database::getInstance(); - if ($db instanceof PDO) { $this->pdo = $db; return; } + if ($db instanceof PDO) { + $this->pdo = $db; + return; + } if (is_object($db) && method_exists($db, 'pdo')) { $try = $db->pdo(); - if ($try instanceof PDO) { $this->pdo = $try; return; } + if ($try instanceof PDO) { + $this->pdo = $try; + return; + } } } } @@ -38,7 +54,10 @@ final class ProfileRepository // 2) Fonction globale éventuelle if (function_exists('db')) { $try = db(); - if ($try instanceof PDO) { $this->pdo = $try; return; } + if ($try instanceof PDO) { + $this->pdo = $try; + return; + } } // 3) Variable globale éventuelle @@ -110,7 +129,7 @@ final class ProfileRepository ':slug' => $slug, ':label' => $label, ':desc' => $description, - ':perms' => json_encode($permissions, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES), + ':perms' => json_encode($permissions, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ':sys' => $isSystem, ':act' => $isActive, ]); @@ -125,7 +144,7 @@ final class ProfileRepository ':slug' => $slug, ':label' => $label, ':desc' => $description, - ':perms' => json_encode($permissions, JSON_UNESCAPED_UNICODE|JSON_UNESCAPED_SLASHES), + ':perms' => json_encode($permissions, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), ':sys' => $isSystem, ':act' => $isActive, ]); diff --git a/src/Repository/UserRepository.php b/src/Repository/UserRepository.php index 5543a95..a8a4328 100644 --- a/src/Repository/UserRepository.php +++ b/src/Repository/UserRepository.php @@ -1,4 +1,5 @@ users->findByEmail($email); $ok = $user && $user->isActive && password_verify($password, $user->passwordHash); - $pdo = \App\Infrastructure\Database::pdo(); - $st = $pdo->prepare('insert into login_attempts(email, ip, success) values(:e, :ip, :s)'); - $st->bindValue(':e', $email, \PDO::PARAM_STR); - $st->bindValue(':ip', $ip, \PDO::PARAM_STR); - $st->bindValue(':s', $ok, \PDO::PARAM_BOOL); - $st->execute(); - + $pdo = \App\Infrastructure\Database::pdo(); + $st = $pdo->prepare('insert into login_attempts(email, ip, success) values(:e, :ip, :s)'); + $st->bindValue(':e', $email, \PDO::PARAM_STR); + $st->bindValue(':ip', $ip, \PDO::PARAM_STR); + $st->bindValue(':s', $ok, \PDO::PARAM_BOOL); + $st->execute(); + if ($ok) { \App\Infrastructure\Session::regenerate(); $_SESSION['uid'] = $user->id; @@ -44,8 +47,8 @@ final class AuthService } return $ok; } - - + + public function changePassword(string $userId, string $currentPassword, string $newPassword): bool { // Récupération de l’utilisateur (rapide : requête directe ; tu peux créer findById() si tu préfères) @@ -53,7 +56,9 @@ final class AuthService $st = $pdo->prepare('select id, email, password_hash, is_active from users where id = :id'); $st->execute([':id' => $userId]); $row = $st->fetch(\PDO::FETCH_ASSOC); - if (!$row || !(bool)$row['is_active']) return false; + if (!$row || !(bool)$row['is_active']) { + return false; + } // Vérifier l’ancien mot de passe if (!password_verify($currentPassword, (string)$row['password_hash'])) { diff --git a/src/Service/MailQueue.php b/src/Service/MailQueue.php index 466e0c2..b891849 100644 --- a/src/Service/MailQueue.php +++ b/src/Service/MailQueue.php @@ -144,7 +144,7 @@ final class MailQueue if ($rows) { // 2) Marquer locked_at + sending - $ids = array_map(static fn($r) => (int)$r['id'], $rows); + $ids = array_map(static fn ($r) => (int)$r['id'], $rows); $in = implode(',', array_fill(0, count($ids), '?')); $up = $this->pdo->prepare( diff --git a/src/Service/MailService.php b/src/Service/MailService.php index 148481d..ab59493 100644 --- a/src/Service/MailService.php +++ b/src/Service/MailService.php @@ -8,7 +8,6 @@ use App\Infrastructure\Database; use PHPMailer\PHPMailer\PHPMailer; use PHPMailer\PHPMailer\Exception as MailException; use PDO; -use DateInterval; use DateTimeImmutable; /** diff --git a/src/Service/UiFormRenderer.php b/src/Service/UiFormRenderer.php index 4355dd4..566405a 100644 --- a/src/Service/UiFormRenderer.php +++ b/src/Service/UiFormRenderer.php @@ -1,4 +1,5 @@ dict->getEntityByCode($entityCode); - if (!$e) return '
Entité inconnue
'; + if (!$e) { + return '
Entité inconnue
'; + } $html = ''; foreach ($e['fields'] as $f) { - if (!$f['form_visible']) continue; - if ($f['read_only']) continue; + if (!$f['form_visible']) { + continue; + } + if ($f['read_only']) { + continue; + } $name = $f['code']; $label = $f['label']; $help = $f['help_text'] ?? ''; - $widget= $f['ui_widget'] ?? 'text'; + $widget = $f['ui_widget'] ?? 'text'; $val = $values[$name] ?? ''; $html .= '
'; @@ -41,7 +51,7 @@ final class UiFormRenderer 'email' => 'email', 'number' => 'number', 'date' => 'date', - 'checkbox'=> 'checkbox', + 'checkbox' => 'checkbox', default => 'text', }; if ($type === 'checkbox') { diff --git a/src/Service/Validator.php b/src/Service/Validator.php index 48a39a1..5214a2a 100644 --- a/src/Service/Validator.php +++ b/src/Service/Validator.php @@ -1,4 +1,5 @@ dict->getEntityByCode($entityCode); - if (!$e) return ['_global' => ['Entité inconnue']]; + if (!$e) { + return ['_global' => ['Entité inconnue']]; + } // Index les champs $fields = []; @@ -30,7 +36,9 @@ final class Validator switch ($type) { case 'required': - if ($code && ($v === null || $v === '')) $errors[$code][] = $msg; + if ($code && ($v === null || $v === '')) { + $errors[$code][] = $msg; + } break; case 'regex': if ($code && $v !== null && $v !== '' && !preg_match('#'.$val.'#u', (string)$v)) { @@ -38,16 +46,22 @@ final class Validator } break; case 'min': - if ($code && is_numeric($v) && (float)$v < (float)$val) $errors[$code][] = $msg; + if ($code && is_numeric($v) && (float)$v < (float)$val) { + $errors[$code][] = $msg; + } break; case 'max': - if ($code && is_numeric($v) && (float)$v > (float)$val) $errors[$code][] = $msg; + if ($code && is_numeric($v) && (float)$v > (float)$val) { + $errors[$code][] = $msg; + } break; case 'between': if ($code && is_numeric($v)) { [$a,$b] = array_map('floatval', explode(',', $val)); $fv = (float)$v; - if ($fv < $a || $fv > $b) $errors[$code][] = $msg; + if ($fv < $a || $fv > $b) { + $errors[$code][] = $msg; + } } break; case 'unique': diff --git a/src/auth.php b/src/auth.php index 11bf62d..e37c40c 100644 --- a/src/auth.php +++ b/src/auth.php @@ -1,10 +1,13 @@ $output"; -} \ No newline at end of file +} diff --git a/includes/mailer.php b/src/mailer.php similarity index 77% rename from includes/mailer.php rename to src/mailer.php index 72fc5d9..4d185f3 100644 --- a/includes/mailer.php +++ b/src/mailer.php @@ -1,4 +1,5 @@ prepare($q1); - $stmt->execute([':e'=>$email, ':cool'=>sprintf('%d minutes', $coolMin)]); - if ($stmt->fetchColumn()) return [false, "Un email vient d’être envoyé. Réessayez dans {$coolMin} min."]; + $stmt->execute([':e' => $email, ':cool' => sprintf('%d minutes', $coolMin)]); + if ($stmt->fetchColumn()) { + return [false, "Un email vient d’être envoyé. Réessayez dans {$coolMin} min."]; + } } // Plafond 12h (actif seulement si >0) @@ -48,8 +62,10 @@ function mailer_can_send(string $email, int $coolMin = 5, int $maxPer12h = 5): a WHERE to_email = :e AND created_at >= NOW() - INTERVAL '12 hours' AND status IN ('sent','queued')"; $stmt = $pdo->prepare($q2); - $stmt->execute([':e'=>$email]); - if ((int)$stmt->fetchColumn() >= $maxPer12h) return [false, 'Quota atteint. Réessayez plus tard.']; + $stmt->execute([':e' => $email]); + if ((int)$stmt->fetchColumn() >= $maxPer12h) { + return [false, 'Quota atteint. Réessayez plus tard.']; + } } return [true, '']; @@ -64,9 +80,12 @@ function mailer_can_send(string $email, int $coolMin = 5, int $maxPer12h = 5): a * @param string|null $text corps texte brut (optionnel, auto-généré si null) * @param array $opts ['reply_to'=>['email','name']] */ -function envoyer_mail_smtp(string $to, string $subject, string $html, ?string $text = null, array $opts = []): bool { +function envoyer_mail_smtp(string $to, string $subject, string $html, ?string $text = null, array $opts = []): bool +{ [$ok, $msg] = mailer_can_send($to, (int)env('SMTP_COOLDOWN_MINUTES', '5'), (int)env('SMTP_MAX_PER_12H', '5')); - if (!$ok) throw new RuntimeException($msg); + if (!$ok) { + throw new RuntimeException($msg); + } $pdo = db(); $pdo->beginTransaction(); @@ -87,7 +106,9 @@ function envoyer_mail_smtp(string $to, string $subject, string $html, ?string $t $rowId = (int)$stmt->fetchColumn(); $pdo->commit(); } catch (\Throwable $e) { - if ($pdo->inTransaction()) $pdo->rollBack(); + if ($pdo->inTransaction()) { + $pdo->rollBack(); + } throw $e; } @@ -100,12 +121,15 @@ function envoyer_mail_smtp(string $to, string $subject, string $html, ?string $t $mail->Username = (string)env('SMTP_USER', ''); $mail->Password = (string)env('SMTP_PASS', ''); $secure = strtolower((string)env('SMTP_SECURE', 'tls')); - if ($secure === 'ssl') $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; - elseif ($secure === 'tls') $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; - + if ($secure === 'ssl') { + $mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS; + } elseif ($secure === 'tls') { + $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS; + } + $mail->SMTPKeepAlive = true; // réutilise la connexion $mail->Timeout = 30; // évite les blocages longs - $mail->SMTPOptions = ['ssl'=>['verify_peer'=>true,'verify_peer_name'=>true,'allow_self_signed'=>false]]; + $mail->SMTPOptions = ['ssl' => ['verify_peer' => true,'verify_peer_name' => true,'allow_self_signed' => false]]; $mail->CharSet = 'UTF-8'; $mail->isHTML(true); @@ -138,12 +162,11 @@ function envoyer_mail_smtp(string $to, string $subject, string $html, ?string $t $mail->send(); - $pdo->prepare("UPDATE journal_smtp SET status='sent', sent_at=NOW() WHERE id=:id")->execute([':id'=>$rowId]); + $pdo->prepare("UPDATE journal_smtp SET status='sent', sent_at=NOW() WHERE id=:id")->execute([':id' => $rowId]); return true; } catch (Exception $e) { $pdo->prepare("UPDATE journal_smtp SET status='error', error_message=:err, sent_at=NOW() WHERE id=:id") - ->execute([':id'=>$rowId, ':err'=>substr($e->getMessage(),0,1000)]); + ->execute([':id' => $rowId, ':err' => substr($e->getMessage(), 0, 1000)]); throw new RuntimeException('Envoi email impossible: '.$e->getMessage()); } } - diff --git a/includes/footer.php b/templates/footer.php similarity index 100% rename from includes/footer.php rename to templates/footer.php diff --git a/includes/header.php b/templates/header.php similarity index 98% rename from includes/header.php rename to templates/header.php index 9daed94..7fec33a 100644 --- a/includes/header.php +++ b/templates/header.php @@ -15,9 +15,9 @@ sessionAlready()) { -?> + +if ($messageManager->sessionAlready()) { + ?>