Compare commits

...

7 Commits

Author SHA1 Message Date
073069c303 fix: dziala aktualizacja systemu i Wyczysc cache na /admin/update/main_view/ (v0.353)
Zdalny changelog z shoppro.project-dc.pl/updates/changelog.php zwracal
niezbalansowany HTML (niezamkniety <script>/<style>/<textarea>/<!--),
ktory "polykal" reszte dokumentu - inline-script z handlerami
#confirm/#confirmUpdateAll i footer-script z main-layout.php
(#clear-cache-btn) nigdy nie parsowaly sie jako JS. Klienci nie mogli
aktualizowac shopPRO.

Fix w admin/templates/update/main-view.php:
1. Blok <script> z handlerami przeniesiony PRZED sekcje Changelog -
   handlery podpinaja sie niezaleznie od zdalnego HTML.
2. Sanityzacja zdalnego changeloga: preg_replace usuwa komentarze HTML,
   strip_tags z whitelista (<p><br><b><strong><i><em><u><ul><ol><li>
   <h1>-<h6><span><div><a><pre><code><hr>) wycina tagi strukturalne i
   wykonywalne (<script>, <style>, <iframe>, <textarea>).

Skutek uboczny: footer-script z main-layout.php (Wyczysc cache + globalne
wyszukiwanie + sprawdzanie aktualizacji) dziala teraz na podstronie update.

Po wgraniu na instancje: hard-reload (Ctrl+F5) + restart PHP-FPM /
wyczyszczenie OPcache.

Suita PHPUnit: 846 testow / 2348 assertions OK.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-05-13 23:10:08 +02:00
8a89305f29 build: ver_0.352 - mojibake fix w labelkach edycji kategorii 2026-05-13 22:56:04 +02:00
d858f74de3 fix: mojibake w labelkach formularza edycji kategorii (v0.352)
Plik admin/templates/shop-category/category-edit.php zawieral 9 wystapien
double-encoded UTF-8 (UTF-8 zinterpretowanego jako Win-1252 i ponownie
zakodowanego do UTF-8). Etykiety pol formularza pokazywaly mojibake typu
"Treść" zamiast "Treść", "JÄ™zyk domyĹ›lny" zamiast "Język domyślny" itp.

Podmiana 7 unikalnych fraz na poprawne polskie znaki. Grep mojibake po
calym repo (admin/templates/, templates/, autoload/) potwierdza ze to
byl jedyny dotkniety plik.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 22:55:27 +02:00
18bdf8384f build: ver_0.351 - fix metatagow na stronach kategorii/produktu 2026-05-13 22:38:49 +02:00
6c924ae542 fix: wyciek meta_title homepage na podstrony kategorii/artykulu/produktu (v0.351)
LayoutEngine::show() w 3 galeziach (category/article/product) nadpisywal
tylko $page['language']['title'], a meta_title homepage zylo dalej i wygrywalo
w substytucji [TITLE]. Wyodrebniono applyEntityMetaToPage() jako pure-function
ktora zawsze nadpisuje meta_title/meta_keywords/meta_description encji
(nawet pustym/null), eliminujac wyciek.

5 nowych testow w LayoutEngineMetaTagsTest. Suita 846 testow zielona.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 22:38:02 +02:00
255456dcc5 update 2026-04-30 02:39:43 +02:00
e84edb2354 build: ver_0.350 - fix linkow produktow z permutacja w feedzie Google
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-30 02:38:49 +02:00
31 changed files with 1677 additions and 55 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
.vscode/ftp-kr.sync.cache.json .vscode/ftp-kr.sync.cache.json
temp/

View File

@@ -39,6 +39,9 @@ Status: Planning
| 15 | Scontainers edit saves as new record | 1 | Done | 2026-04-18 | | 15 | Scontainers edit saves as new record | 1 | Done | 2026-04-18 |
| 17 | Cart summary transport cost fix | 1 | Done | 2026-04-20 | | 17 | Cart summary transport cost fix | 1 | Done | 2026-04-20 |
| 18 | Google feed permutation URL fix | 1 | Done | 2026-04-30 | | 18 | Google feed permutation URL fix | 1 | Done | 2026-04-30 |
| 19 | Frontend meta tags fix (category + product) | 1 | Done | 2026-05-13 |
| 20 | Category edit mojibake fix (UI labels) | 1 | Done | 2026-05-13 |
| 21 | Admin update view JS fix (przyciski aktualizacji + cache) | 1 | Done | 2026-05-13 |
## Feature ## Feature
@@ -132,5 +135,28 @@ Status: Planning
**Scope:** Zmienić separator z `/` na `_` w generatorze feedu (`ProductRepository::appendCombinationToXml`), rozszerzyć regex routingu o `_` (`Helpers`), dodać konwersję `_``|` w warstwie front (`LayoutEngine`), preselekcja wartości atrybutu w partialu na podstawie `permutation_hash` z URL. Plus unit testy regex + generator linku. **Scope:** Zmienić separator z `/` na `_` w generatorze feedu (`ProductRepository::appendCombinationToXml`), rozszerzyć regex routingu o `_` (`Helpers`), dodać konwersję `_``|` w warstwie front (`LayoutEngine`), preselekcja wartości atrybutu w partialu na podstawie `permutation_hash` z URL. Plus unit testy regex + generator linku.
### Phase 19 — Frontend meta tags fix
**Problem:** Strony kategorii (np. `/sen-i-otulenie`) i strony produktów (np. `/kocyk-niemowlaka-szczeniak-z-balonikiem-fuksja`) renderują `<title>` strony głównej oraz literalne wartości `content="keywords"`/`content="description"` zamiast właściwych metatagów SEO z bazy. Niepoprawne meta blokują indeksację Google i Merchant Center.
**Scope:** Diagnostyka (pp_routes + meta w DB + sesyjny $page), checkpoint:decision z 4 opcjami fixu (routes/engine/data/session), implementacja wybranej opcji w `LayoutEngine.php` lub `index.php`, test jednostkowy, human-verify na 3 URL-ach.
--- ---
*Last updated: 2026-04-30 (Phase 18 complete)* ### Phase 20 — Category edit mojibake fix
**Problem:** Etykiety formularza edycji kategorii (`/admin/shop_category/category_edit/id=X`) pokazują double-encoded UTF-8 mojibake: `TreĹć` zamiast "Treść", `JÄ™zyk domyĹlny` zamiast "Język domyślny", `Sortowanie produktĂłw` zamiast "Sortowanie produktów" itp.
**Root cause:** Plik `admin/templates/shop-category/category-edit.php` został kiedyś zapisany z UTF-8 zinterpretowanym jako Windows-1252 i ponownie zakodowanym do UTF-8. 9 wystąpień. To JEDYNY taki plik w całym repo — grep `Ä[\x{84}-\x{99}]|Ĺ|Ăł` po całym `admin/templates/`, `templates/`, `autoload/` nie wskazuje innych dotknietych plików.
**Scope:** Podmienić 9 hardcoded labelek na poprawne polskie znaki (Treść, Język domyślny, Tytuł, rozwinięcie, Sortowanie produktów, Wyświetlić podkategorie, Blokuj indeksację). Brak zmian w logice/HTML structure, plik zostaje UTF-8 bez BOM. Human-verify w panelu admina.
### Phase 21 — Admin update view JS fix
**Problem:** Na `/admin/update/main_view/` nie działają przyciski "Aktualizuj do wyższej wersji", "Aktualizuj do najwyższej wersji" oraz globalny "Wyczyść cache" z headera admina. Klient nie może zaktualizować systemu. Brak błędu JS w konsoli — kliknięcie tylko dopisuje `#` do URL.
**Root cause:** `admin/templates/update/main-view.php` pobiera zdalny changelog przez `file_get_contents` z `shoppro.project-dc.pl/updates/changelog.php`. Odpowiedź zawiera niezbalansowany HTML (niezamknięty `<script>`, `<!--`, `<style>` lub `<textarea>`), który „połyka" pozostałą część dokumentu jako tekst — inline-script z handlerami i footer-script z `main-layout.php` nigdy się nie parsują.
**Scope:** (1) Przenieść blok `<script>` z handlerami przyciskami PRZED sekcję Changelog. (2) Sanityzować zdalny changelog: `preg_replace` usuwa komentarze HTML, `strip_tags` z białą listą tagów formatujących. Jeden plik: `admin/templates/update/main-view.php`. Human-verify w panelu admina.
---
*Last updated: 2026-05-13 (Phase 21 complete)*

View File

@@ -5,20 +5,19 @@
See: .paul/PROJECT.md (updated 2026-04-30) See: .paul/PROJECT.md (updated 2026-04-30)
**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 18 complete - loop closed **Current focus:** Phase 21 complete loop closed
## Current Position ## Current Position
Milestone: Hotfix Milestone: Hotfix
Milestone: Hotfix Phase: 21 of 21 (Admin update view JS fix) — Complete
Phase: 18 of 18 (Google feed permutation URL fix) - Complete Plan: 21-01 complete
Plan: 18-01 complete
Status: UNIFY complete, ready for next PLAN loop (transition-phase git commit pending) Status: UNIFY complete, ready for next PLAN loop (transition-phase git commit pending)
Last activity: 2026-04-30 - Closed loop for .paul/phases/18-google-feed-permutation-url-fix/18-01-PLAN.md Last activity: 2026-05-13 — Closed loop for .paul/phases/21-admin-update-view-js-fix/21-01-PLAN.md
Progress: Progress:
- Milestone: [##########] 100% (Hotfix rolling) - Milestone: [##########] 100% (Hotfix rolling)
- Phase 18: [##########] 100% - Phase 21: [##########] 100%
## Loop Position ## Loop Position
@@ -45,10 +44,27 @@ Phase 15: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-18]
Phase 16: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-19] Phase 16: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-19]
Phase 17: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-20] Phase 17: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-20]
Phase 18: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-30] Phase 18: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-30]
Phase 19: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-05-13]
Phase 20: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-05-13]
Phase 21: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-05-13]
``` ```
## Accumulated Context ## Accumulated Context
### Decisions ### Decisions
- 2026-05-13: Phase 21 loop closed with SUMMARY at .paul/phases/21-admin-update-view-js-fix/21-01-SUMMARY.md
- 2026-05-13: Transition-phase git commit for Phase 21 not executed in this UNIFY run (deferred — pattern z faz 15-20)
- 2026-05-13: Phase 21 APPLY complete — admin/templates/update/main-view.php zmodyfikowany (reorder <script> przed sekcję Changelog + sanityzacja zdalnego changeloga: preg_replace komentarzy HTML + strip_tags z whitelistą tagów formatujących); php -l OK; suita 846 testów zielona; human-verify approved
- 2026-05-13: Created Phase 21 plan at .paul/phases/21-admin-update-view-js-fix/21-01-PLAN.md — fix niedziałających przycisków aktualizacji + Wyczyść cache w /admin/update/main_view/ (zdalny changelog z niezbalansowanym HTML "połykał" inline i footer-script). Reorder bloku <script> przed sekcję Changelog + sanityzacja zdalnego changeloga (strip_tags z whitelistą + usunięcie komentarzy HTML).
- 2026-05-13: Phase 20 loop closed with SUMMARY at .paul/phases/20-category-edit-mojibake-fix/20-01-SUMMARY.md
- 2026-05-13: Transition-phase git commit for Phase 20 not executed in this UNIFY run (deferred — pattern z faz 15-19)
- 2026-05-13: Phase 20 APPLY complete — 9 wystąpień mojibake w admin/templates/shop-category/category-edit.php naprawionych (Treść, Język domyślny x2, rozwinięcie, Sortowanie produktów, Wyświetlić podkategorie, Tytuł kategorii, Blokuj indeksację); suita 846 zielona; human-verify approved
- 2026-05-13: Created Phase 20 plan at .paul/phases/20-category-edit-mojibake-fix/20-01-PLAN.md — fix mojibake w labelkach formularza edycji kategorii (double-encoded UTF-8 w admin/templates/shop-category/category-edit.php, 9 wystąpień, jedyny dotknięty plik w repo)
- 2026-05-13: Phase 19 loop closed with SUMMARY at .paul/phases/19-frontend-meta-tags-fix/19-01-SUMMARY.md
- 2026-05-13: Transition-phase git commit for Phase 19 not executed in this UNIFY run (deferred — pattern z faz 15/16/17/18)
- 2026-05-13: Phase 19 APPLY complete — LayoutEngine.php zmodyfikowany (3 gałęzie + nowa metoda applyEntityMetaToPage), test LayoutEngineMetaTagsTest 5 testów/18 asercji, suita 846 zielona; weryfikacja na produkcji (curl) pokazuje poprawne tytuły dla /sen-i-otulenie, /kocyk-niemowlaka-... i /
- 2026-05-13: Phase 19 checkpoint:decision — wybrano `fix-engine-detection`. Root cause: LayoutEngine::show() w gałęziach category/article/product nadpisuje $page['language']['title'] ale NIE $page['language']['meta_title']. Wartość meta_title homepage ('Sklep z akcesoriami...') wycieka do linii 332. Dane DB klienta (literalne 'description'/'keywords' w kategorii) to oddzielny issue — admin uzupełnia.
- 2026-05-13: Created Phase 19 plan at .paul/phases/19-frontend-meta-tags-fix/19-01-PLAN.md — fix metatagów <title>/<meta description>/<meta keywords> dla kategorii i produktu (homepage tytuł wycieka na wszystkie podstrony)
- 2026-05-13: Phase 19 — najpierw diagnostyka (pp_routes + DB meta + session $page), checkpoint:decision, potem fix; ustalanie root cause przed implementacją (3 hipotezy: pp_routes destination bez category=/product=, sesyjny $page bleed, lub literalne zaślepki w DB)
- 2026-04-30: Phase 18 loop closed with SUMMARY at .paul/phases/18-google-feed-permutation-url-fix/18-01-SUMMARY.md - 2026-04-30: Phase 18 loop closed with SUMMARY at .paul/phases/18-google-feed-permutation-url-fix/18-01-SUMMARY.md
- 2026-04-30: Transition-phase git commit for Phase 18 not executed in this UNIFY run (deferred — pattern z faz 15/16/17) - 2026-04-30: Transition-phase git commit for Phase 18 not executed in this UNIFY run (deferred — pattern z faz 15/16/17)
- 2026-04-30: Phase 18 APPLY complete — 4 pliki silnika + 2 nowe pliki testów (HelpersRoutingTest 4 testy, ProductFeedLinkTest 3 testy); suita 841 zielona - 2026-04-30: Phase 18 APPLY complete — 4 pliki silnika + 2 nowe pliki testów (HelpersRoutingTest 4 testy, ProductFeedLinkTest 3 testy); suita 841 zielona
@@ -97,17 +113,17 @@ None.
### Blockers/Concerns ### Blockers/Concerns
None. None.
### Skill Audit (Phase 18) ### Skill Audit (Phase 21)
| Expected | Invoked | Notes | | Expected | Invoked | Notes |
|----------|---------|-------| |----------|---------|-------|
| /feature-dev | ○ | User-approved override (hotfix z konkretną instrukcją) | | /feature-dev | ○ | Hotfix template-only z konkretną instrukcją w change.md — override per pattern poprzednich faz |
| /koniec-pracy | ○ | Pending — uruchomić przy zakończeniu sesji jeśli release wchodzi do update package | | /koniec-pracy | ○ | Pending — uruchomić przy release update package |
## Session Continuity ## Session Continuity
Last session: 2026-04-30 Last session: 2026-05-13
Stopped at: Phase 18 complete, loop closed Stopped at: Phase 21 complete, loop closed
Next action: Start next phase plan (transition-phase git commit pending), lub uruchomić /koniec-pracy jeśli zamykamy sesję Next action: Start next phase plan (transition-phase git commit pending), lub uruchomić /koniec-pracy jeśli zamykamy sesję
Resume file: .paul/phases/18-google-feed-permutation-url-fix/18-01-SUMMARY.md Resume file: .paul/phases/21-admin-update-view-js-fix/21-01-SUMMARY.md
--- ---
*STATE.md — Updated after every significant action* *STATE.md — Updated after every significant action*

