Files
shopPRO/.paul/phases/11-datalayer-ga4-fix/11-01-PLAN.md
Jacek 589f9d9a38 feat: DataLayer GA4 analytics fix — poprawka eventów ecommerce
Naprawione eventy purchase, begin_checkout, view_item, add_to_cart
do formatu GA4 (item_id/item_name zamiast id/name, currency PLN,
google_business_vertical, poprawne typy danych).
Dodany nowy event view_cart na stronie koszyka.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 19:01:22 +01:00

10 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous
phase plan type wave depends_on files_modified autonomous
11-datalayer-ga4-fix 01 execute 1
templates/shop-order/order-details.php
templates/shop-basket/summary-view.php
templates/shop-product/product.php
templates/shop-basket/basket.php
true
## Goal Naprawic wszystkie eventy dataLayer ecommerce (purchase, begin_checkout, view_item, add_to_cart) do formatu GA4 oraz dodac brakujacy event view_cart. Poprawki krytyczne dla remarketingu dynamicznego i konwersji.

Purpose

Bez tych poprawek remarketing dynamiczny Google Ads i konwersje GA4 nie dzialaja poprawnie — ceny produktow sa zerowe, klucze itemow niezgodne z GA4, brakuje walut i eventow.

Output

Poprawione 4 szablony PHP z prawidlowymi eventami dataLayer GA4.

## Project Context @.paul/PROJECT.md @.paul/ROADMAP.md @.paul/STATE.md

Source Files

@templates/shop-order/order-details.php (purchase event, lines 164-192) @templates/shop-basket/summary-view.php (begin_checkout event, lines 72-80, 175-187) @templates/shop-product/product.php (view_item lines 273-288, add_to_cart lines 607-625) @templates/shop-basket/basket.php (brak view_cart — do dodania)

Technical Reference

@poprawki_datalayer_projectpro.md (specyfikacja zmian z audytu)

Data Model

Order products (pp_shop_order_products) mają kolumny: product_id, name, price_brutto, price_brutto_promo, quantity. Basket products — surowa tablica z sesji, product data fetchowana przez ProductRepository::findCached().

No specialized flows configured — standard execute plan.

<acceptance_criteria>

AC-1: Purchase event — format GA4 z prawidlowa cena

