--- phase: 02-shared-email-security plan: 01 type: execute wave: 1 depends_on: ["01-01"] files_modified: - autoload/Shared/Email/Email.php - autoload/Shared/Security/CsrfToken.php - autoload/Shared/Helpers/Helpers.php autonomous: true delegation: off --- ## Goal Utworzyć Shared\Email\Email i Shared\Security\CsrfToken wzorując się na shopPRO. Przenieść logikę z Helpers::send_email() i Helpers::get_token()/is_token_valid() do dedykowanych klas. Zachować wrappery w Helpers dla kompatybilności. ## Purpose Email i Security to brakujące moduły Shared potrzebne przed refaktoryzacją Admin i Frontend kontrolerów. CsrfToken z kryptograficznie bezpiecznym tokenem zastąpi słaby sha1(mt_rand()). ## Output - autoload/Shared/Email/Email.php — klasa email z PHPMailer - autoload/Shared/Security/CsrfToken.php — CSRF z random_bytes + hash_equals - Wrappery w Helpers.php delegujące do nowych klas ## Project Context @.paul/PROJECT.md @.paul/ROADMAP.md ## Prior Work @.paul/phases/01-infrastructure/01-01-SUMMARY.md — autoloader gotowy, PSR-4 działa ## Source Files @autoload/Shared/Helpers/Helpers.php — zawiera send_email(), get_token(), is_token_valid() ## Reference shopPRO autoload/Shared/Email/Email.php — docelowa implementacja shopPRO autoload/Shared/Security/CsrfToken.php — docelowa implementacja ## AC-1: Email class ```gherkin Given plik autoload/Shared/Email/Email.php istnieje When załaduję klasę Shared\Email\Email Then klasa ma metody: send(), email_check(), load_by_name() And send() używa PHPMailer do wysyłki maili ``` ## AC-2: CsrfToken class ```gherkin Given plik autoload/Shared/Security/CsrfToken.php istnieje When załaduję klasę Shared\Security\CsrfToken Then klasa ma statyczne metody: getToken(), validate(), regenerate() And getToken() używa bin2hex(random_bytes(32)) And validate() używa hash_equals() (timing-safe) ``` ## AC-3: Wrappery w Helpers ```gherkin Given Helpers::send_email() i Helpers::get_token() nadal istnieją When wywołam je z istniejącego kodu Then delegują do nowych klas (Shared\Email\Email i Shared\Security\CsrfToken) And istniejący kod działa bez zmian ``` Task 1: Utworzenie Shared\Email\Email autoload/Shared/Email/Email.php Utworzyć klasę Email wzorowaną na shopPRO: - namespace Shared\Email - Właściwość $table = 'pp_newsletter_templates' - Właściwość $text (treść maila), $headers, $newsletter_headers, $newsletter_footers - Metoda load_by_name(string $name) — ładuje szablon z DB - Metoda email_check($email) — walidacja filter_var - Metoda send(string $email, string $subject, bool $newsletter_headers = false, string $file = null) - Używa PHPMailer (require_once z libraries/) - Regex do naprawy relatywnych URL w obrazkach/linkach - Obsługa załączników - Return $mail->Send() WAŻNE: Sprawdzić w Helpers.php jak wygląda obecna implementacja send_email() i przenieść tę logikę do nowej klasy, dostosowując do wzorca shopPRO. PHP < 8.0 — brak named args, union types, match. Sprawdzić że plik istnieje, ma namespace Shared\Email, klasę Email z metodami send(), email_check() AC-1 satisfied: Email class z PHPMailer Task 2: Utworzenie Shared\Security\CsrfToken autoload/Shared/Security/CsrfToken.php Utworzyć klasę CsrfToken wzorowaną na shopPRO: - namespace Shared\Security - const SESSION_KEY = 'csrf_token' - static getToken(): string - Jeśli brak tokenu w sesji → generuje bin2hex(random_bytes(32)) - Zapisuje w $_SESSION[self::SESSION_KEY] - Zwraca token - static validate(string $token): bool - Porównuje z $_SESSION[self::SESSION_KEY] używając hash_equals() - Return true/false (NIE usuwać tokenu po walidacji — to robi regenerate()) - static regenerate(): void - Wymusza nowy token: unset($_SESSION[self::SESSION_KEY]) PHP < 8.0 — brak named args, union types, match. Sprawdzić że plik istnieje, ma namespace Shared\Security, klasę CsrfToken z metodami getToken(), validate(), regenerate() AC-2 satisfied: CsrfToken z random_bytes + hash_equals Task 3: Wrappery w Helpers.php autoload/Shared/Helpers/Helpers.php W klasie Helpers: 1. Metoda send_email() — zamienić ciało na delegację: $email = new \Shared\Email\Email(); $email->text = $text; return $email->send($to, $subject, false, $file); 2. Metoda get_token() — zamienić ciało na delegację: return \Shared\Security\CsrfToken::getToken(); 3. Metoda is_token_valid() — zamienić ciało na delegację: return \Shared\Security\CsrfToken::validate($token); Zachować sygnatury metod identyczne — żaden calling code się nie zmienia. NIE usuwać metod — to wrappery dla kompatybilności wstecznej. NIE zmieniać żadnych innych metod w Helpers. Sprawdzić że Helpers::send_email(), get_token(), is_token_valid() delegują do nowych klas AC-3 satisfied: Wrappery delegują, istniejący kod działa bez zmian ## DO NOT CHANGE - autoload/Domain/* (nie ruszać repositories) - autoload/Shared/Cache/* (nie ruszać) - autoload/Shared/Html/* (nie ruszać) - autoload/Shared/Image/* (nie ruszać) - autoload/Shared/Tpl/* (nie ruszać) - config.php, libraries/* (nie ruszać) ## SCOPE LIMITS - Tylko Email i Security — nie refaktoryzować innych metod Helpers - Nie zmieniać callerów (admin/, front/) — oni nadal używają Helpers:: - Nie dodawać nowych zależności poza tym co już jest w libraries/ Before declaring plan complete: - [ ] autoload/Shared/Email/Email.php istnieje z namespace Shared\Email - [ ] autoload/Shared/Security/CsrfToken.php istnieje z namespace Shared\Security - [ ] Helpers::send_email() deleguje do Email class - [ ] Helpers::get_token() deleguje do CsrfToken::getToken() - [ ] Helpers::is_token_valid() deleguje do CsrfToken::validate() - [ ] Żadne inne metody w Helpers nie zostały zmienione - All acceptance criteria met - Email i CsrfToken klasy utworzone z poprawnymi namespace'ami - Wrappery w Helpers zachowują kompatybilność wsteczną - Zero regresji — istniejący kod używający Helpers:: działa bez zmian After completion, create `.paul/phases/02-shared-email-security/02-01-SUMMARY.md`