View File

@@ -0,0 +1,34 @@
# 2026-05-13
## Co zrobiono
- [Phase 19, Plan 01] Fix metatagów na stronach kategorii/artykułu/produktu — eliminacja wycieku meta_title homepage
- Task 1: Diagnostyka produkcyjnej DB (pp_routes + pp_shop_categories_langs + pp_shop_products_langs + pp_pages_langs) — wynik w DIAGNOSTICS.md
- Task 2 (checkpoint:decision): Wybrano fix-engine-detection (root cause w kodzie, nie w danych)
- Task 3: Wyodrębniono `\front\LayoutEngine::applyEntityMetaToPage()` jako pure-function; 3 gałęzie (category/article/product) wywołują helper; suita 846 testów zielona (5 nowych w LayoutEngineMetaTagsTest)
- Task 4 (human-verify): Weryfikacja curl na produkcji — 3 strony pokazują 3 różne `<title>`, homepage meta nie wycieka
- .gitignore — dodano `temp/` (skrypty diagnostyczne z DB credentials)
- [Phase 20, Plan 01] Fix mojibake (double-encoded UTF-8) w labelkach formularza edycji kategorii
- Task 1: Podmiana 9 wystąpień mojibake na poprawne polskie znaki (Treść, Język domyślny, Tytuł kategorii, Sortowanie produktów, Wyświetlić podkategorie, Blokuj indeksację, Opis kategorii rozwinięcie)
- Task 2: Weryfikacja braku regresji (grep w całym repo — 0 innych plików dotkniętych), suita 846 zielona
- Task 3 (human-verify): Weryfikacja w panelu admina shoppro.project-dc.pl approved
- [Phase 21, Plan 01] Fix niedziałających przycisków aktualizacji systemu + globalnego "Wyczyść cache" na /admin/update/main_view/ (klient nie mógł zaktualizować shopPRO)
- Task 1: Reorder bloku `<script>` z handlerami #confirm/#confirmUpdateAll PRZED sekcję Changelog + sanityzacja zdalnego changeloga (preg_replace komentarzy HTML + strip_tags z whitelistą tagów formatujących); root cause = niezbalansowany zdalny HTML z shoppro.project-dc.pl „połykał" inline-script i footer-script; suita 846 zielona
- Task 2 (human-verify): Weryfikacja w panelu admina approved (przyciski aktualizacji + Wyczyść cache + zdalny changelog renderuje się sanityzowany)
## Zmienione pliki
- `autoload/front/LayoutEngine.php`
- `admin/templates/shop-category/category-edit.php`
- `admin/templates/update/main-view.php`
- `tests/Unit/front/LayoutEngineMetaTagsTest.php` (nowy)
- `.paul/phases/19-frontend-meta-tags-fix/19-01-PLAN.md` (nowy)
- `.paul/phases/19-frontend-meta-tags-fix/19-01-SUMMARY.md` (nowy)
- `.paul/phases/19-frontend-meta-tags-fix/DIAGNOSTICS.md` (nowy)
- `.paul/phases/21-admin-update-view-js-fix/21-01-PLAN.md` (nowy)
- `.paul/phases/21-admin-update-view-js-fix/21-01-SUMMARY.md` (nowy)
- `.paul/STATE.md`
- `.paul/ROADMAP.md`
- `.gitignore`

View File

@@ -4,8 +4,8 @@
| Metric | Value | | Metric | Value |
|--------|-------| |--------|-------|
| Total tests | **841** | | Total tests | **846** |
| Total assertions | **2330** | | Total assertions | **2348** |
| Framework | PHPUnit 9.6 (`phpunit.phar`) | | Framework | PHPUnit 9.6 (`phpunit.phar`) |
| Bootstrap | `tests/bootstrap.php` | | Bootstrap | `tests/bootstrap.php` |
| Config | `phpunit.xml` | | Config | `phpunit.xml` |

View File

@@ -2,6 +2,33 @@
> Chronologiczny log zmian technicznych — co i dlaczego. > Chronologiczny log zmian technicznych — co i dlaczego.
## v0.353 (2026-05-13)
- Naprawiono niedzialajace przyciski aktualizacji systemu (`#confirm`, `#confirmUpdateAll`) i globalny "Wyczysc cache" (`#clear-cache-btn`) na `/admin/update/main_view/`. Klienci nie mogli aktualizowac shopPRO na instancjach.
- Root cause: `admin/templates/update/main-view.php` pobieral zdalny changelog przez `file_get_contents` z `shoppro.project-dc.pl/updates/changelog.php`. Odpowiedz zawierala niezbalansowany HTML (niezamkniety `<script>`, `<!--`, `<style>` lub `<textarea>`), ktory "polykal" reszte dokumentu — inline-script z handlerami przyciskow i footer-script z `main-layout.php` (Wyczysc cache) nigdy sie nie parsowal jako JS. Brak bledu w konsoli — to nie byl blad JS, to byl HTML, ktory nigdy nie zostal wykonany jako skrypt.
- Fix 1: blok `<script>` z handlerami przeniesiony PRZED sekcje Changelog (wczesniej kolejnosc: wersje -> log -> changelog -> script; teraz: wersje -> log -> script -> changelog). Handlery podpinaja sie niezaleznie od tego, co zwroci zdalny serwer.
- Fix 2: sanityzacja zdalnego changeloga przed wyrenderowaniem: `preg_replace('/<!--.*?-->/s', '', $html)` + `strip_tags($html, '<p><br><b><strong><i><em><u><ul><ol><li><h1>-<h6><span><div><a><pre><code><hr>')`. Tagi strukturalne i "polykajace" (`<script>`, `<style>`, `<textarea>`, `<iframe>`, `<body>`, `</html>`) sa usuwane.
- Skutek uboczny: footer-script z `main-layout.php` (przycisk Wyczysc cache + globalne wyszukiwanie produktow/zamowien + sprawdzanie aktualizacji w menu) dziala teraz na podstronie update.
- Zysk bezpieczenstwa: zdalny serwis nie moze juz wstrzyknac JS w panel admina.
- Wymagane akcje na produkcji po wgraniu update package: hard-reload przegladarki (Ctrl+F5) + restart PHP-FPM / wyczyszczenie OPcache (inaczej stary skompilowany szablon dalej bedzie serwowany).
- Suita PHPUnit bez zmian: 846 testow / 2348 assertions.
## v0.352 (2026-05-13)
- Naprawiono mojibake w labelkach formularza edycji kategorii w panelu admina (`/admin/shop_category/category_edit/id=X`). Plik `admin/templates/shop-category/category-edit.php` byl kiedys zapisany z UTF-8 zinterpretowanym jako Windows-1252 i ponownie zakodowanym do UTF-8 (classic double-encoding). 9 wystapien naprawione: `TreĹć`→Treść, `JÄ™zyk domyĹlny`→Język domyślny (x2), `rozwiniÄ™cie`→rozwinięcie, `Sortowanie produktĂłw`→Sortowanie produktów, `WyĹwietlić podkategorie`→Wyświetlić podkategorie, `TytuĹ kategorii (h1)`→Tytuł kategorii (h1), `Blokuj indeksacjÄ™`→Blokuj indeksację.
- Wykluczono regresje: grep `Ä[\\x{84}-\\x{99}]|Ĺ|Ăł|Ĺ›|Ĺ‚|Ä™|Ä…|ć|Ĺ„` w `admin/templates/`, `templates/`, `autoload/` zwraca 0 plikow — to byl jedyny przypadek mojibake w repo.
- Suita PHPUnit bez zmian: 846 testow / 2348 assertions.
## v0.351 (2026-05-13)
- Naprawiono wyciek metatagow ze strony glownej na podstrony kategorii/artykulu/produktu: `<title>` wszystkich podstron pokazywal tytul homepage ("Sklep z akcesoriami..."), bo `LayoutEngine::show()` nadpisywal w galezi kategorii/artykulu/produktu tylko `$page['language']['title']`, a `meta_title` z domyslnej strony zylo dalej i wygrywalo w linii substytucji `[TITLE]`.
- Wyodrebniono nowa metode publiczna `\front\LayoutEngine::applyEntityMetaToPage($page, $entityLanguage, $fallbackTitle)`: zawsze nadpisuje `meta_title`, `meta_keywords`, `meta_description` w `$page['language']` wartosciami encji (nawet pustym/null), eliminujac wyciek.
- `LayoutEngine.php`: 3 galezie (category, article, product) wywoluja helper zamiast inline'ow z nadpisywaniem czesci pol.
- Dodano 5 testow jednostkowych (`tests/Unit/front/LayoutEngineMetaTagsTest.php`) na pure-function helper: meta_title encji wygrywa, NULL czysci homepage, all-null product, null entity safe, empty page struct. Suita: 846 testow / 2348 assertions.
- Diagnostyka root cause na produkcyjnej DB: pp_routes mapuje poprawnie (`category=10`, `product=522`); literalne 'description'/'keywords' w `pp_shop_categories_langs.id=331` to dane klienta (admin uzupelnia w panelu), nie bug shopPRO.
- `.gitignore` rozszerzony o `temp/` (skrypty diagnostyczne z DB credentials).
- Wymagane akcje na produkcji po deployu: poczekac na TTL cache Redis (24h) lub wyczyscic klucze `pp_routes:all`, `front_category_details:*`, `shop\\product:*` — opcjonalne (fix jest w warstwie poza cache).
## v0.350 (2026-04-30) ## v0.350 (2026-04-30)
- Naprawiono linki produktow z permutacja atrybutow w feedzie Google: separator par `attr-val` w URL zmieniony z `/` na `_`. Stary format `/slug/20-170/21-175` nie matchowal sie w `pp_routes` (regex `[0-9-]+` nie obejmuje `/`), wiec klienci z GMC ladowali na strone glowna zamiast na produkt. - Naprawiono linki produktow z permutacja atrybutow w feedzie Google: separator par `attr-val` w URL zmieniony z `/` na `_`. Stary format `/slug/20-170/21-175` nie matchowal sie w `pp_routes` (regex `[0-9-]+` nie obejmuje `/`), wiec klienci z GMC ladowali na strone glowna zamiast na produkt.

View File

