Apply staged updates

This commit is contained in:
2026-03-01 00:42:10 +01:00
parent 4cf7039759
commit 412e235512
16 changed files with 574 additions and 539 deletions

BIN
autoload/.DS_Store vendored

Binary file not shown.

View File

@@ -360,6 +360,9 @@ class ArticleRepository
public function archive(int $articleId): bool
{
$result = $this->db->update('pp_articles', ['status' => -1], ['id' => $articleId]);
if ($result) {
$this->db->delete('pp_routes', ['article_id' => $articleId]);
}
return (bool)$result;
}
@@ -381,6 +384,7 @@ class ArticleRepository
$this->db->delete('pp_articles_langs', ['article_id' => $articleId]);
$this->db->delete('pp_articles_images', ['article_id' => $articleId]);
$this->db->delete('pp_articles_files', ['article_id' => $articleId]);
$this->db->delete('pp_routes', ['article_id' => $articleId]);
$this->db->delete('pp_articles', ['id' => $articleId]);
\Shared\Helpers\Helpers::delete_dir('../upload/article_images/article_' . $articleId . '/');

View File

@@ -174,6 +174,7 @@ class CategoryRepository
$deleted = (bool)$this->db->delete('pp_shop_categories', ['id' => $id]);
if ($deleted) {
$this->db->delete('pp_routes', ['category_id' => $id]);
$this->refreshCategoryArtifacts();
}

View File

