update
This commit is contained in:
@@ -133,3 +133,17 @@ symbol_info_budget:
|
|||||||
# list of regex patterns which, when matched, mark a memory entry as read‑only.
|
# list of regex patterns which, when matched, mark a memory entry as read‑only.
|
||||||
# Extends the list from the global configuration, merging the two lists.
|
# Extends the list from the global configuration, merging the two lists.
|
||||||
read_only_memory_patterns: []
|
read_only_memory_patterns: []
|
||||||
|
|
||||||
|
# list of regex patterns for memories to completely ignore.
|
||||||
|
# Matching memories will not appear in list_memories or activate_project output
|
||||||
|
# and cannot be accessed via read_memory or write_memory.
|
||||||
|
# To access ignored memory files, use the read_file tool on the raw file path.
|
||||||
|
# Extends the list from the global configuration, merging the two lists.
|
||||||
|
# Example: ["_archive/.*", "_episodes/.*"]
|
||||||
|
ignored_memory_patterns: []
|
||||||
|
|
||||||
|
# advanced configuration option allowing to configure language server-specific options.
|
||||||
|
# Maps the language key to the options.
|
||||||
|
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
|
||||||
|
# No documentation on options means no options are available.
|
||||||
|
ls_specific_settings: {}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ class ShopBasketController
|
|||||||
{
|
{
|
||||||
private const ORDER_SUBMIT_TOKEN_SESSION_KEY = 'order-submit-token';
|
private const ORDER_SUBMIT_TOKEN_SESSION_KEY = 'order-submit-token';
|
||||||
private const ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY = 'order-submit-last-order-id';
|
private const ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY = 'order-submit-last-order-id';
|
||||||
|
private const ORDER_SUBMIT_TOKEN_TTL = 1800; // 30 minutes
|
||||||
|
|
||||||
public static $title = [
|
public static $title = [
|
||||||
'mainView' => 'Koszyk'
|
'mainView' => 'Koszyk'
|
||||||
@@ -276,19 +277,6 @@ class ShopBasketController
|
|||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$existingOrderId = isset( $_SESSION[ self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY ] )
|
|
||||||
? (int)$_SESSION[ self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY ]
|
|
||||||
: 0;
|
|
||||||
if ( $existingOrderId > 0 )
|
|
||||||
{
|
|
||||||
$existingOrderHash = $this->orderRepository->findHashById( $existingOrderId );
|
|
||||||
if ( $existingOrderHash )
|
|
||||||
{
|
|
||||||
header( 'Location: /zamowienie/' . $existingOrderHash );
|
|
||||||
exit;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$client = \Shared\Helpers\Helpers::get_session( 'client' );
|
$client = \Shared\Helpers\Helpers::get_session( 'client' );
|
||||||
$orderSubmitToken = $this->createOrderSubmitToken();
|
$orderSubmitToken = $this->createOrderSubmitToken();
|
||||||
|
|
||||||
@@ -308,23 +296,26 @@ class ShopBasketController
|
|||||||
|
|
||||||
public function basketSave()
|
public function basketSave()
|
||||||
{
|
{
|
||||||
$orderSubmitToken = (string)\Shared\Helpers\Helpers::get( 'order_submit_token', true );
|
$basket = \Shared\Helpers\Helpers::get_session( 'basket' );
|
||||||
$existingOrderId = isset( $_SESSION[ self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY ] ) ? (int)$_SESSION[ self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY ] : 0;
|
$existingOrderId = isset( $_SESSION[ self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY ] ) ? (int)$_SESSION[ self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY ] : 0;
|
||||||
|
|
||||||
if ( !$this->isValidOrderSubmitToken( $orderSubmitToken ) )
|
if ( ( !is_array( $basket ) || empty( $basket ) ) && $existingOrderId > 0 )
|
||||||
{
|
|
||||||
if ( $existingOrderId > 0 )
|
|
||||||
{
|
{
|
||||||
$existingOrderHash = $this->orderRepository->findHashById( $existingOrderId );
|
$existingOrderHash = $this->orderRepository->findHashById( $existingOrderId );
|
||||||
if ( $existingOrderHash )
|
if ( $existingOrderHash )
|
||||||
{
|
{
|
||||||
|
$this->logOrder( 'Double-submit detected, redirecting to existing order id=' . $existingOrderId );
|
||||||
header( 'Location: /zamowienie/' . $existingOrderHash );
|
header( 'Location: /zamowienie/' . $existingOrderHash );
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$orderSubmitToken = (string)\Shared\Helpers\Helpers::get( 'order_submit_token', true );
|
||||||
|
if ( !$this->isValidOrderSubmitToken( $orderSubmitToken ) )
|
||||||
|
{
|
||||||
|
$this->logOrder( 'Token validation failed. formToken=' . ($orderSubmitToken ?: '(empty)') );
|
||||||
\Shared\Helpers\Helpers::error( \Shared\Helpers\Helpers::lang( 'zamowienie-zostalo-zlozone-komunikat-blad' ) );
|
\Shared\Helpers\Helpers::error( \Shared\Helpers\Helpers::lang( 'zamowienie-zostalo-zlozone-komunikat-blad' ) );
|
||||||
header( 'Location: /koszyk' );
|
header( 'Location: /koszyk-podsumowanie' );
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -367,7 +358,7 @@ class ShopBasketController
|
|||||||
}
|
}
|
||||||
catch ( \Exception $e )
|
catch ( \Exception $e )
|
||||||
{
|
{
|
||||||
error_log( '[basketSave] createFromBasket exception: ' . $e->getMessage() );
|
$this->logOrder( 'createFromBasket exception: ' . $e->getMessage() );
|
||||||
\Shared\Helpers\Helpers::error( \Shared\Helpers\Helpers::lang( 'zamowienie-zostalo-zlozone-komunikat-blad' ) );
|
\Shared\Helpers\Helpers::error( \Shared\Helpers\Helpers::lang( 'zamowienie-zostalo-zlozone-komunikat-blad' ) );
|
||||||
header( 'Location: /koszyk' );
|
header( 'Location: /koszyk' );
|
||||||
exit;
|
exit;
|
||||||
@@ -400,6 +391,7 @@ class ShopBasketController
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
$this->logOrder( 'createFromBasket returned falsy order_id. client_id=' . ($client['id'] ?? 'guest') . ' email=' . \Shared\Helpers\Helpers::get( 'email', true ) );
|
||||||
\Shared\Helpers\Helpers::error( \Shared\Helpers\Helpers::lang( 'zamowienie-zostalo-zlozone-komunikat-blad' ) );
|
\Shared\Helpers\Helpers::error( \Shared\Helpers\Helpers::lang( 'zamowienie-zostalo-zlozone-komunikat-blad' ) );
|
||||||
header( 'Location: /koszyk' );
|
header( 'Location: /koszyk' );
|
||||||
exit;
|
exit;
|
||||||
@@ -471,8 +463,23 @@ class ShopBasketController
|
|||||||
|
|
||||||
private function createOrderSubmitToken()
|
private function createOrderSubmitToken()
|
||||||
{
|
{
|
||||||
|
$existingTokenData = isset( $_SESSION[ self::ORDER_SUBMIT_TOKEN_SESSION_KEY ] ) ? $_SESSION[ self::ORDER_SUBMIT_TOKEN_SESSION_KEY ] : null;
|
||||||
|
|
||||||
|
if ( is_array( $existingTokenData ) && !empty( $existingTokenData['token'] ) && !empty( $existingTokenData['created_at'] ) )
|
||||||
|
{
|
||||||
|
$age = time() - (int)$existingTokenData['created_at'];
|
||||||
|
if ( $age < self::ORDER_SUBMIT_TOKEN_TTL )
|
||||||
|
{
|
||||||
|
\Shared\Helpers\Helpers::delete_session( self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY );
|
||||||
|
return $existingTokenData['token'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$token = $this->generateOrderSubmitToken();
|
$token = $this->generateOrderSubmitToken();
|
||||||
\Shared\Helpers\Helpers::set_session( self::ORDER_SUBMIT_TOKEN_SESSION_KEY, $token );
|
\Shared\Helpers\Helpers::set_session( self::ORDER_SUBMIT_TOKEN_SESSION_KEY, [
|
||||||
|
'token' => $token,
|
||||||
|
'created_at' => time()
|
||||||
|
] );
|
||||||
\Shared\Helpers\Helpers::delete_session( self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY );
|
\Shared\Helpers\Helpers::delete_session( self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY );
|
||||||
|
|
||||||
return $token;
|
return $token;
|
||||||
@@ -495,10 +502,26 @@ class ShopBasketController
|
|||||||
if ( !$token )
|
if ( !$token )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
$sessionToken = isset( $_SESSION[ self::ORDER_SUBMIT_TOKEN_SESSION_KEY ] ) ? (string)$_SESSION[ self::ORDER_SUBMIT_TOKEN_SESSION_KEY ] : '';
|
$tokenData = isset( $_SESSION[ self::ORDER_SUBMIT_TOKEN_SESSION_KEY ] ) ? $_SESSION[ self::ORDER_SUBMIT_TOKEN_SESSION_KEY ] : null;
|
||||||
|
|
||||||
|
if ( is_string( $tokenData ) )
|
||||||
|
{
|
||||||
|
$sessionToken = $tokenData;
|
||||||
if ( !$sessionToken )
|
if ( !$sessionToken )
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
return function_exists( 'hash_equals' ) ? hash_equals( $sessionToken, $token ) : $sessionToken === $token;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( !is_array( $tokenData ) || empty( $tokenData['token'] ) || empty( $tokenData['created_at'] ) )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$age = time() - (int)$tokenData['created_at'];
|
||||||
|
if ( $age > self::ORDER_SUBMIT_TOKEN_TTL )
|
||||||
|
return false;
|
||||||
|
|
||||||
|
$sessionToken = (string)$tokenData['token'];
|
||||||
|
|
||||||
if ( function_exists( 'hash_equals' ) )
|
if ( function_exists( 'hash_equals' ) )
|
||||||
return hash_equals( $sessionToken, $token );
|
return hash_equals( $sessionToken, $token );
|
||||||
|
|
||||||
@@ -509,4 +532,12 @@ class ShopBasketController
|
|||||||
{
|
{
|
||||||
\Shared\Helpers\Helpers::delete_session( self::ORDER_SUBMIT_TOKEN_SESSION_KEY );
|
\Shared\Helpers\Helpers::delete_session( self::ORDER_SUBMIT_TOKEN_SESSION_KEY );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function logOrder( $message )
|
||||||
|
{
|
||||||
|
$logDir = $_SERVER['DOCUMENT_ROOT'] . '/logs';
|
||||||
|
$logFile = $logDir . '/logs-order-' . date( 'Y-m-d' ) . '.log';
|
||||||
|
$line = '[' . date( 'Y-m-d H:i:s' ) . '] ' . $message . PHP_EOL;
|
||||||
|
@file_put_contents( $logFile, $line, FILE_APPEND | LOCK_EX );
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
75
change.md
Normal file
75
change.md
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
# Zmiana: Fix przekierowania w summaryView
|
||||||
|
|
||||||
|
## Plik
|
||||||
|
`autoload/front/Controllers/ShopBasketController.php`
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
Po złożeniu pierwszego zamówienia, próba złożenia drugiego zamówienia powodowała przekierowanie na stronę poprzedniego zamówienia (`/zamowienie/{hash}`) zamiast na stronę podsumowania koszyka (`/koszyk-podsumowanie`).
|
||||||
|
|
||||||
|
## Przyczyna
|
||||||
|
W metodzie `summaryView()` był guard sprawdzający sesyjny klucz `order-submit-last-order-id`. Jeśli istniał (a istniał po każdym złożonym zamówieniu), metoda od razu redirectowała na stronę starego zamówienia — nigdy nie dochodziło do `createOrderSubmitToken()`, które czyści ten klucz.
|
||||||
|
|
||||||
|
## Co usunięto
|
||||||
|
Usunięto blok (dawne linie 279-290):
|
||||||
|
```php
|
||||||
|
$existingOrderId = isset( $_SESSION[ self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY ] )
|
||||||
|
? (int)$_SESSION[ self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY ]
|
||||||
|
: 0;
|
||||||
|
if ( $existingOrderId > 0 )
|
||||||
|
{
|
||||||
|
$existingOrderHash = $this->orderRepository->findHashById( $existingOrderId );
|
||||||
|
if ( $existingOrderHash )
|
||||||
|
{
|
||||||
|
header( 'Location: /zamowienie/' . $existingOrderHash );
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Zabezpieczenie double-submit
|
||||||
|
Ochrona przed podwójnym wysłaniem formularza pozostaje nienaruszona w metodzie `basketSave()` — tam ten sam mechanizm działa poprawnie.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Zmiana 2: Logowanie błędów w basketSave + naprawa tokena zamówienia
|
||||||
|
|
||||||
|
## Plik
|
||||||
|
`autoload/front/Controllers/ShopBasketController.php`
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
Klientka nie mogła złożyć zamówienia — komunikat "Podczas składania zamówienia wystąpił błąd". Brak logowania uniemożliwiał diagnozę. Dodatkowo token zamówienia był jednorazowy i nadpisywany przy każdym wejściu na podsumowanie — otwarcie drugiej karty, użycie "wstecz" w przeglądarce lub odświeżenie strony unieważniało token i blokowało złożenie zamówienia.
|
||||||
|
|
||||||
|
## Dodane logowanie do `logs/logs-order-{data}.log`
|
||||||
|
Dodana prywatna metoda `logOrder()` zapisująca do pliku `logs/logs-order-YYYY-MM-DD.log` (schemat jak `logs-db-*`).
|
||||||
|
|
||||||
|
Logowanie w 4 miejscach w `basketSave()`:
|
||||||
|
|
||||||
|
1. **Double-submit (pusty koszyk + istnieje stare zamówienie)** — log: `Double-submit detected, redirecting to existing order id=...`
|
||||||
|
2. **Token nieprawidłowy** — log: `Token validation failed. formToken=...`
|
||||||
|
3. **createFromBasket rzucił wyjątek** — log: `createFromBasket exception: ...`
|
||||||
|
4. **createFromBasket zwróciło falsy order_id** — log: `createFromBasket returned falsy order_id. client_id=... email=...`
|
||||||
|
|
||||||
|
## Naprawa tokena — z jednorazowego na czasowy (TTL 30 min)
|
||||||
|
|
||||||
|
### Stare zachowanie
|
||||||
|
- `createOrderSubmitToken()` generował nowy token przy KAŻDYM wejściu na podsumowanie
|
||||||
|
- Każde otwarcie nowej karty/odświeżenie nadpisywało token w sesji
|
||||||
|
- Formularz w starej karcie miał stary token → walidacja failowała
|
||||||
|
- Przy nieudanej walidacji tokena redirect na `/koszyk` (użytkownik tracił cały kontekst)
|
||||||
|
|
||||||
|
### Nowe zachowanie
|
||||||
|
- Dodana stała `ORDER_SUBMIT_TOKEN_TTL = 1800` (30 minut)
|
||||||
|
- Token przechowywany jako `['token' => '...', 'created_at' => time()]`
|
||||||
|
- `createOrderSubmitToken()`: jeśli istnieje ważny token (< 30 min), zwraca ten sam zamiast generować nowy
|
||||||
|
- `isValidOrderSubmitToken()`: sprawdza czy token nie wygasł + backward compatibility ze starymi stringowymi tokenami
|
||||||
|
- `consumeOrderSubmitToken()`: bez zmian — po złożeniu zamówienia token jest usuwany
|
||||||
|
- Przy nieudanej walidacji tokena redirect na `/koszyk-podsumowanie` (nowy token się generuje, użytkownik może od razu ponowić)
|
||||||
|
- Dodany osobny guard na double-submit: pusty koszyk + istniejące zamówienie w sesji → redirect na stronę zamówienia
|
||||||
|
|
||||||
|
### Efekt
|
||||||
|
- Wiele kart z podsumowaniem → ten sam token → wszystkie działają
|
||||||
|
- Przycisk "wstecz" → token nadal ważny
|
||||||
|
- Odświeżenie strony → token nadal ważny
|
||||||
|
- Po 30 minutach token wygasa → redirect na podsumowanie, nowy token, ponów
|
||||||
|
- Po złożeniu zamówienia token jest konsumowany + koszyk czyszczony → double-submit chroniony
|
||||||
|
- Błąd tokena nie wyrzuca na /koszyk tylko na /koszyk-podsumowanie
|
||||||
Reference in New Issue
Block a user