Files
wyszynskiego12.pagedev.pl/.paul/phases/01-historia-cen/01-01-PLAN.md
Jacek Pyziak 972c69b136 feat(v0.1): historia cen + jawnosc cen — milestone Initial Release
Historia cen:
- Tabela wp_price_history z WP Cronem dziennym (snapshot cen)
- AJAX endpoint apartamenty_get_price_history (zabezpieczony nonce)
- Popup "Historia cen" w widgecie — vanilla JS, modal zgodny z projektem

Jawnosc cen:
- Endpointy /ceny-mieszkan.xml + /dane-gov-pl.xml (XSD-compliant)
- Pliki MD5 dla obu XML
- Strona admina: Narzedzia -> Jawnosc Cen z URL-ami do Ministerstwa
- Transient cache 1h z inwalidacja przez cron

Dokumentacja: docs/readme.md + docs/jawnosc-cen.md

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-12 15:40:29 +01:00

9.2 KiB

phase, plan, type, wave, depends_on, files_modified, autonomous
phase plan type wave depends_on files_modified autonomous
01-historia-cen 01 execute 1
wp-content/plugins/elementor-addon/elementor-addon.php
true
## Goal Stworzyć backend dla historii cen apartamentów: - tabela `wp_price_history` w bazie danych - WP Cron zapisujący ceny raz dziennie - AJAX endpoint zwracający historię dla danego apartamentu

Purpose

Ceny apartamentów zarządzane są przez ACF (pola information_price, information_price_m2, information_floor_space). Historia musi być zapisywana codziennie, bo ACF nie przechowuje zmian — tylko aktualną wartość.

Output

  • Tabela wp_price_history założona przy aktywacji pluginu
  • Hook apartamenty_record_prices uruchamiany codziennie przez WP Cron
  • AJAX action apartamenty_get_price_history zwracający JSON
## Project Context @.paul/PROJECT.md @.paul/STATE.md

Source Files

@wp-content/plugins/elementor-addon/elementor-addon.php

Struktura danych ACF

Każdy apartament (post_type: apartamenty) posiada w wp_postmeta:

  • information_price → string np. "677 920" (cena brutto w zł)
  • information_price_m2 → string np. "19 000" (cena za m² w zł)
  • information_floor_space → string np. "35,68" (metraż w m²)

Prefix tabel WP: wp_ DB credentials: z wp-config.php (DB_NAME, DB_USER, DB_PASSWORD, DB_HOST)

<acceptance_criteria>

AC-1: Tabela istnieje w bazie

Given plugin elementor-addon jest aktywny
When WordPress się inicjalizuje
Then tabela wp_price_history istnieje z kolumnami: id, post_id, price, price_m2, floor_space, recorded_at
And istnieje UNIQUE KEY na (post_id, recorded_at) — jeden rekord na apt na dzień

AC-2: Cron zapisuje ceny codziennie

Given istnieją apartamenty z wypełnionymi polami cen ACF
When hook `apartamenty_record_prices` zostanie wywołany
Then dla każdego apartamentu z post_type=apartamenty i post_status=publish
  zostaje wstawiony lub zignorowany (INSERT IGNORE) rekord do wp_price_history
  z aktualną ceną i datą TODAY()

AC-3: AJAX endpoint zwraca historię

Given apartament o ID=X ma rekordy w wp_price_history
When POST na /wp-admin/admin-ajax.php z action=apartamenty_get_price_history i post_id=X
Then odpowiedź JSON zawiera:
  { success: true, data: { title, price, price_m2, history: [{date, price, price_m2}] } }
And historia jest posortowana od najnowszej daty

AC-4: Nonce zabezpiecza AJAX

Given request AJAX bez poprawnego nonce
When POST na admin-ajax.php
Then odpowiedź: { success: false }

</acceptance_criteria>

Task 1: Utwórz tabelę wp_price_history i zarejestruj cron wp-content/plugins/elementor-addon/elementor-addon.php Dodaj na końcu pliku elementor-addon.php (przed zamknięciem) następujące funkcje:
1. **Funkcja tworząca tabelę** `elementor_addon_create_price_history_table()`:
   - Używa `$wpdb->prefix . 'price_history'`
   - SQL z `dbDelta()` (wymaga `require_once ABSPATH . 'wp-admin/includes/upgrade.php'`)
   - Schemat tabeli:
     ```sql
     CREATE TABLE {prefix}price_history (
       id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
       post_id BIGINT UNSIGNED NOT NULL,
       price VARCHAR(50) NOT NULL DEFAULT '',
       price_m2 VARCHAR(50) NOT NULL DEFAULT '',
       floor_space VARCHAR(50) NOT NULL DEFAULT '',
       recorded_at DATE NOT NULL,
       PRIMARY KEY (id),
       UNIQUE KEY unique_daily (post_id, recorded_at),
       KEY idx_post_id (post_id)
     ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
     ```
   - Podpięty do hooków: `register_activation_hook(__FILE__, ...)` ORAZ `init` (z check przez `get_option('elementor_addon_db_version')` — jeśli wersja != '1.0', wywołaj i ustaw opcję)

2. **Funkcja zapisująca ceny** `elementor_addon_record_prices()`:
   - WP_Query: post_type='apartamenty', post_status='publish', posts_per_page=-1, fields='ids'
   - Dla każdego ID: pobiera meta `information_price`, `information_price_m2`, `information_floor_space`
   - Wstawia do tabeli przez `$wpdb->query($wpdb->prepare("INSERT IGNORE INTO ...", ...))`
   - `recorded_at` = `current_time('Y-m-d')`

