release 0.267: front layout/basket fixes and product redirect hardening

This commit is contained in:
2026-02-14 00:56:09 +01:00
parent 3dad04f927
commit 4efca23069
17 changed files with 251 additions and 607 deletions

View File

@@ -3,6 +3,38 @@ namespace admin\factory;
use shop\Product;
class ShopProduct
{
private static function seoLinkUsedByOtherProduct( int $product_id, string $lang_id, string $seo_link ): bool
{
global $mdb;
if ( !$seo_link )
return false;
return (bool) $mdb -> count( 'pp_shop_products_langs', [
'AND' => [
'lang_id' => $lang_id,
'seo_link' => $seo_link,
'product_id[!]' => $product_id,
],
] );
}
private static function removeConflictingRedirectSources( int $product_id, string $lang_id, string $from ): void
{
global $mdb;
if ( !$from )
return;
$mdb -> delete( 'pp_redirects', [
'AND' => [
'from' => $from,
'lang_id' => $lang_id,
'product_id[!]' => $product_id,
],
] );
}
// count_product
static public function count_product( $where = null )
{
@@ -1010,18 +1042,30 @@ class ShopProduct
if ( $new_seo_link !== $current_seo_link and $current_seo_link != '' )
{
if ( !$mdb -> count( 'pp_redirects', [ 'from' => $current_seo_link, 'to' => $new_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] ) )
if ( $mdb -> count( 'pp_redirects', [ 'from' => $new_seo_link, 'to' => $current_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] ) )
$mdb -> delete( 'pp_redirects', [ 'from' => $new_seo_link, 'to' => $current_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] );
$mdb -> delete( 'pp_redirects', [
'AND' => [
'product_id' => $product_id,
'lang_id' => $lg['id'],
'from' => $current_seo_link,
'to[!]' => $new_seo_link,
],
] );
if ( !self::seoLinkUsedByOtherProduct( (int) $product_id, (string) $lg['id'], (string) $current_seo_link ) )
{
if ( $mdb -> count( 'pp_redirects', [ 'from' => $new_seo_link, 'to' => $current_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] ) )
$mdb -> delete( 'pp_redirects', [ 'from' => $new_seo_link, 'to' => $current_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] );
else
self::removeConflictingRedirectSources( (int) $product_id, (string) $lg['id'], (string) $current_seo_link );
if ( !$mdb -> count( 'pp_redirects', [ 'from' => $current_seo_link, 'to' => $new_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] ) )
{
if ( \S::canAddRedirect( $current_seo_link, $new_seo_link ) )
if ( \S::canAddRedirect( $current_seo_link, $new_seo_link, $lg['id'] ) )
$mdb -> insert( 'pp_redirects', [ 'from' => $current_seo_link, 'to' => $new_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] );
else
$mdb -> delete( 'pp_redirects', [ 'product_id' => $product_id, 'lang_id' => $lg['id'] ] );
}
}
else
$mdb -> delete( 'pp_redirects', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $lg['id'], 'from' => $current_seo_link ] ] );
}
$mdb -> update( 'pp_shop_products_langs', [

View File

@@ -49,46 +49,58 @@ class S
return $parts;
}
static function canAddRedirect( $from, $to )
static function canAddRedirect( $from, $to, $lang_id = null )
{
global $mdb;
$redirects = $mdb -> select( 'pp_redirects', '*' );
if ( !$from or !$to or $from === $to )
return false;
$where = [];
if ( null !== $lang_id )
$where['lang_id'] = $lang_id;
$redirects = $mdb -> select( 'pp_redirects', [ 'from', 'to' ], $where );
$redirectMap = [];
foreach ( $redirects as $redirect )
{
$redirectMap[$redirect['from']] = $redirect['to'];
if ( !isset( $redirectMap[$redirect['from']] ) )
$redirectMap[$redirect['from']] = [];
if ( !in_array( $redirect['to'], $redirectMap[$redirect['from']], true ) )
$redirectMap[$redirect['from']][] = $redirect['to'];
}
// Dodaj nowe przekierowanie do mapy tymczasowo
$redirectMap[$from] = $to;
if ( !isset( $redirectMap[$from] ) )
$redirectMap[$from] = [];
if ( !in_array( $to, $redirectMap[$from], true ) )
$redirectMap[$from][] = $to;
// Funkcja do sprawdzania cyklu za pomocą DFS
$visited = [];
$stack = [];
function hasCycle($current, $target, &$redirectMap, &$visited)
$stack = [ $to ];
while ( !empty( $stack ) )
{
if ($current === $target) {
return true;
}
$current = array_pop( $stack );
if (isset($visited[$current])) {
return false;
}
if ( $current === $from )
return false;
if ( isset( $visited[$current] ) )
continue;
$visited[$current] = true;
if (isset($redirectMap[$current])) {
return hasCycle($redirectMap[$current], $target, $redirectMap, $visited);
if ( isset( $redirectMap[$current] ) )
{
foreach ( $redirectMap[$current] as $next )
if ( !isset( $visited[$next] ) )
$stack[] = $next;
}
return false;
}
// Sprawdź, czy istnieje ścieżka z $newTo do $newFrom
return !hasCycle($to, $from, $redirectMap, $visited);
return true;
}
static public function clear_redis_cache()
@@ -1113,3 +1125,4 @@ class S
return false;
}
}