@@ -134,7 +134,11 @@ class PagesRepository
return false;
}
return (bool)$this->db->delete('pp_pages', ['id' => $pageId]);
$deleted = (bool)$this->db->delete('pp_pages', ['id' => $pageId]);
if ($deleted) {
$this->db->delete('pp_routes', ['page_id' => $pageId]);
}
return $deleted;
}
/**

View File

@@ -172,13 +172,19 @@ class UpdateRepository
foreach ( $manifest['sql'] as $query ) {
$query = trim( $query );
if ( $query !== '' ) {
if ( $query === '' || strpos( $query, '--' ) === 0 ) {
continue;
}
try {
if ( $this->db->query( $query ) ) {
$success++;
} else {
$errors++;
$log[] = '[WARNING] Błąd SQL: ' . $query;
}
} catch ( \Exception $e ) {
$errors++;
$log[] = '[WARNING] Wyjątek SQL: ' . $e->getMessage() . ' | Query: ' . substr( $query, 0, 200 );
}
}

View File

@@ -425,42 +425,206 @@ class Helpers
$site_map .= '<priority>1</priority>' . PHP_EOL;
$site_map .= '</url>' . PHP_EOL;
$htaccess_data = file_get_contents( $dir . 'libraries/htaccess.conf' );
$htaccess_data = str_replace( '{PAGE}', $url, $htaccess_data );
//
// SYSTEM ROUTES — delete all and reinsert
//
$mdb->delete( 'pp_routes', [ 'type' => 'system' ] );
$results = $mdb -> select( 'pp_langs', [ 'id' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
// 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 )
{
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $row['id'] . '/$ index.php?a=change_language&id=' . $row['id'] . ' [L]';
$mdb->insert( 'pp_routes', [
'type' => 'system',
'lang_id' => 0,
'pattern' => '^' . $row['id'] . '$',
'destination' => 'index.php?a=change_language&id=' . $row['id'],
] );
}
//
// INNE
//
$htaccess_data .= PHP_EOL;
$htaccess_data .= 'RewriteRule ^newsletter/signin/$ index.php?module=newsletter&action=signin [L]' . PHP_EOL;
$htaccess_data .= 'RewriteRule ^newsletter/confirm/hash=(.*)$ index.php?module=newsletter&action=confirm&hash=$1 [L]' . PHP_EOL;
$htaccess_data .= 'RewriteRule ^newsletter/unsubscribe/hash=(.*)$ index.php?module=newsletter&action=unsubscribe&hash=$1 [L]' . PHP_EOL;
//
// PRODUCENCI
//
// Dynamic system routes — producenci
$categoryDefaultLayoutId = ( new \Domain\Layouts\LayoutsRepository( $mdb ) )->categoryDefaultLayoutId();
$htaccess_data .= 'RewriteRule ^producenci$ index.php?module=shop_producer&action=list&layout_id=' . $categoryDefaultLayoutId . '&%{QUERY_STRING} [L]' . PHP_EOL;
$rows = $mdb -> select( 'pp_shop_producer', '*', [ 'status' => 1 ] );
$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 )
{
$htaccess_data .= 'RewriteRule ^producent/' . self::seo( $row['name'] ) . '$ index.php?module=shop_producer&action=products&producer_id=' . $row['id'] . '&layout_id=' . $categoryDefaultLayoutId . '&%{QUERY_STRING} [L]' . PHP_EOL;
$htaccess_data .= 'RewriteRule ^producent/' . self::seo( $row['name'] ) . '/([0-9]+)$ index.php?module=shop_producer&action=products&producer_id=' . $row['id'] . '&layout_id=' . $categoryDefaultLayoutId . '&bs=$1&%{QUERY_STRING} [L]' . PHP_EOL;
$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',
] );
}
$results = $mdb -> select( 'pp_langs', [ 'id', 'start' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
//
// 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 block */
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' ] ] );
$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'] )
@@ -475,35 +639,42 @@ class Helpers
$site_map .= '<priority>1</priority>' . PHP_EOL;
$site_map .= '</url>' . PHP_EOL;
if ( $row2['seo_link'] )
{
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . self::seo( $row2['seo_link'] ) . '$ index.php?category=' . $row2['category_id'] . '&lang=' . $row['id'] . '&%{QUERY_STRING} [L]';
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . self::seo( $row2['seo_link'] ) . '/([0-9]+)$ index.php?category=' . $row2['category_id'] . '&lang=' . $row['id'] . '&bs=$1&%{QUERY_STRING} [L]';
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . self::seo( $row2['seo_link'] ) . '/1$ ' . $language_link . self::seo( $row2['seo_link'] ) . ' [R=301,L]';
}
else
{
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . 'k-' . $row2['category_id'] . '-' . self::seo( $row2['title'] ) . '$ index.php?category=' . $row2['category_id'] . '&lang=' . $row['id'] . '&%{QUERY_STRING} [L]';
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . 'k-' . $row2['category_id'] . '-' . self::seo( $row2['title'] ) . '/([0-9]+)$ index.php?category=' . $row2['category_id'] . '&lang=' . $row['id'] . '&bs=$1&%{QUERY_STRING} [L]';
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . 'k-' . $row2['category_id'] . '-' . self::seo( $row2['title'] ) . '/1$ ' . $language_link . 'k-' . $row2['category_id'] . '-' . self::seo( $row2['title'] ) . ' [R=301,L]';
}
$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',
] );
}
}
}
$results = $mdb -> select( 'pp_langs', [ 'id', 'start' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
//
// 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' ] ] );
$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'] ] ] );
$mdb->delete( 'pp_routes', [ 'AND' => [ 'product_id' => $row2['product_id'], 'lang_id' => $row['id'] ] ] );
if ( $row2['name'] )
{
@@ -519,27 +690,13 @@ class Helpers
if ( $row2['seo_link'] )
{
$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' => $pattern, 'destination' => $destination ] );
$pattern = '^' . $language_link . self::seo( $row2['seo_link'] ) . '/([0-9-]+)$';
$destination = 'index.php?product=' . $row2['product_id'] . '&permutation_hash=$1';
$mdb -> insert( 'pp_routes', [ 'product_id' => $row2['product_id'], 'lang_id' => $row['id'], 'pattern' => $pattern, 'destination' => $destination ] );
$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
{
$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' => $pattern, 'destination' => $destination ] );
$pattern = '^' . $language_link . 'p-' . $row2['product_id'] . '-' . self::seo( $row2['name'] ) . '/([0-9-]+)$';
$destination = 'index.php?product=' . $row2['product_id'] . '&permutation_hash=$1';
$mdb -> insert( 'pp_routes', [ 'product_id' => $row2['product_id'], 'lang_id' => $row['id'], 'pattern' => $pattern, 'destination' => $destination ] );
$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' ] );
}
}
}
@@ -547,13 +704,16 @@ class Helpers
}
}
$results = $mdb -> select( 'pp_langs', [ 'id', 'start' ], [ 'status' => 1, 'ORDER' => [ 'o' => 'ASC' ] ] );
//
// 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' ] ] );
$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 )
{
@@ -590,44 +750,39 @@ class Helpers
{
$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]';
}
if ( $row2['seo_link'] )
{
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . self::seo( $row2['seo_link'] ) . '$ index.php?a=page&id=' . $row2['page_id'] . '&lang=' . $row['id'] . '&%{QUERY_STRING} [L]';
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . self::seo( $row2['seo_link'] ) . '/([0-9]+)$ index.php?a=page&id=' . $row2['page_id'] . '&lang=' . $row['id'] . '&bs=$1&%{QUERY_STRING} [L]';
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . self::seo( $row2['seo_link'] ) . '/1$ ' . $language_link . self::seo( $row2['seo_link'] ) . ' [R=301,L]';
}
else
{
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . 's-' . $row2['page_id'] . '-' . self::seo( $row2['title'] ) . '$ index.php?a=page&id=' . $row2['page_id'] . '&lang=' . $row['id'] . '&%{QUERY_STRING} [L]';
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . 's-' . $row2['page_id'] . '-' . self::seo( $row2['title'] ) . '/([0-9]+)$ index.php?a=page&id=' . $row2['page_id'] . '&lang=' . $row['id'] . '&bs=$1&%{QUERY_STRING} [L]';
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . 's-' . $row2['page_id'] . '-' . self::seo( $row2['title'] ) . '/1$ ' . $language_link . 's-' . $row2['page_id'] . '-' . self::seo( $row2['title'] ) . ' [R=301,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 ] ] );
$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']
]
] );
$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'];
}
@@ -650,81 +805,52 @@ class Helpers
$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'] )
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . self::seo( $row2['seo_link'] ) . '$ index.php?article=' . $row2['article_id'] . '&lang=' . $row['id'] . '&%{QUERY_STRING} [L]';
{
$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 )
$htaccess_data .= PHP_EOL . 'RewriteRule ^' . $language_link . 'a-' . $row2['article_id'] . '-' . self::seo( $row2['title'] ) . '$ index.php?article=' . $row2['article_id'] . '&lang=' . $row['id'] . '&%{QUERY_STRING} [L]';
{
$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'],
] );
}
}
}
$results = $mdb -> get( 'pp_settings', 'value', [ 'param' => 'htaccess' ] );
// 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' ] );
$results = $mdb->get( 'pp_settings', 'value', [ 'param' => 'robots' ] );
if ( $results )
$robots .= PHP_EOL . $results;
$robots .= PHP_EOL . $results;
$site_map .= '</urlset>';
/* cache */
if ( $settings['htaccess_cache'] )
{
$htaccess_data = str_replace( '{HTACCESS_CACHE}',
'<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>'
, $htaccess_data );
}
else
{
$htaccess_data = str_replace( '{HTACCESS_CACHE}',
'<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>',
$htaccess_data );
}
$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.
// Automatycznie komentujemy niedozwolone dyrektywy, aby generowany plik byl kompatybilny.
$htaccess_data = preg_replace( '/^(\\s*)(AddHandler|SetHandler|ForceType)\\b/im', '$1# $2', $htaccess_data );
$fp = fopen( $dir . '.htaccess', 'w' );

