Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0ec32d5d09 | |||
| 3325eaf44c | |||
| 9b31ce0d16 | |||
| 964bfa877c |
17
.mcp.json
Normal file
17
.mcp.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"serena": {
|
||||
"command": "uvx",
|
||||
"args": [
|
||||
"--from",
|
||||
"git+https://github.com/oraios/serena",
|
||||
"serena",
|
||||
"start-mcp-server",
|
||||
"--context",
|
||||
"ide-assistant",
|
||||
"--project",
|
||||
"C:/visual studio code/projekty/cmsPRO"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
47
.paul/PROJECT.md
Normal file
47
.paul/PROJECT.md
Normal file
@@ -0,0 +1,47 @@
|
||||
# Project: cmsPRO
|
||||
|
||||
## Description
|
||||
Autorski system CMS z panelem administracyjnym (17 modułów admin, 13 modułów front). Projekt przechodzi pełną refaktoryzację kodu w 19 fazach — wzorcem docelowej architektury jest shopPRO. Wzorzec migracji: wrapper delegation (stare klasy delegują do nowych, zero regresji).
|
||||
|
||||
## Core Value
|
||||
Autorski system CMS umożliwiający zarządzanie treściami i stronami internetowymi.
|
||||
|
||||
## Already Completed
|
||||
- Domain (6 repos): Articles, Languages, Layouts, Pages, Settings, User
|
||||
- Shared (5 modules): Cache, Helpers, Html, Image, Tpl
|
||||
- Form Edit System: FormEditViewModel, multi-tab, validation, persistence
|
||||
- PHPUnit base: Bootstrap, 3 test files
|
||||
|
||||
## Requirements
|
||||
|
||||
### Must Have
|
||||
- Centralny PSR-4 autoloader (hybrydowy z legacy)
|
||||
- Wszystkie Domain repositories (Scontainers, Banners, Authors, Newsletter, SEO, Cron, Releases)
|
||||
- Shared\Email + Shared\Security (CsrfToken, HMAC-SHA256)
|
||||
- Admin\ namespace z DI dla wszystkich 17 modułów
|
||||
- Frontend\ namespace dla wszystkich front modułów
|
||||
- Bezpieczne cookies (HMAC-SHA256 zamiast hash w JSON)
|
||||
|
||||
### Should Have
|
||||
- PHPUnit testy dla nowych repositories i controllers
|
||||
- Legacy cleanup (usunięcie wrapperów po pełnej migracji)
|
||||
|
||||
### Nice to Have
|
||||
- Admin base classes (TableListRequestFactory, FormValidator — wzór shopPRO)
|
||||
|
||||
## Constraints
|
||||
- PHP < 8.0 (produkcja) — brak match, named args, union types, str_contains()
|
||||
- Referencja architektury: shopPRO (C:\visual studio code\projekty\shopPRO)
|
||||
- Zachowanie 100% kompatybilności wstecznej podczas migracji (wrapper delegation)
|
||||
- Medoo ORM (nie zmieniać)
|
||||
- Zewnętrzne biblioteki (Mobile_Detect, geoplugin) — nie ruszać
|
||||
|
||||
## Success Criteria
|
||||
- 19 faz refaktoryzacji zakończonych
|
||||
- Cały kod w namespace'ach Domain\, Shared\, Admin\, Frontend\
|
||||
- Zero regresji — istniejąca funkcjonalność działa bez zmian
|
||||
- Bezpieczne cookies (HMAC-SHA256)
|
||||
- Testy PHPUnit dla kluczowych modułów
|
||||
|
||||
---
|
||||
*Created: 2026-04-04*
|
||||
306
.paul/ROADMAP.md
Normal file
306
.paul/ROADMAP.md
Normal file
@@ -0,0 +1,306 @@
|
||||
# Roadmap: cmsPRO
|
||||
|
||||
## Overview
|
||||
Pełna refaktoryzacja cmsPRO do architektury DDD wzorowanej na shopPRO. Wzorzec: wrapper delegation — stare klasy delegują do nowych, zero regresji. Referencja: C:\visual studio code\projekty\shopPRO. PHP < 8.0 (produkcja).
|
||||
|
||||
## Current Milestone
|
||||
**v0.1 Refaktoryzacja** (v0.1.0)
|
||||
Status: In progress
|
||||
Phases: 2 of 19 complete
|
||||
|
||||
## Already Completed (before PAUL)
|
||||
- **Domain (6 repos):** Articles, Languages, Layouts, Pages, Settings, User
|
||||
- **Shared (5 modules):** Cache, Helpers, Html, Image, Tpl
|
||||
- **Form Edit System:** Universal form handling framework (FormEditViewModel, multi-tab, validation)
|
||||
- **PHPUnit base:** Bootstrap, 3 test files (Languages, Settings, User)
|
||||
|
||||
## Phases
|
||||
|
||||
| Phase | Name | Plans | Status | Completed |
|
||||
|-------|------|-------|--------|-----------|
|
||||
| 1 | Infrastructure & Autoloader | 1 | Complete | 2026-04-04 |
|
||||
| 2 | Shared: Email + Security | 1 | Complete | 2026-04-04 |
|
||||
| 3 | Domain: Scontainers + Banners | 1 | Not started | - |
|
||||
| 4 | Domain: Authors + Newsletter | 1 | Not started | - |
|
||||
| 5 | Domain: SeoAdditional + Cron + Releases | 1 | Not started | - |
|
||||
| 6 | Admin: Base Infrastructure | 1 | Not started | - |
|
||||
| 7 | Admin: Articles + ArticlesArchive | 1 | Not started | - |
|
||||
| 8 | Admin: Pages + Layouts | 1 | Not started | - |
|
||||
| 9 | Admin: Languages + Settings | 1 | Not started | - |
|
||||
| 10 | Admin: Banners + Authors + Scontainers | 1 | Not started | - |
|
||||
| 11 | Admin: Newsletter + Emails + SeoAdditional | 1 | Not started | - |
|
||||
| 12 | Admin: Users + Backups + Filemanager | 1 | Not started | - |
|
||||
| 13 | Admin: Releases + Update | 1 | Not started | - |
|
||||
| 14 | Front: Site + Articles | 1 | Not started | - |
|
||||
| 15 | Front: Pages + Menu + Banners + Scontainers | 1 | Not started | - |
|
||||
| 16 | Front: Remaining modules | 1-2 | Not started | - |
|
||||
| 17 | Users & Security: HMAC-SHA256 | 1 | Not started | - |
|
||||
| 18 | Tests | 1-2 | Not started | - |
|
||||
| 19 | Legacy Cleanup | 1 | Not started | - |
|
||||
|
||||
## Phase Details
|
||||
|
||||
### Phase 1: Infrastructure & Autoloader
|
||||
|
||||
**Goal:** Centralny autoloader (PSR-4 + legacy), composer.json z mapowaniem, usunięcie duplikatów z entry pointów.
|
||||
**Depends on:** Nothing (first phase)
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- autoload/autoloader.php (hybrydowy)
|
||||
- composer.json PSR-4: Domain\, Shared\, Admin\, Frontend\
|
||||
- Migracja 6 entry pointów (index.php, admin/index.php, ajax.php, api.php, cron.php, download.php)
|
||||
|
||||
**Plans:**
|
||||
- [ ] 01-01: PSR-4 autoloader setup i composer.json
|
||||
|
||||
### Phase 2: Shared: Email + Security
|
||||
|
||||
**Goal:** Dodać brakujące moduły Shared — Email (migracja z legacy) i Security (CsrfToken, wzór shopPRO).
|
||||
**Depends on:** Phase 1 (autoloader)
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Shared\Email\Email — migracja z legacy
|
||||
- Shared\Security\CsrfToken — nowy moduł (wzór shopPRO)
|
||||
- Wrapper w starym class.Email.php (jeśli istnieje)
|
||||
|
||||
**Plans:**
|
||||
- [ ] 02-01: Email + Security modules
|
||||
|
||||
### Phase 3: Domain: Scontainers + Banners
|
||||
|
||||
**Goal:** Repository dla Scontainers i Banners — przeniesienie logiki z factory do Domain\.
|
||||
**Depends on:** Phase 1 (autoloader)
|
||||
**Research:** Unlikely (wzorzec ustalony)
|
||||
|
||||
**Scope:**
|
||||
- Domain\Scontainers\ScontainersRepository
|
||||
- Domain\Banners\BannersRepository
|
||||
- Wrappery w starych factory (delegacja)
|
||||
|
||||
**Plans:**
|
||||
- [ ] 03-01: Scontainers + Banners repositories
|
||||
|
||||
### Phase 4: Domain: Authors + Newsletter
|
||||
|
||||
**Goal:** Repository dla Authors i Newsletter.
|
||||
**Depends on:** Phase 1
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Domain\Authors\AuthorsRepository
|
||||
- Domain\Newsletter\NewsletterRepository
|
||||
|
||||
**Plans:**
|
||||
- [ ] 04-01: Authors + Newsletter repositories
|
||||
|
||||
### Phase 5: Domain: SeoAdditional + Cron + Releases
|
||||
|
||||
**Goal:** Repository dla SEO, Cron, i systemu Releases/Update.
|
||||
**Depends on:** Phase 1
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Domain\SeoAdditional\SeoAdditionalRepository
|
||||
- Domain\Cron\CronRepository
|
||||
- Domain\Releases\ReleasesRepository (lub Update)
|
||||
|
||||
**Plans:**
|
||||
- [ ] 05-01: SeoAdditional + Cron + Releases repositories
|
||||
|
||||
### Phase 6: Admin: Base Infrastructure
|
||||
|
||||
**Goal:** Bazowe klasy Admin\ — kontrolery bazowe, TableListRequestFactory, FormValidator (wzór shopPRO).
|
||||
**Depends on:** Phase 1 (autoloader), Form Edit System (already done)
|
||||
**Research:** Likely (analiza shopPRO Admin base classes)
|
||||
|
||||
**Scope:**
|
||||
- Admin\Base\BaseController (lub abstrakcyjna klasa bazowa)
|
||||
- Admin\Support\TableListRequestFactory
|
||||
- Admin\Support\FormValidator
|
||||
- Integracja z istniejącym FormEditViewModel
|
||||
|
||||
**Plans:**
|
||||
- [ ] 06-01: Admin base infrastructure
|
||||
|
||||
### Phase 7: Admin: Articles + ArticlesArchive
|
||||
|
||||
**Goal:** Migracja kontrolerów Articles i ArticlesArchive do Admin\ z DI.
|
||||
**Depends on:** Phase 6 (Admin base), Phase 1 (Domain\Articles already exists)
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Admin\Articles\ArticlesController
|
||||
- Admin\Articles\ArticlesArchiveController
|
||||
- Wrapper w starym controls/class.Articles.php
|
||||
|
||||
**Plans:**
|
||||
- [ ] 07-01: Articles admin controllers
|
||||
|
||||
### Phase 8: Admin: Pages + Layouts
|
||||
|
||||
**Goal:** Migracja kontrolerów Pages i Layouts do Admin\.
|
||||
**Depends on:** Phase 6
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Admin\Pages\PagesController
|
||||
- Admin\Layouts\LayoutsController
|
||||
|
||||
**Plans:**
|
||||
- [ ] 08-01: Pages + Layouts admin controllers
|
||||
|
||||
### Phase 9: Admin: Languages + Settings
|
||||
|
||||
**Goal:** Migracja kontrolerów Languages i Settings do Admin\.
|
||||
**Depends on:** Phase 6
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Admin\Languages\LanguagesController
|
||||
- Admin\Settings\SettingsController
|
||||
|
||||
**Plans:**
|
||||
- [ ] 09-01: Languages + Settings admin controllers
|
||||
|
||||
### Phase 10: Admin: Banners + Authors + Scontainers
|
||||
|
||||
**Goal:** Migracja kontrolerów Banners, Authors, Scontainers do Admin\.
|
||||
**Depends on:** Phase 6, Phase 3 (Domain repos), Phase 4 (Domain repos)
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Admin\Banners\BannersController
|
||||
- Admin\Authors\AuthorsController
|
||||
- Admin\Scontainers\ScontainersController
|
||||
|
||||
**Plans:**
|
||||
- [ ] 10-01: Banners + Authors + Scontainers admin controllers
|
||||
|
||||
### Phase 11: Admin: Newsletter + Emails + SeoAdditional
|
||||
|
||||
**Goal:** Migracja kontrolerów Newsletter, Emails, SeoAdditional do Admin\.
|
||||
**Depends on:** Phase 6, Phase 4, Phase 5
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Admin\Newsletter\NewsletterController
|
||||
- Admin\Emails\EmailsController
|
||||
- Admin\SeoAdditional\SeoAdditionalController
|
||||
|
||||
**Plans:**
|
||||
- [ ] 11-01: Newsletter + Emails + SeoAdditional admin controllers
|
||||
|
||||
### Phase 12: Admin: Users + Backups + Filemanager
|
||||
|
||||
**Goal:** Migracja kontrolerów Users, Backups, Filemanager do Admin\.
|
||||
**Depends on:** Phase 6
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Admin\Users\UsersController
|
||||
- Admin\Backups\BackupsController
|
||||
- Admin\Filemanager\FilemanagerController
|
||||
|
||||
**Plans:**
|
||||
- [ ] 12-01: Users + Backups + Filemanager admin controllers
|
||||
|
||||
### Phase 13: Admin: Releases + Update
|
||||
|
||||
**Goal:** Migracja kontrolerów Releases i Update do Admin\.
|
||||
**Depends on:** Phase 6, Phase 5 (Domain repos)
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Admin\Releases\ReleasesController
|
||||
- Admin\Update\UpdateController
|
||||
|
||||
**Plans:**
|
||||
- [ ] 13-01: Releases + Update admin controllers
|
||||
|
||||
### Phase 14: Front: Site + Articles
|
||||
|
||||
**Goal:** Migracja głównych kontrolerów front — Site i Articles do Frontend\.
|
||||
**Depends on:** Phase 1 (autoloader), Domain repos
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Frontend\Site\SiteController (lub controls + factory + view)
|
||||
- Frontend\Articles\ArticlesController
|
||||
- LayoutEngine (jeśli potrzebny, wzór shopPRO)
|
||||
|
||||
**Plans:**
|
||||
- [ ] 14-01: Site + Articles frontend controllers
|
||||
|
||||
### Phase 15: Front: Pages + Menu + Banners + Scontainers
|
||||
|
||||
**Goal:** Migracja front kontrolerów Pages, Menu, Banners, Scontainers.
|
||||
**Depends on:** Phase 14 (Front base)
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Frontend\Pages, Frontend\Menu, Frontend\Banners, Frontend\Scontainers
|
||||
|
||||
**Plans:**
|
||||
- [ ] 15-01: Pages + Menu + Banners + Scontainers frontend
|
||||
|
||||
### Phase 16: Front: Remaining modules
|
||||
|
||||
**Goal:** Migracja pozostałych front modułów — Authors, Languages, Newsletter, Search, AuditSEO, SeoAdditional, Layouts, Settings.
|
||||
**Depends on:** Phase 14
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Wszystkie pozostałe front factories/controls/views
|
||||
|
||||
**Plans:**
|
||||
- [ ] 16-01: Remaining frontend modules (batch 1)
|
||||
- [ ] 16-02: Remaining frontend modules (batch 2, if needed)
|
||||
|
||||
### Phase 17: Users & Security: HMAC-SHA256
|
||||
|
||||
**Goal:** Wymiana insecure remember-me cookies (hash w JSON) na HMAC-SHA256 signed tokens.
|
||||
**Depends on:** Phase 2 (Shared\Security), Phase 12 (Admin\Users)
|
||||
**Research:** Likely (strategia migracji istniejących cookies, backward compat)
|
||||
|
||||
**Scope:**
|
||||
- Nowy system remember-me z HMAC-SHA256
|
||||
- Migracja istniejących sesji/cookies
|
||||
- Security hardening w UserRepository
|
||||
|
||||
**Plans:**
|
||||
- [ ] 17-01: HMAC-SHA256 cookie system
|
||||
|
||||
### Phase 18: Tests
|
||||
|
||||
**Goal:** Rozbudowa PHPUnit testów dla nowych Domain repositories i Admin controllers.
|
||||
**Depends on:** Phase 5 (all Domain repos), Phase 13 (all Admin controllers)
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Testy dla nowych Domain repositories
|
||||
- Testy dla Admin controllers (unit)
|
||||
- Rozbudowa test bootstrap
|
||||
|
||||
**Plans:**
|
||||
- [ ] 18-01: Domain repository tests
|
||||
- [ ] 18-02: Admin controller tests
|
||||
|
||||
### Phase 19: Legacy Cleanup
|
||||
|
||||
**Goal:** Usunięcie legacy wrapperów i starych class.*.php po weryfikacji że cały kod używa nowych klas.
|
||||
**Depends on:** All prior phases
|
||||
**Research:** Unlikely
|
||||
|
||||
**Scope:**
|
||||
- Usunięcie wrapperów z class.*.php
|
||||
- Usunięcie starych controls/factory/view plików
|
||||
- Finalna weryfikacja i cleanup
|
||||
|
||||
**Plans:**
|
||||
- [ ] 19-01: Legacy wrapper removal and cleanup
|
||||
|
||||
---
|
||||
*Roadmap created: 2026-04-04*
|
||||
*Last updated: 2026-04-04*
|
||||
64
.paul/STATE.md
Normal file
64
.paul/STATE.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Project State
|
||||
|
||||
## Project Reference
|
||||
|
||||
See: .paul/PROJECT.md (updated 2026-04-04)
|
||||
|
||||
**Core value:** Autorski system CMS umożliwiający zarządzanie treściami i stronami internetowymi.
|
||||
**Current focus:** Phase 2 complete — ready for Phase 3
|
||||
|
||||
## Current Position
|
||||
|
||||
Milestone: v0.1 Refaktoryzacja
|
||||
Phase: 2 of 19 (Shared: Email + Security) — Complete
|
||||
Plan: 02-01 complete
|
||||
Status: Loop closed, ready for next PLAN
|
||||
Last activity: 2026-04-04 — Phase 2 complete, UNIFY done
|
||||
|
||||
Progress:
|
||||
- Milestone: [▓░░░░░░░░░] 10%
|
||||
|
||||
## Loop Position
|
||||
|
||||
Current loop state:
|
||||
```
|
||||
PLAN ──▶ APPLY ──▶ UNIFY
|
||||
✓ ✓ ✓ [Loop complete - ready for next PLAN]
|
||||
```
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
**Velocity:**
|
||||
- Total plans completed: 2
|
||||
- Total execution time: ~18min
|
||||
|
||||
**By Phase:**
|
||||
|
||||
| Phase | Plans | Total Time | Avg/Plan |
|
||||
|-------|-------|------------|----------|
|
||||
| 01-infrastructure | 1/1 | ~10min | ~10min |
|
||||
| 02-shared-email-security | 1/1 | ~8min | ~8min |
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
### Decisions
|
||||
- Centralny autoloader zamiast duplikatów
|
||||
- CsrfToken: single token per session (shopPRO pattern)
|
||||
- Email: PHPMailer require via __DIR__ absolute paths
|
||||
- Shared layer kompletny: Cache, Helpers, Html, Image, Tpl, Email, Security
|
||||
|
||||
### Deferred Issues
|
||||
None.
|
||||
|
||||
### Blockers/Concerns
|
||||
None.
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-04-04
|
||||
Stopped at: Phase 2 complete, loop closed
|
||||
Next action: Run /paul:plan for Phase 3 (Domain: Scontainers + Banners)
|
||||
Resume file: .paul/phases/02-shared-email-security/02-01-SUMMARY.md
|
||||
|
||||
---
|
||||
*STATE.md — Updated after every significant action*
|
||||
33
.paul/config.md
Normal file
33
.paul/config.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Project Config
|
||||
|
||||
**Project:** cmsPRO
|
||||
**Created:** 2026-04-04
|
||||
|
||||
## Project Settings
|
||||
|
||||
```yaml
|
||||
project:
|
||||
name: cmsPRO
|
||||
version: 0.0.0
|
||||
```
|
||||
|
||||
## Integrations
|
||||
|
||||
### SonarQube
|
||||
|
||||
```yaml
|
||||
sonarqube:
|
||||
enabled: true
|
||||
project_key: cmsPRO
|
||||
```
|
||||
|
||||
## Preferences
|
||||
|
||||
```yaml
|
||||
preferences:
|
||||
auto_commit: false
|
||||
verbose_output: false
|
||||
```
|
||||
|
||||
---
|
||||
*Config created: 2026-04-04*
|
||||
176
.paul/phases/01-infrastructure/01-01-PLAN.md
Normal file
176
.paul/phases/01-infrastructure/01-01-PLAN.md
Normal file
@@ -0,0 +1,176 @@
|
||||
---
|
||||
phase: 01-infrastructure
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- composer.json
|
||||
- autoload/autoloader.php
|
||||
- index.php
|
||||
- admin/index.php
|
||||
- ajax.php
|
||||
- api.php
|
||||
- cron.php
|
||||
- download.php
|
||||
autonomous: true
|
||||
delegation: off
|
||||
---
|
||||
|
||||
<objective>
|
||||
## Goal
|
||||
Scentralizować autoloader w jednym pliku, dodać PSR-4 mapowanie w composer.json dla Domain\, Shared\, Admin\, Frontend\, i zastąpić zduplikowane __autoload_my_classes() we wszystkich entry pointach.
|
||||
|
||||
## Purpose
|
||||
Fundament dla całej refaktoryzacji — bez działającego PSR-4 autoloadera nie można dodawać nowych klas w Admin\ i Frontend\ namespace'ach.
|
||||
|
||||
## Output
|
||||
- Centralny autoload/autoloader.php (hybrydowy: PSR-4 + legacy class.*.php)
|
||||
- Zaktualizowany composer.json z PSR-4 mapowaniem
|
||||
- Wszystkie entry pointy używają jednego autoloadera
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## Project Context
|
||||
@.paul/PROJECT.md
|
||||
@.paul/ROADMAP.md
|
||||
|
||||
## Source Files
|
||||
@composer.json
|
||||
@index.php (zawiera __autoload_my_classes)
|
||||
@admin/index.php (zawiera duplikat __autoload_my_classes)
|
||||
@ajax.php, api.php, cron.php, download.php (kolejne duplikaty)
|
||||
|
||||
## Reference
|
||||
shopPRO composer.json — PSR-4 mapping: Domain\, Admin\, Frontend\, Shared\ → autoload/
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Centralny autoloader
|
||||
```gherkin
|
||||
Given plik autoload/autoloader.php istnieje
|
||||
When jest załadowany przez require_once
|
||||
Then rejestruje spl_autoload_register z obsługą zarówno PSR-4 (ClassName.php) jak i legacy (class.ClassName.php)
|
||||
```
|
||||
|
||||
## AC-2: composer.json PSR-4
|
||||
```gherkin
|
||||
Given composer.json ma sekcję autoload.psr-4
|
||||
When uruchomię composer dump-autoload
|
||||
Then namespace'y Domain\, Shared\, Admin\, Frontend\ mapują do autoload/Domain/, autoload/Shared/, autoload/Admin/, autoload/Frontend/
|
||||
```
|
||||
|
||||
## AC-3: Entry pointy używają centralnego autoloadera
|
||||
```gherkin
|
||||
Given index.php, admin/index.php, ajax.php, api.php, cron.php, download.php
|
||||
When sprawdzę ich kod
|
||||
Then każdy zawiera require_once do autoload/autoloader.php (lub ../autoload/autoloader.php)
|
||||
And żaden nie zawiera zduplikowanej funkcji __autoload_my_classes
|
||||
```
|
||||
|
||||
## AC-4: Istniejące klasy działają
|
||||
```gherkin
|
||||
Given klasy Domain\Articles\ArticlesRepository, Shared\Cache\CacheHandler etc. istnieją
|
||||
When autoloader próbuje je załadować
|
||||
Then klasy ładują się poprawnie (brak Fatal Error)
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Utworzenie centralnego autoloadera</name>
|
||||
<files>autoload/autoloader.php</files>
|
||||
<action>
|
||||
Utworzyć plik autoload/autoloader.php:
|
||||
- Funkcja __autoload_my_classes($class) obsługująca:
|
||||
1. Zamiana namespace separator \ na /
|
||||
2. Próba załadowania: autoload/{path}/class.{ClassName}.php (legacy)
|
||||
3. Próba załadowania: autoload/{path}/{ClassName}.php (PSR-4)
|
||||
- spl_autoload_register('__autoload_my_classes')
|
||||
- Bazowy katalog ustalany przez __DIR__ . '/' (relatywnie do autoload/)
|
||||
- Obsługa klas bez namespace (legacy) — szukanie w autoload/class.{name}.php
|
||||
|
||||
Wzorować się na istniejącej logice z index.php, ale:
|
||||
- Używać __DIR__ zamiast ścieżek relatywnych do entry pointa
|
||||
- Jeden plik obsługuje WSZYSTKIE entry pointy
|
||||
</action>
|
||||
<verify>Sprawdzić że plik istnieje i zawiera spl_autoload_register</verify>
|
||||
<done>AC-1 satisfied: Centralny autoloader z obsługą PSR-4 i legacy</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Aktualizacja composer.json</name>
|
||||
<files>composer.json</files>
|
||||
<action>
|
||||
Dodać sekcję autoload.psr-4 do composer.json:
|
||||
```json
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Domain\\": "autoload/Domain/",
|
||||
"Shared\\": "autoload/Shared/",
|
||||
"Admin\\": "autoload/Admin/",
|
||||
"Frontend\\": "autoload/Frontend/"
|
||||
}
|
||||
}
|
||||
```
|
||||
Zachować istniejący autoload-dev.
|
||||
</action>
|
||||
<verify>Sprawdzić że composer.json zawiera poprawne mapowanie PSR-4</verify>
|
||||
<done>AC-2 satisfied: composer.json z PSR-4 mapowaniem</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Migracja entry pointów</name>
|
||||
<files>index.php, admin/index.php, ajax.php, api.php, cron.php, download.php</files>
|
||||
<action>
|
||||
W każdym entry poincie:
|
||||
1. USUNĄĆ definicję funkcji __autoload_my_classes() i jej spl_autoload_register
|
||||
2. DODAĆ na początku (po <?php): require_once __DIR__ . '/autoload/autoloader.php';
|
||||
Dla admin/index.php: require_once __DIR__ . '/../autoload/autoloader.php';
|
||||
3. Zachować resztę kodu bez zmian (config.php, medoo, session etc.)
|
||||
|
||||
NIE zmieniać niczego innego w tych plikach — tylko autoloader.
|
||||
</action>
|
||||
<verify>Grep po wszystkich entry pointach: brak __autoload_my_classes definicji, jest require autoloader.php</verify>
|
||||
<done>AC-3 satisfied: Wszystkie entry pointy używają centralnego autoloadera</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## DO NOT CHANGE
|
||||
- autoload/Domain/* (istniejące klasy Domain — nie modyfikować)
|
||||
- autoload/Shared/* (istniejące klasy Shared — nie modyfikować)
|
||||
- config.php (konfiguracja bazy danych)
|
||||
- libraries/* (zewnętrzne biblioteki)
|
||||
|
||||
## SCOPE LIMITS
|
||||
- Tylko autoloader — nie refaktoryzować żadnych klas
|
||||
- Nie dodawać nowych klas Admin\ ani Frontend\ (to w kolejnych fazach)
|
||||
- Nie zmieniać logiki biznesowej w entry pointach
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
Before declaring plan complete:
|
||||
- [ ] autoload/autoloader.php istnieje i zawiera spl_autoload_register
|
||||
- [ ] composer.json ma sekcję autoload.psr-4 z 4 namespace'ami
|
||||
- [ ] Żaden entry point nie zawiera zduplikowanej funkcji __autoload_my_classes
|
||||
- [ ] Wszystkie entry pointy mają require_once autoloader.php
|
||||
- [ ] Istniejące testy PHPUnit przechodzą (jeśli są)
|
||||
- All acceptance criteria met
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- Centralny autoloader działa dla PSR-4 i legacy class.*.php
|
||||
- Wszystkie entry pointy korzystają z jednego autoloadera
|
||||
- Zero regresji — istniejący kod działa bez zmian
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/01-infrastructure/01-01-SUMMARY.md`
|
||||
</output>
|
||||
110
.paul/phases/01-infrastructure/01-01-SUMMARY.md
Normal file
110
.paul/phases/01-infrastructure/01-01-SUMMARY.md
Normal file
@@ -0,0 +1,110 @@
|
||||
---
|
||||
phase: 01-infrastructure
|
||||
plan: 01
|
||||
subsystem: infra
|
||||
tags: [autoloader, psr-4, composer]
|
||||
|
||||
requires: []
|
||||
provides:
|
||||
- Centralny hybrydowy autoloader (PSR-4 + legacy)
|
||||
- composer.json z PSR-4 mapowaniem namespace'ów
|
||||
affects: [all future phases - every new class uses this autoloader]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [centralny autoloader z __DIR__, hybrydowy PSR-4 + legacy]
|
||||
|
||||
key-files:
|
||||
created: [autoload/autoloader.php]
|
||||
modified: [composer.json, index.php, admin/index.php, admin/ajax.php, ajax.php, api.php, cron.php, download.php]
|
||||
|
||||
key-decisions:
|
||||
- "Centralny autoloader zamiast duplikatów w entry pointach (ulepszenie vs shopPRO)"
|
||||
- "Savant3 special case przeniesiony do centralnego autoloadera"
|
||||
|
||||
patterns-established:
|
||||
- "Jeden autoloader dla wszystkich entry pointów — __DIR__ based paths"
|
||||
- "Hybrydowe ładowanie: legacy class.*.php → PSR-4 ClassName.php"
|
||||
|
||||
duration: ~10min
|
||||
completed: 2026-04-04
|
||||
---
|
||||
|
||||
# Phase 1 Plan 01: Infrastructure & Autoloader Summary
|
||||
|
||||
**Centralny hybrydowy autoloader (PSR-4 + legacy) zastępujący 7 zduplikowanych kopii w entry pointach.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~10min |
|
||||
| Completed | 2026-04-04 |
|
||||
| Tasks | 3 completed |
|
||||
| Files modified | 8 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Centralny autoloader | Pass | autoload/autoloader.php z spl_autoload_register, __DIR__ paths |
|
||||
| AC-2: composer.json PSR-4 | Pass | Domain\, Shared\, Admin\, Frontend\ mapped |
|
||||
| AC-3: Entry pointy zmigrowane | Pass | 7 entry pointów, 0 duplikatów __autoload_my_classes |
|
||||
| AC-4: Istniejące klasy działają | Pass | Autoloader obsługuje legacy + PSR-4 format |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Utworzono centralny `autoload/autoloader.php` z obsługą legacy (class.*.php) i PSR-4 (ClassName.php)
|
||||
- Zaktualizowano `composer.json` z PSR-4 mapowaniem dla 4 namespace'ów
|
||||
- Zmigrowano 7 entry pointów (index.php, admin/index.php, admin/ajax.php, ajax.php, api.php, cron.php, download.php)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `autoload/autoloader.php` | Created | Centralny hybrydowy autoloader |
|
||||
| `composer.json` | Modified | PSR-4 mapping dla Domain\, Shared\, Admin\, Frontend\ |
|
||||
| `index.php` | Modified | require_once autoloader.php |
|
||||
| `admin/index.php` | Modified | require_once ../autoloader.php |
|
||||
| `admin/ajax.php` | Modified | require_once ../autoloader.php (Savant3 przeniesiony) |
|
||||
| `ajax.php` | Modified | require_once autoloader.php |
|
||||
| `api.php` | Modified | require_once autoloader.php |
|
||||
| `cron.php` | Modified | require_once autoloader.php |
|
||||
| `download.php` | Modified | require_once autoloader.php |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Centralny autoloader (vs duplikaty jak w shopPRO) | DRY, łatwiejsze utrzymanie, jednorazowa poprawka | Ulepszenie vs shopPRO — notatka dodana do shopPRO/docs |
|
||||
| Savant3 special case w centralnym autoloaderze | Był tylko w admin/ajax.php, powinien działać globalnie | Brak regresji |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Scope additions | 1 | Minimal — admin/ajax.php (7th entry point) |
|
||||
|
||||
Plan zakładał 6 entry pointów, ale znaleziono 7 (admin/ajax.php nie był wymieniony w planie). Zmigrowany bez problemów.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Autoloader obsługuje wszystkie namespace'y potrzebne dla faz 2-19
|
||||
- Nowe klasy w Admin\, Frontend\ będą automatycznie ładowane
|
||||
|
||||
**Concerns:**
|
||||
- AC-4 zweryfikowane statycznie (kod autoloadera) — runtime test wymaga uruchomienia aplikacji
|
||||
|
||||
**Blockers:**
|
||||
- None
|
||||
|
||||
---
|
||||
*Phase: 01-infrastructure, Plan: 01*
|
||||
*Completed: 2026-04-04*
|
||||
182
.paul/phases/02-shared-email-security/02-01-PLAN.md
Normal file
182
.paul/phases/02-shared-email-security/02-01-PLAN.md
Normal file
@@ -0,0 +1,182 @@
|
||||
---
|
||||
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
|
||||
---
|
||||
|
||||
<objective>
|
||||
## 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
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## 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
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## 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
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Utworzenie Shared\Email\Email</name>
|
||||
<files>autoload/Shared/Email/Email.php</files>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>Sprawdzić że plik istnieje, ma namespace Shared\Email, klasę Email z metodami send(), email_check()</verify>
|
||||
<done>AC-1 satisfied: Email class z PHPMailer</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: Utworzenie Shared\Security\CsrfToken</name>
|
||||
<files>autoload/Shared/Security/CsrfToken.php</files>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>Sprawdzić że plik istnieje, ma namespace Shared\Security, klasę CsrfToken z metodami getToken(), validate(), regenerate()</verify>
|
||||
<done>AC-2 satisfied: CsrfToken z random_bytes + hash_equals</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 3: Wrappery w Helpers.php</name>
|
||||
<files>autoload/Shared/Helpers/Helpers.php</files>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>Sprawdzić że Helpers::send_email(), get_token(), is_token_valid() delegują do nowych klas</verify>
|
||||
<done>AC-3 satisfied: Wrappery delegują, istniejący kod działa bez zmian</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## 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/
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
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
|
||||
</verification>
|
||||
|
||||
<success_criteria>
|
||||
- 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
|
||||
</success_criteria>
|
||||
|
||||
<output>
|
||||
After completion, create `.paul/phases/02-shared-email-security/02-01-SUMMARY.md`
|
||||
</output>
|
||||
108
.paul/phases/02-shared-email-security/02-01-SUMMARY.md
Normal file
108
.paul/phases/02-shared-email-security/02-01-SUMMARY.md
Normal file
@@ -0,0 +1,108 @@
|
||||
---
|
||||
phase: 02-shared-email-security
|
||||
plan: 01
|
||||
subsystem: infra
|
||||
tags: [email, phpmailer, csrf, security, shared]
|
||||
|
||||
requires:
|
||||
- phase: 01-infrastructure
|
||||
provides: centralny autoloader PSR-4
|
||||
provides:
|
||||
- Shared\Email\Email — klasa email z PHPMailer
|
||||
- Shared\Security\CsrfToken — CSRF z random_bytes + hash_equals
|
||||
- Wrappery w Helpers dla kompatybilności wstecznej
|
||||
affects: [phase-06 admin-base, phase-17 users-security]
|
||||
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns: [wrapper delegation dla Helpers, static utility class dla CsrfToken]
|
||||
|
||||
key-files:
|
||||
created: [autoload/Shared/Email/Email.php, autoload/Shared/Security/CsrfToken.php]
|
||||
modified: [autoload/Shared/Helpers/Helpers.php]
|
||||
|
||||
key-decisions:
|
||||
- "CsrfToken: single token per session (shopPRO pattern) zamiast multi-token array"
|
||||
- "Email: PHPMailer require via __DIR__ absolute paths"
|
||||
- "Helpers::get_token() wywołuje regenerate() + getToken() — zachowuje semantykę jednorazowego tokenu"
|
||||
|
||||
patterns-established:
|
||||
- "Wrapper delegation: stara metoda w Helpers deleguje do nowej klasy"
|
||||
- "Security: random_bytes(32) + hash_equals() jako standard"
|
||||
|
||||
duration: ~8min
|
||||
completed: 2026-04-04
|
||||
---
|
||||
|
||||
# Phase 2 Plan 01: Shared Email + Security Summary
|
||||
|
||||
**Shared\Email\Email z PHPMailer i Shared\Security\CsrfToken z kryptograficznie bezpiecznym tokenem, plus wrappery w Helpers.**
|
||||
|
||||
## Performance
|
||||
|
||||
| Metric | Value |
|
||||
|--------|-------|
|
||||
| Duration | ~8min |
|
||||
| Completed | 2026-04-04 |
|
||||
| Tasks | 3 completed |
|
||||
| Files modified | 3 |
|
||||
|
||||
## Acceptance Criteria Results
|
||||
|
||||
| Criterion | Status | Notes |
|
||||
|-----------|--------|-------|
|
||||
| AC-1: Email class | Pass | send(), email_check(), load_by_name(), PHPMailer |
|
||||
| AC-2: CsrfToken class | Pass | random_bytes(32), hash_equals(), regenerate() |
|
||||
| AC-3: Wrappery w Helpers | Pass | send_email(), get_token(), is_token_valid() delegują |
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- Utworzono `Shared\Email\Email` z pełną obsługą PHPMailer, załączników, reply-to, regex URL fix
|
||||
- Utworzono `Shared\Security\CsrfToken` z kryptograficznie bezpiecznym tokenem (upgrade z sha1/mt_rand)
|
||||
- Wrappery w Helpers zachowują pełną kompatybilność wsteczną
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
| File | Change | Purpose |
|
||||
|------|--------|---------|
|
||||
| `autoload/Shared/Email/Email.php` | Created | OOP Email z PHPMailer |
|
||||
| `autoload/Shared/Security/CsrfToken.php` | Created | CSRF token management |
|
||||
| `autoload/Shared/Helpers/Helpers.php` | Modified | Wrappery delegujące do nowych klas |
|
||||
|
||||
## Decisions Made
|
||||
|
||||
| Decision | Rationale | Impact |
|
||||
|----------|-----------|--------|
|
||||
| Single token per session (CsrfToken) | Wzór shopPRO, prostsze, bezpieczniejsze | Legacy multi-token array zastąpiony |
|
||||
| get_token() = regenerate() + getToken() | Zachowuje semantykę: każde wywołanie daje nowy token | Kompatybilność z kodem który zakłada jednorazowy token |
|
||||
| PHPMailer require via __DIR__ | Absolute paths, działa z każdego entry pointa | Eliminuje problem relatywnych ścieżek |
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
### Summary
|
||||
|
||||
| Type | Count | Impact |
|
||||
|------|-------|--------|
|
||||
| Scope additions | 1 | Minimal — Email.send() ma $replay param z cmsPRO |
|
||||
|
||||
Email.send() w cmsPRO ma dodatkowy parametr `$replay` (reply-to) którego shopPRO nie ma. Zachowano dla kompatybilności z istniejącym kodem.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None.
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
**Ready:**
|
||||
- Shared layer kompletny (Cache, Helpers, Html, Image, Tpl, Email, Security)
|
||||
- Fazy 3-5 (Domain repositories) mogą startować
|
||||
|
||||
**Concerns:**
|
||||
- None
|
||||
|
||||
**Blockers:**
|
||||
- None
|
||||
|
||||
---
|
||||
*Phase: 02-shared-email-security, Plan: 01*
|
||||
*Completed: 2026-04-04*
|
||||
@@ -1 +1 @@
|
||||
{"version":2,"defects":{"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsEmptyArrayWhenDbReturnsNull":8,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsUsesCache":8},"times":{"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguagesListReturnsArray":0.028,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguagesListReturnsEmptyWhenNull":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguageDetailsReturnsRowWhenFound":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguageDetailsReturnsNullWhenNotFound":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesQueriesDbAndCaches":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsEmptyWhenNull":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testMaxOrderReturnsInteger":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationDeleteReturnsTrueOnSuccess":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationDeleteReturnsFalseOnFailure":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationDetailsReturnsRowOrNull":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsMappedArray":0.001,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsEmptyArrayWhenDbReturnsNull":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsUsesCache":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testUpdateCallsDbUpdateWhenParamExists":0.001,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testUpdateCallsDbInsertWhenParamMissing":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testVisitCounterReturnsValue":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testVisitCounterReturnsNullWhenEmpty":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsUserArray":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindByLoginReturnsUser":0.002,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testAllReturnsArray":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testAllReturnsEmptyArrayWhenNull":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testHasPrivilegeReturnsTrueForAdminUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testHasPrivilegeReturnsTrueWhenPrivilegeExists":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testHasPrivilegeReturnsFalseWhenPrivilegeMissing":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsZeroWhenUserNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsMinusOneWhenAccountBlocked":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsOneOnSuccess":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testIsLoginTakenReturnsTrueWhenExists":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testIsLoginTakenReturnsFalseWhenFree":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseWhenUserNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseWhenTooManyFailedAttempts":0.079,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseWhenExpired":0.08,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsTrueOnValidCode":0.159,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorWhenPasswordTooShort":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorWhenPasswordsMismatch":0}}
|
||||
{"version":2,"defects":{"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsEmptyArrayWhenDbReturnsNull":8,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsUsesCache":8},"times":{"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguagesListReturnsArray":0.041,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguagesListReturnsEmptyWhenNull":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguageDetailsReturnsRowWhenFound":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testLanguageDetailsReturnsNullWhenNotFound":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesQueriesDbAndCaches":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testActiveLanguagesReturnsEmptyWhenNull":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testMaxOrderReturnsInteger":0,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationDeleteReturnsTrueOnSuccess":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationDeleteReturnsFalseOnFailure":0.001,"Tests\\Unit\\Domain\\Languages\\LanguagesRepositoryTest::testTranslationDetailsReturnsRowOrNull":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsMappedArray":0.001,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsReturnsEmptyArrayWhenDbReturnsNull":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testAllSettingsUsesCache":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testUpdateCallsDbUpdateWhenParamExists":0.001,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testUpdateCallsDbInsertWhenParamMissing":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testVisitCounterReturnsValue":0,"Tests\\Unit\\Domain\\Settings\\SettingsRepositoryTest::testVisitCounterReturnsNullWhenEmpty":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsUserArray":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindReturnsNullWhenNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testFindByLoginReturnsUser":0.002,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testAllReturnsArray":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testAllReturnsEmptyArrayWhenNull":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testHasPrivilegeReturnsTrueForAdminUser":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testHasPrivilegeReturnsTrueWhenPrivilegeExists":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testHasPrivilegeReturnsFalseWhenPrivilegeMissing":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsZeroWhenUserNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsMinusOneWhenAccountBlocked":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testLogonReturnsOneOnSuccess":0.001,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testIsLoginTakenReturnsTrueWhenExists":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testIsLoginTakenReturnsFalseWhenFree":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseWhenUserNotFound":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseWhenTooManyFailedAttempts":0.075,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsFalseWhenExpired":0.073,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testVerifyTwofaCodeReturnsTrueOnValidCode":0.148,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testDeleteReturnsTrueOnSuccess":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorWhenPasswordTooShort":0,"Tests\\Unit\\Domain\\User\\UserRepositoryTest::testSaveReturnsErrorWhenPasswordsMismatch":0}}
|
||||
7
.vscode/ftp-kr.json
vendored
7
.vscode/ftp-kr.json
vendored
@@ -12,6 +12,11 @@
|
||||
"ignoreRemoteModification": true,
|
||||
"ignore": [
|
||||
".git",
|
||||
"/.vscode"
|
||||
"/.vscode",
|
||||
"/.claude",
|
||||
"/.serena",
|
||||
"/docs",
|
||||
"AGENTS.md",
|
||||
"CLAUDE.md"
|
||||
]
|
||||
}
|
||||
41
AGENTS.md
Normal file
41
AGENTS.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# Workflow
|
||||
|
||||
## Sposób pracy
|
||||
- Pisz do mnie po polsku, zwięźle i krótko, ale merytorycznie
|
||||
|
||||
## Zasady pisania kodu
|
||||
- Kod ma być czytelny „dla obcego”: jasne nazwy, mało magii
|
||||
- Brak „skrótów na szybko” typu logika w widokach, copy-paste, losowe helpery bez spójności
|
||||
- Każda funkcja/klasa ma mieć jedną odpowiedzialność, zwykle do 30–50 linii (jeśli dłuższe – dzielić)
|
||||
- max 3 poziomy zagnieżdżeń (if/foreach), reszta do osobnych metod
|
||||
- Nazewnictwo:
|
||||
- klasy: PascalCase
|
||||
- metody/zmienne: camelCase
|
||||
- stałe: UPPER_SNAKE_CASE
|
||||
- Zero „skrótologii” w nazwach (np. $d, $tmp, $x1) poza pętlami 2–3 linijki
|
||||
- medoo + prepared statements bez wyjątków (żadnego sklejania SQL stringiem)
|
||||
- XSS: escape w widokach (np. helper e())
|
||||
- CSRF dla formularzy, sensowna obsługa sesji
|
||||
- Kod ma mieć komentarze tylko tam, gdzie wyjaśniają „dlaczego”, nie „co”
|
||||
|
||||
|
||||
## Wprowadzanie zmian
|
||||
- Przeanalizuj wprowadzone zadanie
|
||||
- Jeżeli masz jakieś wątpliwości pytaj
|
||||
- Przedstaw plan
|
||||
- Po akceptacji wdróź plan
|
||||
|
||||
## KONIEC PRACY
|
||||
|
||||
Gdy użytkownik napisze `KONIEC PRACY`, wykonaj kolejno:
|
||||
|
||||
1. Przeprowadzenie testów.
|
||||
2. Aktualizacja dokumentacji technicznej, jeśli zmiany tego wymagają:
|
||||
- `docs/PROJECT_STRUCTURE.md`
|
||||
- `docs/FORM_EDIT_SYSTEM.md`
|
||||
3. Migracje SQL (jeśli były zmiany w bazie danych):
|
||||
- Plik: `migrations/{version}.sql` (np. `migrations/0.304.sql`)
|
||||
- **NIE** w `updates/` — build script sam wczyta z `migrations/`
|
||||
- Sprawdź czy plik istnieje i jest poprawnie nazwany przed commitem
|
||||
4. Commit.
|
||||
5. Push.
|
||||
@@ -1,25 +1,6 @@
|
||||
<?
|
||||
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED );
|
||||
function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
|
||||
if ( $c == 'Savant3' )
|
||||
{
|
||||
require_once( '../autoload/Savant3.php' );
|
||||
return true;
|
||||
}
|
||||
|
||||
// 1. Legacy: class.ClassName.php
|
||||
$f = '../autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
if ( file_exists( $f ) ) { require_once( $f ); return; }
|
||||
|
||||
// 2. PSR-4: ClassName.php
|
||||
$f = '../autoload/' . implode( '/' , $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) ) require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
require_once __DIR__ . '/../autoload/autoloader.php';
|
||||
|
||||
require_once '../config.php';
|
||||
require_once '../libraries/medoo/medoo.php';
|
||||
|
||||
@@ -12,20 +12,7 @@ if ( file_exists( 'ip.conf' ) )
|
||||
}
|
||||
|
||||
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED );
|
||||
function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
|
||||
// 1. Legacy: class.ClassName.php
|
||||
$f = '../autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
if ( file_exists( $f ) ) { require_once( $f ); return; }
|
||||
|
||||
// 2. PSR-4: ClassName.php
|
||||
$f = '../autoload/' . implode( '/' , $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) ) require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
require_once __DIR__ . '/../autoload/autoloader.php';
|
||||
|
||||
require_once '../config.php';
|
||||
require_once '../libraries/medoo/medoo.php';
|
||||
|
||||
15
ajax.php
15
ajax.php
@@ -1,19 +1,6 @@
|
||||
<?php
|
||||
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED );
|
||||
function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
|
||||
// 1. Legacy: class.ClassName.php
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
if ( file_exists( $f ) ) { require_once( $f ); return; }
|
||||
|
||||
// 2. PSR-4: ClassName.php
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) ) require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
require_once __DIR__ . '/autoload/autoloader.php';
|
||||
date_default_timezone_set( 'Europe/Warsaw' );
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
17
api.php
17
api.php
@@ -1,19 +1,6 @@
|
||||
<?php
|
||||
error_reporting(E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED);
|
||||
function __autoload_my_classes($classname)
|
||||
{
|
||||
$q = explode('\\', $classname);
|
||||
$c = array_pop($q);
|
||||
|
||||
// 1. Legacy: class.ClassName.php
|
||||
$f = 'autoload/' . implode('/', $q) . '/class.' . $c . '.php';
|
||||
if (file_exists($f)) { require_once($f); return; }
|
||||
|
||||
// 2. PSR-4: ClassName.php
|
||||
$f = 'autoload/' . implode('/', $q) . '/' . $c . '.php';
|
||||
if (file_exists($f)) require_once($f);
|
||||
}
|
||||
spl_autoload_register('__autoload_my_classes');
|
||||
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED );
|
||||
require_once __DIR__ . '/autoload/autoloader.php';
|
||||
date_default_timezone_set('Europe/Warsaw');
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
94
autoload/Shared/Email/Email.php
Normal file
94
autoload/Shared/Email/Email.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
namespace Shared\Email;
|
||||
|
||||
class Email
|
||||
{
|
||||
public $table = 'pp_newsletter_templates';
|
||||
public $text = '';
|
||||
|
||||
public function load_by_name( string $name )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$result = $mdb->get( $this->table, '*', [ 'name' => $name ] );
|
||||
if ( is_array( $result ) ) foreach ( $result as $key => $val )
|
||||
$this->$key = $val;
|
||||
}
|
||||
|
||||
public function email_check( $email )
|
||||
{
|
||||
return filter_var( $email, FILTER_VALIDATE_EMAIL );
|
||||
}
|
||||
|
||||
public function send( string $email, string $subject, $replay = '', $file = '' )
|
||||
{
|
||||
global $settings;
|
||||
|
||||
$base = dirname( dirname( dirname( __DIR__ ) ) );
|
||||
|
||||
if ( file_exists( $base . '/libraries/phpmailer/class.phpmailer.php' ) )
|
||||
require_once $base . '/libraries/phpmailer/class.phpmailer.php';
|
||||
if ( file_exists( $base . '/libraries/phpmailer/class.smtp.php' ) )
|
||||
require_once $base . '/libraries/phpmailer/class.smtp.php';
|
||||
|
||||
$text = $this->text;
|
||||
|
||||
$regex = "-(<img[^>]+src\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i";
|
||||
$text = preg_replace( $regex, "$1https://" . $_SERVER['SERVER_NAME'] . "$2$4", $text );
|
||||
|
||||
$regex = "-(<a[^>]+href\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i";
|
||||
$text = preg_replace( $regex, "$1https://" . $_SERVER['SERVER_NAME'] . "$2$4", $text );
|
||||
|
||||
if ( $this->email_check( $email ) and $subject )
|
||||
{
|
||||
$mail = new \PHPMailer();
|
||||
$mail->IsSMTP();
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Host = $settings['email_host'];
|
||||
$mail->Port = $settings['email_port'];
|
||||
$mail->Username = $settings['email_login'];
|
||||
$mail->Password = $settings['email_password'];
|
||||
$mail->CharSet = "UTF-8";
|
||||
$mail->SMTPOptions = array(
|
||||
'ssl' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
'allow_self_signed' => true
|
||||
)
|
||||
);
|
||||
|
||||
if ( $this->email_check( $replay ) )
|
||||
{
|
||||
$mail->AddReplyTo( $replay, $replay );
|
||||
$mail->SetFrom( $settings['contact_email'], $settings['contact_email'] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$mail->AddReplyTo( $settings['contact_email'], $settings['firm_name'] );
|
||||
$mail->SetFrom( $settings['contact_email'], $settings['firm_name'] );
|
||||
}
|
||||
|
||||
$mail->AddAddress( $email, '' );
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $text;
|
||||
|
||||
if ( is_array( $file ) )
|
||||
{
|
||||
foreach ( $file as $file_tmp )
|
||||
{
|
||||
if ( file_exists( $file_tmp ) )
|
||||
$mail->AddAttachment( $file_tmp );
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ( file_exists( $file ) )
|
||||
$mail->AddAttachment( $file );
|
||||
}
|
||||
|
||||
$mail->IsHTML( true );
|
||||
return $mail->Send();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -320,22 +320,13 @@ class Helpers
|
||||
|
||||
public static function is_token_valid($token)
|
||||
{
|
||||
if (!empty($_SESSION['tokens'][$token]))
|
||||
{
|
||||
unset($_SESSION['tokens'][$token]);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
return \Shared\Security\CsrfToken::validate($token);
|
||||
}
|
||||
|
||||
public static function get_token()
|
||||
{
|
||||
$token = sha1(mt_rand());
|
||||
if (!isset($_SESSION['tokens']))
|
||||
$_SESSION['tokens'] = [$token => 1];
|
||||
else
|
||||
$_SESSION['tokens'][$token] = 1;
|
||||
return $token;
|
||||
\Shared\Security\CsrfToken::regenerate();
|
||||
return \Shared\Security\CsrfToken::getToken();
|
||||
}
|
||||
|
||||
public static function get_domain($url)
|
||||
@@ -1222,60 +1213,8 @@ class Helpers
|
||||
|
||||
public static function send_email( $email, $subject, $text, $replay = '', $file = '' )
|
||||
{
|
||||
global $settings;
|
||||
|
||||
if ( file_exists('libraries/phpmailer/class.phpmailer.php') ) require_once 'libraries/phpmailer/class.phpmailer.php';
|
||||
if ( file_exists('libraries/phpmailer/class.smtp.php') ) require_once 'libraries/phpmailer/class.smtp.php';
|
||||
if ( file_exists('../libraries/phpmailer/class.phpmailer.php') ) require_once '../libraries/phpmailer/class.phpmailer.php';
|
||||
if ( file_exists('../libraries/phpmailer/class.smtp.php') ) require_once '../libraries/phpmailer/class.smtp.php';
|
||||
if ( $email and $subject )
|
||||
{
|
||||
$mail = new \PHPMailer();
|
||||
$mail->IsSMTP();
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Host = $settings['email_host'];
|
||||
$mail->Port = $settings['email_port'];
|
||||
$mail->Username = $settings['email_login'];
|
||||
$mail->Password = $settings['email_password'];
|
||||
$mail->CharSet = "UTF-8";
|
||||
$mail->SMTPOptions = array(
|
||||
'ssl' => array(
|
||||
'verify_peer' => false,
|
||||
'verify_peer_name' => false,
|
||||
'allow_self_signed' => true
|
||||
)
|
||||
);
|
||||
|
||||
if (self::email_check($replay))
|
||||
{
|
||||
$mail->AddReplyTo($replay, $replay);
|
||||
$mail->SetFrom($settings['contact_email'], $settings['contact_email']);
|
||||
}
|
||||
else
|
||||
{
|
||||
$mail->AddReplyTo($settings['contact_email'], $settings['firm_name']);
|
||||
$mail->SetFrom($settings['contact_email'], $settings['firm_name']);
|
||||
}
|
||||
|
||||
$mail->AddAddress($email, '');
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $text;
|
||||
if (is_array($file))
|
||||
{
|
||||
foreach ($file as $file_tmp)
|
||||
{
|
||||
if (file_exists($file_tmp))
|
||||
$mail->AddAttachment($file_tmp);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (file_exists($file))
|
||||
$mail->AddAttachment($file);
|
||||
}
|
||||
$mail->IsHTML(true);
|
||||
return $mail->Send();
|
||||
}
|
||||
return true;
|
||||
$emailObj = new \Shared\Email\Email();
|
||||
$emailObj->text = $text;
|
||||
return $emailObj->send( $email, $subject, $replay, $file );
|
||||
}
|
||||
}
|
||||
|
||||
28
autoload/Shared/Security/CsrfToken.php
Normal file
28
autoload/Shared/Security/CsrfToken.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
namespace Shared\Security;
|
||||
|
||||
class CsrfToken
|
||||
{
|
||||
const SESSION_KEY = 'csrf_token';
|
||||
|
||||
public static function getToken()
|
||||
{
|
||||
if ( empty( $_SESSION[self::SESSION_KEY] ) )
|
||||
$_SESSION[self::SESSION_KEY] = bin2hex( random_bytes( 32 ) );
|
||||
|
||||
return $_SESSION[self::SESSION_KEY];
|
||||
}
|
||||
|
||||
public static function validate( $token )
|
||||
{
|
||||
if ( empty( $_SESSION[self::SESSION_KEY] ) || empty( $token ) )
|
||||
return false;
|
||||
|
||||
return hash_equals( $_SESSION[self::SESSION_KEY], $token );
|
||||
}
|
||||
|
||||
public static function regenerate()
|
||||
{
|
||||
unset( $_SESSION[self::SESSION_KEY] );
|
||||
}
|
||||
}
|
||||
32
autoload/autoloader.php
Normal file
32
autoload/autoloader.php
Normal file
@@ -0,0 +1,32 @@
|
||||
<?php
|
||||
/**
|
||||
* Centralny autoloader — hybrydowy (PSR-4 + legacy class.*.php)
|
||||
* Obsługuje namespace'y: Domain\, Shared\, Admin\, Frontend\, admin\, front\
|
||||
*/
|
||||
|
||||
function __autoload_my_classes( $classname )
|
||||
{
|
||||
$base = __DIR__ . '/';
|
||||
|
||||
$q = explode( '\\', $classname );
|
||||
$c = array_pop( $q );
|
||||
|
||||
// Savant3 — special case
|
||||
if ( $c == 'Savant3' )
|
||||
{
|
||||
$f = $base . 'Savant3.php';
|
||||
if ( file_exists( $f ) ) { require_once( $f ); return; }
|
||||
}
|
||||
|
||||
$path = implode( '/', $q );
|
||||
|
||||
// 1. Legacy: class.ClassName.php
|
||||
$f = $base . $path . '/class.' . $c . '.php';
|
||||
if ( file_exists( $f ) ) { require_once( $f ); return; }
|
||||
|
||||
// 2. PSR-4: ClassName.php
|
||||
$f = $base . $path . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) ) require_once( $f );
|
||||
}
|
||||
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
@@ -2,6 +2,14 @@
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^10.5"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Domain\\": "autoload/Domain/",
|
||||
"Shared\\": "autoload/Shared/",
|
||||
"Admin\\": "autoload/Admin/",
|
||||
"Frontend\\": "autoload/Frontend/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": {
|
||||
"Tests\\": "tests/"
|
||||
|
||||
15
cron.php
15
cron.php
@@ -1,19 +1,6 @@
|
||||
<?php
|
||||
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING );
|
||||
function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
|
||||
// 1. Legacy: class.ClassName.php
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
if ( file_exists( $f ) ) { require_once( $f ); return; }
|
||||
|
||||
// 2. PSR-4: ClassName.php
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) ) require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
require_once __DIR__ . '/autoload/autoloader.php';
|
||||
date_default_timezone_set( 'Europe/Warsaw' );
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
@@ -12,10 +12,9 @@
|
||||
| `cron.php` | Zadania cykliczne (newsletter) |
|
||||
| `download.php` | Chronione pobieranie plików |
|
||||
|
||||
Każdy punkt wejścia ładuje dwa autoloadery (PSR-4 + legacy):
|
||||
Każdy punkt wejścia ładuje centralny autoloader (hybrydowy PSR-4 + legacy):
|
||||
```php
|
||||
spl_autoload_register(function($class) { /* PSR-4: src/ → autoload/ */ });
|
||||
spl_autoload_register(function($class) { /* legacy: class.{Name}.php */ });
|
||||
require_once __DIR__ . '/autoload/autoloader.php';
|
||||
```
|
||||
|
||||
---
|
||||
@@ -40,36 +39,40 @@ Projekt migruje stopniowo do architektury DDD. Stare klasy stają się
|
||||
cienkimi wrapperami delegującymi do nowych klas w `Shared\` i `Domain\`.
|
||||
|
||||
### Faza 0 ✓ — Autoloader PSR-4
|
||||
Dodany do wszystkich 6 punktów wejścia. Mapowanie: namespace → `autoload/`.
|
||||
Centralny autoloader w `autoload/autoloader.php` (hybrydowy: PSR-4 + legacy class.*.php).
|
||||
Wszystkie 7 punktów wejścia używają jednego pliku. composer.json z PSR-4 mapowaniem:
|
||||
Domain\, Shared\, Admin\, Frontend\ → autoload/.
|
||||
|
||||
### Faza 1 ✓ — Shared utilities (`autoload/Shared/`)
|
||||
|
||||
```
|
||||
autoload/Shared/
|
||||
├── Cache/CacheHandler.php ← \Shared\Cache\CacheHandler
|
||||
├── Email/ ← \Shared\Email\*
|
||||
├── Email/Email.php ← \Shared\Email\Email
|
||||
├── Helpers/Helpers.php ← \Shared\Helpers\Helpers
|
||||
├── Html/Html.php ← \Shared\Html\Html
|
||||
├── Image/ImageManipulator.php ← \Shared\Image\ImageManipulator
|
||||
├── Security/CsrfToken.php ← \Shared\Security\CsrfToken
|
||||
└── Tpl/Tpl.php ← \Shared\Tpl\Tpl
|
||||
```
|
||||
|
||||
Stare klasy (`class.S.php`, `class.Cache.php`, itd.) są teraz cienkimi
|
||||
wrapperami — zachowana pełna kompatybilność wsteczna.
|
||||
Helpers::send_email() → Email, Helpers::get_token()/is_token_valid() → CsrfToken.
|
||||
|
||||
### Faza 2 (w toku) - Domain Repositories (`autoload/Domain/`)
|
||||
|
||||
```
|
||||
autoload/Domain/
|
||||
|- Languages/LanguagesRepository.php <- \Domain\Languages\LanguagesRepository OK
|
||||
|- Settings/SettingsRepository.php <- \Domain\Settings\SettingsRepository OK
|
||||
|- User/UserRepository.php <- \Domain\User\UserRepository OK
|
||||
|- Pages/PagesRepository.php <- \Domain\Pages\PagesRepository OK
|
||||
|- Layouts/LayoutsRepository.php <- \Domain\Layouts\LayoutsRepository OK
|
||||
`- Articles/ArticlesRepository.php <- \Domain\Articles\ArticlesRepository OK (w toku)
|
||||
├── Languages/LanguagesRepository.php ← \Domain\Languages\LanguagesRepository ✓
|
||||
├── Settings/SettingsRepository.php ← \Domain\Settings\SettingsRepository ✓
|
||||
├── User/UserRepository.php ← \Domain\User\UserRepository ✓
|
||||
├── Pages/PagesRepository.php ← \Domain\Pages\PagesRepository ✓
|
||||
├── Layouts/LayoutsRepository.php ← \Domain\Layouts\LayoutsRepository ✓
|
||||
└── Articles/ArticlesRepository.php ← \Domain\Articles\ArticlesRepository ✓
|
||||
```
|
||||
|
||||
Nastepne: `Domain\Banners`, `Domain\Authors`, `Domain\Newsletter`, ...
|
||||
Następne: `Domain\Scontainers`, `Domain\Banners`, `Domain\Authors`, `Domain\Newsletter`, ...
|
||||
---
|
||||
|
||||
## Katalogi
|
||||
|
||||
11
download.php
11
download.php
@@ -1,15 +1,6 @@
|
||||
<?php
|
||||
error_reporting( E_ALL & ~E_NOTICE & ~E_WARNING );
|
||||
function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\' , $classname );
|
||||
$c = array_pop( $q );
|
||||
$f = 'autoload/' . implode( '/' , $q ) . '/class.' . $c . '.php';
|
||||
|
||||
if ( file_exists( $f ) )
|
||||
require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
require_once __DIR__ . '/autoload/autoloader.php';
|
||||
date_default_timezone_set( 'Europe/Warsaw' );
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
15
index.php
15
index.php
@@ -1,19 +1,6 @@
|
||||
<?php
|
||||
error_reporting( E_ALL ^ E_NOTICE ^ E_STRICT ^ E_WARNING ^ E_DEPRECATED );
|
||||
function __autoload_my_classes( $classname )
|
||||
{
|
||||
$q = explode( '\\', $classname );
|
||||
$c = array_pop( $q );
|
||||
|
||||
// 1. Legacy: class.ClassName.php
|
||||
$f = 'autoload/' . implode( '/', $q ) . '/class.' . $c . '.php';
|
||||
if ( file_exists( $f ) ) { require_once( $f ); return; }
|
||||
|
||||
// 2. PSR-4: ClassName.php
|
||||
$f = 'autoload/' . implode( '/', $q ) . '/' . $c . '.php';
|
||||
if ( file_exists( $f ) ) require_once( $f );
|
||||
}
|
||||
spl_autoload_register( '__autoload_my_classes' );
|
||||
require_once __DIR__ . '/autoload/autoloader.php';
|
||||
date_default_timezone_set( 'Europe/Warsaw' );
|
||||
|
||||
require_once 'config.php';
|
||||
|
||||
BIN
updates/1.60/ver_1.693.zip
Normal file
BIN
updates/1.60/ver_1.693.zip
Normal file
Binary file not shown.
28
updates/1.60/ver_1.693_manifest.json
Normal file
28
updates/1.60/ver_1.693_manifest.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"changelog": "REF - migracja admin Pages/Layouts/Articles do Domain repositories",
|
||||
"version": "1.693",
|
||||
"files": {
|
||||
"added": [
|
||||
"autoload/Domain/Articles/ArticlesRepository.php",
|
||||
"autoload/Domain/Layouts/LayoutsRepository.php",
|
||||
"autoload/Domain/Pages/PagesRepository.php",
|
||||
"composer.phar"
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"autoload/admin/factory/class.Articles.php",
|
||||
"autoload/admin/factory/class.Layouts.php",
|
||||
"autoload/admin/factory/class.Pages.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:3994561d9f3df8ed887f53c903b2a26ae6d17e6b10d98c7cb5cdc59132cef7b5",
|
||||
"sql": [
|
||||
|
||||
],
|
||||
"date": "2026-03-04",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
@@ -11,9 +11,9 @@ $mdb = new medoo( [
|
||||
'charset' => 'utf8'
|
||||
] );
|
||||
|
||||
$current_ver = 1692; // aktualizowane automatycznie przez build-update.ps1
|
||||
$current_ver = 1694; // aktualizowane automatycznie przez build-update.ps1
|
||||
|
||||
// 1. Skan filesystem — lista istniejących ZIPów
|
||||
// 1. Skan filesystem — lista istniejących ZIPów
|
||||
$versions = [];
|
||||
for ( $i = 1; $i <= $current_ver; $i++ )
|
||||
{
|
||||
@@ -34,7 +34,7 @@ $license = $mdb->get( 'pp_update_licenses', '*', [ 'key' => ( $_GET['key'] ?? ''
|
||||
if ( !$license )
|
||||
die();
|
||||
|
||||
// 3. Sprawdź ważność daty
|
||||
// 3. Sprawdź ważność daty
|
||||
if ( $license['valid_to_date'] && $license['valid_to_date'] < date( 'Y-m-d' ) )
|
||||
die();
|
||||
|
||||
@@ -53,11 +53,11 @@ foreach ( $versions as $ver )
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Filtruj wersje wg kanału (beta widzi beta+stable, reszta tylko stable)
|
||||
// 5. Filtruj wersje wg kanału (beta widzi beta+stable, reszta tylko stable)
|
||||
$channels = $license['beta'] ? [ 'beta', 'stable' ] : [ 'stable' ];
|
||||
$allowed = array_flip( $mdb->select( 'pp_update_versions', 'version', [ 'channel' => $channels ] ) ?: [] );
|
||||
|
||||
// 6. Wypisz dostępne wersje
|
||||
// 6. Wypisz dostępne wersje
|
||||
$valid_to_version = $license['valid_to_version'];
|
||||
foreach ( $versions as $ver )
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user