Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7be154d57 | ||
|
|
e0a1847127 | ||
|
|
b085c597ca | ||
|
|
34916b2dad | ||
|
|
6fa48129f8 | ||
|
|
4b3208f824 | ||
|
|
7d0e4558ab | ||
|
|
9bbcc032c2 | ||
| c9cb10950f | |||
| 1cd18c052f | |||
| d60e335ca6 |
@@ -70,7 +70,11 @@
|
||||
"mcp__serena__find_referencing_symbols",
|
||||
"Bash(cd C:\\\\visual studio code\\\\projekty\\\\shopPRO:*)",
|
||||
"Bash(cd \"/c/visual studio code/projekty/shopPRO\" && rm -rf temp/temp_317 && powershell -ExecutionPolicy Bypass -File build-update.ps1 -FromTag v0.316 -ToTag v0.317 -ChangelogEntry \"FIX - klucz API: fix zapisu \\(brakowalo w whiteliście\\), przycisk Generuj losowy klucz, ulepszony routing API\" 2>&1)",
|
||||
"Bash(./test.ps1)"
|
||||
"Bash(./test.ps1)",
|
||||
"mcp__serena__read_memory",
|
||||
"Bash(mysql -h host117523.hostido.net.pl -u host117523_shoppro -pmhA9WCEXEnRfTtbN33hL host117523_shoppro -e \"SELECT pattern, destination FROM pp_routes WHERE destination LIKE ''%product%'' OR destination LIKE ''%category%'' LIMIT 20;\")",
|
||||
"Bash(/c/xampp/php/php.exe -r \":*)",
|
||||
"Bash(/c/xampp/php/php.exe phpunit.phar --configuration phpunit.xml)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
205
.htaccess
205
.htaccess
@@ -7,67 +7,25 @@ Options -Indexes
|
||||
RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
|
||||
RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]
|
||||
|
||||
# Przekierowanie z http na https, jeśli nie zawiera www
|
||||
# Przekierowanie z http na https, jesli nie zawiera www
|
||||
RewriteCond %{HTTPS} off
|
||||
RewriteCond %{REQUEST_URI} !^/(tpay-status|platnosc-status|przelewy24-status)$ [NC]
|
||||
RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
||||
|
||||
# Usuwanie końcowego slash'a dla niekatalogów
|
||||
# Usuwanie koncowego slasha dla niekatalogów
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteCond %{REQUEST_URI} !^/admin/.*$ [NC] # Wyklucza ścieżki rozpoczynające się od "admin/"
|
||||
RewriteCond %{REQUEST_URI} !^/admin/.*$ [NC]
|
||||
RewriteCond %{REQUEST_URI} (.+)/$
|
||||
RewriteRule ^ %1 [R=301,L]
|
||||
|
||||
ErrorDocument 404 /index.php
|
||||
|
||||
RewriteCond %{REQUEST_URI} !^(.*)/libraries/(.*) [NC]
|
||||
RewriteCond %{REQUEST_URI} !^(.*)/layout/(.*) [NC]
|
||||
RewriteRule ^admin/([^/]*)/([^/]*)/(.*)$ admin/index.php?module=$1&action=$2&$3 [QSA,L]
|
||||
RewriteRule ^admin/([^/]*)/([^/]*)/(.*)$ admin/index.php?module=$1&action=$2&$3 [L]
|
||||
|
||||
RewriteRule ^admin/$ admin/index.php [L]
|
||||
|
||||
RewriteRule ^wyszukiwarka/(.*)/([0-9]*)$ index.php?module=search&action=search_results&query=$1&bs=$2 [L]
|
||||
RewriteRule ^wyszukiwarka/(.*)$ index.php?module=search&action=search_results&query=$1&bs=1 [L]
|
||||
RewriteRule ^zamowienie/([a-zA-Z0-9-]*)$ index.php?module=shop_order&action=order_details&order_hash=$1 [L]
|
||||
RewriteRule ^potwierdzenie-platnosci/([a-zA-Z0-9-]*)$ index.php?module=shop_order&action=payment_confirmation&order_hash=$1 [L]
|
||||
RewriteRule ^tpay-status$ index.php?module=shop_order&action=payment_status_tpay%{QUERY_STRING} [L]
|
||||
RewriteRule ^platnosc-status$ index.php?module=shop_order&action=payment_status_hotpay%{QUERY_STRING} [L]
|
||||
RewriteRule ^przelewy24-status$ index.php?module=shop_order&action=payment_status_przelewy24pl%{QUERY_STRING} [L]
|
||||
RewriteRule ^koszyk$ index.php?module=shop_basket&action=main_view [L]
|
||||
RewriteRule ^koszyk-podsumowanie$ index.php?module=shop_basket&action=summary_view [L]
|
||||
RewriteRule ^zloz-zamowienie$ index.php?module=shop_basket&action=basket_save [L]
|
||||
RewriteRule ^rejestracja$ index.php?module=shop_client&action=register_form [L]
|
||||
RewriteRule ^logowanie$ index.php?module=shop_client&action=login_form [L]
|
||||
RewriteRule ^wylogowanie$ index.php?module=shop_client&action=logout [L]
|
||||
RewriteRule ^odzyskiwanie-hasla$ index.php?module=shop_client&action=recover_password [L]
|
||||
RewriteRule ^panel-klienta/zamowienia$ index.php?module=shop_client&action=client_orders [L]
|
||||
RewriteRule ^panel-klienta/adresy$ index.php?module=shop_client&action=client_addresses [L]
|
||||
RewriteRule ^panel-klienta/nowy-adres$ index.php?module=shop_client&action=address_edit [L]
|
||||
RewriteRule ^panel-klienta/edytuj-adres/([0-9]*)$ index.php?module=shop_client&action=address_edit&id=$1 [L]
|
||||
RewriteRule ^panel-klienta/usun-adres/([0-9]*)$ index.php?module=shop_client&action=address_delete&id=$1 [L]
|
||||
RewriteRule ^thumb/([0-9]*)/([0-9]*)/(.*)$ /libraries/thumb.php?img=$3&w=$1&h=$2 [L]
|
||||
|
||||
RewriteCond %{REQUEST_URI} ^/shopBasket/(.*)/(.*) [NC]
|
||||
RewriteRule ^([^/]*)/([^/]*)/(.*)$ index.php?module=$1&action=$2&$3 [L]
|
||||
RewriteCond %{REQUEST_URI} ^/shopClient/(.*)/(.*) [NC]
|
||||
RewriteRule ^([^/]*)/([^/]*)/(.*)$ index.php?module=$1&action=$2&$3 [L]
|
||||
RewriteCond %{REQUEST_URI} ^/shopProduct/(.*)/(.*) [NC]
|
||||
RewriteRule ^([^/]*)/([^/]*)/(.*)$ index.php?module=$1&action=$2&$3 [L]
|
||||
RewriteCond %{REQUEST_URI} ^/shopCoupon/(.*)/(.*) [NC]
|
||||
RewriteRule ^([^/]*)/([^/]*)/(.*)$ index.php?module=$1&action=$2&$3 [L]
|
||||
RewriteCond %{REQUEST_URI} ^/search/(.*)/(.*) [NC]
|
||||
RewriteRule ^([^/]*)/([^/]*)/(.*)$ index.php?module=$1&action=$2&$3 [L]
|
||||
|
||||
RewriteCond %{REQUEST_URI} ^/shopBasket/(.*) [NC]
|
||||
RewriteRule ^([^/]*)/([^/]*)$ index.php?module=$1&action=$2 [L]
|
||||
RewriteCond %{REQUEST_URI} ^/shopClient/(.*) [NC]
|
||||
RewriteRule ^([^/]*)/([^/]*)$ index.php?module=$1&action=$2 [L]
|
||||
RewriteCond %{REQUEST_URI} ^/shopProduct/(.*) [NC]
|
||||
RewriteRule ^([^/]*)/([^/]*)$ index.php?module=$1&action=$2 [L]
|
||||
RewriteCond %{REQUEST_URI} ^/shopCoupon/(.*) [NC]
|
||||
RewriteRule ^([^/]*)/([^/]*)$ index.php?module=$1&action=$2 [L]
|
||||
RewriteCond %{REQUEST_URI} ^/search/(.*) [NC]
|
||||
RewriteRule ^([^/]*)/([^/]*)$ index.php?module=$1&action=$2 [L]
|
||||
|
||||
RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /index.php
|
||||
RewriteRule ^ /%1 [R=301,L]
|
||||
<IfModule mod_deflate.c>
|
||||
@@ -116,168 +74,17 @@ ExpiresByType image/svg+xml "access plus 1 month"
|
||||
Order Deny,Allow
|
||||
Deny from all
|
||||
</Files>
|
||||
RewriteRule ^pl/$ index.php?a=change_language&id=pl [L]
|
||||
RewriteRule ^en/$ index.php?a=change_language&id=en [L]
|
||||
RewriteRule ^newsletter/signin/$ index.php?module=newsletter&action=signin [L]
|
||||
RewriteRule ^newsletter/confirm/hash=(.*)$ index.php?module=newsletter&action=confirm&hash=$1 [L]
|
||||
RewriteRule ^newsletter/unsubscribe/hash=(.*)$ index.php?module=newsletter&action=unsubscribe&hash=$1 [L]
|
||||
RewriteRule ^producenci$ index.php?module=shop_producer&action=list&layout_id=2&%{QUERY_STRING} [L]
|
||||
RewriteRule ^producent/bibs$ index.php?module=shop_producer&action=products&producer_id=3&layout_id=2&%{QUERY_STRING} [L]
|
||||
RewriteRule ^producent/bibs/([0-9]+)$ index.php?module=shop_producer&action=products&producer_id=3&layout_id=2&bs=$1&%{QUERY_STRING} [L]
|
||||
|
||||
RewriteRule ^sen-i-otulenie$ index.php?category=10&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^sen-i-otulenie/([0-9]+)$ index.php?category=10&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^sen-i-otulenie/1$ sen-i-otulenie [R=301,L]
|
||||
RewriteRule ^kocyki-minky$ index.php?category=5&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kocyki-minky/([0-9]+)$ index.php?category=5&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kocyki-minky/1$ kocyki-minky [R=301,L]
|
||||
RewriteRule ^kocyki-niemowlece-minky-50x70$ index.php?category=6&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kocyki-niemowlece-minky-50x70/([0-9]+)$ index.php?category=6&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kocyki-niemowlece-minky-50x70/1$ kocyki-niemowlece-minky-50x70 [R=301,L]
|
||||
RewriteRule ^kocyki-sredniaka-minky-75x100$ index.php?category=7&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kocyki-sredniaka-minky-75x100/([0-9]+)$ index.php?category=7&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kocyki-sredniaka-minky-75x100/1$ kocyki-sredniaka-minky-75x100 [R=301,L]
|
||||
RewriteRule ^kocyki-przedszkolaka-minky-100x130$ index.php?category=8&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kocyki-przedszkolaka-minky-100x130/([0-9]+)$ index.php?category=8&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kocyki-przedszkolaka-minky-100x130/1$ kocyki-przedszkolaka-minky-100x130 [R=301,L]
|
||||
RewriteRule ^poduszki$ index.php?category=2&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^poduszki/([0-9]+)$ index.php?category=2&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^poduszki/1$ poduszki [R=301,L]
|
||||
RewriteRule ^poduszki-niemowlaka-minky-25x35$ index.php?category=18&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^poduszki-niemowlaka-minky-25x35/([0-9]+)$ index.php?category=18&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^poduszki-niemowlaka-minky-25x35/1$ poduszki-niemowlaka-minky-25x35 [R=301,L]
|
||||
RewriteRule ^poduszki/gwiazdki-40x40$ index.php?category=9&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^poduszki/gwiazdki-40x40/([0-9]+)$ index.php?category=9&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^poduszki/gwiazdki-40x40/1$ poduszki/gwiazdki-40x40 [R=301,L]
|
||||
RewriteRule ^rozki$ index.php?category=1&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^rozki/([0-9]+)$ index.php?category=1&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^rozki/1$ rozki [R=301,L]
|
||||
RewriteRule ^akcesoria$ index.php?category=4&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^akcesoria/([0-9]+)$ index.php?category=4&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^akcesoria/1$ akcesoria [R=301,L]
|
||||
RewriteRule ^metryczki-dzieciece$ index.php?category=11&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^metryczki-dzieciece/([0-9]+)$ index.php?category=11&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^metryczki-dzieciece/1$ metryczki-dzieciece [R=301,L]
|
||||
RewriteRule ^metryczki-ze-zdjeciem$ index.php?category=39&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^metryczki-ze-zdjeciem/([0-9]+)$ index.php?category=39&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^metryczki-ze-zdjeciem/1$ metryczki-ze-zdjeciem [R=301,L]
|
||||
RewriteRule ^metryczki-dla-dziewczynki$ index.php?category=40&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^metryczki-dla-dziewczynki/([0-9]+)$ index.php?category=40&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^metryczki-dla-dziewczynki/1$ metryczki-dla-dziewczynki [R=301,L]
|
||||
RewriteRule ^metryczki-dla-chlopca$ index.php?category=41&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^metryczki-dla-chlopca/([0-9]+)$ index.php?category=41&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^metryczki-dla-chlopca/1$ metryczki-dla-chlopca [R=301,L]
|
||||
RewriteRule ^termofory-dla-dzieci$ index.php?category=17&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^termofory-dla-dzieci/([0-9]+)$ index.php?category=17&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^termofory-dla-dzieci/1$ termofory-dla-dzieci [R=301,L]
|
||||
RewriteRule ^zawieszki$ index.php?category=43&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^zawieszki/([0-9]+)$ index.php?category=43&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^zawieszki/1$ zawieszki [R=301,L]
|
||||
RewriteRule ^zawieszki-dekoracyjne$ index.php?category=32&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^zawieszki-dekoracyjne/([0-9]+)$ index.php?category=32&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^zawieszki-dekoracyjne/1$ zawieszki-dekoracyjne [R=301,L]
|
||||
RewriteRule ^zawieszki-do-smoczkow-i-gryzakow$ index.php?category=44&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^zawieszki-do-smoczkow-i-gryzakow/([0-9]+)$ index.php?category=44&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^zawieszki-do-smoczkow-i-gryzakow/1$ zawieszki-do-smoczkow-i-gryzakow [R=301,L]
|
||||
RewriteRule ^zawieszki-do-wozka$ index.php?category=45&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^zawieszki-do-wozka/([0-9]+)$ index.php?category=45&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^zawieszki-do-wozka/1$ zawieszki-do-wozka [R=301,L]
|
||||
RewriteRule ^odziez-dziecieca$ index.php?category=12&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^odziez-dziecieca/([0-9]+)$ index.php?category=12&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^odziez-dziecieca/1$ odziez-dziecieca [R=301,L]
|
||||
RewriteRule ^apaszki$ index.php?category=35&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^apaszki/([0-9]+)$ index.php?category=35&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^apaszki/1$ apaszki [R=301,L]
|
||||
RewriteRule ^kominy-dzieciece$ index.php?category=15&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kominy-dzieciece/([0-9]+)$ index.php?category=15&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kominy-dzieciece/1$ kominy-dzieciece [R=301,L]
|
||||
RewriteRule ^opaski$ index.php?category=37&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^opaski/([0-9]+)$ index.php?category=37&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^opaski/1$ opaski [R=301,L]
|
||||
RewriteRule ^opaski-pin-up$ index.php?category=38&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^opaski-pin-up/([0-9]+)$ index.php?category=38&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^opaski-pin-up/1$ opaski-pin-up [R=301,L]
|
||||
RewriteRule ^turbany$ index.php?category=14&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^turbany/([0-9]+)$ index.php?category=14&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^turbany/1$ turbany [R=301,L]
|
||||
RewriteRule ^ubrania-dla-dziewczynek$ index.php?category=13&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^ubrania-dla-dziewczynek/([0-9]+)$ index.php?category=13&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^ubrania-dla-dziewczynek/1$ ubrania-dla-dziewczynek [R=301,L]
|
||||
RewriteRule ^zestawy-i-kolekcje$ index.php?category=16&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^zestawy-i-kolekcje/([0-9]+)$ index.php?category=16&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^zestawy-i-kolekcje/1$ zestawy-i-kolekcje [R=301,L]
|
||||
RewriteRule ^zestawy$ index.php?category=20&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^zestawy/([0-9]+)$ index.php?category=20&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^zestawy/1$ zestawy [R=301,L]
|
||||
RewriteRule ^komplet-niemowlaka$ index.php?category=24&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^komplet-niemowlaka/([0-9]+)$ index.php?category=24&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^komplet-niemowlaka/1$ komplet-niemowlaka [R=301,L]
|
||||
RewriteRule ^komplet-sredniaka$ index.php?category=28&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^komplet-sredniaka/([0-9]+)$ index.php?category=28&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^komplet-sredniaka/1$ komplet-sredniaka [R=301,L]
|
||||
RewriteRule ^kolekcje$ index.php?category=29&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kolekcje/([0-9]+)$ index.php?category=29&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kolekcje/1$ kolekcje [R=301,L]
|
||||
RewriteRule ^mama-bear-chmurki-mietowe$ index.php?category=36&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^mama-bear-chmurki-mietowe/([0-9]+)$ index.php?category=36&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^mama-bear-chmurki-mietowe/1$ mama-bear-chmurki-mietowe [R=301,L]
|
||||
RewriteRule ^koniki-na-biegunach$ index.php?category=31&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^koniki-na-biegunach/([0-9]+)$ index.php?category=31&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^koniki-na-biegunach/1$ koniki-na-biegunach [R=301,L]
|
||||
RewriteRule ^kroliki-na-hustawkach$ index.php?category=30&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kroliki-na-hustawkach/([0-9]+)$ index.php?category=30&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kroliki-na-hustawkach/1$ kroliki-na-hustawkach [R=301,L]
|
||||
RewriteRule ^wyprzedaz$ index.php?category=27&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^wyprzedaz/([0-9]+)$ index.php?category=27&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^wyprzedaz/1$ wyprzedaz [R=301,L]
|
||||
RewriteRule ^en/kocyk-minky-niemowlaka-50x70-en$ index.php?category=6&lang=en&%{QUERY_STRING} [L]
|
||||
RewriteRule ^en/kocyk-minky-niemowlaka-50x70-en/([0-9]+)$ index.php?category=6&lang=en&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^en/kocyk-minky-niemowlaka-50x70-en/1$ en/kocyk-minky-niemowlaka-50x70-en [R=301,L]
|
||||
RewriteCond %{REQUEST_URI} ^/home$
|
||||
RewriteRule ^(.*)$ http://www.shoppro.project-dc.pl/ [R=permanent,L]
|
||||
RewriteCond %{REQUEST_URI} ^/home-1$
|
||||
RewriteRule ^(.*)$ http://www.shoppro.project-dc.pl/ [R=permanent,L]
|
||||
RewriteRule ^$ index.php?a=page&id=6&lang=pl [L]
|
||||
RewriteRule ^home$ index.php?a=page&id=6&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^home/([0-9]+)$ index.php?a=page&id=6&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^home/1$ home [R=301,L]
|
||||
RewriteRule ^regulamin$ index.php?a=page&id=12&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^regulamin/([0-9]+)$ index.php?a=page&id=12&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^regulamin/1$ regulamin [R=301,L]
|
||||
RewriteRule ^formy-platnosci$ index.php?a=page&id=13&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^formy-platnosci/([0-9]+)$ index.php?a=page&id=13&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^formy-platnosci/1$ formy-platnosci [R=301,L]
|
||||
RewriteRule ^koszty-dostawy$ index.php?a=page&id=14&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^koszty-dostawy/([0-9]+)$ index.php?a=page&id=14&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^koszty-dostawy/1$ koszty-dostawy [R=301,L]
|
||||
RewriteRule ^zwroty-i-reklamacje$ index.php?a=page&id=15&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^zwroty-i-reklamacje/([0-9]+)$ index.php?a=page&id=15&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^zwroty-i-reklamacje/1$ zwroty-i-reklamacje [R=301,L]
|
||||
RewriteRule ^o-nas$ index.php?a=page&id=4&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^o-nas/([0-9]+)$ index.php?a=page&id=4&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^o-nas/1$ o-nas [R=301,L]
|
||||
RewriteRule ^blog$ index.php?a=page&id=9&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^blog/([0-9]+)$ index.php?a=page&id=9&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^blog/1$ blog [R=301,L]
|
||||
RewriteRule ^kontakt$ index.php?a=page&id=5&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kontakt/([0-9]+)$ index.php?a=page&id=5&lang=pl&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^kontakt/1$ kontakt [R=301,L]
|
||||
RewriteRule ^kolka-u-niemowlat-przyczyny-objawy-leczenie$ index.php?article=11&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^spacery-z-niemowlakiem-jak-sie-do-nich-przygotowac$ index.php?article=12&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^jak-wybrac-kocyk-i-poduszke-niemowlaka$ index.php?article=10&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteRule ^jak-wzmocnic-odpornosc-dziecka-w-trakcie-zimy-sprawdzone-sposoby-na-odpornosc$ index.php?article=13&lang=pl&%{QUERY_STRING} [L]
|
||||
RewriteCond %{REQUEST_URI} ^/home-en$
|
||||
RewriteRule ^(.*)$ http://www.shoppro.project-dc.pl/en/ [R=permanent,L]
|
||||
RewriteCond %{REQUEST_URI} ^/home-en-1$
|
||||
RewriteRule ^(.*)$ http://www.shoppro.project-dc.pl/en/ [R=permanent,L]
|
||||
RewriteRule ^$ index.php?a=page&id=6&lang=en [L]
|
||||
RewriteRule ^en/home-en$ index.php?a=page&id=6&lang=en&%{QUERY_STRING} [L]
|
||||
RewriteRule ^en/home-en/([0-9]+)$ index.php?a=page&id=6&lang=en&bs=$1&%{QUERY_STRING} [L]
|
||||
RewriteRule ^en/home-en/1$ en/home-en [R=301,L]
|
||||
RewriteRule ^en/tytul-en$ index.php?article=13&lang=en&%{QUERY_STRING} [L]
|
||||
RewriteCond %{REQUEST_FILENAME} !-f
|
||||
RewriteCond %{REQUEST_FILENAME} !-d
|
||||
RewriteRule ^ index.php [L]
|
||||
# <FilesMatch "\.(php4|php5|php3|php2|php|phtml)$">
|
||||
# SetHandler application/x-lsphp83 /opt/alt/php83 usr/bin/lsphp
|
||||
# </FilesMatch>
|
||||
RewriteRule ^ index.php [L]
|
||||
File diff suppressed because one or more lines are too long
@@ -123,3 +123,7 @@ symbol_info_budget:
|
||||
# Note: the backend is fixed at startup. If a project with a different backend
|
||||
# is activated post-init, an error will be returned.
|
||||
language_backend:
|
||||
|
||||
# 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.
|
||||
read_only_memory_patterns: []
|
||||
|
||||
262
AGENTS.md
262
AGENTS.md
@@ -1,30 +1,246 @@
|
||||
# Workflow
|
||||
# CLAUDE.md
|
||||
|
||||
## KONIEC PRACY
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||
|
||||
Gdy użytkownik napisze `KONIEC PRACY`, wykonaj kolejno:
|
||||
## Project Overview
|
||||
|
||||
1. Przeprowadzenie testów.
|
||||
2. Aktualizacja dokumentacji technicznej, jeśli zmiany tego wymagają:
|
||||
- `docs/DATABASE_STRUCTURE.md`
|
||||
- `docs/PROJECT_STRUCTURE.md`
|
||||
- `docs/FORM_EDIT_SYSTEM.md`
|
||||
- `docs/CHANGELOG.md`
|
||||
- `docs/TESTING.md`
|
||||
3. Migracje SQL (jeśli były zmiany w bazie danych):
|
||||
- Plik: `migrations/{version}.sql` (np. `migrations/0.304.sql`)
|
||||
- **NIE** w `updates/` — build script sam wczyta z `migrations/`
|
||||
- Sprawdź czy plik istnieje i jest poprawnie nazwany przed commitem
|
||||
4. Commit.
|
||||
5. Push.
|
||||
shopPRO is a PHP e-commerce platform with an admin panel and customer-facing storefront. It uses Medoo ORM (`$mdb`), Redis caching, and a Domain-Driven Design architecture with Dependency Injection (migration from legacy architecture complete).
|
||||
|
||||
## PRZED ROZPOCZĘCIEM PRACY
|
||||
## Zasady pisania kodu
|
||||
- Kod ma być czytelny „dla obcego”: jasne nazwy, mało magii
|
||||
- Brak „skrótów na szybko” typu logika w widokach, copy-paste, losowe helpery bez spójności
|
||||
- Każda funkcja/klasa ma mieć jedną odpowiedzialność, zwykle do 30–50 linii (jeśli dłuższe – dzielić)
|
||||
- max 3 poziomy zagnieżdżeń (if/foreach), reszta do osobnych metod
|
||||
- Nazewnictwo:
|
||||
- klasy: PascalCase
|
||||
- metody/zmienne: camelCase
|
||||
- stałe: UPPER_SNAKE_CASE
|
||||
- Zero „skrótologii” w nazwach (np. $d, $tmp, $x1) poza pętlami 2–3 linijki
|
||||
- medoo + prepared statements bez wyjątków (żadnego sklejania SQL stringiem)
|
||||
- XSS: escape w widokach (np. helper e())
|
||||
- CSRF dla formularzy, sensowna obsługa sesji
|
||||
- Kod ma mieć komentarze tylko tam, gdzie wyjaśniają „dlaczego”, nie „co”
|
||||
|
||||
Przed rozpoczęciem implementacji sprawdź aktualną zawartość:
|
||||
## PHP Version Constraint
|
||||
|
||||
- `docs/DATABASE_STRUCTURE.md`
|
||||
- `docs/PROJECT_STRUCTURE.md`
|
||||
- `docs/CHANGELOG.md`
|
||||
- `docs/TESTING.md`
|
||||
**Production runs PHP < 8.0.** Do NOT use:
|
||||
- `match` expressions (use ternary operators or if/else)
|
||||
- Named arguments
|
||||
- Union types (`int|string`)
|
||||
- `str_contains()`, `str_starts_with()`, `str_ends_with()`
|
||||
- Other PHP 8.0+ syntax
|
||||
|
||||
To ma pomóc zachować spójność zmian i dokumentacji.
|
||||
`composer.json` requires `>=7.4`.
|
||||
|
||||
## Commands
|
||||
|
||||
### Running Tests
|
||||
```bash
|
||||
# Full suite (recommended — PowerShell, auto-finds php)
|
||||
./test.ps1
|
||||
|
||||
# Specific file
|
||||
./test.ps1 tests/Unit/Domain/Product/ProductRepositoryTest.php
|
||||
|
||||
# Specific test method
|
||||
./test.ps1 --filter testGetQuantityReturnsCorrectValue
|
||||
|
||||
# Alternative
|
||||
composer test
|
||||
```
|
||||
|
||||
PHPUnit 9.6 via `phpunit.phar`. Bootstrap: `tests/bootstrap.php`. Config: `phpunit.xml`.
|
||||
|
||||
Current suite: **805 tests, 2253 assertions**.
|
||||
|
||||
### Creating Updates
|
||||
See `docs/UPDATE_INSTRUCTIONS.md` for the full procedure. Updates are ZIP packages in `updates/0.XX/`. Never include `*.md` files, `updates/changelog.php`, or root `.htaccess` in update ZIPs.
|
||||
|
||||
## Architecture
|
||||
|
||||
### Directory Structure
|
||||
```
|
||||
shopPRO/
|
||||
├── autoload/ # Autoloaded classes (core codebase)
|
||||
│ ├── Domain/ # Business logic repositories (\Domain\)
|
||||
│ ├── Shared/ # Shared utilities (\Shared\)
|
||||
│ │ ├── Cache/ # CacheHandler, RedisConnection
|
||||
│ │ ├── Email/ # Email (PHPMailer wrapper)
|
||||
│ │ ├── Helpers/ # Helpers (formerly class.S.php)
|
||||
│ │ ├── Html/ # Html utility
|
||||
│ │ ├── Image/ # ImageManipulator
|
||||
│ │ └── Tpl/ # Template engine
|
||||
│ ├── api/ # REST API layer (\api\)
|
||||
│ │ ├── ApiRouter.php # API router (\api\ApiRouter)
|
||||
│ │ └── Controllers/ # API controllers (\api\Controllers\)
|
||||
│ ├── admin/ # Admin panel layer
|
||||
│ │ ├── App.php # Admin router (\admin\App)
|
||||
│ │ ├── Controllers/ # DI controllers (\admin\Controllers\) — 28 controllers
|
||||
│ │ ├── Support/ # TableListRequestFactory, Forms/FormRequestHandler, Forms/FormFieldRenderer
|
||||
│ │ ├── Validation/ # FormValidator
|
||||
│ │ └── ViewModels/ # Forms/ (FormEditViewModel, FormField, FormTab, FormAction, FormFieldType), Common/ (PaginatedTableViewModel)
|
||||
│ └── front/ # Frontend layer
|
||||
│ ├── App.php # Frontend router (\front\App)
|
||||
│ ├── LayoutEngine.php # Layout engine (\front\LayoutEngine)
|
||||
│ ├── Controllers/ # DI controllers (\front\Controllers\) — 8 controllers
|
||||
│ └── Views/ # Static views (\front\Views\) — 11 view classes
|
||||
├── admin/ # Admin panel
|
||||
│ ├── templates/ # Admin view templates
|
||||
│ └── layout/ # Admin CSS/JS/icons
|
||||
├── templates/ # Frontend view templates
|
||||
├── libraries/ # Third-party libraries (Medoo, RedBeanPHP, PHPMailer)
|
||||
├── tests/ # PHPUnit tests
|
||||
│ ├── bootstrap.php
|
||||
│ ├── stubs/ # Test stubs (CacheHandler, Helpers, ShopProduct)
|
||||
│ └── Unit/
|
||||
│ ├── Domain/ # Repository tests
|
||||
│ ├── admin/Controllers/ # Controller tests
|
||||
│ └── api/ # API tests
|
||||
├── updates/ # Update packages for clients
|
||||
├── docs/ # Technical documentation
|
||||
├── config.php # Database/Redis config (not in repo)
|
||||
├── index.php # Frontend entry point
|
||||
├── ajax.php # Frontend AJAX handler
|
||||
├── admin/index.php # Admin entry point
|
||||
├── admin/ajax.php # Admin AJAX handler
|
||||
├── cron.php # CRON jobs (Apilo sync)
|
||||
└── api.php # REST API (ordersPRO + Ekomi)
|
||||
```
|
||||
|
||||
### Autoloader
|
||||
|
||||
Custom autoloader in each entry point (not Composer autoload at runtime). Tries two filename conventions:
|
||||
1. `autoload/{namespace}/class.{ClassName}.php` (legacy)
|
||||
2. `autoload/{namespace}/{ClassName}.php` (PSR-4 style, fallback)
|
||||
|
||||
### Namespace Conventions (case-sensitive on Linux!)
|
||||
- `\Domain\` → `autoload/Domain/` (uppercase D)
|
||||
- `\admin\Controllers\` → `autoload/admin/Controllers/` (lowercase a)
|
||||
- `\Shared\` → `autoload/Shared/`
|
||||
- `\api\` → `autoload/api/`
|
||||
- Do NOT use `\Admin\` (uppercase A) — the server directory is `admin/` (lowercase)
|
||||
- `\shop\` namespace is **deleted** — all 12 legacy classes migrated to `\Domain\`, `autoload/shop/` directory removed
|
||||
|
||||
### Domain-Driven Architecture (migration complete)
|
||||
|
||||
All legacy directories (`admin/controls/`, `admin/factory/`, `admin/view/`, `front/controls/`, `front/view/`, `front/factory/`, `shop/`) have been deleted. All modules now use this pattern:
|
||||
|
||||
**Domain Layer** (`autoload/Domain/{Module}/`):
|
||||
- `{Module}Repository.php` — data access, business logic, Redis caching
|
||||
- Constructor DI with `$db` (Medoo instance)
|
||||
- Methods serve both admin and frontend (shared Domain, no separate services)
|
||||
|
||||
**Domain Modules**: Article, Attribute, Banner, Basket, Cache, Category, Client, Coupon, CronJob, Dashboard, Dictionaries, Integrations, Languages, Layouts, Newsletter, Order, Pages, PaymentMethod, Producer, Product, ProductSet, Promotion, Scontainers, Settings, ShopStatus, Transport, Update, User
|
||||
|
||||
**Admin Controllers** (`autoload/admin/Controllers/`):
|
||||
- DI via constructor (repositories injected)
|
||||
- Wired in `admin\App::getControllerFactories()`
|
||||
|
||||
**Frontend Controllers** (`autoload/front/Controllers/`):
|
||||
- DI via constructor
|
||||
- Wired in `front\App::getControllerFactories()`
|
||||
|
||||
**Frontend Views** (`autoload/front/Views/`):
|
||||
- Static classes, no state, no DI — pure rendering
|
||||
|
||||
**API Controllers** (`autoload/api/Controllers/`):
|
||||
- DI via constructor, stateless (no session)
|
||||
- Wired in `api\ApiRouter::getControllerFactories()`
|
||||
- Auth: `X-Api-Key` header vs `pp_settings.api_key`
|
||||
|
||||
### Key Classes
|
||||
| Class | Purpose |
|
||||
|-------|---------|
|
||||
| `\admin\App` | Admin router — maps URL segments to controllers |
|
||||
| `\front\App` | Frontend router — `route()`, `checkUrlParams()` |
|
||||
| `\front\LayoutEngine` | Frontend layout engine — `show()`, tag replacement |
|
||||
| `\Shared\Helpers\Helpers` | Utility methods (SEO, email, cache clearing) |
|
||||
| `\Shared\Tpl\Tpl` | Template engine — `render()`, `set()` |
|
||||
| `\Shared\Cache\CacheHandler` | Redis cache — `get()`, `set()`, `delete()`, `deletePattern()` |
|
||||
| `\api\ApiRouter` | REST API router — auth, routing, response helpers |
|
||||
|
||||
### Database
|
||||
- ORM: Medoo (`$mdb` global variable, injected via DI in new code)
|
||||
- Table prefix: `pp_`
|
||||
- Key tables: `pp_shop_products`, `pp_shop_orders`, `pp_shop_categories`, `pp_shop_clients`
|
||||
- Full schema: `docs/DATABASE_STRUCTURE.md`
|
||||
|
||||
### Form Edit System
|
||||
Universal form system for admin edit views. Docs: `docs/FORM_EDIT_SYSTEM.md`.
|
||||
- **ViewModels** (`admin\ViewModels\Forms\`): `FormEditViewModel`, `FormField`, `FormTab`, `FormAction`, `FormFieldType`
|
||||
- **Validation**: `admin\Validation\FormValidator`
|
||||
- **Rendering**: `admin\Support\Forms\FormFieldRenderer`, `admin\Support\Forms\FormRequestHandler`
|
||||
- **Template**: `admin/templates/components/form-edit.php`
|
||||
- **Table lists**: `admin\Support\TableListRequestFactory` + `admin\ViewModels\Common\PaginatedTableViewModel`
|
||||
|
||||
### Caching
|
||||
- Redis via `\Shared\Cache\CacheHandler` (singleton `RedisConnection`)
|
||||
- Key pattern for products: `shop\product:{id}:{lang}:{permutation_hash}`
|
||||
- Clear product cache: `\Shared\Helpers\Helpers::clear_product_cache($id)`
|
||||
- Pattern delete: `CacheHandler::deletePattern("shop\\product:{$id}:*")`
|
||||
- Default TTL: 86400 (24h)
|
||||
- Data is serialized — requires `unserialize()` after `get()`
|
||||
- Config: `config.php` (`$config['redis']`)
|
||||
|
||||
## Code Patterns
|
||||
|
||||
### New code should follow DI pattern
|
||||
```php
|
||||
// Repository with constructor DI
|
||||
class ExampleRepository {
|
||||
private $db;
|
||||
public function __construct($db) {
|
||||
$this->db = $db;
|
||||
}
|
||||
public function find(int $id): ?array {
|
||||
return $this->db->get('pp_table', '*', ['id' => $id]);
|
||||
}
|
||||
}
|
||||
|
||||
// Controller wiring (in admin\App or front\App)
|
||||
$repo = new \Domain\Example\ExampleRepository($mdb);
|
||||
$controller = new \admin\Controllers\ExampleController($repo);
|
||||
```
|
||||
|
||||
### Medoo ORM pitfalls
|
||||
- `$mdb->delete($table, $where)` takes **2 arguments**, NOT 3 — has caused bugs
|
||||
- `$mdb->get()` returns `null` when no record, NOT `false`
|
||||
- After `$mdb->insert()`, check `$mdb->id()` to confirm success
|
||||
|
||||
### File naming
|
||||
- New classes: `ClassName.php` (no `class.` prefix)
|
||||
- Legacy classes: `class.ClassName.php` (leave until migrated)
|
||||
|
||||
### Test conventions
|
||||
- Extend `PHPUnit\Framework\TestCase`
|
||||
- Mock Medoo: `$this->createMock(\medoo::class)`
|
||||
- AAA pattern: Arrange, Act, Assert
|
||||
- Tests mirror source structure: `tests/Unit/Domain/{Module}/{Class}Test.php`
|
||||
|
||||
## Workflow (AGENTS.md)
|
||||
|
||||
When user says **"KONIEC PRACY"**, execute in order:
|
||||
1. Run tests
|
||||
2. Update documentation if needed: `docs/DATABASE_STRUCTURE.md`, `docs/PROJECT_STRUCTURE.md`, `docs/FORM_EDIT_SYSTEM.md`, `docs/CHANGELOG.md`, `docs/TESTING.md`
|
||||
3. SQL migrations (if DB changes): place in `migrations/{version}.sql` (e.g. `migrations/0.304.sql`). **NOT** in `updates/` — build script reads from `migrations/` automatically
|
||||
4. Commit
|
||||
5. Push
|
||||
6. Build update package: `git tag v0.XXX && powershell.exe -ExecutionPolicy Bypass -File build-update.ps1 -FromTag v0.PREV -ToTag v0.XXX -ChangelogEntry "opis"` — skrypt automatycznie aktualizuje `versions.php`
|
||||
7. Commit i push plików paczki (`updates/0.30/ver_0.XXX.zip`, `ver_0.XXX_manifest.json`, `updates/versions.php`, `updates/changelog-data.html`)
|
||||
|
||||
Before starting implementation, review current state of docs (see AGENTS.md for full list).
|
||||
|
||||
## Key Documentation
|
||||
- `docs/MEMORY.md` — project memory: known issues, confirmed patterns, ORM pitfalls, caching conventions
|
||||
- `docs/PROJECT_STRUCTURE.md` — current architecture, layers, cache, entry points, integrations
|
||||
- `docs/DATABASE_STRUCTURE.md` — full database schema
|
||||
- `docs/TESTING.md` — test suite guide and structure
|
||||
- `docs/FORM_EDIT_SYSTEM.md` — form system architecture
|
||||
- `docs/CHANGELOG.md` — version history
|
||||
- `api-docs/api-reference.json` — REST API documentation (ordersPRO)
|
||||
- `api-docs/index.html` — REST API documentation (ordersPRO)
|
||||
- `docs/UPDATE_INSTRUCTIONS.md` — how to build client update packages
|
||||
|
||||
## Za każdym razem jak próbujesz sprawdzić jakiś plik z logami spróbuj go najpierw pobrać z serwera FTP
|
||||
|
||||
## Wszystkie pliki które tworzysz jako pomocnicze, np build_0330.ps1 czy build-update.ps1 twórz w folderze temp
|
||||
27
CLAUDE.md
27
CLAUDE.md
@@ -6,6 +6,21 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
|
||||
|
||||
shopPRO is a PHP e-commerce platform with an admin panel and customer-facing storefront. It uses Medoo ORM (`$mdb`), Redis caching, and a Domain-Driven Design architecture with Dependency Injection (migration from legacy architecture complete).
|
||||
|
||||
## Zasady pisania kodu
|
||||
- Kod ma być czytelny „dla obcego”: jasne nazwy, mało magii
|
||||
- Brak „skrótów na szybko” typu logika w widokach, copy-paste, losowe helpery bez spójności
|
||||
- Każda funkcja/klasa ma mieć jedną odpowiedzialność, zwykle do 30–50 linii (jeśli dłuższe – dzielić)
|
||||
- max 3 poziomy zagnieżdżeń (if/foreach), reszta do osobnych metod
|
||||
- Nazewnictwo:
|
||||
- klasy: PascalCase
|
||||
- metody/zmienne: camelCase
|
||||
- stałe: UPPER_SNAKE_CASE
|
||||
- Zero „skrótologii” w nazwach (np. $d, $tmp, $x1) poza pętlami 2–3 linijki
|
||||
- medoo + prepared statements bez wyjątków (żadnego sklejania SQL stringiem)
|
||||
- XSS: escape w widokach (np. helper e())
|
||||
- CSRF dla formularzy, sensowna obsługa sesji
|
||||
- Kod ma mieć komentarze tylko tam, gdzie wyjaśniają „dlaczego”, nie „co”
|
||||
|
||||
## PHP Version Constraint
|
||||
|
||||
**Production runs PHP < 8.0.** Do NOT use:
|
||||
@@ -36,7 +51,7 @@ composer test
|
||||
|
||||
PHPUnit 9.6 via `phpunit.phar`. Bootstrap: `tests/bootstrap.php`. Config: `phpunit.xml`.
|
||||
|
||||
Current suite: **805 tests, 2253 assertions**.
|
||||
Current suite: **810 tests, 2264 assertions**.
|
||||
|
||||
### Creating Updates
|
||||
See `docs/UPDATE_INSTRUCTIONS.md` for the full procedure. Updates are ZIP packages in `updates/0.XX/`. Never include `*.md` files, `updates/changelog.php`, or root `.htaccess` in update ZIPs.
|
||||
@@ -102,7 +117,6 @@ Custom autoloader in each entry point (not Composer autoload at runtime). Tries
|
||||
- `\Domain\` → `autoload/Domain/` (uppercase D)
|
||||
- `\admin\Controllers\` → `autoload/admin/Controllers/` (lowercase a)
|
||||
- `\Shared\` → `autoload/Shared/`
|
||||
- `\front\` → `autoload/front/`
|
||||
- `\api\` → `autoload/api/`
|
||||
- Do NOT use `\Admin\` (uppercase A) — the server directory is `admin/` (lowercase)
|
||||
- `\shop\` namespace is **deleted** — all 12 legacy classes migrated to `\Domain\`, `autoload/shop/` directory removed
|
||||
@@ -211,6 +225,8 @@ When user says **"KONIEC PRACY"**, execute in order:
|
||||
3. SQL migrations (if DB changes): place in `migrations/{version}.sql` (e.g. `migrations/0.304.sql`). **NOT** in `updates/` — build script reads from `migrations/` automatically
|
||||
4. Commit
|
||||
5. Push
|
||||
6. Build update package: `git tag v0.XXX && powershell.exe -ExecutionPolicy Bypass -File build-update.ps1 -FromTag v0.PREV -ToTag v0.XXX -ChangelogEntry "opis"` — skrypt automatycznie aktualizuje `versions.php`
|
||||
7. Commit i push plików paczki (`updates/0.30/ver_0.XXX.zip`, `ver_0.XXX_manifest.json`, `updates/versions.php`, `updates/changelog-data.html`)
|
||||
|
||||
Before starting implementation, review current state of docs (see AGENTS.md for full list).
|
||||
|
||||
@@ -221,7 +237,10 @@ Before starting implementation, review current state of docs (see AGENTS.md for
|
||||
- `docs/TESTING.md` — test suite guide and structure
|
||||
- `docs/FORM_EDIT_SYSTEM.md` — form system architecture
|
||||
- `docs/CHANGELOG.md` — version history
|
||||
- `docs/API.md` — REST API documentation (ordersPRO)
|
||||
- `api-docs/api-reference.json` — REST API documentation (ordersPRO)
|
||||
- `api-docs/index.html` — REST API documentation (ordersPRO)
|
||||
- `docs/UPDATE_INSTRUCTIONS.md` — how to build client update packages
|
||||
|
||||
## Za każdym razem jak próbujesz sprawdzić jakiś plik z logami spróbuj go najpierw pobrać z serwera FTP
|
||||
## Za każdym razem jak próbujesz sprawdzić jakiś plik z logami spróbuj go najpierw pobrać z serwera FTP
|
||||
|
||||
## Wszystkie pliki które tworzysz jako pomocnicze, np build_0330.ps1 czy build-update.ps1 twórz w folderze temp
|
||||
292
api-docs/api-reference.json
Normal file
292
api-docs/api-reference.json
Normal file
@@ -0,0 +1,292 @@
|
||||
{
|
||||
"name": "shopPRO API",
|
||||
"version": "1.0.0",
|
||||
"entrypoint": "/api.php",
|
||||
"authentication": {
|
||||
"type": "header",
|
||||
"header": "X-Api-Key",
|
||||
"required": true,
|
||||
"description": "API key stored in pp_settings.param=api_key"
|
||||
},
|
||||
"response_format": {
|
||||
"success": {
|
||||
"status": "ok",
|
||||
"data": {}
|
||||
},
|
||||
"error": {
|
||||
"status": "error",
|
||||
"code": "BAD_REQUEST",
|
||||
"message": "Human-readable error message"
|
||||
},
|
||||
"error_codes": [
|
||||
{ "code": "UNAUTHORIZED", "http": 401 },
|
||||
{ "code": "BAD_REQUEST", "http": 400 },
|
||||
{ "code": "NOT_FOUND", "http": 404 },
|
||||
{ "code": "METHOD_NOT_ALLOWED", "http": 405 },
|
||||
{ "code": "INTERNAL_ERROR", "http": 500 }
|
||||
]
|
||||
},
|
||||
"endpoints": [
|
||||
{
|
||||
"group": "orders",
|
||||
"action": "list",
|
||||
"method": "GET",
|
||||
"url_template": "/api.php?endpoint=orders&action=list",
|
||||
"query_params": [
|
||||
{ "name": "status", "type": "string", "required": false },
|
||||
{ "name": "paid", "type": "string", "required": false },
|
||||
{ "name": "date_from", "type": "string", "required": false, "format": "YYYY-MM-DD" },
|
||||
{ "name": "date_to", "type": "string", "required": false, "format": "YYYY-MM-DD" },
|
||||
{ "name": "updated_since", "type": "string", "required": false, "format": "YYYY-MM-DD HH:MM:SS" },
|
||||
{ "name": "number", "type": "string", "required": false },
|
||||
{ "name": "client", "type": "string", "required": false },
|
||||
{ "name": "page", "type": "integer", "required": false, "default": 1, "min": 1 },
|
||||
{ "name": "per_page", "type": "integer", "required": false, "default": 50, "min": 1, "max": 100 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "orders",
|
||||
"action": "get",
|
||||
"method": "GET",
|
||||
"url_template": "/api.php?endpoint=orders&action=get&id={order_id}",
|
||||
"query_params": [
|
||||
{ "name": "id", "type": "integer", "required": true, "min": 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "orders",
|
||||
"action": "change_status",
|
||||
"method": "PUT",
|
||||
"url_template": "/api.php?endpoint=orders&action=change_status&id={order_id}",
|
||||
"query_params": [
|
||||
{ "name": "id", "type": "integer", "required": true, "min": 1 }
|
||||
],
|
||||
"json_body": {
|
||||
"required_fields": ["status_id"],
|
||||
"fields": {
|
||||
"status_id": { "type": "integer" },
|
||||
"send_email": { "type": "boolean", "required": false }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"group": "orders",
|
||||
"action": "set_paid",
|
||||
"method": "PUT",
|
||||
"url_template": "/api.php?endpoint=orders&action=set_paid&id={order_id}",
|
||||
"query_params": [
|
||||
{ "name": "id", "type": "integer", "required": true, "min": 1 }
|
||||
],
|
||||
"json_body": {
|
||||
"required_fields": [],
|
||||
"fields": {
|
||||
"send_email": { "type": "boolean", "required": false }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"group": "orders",
|
||||
"action": "set_unpaid",
|
||||
"method": "PUT",
|
||||
"url_template": "/api.php?endpoint=orders&action=set_unpaid&id={order_id}",
|
||||
"query_params": [
|
||||
{ "name": "id", "type": "integer", "required": true, "min": 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "products",
|
||||
"action": "list",
|
||||
"method": "GET",
|
||||
"url_template": "/api.php?endpoint=products&action=list",
|
||||
"query_params": [
|
||||
{ "name": "search", "type": "string", "required": false },
|
||||
{ "name": "status", "type": "string", "required": false },
|
||||
{ "name": "promoted", "type": "string", "required": false },
|
||||
{ "name": "attribute_{id}", "type": "integer", "required": false, "description": "e.g. attribute_5=12" },
|
||||
{ "name": "sort", "type": "string", "required": false, "default": "id", "allowed": ["id", "name", "price_brutto", "status", "promoted", "quantity"] },
|
||||
{ "name": "sort_dir", "type": "string", "required": false, "default": "DESC", "allowed": ["ASC", "DESC"] },
|
||||
{ "name": "page", "type": "integer", "required": false, "default": 1, "min": 1 },
|
||||
{ "name": "per_page", "type": "integer", "required": false, "default": 50, "min": 1, "max": 100 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "products",
|
||||
"action": "get",
|
||||
"method": "GET",
|
||||
"url_template": "/api.php?endpoint=products&action=get&id={product_id}",
|
||||
"query_params": [
|
||||
{ "name": "id", "type": "integer", "required": true, "min": 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "products",
|
||||
"action": "create",
|
||||
"method": "POST",
|
||||
"url_template": "/api.php?endpoint=products&action=create",
|
||||
"json_body": {
|
||||
"required_fields": ["languages", "price_brutto"],
|
||||
"rules": [
|
||||
"languages must be an object with at least one language entry containing name",
|
||||
"price_brutto must be numeric and >= 0"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"group": "products",
|
||||
"action": "update",
|
||||
"method": "PUT",
|
||||
"url_template": "/api.php?endpoint=products&action=update&id={product_id}",
|
||||
"query_params": [
|
||||
{ "name": "id", "type": "integer", "required": true, "min": 1 }
|
||||
],
|
||||
"json_body": {
|
||||
"required_fields": [],
|
||||
"rules": ["partial update; only changed fields are needed"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"group": "products",
|
||||
"action": "variants",
|
||||
"method": "GET",
|
||||
"url_template": "/api.php?endpoint=products&action=variants&id={product_id}",
|
||||
"query_params": [
|
||||
{ "name": "id", "type": "integer", "required": true, "min": 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "products",
|
||||
"action": "create_variant",
|
||||
"method": "POST",
|
||||
"url_template": "/api.php?endpoint=products&action=create_variant&id={product_id}",
|
||||
"query_params": [
|
||||
{ "name": "id", "type": "integer", "required": true, "min": 1 }
|
||||
],
|
||||
"json_body": {
|
||||
"required_fields": ["attributes"],
|
||||
"fields": {
|
||||
"attributes": { "type": "object", "description": "Map attribute_id -> value_id" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"group": "products",
|
||||
"action": "update_variant",
|
||||
"method": "PUT",
|
||||
"url_template": "/api.php?endpoint=products&action=update_variant&id={variant_id}",
|
||||
"query_params": [
|
||||
{ "name": "id", "type": "integer", "required": true, "min": 1 }
|
||||
],
|
||||
"json_body": {
|
||||
"required_fields": [],
|
||||
"rules": ["partial update of variant fields"]
|
||||
}
|
||||
},
|
||||
{
|
||||
"group": "products",
|
||||
"action": "delete_variant",
|
||||
"method": "DELETE",
|
||||
"url_template": "/api.php?endpoint=products&action=delete_variant&id={variant_id}",
|
||||
"query_params": [
|
||||
{ "name": "id", "type": "integer", "required": true, "min": 1 }
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "products",
|
||||
"action": "upload_image",
|
||||
"method": "POST",
|
||||
"url_template": "/api.php?endpoint=products&action=upload_image",
|
||||
"json_body": {
|
||||
"required_fields": ["id", "file_name", "content_base64"],
|
||||
"fields": {
|
||||
"id": { "type": "integer", "description": "product id" },
|
||||
"file_name": { "type": "string" },
|
||||
"content_base64": { "type": "string", "description": "base64 payload" },
|
||||
"alt": { "type": "string", "required": false },
|
||||
"o": { "type": "integer", "required": false, "description": "image position" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"group": "dictionaries",
|
||||
"action": "statuses",
|
||||
"method": "GET",
|
||||
"url_template": "/api.php?endpoint=dictionaries&action=statuses"
|
||||
},
|
||||
{
|
||||
"group": "dictionaries",
|
||||
"action": "transports",
|
||||
"method": "GET",
|
||||
"url_template": "/api.php?endpoint=dictionaries&action=transports"
|
||||
},
|
||||
{
|
||||
"group": "dictionaries",
|
||||
"action": "payment_methods",
|
||||
"method": "GET",
|
||||
"url_template": "/api.php?endpoint=dictionaries&action=payment_methods"
|
||||
},
|
||||
{
|
||||
"group": "dictionaries",
|
||||
"action": "attributes",
|
||||
"method": "GET",
|
||||
"url_template": "/api.php?endpoint=dictionaries&action=attributes"
|
||||
},
|
||||
{
|
||||
"group": "dictionaries",
|
||||
"action": "ensure_attribute",
|
||||
"method": "POST",
|
||||
"url_template": "/api.php?endpoint=dictionaries&action=ensure_attribute",
|
||||
"json_body": {
|
||||
"required_fields": ["name"],
|
||||
"fields": {
|
||||
"name": { "type": "string" },
|
||||
"type": { "type": "integer", "required": false, "default": 0 },
|
||||
"lang": { "type": "string", "required": false, "default": "pl" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"group": "dictionaries",
|
||||
"action": "ensure_attribute_value",
|
||||
"method": "POST",
|
||||
"url_template": "/api.php?endpoint=dictionaries&action=ensure_attribute_value",
|
||||
"json_body": {
|
||||
"required_fields": ["attribute_id", "name"],
|
||||
"fields": {
|
||||
"attribute_id": { "type": "integer" },
|
||||
"name": { "type": "string" },
|
||||
"lang": { "type": "string", "required": false, "default": "pl" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"group": "dictionaries",
|
||||
"action": "ensure_producer",
|
||||
"method": "POST",
|
||||
"url_template": "/api.php?endpoint=dictionaries&action=ensure_producer",
|
||||
"json_body": {
|
||||
"required_fields": ["name"],
|
||||
"fields": {
|
||||
"name": { "type": "string" }
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"group": "categories",
|
||||
"action": "list",
|
||||
"method": "GET",
|
||||
"url_template": "/api.php?endpoint=categories&action=list"
|
||||
}
|
||||
],
|
||||
"examples": {
|
||||
"curl_list_products": "curl -X GET \"https://twoja-domena.pl/api.php?endpoint=products&action=list&page=1&per_page=20\" -H \"X-Api-Key: TWOJ_KLUCZ\"",
|
||||
"curl_get_order": "curl -X GET \"https://twoja-domena.pl/api.php?endpoint=orders&action=get&id=42\" -H \"X-Api-Key: TWOJ_KLUCZ\"",
|
||||
"curl_create_product": "curl -X POST \"https://twoja-domena.pl/api.php?endpoint=products&action=create\" -H \"X-Api-Key: TWOJ_KLUCZ\" -H \"Content-Type: application/json\" -d \"{\\\"price_brutto\\\":99.99,\\\"languages\\\":{\\\"pl\\\":{\\\"name\\\":\\\"Nowy produkt\\\"}}}\""
|
||||
},
|
||||
"source_of_truth": [
|
||||
"autoload/api/ApiRouter.php",
|
||||
"autoload/api/Controllers/OrdersApiController.php",
|
||||
"autoload/api/Controllers/ProductsApiController.php",
|
||||
"autoload/api/Controllers/DictionariesApiController.php",
|
||||
"autoload/api/Controllers/CategoriesApiController.php"
|
||||
]
|
||||
}
|
||||
60
api-docs/index.html
Normal file
60
api-docs/index.html
Normal file
@@ -0,0 +1,60 @@
|
||||
<!doctype html>
|
||||
<html lang="pl">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>shopPRO API docs</title>
|
||||
<style>
|
||||
:root { color-scheme: light; }
|
||||
body { font-family: Arial, sans-serif; margin: 24px; line-height: 1.4; }
|
||||
h1, h2 { margin-bottom: 8px; }
|
||||
.meta { color: #444; margin-bottom: 16px; }
|
||||
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
|
||||
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; vertical-align: top; }
|
||||
th { background: #f4f4f4; }
|
||||
code { background: #f7f7f7; padding: 2px 4px; border-radius: 3px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>shopPRO API - public docs</h1>
|
||||
<div class="meta" id="meta">Ladowanie...</div>
|
||||
<p>Machine-readable JSON: <a href="./api-reference.json">api-reference.json</a></p>
|
||||
|
||||
<h2>Endpointy</h2>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Group</th>
|
||||
<th>Action</th>
|
||||
<th>Method</th>
|
||||
<th>URL template</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="rows"></tbody>
|
||||
</table>
|
||||
|
||||
<script>
|
||||
fetch("./api-reference.json")
|
||||
.then(function (res) { return res.json(); })
|
||||
.then(function (spec) {
|
||||
var meta = document.getElementById("meta");
|
||||
meta.textContent = spec.name + " v" + spec.version + " | entrypoint: " + spec.entrypoint;
|
||||
|
||||
var rows = document.getElementById("rows");
|
||||
spec.endpoints.forEach(function (ep) {
|
||||
var tr = document.createElement("tr");
|
||||
tr.innerHTML =
|
||||
"<td>" + ep.group + "</td>" +
|
||||
"<td>" + ep.action + "</td>" +
|
||||
"<td><code>" + ep.method + "</code></td>" +
|
||||
"<td><code>" + ep.url_template + "</code></td>";
|
||||
rows.appendChild(tr);
|
||||
});
|
||||
})
|
||||
.catch(function () {
|
||||
var meta = document.getElementById("meta");
|
||||
meta.textContent = "Nie udalo sie wczytac api-reference.json";
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
BIN
autoload/.DS_Store
vendored
BIN
autoload/.DS_Store
vendored
Binary file not shown.
@@ -296,7 +296,7 @@ class LayoutsRepository
|
||||
if (is_array($layoutRows) && isset($layoutRows[0])) {
|
||||
$layout = $layoutRows[0];
|
||||
} else {
|
||||
$layout = $this->db->get('pp_layouts', '*', ['categories_default' => 1]);
|
||||
$layout = $this->db->get('pp_layouts', '*', ['status' => 1]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -654,6 +654,10 @@ class ProductRepository
|
||||
'custom_label_2' => $product['custom_label_2'],
|
||||
'custom_label_3' => $product['custom_label_3'],
|
||||
'custom_label_4' => $product['custom_label_4'],
|
||||
'new_to_date' => $product['new_to_date'],
|
||||
'additional_message' => (int)($product['additional_message'] ?? 0),
|
||||
'additional_message_required' => (int)($product['additional_message_required'] ?? 0),
|
||||
'additional_message_text' => $product['additional_message_text'],
|
||||
'set_id' => $product['set_id'] !== null ? (int)$product['set_id'] : null,
|
||||
'product_unit_id' => $product['product_unit_id'] !== null ? (int)$product['product_unit_id'] : null,
|
||||
'producer_id' => $product['producer_id'] !== null ? (int)$product['producer_id'] : null,
|
||||
|
||||
@@ -437,7 +437,7 @@ class ProductsApiController
|
||||
// String fields — direct mapping
|
||||
$stringFields = [
|
||||
'sku', 'ean', 'custom_label_0', 'custom_label_1', 'custom_label_2',
|
||||
'custom_label_3', 'custom_label_4', 'wp',
|
||||
'custom_label_3', 'custom_label_4', 'wp', 'new_to_date', 'additional_message_text',
|
||||
];
|
||||
foreach ($stringFields as $field) {
|
||||
if (isset($body[$field])) {
|
||||
@@ -447,6 +447,18 @@ class ProductsApiController
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($body['additional_message'])) {
|
||||
$d['additional_message'] = !empty($body['additional_message']) ? 'on' : '';
|
||||
} elseif ($existing !== null) {
|
||||
$d['additional_message'] = !empty($existing['additional_message']) ? 'on' : '';
|
||||
}
|
||||
|
||||
if (isset($body['additional_message_required'])) {
|
||||
$d['additional_message_required'] = !empty($body['additional_message_required']) ? 'on' : '';
|
||||
} elseif ($existing !== null) {
|
||||
$d['additional_message_required'] = !empty($existing['additional_message_required']) ? 'on' : '';
|
||||
}
|
||||
|
||||
// Foreign keys
|
||||
if (isset($body['set_id'])) {
|
||||
$d['set'] = $body['set_id'];
|
||||
|
||||
BIN
autoload/front/.DS_Store
vendored
BIN
autoload/front/.DS_Store
vendored
Binary file not shown.
@@ -3,6 +3,9 @@ namespace front\Controllers;
|
||||
|
||||
class ShopBasketController
|
||||
{
|
||||
private const ORDER_SUBMIT_TOKEN_SESSION_KEY = 'order-submit-token';
|
||||
private const ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY = 'order-submit-last-order-id';
|
||||
|
||||
public static $title = [
|
||||
'mainView' => 'Koszyk'
|
||||
];
|
||||
@@ -274,6 +277,7 @@ class ShopBasketController
|
||||
}
|
||||
|
||||
$client = \Shared\Helpers\Helpers::get_session( 'client' );
|
||||
$orderSubmitToken = $this->createOrderSubmitToken();
|
||||
|
||||
return \Shared\Tpl\Tpl::view( 'shop-basket/summary-view', [
|
||||
'lang_id' => $lang_id,
|
||||
@@ -284,12 +288,35 @@ class ShopBasketController
|
||||
'addresses' => ( new \Domain\Client\ClientRepository( $GLOBALS['mdb'] ) )->clientAddresses( (int)$client['id'] ),
|
||||
'settings' => $settings,
|
||||
'coupon' => \Shared\Helpers\Helpers::get_session( 'coupon' ),
|
||||
'basket_message' => \Shared\Helpers\Helpers::get_session( 'basket_message' )
|
||||
'basket_message' => \Shared\Helpers\Helpers::get_session( 'basket_message' ),
|
||||
'order_submit_token' => $orderSubmitToken
|
||||
] );
|
||||
}
|
||||
|
||||
public function basketSave()
|
||||
{
|
||||
$orderSubmitToken = (string)\Shared\Helpers\Helpers::get( 'order_submit_token', true );
|
||||
$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 ( $existingOrderId > 0 )
|
||||
{
|
||||
$existingOrderHash = $this->orderRepository->findHashById( $existingOrderId );
|
||||
if ( $existingOrderHash )
|
||||
{
|
||||
header( 'Location: /zamowienie/' . $existingOrderHash );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
\Shared\Helpers\Helpers::error( \Shared\Helpers\Helpers::lang( 'zamowienie-zostalo-zlozone-komunikat-blad' ) );
|
||||
header( 'Location: /koszyk' );
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->consumeOrderSubmitToken();
|
||||
|
||||
$client = \Shared\Helpers\Helpers::get_session( 'client' );
|
||||
|
||||
if ( \Domain\Basket\BasketCalculator::checkProductQuantityInStock( \Shared\Helpers\Helpers::get_session( 'basket' ) ) )
|
||||
@@ -322,6 +349,7 @@ class ShopBasketController
|
||||
\Shared\Helpers\Helpers::get_session( 'basket_message' )
|
||||
) )
|
||||
{
|
||||
\Shared\Helpers\Helpers::set_session( self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY, (int)$order_id );
|
||||
\Shared\Helpers\Helpers::alert( \Shared\Helpers\Helpers::lang( 'zamowienie-zostalo-zlozone-komunikat' ) );
|
||||
\Shared\Helpers\Helpers::delete_session( 'basket' );
|
||||
\Shared\Helpers\Helpers::delete_session( 'basket-transport-method-id' );
|
||||
@@ -414,4 +442,45 @@ class ShopBasketController
|
||||
] );
|
||||
exit;
|
||||
}
|
||||
|
||||
private function createOrderSubmitToken()
|
||||
{
|
||||
$token = $this->generateOrderSubmitToken();
|
||||
\Shared\Helpers\Helpers::set_session( self::ORDER_SUBMIT_TOKEN_SESSION_KEY, $token );
|
||||
\Shared\Helpers\Helpers::delete_session( self::ORDER_SUBMIT_LAST_ORDER_ID_SESSION_KEY );
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
private function generateOrderSubmitToken()
|
||||
{
|
||||
try
|
||||
{
|
||||
return bin2hex( random_bytes( 16 ) );
|
||||
}
|
||||
catch ( \Exception $exception )
|
||||
{
|
||||
return md5( uniqid( (string)mt_rand(), true ) );
|
||||
}
|
||||
}
|
||||
|
||||
private function isValidOrderSubmitToken( $token )
|
||||
{
|
||||
if ( !$token )
|
||||
return false;
|
||||
|
||||
$sessionToken = isset( $_SESSION[ self::ORDER_SUBMIT_TOKEN_SESSION_KEY ] ) ? (string)$_SESSION[ self::ORDER_SUBMIT_TOKEN_SESSION_KEY ] : '';
|
||||
if ( !$sessionToken )
|
||||
return false;
|
||||
|
||||
if ( function_exists( 'hash_equals' ) )
|
||||
return hash_equals( $sessionToken, $token );
|
||||
|
||||
return $sessionToken === $token;
|
||||
}
|
||||
|
||||
private function consumeOrderSubmitToken()
|
||||
{
|
||||
\Shared\Helpers\Helpers::delete_session( self::ORDER_SUBMIT_TOKEN_SESSION_KEY );
|
||||
}
|
||||
}
|
||||
|
||||
584
docs/API.md
584
docs/API.md
@@ -1,584 +0,0 @@
|
||||
# shopPRO REST API
|
||||
|
||||
REST API do integracji z ordersPRO i innymi systemami zewnetrznymi.
|
||||
|
||||
## Autentykacja
|
||||
|
||||
Kazde zapytanie wymaga headera `X-Api-Key` z kluczem API.
|
||||
|
||||
```
|
||||
X-Api-Key: {klucz_api}
|
||||
```
|
||||
|
||||
Klucz przechowywany jest w `pp_settings` jako parametr `api_key`. API jest stateless (bez sesji).
|
||||
|
||||
## Format odpowiedzi
|
||||
|
||||
### Sukces (HTTP 200)
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": { ... }
|
||||
}
|
||||
```
|
||||
|
||||
### Blad
|
||||
```json
|
||||
{
|
||||
"status": "error",
|
||||
"code": "UNAUTHORIZED",
|
||||
"message": "Invalid or missing API key"
|
||||
}
|
||||
```
|
||||
|
||||
Kody bledow:
|
||||
| Kod | HTTP | Opis |
|
||||
|-----|------|------|
|
||||
| `UNAUTHORIZED` | 401 | Brak lub nieprawidlowy klucz API |
|
||||
| `BAD_REQUEST` | 400 | Brakujace lub niepoprawne parametry |
|
||||
| `NOT_FOUND` | 404 | Nie znaleziono zasobu/endpointu/akcji |
|
||||
| `METHOD_NOT_ALLOWED` | 405 | Nieprawidlowa metoda HTTP |
|
||||
| `INTERNAL_ERROR` | 500 | Blad wewnetrzny serwera |
|
||||
|
||||
## Endpointy
|
||||
|
||||
### Zamowienia
|
||||
|
||||
#### Lista zamowien
|
||||
```
|
||||
GET api.php?endpoint=orders&action=list
|
||||
```
|
||||
|
||||
Parametry filtrowania (opcjonalne):
|
||||
| Parametr | Typ | Opis |
|
||||
|----------|-----|------|
|
||||
| `status` | int | Filtruj po statusie zamowienia |
|
||||
| `paid` | int (0/1) | Filtruj po statusie platnosci |
|
||||
| `date_from` | date (YYYY-MM-DD) | Zamowienia od daty |
|
||||
| `date_to` | date (YYYY-MM-DD) | Zamowienia do daty |
|
||||
| `updated_since` | datetime (YYYY-MM-DD HH:MM:SS) | Zamowienia zmodyfikowane od podanej daty (klucz do pollingu) |
|
||||
| `number` | string | Szukaj po numerze zamowienia |
|
||||
| `client` | string | Szukaj po imieniu, nazwisku lub emailu klienta |
|
||||
| `page` | int | Numer strony (domyslnie 1) |
|
||||
| `per_page` | int | Wynikow na strone (domyslnie 50, max 100) |
|
||||
|
||||
Odpowiedz:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": 42,
|
||||
"number": "2026/02/001",
|
||||
"date_order": "2026-02-19 10:30:00",
|
||||
"updated_at": "2026-02-19 12:00:00",
|
||||
"status": 4,
|
||||
"paid": 1,
|
||||
"client_name": "Jan",
|
||||
"client_surname": "Kowalski",
|
||||
"client_email": "jan@example.com",
|
||||
"client_phone": "111222333",
|
||||
"client_street": "Testowa 1",
|
||||
"client_postal_code": "00-000",
|
||||
"client_city": "Warszawa",
|
||||
"firm_name": null,
|
||||
"firm_nip": null,
|
||||
"transport": "Kurier DPD",
|
||||
"transport_cost": 15.00,
|
||||
"payment_method": "Przelew bankowy",
|
||||
"summary": 150.00
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"per_page": 50
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Szczegoly zamowienia
|
||||
```
|
||||
GET api.php?endpoint=orders&action=get&id={order_id}
|
||||
```
|
||||
|
||||
Zwraca pelne dane zamowienia z produktami i historia statusow.
|
||||
|
||||
#### Zmiana statusu zamowienia
|
||||
```
|
||||
PUT api.php?endpoint=orders&action=change_status&id={order_id}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"status_id": 5,
|
||||
"send_email": true
|
||||
}
|
||||
```
|
||||
|
||||
Odpowiedz:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"order_id": 42,
|
||||
"status_id": 5,
|
||||
"changed": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Oznacz jako oplacone
|
||||
```
|
||||
PUT api.php?endpoint=orders&action=set_paid&id={order_id}
|
||||
```
|
||||
|
||||
Opcjonalnie w body: `{"send_email": true}`
|
||||
|
||||
#### Oznacz jako nieoplacone
|
||||
```
|
||||
PUT api.php?endpoint=orders&action=set_unpaid&id={order_id}
|
||||
```
|
||||
|
||||
### Produkty
|
||||
|
||||
#### Lista produktow
|
||||
```
|
||||
GET api.php?endpoint=products&action=list
|
||||
```
|
||||
|
||||
Parametry filtrowania (opcjonalne):
|
||||
| Parametr | Typ | Opis |
|
||||
|----------|-----|------|
|
||||
| `search` | string | Szukaj po nazwie, EAN lub SKU |
|
||||
| `status` | int (0/1) | Filtruj po statusie (1 = aktywny, 0 = nieaktywny) |
|
||||
| `promoted` | int (0/1) | Filtruj po promocji |
|
||||
| `attribute_{id}` | int | Filtruj po atrybucie — `attribute_1=3` oznacza atrybut 1 = wartosc 3 (wiele filtrow AND) |
|
||||
| `sort` | string | Sortuj po: id, name, price_brutto, status, promoted, quantity (domyslnie id) |
|
||||
| `sort_dir` | string | Kierunek: ASC lub DESC (domyslnie DESC) |
|
||||
| `page` | int | Numer strony (domyslnie 1) |
|
||||
| `per_page` | int | Wynikow na strone (domyslnie 50, max 100) |
|
||||
|
||||
Odpowiedz:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"sku": "PROD-001",
|
||||
"ean": "5901234123457",
|
||||
"name": "Produkt testowy",
|
||||
"price_brutto": 99.99,
|
||||
"price_brutto_promo": null,
|
||||
"price_netto": 81.29,
|
||||
"price_netto_promo": null,
|
||||
"quantity": 10,
|
||||
"status": 1,
|
||||
"promoted": 0,
|
||||
"vat": 23,
|
||||
"weight": 0.5,
|
||||
"main_image": "product1.jpg",
|
||||
"date_add": "2026-01-15 10:00:00",
|
||||
"date_modify": "2026-02-19 12:00:00"
|
||||
}
|
||||
],
|
||||
"total": 1,
|
||||
"page": 1,
|
||||
"per_page": 50
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Szczegoly produktu
|
||||
```
|
||||
GET api.php?endpoint=products&action=get&id={product_id}
|
||||
```
|
||||
|
||||
Zwraca pelne dane produktu z jezykami, zdjeciami, kategoriami i atrybutami.
|
||||
|
||||
Odpowiedz:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"id": 1,
|
||||
"sku": "PROD-001",
|
||||
"ean": "5901234123457",
|
||||
"price_brutto": 99.99,
|
||||
"price_brutto_promo": null,
|
||||
"price_netto": 81.29,
|
||||
"price_netto_promo": null,
|
||||
"quantity": 10,
|
||||
"status": 1,
|
||||
"promoted": 0,
|
||||
"vat": 23,
|
||||
"weight": 0.5,
|
||||
"stock_0_buy": 0,
|
||||
"custom_label_0": null,
|
||||
"set_id": null,
|
||||
"product_unit_id": 1,
|
||||
"producer_id": 3,
|
||||
"producer_name": "Nike",
|
||||
"date_add": "2026-01-15 10:00:00",
|
||||
"date_modify": "2026-02-19 12:00:00",
|
||||
"languages": {
|
||||
"pl": {
|
||||
"name": "Produkt testowy",
|
||||
"short_description": "Krotki opis",
|
||||
"description": "<p>Pelny opis produktu</p>",
|
||||
"meta_description": null,
|
||||
"meta_keywords": null,
|
||||
"meta_title": null,
|
||||
"seo_link": "produkt-testowy",
|
||||
"copy_from": null,
|
||||
"warehouse_message_zero": null,
|
||||
"warehouse_message_nonzero": null,
|
||||
"tab_name_1": null,
|
||||
"tab_description_1": null,
|
||||
"tab_name_2": null,
|
||||
"tab_description_2": null,
|
||||
"canonical": null,
|
||||
"security_information": null
|
||||
}
|
||||
},
|
||||
"images": [
|
||||
{"id": 1, "src": "product1.jpg", "alt": "Zdjecie produktu"}
|
||||
],
|
||||
"categories": [1, 5],
|
||||
"attributes": [
|
||||
{
|
||||
"attribute_id": 1,
|
||||
"attribute_type": 1,
|
||||
"attribute_names": {"pl": "Kolor", "en": "Color"},
|
||||
"value_id": 3,
|
||||
"value_names": {"pl": "Czerwony", "en": "Red"}
|
||||
}
|
||||
],
|
||||
"custom_fields": [
|
||||
{"name": "Napis na koszulce", "type": "text", "is_required": 1}
|
||||
],
|
||||
"variants": [
|
||||
{
|
||||
"id": 101,
|
||||
"permutation_hash": "1-3|2-5",
|
||||
"sku": "PROD-001-RED-L",
|
||||
"ean": null,
|
||||
"price_brutto": 109.99,
|
||||
"price_brutto_promo": null,
|
||||
"price_netto": 89.42,
|
||||
"price_netto_promo": null,
|
||||
"quantity": 5,
|
||||
"stock_0_buy": 0,
|
||||
"weight": 0.5,
|
||||
"status": 1,
|
||||
"attributes": [
|
||||
{"attribute_id": 1, "attribute_names": {"pl": "Kolor"}, "value_id": 3, "value_names": {"pl": "Czerwony"}},
|
||||
{"attribute_id": 2, "attribute_names": {"pl": "Rozmiar"}, "value_id": 5, "value_names": {"pl": "L"}}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Tworzenie produktu
|
||||
```
|
||||
POST api.php?endpoint=products&action=create
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"price_brutto": 99.99,
|
||||
"vat": 23,
|
||||
"quantity": 10,
|
||||
"status": 1,
|
||||
"sku": "PROD-001",
|
||||
"ean": "5901234123457",
|
||||
"weight": 0.5,
|
||||
"languages": {
|
||||
"pl": {
|
||||
"name": "Nowy produkt",
|
||||
"description": "<p>Opis produktu</p>"
|
||||
}
|
||||
},
|
||||
"categories": [1, 5],
|
||||
"products_related": [10, 20],
|
||||
"custom_fields": [
|
||||
{"name": "Napis na koszulce", "type": "text", "is_required": 1}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Wymagane: `languages` (min. 1 jezyk z `name`) oraz `price_brutto`.
|
||||
`custom_fields` — opcjonalne; kazdy element wymaga `name`, `type` (domyslnie `text`), `is_required` (0/1).
|
||||
|
||||
Odpowiedz (HTTP 201):
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"id": 42
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Aktualizacja produktu
|
||||
```
|
||||
PUT api.php?endpoint=products&action=update&id={product_id}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"price_brutto": 129.99,
|
||||
"status": 1,
|
||||
"languages": {
|
||||
"pl": {
|
||||
"name": "Zaktualizowana nazwa"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Partial update — wystarczy przeslac tylko zmienione pola. Pola nieprzeslane zachowuja aktualna wartosc.
|
||||
|
||||
Odpowiedz: pelne dane produktu (jak w `get`).
|
||||
|
||||
### Warianty produktow
|
||||
|
||||
#### Lista wariantow produktu
|
||||
```
|
||||
GET api.php?endpoint=products&action=variants&id={product_id}
|
||||
```
|
||||
|
||||
Zwraca warianty produktu nadrzednego wraz z dostepnymi atrybutami.
|
||||
|
||||
Odpowiedz:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"product_id": 1,
|
||||
"available_attributes": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": 1,
|
||||
"status": 1,
|
||||
"names": {"pl": "Kolor", "en": "Color"},
|
||||
"values": [
|
||||
{"id": 3, "names": {"pl": "Czerwony", "en": "Red"}, "is_default": 0, "impact_on_the_price": null},
|
||||
{"id": 4, "names": {"pl": "Niebieski", "en": "Blue"}, "is_default": 0, "impact_on_the_price": 10.0}
|
||||
]
|
||||
}
|
||||
],
|
||||
"variants": [
|
||||
{
|
||||
"id": 101,
|
||||
"permutation_hash": "1-3",
|
||||
"sku": "PROD-001-RED",
|
||||
"ean": null,
|
||||
"price_brutto": 109.99,
|
||||
"price_brutto_promo": null,
|
||||
"price_netto": 89.42,
|
||||
"price_netto_promo": null,
|
||||
"quantity": 5,
|
||||
"stock_0_buy": 0,
|
||||
"weight": 0.5,
|
||||
"status": 1,
|
||||
"attributes": [
|
||||
{"attribute_id": 1, "attribute_names": {"pl": "Kolor"}, "value_id": 3, "value_names": {"pl": "Czerwony"}}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Tworzenie wariantu
|
||||
```
|
||||
POST api.php?endpoint=products&action=create_variant&id={product_id}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"attributes": {"1": 3, "2": 5},
|
||||
"sku": "PROD-001-RED-L",
|
||||
"ean": "5901234123458",
|
||||
"price_brutto": 109.99,
|
||||
"quantity": 5,
|
||||
"weight": 0.5
|
||||
}
|
||||
```
|
||||
|
||||
Wymagane: `attributes` (mapa attribute_id -> value_id, min. 1). Kombinacja atrybutow musi byc unikalna.
|
||||
|
||||
Odpowiedz (HTTP 201): pelne dane wariantu.
|
||||
|
||||
#### Aktualizacja wariantu
|
||||
```
|
||||
PUT api.php?endpoint=products&action=update_variant&id={variant_id}
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"sku": "PROD-001-RED-XL",
|
||||
"price_brutto": 119.99,
|
||||
"quantity": 3
|
||||
}
|
||||
```
|
||||
|
||||
Partial update — mozna zmienic: sku, ean, price_brutto, price_netto, price_brutto_promo, price_netto_promo, quantity, stock_0_buy, weight, status.
|
||||
|
||||
Odpowiedz: pelne dane wariantu.
|
||||
|
||||
#### Usuwanie wariantu
|
||||
```
|
||||
DELETE api.php?endpoint=products&action=delete_variant&id={variant_id}
|
||||
```
|
||||
|
||||
Odpowiedz:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {"id": 101, "deleted": true}
|
||||
}
|
||||
```
|
||||
|
||||
### Kategorie
|
||||
|
||||
#### Lista kategorii
|
||||
```
|
||||
GET api.php?endpoint=categories&action=list
|
||||
```
|
||||
|
||||
Zwraca plaska liste wszystkich aktywnych kategorii w domyslnym jezyku sklepu.
|
||||
|
||||
Odpowiedz:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"categories": [
|
||||
{"id": 1, "parent_id": null, "title": "Kategoria glowna"},
|
||||
{"id": 3, "parent_id": 1, "title": "Podkategoria A"},
|
||||
{"id": 5, "parent_id": 1, "title": "Podkategoria B"}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Pola odpowiedzi:
|
||||
| Pole | Typ | Opis |
|
||||
|------|-----|------|
|
||||
| `id` | int | ID kategorii |
|
||||
| `parent_id` | int\|null | ID kategorii nadrzednej (null = kategoria glowna) |
|
||||
| `title` | string | Nazwa w domyslnym jezyku; fallback na inny jezyk jesli brak tlumaczenia |
|
||||
|
||||
---
|
||||
|
||||
### Slowniki
|
||||
|
||||
#### Lista statusow zamowien
|
||||
```
|
||||
GET api.php?endpoint=dictionaries&action=statuses
|
||||
```
|
||||
|
||||
Odpowiedz:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": [
|
||||
{"id": 0, "name": "Nowe"},
|
||||
{"id": 1, "name": "Oplacone"},
|
||||
{"id": 4, "name": "W realizacji"},
|
||||
{"id": 6, "name": "Wyslane"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
#### Lista metod transportu
|
||||
```
|
||||
GET api.php?endpoint=dictionaries&action=transports
|
||||
```
|
||||
|
||||
#### Lista metod platnosci
|
||||
```
|
||||
GET api.php?endpoint=dictionaries&action=payment_methods
|
||||
```
|
||||
|
||||
#### Lista atrybutow
|
||||
```
|
||||
GET api.php?endpoint=dictionaries&action=attributes
|
||||
```
|
||||
|
||||
Zwraca aktywne atrybuty z wartosciami i wielojezycznymi nazwami.
|
||||
|
||||
#### Znajdz lub utworz producenta
|
||||
```
|
||||
POST api.php?endpoint=dictionaries&action=ensure_producer
|
||||
Content-Type: application/json
|
||||
|
||||
{
|
||||
"name": "Nike"
|
||||
}
|
||||
```
|
||||
|
||||
Zwraca istniejacego producenta po nazwie lub tworzy nowego. Uzyc przed tworzeniem produktu, jesli producent moze nie istniec.
|
||||
|
||||
Odpowiedz:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": {
|
||||
"id": 5,
|
||||
"created": false
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
`created: true` gdy producent zostal nowo dodany, `false` gdy juz istnial.
|
||||
|
||||
Odpowiedz:
|
||||
```json
|
||||
{
|
||||
"status": "ok",
|
||||
"data": [
|
||||
{
|
||||
"id": 1,
|
||||
"type": 1,
|
||||
"status": 1,
|
||||
"names": {"pl": "Kolor", "en": "Color"},
|
||||
"values": [
|
||||
{"id": 3, "names": {"pl": "Czerwony"}, "is_default": 0, "impact_on_the_price": null},
|
||||
{"id": 4, "names": {"pl": "Niebieski"}, "is_default": 1, "impact_on_the_price": 10.0}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Polling
|
||||
|
||||
Aby pobierac tylko nowe/zmienione zamowienia, uzyj parametru `updated_since`:
|
||||
|
||||
```
|
||||
GET api.php?endpoint=orders&action=list&updated_since=2026-02-19 12:00:00
|
||||
```
|
||||
|
||||
Kolumna `updated_at` w `pp_shop_orders` jest aktualizowana automatycznie przy kazdej modyfikacji zamowienia (zmiana statusu, platnosci, edycja danych, tworzenie zamowienia).
|
||||
|
||||
## Konfiguracja
|
||||
|
||||
Klucz API ustawia sie w panelu admina w ustawieniach sklepu lub bezposrednio w bazie:
|
||||
|
||||
```sql
|
||||
INSERT INTO pp_settings (param, value) VALUES ('api_key', 'twoj-klucz-api');
|
||||
-- lub
|
||||
UPDATE pp_settings SET value = 'twoj-klucz-api' WHERE param = 'api_key';
|
||||
```
|
||||
|
||||
## Architektura
|
||||
|
||||
- Entry point: `api.php`
|
||||
- Router: `\api\ApiRouter` (`autoload/api/ApiRouter.php`)
|
||||
- Kontrolery: `autoload/api/Controllers/`
|
||||
- `OrdersApiController` — zamowienia (5 akcji)
|
||||
- `ProductsApiController` — produkty (8 akcji: list, get, create, update, variants, create_variant, update_variant, delete_variant)
|
||||
- `DictionariesApiController` — slowniki (5 akcji: statuses, transports, payment_methods, attributes, ensure_producer)
|
||||
- `CategoriesApiController` — kategorie (1 akcja: list)
|
||||
@@ -4,6 +4,30 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
|
||||
|
||||
---
|
||||
|
||||
## 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}`)
|
||||
|
||||
@@ -23,10 +23,10 @@ composer test # standard
|
||||
## Aktualny stan
|
||||
|
||||
```text
|
||||
OK (805 tests, 2253 assertions)
|
||||
OK (810 tests, 2264 assertions)
|
||||
```
|
||||
|
||||
Zweryfikowano: 2026-02-24 (ver. 0.318)
|
||||
Zweryfikowano: 2026-03-10 (ver. 0.333)
|
||||
|
||||
## Konfiguracja
|
||||
|
||||
@@ -89,6 +89,8 @@ tests/
|
||||
| |-- ShopStatusesControllerTest.php
|
||||
| |-- ShopTransportControllerTest.php
|
||||
| `-- UsersControllerTest.php
|
||||
| |-- front/Controllers/
|
||||
| | `-- ShopBasketControllerTest.php
|
||||
| `-- api/
|
||||
| |-- ApiRouterTest.php
|
||||
| `-- Controllers/
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
1. Dodać przycisk kopiowania przy atrybutach produktu w zamówieniu
|
||||
2. Poprawić htaccess, żeby w nim nie było w ogóle adresów strona wszystko z bazy.
|
||||
3. Dodać uwierzytelnienie dwuskładnikowe za pomocą aplikacji.
|
||||
4. Dodać zarządzanie uprawnieniami na poziomie urzytkownika, na razie uprawnienia do poszczególnych modułów.
|
||||
4. Dodać zarządzanie uprawnieniami na poziomie urzytkownika, na razie uprawnienia do poszczególnych modułów.
|
||||
naprawić działanie newslettera i zapis do bazy newslettera
|
||||
program lojalnościowy
|
||||
proponowane produkty w koszyku
|
||||
Do zamówień w statusie: realizowane lub oczekuje na wpłatę. Opcja tylko dla zarejestrowanych klientów. https://royal-stone.pl/pl/order1.html
|
||||
Dodać możliwość ustawienia limitu znaków w wiadomościach do produktu
|
||||
@@ -1,658 +0,0 @@
|
||||
# htaccess.conf Elimination — Implementation Plan
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** Eliminate `libraries/htaccess.conf` as a template file and move all remaining hardcoded URL routes into `pp_routes`, leaving only true Apache-level directives in the generated `.htaccess`.
|
||||
|
||||
**Architecture:** `Helpers::htacces()` generates the full `.htaccess` content from PHP strings instead of loading a template. All URL→PHP mappings (static system routes + dynamic per language/producer) are inserted into `pp_routes` with `type='system'`, deleted and reinserted on every `htacces()` call. Apache-level rules (HTTPS redirect, admin routing, thumb.php) stay in `.htaccess` only.
|
||||
|
||||
**Tech Stack:** PHP 7.4, Medoo ORM (`$mdb`), Redis (CacheHandler), PHPUnit 9.6
|
||||
|
||||
---
|
||||
|
||||
## Context
|
||||
|
||||
### Current `Helpers::htacces()` structure (before this plan)
|
||||
1. Loads `libraries/htaccess.conf` template (contains many hardcoded URL routes)
|
||||
2. Appends language switch rules to `$htaccess_data`
|
||||
3. Appends newsletter and producer rules to `$htaccess_data`
|
||||
4. Inserts category/product/page/article routes into `pp_routes` (done in v0.329)
|
||||
5. Replaces `{HTACCESS_CACHE}` placeholder
|
||||
6. Appends catch-all, writes files
|
||||
|
||||
### What stays in `.htaccess` after this plan
|
||||
- `RewriteEngine On`, `RewriteBase /`, `Options`
|
||||
- www→https redirect
|
||||
- http→https redirect (with tpay/przelewy24/hotpay exclusion)
|
||||
- Trailing slash removal (excluding `/admin/`)
|
||||
- Admin routing: `^admin/([^/]*)/([^/]*)/(.*)$`
|
||||
- `^admin/$`
|
||||
- Thumbnail: `^thumb/([0-9]*)/([0-9]*)/(.*)$` → `/libraries/thumb.php` (different PHP file, cannot use pp_routes)
|
||||
- `THE_REQUEST` index.php redirect
|
||||
- Cache headers block (gzip/expires or no-cache based on `$settings['htaccess_cache']`)
|
||||
- File protection: `<Files *.conf>`, `<Files *.log>`, `<Files *.ini>`
|
||||
- Start page 301 redirects (generated dynamically in pages loop)
|
||||
- Custom htaccess from `pp_settings` (param=htaccess)
|
||||
- Catch-all: `RewriteRule ^ index.php [L]`
|
||||
|
||||
### New `type` column in `pp_routes`
|
||||
- `NULL` = entity route (product/category/page/article)
|
||||
- `'system'` = system route (all routes in this plan)
|
||||
- On every `htacces()` call: `DELETE WHERE type='system'`, then reinsert all
|
||||
|
||||
---
|
||||
|
||||
## Task 1: Update SQL migration — add `type` column
|
||||
|
||||
**Files:**
|
||||
- Modify: `migrations/0.329.sql`
|
||||
|
||||
**Step 1: Add `type` column to the migration**
|
||||
|
||||
Open `migrations/0.329.sql` (currently has 4 lines). Append the `type` column:
|
||||
|
||||
```sql
|
||||
ALTER TABLE pp_routes
|
||||
ADD COLUMN category_id INT NULL AFTER product_id,
|
||||
ADD COLUMN page_id INT NULL AFTER category_id,
|
||||
ADD COLUMN article_id INT NULL AFTER page_id,
|
||||
ADD COLUMN type VARCHAR(20) NULL AFTER article_id;
|
||||
```
|
||||
|
||||
**Step 2: Apply migration on server**
|
||||
|
||||
Run on the production/staging database:
|
||||
```sql
|
||||
ALTER TABLE pp_routes ADD COLUMN type VARCHAR(20) NULL AFTER article_id;
|
||||
```
|
||||
(The other 3 columns from 0.329 should already be applied from the previous deployment.)
|
||||
|
||||
**Step 3: No test needed** — pure schema change, verified when routes are inserted in Task 2.
|
||||
|
||||
---
|
||||
|
||||
## Task 2: Refactor `Helpers::htacces()` — replace template + move all routes to pp_routes
|
||||
|
||||
**Files:**
|
||||
- Modify: `autoload/Shared/Helpers/Helpers.php` (method `htacces()`, lines ~408–773)
|
||||
|
||||
This is the core task. The entire method is refactored. Here is the complete new body:
|
||||
|
||||
**Step 1: Replace the method body**
|
||||
|
||||
Find the opening of `htacces()` at line ~408. Replace everything from the start of the method body through the end (line ~773) with the code below.
|
||||
|
||||
The key structural changes:
|
||||
- Remove `file_get_contents(htaccess.conf)` and `str_replace('{PAGE}', ...)`
|
||||
- Remove `str_replace('{HTACCESS_CACHE}', ...)` — cache block is now inline
|
||||
- Build `$htaccess_data` directly as PHP string
|
||||
- Delete all `type='system'` routes, then reinsert static + dynamic ones
|
||||
- Language switch → `pp_routes` (remove from `$htaccess_data`)
|
||||
- Newsletter → `pp_routes` (remove from `$htaccess_data`)
|
||||
- Producenci/producent → `pp_routes` (remove from `$htaccess_data`)
|
||||
|
||||
**New `htacces()` method body** — replace lines 409–773 with:
|
||||
|
||||
```php
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$settings = ( new \Domain\Settings\SettingsRepository( $mdb ) )->allSettings( true );
|
||||
|
||||
$url = preg_replace( '#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME'] );
|
||||
|
||||
$robots = 'User-agent: *' . PHP_EOL;
|
||||
$robots .= 'Allow: /' . PHP_EOL;
|
||||
|
||||
$site_map = '<?xml version="1.0" encoding="UTF-8"?>' . PHP_EOL;
|
||||
$site_map .= '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">' . PHP_EOL;
|
||||
$site_map .= '<url>' . PHP_EOL;
|
||||
$site_map .= '<loc>https://' . $url . '</loc>' . PHP_EOL;
|
||||
$site_map .= '<lastmod>' . date( 'Y-m-d' ) . '</lastmod>' . PHP_EOL;
|
||||
$site_map .= '<changefreq>daily</changefreq>' . PHP_EOL;
|
||||
$site_map .= '<priority>1</priority>' . PHP_EOL;
|
||||
$site_map .= '</url>' . PHP_EOL;
|
||||
|
||||
//
|
||||
// SYSTEM ROUTES — delete all and reinsert
|
||||
//
|
||||
$mdb->delete( 'pp_routes', [ 'type' => 'system' ] );
|
||||
|
||||
// Static system routes (hardcoded, never change)
|
||||
$systemRoutes = [
|
||||
// Wyszukiwarka
|
||||
[ 'pattern' => '^wyszukiwarka/([^/]+)/([0-9]+)$', 'destination' => 'index.php?module=search&action=search_results&query=$1&bs=$2' ],
|
||||
[ 'pattern' => '^wyszukiwarka/([^/]+)$', 'destination' => 'index.php?module=search&action=search_results&query=$1&bs=1' ],
|
||||
// Zamowienia
|
||||
[ 'pattern' => '^zamowienie/([a-zA-Z0-9-]+)$', 'destination' => 'index.php?module=shop_order&action=order_details&order_hash=$1' ],
|
||||
[ 'pattern' => '^potwierdzenie-platnosci/([a-zA-Z0-9-]+)$', 'destination' => 'index.php?module=shop_order&action=payment_confirmation&order_hash=$1' ],
|
||||
// Platnosci
|
||||
[ 'pattern' => '^tpay-status$', 'destination' => 'index.php?module=shop_order&action=payment_status_tpay' ],
|
||||
[ 'pattern' => '^platnosc-status$', 'destination' => 'index.php?module=shop_order&action=payment_status_hotpay' ],
|
||||
[ 'pattern' => '^przelewy24-status$', 'destination' => 'index.php?module=shop_order&action=payment_status_przelewy24pl' ],
|
||||
// Koszyk
|
||||
[ 'pattern' => '^koszyk$', 'destination' => 'index.php?module=shop_basket&action=main_view' ],
|
||||
[ 'pattern' => '^koszyk-podsumowanie$', 'destination' => 'index.php?module=shop_basket&action=summary_view' ],
|
||||
[ 'pattern' => '^zloz-zamowienie$', 'destination' => 'index.php?module=shop_basket&action=basket_save' ],
|
||||
// Klient
|
||||
[ 'pattern' => '^rejestracja$', 'destination' => 'index.php?module=shop_client&action=register_form' ],
|
||||
[ 'pattern' => '^logowanie$', 'destination' => 'index.php?module=shop_client&action=login_form' ],
|
||||
[ 'pattern' => '^wylogowanie$', 'destination' => 'index.php?module=shop_client&action=logout' ],
|
||||
[ 'pattern' => '^odzyskiwanie-hasla$', 'destination' => 'index.php?module=shop_client&action=recover_password' ],
|
||||
[ 'pattern' => '^panel-klienta/zamowienia$', 'destination' => 'index.php?module=shop_client&action=client_orders' ],
|
||||
[ 'pattern' => '^panel-klienta/adresy$', 'destination' => 'index.php?module=shop_client&action=client_addresses' ],
|
||||
[ 'pattern' => '^panel-klienta/nowy-adres$', 'destination' => 'index.php?module=shop_client&action=address_edit' ],
|
||||
[ 'pattern' => '^panel-klienta/edytuj-adres/([0-9]+)$', 'destination' => 'index.php?module=shop_client&action=address_edit&id=$1' ],
|
||||
[ 'pattern' => '^panel-klienta/usun-adres/([0-9]+)$', 'destination' => 'index.php?module=shop_client&action=address_delete&id=$1' ],
|
||||
// Newsletter
|
||||
[ 'pattern' => '^newsletter/signin$', 'destination' => 'index.php?module=newsletter&action=signin' ],
|
||||
[ 'pattern' => '^newsletter/confirm/hash=(.+)$', 'destination' => 'index.php?module=newsletter&action=confirm&hash=$1' ],
|
||||
[ 'pattern' => '^newsletter/unsubscribe/hash=(.+)$', 'destination' => 'index.php?module=newsletter&action=unsubscribe&hash=$1' ],
|
||||
// Moduły AJAX (shopBasket, shopClient, shopProduct, shopCoupon, search)
|
||||
[ 'pattern' => '^shopBasket/([^/]+)/(.+)$', 'destination' => 'index.php?module=shopBasket&action=$1&$2' ],
|
||||
[ 'pattern' => '^shopBasket/([^/]+)$', 'destination' => 'index.php?module=shopBasket&action=$1' ],
|
||||
[ 'pattern' => '^shopClient/([^/]+)/(.+)$', 'destination' => 'index.php?module=shopClient&action=$1&$2' ],
|
||||
[ 'pattern' => '^shopClient/([^/]+)$', 'destination' => 'index.php?module=shopClient&action=$1' ],
|
||||
[ 'pattern' => '^shopProduct/([^/]+)/(.+)$', 'destination' => 'index.php?module=shopProduct&action=$1&$2' ],
|
||||
[ 'pattern' => '^shopProduct/([^/]+)$', 'destination' => 'index.php?module=shopProduct&action=$1' ],
|
||||
[ 'pattern' => '^shopCoupon/([^/]+)/(.+)$', 'destination' => 'index.php?module=shopCoupon&action=$1&$2' ],
|
||||
[ 'pattern' => '^shopCoupon/([^/]+)$', 'destination' => 'index.php?module=shopCoupon&action=$1' ],
|
||||
[ 'pattern' => '^search/([^/]+)/(.+)$', 'destination' => 'index.php?module=search&action=$1&$2' ],
|
||||
[ 'pattern' => '^search/([^/]+)$', 'destination' => 'index.php?module=search&action=$1' ],
|
||||
];
|
||||
|
||||
foreach ( $systemRoutes as $route )
|
||||
{
|
||||
$mdb->insert( 'pp_routes', [
|
||||
'type' => 'system',
|
||||
'lang_id' => 0,
|
||||
'pattern' => $route['pattern'],
|
||||
'destination' => $route['destination'],
|
||||
] );
|
||||
}
|
||||
|
||||
// Dynamic system routes — languages
|
||||
$results = $mdb->select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
$mdb->insert( 'pp_routes', [
|
||||
'type' => 'system',
|
||||
'lang_id' => 0,
|
||||
'pattern' => '^' . $row['id'] . '$',
|
||||
'destination' => 'index.php?a=change_language&id=' . $row['id'],
|
||||
] );
|
||||
}
|
||||
|
||||
// Dynamic system routes — producenci
|
||||
$categoryDefaultLayoutId = ( new \Domain\Layouts\LayoutsRepository( $mdb ) )->categoryDefaultLayoutId();
|
||||
|
||||
$mdb->insert( 'pp_routes', [
|
||||
'type' => 'system',
|
||||
'lang_id' => 0,
|
||||
'pattern' => '^producenci$',
|
||||
'destination' => 'index.php?module=shop_producer&action=list&layout_id=' . $categoryDefaultLayoutId,
|
||||
] );
|
||||
|
||||
$rows = $mdb->select( 'pp_shop_producer', '*', [ 'status' => 1 ] );
|
||||
if ( self::is_array_fix( $rows ) ) foreach ( $rows as $row )
|
||||
{
|
||||
$mdb->insert( 'pp_routes', [
|
||||
'type' => 'system',
|
||||
'lang_id' => 0,
|
||||
'pattern' => '^producent/' . self::seo( $row['name'] ) . '$',
|
||||
'destination' => 'index.php?module=shop_producer&action=products&producer_id=' . $row['id'] . '&layout_id=' . $categoryDefaultLayoutId,
|
||||
] );
|
||||
$mdb->insert( 'pp_routes', [
|
||||
'type' => 'system',
|
||||
'lang_id' => 0,
|
||||
'pattern' => '^producent/' . self::seo( $row['name'] ) . '/([0-9]+)$',
|
||||
'destination' => 'index.php?module=shop_producer&action=products&producer_id=' . $row['id'] . '&layout_id=' . $categoryDefaultLayoutId . '&bs=$1',
|
||||
] );
|
||||
}
|
||||
|
||||
//
|
||||
// HTACCESS — generuj z PHP (bez szablonu htaccess.conf)
|
||||
//
|
||||
$htaccess_data = 'RewriteEngine On' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteBase /' . PHP_EOL;
|
||||
$htaccess_data .= 'Options +FollowSymlinks' . PHP_EOL;
|
||||
$htaccess_data .= 'Options -Indexes' . PHP_EOL;
|
||||
$htaccess_data .= PHP_EOL;
|
||||
$htaccess_data .= '# Przekierowanie z www na bez www i z http na https w jednym kroku' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteRule ^ https://%1%{REQUEST_URI} [R=301,L]' . PHP_EOL;
|
||||
$htaccess_data .= PHP_EOL;
|
||||
$htaccess_data .= '# Przekierowanie z http na https, jesli nie zawiera www' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteCond %{HTTPS} off' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteCond %{REQUEST_URI} !^/(tpay-status|platnosc-status|przelewy24-status)$ [NC]' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]' . PHP_EOL;
|
||||
$htaccess_data .= PHP_EOL;
|
||||
$htaccess_data .= '# Usuwanie koncowego slasha dla niekatalogów' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteCond %{REQUEST_FILENAME} !-d' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteCond %{REQUEST_URI} !^/admin/.*$ [NC]' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteCond %{REQUEST_URI} (.+)/$' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteRule ^ %1 [R=301,L]' . PHP_EOL;
|
||||
$htaccess_data .= PHP_EOL;
|
||||
$htaccess_data .= 'RewriteCond %{REQUEST_URI} !^(.*)/libraries/(.*) [NC]' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteCond %{REQUEST_URI} !^(.*)/layout/(.*) [NC]' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteRule ^admin/([^/]*)/([^/]*)/(.*)$ admin/index.php?module=$1&action=$2&$3 [L]' . PHP_EOL;
|
||||
$htaccess_data .= PHP_EOL;
|
||||
$htaccess_data .= 'RewriteRule ^admin/$ admin/index.php [L]' . PHP_EOL;
|
||||
$htaccess_data .= PHP_EOL;
|
||||
$htaccess_data .= 'RewriteRule ^thumb/([0-9]*)/([0-9]*)/(.*)$ /libraries/thumb.php?img=$3&w=$1&h=$2 [L]' . PHP_EOL;
|
||||
$htaccess_data .= PHP_EOL;
|
||||
$htaccess_data .= 'RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /index.php' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteRule ^ /%1 [R=301,L]' . PHP_EOL;
|
||||
|
||||
/* cache — zastąpienie placeholdera {HTACCESS_CACHE} */
|
||||
if ( $settings['htaccess_cache'] )
|
||||
{
|
||||
$htaccess_data .= '<IfModule mod_deflate.c>' . PHP_EOL
|
||||
. 'AddOutputFilterByType DEFLATE text/html text/plain text/xml application/xml application/xhtml+xml text/css text/javascript application/javascript application/x-javascript' . PHP_EOL
|
||||
. '</IfModule>' . PHP_EOL
|
||||
. '<IfModule mod_headers.c>' . PHP_EOL
|
||||
. 'Header set Access-Control-Allow-Origin "*"' . PHP_EOL
|
||||
. '</IfModule>' . PHP_EOL
|
||||
. '<IfModule mod_expires.c>' . PHP_EOL
|
||||
. 'ExpiresActive on' . PHP_EOL
|
||||
. 'ExpiresDefault "access plus 1 month"' . PHP_EOL
|
||||
. 'ExpiresByType text/css "access plus 1 year"' . PHP_EOL
|
||||
. 'ExpiresByType application/json "access plus 0 seconds"' . PHP_EOL
|
||||
. 'ExpiresByType application/xml "access plus 0 seconds"' . PHP_EOL
|
||||
. 'ExpiresByType text/xml "access plus 0 seconds"' . PHP_EOL
|
||||
. 'ExpiresByType image/x-icon "access plus 1 week"' . PHP_EOL
|
||||
. 'ExpiresByType text/x-component "access plus 1 month"' . PHP_EOL
|
||||
. 'ExpiresByType text/html "access plus 0 seconds"' . PHP_EOL
|
||||
. 'ExpiresByType application/javascript "access plus 1 year"' . PHP_EOL
|
||||
. 'ExpiresByType application/x-web-app-manifest+json "access plus 0 seconds"' . PHP_EOL
|
||||
. 'ExpiresByType text/cache-manifest "access plus 0 seconds"' . PHP_EOL
|
||||
. 'ExpiresByType audio/ogg "access plus 1 month"' . PHP_EOL
|
||||
. 'ExpiresByType image/gif "access plus 1 month"' . PHP_EOL
|
||||
. 'ExpiresByType image/jpeg "access plus 1 month"' . PHP_EOL
|
||||
. 'ExpiresByType image/png "access plus 1 month"' . PHP_EOL
|
||||
. 'ExpiresByType video/mp4 "access plus 1 month"' . PHP_EOL
|
||||
. 'ExpiresByType video/ogg "access plus 1 month"' . PHP_EOL
|
||||
. 'ExpiresByType video/webm "access plus 1 month"' . PHP_EOL
|
||||
. 'ExpiresByType application/atom+xml "access plus 1 hour"' . PHP_EOL
|
||||
. 'ExpiresByType application/rss+xml "access plus 1 hour"' . PHP_EOL
|
||||
. 'ExpiresByType application/font-woff "access plus 1 month"' . PHP_EOL
|
||||
. 'ExpiresByType application/vnd.ms-fontobject "access plus 1 month"' . PHP_EOL
|
||||
. 'ExpiresByType application/x-font-ttf "access plus 1 month"' . PHP_EOL
|
||||
. 'ExpiresByType font/opentype "access plus 1 month"' . PHP_EOL
|
||||
. 'ExpiresByType image/svg+xml "access plus 1 month"' . PHP_EOL
|
||||
. '</IfModule>' . PHP_EOL;
|
||||
}
|
||||
else
|
||||
{
|
||||
$htaccess_data .= '<IfModule mod_headers.c>' . PHP_EOL
|
||||
. 'Header set Cache-Control "no-cache, no-store, must-revalidate"' . PHP_EOL
|
||||
. 'Header set Pragma "no-cache"' . PHP_EOL
|
||||
. 'Header set Expires 0' . PHP_EOL
|
||||
. '</IfModule>' . PHP_EOL;
|
||||
}
|
||||
|
||||
$htaccess_data .= '<Files *.conf>' . PHP_EOL;
|
||||
$htaccess_data .= ' Order Deny,Allow' . PHP_EOL;
|
||||
$htaccess_data .= ' Deny from all' . PHP_EOL;
|
||||
$htaccess_data .= '</Files>' . PHP_EOL;
|
||||
$htaccess_data .= '<Files *.log>' . PHP_EOL;
|
||||
$htaccess_data .= ' Order Deny,Allow' . PHP_EOL;
|
||||
$htaccess_data .= ' Deny from all' . PHP_EOL;
|
||||
$htaccess_data .= '</Files>' . PHP_EOL;
|
||||
$htaccess_data .= '<Files *.ini>' . PHP_EOL;
|
||||
$htaccess_data .= ' Order Deny,Allow' . PHP_EOL;
|
||||
$htaccess_data .= ' Deny from all' . PHP_EOL;
|
||||
$htaccess_data .= '</Files>' . PHP_EOL;
|
||||
|
||||
//
|
||||
// KATEGORIE — sitemap + pp_routes (bez zmian)
|
||||
//
|
||||
$results = $mdb->select( 'pp_langs', [ 'id', 'start' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
!$row['start'] ? $language_link = $row['id'] . '/' : $language_link = '';
|
||||
|
||||
$results2 = $mdb->select( 'pp_shop_categories_langs', [ '[><]pp_shop_categories' => [ 'category_id' => 'id' ] ], [ 'seo_link', 'title', 'category_id' ], [ 'lang_id' => $row['id'], 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results2 ) ) foreach ( $results2 as $row2 )
|
||||
{
|
||||
if ( $row2['title'] )
|
||||
{
|
||||
$site_map .= '<url>' . PHP_EOL;
|
||||
if ( $row2['seo_link'] )
|
||||
$site_map .= '<loc>https://' . $url . '/' . $language_link . self::seo( $row2['seo_link'] ) . '</loc>' . PHP_EOL;
|
||||
else
|
||||
$site_map .= '<loc>https://' . $url . '/' . $language_link . 'k-' . $row2['category_id'] . '-' . self::seo( $row2['title'] ) . '</loc>' . PHP_EOL;
|
||||
$site_map .= '<lastmod>' . date( 'Y-m-d' ) . '</lastmod>' . PHP_EOL;
|
||||
$site_map .= '<changefreq>daily</changefreq>' . PHP_EOL;
|
||||
$site_map .= '<priority>1</priority>' . PHP_EOL;
|
||||
$site_map .= '</url>' . PHP_EOL;
|
||||
|
||||
$seoSlug = $row2['seo_link'] ? self::seo( $row2['seo_link'] ) : 'k-' . $row2['category_id'] . '-' . self::seo( $row2['title'] );
|
||||
|
||||
$mdb->delete( 'pp_routes', [ 'AND' => [ 'category_id' => $row2['category_id'], 'lang_id' => $row['id'] ] ] );
|
||||
|
||||
$mdb->insert( 'pp_routes', [
|
||||
'category_id' => $row2['category_id'],
|
||||
'lang_id' => $row['id'],
|
||||
'pattern' => '^' . $language_link . $seoSlug . '$',
|
||||
'destination' => 'index.php?category=' . $row2['category_id'] . '&lang=' . $row['id'],
|
||||
] );
|
||||
$mdb->insert( 'pp_routes', [
|
||||
'category_id' => $row2['category_id'],
|
||||
'lang_id' => $row['id'],
|
||||
'pattern' => '^' . $language_link . $seoSlug . '/([0-9]+)$',
|
||||
'destination' => 'index.php?category=' . $row2['category_id'] . '&lang=' . $row['id'] . '&bs=$1',
|
||||
] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// PRODUKTY — sitemap + pp_routes (bez zmian)
|
||||
//
|
||||
$results = $mdb->select( 'pp_langs', [ 'id', 'start' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) )
|
||||
{
|
||||
foreach ( $results as $row )
|
||||
{
|
||||
!$row['start'] ? $language_link = $row['id'] . '/' : $language_link = '';
|
||||
|
||||
$results2 = $mdb->select( 'pp_shop_products_langs', [ '[><]pp_shop_products' => [ 'product_id' => 'id' ] ], [ 'seo_link', 'name', 'product_id' ], [ 'lang_id' => $row['id'], 'ORDER' => [ 'name' => 'ASC' ] ] );
|
||||
if ( is_array( $results2 ) )
|
||||
{
|
||||
foreach ( $results2 as $row2 )
|
||||
{
|
||||
$mdb->delete( 'pp_routes', [ 'AND' => [ 'product_id' => $row2['product_id'], 'lang_id' => $row['id'] ] ] );
|
||||
|
||||
if ( $row2['name'] )
|
||||
{
|
||||
$site_map .= '<url>' . PHP_EOL;
|
||||
if ( $row2['seo_link'] )
|
||||
$site_map .= '<loc>https://' . $url . '/' . $language_link . self::seo( $row2['seo_link'] ) . '</loc>' . PHP_EOL;
|
||||
else
|
||||
$site_map .= '<loc>https://' . $url . '/' . $language_link . 'p-' . $row2['product_id'] . '-' . self::seo( $row2['name'] ) . '</loc>' . PHP_EOL;
|
||||
$site_map .= '<lastmod>' . date( 'Y-m-d' ) . '</lastmod>' . PHP_EOL;
|
||||
$site_map .= '<changefreq>daily</changefreq>' . PHP_EOL;
|
||||
$site_map .= '<priority>1</priority>' . PHP_EOL;
|
||||
$site_map .= '</url>' . PHP_EOL;
|
||||
|
||||
if ( $row2['seo_link'] )
|
||||
{
|
||||
$mdb->insert( 'pp_routes', [ 'product_id' => $row2['product_id'], 'lang_id' => $row['id'], 'pattern' => '^' . $language_link . self::seo( $row2['seo_link'] ) . '$', 'destination' => 'index.php?product=' . $row2['product_id'] ] );
|
||||
$mdb->insert( 'pp_routes', [ 'product_id' => $row2['product_id'], 'lang_id' => $row['id'], 'pattern' => '^' . $language_link . self::seo( $row2['seo_link'] ) . '/([0-9-]+)$', 'destination' => 'index.php?product=' . $row2['product_id'] . '&permutation_hash=$1' ] );
|
||||
}
|
||||
else
|
||||
{
|
||||
$mdb->insert( 'pp_routes', [ 'product_id' => $row2['product_id'], 'lang_id' => $row['id'], 'pattern' => '^' . $language_link . 'p-' . $row2['product_id'] . '-' . self::seo( $row2['name'] ) . '$', 'destination' => 'index.php?product=' . $row2['product_id'] ] );
|
||||
$mdb->insert( 'pp_routes', [ 'product_id' => $row2['product_id'], 'lang_id' => $row['id'], 'pattern' => '^' . $language_link . 'p-' . $row2['product_id'] . '-' . self::seo( $row2['name'] ) . '/([0-9-]+)$', 'destination' => 'index.php?product=' . $row2['product_id'] . '&permutation_hash=$1' ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// STRONY + ARTYKULY — sitemap + pp_routes (bez zmian)
|
||||
//
|
||||
$results = $mdb->select( 'pp_langs', [ 'id', 'start' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results ) )
|
||||
foreach ( $results as $row )
|
||||
{
|
||||
( !$row['start'] and count( $results ) > 1 ) ? $language_link = $row['id'] . '/' : $language_link = '';
|
||||
|
||||
$results2 = $mdb->select( 'pp_pages_langs', [ '[><]pp_pages' => [ 'page_id' => 'id' ] ], [ 'seo_link', 'title', 'page_id', 'noindex', 'start', 'link', 'page_type' ], [ 'lang_id' => $row['id'], 'ORDER' => [ 'start' => 'DESC', 'o' => 'ASC' ] ] );
|
||||
if ( is_array( $results2 ) )
|
||||
foreach ( $results2 as $row2 )
|
||||
{
|
||||
if ( $row2['title'] and $row2['page_type'] != 3 and $row2['page_type'] != 5 )
|
||||
{
|
||||
if ( !$row2['noindex'] )
|
||||
{
|
||||
$site_map .= '<url>' . PHP_EOL;
|
||||
if ( $row2['seo_link'] )
|
||||
$site_map .= '<loc>https://' . $url . '/' . self::seo( $row2['seo_link'] ) . '</loc>' . PHP_EOL;
|
||||
else
|
||||
$site_map .= '<loc>https://' . $url . '/s-' . $row2['page_id'] . '-' . self::seo( $row2['title'] ) . '</loc>' . PHP_EOL;
|
||||
$site_map .= '<lastmod>' . date( 'Y-m-d' ) . '</lastmod>' . PHP_EOL;
|
||||
$site_map .= '<changefreq>daily</changefreq>' . PHP_EOL;
|
||||
$site_map .= '<priority>1</priority>' . PHP_EOL;
|
||||
$site_map .= '</url>' . PHP_EOL;
|
||||
}
|
||||
else if ( $row2['noindex'] and $row2['seo_link'] )
|
||||
{
|
||||
$robots .= 'User-agent: GoogleBot' . PHP_EOL;
|
||||
$robots .= 'Disallow: /' . $row2['seo_link'] . PHP_EOL;
|
||||
}
|
||||
|
||||
if ( $row2['start'] )
|
||||
{
|
||||
if ( $row2['seo_link'] )
|
||||
{
|
||||
$htaccess_data .= PHP_EOL . 'RewriteCond %{REQUEST_URI} ^/' . self::seo( $row2['seo_link'] ) . '$';
|
||||
$htaccess_data .= PHP_EOL . 'RewriteRule ^(.*)$ http://www.' . $url . '/' . $language_link . ' [R=permanent,L]';
|
||||
$htaccess_data .= PHP_EOL . 'RewriteCond %{REQUEST_URI} ^/' . self::seo( $row2['seo_link'] ) . '-1$';
|
||||
$htaccess_data .= PHP_EOL . 'RewriteRule ^(.*)$ http://www.' . $url . '/' . $language_link . ' [R=permanent,L]';
|
||||
}
|
||||
else
|
||||
{
|
||||
$htaccess_data .= PHP_EOL . 'RewriteCond %{REQUEST_URI} ^/s-' . $row2['page_id'] . '-' . self::seo( $row2['title'] ) . '$';
|
||||
$htaccess_data .= PHP_EOL . 'RewriteRule ^(.*)$ http://www.' . $url . '/' . $language_link . ' [R=permanent,L]';
|
||||
$htaccess_data .= PHP_EOL . 'RewriteCond %{REQUEST_URI} ^/s-' . $row2['page_id'] . '-' . self::seo( $row2['title'] ) . '-1$';
|
||||
$htaccess_data .= PHP_EOL . 'RewriteRule ^(.*)$ http://www.' . $url . '/' . $language_link . ' [R=permanent,L]';
|
||||
}
|
||||
$htaccess_data .= PHP_EOL . 'RewriteRule ^$ index.php?a=page&id=' . $row2['page_id'] . '&lang=' . $row['id'] . ' [L]';
|
||||
}
|
||||
|
||||
$seoSlug = $row2['seo_link'] ? self::seo( $row2['seo_link'] ) : 's-' . $row2['page_id'] . '-' . self::seo( $row2['title'] );
|
||||
$langPrefix = $row2['start'] ? '' : $language_link;
|
||||
|
||||
$mdb->delete( 'pp_routes', [ 'AND' => [ 'page_id' => $row2['page_id'], 'lang_id' => $row['id'] ] ] );
|
||||
|
||||
$mdb->insert( 'pp_routes', [
|
||||
'page_id' => $row2['page_id'],
|
||||
'lang_id' => $row['id'],
|
||||
'pattern' => '^' . $langPrefix . $seoSlug . '$',
|
||||
'destination' => 'index.php?a=page&id=' . $row2['page_id'] . '&lang=' . $row['id'],
|
||||
] );
|
||||
$mdb->insert( 'pp_routes', [
|
||||
'page_id' => $row2['page_id'],
|
||||
'lang_id' => $row['id'],
|
||||
'pattern' => '^' . $langPrefix . $seoSlug . '/([0-9]+)$',
|
||||
'destination' => 'index.php?a=page&id=' . $row2['page_id'] . '&lang=' . $row['id'] . '&bs=$1',
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
$results2 = $mdb->select( 'pp_articles_langs', [ '[><]pp_articles' => [ 'article_id' => 'id' ] ], [ 'seo_link', 'title', 'article_id', 'noindex', 'copy_from' ], [ 'AND' => [ 'status' => 1, 'lang_id' => $row['id'], 'block_direct_access' => 0 ] ] );
|
||||
if ( is_array( $results2 ) )
|
||||
foreach ( $results2 as $row2 )
|
||||
{
|
||||
if ( $row2['copy_from'] != null )
|
||||
{
|
||||
$results_tmp = $mdb->get( 'pp_articles_langs', [ 'seo_link', 'title' ], [ 'AND' => [ 'article_id' => $row2['article_id'], 'lang_id' => $row2['copy_from'] ] ] );
|
||||
$row2['seo_link'] = $results_tmp['seo_link'];
|
||||
$row2['title'] = $results_tmp['title'];
|
||||
}
|
||||
|
||||
if ( !$row2['noindex'] )
|
||||
{
|
||||
$site_map .= '<url>' . PHP_EOL;
|
||||
if ( $row2['seo_link'] )
|
||||
$site_map .= '<loc>https://' . $url . '/' . self::seo( $row2['seo_link'] ) . '</loc>' . PHP_EOL;
|
||||
else
|
||||
$site_map .= '<loc>https://' . $url . '/a-' . $row2['article_id'] . '-' . self::seo( $row2['title'] ) . '</loc>' . PHP_EOL;
|
||||
$site_map .= '<lastmod>' . date( 'Y-m-d' ) . '</lastmod>' . PHP_EOL;
|
||||
$site_map .= '<changefreq>daily</changefreq>' . PHP_EOL;
|
||||
$site_map .= '<priority>1</priority>' . PHP_EOL;
|
||||
$site_map .= '</url>' . PHP_EOL;
|
||||
}
|
||||
else if ( $row2['noindex'] and $row2['seo_link'] )
|
||||
{
|
||||
$robots .= 'User-agent: GoogleBot' . PHP_EOL;
|
||||
$robots .= 'Disallow: /' . $row2['seo_link'] . PHP_EOL;
|
||||
}
|
||||
|
||||
$mdb->delete( 'pp_routes', [ 'AND' => [ 'article_id' => $row2['article_id'], 'lang_id' => $row['id'] ] ] );
|
||||
|
||||
if ( $row2['seo_link'] )
|
||||
{
|
||||
$mdb->insert( 'pp_routes', [
|
||||
'article_id' => $row2['article_id'],
|
||||
'lang_id' => $row['id'],
|
||||
'pattern' => '^' . $language_link . self::seo( $row2['seo_link'] ) . '$',
|
||||
'destination' => 'index.php?article=' . $row2['article_id'] . '&lang=' . $row['id'],
|
||||
] );
|
||||
}
|
||||
else if ( $row2['title'] != null )
|
||||
{
|
||||
$mdb->insert( 'pp_routes', [
|
||||
'article_id' => $row2['article_id'],
|
||||
'lang_id' => $row['id'],
|
||||
'pattern' => '^' . $language_link . 'a-' . $row2['article_id'] . '-' . self::seo( $row2['title'] ) . '$',
|
||||
'destination' => 'index.php?article=' . $row2['article_id'] . '&lang=' . $row['id'],
|
||||
] );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Invalidacja cache tras
|
||||
try {
|
||||
( new \Shared\Cache\CacheHandler() )->delete( 'pp_routes:all' );
|
||||
} catch ( \Exception $e ) {
|
||||
// Redis niedostepny — ignorujemy
|
||||
}
|
||||
|
||||
$results = $mdb->get( 'pp_settings', 'value', [ 'param' => 'htaccess' ] );
|
||||
if ( $results )
|
||||
$htaccess_data .= PHP_EOL . $results;
|
||||
|
||||
$results = $mdb->get( 'pp_settings', 'value', [ 'param' => 'robots' ] );
|
||||
if ( $results )
|
||||
$robots .= PHP_EOL . $results;
|
||||
|
||||
$site_map .= '</urlset>';
|
||||
|
||||
$htaccess_data .= PHP_EOL;
|
||||
$htaccess_data .= 'RewriteCond %{REQUEST_FILENAME} !-f' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteCond %{REQUEST_FILENAME} !-d' . PHP_EOL;
|
||||
$htaccess_data .= 'RewriteRule ^ index.php [L]';
|
||||
|
||||
// Niektore hostingi blokuja zmiane wersji PHP przez .htaccess.
|
||||
$htaccess_data = preg_replace( '/^(\\s*)(AddHandler|SetHandler|ForceType)\\b/im', '$1# $2', $htaccess_data );
|
||||
|
||||
$fp = fopen( $dir . '.htaccess', 'w' );
|
||||
fwrite( $fp, $htaccess_data );
|
||||
fclose( $fp );
|
||||
|
||||
$fp = fopen( $dir . 'sitemap.xml', 'w' );
|
||||
fwrite( $fp, $site_map );
|
||||
fclose( $fp );
|
||||
|
||||
$fp = fopen( $dir . 'robots.txt', 'w' );
|
||||
fwrite( $fp, $robots );
|
||||
fclose( $fp );
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: Run tests**
|
||||
```
|
||||
php phpunit.phar --configuration phpunit.xml
|
||||
```
|
||||
Expected: all tests pass (htacces() has no unit tests, covered by integration).
|
||||
|
||||
---
|
||||
|
||||
## Task 3: Delete `libraries/htaccess.conf`
|
||||
|
||||
**Files:**
|
||||
- Delete: `libraries/htaccess.conf`
|
||||
|
||||
**Step 1: Verify htacces() no longer references the file**
|
||||
|
||||
Search for any remaining `file_get_contents` referencing `htaccess.conf`:
|
||||
```bash
|
||||
grep -r "htaccess.conf" autoload/
|
||||
```
|
||||
Expected: no results.
|
||||
|
||||
**Step 2: Delete the file**
|
||||
```bash
|
||||
rm libraries/htaccess.conf
|
||||
```
|
||||
|
||||
**Step 3: Run tests**
|
||||
```
|
||||
php phpunit.phar --configuration phpunit.xml
|
||||
```
|
||||
Expected: all tests still pass.
|
||||
|
||||
---
|
||||
|
||||
## Task 4: Update `docs/DATABASE_STRUCTURE.md`
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/DATABASE_STRUCTURE.md` (section `## pp_routes`)
|
||||
|
||||
**Step 1: Add `type` column to the pp_routes table description**
|
||||
|
||||
Find the `## pp_routes` section and add the `type` row to the column table:
|
||||
|
||||
```markdown
|
||||
| type | Typ trasy: NULL = encja (produkt/kategoria/strona/artykuł), 'system' = trasa systemowa |
|
||||
```
|
||||
|
||||
Also update the description paragraph to mention that system routes are managed automatically.
|
||||
|
||||
---
|
||||
|
||||
## Task 5: Manual integration test on server
|
||||
|
||||
**Step 1: Apply migration**
|
||||
```sql
|
||||
ALTER TABLE pp_routes ADD COLUMN type VARCHAR(20) NULL AFTER article_id;
|
||||
```
|
||||
|
||||
**Step 2: Trigger `htacces()` regeneration**
|
||||
|
||||
Log in to admin panel → save any product or category → this calls `htacces()`.
|
||||
|
||||
**Step 3: Verify pp_routes has system routes**
|
||||
```sql
|
||||
SELECT COUNT(*) FROM pp_routes WHERE type = 'system';
|
||||
```
|
||||
Expected: ~35+ rows (32 static + language rows + producer rows).
|
||||
|
||||
**Step 4: Verify .htaccess was generated correctly**
|
||||
|
||||
Open `.htaccess` — should NOT contain `RewriteRule ^koszyk$`, `^logowanie$`, etc. Should contain HTTPS redirect, admin routing, thumb routing, cache block.
|
||||
|
||||
**Step 5: Test URLs in browser**
|
||||
- `/koszyk` → koszyk page ✓
|
||||
- `/logowanie` → login page ✓
|
||||
- `/wyszukiwarka/test` → search results ✓
|
||||
- `/zamowienie/abc123` → order details ✓
|
||||
- `/shopClient/confirm/hash=xyz` → client confirm action ✓
|
||||
- Category URL → category page ✓
|
||||
- Product URL → product page ✓
|
||||
|
||||
**Step 6: Run full test suite**
|
||||
```
|
||||
php phpunit.phar --configuration phpunit.xml
|
||||
```
|
||||
Expected: 807 tests, all pass.
|
||||
|
||||
---
|
||||
|
||||
## Task 6: Commit
|
||||
|
||||
**Step 1: Stage and commit**
|
||||
```bash
|
||||
git add migrations/0.329.sql
|
||||
git add autoload/Shared/Helpers/Helpers.php
|
||||
git add docs/DATABASE_STRUCTURE.md
|
||||
git add docs/plans/2026-02-27-htaccess-conf-elimination.md
|
||||
git add docs/plans/2026-02-27-htaccess-to-routes-design.md
|
||||
git rm libraries/htaccess.conf
|
||||
git commit -m "feat: eliminate htaccess.conf, move all routes to pp_routes (v0.330)"
|
||||
```
|
||||
@@ -1,121 +0,0 @@
|
||||
# Design: Eliminacja htaccess.conf i przeniesienie wszystkich tras do pp_routes
|
||||
|
||||
**Data:** 2026-02-27
|
||||
**Wersja docelowa:** 0.330
|
||||
|
||||
---
|
||||
|
||||
## Cel
|
||||
|
||||
Wyeliminowanie pliku `libraries/htaccess.conf` jako szablonu i przeniesienie wszystkich URL-i, które dotychczas były wpisane na sztywno w `.htaccess`, do tabeli `pp_routes`. Logika generowania `.htaccess` zostaje w całości w `Helpers::htacces()`.
|
||||
|
||||
---
|
||||
|
||||
## Co zostaje w `.htaccess` (reguły Apache-level)
|
||||
|
||||
Tylko dyrektywy, których PHP nie może obsłużyć:
|
||||
|
||||
- `RewriteEngine On`, `Options`
|
||||
- Redirect HTTPS/www
|
||||
- Redirect HTTP→HTTPS (z wyłączeniem tpay-status, platnosc-status, przelewy24-status)
|
||||
- Usuwanie trailing slash (z wyłączeniem `/admin/`)
|
||||
- Routing `/admin/` → `admin/index.php`
|
||||
- `thumb/([0-9]*)/([0-9]*)/(.*)` → `/libraries/thumb.php` (inny plik PHP — niemożliwe przez pp_routes)
|
||||
- `RewriteCond %{THE_REQUEST} ^[A-Z]{3,9}\ /index.php` — redirect z index.php
|
||||
- Blok cache headers (gzip, expires) — zależny od `$settings['htaccess_cache']`
|
||||
- Ochrona plików: `<Files *.conf>`, `<Files *.log>`, `<Files *.ini>`
|
||||
- Przekierowania 301 stron startowych (generowane dynamicznie w pętli pages)
|
||||
- Niestandardowe reguły z `pp_settings` (param=htaccess)
|
||||
- Catch-all: `RewriteCond !-f`, `!-d`, `RewriteRule ^ index.php [L]`
|
||||
|
||||
---
|
||||
|
||||
## Co przechodzi do `pp_routes`
|
||||
|
||||
### Statyczne trasy systemowe (hardcoded, niezmienne)
|
||||
|
||||
| Pattern | Destination |
|
||||
|---------|-------------|
|
||||
| `^wyszukiwarka/([^/]+)/([0-9]+)$` | `index.php?module=search&action=search_results&query=$1&bs=$2` |
|
||||
| `^wyszukiwarka/([^/]+)$` | `index.php?module=search&action=search_results&query=$1&bs=1` |
|
||||
| `^zamowienie/([a-zA-Z0-9-]+)$` | `index.php?module=shop_order&action=order_details&order_hash=$1` |
|
||||
| `^potwierdzenie-platnosci/([a-zA-Z0-9-]+)$` | `index.php?module=shop_order&action=payment_confirmation&order_hash=$1` |
|
||||
| `^tpay-status$` | `index.php?module=shop_order&action=payment_status_tpay` |
|
||||
| `^platnosc-status$` | `index.php?module=shop_order&action=payment_status_hotpay` |
|
||||
| `^przelewy24-status$` | `index.php?module=shop_order&action=payment_status_przelewy24pl` |
|
||||
| `^koszyk$` | `index.php?module=shop_basket&action=main_view` |
|
||||
| `^koszyk-podsumowanie$` | `index.php?module=shop_basket&action=summary_view` |
|
||||
| `^zloz-zamowienie$` | `index.php?module=shop_basket&action=basket_save` |
|
||||
| `^rejestracja$` | `index.php?module=shop_client&action=register_form` |
|
||||
| `^logowanie$` | `index.php?module=shop_client&action=login_form` |
|
||||
| `^wylogowanie$` | `index.php?module=shop_client&action=logout` |
|
||||
| `^odzyskiwanie-hasla$` | `index.php?module=shop_client&action=recover_password` |
|
||||
| `^panel-klienta/zamowienia$` | `index.php?module=shop_client&action=client_orders` |
|
||||
| `^panel-klienta/adresy$` | `index.php?module=shop_client&action=client_addresses` |
|
||||
| `^panel-klienta/nowy-adres$` | `index.php?module=shop_client&action=address_edit` |
|
||||
| `^panel-klienta/edytuj-adres/([0-9]+)$` | `index.php?module=shop_client&action=address_edit&id=$1` |
|
||||
| `^panel-klienta/usun-adres/([0-9]+)$` | `index.php?module=shop_client&action=address_delete&id=$1` |
|
||||
| `^newsletter/signin$` | `index.php?module=newsletter&action=signin` |
|
||||
| `^newsletter/confirm/hash=(.+)$` | `index.php?module=newsletter&action=confirm&hash=$1` |
|
||||
| `^newsletter/unsubscribe/hash=(.+)$` | `index.php?module=newsletter&action=unsubscribe&hash=$1` |
|
||||
|
||||
### Trasy modułów AJAX (shopBasket, shopClient, shopProduct, shopCoupon, search)
|
||||
|
||||
Dwa wzorce na moduł — 3-segmentowy (z parametrami) i 2-segmentowy:
|
||||
|
||||
| Pattern | Destination |
|
||||
|---------|-------------|
|
||||
| `^shopBasket/([^/]+)/(.+)$` | `index.php?module=shopBasket&action=$1&$2` |
|
||||
| `^shopBasket/([^/]+)$` | `index.php?module=shopBasket&action=$1` |
|
||||
| `^shopClient/([^/]+)/(.+)$` | `index.php?module=shopClient&action=$1&$2` |
|
||||
| `^shopClient/([^/]+)$` | `index.php?module=shopClient&action=$1` |
|
||||
| `^shopProduct/([^/]+)/(.+)$` | `index.php?module=shopProduct&action=$1&$2` |
|
||||
| `^shopProduct/([^/]+)$` | `index.php?module=shopProduct&action=$1` |
|
||||
| `^shopCoupon/([^/]+)/(.+)$` | `index.php?module=shopCoupon&action=$1&$2` |
|
||||
| `^shopCoupon/([^/]+)$` | `index.php?module=shopCoupon&action=$1` |
|
||||
| `^search/([^/]+)/(.+)$` | `index.php?module=search&action=$1&$2` |
|
||||
| `^search/([^/]+)$` | `index.php?module=search&action=$1` |
|
||||
|
||||
### Dynamiczne trasy systemowe (wstawiane przy każdym `htacces()`)
|
||||
|
||||
- **Języki:** `^{lang_id}$` → `index.php?a=change_language&id={lang_id}` (per każdy aktywny język)
|
||||
- **Producenci lista:** `^producenci$` → `index.php?module=shop_producer&action=list&layout_id={id}`
|
||||
- **Producent detail:** `^producent/{slug}$` i `^producent/{slug}/([0-9]+)$` (per producent z DB)
|
||||
|
||||
---
|
||||
|
||||
## Nowa kolumna `type` w `pp_routes`
|
||||
|
||||
```sql
|
||||
ADD COLUMN type VARCHAR(20) NULL AFTER article_id
|
||||
```
|
||||
|
||||
| Wartość | Znaczenie |
|
||||
|---------|-----------|
|
||||
| `NULL` | Trasa encji (produkt, kategoria, strona, artykuł) |
|
||||
| `'system'` | Trasa systemowa (wszystkie powyższe) |
|
||||
|
||||
**Zarządzanie:** przy każdym `htacces()`:
|
||||
```php
|
||||
$mdb->delete('pp_routes', ['type' => 'system']); // usuń wszystkie
|
||||
// ... wstaw na nowo (statyczne + dynamiczne)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Eliminacja `htaccess.conf`
|
||||
|
||||
`file_get_contents($dir . 'libraries/htaccess.conf')` zastąpione PHP stringiem z tą samą treścią (tylko Apache-level reguły). Placeholder `{HTACCESS_CACHE}` zastąpiony bezpośrednim `if ($settings['htaccess_cache']) { ... } else { ... }` wbudowanym w odpowiednim miejscu.
|
||||
|
||||
Plik `libraries/htaccess.conf` zostaje usunięty.
|
||||
|
||||
---
|
||||
|
||||
## Pliki do modyfikacji
|
||||
|
||||
| Plik | Zmiana |
|
||||
|------|--------|
|
||||
| `migrations/0.329.sql` | Dodać `ADD COLUMN type VARCHAR(20) NULL` |
|
||||
| `Helpers::htacces()` | Usunąć `file_get_contents`, wbudować statyczny header, dodać inserty system routes, usunąć htaccess rules dla języków/newsletter/producenci |
|
||||
| `libraries/htaccess.conf` | Usunąć plik |
|
||||
| `docs/DATABASE_STRUCTURE.md` | Dodać kolumnę `type` do opisu pp_routes |
|
||||
@@ -34,6 +34,7 @@
|
||||
<? endif;?>
|
||||
<? if ( $this -> client ):?><div class="right"><? endif;?>
|
||||
<form class="form-horizontal" action="/zloz-zamowienie" method="POST" id="form-order">
|
||||
<input type="hidden" name="order_submit_token" value="<?= htmlspecialchars( (string)($this -> order_submit_token ?? ''), ENT_QUOTES, 'UTF-8' );?>">
|
||||
<? if ( !$this -> client ):?>
|
||||
<div class="form-group row">
|
||||
<div class="col-12">
|
||||
@@ -198,4 +199,4 @@
|
||||
$( '#address-' + address_id ).addClass( 'active' );
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -140,14 +140,16 @@
|
||||
</div>
|
||||
<div class="right">
|
||||
<?= \Shared\Tpl\Tpl::view( 'shop-basket/address-form', [
|
||||
'transport_method' => $this -> transport
|
||||
'transport_method' => $this -> transport,
|
||||
'order_submit_token' => $this -> order_submit_token
|
||||
] );?>
|
||||
</div>
|
||||
<? else:?>
|
||||
<?= \Shared\Tpl\Tpl::view( 'shop-basket/address-form', [
|
||||
'client' => $this -> client,
|
||||
'addresses' => $this -> addresses,
|
||||
'transport_method' => $this -> transport
|
||||
'transport_method' => $this -> transport,
|
||||
'order_submit_token' => $this -> order_submit_token
|
||||
] );?>
|
||||
<? endif;?>
|
||||
</div>
|
||||
@@ -156,17 +158,20 @@
|
||||
<? endif;?>
|
||||
</div>
|
||||
<script class="footer" type="text/javascript">
|
||||
document.getElementById('order-send').addEventListener('click', function() {
|
||||
var form = document.getElementById('form-order'); // Zastąp 'form-id' rzeczywistym ID Twojego formularza
|
||||
if (form.checkValidity()) {
|
||||
this.classList.add('loading-button');
|
||||
this.disabled = true;
|
||||
form.submit();
|
||||
} else {
|
||||
// Opcjonalnie: wywołaj funkcję reportValidity(), aby wyświetlić komunikaty o błędach formularza
|
||||
form.reportValidity();
|
||||
}
|
||||
});
|
||||
var orderForm = document.getElementById('form-order');
|
||||
var orderSendButton = document.getElementById('order-send');
|
||||
|
||||
if (orderForm && orderSendButton) {
|
||||
orderForm.addEventListener('submit', function(event) {
|
||||
if (orderSendButton.disabled) {
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
orderSendButton.classList.add('loading-button');
|
||||
orderSendButton.disabled = true;
|
||||
});
|
||||
}
|
||||
<? if ( $this -> settings['google_tag_manager_id'] ):?>
|
||||
dataLayer.push({ ecommerce: null });
|
||||
dataLayer.push({
|
||||
@@ -180,4 +185,4 @@
|
||||
}
|
||||
});
|
||||
<? endif;?>
|
||||
</script>
|
||||
</script>
|
||||
|
||||
44
tests/Unit/front/Controllers/ShopBasketControllerTest.php
Normal file
44
tests/Unit/front/Controllers/ShopBasketControllerTest.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
namespace Tests\Unit\front\Controllers;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use front\Controllers\ShopBasketController;
|
||||
use Domain\Order\OrderRepository;
|
||||
use Domain\PaymentMethod\PaymentMethodRepository;
|
||||
|
||||
class ShopBasketControllerTest extends TestCase
|
||||
{
|
||||
private $orderRepository;
|
||||
private $paymentMethodRepository;
|
||||
private $controller;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->orderRepository = $this->createMock(OrderRepository::class);
|
||||
$this->paymentMethodRepository = $this->createMock(PaymentMethodRepository::class);
|
||||
$this->controller = new ShopBasketController($this->orderRepository, $this->paymentMethodRepository);
|
||||
}
|
||||
|
||||
public function testConstructorAcceptsRepositories(): void
|
||||
{
|
||||
$controller = new ShopBasketController($this->orderRepository, $this->paymentMethodRepository);
|
||||
$this->assertInstanceOf(ShopBasketController::class, $controller);
|
||||
}
|
||||
|
||||
public function testHasCheckoutMethods(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'summaryView'));
|
||||
$this->assertTrue(method_exists($this->controller, 'basketSave'));
|
||||
}
|
||||
|
||||
public function testConstructorRequiresDependencies(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(ShopBasketController::class);
|
||||
$constructor = $reflection->getConstructor();
|
||||
$params = $constructor->getParameters();
|
||||
|
||||
$this->assertCount(2, $params);
|
||||
$this->assertEquals('Domain\Order\OrderRepository', $params[0]->getType()->getName());
|
||||
$this->assertEquals('Domain\PaymentMethod\PaymentMethodRepository', $params[1]->getType()->getName());
|
||||
}
|
||||
}
|
||||
BIN
updates/0.30/ver_0.329.zip
Normal file
BIN
updates/0.30/ver_0.329.zip
Normal file
Binary file not shown.
1
updates/0.30/ver_0.329_files.txt
Normal file
1
updates/0.30/ver_0.329_files.txt
Normal file
@@ -0,0 +1 @@
|
||||
F: ../libraries/htaccess.conf
|
||||
27
updates/0.30/ver_0.329_manifest.json
Normal file
27
updates/0.30/ver_0.329_manifest.json
Normal file
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"changelog": "REFACT - routing kategorii, stron i artykulow przez pp_routes; blok routingu w index.php przed checkUrlParams(); Redis cache tras; nowe kolumny category_id/page_id/article_id/type w pp_routes",
|
||||
"version": "0.329",
|
||||
"files": {
|
||||
"added": [
|
||||
|
||||
],
|
||||
"deleted": [
|
||||
"libraries/htaccess.conf"
|
||||
],
|
||||
"modified": [
|
||||
"autoload/Domain/Article/ArticleRepository.php",
|
||||
"autoload/Domain/Category/CategoryRepository.php",
|
||||
"autoload/Domain/Pages/PagesRepository.php",
|
||||
"autoload/Shared/Helpers/Helpers.php",
|
||||
"index.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:73b225b9d68d985ce44d79320350b6ef6fe602c30126a6bf322b8ab44eaab230",
|
||||
"sql": [
|
||||
"ALTER TABLE pp_routes\n ADD COLUMN category_id INT NULL AFTER product_id,\n ADD COLUMN page_id INT NULL AFTER category_id,\n ADD COLUMN article_id INT NULL AFTER page_id,\n ADD COLUMN type VARCHAR(20) NULL AFTER article_id"
|
||||
],
|
||||
"date": "2026-02-27",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
5
updates/0.30/ver_0.329_sql.txt
Normal file
5
updates/0.30/ver_0.329_sql.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
ALTER TABLE pp_routes
|
||||
ADD COLUMN category_id INT NULL AFTER product_id,
|
||||
ADD COLUMN page_id INT NULL AFTER category_id,
|
||||
ADD COLUMN article_id INT NULL AFTER page_id,
|
||||
ADD COLUMN type VARCHAR(20) NULL AFTER article_id
|
||||
BIN
updates/0.30/ver_0.330.zip
Normal file
BIN
updates/0.30/ver_0.330.zip
Normal file
Binary file not shown.
23
updates/0.30/ver_0.330_manifest.json
Normal file
23
updates/0.30/ver_0.330_manifest.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"changelog": "REFACT - eliminacja htaccess.conf; Helpers::htacces() generuje .htaccess w calosci z PHP; 32 statyczne trasy systemowe w pp_routes (type='system'); dynamiczne trasy jezykowe i producentow w pp_routes; invalidacja cache Redis pp_routes:all po htacces()",
|
||||
"version": "0.330",
|
||||
"files": {
|
||||
"added": [
|
||||
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"index.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:7e55f52c8d66a38231d3c19b65e70d1201af7a7fef0bfe69b1967fccae798bec",
|
||||
"sql": [
|
||||
|
||||
],
|
||||
"date": "2026-02-27",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
BIN
updates/0.30/ver_0.331.zip
Normal file
BIN
updates/0.30/ver_0.331.zip
Normal file
Binary file not shown.
23
updates/0.30/ver_0.331_manifest.json
Normal file
23
updates/0.30/ver_0.331_manifest.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"changelog": "FIX - getProductLayout: fallback categories_default zmieniony na status (produkty bez layoutu pobieraly szablon kategorii zamiast domyslnego)",
|
||||
"version": "0.331",
|
||||
"files": {
|
||||
"added": [
|
||||
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"autoload/Domain/Layouts/LayoutsRepository.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:c5246fe62ee19ccdc0424b2836cb18543ef20aa4449eda295a358c6022583ed5",
|
||||
"sql": [
|
||||
|
||||
],
|
||||
"date": "2026-03-01",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
BIN
updates/0.30/ver_0.332.zip
Normal file
BIN
updates/0.30/ver_0.332.zip
Normal file
Binary file not shown.
24
updates/0.30/ver_0.332_manifest.json
Normal file
24
updates/0.30/ver_0.332_manifest.json
Normal file
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"changelog": "API produktow: nowe pola new_to_date i additional_message",
|
||||
"version": "0.332",
|
||||
"files": {
|
||||
"added": [
|
||||
|
||||
],
|
||||
"deleted": [
|
||||
|
||||
],
|
||||
"modified": [
|
||||
"autoload/Domain/Product/ProductRepository.php",
|
||||
"autoload/api/Controllers/ProductsApiController.php"
|
||||
]
|
||||
},
|
||||
"checksum_zip": "sha256:5405c0524ef3c2bdfb4039738299e8aac9c65a7b37d9bd4d21cdf566ce6701e7",
|
||||
"sql": [
|
||||
|
||||
],
|
||||
"date": "2026-03-01",
|
||||
"directories_deleted": [
|
||||
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
||||
<?
|
||||
$current_ver = 328;
|
||||
$current_ver = 332;
|
||||
|
||||
for ($i = 1; $i <= $current_ver; $i++)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user