View File

@@ -92,6 +92,7 @@ class ProductArchiveController
. $skuEanHtml;
$rows[] = [
'_checkbox' => '<input type="checkbox" class="js-bulk-check" value="' . $id . '" aria-label="Zaznacz produkt">',
'lp' => $lp++ . '.',
'product' => $productCell,
'price_brutto' => $priceBrutto !== '' ? $priceBrutto : '-',
@@ -123,6 +124,7 @@ class ProductArchiveController
$viewModel = new \admin\ViewModels\Common\PaginatedTableViewModel(
[
['key' => '_checkbox', 'label' => '', 'class' => 'text-center table-col-bulk-check', 'sortable' => false, 'raw' => true],
['key' => 'lp', 'label' => 'Lp.', 'class' => 'text-center', 'sortable' => false],
['key' => 'product', 'sort_key' => 'name', 'label' => 'Nazwa', 'sortable' => true, 'raw' => true],
['key' => 'price_brutto', 'sort_key' => 'price_brutto', 'label' => 'Cena', 'class' => 'text-center', 'sortable' => true],
@@ -190,4 +192,40 @@ class ProductArchiveController
header( 'Location: /admin/product_archive/list/' );
exit;
}
public function bulk_delete_permanent(): void
{
header( 'Content-Type: application/json; charset=utf-8' );
$rawIds = isset( $_POST['ids'] ) && is_array( $_POST['ids'] ) ? $_POST['ids'] : [];
$ids = [];
foreach ( $rawIds as $raw ) {
$id = (int) $raw;
if ( $id > 0 ) {
$ids[] = $id;
}
}
if ( empty( $ids ) ) {
echo json_encode( ['success' => false, 'message' => 'Nie wybrano żadnych produktów.'] );
exit;
}
$deleted = 0;
$errors = [];
foreach ( $ids as $id ) {
if ( $this->productRepository->delete( $id ) ) {
$deleted++;
} else {
$errors[] = $id;
}
}
echo json_encode( [
'success' => empty( $errors ),
'deleted' => $deleted,
'errors' => $errors,
] );
exit;
}
}

View File

@@ -104,6 +104,9 @@ class ApiRouter
$producerRepo = new \Domain\Producer\ProducerRepository($db);
return new Controllers\DictionariesApiController($statusRepo, $transportRepo, $paymentRepo, $attrRepo, $producerRepo);
},
'categories' => function () use ($db) {
return new Controllers\CategoriesApiController();
},
];
}

