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>
This commit is contained in:
225
.paul/phases/01-historia-cen/01-01-PLAN.md
Normal file
225
.paul/phases/01-historia-cen/01-01-PLAN.md
Normal file
@@ -0,0 +1,225 @@
|
||||
---
|
||||
phase: 01-historia-cen
|
||||
plan: 01
|
||||
type: execute
|
||||
wave: 1
|
||||
depends_on: []
|
||||
files_modified:
|
||||
- wp-content/plugins/elementor-addon/elementor-addon.php
|
||||
autonomous: true
|
||||
---
|
||||
|
||||
<objective>
|
||||
## 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
|
||||
</objective>
|
||||
|
||||
<context>
|
||||
## 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)
|
||||
</context>
|
||||
|
||||
<acceptance_criteria>
|
||||
|
||||
## AC-1: Tabela istnieje w bazie
|
||||
```gherkin
|
||||
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
|
||||
```gherkin
|
||||
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ę
|
||||
```gherkin
|
||||
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
|
||||
```gherkin
|
||||
Given request AJAX bez poprawnego nonce
|
||||
When POST na admin-ajax.php
|
||||
Then odpowiedź: { success: false }
|
||||
```
|
||||
|
||||
</acceptance_criteria>
|
||||
|
||||
<tasks>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 1: Utwórz tabelę wp_price_history i zarejestruj cron</name>
|
||||
<files>wp-content/plugins/elementor-addon/elementor-addon.php</files>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>
|
||||
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'
|
||||
</verify>
|
||||
<done>AC-1 i AC-2 spełnione: tabela istnieje, cron zarejestrowany</done>
|
||||
</task>
|
||||
|
||||
<task type="auto">
|
||||
<name>Task 2: AJAX endpoint historii cen</name>
|
||||
<files>wp-content/plugins/elementor-addon/elementor-addon.php</files>
|
||||
<action>
|
||||
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.
|
||||
</action>
|
||||
<verify>
|
||||
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",...}}
|
||||
</verify>
|
||||
<done>AC-3 i AC-4 spełnione: endpoint zwraca dane JSON, nonce weryfikowany</done>
|
||||
</task>
|
||||
|
||||
</tasks>
|
||||
|
||||
<boundaries>
|
||||
|
||||
## 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
|
||||
|
||||
</boundaries>
|
||||
|
||||
<verification>
|
||||
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)
|
||||
</verification>
|
||||
|
||||
<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>
|
||||
|
||||
<output>
|
||||
Po ukończeniu utwórz `.paul/phases/01-historia-cen/01-01-SUMMARY.md`
|
||||
</output>
|
||||
Reference in New Issue
Block a user