diff --git a/.gitignore b/.gitignore index e52c598..e4cacb0 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ .vscode/ftp-kr.sync.cache.json +temp/ diff --git a/.paul/ROADMAP.md b/.paul/ROADMAP.md index 449472b..e30daa1 100644 --- a/.paul/ROADMAP.md +++ b/.paul/ROADMAP.md @@ -39,6 +39,7 @@ Status: Planning | 15 | Scontainers edit saves as new record | 1 | Done | 2026-04-18 | | 17 | Cart summary transport cost fix | 1 | Done | 2026-04-20 | | 18 | Google feed permutation URL fix | 1 | Done | 2026-04-30 | +| 19 | Frontend meta tags fix (category + product) | 1 | Done | 2026-05-13 | ## Feature @@ -132,5 +133,11 @@ 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. +### 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ą `` 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)* +*Last updated: 2026-05-13 (Phase 19 complete)* diff --git a/.paul/STATE.md b/.paul/STATE.md index c80b6ce..0c801e8 100644 --- a/.paul/STATE.md +++ b/.paul/STATE.md @@ -5,20 +5,19 @@ 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. -**Current focus:** Phase 18 complete - loop closed +**Current focus:** Phase 19 complete — loop closed ## Current Position Milestone: Hotfix -Milestone: Hotfix -Phase: 18 of 18 (Google feed permutation URL fix) - Complete -Plan: 18-01 complete +Phase: 19 of 19 (Frontend meta tags fix) — Complete +Plan: 19-01 complete 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/19-frontend-meta-tags-fix/19-01-PLAN.md Progress: - Milestone: [##########] 100% (Hotfix rolling) -- Phase 18: [##########] 100% +- Phase 19: [##########] 100% ## Loop Position @@ -45,10 +44,17 @@ Phase 15: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-18] Phase 16: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-19] Phase 17: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-20] Phase 18: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-04-30] +Phase 19: PLAN --> APPLY --> UNIFY ✓ ✓ ✓ [COMPLETE - 2026-05-13] ``` ## Accumulated Context ### Decisions +- 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: 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 @@ -97,7 +103,7 @@ None. ### Blockers/Concerns None. -### Skill Audit (Phase 18) +### Skill Audit (Phase 19) | Expected | Invoked | Notes | |----------|---------|-------| | /feature-dev | ○ | User-approved override (hotfix z konkretną instrukcją) | @@ -105,9 +111,9 @@ None. ## Session Continuity -Last session: 2026-04-30 -Stopped at: Phase 18 complete, loop closed +Last session: 2026-05-13 +Stopped at: Phase 19 complete, loop closed 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/19-frontend-meta-tags-fix/19-01-SUMMARY.md --- *STATE.md — Updated after every significant action* diff --git a/.paul/changelog/2026-05-13.md b/.paul/changelog/2026-05-13.md new file mode 100644 index 0000000..09089f2 --- /dev/null +++ b/.paul/changelog/2026-05-13.md @@ -0,0 +1,21 @@ +# 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) + +## Zmienione pliki + +- `autoload/front/LayoutEngine.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/STATE.md` +- `.paul/ROADMAP.md` +- `.gitignore` diff --git a/.paul/codebase/testing.md b/.paul/codebase/testing.md index 32346c2..f5daff9 100644 --- a/.paul/codebase/testing.md +++ b/.paul/codebase/testing.md @@ -4,8 +4,8 @@ | Metric | Value | |--------|-------| -| Total tests | **841** | -| Total assertions | **2330** | +| Total tests | **846** | +| Total assertions | **2348** | | Framework | PHPUnit 9.6 (`phpunit.phar`) | | Bootstrap | `tests/bootstrap.php` | | Config | `phpunit.xml` | diff --git a/.paul/docs/TECH_CHANGELOG.md b/.paul/docs/TECH_CHANGELOG.md index ce12c11..bdb5dd3 100644 --- a/.paul/docs/TECH_CHANGELOG.md +++ b/.paul/docs/TECH_CHANGELOG.md @@ -2,6 +2,16 @@ > Chronologiczny log zmian technicznych — co i dlaczego. +## 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) - 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. diff --git a/.paul/phases/19-frontend-meta-tags-fix/19-01-PLAN.md b/.paul/phases/19-frontend-meta-tags-fix/19-01-PLAN.md new file mode 100644 index 0000000..049e1ff --- /dev/null +++ b/.paul/phases/19-frontend-meta-tags-fix/19-01-PLAN.md @@ -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]`, ``, `` — 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`). + + + + +## 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 `` przed Task 2 +``` + +## AC-2: Strona kategorii pokazuje własny `` 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> diff --git a/.paul/phases/19-frontend-meta-tags-fix/19-01-SUMMARY.md b/.paul/phases/19-frontend-meta-tags-fix/19-01-SUMMARY.md new file mode 100644 index 0000000..7be8335 --- /dev/null +++ b/.paul/phases/19-frontend-meta-tags-fix/19-01-SUMMARY.md @@ -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` | +| AC-3: Strona produktu pokazuje własny title | Pass | Curl: `/kocyk-niemowlaka-...` → `Kocyk niemowlaka - Szczeniak z balonikiem - Fuksja | shopPRO 1` | +| 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 `` 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* diff --git a/.paul/phases/19-frontend-meta-tags-fix/DIAGNOSTICS.md b/.paul/phases/19-frontend-meta-tags-fix/DIAGNOSTICS.md new file mode 100644 index 0000000..c51ac7a --- /dev/null +++ b/.paul/phases/19-frontend-meta-tags-fix/DIAGNOSTICS.md @@ -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. diff --git a/autoload/front/LayoutEngine.php b/autoload/front/LayoutEngine.php index 3c27ab3..5679298 100644 --- a/autoload/front/LayoutEngine.php +++ b/autoload/front/LayoutEngine.php @@ -153,15 +153,9 @@ class LayoutEngine { $category = $categoryRepo->frontCategoryDetails( (int)\Shared\Helpers\Helpers::get( 'category' ), $lang_id ); - if ( $category['language']['meta_title'] ) - $page['language']['title'] = $category['language']['meta_title']; - else - $page['language']['title'] = $category['language']['title']; - + $page = self::applyEntityMetaToPage( $page, isset( $category['language'] ) ? $category['language'] : null, isset( $category['language']['title'] ) ? $category['language']['title'] : '' ); $page['show_title'] = true; - $page['language']['meta_keywords'] = $category['language']['meta_keywords']; - $page['language']['meta_description'] = $category['language']['meta_description']; - $page['language']['page_title'] = $category['language']['category_title'] ? $category['language']['category_title'] : $category['language']['title']; + $page['language']['page_title'] = !empty( $category['language']['category_title'] ) ? $category['language']['category_title'] : ( isset( $category['language']['title'] ) ? $category['language']['title'] : '' ); // CANONICAL $html = str_replace( '[CANONICAL]', '', $html ); @@ -175,14 +169,8 @@ class LayoutEngine { $article = $articleRepo->articleDetailsFrontend( (int)\Shared\Helpers\Helpers::get( 'article' ), $lang_id ); - if ( $article['language']['meta_title'] ) - $page['language']['title'] = $article['language']['meta_title']; - else - $page['language']['title'] = $article['language']['title']; - + $page = self::applyEntityMetaToPage( $page, isset( $article['language'] ) ? $article['language'] : null, isset( $article['language']['title'] ) ? $article['language']['title'] : '' ); $page['show_title'] = false; - $page['language']['meta_keywords'] = $article['language']['meta_keywords']; - $page['language']['meta_description'] = $article['language']['meta_description']; // CANONICAL $html = str_replace( '[CANONICAL]', '', $html ); @@ -196,14 +184,8 @@ class LayoutEngine $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 ); - if ( $product['language']['meta_title'] ) - $page['language']['title'] = $product['language']['meta_title']; - else - $page['language']['title'] = $product['language']['name']; - + $page = self::applyEntityMetaToPage( $page, isset( $product['language'] ) ? $product['language'] : null, isset( $product['language']['name'] ) ? $product['language']['name'] : '' ); $page['show_title'] = false; - $page['language']['meta_keywords'] = $product['language']['meta_keywords']; - $page['language']['meta_description'] = $product['language']['meta_description']; // 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() { if ( $alert = \Shared\Helpers\Helpers::get_session( 'alert' ) ) diff --git a/tests/Unit/front/LayoutEngineMetaTagsTest.php b/tests/Unit/front/LayoutEngineMetaTagsTest.php new file mode 100644 index 0000000..7c3495a --- /dev/null +++ b/tests/Unit/front/LayoutEngineMetaTagsTest.php @@ -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, + ], + ]; + } +}