View File

@@ -0,0 +1,104 @@
<?php
namespace api\Controllers;
use api\ApiRouter;
class CategoriesApiController
{
public function list(): void
{
if (!ApiRouter::requireMethod('GET')) {
return;
}
$db = $GLOBALS['mdb'] ?? null;
if (!$db) {
ApiRouter::sendError('INTERNAL_ERROR', 'Database not available', 500);
return;
}
// Default shop language
$defaultLang = $db->get('pp_langs', 'id', ['start' => 1]);
if (!$defaultLang) {
$defaultLang = 'pl';
}
$defaultLang = (string)$defaultLang;
// All active categories, ordered by display order
$rows = $db->select(
'pp_shop_categories',
['id', 'parent_id'],
[
'status' => 1,
'ORDER' => ['o' => 'ASC'],
]
);
if (!is_array($rows) || empty($rows)) {
ApiRouter::sendSuccess(['categories' => []]);
return;
}
$categoryIds = array_values(array_filter(
array_map(fn($row) => (int)($row['id'] ?? 0), $rows),
fn($id) => $id > 0
));
// Bulk fetch titles for default language
$titlesByCategory = [];
$titleRows = $db->select('pp_shop_categories_langs', ['category_id', 'title'], [
'AND' => [
'category_id' => $categoryIds,
'lang_id' => $defaultLang,
'title[!]' => '',
],
]);
if (is_array($titleRows)) {
foreach ($titleRows as $tr) {
$tid = (int)($tr['category_id'] ?? 0);
if ($tid > 0 && !isset($titlesByCategory[$tid])) {
$titlesByCategory[$tid] = (string)($tr['title'] ?? '');
}
}
}
// Bulk fetch fallback titles for categories without a title in default language
$missingIds = array_values(array_filter($categoryIds, fn($id) => !isset($titlesByCategory[$id])));
if (!empty($missingIds)) {
$fallbackRows = $db->select('pp_shop_categories_langs', ['category_id', 'title'], [
'AND' => [
'category_id' => $missingIds,
'title[!]' => '',
],
]);
if (is_array($fallbackRows)) {
foreach ($fallbackRows as $fr) {
$fid = (int)($fr['category_id'] ?? 0);
if ($fid > 0 && !isset($titlesByCategory[$fid])) {
$titlesByCategory[$fid] = (string)($fr['title'] ?? '');
}
}
}
}
// Build flat category list
$categories = [];
foreach ($rows as $row) {
$categoryId = (int)($row['id'] ?? 0);
if ($categoryId <= 0) {
continue;
}
$parentId = $row['parent_id'] !== null ? (int)$row['parent_id'] : null;
$title = $titlesByCategory[$categoryId] ?? ('Kategoria #' . $categoryId);
$categories[] = [
'id' => $categoryId,
'parent_id' => $parentId,
'title' => $title,
];
}
ApiRouter::sendSuccess(['categories' => $categories]);
}
}

Binary file not shown.