Given strona potwierdzenia zamowienia /zamowienie/*
When dataLayer.push(purchase) jest wywolany
Then items maja klucze item_id (string), item_name, price (number > 0), quantity (number), google_business_vertical: "retail"
And ecommerce ma currency: "PLN"
And nie ma zduplikowanego klucza value ani hardcoded wartosci

AC-2: Begin_checkout event — format GA4

Given strona /koszyk-podsumowanie z produktami w koszyku
When dataLayer.push(begin_checkout) jest wywolany
Then items maja klucze item_id (string), item_name (zamiast id, name), price (number), quantity (number), google_business_vertical: "retail"

AC-3: View_item event — kompletne dane

Given strona produktu
When dataLayer.push(view_item) jest wywolany
Then ecommerce zawiera currency: "PLN" i value (number)
And items maja price jako number (nie string), google_business_vertical: "retail"

AC-4: Add_to_cart event — poprawne typy

Given klikniecie "dodaj do koszyka" na stronie produktu
When dataLayer.push(add_to_cart) jest wywolany
Then items maja google_business_vertical: "retail"
And quantity jest number (nie string)

AC-5: View_cart event — nowy event na stronie koszyka

Given strona /koszyk z produktami w koszyku
When strona sie zaladuje
Then dataLayer.push({event: "view_cart"}) jest wywolany z currency, value i items w formacie GA4

</acceptance_criteria>

Task 1: Naprawic istniejace eventy dataLayer (purchase, begin_checkout, view_item, add_to_cart) templates/shop-order/order-details.php, templates/shop-basket/summary-view.php, templates/shop-product/product.php **order-details.php (purchase event, linie 167-187):** 1. Usunac hardcoded `value: 25.42` (linia 172) — zostawic tylko dynamiczny `value` z linii 174 2. Zamienic `'id': ` na `item_id: ""` 3. Zamienic `'name': ''` na `item_name: ""` 4. Zamienic logike price na: `price: 0 && (float)$product['price_brutto_promo'] < (float)$product['price_brutto']) ? \Shared\Helpers\Helpers::normalize_decimal($product['price_brutto_promo']) : \Shared\Helpers\Helpers::normalize_decimal($product['price_brutto']);?>` 5. Dodac `google_business_vertical: "retail"` do kazdego itemu 6. Zamienic single quotes na double quotes w kluczach itemow (konsystencja) 7. Dodac `'quantity': ` (rzutowanie na int)
**summary-view.php (begin_checkout items, linie 72-80):**
1. Zamienic `'"id": "' . $product['id']` na `'"item_id": "' . $product['id']`
2. Zamienic `'"name": "' . $product['language']['name']` na `'"item_name": "' . str_replace('"', '', $product['language']['name'])`
3. Dodac `'"google_business_vertical": "retail"'` do kazdego itemu

**product.php (view_item, linie 273-287):**
1. Dodac `currency: "PLN",` do obiektu ecommerce (przed items)
2. Dodac `value: <cena>,` do obiektu ecommerce (po currency)
3. Zmienic `price: '<cena>'` na `price: <cena>` (usunac cudzyslow — number zamiast string)
4. Dodac `google_business_vertical: "retail"` do itemu

**product.php (add_to_cart, linie 607-624):**
1. Dodac `google_business_vertical: "retail"` do itemu
- quantity jest juz prawidlowo number (zmienna JS `quantity` pochodzi z parseInt/parseFloat lub .val() — sprawdzic i ewentualnie dodac parseInt)

**Wazne:** Nie zmieniac struktury warunkow `if ($this->settings['google_tag_manager_id'])` — zostawic identycznie.
**Wazne:** Uzywac normalize_decimal() dla cen (zapewnia format z kropka, nie przecinkiem).
1. Przegladnac wygenerowany HTML kazdego eventu — sprawdzic format kluczy, typy, obecnosc currency i google_business_vertical 2. Sprawdzic brak bledow skladni JS (cudzyslow, przecinki) 3. Testy PHPUnit nie powinny byc dotknięte (zmiany tylko w szablonach) AC-1, AC-2, AC-3, AC-4 satisfied: Wszystkie eventy uzywaja item_id/item_name, price jako number, currency PLN, google_business_vertical Task 2: Dodac event view_cart na stronie koszyka templates/shop-basket/basket.php Dodac dataLayer.push dla view_cart w sekcji `<script>` na poczatku bloku `$(function() {` w basket.php (linia 209).
Implementacja:
1. Dodac blok PHP+JS wewnatrz istniejacego `<script>` (po linii 50, w nowym `<script>` z warunkiem GTM):
```
<? if ( $this -> settings['google_tag_manager_id'] ?? false ): ?>
<? if ( is_array( $this -> basket ) and count( $this -> basket ) ): ?>
<script type="text/javascript">
  dataLayer.push({ ecommerce: null });
  dataLayer.push({
    event: "view_cart",
    ecommerce: {
      currency: "PLN",
      value: [obliczona suma],
      items: [
        // iteracja po $this->basket z fetchem produktu przez ProductRepository
      ]
    }
  });
</script>
<? endif; ?>
<? endif; ?>
```

2. Iterowac po `$this->basket`, dla kazdego elementu pobrac product data (ProductRepository::findCached) i zbudowac item z item_id, item_name, price, quantity, google_business_vertical.

3. Obliczyc value jako sume (price * quantity) wszystkich produktow.

**Uwaga:** basket.php ma dostep do `$this->basket` (raw basket array). Kazdy element ma klucze: 'product-id', 'quantity', 'parent_id', 'attributes'.
Product data nalezy pobrac przez: `(new \Domain\Product\ProductRepository($GLOBALS['mdb']))->findCached((int)$position['product-id'], $lang_id)` — identycznie jak robi basket-details.php.
Uzyc `$GLOBALS['mdb']` i `(new \Domain\Languages\LanguagesRepository($GLOBALS['mdb']))->defaultLanguage()` dla lang_id (lub sprawdzic czy $this->lang_id jest dostepny — jesli nie, pobrac z sesji).

**Wazne:** Dodac nowy `<script>` blok PRZED istniejacym blokiem `<script>` (przed linia 36), nie wewnatrz istniejacego — zeby uniknac konfliktow z jQuery ready i AJAX reload.
**Wazne:** Warunek `settings['google_tag_manager_id']` — uzyc `$settings` (global) lub `$this->settings` — sprawdzic ktore jest dostepne w basket.php (linia 1: `global $settings` sugeruje ze $settings jest dostepny).
1. Otworzyc /koszyk z produktami — sprawdzic w konsoli przegladarki dataLayer na obecnosc view_cart 2. Sprawdzic czy items maja poprawne pola: item_id (string), item_name, price (number), quantity (number), google_business_vertical 3. Sprawdzic czy value = suma cen * ilosci 4. Sprawdzic czy event NIE odapla sie ponownie po AJAX reload koszyka (bo jest w osobnym script poza basket-details) AC-5 satisfied: Event view_cart jest pushowany na stronie /koszyk z pelnym zestawem danych GA4

DO NOT CHANGE

  • autoload/Domain/* (warstwa domenowa — bez zmian)
  • autoload/front/Controllers/* (kontrolery — bez zmian)
  • templates/shop-basket/basket-details.php (AJAX-replaceable — nie dodawac tam skryptow)
  • Logika sesji google-analytics-purchase (purchase dedup)
  • Warunki if ($this->settings['google_tag_manager_id']) — zachowac identycznie

SCOPE LIMITS

  • Tylko eventy dataLayer — nie dodawac/zmieniac Facebook Pixel, gtag, ani innych trackerow
  • Nie zmieniac struktury HTML szablonow
  • Nie dodawac user_data do purchase (opcjonalne w specyfikacji, wymaga osobnej analizy RODO)
  • Nie usuwac/przenosic kodu GADS conversion (nie znaleziono w kodzie — prawdopodobnie w GTM)
  • Nie dodawac nowych eventow poza view_cart (np. remove_from_cart — poza zakresem)
Before declaring plan complete: - [ ] Wszystkie eventy uzywaja item_id (string) i item_name zamiast id/name - [ ] price jest zawsze number (nie string, nie 0 dla prawidlowych produktow) - [ ] currency: "PLN" obecne we wszystkich eventach ecommerce - [ ] google_business_vertical: "retail" w kazdym item - [ ] quantity jest zawsze number - [ ] Nowy event view_cart dziala na /koszyk - [ ] Brak hardcoded value: 25.42 w purchase - [ ] Brak bledow skladni JS w wygenerowanym HTML - [ ] PHPUnit testy przechodzą (./test.ps1)

<success_criteria>

  • All tasks completed
  • All verification checks pass
  • No errors or warnings introduced
  • DataLayer eventy zgodne z formatem GA4 (item_id, item_name, currency, google_business_vertical)
  • Remarketing dynamiczny Google Ads ma prawidlowe ceny produktow </success_criteria>
After completion, create `.paul/phases/11-datalayer-ga4-fix/11-01-SUMMARY.md`