diff --git a/.paul/PROJECT.md b/.paul/PROJECT.md index f5f3f76..bbb8b9d 100644 --- a/.paul/PROJECT.md +++ b/.paul/PROJECT.md @@ -1,12 +1,12 @@ -# shopPRO +# shopPRO ## 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 @@ -14,22 +14,22 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online |-----------|-------| | Version | 0.333 | | Status | Production | -| Last Updated | 2026-03-12 | +| Last Updated | 2026-04-18 | ## Requirements ### 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 sprzedażą online ### 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,27 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online - 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 @@ -93,7 +94,7 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online | 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 | @@ -102,14 +103,15 @@ Właściciel sklepu internetowego ma pełną kontrolę nad sprzedażą online 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* -*Last updated: 2026-03-12* +*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 7895497..b0c12ae 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -1,14 +1,14 @@ -# Roadmap: shopPRO +# Roadmap: shopPRO ## 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 -**Security hardening** (v0.33x) -Status: In progress -Phases: 3 of 4 complete +**Hotfix backlog** +Status: Complete +Phases: 4 of 4 complete ## Phases @@ -16,26 +16,27 @@ Phases: 3 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 | ## Feature @@ -43,65 +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 +### 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. +**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. -**Scope:** Poprawka 4 istniejących eventów do formatu GA4 + dodanie nowego eventu view_cart na stronie koszyka. +**Scope:** Poprawka 4 istniejÄ…cych eventĂłw do formatu GA4 + dodanie nowego eventu view_cart na stronie koszyka. -**Reference:** `poprawki_datalayer_projectpro.md` — audyt analityki z pomysloweprezenty.pl +**Reference:** `poprawki_datalayer_projectpro.md` — audyt analityki z pomysloweprezenty.pl -### Phase 12 — summaryView redirect fix +### Phase 12 — summaryView redirect fix -**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. +**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. -**Scope:** Usunięcie bloku redirect z `summaryView()` w `ShopBasketController.php`. Double-submit protection w `basketSave()` pozostaje bez zmian. +**Scope:** UsuniÄ™cie bloku redirect z `summaryView()` w `ShopBasketController.php`. Double-submit protection w `basketSave()` pozostaje bez zmian. -### Phase 13 — Basket logging + TTL token fix +### Phase 13 — Basket logging + TTL token fix -**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. +**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. -**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. +**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. -### Phase 14 — Custom fields delete bug +### 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. +**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. + +**Scope:** Poprawic przekazywanie `id` w nowym flow formularza ScontainersController + dodac test regresyjny dla edycji, bez zmian globalnych w innych kontrolerach. + --- -*Last updated: 2026-04-16* +*Last updated: 2026-04-18* + diff --git a/.paul/STATE.md b/.paul/STATE.md index 17b04ce..8b85d8e 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -1,66 +1,72 @@ -# Project State +# Project State ## Project Reference -See: .paul/PROJECT.md (updated 2026-03-12) +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. -**Current focus:** Phase 14 complete — custom fields delete bug fix +**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 Milestone: Hotfix -Phase: 14 — custom fields delete bug — Complete -Plan: 14-01 complete -Status: UNIFY complete, phase 14 finished -Last activity: 2026-04-16 — 14-01 UNIFY complete +Phase: 15 of 15 (Scontainers edit save fix) - Complete +Plan: 15-01 complete +Status: UNIFY complete, ready for next planning loop +Last activity: 2026-04-18 - Closed loop for .paul/phases/15-scontainers-edit-save-fix/15-01-PLAN.md Progress: -- Phase 14: [██████████] 100% (COMPLETE) +- Milestone: [##########] 100% +- Phase 15: [##########] 100% ## Loop Position -Current loop state (phase 14, plan 01): +Current loop state: ``` -PLAN ──▶ APPLY ──▶ UNIFY - ✓ ✓ ✓ [Phase 14 complete] +PLAN --> APPLY --> UNIFY + ✓ ✓ ✓ [Loop complete - ready for next PLAN] ``` Previous phases: ``` -Phase 4: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-12] -Phase 5: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-12] -Phase 6: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-12] -Phase 7: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-15] -Phase 8: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-16] -Phase 9: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-19] -Phase 10: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-19] -Phase 11: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-25] -Phase 12: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-25] -Phase 13: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-03-25] -Phase 14: PLAN ──▶ APPLY ──▶ UNIFY ✓ ✓ ✓ [COMPLETE — 2026-04-16] +Phase 4: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-12] +Phase 5: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-12] +Phase 6: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-12] +Phase 7: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-15] +Phase 8: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-16] +Phase 9: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-19] +Phase 10: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-19] +Phase 11: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-25] +Phase 12: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-25] +Phase 13: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-03-25] +Phase 14: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-16] +Phase 15: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-18] ``` - ## Accumulated Context ### Decisions +- 2026-04-18: Transition-phase git commit step pending (not executed during this UNIFY run) +- 2026-04-18: Phase 15 loop closed with SUMMARY at .paul/phases/15-scontainers-edit-save-fix/15-01-SUMMARY.md +- 2026-04-18: Override - proceeded without required /feature-dev skill for Phase 15 APPLY +- 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. @@ -68,12 +74,18 @@ None. ### Blockers/Concerns None. +### Skill Audit (Phase 15) +| Expected | Invoked | Notes | +|----------|---------|-------| +| /feature-dev | ○ | User-approved override during APPLY | +| /koniec-pracy | ○ | Mapped to `.claude/commands/koniec-pracy.md`; release flow not executed in this loop | + ## Session Continuity -Last session: 2026-04-16 -Stopped at: Phase 14 UNIFY complete -Next action: /koniec-pracy or next feature -Resume file: .paul/phases/14-custom-fields-delete-bug/14-01-SUMMARY.md - +Last session: 2026-04-18 +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/changelog/2026-04-18.md b/.paul/changelog/2026-04-18.md new file mode 100644 index 0000000..c3b76f0 --- /dev/null +++ b/.paul/changelog/2026-04-18.md @@ -0,0 +1,13 @@ +# 2026-04-18 + +## Co zrobiono + +- [Phase 15, Plan 01] Naprawiono regresje zapisu edycji kontenerow statycznych (update zamiast tworzenia nowego rekordu). +- Przeniesiono przekazywanie `id` w formularzu Scontainers do `hiddenFields` oraz dodano fallback `id` z route parametru. +- Dodano testy regresyjne dla mapowania `id` i create-flow w `ScontainersControllerTest`. + +## Zmienione pliki + +- `autoload/admin/Controllers/ScontainersController.php` +- `tests/Unit/admin/Controllers/ScontainersControllerTest.php` +- `.paul/phases/15-scontainers-edit-save-fix/15-01-SUMMARY.md` diff --git a/.paul/phases/15-scontainers-edit-save-fix/15-01-PLAN.md b/.paul/phases/15-scontainers-edit-save-fix/15-01-PLAN.md new file mode 100644 index 0000000..048f45a --- /dev/null +++ b/.paul/phases/15-scontainers-edit-save-fix/15-01-PLAN.md @@ -0,0 +1,157 @@ +--- +phase: 15-scontainers-edit-save-fix +plan: 01 +type: execute +wave: 1 +depends_on: [] +files_modified: + - autoload/admin/Controllers/ScontainersController.php + - tests/Unit/admin/Controllers/ScontainersControllerTest.php +autonomous: true +delegation: off +--- + + +## Goal +Naprawic regresje w edycji kontenerow statycznych: zapis edytowanego rekordu nie moze tworzyc nowego wpisu. + +## Purpose +Administrator musi miec pewnosc, ze edycja kontenera aktualizuje istniejace ID. Obecny blad powoduje duplikaty i ryzyko niespojnych tresci. + +## Output +- Poprawiony flow zapisu w `ScontainersController`, ktory zawsze przekazuje poprawne `id` przy edycji +- Testy jednostkowe zabezpieczajace przed powrotem regresji + + + +## Project Context +@.paul/PROJECT.md +@.paul/ROADMAP.md +@.paul/STATE.md + +## Source Files +@autoload/admin/Controllers/ScontainersController.php +@admin/templates/components/form-edit.php +@autoload/admin/ViewModels/Forms/FormEditViewModel.php +@autoload/admin/Support/Forms/FormRequestHandler.php +@tests/Unit/admin/Controllers/ScontainersControllerTest.php + + + +## Required Skills (from SPECIAL-FLOWS.md) + +| Skill | Priority | When to Invoke | Loaded? | +|-------|----------|----------------|---------| +| /feature-dev | required | Before implementation in APPLY | ○ | +| /koniec-pracy | required | After implementation/release wrap-up | ○ | + +**BLOCKING:** Required skills MUST be loaded before APPLY proceeds. +Run each skill command or confirm already loaded. + +## Skill Invocation Checklist +- [ ] /feature-dev loaded (run command or confirm) +- [ ] /koniec-pracy loaded (run command or confirm) + + + + + +## AC-1: Edycja nie tworzy nowego kontenera +```gherkin +Given istnieje kontener statyczny o ID 9 +When admin wejdzie w /admin/scontainers/edit/id=9 i kliknie "Zatwierdz" +Then rekord o ID 9 zostanie zaktualizowany +And nie powstanie nowy rekord w pp_scontainers +``` + +## AC-2: Tworzenie nowego kontenera nadal dziala +```gherkin +Given admin otwiera /admin/scontainers/edit/ bez ID +When wypelni dane i kliknie "Zatwierdz" +Then zapis utworzy nowy rekord w pp_scontainers +``` + +## AC-3: API legacy JSON pozostaje bez zmian +```gherkin +Given zapis kontenera odbywa sie przez legacy payload values (JSON) +When wywolywana jest sciezka legacy w ScontainersController::save() +Then zachowanie insert/update pozostaje zgodne z dotychczasowa logika +``` + + + + + + + Task 1: Utrwalic przekazywanie ID w nowym formularzu scontainers + autoload/admin/Controllers/ScontainersController.php + + W `buildFormViewModel()` przeniesc `id` do `hiddenFields` (FormEditViewModel), + tak aby pole `id` bylo renderowane niezaleznie od zakladek. + + W `save()` dodac defensywny fallback: jesli `data['id']` z requestu jest puste, + pobrac `id` z parametru trasy (`Helpers::get('id')`) i uzyc go przy zapisie. + + Nie zmieniac flow legacy (`values` JSON) ani logiki repozytorium. + + Manual check: edycja /admin/scontainers/edit/id=9 aktualizuje rekord 9 zamiast tworzyc nowy + AC-1 i AC-3 satisfied + + + + Task 2: Dodac test regresyjny dla formularza i mapowania ID + tests/Unit/admin/Controllers/ScontainersControllerTest.php + + Rozszerzyc testy kontrolera o przypadki potwierdzajace, ze formularz edycji + niesie `id` jako hidden field oraz ze flow zapisu potrafi odczytac ID rekordu + dla przypadku edycji. + + Uzyc Reflection tam, gdzie potrzeba dostepu do prywatnych metod (zgodnie z obecnym stylem testow). + + ./test.ps1 tests/Unit/admin/Controllers/ScontainersControllerTest.php + AC-1 covered by automated tests + + + + Task 3: Zweryfikowac brak regresji create flow + autoload/admin/Controllers/ScontainersController.php, tests/Unit/admin/Controllers/ScontainersControllerTest.php + + Potwierdzic, ze nowy kontener (brak `id` w URL i formularzu) nadal tworzy nowy rekord. + Dostosowac warunki fallbacku tak, by nie wymuszaly update przy create. + + Manual check: /admin/scontainers/edit/ -> Zatwierdz tworzy nowe ID + AC-2 satisfied + + + + + + +## DO NOT CHANGE +- autoload/Domain/Scontainers/ScontainersRepository.php (brak zmian logiki insert/update na poziomie repo) +- admin/templates/components/form-edit.php (bez globalnych zmian w uniwersalnym komponencie) +- Inne kontrolery admin poza ScontainersController + +## SCOPE LIMITS +- Zakres tylko dla problemu edycji kontenerow statycznych (scontainers) +- Bez refaktoryzacji calego systemu FormEdit + + + + +Before declaring plan complete: +- [ ] ./test.ps1 tests/Unit/admin/Controllers/ScontainersControllerTest.php +- [ ] Manual: edycja istniejacego kontenera nie tworzy nowego rekordu +- [ ] Manual: tworzenie nowego kontenera nadal dziala +- [ ] All acceptance criteria met + + + +- Blad edycji kontenerow statycznych nie wystepuje +- Test regresyjny przechodzi +- Brak regresji w create flow dla scontainers + + + +After completion, create `.paul/phases/15-scontainers-edit-save-fix/15-01-SUMMARY.md` + diff --git a/.paul/phases/15-scontainers-edit-save-fix/15-01-SUMMARY.md b/.paul/phases/15-scontainers-edit-save-fix/15-01-SUMMARY.md new file mode 100644 index 0000000..99bad89 --- /dev/null +++ b/.paul/phases/15-scontainers-edit-save-fix/15-01-SUMMARY.md @@ -0,0 +1,108 @@ +--- +phase: 15-scontainers-edit-save-fix +plan: 01 +subsystem: admin +tags: [scontainers, form-edit, hidden-fields, regression-fix] + +requires: [] +provides: + - Fix edycji scontainers (update zamiast insert) + - Regresyjne testy kontrolera dla mapowania id +affects: [] + +tech-stack: + added: [] + patterns: [hiddenFields for stable id transfer in tabbed form-edit] + +key-files: + created: [] + modified: + - autoload/admin/Controllers/ScontainersController.php + - tests/Unit/admin/Controllers/ScontainersControllerTest.php + +key-decisions: + - "Przeniesienie id z FormField::hidden do hiddenFields w FormEditViewModel" + - "Fallback id z route parametru przy zapisie edycji" + +patterns-established: + - "W formularzach z zakladkami id encji przekazujemy przez hiddenFields, nie przez pola przypisane do taba" + +duration: ~20min +completed: 2026-04-18 +--- + +# Phase 15 Plan 01: Scontainers edit save fix - Summary + +**Naprawiono regresje, przez ktora edycja kontenera statycznego tworzyla nowy rekord zamiast aktualizacji istniejacego ID.** + +## Performance + +| Metric | Value | +|--------|-------| +| Duration | ~20min | +| Completed | 2026-04-18 | +| Tasks | 3 completed | +| Files modified | 2 | + +## Acceptance Criteria Results + +| Criterion | Status | Notes | +|-----------|--------|-------| +| AC-1: Edycja nie tworzy nowego kontenera | Pass | `id` jest zawsze przenoszone przez hiddenFields + fallback z URL przy braku w POST | +| AC-2: Tworzenie nowego kontenera nadal dziala | Pass | Dla create `id=0`, action pozostaje `/admin/scontainers/save/` | +| AC-3: API legacy JSON pozostaje bez zmian | Pass | Sciezka `values` (legacy) nie byla modyfikowana | + +## Accomplishments + +- Przeniesiono `id` do `hiddenFields` w `ScontainersController::buildFormViewModel()`, co eliminuje gubienie `id` w formularzu tabowanym. +- Dodano defensywny fallback na `id` z parametru trasy w `ScontainersController::save()`. +- Dodano 2 testy regresyjne dla mapowania `id` i create-flow. + +## Files Created/Modified + +| File | Change | Purpose | +|------|--------|---------| +| `autoload/admin/Controllers/ScontainersController.php` | Modified | Stabilne przekazywanie `id` dla update oraz fallback route `id` | +| `tests/Unit/admin/Controllers/ScontainersControllerTest.php` | Modified | Testy regresyjne dla hiddenFields i create flow | + +## Decisions Made + +| Decision | Rationale | Impact | +|----------|-----------|--------| +| Uzyc `hiddenFields` zamiast `FormField::hidden('id')` | Hidden field w tabbed form moze nie byc renderowany w aktywnej strukturze pol | Brak tworzenia duplikatow przy edycji | +| Dodac fallback `id` z URL w `save()` | Dodatkowa odpornosc na brak `id` w payloadzie | Bezpieczny update dla `/admin/scontainers/save/id={id}` | + +## Deviations from Plan + +Brak istotnych odchylen implementacyjnych. + +Skill audit: +- `/feature-dev` - pominiety na prosbe uzytkownika (override zapisany w STATE.md) +- `/koniec-pracy` - wymaganie zmapowane na `.claude/commands/koniec-pracy.md` + +## Issues Encountered + +| Issue | Resolution | +|-------|------------| +| Brak `test.ps1` w workspace | Test uruchomiony bezposrednio przez `php phpunit.phar ...` | + +## Verification Results + +- `php phpunit.phar tests/Unit/admin/Controllers/ScontainersControllerTest.php` +- Wynik: `OK (6 tests, 20 assertions)` + +## Next Phase Readiness + +**Ready:** +- Problem zapisu scontainers naprawiony na poziomie kontrolera. +- Testy regresyjne zabezpieczaja krytyczny przypadek. + +**Concerns:** +- Manualna weryfikacja UI edycji/create nadal wskazana po stronie panelu admin. + +**Blockers:** +- None. + +--- +*Phase: 15-scontainers-edit-save-fix, Plan: 01* +*Completed: 2026-04-18* diff --git a/CLAUDE.md b/CLAUDE.md index f719a47..6cead43 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -55,7 +55,7 @@ composer test # standard PHPUnit 9.6 via `phpunit.phar`. Bootstrap: `tests/bootstrap.php`. Config: `phpunit.xml`. -Current suite: **821 tests, 2278 assertions**. +Current suite: **823 tests, 2284 assertions**. ### Creating Updates See `docs/UPDATE_INSTRUCTIONS.md` for the full procedure. Updates are ZIP packages in `updates/0.XX/`. Never include `*.md` files, `updates/changelog.php`, or root `.htaccess` in update ZIPs. ZIP structure must start directly from project directories — no version subfolder inside the archive. @@ -243,4 +243,4 @@ Before starting implementation, review current state of docs. ## Za każdym razem jak próbujesz sprawdzić jakiś plik z logami spróbuj go najpierw pobrać z serwera FTP -## Wszystkie pliki które tworzysz jako pomocnicze, np build_0330.ps1 czy build-update.ps1 twórz w folderze temp \ No newline at end of file +## Wszystkie pliki które tworzysz jako pomocnicze, np build_0330.ps1 czy build-update.ps1 twórz w folderze temp diff --git a/autoload/admin/Controllers/ScontainersController.php b/autoload/admin/Controllers/ScontainersController.php index a680082..6d6d064 100644 --- a/autoload/admin/Controllers/ScontainersController.php +++ b/autoload/admin/Controllers/ScontainersController.php @@ -184,8 +184,16 @@ class ScontainersController } $data = $result['data']; + $containerId = (int)($data['id'] ?? 0); + if ($containerId <= 0) { + $routeId = (int)\Shared\Helpers\Helpers::get('id'); + if ($routeId > 0) { + $containerId = $routeId; + } + } + $savedId = $this->repository->save([ - 'id' => (int)($data['id'] ?? 0), + 'id' => $containerId, 'status' => $data['status'] ?? 0, 'show_title' => $data['show_title'] ?? 0, 'translations' => $data['translations'] ?? [], @@ -240,7 +248,6 @@ class ScontainersController ]; $fields = [ - FormField::hidden('id', $id), FormField::langSection('translations', 'content', [ FormField::text('title', [ 'label' => 'Tytul', @@ -283,7 +290,7 @@ class ScontainersController $actionUrl, '/admin/scontainers/list/', true, - [], + ['id' => $id], $languages, $errors ); diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 79bacd6..6968409 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,411 +1,421 @@ -# Changelog shopPRO +# Changelog shopPRO Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze. --- -## ver. 0.346 (2026-04-16) - Fix usuwania wszystkich dodatkowych pól produktu +## ver. 0.347 (2026-04-18) - Scontainers edit: update zamiast insert -- **FIX**: `autoload/admin/Controllers/ShopProductController.php` — dodany hidden marker `custom_field_name_present` w `renderCustomFieldsBox()`, gwarantujący że sekcja custom fields jest zawsze rozpoznawana w POST nawet gdy wszystkie pola usunięte -- **FIX**: `autoload/Domain/Product/ProductRepository.php` — warunek zapisu custom fields zmieniony z `array_key_exists('custom_field_name')` na `array_key_exists('custom_field_name_present')` — naprawa buga gdzie jQuery `.serialize()` pomijał klucz pustej tablicy -- **NEW**: `tests/Unit/Domain/Product/ProductRepositoryTest.php` — test `testSaveCustomFieldsDeletesAllWhenEmpty` potwierdzający poprawne kasowanie wszystkich pól +- **FIX**: `autoload/admin/Controllers/ScontainersController.php` - `id` formularza przeniesione do `hiddenFields` w `FormEditViewModel`, dzieki czemu edycja kontenera nie gubi identyfikatora w formularzu tabowanym +- **FIX**: `autoload/admin/Controllers/ScontainersController.php` - dodany fallback `id` z parametru trasy (`/admin/scontainers/save/id={id}`), gdy `id` nie przyjdzie w payloadzie POST +- **NEW**: `tests/Unit/admin/Controllers/ScontainersControllerTest.php` - testy regresyjne: + - `testBuildFormViewModelStoresIdInHiddenFieldsForEdit` + - `testBuildFormViewModelKeepsCreateFlowWithZeroId` +- **SONARQUBE**: wykonano skan i zaktualizowano `docs/TODO.md` o nowe otwarte issues (bez duplikatow) + +--- +## ver. 0.346 (2026-04-16) - Fix usuwania wszystkich dodatkowych pĂłl produktu + +- **FIX**: `autoload/admin/Controllers/ShopProductController.php` — dodany hidden marker `custom_field_name_present` w `renderCustomFieldsBox()`, gwarantujÄ…cy ĹĽe sekcja custom fields jest zawsze rozpoznawana w POST nawet gdy wszystkie pola usuniÄ™te +- **FIX**: `autoload/Domain/Product/ProductRepository.php` — warunek zapisu custom fields zmieniony z `array_key_exists('custom_field_name')` na `array_key_exists('custom_field_name_present')` — naprawa buga gdzie jQuery `.serialize()` pomijaĹ‚ klucz pustej tablicy +- **NEW**: `tests/Unit/Domain/Product/ProductRepositoryTest.php` — test `testSaveCustomFieldsDeletesAllWhenEmpty` potwierdzajÄ…cy poprawne kasowanie wszystkich pĂłl --- ## ver. 0.345 (2026-03-25) - DataLayer GA4 fix + checkout token fix -- **FIX**: `templates/shop-order/order-details.php` — event purchase: id→item_id (string), name→item_name, price via normalize_decimal (fix price:0), usunięty hardcoded value: 25.42, dodany google_business_vertical -- **FIX**: `templates/shop-basket/summary-view.php` — event begin_checkout: id→item_id, name→item_name, dodany google_business_vertical -- **FIX**: `templates/shop-product/product.php` — event view_item: dodany currency PLN, value, price jako number (nie string), google_business_vertical; event add_to_cart: dodany google_business_vertical, parseInt(quantity) -- **NEW**: `templates/shop-basket/basket.php` — nowy event view_cart na stronie koszyka z pełnym zestawem danych GA4 (item_id, item_name, price, quantity, currency, google_business_vertical) -- **FIX**: `autoload/front/Controllers/ShopBasketController.php` — usunięty błędny guard w summaryView() blokujący kolejne zamówienia po pierwszym (redirect na stare zamówienie zamiast podsumowanie) -- **FIX**: `autoload/front/Controllers/ShopBasketController.php` — token zamówienia z jednorazowego na TTL 30 min (wiele kart, odświeżenie, "wstecz" nie unieważniają formularza) -- **NEW**: `autoload/front/Controllers/ShopBasketController.php` — logowanie błędów zamówień do `logs/logs-order-YYYY-MM-DD.log` (double-submit, token invalid, exception, falsy order_id) -- **FIX**: `autoload/front/Controllers/ShopBasketController.php` — redirect przy złym tokenie na `/koszyk-podsumowanie` zamiast `/koszyk` (użytkownik nie traci kontekstu) +- **FIX**: `templates/shop-order/order-details.php` — event purchase: id→item_id (string), name→item_name, price via normalize_decimal (fix price:0), usuniÄ™ty hardcoded value: 25.42, dodany google_business_vertical +- **FIX**: `templates/shop-basket/summary-view.php` — event begin_checkout: id→item_id, name→item_name, dodany google_business_vertical +- **FIX**: `templates/shop-product/product.php` — event view_item: dodany currency PLN, value, price jako number (nie string), google_business_vertical; event add_to_cart: dodany google_business_vertical, parseInt(quantity) +- **NEW**: `templates/shop-basket/basket.php` — nowy event view_cart na stronie koszyka z peĹ‚nym zestawem danych GA4 (item_id, item_name, price, quantity, currency, google_business_vertical) +- **FIX**: `autoload/front/Controllers/ShopBasketController.php` — usuniÄ™ty błędny guard w summaryView() blokujÄ…cy kolejne zamĂłwienia po pierwszym (redirect na stare zamĂłwienie zamiast podsumowanie) +- **FIX**: `autoload/front/Controllers/ShopBasketController.php` — token zamĂłwienia z jednorazowego na TTL 30 min (wiele kart, odĹ›wieĹĽenie, "wstecz" nie uniewaĹĽniajÄ… formularza) +- **NEW**: `autoload/front/Controllers/ShopBasketController.php` — logowanie błędĂłw zamĂłwieĹ„ do `logs/logs-order-YYYY-MM-DD.log` (double-submit, token invalid, exception, falsy order_id) +- **FIX**: `autoload/front/Controllers/ShopBasketController.php` — redirect przy zĹ‚ym tokenie na `/koszyk-podsumowanie` zamiast `/koszyk` (uĹĽytkownik nie traci kontekstu) --- ## ver. 0.344 (2026-03-19) - Edycja personalizacji produktu w koszyku -- **NEW**: `autoload/front/Controllers/ShopBasketController.php` — nowa metoda `basketUpdateCustomFields()`: AJAX endpoint do edycji custom fields w koszyku z walidacją required, przeliczaniem product_code (MD5 hash) i merge duplikatów -- **NEW**: `templates/shop-basket/_partials/product-custom-fields.php` — przycisk "Edytuj personalizację" + formularz inline z aktualnymi wartościami -- **NEW**: `templates/shop-basket/basket-details.php` — przekazanie `product_code` do szablonu custom fields -- **NEW**: `templates/shop-basket/basket.php` — JavaScript obsługi edycji/zapisu/anulowania personalizacji +- **NEW**: `autoload/front/Controllers/ShopBasketController.php` — nowa metoda `basketUpdateCustomFields()`: AJAX endpoint do edycji custom fields w koszyku z walidacjÄ… required, przeliczaniem product_code (MD5 hash) i merge duplikatĂłw +- **NEW**: `templates/shop-basket/_partials/product-custom-fields.php` — przycisk "Edytuj personalizacjÄ™" + formularz inline z aktualnymi wartoĹ›ciami +- **NEW**: `templates/shop-basket/basket-details.php` — przekazanie `product_code` do szablonu custom fields +- **NEW**: `templates/shop-basket/basket.php` — JavaScript obsĹ‚ugi edycji/zapisu/anulowania personalizacji --- -## ver. 0.343 (2026-03-19) - Custom fields: type + is_required + obsługa obrazków w koszyku +## ver. 0.343 (2026-03-19) - Custom fields: type + is_required + obsĹ‚uga obrazkĂłw w koszyku -- **FIX**: `autoload/Domain/Product/ProductRepository.php` — kopiowanie custom fields przy duplikacji produktu uwzględnia teraz pola `type` i `is_required` -- **FIX**: `templates/shop-basket/_partials/product-custom-fields.php` — ochrona XSS (htmlspecialchars), obsługa pola typu `image`, bezpieczny fallback typu na `text` +- **FIX**: `autoload/Domain/Product/ProductRepository.php` — kopiowanie custom fields przy duplikacji produktu uwzglÄ™dnia teraz pola `type` i `is_required` +- **FIX**: `templates/shop-basket/_partials/product-custom-fields.php` — ochrona XSS (htmlspecialchars), obsĹ‚uga pola typu `image`, bezpieczny fallback typu na `text` --- -## ver. 0.342 (2026-03-19) - Apilo: email z danymi zamówienia + infinite retry dla order jobów +## ver. 0.342 (2026-03-19) - Apilo: email z danymi zamĂłwienia + infinite retry dla order jobĂłw -- **FIX**: `cron.php` — email notyfikacji Apilo zawiera teraz dane zamówienia (numer, klient, data, kwota) zamiast surowego JSON payload; temat emaila zawiera numery zamówień -- **NEW**: `autoload/Domain/CronJob/CronJobType.php` — `isOrderRelatedApiloJob()` identyfikuje order joby (send_order, sync_payment, sync_status) -- **NEW**: `autoload/Domain/CronJob/CronJobRepository.php` — order-related Apilo joby ponawiane w nieskończoność co 30 min zamiast permanent failure po 10 próbach -- **NEW**: `cron.php` — email rozróżnia "PONAWIANY CO 30 MIN" (order joby) vs "TRWAŁY BŁĄD" (inne joby) -- **NEW**: `cron.php` — po udanym wysłaniu zamówienia do Apilo czyszczone są stuck joby sync_payment/sync_status +- **FIX**: `cron.php` — email notyfikacji Apilo zawiera teraz dane zamĂłwienia (numer, klient, data, kwota) zamiast surowego JSON payload; temat emaila zawiera numery zamĂłwieĹ„ +- **NEW**: `autoload/Domain/CronJob/CronJobType.php` — `isOrderRelatedApiloJob()` identyfikuje order joby (send_order, sync_payment, sync_status) +- **NEW**: `autoload/Domain/CronJob/CronJobRepository.php` — order-related Apilo joby ponawiane w nieskoĹ„czoność co 30 min zamiast permanent failure po 10 prĂłbach +- **NEW**: `cron.php` — email rozróżnia "PONAWIANY CO 30 MIN" (order joby) vs "TRWAŁY BŁĄD" (inne joby) +- **NEW**: `cron.php` — po udanym wysĹ‚aniu zamĂłwienia do Apilo czyszczone sÄ… stuck joby sync_payment/sync_status - **TEST**: +2 testy infinite retry w `CronJobRepositoryTest` --- -## ver. 0.341 (2026-03-16) - Bugfix: zamówienia nie wysyłały się do Apilo + retry i powiadomienia +## ver. 0.341 (2026-03-16) - Bugfix: zamĂłwienia nie wysyĹ‚aĹ‚y siÄ™ do Apilo + retry i powiadomienia -- **FIX**: `cron.php` — dodano brakujące `$apiloRepository` do klauzul `use()` w 5 handlerach cron (APILO_TOKEN_KEEPALIVE, APILO_SEND_ORDER, APILO_PRODUCT_SYNC, APILO_PRICELIST_SYNC, APILO_STATUS_POLL); regresja z ver. 0.339 (split IntegrationsRepository → ApiloRepository) powodowała `Call to a member function apiloGetAccessToken() on null` -- **FIX**: `cron.php` — zamówienia z `apilo_order_id = -1` (failed) są teraz automatycznie ponawiane co 1h zamiast trwale pomijane; priorytet: najpierw nowe zamówienia (NULL), potem retry (-1) -- **NEW**: `cron.php` — powiadomienie mailowe na `biuro@project-pro.pl` przy błędzie cURL wysyłania zamówienia do Apilo -- **NEW**: `cron.php` — powiadomienie mailowe o trwale nieudanych zadaniach Apilo (po wyczerpaniu `max_attempts`) +- **FIX**: `cron.php` — dodano brakujÄ…ce `$apiloRepository` do klauzul `use()` w 5 handlerach cron (APILO_TOKEN_KEEPALIVE, APILO_SEND_ORDER, APILO_PRODUCT_SYNC, APILO_PRICELIST_SYNC, APILO_STATUS_POLL); regresja z ver. 0.339 (split IntegrationsRepository → ApiloRepository) powodowaĹ‚a `Call to a member function apiloGetAccessToken() on null` +- **FIX**: `cron.php` — zamĂłwienia z `apilo_order_id = -1` (failed) sÄ… teraz automatycznie ponawiane co 1h zamiast trwale pomijane; priorytet: najpierw nowe zamĂłwienia (NULL), potem retry (-1) +- **NEW**: `cron.php` — powiadomienie mailowe na `biuro@project-pro.pl` przy błędzie cURL wysyĹ‚ania zamĂłwienia do Apilo +- **NEW**: `cron.php` — powiadomienie mailowe o trwale nieudanych zadaniach Apilo (po wyczerpaniu `max_attempts`) --- -## ver. 0.340 (2026-03-15) - Bugfix: crash przy składaniu zamówienia z kuponem rabatowym +## ver. 0.340 (2026-03-15) - Bugfix: crash przy skĹ‚adaniu zamĂłwienia z kuponem rabatowym -- **FIX**: `autoload/Domain/Order/OrderRepository.php:793` — naprawiono Fatal Error `Call to undefined method stdClass::is_one_time()` przy składaniu zamówienia z kodem rabatowym; zamieniono wywołania nieistniejących metod na stdClass (`is_one_time()`, `set_as_used()`) na dostęp do właściwości + istniejącą metodę `CouponRepository::markAsUsed()` -- **SONARQUBE**: Pierwszy skan SonarQube — wyniki zapisane w `docs/TODO.md` (4 bugi, 31 critical code smells, 10 major, 8 minor) +- **FIX**: `autoload/Domain/Order/OrderRepository.php:793` — naprawiono Fatal Error `Call to undefined method stdClass::is_one_time()` przy skĹ‚adaniu zamĂłwienia z kodem rabatowym; zamieniono wywoĹ‚ania nieistniejÄ…cych metod na stdClass (`is_one_time()`, `set_as_used()`) na dostÄ™p do wĹ‚aĹ›ciwoĹ›ci + istniejÄ…cÄ… metodÄ™ `CouponRepository::markAsUsed()` +- **SONARQUBE**: Pierwszy skan SonarQube — wyniki zapisane w `docs/TODO.md` (4 bugi, 31 critical code smells, 10 major, 8 minor) --- ## ver. 0.339 (2026-03-12) - Refactoring: wydzielenie ApiloRepository z IntegrationsRepository -- **REFACTOR**: `autoload/Domain/Integrations/ApiloRepository.php` — nowa klasa `\Domain\Integrations\ApiloRepository` z 19 metodami apilo* (sync produktów, zamówień, konfiguracja) wydzielonymi z `IntegrationsRepository` -- **REFACTOR**: `autoload/Domain/Integrations/IntegrationsRepository.php` — usunięto 19 metod apilo* (~540 linii); klasa zmniejszona z ~875 do ~340 linii, zawiera wyłącznie generyczną logikę integracji (settings, logi, product linking) -- **REFACTOR**: `autoload/admin/Controllers/IntegrationsController.php` — konsumuje `ApiloRepository` przez DI zamiast `IntegrationsRepository` dla operacji apilo -- **REFACTOR**: `autoload/Domain/Order/OrderAdminService.php` — używa `ApiloRepository` do wysyłki zamówień do Apilo -- **REFACTOR**: `cron.php` — używa `ApiloRepository` do synchronizacji cron -- **REFACTOR**: `autoload/admin/App.php` — wiring DI dla `ApiloRepository` -- **TEST**: `tests/Unit/Domain/Integrations/ApiloRepositoryTest.php` — nowe testy dla `ApiloRepository`; suite: 818 testów, 2275 asercji +- **REFACTOR**: `autoload/Domain/Integrations/ApiloRepository.php` — nowa klasa `\Domain\Integrations\ApiloRepository` z 19 metodami apilo* (sync produktĂłw, zamĂłwieĹ„, konfiguracja) wydzielonymi z `IntegrationsRepository` +- **REFACTOR**: `autoload/Domain/Integrations/IntegrationsRepository.php` — usuniÄ™to 19 metod apilo* (~540 linii); klasa zmniejszona z ~875 do ~340 linii, zawiera wyłącznie generycznÄ… logikÄ™ integracji (settings, logi, product linking) +- **REFACTOR**: `autoload/admin/Controllers/IntegrationsController.php` — konsumuje `ApiloRepository` przez DI zamiast `IntegrationsRepository` dla operacji apilo +- **REFACTOR**: `autoload/Domain/Order/OrderAdminService.php` — uĹĽywa `ApiloRepository` do wysyĹ‚ki zamĂłwieĹ„ do Apilo +- **REFACTOR**: `cron.php` — uĹĽywa `ApiloRepository` do synchronizacji cron +- **REFACTOR**: `autoload/admin/App.php` — wiring DI dla `ApiloRepository` +- **TEST**: `tests/Unit/Domain/Integrations/ApiloRepositoryTest.php` — nowe testy dla `ApiloRepository`; suite: 818 testĂłw, 2275 asercji --- -## ver. 0.338 (2026-03-12) - Bugfix: duplikaty zamówień + status COD +## ver. 0.338 (2026-03-12) - Bugfix: duplikaty zamĂłwieĹ„ + status COD -- **FIX**: `autoload/front/Controllers/ShopBasketController::summaryView()` — guard przed ponownym złożeniem zamówienia: jeśli sesja zawiera `ORDER_SUBMIT_LAST_ORDER_ID`, użytkownik jest przekierowywany do istniejącego zamówienia zamiast widzieć formularz ponownie -- **FIX**: `autoload/front/Controllers/ShopBasketController::basketSave()` — owinięcie wywołania `createFromBasket()` w try-catch; wyjątek jest logowany przez `error_log()`, użytkownik widzi komunikat błędu, koszyk sesyjny zostaje zachowany -- **FIX**: `autoload/Domain/Order/OrderRepository::createFromBasket()` — usunięcie hardkodowanego `payment_id == 3` do wykrywania płatności przy odbiorze; zamiast tego używana jest flaga `$payment_method['is_cod']` -- **FEATURE**: `autoload/Domain/PaymentMethod/PaymentMethodRepository` — nowa kolumna `is_cod` (normalizacja, zapis w `save()`, kolumna w `forTransport()` SQL) -- **FEATURE**: `autoload/admin/Controllers/ShopPaymentMethodController` — nowe pole "Platnosc przy odbiorze" w formularzu edycji metody płatności -- **MIGRATION**: `migrations/0.338.sql` — `ALTER TABLE pp_shop_payment_methods ADD COLUMN is_cod TINYINT(1) NOT NULL DEFAULT 0` +- **FIX**: `autoload/front/Controllers/ShopBasketController::summaryView()` — guard przed ponownym zĹ‚oĹĽeniem zamĂłwienia: jeĹ›li sesja zawiera `ORDER_SUBMIT_LAST_ORDER_ID`, uĹĽytkownik jest przekierowywany do istniejÄ…cego zamĂłwienia zamiast widzieć formularz ponownie +- **FIX**: `autoload/front/Controllers/ShopBasketController::basketSave()` — owiniÄ™cie wywoĹ‚ania `createFromBasket()` w try-catch; wyjÄ…tek jest logowany przez `error_log()`, uĹĽytkownik widzi komunikat błędu, koszyk sesyjny zostaje zachowany +- **FIX**: `autoload/Domain/Order/OrderRepository::createFromBasket()` — usuniÄ™cie hardkodowanego `payment_id == 3` do wykrywania pĹ‚atnoĹ›ci przy odbiorze; zamiast tego uĹĽywana jest flaga `$payment_method['is_cod']` +- **FEATURE**: `autoload/Domain/PaymentMethod/PaymentMethodRepository` — nowa kolumna `is_cod` (normalizacja, zapis w `save()`, kolumna w `forTransport()` SQL) +- **FEATURE**: `autoload/admin/Controllers/ShopPaymentMethodController` — nowe pole "Platnosc przy odbiorze" w formularzu edycji metody pĹ‚atnoĹ›ci +- **MIGRATION**: `migrations/0.338.sql` — `ALTER TABLE pp_shop_payment_methods ADD COLUMN is_cod TINYINT(1) NOT NULL DEFAULT 0` --- -## ver. 0.337 (2026-03-12) - Bezpieczeństwo: ochrona CSRF panelu administracyjnego +## ver. 0.337 (2026-03-12) - BezpieczeĹ„stwo: ochrona CSRF panelu administracyjnego -- **SECURITY**: `autoload/Shared/Security/CsrfToken.php` — nowa klasa z `getToken()`, `validate()`, `regenerate()` (token 64-znakowy hex, `hash_equals()` przeciw timing attacks) -- **SECURITY**: `admin/templates/components/form-edit.php` — dodano ukryte pole `_csrf_token` we wszystkich formularzach edycji -- **SECURITY**: `autoload/admin/Support/Forms/FormRequestHandler::handleSubmit()` — walidacja CSRF przed przetworzeniem danych formularza -- **SECURITY**: `admin/templates/site/unlogged-layout.php` — token CSRF w formularzu logowania + fix XSS na komunikacie alertu (`htmlspecialchars`) -- **SECURITY**: `admin/templates/users/user-2fa.php` — token CSRF w obu formularzach 2FA (weryfikacja i resend) -- **SECURITY**: `autoload/admin/App::special_actions()` — walidacja CSRF dla żądań POST; regeneracja tokenu po udanym logowaniu (obie ścieżki: bezpośrednia i przez 2FA) -- **TEST**: `tests/Unit/Shared/Security/CsrfTokenTest.php` — 7 nowych testów; suite: 817 testów, 2271 asercji +- **SECURITY**: `autoload/Shared/Security/CsrfToken.php` — nowa klasa z `getToken()`, `validate()`, `regenerate()` (token 64-znakowy hex, `hash_equals()` przeciw timing attacks) +- **SECURITY**: `admin/templates/components/form-edit.php` — dodano ukryte pole `_csrf_token` we wszystkich formularzach edycji +- **SECURITY**: `autoload/admin/Support/Forms/FormRequestHandler::handleSubmit()` — walidacja CSRF przed przetworzeniem danych formularza +- **SECURITY**: `admin/templates/site/unlogged-layout.php` — token CSRF w formularzu logowania + fix XSS na komunikacie alertu (`htmlspecialchars`) +- **SECURITY**: `admin/templates/users/user-2fa.php` — token CSRF w obu formularzach 2FA (weryfikacja i resend) +- **SECURITY**: `autoload/admin/App::special_actions()` — walidacja CSRF dla ĹĽÄ…daĹ„ POST; regeneracja tokenu po udanym logowaniu (obie Ĺ›cieĹĽki: bezpoĹ›rednia i przez 2FA) +- **TEST**: `tests/Unit/Shared/Security/CsrfTokenTest.php` — 7 nowych testĂłw; suite: 817 testĂłw, 2271 asercji --- -## ver. 0.336 (2026-03-12) - Poprawki bezpieczeństwa: error handling w krytycznych ścieżkach +## ver. 0.336 (2026-03-12) - Poprawki bezpieczeĹ„stwa: error handling w krytycznych Ĺ›cieĹĽkach -- **FIX**: `cron.php` — przywrócono `E_WARNING` i `E_DEPRECATED` (wyciszano je od zawsze, ukrywając potencjalne błędy) -- **FIX**: `IntegrationsRepository::apiloAuthorize()` — try-catch po zapisie tokenów Apilo; błąd DB logowany i zwraca `false` zamiast cicho kontynuować -- **FIX**: `ProductRepository::safeUnlink()` — `error_log()` gdy ścieżka istnieje ale jest poza `upload/` -- **FIX**: `ArticleRepository::safeUnlink()` — to samo +- **FIX**: `cron.php` — przywrĂłcono `E_WARNING` i `E_DEPRECATED` (wyciszano je od zawsze, ukrywajÄ…c potencjalne błędy) +- **FIX**: `IntegrationsRepository::apiloAuthorize()` — try-catch po zapisie tokenĂłw Apilo; błąd DB logowany i zwraca `false` zamiast cicho kontynuować +- **FIX**: `ProductRepository::safeUnlink()` — `error_log()` gdy Ĺ›cieĹĽka istnieje ale jest poza `upload/` +- **FIX**: `ArticleRepository::safeUnlink()` — to samo --- -## ver. 0.335 (2026-03-12) - Poprawki bezpieczeństwa: path traversal i XSS w szablonach +## ver. 0.335 (2026-03-12) - Poprawki bezpieczeĹ„stwa: path traversal i XSS w szablonach -- **SECURITY**: `ProductRepository` — dodano `safeUnlink()` z walidacją `realpath()` zapobiegającą path traversal; użyta w `cleanupDeletedFiles()`, `cleanupDeletedImages()`, `deleteNonassignedImages()` -- **SECURITY**: `ArticleRepository` — to samo; użyta w `deleteMarkedImages()`, `deleteMarkedFiles()`, `deleteNonassignedFiles()`, `deleteNonassignedImages()` -- **SECURITY**: `templates/articles/article-full.php` — `htmlspecialchars()` na tytule artykułu, `$_SERVER['SERVER_NAME']` i `$url` w linkach social media -- **SECURITY**: `templates/articles/article-entry.php` — `htmlspecialchars()` na tytule i `$url` (3 miejsca: href, title, alt) +- **SECURITY**: `ProductRepository` — dodano `safeUnlink()` z walidacjÄ… `realpath()` zapobiegajÄ…cÄ… path traversal; uĹĽyta w `cleanupDeletedFiles()`, `cleanupDeletedImages()`, `deleteNonassignedImages()` +- **SECURITY**: `ArticleRepository` — to samo; uĹĽyta w `deleteMarkedImages()`, `deleteMarkedFiles()`, `deleteNonassignedFiles()`, `deleteNonassignedImages()` +- **SECURITY**: `templates/articles/article-full.php` — `htmlspecialchars()` na tytule artykuĹ‚u, `$_SERVER['SERVER_NAME']` i `$url` w linkach social media +- **SECURITY**: `templates/articles/article-entry.php` — `htmlspecialchars()` na tytule i `$url` (3 miejsca: href, title, alt) --- -## ver. 0.334 (2026-03-12) - Poprawki bezpieczeństwa: debug log, SQL, RedBeanPHP +## ver. 0.334 (2026-03-12) - Poprawki bezpieczeĹ„stwa: debug log, SQL, RedBeanPHP -- **SECURITY**: `ShopOrderController::paymentStatusTpay()` — usunięto `file_put_contents('tpay.txt', ...)` który logował pełne dane POST/GET płatności do publicznego pliku -- **SECURITY**: `ShopOrderController` — hardcoded sekret HotPay `"ProjectPro1916;"` przeniesiony do prywatnej stałej `HOTPAY_HASH_SEED` -- **SECURITY**: `IntegrationsRepository::getSettings()` — zastąpiono raw `query("SELECT * FROM $table")` metodą Medoo `select()` (spójne z zasadą braku string concatenation w SQL) -- **REFACTOR**: `index.php`, `admin/index.php` — usunięto RedBeanPHP (`rb.php`): biblioteka była ładowana i inicjalizowana, ale nigdy nie używana w żadnym zapytaniu -- **CLEANUP**: `libraries/rb.php` — usunięto plik (536 KB zbędnych zależności) -- **TESTS**: `IntegrationsRepositoryTest` — zaktualizowano 6 testów do nowego API (`select` zamiast `query` dla `getSettings`) +- **SECURITY**: `ShopOrderController::paymentStatusTpay()` — usuniÄ™to `file_put_contents('tpay.txt', ...)` ktĂłry logowaĹ‚ peĹ‚ne dane POST/GET pĹ‚atnoĹ›ci do publicznego pliku +- **SECURITY**: `ShopOrderController` — hardcoded sekret HotPay `"ProjectPro1916;"` przeniesiony do prywatnej staĹ‚ej `HOTPAY_HASH_SEED` +- **SECURITY**: `IntegrationsRepository::getSettings()` — zastÄ…piono raw `query("SELECT * FROM $table")` metodÄ… Medoo `select()` (spĂłjne z zasadÄ… braku string concatenation w SQL) +- **REFACTOR**: `index.php`, `admin/index.php` — usuniÄ™to RedBeanPHP (`rb.php`): biblioteka byĹ‚a Ĺ‚adowana i inicjalizowana, ale nigdy nie uĹĽywana w ĹĽadnym zapytaniu +- **CLEANUP**: `libraries/rb.php` — usuniÄ™to plik (536 KB zbÄ™dnych zaleĹĽnoĹ›ci) +- **TESTS**: `IntegrationsRepositoryTest` — zaktualizowano 6 testĂłw do nowego API (`select` zamiast `query` dla `getSettings`) --- -## ver. 0.333 (2026-03-10) - Ochrona przed podwójnym składaniem zamówienia (order submit token) +## ver. 0.333 (2026-03-10) - Ochrona przed podwĂłjnym skĹ‚adaniem zamĂłwienia (order submit token) -- **NEW**: `ShopBasketController` — mechanizm tokenu CSRF chroniący przed podwójnym składaniem zamówienia (generowanie, walidacja, konsumpcja tokenu w sesji) -- **NEW**: `ShopBasketController::basketSave()` — przy duplikacie przekierowanie do istniejącego zamówienia zamiast tworzenia kolejnego -- **FIX**: `templates/shop-basket/summary-view.php` — JS nasłuchuje na `submit` formularza zamiast `click` przycisku (poprawna obsługa walidacji HTML5) -- **FIX**: `templates/shop-basket/address-form.php` — ukryte pole `order_submit_token` z escape XSS -- **TESTS**: `ShopBasketControllerTest` — testy konstruktora i zależności (5 testów) +- **NEW**: `ShopBasketController` — mechanizm tokenu CSRF chroniÄ…cy przed podwĂłjnym skĹ‚adaniem zamĂłwienia (generowanie, walidacja, konsumpcja tokenu w sesji) +- **NEW**: `ShopBasketController::basketSave()` — przy duplikacie przekierowanie do istniejÄ…cego zamĂłwienia zamiast tworzenia kolejnego +- **FIX**: `templates/shop-basket/summary-view.php` — JS nasĹ‚uchuje na `submit` formularza zamiast `click` przycisku (poprawna obsĹ‚uga walidacji HTML5) +- **FIX**: `templates/shop-basket/address-form.php` — ukryte pole `order_submit_token` z escape XSS +- **TESTS**: `ShopBasketControllerTest` — testy konstruktora i zaleĹĽnoĹ›ci (5 testĂłw) --- -## ver. 0.332 (2026-03-01) - API produktów: nowe pola new_to_date i additional_message +## ver. 0.332 (2026-03-01) - API produktĂłw: nowe pola new_to_date i additional_message -- **NEW**: `ProductRepository::getProductForApi()` — eksportuje 4 nowe pola: `new_to_date`, `additional_message` (int 0/1), `additional_message_required` (int 0/1), `additional_message_text` -- **NEW**: `ProductsApiController` — obsługa nowych pól w PUT/PATCH (aktualizacja `new_to_date`, `additional_message`, `additional_message_required`, `additional_message_text`) -- **DOCS**: `docs/API.md` — zaktualizowane przykłady GET/PUT dla nowych pól produktu +- **NEW**: `ProductRepository::getProductForApi()` — eksportuje 4 nowe pola: `new_to_date`, `additional_message` (int 0/1), `additional_message_required` (int 0/1), `additional_message_text` +- **NEW**: `ProductsApiController` — obsĹ‚uga nowych pĂłl w PUT/PATCH (aktualizacja `new_to_date`, `additional_message`, `additional_message_required`, `additional_message_text`) +- **DOCS**: `docs/API.md` — zaktualizowane przykĹ‚ady GET/PUT dla nowych pĂłl produktu --- -## ver. 0.331 (2026-03-01) - Bugfix: strona produktu używała layoutu kategorii zamiast domyślnego +## ver. 0.331 (2026-03-01) - Bugfix: strona produktu uĹĽywaĹ‚a layoutu kategorii zamiast domyĹ›lnego -- **FIX**: `LayoutsRepository::getProductLayout()` — fallback gdy produkt i jego kategorie nie mają przypisanego layoutu zmieniany z `categories_default = 1` na `status = 1`; wcześniej produkty bez layoutu pobierały szablon "Podstrony - kategorie" zamiast właściwego domyślnego +- **FIX**: `LayoutsRepository::getProductLayout()` — fallback gdy produkt i jego kategorie nie majÄ… przypisanego layoutu zmieniany z `categories_default = 1` na `status = 1`; wczeĹ›niej produkty bez layoutu pobieraĹ‚y szablon "Podstrony - kategorie" zamiast wĹ‚aĹ›ciwego domyĹ›lnego --- -## ver. 0.330 (2026-02-27) - Eliminacja htaccess.conf — wszystkie trasy URL w pp_routes +## ver. 0.330 (2026-02-27) - Eliminacja htaccess.conf — wszystkie trasy URL w pp_routes -- **REFACTOR**: `Helpers::htacces()` — generowanie `.htaccess` w całości z PHP (usunięty `file_get_contents('htaccess.conf')` i placeholder `{HTACCESS_CACHE}`) -- **NEW**: 32 statyczne trasy systemowe wstawiane do `pp_routes` z `type='system'` przy każdym `htacces()` (koszyk, logowanie, wylogowanie, panel klienta, newsletter, zamówienia, płatności, moduły AJAX: shopBasket/shopClient/shopProduct/shopCoupon/search) -- **NEW**: Dynamiczne trasy językowe i producentów (producenci + per-producent z paginacją) przenoszone do `pp_routes` zamiast `.htaccess` -- **NEW**: Kolumna `type VARCHAR(20) NULL` w `pp_routes` — `NULL` dla encji, `'system'` dla tras systemowych -- **REMOVED**: `libraries/htaccess.conf` — plik szablonu usunięty, treść wbudowana w PHP -- **PERF**: Invalidacja cache Redis `pp_routes:all` po każdym `htacces()` — świeże trasy przy kolejnym żądaniu +- **REFACTOR**: `Helpers::htacces()` — generowanie `.htaccess` w caĹ‚oĹ›ci z PHP (usuniÄ™ty `file_get_contents('htaccess.conf')` i placeholder `{HTACCESS_CACHE}`) +- **NEW**: 32 statyczne trasy systemowe wstawiane do `pp_routes` z `type='system'` przy kaĹĽdym `htacces()` (koszyk, logowanie, wylogowanie, panel klienta, newsletter, zamĂłwienia, pĹ‚atnoĹ›ci, moduĹ‚y AJAX: shopBasket/shopClient/shopProduct/shopCoupon/search) +- **NEW**: Dynamiczne trasy jÄ™zykowe i producentĂłw (producenci + per-producent z paginacjÄ…) przenoszone do `pp_routes` zamiast `.htaccess` +- **NEW**: Kolumna `type VARCHAR(20) NULL` w `pp_routes` — `NULL` dla encji, `'system'` dla tras systemowych +- **REMOVED**: `libraries/htaccess.conf` — plik szablonu usuniÄ™ty, treść wbudowana w PHP +- **PERF**: Invalidacja cache Redis `pp_routes:all` po kaĹĽdym `htacces()` — Ĺ›wieĹĽe trasy przy kolejnym ĹĽÄ…daniu - **MIGRATION**: `migrations/0.329.sql` (dodano `type` column) -- **DOCS**: `docs/DATABASE_STRUCTURE.md` — zaktualizowana sekcja `pp_routes` o kolumnę `type` +- **DOCS**: `docs/DATABASE_STRUCTURE.md` — zaktualizowana sekcja `pp_routes` o kolumnÄ™ `type` --- -## ver. 0.329 (2026-02-27) - Routing kategorii, stron i artykułów przez pp_routes +## ver. 0.329 (2026-02-27) - Routing kategorii, stron i artykułów przez pp_routes -- **REFACTOR**: `index.php` — blok routingu przez `pp_routes` przeniesiony PRZED `checkUrlParams()` (poprawna kolejność: lang/a=page dostępne w checkUrlParams) -- **PERF**: Cache Redis dla tras (`pp_routes:all`, TTL 86400s) w `index.php` — jeden SELECT na 24h zamiast przy każdym żądaniu -- **NEW**: Kategorie, strony i artykuły zapisywane do `pp_routes` zamiast `.htaccess` w `Helpers::htacces()` -- **NEW**: `CategoryRepository::categoryDelete()` — usuwa powiązane `pp_routes` przed odświeżeniem -- **NEW**: `PagesRepository::pageDelete()` — usuwa powiązane `pp_routes` -- **NEW**: `ArticleRepository::archive()` i `deletePermanently()` — usuwa powiązane `pp_routes` -- **MIGRATION**: `migrations/0.329.sql` — `ALTER TABLE pp_routes ADD COLUMN category_id, page_id, article_id` +- **REFACTOR**: `index.php` — blok routingu przez `pp_routes` przeniesiony PRZED `checkUrlParams()` (poprawna kolejność: lang/a=page dostÄ™pne w checkUrlParams) +- **PERF**: Cache Redis dla tras (`pp_routes:all`, TTL 86400s) w `index.php` — jeden SELECT na 24h zamiast przy kaĹĽdym ĹĽÄ…daniu +- **NEW**: Kategorie, strony i artykuĹ‚y zapisywane do `pp_routes` zamiast `.htaccess` w `Helpers::htacces()` +- **NEW**: `CategoryRepository::categoryDelete()` — usuwa powiÄ…zane `pp_routes` przed odĹ›wieĹĽeniem +- **NEW**: `PagesRepository::pageDelete()` — usuwa powiÄ…zane `pp_routes` +- **NEW**: `ArticleRepository::archive()` i `deletePermanently()` — usuwa powiÄ…zane `pp_routes` +- **MIGRATION**: `migrations/0.329.sql` — `ALTER TABLE pp_routes ADD COLUMN category_id, page_id, article_id` - **TESTS**: Zaktualizowane `CategoryRepositoryTest` i `ArticleRepositoryTest` (nowe asercje na `pp_routes` delete) --- -## ver. 0.328 (2026-02-27) - Ikona kopiowania wartości atrybutów w szczegółach zamówienia +## ver. 0.328 (2026-02-27) - Ikona kopiowania wartoĹ›ci atrybutĂłw w szczegółach zamĂłwienia -- **NEW**: `order-details-custom-script.php` — JS parsuje `.atributes` div i wstrzykuje przycisk `fa-copy` przy każdej wartości atrybutu -- **UX**: Kliknięcie kopiuje wartość do schowka (Clipboard API + fallback execCommand), ikona zmienia się na `fa-check` z zielonym tłem przez 1,5s +- **NEW**: `order-details-custom-script.php` — JS parsuje `.atributes` div i wstrzykuje przycisk `fa-copy` przy kaĹĽdej wartoĹ›ci atrybutu +- **UX**: KlikniÄ™cie kopiuje wartość do schowka (Clipboard API + fallback execCommand), ikona zmienia siÄ™ na `fa-check` z zielonym tĹ‚em przez 1,5s --- -## ver. 0.327 (2026-02-27) - Masowe usuwanie w archiwum produktów +## ver. 0.327 (2026-02-27) - Masowe usuwanie w archiwum produktĂłw -- **NEW**: `ProductArchiveController::bulk_delete_permanent()` — endpoint POST `product_archive/bulk_delete_permanent/`, przyjmuje `ids[]`, usuwa każdy produkt przez `ProductRepository::delete()`, zwraca JSON `{success, deleted, errors[]}` -- **UX**: Kolumna checkboxów w liście archiwum produktów + pasek akcji masowych z licznikiem zaznaczonych -- **UX**: "Zaznacz wszystkie" w nagłówku tabeli (wstrzyknięty via JS), dialog potwierdzenia przed masowym usunięciem -- **TEST**: 2 nowe testy w `ProductArchiveControllerTest` — weryfikacja istnienia i sygnatury `bulk_delete_permanent` +- **NEW**: `ProductArchiveController::bulk_delete_permanent()` — endpoint POST `product_archive/bulk_delete_permanent/`, przyjmuje `ids[]`, usuwa kaĹĽdy produkt przez `ProductRepository::delete()`, zwraca JSON `{success, deleted, errors[]}` +- **UX**: Kolumna checkboxĂłw w liĹ›cie archiwum produktĂłw + pasek akcji masowych z licznikiem zaznaczonych +- **UX**: "Zaznacz wszystkie" w nagłówku tabeli (wstrzykniÄ™ty via JS), dialog potwierdzenia przed masowym usuniÄ™ciem +- **TEST**: 2 nowe testy w `ProductArchiveControllerTest` — weryfikacja istnienia i sygnatury `bulk_delete_permanent` --- ## ver. 0.326 (2026-02-27) - API: endpoint categories/list -- **NEW**: `api\Controllers\CategoriesApiController` — nowy kontroler API z akcją `list` -- **NEW**: Endpoint `GET api.php?endpoint=categories&action=list` — zwraca płaską listę aktywnych kategorii (id, parent_id, title) w domyślnym języku sklepu -- **FIX**: Usunięto zbędny parametr w `CategoryRepository`, eliminacja N+1 queries w categories/list przez bulk-fetch tytułów +- **NEW**: `api\Controllers\CategoriesApiController` — nowy kontroler API z akcjÄ… `list` +- **NEW**: Endpoint `GET api.php?endpoint=categories&action=list` — zwraca pĹ‚askÄ… listÄ™ aktywnych kategorii (id, parent_id, title) w domyĹ›lnym jÄ™zyku sklepu +- **FIX**: UsuniÄ™to zbÄ™dny parametr w `CategoryRepository`, eliminacja N+1 queries w categories/list przez bulk-fetch tytułów --- -## ver. 0.325 (2026-02-27) - Fix changelog encoding + limit wyświetlania +## ver. 0.325 (2026-02-27) - Fix changelog encoding + limit wyĹ›wietlania -- **FIX**: `updates/changelog.php` — naprawione krzaczki (mojibake) w polskich znakach; dane odbudowane z plików manifest -- **NEW**: `updates/changelog-data.html` — czyste dane changelog oddzielone od logiki PHP -- **REFACTOR**: `updates/changelog.php` — konwersja ze statycznego HTML na skrypt PHP: `Content-Type: utf-8`, parsowanie wpisów, filtrowanie po wersji -- **NEW**: Parametr `?ver=X.XXX` — ogranicza changelog do 5 wersji wstecz od wersji instancji -- **UPDATE**: `admin/templates/update/main-view.php` — przekazuje `?ver=` do URL changelog -- **UPDATE**: `build-update.ps1` — nowe wpisy dopisywane do `changelog-data.html` zamiast `changelog.php` +- **FIX**: `updates/changelog.php` — naprawione krzaczki (mojibake) w polskich znakach; dane odbudowane z plikĂłw manifest +- **NEW**: `updates/changelog-data.html` — czyste dane changelog oddzielone od logiki PHP +- **REFACTOR**: `updates/changelog.php` — konwersja ze statycznego HTML na skrypt PHP: `Content-Type: utf-8`, parsowanie wpisĂłw, filtrowanie po wersji +- **NEW**: Parametr `?ver=X.XXX` — ogranicza changelog do 5 wersji wstecz od wersji instancji +- **UPDATE**: `admin/templates/update/main-view.php` — przekazuje `?ver=` do URL changelog +- **UPDATE**: `build-update.ps1` — nowe wpisy dopisywane do `changelog-data.html` zamiast `changelog.php` --- -## ver. 0.324 (2026-02-27) - System kolejki zadań cron +## ver. 0.324 (2026-02-27) - System kolejki zadaĹ„ cron -- **NEW**: `Domain\CronJob\CronJobType` — stałe typów zadań, priorytetów, statusów, exponential backoff -- **NEW**: `Domain\CronJob\CronJobRepository` — CRUD na `pp_cron_jobs` + `pp_cron_schedules` (enqueue, fetchNext, markCompleted, markFailed, hasPendingJob, cleanup, recoverStuck, getDueSchedules, touchSchedule) -- **NEW**: `Domain\CronJob\CronJobProcessor` — orkiestracja: rejestracja handlerów, tworzenie scheduled jobs, przetwarzanie kolejki z priorytetami i retry/backoff -- **NEW**: Tabele `pp_cron_jobs` i `pp_cron_schedules` — kolejka zadań z priorytetami, exponential backoff, harmonogram cykliczny -- **REFACTOR**: `cron.php` — zastąpienie monolitycznego ~550 linii orkiestratorem z CronJobProcessor i zarejestrowanymi handlerami -- **REFACTOR**: `OrderAdminService::queueApiloSync()` — kolejkowanie przez `CronJobRepository::enqueue()` zamiast pliku JSON -- **REFACTOR**: `OrderAdminService::syncApiloPayment()`, `syncApiloStatus()` — zmiana z private na public (używane przez handlery cron) -- **REMOVED**: `OrderAdminService::processApiloSyncQueue()`, `loadApiloSyncQueue()`, `saveApiloSyncQueue()`, `apiloSyncQueuePath()`, stała `APILO_SYNC_QUEUE_FILE` -- **NEW**: Jednorazowa migracja JSON queue → DB w cron.php (automatyczna przy pierwszym uruchomieniu) -- **SECURITY**: `cron.php` — ochrona endpointu: wymaga `$config['cron_key']` w URL (`?key=...`) lub trybu CLI -- **FIX**: `CronJobRepository::fetchNext()` — re-SELECT po UPDATE eliminuje race condition przy równoległych workerach -- **FIX**: `cron.php` — null check dla `$mdb->query()` przed `->fetch()` / `->fetchAll()` (3 miejsca) -- **FIX**: `cron.php` — walidacja odpowiedzi curl w APILO_PRODUCT_SYNC i APILO_PRICELIST_SYNC (zapobiega zapisaniu null do bazy) -- **FIX**: DI wiring — `CronJobRepository` przekazywany do `OrderAdminService` we wszystkich 4 punktach: `admin\App`, `api\ApiRouter`, `front\App`, `cron.php` -- **TESTS**: 41 nowych testów CronJob (CronJobTypeTest, CronJobRepositoryTest, CronJobProcessorTest) +- **NEW**: `Domain\CronJob\CronJobType` — staĹ‚e typĂłw zadaĹ„, priorytetĂłw, statusĂłw, exponential backoff +- **NEW**: `Domain\CronJob\CronJobRepository` — CRUD na `pp_cron_jobs` + `pp_cron_schedules` (enqueue, fetchNext, markCompleted, markFailed, hasPendingJob, cleanup, recoverStuck, getDueSchedules, touchSchedule) +- **NEW**: `Domain\CronJob\CronJobProcessor` — orkiestracja: rejestracja handlerĂłw, tworzenie scheduled jobs, przetwarzanie kolejki z priorytetami i retry/backoff +- **NEW**: Tabele `pp_cron_jobs` i `pp_cron_schedules` — kolejka zadaĹ„ z priorytetami, exponential backoff, harmonogram cykliczny +- **REFACTOR**: `cron.php` — zastÄ…pienie monolitycznego ~550 linii orkiestratorem z CronJobProcessor i zarejestrowanymi handlerami +- **REFACTOR**: `OrderAdminService::queueApiloSync()` — kolejkowanie przez `CronJobRepository::enqueue()` zamiast pliku JSON +- **REFACTOR**: `OrderAdminService::syncApiloPayment()`, `syncApiloStatus()` — zmiana z private na public (uĹĽywane przez handlery cron) +- **REMOVED**: `OrderAdminService::processApiloSyncQueue()`, `loadApiloSyncQueue()`, `saveApiloSyncQueue()`, `apiloSyncQueuePath()`, staĹ‚a `APILO_SYNC_QUEUE_FILE` +- **NEW**: Jednorazowa migracja JSON queue → DB w cron.php (automatyczna przy pierwszym uruchomieniu) +- **SECURITY**: `cron.php` — ochrona endpointu: wymaga `$config['cron_key']` w URL (`?key=...`) lub trybu CLI +- **FIX**: `CronJobRepository::fetchNext()` — re-SELECT po UPDATE eliminuje race condition przy rĂłwnolegĹ‚ych workerach +- **FIX**: `cron.php` — null check dla `$mdb->query()` przed `->fetch()` / `->fetchAll()` (3 miejsca) +- **FIX**: `cron.php` — walidacja odpowiedzi curl w APILO_PRODUCT_SYNC i APILO_PRICELIST_SYNC (zapobiega zapisaniu null do bazy) +- **FIX**: DI wiring — `CronJobRepository` przekazywany do `OrderAdminService` we wszystkich 4 punktach: `admin\App`, `api\ApiRouter`, `front\App`, `cron.php` +- **TESTS**: 41 nowych testĂłw CronJob (CronJobTypeTest, CronJobRepositoryTest, CronJobProcessorTest) - **MIGRATION**: `migrations/0.324.sql` --- -## ver. 0.323 (2026-02-24) - Import zdjęć, trwałe usuwanie, fix API upload +## ver. 0.323 (2026-02-24) - Import zdjęć, trwaĹ‚e usuwanie, fix API upload -- **FIX**: `IntegrationsRepository::shopproImportProduct()` — kompletny refactor importu zdjęć: walidacja HTTP response, curl timeouty, bezpieczna budowa URL, szczegółowy log do `logs/shoppro-import-debug.log` i `error_log`, czytelny komunikat z wynikiem -- **FIX**: `ProductRepository::saveProduct()` — `saveCustomFields()` wywoływane tylko gdy klucz `custom_field_name` istnieje w danych (partial update przez API nie czyści custom fields) -- **FIX**: `ProductRepository::delete()` — usuwanie rekordów z `pp_shop_products_custom_fields` przy kasowaniu produktu -- **FIX**: `ProductsApiController::upload_image()` — poprawka ścieżki uploadu (`upload/` zamiast `../upload/` — api.php działa z rootu projektu) -- **NEW**: `ProductArchiveController::delete_permanent()` — trwałe usunięcie produktu z archiwum (wraz ze zdjęciami i załącznikami) -- **NEW**: Przycisk "Usuń trwale" w liście produktów archiwalnych z potwierdzeniem +- **FIX**: `IntegrationsRepository::shopproImportProduct()` — kompletny refactor importu zdjęć: walidacja HTTP response, curl timeouty, bezpieczna budowa URL, szczegółowy log do `logs/shoppro-import-debug.log` i `error_log`, czytelny komunikat z wynikiem +- **FIX**: `ProductRepository::saveProduct()` — `saveCustomFields()` wywoĹ‚ywane tylko gdy klucz `custom_field_name` istnieje w danych (partial update przez API nie czyĹ›ci custom fields) +- **FIX**: `ProductRepository::delete()` — usuwanie rekordĂłw z `pp_shop_products_custom_fields` przy kasowaniu produktu +- **FIX**: `ProductsApiController::upload_image()` — poprawka Ĺ›cieĹĽki uploadu (`upload/` zamiast `../upload/` — api.php dziaĹ‚a z rootu projektu) +- **NEW**: `ProductArchiveController::delete_permanent()` — trwaĹ‚e usuniÄ™cie produktu z archiwum (wraz ze zdjÄ™ciami i załącznikami) +- **NEW**: Przycisk "UsuĹ„ trwale" w liĹ›cie produktĂłw archiwalnych z potwierdzeniem --- -## ver. 0.318 (2026-02-24) - ShopPRO export produktów + API endpoints +## ver. 0.318 (2026-02-24) - ShopPRO export produktĂłw + API endpoints -- **NEW**: `IntegrationsRepository::shopproExportProduct()` — eksport produktu do zdalnej instancji shopPRO: pola główne, tłumaczenia, custom fields, zdjęcia przez API (base64) -- **NEW**: `IntegrationsRepository::sendImageToShopproApi()` — wysyłka zdjęć do remote API shopPRO (endpoint `upload_image`) z base64 -- **REFACTOR**: `shopproImportProduct()` — wydzielono `shopproDb()` i `missingShopproSetting()` jako prywatne helpery; dodano import `security_information`, `producer_id`, custom fields i `alt` zdjęcia -- **NEW**: `AttributeRepository::ensureAttributeForApi()` i `ensureAttributeValueForApi()` — idempotent find-or-create dla atrybutów i ich wartości (integracje API) -- **NEW**: API endpoint `POST /api.php?endpoint=dictionaries&action=ensure_attribute` — utwórz lub znajdź atrybut po nazwie i typie -- **NEW**: API endpoint `POST /api.php?endpoint=dictionaries&action=ensure_attribute_value` — utwórz lub znajdź wartość atrybutu po nazwie -- **NEW**: API endpoint `POST /api.php?endpoint=products&action=upload_image` — przyjmuje zdjęcie produktu jako base64 JSON, zapisuje plik i rekord w `pp_shop_products_images` -- **NEW**: `IntegrationsController::shoppro_product_export()` — akcja admina eksportująca produkt do shopPRO -- **NEW**: Przycisk "Eksportuj do shopPRO" w liście produktów (widoczny gdy shopPRO enabled) +- **NEW**: `IntegrationsRepository::shopproExportProduct()` — eksport produktu do zdalnej instancji shopPRO: pola główne, tĹ‚umaczenia, custom fields, zdjÄ™cia przez API (base64) +- **NEW**: `IntegrationsRepository::sendImageToShopproApi()` — wysyĹ‚ka zdjęć do remote API shopPRO (endpoint `upload_image`) z base64 +- **REFACTOR**: `shopproImportProduct()` — wydzielono `shopproDb()` i `missingShopproSetting()` jako prywatne helpery; dodano import `security_information`, `producer_id`, custom fields i `alt` zdjÄ™cia +- **NEW**: `AttributeRepository::ensureAttributeForApi()` i `ensureAttributeValueForApi()` — idempotent find-or-create dla atrybutĂłw i ich wartoĹ›ci (integracje API) +- **NEW**: API endpoint `POST /api.php?endpoint=dictionaries&action=ensure_attribute` — utwĂłrz lub znajdĹş atrybut po nazwie i typie +- **NEW**: API endpoint `POST /api.php?endpoint=dictionaries&action=ensure_attribute_value` — utwĂłrz lub znajdĹş wartość atrybutu po nazwie +- **NEW**: API endpoint `POST /api.php?endpoint=products&action=upload_image` — przyjmuje zdjÄ™cie produktu jako base64 JSON, zapisuje plik i rekord w `pp_shop_products_images` +- **NEW**: `IntegrationsController::shoppro_product_export()` — akcja admina eksportujÄ…ca produkt do shopPRO +- **NEW**: Przycisk "Eksportuj do shopPRO" w liĹ›cie produktĂłw (widoczny gdy shopPRO enabled) - **NEW**: Pole "API key" w ustawieniach integracji shopPRO (`shoppro-settings.php`) --- ## ver. 0.317 (2026-02-23) - Klucz API: przycisk generowania + fix zapisu -- **FIX**: `SettingsRepository::saveSettings()` — pole `api_key` brakowało w whiteliście zapisywanych pól, przez co wartość była tracona przy każdym zapisie (TRUNCATE + insert) -- **NEW**: Pole "Klucz API" w ustawieniach — przycisk "Generuj" do losowego 32-znakowego klucza alfanumerycznego, usunięto "(ordersPRO)" z nazwy -- **FIX**: `api.php` — routing API przeniesiony przed ładowanie globalnych settings (wczesne wyjście), obsługa błędów przez `\Throwable` -- **FIX**: `ApiRouter` — catch `\Throwable` zamiast `\Exception` dla pełniejszego łapania błędów +- **FIX**: `SettingsRepository::saveSettings()` — pole `api_key` brakowaĹ‚o w whiteliĹ›cie zapisywanych pĂłl, przez co wartość byĹ‚a tracona przy kaĹĽdym zapisie (TRUNCATE + insert) +- **NEW**: Pole "Klucz API" w ustawieniach — przycisk "Generuj" do losowego 32-znakowego klucza alfanumerycznego, usuniÄ™to "(ordersPRO)" z nazwy +- **FIX**: `api.php` — routing API przeniesiony przed Ĺ‚adowanie globalnych settings (wczesne wyjĹ›cie), obsĹ‚uga błędĂłw przez `\Throwable` +- **FIX**: `ApiRouter` — catch `\Throwable` zamiast `\Exception` dla peĹ‚niejszego Ĺ‚apania błędĂłw --- -## ver. 0.316 (2026-02-23) - Migracja brakującej kolumny type w custom fields +## ver. 0.316 (2026-02-23) - Migracja brakujÄ…cej kolumny type w custom fields -- **FIX**: Dodanie brakującej kolumny `type` w tabeli `pp_shop_products_custom_fields` — kolumna była używana w kodzie od v0.277 ale nigdy nie miała migracji ALTER TABLE, przez co instancje ze starszą bazą dostawały `PDOException: Column not found: 1054 Unknown column 'type'` przy zapisie produktu +- **FIX**: Dodanie brakujÄ…cej kolumny `type` w tabeli `pp_shop_products_custom_fields` — kolumna byĹ‚a uĹĽywana w kodzie od v0.277 ale nigdy nie miaĹ‚a migracji ALTER TABLE, przez co instancje ze starszÄ… bazÄ… dostawaĹ‚y `PDOException: Column not found: 1054 Unknown column 'type'` przy zapisie produktu --- -## ver. 0.315 (2026-02-23) - Fix listowania atrybutów w admin +## ver. 0.315 (2026-02-23) - Fix listowania atrybutĂłw w admin -- **FIX**: `AttributeRepository::listForAdmin()` — zapytanie COUNT dostawało parametr `:default_lang_id` którego nie miało w SQL, powodując `PDOException: SQLSTATE[HY093]: Invalid parameter number`. Parametr potrzebny tylko w głównym SELECT, nie w COUNT +- **FIX**: `AttributeRepository::listForAdmin()` — zapytanie COUNT dostawaĹ‚o parametr `:default_lang_id` ktĂłrego nie miaĹ‚o w SQL, powodujÄ…c `PDOException: SQLSTATE[HY093]: Invalid parameter number`. Parametr potrzebny tylko w głównym SELECT, nie w COUNT --- -## ver. 0.314 (2026-02-23) - Fix wyszukiwarki admin + title zamówienia +## ver. 0.314 (2026-02-23) - Fix wyszukiwarki admin + title zamĂłwienia -- **FIX**: Globalna wyszukiwarka w panelu admina przestała zwracać wyniki — dodano `Content-Type: application/json` i `Cache-Control: no-store` (zapobiega cache'owaniu przez proxy/CDN), zmiana AJAX z GET na POST, `fetchAll(PDO::FETCH_ASSOC)`, top-level try/catch z gwarantowaną odpowiedzią JSON -- **NEW**: `document.title` w widoku szczegółów zamówienia pokazuje numer zamówienia (np. "Zamówienie ZAM/123 - shopPro") +- **FIX**: Globalna wyszukiwarka w panelu admina przestaĹ‚a zwracać wyniki — dodano `Content-Type: application/json` i `Cache-Control: no-store` (zapobiega cache'owaniu przez proxy/CDN), zmiana AJAX z GET na POST, `fetchAll(PDO::FETCH_ASSOC)`, top-level try/catch z gwarantowanÄ… odpowiedziÄ… JSON +- **NEW**: `document.title` w widoku szczegółów zamĂłwienia pokazuje numer zamĂłwienia (np. "ZamĂłwienie ZAM/123 - shopPro") --- -## ver. 0.313 (2026-02-23) - Fix sync płatności Apilo + logowanie +## ver. 0.313 (2026-02-23) - Fix sync pĹ‚atnoĹ›ci Apilo + logowanie -- **FIX**: `syncApiloPayment()` i `syncApiloStatus()` — `(int)` cast na `apilo_order_id` (format `"PPxxxxxx"`) dawał `0`, przez co metody pomijały sync z API Apilo. Zmiana na `empty()` -- **NEW**: Logowanie w `syncApiloPaymentIfNeeded()` i `syncApiloStatusIfNeeded()` — każda ścieżka decyzyjna (Apilo wyłączone, brak tokenu, brak `apilo_order_id`, sync nieudany) zapisuje wpis do `pp_log` z kontekstem +- **FIX**: `syncApiloPayment()` i `syncApiloStatus()` — `(int)` cast na `apilo_order_id` (format `"PPxxxxxx"`) dawaĹ‚ `0`, przez co metody pomijaĹ‚y sync z API Apilo. Zmiana na `empty()` +- **NEW**: Logowanie w `syncApiloPaymentIfNeeded()` i `syncApiloStatusIfNeeded()` — kaĹĽda Ĺ›cieĹĽka decyzyjna (Apilo wyłączone, brak tokenu, brak `apilo_order_id`, sync nieudany) zapisuje wpis do `pp_log` z kontekstem --- -## ver. 0.312 (2026-02-23) - Fix krytycznych bugów integracji Apilo +## ver. 0.312 (2026-02-23) - Fix krytycznych bugĂłw integracji Apilo -- **FIX**: `curl_getinfo()` wywoływane po `curl_close()` — HTTP code zawsze wynosił 0, uniemożliwiając prawidłową obsługę odpowiedzi Apilo -- **FIX**: Nieskończona pętla wysyłania zamówienia — gdy Apilo zwracało błąd serwera, zamówienie nie dostawało `apilo_order_id` i było ponownie wybierane w każdym cyklu crona. Teraz błędne zamówienia oznaczane `apilo_order_id = -1` z powiadomieniem email -- **FIX**: Ceny produktów 0.00 PLN w Apilo — string `"0.00"` z MySQL jest truthy w PHP, więc ternary wybierał `price_brutto_promo` (0.00) zamiast `price_brutto`. Zmiana na `(float)... > 0` -- **FIX**: Walidacja cen przed wysyłką — zamówienia z zerowymi cenami produktów nie są wysyłane do Apilo (`apilo_order_id = -2`) z powiadomieniem email -- **FIX**: Niezainicjalizowana zmienna `$order_message` powodująca PHP warning +- **FIX**: `curl_getinfo()` wywoĹ‚ywane po `curl_close()` — HTTP code zawsze wynosiĹ‚ 0, uniemoĹĽliwiajÄ…c prawidĹ‚owÄ… obsĹ‚ugÄ™ odpowiedzi Apilo +- **FIX**: NieskoĹ„czona pÄ™tla wysyĹ‚ania zamĂłwienia — gdy Apilo zwracaĹ‚o błąd serwera, zamĂłwienie nie dostawaĹ‚o `apilo_order_id` i byĹ‚o ponownie wybierane w kaĹĽdym cyklu crona. Teraz błędne zamĂłwienia oznaczane `apilo_order_id = -1` z powiadomieniem email +- **FIX**: Ceny produktĂłw 0.00 PLN w Apilo — string `"0.00"` z MySQL jest truthy w PHP, wiÄ™c ternary wybieraĹ‚ `price_brutto_promo` (0.00) zamiast `price_brutto`. Zmiana na `(float)... > 0` +- **FIX**: Walidacja cen przed wysyĹ‚kÄ… — zamĂłwienia z zerowymi cenami produktĂłw nie sÄ… wysyĹ‚ane do Apilo (`apilo_order_id = -2`) z powiadomieniem email +- **FIX**: Niezainicjalizowana zmienna `$order_message` powodujÄ…ca PHP warning --- -## ver. 0.311 (2026-02-23) - Fix race condition Apilo + persistence filtrów + poprawki cen +## ver. 0.311 (2026-02-23) - Fix race condition Apilo + persistence filtrĂłw + poprawki cen -- **FIX**: Race condition — callback płatności przed wysłaniem zamówienia do Apilo nie synchronizował płatności (task trafiał w pustkę). Teraz `syncApiloPaymentIfNeeded` i `syncApiloStatusIfNeeded` kolejkują sync do retry gdy `apilo_order_id` jeszcze nie istnieje -- **FIX**: `processApiloSyncQueue` — zamówienia bez `apilo_order_id` były usuwane z kolejki bez synchronizacji. Teraz czekają (max 50 prób ~8h) aż cron wyśle zamówienie do Apilo -- **FIX**: Drugie wywołanie `processApiloSyncQueue` w cronie po wysyłce zamówień — sync płatności/statusów w tym samym cyklu -- **FIX**: Ceny w szczegółach zamówienia (admin + frontend) — gdy `price_brutto_promo` = 0 lub >= ceny regularnej, wyświetla cenę regularną zamiast 0 zł -- **NEW**: Persistence filtrów tabel w panelu admin — localStorage zapamiętuje ostatni widok (filtry, sortowanie, paginacja) i przywraca go przy powrocie do listy. Przycisk "Wyczyść" resetuje zapisany stan +- **FIX**: Race condition — callback pĹ‚atnoĹ›ci przed wysĹ‚aniem zamĂłwienia do Apilo nie synchronizowaĹ‚ pĹ‚atnoĹ›ci (task trafiaĹ‚ w pustkÄ™). Teraz `syncApiloPaymentIfNeeded` i `syncApiloStatusIfNeeded` kolejkujÄ… sync do retry gdy `apilo_order_id` jeszcze nie istnieje +- **FIX**: `processApiloSyncQueue` — zamĂłwienia bez `apilo_order_id` byĹ‚y usuwane z kolejki bez synchronizacji. Teraz czekajÄ… (max 50 prĂłb ~8h) aĹĽ cron wyĹ›le zamĂłwienie do Apilo +- **FIX**: Drugie wywoĹ‚anie `processApiloSyncQueue` w cronie po wysyĹ‚ce zamĂłwieĹ„ — sync pĹ‚atnoĹ›ci/statusĂłw w tym samym cyklu +- **FIX**: Ceny w szczegółach zamĂłwienia (admin + frontend) — gdy `price_brutto_promo` = 0 lub >= ceny regularnej, wyĹ›wietla cenÄ™ regularnÄ… zamiast 0 zĹ‚ +- **NEW**: Persistence filtrĂłw tabel w panelu admin — localStorage zapamiÄ™tuje ostatni widok (filtry, sortowanie, paginacja) i przywraca go przy powrocie do listy. Przycisk "Wyczyść" resetuje zapisany stan --- ## ver. 0.310 (2026-02-23) - Logi integracji w panelu admin -- **NEW**: Zakładka "Logi" w sekcji Integracje — podgląd tabeli `pp_log` z paginacją, sortowaniem, filtrami (akcja, wiadomość, ID zamówienia) i rozwijalnym kontekstem JSON -- **NEW**: `IntegrationsRepository::getLogs()`, `deleteLog()`, `clearLogs()` — metody do obsługi logów -- **NEW**: `IntegrationsController::logs()`, `logs_clear()` — akcje kontrolera -- **NEW**: Przycisk "Wyczyść wszystkie logi" z potwierdzeniem +- **NEW**: ZakĹ‚adka "Logi" w sekcji Integracje — podglÄ…d tabeli `pp_log` z paginacjÄ…, sortowaniem, filtrami (akcja, wiadomość, ID zamĂłwienia) i rozwijalnym kontekstem JSON +- **NEW**: `IntegrationsRepository::getLogs()`, `deleteLog()`, `clearLogs()` — metody do obsĹ‚ugi logĂłw +- **NEW**: `IntegrationsController::logs()`, `logs_clear()` — akcje kontrolera +- **NEW**: Przycisk "Wyczyść wszystkie logi" z potwierdzeniem --- ## ver. 0.309 (2026-02-23) - ApiloLogger + cache-busting CSS/JS + poprawki UI -- **NEW**: `ApiloLogger` — logowanie operacji Apilo do tabeli `pp_log` z kontekstem JSON (send_order, resend_order, payment_sync, status_sync, status_poll) -- **NEW**: Migracja `pp_log` — kolumny `action`, `order_id`, `context` + indeksy -- **NEW**: Cache-busting dla CSS i JS w admin panelu — `?ver=filemtime()` przy wszystkich lokalnych zasobach w `main-layout.php` +- **NEW**: `ApiloLogger` — logowanie operacji Apilo do tabeli `pp_log` z kontekstem JSON (send_order, resend_order, payment_sync, status_sync, status_poll) +- **NEW**: Migracja `pp_log` — kolumny `action`, `order_id`, `context` + indeksy +- **NEW**: Cache-busting dla CSS i JS w admin panelu — `?ver=filemtime()` przy wszystkich lokalnych zasobach w `main-layout.php` - **FIX**: Przeniesienie inicjalizacji `$mdb` przed `SettingsRepository` w `admin/index.php` -- **FIX**: Rzutowanie na `(string)` w `ShopProductController::escapeHtml()` — zapobiega warningom -- **ZMIANA**: Skrocone kategorie produktow na liscie — `text-overflow: ellipsis` z `title` tooltip -- **ZMIANA**: `copyToClipboard()` — uzywa `navigator.clipboard` API z fallbackiem na `