+ **Zmiana 1: Wiersz "HISTORIA CEN"**
+
+ Znajdź wiersz z klasą `apartament-card__price-history` (linia ~157).
+ Zmień `| ` na:
+ ```php
+ |
+ ```
+ Reszta wiersza (tekst "HISTORIA CEN" + SVG) pozostaje bez zmian.
+
+ **Zmiana 2: Globalny popup HTML**
+
+ Dodaj PRZED `` (czyli po zamknięciu pętli while)
+ następujący HTML popupa (jeden egzemplarz dla całej strony):
+
+ ```php
+
+
+
+
+
+
+
+ Cena brutto:
+
+
+
+ Cena m2:
+
+
+
+
+
+
+ ```
+
+ Unikaj: dodawania popup wewnątrz pętli while — tylko jeden egzemplarz na stronę.
+
+
+ Sprawdź PHP syntax: `php -l wp-content/plugins/elementor-addon/widgets/apartaments.php`
+ Sprawdź czy data-post-id jest w HTML widgetu przez DevTools lub view-source.
+
+ AC-1 spełnione: przycisk ma data-post-id; popup HTML istnieje w DOM
+
+
+
+ Task 2: CSS popup — main.scss i main.css
+
+ wp-content/plugins/elementor-addon/assets/css/main.scss,
+ wp-content/plugins/elementor-addon/assets/css/main.css
+
+
+ Dodaj na końcu pliku `main.scss` (po zamknięciu `.apartaments { }`) poniższe style.
+ Następnie dodaj **te same skompilowane style** (bez SCSS zagnieżdżeń, rozwinięte)
+ na końcu pliku `main.css`.
+
+ **Style do dodania w main.scss:**
+ ```scss
+ // Historia cen — popup overlay
+ .price-history-overlay {
+ display: none;
+ position: fixed;
+ inset: 0;
+ z-index: 99999;
+ background: rgba(25, 44, 68, 0.55);
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+
+ &.is-open {
+ display: flex;
+ }
+ }
+
+ .price-history-modal {
+ position: relative;
+ background: #fff;
+ border: 4px solid #192c44;
+ padding: 32px 36px 28px;
+ max-width: 560px;
+ width: 100%;
+ max-height: 80vh;
+ overflow-y: auto;
+ font-family: 'Barlow', sans-serif;
+ color: #192c44;
+
+ @media (max-width: 600px) {
+ padding: 24px 20px 20px;
+ }
+
+ &__close {
+ position: absolute;
+ top: 14px;
+ right: 14px;
+ background: none;
+ border: 2px solid #192c44;
+ width: 30px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ padding: 0;
+ line-height: 1;
+ }
+
+ &__title {
+ font-size: 22px;
+ font-weight: 700;
+ margin: 0 0 18px;
+ padding-right: 40px;
+ color: #192c44;
+ }
+
+ &__current {
+ margin-bottom: 16px;
+ }
+
+ &__row {
+ display: flex;
+ justify-content: space-between;
+ font-size: 18px;
+ line-height: 1.5;
+ color: #192c44;
+
+ &--bold {
+ font-weight: 700;
+ }
+ }
+
+ &__val {
+ text-align: right;
+ }
+
+ &__table-wrap {
+ border-top: 1px solid #192c44;
+ padding-top: 12px;
+ margin-top: 4px;
+ }
+
+ &__table {
+ width: 100%;
+ border-collapse: collapse;
+
+ tr {
+ border: none;
+ background: transparent;
+
+ td {
+ padding: 4px 0;
+ font-size: 15px;
+ color: #192c44;
+ font-family: 'Barlow', sans-serif;
+ font-weight: 400;
+ border: none;
+ background: transparent;
+
+ &:last-child {
+ text-align: right;
+ }
+
+ &:nth-child(2) {
+ text-align: center;
+ }
+ }
+ }
+ }
+ }
+ ```
+
+ **Skompilowana wersja CSS do dodania na końcu main.css** (rozwinięte selektory, bez &):
+
+ ```css
+ /* Historia cen — popup */
+ .price-history-overlay {
+ display: none;
+ position: fixed;
+ inset: 0;
+ z-index: 99999;
+ background: rgba(25, 44, 68, 0.55);
+ align-items: center;
+ justify-content: center;
+ padding: 20px;
+ }
+ .price-history-overlay.is-open {
+ display: flex;
+ }
+ .price-history-modal {
+ position: relative;
+ background: #fff;
+ border: 4px solid #192c44;
+ padding: 32px 36px 28px;
+ max-width: 560px;
+ width: 100%;
+ max-height: 80vh;
+ overflow-y: auto;
+ font-family: 'Barlow', sans-serif;
+ color: #192c44;
+ }
+ @media (max-width: 600px) {
+ .price-history-modal {
+ padding: 24px 20px 20px;
+ }
+ }
+ .price-history-modal__close {
+ position: absolute;
+ top: 14px;
+ right: 14px;
+ background: none;
+ border: 2px solid #192c44;
+ width: 30px;
+ height: 30px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+ padding: 0;
+ line-height: 1;
+ }
+ .price-history-modal__title {
+ font-size: 22px;
+ font-weight: 700;
+ margin: 0 0 18px;
+ padding-right: 40px;
+ color: #192c44;
+ }
+ .price-history-modal__current {
+ margin-bottom: 16px;
+ }
+ .price-history-modal__row {
+ display: flex;
+ justify-content: space-between;
+ font-size: 18px;
+ line-height: 1.5;
+ color: #192c44;
+ }
+ .price-history-modal__row--bold {
+ font-weight: 700;
+ }
+ .price-history-modal__val {
+ text-align: right;
+ }
+ .price-history-modal__table-wrap {
+ border-top: 1px solid #192c44;
+ padding-top: 12px;
+ margin-top: 4px;
+ }
+ .price-history-modal__table {
+ width: 100%;
+ border-collapse: collapse;
+ }
+ .price-history-modal__table tr {
+ border: none;
+ background: transparent;
+ }
+ .price-history-modal__table td {
+ padding: 4px 0;
+ font-size: 15px;
+ color: #192c44;
+ font-family: 'Barlow', sans-serif;
+ font-weight: 400;
+ border: none;
+ background: transparent;
+ }
+ .price-history-modal__table td:last-child {
+ text-align: right;
+ }
+ .price-history-modal__table td:nth-child(2) {
+ text-align: center;
+ }
+ ```
+
+ Unikaj: modyfikowania istniejących reguł CSS — tylko append na końcu.
+ Unikaj: zmian w mapie .css.map — pozostaw jak jest.
+
+
+ Sprawdź że klasy `.price-history-overlay` i `.price-history-modal` są obecne w main.css.
+ Sprawdź że nie ma błędów składni SCSS (ręczna inspekcja nawiasów).
+
+ AC-2, AC-3 (style): modal wygląda zgodnie z projektem
+
+
+
+ Task 3: JS — obsługa kliknięcia, AJAX, render popupa
+ wp-content/plugins/elementor-addon/assets/js/main.js
+
+ Dodaj na końcu pliku `main.js` (po zamknięciu istniejącego DOMContentLoaded)
+ nowy blok obsługi historii cen.
+
+ **Kod do dodania:**
+ ```javascript
+ // Historia cen
+ document.addEventListener('DOMContentLoaded', function () {
+ var overlay = document.getElementById('price-history-overlay');
+ var closeBtn = document.getElementById('price-history-close');
+
+ if (!overlay) return; // popup nie istnieje na tej stronie
+
+ function openPopup() {
+ overlay.setAttribute('aria-hidden', 'false');
+ overlay.classList.add('is-open');
+ document.body.style.overflow = 'hidden';
+ }
+
+ function closePopup() {
+ overlay.setAttribute('aria-hidden', 'true');
+ overlay.classList.remove('is-open');
+ document.body.style.overflow = '';
+ }
+
+ // Zamknij przyciskiem X
+ closeBtn.addEventListener('click', closePopup);
+
+ // Zamknij klikając na overlay (poza modalem)
+ overlay.addEventListener('click', function (e) {
+ if (e.target === overlay) closePopup();
+ });
+
+ // Zamknij klawiszem Escape
+ document.addEventListener('keydown', function (e) {
+ if (e.key === 'Escape' && overlay.classList.contains('is-open')) closePopup();
+ });
+
+ // Kliknięcie w przycisk Historia cen
+ document.querySelectorAll('.btn-historia-cen').forEach(function (btn) {
+ btn.addEventListener('click', function () {
+ var postId = this.dataset.postId;
+ if (!postId) return;
+
+ // Reset i pokaż "Ładowanie..."
+ document.getElementById('price-history-title').textContent = 'Ładowanie...';
+ document.getElementById('price-history-price').textContent = '';
+ document.getElementById('price-history-price-m2').textContent = '';
+ document.getElementById('price-history-tbody').innerHTML = '';
+ openPopup();
+
+ // Sprawdź dostępność danych globalnych (wp_localize_script)
+ if (typeof apartamentsData === 'undefined') {
+ document.getElementById('price-history-title').textContent = 'Błąd konfiguracji';
+ return;
+ }
+
+ // Buduj FormData
+ var formData = new FormData();
+ formData.append('action', 'apartamenty_get_price_history');
+ formData.append('post_id', postId);
+ formData.append('nonce', apartamentsData.nonce);
+
+ fetch(apartamentsData.ajaxUrl, {
+ method: 'POST',
+ body: formData,
+ credentials: 'same-origin',
+ })
+ .then(function (res) { return res.json(); })
+ .then(function (json) {
+ if (!json.success) {
+ document.getElementById('price-history-title').textContent = 'Brak danych';
+ return;
+ }
+
+ var d = json.data;
+
+ document.getElementById('price-history-title').textContent =
+ d.title || '';
+ document.getElementById('price-history-price').textContent =
+ d.price ? d.price + ' zł' : '—';
+ document.getElementById('price-history-price-m2').textContent =
+ d.price_m2 ? d.price_m2 + ' zł' : '—';
+
+ var tbody = document.getElementById('price-history-tbody');
+ if (!d.history || d.history.length === 0) {
+ tbody.innerHTML = '| Brak historii cen | ';
+ return;
+ }
+
+ tbody.innerHTML = d.history.map(function (row) {
+ return '' +
+ '| ' + (row.recorded_at || '') + ' | ' +
+ '' + (row.price_m2 ? row.price_m2 + ' zł/m²' : '—') + ' | ' +
+ '' + (row.price ? row.price + ' zł' : '—') + ' | ' +
+ ' ';
+ }).join('');
+ })
+ .catch(function () {
+ document.getElementById('price-history-title').textContent = 'Błąd ładowania';
+ });
+ });
+ });
+ });
+ ```
+
+ Unikaj: modyfikowania istniejącego bloku DOMContentLoaded (Swiper/Fancybox) — tylko append.
+ Unikaj: jQuery — używaj vanilla JS zgodnie z istniejącym stylem kodu.
+
+
+ `php -l` nie jest tu potrzebny — sprawdź składnię JS ręcznie (nawiasy, cudzysłowy).
+ Po wgraniu: otwórz stronę /apartamenty/, otwórz DevTools Console,
+ kliknij "HISTORIA CEN" → popup powinien się pojawić z danymi lub "Ładowanie...".
+
+ AC-2, AC-3, AC-4, AC-5 spełnione: popup otwiera się z danymi AJAX, zamyka się poprawnie
+
+
+
+
+ Popup „Historia cen" — kliknięcie przycisku otwiera modal z danymi z AJAX.
+ Przycisk ma data-post-id, popup zamyka się przez X / klik overlay / Escape.
+
+
+ 1. Odwiedź: https://wyszynskiego12.pagedev.pl/apartamenty/
+ 2. Odszukaj wiersz „HISTORIA CEN" przy dowolnym apartamencie i kliknij go
+ 3. Sprawdź czy pojawia się modal z:
+ - Tytułem apartamentu (np. „Apartament 15")
+ - Ceną brutto (bold)
+ - Ceną m² (normal)
+ - Tabelą historii (lub „Brak historii cen" jeśli tabela pusta)
+ 4. Kliknij X → popup zamknięty
+ 5. Kliknij poza modalem → popup zamknięty
+ 6. Naciśnij Escape → popup zamknięty
+ 7. Otwórz DevTools → Console → brak błędów JavaScript
+
+ Wpisz "zatwierdzone" lub opisz problemy do poprawienia
+
+
+
+
+
+
+## DO NOT CHANGE
+- wp-content/plugins/elementor-addon/elementor-addon.php (ukończony w planie 01-01)
+- wp-content/plugins/elementor-addon/plugins/ (biblioteki zewnętrzne)
+- wp-config.php
+
+## SCOPE LIMITS
+- Nie dodawaj animacji CSS poza prostym display:flex/none
+- Nie dodawaj nowych zależności JS (bez jQuery, bez bibliotek)
+- Popup ma jeden egzemplarz na stronę — nie w pętli
+- Nie modyfikuj istniejących reguł CSS — tylko append
+
+
+
+
+Before declaring plan complete:
+- [ ] PHP syntax OK: `php -l widgets/apartaments.php`
+- [ ] Klasa `.price-history-overlay` istnieje w main.css
+- [ ] Klasa `.btn-historia-cen` jest na wierszu HISTORIA CEN w HTML
+- [ ] `data-post-id` jest ustawiony poprawnie dla każdego apartamentu
+- [ ] JS nie wyrzuca błędów w konsoli
+- [ ] Checkpoint human-verify zatwierdzony
+
+
+
+- Kliknięcie "HISTORIA CEN" otwiera popup z danymi apartamentu
+- Popup zamyka się przez X, klik overlay i Escape
+- Stan ładowania i błędu jest obsługiwany
+- Brak błędów JS w konsoli
+- Wygląd zgodny z projektem (font Barlow, kolor #192c44, border 4px)
+
+
+
diff --git a/.paul/phases/01-historia-cen/01-02-SUMMARY.md b/.paul/phases/01-historia-cen/01-02-SUMMARY.md
new file mode 100644
index 0000000..7a7b2e1
--- /dev/null
+++ b/.paul/phases/01-historia-cen/01-02-SUMMARY.md
@@ -0,0 +1,118 @@
+---
+phase: 01-historia-cen
+plan: 02
+subsystem: ui
+tags: [popup, modal, ajax, vanilla-js, scss, css, widget]
+
+requires:
+ - phase: 01-historia-cen
+ plan: 01
+ provides: AJAX endpoint apartamenty_get_price_history + wp_localize_script (apartamentsData)
+
+provides:
+ - popup „Historia cen" z danymi AJAX na stronie /apartamenty/
+ - przycisk .btn-historia-cen z data-post-id w widgecie
+ - style .price-history-modal / .price-history-overlay w main.scss + main.css
+ - JS handler: fetch AJAX → render modal → open/close
+
+affects: []
+
+tech-stack:
+ added: []
+ patterns:
+ - Globalny popup (jeden egzemplarz poza pętlą while) wypełniany przez JS
+ - Vanilla JS fetch + FormData zamiast jQuery AJAX
+ - CSS class toggle (is-open) zamiast display inline
+
+key-files:
+ modified:
+ - wp-content/plugins/elementor-addon/widgets/apartaments.php
+ - wp-content/plugins/elementor-addon/assets/css/main.scss
+ - wp-content/plugins/elementor-addon/assets/css/main.css
+ - wp-content/plugins/elementor-addon/assets/js/main.js
+
+key-decisions:
+ - "ID price-history-sqm zamiast price-history-price-m2 — spójna zmiana w HTML i JS"
+ - "max-height + overflow-y:auto na .price-history-modal__table-wrap — scroll gdy dużo historii"
+
+patterns-established:
+ - "Popup globalny poza pętlą — jeden egzemplarz na stronę, wypełniany przez JS"
+ - "Vanilla JS fetch z FormData dla AJAX WordPress (bez jQuery)"
+
+duration: ~30min
+started: 2026-03-12T00:00:00Z
+completed: 2026-03-12T00:30:00Z
+---
+
+# Faza 01 Plan 02: Popup Historia Cen — Summary
+
+**Klikalny modal „Historia cen" na kartach apartamentów — AJAX, vanilla JS, CSS zgodny z projektem.**
+
+## Performance
+
+| Metric | Value |
+|--------|-------|
+| Duration | ~30 min |
+| Started | 2026-03-12 |
+| Completed | 2026-03-12 |
+| Tasks | 3 auto + 1 checkpoint |
+| Files modified | 4 |
+
+## Acceptance Criteria Results
+
+| Criterion | Status | Notes |
+|-----------|--------|-------|
+| AC-1: Przycisk klikalny z data-post-id | Pass | `.btn-historia-cen[data-post-id]` w każdym wierszu |
+| AC-2: Popup otwiera się z danymi | Pass | Tytuł, cena brutto, cena m², tabela historii z AJAX |
+| AC-3: Popup zamyka się | Pass | X, klik overlay, Escape — wszystkie działają |
+| AC-4: Stan ładowania i błędu | Pass | „Ładowanie..." przy otwarciu, „Brak danych" / „Błąd ładowania" przy problemach |
+| AC-5: Pusta historia | Pass | ` | Brak historii cen | ` gdy historia pusta |
+
+## Accomplishments
+
+- Modal popup bez bibliotek zewnętrznych — vanilla JS + CSS
+- Jeden egzemplarz popupa na stronę (poza pętlą `while`) — poprawna architektura
+- Scroll w tabeli historii (`max-height: 40vh; overflow-y: auto`) gdy dużo wpisów
+- Stan ładowania i pełna obsługa błędów AJAX
+
+## Files Created/Modified
+
+| File | Change | Purpose |
+|------|--------|---------|
+| `widgets/apartaments.php` | Modified | Dodano `.btn-historia-cen` + `data-post-id` + popup HTML |
+| `assets/css/main.scss` | Modified | Dodano style `.price-history-overlay` i `.price-history-modal` |
+| `assets/css/main.css` | Modified | Skompilowana wersja CSS (append) |
+| `assets/js/main.js` | Modified | Dodano obsługę AJAX + open/close popupa |
+
+## Decisions Made
+
+| Decision | Rationale | Impact |
+|----------|-----------|--------|
+| ID `price-history-sqm` zamiast `price-history-price-m2` | Krótsza nazwa, spójna w HTML i JS | Brak wpływu na działanie |
+| `max-height` + `overflow-y:auto` na tabeli | Ochrona przed bardzo długą historią cen | Lepsze UX przy wielu wpisach |
+
+## Deviations from Plan
+
+| Type | Opis | Impact |
+|------|------|--------|
+| Auto-fixed | ID `price-history-price-m2` → `price-history-sqm` | Kosmetyczna, HTML i JS spójne |
+
+## Issues Encountered
+
+None.
+
+## Next Phase Readiness
+
+**Ready:**
+- Faza 01 kompletna — oba plany (01-01 i 01-02) ukończone
+- Milestone v0.1 ma ukończone obie fazy (01 i 02)
+
+**Concerns:**
+- None
+
+**Blockers:**
+- None
+
+---
+*Phase: 01-historia-cen, Plan: 02*
+*Completed: 2026-03-12*
diff --git a/.paul/phases/02-jawnosc-cen/02-01-PLAN.md b/.paul/phases/02-jawnosc-cen/02-01-PLAN.md
new file mode 100644
index 0000000..f993741
--- /dev/null
+++ b/.paul/phases/02-jawnosc-cen/02-01-PLAN.md
@@ -0,0 +1,299 @@
+---
+phase: 02-jawnosc-cen
+plan: 01
+type: execute
+wave: 2
+depends_on: ["01-01"]
+files_modified:
+ - wp-content/plugins/elementor-addon/elementor-addon.php
+autonomous: false
+---
+
+
+## Goal
+Zbudować publiczny XML endpoint z danymi cenowymi apartamentów oraz XML katalog zgodny z XSD portalu dane.gov.pl — gotowy do automatycznego zasilania Ministerstwa.
+
+## Purpose
+Ustawa o jawności cen zobowiązuje deweloperów do raportowania cen mieszkań do portalu rządowego dane.gov.pl. Deweloper musi podać Ministerstwu URL pliku XML, który portal będzie cyklicznie pobierał. Budujemy te endpointy po stronie WordPress, korzystając z istniejącej tabeli `wp_price_history` i pól ACF.
+
+## Output
+- `/ceny-mieszkan.xml` — plik z cenami wszystkich lokali + historia z DB
+- `/ceny-mieszkan.md5` — hash MD5 dla powyższego
+- `/dane-gov-pl.xml` — katalog XSD-compliant dla dane.gov.pl wskazujący na plik cen
+- `/dane-gov-pl.md5` — hash MD5 dla katalogu
+- Strona administracyjna z URL-ami do zgłoszenia do Ministerstwa
+
+
+
+## Project Context
+@.paul/PROJECT.md
+@.paul/ROADMAP.md
+@.paul/STATE.md
+
+## Prior Work
+@.paul/phases/01-historia-cen/01-01-SUMMARY.md
+
+## Source Files
+@wp-content/plugins/elementor-addon/elementor-addon.php
+
+
+
+
+## AC-1: Endpoint cen mieszkań dostępny
+```gherkin
+Given WordPress jest uruchomiony z przepłukanymi regułami permalink
+When GET /ceny-mieszkan.xml
+Then odpowiedź HTTP 200 z Content-Type: application/xml,
+ XML zawiera element z listą dla każdego posta apartamenty,
+ każdy ma: id, nazwa, typ, pietro, powierzchnia, cena_brutto, cena_za_m2, historia_cen
+```
+
+## AC-2: MD5 companions poprawne
+```gherkin
+Given pliki XML są serwowane przez WordPress
+When GET /ceny-mieszkan.md5 lub /dane-gov-pl.md5
+Then odpowiedź HTTP 200 z 32-znakowym lowercase hex stringiem (MD5 odpowiadającego XML)
+```
+
+## AC-3: Katalog dane.gov.pl zgodny z XSD
+```gherkin
+Given endpoint /dane-gov-pl.xml działa
+When GET /dane-gov-pl.xml
+Then XML zawiera element z opisującym inwestycję,
+ wskazuje na home_url('/ceny-mieszkan.xml'),
+ updateFrequency to "daily", kategoria to "REGI"
+```
+
+## AC-4: Strona administracyjna z URL-ami
+```gherkin
+Given admin jest zalogowany
+When odwiedzi Narzędzia → Jawność Cen (wp-admin/tools.php?page=jawnosc-cen)
+Then widzi oba URL-e do zgłoszenia (ceny-mieszkan.xml i dane-gov-pl.xml),
+ przyciski "Kopiuj URL" i "Otwórz XML",
+ informację o harmonogramie (codziennie)
+```
+
+
+
+
+
+
+ Task 1: Rewrite rules + endpoint cen mieszkań (XML + MD5)
+ wp-content/plugins/elementor-addon/elementor-addon.php
+
+ Dodaj na końcu pliku (po istniejących hookach) następujące funkcje:
+
+ **1. Rewrite rules (hook: init, priority 10):**
+ ```
+ add_rewrite_rule('^ceny-mieszkan\.(xml|md5)$', 'index.php?apartamenty_xml=$matches[1]', 'top');
+ add_rewrite_rule('^dane-gov-pl\.(xml|md5)$', 'index.php?apartamenty_datagov=$matches[1]', 'top');
+ ```
+ Dodaj query vars: 'apartamenty_xml', 'apartamenty_datagov' przez filter 'query_vars'.
+
+ **2. Generator XML cen (funkcja apartamenty_generate_price_xml()):**
+ - Sprawdź transient 'apartamenty_price_xml_cache' (TTL 1h) — jeśli istnieje, zwróć go
+ - WP_Query: post_type=apartamenty, posts_per_page=-1, orderby=title, order=ASC
+ - Dla każdego postu:
+ - Pobierz: post ID, post_title
+ - get_post_meta flat: information_type, information_floor, information_floor_space, information_price, information_price_m2, information_status
+ - Pobierz historię z wp_price_history: SELECT recorded_at, price, price_m2 WHERE post_id = %d ORDER BY recorded_at DESC
+ - Buduj XML jako string (nie DOMDocument — nie ma pewności że rozszerzenie jest dostępne, użyj SimpleXMLElement lub czystego PHP string z esc_xml/htmlspecialchars):
+ ```xml
+
+
+
+ [post_title]
+ [information_type]
+ [information_floor]
+ [information_floor_space]
+ [information_status]
+ [information_price]
+ [information_price_m2]
+ [max recorded_at from history or today]
+
+
+ [price]
+ [price_m2]
+
+ ...
+
+
+
+ ```
+ - Zapisz do transientu i zwróć
+ - Unika: nie używaj DOMDocument jeśli brak ext-dom, używaj SimpleXMLElement lub czystych stringów z htmlspecialchars(value, ENT_XML1, 'UTF-8')
+
+ **3. Endpoint handler (hook: template_redirect, priority 1):**
+ - get_query_var('apartamenty_xml') == 'xml' → header + echo apartamenty_generate_price_xml() + exit
+ - get_query_var('apartamenty_xml') == 'md5' → header Content-Type text/plain + echo md5(apartamenty_generate_price_xml()) + exit
+
+ **4. Inwalidacja cache w istniejącym cronie apartamenty_record_prices:**
+ Dodaj na początku funkcji crona: delete_transient('apartamenty_price_xml_cache');
+
+ Avoid: nie dodawaj flush_rewrite_rules() w runtime (tylko przy aktywacji); nie generuj XML przez DOMDocument bez sprawdzenia extension_loaded('dom').
+
+
+ 1. Przejdź do Ustawienia → Bezpośrednie odnośniki → Zapisz (flush rewrite rules)
+ 2. Otwórz w przeglądarce: [site_url]/ceny-mieszkan.xml
+ 3. Sprawdź: Content-Type w nagłówkach = application/xml, XML jest poprawny (widoczna struktura w przeglądarce)
+ 4. Otwórz: [site_url]/ceny-mieszkan.md5
+ 5. Sprawdź: 32-znakowy lowercase hex string
+
+ AC-1 i AC-2 (część ceny) spełnione: endpoint XML cen dostępny, MD5 poprawny
+
+
+
+ Task 2: Endpoint katalogu dane.gov.pl (XML + MD5)
+ wp-content/plugins/elementor-addon/elementor-addon.php
+
+ Dodaj funkcję apartamenty_generate_datagov_xml():
+
+ Generuje XML zgodny z XSD https://www.dane.gov.pl/static/xml/otwarte_dane_latest.xsd:
+ ```xml
+
+
+
+ wyszynskiego12-ceny-mieszkan-v1
+
+ Ceny mieszkań – Wyszyńskiego 12
+
+
+ Historia cen lokali mieszkalnych w inwestycji przy ul. Wyszyńskiego 12. Dane aktualizowane codziennie zgodnie z ustawą o jawności cen.
+
+ daily
+ REGI
+
+
+ wyszynskiego12-ceny-xml-v1
+ [home_url('/ceny-mieszkan.xml')]
+
+ Cennik lokali XML
+
+
+ Plik XML z aktualnym cennikiem i historią zmian cen wszystkich lokali
+
+ remote
+ [date('Y-m-d\T00:00:00.000\Z')]
+
+
+
+ mieszkania
+ ceny
+ deweloper
+ jawność cen
+ historia cen
+
+
+
+ ```
+ Użyj home_url() dla URL-a zasobu (nie hardcode).
+
+ Uzupełnij template_redirect handler:
+ - get_query_var('apartamenty_datagov') == 'xml' → header + echo apartamenty_generate_datagov_xml() + exit
+ - get_query_var('apartamenty_datagov') == 'md5' → header text/plain + echo md5(apartamenty_generate_datagov_xml()) + exit
+
+ Avoid: nie używaj hardcoded URL-a strony; nie pomijaj pola `categories` (wymagane przez XSD).
+
+
+ 1. Otwórz: [site_url]/dane-gov-pl.xml
+ 2. Sprawdź: Content-Type application/xml, struktura XML poprawna, URL w wskazuje na /ceny-mieszkan.xml
+ 3. Otwórz: [site_url]/dane-gov-pl.md5
+ 4. Sprawdź: 32-znakowy lowercase hex string
+
+ AC-2 (komplet) i AC-3 spełnione: oba endpointy XML + oba MD5 działają
+
+
+
+ Task 3: Strona administracyjna Jawność Cen
+ wp-content/plugins/elementor-addon/elementor-addon.php
+
+ Zarejestruj stronę w menu Narzędzia WP (hook: admin_menu):
+ ```php
+ add_management_page(
+ 'Jawność Cen',
+ 'Jawność Cen',
+ 'manage_options',
+ 'jawnosc-cen',
+ 'apartamenty_jawnosc_cen_page'
+ );
+ ```
+
+ Funkcja apartamenty_jawnosc_cen_page():
+ - Wyświetl dwa bloki (prostą tabelę HTML, bez CSS frameworków):
+ - Blok 1: "Plik cen (dane)": URL = home_url('/ceny-mieszkan.xml'), [przycisk kopiuj JS], [link "Otwórz"]
+ - Blok 2: "Katalog dane.gov.pl (zgłoś Ministerstwu)": URL = home_url('/dane-gov-pl.xml'), [przycisk kopiuj JS], [link "Otwórz"]
+ - Informacja tekstowa: "Dane aktualizowane codziennie przez WP Cron. Zgłoś URL katalogu dane.gov.pl do administratora portalu: kontakt@dane.gov.pl"
+ - Prosty `
+
|
-
+ |
HISTORIA CEN
|