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>
226 lines
10 KiB
Markdown
226 lines
10 KiB
Markdown
---
|
|
phase: 11-datalayer-ga4-fix
|
|
plan: 01
|
|
type: execute
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- templates/shop-order/order-details.php
|
|
- templates/shop-basket/summary-view.php
|
|
- templates/shop-product/product.php
|
|
- templates/shop-basket/basket.php
|
|
autonomous: true
|
|
---
|
|
|
|
<objective>
|
|
## 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.
|
|
</objective>
|
|
|
|
<context>
|
|
## 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().
|
|
</context>
|
|
|
|
<skills>
|
|
No specialized flows configured — standard execute plan.
|
|
</skills>
|
|
|
|
<acceptance_criteria>
|
|
|
|
## AC-1: Purchase event — format GA4 z prawidlowa cena
|
|
```gherkin
|
|
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
|
|
```gherkin
|
|
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
|
|
```gherkin
|
|
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
|
|
```gherkin
|
|
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
|
|
```gherkin
|
|
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>
|
|
|
|
<tasks>
|
|
|
|
<task type="auto">
|
|
<name>Task 1: Naprawic istniejace eventy dataLayer (purchase, begin_checkout, view_item, add_to_cart)</name>
|
|
<files>templates/shop-order/order-details.php, templates/shop-basket/summary-view.php, templates/shop-product/product.php</files>
|
|
<action>
|
|
**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': <?= (int)$product['product_id'];?>` na `item_id: "<?= $product['product_id'];?>"`
|
|
3. Zamienic `'name': '<?= $product['name'];?>'` na `item_name: "<?= str_replace('"', '', $product['name']);?>"`
|
|
4. Zamienic logike price na: `price: <?= ((float)$product['price_brutto_promo'] > 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': <?= (int)$product['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).
|
|
</action>
|
|
<verify>
|
|
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)
|
|
</verify>
|
|
<done>AC-1, AC-2, AC-3, AC-4 satisfied: Wszystkie eventy uzywaja item_id/item_name, price jako number, currency PLN, google_business_vertical</done>
|
|
</task>
|
|
|
|
<task type="auto">
|
|
<name>Task 2: Dodac event view_cart na stronie koszyka</name>
|
|
<files>templates/shop-basket/basket.php</files>
|
|
<action>
|
|
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).
|
|
</action>
|
|
<verify>
|
|
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)
|
|
</verify>
|
|
<done>AC-5 satisfied: Event view_cart jest pushowany na stronie /koszyk z pelnym zestawem danych GA4</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<boundaries>
|
|
|
|
## 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)
|
|
|
|
</boundaries>
|
|
|
|
<verification>
|
|
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)
|
|
</verification>
|
|
|
|
<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>
|
|
|
|
<output>
|
|
After completion, create `.paul/phases/11-datalayer-ga4-fix/11-01-SUMMARY.md`
|
|
</output>
|