07-01: Performance — N+1 subqueries, information_schema static, DB indexes 07-02: Stability — SSL verification (4 clients), cron throttle→DB, migration 000014b 07-03: UX — orderpro_to_allegro disable, orders list items 14-17 07-04: Tests — AllegroTokenManager + AllegroOrderImportService unit tests 07-05: InPost ShipmentProviderInterface (replaces allegro_wza workaround) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
11 KiB
11 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous
| phase | plan | type | wave | depends_on | files_modified | autonomous | |||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 07-pre-expansion-fixes | 02 | execute | 1 |
|
true |
Purpose
Każdy nowy klient API dodany bez wzorca SSL verification powiela podatność. Cron throttle w sesji powoduje wielokrotne uruchamianie crona przy wielu aktywnych sesjach. Duplikat migracji wprowadza niejednoznaczność przy deploy na nowe środowisko. Wszystkie trzy to fundament do naprawy przed dodawaniem nowych integracji.
Output
- 4 pliki ApiClient z
CURLOPT_SSL_VERIFYPEER => true+CURLOPT_CAINFO .env.examplezCURL_CA_BUNDLE_PATHApplication::isWebCronThrottled()czytający timestamp zapp_settingszamiast$_SESSION- Nowa migracja seed dla klucza
cron_web_last_run_atwapp_settings - Migracja 000014b (rename duplikatu)
Source Files
@src/Modules/Settings/AllegroApiClient.php @src/Modules/Settings/AllegroOAuthClient.php @src/Core/Application.php @.env.example
<acceptance_criteria>
AC-1: SSL weryfikowany w każdym cURL wywołaniu
Given 4 klasy ApiClient (AllegroApiClient, AllegroOAuthClient, ShopproApiClient, ApaczkaApiClient) wykonują cURL bez CURLOPT_SSL_VERIFYPEER
When dowolny ApiClient wykonuje zapytanie HTTP
Then CURLOPT_SSL_VERIFYPEER jest ustawione na true
AND CURLOPT_CAINFO wskazuje na ścieżkę z .env (lub systemowy bundle jako fallback)
AND .env.example zawiera CURL_CA_BUNDLE_PATH z komentarzem
AC-2: Web cron throttle oparty na DB, nie sesji
Given Application::isWebCronThrottled() zapisuje/czyta timestamp z $_SESSION['cron_web_last_run_at']
When isWebCronThrottled() jest wywołane
Then timestamp jest czytany z app_settings (klucz: cron_web_last_run_at) zamiast $_SESSION
AND zapis po uruchomieniu crona idzie do app_settings (UPDATE lub INSERT ON DUPLICATE KEY)
AND $_SESSION['cron_web_last_run_at'] nie jest już używany
AC-3: Migracja 000014 zdeduplikowana
Given dwa pliki mają sekwencję 000014 w nazwie
When sprawdzasz listę migracji
Then jeden z nich jest przemianowany na 000014b
AND plik zawiera idempotentny INSERT (ON DUPLICATE KEY UPDATE lub INSERT IGNORE)
żeby powtórne uruchomienie na istniejącej bazie nie powodowało błędu
</acceptance_criteria>
Task 1: Dodaj SSL verification do 4 ApiClient klas src/Modules/Settings/AllegroApiClient.php, src/Modules/Settings/AllegroOAuthClient.php, src/Modules/Settings/ShopproApiClient.php, src/Modules/Settings/ApaczkaApiClient.php, .env.example Dla każdej z 4 klas: 1. Odczytaj plik — zidentyfikuj wszystkie `curl_setopt_array($ch, [...])` wywołania 2. Do każdego array dodaj (jeśli nie ma): ```php CURLOPT_SSL_VERIFYPEER => true, CURLOPT_SSL_VERIFYHOST => 2, CURLOPT_CAINFO => $this->getCaBundlePath(), ``` 3. Dodaj prywatną metodę pomocniczą do każdej klasy: ```php private function getCaBundlePath(): string { $envPath = (string) ($_ENV['CURL_CA_BUNDLE_PATH'] ?? ''); if ($envPath !== '' && is_file($envPath)) { return $envPath; } // Windows XAMPP fallback $xamppCa = 'C:/xampp/php/extras/ssl/cacert.pem'; if (is_file($xamppCa)) { return $xamppCa; } // System default (Linux/macOS) return '/etc/ssl/certs/ca-certificates.crt'; } ``` Jeśli wszystkie 4 klasy mają wspólny trait lub klasę bazową — wydziel metodę tam. Jeśli nie — dodaj do każdej osobno (duplikacja jest OK, unikamy przedwczesnej abstrakcji).4. W `.env.example` dodaj sekcję:
```
# SSL/TLS — ścieżka do CA bundle dla cURL
# Windows XAMPP: C:/xampp/php/extras/ssl/cacert.pem
# Linux: /etc/ssl/certs/ca-certificates.crt
CURL_CA_BUNDLE_PATH=
```
WAŻNE: Nie dodawaj CURLOPT_SSL_VERIFYPEER => false nigdzie. Jeśli CA bundle nie istnieje,
metoda getCaBundlePath() zwraca ścieżkę — PHP zgłosi błąd cURL jeśli plik nie istnieje.
To jest pożądane zachowanie (fail-fast, nie silent MITM).
php -l src/Modules/Settings/AllegroApiClient.php
php -l src/Modules/Settings/AllegroOAuthClient.php
php -l src/Modules/Settings/ShopproApiClient.php
php -l src/Modules/Settings/ApaczkaApiClient.php
grep -n "CURLOPT_SSL_VERIFYPEER" src/Modules/Settings/AllegroApiClient.php
grep -c "CURLOPT_SSL_VERIFYPEER" src/Modules/Settings/ShopproApiClient.php
AC-1 satisfied: wszystkie 4 ApiClienty mają CURLOPT_SSL_VERIFYPEER => true
Task 2: Cron throttle z $_SESSION → app_settings DB
src/Core/Application.php,
database/migrations/20260313_000049_add_cron_last_run_at_setting.sql
W `Application.php`, znajdź metodę `isWebCronThrottled()` (linie ~268-285).
Obecna logika:
```php
$lastRunAt = isset($_SESSION['cron_web_last_run_at']) ? (int) $_SESSION['cron_web_last_run_at'] : 0;
// ...
$_SESSION['cron_web_last_run_at'] = $now;
```
Zastąp odczyt sesji odczytem z DB:
```php
$lastRunAt = $this->getWebCronLastRunAt();
```
I zapis sesji zapisem do DB:
```php
$this->setWebCronLastRunAt($now);
```
Dodaj prywatne metody pomocnicze:
```php
private function getWebCronLastRunAt(): int
{
try {
$stmt = $this->db->pdo()->prepare(
"SELECT setting_value FROM app_settings WHERE setting_key = 'cron_web_last_run_at'"
);
$stmt->execute();
$value = $stmt->fetchColumn();
return $value !== false ? (int) $value : 0;
} catch (Throwable) {
return 0;
}
}
private function setWebCronLastRunAt(int $timestamp): void
{
try {
$this->db->pdo()->prepare(
"INSERT INTO app_settings (setting_key, setting_value, created_at, updated_at)
VALUES ('cron_web_last_run_at', :ts, NOW(), NOW())
ON DUPLICATE KEY UPDATE setting_value = :ts, updated_at = NOW()"
)->execute(['ts' => (string) $timestamp]);
} catch (Throwable) {
// throttle failure is non-critical — GET_LOCK is the real guard
}
}
```
Sprawdź jak Application uzyskuje dostęp do PDO — jeśli przez `$this->db->pdo()` lub
`$this->db->query()` — dostosuj. Sprawdź jak CronHandlerFactory i inne miejsca używają DB.
Usuń odwołania do `$_SESSION['cron_web_last_run_at']` całkowicie.
Stwórz migrację `20260313_000049_add_cron_last_run_at_setting.sql`:
```sql
INSERT INTO app_settings (setting_key, setting_value, created_at, updated_at)
VALUES ('cron_web_last_run_at', '0', NOW(), NOW())
ON DUPLICATE KEY UPDATE updated_at = updated_at;
```
php -l src/Core/Application.php
grep -n "SESSION.*cron_web\|cron_web.*SESSION" src/Core/Application.php
# Powinno zwrócić 0 wyników
grep -n "getWebCronLastRunAt\|setWebCronLastRunAt" src/Core/Application.php
AC-2 satisfied: isWebCronThrottled() nie używa $_SESSION, czyta/zapisuje app_settings
Task 3: Rename duplikatu migracji 000014
database/migrations/20260301_000014b_add_products_sku_format_setting.sql
Migracja `20260301_000014_add_products_sku_format_setting.sql` ma ten sam numer co
`20260227_000014_create_product_integration_translations.sql`.
Ponieważ migrator śledzi migracje po **pełnej nazwie pliku** (nie numerze sekwencji),
rename jest bezpieczny dla nowych instalacji. Dla istniejących instalacji plik pod nową
nazwą będzie wyglądał jako nowy — dlatego zawartość musi być idempotentna.
Kroki:
1. Odczytaj zawartość `20260301_000014_add_products_sku_format_setting.sql`
2. Sprawdź czy INSERT używa `ON DUPLICATE KEY UPDATE` lub `INSERT IGNORE`
- Jeśli nie: zmień INSERT na idempotentny (INSERT IGNORE lub ON DUPLICATE KEY)
3. Utwórz nowy plik `20260301_000014b_add_products_sku_format_setting.sql` z zaktualizowaną treścią
4. Usuń stary plik `20260301_000014_add_products_sku_format_setting.sql`
UWAGA: Użyj `git mv` (przez bash) żeby git śledził rename:
```bash
git mv "database/migrations/20260301_000014_add_products_sku_format_setting.sql" \
"database/migrations/20260301_000014b_add_products_sku_format_setting.sql"
```
Potem edytuj plik (jeśli INSERT wymaga korekty na idempotentny).
ls database/migrations/ | grep "000014"
# Powinno pokazać: 20260227_000014_create... i 20260301_000014b_add...
# NIE powinno być: 20260301_000014_add... (stary plik)
AC-3 satisfied: brak duplikatu, jeden plik przemianowany na 000014b
DO NOT CHANGE
- Logika biznesowa żadnego ApiClienta — tylko dodanie SSL options do istniejących curl_setopt_array
- GET_LOCK logika w Application.php — nie ruszamy mutex, tylko session→DB dla timestamp
- Inne klucze w app_settings
- Istniejące migracje poza rename 000014
SCOPE LIMITS
- Nie implementuj rotacji CSRF tokenów (osobny deferred concern)
- Nie dodawaj path traversal check dla label files (osobny deferred concern)
- CA bundle path — środowisko aplikacyjne, nie certyfikat self-signed
<success_criteria>
- 4 ApiClienty z SSL verification
- .env.example z CURL_CA_BUNDLE_PATH
- Application::isWebCronThrottled() bez $_SESSION
- Migracja 000049 (cron_web_last_run_at seed)
- Rename 000014 → 000014b
- Zero błędów składniowych PHP </success_criteria>