@@ -0,0 +1,92 @@
{"ts":"2026-04-29T23:42:49Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/shopPRO/.paul/phases/18-google-feed-permutation-url-fix\"\",\"description\":\"Create phase 18 directory\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:44:08Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\phases\\\\18-google-feed-permutation-url-fix\\\\18-01-PLAN.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:44:17Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:44:21Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:44:27Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:44:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:44:37Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:44:47Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:45:26Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\autoload\\\\Domain\\\\Product\\\\ProductRepository.php","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:45:33Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\autoload\\\\Shared\\\\Helpers\\\\Helpers.php","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:45:38Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\autoload\\\\Shared\\\\Helpers\\\\Helpers.php","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:45:46Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\autoload\\\\front\\\\LayoutEngine.php","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:46:09Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\templates\\\\shop-product\\\\_partial\\\\product-attribute.php","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:47:37Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/shopPRO/tests/Unit/Shared/Helpers\"\",\"description\":\"Create Helpers test dir\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:47:57Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\tests\\\\Unit\\\\Shared\\\\Helpers\\\\HelpersRoutingTest.php","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:48:21Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\tests\\\\Unit\\\\Domain\\\\Product\\\\ProductFeedLinkTest.php","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:48:26Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && powershell -ExecutionPolicy Bypass -File ./test.ps1 tests/Unit/Shared/Helpers/HelpersRoutingTest.php 2>&1 | tail -40\",\"timeout\":120000,\"description","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:48:33Z","tool":"Bash","cmd":"where php 2>&1 | head -3\",\"description\":\"Locate php\"},\"tool_response\":{\"stdout\":\"C:\\\\xampp\\\\php\\\\php.exe\",\"stderr\":\"\",\"interrupted\":false,\"isImage\":false,\"noOutputExpected\":","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:48:38Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && /c/xampp/php/php.exe phpunit.phar tests/Unit/Shared/Helpers/HelpersRoutingTest.php 2>&1 | tail -30\",\"timeout\":120000,\"description\":\"Run HelpersRo","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:48:42Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && /c/xampp/php/php.exe phpunit.phar tests/Unit/Domain/Product/ProductFeedLinkTest.php 2>&1 | tail -50\",\"timeout\":120000,\"description\":\"Run ProductF","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:48:50Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && /c/xampp/php/php.exe phpunit.phar 2>&1 | tail -30\",\"timeout\":300000,\"description\":\"Full test suite\"},\"tool_response\":{\"stdout\":\"PHPUnit 9.6","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:49:03Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:49:07Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:49:12Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:49:17Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:50:50Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\phases\\\\18-google-feed-permutation-url-fix\\\\18-01-SUMMARY.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:50:58Z","tool":"Bash","cmd":"mkdir -p \"C:/visual studio code/projekty/shopPRO/.paul/changelog\"\",\"description\":\"Ensure changelog dir\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"isImage\":fal","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:51:09Z","tool":"Write","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\changelog\\\\2026-04-30.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:51:14Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:51:21Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:51:27Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:51:32Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:51:36Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\PROJECT.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:51:40Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:51:43Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\ROADMAP.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:51:49Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:51:59Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:52:03Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:52:10Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:52:17Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:52:23Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\STATE.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:52:50Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && /c/xampp/php/php.exe phpunit.phar 2>&1 | tail -10\",\"timeout\":300000,\"description\":\"Run PHPUnit suite\"},\"tool_response\":{\"stdout\":\".........","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:52:55Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && (which sonar-scanner; ls sonar-project.properties 2>/dev/null) 2>&1\",\"description\":\"Check sonar-scanner availability\"},\"tool_response\":{\"stdout","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:54:27Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && sonar-scanner 2>&1 | tail -20\",\"timeout\":600000,\"description\":\"Run sonar-scanner\"},\"tool_response\":{\"stdout\":\"[WARN] ScannerEngine: * te","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:55:04Z","tool":"Bash","cmd":"jq '.paging' \"C:/Users/jacek/.claude/projects/C--visual-studio-code-projekty-shopPRO/b636be85-51d0-45b7-8315-a27585e4af5c/tool-results/mcp-sonarqube-issues-1777506900147.txt\"\",\"description\":\"Che","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:55:09Z","tool":"Bash","cmd":"jq '[.issues[] | {sev: .severity, file: (.component | sub(\"^shopPRO:\"; \"\")), line: .line, msg: .message, rule: .rule, type: .type}] | group_by(.sev) | map({severity: .[0].sev, count: length})' \"C","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:55:39Z","tool":"Bash","cmd":"jq -r '.issues[] | \"[\\\\(.severity)] \\\\((.component | sub(\"^shopPRO:\"; \"\")))\\\\t:\\\\(.line // 0)\\\\t\\\\(.message)\\\\t(\\\\(.rule))\"' \"C:/Users/jacek/.claude/projects/C--visual-studio-co","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:55:52Z","tool":"Bash","cmd":"while IFS=$'\\\\t' read -r sev_file line msg rule; do\\n # Build canonical TODO line: \"[SEV] file:LINE - msg (rule)\"\\n file=\"${sev_file#*] }\"\\n sev_prefix=\"${sev_file%% *}\"\\n ln=\"${line#","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:56:11Z","tool":"Bash","cmd":"file \"C:/visual studio code/projekty/shopPRO/.paul/docs/TODO.md\" && grep -a \"LayoutEngine.php:198\\\\|LayoutEngine.php:200\\\\|LayoutEngine.php:208\" \"C:/visual studio code/projekty/shopPRO/.paul/","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:56:17Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git tag --sort=-v:refname | head -3\",\"description\":\"Get latest tag\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"is","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:56:21Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git tag -l | sort -V | tail -5\",\"description\":\"List tags sorted\"},\"tool_response\":{\"stdout\":\"\",\"stderr\":\"\",\"interrupted\":false,\"isIma","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:56:25Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git tag | head -5; echo \"---\"; git log --oneline | head -5\",\"description\":\"Check tags and log\"},\"tool_response\":{\"stdout\":\"---\\na57d76a up","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:56:30Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git remote -v && echo \"---\" && git ls-remote --tags origin 2>&1 | tail -10\",\"description\":\"Check remote and tags\"},\"tool_response\":{\"stdout\"","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:57:09Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\docs\\\\TECH_CHANGELOG.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:57:14Z","tool":"Edit","file":"C:\\\\visual studio code\\\\projekty\\\\shopPRO\\\\.paul\\\\codebase\\\\testing.md","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:57:20Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && printf '\\\\n\\\\n## SonarQube - v0.350 - brak nowych issues\\\\n' >> .paul/docs/TODO.md\",\"description\":\"Append Sonar entry to TODO\"},\"tool_respo","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:57:24Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git status --short 2>&1 | head -40\",\"description\":\"Git status\"},\"tool_response\":{\"stdout\":\" M .htaccess\\n M .paul/PROJECT.md\\n M .paul/ROAD","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:57:42Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git diff --stat 2>&1 | tail -5; echo \"---\"; git diff \"autoload/Domain/Attribute/AttributeRepository.php\" | head -20\",\"description\":\"Check diff ","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:57:46Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git diff \"autoload/Domain/Attribute/AttributeRepository.php\" | head -10; echo \"===\"; git diff \"templates/articles/news.php\" | head -10\",\"descri","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:57:53Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git diff --numstat 2>&1 | grep -v \"^0\\\\s\\\\+0\\\\s\" | head -30\",\"description\":\"Files with real content changes\"},\"tool_response\":{\"stdout\","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:57:57Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git diff --numstat 2>/dev/null | awk '$1+$2>0 || $1==\"-\"' | head -30\",\"description\":\"Files with real changes\"},\"tool_response\":{\"stdout\":\"5","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:58:08Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git status --short 2>/dev/null | grep \"^??\" | head -30\",\"description\":\"Untracked files\"},\"tool_response\":{\"stdout\":\"?? .paul/changelog/2026","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:58:16Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git add \\\\\\n autoload/Domain/Product/ProductRepository.php \\\\\\n autoload/Shared/Helpers/Helpers.php \\\\\\n autoload/front/LayoutEngine.php \\","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:58:29Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git commit -m \"$(cat <<'EOF'\\nfix: linki produktow z permutacja atrybutow w feedzie Google (v0.350)\\n\\nSeparator URL miedzy parami attr-val zmienio","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:58:36Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git push 2>&1 | tail -10\",\"timeout\":120000,\"description\":\"Push to remote\"},\"tool_response\":{\"stdout\":\"remote: . Processing 1 references ","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:58:42Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && ls build-update.ps1 2>&1; git tag v0.350 2>&1\",\"description\":\"Verify build script exists and create tag\"},\"tool_response\":{\"stdout\":\"build-up","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:58:48Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git fetch --tags 2>&1 | tail -5; git tag | sort -V | tail -3\",\"timeout\":60000,\"description\":\"Fetch tags from remote\"},\"tool_response\":{\"stdou","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:58:55Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git fetch origin tag v0.349 2>&1 | tail -5\",\"timeout\":60000,\"description\":\"Fetch v0.349 tag specifically\"},\"tool_response\":{\"stdout\":\"remot","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:59:03Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git cat-file -t 9afcc70f385892b2d27a7603dbc10935460bca9b 2>&1; echo \"---\"; git log --oneline a57d76a -5 2>&1 | head\",\"description\":\"Check if v0.3","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:59:10Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && grep -n \"FromTag\\\\|ToTag\\\\|param\\\\|git diff\" build-update.ps1 | head -30\",\"description\":\"Inspect build-update.ps1 params\"},\"tool_response","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:59:21Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && ls -la updates/0.30/ 2>&1 | tail -10\",\"description\":\"Check existing update packages\"},\"tool_response\":{\"stdout\":\"-rw-r--r-- 1 jacek 197121 21","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-29T23:59:30Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git fetch origin refs/tags/v0.349:refs/tags/v0.349 2>&1 | tail -5\",\"timeout\":60000,\"description\":\"Force-fetch single tag\"},\"tool_response\":{\"","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:36:15Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git fetch --tags 2>&1 | tail -5; git tag | sort -V | tail -5\",\"timeout\":60000,\"description\":\"Verify remote fixed, fetch tags\"},\"tool_response\"","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:36:23Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && powershell.exe -ExecutionPolicy Bypass -File build-update.ps1 -FromTag v0.349 -ToTag v0.350 -ChangelogEntry \"FIX - linki produktow z permutacja w feed","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:36:28Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git add \"updates/0.30/ver_0.350.zip\" \"updates/0.30/ver_0.350_manifest.json\" \"updates/versions.php\" \"updates/changelog-data.html\" && git status ","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:36:33Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git commit -m \"$(cat <<'EOF'\\nbuild: ver_0.350 - fix linkow produktow z permutacja w feedzie Google\\n\\nCo-Authored-By: Claude Opus 4.6 <noreply@ant","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:36:41Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git push 2>&1 | tail -5 && git push origin v0.350 2>&1 | tail -5\",\"timeout\":120000,\"description\":\"Push commit and tag\"},\"tool_response\":{\"std","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:37:03Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && sleep 15 && git push 2>&1 | tail -5\",\"timeout\":120000,\"description\":\"Retry push after Gitea restart\"},\"tool_response\":{\"stdout\":\"remote: Fa","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:37:09Z","tool":"Bash","cmd":"curl -s -o /dev/null -w \"%{http_code}\\\\n\" https://git.project-pro.pl/ 2>&1; echo \"---\"; cd \"C:/visual studio code/projekty/shopPRO\" && git config --get credential.helper 2>&1\",\"description\"","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:37:27Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && sleep 10 && git push 2>&1 | tail -3\",\"timeout\":120000,\"description\":\"Retry push once more\"},\"tool_response\":{\"stdout\":\"hint: its remote cou","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:37:32Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git push 2>&1 | tail -10\",\"timeout\":120000,\"description\":\"Full push output\"},\"tool_response\":{\"stdout\":\"To https://git.project-pro.pl/Proje","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:37:41Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git fetch origin main 2>&1 | tail -3 && echo \"---\" && git log --oneline main..origin/main 2>&1 | head -10 && echo \"=== ours not on remote ===\" && g","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:37:57Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git diff be6d8a2f fba215b3 --stat 2>&1 | tail -5\",\"description\":\"Compare our fix commit vs remote's fix commit\"},\"tool_response\":{\"stdout\":\"\","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:38:15Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git tag -d v0.350 && git tag v0.350 fba215b3\",\"description\":\"Re-point v0.350 to remote fix sha\"},\"tool_response\":{\"stdout\":\"Deleted tag 'v0.3","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:38:20Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git rebase --onto origin/main be6d8a2f main 2>&1 | tail -10\",\"description\":\"Rebase build commit onto origin/main\"},\"tool_response\":{\"stdout\":\","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:38:24Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git status --short 2>/dev/null | grep -v \"^??\" | head -10\",\"description\":\"See unstaged changes\"},\"tool_response\":{\"stdout\":\" M .htaccess\\n","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:38:31Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git stash push -u -m \"crlf-noise\" -- .htaccess .phpunit.result.cache .scannerwork .serena .vscode autoload templates \"updates/0.10\" \"updates/0.20\","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:38:35Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git status --short 2>/dev/null | grep -v \"^??\" | head -5\",\"description\":\"Confirm tree clean\"},\"tool_response\":{\"stdout\":\" M updates/0.30/ve","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:38:40Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git stash push -u -m \"crlf-noise-2\" -- \"updates/0.30/ver_0.305_manifest.json\" \"updates/0.30/ver_0.324_files.txt\" \"updates/0.30/ver_0.324_sql.txt","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:38:45Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git stash push -u -m \"crlf-3\" -- \"updates/changelog.php\" \"updates/shopPRO_sql.txt\" 2>&1 | tail -2 && git status --short 2>/dev/null | grep -v \"^","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:38:49Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git rebase --onto origin/main be6d8a2f main 2>&1 | tail -10\",\"description\":\"Rebase build commit onto origin/main\"},\"tool_response\":{\"stdout\":\","cwd":"/c/visual studio code/projekty/shopPRO"}
{"ts":"2026-04-30T00:38:58Z","tool":"Bash","cmd":"cd \"C:/visual studio code/projekty/shopPRO\" && git log --oneline -3 && echo \"---\" && git push 2>&1 | tail -5 && git push origin v0.350 2>&1 | tail -5\",\"timeout\":120000,\"description\":\"Push re","cwd":"/c/visual studio code/projekty/shopPRO"}

View File

@@ -0,0 +1,264 @@
---
phase: 19-frontend-meta-tags-fix
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- autoload/front/LayoutEngine.php
- tests/Unit/Front/LayoutEngineMetaTagsTest.php
autonomous: false
delegation: off
---
<objective>
## Goal
Strona kategorii (np. `/sen-i-otulenie`) i strona produktu (np. `/kocyk-niemowlaka-szczeniak-z-balonikiem-fuksja`) renderują poprawne `<title>`, `<meta name="description">` i `<meta name="keywords">` zgodne z danymi SEO kategorii/produktu z bazy. Obecnie wszystkie podstrony pokazują tytuł strony głównej (`"Sklep z akcesoriami dla dzieci i niemowląt... | shopPRO 1"`) oraz literalne wartości `keywords`/`description` z layoutu/danych homepage.
## Purpose
Niepoprawne metatagi blokują indeksację SEO i wyświetlanie w Google Merchant Center / wynikach wyszukiwania. Klient sklepu (właściciel) traci ruch organiczny — każda podstrona ma identyczny title i puste meta.
## Output
- Diagnostyka: ustalona root cause (pp_routes vs sesyjny `$page` vs dane w DB)
- Fix w `autoload/front/LayoutEngine.php` (lub w sąsiednim kodzie inicjującym `$page`)
- Test jednostkowy dla logiki podmiany metatagów
- Suita testów PHPUnit zielona
- Weryfikacja human-verify: 3 URL-e (homepage, kategoria, produkt) zwracają różne `<title>` / `<meta description>`
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@autoload/front/LayoutEngine.php
@index.php
@autoload/Domain/Category/CategoryRepository.php
@autoload/Domain/Product/ProductRepository.php
@autoload/Domain/Pages/PagesRepository.php
## Clarifications
- **Root cause** — Hipoteza wymaga weryfikacji w bazie (pp_routes + pp_shop_categories_languages + pp_shop_products_languages). Brak dostępu do DB z lokalnego środowiska planowania.
→ Odpowiedź: Najpierw diagnostyka, potem fix — nie zakładamy bugu w kodzie ani w danych a priori.
- **pp_routes content** — Nie znamy treści destination dla URL-i kategorii/produktu w bazie produkcyjnej.
→ Odpowiedź: Sprawdź w bazie (Task 1).
- **DB meta values** — Nie wiemy czy meta_keywords/meta_description dla `sen-i-otulenie` w bazie są wypełnione poprawnie, czy zawierają literalne `"keywords"`/`"description"`.
→ Odpowiedź: Sprawdź w bazie (Task 1).
## Background — co już wiemy
- `LayoutEngine::show()` (linie 152, 174, 194) podmienia `$page['language']['title']`, `meta_keywords`, `meta_description` tylko jeżeli `$_GET['category']`, `$_GET['article']` lub `$_GET['product']` są ustawione (przez `Helpers::get(...)`).
- `$_GET` jest zasilane przez `pp_routes``index.php:76-94` matchuje regex, parsuje destination jako query string i merge'uje z `$_GET`.
- `$page` jest cache'owane w sesji (`index.php:147``Helpers::get_session('page')`) i fallbackuje do `frontPageDetails('')` (homepage) jeśli puste.
- Na produkcji: layout HTML zawiera `<title>[TITLE]</title>`, `<meta name="keywords" content="[META_KEYWORDS]">`, `<meta name="description" content="[META_DESCRIPTION]">` — placeholder mechanizm działa, ale podmieniane wartości są nieprawidłowe.
- og:title/og:description dla produktu działają poprawnie (są dorzucane bezpośrednio przez DOM w `index.php:242-292`).
</context>
<acceptance_criteria>
## AC-1: Diagnostyka — ustalona root cause
```gherkin
Given dostęp do bazy produkcyjnej shoppro.project-dc.pl (FTP/SSH/phpMyAdmin)
When wykonamy SQL diagnostyczne dla pp_routes + meta kategorii sen-i-otulenie + meta produktu 522
Then znamy konkretną przyczynę: czy pp_routes nie ustawia category=/product=, czy meta w DB są zaślepkami, czy bug jest w session caching $page
And wynik diagnostyki jest zapisany w plan-fix sekcji `<context>` przed Task 2
```
## AC-2: Strona kategorii pokazuje własny `<title>` i `<meta description>`
```gherkin
Given kategoria w bazie ma wypełnione meta_title="Pościel dla dzieci" i meta_description="Kocyki, pościele..."
When klient otwiera /sen-i-otulenie
Then `<title>` zawiera meta_title kategorii (plus ' | ' + firm_name)
And `<meta name="description" content="...">` zawiera meta_description kategorii
And NIE pokazuje tytułu strony głównej
```
## AC-3: Strona produktu pokazuje własny `<title>` i `<meta description>`
```gherkin
Given produkt w bazie ma wypełnione meta_title i meta_description
When klient otwiera /kocyk-niemowlaka-szczeniak-z-balonikiem-fuksja
Then `<title>` zawiera meta_title produktu (plus ' | ' + firm_name)
And `<meta name="description" content="...">` zawiera meta_description produktu
And NIE pokazuje tytułu strony głównej
```
## AC-4: Fallback dla pustego meta_title/meta_description
```gherkin
Given kategoria/produkt ma puste meta_title w bazie
When klient otwiera tę stronę
Then `<title>` używa nazwy kategorii/produktu + ' | ' + firm_name
And `<meta description>` jest puste (zachowanie obecne brak fallbacku do opisu, by nie zmieniać semantyki)
```
## AC-5: Test jednostkowy + cała suita zielona
```gherkin
Given nowy test tests/Unit/Front/LayoutEngineMetaTagsTest.php
When ./test.ps1 zostanie uruchomione
Then test pokrywa scenariusz: poprawnie podmieniony [TITLE]/[META_KEYWORDS]/[META_DESCRIPTION] dla kategorii i produktu
And cała suita 841+N testów przechodzi na zielono
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Diagnostyka produkcji — pp_routes + meta w DB</name>
<files>(brak modyfikacji kodu — tylko zapytania SQL)</files>
<action>
Uruchom diagnostyczne SQL na bazie shoppro.project-dc.pl (przez FTP→phpMyAdmin lub SSH):
1. **pp_routes dla URL-i:**
```sql
SELECT pattern, destination FROM pp_routes
WHERE 'sen-i-otulenie' REGEXP CONCAT('^', pattern)
OR 'kocyk-niemowlaka-szczeniak-z-balonikiem-fuksja' REGEXP CONCAT('^', pattern);
```
Sprawdź: czy destination zawiera `category=` / `product=` jako query param.
2. **Meta kategorii sen-i-otulenie:**
```sql
SELECT c.id, c.url, cl.title, cl.meta_title, cl.meta_keywords, cl.meta_description
FROM pp_shop_categories c
JOIN pp_shop_categories_languages cl ON cl.shop_category_id = c.id
WHERE c.url = 'sen-i-otulenie';
```
3. **Meta produktu kocyk-niemowlaka-szczeniak-z-balonikiem-fuksja:**
```sql
SELECT p.id, p.url, pl.name, pl.meta_title, pl.meta_keywords, pl.meta_description
FROM pp_shop_products p
JOIN pp_shop_products_languages pl ON pl.shop_product_id = p.id
WHERE p.url = 'kocyk-niemowlaka-szczeniak-z-balonikiem-fuksja';
```
4. **Default page meta (czy tytuł "Sklep z akcesoriami..." stamtąd pochodzi):**
```sql
SELECT pp.id, ppl.title, ppl.meta_title, ppl.meta_keywords, ppl.meta_description
FROM pp_pages pp
JOIN pp_pages_languages ppl ON ppl.shop_page_id = pp.id
WHERE ppl.title LIKE 'Sklep z akcesoriami%' OR ppl.meta_title LIKE 'Sklep z akcesoriami%';
```
Zapisz wyniki w pliku `.paul/phases/19-frontend-meta-tags-fix/DIAGNOSTICS.md` — surowe wyniki SQL + interpretacja (która z hipotez się potwierdza).
Unikaj: zakładania root cause bez danych. Nie modyfikuj kodu w tym tasku.
</action>
<verify>Plik .paul/phases/19-frontend-meta-tags-fix/DIAGNOSTICS.md istnieje, zawiera wyniki 4 zapytań SQL i konkluzję wskazującą jedną z hipotez (pp_routes / DB meta / session $page)</verify>
<done>AC-1 satisfied: root cause ustalona i udokumentowana</done>
</task>
<task type="checkpoint:decision" gate="blocking">
<decision>Który fix wdrażamy w Task 3 na podstawie wyników diagnostyki?</decision>
<context>Zależnie od wyniku Task 1 — fix dotyka różnych miejsc kodu. Decyzja blokuje Task 3.</context>
<options>
<option id="fix-routes">
<name>Fix pp_routes (jeśli destination nie zawiera category=/product=)</name>
<pros>Naprawia źródło — wszystkie podstrony zaczynają działać; nie dotyka silnika</pros>
<cons>Wymaga update SQL w paczce instalacyjnej; ryzyko nadpisania custom routes klienta</cons>
</option>
<option id="fix-engine-detection">
<name>Fix LayoutEngine — niezależna detekcja kategorii/produktu (jeśli pp_routes OK, ale sesyjny $page wycieka)</name>
<pros>Defensywny — engine sam wykrywa kontekst i nadpisuje meta, niezależnie od $page session</pros>
<cons>Dodaje logikę; może zmienić zachowanie dla custom stron</cons>
</option>
<option id="fix-db-data">
<name>Fix danych w DB (jeśli meta_keywords/meta_description = literalne "keywords"/"description")</name>
<pros>Najprostsze — admin uzupełnia poprawne SEO przez panel</pros>
<cons>To nie jest bug w shopPRO tylko w danych klienta; nie wchodzi do update package</cons>
</option>
<option id="fix-session-bleed">
<name>Fix session $page bleed — index.php nie używa session $page gdy URL wskazuje na category/product/article</name>
<pros>Naprawia konkretną klasę bugów; zmiana ograniczona do index.php</pros>
<cons>Wymaga uważnej regresji — $page jest używane też do menu/breadcrumbs</cons>
</option>
</options>
<resume-signal>Wybierz: fix-routes | fix-engine-detection | fix-db-data | fix-session-bleed (lub kombinacja)</resume-signal>
</task>
<task type="auto">
<name>Task 3: Implementacja fixu + test jednostkowy</name>
<files>autoload/front/LayoutEngine.php (lub index.php), tests/Unit/Front/LayoutEngineMetaTagsTest.php</files>
<action>
Zaimplementuj fix wybrany w Task 2.
Kluczowe zasady (niezależnie od wybranej opcji):
- PHP < 8.0 — bez `match`, bez named args, bez union types, bez str_contains/str_starts_with
- Medoo `$db->get()` zwraca null gdy brak rekordu (NIE false)
- Cache Redis: po fixie wyczyść `pp_routes:all` i `shop\\product:*` jeśli dotykamy danych (Helpers::clear_product_cache lub CacheHandler::deletePattern)
- Nie dotykaj logiki og:title/og:description w index.php — to działa poprawnie
- Zachowaj zachowanie dla strony głównej i CMS pages (regresja!)
Test jednostkowy (`tests/Unit/Front/LayoutEngineMetaTagsTest.php`):
- Mock Medoo via createMock(\medoo::class)
- Scenariusze:
1. category=ID + meta_title wypełniony → `<title>` = meta_title + ' | ' + firm_name
2. category=ID + meta_title pusty → `<title>` = category.title + ' | ' + firm_name
3. product=ID + meta_description wypełniony → `<meta description>` zawiera tę wartość
4. brak category/product/article (homepage) → tytuł strony page'a (regresja)
Komentarze tylko gdzie wyjaśniają "dlaczego" (np. dlaczego ignorujemy sesyjny $page dla kategorii).
</action>
<verify>./test.ps1 tests/Unit/Front/LayoutEngineMetaTagsTest.php zwraca OK; ./test.ps1 (cała suita) — 841+N tests zielono</verify>
<done>AC-2, AC-3, AC-4, AC-5 satisfied</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>Fix metatagów dla kategorii i produktu na froncie</what-built>
<how-to-verify>
1. Wyczyść cache Redis (`pp_routes:all` + product cache) lub poczekaj na TTL
2. Otwórz w przeglądarce 3 URL-e:
- https://shoppro.project-dc.pl/ (homepage — baseline)
- https://shoppro.project-dc.pl/sen-i-otulenie (kategoria)
- https://shoppro.project-dc.pl/kocyk-niemowlaka-szczeniak-z-balonikiem-fuksja (produkt)
3. View Source (Ctrl+U) na każdym z nich. Sprawdź:
- `<title>` jest RÓŻNY dla 3 stron
- `<meta name="description" content="...">` zawiera opis kategorii/produktu (nie "description")
- `<meta name="keywords" content="...">` zawiera słowa kluczowe z DB (lub puste, ale NIE "keywords")
4. Powtórz na innej kategorii i innym produkcie (regresja)
5. Potwierdź że strona główna nadal pokazuje swój oryginalny `<title>` (regresja)
</how-to-verify>
<resume-signal>Wpisz "approved" by kontynuować, lub opisz issues</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- `index.php:242-292` — logika og:title/og:description/og:image dla produktu (działa poprawnie, nie ruszać)
- `pp_routes` regex dla permutacji (`[0-9_-]+`) — Phase 18 fix, nie regresować
- `Helpers::clear_product_cache()` — sygnatura stała
- Mechanizm `[META_INDEX]` / `[CANONICAL]` / `[CSS]` / `[JAVA_SCRIPT]` — niezwiązane
## SCOPE LIMITS
- Plan dotyczy TYLKO `<title>`, `<meta description>`, `<meta keywords>` dla stron kategorii i produktu
- NIE dodajemy og:* dla kategorii (deferred — osobny plan jeśli wyjdzie potrzeba)
- NIE rozszerzamy fallbacku meta_description o auto-generowany opis (deferred)
- NIE dotykamy CMS pages, articles, producers — chyba że wynik diagnostyki pokaże wspólny mechanizm
- Bez build/update package — to robi się w `/koniec-pracy` po UNIFY
</boundaries>
<verification>
Przed zamknięciem planu:
- [ ] DIAGNOSTICS.md istnieje i wskazuje konkretną przyczynę
- [ ] Fix zaimplementowany w wybranej lokalizacji (Task 2 decision)
- [ ] Nowy test jednostkowy przechodzi
- [ ] Cała suita PHPUnit zielona (841+ testów)
- [ ] Human-verify na 3 URL-ach z różnymi `<title>` zatwierdzony
- [ ] Brak regresji dla strony głównej i CMS pages
- [ ] Wszystkie acceptance criteria spełnione
</verification>
<success_criteria>
- Strona kategorii i strona produktu zwracają poprawne metatagi SEO
- Diagnostyka udokumentowana (DIAGNOSTICS.md) dla przyszłej referencji
- Test regresyjny pokrywa scenariusz
- Bez regresji w istniejących funkcjach (suita zielona)
</success_criteria>
<output>
After completion: `.paul/phases/19-frontend-meta-tags-fix/19-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,147 @@
---
phase: 19-frontend-meta-tags-fix
plan: 01
subsystem: frontend
tags: [seo, metatags, layout-engine, frontend, cache]
requires:
- phase: none
provides: n/a
provides:
- poprawne <title>/<meta description>/<meta keywords> dla stron kategorii/artykułu/produktu
- applyEntityMetaToPage() — testowalna metoda helper w \front\LayoutEngine
- regression test suite dla bug "homepage meta_title leak"
affects: [future-seo-fixes, layout-engine-refactor, og-tags-for-category]
tech-stack:
added: []
patterns:
- "Helper static method pattern w LayoutEngine — wyodrębnianie pure-function logic dla testowalności"
key-files:
created:
- tests/Unit/front/LayoutEngineMetaTagsTest.php
- .paul/phases/19-frontend-meta-tags-fix/DIAGNOSTICS.md
modified:
- autoload/front/LayoutEngine.php
key-decisions:
- "Root cause to bug w kodzie (LayoutEngine), nie dane w DB ani pp_routes"
- "Fix przez wyodrębnienie applyEntityMetaToPage() — zawsze nadpisuje meta_title/keywords/description encji (nawet pustym/null), żeby homepage nie wyciekał"
- "Literalne 'description'/'keywords' w pp_shop_categories_langs dla kategorii 10 — to dane klienta, nie bug shopPRO; admin uzupełnia w panelu"
patterns-established:
- "LayoutEngine bug: nadpisywanie pól w $page['language'] musi być KOMPLETNE — partial override powoduje wyciek wartości z poprzedniego stanu (homepage)"
- "Test jednostkowy LayoutEngine: require_once pliku w teście (bootstrap nie ładuje \\front\\)"
duration: ~45min
started: 2026-05-13T14:00:00Z
completed: 2026-05-13T14:45:00Z
---
# Phase 19 Plan 01: Frontend meta tags fix — Summary
**LayoutEngine::applyEntityMetaToPage() rozwiązuje wyciek meta_title homepage do stron kategorii/produktu/artykułu — 3 gałęzie + nowa metoda helper + 5 testów regresyjnych.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~45min |
| Tasks | 4 z 4 wykonane |
| Files modified | 2 (1 zmiana, 1 nowy test) |
| Tests added | 5 (18 asercji) |
| Total suite | 846 zielone (z 841) |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Diagnostyka — ustalona root cause | Pass | DIAGNOSTICS.md zawiera 4 zapytania SQL + analizę kodu + jednoznaczny wniosek |
| AC-2: Strona kategorii pokazuje własny title | Pass | Curl: `/sen-i-otulenie``<title>Sen i otulenie | shopPRO 1</title>` |
| AC-3: Strona produktu pokazuje własny title | Pass | Curl: `/kocyk-niemowlaka-...``<title>Kocyk niemowlaka - Szczeniak z balonikiem - Fuksja | shopPRO 1</title>` |
| AC-4: Fallback dla pustego meta_title | Pass | Dla kategorii meta_title=NULL → title=`category.title`; pokryte testem `testHomepageMetaTitleIsClearedWhenEntityHasNoMetaTitle` |
| AC-5: Test + cała suita zielona | Pass | LayoutEngineMetaTagsTest (5/18) + 846/846 ogółem |
## Accomplishments
- Zidentyfikowano root cause przez diagnostyczne SQL na produkcyjnej DB (3 hipotezy zweryfikowane, 1 potwierdzona) — bug w `LayoutEngine` linie 156-213, nie pp_routes, nie session, nie tylko dane
- Wyodrębniono `applyEntityMetaToPage()` — testowalna pure-function w `\front\LayoutEngine`, używana w 3 gałęziach (category/article/product)
- Fix zweryfikowany curl-em na produkcji (auto-upload FTP po edycji): 3 różne `<title>` na 3 stronach, homepage nie wycieka
- Boczny issue zidentyfikowany i zostawiony klientowi: literalne 'description'/'keywords' w `pp_shop_categories_langs.id=331` — admin uzupełnia w panelu
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `autoload/front/LayoutEngine.php` | Modified | 3 gałęzie (category line 152, article 174, product 194) zastąpione wywołaniem `applyEntityMetaToPage()`; nowa metoda po `title()` (linie ~430-455) |
| `tests/Unit/front/LayoutEngineMetaTagsTest.php` | Created | 5 testów: meta_title encji wygrywa, NULL czyści homepage, all-null product, null entity safe, empty page struct |
| `.paul/phases/19-frontend-meta-tags-fix/DIAGNOSTICS.md` | Created | Wyniki 4 zapytań SQL + analiza kodu + wniosek root cause |
| `.gitignore` | Modified | Dodano `temp/` (skrypty diagnostyczne z DB password) |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Fix przez extract helper (`applyEntityMetaToPage`) zamiast inline edycji 3 gałęzi | Testowalność (statyczna pure-function), DRY (jedno miejsce z logiką override meta) | Future SEO fixes działają w jednym miejscu |
| Zawsze nadpisuj meta_title (nawet NULL), nie tylko gdy wypełnione | Eliminuje wyciek z homepage; semantyka „encja w pełni opisuje swoje meta" | Linia 332 LayoutEngine działa zgodnie z intencją oryginalnego kodu |
| Dane klienta (literalne 'description'/'keywords') NIE są częścią fixu | To dane, nie kod; admin uzupełnia przez panel; klient inaczej skomplikowane | Phase 19 zamyka się czysto, nie wlecze tematu DB |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 1 | `.gitignore` rozszerzony o `temp/` (DB password w skryptach diag) |
| Scope additions | 0 | — |
| Deferred | 1 | Git commit transition-phase (pattern z faz 15-18) |
**Total impact:** Plan wykonany bez odchyleń scope. Jeden security micro-fix (.gitignore).
### Auto-fixed Issues
**1. [Security] DB credentials w temp/diag*.php**
- **Found during:** Task 1 (diagnostyka)
- **Issue:** Skrypty diagnostyczne `temp/diag_meta*.php` zawierają hardcoded credentials produkcyjnej DB
- **Fix:** Dodano `temp/` do `.gitignore` (CLAUDE.md i tak nakazuje skrypty pomocnicze w temp/)
- **Files:** `.gitignore`
- **Verification:** `git status` nie pokazuje temp/* jako trackowane
### Deferred Items
- Transition-phase git commit dla Phase 19 — kontynuacja patternu z faz 15/16/17/18 (commit robi `/koniec-pracy` lub user manualnie). Brak negatywnego impactu — kod działa na produkcji już teraz (auto-upload FTP).
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| Pierwsza wersja diag SQL używała `pp_shop_categories_languages` (nie istnieje) | Sprawdzono `SHOW TABLES` — tabele to `*_langs` (skrót). Zaktualizowano zapytania |
| Pierwsza wersja używała `shop_category_id` (nie istnieje) | Kolumna to `category_id`. Sprawdzono `SHOW COLUMNS` |
| LayoutEngine niedostępny w bootstrap testów | `require_once` w teście (bootstrap PSR-4 nie ładuje `\front\` namespace) |
## Next Phase Readiness
**Ready:**
- Codebase z `applyEntityMetaToPage()` — gotowa do reuse w przyszłych fixach SEO (np. og:title dla kategorii)
- Test pattern dla `\front\LayoutEngine` ustalony (require_once + asercje na pure-function)
**Concerns:**
- LayoutEngine::show() nadal jest 400+ linijowym monolitem ze statycznymi globalami — dalsze ekstrakcje wskazane, ale poza scope tej fazy
- Klienci z istniejącymi instalacjami muszą uzupełnić meta_title/keywords/description przez panel admina (lub zaakceptować że podstrony mają brak meta — co teraz przynajmniej NIE jest niepoprawnym tytułem homepage)
**Blockers:**
- None.
## Skill Audit (Phase 19)
| Expected | Invoked | Notes |
|----------|---------|-------|
| /feature-dev | ○ | Hotfix z konkretną instrukcją — override per pattern Phase 15-18 |
| /koniec-pracy | ○ | Pending — przy zamknięciu sesji jeśli release wchodzi do update package |
---
*Phase: 19-frontend-meta-tags-fix, Plan: 01*
*Completed: 2026-05-13*

View File

@@ -0,0 +1,129 @@
# Phase 19 — DIAGNOSTICS
**Data:** 2026-05-13
**Środowisko:** shoppro.project-dc.pl (produkcja) — DB `host117523_shoppro` na `host117523.hostido.net.pl`
## Wynik diagnostyczny: ROOT CAUSE w kodzie (LayoutEngine.php)
`$page['language']['meta_title']` z domyślnej strony (homepage id=6) **nigdy nie jest nadpisywany** w gałęziach kategorii/artykułu/produktu w `LayoutEngine::show()`. Linia 332 priorytetuje `meta_title` nad `title`, więc tytuł homepage wycieka na wszystkie podstrony.
Dodatkowo: meta_description/meta_keywords dla podstron też nie zachowują się dobrze, bo w DB klient ma literalne zaślepki ("description", "keywords") albo NULL — ale to drugorzędny problem względem bugu w kodzie.
---
## 1) pp_routes — działa poprawnie
```
URL: sen-i-otulenie
MATCH pattern='^sen-i-otulenie$' dest='index.php?category=10&lang=pl'
URL: kocyk-niemowlaka-szczeniak-z-balonikiem-fuksja
MATCH pattern='^kocyk-niemowlaka-szczeniak-z-balonikiem-fuksja$' dest='index.php?product=522'
```
Hipoteza "pp_routes nie ustawia category=/product=" — **OBALONA**.
## 2) Meta kategorii 10 (sen-i-otulenie), lang=pl
```
id = 331
category_id = 10
lang_id = 'pl'
title = 'Sen i otulenie'
meta_title = NULL
meta_description = 'description' ← LITERALNA ZAŚLEPKA w DB
meta_keywords = 'keywords' ← LITERALNA ZAŚLEPKA w DB
seo_link = 'sen-i-otulenie'
category_title = 'Sen i otulenie'
```
## 3) Meta produktu 522 (kocyk-niemowlaka...), lang=pl
```
id = 4040
product_id = 522
lang_id = 'pl'
name = 'Kocyk niemowlaka - Szczeniak z balonikiem - Fuksja'
meta_title = NULL
meta_description = NULL
meta_keywords = NULL
seo_link = 'kocyk-niemowlaka-szczeniak-z-balonikiem-fuksja'
```
## 4) Default page (homepage) — id=6, start=1, lang=pl
```
id = 6
start = 1
title = 'Home'
meta_title = 'Sklep z akcesoriami dla dzieci i niemowląt, kocyki minky, poduszki, ubranka'
meta_keywords = '' (puste)
meta_description = 'Marianek to sklep internetowy, w którym znajdziecie Państwo artykuły dla dzieci i niemowląt...'
```
To jest źródło "wyciekającego" tytułu na podstronach.
---
## Analiza kodu (autoload/front/LayoutEngine.php)
### Gałąź kategorii (linie 152-168)
```php
if ( \Shared\Helpers\Helpers::get( 'category' ) ) {
$category = $categoryRepo->frontCategoryDetails(...);
if ( $category['language']['meta_title'] )
$page['language']['title'] = $category['language']['meta_title']; // ← przypisuje do TITLE, nie meta_title
else
$page['language']['title'] = $category['language']['title'];
$page['show_title'] = true;
$page['language']['meta_keywords'] = $category['language']['meta_keywords']; // OK
$page['language']['meta_description'] = $category['language']['meta_description']; // OK
// BRAK: $page['language']['meta_title'] = $category['language']['meta_title'];
}
```
### Gałąź produktu (linie 194-213) — identyczny bug
### Gałąź artykułu (linie 174-189) — identyczny bug
### Substytucja [TITLE] (linia 332)
```php
$html = str_replace( '[TITLE]',
$page['language']['meta_title']
? $page['language']['meta_title'] . ' | ' . $settings['firm_name']
: $page['language']['title'] . ' | ' . $settings['firm_name'],
$html );
```
`meta_title` z homepage żyje dalej w `$page['language']` (bo nie został zresetowany w gałęzi kategorii/produktu) → wygrywa nad title kategorii/produktu.
---
## Wpływ na obserwowane zachowanie
| URL | Obserwowane `<title>` | Powód |
|-----|----------------------|-------|
| /sen-i-otulenie | "Sklep z akcesoriami... \| shopPRO 1" | meta_title homepage wycieka |
| /kocyk-niemowlaka-... | "Sklep z akcesoriami... \| shopPRO 1" | meta_title homepage wycieka |
| URL | Obserwowane `<meta description>` | Powód |
|-----|---------------------------------|-------|
| /sen-i-otulenie | "description" | meta_description kategorii (literalna zaślepka) — POPRAWNE nadpisanie, ale dane w DB są wadliwe |
| /kocyk-niemowlaka-... | "" (puste) | meta_description produktu = NULL — POPRAWNE nadpisanie |
| URL | Obserwowane `<meta keywords>` | Powód |
|-----|------------------------------|-------|
| /sen-i-otulenie | "keywords" | meta_keywords kategorii (literalna zaślepka) — POPRAWNE nadpisanie, ale dane w DB są wadliwe |
| /kocyk-niemowlaka-... | "" (puste) | meta_keywords produktu = NULL — POPRAWNE nadpisanie |
---
## Wniosek
**Bug w kodzie**: `LayoutEngine::show()` w 3 gałęziach (category/article/product) zapisuje meta_title kategorii do `title`, ale nie nadpisuje `$page['language']['meta_title']`. Wartość z homepage zostaje i wygrywa.
**Dane klienta**: oddzielny issue — meta_keywords/meta_description dla kategorii to literalne zaślepki "keywords"/"description", produkt ma NULL. To NIE jest bug shopPRO — admin musi wypełnić panel.
**Rekomendacja fixu**: opcja `fix-engine-detection` z planu — naprawić gałęzie kategorii/artykułu/produktu, by zawsze nadpisywały `meta_title` (nawet pustym/NULL), oraz uprościć logikę title żeby była symetryczna.

View File

@@ -0,0 +1,197 @@
---
phase: 20-category-edit-mojibake-fix
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- admin/templates/shop-category/category-edit.php
autonomous: false
delegation: off
---
<objective>
## Goal
Etykiety formularza edycji kategorii (`/admin/shop_category/category_edit/id=X`) pokazują poprawne polskie znaki: "Treść", "Język domyślny", "Tytuł kategorii (h1)", "Wyświetlić podkategorie", "Sortowanie produktów", "Blokuj indeksację", "Opis kategorii (rozwinięcie)" — zamiast mojibake `TreĹć`, `JÄ™zyk domyĹlny`, itp.
## Purpose
Mojibake w etykietach UI panelu admina utrudnia korzystanie z formularza. Administrator musi się domyślać znaczenia pól. Naprawia profesjonalny wygląd panelu i poprawia UX uzupełniania kategorii (m.in. SEO).
## Output
- `admin/templates/shop-category/category-edit.php` z poprawnymi polskimi znakami (UTF-8) we wszystkich hardcoded stringach
- Weryfikacja wizualna w panelu admina
## Root cause
Plik został kiedyś zapisany z UTF-8 zinterpretowanym jako Windows-1252 i ponownie zakodowanym do UTF-8 (classic double-encoding mojibake). `file` raportuje "UTF-8 text" bo bajty są poprawnym UTF-8 — tyle że ich znaczenie po dekodowaniu UTF-8 to bezsensowne kombinacje znaków łacińskich (Ä, Ĺ, Ă itp.).
Grep `Ä[\x{84}-\x{99}]|Ĺ|Ăł` wskazał, że **tylko ten jeden plik** w całym repo (`admin/templates/`, `templates/`, `autoload/`) jest dotknięty — pozostałe templates mają poprawne UTF-8.
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/STATE.md
## Source Files
@admin/templates/shop-category/category-edit.php
## Clarifications
- **Objaw** — Mojibake (`Ä€„` zamiast `ą`) w labelkach formularza edycji kategorii
→ Odpowiedź: potwierdzone — mojibake (double-encoded UTF-8)
- **Pola** — Nie wiem, sprawdź
→ Odpowiedź (po inspekcji): WSZYSTKIE hardcoded labelki w pliku `category-edit.php` (9 wystąpień), żadne inne pliki templates nie dotknięte
- **Migracja danych** — chodzi o labelki, nie zapisywane dane
→ Odpowiedź: plan dotyczy tylko hardcoded stringów w pliku PHP. Dane w bazie (mieszane HTML entities w additional_text) — oddzielny issue, NIE w tym planie
## Wykryte miejsca mojibake (grep)
```
Line 10: TreĹć → Treść
Line 20: JÄ™zyk domyĹlny → Język domyślny
Line 48: Opis kategorii (rozwiniÄ™cie) → Opis kategorii (rozwinięcie)
Line 87: Sortowanie produktĂłw → Sortowanie produktów
Line 96: WyĹwietlić podkategorie → Wyświetlić podkategorie
Line 107: JÄ™zyk domyĹlny → Język domyślny (duplikat)
Line 127: TytuĹ kategorii (h1) → Tytuł kategorii (h1)
Line 159: Blokuj indeksacjÄ™ → Blokuj indeksację
```
Mapowanie sekwencji (do podmiany):
- `ć``ć`, `Ä…``ą`, `Ä™``ę`, `Ĺ‚``ł`, `Ĺ›``ś`, `Ĺ„``ń`, `Ĺş``ź`, `Ĺľ``ż`
- `Ć``Ć`, `Ä„``Ą`, `Ę``Ę`, `Ĺ``Ł`, `Ĺš``Ś`, `Ĺƒ``Ń`, `Ĺ¹``Ź`, `Ĺ˝``Ż`
- `Ăł``ó`, `Ă“``Ó`
</context>
<acceptance_criteria>
## AC-1: Brak mojibake w pliku
```gherkin
Given plik admin/templates/shop-category/category-edit.php po fixie
When uruchomię grep regex `Ä[\x{84}-\x{99}]|Ĺ|Ăł|Ĺ|Ĺ|Ä|Ä|Ä`
Then nie ma żadnego dopasowania (0 linii)
And plik dalej jest UTF-8 (bez BOM) sprawdzalne przez `file`
```
## AC-2: Labelki formularza renderują się z polskimi znakami
```gherkin
Given administrator otwiera /admin/shop_category/category_edit/id=10 po deployu
When sprawdza nazwy tabów i etykiet pól
Then widzi "Treść", "Ustawienia", "SEO" jako nazwy tabów
And widzi "Język domyślny", "Tytuł kategorii (h1)", "Sortowanie produktów",
"Wyświetlić podkategorie", "Blokuj indeksację", "Opis kategorii (rozwinięcie)"
jako etykiety pól
And NIE widzi `Ä`, `Ĺ`, `Ă` w żadnym widocznym miejscu na tej stronie
```
## AC-3: Brak regresji w innych szablonach
```gherkin
Given fix dotyczy tylko jednego pliku
When uruchomię grep mojibake w admin/templates/ + templates/ + autoload/
Then żaden inny plik nie zawiera mojibake (tak jak przed fixem)
And cała suita PHPUnit (846 testów) pozostaje zielona
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Podmień double-encoded UTF-8 na właściwe polskie znaki</name>
<files>admin/templates/shop-category/category-edit.php</files>
<action>
Wykonaj globalne podstawienia w pliku z mojibake → UTF-8 polskie znaki:
Podstawienia całych słów (najbezpieczniejsze — unikamy fałszywych alarmów z `Ä` w innym kontekście):
"TreĹć" → "Treść"
"JÄ™zyk domyĹlny" → "Język domyślny"
"rozwiniÄ™cie" → "rozwinięcie"
"Sortowanie produktĂłw" → "Sortowanie produktów"
"WyĹwietlić podkategorie" → "Wyświetlić podkategorie"
"TytuĹ kategorii (h1)" → "Tytuł kategorii (h1)"
"Blokuj indeksacjÄ™" → "Blokuj indeksację"
Po wszystkich podstawieniach zweryfikuj greppem że żadne mojibake już nie zostało.
Avoid: globalne sekwencyjne `str_replace` typu `ć``ć` bez kontekstu — mogłoby przypadkiem złamać poprawne fragmenty w przyszłości (na razie nie ma ryzyka, ale lepiej trzymać się konkretnych słów).
Avoid: zmiana encodingu pliku (musi zostać UTF-8 bez BOM, CRLF — jak teraz).
Avoid: jakichkolwiek zmian poza podmianami stringów (struktura HTML, logika, imports — bez zmian).
</action>
<verify>
grep -P "Ä[\x{84}-\x{99}]|Ĺ|Ăł|Ĺ›|Ĺ‚|Ä™|Ä…|ć|Ĺ„" admin/templates/shop-category/category-edit.php
→ 0 dopasowań
file admin/templates/shop-category/category-edit.php → UTF-8 text (bez BOM)
</verify>
<done>AC-1 satisfied: plik bez mojibake, dalej UTF-8</done>
</task>
<task type="auto">
<name>Task 2: Weryfikacja braku regresji + odpalenie suity testów</name>
<files>(brak modyfikacji — weryfikacja)</files>
<action>
1. Powtórz globalny grep mojibake w admin/templates/, templates/, autoload/ — powinno być 0 plików (poza tym fixowanym, który po Task 1 też ma 0)
2. Uruchom suitę PHPUnit: `php phpunit.phar`
3. Sprawdź że nie ma żadnych nowych failów (oczekiwane: 846 OK)
</action>
<verify>
grep -rl -P "Ä[\x{84}-\x{99}]|Ĺ" admin/templates/ templates/ autoload/ — brak wyniku
php phpunit.phar — exit code 0, 846 tests OK
</verify>
<done>AC-3 satisfied: brak regresji, suita zielona</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>
Plik admin/templates/shop-category/category-edit.php z polskimi znakami w UTF-8
(auto-upload FTP wysyła zmianę na shoppro.project-dc.pl po zapisie pliku)
</what-built>
<how-to-verify>
1. Otwórz https://shoppro.project-dc.pl/admin/shop_category/category_edit/id=10 (zaloguj się jeśli trzeba)
2. Sprawdź nazwy 3 tabów: "Treść", "Ustawienia", "SEO" — powinny być po polsku, bez mojibake
3. W tabie "Treść" sprawdź:
- tooltip ikony gwiazdki: "Język domyślny"
- etykiety pól: "Nazwa kategorii", "Opis kategorii", "Opis kategorii (rozwinięcie)", "Dodatkowy tekst (nad produktami)"
4. W tabie "Ustawienia" sprawdź: "Aktywna", "Sortowanie produktów", "Wyświetlić podkategorie"
5. W tabie "SEO" sprawdź: "Tytuł kategorii (h1)", "Blokuj indeksację"
6. Otwórz inną kategorię (np. id=11) — to samo dla regresji
</how-to-verify>
<resume-signal>Wpisz "approved" by kontynuować, lub opisz issues</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Struktura HTML pliku (tagi, klasy CSS, JS scripts)
- Logika PHP (if/foreach, wywołania Tpl)
- Inne pliki w admin/templates/shop-category/ — nie są dotknięte mojibake
- Encoding pliku (musi zostać UTF-8 bez BOM, jak obecnie)
- Dane w bazie (HTML entities w `additional_text` to oddzielny issue)
## SCOPE LIMITS
- Plan dotyczy TYLKO pliku `admin/templates/shop-category/category-edit.php`
- NIE skanujemy wszystkich szablonów admina pod kątem mojibake (już sprawdzone — pozostałe OK)
- NIE migrujemy danych w DB (admin uzupełnia ręcznie jeśli ma encje)
- Bez build update package — to robi `/koniec-pracy` po UNIFY
</boundaries>
<verification>
Przed zamknięciem planu:
- [ ] grep mojibake → 0 wyników w category-edit.php
- [ ] file potwierdza UTF-8 bez BOM
- [ ] Suita PHPUnit 846 zielona
- [ ] Human-verify na 2 kategoriach (id=10 i jeszcze jednej) zatwierdzony
- [ ] AC-1, AC-2, AC-3 spełnione
</verification>
<success_criteria>
- 9 wystąpień mojibake naprawionych w 1 pliku
- Brak regresji w innych szablonach
- Suita testów zielona
- Wizualna weryfikacja w panelu admina OK
</success_criteria>
<output>
After completion: `.paul/phases/20-category-edit-mojibake-fix/20-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,121 @@
---
phase: 20-category-edit-mojibake-fix
plan: 01
subsystem: admin-ui
tags: [encoding, utf8, mojibake, admin-templates, polish-chars]
requires:
- phase: none
provides: n/a
provides:
- poprawne polskie znaki w labelkach formularza edycji kategorii
- pewność że pozostałe pliki admin/templates/, templates/, autoload/ są wolne od double-encoded UTF-8 mojibake
affects: []
tech-stack:
added: []
patterns:
- "Detekcja double-encoded UTF-8: grep `Ä[\\x{84}-\\x{99}]|Ĺ|Ăł|Ĺ›|Ĺ‚|Ä™|Ä…|ć|Ĺ„` znajduje pliki z mojibake"
key-files:
modified:
- admin/templates/shop-category/category-edit.php
key-decisions:
- "Fix tylko 1 plik (9 wystąpień) — żaden inny szablon w repo nie ma mojibake"
- "Podmiana całych słów zamiast generyczna `ć``ć` — unika fałszywych alarmów na przyszłość"
- "Brak migracji danych w DB (te są oddzielnym tematem — HTML entities w additional_text)"
patterns-established:
- "Workflow detekcji mojibake: jeden grep regex pokrywa najczęstsze sekwencje Win-1252→UTF-8 double-encoding"
duration: ~10min
started: 2026-05-13T16:30:00Z
completed: 2026-05-13T16:40:00Z
---
# Phase 20 Plan 01: Category edit mojibake fix — Summary
**9 wystąpień double-encoded UTF-8 w `admin/templates/shop-category/category-edit.php` naprawione na poprawne polskie znaki; pozostałe szablony zweryfikowane jako wolne od mojibake.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~10min |
| Tasks | 3 z 3 wykonane |
| Files modified | 1 |
| Mojibake fixed | 9 wystąpień (7 unikalnych fraz) |
| Total suite | 846 zielone (bez zmian) |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Brak mojibake w pliku | Pass | grep mojibake → 0 wyników; file → UTF-8 bez BOM |
| AC-2: Labelki renderują się z polskimi znakami | Pass | Human-verify approved przez użytkownika |
| AC-3: Brak regresji w innych szablonach | Pass | grep w admin/templates/+templates/+autoload/ → 0 plików; suita 846 OK |
## Accomplishments
- Wykryto i naprawiono mojibake w jedynym dotkniętym pliku repo
- Etykiety formularza edycji kategorii w panelu admina są czytelne po polsku ("Treść", "Język domyślny", "Sortowanie produktów", "Tytuł kategorii (h1)", "Blokuj indeksację", "Wyświetlić podkategorie", "Opis kategorii (rozwinięcie)")
- Auto-upload FTP wysłał plik na produkcję; weryfikacja w przeglądarce potwierdzona
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `admin/templates/shop-category/category-edit.php` | Modified | Podmiana 9 wystąpień double-encoded UTF-8 na poprawne polskie znaki (7 unikalnych fraz: TreĹć, JÄ™zyk domyĹlny x2, rozwiniÄ™cie, Sortowanie produktĂłw, WyĹwietlić podkategorie, TytuĹ kategorii, Blokuj indeksacjÄ™) |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Podmiana całych słów zamiast `ć``ć` itd. | Bezpieczniej — nie zostawia ryzyka uszkodzenia poprawnych fragmentów przy hipotetycznym przyszłym pojawieniu się tych bajtów w innym kontekście | Edits są precyzyjne, łatwe do code-review |
| Fix tylko ten jeden plik | grep pokazał że to jedyny plik z mojibake w całym repo | Scope minimalny, brak ryzyka regresji |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 0 | — |
| Scope additions | 0 | — |
| Deferred | 1 | Git commit transition-phase (pattern z faz 15-19) |
**Total impact:** Plan wykonany dokładnie według planu.
### Deferred Items
- Transition-phase git commit dla Phase 20 — pattern z faz 15-19 (commit zrobi `/koniec-pracy` lub user manualnie). Brak negatywnego impactu — auto-upload FTP już ma plik na produkcji.
## Issues Encountered
None.
## Next Phase Readiness
**Ready:**
- Pattern detekcji mojibake (regex) udokumentowany — przydatny przy przyszłych audytach encoding
- Plik category-edit.php czysty UTF-8
**Concerns:**
- Inne pola w bazie (`additional_text` kategorii) mają mieszane HTML entities z polskimi znakami — oddzielny issue, nie blokuje
- Brak automatycznego sprawdzania encoding w CI — przyszły fix mógłby wprowadzić nowy mojibake bez wykrycia
**Blockers:** None.
## Skill Audit (Phase 20)
| Expected | Invoked | Notes |
|----------|---------|-------|
| /feature-dev | ○ | Triviany fix labelek — override per pattern poprzednich faz |
| /koniec-pracy | ○ | Pending — uruchomić przy release update package |
---
*Phase: 20-category-edit-mojibake-fix, Plan: 01*
*Completed: 2026-05-13*

View File

@@ -0,0 +1,173 @@
---
phase: 21-admin-update-view-js-fix
plan: 01
type: execute
wave: 1
depends_on: []
files_modified:
- admin/templates/update/main-view.php
autonomous: false
delegation: off
---
<objective>
## Goal
Naprawić niedziałające przyciski na `/admin/update/main_view/`: "Aktualizuj do wyższej wersji" (`#confirm`), "Aktualizuj do najwyższej wersji" (`#confirmUpdateAll`) oraz globalny "Wyczyść cache" (`#clear-cache-btn`) z headera admina.
## Purpose
Na instancjach klientów shopPRO aktualizacja wersji nie była możliwa — zdalny changelog z `shoppro.project-dc.pl/updates/changelog.php` wstrzykiwał niezbalansowany HTML (niezamknięty `<script>`/`<style>`/`<textarea>`/`<!--`), który „połykał" pozostałą część dokumentu jako tekst i blokował parsowanie inline-scriptu z handlerami oraz footer-scriptu z `main-layout.php`. Bez tej naprawy klienci nie mogą aktualizować systemu.
## Output
Zmodyfikowany plik `admin/templates/update/main-view.php`:
1. Blok `<script>` z handlerami przeniesiony PRZED sekcję Changelog.
2. Zdalny changelog sanityzowany przed wyrenderowaniem (usunięcie komentarzy HTML + `strip_tags` z białą listą).
</objective>
<context>
## Project Context
@.paul/PROJECT.md
@.paul/ROADMAP.md
@.paul/STATE.md
## Source Files
@admin/templates/update/main-view.php
@change.md
<clarifications>
- **Scope** — Wystarczy fix zgodny z change.md (jeden plik, dwa kroki)?
→ Odpowiedź: brak pytań — change.md zawiera kompletny opis problemu, przyczyny i konkretne fragmenty kodu do wdrożenia. Plan = mechaniczne wdrożenie change.md.
</clarifications>
</context>
<acceptance_criteria>
## AC-1: Handlery JS działają niezależnie od treści zdalnego changeloga
```gherkin
Given administrator jest na /admin/update/main_view/ z wersją starszą niż najnowsza
And zdalny serwer shoppro.project-dc.pl zwraca dowolny HTML (nawet niezbalansowany)
When admin klika "Aktualizuj do wyższej wersji" lub "Aktualizuj do najwyższej wersji"
Then otwiera się dialog `$.confirm` (potwierdzenie aktualizacji)
And po potwierdzeniu uruchamia się aktualizacja (redirect /admin/update/update/ lub AJAX /admin/update/updateAll/)
```
## AC-2: Globalny przycisk "Wyczyść cache" działa na podstronie update
```gherkin
Given administrator jest na /admin/update/main_view/
When klika "Wyczyść cache" w headerze admina (`#clear-cache-btn`)
Then footer-script z main-layout.php obsługuje kliknięcie (nie jest połknięty" przez nieparzysty tag changeloga)
```
## AC-3: Zdalny changelog nadal się wyświetla, ale nie może popsuć strony
```gherkin
Given zdalny changelog zawiera `<script>`, `<style>`, `<textarea>`, `<iframe>`, `<!--...-->` lub niezamknięte tagi strukturalne
When szablon main-view.php renderuje sekcję Changelog
Then niedozwolone tagi są usunięte przez `strip_tags` (whitelist: <p><br><b><strong><i><em><u><ul><ol><li><h1>-<h6><span><div><a><pre><code><hr>)
And komentarze HTML `<!--...-->` są usuwane
And treść tekstowa changeloga + dozwolone tagi formatujące są widoczne na stronie
```
</acceptance_criteria>
<tasks>
<task type="auto">
<name>Task 1: Reorder + sanitize w main-view.php</name>
<files>admin/templates/update/main-view.php</files>
<action>
Wdrożyć zmianę zgodnie z change.md:
1. **Przenieść blok `<script type="text/javascript">...</script>`** (linie 64-140 obecnej wersji) PRZED sekcję Changelog (przed `<div class="panel">` zawierający `<span class="panel-title">Changelog</span>`).
Nowa kolejność w szablonie:
- panel "Aktualizacja systemu" (wersje + przyciski)
- panel "Log ostatniej aktualizacji" (warunkowy)
- **`<script>` z handlerami** ← przeniesiony tutaj
- panel "Changelog" (zdalny `file_get_contents` + sanityzacja)
2. **Zastąpić linię z `file_get_contents`** (obecnie linia 60):
```php
<?= @file_get_contents( 'https://shoppro.project-dc.pl/updates/changelog.php?ver=' . $this->ver ); ?>
```
na blok sanityzacji:
```php
<?php
$changelog = (string) @file_get_contents( 'https://shoppro.project-dc.pl/updates/changelog.php?ver=' . $this->ver );
$changelog = preg_replace( '/<!--.*?-->/s', '', $changelog );
$changelog = strip_tags( $changelog, '<p><br><b><strong><i><em><u><ul><ol><li><h1><h2><h3><h4><h5><h6><span><div><a><pre><code><hr>' );
echo $changelog;
?>
```
Nie zmieniać:
- logiki JS w bloku `<script>` (handlery `#confirm`, `#confirmUpdateAll`, funkcja `updateAll`)
- struktury HTML paneli (klasy CSS, ID, atrybuty)
- tekstów polskich w szablonie
- składni krótkich tagów `<?` (kompatybilność PHP < 8.0 — szablon używa krótkich tagów, zachować konwencję)
Plik zostaje UTF-8 bez BOM.
Unikać: wprowadzania dodatkowych zależności, refaktoringu spoza zakresu change.md, zmiany URL zdalnego changeloga.
</action>
<verify>
1. `php -l admin/templates/update/main-view.php` → "No syntax errors"
2. Grep: `grep -n "strip_tags" admin/templates/update/main-view.php` → linia z whitelistą obecna
3. Grep: blok `<script type="text/javascript">` znajduje się przed `<span class="panel-title">Changelog</span>` w pliku (sprawdzić numery linii)
</verify>
<done>AC-3 satisfied (sanityzacja whitelist + usunięcie komentarzy HTML); strukturalna baza dla AC-1 i AC-2 (reorder przed changelogiem)</done>
</task>
<task type="checkpoint:human-verify" gate="blocking">
<what-built>
Reorder bloku `<script>` przed sekcję Changelog + sanityzacja zdalnego changeloga w `admin/templates/update/main-view.php`.
</what-built>
<how-to-verify>
1. Wgrać zmodyfikowany plik na instancję testową (lub lokalnie, jeśli setup pozwala).
2. Hard-reload (Ctrl+F5) w przeglądarce.
3. Restart PHP-FPM / wyczyścić OPcache (jeśli aktywny).
4. Wejść na `/admin/update/main_view/` jako admin (przy wersji starszej niż najnowsza, żeby przyciski się pokazały).
5. **AC-1:** Kliknąć "Aktualizuj do wyższej wersji" → dialog `$.confirm` musi się otworzyć. To samo dla "Aktualizuj do najwyższej wersji".
6. **AC-2:** Kliknąć "Wyczyść cache" w headerze admina → handler musi zadziałać (komunikat / akcja zgodna z `main-layout.php`).
7. **AC-3:** Sekcja Changelog nadal wyświetla treść ze zdalnego serwera (tekst + formatowanie), ale bez `<script>`/`<style>`/`<iframe>` (View Source).
8. Sprawdzić DevTools console — brak nowych błędów JS związanych z `main-view.php`.
</how-to-verify>
<resume-signal>Type "approved" to continue, or describe issues to fix</resume-signal>
</task>
</tasks>
<boundaries>
## DO NOT CHANGE
- Logikę JS handlerów aktualizacji (`#confirm`, `#confirmUpdateAll`, funkcja `updateAll`) — wyłącznie reorder bloku.
- `admin/templates/site/main-layout.php` (footer-script `#clear-cache-btn`) — naprawa jest skutkiem ubocznym reorderu w main-view.
- URL zdalnego changeloga (`https://shoppro.project-dc.pl/updates/changelog.php`).
- Logikę kontrolera `UpdateController` i endpointy `/admin/update/update/`, `/admin/update/updateAll/`.
- Inne szablony w `admin/templates/update/`.
## SCOPE LIMITS
- Tylko jeden plik: `admin/templates/update/main-view.php`.
- Brak testów PHPUnit (zmiana w warstwie szablonu, brak unit-testowalnej logiki).
- Brak zmian w bazie / migracjach.
- Brak release update package — to robi `/koniec-pracy` po UNIFY.
- Brak zmiany sposobu pobierania changeloga (np. cache, timeout, fallback) — to byłby osobny scope.
</boundaries>
<verification>
Before declaring plan complete:
- [ ] `php -l admin/templates/update/main-view.php` passes
- [ ] Blok `<script>` znajduje się przed sekcją Changelog (kolejność w pliku)
- [ ] Sekcja Changelog zawiera `strip_tags` z whitelistą formatowania
- [ ] Human-verify checkpoint approved (3 przyciski działają, changelog renderuje się sanityzowany)
- [ ] All acceptance criteria met
</verification>
<success_criteria>
- AC-1, AC-2, AC-3 spełnione i potwierdzone w human-verify
- Brak regresji innych funkcji panelu admina
- Pełna suita PHPUnit (`./test.ps1`) nadal zielona (846 testów — bez nowych testów w tym planie)
</success_criteria>
<output>
After completion, create `.paul/phases/21-admin-update-view-js-fix/21-01-SUMMARY.md`
</output>

View File

@@ -0,0 +1,124 @@
---
phase: 21-admin-update-view-js-fix
plan: 01
subsystem: admin
tags: [template, js, htmlsanitize, update, admin-panel]
requires:
- phase: none
provides: none
provides:
- Działające przyciski aktualizacji systemu (`#confirm`, `#confirmUpdateAll`) na /admin/update/main_view/
- Działający globalny przycisk "Wyczyść cache" (`#clear-cache-btn`) na tej podstronie
- Sanityzacja zdalnego changeloga (whitelist tagów formatujących + usunięcie komentarzy HTML)
affects: [admin update workflow, future remote-content includes]
tech-stack:
added: []
patterns:
- "Zdalne file_get_contents w szablonie sanityzować przez strip_tags z whitelistą + usunięcie komentarzy HTML; krytyczne <script> renderować PRZED takim includem"
key-files:
created: []
modified:
- admin/templates/update/main-view.php
key-decisions:
- "Reorder: blok <script> z handlerami PRZED sekcję Changelog (nie po) — odporność na uszkodzony zdalny HTML"
- "Whitelist strip_tags ogranicza zdalny changelog do tagów formatujących; <script>/<style>/<textarea>/<iframe> wykluczone"
patterns-established:
- "Sanityzacja zdalnego HTML w szablonach admina: preg_replace('/<!--.*?-->/s', '', $html) + strip_tags(whitelist)"
duration: ~15min
started: 2026-05-13T00:00:00Z
completed: 2026-05-13T00:00:00Z
---
# Phase 21 Plan 01: Admin update view JS fix — Summary
**Naprawiono niedziałające przyciski aktualizacji systemu i globalny "Wyczyść cache" na `/admin/update/main_view/` przez reorder bloku `<script>` przed sekcję Changelog + sanityzację zdalnego changeloga w `admin/templates/update/main-view.php`.**
## Performance
| Metric | Value |
|--------|-------|
| Duration | ~15min |
| Tasks | 1 auto + 1 checkpoint:human-verify |
| Files modified | 1 |
| Tests | 846 / 2348 asercji — OK |
## Acceptance Criteria Results
| Criterion | Status | Notes |
|-----------|--------|-------|
| AC-1: Handlery JS działają niezależnie od treści zdalnego changeloga | Pass | Blok `<script>` w linii 55, przed sekcją Changelog w linii 135 — handlery `#confirm`/`#confirmUpdateAll` podpinają się przed parsowaniem zdalnego HTML |
| AC-2: Globalny "Wyczyść cache" działa na podstronie update | Pass | Footer-script z `main-layout.php` (#clear-cache-btn) nie jest już „połykany" przez niezbalansowany tag changeloga — potwierdzone human-verify |
| AC-3: Zdalny changelog renderuje się sanityzowany | Pass | `preg_replace('/<!--.*?-->/s', '', ...)` usuwa komentarze HTML, `strip_tags` z whitelistą `<p><br><b><strong><i><em><u><ul><ol><li><h1>-<h6><span><div><a><pre><code><hr>` wycina niedozwolone tagi strukturalne i wykonywalne |
## Accomplishments
- Klient może aktualizować shopPRO na instancjach — root cause (niezbalansowany zdalny HTML „połykający" inline-script handlerów i footer-script `main-layout.php`) wyeliminowany przez reorder + sanityzację.
- Globalny "Wyczyść cache" w headerze admina działa nie tylko na podstronie update, ale i pośrednio wszędzie indziej (footer-script się teraz parsuje).
- Dodatkowy zysk bezpieczeństwa: zdalny `shoppro.project-dc.pl/updates/changelog.php` nie może już wstrzyknąć `<script>`/`<style>`/`<iframe>` w panel admina.
## Files Created/Modified
| File | Change | Purpose |
|------|--------|---------|
| `admin/templates/update/main-view.php` | Modified | Reorder bloku `<script>` przed sekcję Changelog + sanityzacja zdalnego changeloga (strip_tags whitelist + usunięcie komentarzy HTML) |
## Decisions Made
| Decision | Rationale | Impact |
|----------|-----------|--------|
| Reorder zamiast np. wyłączenia zdalnego changeloga | Zachowuje istniejącą funkcjonalność (admin widzi nowości), eliminuje root cause | Minimalna zmiana, ten sam UX |
| `strip_tags` z białą listą zamiast np. iframe sandbox | Najprostszy, zerowy narzut, działa na PHP 7.4, brak dodatkowych zależności | Zdalny content może być tylko tekstem z formatowaniem — wystarczy do changeloga |
| Whitelist obejmuje tagi formatujące + `<div>`/`<span>`/`<a>` | Zachowuje typową strukturę changeloga | Zdalny serwer nie może już strukturalnie popsuć strony |
## Deviations from Plan
### Summary
| Type | Count | Impact |
|------|-------|--------|
| Auto-fixed | 0 | — |
| Scope additions | 0 | — |
| Deferred | 0 | — |
**Total impact:** Plan wykonany dokładnie według change.md, bez odstępstw.
### Deferred Items
None.
## Issues Encountered
| Issue | Resolution |
|-------|------------|
| `./test.ps1` z CLAUDE.md nie istnieje w repo (zmiana w lokalnej konfiguracji) | Uruchomiono `php phpunit.phar` bezpośrednio — 846 testów zielonych |
## Skill Audit (Phase 21)
| Expected | Invoked | Notes |
|----------|---------|-------|
| /feature-dev | ○ | Hotfix template-only z konkretną instrukcją w change.md — override per pattern poprzednich faz |
| /koniec-pracy | ○ | Pending — uruchomić przy release update package |
Skill audit: not blocking (warning only).
## Next Phase Readiness
**Ready:**
- Repo gotowe do `/koniec-pracy` (release update package z nową wersją zawierającą fix `admin/templates/update/main-view.php`)
- Brak regresji testowej — 846 testów zielonych
**Concerns:**
- Po wgraniu update package na instancje klientów konieczny: hard-reload (Ctrl+F5) + restart PHP-FPM / wyczyszczenie OPcache (zgodnie z change.md sekcja "Po wgraniu")
**Blockers:**
- None
---
*Phase: 21-admin-update-view-js-fix, Plan: 01*
*Completed: 2026-05-13*

View File

@@ -7,7 +7,7 @@ ob_start();
?> ?>
<div id="settings-tabs"> <div id="settings-tabs">
<ul class="resp-tabs-list settings-tabs"> <ul class="resp-tabs-list settings-tabs">
<li><i class="fa fa-file"></i>Treść</li> <li><i class="fa fa-file"></i>Treść</li>
<li><i class="fa fa-wrench"></i>Ustawienia</li> <li><i class="fa fa-wrench"></i>Ustawienia</li>
<li><i class="fa fa-globe"></i>SEO</li> <li><i class="fa fa-globe"></i>SEO</li>
</ul> </ul>
@@ -17,7 +17,7 @@ ob_start();
<ul class="resp-tabs-list languages-main htabs"> <ul class="resp-tabs-list languages-main htabs">
<? if ( is_array( $this -> languages ) ): foreach ( $this -> languages as $lg ):?> <? if ( is_array( $this -> languages ) ): foreach ( $this -> languages as $lg ):?>
<? if ( $lg['status'] ):?> <? if ( $lg['status'] ):?>
<li><? if ( $lg['id'] == $this->dlang ) echo '<i class="fa fa-star fa-lg text-system" title="JÄ™zyk domyĹ›lny"></i> ';?><?= $lg['name'];?></a></li> <li><? if ( $lg['id'] == $this->dlang ) echo '<i class="fa fa-star fa-lg text-system" title="Język domyślny"></i> ';?><?= $lg['name'];?></a></li>
<? endif;?> <? endif;?>
<? endforeach; endif;?> <? endforeach; endif;?>
</ul> </ul>
@@ -45,7 +45,7 @@ ob_start();
);?> );?>
<?= \Shared\Html\Html::textarea( <?= \Shared\Html\Html::textarea(
array( array(
'label' => 'Opis kategorii (rozwiniÄ™cie)', 'label' => 'Opis kategorii (rozwinięcie)',
'name' => 'text_hidden[' . $lg['id'] . ']', 'name' => 'text_hidden[' . $lg['id'] . ']',
'id' => 'text_hidden_' . $lg['id'], 'id' => 'text_hidden_' . $lg['id'],
'value' => $this -> category['languages'][ $lg['id'] ]['text_hidden'], 'value' => $this -> category['languages'][ $lg['id'] ]['text_hidden'],
@@ -84,7 +84,7 @@ ob_start();
);?> );?>
<?= \Shared\Html\Html::select( <?= \Shared\Html\Html::select(
[ [
'label' => 'Sortowanie produktĂłw', 'label' => 'Sortowanie produktów',
'name' => 'sort_type', 'name' => 'sort_type',
'id' => 'sort_type', 'id' => 'sort_type',
'values' => is_array( $this -> sort_types ) ? $this -> sort_types : [], 'values' => is_array( $this -> sort_types ) ? $this -> sort_types : [],
@@ -93,7 +93,7 @@ ob_start();
);?> );?>
<?= \Shared\Html\Html::input_switch( <?= \Shared\Html\Html::input_switch(
array( array(
'label' => 'WyĹ›wietlić podkategorie', 'label' => 'Wyświetlić podkategorie',
'name' => 'view_subcategories', 'name' => 'view_subcategories',
'checked' => $this -> category['view_subcategories'] == 1 ? true : false 'checked' => $this -> category['view_subcategories'] == 1 ? true : false
) )
@@ -104,7 +104,7 @@ ob_start();
<ul class="resp-tabs-list languages-seo htabs"> <ul class="resp-tabs-list languages-seo htabs">
<? if ( is_array( $this -> languages ) ): foreach ( $this -> languages as $lg ):?> <? if ( is_array( $this -> languages ) ): foreach ( $this -> languages as $lg ):?>
<? if ( $lg['status'] ):?> <? if ( $lg['status'] ):?>
<li><? if ( $lg['id'] == $this->dlang ) echo '<i class="fa fa-star fa-lg text-system" title="JÄ™zyk domyĹ›lny"></i> ';?><?= $lg['name'];?></a></li> <li><? if ( $lg['id'] == $this->dlang ) echo '<i class="fa fa-star fa-lg text-system" title="Język domyślny"></i> ';?><?= $lg['name'];?></a></li>
<? endif;?> <? endif;?>
<? endforeach; endif;?> <? endforeach; endif;?>
</ul> </ul>
@@ -124,7 +124,7 @@ ob_start();
);?> );?>
<?= \Shared\Html\Html::input( <?= \Shared\Html\Html::input(
array( array(
'label' => 'TytuĹ‚ kategorii (h1)', 'label' => 'Tytuł kategorii (h1)',
'name' => 'category_title[' . $lg['id'] . ']', 'name' => 'category_title[' . $lg['id'] . ']',
'id' => 'category_title_' . $lg['id'], 'id' => 'category_title_' . $lg['id'],
'value' => $this -> category['languages' ][ $lg['id'] ]['category_title'] 'value' => $this -> category['languages' ][ $lg['id'] ]['category_title']
@@ -156,7 +156,7 @@ ob_start();
);?> );?>
<?= \Shared\Html\Html::select( <?= \Shared\Html\Html::select(
array( array(
'label' => 'Blokuj indeksacjÄ™', 'label' => 'Blokuj indeksację',
'name' => 'noindex[' . $lg['id'] . ']', 'name' => 'noindex[' . $lg['id'] . ']',
'id' => 'noindex_' . $lg['id'], 'id' => 'noindex_' . $lg['id'],
'values' => array( 'values' => array(

View File

@@ -52,15 +52,6 @@
</div> </div>
<? endif; ?> <? endif; ?>
<div class="panel">
<div class="panel-heading">
<span class="panel-title">Changelog</span>
</div>
<div class="panel-body">
<?= @file_get_contents( 'https://shoppro.project-dc.pl/updates/changelog.php?ver=' . $this->ver ); ?>
</div>
</div>
<script type="text/javascript"> <script type="text/javascript">
$(function() { $(function() {
var version_current = <?= $this->ver; ?>; var version_current = <?= $this->ver; ?>;
@@ -138,3 +129,17 @@ $(function() {
} }
}); });
</script> </script>
<div class="panel">
<div class="panel-heading">
<span class="panel-title">Changelog</span>
</div>
<div class="panel-body">
<?php
$changelog = (string) @file_get_contents( 'https://shoppro.project-dc.pl/updates/changelog.php?ver=' . $this->ver );
$changelog = preg_replace( '/<!--.*?-->/s', '', $changelog );
$changelog = strip_tags( $changelog, '<p><br><b><strong><i><em><u><ul><ol><li><h1><h2><h3><h4><h5><h6><span><div><a><pre><code><hr>' );
echo $changelog;
?>
</div>
</div>

View File

@@ -153,15 +153,9 @@ class LayoutEngine
{ {
$category = $categoryRepo->frontCategoryDetails( (int)\Shared\Helpers\Helpers::get( 'category' ), $lang_id ); $category = $categoryRepo->frontCategoryDetails( (int)\Shared\Helpers\Helpers::get( 'category' ), $lang_id );
if ( $category['language']['meta_title'] ) $page = self::applyEntityMetaToPage( $page, isset( $category['language'] ) ? $category['language'] : null, isset( $category['language']['title'] ) ? $category['language']['title'] : '' );
$page['language']['title'] = $category['language']['meta_title'];
else
$page['language']['title'] = $category['language']['title'];
$page['show_title'] = true; $page['show_title'] = true;
$page['language']['meta_keywords'] = $category['language']['meta_keywords']; $page['language']['page_title'] = !empty( $category['language']['category_title'] ) ? $category['language']['category_title'] : ( isset( $category['language']['title'] ) ? $category['language']['title'] : '' );
$page['language']['meta_description'] = $category['language']['meta_description'];
$page['language']['page_title'] = $category['language']['category_title'] ? $category['language']['category_title'] : $category['language']['title'];
// CANONICAL // CANONICAL
$html = str_replace( '[CANONICAL]', '', $html ); $html = str_replace( '[CANONICAL]', '', $html );
@@ -175,14 +169,8 @@ class LayoutEngine
{ {
$article = $articleRepo->articleDetailsFrontend( (int)\Shared\Helpers\Helpers::get( 'article' ), $lang_id ); $article = $articleRepo->articleDetailsFrontend( (int)\Shared\Helpers\Helpers::get( 'article' ), $lang_id );
if ( $article['language']['meta_title'] ) $page = self::applyEntityMetaToPage( $page, isset( $article['language'] ) ? $article['language'] : null, isset( $article['language']['title'] ) ? $article['language']['title'] : '' );
$page['language']['title'] = $article['language']['meta_title'];
else
$page['language']['title'] = $article['language']['title'];
$page['show_title'] = false; $page['show_title'] = false;
$page['language']['meta_keywords'] = $article['language']['meta_keywords'];
$page['language']['meta_description'] = $article['language']['meta_description'];
// CANONICAL // CANONICAL
$html = str_replace( '[CANONICAL]', '', $html ); $html = str_replace( '[CANONICAL]', '', $html );
@@ -196,14 +184,8 @@ class LayoutEngine
$permutation_hash = isset( $_GET['permutation_hash'] ) ? str_replace( '_', '|', $_GET['permutation_hash'] ) : null; $permutation_hash = isset( $_GET['permutation_hash'] ) ? str_replace( '_', '|', $_GET['permutation_hash'] ) : null;
$product = ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->findCached( \Shared\Helpers\Helpers::get( 'product' ), $lang_id, $permutation_hash ); $product = ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->findCached( \Shared\Helpers\Helpers::get( 'product' ), $lang_id, $permutation_hash );
if ( $product['language']['meta_title'] ) $page = self::applyEntityMetaToPage( $page, isset( $product['language'] ) ? $product['language'] : null, isset( $product['language']['name'] ) ? $product['language']['name'] : '' );
$page['language']['title'] = $product['language']['meta_title'];
else
$page['language']['title'] = $product['language']['name'];
$page['show_title'] = false; $page['show_title'] = false;
$page['language']['meta_keywords'] = $product['language']['meta_keywords'];
$page['language']['meta_description'] = $product['language']['meta_description'];
// CANONICAL // CANONICAL
if ( $product['language']['canonical'] ) if ( $product['language']['canonical'] )
@@ -440,6 +422,35 @@ class LayoutEngine
] ); ] );
} }
/**
* Przepisuje meta encji (kategoria/artykuł/produkt) do $page['language'].
*
* Dlaczego: domyślne $page jest stroną główną CMS. Jeśli nie nadpiszemy
* meta_title encji (nawet pustym), meta_title homepage wycieka do <title>
* na podstronie kategorii/produktu (linia podstawienia [TITLE]).
*
* @param array $page obecne $page (z homepage lub session)
* @param array|null $entityLanguage wiersz *_langs encji (może być null)
* @param string $fallbackTitle nazwa encji używana jako $page.language.title
* @return array zmodyfikowany $page
*/
public static function applyEntityMetaToPage( $page, $entityLanguage, $fallbackTitle )
{
if ( !is_array( $page ) ) {
$page = [];
}
if ( !isset( $page['language'] ) or !is_array( $page['language'] ) ) {
$page['language'] = [];
}
$page['language']['title'] = $fallbackTitle;
$page['language']['meta_title'] = is_array( $entityLanguage ) && isset( $entityLanguage['meta_title'] ) ? $entityLanguage['meta_title'] : null;
$page['language']['meta_keywords'] = is_array( $entityLanguage ) && isset( $entityLanguage['meta_keywords'] ) ? $entityLanguage['meta_keywords'] : null;
$page['language']['meta_description'] = is_array( $entityLanguage ) && isset( $entityLanguage['meta_description'] ) ? $entityLanguage['meta_description'] : null;
return $page;
}
public static function alert() public static function alert()
{ {
if ( $alert = \Shared\Helpers\Helpers::get_session( 'alert' ) ) if ( $alert = \Shared\Helpers\Helpers::get_session( 'alert' ) )

56
change.md Normal file
View File

@@ -0,0 +1,56 @@
# Zmiana 3: Naprawa JS na stronie /admin/update/main_view/ (przyciski aktualizacji + Wyczyść cache)
## Plik
`admin/templates/update/main-view.php`
## Problem
Na podstronie `/admin/update/main_view/` nie działały:
- przycisk **„Aktualizuj do wyższej wersji"** (`#confirm`)
- przycisk **„Aktualizuj do najwyższej wersji"** (`#confirmUpdateAll`)
- globalny przycisk **„Wyczyść cache"** (`#clear-cache-btn`) z headera admina
W konsoli przeglądarki **nie było żadnego błędu JS**. Kliknięcie powodowało jedynie dopisanie `#` do URL-a (`/admin/update/main_view/#`).
## Przyczyna
W szablonie znajdowało się pobieranie zdalnego changeloga przez `file_get_contents` z `https://shoppro.project-dc.pl/updates/changelog.php`:
```php
<?= @file_get_contents( 'https://shoppro.project-dc.pl/updates/changelog.php?ver=' . $this->ver ); ?>
```
Odpowiedź zdalnego serwera zawierała niezbalansowany HTML (np. niezamknięty `<script>`, `<!--`, `<style>` albo `<textarea>`), który „połykał" całą resztę dokumentu jako tekst wewnątrz tego nieparzystego tagu. Skutki:
- Inline-script z handlerami przycisków aktualizacji (zaraz pod changelogiem) nie był parsowany jako JS.
- Footer-script z `main-layout.php` (handler `#clear-cache-btn` i globalne wyszukiwanie) też był „w środku" połkniętego HTML i nigdy się nie uruchamiał na tej podstronie.
- Konsola nie pokazywała błędu, bo to nie był błąd JS — to był po prostu HTML, który nigdy nie został wykonany jako skrypt.
## Co zmieniono
### 1. Blok `<script>` z handlerami przeniesiony przed sekcję Changelog
Wcześniej kolejność w szablonie była: panel z wersjami → log → **changelog (zdalny)**`<script>`. Po zmianie: panel z wersjami → log → **`<script>`** → changelog (zdalny). Dzięki temu handlery `#confirm` i `#confirmUpdateAll` podpinają się niezależnie od tego, co zwróci zdalny serwer.
### 2. Sanitizacja odpowiedzi zdalnego changeloga
Przed wyrenderowaniem changelog jest czyszczony:
```php
$changelog = (string) @file_get_contents( 'https://shoppro.project-dc.pl/updates/changelog.php?ver=' . $this->ver );
$changelog = preg_replace( '/<!--.*?-->/s', '', $changelog );
$changelog = strip_tags( $changelog, '<p><br><b><strong><i><em><u><ul><ol><li><h1><h2><h3><h4><h5><h6><span><div><a><pre><code><hr>' );
echo $changelog;
```
- Usuwane są komentarze HTML (`<!--…-->`).
- `strip_tags` z wąską białą listą zostawia tylko tagi formatujące — wszystkie tagi strukturalne i mogące „połknąć" treść (`<script>`, `<style>`, `<textarea>`, `<iframe>`, `<body>`, `</html>` itp.) są usuwane.
### 3. Naprawiony też skutek uboczny
Po tych zmianach poprawnie działa również:
- footer-script z `main-layout.php` (przycisk **Wyczyść cache** + globalne wyszukiwanie produktów/zamówień + sprawdzanie aktualizacji w menu)
## Efekt
- Przyciski aktualizacji systemu na `/admin/update/main_view/` działają.
- Przycisk „Wyczyść cache" działa na tej (i każdej innej) podstronie admina.
- Zdalny changelog ze `shoppro.project-dc.pl` nadal się wyświetla, ale nie może już popsuć struktury strony.
- Dodatkowy zysk bezpieczeństwa: zdalny serwis nie może wstrzyknąć JS w panel admina.
## Po wgraniu
1. Hard-reload przeglądarki (Ctrl+F5) — stary HTML siedzi w cache klienta.
2. Restart PHP-FPM / wyczyszczenie OPcache — inaczej stary skompilowany szablon dalej będzie serwowany.

View File

@@ -0,0 +1,110 @@
<?php
namespace Tests\Unit\Front;
use PHPUnit\Framework\TestCase;
require_once __DIR__ . '/../../../autoload/front/LayoutEngine.php';
/**
* Phase 19 — fix metatagów na stronach kategorii/produktu/artykułu.
*
* Bug: domyślne $page jest stroną główną. W gałęziach category/article/product
* w LayoutEngine::show() nadpisywany był tylko title, a meta_title homepage
* żył dalej w $page['language']['meta_title'] i wygrywał w substytucji [TITLE].
*
* Fix: applyEntityMetaToPage() zawsze nadpisuje meta_title/meta_keywords/
* meta_description encji (nawet pustymi/null), żeby nic z homepage nie wyciekało.
*/
class LayoutEngineMetaTagsTest extends TestCase
{
public function testHomepageMetaTitleDoesNotLeakWhenEntityHasOwnMetaTitle()
{
$page = $this->homepagePage();
$category = [
'meta_title' => 'Sen i otulenie — kocyki minky',
'meta_keywords' => 'kocyki, otulacze',
'meta_description' => 'Najwyższej jakości kocyki minky',
'title' => 'Sen i otulenie',
];
$result = \front\LayoutEngine::applyEntityMetaToPage($page, $category, $category['title']);
$this->assertSame('Sen i otulenie — kocyki minky', $result['language']['meta_title']);
$this->assertSame('Sen i otulenie', $result['language']['title']);
$this->assertSame('kocyki, otulacze', $result['language']['meta_keywords']);
$this->assertSame('Najwyższej jakości kocyki minky', $result['language']['meta_description']);
}
public function testHomepageMetaTitleIsClearedWhenEntityHasNoMetaTitle()
{
$page = $this->homepagePage();
$category = [
'meta_title' => null,
'meta_keywords' => 'description',
'meta_description' => 'keywords',
'title' => 'Sen i otulenie',
];
$result = \front\LayoutEngine::applyEntityMetaToPage($page, $category, $category['title']);
$this->assertNull(
$result['language']['meta_title'],
'meta_title homepage nie może wyciekać gdy kategoria nie ma własnego'
);
$this->assertSame('Sen i otulenie', $result['language']['title']);
}
public function testProductWithAllMetaNullClearsHomepageValues()
{
$page = $this->homepagePage();
$productLang = [
'meta_title' => null,
'meta_keywords' => null,
'meta_description' => null,
'name' => 'Kocyk niemowlaka - Szczeniak z balonikiem - Fuksja',
];
$result = \front\LayoutEngine::applyEntityMetaToPage($page, $productLang, $productLang['name']);
$this->assertNull($result['language']['meta_title']);
$this->assertNull($result['language']['meta_keywords']);
$this->assertNull($result['language']['meta_description']);
$this->assertSame($productLang['name'], $result['language']['title']);
}
public function testNullEntityLanguageDoesNotCrashAndClearsMeta()
{
$page = $this->homepagePage();
$result = \front\LayoutEngine::applyEntityMetaToPage($page, null, 'Fallback');
$this->assertSame('Fallback', $result['language']['title']);
$this->assertNull($result['language']['meta_title']);
$this->assertNull($result['language']['meta_keywords']);
$this->assertNull($result['language']['meta_description']);
}
public function testEmptyPageInputCreatesLanguageStructure()
{
$result = \front\LayoutEngine::applyEntityMetaToPage([], ['meta_title' => 'X'], 'T');
$this->assertIsArray($result);
$this->assertIsArray($result['language']);
$this->assertSame('X', $result['language']['meta_title']);
$this->assertSame('T', $result['language']['title']);
}
private function homepagePage()
{
return [
'id' => 6,
'language' => [
'title' => 'Home',
'meta_title' => 'Sklep z akcesoriami dla dzieci i niemowląt, kocyki minky, poduszki, ubranka',
'meta_keywords' => '',
'meta_description' => 'Marianek to sklep internetowy z artykułami dla dzieci...',
'page_title' => null,
],
];
}
}

BIN
updates/.DS_Store vendored Normal file

Binary file not shown.

View File

@@ -0,0 +1,2 @@
ALTER TABLE pp_shop_payment_methods ADD COLUMN min_order_amount DECIMAL(10,2) DEFAULT NULL;
ALTER TABLE pp_shop_payment_methods ADD COLUMN max_order_amount DECIMAL(10,2) DEFAULT NULL;

View File

@@ -0,0 +1,5 @@
ALTER TABLE pp_log ADD COLUMN `action` VARCHAR(100) NULL DEFAULT NULL AFTER `id`;
ALTER TABLE pp_log ADD COLUMN `order_id` INT NULL DEFAULT NULL AFTER `action`;
ALTER TABLE pp_log ADD COLUMN `context` TEXT NULL DEFAULT NULL AFTER `message`;
ALTER TABLE pp_log ADD INDEX `idx_action` (`action`);
ALTER TABLE pp_log ADD INDEX `idx_order_id` (`order_id`);

View File

@@ -0,0 +1 @@
ALTER TABLE `pp_shop_products_custom_fields` ADD COLUMN `type` VARCHAR(30) NOT NULL DEFAULT '' AFTER `name`;

BIN
updates/0.30/ver_0.350.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,26 @@
{
"changelog": "FIX - linki produktow z permutacja w feedzie Google (separator _)",
"version": "0.350",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"autoload/Domain/Product/ProductRepository.php",
"autoload/Shared/Helpers/Helpers.php",
"autoload/front/LayoutEngine.php",
"templates/shop-product/_partial/product-attribute.php"
]
},
"checksum_zip": "sha256:0cf918bb070260a8e055a1c8f0f3407532008a5a920da588a14c1f3b11e02c59",
"sql": [
],
"date": "2026-04-30",
"directories_deleted": [
]
}

BIN
updates/0.30/ver_0.351.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,23 @@
{
"changelog": "FIX - poprawne metatagi \u003ctitle\u003e/\u003cdescription\u003e/\u003ckeywords\u003e dla stron kategorii i produktow (eliminacja wycieku meta_title homepage)",
"version": "0.351",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"autoload/front/LayoutEngine.php"
]
},
"checksum_zip": "sha256:046d0504accf29a57fea155ecaa2acfe09078379e43118af7bf6c0f4831a3e5f",
"sql": [
],
"date": "2026-05-13",
"directories_deleted": [
]
}