View File

@@ -8,6 +8,29 @@ class Layouts
return $mdb -> get( 'pp_layouts', 'id', [ 'categories_default' => 1 ] );
}
static public function default_layout()
{
global $mdb;
$cacheHandler = new \CacheHandler();
$cacheKey = "\front\factory\Layouts::default_layout";
$objectData = $cacheHandler -> get( $cacheKey );
if ( $objectData )
{
$cachedLayout = @unserialize( $objectData );
if ( is_array( $cachedLayout ) and !empty( $cachedLayout ) )
return $cachedLayout;
$cacheHandler -> delete( $cacheKey );
}
$layout = $mdb -> get( 'pp_layouts', '*', [ 'status' => 1 ] );
$cacheHandler -> set( $cacheKey, $layout );
return $layout;
}
static public function product_layout( $product_id )
{
global $mdb;
@@ -16,18 +39,47 @@ class Layouts
$cacheKey = "\front\factory\Layouts::product_layout:$product_id";
$objectData = $cacheHandler -> get( $cacheKey );
if ( !$objectData )
if ( $objectData )
{
$layout = $mdb -> get( 'pp_layouts', [ '[><]pp_shop_products' => [ 'id' => 'layout_id' ] ], '*', [ 'pp_shop_products.id' => (int)$product_id ] );
$cachedLayout = @unserialize( $objectData );
if ( is_array( $cachedLayout ) and !empty( $cachedLayout ) )
return $cachedLayout;
$cacheHandler -> set( $cacheKey, $layout );
$cacheHandler -> delete( $cacheKey );
}
$layoutRows = $mdb -> query(
"SELECT pp_layouts.*
FROM pp_layouts
JOIN pp_shop_products ON pp_layouts.id = pp_shop_products.layout_id
WHERE pp_shop_products.id = " . (int)$product_id . "
ORDER BY pp_layouts.id DESC"
) -> fetchAll( \PDO::FETCH_ASSOC );
if ( is_array( $layoutRows ) and isset( $layoutRows[0] ) )
$layout = $layoutRows[0];
else
{
return unserialize( $objectData );
$layoutRows = $mdb -> query(
"SELECT pp_layouts.*
FROM pp_layouts
JOIN pp_layouts_categories ON pp_layouts.id = pp_layouts_categories.layout_id
JOIN pp_shop_products_categories ON pp_shop_products_categories.category_id = pp_layouts_categories.category_id
WHERE pp_shop_products_categories.product_id = " . (int)$product_id . "
ORDER BY pp_shop_products_categories.o ASC, pp_layouts.id DESC"
) -> fetchAll( \PDO::FETCH_ASSOC );
if ( is_array( $layoutRows ) and isset( $layoutRows[0] ) )
$layout = $layoutRows[0];
else
$layout = $mdb -> get( 'pp_layouts', '*', [ 'categories_default' => 1 ] );
}
if ( !$layout )
$layout = $mdb -> get( 'pp_layouts', '*', [ 'status' => 1 ] );
$cacheHandler -> set( $cacheKey, $layout );
return $layout;
}
@@ -62,21 +114,34 @@ class Layouts
$cacheKey = "\front\factory\Layouts::category_layout:$category_id";
$objectData = $cacheHandler -> get( $cacheKey );
if ( !$objectData )
if ( $objectData )
{
$layout = $mdb -> query( "SELECT pp_layouts.* FROM pp_layouts JOIN pp_layouts_categories ON pp_layouts.id = pp_layouts_categories.layout_id WHERE pp_layouts_categories.category_id = " . (int)$category_id ) -> fetchAll( \PDO::FETCH_ASSOC );
if ( !$layout )
$layout = $mdb -> get( 'pp_layouts', '*', [ 'categories_default' => 1 ] );
$cachedLayout = @unserialize( $objectData );
if ( is_array( $cachedLayout ) and !empty( $cachedLayout ) )
return $cachedLayout;
$cacheHandler -> set( $cacheKey, $layout[0] );
$cacheHandler -> delete( $cacheKey );
}
$layoutRows = $mdb -> query(
"SELECT pp_layouts.*
FROM pp_layouts
JOIN pp_layouts_categories ON pp_layouts.id = pp_layouts_categories.layout_id
WHERE pp_layouts_categories.category_id = " . (int)$category_id . "
ORDER BY pp_layouts.id DESC"
) -> fetchAll( \PDO::FETCH_ASSOC );
if ( is_array( $layoutRows ) and isset( $layoutRows[0] ) )
$layout = $layoutRows[0];
else
{
return unserialize( $objectData );
}
$layout = $mdb -> get( 'pp_layouts', '*', [ 'categories_default' => 1 ] );
return $layout[0];
if ( !$layout )
$layout = $mdb -> get( 'pp_layouts', '*', [ 'status' => 1 ] );
$cacheHandler -> set( $cacheKey, $layout );
return $layout;
}
static public function active_layout( $page_id )

