diff --git a/.claude/commands/koniec-pracy.md b/.claude/commands/koniec-pracy.md index ecf8a80..e565007 100644 --- a/.claude/commands/koniec-pracy.md +++ b/.claude/commands/koniec-pracy.md @@ -1,6 +1,6 @@ -# shopPRO — Koniec Pracy (release workflow) +# shopPRO - Koniec Pracy (release workflow) -Execute the full release workflow for shopPRO. This is a sequential pipeline — each step depends on the previous one succeeding. Stop and report if any step fails. +Execute the full release workflow for shopPRO. This is a sequential pipeline - each step depends on the previous one succeeding. Stop and report if any step fails. ## Step 1: Run tests @@ -8,7 +8,7 @@ Run the full PHPUnit test suite: ```bash php phpunit.phar ``` -All tests must pass. If any test fails, stop here — do not proceed to commit. Report the failures and wait for instructions. +All tests must pass. If any test fails, stop here - do not proceed to commit. Report the failures and wait for instructions. ## Step 1b: SonarQube scan @@ -19,20 +19,20 @@ sonar-scanner After the scan completes, query the SonarQube issues via MCP tool `mcp__sonarqube__issues` with `project_key: "shopPRO"` and `resolved: false`. Fetch all open issues (bugs, vulnerabilities, code smells). -Then open `docs/TODO.md` and append the found issues at the bottom under a new section: +Then open `.paul/docs/TODO.md` and append the found issues at the bottom under a new section: ```markdown -## SonarQube — {VERSION} ({DATE}) +## SonarQube - {VERSION} ({DATE}) -- [ ] [SEVERITY] FILENAME:LINE — description (rule) +- [ ] [SEVERITY] FILENAME:LINE - description (rule) - [ ] ... ``` Rules: -- Only add issues that are NOT already present in `docs/TODO.md` +- Only add issues that are NOT already present in `.paul/docs/TODO.md` - Group by type: first Bugs/Vulnerabilities, then Code Smells -- Skip INFO severity Code Smells — only include MINOR and above -- If there are no new issues, write: `## SonarQube — {VERSION} — brak nowych issues` +- Skip INFO severity Code Smells - only include MINOR and above +- If there are no new issues, write: `## SonarQube - {VERSION} - brak nowych issues` ## Step 2: Determine version @@ -40,24 +40,24 @@ Read the latest git tag to determine the current version number: ```bash git tag --sort=-v:refname | head -1 ``` -The new version is the previous version incremented by 1 (e.g., v0.333 → v0.334). Use this version number throughout the remaining steps. +The new version is the previous version incremented by 1 (e.g., v0.333 -> v0.334). Use this version number throughout the remaining steps. ## Step 3: Update documentation -Update these docs files **only if** changes in this session affect them: +Update these docs files **only if** changes in this session affect them. +Do not update files in root `docs/` directory. | File | When to update | |------|---------------| -| `docs/CHANGELOG.md` | Always — add a new version entry at the top describing what changed | -| `docs/TESTING.md` | If tests were added/removed — update test count and structure | -| `CLAUDE.md` | If test count changed — update the "Current suite" line | -| `docs/DATABASE_STRUCTURE.md` | If database schema changed | -| `docs/PROJECT_STRUCTURE.md` | If architecture/files changed significantly | -| `docs/FORM_EDIT_SYSTEM.md` | If form system was modified | +| `.paul/docs/CHANGELOG.md` | Always - add a new version entry at the top describing what changed | +| `.paul/docs/TESTING.md` | If tests were added/removed - update test count and structure | +| `.paul/docs/DB_SCHEMA.md` | If database schema changed | +| `.paul/docs/ARCHITECTURE.md` | If architecture/files changed significantly | +| `.paul/docs/FORMS.md` | If form system was modified | ## Step 4: SQL migrations -If database schema changes were made, create a migration file at `migrations/{version}.sql` (e.g., `migrations/0.334.sql`). Do NOT put SQL files in `updates/` — the build script reads from `migrations/` automatically. +If database schema changes were made, create a migration file at `migrations/{version}.sql` (e.g., `migrations/0.334.sql`). Do NOT put SQL files in `updates/` - the build script reads from `migrations/` automatically. If no DB changes were made, skip this step. diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md index bbb8b9d..ac8bf0a 100644 --- a/.paul/PROJECT.md +++ b/.paul/PROJECT.md @@ -2,11 +2,11 @@ ## What This Is -Autorski silnik sklepu internetowego pisany od podstaw — odpowiednik WooCommerce lub PrestaShop, ale bez zaleĹĽnoĹ›ci od zewnÄ™trznych platform. SkĹ‚ada siÄ™ z panelu administratora (zarzÄ…dzanie zamĂłwieniami, produktami, klientami) oraz części frontowej dla klienta koĹ„cowego. +Autorski silnik sklepu internetowego pisany od podstaw - odpowiednik WooCommerce lub PrestaShop, ale bez zależności od zewnętrznych platform. Składa się z panelu administratora (zarządzanie zamówieniami, produktami, klientami) oraz części frontowej dla klienta końcowego. ## Core Value -WĹ‚aĹ›ciciel sklepu internetowego ma peĹ‚nÄ… kontrolÄ™ nad sprzedaĹĽÄ… online — produktami, zamĂłwieniami i klientami — w jednym spĂłjnym systemie pisanym od podstaw, bez narzutĂłw zewnÄ™trznych platform. +Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online - produktami, zamówieniami i klientami - w jednym spójnym systemie pisanym od podstaw, bez narzutów zewnętrznych platform. ## Current State @@ -20,16 +20,16 @@ WĹ‚aĹ›ciciel sklepu internetowego ma peĹ‚nÄ… kontrolÄ™ nad sprzed ### Validated (Shipped) -- [x] Panel administratora — zarzÄ…dzanie produktami, kategoriami, atrybutami -- [x] Panel administratora — zarzÄ…dzanie zamĂłwieniami -- [x] Panel administratora — zarzÄ…dzanie klientami -- [x] Część frontowa — przeglÄ…danie i kupowanie produktĂłw -- [x] Koszyk i skĹ‚adanie zamĂłwieĹ„ -- [x] Integracje pĹ‚atnoĹ›ci i dostaw +- [x] Panel administratora - zarządzanie produktami, kategoriami, atrybutami +- [x] Panel administratora - zarządzanie zamówieniami +- [x] Panel administratora - zarządzanie klientami +- [x] Część frontowa - przeglądanie i kupowanie produktów +- [x] Koszyk i składanie zamówień +- [x] Integracje płatności i dostaw - [x] REST API (ordersPRO + Ekomi) - [x] Redis caching -- [x] Ochrona przed podwĂłjnym skĹ‚adaniem zamĂłwienia -- [x] Domain-Driven Architecture (migracja z legacy zakoĹ„czona) +- [x] Ochrona przed podwójnym składaniem zamówienia +- [x] Domain-Driven Architecture (migracja z legacy zakończona) ### Active (In Progress) @@ -41,16 +41,16 @@ WĹ‚aĹ›ciciel sklepu internetowego ma peĹ‚nÄ… kontrolÄ™ nad sprzed ### Out of Scope -- Multitenancy (wiele sklepĂłw w jednej instancji) — nie planowane +- Multitenancy (wiele sklepów w jednej instancji) - nie planowane ## Target Users -**Primary:** WĹ‚aĹ›ciciel/administrator sklepu internetowego -- ZarzÄ…dza produktami, zamĂłwieniami, klientami przez panel admina -- Potrzebuje niezawodnego, szybkiego narzÄ™dzia bez zbÄ™dnych zaleĹĽnoĹ›ci +**Primary:** Właściciel/administrator sklepu internetowego +- Zarządza produktami, zamówieniami, klientami przez panel admina +- Potrzebuje niezawodnego, szybkiego narzędzia bez zbędnych zależności -**Secondary:** Klient koĹ„cowy sklepu -- PrzeglÄ…da produkty, dodaje do koszyka, skĹ‚ada zamĂłwienia +**Secondary:** Klient końcowy sklepu +- Przegląda produkty, dodaje do koszyka, składa zamówienia ## Context @@ -58,26 +58,26 @@ WĹ‚aĹ›ciciel sklepu internetowego ma peĹ‚nÄ… kontrolÄ™ nad sprzed - PHP 7.4+ (produkcja: PHP < 8.0) - Medoo ORM (`$mdb`), Redis caching - Domain-Driven Design z Dependency Injection -- PHPUnit 9.6, 810+ testĂłw +- PHPUnit 9.6, 810+ testów - Namespace: `\Domain\`, `\admin\`, `\front\`, `\api\`, `\Shared\` ## Constraints ### Technical Constraints - PHP < 8.0 na produkcji (brak `match`, named arguments, union types) -- Medoo ORM — prepared statements bez wyjÄ…tkĂłw +- Medoo ORM - prepared statements bez wyjątków - Redis wymagany dla cache ### Business Constraints -- System wdraĹĽany u klientĂłw jako update package (ZIP) +- System wdrażany u klientów jako update package (ZIP) ## Key Decisions | Decision | Rationale | Date | Status | |----------|-----------|------|--------| -| DDD + DI zamiast legacy architektury | Testowalność, separacja odpowiedzialnoĹ›ci | 2025 | Active | -| PHP < 8.0 kompatybilność | Klienci na starszych serwerach | 2025 | Active | -| WĹ‚asny silnik zamiast frameworka | PeĹ‚na kontrola, brak narzutĂłw | - | Active | +| DDD + DI zamiast legacy architektury | Testowalność, separacja odpowiedzialności | 2025 | Active | +| PHP < 8.0 kompatybilność | Klienci na starszych serwerach | 2025 | Active | +| Własny silnik zamiast frameworka | Pełna kontrola, brak narzutów | - | Active | | `id` w tabbed FormEdit przez `hiddenFields` | Zapobiega insert zamiast update przy edycji encji | 2026-04-18 | Active | ## Success Metrics @@ -94,7 +94,7 @@ WĹ‚aĹ›ciciel sklepu internetowego ma peĹ‚nÄ… kontrolÄ™ nad sprzed | Backend | PHP 7.4+ | < 8.0 na produkcji | | ORM | Medoo | `$mdb` global | | Cache | Redis | CacheHandler singleton | -| Frontend | HTML/CSS/JS | WĹ‚asny silnik szablonĂłw (Tpl) | +| Frontend | HTML/CSS/JS | Własny silnik szablonów (Tpl) | | Auth | Sesje PHP | CSRF, XSS protection | | Testy | PHPUnit 9.6 | phpunit.phar | @@ -103,15 +103,14 @@ WĹ‚aĹ›ciciel sklepu internetowego ma peĹ‚nÄ… kontrolÄ™ nad sprzed See: .paul/SPECIAL-FLOWS.md Quick Reference: -- /feature-dev → Nowe funkcje, wiÄ™ksze zmiany (required) -- /koniec-pracy → Release, update package (required) -- /frontend-design → Komponenty UI, szablony widokĂłw -- /code-review → PrzeglÄ…d kodu przed release -- /simplify → Upraszczanie po implementacji -- /claude-md-improver → Utrzymanie CLAUDE.md -- /zapisz + /wznow → Zapis i wznowienie sesji +- /feature-dev -> Nowe funkcje, większe zmiany (required) +- /koniec-pracy -> Release, update package (required) +- /frontend-design -> Komponenty UI, szablony widoków +- /code-review -> Przegląd kodu przed release +- /simplify -> Upraszczanie po implementacji +- /claude-md-improver -> Utrzymanie CLAUDE.md +- /zapisz + /wznow -> Zapis i wznowienie sesji --- -*PROJECT.md — Updated when requirements or context change* +*PROJECT.md - Updated when requirements or context change* *Last updated: 2026-04-18 after Phase 15* - diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index b0c12ae..ece845e 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -2,7 +2,7 @@ ## Overview -shopPRO to autorski silnik sklepu internetowego rozwijany iteracyjnie. Projekt jest juĹĽ na produkcji (v0.333) — roadmap obejmuje planowane funkcje i usprawnienia kolejnych wersji. +shopPRO to autorski silnik sklepu internetowego rozwijany iteracyjnie. Projekt jest już na produkcji (v0.333) — roadmap obejmuje planowane funkcje i usprawnienia kolejnych wersji. ## Current Milestone @@ -16,25 +16,25 @@ Phases: 4 of 4 complete |-------|------|-------|--------|-----------| | 1 | Sensitive data logging fix | 1 | Done | 2026-03 | | 2 | Path traversal + XSS escaping | 1 | Done | 2026-03 (v0.335) | -| 3 | Error handling w krytycznych Ĺ›cieĹĽkach | 1 | Done | 2026-03 (v0.336) | -| 4 | CSRF protection — admin panel forms | 1 | Applied | 2026-03 (v0.337) | -| 5 | Order bugs fix — duplicate + COD status | 1 | Applied | 2026-03 (v0.338) | +| 3 | Error handling w krytycznych ścieżkach | 1 | Done | 2026-03 (v0.336) | +| 4 | CSRF protection — admin panel forms | 1 | Applied | 2026-03 (v0.337) | +| 5 | Order bugs fix — duplicate + COD status | 1 | Applied | 2026-03 (v0.338) | ## Next Milestone -**Tech debt — Integrations refactoring** +**Tech debt — Integrations refactoring** Status: Planning | Phase | Name | Plans | Status | Completed | |-------|------|-------|--------|-----------| -| 6 | IntegrationsRepository split → ApiloRepository | 2 | Done | 2026-03 | +| 6 | IntegrationsRepository split → ApiloRepository | 2 | Done | 2026-03 | ## Hotfix | Phase | Name | Plans | Status | Completed | |-------|------|-------|--------|-----------| -| 7 | Coupon Fatal Error — order placement crash | 1 | Done | 2026-03-15 | -| 8 | Apilo orders not sending — diagnoza i naprawa | 1 | Done | 2026-03-16 | +| 7 | Coupon Fatal Error — order placement crash | 1 | Done | 2026-03-15 | +| 8 | Apilo orders not sending — diagnoza i naprawa | 1 | Done | 2026-03-16 | | 9 | Apilo email notification + infinite retry | 1 | Done | 2026-03-19 | | 15 | Scontainers edit saves as new record | 1 | Done | 2026-04-18 | @@ -44,72 +44,72 @@ Status: Planning |-------|------|-------|--------|-----------| | 10 | Edycja personalizacji produktu w koszyku | 1 | Done | 2026-03-19 | | 11 | DataLayer GA4 analytics fix | 1 | Done | 2026-03-25 | -| 12 | summaryView redirect fix — double order block | 1 | Done | 2026-03-25 | +| 12 | summaryView redirect fix — double order block | 1 | Done | 2026-03-25 | | 13 | Basket logging + TTL token fix | 1 | Done | 2026-03-25 | -| 14 | Custom fields delete bug — usuniÄ™cie wszystkich pĂłl | 1 | Done | 2026-04-16 | +| 14 | Custom fields delete bug — usunięcie wszystkich pól | 1 | Done | 2026-04-16 | ## Phase Details -### Phase 4 — CSRF protection +### Phase 4 — CSRF protection -**Problem:** Brak tokenĂłw CSRF na formularzach panelu admina. State-changing POST endpointy (create/update/delete) sÄ… potencjalnie podatne na ataki CSRF. +**Problem:** Brak tokenów CSRF na formularzach panelu admina. State-changing POST endpointy (create/update/delete) są potencjalnie podatne na ataki CSRF. -**Scope:** Dodanie CSRF tokenĂłw do formularzy i walidacji w panelu administracyjnym. +**Scope:** Dodanie CSRF tokenów do formularzy i walidacji w panelu administracyjnym. -**Reference:** `.paul/codebase/concerns.md` — MEDIUM — Missing CSRF tokens +**Reference:** `.paul/codebase/concerns.md` — MEDIUM — Missing CSRF tokens -### Phase 6 — IntegrationsRepository split +### Phase 6 — IntegrationsRepository split -**Problem:** `IntegrationsRepository` ma 875 linii — miesza logikÄ™ generycznÄ… (settings, logi, product linking) z logikÄ… specyficznÄ… dla Apilo (~650 linii). Narusza zasadÄ™ jednej odpowiedzialnoĹ›ci. +**Problem:** `IntegrationsRepository` ma 875 linii — miesza logikę generyczną (settings, logi, product linking) z logiką specyficzną dla Apilo (~650 linii). Narusza zasadę jednej odpowiedzialności. **Scope:** -- Plan 06-01: UtwĂłrz `ApiloRepository` z metodami apilo* (non-breaking) -- Plan 06-02: Zmigruj konsumentĂłw (IntegrationsController, ShopProductController, OrderAdminService, cron.php), usuĹ„ apilo* z IntegrationsRepository +- Plan 06-01: Utwórz `ApiloRepository` z metodami apilo* (non-breaking) +- Plan 06-02: Zmigruj konsumentów (IntegrationsController, ShopProductController, OrderAdminService, cron.php), usuń apilo* z IntegrationsRepository --- -### Phase 5 — Order bugs fix +### Phase 5 — Order bugs fix -**Problem 1:** Zduplikowane zamĂłwienia — klient widzi błąd i klika złóż zamĂłwienie ponownie. Pierwsze zamĂłwienie trafiĹ‚o do bazy mimo błędu. PowrĂłt do `/podsumowanie` regeneruje token i pozwala zĹ‚oĹĽyć drugie zamĂłwienie. +**Problem 1:** Zduplikowane zamówienia — klient widzi błąd i klika złóż zamówienie ponownie. Pierwsze zamówienie trafiło do bazy mimo błędu. Powrót do `/podsumowanie` regeneruje token i pozwala złożyć drugie zamówienie. -**Problem 2:** ZamĂłwienia COD (pĹ‚atność przy odbiorze) dostajÄ… status "ZamĂłwienie zĹ‚oĹĽone" zamiast "PrzyjÄ™te do realizacji". Kod sprawdza hardkodowane `payment_id == 3`, ktĂłre jest inne w tej instancji sklepu. +**Problem 2:** Zamówienia COD (płatność przy odbiorze) dostają status "Zamówienie złożone" zamiast "Przyjęte do realizacji". Kod sprawdza hardkodowane `payment_id == 3`, które jest inne w tej instancji sklepu. -**Scope:** Guard w `summaryView()`, try-catch w `basketSave()`, kolumna `is_cod` w `pp_shop_payment_methods`, uĹĽycie flagi zamiast hardkodowanego ID. +**Scope:** Guard w `summaryView()`, try-catch w `basketSave()`, kolumna `is_cod` w `pp_shop_payment_methods`, użycie flagi zamiast hardkodowanego ID. --- *Roadmap created: 2026-03-12* -### Phase 11 — DataLayer GA4 analytics fix -**Problem:** Eventy dataLayer ecommerce (purchase, begin_checkout, view_item, add_to_cart) uĹĽywajÄ… starego formatu UA (id/name zamiast item_id/item_name), brak currency w view_item, price:0 w purchase, brak eventu view_cart. Remarketing dynamiczny i konwersje GA4 nie dziaĹ‚ajÄ… poprawnie. +### Phase 11 — DataLayer GA4 analytics fix -**Scope:** Poprawka 4 istniejÄ…cych eventĂłw do formatu GA4 + dodanie nowego eventu view_cart na stronie koszyka. +**Problem:** Eventy dataLayer ecommerce (purchase, begin_checkout, view_item, add_to_cart) używają starego formatu UA (id/name zamiast item_id/item_name), brak currency w view_item, price:0 w purchase, brak eventu view_cart. Remarketing dynamiczny i konwersje GA4 nie działają poprawnie. -**Reference:** `poprawki_datalayer_projectpro.md` — audyt analityki z pomysloweprezenty.pl +**Scope:** Poprawka 4 istniejących eventów do formatu GA4 + dodanie nowego eventu view_cart na stronie koszyka. -### Phase 12 — summaryView redirect fix +**Reference:** `poprawki_datalayer_projectpro.md` — audyt analityki z pomysloweprezenty.pl -**Problem:** Po zĹ‚oĹĽeniu pierwszego zamĂłwienia, guard w `summaryView()` sprawdzaĹ‚ sesyjny `order-submit-last-order-id` i redirectowaĹ‚ na stronÄ™ starego zamĂłwienia. BlokowaĹ‚ dostÄ™p do `/koszyk-podsumowanie` dla kolejnych zamĂłwieĹ„. Poprawka z instancji klienta (change.md) do wdroĹĽenia globalnie. +### Phase 12 — summaryView redirect fix -**Scope:** UsuniÄ™cie bloku redirect z `summaryView()` w `ShopBasketController.php`. Double-submit protection w `basketSave()` pozostaje bez zmian. +**Problem:** Po złożeniu pierwszego zamówienia, guard w `summaryView()` sprawdzał sesyjny `order-submit-last-order-id` i redirectował na stronę starego zamówienia. Blokował dostęp do `/koszyk-podsumowanie` dla kolejnych zamówień. Poprawka z instancji klienta (change.md) do wdrożenia globalnie. -### Phase 13 — Basket logging + TTL token fix +**Scope:** Usunięcie bloku redirect z `summaryView()` w `ShopBasketController.php`. Double-submit protection w `basketSave()` pozostaje bez zmian. -**Problem:** Brak logowania w basketSave() uniemoĹĽliwia diagnozÄ™ błędĂłw zamĂłwieĹ„. Token zamĂłwienia jednorazowy — nadpisywany przy kaĹĽdym wejĹ›ciu na podsumowanie, co powoduje ĹĽe druga karta, "wstecz" lub odĹ›wieĹĽenie uniewaĹĽnia formularz. +### Phase 13 — Basket logging + TTL token fix -**Scope:** Dodanie metody logOrder() z 4 punktami logowania, zmiana tokena z jednorazowego na TTL 30 min, redirect przy błędzie tokena na /koszyk-podsumowanie zamiast /koszyk, nowy double-submit guard. +**Problem:** Brak logowania w basketSave() uniemożliwia diagnozę błędów zamówień. Token zamówienia jednorazowy — nadpisywany przy każdym wejściu na podsumowanie, co powoduje że druga karta, "wstecz" lub odświeżenie unieważnia formularz. -### Phase 14 — Custom fields delete bug +**Scope:** Dodanie metody logOrder() z 4 punktami logowania, zmiana tokena z jednorazowego na TTL 30 min, redirect przy błędzie tokena na /koszyk-podsumowanie zamiast /koszyk, nowy double-submit guard. -**Problem:** UsuniÄ™cie WSZYSTKICH dodatkowych pĂłl z produktu nie dziaĹ‚a. jQuery `.serialize()` nie wysyĹ‚a klucza `custom_field_name[]` gdy nie ma ĹĽadnych pĂłl → `array_key_exists('custom_field_name', $d)` w ProductRepository zwraca false → `saveCustomFields()` nigdy nie jest wywoĹ‚ywany → pola pozostajÄ… w bazie. +### Phase 14 — Custom fields delete bug + +**Problem:** Usunięcie WSZYSTKICH dodatkowych pól z produktu nie działa. jQuery `.serialize()` nie wysyła klucza `custom_field_name[]` gdy nie ma żadnych pól → `array_key_exists('custom_field_name', $d)` w ProductRepository zwraca false → `saveCustomFields()` nigdy nie jest wywoływany → pola pozostają w bazie. **Scope:** Dodanie hidden markera `custom_field_name_present` w szablonie JS + zmiana warunku w ProductRepository na sprawdzanie tego markera. Test jednostkowy. ### Phase 15 - Scontainers edit saves as new record -**Problem:** Edycja kontenera statycznego (`/admin/scontainers/edit/id={id}`) zapisuje rekord jako nowy wpis zamiast aktualizacji. W praktyce podczas zapisu gubi sie `id` i repository wykonuje insert. +**Problem:** Edycja kontenera statycznego (`/admin/scontainers/edit/id={id}`) zapisuje rekord jako nowy wpis zamiast aktualizacji. W praktyce podczas zapisu gubi się `id` i repository wykonuje insert. -**Scope:** Poprawic przekazywanie `id` w nowym flow formularza ScontainersController + dodac test regresyjny dla edycji, bez zmian globalnych w innych kontrolerach. +**Scope:** Poprawić przekazywanie `id` w nowym flow formularza ScontainersController + dodać test regresyjny dla edycji, bez zmian globalnych w innych kontrolerach. --- *Last updated: 2026-04-18* - diff --git a/.paul/SPECIAL-FLOWS.md b/.paul/SPECIAL-FLOWS.md index bab7aa6..bd34c82 100644 --- a/.paul/SPECIAL-FLOWS.md +++ b/.paul/SPECIAL-FLOWS.md @@ -1,37 +1,37 @@ -# Specialized Flows: shopPRO +# Specialized Flows: shopPRO ## Project-Level Dependencies -| Work Type | Skill/Command | Priority | Kiedy używać | +| Work Type | Skill/Command | Priority | Kiedy uzywac | |-----------|---------------|----------|--------------| -| Komponenty UI, szablony widoków | /frontend-design | optional | Przy tworzeniu HTML/CSS | -| Nowe funkcje, większe zmiany | /feature-dev | required | Przed implementacją fazy | -| Przegląd kodu | /code-review | optional | Przed release / KONIEC PRACY | -| Upraszczanie po zmianach | /simplify | optional | Po zakończeniu implementacji | -| Utrzymanie CLAUDE.md | /claude-md-improver | optional | Co kilka faz / po dużych zmianach | -| Release, budowanie update package | /koniec-pracy | required | Na koniec każdej sesji roboczej | -| Zapis i wznowienie sesji | /zapisz + /wznow | optional | Na przerwę / powrót do pracy | +| Komponenty UI, szablony widokow | /frontend-design | optional | Przy tworzeniu HTML/CSS | +| Nowe funkcje, wieksze zmiany | /feature-dev | required | Przed implementacja fazy | +| Przeglad kodu | /code-review | optional | Przed release / KONIEC PRACY | +| Upraszczanie po zmianach | /simplify | optional | Po zakonczeniu implementacji | +| Utrzymanie CLAUDE.md | /claude-md-improver | optional | Co kilka faz / po duzych zmianach | +| Release, budowanie update package | /koniec-pracy | required | Na koniec kazdej sesji roboczej | +| Zapis i wznowienie sesji | /zapisz + /wznow | optional | Na przerwe / powrot do pracy | ## Phase Overrides -Brak — domyślna konfiguracja obowiązuje dla wszystkich faz. +Brak - domyslna konfiguracja obowiazuje dla wszystkich faz. ## Templates & Assets | Asset Type | Location | When Used | |------------|----------|-----------| | CLAUDE.md | CLAUDE.md | Konwencje kodu, architektura, stack techniczny | -| Struktura bazy | docs/DATABASE_STRUCTURE.md | Przy zmianach schematu DB | -| Dokumentacja API | api-docs/api-reference.json | Przy zmianach API | -| TODO | docs/TODO.md | Planowanie nowych funkcji | +| Struktura bazy | .paul/docs/DB_SCHEMA.md | Przy zmianach schematu DB | +| Dokumentacja API | .paul/docs/API.md | Przy zmianach API | +| TODO | .paul/docs/TODO.md | Planowanie nowych funkcji | ## Verification (UNIFY) -Podczas UNIFY sprawdź: -- `/feature-dev` — czy był użyty przed implementacją fazy? -- `/koniec-pracy` — czy release został wykonany? +Podczas UNIFY sprawdz: +- `/feature-dev` - czy byl uzyty przed implementacja fazy? +- `/koniec-pracy` - czy release zostal wykonany? -Braki dokumentuj w STATE.md (Deferred Issues), nie blokują UNIFY. +Braki dokumentuj w STATE.md (Deferred Issues), nie blokuja UNIFY. --- -*SPECIAL-FLOWS.md — Created: 2026-03-12* +*SPECIAL-FLOWS.md - Created: 2026-03-12* diff --git a/.paul/STATE.md b/.paul/STATE.md index 8b85d8e..9ab4c52 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -4,7 +4,7 @@ See: .paul/PROJECT.md (updated 2026-04-18) -**Core value:** WĹ‚aĹ›ciciel sklepu ma peĹ‚nÄ… kontrolÄ™ nad sprzedaĹĽÄ… online w jednym systemie pisanym od podstaw, bez narzutĂłw zewnÄ™trznych platform. +**Core value:** Właściciel sklepu ma pełną kontrolę nad sprzedażą online w jednym systemie pisanym od podstaw, bez narzutów zewnętrznych platform. **Current focus:** Phase 15 complete - loop closed (scontainers edit save fix) ## Current Position @@ -51,22 +51,22 @@ Phase 15: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-18] - 2026-04-18: /koniec-pracy requirement mapped to .claude/commands/koniec-pracy.md guidance for end-of-session release flow - 2026-04-18: Scontainers edit fix - ID from tabbed form can be omitted when hidden field is defined as FormField and not as hiddenFields - Use existing `CouponRepository::markAsUsed()` instead of adding methods to stdClass -- 2026-03-16: Przyczyna braku wysyĹ‚ki = brakujÄ…ce $apiloRepository w use() closures cron.php (regresja z fazy 6) +- 2026-03-16: Przyczyna braku wysyłki = brakujące $apiloRepository w use() closures cron.php (regresja z fazy 6) - 2026-03-16: Retry -1 orders co 1h zamiast permanent failure - 2026-03-16: Email notification o trwale failed Apilo jobach -- 2026-03-19: Order-related Apilo joby — infinite retry co 30 min (nigdy permanent failure) -- 2026-03-19: Email z danymi zamĂłwienia + rozróżnienie PONAWIANY vs TRWAŁY BŁĄD -- 2026-03-19: Cleanup stuck sync_payment/sync_status jobĂłw po udanym wysĹ‚aniu -- 2026-03-19: Edycja custom fields w koszyku — product_code przeliczany po zmianie, merge duplikatĂłw przy identycznym hashu +- 2026-03-19: Order-related Apilo joby — infinite retry co 30 min (nigdy permanent failure) +- 2026-03-19: Email z danymi zamówienia + rozróżnienie PONAWIANY vs TRWAŁY BŁĄD +- 2026-03-19: Cleanup stuck sync_payment/sync_status jobów po udanym wysłaniu +- 2026-03-19: Edycja custom fields w koszyku — product_code przeliczany po zmianie, merge duplikatów przy identycznym hashu - 2026-03-19: JS handlery koszyka w basket.php (nie basket-details.php) bo basket-details jest AJAX-replaceable -- 2026-03-25: view_cart event w basket.php (nie basket-details.php) — ten sam powĂłd +- 2026-03-25: view_cart event w basket.php (nie basket-details.php) — ten sam powód - 2026-03-25: GA4 item format standard: item_id (string), item_name, price (number), quantity (int), google_business_vertical: "retail" -- 2026-03-25: Brak user_data w purchase — wymaga analizy RODO -- 2026-03-25: summaryView() redirect guard usuniÄ™ty — blokowaĹ‚ kolejne zamĂłwienia po pierwszym (z change.md instancji klienta) -- 2026-03-25: Token zamĂłwienia z jednorazowego na TTL 30 min — backward compat z plain string -- 2026-03-25: logOrder() — logowanie błędĂłw zamĂłwieĹ„ do logs/logs-order-YYYY-MM-DD.log -- 2026-03-25: Redirect przy zĹ‚ym tokenie: /koszyk-podsumowanie zamiast /koszyk -- 2026-04-16: Custom fields delete fix — hidden marker `custom_field_name_present` zamiast `array_key_exists('custom_field_name')` +- 2026-03-25: Brak user_data w purchase — wymaga analizy RODO +- 2026-03-25: summaryView() redirect guard usunięty — blokował kolejne zamówienia po pierwszym (z change.md instancji klienta) +- 2026-03-25: Token zamówienia z jednorazowego na TTL 30 min — backward compat z plain string +- 2026-03-25: logOrder() — logowanie błędów zamówień do logs/logs-order-YYYY-MM-DD.log +- 2026-03-25: Redirect przy złym tokenie: /koszyk-podsumowanie zamiast /koszyk +- 2026-04-16: Custom fields delete fix — hidden marker `custom_field_name_present` zamiast `array_key_exists('custom_field_name')` ### Deferred Issues None. @@ -87,5 +87,4 @@ Stopped at: Phase 15 complete, loop closed Next action: Start next work with $paul-plan (or run /koniec-pracy for release flow) Resume file: .paul/phases/15-scontainers-edit-save-fix/15-01-SUMMARY.md --- -*STATE.md — Updated after every significant action* - +*STATE.md — Updated after every significant action* diff --git a/.paul/docs/API.md b/.paul/docs/API.md index cce27e0..c69b55b 100644 --- a/.paul/docs/API.md +++ b/.paul/docs/API.md @@ -1,3 +1,255 @@ # API -> Endpointy, kontrakty request/response, autentykacja. +## Scope + +Dokument opisuje aktualne REST API dostepne przez `api.php` (ordersPRO + slowniki + produkty + kategorie). + +## Base URL + +- Endpoint techniczny: `/api.php` +- Routing odbywa sie przez query params: + - `endpoint` (np. `orders`, `products`) + - `action` (np. `list`, `get`) + +Przyklad: + +```text +GET /api.php?endpoint=orders&action=list +``` + +## Authentication + +- Wymagany naglowek: `X-Api-Key: ` +- Klucz jest porownywany z wartoscia `pp_settings.api_key` +- Brak lub zly klucz: + - HTTP `401` + - payload: + ```json + { + "status": "error", + "code": "UNAUTHORIZED", + "message": "Invalid or missing API key" + } + ``` + +## Response format + +Sukces: + +```json +{ + "status": "ok", + "data": {} +} +``` + +Blad: + +```json +{ + "status": "error", + "code": "BAD_REQUEST", + "message": "..." +} +``` + +## Common HTTP/logic errors + +- `400 BAD_REQUEST` - brak wymaganych parametrow/body +- `401 UNAUTHORIZED` - brak/zly API key +- `404 NOT_FOUND` - nieznany endpoint/action lub brak rekordu +- `405 METHOD_NOT_ALLOWED` - zla metoda HTTP +- `500 INTERNAL_ERROR` - blad serwera + +## Endpoints + +### Orders (`endpoint=orders`) + +- `GET action=list` + - filtry (opcjonalne): `status`, `paid`, `date_from`, `date_to`, `updated_since`, `number`, `client` + - paginacja: `page` (default 1), `per_page` (default 50, max 100) +- `GET action=get&id={id}` +- `PUT action=change_status&id={id}` + - body JSON: + - `status_id` (required, int) + - `send_email` (optional, bool) +- `PUT action=set_paid&id={id}` + - body JSON optional: `send_email` (bool) +- `PUT action=set_unpaid&id={id}` + +### Products (`endpoint=products`) + +- `GET action=list` + - filtry: + - `search`, `status`, `promoted` + - `attribute_{attributeId}={valueId}` (np. `attribute_12=37`) + - sortowanie: `sort` (default `id`), `sort_dir` (default `DESC`) + - paginacja: `page`, `per_page` (max 100) +- `GET action=get&id={id}` +- `POST action=create` + - wymagane minimum: + - `languages` (array, co najmniej jeden jezyk z `name`) + - `price_brutto` (number >= 0) +- `PUT action=update&id={id}` + - partial update przez JSON body +- `GET action=variants&id={parentProductId}` + - dla produktu glownego (nie wariantu) +- `POST action=create_variant&id={parentProductId}` + - body: + - `attributes` (required array) + - opcjonalnie pola wariantu (np. `price_brutto`, `quantity`, `sku`, ...) +- `PUT action=update_variant&id={variantId}` + - partial update wariantu +- `DELETE action=delete_variant&id={variantId}` +- `POST action=upload_image` + - body: + - `id` (product id, required) + - `file_name` (required) + - `content_base64` (required) + - `alt` (optional) + - `o` (optional position) + +### Dictionaries (`endpoint=dictionaries`) + +- `GET action=statuses` +- `GET action=transports` +- `GET action=payment_methods` +- `GET action=attributes` +- `POST action=ensure_attribute` + - body: `name` (required), `type` (optional int), `lang` (optional, default `pl`) +- `POST action=ensure_attribute_value` + - body: `attribute_id` (required), `name` (required), `lang` (optional, default `pl`) +- `POST action=ensure_producer` + - body: `name` (required) + +### Categories (`endpoint=categories`) + +- `GET action=list` + - zwraca aktywne kategorie w formie flat list: + - `id` + - `parent_id` + - `title` + - tytuly pobierane najpierw w jezyku domyslnym (`pp_langs.start=1`), potem fallback. + +## Source of truth (mapa API w kodzie) + +### 1) Wejscie i dispatch + +- `api.php` + - wykrywa request API przez `$_GET['endpoint']` + - ustawia JSON content-type + - tworzy `medoo` + `SettingsRepository` + - przekazuje sterowanie do `\api\ApiRouter::handle()` +- `autoload/api/ApiRouter.php` + - autentykacja (`X-Api-Key` vs `pp_settings.api_key`) + - walidacja `endpoint` i `action` + - mapowanie endpoint -> kontroler (`getControllerFactories()`) + - helpery odpowiedzi: `sendSuccess()`, `sendError()` + - helpery requestu: `getJsonBody()`, `requireMethod()` + +### 2) Endpointy i kontrolery (runtime source) + +#### `endpoint=orders` + +- plik: `autoload/api/Controllers/OrdersApiController.php` +- akcje: + - `list` (GET) + - `get` (GET) + - `change_status` (PUT) + - `set_paid` (PUT) + - `set_unpaid` (PUT) + +#### `endpoint=products` + +- plik: `autoload/api/Controllers/ProductsApiController.php` +- akcje: + - `list` (GET) + - `get` (GET) + - `create` (POST) + - `update` (PUT) + - `variants` (GET) + - `create_variant` (POST) + - `update_variant` (PUT) + - `delete_variant` (DELETE) + - `upload_image` (POST) + +#### `endpoint=dictionaries` + +- plik: `autoload/api/Controllers/DictionariesApiController.php` +- akcje: + - `statuses` (GET) + - `transports` (GET) + - `payment_methods` (GET) + - `attributes` (GET) + - `ensure_attribute` (POST) + - `ensure_attribute_value` (POST) + - `ensure_producer` (POST) + +#### `endpoint=categories` + +- plik: `autoload/api/Controllers/CategoriesApiController.php` +- akcje: + - `list` (GET) + +### 3) Warstwa domenowa pod API (shape danych) + +- Orders: + - `Domain\Order\OrderRepository::listForApi()` + - `Domain\Order\OrderRepository::findForApi()` + - `Domain\Order\OrderAdminService` (zmiana statusu/platnosci) +- Products: + - `Domain\Product\ProductRepository::listForApi()` + - `Domain\Product\ProductRepository::findForApi()` + - `Domain\Product\ProductRepository::saveProduct()` + - metody wariantow `*VariantForApi()` +- Dictionaries: + - `Domain\ShopStatus\ShopStatusRepository` + - `Domain\Transport\TransportRepository` + - `Domain\PaymentMethod\PaymentMethodRepository` + - `Domain\Attribute\AttributeRepository` + - `Domain\Producer\ProducerRepository` +- Categories: + - bezposrednio query przez `$GLOBALS['mdb']` w `CategoriesApiController` + +### 4) Testy API (behavior source) + +- `tests/Unit/api/ApiRouterTest.php` +- `tests/Unit/api/Controllers/OrdersApiControllerTest.php` +- `tests/Unit/api/Controllers/ProductsApiControllerTest.php` +- `tests/Unit/api/Controllers/DictionariesApiControllerTest.php` + +### 5) Dokumentacja kontraktu (human source) + +- `api-docs/api-reference.json` +- `api-docs/index.html` + +Uwaga: dokumentacja z `api-docs/*` moze byc starsza od runtime. +Zrodlem prawdy dla dzialania endpointow jest zawsze: +`api.php` + `autoload/api/ApiRouter.php` + aktualne kontrolery `autoload/api/Controllers/*`. + +## Curl examples + +Pobranie listy zamowien: + +```bash +curl -X GET "https://example.com/api.php?endpoint=orders&action=list&page=1&per_page=20" \ + -H "X-Api-Key: YOUR_API_KEY" +``` + +Zmiana statusu zamowienia: + +```bash +curl -X PUT "https://example.com/api.php?endpoint=orders&action=change_status&id=123" \ + -H "X-Api-Key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d "{\"status_id\": 6, \"send_email\": true}" +``` + +Dodanie wariantu produktu: + +```bash +curl -X POST "https://example.com/api.php?endpoint=products&action=create_variant&id=50" \ + -H "X-Api-Key: YOUR_API_KEY" \ + -H "Content-Type: application/json" \ + -d "{\"attributes\":[{\"attribute_id\":12,\"attribute_value_id\":37}],\"price_brutto\":99.99,\"quantity\":10}" +``` diff --git a/.paul/docs/ARCHITECTURE.md b/.paul/docs/ARCHITECTURE.md index e76060e..b03b519 100644 --- a/.paul/docs/ARCHITECTURE.md +++ b/.paul/docs/ARCHITECTURE.md @@ -1,3 +1,192 @@ # ARCHITECTURE -> Struktura klas, modulow, przeplywow i zaleznosci w projekcie. +## Scope + +Dokument opisuje aktualna architekture runtime projektu `shopPRO`: +- warstwy aplikacji i ich odpowiedzialnosci, +- przeplywy requestow (admin/front/api), +- Dependency Injection i miejsca wiringu, +- konwencje autoloadera i namespace, +- granice miedzy nowa architektura a pozostalosciami legacy. + +## High-level layout + +Kod aplikacji jest podzielony na 4 glowne warstwy: + +1. `autoload/Domain/` - logika biznesowa i dostep do danych (28 modulow) +2. `autoload/admin/` - panel administracyjny (router + kontrolery + form system) +3. `autoload/front/` - frontend sklepu (router + layout engine + kontrolery/widoki) +4. `autoload/api/` - REST API (`api.php`, `ApiRouter`, kontrolery endpointow) + +Komponenty wspoldzielone sa trzymane w `autoload/Shared/`. + +## Directory map (runtime) + +```text +autoload/ + Domain/ # 28 modulow domenowych + Shared/ # Cache, Helpers, Tpl, Html, Email, Image + admin/ + App.php # routing + DI factories + Controllers/ # 28 kontrolerow admin + Support/ # formularze/tabele + Validation/ # walidacja formularzy + ViewModels/ # modele widokow + front/ + App.php # routing + DI factories + fallback legacy + LayoutEngine.php # silnik layoutu frontend + Controllers/ # 8 kontrolerow frontend + Views/ # 11 statycznych klas widokow + api/ + ApiRouter.php # auth + endpoint dispatch + Controllers/ # 4 kontrolery API +``` + +## Entry points i przeplyw + +### Admin (`admin/index.php`) + +1. Bootstrap (sesja, DB, autoload) +2. `admin\App::update()` - uruchamia pending migracje +3. `admin\App::special_actions()` - logowanie/wylogowanie/2FA +4. `admin\App::render()`: + - auth gate (lub formularz logowania), + - `route()` -> kontroler + akcja, + - render przez `Shared\Tpl\Tpl`. + +### Front (`index.php`) + +1. Bootstrap (sesja, DB, autoload, jezyk) +2. Mapowanie URL (redirecty + routes) +3. `front\App::checkUrlParams()` +4. `front\App::route()`: + - artykul/produkt/kategoria, + - nowe kontrolery DI, + - fallback do `front\controls\*` (legacy, jesli istnieje) +5. `front\LayoutEngine::show()` sklada finalny HTML. + +### API (`api.php`) + +1. Bootstrap (bez sesji biznesowej) +2. Tworzenie `\api\ApiRouter` +3. `ApiRouter::handle()`: + - auth przez `X-Api-Key`, + - walidacja `endpoint` i `action`, + - dispatch do kontrolera API, + - JSON response przez `sendSuccess()` / `sendError()`. + +Szczegolowa specyfikacja endpointow: `.paul/docs/API.md`. + +## Dependency Injection (manual factories) + +DI jest realizowane recznie w mapach factory: + +- admin: `autoload/admin/App.php` -> `getControllerFactories()` +- front: `autoload/front/App.php` -> `getControllerFactories()` +- api: `autoload/api/ApiRouter.php` -> `getControllerFactories()` + +Wzorzec: +- router tworzy repozytoria domenowe, +- repozytoria sa wstrzykiwane do kontrolerow przez konstruktor, +- kontroler wywoluje metody domenowe i zwraca HTML/JSON. + +## Autoloader i namespace rules + +Kazdy entry point korzysta z custom autoloadera: + +1. `autoload/{namespace}/class.{ClassName}.php` (legacy) +2. `autoload/{namespace}/{ClassName}.php` (nowy format) + +Mapowanie namespace -> katalog (case-sensitive na Linux): + +- `\Domain\` -> `autoload/Domain/` +- `\Shared\` -> `autoload/Shared/` +- `\admin\` -> `autoload/admin/` (male `a`) +- `\front\` -> `autoload/front/` +- `\api\` -> `autoload/api/` + +Nie uzywac `\Admin\` (duze `A`), bo katalog runtime to `admin/`. + +## Domain layer (28 modulow) + +Aktualne moduly: + +`Article`, `Attribute`, `Banner`, `Basket`, `Cache`, `Category`, `Client`, `Coupon`, `CronJob`, `Dashboard`, `Dictionaries`, `Integrations`, `Languages`, `Layouts`, `Newsletter`, `Order`, `Pages`, `PaymentMethod`, `Producer`, `Product`, `ProductSet`, `Promotion`, `Scontainers`, `Settings`, `ShopStatus`, `Transport`, `Update`, `User`. + +Zasada: logika biznesowa i dostep do danych sa w Domain, bez duplikowania osobnych warstw "admin service" i "front service" dla tych samych przypadkow (wyjatki tylko tam, gdzie historycznie juz istnieja, np. `OrderAdminService`). + +## Admin architecture + +- Router: `admin\App` +- Kontrolery: `autoload/admin/Controllers/*.php` (28 klas) +- Form system: + - `admin\ViewModels\Forms\*` + - `admin\Support\Forms\FormRequestHandler` + - `admin\Support\Forms\FormFieldRenderer` + - `admin\Validation\FormValidator` + - template: `admin/templates/components/form-edit.php` + +Admin ma pelne DI i nie korzysta z fallbacku na legacy kontrolery. + +## Front architecture + +- Router: `front\App` +- Layout engine: `front\LayoutEngine` +- Kontrolery DI: `autoload/front/Controllers/*.php` (8 klas) +- Widoki statyczne: `autoload/front/Views/*.php` (11 klas) + +Wazne: frontend nadal ma fallback do `\front\controls\*`, wiec architektura jest hybrydowa (new DI + remaining legacy paths). + +## API architecture + +- Router: `api\ApiRouter` +- Endpointy: `orders`, `products`, `dictionaries`, `categories` +- Kontrolery: `autoload/api/Controllers/*ApiController.php` (4 klasy) +- API jest stateless, autoryzowane naglowkiem `X-Api-Key` + +Source-of-truth API to runtime: +- `api.php` +- `autoload/api/ApiRouter.php` +- `autoload/api/Controllers/*` + +## Shared components + +Najwazniejsze klasy wspoldzielone: + +- `Shared\Cache\CacheHandler`, `Shared\Cache\RedisConnection` +- `Shared\Helpers\Helpers` +- `Shared\Tpl\Tpl` +- `Shared\Html\Html` +- `Shared\Email\Email` +- `Shared\Image\ImageManipulator` + +## Data and cache conventions + +- ORM: Medoo (`$mdb`) +- Prefix tabel: `pp_` +- Cache: Redis, domyslnie TTL `86400` +- Dane cache czesto serializowane (`serialize`/`unserialize`) +- Czyszczenie cache produktu: pattern `shop\\product:{id}:*` + +## Security boundaries + +- Admin: + - sesja uzytkownika admina, + - CSRF token w akcjach POST, + - 2FA email flow (pending session + verify). +- API: + - `X-Api-Key` porownywany przez `hash_equals()`, + - brak logiki sesyjnej. +- Front: + - sesja klienta + walidacja przeplywow frontendowych. + +## Source files + +Najwazniejsze pliki do szybkiej orientacji: + +- `autoload/admin/App.php` +- `autoload/front/App.php` +- `autoload/front/LayoutEngine.php` +- `autoload/api/ApiRouter.php` +- `.paul/docs/API.md` +- `docs/PROJECT_STRUCTURE.md` diff --git a/.paul/docs/DB_SCHEMA.md b/.paul/docs/DB_SCHEMA.md index 737ad36..4364a45 100644 --- a/.paul/docs/DB_SCHEMA.md +++ b/.paul/docs/DB_SCHEMA.md @@ -1,3 +1,228 @@ -# DB_SCHEMA +# DB_SCHEMA -> Schemat bazy danych — tabele, kolumny, FK, indeksy. +## Scope + +Dokument opisuje praktyczny schema map dla `shopPRO`: +- najwazniejsze tabele i relacje, +- grupowanie po domenach biznesowych, +- kluczowe kolumny i indeksy, ktore maja znaczenie runtime, +- mapowanie tabela -> warstwa Domain. + +Pelna lista tabel i historyczne notki migracyjne: +`docs/DATABASE_STRUCTURE.md` (source of truth dla detali kolumnowych). + +## Konwencje globalne + +- ORM: Medoo (`$mdb`) +- Prefix tabel: `pp_` +- Primary key: najczesciej `id` (INT AUTO_INCREMENT) +- Jezyki/translations: zwykle tabele `*_langs` z kluczem `lang_id` +- Wiele-do-wielu: tabele lacznikowe `*_products`, `*_payment_methods`, itp. + +## Core commerce + +### Produkty + +- `pp_shop_products` + - core produktu i wariantu (`parent_id` dla kombinacji) + - ceny (`price_brutto`, `price_brutto_promo`), stany (`quantity`) + - flagi (`status`, `archive`, `promoted`) +- `pp_shop_products_langs` + - nazwy/opisy per jezyk +- `pp_shop_products_images` + - obrazy produktu +- `pp_shop_products_categories` + - przypisania produkt-kategoria +- `pp_shop_products_attributes` + - przypisania wariantu do wartosci cech +- `pp_shop_products_custom_fields` + - dodatkowe pola produktu + +Warstwa: `Domain\Product\ProductRepository`, `Domain\Attribute\AttributeRepository`. + +### Kategorie + +- `pp_shop_categories` + - drzewo kategorii (`parent_id`), status, kolejnosc +- `pp_shop_categories_langs` + - tresci SEO i opisy kategorii + +Warstwa: `Domain\Category\CategoryRepository`. + +### Zamowienia + +- `pp_shop_orders` + - dane klienta "w momencie zakupu", summary, status/platnosc, daty + - kluczowe pole integracyjne: `updated_at` (polling API) + +Warstwa: `Domain\Order\OrderRepository`, `Domain\Order\OrderAdminService`. + +### Klienci + +- `pp_shop_clients` + - konto klienta i dane adresowe/logowania (uzywane przez ClientRepository) + +Warstwa: `Domain\Client\ClientRepository`. + +## Slowniki i checkout + +### Platnosci + +- `pp_shop_payment_methods` + - status, opis, mapowanie Apilo + - limity kwotowe: `min_order_amount`, `max_order_amount` + - COD flag: `is_cod` + +Warstwa: `Domain\PaymentMethod\PaymentMethodRepository`. + +### Transport + +- `pp_shop_transports` + - koszt, status, limity, mapowanie Apilo +- `pp_shop_transport_payment_methods` + - relacja transport <-> platnosc (N:M) + +Warstwa: `Domain\Transport\TransportRepository`. + +### Statusy zamowien + +- `pp_shop_statuses` + - statusy predefiniowane, kolor, mapowanie Apilo + +Warstwa: `Domain\ShopStatus\ShopStatusRepository`. + +## Marketing i merch + +### Promocje i kupony + +- `pp_shop_promotion` + - reguly promocji, daty aktywnosci, warunki i zakresy (JSON categories) +- `pp_shop_coupon` + - kupony, licznik uzyc, ograniczenia + +Warstwa: `Domain\Promotion\PromotionRepository`, `Domain\Coupon\CouponRepository`. + +### Producenci + +- `pp_shop_producer` +- `pp_shop_producer_lang` + +Warstwa: `Domain\Producer\ProducerRepository`. + +### Zestawy produktow + +- `pp_shop_product_sets` +- `pp_shop_product_sets_products` + +Warstwa: `Domain\ProductSet\ProductSetRepository`. + +### Cechy i wartosci + +- `pp_shop_attributes` +- `pp_shop_attributes_langs` +- `pp_shop_attributes_values` +- `pp_shop_attributes_values_langs` + +Warstwa: `Domain\Attribute\AttributeRepository`. + +## CMS i frontend content + +### Artykuly + +- `pp_articles` +- `pp_articles_langs` +- `pp_articles_pages` +- `pp_articles_images` +- `pp_articles_files` + +Warstwa: `Domain\Article\ArticleRepository`. + +### Strony i layouty + +- `pp_pages` +- `pp_layouts` +- `pp_layouts_pages` +- `pp_layouts_categories` + +Warstwa: `Domain\Pages\PagesRepository`, `Domain\Layouts\LayoutsRepository`. + +### Banery i kontenery statyczne + +- `pp_banners` +- `pp_banners_langs` +- `pp_scontainers` +- `pp_scontainers_langs` + +Warstwa: `Domain\Banner\BannerRepository`, `Domain\Scontainers\ScontainersRepository`. + +## Ustawienia i system + +### Ustawienia aplikacji + +- `pp_settings` + - klucze globalne (w tym `api_key` dla REST API) +- `pp_shop_apilo_settings` +- `pp_shop_shoppro_settings` + +Warstwa: `Domain\Settings\SettingsRepository`, `Domain\Integrations\IntegrationsRepository`. + +### Jezyki i tlumaczenia + +- `pp_langs` +- `pp_langs_translations` + +Warstwa: `Domain\Languages\LanguagesRepository`. + +### Uzytkownicy admina + +- `pp_users` + - login, hash hasla, status + - pola 2FA (`twofa_*`) + +Warstwa: `Domain\User\UserRepository`. + +## Routing i URL mapping + +- `pp_routes` + - regex `pattern` -> `destination` query string + - obsluguje trasy encji oraz trasy systemowe + - cache Redis: `pp_routes:all` + +Runtime wykorzystanie: +- `index.php` +- `Shared\Helpers\Helpers::htacces()` +- repozytoria encji generujace/odswiezajace trasy. + +## Kolejka cron + +- `pp_cron_jobs` + - status processing pipeline (`pending`, `processing`, `completed`, `failed`, `cancelled`) + - retry/backoff: `attempts`, `max_attempts`, `scheduled_at` + - indeksy: + - `(status, priority, scheduled_at)` + - `(job_type)` + - `(status)` +- `pp_cron_schedules` + - harmonogramy okresowe (`interval_seconds`, `next_run_at`) + - indeks `(enabled, next_run_at)` + +Warstwa: `Domain\CronJob\CronJobRepository`, `Domain\CronJob\CronJobProcessor`. + +## Najwazniejsze relacje (FK logiczne) + +- Produkt glowny -> wariant: `pp_shop_products.parent_id -> pp_shop_products.id` +- Produkt -> tlumaczenia: `pp_shop_products_langs.product_id -> pp_shop_products.id` +- Produkt -> kategoria: `pp_shop_products_categories.product_id -> pp_shop_products.id` +- Kategoria -> tlumaczenia: `pp_shop_categories_langs.category_id -> pp_shop_categories.id` +- Zamowienie -> klient: `pp_shop_orders.client_id -> pp_shop_clients.id` (opcjonalne) +- Transport <-> platnosc: `pp_shop_transport_payment_methods` +- Cecha -> wartosci -> warianty: `attributes -> values -> shop_products_attributes` +- Producent -> tlumaczenia: `pp_shop_producer_lang.producer_id -> pp_shop_producer.id` +- Kontener -> tlumaczenia: `pp_scontainers_langs.container_id -> pp_scontainers.id` + +## Uwaga operacyjna + +Ten dokument jest skrotem architektonicznym. +Przy zmianach SQL/migracji zawsze aktualizuj rownolegle: +1. `docs/DATABASE_STRUCTURE.md` (detal techniczny) +2. `.paul/docs/DB_SCHEMA.md` (mapa domenowa i impact runtime) diff --git a/.paul/docs/TODO.md b/.paul/docs/TODO.md index e5893db..c4a2aed 100644 --- a/.paul/docs/TODO.md +++ b/.paul/docs/TODO.md @@ -1,3 +1,3610 @@ -# TODO +3. Dodać uwierzytelnienie dwuskładnikowe za pomocą aplikacji. +4. Dodać zarządzanie uprawnieniami na poziomie urzytkownika, na razie uprawnienia do poszczególnych modułów. +naprawić działanie newslettera i zapis do bazy newslettera +program lojalnościowy +proponowane produkty w koszyku +Do zamówień w statusie: realizowane lub oczekuje na wpłatę. Opcja tylko dla zarejestrowanych klientów. https://royal-stone.pl/pl/order1.html +Dodać możliwość ustawienia limitu znaków w wiadomościach do produktu +8. [] Przerobić analitykę Google Analytics i Google ADS +9. [x] Rozważyć integrację SonarQube (statyczna analiza kodu PHP — bugi, security, code smells). Community Edition darmowy, self-hosted. Wymaga serwera + MCP server w Claude Code. + +## SonarQube — 0.340 (2026-03-15) + +### Bugs + +- [ ] [MAJOR] cron.php:192 — Review the data-flow - use of uninitialized value (php:S836) +- [ ] [MAJOR] cron.php:561 — Review the data-flow - use of uninitialized value (php:S836) +- [ ] [MAJOR] cron.php:590 — Review the data-flow - use of uninitialized value (php:S836) +- [ ] [MAJOR] cron.php:643 — Review the data-flow - use of uninitialized value (php:S836) + +### Code Smells — CRITICAL + +- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:35 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:66 — Define a constant instead of duplicating "Accept: application/json" 5 times (php:S1192) +- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:77 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:159 — Define a constant instead of duplicating "Y-m-d H:i:s" 3 times (php:S1192) +- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:239 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:309 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:315 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:339 — Define a constant instead of duplicating "Authorization: Bearer " 3 times (php:S1192) +- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:359 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:400 — Refactor this function to reduce its Cognitive Complexity (php:S3776) +- [ ] [CRITICAL] autoload/front/Controllers/ShopBasketController.php:499 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] autoload/front/Controllers/ShopBasketController.php:502 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] autoload/api/Controllers/ProductsApiController.php:396 — Refactor this function to reduce its Cognitive Complexity from 83 to 15 (php:S3776) +- [ ] [CRITICAL] autoload/Shared/Helpers/Helpers.php:408 — Refactor this function to reduce its Cognitive Complexity from 165 to 15 (php:S3776) +- [ ] [CRITICAL] autoload/Shared/Helpers/Helpers.php:520 — Define a constant instead of duplicating "/([0-9]+)$" 3 times (php:S1192) +- [ ] [CRITICAL] autoload/Shared/Helpers/Helpers.php:607 — Define a constant instead of duplicating " Order Deny,Allow" 3 times (php:S1192) +- [ ] [CRITICAL] autoload/Shared/Helpers/Helpers.php:650 — Define a constant instead of duplicating "&lang=" 7 times (php:S1192) +- [ ] [CRITICAL] cron.php:200 — Define a constant instead of duplicating "Y-m-d H:i:s" 7 times (php:S1192) +- [ ] [CRITICAL] cron.php:200 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] cron.php:203 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] cron.php:418 — Define a constant instead of duplicating "Authorization: Bearer " 5 times (php:S1192) +- [ ] [CRITICAL] cron.php:419 — Define a constant instead of duplicating "Accept: application/json" 5 times (php:S1192) +- [ ] [CRITICAL] cron.php:526 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] cron.php:529 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] cron.php:531 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] cron.php:533 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] cron.php:542 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] cron.php:545 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] cron.php:547 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] cron.php:555 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] cron.php:559 — Add curly braces around nested statement(s) (php:S121) + +### Code Smells — MAJOR + +- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:130 — Method has 4 returns, max 3 allowed (php:S1142) +- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:233 — Method has 5 returns, max 3 allowed (php:S1142) +- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:307 — Method has 7 returns, max 3 allowed (php:S1142) +- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:400 — Method has 8 returns, max 3 allowed (php:S1142) +- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:449 — Method has 4 returns, max 3 allowed (php:S1142) +- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:481 — Method has 4 returns, max 3 allowed (php:S1142) +- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:513 — Method has 4 returns, max 3 allowed (php:S1142) +- [ ] [MAJOR] autoload/front/Controllers/ShopBasketController.php:493 — Method has 4 returns, max 3 allowed (php:S1142) +- [ ] [MAJOR] autoload/Domain/Order/OrderAdminService.php:673 — Method has 4 returns, max 3 allowed (php:S1142) +- [ ] [MAJOR] autoload/Domain/Order/OrderAdminService.php:740 — Method has 4 returns, max 3 allowed (php:S1142) + +### Code Smells — MINOR + +- [ ] [MINOR] autoload/Domain/Order/OrderRepository.php — Add a new line at the end of file (php:S113) +- [ ] [MINOR] admin/templates/site/unlogged-layout.php — Add a new line at the end of file (php:S113) +- [ ] [MINOR] admin/templates/users/user-2fa.php — Add a new line at the end of file (php:S113) +- [ ] [MINOR] autoload/admin/Controllers/ProductArchiveController.php:196 — Rename function "bulk_delete_permanent" to match camelCase (php:S100) +- [ ] [MINOR] autoload/api/ApiRouter.php:107 — Remove unused "$db" local variable (php:S1481) +- [ ] [MINOR] cron.php:198 — Remove unused "$orderAdminService" local variable (php:S1481) +- [ ] [MINOR] cron.php:524 — Remove unused "$mdb" local variable (php:S1481) +- [ ] [MINOR] cron.php:539 — Remove unused "$mdb" local variable (php:S1481) + +## SonarQube — 0.343 (2026-03-19) + +### Nowe issues (nie występowały w 0.340) + +#### Code Smells — CRITICAL + +- [ ] [CRITICAL] autoload/admin/App.php:39 — Cognitive Complexity 37 (max 15) (php:S3776) +- [ ] [CRITICAL] autoload/admin/App.php:50 — Duplicated literal "Location: /admin/" 8 times (php:S1192) +- [ ] [CRITICAL] autoload/front/Controllers/ShopOrderController.php:86 — Cognitive Complexity 22 (max 15) (php:S3776) +- [ ] [CRITICAL] autoload/front/Controllers/ShopBasketController.php:275 — Duplicated literal "Location: /koszyk" 6 times (php:S1192) +- [ ] [CRITICAL] autoload/front/Controllers/ShopBasketController.php:287 — Duplicated literal "Location: /zamowienie/" 3 times (php:S1192) +- [ ] [CRITICAL] autoload/front/Controllers/ShopBasketController.php:495 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] autoload/Domain/Integrations/IntegrationsRepository.php:33 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] autoload/Domain/Integrations/ApiloRepository.php:449 — Cognitive Complexity 22 (max 15) (php:S3776) +- [ ] [CRITICAL] autoload/Domain/Order/OrderRepository.php:635 — Cognitive Complexity 61 (max 15) (php:S3776) +- [ ] [CRITICAL] cron.php:198 — Cognitive Complexity 109 (max 15) (php:S3776) +- [ ] [CRITICAL] cron.php:651 — Cognitive Complexity 18 (max 15) (php:S3776) + +#### Code Smells — MAJOR + +- [ ] [MAJOR] cron.php:198 — Function has 305 lines (max 150) (php:S138) +- [ ] [MAJOR] cron.php:572 — Unused function parameter "$payload" (php:S1172) +- [ ] [MAJOR] cron.php:572 — 5 returns (max 3) (php:S1142) +- [ ] [MAJOR] cron.php:605 — Unused function parameter "$payload" (php:S1172) +- [ ] [MAJOR] cron.php:605 — 4 returns (max 3) (php:S1142) +- [ ] [MAJOR] cron.php:651 — Unused function parameter "$payload" (php:S1172) +- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:53 — 4 returns (max 3) (php:S1142) +- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:93 — 4 returns (max 3) (php:S1142) +- [ ] [MAJOR] autoload/Domain/Integrations/ApiloRepository.php:105 — Merge if statement with enclosing one (php:S1066) + +## SonarQube — 0.344 (2026-03-19) + +- [ ] [MINOR] autoload/front/Controllers/ShopBasketController.php:484 — Use empty() to check whether the array is empty (php:S1155) + +## SonarQube — 0.345 (2026-03-25) + +- [ ] [MAJOR] autoload/front/Controllers/ShopBasketController.php:574 — This method has 6 returns, which is more than the 3 allowed (php:S1142) +- [ ] [CRITICAL] autoload/front/Controllers/ShopBasketController.php:576 — Add curly braces around nested statement(s) (php:S121) +- [ ] [CRITICAL] autoload/front/Controllers/ShopBasketController.php:602 — Add curly braces around nested statement(s) (php:S121) +## SonarQube - 0.347 (2026-04-18) + +### Bugs/Vulnerabilities + +- [ ] [BLOCKER] autoload/admin/App.php:6 'SECRET' detected in this expression, review this potentially hard-coded secret. (php:S6418) +- [ ] [CRITICAL] autoload/Domain/Integrations/IntegrationsRepository.php:241 Enable server certificate validation on this SSL/TLS connection. (php:S4830) +- [ ] [CRITICAL] autoload/Domain/Integrations/IntegrationsRepository.php:242 Enable server hostname verification on this SSL/TLS connection. (php:S5527) +- [ ] [CRITICAL] src/Modules/Settings/EmailMailboxController.php:223 Change this code to use a stronger protocol. (php:S4423) +- [ ] [CRITICAL] templates/projects/tasks.php:116 Duplicate id "inprogress-tasks-container" found. First occurrence was on line 97. (Web:S7930) +- [ ] [CRITICAL] templates/site/layout-cron.php:52 Duplicate id "divider" found. First occurrence was on line 47. (Web:S7930) +- [ ] [CRITICAL] templates/tasks/work-time.php:179 Duplicate id "billing-empty-state" found. First occurrence was on line 176. (Web:S7930) +- [ ] [MAJOR] admin/templates/dashboard/main-view.php:84 Add "" headers to this "". (Web:S5256) +- [ ] [MAJOR] admin/templates/site/main-layout.php:3 Add "lang" and/or "xml:lang" attributes to this "" element (Web:S5254) +- [ ] [MAJOR] admin/templates/site/unlogged.php:2 Add "lang" and/or "xml:lang" attributes to this "" element (Web:S5254) +- [ ] [MAJOR] admin/templates/site/unlogged-layout.php:2 Add "lang" and/or "xml:lang" attributes to this "" element (Web:S5254) +- [ ] [MAJOR] autoload/class.Excel.php:26 Group parts of the regex together to make the intended operator precedence explicit. (php:S5850) +- [ ] [MAJOR] autoload/class.S.php:167 Remove this conditional structure or edit its code blocks so that they're not all the same. (php:S3923) +- [ ] [MAJOR] autoload/class.S.php:176 Remove this conditional structure or edit its code blocks so that they're not all the same. (php:S3923) +- [ ] [MAJOR] autoload/controls/class.Tasks.php:432 Remove this use of the output from "Controllers\TasksController::taskChangeStatus"; "Controllers\TasksController::taskChangeStatus" doesn't return anything. (php:S3699) +- [ ] [MAJOR] autoload/controls/class.Tasks.php:537 Remove or correct this useless self-assignment (php:S1656) +- [ ] [MAJOR] autoload/controls/class.Users.php:242 Remove this use of the output from "Controllers\UsersController::switchBackToAdmin"; "Controllers\UsersController::switchBackToAdmin" doesn't return anything. (php:S3699) +- [ ] [MAJOR] autoload/Domain/Promotion/PromotionRepository.php:537 Identical sub-expressions on both sides of operator "and" (php:S1764) +- [ ] [MAJOR] autoload/factory/class.Projects.php:326 Delete this unreachable code or refactor the code to make it reachable. (php:S1763) +- [ ] [MAJOR] autoload/factory/class.Tasks.php:851 Delete this unreachable code or refactor the code to make it reachable. (php:S1763) +- [ ] [MAJOR] templates/finances/main-view.php:107 Add either an 'id' or a 'scope' attribute to this
tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:185 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:188 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:191 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:194 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:201 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:204 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:207 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:210 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:222 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:223 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:224 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:225 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:252 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:255 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:94 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:95 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:96 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:97 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:98 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/main-view.php:99 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/operations-list.php:28 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/operations-list.php:29 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/operations-list.php:30 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/operations-list.php:31 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/operations-list.php:65 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MAJOR] templates/finances/operations-list.php:68 Add either an 'id' or a 'scope' attribute to this tag. (Web:TableHeaderHasIdOrScopeCheck) +- [ ] [MINOR] admin/index.php:51 Replace "include" with "include_once". (php:S2003) +- [ ] [MINOR] admin/templates/banners/banner-edit.php:146 Replace "include" with "include_once". (php:S2003) +- [ ] [MINOR] admin/templates/filemanager/filemanager.php:4 Add a "title" attribute to this