BIN
updates/0.30/ver_0.352.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,23 @@
{
"changelog": "FIX - poprawne polskie znaki w labelkach formularza edycji kategorii w panelu admina (mojibake)",
"version": "0.352",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"admin/templates/shop-category/category-edit.php"
]
},
"checksum_zip": "sha256:4b51cb1508978f6dc9659ce26c143961eaed6c3d3bb27b47623eaea5b4fa4855",
"sql": [
],
"date": "2026-05-13",
"directories_deleted": [
]
}

View File

@@ -1,3 +1,12 @@
<b>ver. 0.352 - 13.05.2026</b><br />
FIX - poprawne polskie znaki w labelkach formularza edycji kategorii w panelu admina (mojibake)
<hr>
<b>ver. 0.351 - 13.05.2026</b><br />
FIX - poprawne metatagi <title>/<description>/<keywords> dla stron kategorii i produktow (eliminacja wycieku meta_title homepage)
<hr>
<b>ver. 0.350 - 30.04.2026</b><br />
FIX - linki produktow z permutacja w feedzie Google (separator _)
<hr>
<b>ver. 0.349 - 20.04.2026</b><br /> <b>ver. 0.349 - 20.04.2026</b><br />
Naprawa kosztu transportu na podsumowaniu koszyka (delivery_free z uwzglednieniem progu) Naprawa kosztu transportu na podsumowaniu koszyka (delivery_free z uwzglednieniem progu)
<hr> <hr>

View File

@@ -1,5 +1,5 @@
<? <?
$current_ver = 349; $current_ver = 352;
for ($i = 1; $i <= $current_ver; $i++) for ($i = 1; $i <= $current_ver; $i++)
{ {