View File

@@ -34,6 +34,9 @@ class Site
if ( \S::get( 'category' ) )
$layout = \front\factory\Layouts::category_layout( \S::get( 'category' ) );
if ( !$layout and \S::get( 'module' ) )
$layout = \front\factory\Layouts::default_layout();
if ( !$layout )
$layout = \front\factory\Layouts::active_layout( $page['id'] );

View File

@@ -23,7 +23,27 @@ class Basket implements \ArrayAccess
foreach ( $basket as $key => $val )
{
$quantity_options = \shop\Product::get_product_permutation_quantity_options( $val['parent_id'] ? $val['parent_id'] : $val['product-id'], $val['attributes'][0] );
$permutation = null;
if ( isset( $val['parent_id'] ) and (int)$val['parent_id'] and isset( $val['product-id'] ) )
$permutation = \shop\Product::get_product_permutation_hash( (int)$val['product-id'] );
if ( !$permutation and isset( $val['attributes'] ) and is_array( $val['attributes'] ) and count( $val['attributes'] ) )
$permutation = implode( '|', $val['attributes'] );
$quantity_options = \shop\Product::get_product_permutation_quantity_options(
$val['parent_id'] ? $val['parent_id'] : $val['product-id'],
$permutation
);
if (
(int)$basket[ $key ][ 'quantity' ] < 1
and ( (int)$quantity_options['quantity'] > 0 or (int)$quantity_options['stock_0_buy'] === 1 )
)
{
$basket[ $key ][ 'quantity' ] = 1;
$result = true;
}
if ( ( $val[ 'quantity' ] > $quantity_options['quantity'] ) and !$quantity_options['stock_0_buy'] )
{

View File

@@ -537,7 +537,7 @@ class Product implements \ArrayAccess
global $mdb, $settings;
$cacheHandler = new \CacheHandler();
$cacheKey = "\shop\Product::get_product_permutation_quantity_options:$product_id:$permutation";
$cacheKey = "\shop\Product::get_product_permutation_quantity_options:v2:$product_id:$permutation";
$objectData = $cacheHandler -> get( $cacheKey );
@@ -546,10 +546,15 @@ class Product implements \ArrayAccess
if ( $mdb -> count( 'pp_shop_products', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation ] ] ) )
{
$result['quantity'] = $mdb -> get( 'pp_shop_products', 'quantity', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation ] ] );
$result['stock_0_buy'] = $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation ] ] );
if ( $result['quantity'] == null )
{
$result['quantity'] = $mdb -> get( 'pp_shop_products', 'quantity', [ 'id' => $product_id ] );
$result['stock_0_buy'] = $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'id' => $product_id] );
if ( $result['stock_0_buy'] == null )
$result['stock_0_buy'] = $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'id' => $product_id] );
$result['messages'] = $mdb -> get( 'pp_shop_products_langs', [ 'warehouse_message_zero', 'warehouse_message_nonzero' ], [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => 'pl' ] ] );
if ( !$result['messages']['warehouse_message_zero'] )
@@ -560,7 +565,6 @@ class Product implements \ArrayAccess
}
else
{
$result['stock_0_buy'] = $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation ] ] );
$result['messages'] = $mdb -> get( 'pp_shop_products_langs', [ 'warehouse_message_zero', 'warehouse_message_nonzero' ], [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => 'pl' ] ] );
if ( !$result['messages']['warehouse_message_zero'] )
@@ -758,6 +762,13 @@ class Product implements \ArrayAccess
return $mdb -> get( 'pp_shop_products', 'id', [ 'AND' => [ 'parent_id' => $parent_id, 'permutation_hash' => implode( '|', $attributes ) ] ] );
}
// pobranie permutation_hash dla kombinacji produktu
static public function get_product_permutation_hash( int $product_id )
{
global $mdb;
return $mdb -> get( 'pp_shop_products', 'permutation_hash', [ 'id' => $product_id ] );
}
// pobranie listy atrybutów z wybranymi wartościami
static public function get_product_attributes( $products )
{