3. **Rejestracja crona**:
   - Na hooku `wp` (nie `init`): jeśli `!wp_next_scheduled('apartamenty_record_prices')` → `wp_schedule_event(time(), 'daily', 'apartamenty_record_prices')`
   - `add_action('apartamenty_record_prices', 'elementor_addon_record_prices')`

Unikaj: global $wpdb wewnątrz funkcji — używaj `global $wpdb` na początku każdej funkcji korzystającej z $wpdb.
Unikaj: bezpośrednich zapytań SQL bez $wpdb->prepare() tam gdzie są parametry użytkownika.
1. Aktywuj/dezaktywuj plugin lub odwiedź dowolną stronę WP 2. Sprawdź przez phpMyAdmin lub: `SELECT * FROM wp_price_history LIMIT 5;` 3. Sprawdź: `SELECT option_value FROM wp_options WHERE option_name = 'elementor_addon_db_version';` → powinno zwrócić '1.0' 4. Sprawdź cron: `SELECT * FROM wp_options WHERE option_name = 'cron';` — powinien zawierać 'apartamenty_record_prices' AC-1 i AC-2 spełnione: tabela istnieje, cron zarejestrowany Task 2: AJAX endpoint historii cen wp-content/plugins/elementor-addon/elementor-addon.php Dodaj na końcu pliku `elementor-addon.php` funkcję `elementor_addon_get_price_history_ajax()`:
1. **Weryfikacja nonce**: `check_ajax_referer('apartamenty_price_history_nonce', 'nonce', false)` — jeśli false: `wp_send_json_error()` i `die()`

2. **Walidacja post_id**: `$post_id = absint($_POST['post_id'] ?? 0)` — jeśli 0: `wp_send_json_error()`

3. **Pobierz tytuł i aktualne ceny**:
   - `get_the_title($post_id)`
   - `get_post_meta($post_id, 'information_price', true)`
   - `get_post_meta($post_id, 'information_price_m2', true)`
   - `get_post_meta($post_id, 'information_floor_space', true)`

4. **Pobierz historię z DB**:
   ```php
   $rows = $wpdb->get_results($wpdb->prepare(
       "SELECT recorded_at, price, price_m2, floor_space FROM {$wpdb->prefix}price_history
        WHERE post_id = %d ORDER BY recorded_at DESC LIMIT 50",
       $post_id
   ));
   ```

5. **Zwróć JSON**:
   ```php
   wp_send_json_success([
       'title'      => get_the_title($post_id),
       'price'      => get_post_meta($post_id, 'information_price', true),
       'price_m2'   => get_post_meta($post_id, 'information_price_m2', true),
       'floor_space'=> get_post_meta($post_id, 'information_floor_space', true),
       'history'    => $rows,
   ]);
   ```

6. **Zarejestruj akcję** (dla zalogowanych i niezalogowanych):
   ```php
   add_action('wp_ajax_apartamenty_get_price_history', 'elementor_addon_get_price_history_ajax');
   add_action('wp_ajax_nopriv_apartamenty_get_price_history', 'elementor_addon_get_price_history_ajax');
   ```

7. **Przekaż nonce do JS** przez `wp_localize_script` na istniejący skrypt `elementor-addon-main-js`:
   Dodaj nowy hook `wp_enqueue_scripts` z `wp_localize_script('elementor-addon-main-js', 'apartamentsData', ['ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('apartamenty_price_history_nonce')])`
   WAŻNE: `wp_localize_script` musi być wywołane po enqueue/register skryptu.
Wykonaj w przeglądarce lub przez curl (po uzyskaniu nonce): POST /wp-admin/admin-ajax.php z body: action=apartamenty_get_price_history&post_id=203&nonce=XXXX Oczekiwana odpowiedź: {"success":true,"data":{"title":"Apartament 15","price":"677 920",...}} AC-3 i AC-4 spełnione: endpoint zwraca dane JSON, nonce weryfikowany

DO NOT CHANGE

  • wp-content/plugins/elementor-addon/widgets/apartaments.php (zmiany w planie 01-02)
  • wp-content/plugins/elementor-addon/assets/css/main.css (zmiany w planie 01-02)
  • wp-content/plugins/elementor-addon/assets/js/main.js (zmiany w planie 01-02)
  • wp-config.php
  • Inne wtyczki i motyw

SCOPE LIMITS

  • Ten plan nie dodaje UI ani CSS — tylko backend PHP
  • Nie modyfikuj istniejących funkcji w elementor-addon.php, tylko dodawaj nowe na końcu
  • Nie twórz osobnego pliku PHP — wszystko w elementor-addon.php
Before declaring plan complete: - [ ] Tabela `wp_price_history` istnieje w bazie (sprawdź przez phpMyAdmin lub SQL) - [ ] Opcja `elementor_addon_db_version` = '1.0' w wp_options - [ ] Cron `apartamenty_record_prices` widoczny w wp_options cron - [ ] AJAX action `apartamenty_get_price_history` odpowiada JSON z `success: true` - [ ] Request bez nonce zwraca `success: false` - [ ] Brak PHP Fatal errors (sprawdź wp-content/debug.log lub WP_DEBUG)

<success_criteria>

  • Tabela wp_price_history utworzona z poprawnym schematem
  • Cron rejestruje się automatycznie bez manualnej aktywacji
  • AJAX endpoint działa dla publicznych użytkowników (nopriv)
  • Nonce zabezpiecza endpoint
  • Żadnych błędów PHP </success_criteria>
Po ukończeniu utwórz `.paul/phases/01-historia-cen/01-01-SUMMARY.md`