# Changelog shopPRO Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze. --- ## ver. 0.345 (2026-03-25) - DataLayer GA4 fix + checkout token fix - **FIX**: `templates/shop-order/order-details.php` — event purchase: id→item_id (string), name→item_name, price via normalize_decimal (fix price:0), usunięty hardcoded value: 25.42, dodany google_business_vertical - **FIX**: `templates/shop-basket/summary-view.php` — event begin_checkout: id→item_id, name→item_name, dodany google_business_vertical - **FIX**: `templates/shop-product/product.php` — event view_item: dodany currency PLN, value, price jako number (nie string), google_business_vertical; event add_to_cart: dodany google_business_vertical, parseInt(quantity) - **NEW**: `templates/shop-basket/basket.php` — nowy event view_cart na stronie koszyka z pełnym zestawem danych GA4 (item_id, item_name, price, quantity, currency, google_business_vertical) - **FIX**: `autoload/front/Controllers/ShopBasketController.php` — usunięty błędny guard w summaryView() blokujący kolejne zamówienia po pierwszym (redirect na stare zamówienie zamiast podsumowanie) - **FIX**: `autoload/front/Controllers/ShopBasketController.php` — token zamówienia z jednorazowego na TTL 30 min (wiele kart, odświeżenie, "wstecz" nie unieważniają formularza) - **NEW**: `autoload/front/Controllers/ShopBasketController.php` — logowanie błędów zamówień do `logs/logs-order-YYYY-MM-DD.log` (double-submit, token invalid, exception, falsy order_id) - **FIX**: `autoload/front/Controllers/ShopBasketController.php` — redirect przy złym tokenie na `/koszyk-podsumowanie` zamiast `/koszyk` (użytkownik nie traci kontekstu) --- ## ver. 0.344 (2026-03-19) - Edycja personalizacji produktu w koszyku - **NEW**: `autoload/front/Controllers/ShopBasketController.php` — nowa metoda `basketUpdateCustomFields()`: AJAX endpoint do edycji custom fields w koszyku z walidacją required, przeliczaniem product_code (MD5 hash) i merge duplikatów - **NEW**: `templates/shop-basket/_partials/product-custom-fields.php` — przycisk "Edytuj personalizację" + formularz inline z aktualnymi wartościami - **NEW**: `templates/shop-basket/basket-details.php` — przekazanie `product_code` do szablonu custom fields - **NEW**: `templates/shop-basket/basket.php` — JavaScript obsługi edycji/zapisu/anulowania personalizacji --- ## ver. 0.343 (2026-03-19) - Custom fields: type + is_required + obsługa obrazków w koszyku - **FIX**: `autoload/Domain/Product/ProductRepository.php` — kopiowanie custom fields przy duplikacji produktu uwzględnia teraz pola `type` i `is_required` - **FIX**: `templates/shop-basket/_partials/product-custom-fields.php` — ochrona XSS (htmlspecialchars), obsługa pola typu `image`, bezpieczny fallback typu na `text` --- ## ver. 0.342 (2026-03-19) - Apilo: email z danymi zamówienia + infinite retry dla order jobów - **FIX**: `cron.php` — email notyfikacji Apilo zawiera teraz dane zamówienia (numer, klient, data, kwota) zamiast surowego JSON payload; temat emaila zawiera numery zamówień - **NEW**: `autoload/Domain/CronJob/CronJobType.php` — `isOrderRelatedApiloJob()` identyfikuje order joby (send_order, sync_payment, sync_status) - **NEW**: `autoload/Domain/CronJob/CronJobRepository.php` — order-related Apilo joby ponawiane w nieskończoność co 30 min zamiast permanent failure po 10 próbach - **NEW**: `cron.php` — email rozróżnia "PONAWIANY CO 30 MIN" (order joby) vs "TRWAŁY BŁĄD" (inne joby) - **NEW**: `cron.php` — po udanym wysłaniu zamówienia do Apilo czyszczone są stuck joby sync_payment/sync_status - **TEST**: +2 testy infinite retry w `CronJobRepositoryTest` --- ## ver. 0.341 (2026-03-16) - Bugfix: zamówienia nie wysyłały się do Apilo + retry i powiadomienia - **FIX**: `cron.php` — dodano brakujące `$apiloRepository` do klauzul `use()` w 5 handlerach cron (APILO_TOKEN_KEEPALIVE, APILO_SEND_ORDER, APILO_PRODUCT_SYNC, APILO_PRICELIST_SYNC, APILO_STATUS_POLL); regresja z ver. 0.339 (split IntegrationsRepository → ApiloRepository) powodowała `Call to a member function apiloGetAccessToken() on null` - **FIX**: `cron.php` — zamówienia z `apilo_order_id = -1` (failed) są teraz automatycznie ponawiane co 1h zamiast trwale pomijane; priorytet: najpierw nowe zamówienia (NULL), potem retry (-1) - **NEW**: `cron.php` — powiadomienie mailowe na `biuro@project-pro.pl` przy błędzie cURL wysyłania zamówienia do Apilo - **NEW**: `cron.php` — powiadomienie mailowe o trwale nieudanych zadaniach Apilo (po wyczerpaniu `max_attempts`) --- ## ver. 0.340 (2026-03-15) - Bugfix: crash przy składaniu zamówienia z kuponem rabatowym - **FIX**: `autoload/Domain/Order/OrderRepository.php:793` — naprawiono Fatal Error `Call to undefined method stdClass::is_one_time()` przy składaniu zamówienia z kodem rabatowym; zamieniono wywołania nieistniejących metod na stdClass (`is_one_time()`, `set_as_used()`) na dostęp do właściwości + istniejącą metodę `CouponRepository::markAsUsed()` - **SONARQUBE**: Pierwszy skan SonarQube — wyniki zapisane w `docs/TODO.md` (4 bugi, 31 critical code smells, 10 major, 8 minor) --- ## ver. 0.339 (2026-03-12) - Refactoring: wydzielenie ApiloRepository z IntegrationsRepository - **REFACTOR**: `autoload/Domain/Integrations/ApiloRepository.php` — nowa klasa `\Domain\Integrations\ApiloRepository` z 19 metodami apilo* (sync produktów, zamówień, konfiguracja) wydzielonymi z `IntegrationsRepository` - **REFACTOR**: `autoload/Domain/Integrations/IntegrationsRepository.php` — usunięto 19 metod apilo* (~540 linii); klasa zmniejszona z ~875 do ~340 linii, zawiera wyłącznie generyczną logikę integracji (settings, logi, product linking) - **REFACTOR**: `autoload/admin/Controllers/IntegrationsController.php` — konsumuje `ApiloRepository` przez DI zamiast `IntegrationsRepository` dla operacji apilo - **REFACTOR**: `autoload/Domain/Order/OrderAdminService.php` — używa `ApiloRepository` do wysyłki zamówień do Apilo - **REFACTOR**: `cron.php` — używa `ApiloRepository` do synchronizacji cron - **REFACTOR**: `autoload/admin/App.php` — wiring DI dla `ApiloRepository` - **TEST**: `tests/Unit/Domain/Integrations/ApiloRepositoryTest.php` — nowe testy dla `ApiloRepository`; suite: 818 testów, 2275 asercji --- ## ver. 0.338 (2026-03-12) - Bugfix: duplikaty zamówień + status COD - **FIX**: `autoload/front/Controllers/ShopBasketController::summaryView()` — guard przed ponownym złożeniem zamówienia: jeśli sesja zawiera `ORDER_SUBMIT_LAST_ORDER_ID`, użytkownik jest przekierowywany do istniejącego zamówienia zamiast widzieć formularz ponownie - **FIX**: `autoload/front/Controllers/ShopBasketController::basketSave()` — owinięcie wywołania `createFromBasket()` w try-catch; wyjątek jest logowany przez `error_log()`, użytkownik widzi komunikat błędu, koszyk sesyjny zostaje zachowany - **FIX**: `autoload/Domain/Order/OrderRepository::createFromBasket()` — usunięcie hardkodowanego `payment_id == 3` do wykrywania płatności przy odbiorze; zamiast tego używana jest flaga `$payment_method['is_cod']` - **FEATURE**: `autoload/Domain/PaymentMethod/PaymentMethodRepository` — nowa kolumna `is_cod` (normalizacja, zapis w `save()`, kolumna w `forTransport()` SQL) - **FEATURE**: `autoload/admin/Controllers/ShopPaymentMethodController` — nowe pole "Platnosc przy odbiorze" w formularzu edycji metody płatności - **MIGRATION**: `migrations/0.338.sql` — `ALTER TABLE pp_shop_payment_methods ADD COLUMN is_cod TINYINT(1) NOT NULL DEFAULT 0` --- ## ver. 0.337 (2026-03-12) - Bezpieczeństwo: ochrona CSRF panelu administracyjnego - **SECURITY**: `autoload/Shared/Security/CsrfToken.php` — nowa klasa z `getToken()`, `validate()`, `regenerate()` (token 64-znakowy hex, `hash_equals()` przeciw timing attacks) - **SECURITY**: `admin/templates/components/form-edit.php` — dodano ukryte pole `_csrf_token` we wszystkich formularzach edycji - **SECURITY**: `autoload/admin/Support/Forms/FormRequestHandler::handleSubmit()` — walidacja CSRF przed przetworzeniem danych formularza - **SECURITY**: `admin/templates/site/unlogged-layout.php` — token CSRF w formularzu logowania + fix XSS na komunikacie alertu (`htmlspecialchars`) - **SECURITY**: `admin/templates/users/user-2fa.php` — token CSRF w obu formularzach 2FA (weryfikacja i resend) - **SECURITY**: `autoload/admin/App::special_actions()` — walidacja CSRF dla żądań POST; regeneracja tokenu po udanym logowaniu (obie ścieżki: bezpośrednia i przez 2FA) - **TEST**: `tests/Unit/Shared/Security/CsrfTokenTest.php` — 7 nowych testów; suite: 817 testów, 2271 asercji --- ## ver. 0.336 (2026-03-12) - Poprawki bezpieczeństwa: error handling w krytycznych ścieżkach - **FIX**: `cron.php` — przywrócono `E_WARNING` i `E_DEPRECATED` (wyciszano je od zawsze, ukrywając potencjalne błędy) - **FIX**: `IntegrationsRepository::apiloAuthorize()` — try-catch po zapisie tokenów Apilo; błąd DB logowany i zwraca `false` zamiast cicho kontynuować - **FIX**: `ProductRepository::safeUnlink()` — `error_log()` gdy ścieżka istnieje ale jest poza `upload/` - **FIX**: `ArticleRepository::safeUnlink()` — to samo --- ## ver. 0.335 (2026-03-12) - Poprawki bezpieczeństwa: path traversal i XSS w szablonach - **SECURITY**: `ProductRepository` — dodano `safeUnlink()` z walidacją `realpath()` zapobiegającą path traversal; użyta w `cleanupDeletedFiles()`, `cleanupDeletedImages()`, `deleteNonassignedImages()` - **SECURITY**: `ArticleRepository` — to samo; użyta w `deleteMarkedImages()`, `deleteMarkedFiles()`, `deleteNonassignedFiles()`, `deleteNonassignedImages()` - **SECURITY**: `templates/articles/article-full.php` — `htmlspecialchars()` na tytule artykułu, `$_SERVER['SERVER_NAME']` i `$url` w linkach social media - **SECURITY**: `templates/articles/article-entry.php` — `htmlspecialchars()` na tytule i `$url` (3 miejsca: href, title, alt) --- ## ver. 0.334 (2026-03-12) - Poprawki bezpieczeństwa: debug log, SQL, RedBeanPHP - **SECURITY**: `ShopOrderController::paymentStatusTpay()` — usunięto `file_put_contents('tpay.txt', ...)` który logował pełne dane POST/GET płatności do publicznego pliku - **SECURITY**: `ShopOrderController` — hardcoded sekret HotPay `"ProjectPro1916;"` przeniesiony do prywatnej stałej `HOTPAY_HASH_SEED` - **SECURITY**: `IntegrationsRepository::getSettings()` — zastąpiono raw `query("SELECT * FROM $table")` metodą Medoo `select()` (spójne z zasadą braku string concatenation w SQL) - **REFACTOR**: `index.php`, `admin/index.php` — usunięto RedBeanPHP (`rb.php`): biblioteka była ładowana i inicjalizowana, ale nigdy nie używana w żadnym zapytaniu - **CLEANUP**: `libraries/rb.php` — usunięto plik (536 KB zbędnych zależności) - **TESTS**: `IntegrationsRepositoryTest` — zaktualizowano 6 testów do nowego API (`select` zamiast `query` dla `getSettings`) --- ## ver. 0.333 (2026-03-10) - Ochrona przed podwójnym składaniem zamówienia (order submit token) - **NEW**: `ShopBasketController` — mechanizm tokenu CSRF chroniący przed podwójnym składaniem zamówienia (generowanie, walidacja, konsumpcja tokenu w sesji) - **NEW**: `ShopBasketController::basketSave()` — przy duplikacie przekierowanie do istniejącego zamówienia zamiast tworzenia kolejnego - **FIX**: `templates/shop-basket/summary-view.php` — JS nasłuchuje na `submit` formularza zamiast `click` przycisku (poprawna obsługa walidacji HTML5) - **FIX**: `templates/shop-basket/address-form.php` — ukryte pole `order_submit_token` z escape XSS - **TESTS**: `ShopBasketControllerTest` — testy konstruktora i zależności (5 testów) --- ## ver. 0.332 (2026-03-01) - API produktów: nowe pola new_to_date i additional_message - **NEW**: `ProductRepository::getProductForApi()` — eksportuje 4 nowe pola: `new_to_date`, `additional_message` (int 0/1), `additional_message_required` (int 0/1), `additional_message_text` - **NEW**: `ProductsApiController` — obsługa nowych pól w PUT/PATCH (aktualizacja `new_to_date`, `additional_message`, `additional_message_required`, `additional_message_text`) - **DOCS**: `docs/API.md` — zaktualizowane przykłady GET/PUT dla nowych pól produktu --- ## ver. 0.331 (2026-03-01) - Bugfix: strona produktu używała layoutu kategorii zamiast domyślnego - **FIX**: `LayoutsRepository::getProductLayout()` — fallback gdy produkt i jego kategorie nie mają przypisanego layoutu zmieniany z `categories_default = 1` na `status = 1`; wcześniej produkty bez layoutu pobierały szablon "Podstrony - kategorie" zamiast właściwego domyślnego --- ## ver. 0.330 (2026-02-27) - Eliminacja htaccess.conf — wszystkie trasy URL w pp_routes - **REFACTOR**: `Helpers::htacces()` — generowanie `.htaccess` w całości z PHP (usunięty `file_get_contents('htaccess.conf')` i placeholder `{HTACCESS_CACHE}`) - **NEW**: 32 statyczne trasy systemowe wstawiane do `pp_routes` z `type='system'` przy każdym `htacces()` (koszyk, logowanie, wylogowanie, panel klienta, newsletter, zamówienia, płatności, moduły AJAX: shopBasket/shopClient/shopProduct/shopCoupon/search) - **NEW**: Dynamiczne trasy językowe i producentów (producenci + per-producent z paginacją) przenoszone do `pp_routes` zamiast `.htaccess` - **NEW**: Kolumna `type VARCHAR(20) NULL` w `pp_routes` — `NULL` dla encji, `'system'` dla tras systemowych - **REMOVED**: `libraries/htaccess.conf` — plik szablonu usunięty, treść wbudowana w PHP - **PERF**: Invalidacja cache Redis `pp_routes:all` po każdym `htacces()` — świeże trasy przy kolejnym żądaniu - **MIGRATION**: `migrations/0.329.sql` (dodano `type` column) - **DOCS**: `docs/DATABASE_STRUCTURE.md` — zaktualizowana sekcja `pp_routes` o kolumnę `type` --- ## ver. 0.329 (2026-02-27) - Routing kategorii, stron i artykułów przez pp_routes - **REFACTOR**: `index.php` — blok routingu przez `pp_routes` przeniesiony PRZED `checkUrlParams()` (poprawna kolejność: lang/a=page dostępne w checkUrlParams) - **PERF**: Cache Redis dla tras (`pp_routes:all`, TTL 86400s) w `index.php` — jeden SELECT na 24h zamiast przy każdym żądaniu - **NEW**: Kategorie, strony i artykuły zapisywane do `pp_routes` zamiast `.htaccess` w `Helpers::htacces()` - **NEW**: `CategoryRepository::categoryDelete()` — usuwa powiązane `pp_routes` przed odświeżeniem - **NEW**: `PagesRepository::pageDelete()` — usuwa powiązane `pp_routes` - **NEW**: `ArticleRepository::archive()` i `deletePermanently()` — usuwa powiązane `pp_routes` - **MIGRATION**: `migrations/0.329.sql` — `ALTER TABLE pp_routes ADD COLUMN category_id, page_id, article_id` - **TESTS**: Zaktualizowane `CategoryRepositoryTest` i `ArticleRepositoryTest` (nowe asercje na `pp_routes` delete) --- ## ver. 0.328 (2026-02-27) - Ikona kopiowania wartości atrybutów w szczegółach zamówienia - **NEW**: `order-details-custom-script.php` — JS parsuje `.atributes` div i wstrzykuje przycisk `fa-copy` przy każdej wartości atrybutu - **UX**: Kliknięcie kopiuje wartość do schowka (Clipboard API + fallback execCommand), ikona zmienia się na `fa-check` z zielonym tłem przez 1,5s --- ## ver. 0.327 (2026-02-27) - Masowe usuwanie w archiwum produktów - **NEW**: `ProductArchiveController::bulk_delete_permanent()` — endpoint POST `product_archive/bulk_delete_permanent/`, przyjmuje `ids[]`, usuwa każdy produkt przez `ProductRepository::delete()`, zwraca JSON `{success, deleted, errors[]}` - **UX**: Kolumna checkboxów w liście archiwum produktów + pasek akcji masowych z licznikiem zaznaczonych - **UX**: "Zaznacz wszystkie" w nagłówku tabeli (wstrzyknięty via JS), dialog potwierdzenia przed masowym usunięciem - **TEST**: 2 nowe testy w `ProductArchiveControllerTest` — weryfikacja istnienia i sygnatury `bulk_delete_permanent` --- ## ver. 0.326 (2026-02-27) - API: endpoint categories/list - **NEW**: `api\Controllers\CategoriesApiController` — nowy kontroler API z akcją `list` - **NEW**: Endpoint `GET api.php?endpoint=categories&action=list` — zwraca płaską listę aktywnych kategorii (id, parent_id, title) w domyślnym języku sklepu - **FIX**: Usunięto zbędny parametr w `CategoryRepository`, eliminacja N+1 queries w categories/list przez bulk-fetch tytułów --- ## ver. 0.325 (2026-02-27) - Fix changelog encoding + limit wyświetlania - **FIX**: `updates/changelog.php` — naprawione krzaczki (mojibake) w polskich znakach; dane odbudowane z plików manifest - **NEW**: `updates/changelog-data.html` — czyste dane changelog oddzielone od logiki PHP - **REFACTOR**: `updates/changelog.php` — konwersja ze statycznego HTML na skrypt PHP: `Content-Type: utf-8`, parsowanie wpisów, filtrowanie po wersji - **NEW**: Parametr `?ver=X.XXX` — ogranicza changelog do 5 wersji wstecz od wersji instancji - **UPDATE**: `admin/templates/update/main-view.php` — przekazuje `?ver=` do URL changelog - **UPDATE**: `build-update.ps1` — nowe wpisy dopisywane do `changelog-data.html` zamiast `changelog.php` --- ## ver. 0.324 (2026-02-27) - System kolejki zadań cron - **NEW**: `Domain\CronJob\CronJobType` — stałe typów zadań, priorytetów, statusów, exponential backoff - **NEW**: `Domain\CronJob\CronJobRepository` — CRUD na `pp_cron_jobs` + `pp_cron_schedules` (enqueue, fetchNext, markCompleted, markFailed, hasPendingJob, cleanup, recoverStuck, getDueSchedules, touchSchedule) - **NEW**: `Domain\CronJob\CronJobProcessor` — orkiestracja: rejestracja handlerów, tworzenie scheduled jobs, przetwarzanie kolejki z priorytetami i retry/backoff - **NEW**: Tabele `pp_cron_jobs` i `pp_cron_schedules` — kolejka zadań z priorytetami, exponential backoff, harmonogram cykliczny - **REFACTOR**: `cron.php` — zastąpienie monolitycznego ~550 linii orkiestratorem z CronJobProcessor i zarejestrowanymi handlerami - **REFACTOR**: `OrderAdminService::queueApiloSync()` — kolejkowanie przez `CronJobRepository::enqueue()` zamiast pliku JSON - **REFACTOR**: `OrderAdminService::syncApiloPayment()`, `syncApiloStatus()` — zmiana z private na public (używane przez handlery cron) - **REMOVED**: `OrderAdminService::processApiloSyncQueue()`, `loadApiloSyncQueue()`, `saveApiloSyncQueue()`, `apiloSyncQueuePath()`, stała `APILO_SYNC_QUEUE_FILE` - **NEW**: Jednorazowa migracja JSON queue → DB w cron.php (automatyczna przy pierwszym uruchomieniu) - **SECURITY**: `cron.php` — ochrona endpointu: wymaga `$config['cron_key']` w URL (`?key=...`) lub trybu CLI - **FIX**: `CronJobRepository::fetchNext()` — re-SELECT po UPDATE eliminuje race condition przy równoległych workerach - **FIX**: `cron.php` — null check dla `$mdb->query()` przed `->fetch()` / `->fetchAll()` (3 miejsca) - **FIX**: `cron.php` — walidacja odpowiedzi curl w APILO_PRODUCT_SYNC i APILO_PRICELIST_SYNC (zapobiega zapisaniu null do bazy) - **FIX**: DI wiring — `CronJobRepository` przekazywany do `OrderAdminService` we wszystkich 4 punktach: `admin\App`, `api\ApiRouter`, `front\App`, `cron.php` - **TESTS**: 41 nowych testów CronJob (CronJobTypeTest, CronJobRepositoryTest, CronJobProcessorTest) - **MIGRATION**: `migrations/0.324.sql` --- ## ver. 0.323 (2026-02-24) - Import zdjęć, trwałe usuwanie, fix API upload - **FIX**: `IntegrationsRepository::shopproImportProduct()` — kompletny refactor importu zdjęć: walidacja HTTP response, curl timeouty, bezpieczna budowa URL, szczegółowy log do `logs/shoppro-import-debug.log` i `error_log`, czytelny komunikat z wynikiem - **FIX**: `ProductRepository::saveProduct()` — `saveCustomFields()` wywoływane tylko gdy klucz `custom_field_name` istnieje w danych (partial update przez API nie czyści custom fields) - **FIX**: `ProductRepository::delete()` — usuwanie rekordów z `pp_shop_products_custom_fields` przy kasowaniu produktu - **FIX**: `ProductsApiController::upload_image()` — poprawka ścieżki uploadu (`upload/` zamiast `../upload/` — api.php działa z rootu projektu) - **NEW**: `ProductArchiveController::delete_permanent()` — trwałe usunięcie produktu z archiwum (wraz ze zdjęciami i załącznikami) - **NEW**: Przycisk "Usuń trwale" w liście produktów archiwalnych z potwierdzeniem --- ## ver. 0.318 (2026-02-24) - ShopPRO export produktów + API endpoints - **NEW**: `IntegrationsRepository::shopproExportProduct()` — eksport produktu do zdalnej instancji shopPRO: pola główne, tłumaczenia, custom fields, zdjęcia przez API (base64) - **NEW**: `IntegrationsRepository::sendImageToShopproApi()` — wysyłka zdjęć do remote API shopPRO (endpoint `upload_image`) z base64 - **REFACTOR**: `shopproImportProduct()` — wydzielono `shopproDb()` i `missingShopproSetting()` jako prywatne helpery; dodano import `security_information`, `producer_id`, custom fields i `alt` zdjęcia - **NEW**: `AttributeRepository::ensureAttributeForApi()` i `ensureAttributeValueForApi()` — idempotent find-or-create dla atrybutów i ich wartości (integracje API) - **NEW**: API endpoint `POST /api.php?endpoint=dictionaries&action=ensure_attribute` — utwórz lub znajdź atrybut po nazwie i typie - **NEW**: API endpoint `POST /api.php?endpoint=dictionaries&action=ensure_attribute_value` — utwórz lub znajdź wartość atrybutu po nazwie - **NEW**: API endpoint `POST /api.php?endpoint=products&action=upload_image` — przyjmuje zdjęcie produktu jako base64 JSON, zapisuje plik i rekord w `pp_shop_products_images` - **NEW**: `IntegrationsController::shoppro_product_export()` — akcja admina eksportująca produkt do shopPRO - **NEW**: Przycisk "Eksportuj do shopPRO" w liście produktów (widoczny gdy shopPRO enabled) - **NEW**: Pole "API key" w ustawieniach integracji shopPRO (`shoppro-settings.php`) --- ## ver. 0.317 (2026-02-23) - Klucz API: przycisk generowania + fix zapisu - **FIX**: `SettingsRepository::saveSettings()` — pole `api_key` brakowało w whiteliście zapisywanych pól, przez co wartość była tracona przy każdym zapisie (TRUNCATE + insert) - **NEW**: Pole "Klucz API" w ustawieniach — przycisk "Generuj" do losowego 32-znakowego klucza alfanumerycznego, usunięto "(ordersPRO)" z nazwy - **FIX**: `api.php` — routing API przeniesiony przed ładowanie globalnych settings (wczesne wyjście), obsługa błędów przez `\Throwable` - **FIX**: `ApiRouter` — catch `\Throwable` zamiast `\Exception` dla pełniejszego łapania błędów --- ## ver. 0.316 (2026-02-23) - Migracja brakującej kolumny type w custom fields - **FIX**: Dodanie brakującej kolumny `type` w tabeli `pp_shop_products_custom_fields` — kolumna była używana w kodzie od v0.277 ale nigdy nie miała migracji ALTER TABLE, przez co instancje ze starszą bazą dostawały `PDOException: Column not found: 1054 Unknown column 'type'` przy zapisie produktu --- ## ver. 0.315 (2026-02-23) - Fix listowania atrybutów w admin - **FIX**: `AttributeRepository::listForAdmin()` — zapytanie COUNT dostawało parametr `:default_lang_id` którego nie miało w SQL, powodując `PDOException: SQLSTATE[HY093]: Invalid parameter number`. Parametr potrzebny tylko w głównym SELECT, nie w COUNT --- ## ver. 0.314 (2026-02-23) - Fix wyszukiwarki admin + title zamówienia - **FIX**: Globalna wyszukiwarka w panelu admina przestała zwracać wyniki — dodano `Content-Type: application/json` i `Cache-Control: no-store` (zapobiega cache'owaniu przez proxy/CDN), zmiana AJAX z GET na POST, `fetchAll(PDO::FETCH_ASSOC)`, top-level try/catch z gwarantowaną odpowiedzią JSON - **NEW**: `document.title` w widoku szczegółów zamówienia pokazuje numer zamówienia (np. "Zamówienie ZAM/123 - shopPro") --- ## ver. 0.313 (2026-02-23) - Fix sync płatności Apilo + logowanie - **FIX**: `syncApiloPayment()` i `syncApiloStatus()` — `(int)` cast na `apilo_order_id` (format `"PPxxxxxx"`) dawał `0`, przez co metody pomijały sync z API Apilo. Zmiana na `empty()` - **NEW**: Logowanie w `syncApiloPaymentIfNeeded()` i `syncApiloStatusIfNeeded()` — każda ścieżka decyzyjna (Apilo wyłączone, brak tokenu, brak `apilo_order_id`, sync nieudany) zapisuje wpis do `pp_log` z kontekstem --- ## ver. 0.312 (2026-02-23) - Fix krytycznych bugów integracji Apilo - **FIX**: `curl_getinfo()` wywoływane po `curl_close()` — HTTP code zawsze wynosił 0, uniemożliwiając prawidłową obsługę odpowiedzi Apilo - **FIX**: Nieskończona pętla wysyłania zamówienia — gdy Apilo zwracało błąd serwera, zamówienie nie dostawało `apilo_order_id` i było ponownie wybierane w każdym cyklu crona. Teraz błędne zamówienia oznaczane `apilo_order_id = -1` z powiadomieniem email - **FIX**: Ceny produktów 0.00 PLN w Apilo — string `"0.00"` z MySQL jest truthy w PHP, więc ternary wybierał `price_brutto_promo` (0.00) zamiast `price_brutto`. Zmiana na `(float)... > 0` - **FIX**: Walidacja cen przed wysyłką — zamówienia z zerowymi cenami produktów nie są wysyłane do Apilo (`apilo_order_id = -2`) z powiadomieniem email - **FIX**: Niezainicjalizowana zmienna `$order_message` powodująca PHP warning --- ## ver. 0.311 (2026-02-23) - Fix race condition Apilo + persistence filtrów + poprawki cen - **FIX**: Race condition — callback płatności przed wysłaniem zamówienia do Apilo nie synchronizował płatności (task trafiał w pustkę). Teraz `syncApiloPaymentIfNeeded` i `syncApiloStatusIfNeeded` kolejkują sync do retry gdy `apilo_order_id` jeszcze nie istnieje - **FIX**: `processApiloSyncQueue` — zamówienia bez `apilo_order_id` były usuwane z kolejki bez synchronizacji. Teraz czekają (max 50 prób ~8h) aż cron wyśle zamówienie do Apilo - **FIX**: Drugie wywołanie `processApiloSyncQueue` w cronie po wysyłce zamówień — sync płatności/statusów w tym samym cyklu - **FIX**: Ceny w szczegółach zamówienia (admin + frontend) — gdy `price_brutto_promo` = 0 lub >= ceny regularnej, wyświetla cenę regularną zamiast 0 zł - **NEW**: Persistence filtrów tabel w panelu admin — localStorage zapamiętuje ostatni widok (filtry, sortowanie, paginacja) i przywraca go przy powrocie do listy. Przycisk "Wyczyść" resetuje zapisany stan --- ## ver. 0.310 (2026-02-23) - Logi integracji w panelu admin - **NEW**: Zakładka "Logi" w sekcji Integracje — podgląd tabeli `pp_log` z paginacją, sortowaniem, filtrami (akcja, wiadomość, ID zamówienia) i rozwijalnym kontekstem JSON - **NEW**: `IntegrationsRepository::getLogs()`, `deleteLog()`, `clearLogs()` — metody do obsługi logów - **NEW**: `IntegrationsController::logs()`, `logs_clear()` — akcje kontrolera - **NEW**: Przycisk "Wyczyść wszystkie logi" z potwierdzeniem --- ## ver. 0.309 (2026-02-23) - ApiloLogger + cache-busting CSS/JS + poprawki UI - **NEW**: `ApiloLogger` — logowanie operacji Apilo do tabeli `pp_log` z kontekstem JSON (send_order, resend_order, payment_sync, status_sync, status_poll) - **NEW**: Migracja `pp_log` — kolumny `action`, `order_id`, `context` + indeksy - **NEW**: Cache-busting dla CSS i JS w admin panelu — `?ver=filemtime()` przy wszystkich lokalnych zasobach w `main-layout.php` - **FIX**: Przeniesienie inicjalizacji `$mdb` przed `SettingsRepository` w `admin/index.php` - **FIX**: Rzutowanie na `(string)` w `ShopProductController::escapeHtml()` — zapobiega warningom - **ZMIANA**: Skrocone kategorie produktow na liscie — `text-overflow: ellipsis` z `title` tooltip - **ZMIANA**: `copyToClipboard()` — uzywa `navigator.clipboard` API z fallbackiem na `