Compare commits

..

11 Commits

Author SHA1 Message Date
708a941f13 ver. 0.307: check-update button + auto-generated changelog
- Add "Sprawdź aktualizacje" refresh button in admin sidebar (AJAX check without page reload)
- Add UpdateController::checkUpdate() action clearing session cache and querying update server
- Replace hand-edited changelog.php with auto-generating script (reads manifests + legacy JSON)
- Migrate all legacy changelog entries (0.300-0.001) to changelog-legacy.json

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 18:01:12 +01:00
0b1f289478 build: rebuild update packages v0.304, v0.305 with fixed .updateignore
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:16:26 +01:00
82a655a6af build: update package v0.306
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:13:01 +01:00
f16f5ce8f8 ver. 0.306: hide transport methods with no available payment methods
When all payment methods for a transport are filtered out by
min_order_amount/max_order_amount limits, the transport is now hidden
from the basket. Prevents showing delivery options with empty payment
method lists (e.g. "Kurier - płatność przy odbiorze" when COD exceeds
max amount).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:09:38 +01:00
2dc1360395 remove ver_0.304_sql.txt from updates/ — SQL lives only in migrations/
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 16:50:29 +01:00
5c1842181a fix: UTF-8 BOM in update SQL files causing MariaDB syntax error
PowerShell 5.1 Out-File -Encoding UTF8 adds BOM (EF BB BF) which
breaks SQL execution on production. Also fix manifest JSON serializing
full PS objects instead of plain strings.

- build-update.ps1: use UTF8Encoding($false) for all file writes
- build-update.ps1: force .ToString() on Get-Content results
- UpdateRepository.php: strip BOM and normalize line endings in executeSql
- Rebuild ver_0.304 package files (clean SQL + manifest)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 16:49:23 +01:00
62255541ab build: update packages v0.304, v0.305
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 16:39:20 +01:00
ec77160130 ver. 0.305: Fix permutation attribute sorting + free delivery progress bar
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 16:33:30 +01:00
562495f120 build: update package v0.304
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 15:28:19 +01:00
9de4afec9a ver. 0.304: Configurable payment method order amount limits
Replace hardcoded PayPo condition (id=6, 40-1000 PLN) with generic
min/max order amount columns on pp_shop_payment_methods. Admin form
fields added, frontend basket checkout filters dynamically. Cache
invalidation on save. 4 new tests (734 total, 2080 assertions).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 15:26:51 +01:00
3a3c2adb47 build: update package v0.303
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 15:05:19 +01:00
40 changed files with 4221 additions and 804 deletions

View File

@@ -18,9 +18,7 @@ test.ps1
memory/ memory/
# Infrastruktura aktualizacji (meta, nie runtime) # Infrastruktura aktualizacji (meta, nie runtime)
updates/changelog.php updates/
updates/versions.php
updates/install.php
.updateignore .updateignore
build-update.ps1 build-update.ps1
migrations/ migrations/
@@ -31,6 +29,13 @@ config.php
admin/.htaccess admin/.htaccess
libraries/version.ini libraries/version.ini
# Lokalne style
layout/style-css/style.css
layout/style-css/style.css.map
layout/style-scss/style.scss
layout/style-scss/_mixins.scss
layout/style-scss/_mixins.css
# Temp / cache / backups # Temp / cache / backups
temp/ temp/
backups/ backups/

View File

@@ -11,7 +11,10 @@ Gdy użytkownik napisze `KONIEC PRACY`, wykonaj kolejno:
- `docs/FORM_EDIT_SYSTEM.md` - `docs/FORM_EDIT_SYSTEM.md`
- `docs/CHANGELOG.md` - `docs/CHANGELOG.md`
- `docs/TESTING.md` - `docs/TESTING.md`
3. Przygotowanie aktualizacji zgodnie z plikiem docs/UPDATE_INSTRUCTIONS.md (ZIP, plik z usuwanymi plikami, plik SQL jeśli wymagany). 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. 4. Commit.
5. Push. 5. Push.

View File

@@ -36,7 +36,7 @@ composer test
PHPUnit 9.6 via `phpunit.phar`. Bootstrap: `tests/bootstrap.php`. Config: `phpunit.xml`. PHPUnit 9.6 via `phpunit.phar`. Bootstrap: `tests/bootstrap.php`. Config: `phpunit.xml`.
Current suite: **730 tests, 2066 assertions**. Current suite: **739 tests, 2089 assertions**.
### Creating Updates ### 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. 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.
@@ -208,7 +208,7 @@ $controller = new \admin\Controllers\ExampleController($repo);
When user says **"KONIEC PRACY"**, execute in order: When user says **"KONIEC PRACY"**, execute in order:
1. Run tests 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` 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. Prepare update package per `docs/UPDATE_INSTRUCTIONS.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 4. Commit
5. Push 5. Push

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1351,39 +1351,6 @@ li.sort-collapsed.sort-hover div {
} }
} }
input[type="checkbox"] {
position: relative;
width: 40px;
height: 20px;
-webkit-appearance: none;
background: $cGrayLight;
outline: none;
border-radius: 10px;
box-shadow: inset 0 0 5px rgba(0, 0, 0, .2);
}
input:checked[type="checkbox"] {
background: $cMenuText;
}
input[type="checkbox"]:before {
content: '';
position: absolute;
width: 20px;
height: 20px;
border-radius: 10px;
top: 0;
left: 0;
background: #fff;
transform: scale(1.1);
box-shadow: 0 2px 5px rgba(0, 0, 0, .2);
transition: .5s;
}
input:checked[type="checkbox"]:before {
left: 20px;
}
#images-uploader, #images-uploader,
#files-uploader { #files-uploader {
clear: both; clear: both;

View File

@@ -81,9 +81,6 @@
</div> </div>
</div> </div>
</div> </div>
<link rel="stylesheet" type="text/css" href="/libraries/grid/plugins/icheck/skins/minimal/minimal.css">
<link rel="stylesheet" type="text/css" href="/libraries/grid/plugins/icheck/skins/minimal/blue.css">
<script type="text/javascript" src="/libraries/grid/plugins/icheck/icheck.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
$( function() $( function()
{ {

View File

@@ -18,7 +18,8 @@
<link rel="stylesheet" type="text/css" href="/libraries/easy-tabs/css/easy-responsive-tabs.css"> <link rel="stylesheet" type="text/css" href="/libraries/easy-tabs/css/easy-responsive-tabs.css">
<link rel="stylesheet" type="text/css" href="/libraries/bootstrap-4.5.2-dist/css/bootstrap.css"> <link rel="stylesheet" type="text/css" href="/libraries/bootstrap-4.5.2-dist/css/bootstrap.css">
<link rel="stylesheet" type="text/css" href="/libraries/font-awesome-4.7.0/css/font-awesome.css"> <link rel="stylesheet" type="text/css" href="/libraries/font-awesome-4.7.0/css/font-awesome.css">
<link rel="stylesheet" type="text/css" href="/libraries/grid/plugins/icheck/skins/square/blue.css"> <link rel="stylesheet" type="text/css" href="/libraries/grid/plugins/icheck/skins/minimal/minimal.css">
<link rel="stylesheet" type="text/css" href="/libraries/grid/plugins/icheck/skins/minimal/blue.css">
<script type="text/javascript" src="/libraries/framework/vendor/jquery/jquery-1.11.1.min.js"></script> <script type="text/javascript" src="/libraries/framework/vendor/jquery/jquery-1.11.1.min.js"></script>
<script type="text/javascript" src="/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.min.js"></script> <script type="text/javascript" src="/libraries/framework/vendor/jquery/jquery_ui/jquery-ui.min.js"></script>
<script type="text/javascript" src="/libraries/framework/js/utility/utility.js"></script> <script type="text/javascript" src="/libraries/framework/js/utility/utility.js"></script>
@@ -43,10 +44,16 @@
<div class="menu"> <div class="menu">
<div class="logo sticky-top"> <div class="logo sticky-top">
shop<b>Pro</b> shop<b>Pro</b>
<span>ver. <?= \Shared\Helpers\Helpers::get_version();?></span><br> <span>ver. <?= \Shared\Helpers\Helpers::get_version();?>
<? if ( $settings[ 'update' ] ):?>
<i class="fa fa-refresh check-update-btn" id="check-update-btn" title="Sprawdź aktualizacje" style="cursor:pointer;margin-left:4px;font-size:11px;opacity:0.7;"></i>
<? endif;?>
</span><br>
<span id="update-badge-wrap">
<? if ( $settings[ 'update' ] and \Shared\Helpers\Helpers::get_new_version() > \Shared\Helpers\Helpers::get_version() ):?> <? if ( $settings[ 'update' ] and \Shared\Helpers\Helpers::get_new_version() > \Shared\Helpers\Helpers::get_version() ):?>
<a href="/admin/update/main_view/" class="label label-danger">aktualizacja</a> <a href="/admin/update/main_view/" class="label label-danger">aktualizacja</a>
<? endif;?> <? endif;?>
</span>
</div> </div>
<div class="menu-content"> <div class="menu-content">
<ul> <ul>
@@ -355,6 +362,32 @@
}); });
})(); })();
(function() {
$(document).off('click.checkUpdate', '#check-update-btn').on('click.checkUpdate', '#check-update-btn', function(e) {
e.preventDefault();
var $btn = $(this);
if ($btn.hasClass('fa-spin')) return;
$btn.addClass('fa-spin').css('opacity', 1);
$.ajax({
url: '/admin/update/checkUpdate/',
type: 'GET',
dataType: 'json',
success: function(data) {
$btn.removeClass('fa-spin').css('opacity', 0.7);
var $wrap = $('#update-badge-wrap');
if (data.has_update) {
$wrap.html('<a href="/admin/update/main_view/" class="label label-danger">aktualizacja</a>');
} else {
$wrap.html('');
}
},
error: function() {
$btn.removeClass('fa-spin').css('opacity', 0.7);
}
});
});
})();
$(document).ready(function () { $(document).ready(function () {
var user_agent = navigator.userAgent.toLowerCase(); var user_agent = navigator.userAgent.toLowerCase();
var click_event = user_agent.match(/(iphone|ipod|ipad)/) ? "touchend" : "click"; var click_event = user_agent.match(/(iphone|ipod|ipad)/) ? "touchend" : "click";

View File

@@ -94,8 +94,13 @@ class BasketCalculator
if ( isset( $val['parent_id'] ) and (int)$val['parent_id'] and isset( $val['product-id'] ) ) if ( isset( $val['parent_id'] ) and (int)$val['parent_id'] and isset( $val['product-id'] ) )
$permutation = $productRepo->getProductPermutationHash( (int)$val['product-id'] ); $permutation = $productRepo->getProductPermutationHash( (int)$val['product-id'] );
if ( !$permutation and isset( $val['attributes'] ) and is_array( $val['attributes'] ) and count( $val['attributes'] ) ) if ( !$permutation and isset( $val['attributes'] ) and is_array( $val['attributes'] ) and count( $val['attributes'] ) ) {
$permutation = implode( '|', $val['attributes'] ); $attrs = $val['attributes'];
usort( $attrs, function ( $a, $b ) {
return (int) explode( '-', $a )[0] - (int) explode( '-', $b )[0];
} );
$permutation = implode( '|', $attrs );
}
$quantity_options = $productRepo->getProductPermutationQuantityOptions( $quantity_options = $productRepo->getProductPermutationQuantityOptions(
$val['parent_id'] ? $val['parent_id'] : $val['product-id'], $val['parent_id'] ? $val['parent_id'] : $val['product-id'],

View File

@@ -120,10 +120,16 @@ class PaymentMethodRepository
'description' => trim((string)($data['description'] ?? '')), 'description' => trim((string)($data['description'] ?? '')),
'status' => $this->toSwitchValue($data['status'] ?? 0), 'status' => $this->toSwitchValue($data['status'] ?? 0),
'apilo_payment_type_id' => $this->normalizeApiloPaymentTypeId($data['apilo_payment_type_id'] ?? null), 'apilo_payment_type_id' => $this->normalizeApiloPaymentTypeId($data['apilo_payment_type_id'] ?? null),
'min_order_amount' => $this->normalizeDecimalOrNull($data['min_order_amount'] ?? null),
'max_order_amount' => $this->normalizeDecimalOrNull($data['max_order_amount'] ?? null),
]; ];
$this->db->update('pp_shop_payment_methods', $row, ['id' => $paymentMethodId]); $this->db->update('pp_shop_payment_methods', $row, ['id' => $paymentMethodId]);
$cacheHandler = new \Shared\Cache\CacheHandler();
$cacheHandler->deletePattern('payment_method*');
$cacheHandler->deletePattern('payment_methods*');
return $paymentMethodId; return $paymentMethodId;
} }
@@ -232,7 +238,9 @@ class PaymentMethodRepository
spm.name, spm.name,
spm.description, spm.description,
spm.status, spm.status,
spm.apilo_payment_type_id spm.apilo_payment_type_id,
spm.min_order_amount,
spm.max_order_amount
FROM pp_shop_payment_methods AS spm FROM pp_shop_payment_methods AS spm
INNER JOIN pp_shop_transport_payment_methods AS stpm INNER JOIN pp_shop_transport_payment_methods AS stpm
ON stpm.id_payment_method = spm.id ON stpm.id_payment_method = spm.id
@@ -325,6 +333,8 @@ class PaymentMethodRepository
$row['description'] = (string)($row['description'] ?? ''); $row['description'] = (string)($row['description'] ?? '');
$row['status'] = $this->toSwitchValue($row['status'] ?? 0); $row['status'] = $this->toSwitchValue($row['status'] ?? 0);
$row['apilo_payment_type_id'] = $this->normalizeApiloPaymentTypeId($row['apilo_payment_type_id'] ?? null); $row['apilo_payment_type_id'] = $this->normalizeApiloPaymentTypeId($row['apilo_payment_type_id'] ?? null);
$row['min_order_amount'] = $this->normalizeDecimalOrNull($row['min_order_amount'] ?? null);
$row['max_order_amount'] = $this->normalizeDecimalOrNull($row['max_order_amount'] ?? null);
return $row; return $row;
} }
@@ -350,6 +360,23 @@ class PaymentMethodRepository
return $text; return $text;
} }
/**
* @return float|null
*/
private function normalizeDecimalOrNull($value)
{
if ($value === null || $value === false) {
return null;
}
$text = trim((string)$value);
if ($text === '') {
return null;
}
return (float)$text;
}
private function toSwitchValue($value): int private function toSwitchValue($value): int
{ {
if (is_bool($value)) { if (is_bool($value)) {

View File

@@ -323,7 +323,9 @@ class TransportRepository
$transports[] = $tr; $transports[] = $tr;
} }
if ( \Shared\Helpers\Helpers::normalize_decimal( \Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon ) ) >= \Shared\Helpers\Helpers::normalize_decimal( $settings['free_delivery'] ) ) $products_summary = (float)\Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon );
if ( \Shared\Helpers\Helpers::normalize_decimal( $products_summary ) >= \Shared\Helpers\Helpers::normalize_decimal( $settings['free_delivery'] ) )
{ {
for ( $i = 0; $i < count( $transports ); $i++ ) { for ( $i = 0; $i < count( $transports ); $i++ ) {
if ( $transports[$i]['delivery_free'] == 1 ) { if ( $transports[$i]['delivery_free'] == 1 ) {
@@ -332,7 +334,39 @@ class TransportRepository
} }
} }
return $transports; // Ukryj transporty, dla których nie ma żadnej dostępnej formy płatności
$paymentMethodRepo = new \Domain\PaymentMethod\PaymentMethodRepository( $this->db );
$filtered = [];
foreach ( $transports as $tr )
{
$paymentMethods = $paymentMethodRepo->paymentMethodsByTransport( $tr['id'] );
$order_total = $products_summary + (float)$tr['cost'];
$has_available_pm = false;
foreach ( $paymentMethods as $pm )
{
$min = isset( $pm['min_order_amount'] ) ? (float)$pm['min_order_amount'] : null;
$max = isset( $pm['max_order_amount'] ) ? (float)$pm['max_order_amount'] : null;
$available = true;
if ( $min !== null && $min > 0 && $order_total < $min ) $available = false;
if ( $max !== null && $max > 0 && $order_total > $max ) $available = false;
if ( $available )
{
$has_available_pm = true;
break;
}
}
if ( $has_available_pm )
{
$filtered[] = $tr;
}
}
return $filtered;
} }
/** /**

View File

@@ -416,7 +416,12 @@ class UpdateRepository
return $log; return $log;
} }
$queries = explode( PHP_EOL, $response ); // Usunięcie UTF-8 BOM i normalizacja końców linii
$response = ltrim( $response, "\xEF\xBB\xBF" );
$response = str_replace( "\r\n", "\n", $response );
$response = str_replace( "\r", "\n", $response );
$queries = explode( "\n", $response );
$log[] = '[OK] Pobrano ' . count( $queries ) . ' zapytań SQL'; $log[] = '[OK] Pobrano ' . count( $queries ) . ' zapytań SQL';
$success = 0; $success = 0;
$errors = 0; $errors = 0;

View File

@@ -60,4 +60,9 @@ class Tpl
{ {
return $this->vars[$name]; return $this->vars[$name];
} }
public function __isset($name)
{
return isset($this->vars[$name]);
}
} }

View File

@@ -182,6 +182,8 @@ class ShopPaymentMethodController
'description' => (string)($paymentMethod['description'] ?? ''), 'description' => (string)($paymentMethod['description'] ?? ''),
'status' => (int)($paymentMethod['status'] ?? 0), 'status' => (int)($paymentMethod['status'] ?? 0),
'apilo_payment_type_id' => $paymentMethod['apilo_payment_type_id'] ?? '', 'apilo_payment_type_id' => $paymentMethod['apilo_payment_type_id'] ?? '',
'min_order_amount' => $paymentMethod['min_order_amount'] ?? '',
'max_order_amount' => $paymentMethod['max_order_amount'] ?? '',
]; ];
$fields = [ $fields = [
@@ -203,6 +205,16 @@ class ShopPaymentMethodController
'tab' => 'settings', 'tab' => 'settings',
'rows' => 5, 'rows' => 5,
]), ]),
FormField::number('min_order_amount', [
'label' => 'Min. kwota zamowienia (PLN)',
'tab' => 'settings',
'step' => 0.01,
]),
FormField::number('max_order_amount', [
'label' => 'Maks. kwota zamowienia (PLN)',
'tab' => 'settings',
'step' => 0.01,
]),
FormField::select('apilo_payment_type_id', [ FormField::select('apilo_payment_type_id', [
'label' => 'Typ platnosci Apilo', 'label' => 'Typ platnosci Apilo',
'tab' => 'settings', 'tab' => 'settings',

View File

@@ -49,4 +49,17 @@ class UpdateController
echo json_encode( $response ); echo json_encode( $response );
exit; exit;
} }
public function checkUpdate(): void
{
\Shared\Helpers\Helpers::set_session( 'new-version', null );
$newVer = \Shared\Helpers\Helpers::get_new_version();
$curVer = \Shared\Helpers\Helpers::get_version();
echo json_encode( [
'has_update' => $newVer > $curVer,
'new_ver' => $newVer,
] );
exit;
}
} }

View File

@@ -132,6 +132,11 @@ class ShopBasketController
$attributes[] = $val; $attributes[] = $val;
} }
// Sort by attribute ID to match permutation_hash order (generated with ksort)
usort( $attributes, function ( $a, $b ) {
return (int) explode( '-', $a )[0] - (int) explode( '-', $b )[0];
} );
foreach( $values as $key => $val ) foreach( $values as $key => $val )
{ {
if ( strpos( $key, 'custom_field' ) !== false ) if ( strpos( $key, 'custom_field' ) !== false )
@@ -372,7 +377,9 @@ class ShopBasketController
'transport_id' => \Shared\Helpers\Helpers::get_session( 'basket-transport-method-id' ), 'transport_id' => \Shared\Helpers\Helpers::get_session( 'basket-transport-method-id' ),
'transport_methods' => \Shared\Tpl\Tpl::view( 'shop-basket/basket-transport-methods', [ 'transport_methods' => \Shared\Tpl\Tpl::view( 'shop-basket/basket-transport-methods', [
'transports_methods' => ( new \Domain\Transport\TransportRepository( $GLOBALS['mdb'] ) )->transportMethodsFront( $basket, $coupon ), 'transports_methods' => ( new \Domain\Transport\TransportRepository( $GLOBALS['mdb'] ) )->transportMethodsFront( $basket, $coupon ),
'transport_id' => $basket_transport_method_id 'transport_id' => $basket_transport_method_id,
'free_delivery' => (float)($settings['free_delivery'] ?? 0),
'basket_summary' => (float)\Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon )
] ), ] ),
'payment_method_id' => $payment_method_id, 'payment_method_id' => $payment_method_id,
'basket_details' => \Shared\Tpl\Tpl::view( 'shop-basket/basket-details', [ 'basket_details' => \Shared\Tpl\Tpl::view( 'shop-basket/basket-details', [
@@ -387,6 +394,8 @@ class ShopBasketController
private function jsonBasketResponse( $basket, $coupon, $lang_id, $basket_transport_method_id ) private function jsonBasketResponse( $basket, $coupon, $lang_id, $basket_transport_method_id )
{ {
global $settings;
echo json_encode( [ echo json_encode( [
'basket' => \Shared\Tpl\Tpl::view( 'shop-basket/basket-details', [ 'basket' => \Shared\Tpl\Tpl::view( 'shop-basket/basket-details', [
'basket' => $basket, 'basket' => $basket,
@@ -398,7 +407,9 @@ class ShopBasketController
'products_count' => count( $basket ), 'products_count' => count( $basket ),
'transport_methods' => \Shared\Tpl\Tpl::view( 'shop-basket/basket-transport-methods', [ 'transport_methods' => \Shared\Tpl\Tpl::view( 'shop-basket/basket-transport-methods', [
'transports_methods' => ( new \Domain\Transport\TransportRepository( $GLOBALS['mdb'] ) )->transportMethodsFront( $basket, $coupon ), 'transports_methods' => ( new \Domain\Transport\TransportRepository( $GLOBALS['mdb'] ) )->transportMethodsFront( $basket, $coupon ),
'transport_id' => $basket_transport_method_id 'transport_id' => $basket_transport_method_id,
'free_delivery' => (float)($settings['free_delivery'] ?? 0),
'basket_summary' => (float)\Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon )
] ) ] )
] ); ] );
exit; exit;

View File

@@ -80,16 +80,17 @@ class ShopProductController
{ {
global $lang_id; global $lang_id;
$combination = '';
$selected_values = \Shared\Helpers\Helpers::get( 'selected_values' ); $selected_values = \Shared\Helpers\Helpers::get( 'selected_values' );
foreach ( $selected_values as $value ) // Sort by attribute ID to match permutation_hash order (generated with ksort)
{ if ( is_array( $selected_values ) ) {
$combination .= $value; usort( $selected_values, function ( $a, $b ) {
if ( $value != end( $selected_values ) ) return (int) explode( '-', $a )[0] - (int) explode( '-', $b )[0];
$combination .= '|'; } );
} }
$combination = is_array( $selected_values ) ? implode( '|', $selected_values ) : '';
$product_id = \Shared\Helpers\Helpers::get( 'product_id' ); $product_id = \Shared\Helpers\Helpers::get( 'product_id' );
$productRepo = new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ); $productRepo = new \Domain\Product\ProductRepository( $GLOBALS['mdb'] );
$product = $productRepo->findCached( $product_id, $lang_id ); $product = $productRepo->findCached( $product_id, $lang_id );
@@ -102,6 +103,10 @@ class ShopProductController
private static function getPermutation( $attributes ) private static function getPermutation( $attributes )
{ {
if ( !is_array( $attributes ) || !count( $attributes ) ) return null; if ( !is_array( $attributes ) || !count( $attributes ) ) return null;
// Sort by attribute ID to match permutation_hash order (generated with ksort)
usort( $attributes, function ( $a, $b ) {
return (int) explode( '-', $a )[0] - (int) explode( '-', $b )[0];
} );
return implode( '|', $attributes ); return implode( '|', $attributes );
} }

View File

@@ -32,6 +32,7 @@ param(
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
$Utf8NoBom = New-Object System.Text.UTF8Encoding $false
# --- Helpers --- # --- Helpers ---
@@ -214,7 +215,7 @@ $sqlQueries = @()
$migrationFile = "migrations/$versionNumber.sql" $migrationFile = "migrations/$versionNumber.sql"
if (Test-Path $migrationFile) { if (Test-Path $migrationFile) {
$sqlQueries = Get-Content $migrationFile | Where-Object { $_.Trim() -ne '' } $sqlQueries = @(Get-Content $migrationFile | Where-Object { $_.Trim() -ne '' } | ForEach-Object { $_.ToString() })
Write-Step "Znaleziono migracje SQL: $migrationFile ($($sqlQueries.Count) zapytan)" Write-Step "Znaleziono migracje SQL: $migrationFile ($($sqlQueries.Count) zapytan)"
} else { } else {
Write-Step "Brak migracji SQL ($migrationFile nie istnieje)" Write-Step "Brak migracji SQL ($migrationFile nie istnieje)"
@@ -322,7 +323,7 @@ $manifest = @{
$manifestJson = $manifest | ConvertTo-Json -Depth 4 $manifestJson = $manifest | ConvertTo-Json -Depth 4
$manifestPath = "$updatesDir/ver_${versionNumber}_manifest.json" $manifestPath = "$updatesDir/ver_${versionNumber}_manifest.json"
$manifestJson | Out-File $manifestPath -Encoding UTF8 [System.IO.File]::WriteAllText($manifestPath, $manifestJson, $Utf8NoBom)
Write-Ok "Utworzono manifest: $manifestPath" Write-Ok "Utworzono manifest: $manifestPath"
@@ -330,7 +331,7 @@ Write-Ok "Utworzono manifest: $manifestPath"
if ($sqlQueries.Count -gt 0) { if ($sqlQueries.Count -gt 0) {
$sqlPath = "$updatesDir/ver_${versionNumber}_sql.txt" $sqlPath = "$updatesDir/ver_${versionNumber}_sql.txt"
($sqlQueries -join "`n") | Out-File $sqlPath -Encoding UTF8 -NoNewline [System.IO.File]::WriteAllText($sqlPath, ($sqlQueries -join "`n"), $Utf8NoBom)
Write-Ok "Utworzono legacy SQL: $sqlPath" Write-Ok "Utworzono legacy SQL: $sqlPath"
} }
@@ -340,7 +341,7 @@ if ($deletedFilesOnly.Count -gt 0 -or $deletedDirs.Count -gt 0) {
foreach ($d in $deletedDirs) { $filesContent += "D: ../$d" } foreach ($d in $deletedDirs) { $filesContent += "D: ../$d" }
$filesPath = "$updatesDir/ver_${versionNumber}_files.txt" $filesPath = "$updatesDir/ver_${versionNumber}_files.txt"
($filesContent -join "`n") | Out-File $filesPath -Encoding UTF8 -NoNewline [System.IO.File]::WriteAllText($filesPath, ($filesContent -join "`n"), $Utf8NoBom)
Write-Ok "Utworzono legacy files: $filesPath" Write-Ok "Utworzono legacy files: $filesPath"
} }
@@ -350,7 +351,7 @@ $versionsFile = "updates/versions.php"
if (Test-Path $versionsFile) { if (Test-Path $versionsFile) {
$content = Get-Content $versionsFile -Raw $content = Get-Content $versionsFile -Raw
$content = $content -replace '\$current_ver\s*=\s*\d+;', "`$current_ver = $versionInt;" $content = $content -replace '\$current_ver\s*=\s*\d+;', "`$current_ver = $versionInt;"
$content | Out-File $versionsFile -Encoding UTF8 -NoNewline [System.IO.File]::WriteAllText($versionsFile, $content, $Utf8NoBom)
Write-Ok "Zaktualizowano versions.php: `$current_ver = $versionInt" Write-Ok "Zaktualizowano versions.php: `$current_ver = $versionInt"
} }
@@ -363,7 +364,7 @@ if (Test-Path $changelogFile) {
$changelogContent = Get-Content $changelogFile -Raw $changelogContent = Get-Content $changelogFile -Raw
$changelogContent = $newEntry + $changelogContent $changelogContent = $newEntry + $changelogContent
$changelogContent | Out-File $changelogFile -Encoding UTF8 -NoNewline [System.IO.File]::WriteAllText($changelogFile, $changelogContent, $Utf8NoBom)
Write-Ok "Zaktualizowano changelog.php" Write-Ok "Zaktualizowano changelog.php"
} }

View File

@@ -4,6 +4,45 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
--- ---
## ver. 0.307 (2026-02-22) - Przycisk sprawdzania aktualizacji + auto-changelog
- **NEW**: Przycisk "Sprawdz aktualizacje" w panelu admina — ikona odswiezenia obok numeru wersji, klik odpytuje serwer AJAX-em i pokazuje/ukrywa badge "aktualizacja" bez przeladowania strony
- **NEW**: `UpdateController::checkUpdate()` — nowa akcja AJAX czyszczaca sesje `new-version` i sprawdzajaca wersje z serwera
- **NEW**: `updates/changelog.php` — auto-generowany z manifestow JSON (`_manifest.json`) + `changelog-legacy.json`, zamiast recznej edycji HTML
- **NEW**: `updates/changelog-legacy.json` — migracja 295 wpisow changelogu (wersje 0.300-0.001) do formatu JSON
- **ZMIANA**: Workflow KONIEC PRACY — usuniety krok recznej edycji `changelog.php` (teraz automatyczny)
---
## ver. 0.306 (2026-02-22) - Ukrywanie form dostawy bez dostepnych platnosci
- **FIX**: Formy dostawy, dla ktorych nie ma zadnej dostepnej formy platnosci (np. wszystkie odfiltrowane przez `min_order_amount`/`max_order_amount`), sa teraz ukrywane z listy w koszyku
- **ZMIANA**: `TransportRepository::transportMethodsFront()` — po filtrze wagowym i korekcie darmowej dostawy, dodano filtr sprawdzajacy dostepnosc form platnosci per transport (z wykorzystaniem `PaymentMethodRepository::paymentMethodsByTransport()` z cache Redis)
- **NEW**: 5 nowych testow jednostkowych (739 total, 2089 assertions)
---
## ver. 0.305 (2026-02-22) - Sortowanie permutacji + pasek darmowej dostawy
- **FIX**: Naprawa kolejnosci atrybutow permutacji — sortowanie po ID atrybutu (`usort` wg pierwszego segmentu przed `-`) we wszystkich miejscach: koszyk, kombinacje AJAX, `getPermutation()`, `BasketCalculator`. Zapewnia zgodnosc z `permutation_hash` generowanym przez `ksort`.
- **NEW**: Pasek postepu darmowej dostawy w koszyku — progress bar z ikona, tekstem i animacja. Pokazuje ile brakuje do progu `free_delivery` z ustawien sklepu.
- **FIX**: icheck CSS/JS przeniesione z szablonu `product-combination` do globalnego layoutu admina (`main-layout.php`)
- **FIX**: `Tpl::__isset()` — obsluga `isset()` na zmiennych szablonu
- **.updateignore** — lokalne style frontendowe dodane do listy ignorowania
---
## ver. 0.304 (2026-02-22) - Konfigurowalne limity kwotowe metod platnosci
- **NEW**: Kolumny `min_order_amount` i `max_order_amount` w `pp_shop_payment_methods` — konfigurowalne limity kwotowe per metoda platnosci
- **NEW**: Pola min/max kwoty zamowienia w formularzu edycji metody platnosci (admin)
- **FIX**: Zastapiono hardcoded warunek PayPo (id=6, 40-1000 PLN) generycznym filtrowaniem na froncie (basket checkout)
- **NEW**: Cache invalidation po zapisie metody platnosci
- **NEW**: 4 nowe testy jednostkowe (734 total, 2080 assertions)
- **MIGRATION**: `migrations/0.304.sql` — ALTER TABLE pp_shop_payment_methods ADD min/max_order_amount
---
## ver. 0.303 (2026-02-22) - Fix: wyswietlanie atrybutow produktu na froncie + podglad produktu w adminie ## ver. 0.303 (2026-02-22) - Fix: wyswietlanie atrybutow produktu na froncie + podglad produktu w adminie
- **FIX**: Naprawiono wyswietlanie atrybutow produktu na froncie — gdy dwa atrybuty mialy te sama wartosc kolejnosci (`o`), jeden nadpisywal drugi (kolizja kluczy tablicy). Teraz atrybuty sortowane przez `usort()` z unikalnymi kluczami sekwencyjnymi. - **FIX**: Naprawiono wyswietlanie atrybutow produktu na froncie — gdy dwa atrybuty mialy te sama wartosc kolejnosci (`o`), jeden nadpisywal drugi (kolizja kluczy tablicy). Teraz atrybuty sortowane przez `usort()` z unikalnymi kluczami sekwencyjnymi.

View File

@@ -508,12 +508,16 @@ Metody platnosci sklepu (modul `/admin/shop_payment_method`).
| description | Opis metody platnosci (wyswietlany m.in. w checkout) | | description | Opis metody platnosci (wyswietlany m.in. w checkout) |
| status | Status: 1 = aktywna, 0 = nieaktywna | | status | Status: 1 = aktywna, 0 = nieaktywna |
| apilo_payment_type_id | ID typu platnosci Apilo (NULL gdy brak mapowania) | | apilo_payment_type_id | ID typu platnosci Apilo (NULL gdy brak mapowania) |
| min_order_amount | Minimalna kwota zamowienia (DECIMAL(10,2), NULL = brak limitu) |
| max_order_amount | Maksymalna kwota zamowienia (DECIMAL(10,2), NULL = brak limitu) |
| sellasist_payment_type_id | DEPRECATED (integracja Sellasist usunieta w ver. 0.263) | | sellasist_payment_type_id | DEPRECATED (integracja Sellasist usunieta w ver. 0.263) |
**Uzywane w:** `Domain\PaymentMethod\PaymentMethodRepository`, `admin\Controllers\ShopPaymentMethodController`, `front\factory\ShopPaymentMethod`, `shop\PaymentMethod`, `admin\controls\ShopTransport`, `cron.php` **Uzywane w:** `Domain\PaymentMethod\PaymentMethodRepository`, `admin\Controllers\ShopPaymentMethodController`, `front\factory\ShopPaymentMethod`, `shop\PaymentMethod`, `admin\controls\ShopTransport`, `cron.php`
**Aktualizacja 2026-02-14 (ver. 0.268):** modul `/admin/shop_payment_method` korzysta z `Domain\PaymentMethod\PaymentMethodRepository` przez `admin\Controllers\ShopPaymentMethodController`. Usunieto legacy klasy `admin\controls\ShopPaymentMethod`, `admin\factory\ShopPaymentMethod`, `admin\view\ShopPaymentMethod` oraz widok `admin/templates/shop-payment-method/view-list.php`. **Aktualizacja 2026-02-14 (ver. 0.268):** modul `/admin/shop_payment_method` korzysta z `Domain\PaymentMethod\PaymentMethodRepository` przez `admin\Controllers\ShopPaymentMethodController`. Usunieto legacy klasy `admin\controls\ShopPaymentMethod`, `admin\factory\ShopPaymentMethod`, `admin\view\ShopPaymentMethod` oraz widok `admin/templates/shop-payment-method/view-list.php`.
**Aktualizacja 2026-02-22 (ver. 0.304):** dodano kolumny `min_order_amount` i `max_order_amount` — konfigurowalne limity kwotowe metod platnosci. Zastapiono hardcoded warunek PayPo (id=6, 40-1000 PLN) generycznym filtrowaniem na froncie.
## pp_shop_transports ## pp_shop_transports
Rodzaje transportu sklepu (modul `/admin/shop_transport`). Rodzaje transportu sklepu (modul `/admin/shop_transport`).

View File

@@ -23,10 +23,10 @@ composer test # standard
## Aktualny stan ## Aktualny stan
```text ```text
OK (730 tests, 2066 assertions) OK (739 tests, 2089 assertions)
``` ```
Zweryfikowano: 2026-02-21 (ver. 0.300) Zweryfikowano: 2026-02-22 (ver. 0.304)
## Konfiguracja ## Konfiguracja

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2349,6 +2349,60 @@ ul.pager {
white-space: nowrap; white-space: nowrap;
} }
} }
.free-delivery-bar {
background: #3a3a3a;
border-radius: 8px;
padding: 15px 20px;
margin-top: 15px;
display: flex;
align-items: center;
gap: 15px;
color: #fff;
&.success {
background: #28a745;
}
&__icon {
font-size: 28px;
flex-shrink: 0;
}
&__content {
flex: 1;
}
&__text {
font-size: 14px;
font-weight: 600;
margin-bottom: 8px;
}
&__progress {
height: 8px;
background: #555;
border-radius: 4px;
overflow: hidden;
}
&__progress-fill {
height: 100%;
background: linear-gradient(90deg, #28a745, #5cb85c);
border-radius: 4px;
transition: width 0.3s ease;
}
&__remaining {
font-size: 12px;
margin-top: 5px;
color: #ccc;
strong {
color: #ff9800;
}
}
}
} }
} }
} }

2
migrations/0.304.sql Normal file
View File

@@ -0,0 +1,2 @@
ALTER TABLE pp_shop_payment_methods ADD COLUMN min_order_amount DECIMAL(10,2) DEFAULT NULL;
ALTER TABLE pp_shop_payment_methods ADD COLUMN max_order_amount DECIMAL(10,2) DEFAULT NULL;

View File

@@ -13,7 +13,14 @@
$basket_summary = \Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon ) + $transport_cost; $basket_summary = \Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon ) + $transport_cost;
?> ?>
<? if ( is_array( $this -> payment_methods ) ): foreach ( $this -> payment_methods as $payment_method ):?> <? if ( is_array( $this -> payment_methods ) ): foreach ( $this -> payment_methods as $payment_method ):?>
<? if ( $payment_method['id'] != 6 or $payment_method['id'] == 6 and $basket_summary >= 40 and $basket_summary <= 1000 ):?> <?
$min = isset($payment_method['min_order_amount']) ? (float)$payment_method['min_order_amount'] : null;
$max = isset($payment_method['max_order_amount']) ? (float)$payment_method['max_order_amount'] : null;
$show = true;
if ($min !== null && $min > 0 && $basket_summary < $min) $show = false;
if ($max !== null && $max > 0 && $basket_summary > $max) $show = false;
?>
<? if ( $show ):?>
<div class="options"> <div class="options">
<div class="check"> <div class="check">
<input type="radio" class="icheck" name="payment_method" value="<?= $payment_method['id'];?>" <input type="radio" class="icheck" name="payment_method" value="<?= $payment_method['id'];?>"

View File

@@ -52,6 +52,32 @@ if ( is_array( $this -> transports_methods ) )
</div> </div>
<? endif;?> <? endif;?>
<? endforeach; endif;?> <? endforeach; endif;?>
<?php if ( $this->free_delivery > 0 ): ?>
<?php
$percentage = min(100, ($this->basket_summary / $this->free_delivery) * 100);
$remaining = $this->free_delivery - $this->basket_summary;
?>
<div class="free-delivery-bar <?= $percentage >= 100 ? 'success' : '' ?>">
<div class="free-delivery-bar__icon">&#x1F69A;</div>
<div class="free-delivery-bar__content">
<?php if ($percentage >= 100): ?>
<div class="free-delivery-bar__text">Gratulacje! Masz darmow&#261; dostaw&#281;!</div>
<?php else: ?>
<div class="free-delivery-bar__text">
Zr&#243;b zakupy za <?= \Shared\Helpers\Helpers::decimal( $this->free_delivery, 2 );?> z&#322; i otrzymaj darmow&#261; dostaw&#281;
</div>
<?php endif; ?>
<div class="free-delivery-bar__progress">
<div class="free-delivery-bar__progress-fill" style="width: <?= $percentage ?>%"></div>
</div>
<?php if ($percentage < 100): ?>
<div class="free-delivery-bar__remaining">
Brakuje <strong><?= \Shared\Helpers\Helpers::decimal( $remaining, 2 );?> z&#322;</strong> do darmowej dostawy.
</div>
<?php endif; ?>
</div>
</div>
<?php endif; ?>
<script class="footer" type="text/javascript"> <script class="footer" type="text/javascript">
$( function() { $( function() {

View File

@@ -78,6 +78,8 @@ class PaymentMethodRepositoryTest extends TestCase
$this->assertSame('test', $updateRow['description']); $this->assertSame('test', $updateRow['description']);
$this->assertSame(1, $updateRow['status']); $this->assertSame(1, $updateRow['status']);
$this->assertSame(22, $updateRow['apilo_payment_type_id']); $this->assertSame(22, $updateRow['apilo_payment_type_id']);
$this->assertNull($updateRow['min_order_amount']);
$this->assertNull($updateRow['max_order_amount']);
$this->assertSame(['id' => 3], $updateWhere); $this->assertSame(['id' => 3], $updateWhere);
} }
@@ -113,6 +115,102 @@ class PaymentMethodRepositoryTest extends TestCase
$this->assertNull($repository->save(0, ['status' => 1])); $this->assertNull($repository->save(0, ['status' => 1]));
} }
public function testSavePersistsMinMaxOrderAmount(): void
{
$mockDb = $this->createMock(\medoo::class);
$updateRow = null;
$mockDb->expects($this->once())
->method('update')
->willReturnCallback(function ($table, $row) use (&$updateRow) {
$updateRow = $row;
return true;
});
$repository = new PaymentMethodRepository($mockDb);
$repository->save(5, [
'description' => 'test',
'status' => 1,
'apilo_payment_type_id' => '',
'min_order_amount' => '40.00',
'max_order_amount' => '1000.00',
]);
$this->assertSame(40.0, $updateRow['min_order_amount']);
$this->assertSame(1000.0, $updateRow['max_order_amount']);
}
public function testSaveConvertsEmptyMinMaxToNull(): void
{
$mockDb = $this->createMock(\medoo::class);
$updateRow = null;
$mockDb->expects($this->once())
->method('update')
->willReturnCallback(function ($table, $row) use (&$updateRow) {
$updateRow = $row;
return true;
});
$repository = new PaymentMethodRepository($mockDb);
$repository->save(6, [
'description' => 'test',
'status' => 1,
'apilo_payment_type_id' => '',
'min_order_amount' => '',
'max_order_amount' => '',
]);
$this->assertNull($updateRow['min_order_amount']);
$this->assertNull($updateRow['max_order_amount']);
}
public function testFindNormalizesMinMaxOrderAmount(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_shop_payment_methods', '*', ['id' => 6])
->willReturn([
'id' => '6',
'name' => 'PayPo',
'description' => '',
'status' => '1',
'apilo_payment_type_id' => null,
'min_order_amount' => '40.00',
'max_order_amount' => '1000.00',
]);
$repository = new PaymentMethodRepository($mockDb);
$result = $repository->find(6);
$this->assertSame(40.0, $result['min_order_amount']);
$this->assertSame(1000.0, $result['max_order_amount']);
}
public function testFindNormalizesNullMinMaxOrderAmount(): void
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->expects($this->once())
->method('get')
->with('pp_shop_payment_methods', '*', ['id' => 7])
->willReturn([
'id' => '7',
'name' => 'Przelew',
'description' => '',
'status' => '1',
'apilo_payment_type_id' => null,
'min_order_amount' => null,
'max_order_amount' => null,
]);
$repository = new PaymentMethodRepository($mockDb);
$result = $repository->find(7);
$this->assertNull($result['min_order_amount']);
$this->assertNull($result['max_order_amount']);
}
public function testListForAdminWhitelistsSortAndDirection(): void public function testListForAdminWhitelistsSortAndDirection(): void
{ {
$mockDb = $this->createMock(\medoo::class); $mockDb = $this->createMock(\medoo::class);

View File

@@ -420,4 +420,197 @@ class TransportRepositoryTest extends TestCase
$this->assertEquals([], $repository->forPaymentMethod(0)); $this->assertEquals([], $repository->forPaymentMethod(0));
} }
// =========================================================================
// transportMethodsFront — filtrowanie po dostępności form płatności
// =========================================================================
private function createMockDbForTransportMethodsFront(array $transports, array $paymentMethodsByTransportId): \medoo
{
$mockDb = $this->createMock(\medoo::class);
$mockDb->method('select')
->willReturn($transports);
$mockDb->method('query')
->willReturnCallback(function ($sql, $params) use ($paymentMethodsByTransportId) {
$transportId = $params[':transport_id'] ?? 0;
$rows = $paymentMethodsByTransportId[$transportId] ?? [];
return new class($rows) {
private $rows;
public function __construct($rows) { $this->rows = $rows; }
public function fetchAll() { return $this->rows; }
};
});
return $mockDb;
}
private function makeTransport(int $id, float $cost, int $deliveryFree = 0, $maxWp = null, int $default = 0): array
{
return [
'id' => (string)$id, 'name' => 'Transport ' . $id, 'name_visible' => '',
'description' => '', 'status' => '1', 'cost' => (string)$cost,
'max_wp' => $maxWp !== null ? (string)$maxWp : null,
'default' => (string)$default, 'delivery_free' => (string)$deliveryFree,
'apilo_carrier_account_id' => null, 'o' => (string)$id,
];
}
private function makePaymentMethod(int $id, $minAmount = null, $maxAmount = null): array
{
return [
'id' => $id, 'name' => 'Payment ' . $id, 'description' => '',
'status' => 1, 'apilo_payment_type_id' => null,
'min_order_amount' => $minAmount !== null ? (string)$minAmount : null,
'max_order_amount' => $maxAmount !== null ? (string)$maxAmount : null,
];
}
/**
* Transport z formą płatności przekraczającą max_order_amount powinien być ukryty
*/
public function testTransportMethodsFrontHidesTransportWhenAllPaymentsExceedMaxAmount(): void
{
global $settings;
$settings = ['free_delivery' => 9999];
$GLOBALS['mdb'] = $this->createMock(\medoo::class);
$transports = [
$this->makeTransport(1, 15.00, 0, null, 1),
$this->makeTransport(2, 20.00),
];
$paymentMethods = [
1 => [$this->makePaymentMethod(1)],
2 => [$this->makePaymentMethod(3, null, 10.00)],
];
$mockDb = $this->createMockDbForTransportMethodsFront($transports, $paymentMethods);
$repository = new TransportRepository($mockDb);
// Pusty koszyk: summaryPrice=0, transport 2 cost=20, order_total=20, max=10 → ukryty
$result = $repository->transportMethodsFront([], null);
$this->assertCount(1, $result);
$this->assertSame(1, $result[0]['id']);
}
/**
* Transport z formą płatności poniżej min_order_amount powinien być ukryty
*/
public function testTransportMethodsFrontHidesTransportWhenAllPaymentsBelowMinAmount(): void
{
global $settings;
$settings = ['free_delivery' => 9999];
$GLOBALS['mdb'] = $this->createMock(\medoo::class);
$transports = [
$this->makeTransport(1, 10.00, 0, null, 1),
$this->makeTransport(2, 10.00),
];
$paymentMethods = [
1 => [$this->makePaymentMethod(1)],
2 => [$this->makePaymentMethod(3, 100.00, null)],
];
$mockDb = $this->createMockDbForTransportMethodsFront($transports, $paymentMethods);
$repository = new TransportRepository($mockDb);
// Pusty koszyk: summaryPrice=0, transport 2 cost=10, order_total=10, min=100 → ukryty
$result = $repository->transportMethodsFront([], null);
$this->assertCount(1, $result);
$this->assertSame(1, $result[0]['id']);
}
/**
* Transport bez żadnych form płatności powinien być ukryty
*/
public function testTransportMethodsFrontHidesTransportWithNoPaymentMethods(): void
{
global $settings;
$settings = ['free_delivery' => 9999];
$GLOBALS['mdb'] = $this->createMock(\medoo::class);
$transports = [
$this->makeTransport(1, 10.00, 0, null, 1),
$this->makeTransport(2, 10.00),
];
$paymentMethods = [
1 => [$this->makePaymentMethod(1)],
2 => [],
];
$mockDb = $this->createMockDbForTransportMethodsFront($transports, $paymentMethods);
$repository = new TransportRepository($mockDb);
$result = $repository->transportMethodsFront([], null);
$this->assertCount(1, $result);
$this->assertSame(1, $result[0]['id']);
}
/**
* Oba transporty z dostępnymi płatnościami — oba widoczne
*/
public function testTransportMethodsFrontKeepsBothTransportsWhenPaymentsAvailable(): void
{
global $settings;
$settings = ['free_delivery' => 9999];
$GLOBALS['mdb'] = $this->createMock(\medoo::class);
$transports = [
$this->makeTransport(1, 10.00, 0, null, 1),
$this->makeTransport(2, 15.00),
];
$paymentMethods = [
1 => [$this->makePaymentMethod(1)],
2 => [$this->makePaymentMethod(2), $this->makePaymentMethod(3, 50.00, null)],
];
$mockDb = $this->createMockDbForTransportMethodsFront($transports, $paymentMethods);
$repository = new TransportRepository($mockDb);
// Transport 2 ma 2 płatności: jedna bez limitów (dostępna), druga z min=50 (niedostępna)
// Wystarczy 1 dostępna → transport widoczny
$result = $repository->transportMethodsFront([], null);
$this->assertCount(2, $result);
}
/**
* Transport z wieloma płatnościami — przynajmniej jedna dostępna = transport widoczny
*/
public function testTransportMethodsFrontKeepsTransportIfAtLeastOnePaymentAvailable(): void
{
global $settings;
$settings = ['free_delivery' => 9999];
$GLOBALS['mdb'] = $this->createMock(\medoo::class);
$transports = [
$this->makeTransport(1, 5.00, 0, null, 1),
];
// 3 płatności: 2 niedostępne (min za wysoki, max za niski), 1 dostępna (brak limitów)
$paymentMethods = [
1 => [
$this->makePaymentMethod(1, 100.00, null),
$this->makePaymentMethod(2, null, 1.00),
$this->makePaymentMethod(3),
],
];
$mockDb = $this->createMockDbForTransportMethodsFront($transports, $paymentMethods);
$repository = new TransportRepository($mockDb);
$result = $repository->transportMethodsFront([], null);
$this->assertCount(1, $result);
$this->assertSame(1, $result[0]['id']);
}
} }

BIN
updates/0.30/ver_0.303.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,26 @@
{
"changelog": "FIX - naprawiono wyswietlanie atrybutow produktu na froncie (kolizja kolejnosci), NEW - przycisk Podglad w edycji produktu",
"version": "0.303",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"admin/templates/components/form-edit.php",
"autoload/Domain/Product/ProductRepository.php",
"autoload/admin/Controllers/ShopProductController.php",
"autoload/admin/ViewModels/Forms/FormAction.php"
]
},
"checksum_zip": "sha256:6d8cc0c3419c50345d9c4fa1f526a09004194cbf7f76302f3cfe7afe64b00545",
"sql": [
],
"date": "2026-02-22",
"directories_deleted": [
]
}

BIN
updates/0.30/ver_0.304.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,26 @@
{
"changelog": "NEW - konfigurowalne limity kwotowe metod platnosci (min/max kwota zamowienia)",
"version": "0.304",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"autoload/Domain/PaymentMethod/PaymentMethodRepository.php",
"autoload/admin/Controllers/ShopPaymentMethodController.php",
"templates/shop-basket/basket-payments-methods.php"
]
},
"checksum_zip": "sha256:86ce0ba4f6e46c380b1cfcefe2d5b206aa9bf26f25e62661d1fac3144fa35f07",
"sql": [
"ALTER TABLE pp_shop_payment_methods ADD COLUMN min_order_amount DECIMAL(10,2) DEFAULT NULL;",
"ALTER TABLE pp_shop_payment_methods ADD COLUMN max_order_amount DECIMAL(10,2) DEFAULT NULL;"
],
"date": "2026-02-22",
"directories_deleted": [
]
}

BIN
updates/0.30/ver_0.305.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,24 @@
{
"version": "0.305",
"date": "2026-02-22",
"checksum_zip": "sha256:05c06817cc7fdbf362e84be5a547010b9cf9eedba423d8d23fc5de2308bec084",
"files": {
"added": [],
"modified": [
"admin/layout/style-css/style.css",
"admin/layout/style-css/style.css.map",
"admin/layout/style-scss/style.scss",
"admin/templates/shop-product/product-combination.php",
"admin/templates/site/main-layout.php",
"autoload/Domain/Basket/BasketCalculator.php",
"autoload/Shared/Tpl/Tpl.php",
"autoload/front/Controllers/ShopBasketController.php",
"autoload/front/Controllers/ShopProductController.php",
"templates/shop-basket/basket-transport-methods.php"
],
"deleted": []
},
"directories_deleted": [],
"sql": [],
"changelog": "FIX - naprawa kolejnosci atrybutow permutacji, NEW - pasek postepu darmowej dostawy w koszyku"
}

BIN
updates/0.30/ver_0.306.zip Normal file

Binary file not shown.

View File

@@ -0,0 +1,24 @@
{
"changelog": "FIX - ukrywanie form dostawy gdy nie ma dostepnych form platnosci",
"version": "0.306",
"files": {
"added": [
],
"deleted": [
],
"modified": [
"autoload/Domain/Transport/TransportRepository.php",
"autoload/Domain/Update/UpdateRepository.php"
]
},
"checksum_zip": "sha256:f0fefc2d28c00257f1015fb51bcd9b8b43c04fee4c947f6df1ca04761c61326f",
"sql": [
],
"date": "2026-02-22",
"directories_deleted": [
]
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,728 +1,72 @@
<b>ver. 0.302 - 22.02.2026</b><br /> <?php
NEW - REST API wariantów produktów (CRUD), słownik atrybutów, filtrowanie po atrybutach, wzbogacone atrybuty z tłumaczeniami // Auto-generated changelog from manifest files + legacy entries.
<hr> // Scans manifest JSON files, merges with changelog-legacy.json, sorts descending, outputs HTML.
<b>ver. 0.301 - 22.02.2026</b><br />
NEW - Ukrywalne filtry tabel, mobilna wersja szczegółów zamówienia $entries = [];
<hr>
<b>ver. 0.300 - 21.02.2026</b><br /> // 1. Scan manifest files
- NEW - System aktualizacji oparty na manifestach JSON (checksum SHA256, backup plikĂłw, automatyczny build) $manifests = glob( __DIR__ . '/0.*/ver_*_manifest.json' );
- NEW - Panel logu aktualizacji w panelu admina if ( $manifests ) {
<hr> foreach ( $manifests as $file ) {
<b>ver. 0.299 - 21.02.2026</b><br /> $json = @file_get_contents( $file );
- NEW - Ukrywanie/pokazywanie kolumn w tabelach admina (toggle switch + localStorage) if ( !$json ) continue;
<hr> // Strip UTF-8 BOM if present
<b>ver. 0.298 - 20.02.2026</b><br /> if ( substr( $json, 0, 3 ) === "\xEF\xBB\xBF" ) {
- FIX - kilka poprawek po aktualizacji $json = substr( $json, 3 );
<hr> }
<b>ver. 0.297 - 19.02.2026</b><br /> $data = @json_decode( $json, true );
- NEW - REST API produktĂłw (lista, szczegĂłĹy, tworzenie, aktualizacja) if ( !$data || empty( $data['version'] ) || empty( $data['changelog'] ) ) continue;
- NEW - Endpoint products z filtrowaniem, sortowaniem i paginacjÄ…
- NEW - Partial update produktĂłw (tylko zmienione pola) $date = isset( $data['date'] ) ? $data['date'] : '';
<hr> // Convert YYYY-MM-DD to DD.MM.YYYY
<b>ver. 0.296 - 19.02.2026</b><br /> if ( $date && preg_match( '/^(\d{4})-(\d{2})-(\d{2})$/', $date, $m ) ) {
- NEW - REST API zamĂłwieĹ„ dla ordersPRO (lista, szczegĂłĹy, zmiana statusu, atnoĹci) $date = $m[3] . '.' . $m[2] . '.' . $m[1];
- NEW - Endpointy ownikowe (statusy, transporty, metody atnoĹci) }
- NEW - Autentykacja API przez X-Api-Key header
- NEW - Kolumna updated_at w pp_shop_orders (polling zmian) $entries[] = [
<hr> 'version' => (float) $data['version'],
<b>ver. 0.295 - 19.02.2026</b><br /> 'ver_str' => $data['version'],
- NEW - Edycja produktĂłw w zamĂłwieniu z panelu admina (dodawanie, usuwanie, zmiana iloĹci/cen) 'date' => $date,
- NEW - Wyszukiwarka produktĂłw AJAX w formularzu edycji zamĂłwienia 'text' => $data['changelog'],
- NEW - Automatyczna korekta stanĂłw magazynowych i przeliczanie kosztu dostawy ];
- FIX - Cena promo w zamĂłwieniu = 0 gdy identyczna z cenÄ… bazowÄ… }
<hr> }
<b>ver. 0.294 - 19.02.2026</b><br />
- FIX - Code review zakończony (96/96 klas, ~1144 metod): 27 fixów across all layers // 2. Load legacy entries
- FIX - Domain: null guard na query()->fetchAll() w 8 repozytoriach, redundancja DI w PromotionRepository $legacyFile = __DIR__ . '/changelog-legacy.json';
- FIX - Admin: null safety find() ?: [] w 10 kontrolerach, null guard w App logowaniu/2FA if ( file_exists( $legacyFile ) ) {
- FIX - Front: LayoutEngine undefined $level + $_GET null check, ShopBasketController missing global $lang_id $legacyJson = @file_get_contents( $legacyFile );
- FIX - Shared: Helpers $_GET null check + bug 'png' → 'image/png' (Imagick lossless WebP nigdy nie dziaĹ) if ( $legacyJson ) {
<hr> $legacy = @json_decode( $legacyJson, true );
<b>ver. 0.293 - 19.02.2026</b><br /> if ( is_array( $legacy ) ) {
- FIX - ArticleRepository: SQL injection fix (addslashesâ†parameterized), uproszczenie articleDetailsFrontend foreach ( $legacy as $item ) {
- FIX - AttributeRepository: martwy class_exists('\S') blokowal czyszczenie cache/temp if ( empty( $item['version'] ) ) continue;
- FIX - CategoryRepository: martwy class_exists('\S') blokowal generowanie linkow SEO kategorii $entries[] = [
- FIX - BannerRepository: parametryzacja dat w SQL + null guard na query() 'version' => (float) $item['version'],
- FIX - BasketCalculator: null guard checkProductQuantityInStock + opcjonalne DI params summaryPrice/calculateBasketProductPrice 'ver_str' => $item['version'],
- FIX - PromotionRepository: null guard na $basket (produkcyjny fatal error) 'date' => isset( $item['date'] ) ? $item['date'] : '',
- UPDATE - OrderRepository, ShopBasketController, ajax.php: jawne DI zamiast globals w callerach BasketCalculator 'text' => isset( $item['text'] ) ? $item['text'] : '',
<hr> ];
<b>ver. 0.292 - 18.02.2026</b><br /> }
- UPDATE - pelna migracja front\factory\ do Domain (5 ostatnich klas: ShopProduct, ShopPaymentMethod, ShopPromotion, ShopStatuses, ShopTransport) }
- UPDATE - ProductRepository: ~20 nowych metod frontendowych (cache Redis, lazy loading, SKU/EAN fallback) }
- UPDATE - PromotionRepository: 5 metod aplikowania promocji (applyTypeWholeBasket/CheapestProduct/CategoriesOr/CategoriesAnd/CategoryCondition) }
- UPDATE - TransportRepository: 4 metody frontendowe z cache (transportMethodsFront, transportCostCached, findActiveByIdCached, forPaymentMethod)
- UPDATE - PaymentMethodRepository: metody frontendowe z Redis cache // 3. Sort descending by version
- CLEANUP - usuniety caly folder front\factory\ (20 klas zmigrowanych) + 4 inne klasy legacy usort( $entries, function ( $a, $b ) {
- FIX - broken transports_list() w ajax.php zastapiony nowa metoda forPaymentMethod() if ( $a['version'] == $b['version'] ) return 0;
- UPDATE - front\controls\Site przemianowany na front\App (router, camelCase: checkUrlParams, pageTitle) return ( $a['version'] > $b['version'] ) ? -1 : 1;
- UPDATE - front\view\Site przemianowany na front\LayoutEngine (layout engine, camelCase: cookieInformation) } );
- CLEANUP - usuniete autoload/front/controls/ i autoload/front/view/ (puste foldery + pliki legacy)
- UPDATE - usuniecie 12 legacy klas z autoload/shop/ (~2363 linii) — pelna migracja na Domain-Driven Architecture // 4. Output HTML
- UPDATE - class.Order.php: logika Apilo sync i email statusu przeniesiona do OrderAdminService foreach ( $entries as $entry ) {
- UPDATE - class.Product.php: ~20 metod przeniesionych do ProductRepository, calculate_basket_product_price do BasketCalculator $header = 'ver. ' . htmlspecialchars( $entry['ver_str'] );
- FIX - findCached(): stale Redis cache z obiektami \shop\Product powodowal ceny 0,00 zl if ( $entry['date'] ) {
- FIX - szablony: konwersja object access na array access po migracji Product $header .= ' - ' . htmlspecialchars( $entry['date'] );
- UPDATE - AttributeRepository::getAttributeValueById() — dodano Redis cache }
- CLEANUP - katalog autoload/shop/ pusty, zero referencji \shop\ w aktywnym kodzie $text = nl2br( htmlspecialchars( $entry['text'] ) );
<hr>
<b>ver. 0.291 - 17.02.2026</b><br /> echo '<b>' . $header . '</b><br />' . "\n";
- UPDATE - migracja front\controls\ShopProducer + shop\Producer do Domain\Producer\ProducerRepository + front\Controllers\ShopProducerController echo $text . "\n";
- FIX - bug shop\Producer::__get() referowal nieistniejace $this->data echo '<hr>' . "\n";
<hr> }
<b>ver. 0.290 - 17.02.2026</b><br />
- UPDATE - migracja front\factory\ShopCoupon + front\controls\ShopCoupon do Domain\Coupon\CouponRepository + front\Controllers\ShopCouponController
- UPDATE - migracja front\factory\ShopOrder + front\controls\ShopOrder + front\view\ShopOrder do Domain\Order\OrderRepository + front\Controllers\ShopOrderController
- FIX - kupony jednorazowe nigdy nie byly oznaczane jako uzyte (is_one_time/set_as_used w shop\Coupon)
- FIX - webhooks przelewy24/hotpay ujednolicone z tpay (poprawna obsluga Apilo sync)
<hr>
<b>ver. 0.289 - 17.02.2026</b><br />
- UPDATE - migracja front\factory\ShopCategory + front\view\ShopCategory do Domain\Category\CategoryRepository + front\Views\ShopCategory
- UPDATE - migracja front\factory\ShopClient + front\view\ShopClient + front\controls\ShopClient do Domain\Client\ClientRepository + front\Views\ShopClient + front\Controllers\ShopClientController
- FIX - usuniety hardcoded password bypass 'Legia1916' w logowaniu klienta
<hr>
<b>ver. 0.288 - 17.02.2026</b><br />
- UPDATE - migracja front\factory\ShopBasket do Domain\Basket\BasketCalculator (4 metody statyczne)
- UPDATE - migracja front\controls\ShopBasket do front\Controllers\ShopBasketController (camelCase, instancyjny)
- UPDATE - routing snake_case->camelCase w dispatch dla nowych kontrolerow
- CLEANUP - usunieta klasa cms\Layout (zastapiona $layoutsRepo->find)
- CLEANUP - usuniete 3 klasy legacy (front\factory\ShopBasket, front\controls\ShopBasket, cms\Layout)
<hr>
<b>ver. 0.287 - 17.02.2026</b><br />
- UPDATE - migracja front\factory\Scontainers do Domain\Scontainers\ScontainersRepository (frontScontainerDetails z Redis cache)
- UPDATE - migracja front\factory\ShopAttribute do Domain\Attribute\AttributeRepository (frontAttributeDetails, frontValueDetails z Redis cache)
- CLEANUP - usuniete 3 klasy legacy (front\factory\Scontainers, front\view\Scontainers, front\factory\ShopAttribute)
<hr>
<b>ver. 0.286 - 17.02.2026</b><br />
- UPDATE - migracja front\factory\Layouts do Domain\Layouts\LayoutsRepository (6 metod frontend z Redis cache)
- UPDATE - migracja front\factory\Menu + front\factory\Pages do Domain\Pages\PagesRepository (6 metod frontend z Redis cache)
- UPDATE - migracja front\view\Menu do front\Views\Menu (nowy namespace)
- CLEANUP - usuniete 4 klasy legacy + 1 martwy szablon (submenu.php)
- FIX - null $lang_id przy wczesnym wywolaniu check_url_params()
<hr>
<b>ver. 0.285 - 17.02.2026</b><br />
- UPDATE - migracja class.Tpl.php do Shared\Tpl\Tpl (~135 plikow przepietych)
- CLEANUP - usunieta nieuzywana klasa CurlServer (curl.class.php)
- FIX - thumb.php: naprawa require po migracji Image do Shared namespace
- FIX - Tpl::render() branch 3: sprawdzal templates_user ale ladowal templates
<hr>
<b>ver. 0.284 - 16.02.2026</b><br />
- CLEANUP - usunieta klasa DbModel (base ORM) — logika wbudowana bezposrednio w shop\Promotion
<hr>
<b>ver. 0.283 - 16.02.2026</b><br />
- UPDATE - migracja class.S.php do Shared\Helpers\Helpers (~140 plikow przepietych)
- UPDATE - migracja class.Html.php do Shared\Html\Html
- UPDATE - migracja class.Email.php do Shared\Email\Email
- UPDATE - migracja class.Image.php do Shared\Image\ImageManipulator
- UPDATE - migracja class.Log.php do Shared\Log\Log (usunieta — logika przeniesiona)
- CLEANUP - usunieta class.Mobile_Detect.php (przestarzala detekcja UA)
- CLEANUP - usunieto 12 nieuzywanych metod z klasy S
- FIX - array_cartesian_product() — blad iteracji po niezdefiniowanej zmiennej
<hr>
<b>ver. 0.282 - 16.02.2026</b><br />
- UPDATE - Cache cleanup: eliminacja legacy class.Cache.php, migracja CacheHandler i RedisConnection do Shared\Cache namespace
- UPDATE - 60 odwolan CacheHandler i 12 odwolan RedisConnection przepietych na Shared\Cache\
- UPDATE - 13 metod front\factory przepietych z \Cache::fetch/store na CacheHandler (ShopProduct, ShopPaymentMethod, ShopCategory, ShopTransport, ShopAttribute)
- FIX - naprawione rozbieznosci kluczy cache (random_products, category_name)
- CLEANUP - usuniete: class.Cache.php, class.CacheHandler.php, class.RedisConnection.php
- UPDATE - testy: OK (454 tests, 1449 assertions)
<hr>
<b>ver. 0.281 - 16.02.2026</b><br />
- UPDATE - migracja Banners frontend: factory + view do Domain/Views (DI)
- NEW - `front\Views\Banners` — czysty VIEW (banners, mainBanner)
- UPDATE - `BannerRepository` rozszerzony o 2 metody frontendowe (banners, mainBanner) z Redis cache
- UPDATE - `front\view\Site::show()` przepiety na repo + Views
- CLEANUP - usuniete: front\factory\Banners, front\view\Banners
- UPDATE - testy: OK (454 tests, 1449 assertions)
<hr>
<b>ver. 0.280 - 16.02.2026</b><br />
- UPDATE - migracja Articles frontend: factory + view + encja do Domain/Views (DI)
- NEW - `front\Views\Articles` — czysty VIEW + utility (renderowanie, generateTableOfContents, generateHeadersIds, getImage)
- UPDATE - `ArticleRepository` rozszerzony o 8 metod frontendowych (z Redis cache)
- UPDATE - `front\view\Site::show()` — 5 sekcji przepietych na repo + Views
- UPDATE - `front\controls\Site::route()` — single article + page_type switch przepiete na repo + Views
- UPDATE - 5 szablonow `templates/articles/*` przepietych na `\front\Views\Articles::`
- CLEANUP - usuniete: `class.Article` (encja + metody statyczne), `front\factory\Settings` (fasada)
- FIX - eliminacja `global $lang` z `articleNoindex()`, eliminacja zaleznosci od `front\factory\Pages::page_sort()`
- UPDATE - testy: `OK (450 tests, 1431 assertions)`
<hr>
<b>ver. 0.279 - 16.02.2026</b><br />
- UPDATE - migracja Newsletter frontend: factory + view + controls do Domain/Controllers/Views (DI)
- UPDATE - nowy namespace `front\Controllers` z `NewsletterController` (DI via factory closures)
- UPDATE - nowy namespace `front\Views` z `Languages` i `Newsletter` (czyste VIEW, statyczne metody)
- UPDATE - routing frontend: `Site::getControllerFactories()` z fallback na stare kontrolery
- FIX - `newsletter_unsubscribe()` — poprawiona skladnia medoo `delete()` (2 argumenty zamiast 3)
- UPDATE - eliminacja fasady `front\factory\Languages` — 26 zaleznosci przepietych na `LanguagesRepository`
- CLEANUP - usuniete: `front\factory\Languages`, `front\factory\Newsletter`, `front\view\Languages`, `front\view\Newsletter`, `front\controls\Newsletter`
- UPDATE - testy: `OK (437 tests, 1398 assertions)`
<hr>
<b>ver. 0.278 - 16.02.2026</b><br />
- UPDATE - migracja Settings + Languages do wspolnych klas Domain (z cache Redis)
- FIX - `get_single_settings_value()` — parametr `$param` poprawnie uzywany (wczesniej hardcoded `firm_name`)
<hr>
<b>ver. 0.277 - 16.02.2026</b><br />
- NEW - migracja modulu `ShopProduct` (factory) — pelna migracja ~40 metod do `ProductRepository` + ~30 akcji w `ShopProductController`
- NEW - migracja modulu `Dashboard` do Domain + DI (`DashboardRepository`, `DashboardController`)
- NEW - migracja modulu `Update` do Domain + DI (`UpdateRepository`, `UpdateController`)
- UPDATE - klasa `admin\Site` przemianowana na `admin\App` (plik `App.php`)
- UPDATE - refaktoring routingu — usunieto fallback na stare kontrolery, uproszczony routing
- UPDATE - template `update/main-view.php` — panele zamiast `gridEdit`, `$.confirm()` zamiast `$.prompt()`
- CLEANUP - usuniete stare foldery: `autoload/admin/controls/`, `autoload/admin/factory/`, `autoload/admin/view/`
- CLEANUP - usuniete legacy: `class.Dashboard.php` (controls/shop), `class.Update.php` (controls/factory/view), `class.Articles.php` (factory), `class.Page.php` (view), `class.ShopProduct.php` (controls/factory/view)
- UPDATE - `front\factory\Newsletter` przepieta na `ArticleRepository::articlesByDateAdd()`
- UPDATE - testy: `OK (414 tests, 1335 assertions)`
<hr>
<b>ver. 0.276 - 15.02.2026</b><br />
- NEW - migracja modulu `ShopOrder` do architektury Domain + DI (`Domain\Order\OrderRepository`, `Domain\Order\OrderAdminService`, `admin\Controllers\ShopOrderController`)
- UPDATE - modul `/admin/shop_order/*` przepiety na nowy routing (kanoniczny URL `/admin/shop_order/list/`) i nowe widoki (`orders-list`, `order-details`, `order-edit`)
- FIX - stabilizacja listy zamowien (`OrderRepository::listForAdmin`) oraz poprawa wygladu tabeli (`components/table-list`, wyrownanie komorek i `text-right`)
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopOrder.php`, `autoload/admin/factory/class.ShopOrder.php`, `admin/templates/shop-order/view-list.php`
- UPDATE - usunieta fasada `autoload/admin/factory/class.Integrations.php`; wywolania przepiete na `Domain\Integrations\IntegrationsRepository`
- NEW - globalna wyszukiwarka admin (produkty + zamowienia) przy "Wyczysc cache" + endpoint `/admin/settings/globalSearchAjax/`
- FIX - wyszukiwanie po pelnym imieniu i nazwisku w global search
- UPDATE - testy: `OK (385 tests, 1246 assertions)`
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.276.zip`, `ver_0.276_files.txt`
<hr>
<b>ver. 0.275 - 15.02.2026</b><br />
- NEW - migracja modulu `ShopCategory` do architektury Domain + DI (`Domain\Category\CategoryRepository`, `admin\Controllers\ShopCategoryController`)
- UPDATE - modul `/admin/shop_category/*` przepiety na nowy routing (kanoniczny URL `/admin/shop_category/list/`) i endpointy AJAX kontrolera (`save_categories_order`, `save_products_order`, `cookie_categories`)
- UPDATE - widoki `shop-category/*`: wydzielenie skryptow do `*-custom-script.php`, ujednolicone strzalki drzewa (`button + caret + aria-expanded`)
- UPDATE - przepiecie zaleznosci `ShopProduct` z `admin\factory\ShopCategory` na `Domain\Category\CategoryRepository`
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopCategory.php`, `autoload/admin/factory/class.ShopCategory.php`, `autoload/admin/view/class.ShopCategory.php`
- UPDATE - testy: `OK (377 tests, 1197 assertions)`
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.275.zip`, `ver_0.275_files.txt`
<hr>
<b>ver. 0.274 - 15.02.2026</b><br />
- NEW - migracja modulu `ShopClients` do architektury Domain + DI (`Domain\Client\ClientRepository`, `admin\Controllers\ShopClientsController`)
- UPDATE - modul `/admin/shop_clients/*` przepiety na `components/table-list` (lista klientow i szczegoly zamowien)
- UPDATE - routing i menu admin przepiete na kanoniczny URL `/admin/shop_clients/list/`
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopClients.php`, `autoload/admin/factory/class.ShopClients.php`
- UPDATE - testy: `OK (361 tests, 1125 assertions)`
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.274.zip`, `ver_0.274_files.txt`
<hr>
<b>ver. 0.273 - 15.02.2026</b><br />
- NEW - migracja `/admin/shop_product/mass_edit/*` do `Domain\Product\ProductRepository` + `admin\Controllers\ShopProductController` (DI + routing)
- UPDATE - nowy widok/skrypt masowej edycji (`mass-edit`, `mass-edit-custom-script`) z iCheck i ujednoliconymi strzalkami drzewa
- FIX - zaznaczanie kategorii w mass-edit nie zaznacza automatycznie produktow na liscie
- UPDATE - ujednolicenie strzalek/checkboxow w drzewkach: `/admin/pages/list/*` oraz zakladka wyswietlania w `/admin/articles/edit/*`
- UPDATE - testy: `OK (351 tests, 1091 assertions)`
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.273.zip`, `ver_0.273_files.txt`
<hr>
<b>ver. 0.272 - 15.02.2026</b><br />
- NEW - migracja modulu `ShopProductSets` do architektury Domain + DI (`Domain\ProductSet\ProductSetRepository`, `admin\Controllers\ShopProductSetsController`)
- UPDATE - modul `/admin/shop_product_sets/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit` + multi-select Selectize
- UPDATE - routing i menu admin przepiete na kanoniczny URL `/admin/shop_product_sets/list/`
- UPDATE - `shop\ProductSet` przepiety na fasade do `Domain\ProductSet\ProductSetRepository`
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopProductSets.php`, `autoload/admin/factory/class.ShopProductSet.php`, `admin/templates/shop-product-sets/view-list.php`, `admin/templates/shop-product-sets/set-edit.php`
- UPDATE - testy: `OK (324 tests, 1000 assertions)` + nowe pliki testowe `ProductSetRepositoryTest`, `ShopProductSetsControllerTest`
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.272.zip`, `ver_0.272_files.txt`
<hr>
<b>ver. 0.271 - 14.02.2026</b><br />
- NEW - migracja modulu `ShopAttribute` do architektury Domain + DI (`Domain\Attribute\AttributeRepository`, `admin\Controllers\ShopAttributeController`)
- UPDATE - modul `/admin/shop_attribute/*` przepiety z legacy `grid/gridEdit` na `components/table-list`, `components/form-edit` oraz nowy edytor wartosci (`values-edit`)
- UPDATE - routing i menu admin przepiete na kanoniczny URL `/admin/shop_attribute/list/` (bez aliasow legacy)
- UPDATE - przepiecie zaleznosci kombinacji produktu: `admin\controls\ShopProduct`, `admin\factory\ShopProduct`, `admin/templates/shop-product/product-combination.php`
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopAttribute.php`, `autoload/admin/factory/class.ShopAttribute.php`, `autoload/admin/view/class.ShopAttribute.php`, `admin/templates/shop-attribute/_partials/value.php`
- UPDATE - testy: `OK (312 tests, 948 assertions)` + nowe pliki testowe `AttributeRepositoryTest`, `ShopAttributeControllerTest`
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.271.zip`, `ver_0.271_files.txt`
<hr>
<b>ver. 0.270 - 14.02.2026</b><br />
- FIX - Apilo: `shop\Order::set_as_paid()` wysyla mapowany typ platnosci Apilo (z `payment_method_id`), zamiast stalego `type = 1`
- NEW - Apilo: dodana kolejka retry `temp/apilo-sync-queue.json` dla nieudanych syncow platnosci/statusu (chwilowa niedostepnosc API)
- UPDATE - `cron.php`: automatyczne ponawianie zaleglych syncow przez `Order::process_apilo_sync_queue(10)`
- UPDATE - debug Apilo: rozszerzone logi odpowiedzi o HTTP code i bledy cURL dla sync platnosci/statusu
- UPDATE - testy: `OK (300 tests, 895 assertions)`
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.270.zip`, `ver_0.270_files.txt`
<hr>
<b>ver. 0.269 - 14.02.2026</b><br />
- NEW - migracja modulu `ShopPaymentMethod` do architektury Domain + DI (`Domain\PaymentMethod\PaymentMethodRepository`, `admin\Controllers\ShopPaymentMethodController`)
- UPDATE - modul `/admin/shop_payment_method/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit` (nowe widoki listy i edycji)
- UPDATE - przepiecie zaleznosci na nowe repozytorium: `admin\controls\ShopTransport`, `front\factory\ShopPaymentMethod`, `shop\PaymentMethod`
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopPaymentMethod.php`, `autoload/admin/factory/class.ShopPaymentMethod.php`, `autoload/admin/view/class.ShopPaymentMethod.php`, `admin/templates/shop-payment-method/view-list.php`
- UPDATE - Apilo: dodane automatyczne odswiezanie tokenu przed wygasnieciem (`apiloKeepalive`) oraz bardziej szczegolowe komunikaty bledow integracji
- UPDATE - testy: `OK (280 tests, 828 assertions)` + nowe pliki testowe `PaymentMethodRepositoryTest`, `ShopPaymentMethodControllerTest`
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.269.zip`, `ver_0.269_files.txt`
<hr>
<b>ver. 0.268 - 14.02.2026</b><br />
- NEW - migracja modulu `ShopStatuses` do architektury Domain + DI (`Domain\ShopStatus\ShopStatusRepository`, `admin\Controllers\ShopStatusesController`)
- UPDATE - modul `/admin/shop_statuses/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`
- NEW - nowy typ pola formularza `color` (HTML5 color picker + pole tekstowe zsynchronizowane)
- UPDATE - `front\factory\ShopStatuses` dziala jako fasada do `Domain\ShopStatus\ShopStatusRepository`
- UPDATE - menu admin przepiete na kanoniczny URL `/admin/shop_statuses/list/`
- CLEANUP - usuniete legacy klasy: `autoload/admin/controls/class.ShopStatuses.php`, `autoload/admin/factory/class.ShopStatuses.php`
- UPDATE - reorganizacja dokumentacji technicznej: pliki przeniesione do folderu `docs/` i rozbite na mniejsze pliki tematyczne
- UPDATE - testy: `OK (254 tests, 736 assertions)` + nowe pliki testowe `ShopStatusRepositoryTest`, `ShopStatusesControllerTest`
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.268.zip`, `ver_0.268_files.txt`
<hr>
<b>ver. 0.267 - 13.02.2026</b><br />
- FIX - front: poprawione dobieranie layoutu dla kategorii/produktu/koszyka i innych stron moduĹowych (fallback do layoutu domyĹlnego)
- FIX - produkt/koszyk: poprawiona obsĹuga iloĹci dla kombinacji (stan 0 po dodaniu do koszyka, limit max, odczyt `stock_0_buy`)
- FIX - produkt: usunięty ąd JS `TypeError: $(...).visible is not a function` (zamiana na `:visible`)
- FIX - SEO redirecty produktĂłw: blokada konfliktĂłw po kopiowaniu URL oraz utwardzone wykrywanie pÄ™tli redirectĂłw (`lang_id` + graf przejĹć)
- UPDATE - admin: `input-switch` zapisuje wartoĹć `on` (spĂłjnie z obsĹugÄ… pĂłl checkbox w formularzach)
- CLEANUP - usunięte pliki: `apilo-bck`, `geocode-cache.php`
- UPDATE - testy: `OK (235 tests, 682 assertions)`
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.267.zip`, `ver_0.267_files.txt`, `ver_0.267_sql.txt`
<hr>
<b>ver. 0.266 - 13.02.2026</b><br />
- NEW - migracja modulu `ShopCoupon` do architektury Domain + DI (`Domain\Coupon\CouponRepository`, `admin\Controllers\ShopCouponController`)
- UPDATE - modul `/admin/shop_coupon/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`
- UPDATE - nowe widoki i partiale: `shop-coupon/coupons-list`, `shop-coupon/coupon-edit-new`, `shop-coupon/coupon-categories-selector`, `shop-coupon/coupon-categories-tree`, `shop-coupon/coupon-edit-custom-script`
- UPDATE - zachowana kompatybilnosc aliasow legacy akcji (`view_list`, `coupon_edit`, `coupon_save`, `coupon_delete`) w nowym kontrolerze
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopCoupon.php`, `autoload/admin/factory/class.ShopCoupon.php`, `admin/templates/shop-coupon/view-list.php`, `admin/templates/shop-coupon/coupon-edit.php`
- UPDATE - menu admin wskazuje kanoniczny URL `/admin/shop_coupon/list/`
- FIX - ujednolicone drzewka (strzalki + focus) i wyglad checkboxow miedzy `/admin/shop_coupon/edit/*` oraz `/admin/layouts/edit/*`
- UPDATE - testy: `OK (235 tests, 682 assertions)` + nowe pliki testowe `CouponRepositoryTest`, `ShopCouponControllerTest`
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.266.zip`, `ver_0.266_files.txt`
<hr>
<b>ver. 0.265 - 13.02.2026</b><br />
- UPDATE - modul `/admin/shop_promotion/*`: dodano pole `Data od` (`date_from`) w repozytorium, formularzu i liscie
- UPDATE - front: `shop\Promotion::get_active_promotions()` uwzglednia `date_from` (okno aktywnosci od-do)
- FIX - edycja promocji zapisuje aktualizacje rekordu zamiast tworzenia nowego (`id` przekazywane przez hidden field + fallback z URL)
- UPDATE - testy: `OK (222 tests, 614 assertions)`
<hr><b>ver. 0.264 - 13.02.2026</b><br />
- NEW - migracja modulu `ShopPromotion` do architektury Domain + DI (`Domain\Promotion\PromotionRepository`, `admin\Controllers\ShopPromotionController`)
- UPDATE - modul `/admin/shop_promotion/*` przepiety z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`
- UPDATE - nowe widoki i partiale: `shop-promotion/promotions-list`, `shop-promotion/promotion-edit`, `shop-promotion/promotion-categories-selector`, `shop-promotion/promotion-categories-tree`, `shop-promotion/promotion-edit-custom-script`
- CLEANUP - usuniete legacy klasy/pliki: `autoload/admin/controls/class.ShopPromotion.php`, `autoload/admin/factory/class.ShopPromotion.php`, `admin/templates/shop-promotion/view-list.php`
- UPDATE - menu admin wskazuje kanoniczny URL `/admin/shop_promotion/list/`
- UPDATE - testy: `OK (222 tests, 609 assertions)` + nowe pliki testowe `PromotionRepositoryTest`, `ShopPromotionControllerTest`
<hr><b>ver. 0.263 - 13.02.2026</b><br />
- NEW - migracja modulu `Integrations` do architektury Domain + DI (`Domain\Integrations\IntegrationsRepository`, `admin\Controllers\IntegrationsController`)
- CLEANUP - usunieto integracje Sellasist i Baselinker z calego projektu (kontrolery, factory, szablony, referencje w cron/Order/ShopStatuses/ShopTransport/ShopPaymentMethod/ShopProduct)
- UPDATE - `admin\factory\Integrations` jako fasada delegujaca do repozytorium (tylko Apilo + ShopPRO)
- FIX - naprawione polskie znaki w `product-edit.php` (usuniety podwojny encoding UTF-8/CP1250)
- CLEANUP - usuniete pliki: `controls/Integrations`, `controls/Baselinker`, `factory/Baselinker`, `front/factory/Shop`, `shop/ShopStatus`, szablony sellasist/baselinker
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.263.zip`, `ver_0.263_files.txt`
<hr><b>ver. 0.262 - 13.02.2026</b><br />
- NEW - migracja modulu `Pages` do architektury Domain + DI (`Domain\\Pages\\PagesRepository`, `admin\\Controllers\\PagesController`)
- UPDATE - widoki `/admin/pages/*` przepiete na nowy routing i komponent `components/form-edit` (menu/page edit)
- FIX - przywrocony przycisk generowania linku SEO w edycji strony (zakladka SEO, pola jezykowe)
- FIX - popup potwierdzenia usuwania menu/strony ujednolicony z `table-list-confirm-dialog` + poprawione polskie znaki
- CLEANUP - usuniete legacy pliki Pages: `admin/ajax/pages.php`, `autoload/admin/controls/class.Pages.php`, `autoload/admin/factory/class.Pages.php`, `autoload/admin/view/class.Pages.php`
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.262.zip`, `ver_0.262_files.txt`
<hr><b>ver. 0.261 - 13.02.2026</b><br />
- UPDATE - finalizacja refaktoryzacji modulu `Articles` (`/admin/articles`) w warstwie Domain + DI
- UPDATE - nowe akcje AJAX w `admin\Controllers\ArticlesController` (m.in. `files_order_save`)
- UPDATE - sortowanie zalacznikow i zdjec w edycji artykulu (drag&drop + zapis kolejnosci przy pierwszym zapisie)
- UPDATE - utwardzenie uploadow (wspolny helper `libraries/plupload/upload-common.php`, walidacje i tokeny)
- FIX - potwierdzenia usuwania zdjec/zalacznikow ujednolicone z widokiem listy (jquery-confirm)
- CLEANUP - usuniete legacy `admin/ajax/articles.php` i `autoload/admin/view/class.Articles.php`
- UPDATE - pliki aktualizacji: `updates/0.20/ver_0.261.zip`, `ver_0.261_files.txt`, `ver_0.261_sql.txt`
<hr><b>ver. 0.260 - 12.02.2026</b><br />
- NEW - migracja modulu `ArticlesArchive` do architektury Domain + DI (`admin\\Controllers\\ArticlesArchiveController`)
- UPDATE - `Domain\\Article\\ArticleRepository` rozszerzone o metody `listArchivedForAdmin`, `restore`, `deletePermanently`
- UPDATE - widok `/admin/articles_archive/view_list/` przepiety z legacy `grid` na `components/table-list`
- UPDATE - routing DI (`admin\\Site`) rozszerzony o modul `ArticlesArchive` + mapowanie akcji `article_restore -> restore`
- CLEANUP - usuniete legacy klasy `autoload/admin/controls/class.ArticlesArchive.php`, `autoload/admin/factory/class.ArticlesArchive.php`, `autoload/admin/view/class.ArticlesArchive.php`
- UPDATE - plik do usuniecia dodany w `updates/0.20/ver_0.260_files.txt`
<hr><b>ver. 0.259 - 12.02.2026</b><br />
- NEW - migracja modulu `Scontainers` do architektury Domain + DI (`Domain\\Scontainers\\ScontainersRepository`, `admin\\Controllers\\ScontainersController`)
- UPDATE - widoki `/admin/scontainers/*` przepiete z legacy `grid/gridEdit` na `components/table-list` i `components/form-edit`
- UPDATE - routing DI (`admin\\Site`) rozszerzony o modul `Scontainers` + mapowanie akcji `container_edit/container_save/container_delete`
- UPDATE - `admin\\factory\\Scontainers` dziala jako fasada do repozytorium (backward compatibility)
- UPDATE - `front\\factory\\Scontainers` korzysta z `Domain\\Scontainers\\ScontainersRepository`
- CLEANUP - usuniete legacy klasy `autoload/admin/controls/class.Scontainers.php`, `autoload/admin/view/class.Scontainers.php`
- UPDATE - plik do usuniecia dodany w `updates/0.20/ver_0.259_files.txt`
<hr><b>ver. 0.258 - 12.02.2026</b><br />
- UPDATE - modul `Newsletter`: funkcjonalnosc `Wysylka - przygotowanie` zostala tymczasowo wylaczona (menu + akcje `prepare/send/preview`)
- UPDATE - modul `Newsletter`: lista `Szablony uzytkownika` zostala tymczasowo wylaczona (menu + akcja `email_templates_user`)
- UPDATE - `NewsletterController`: lista szablonow ograniczona do szablonow administracyjnych (`is_admin = 1`)
- UPDATE - `email_template_edit` i `template_save` obsluguja tylko szablony administracyjne
- CLEANUP - usuniete nieuzywane szablony newslettera: `admin/templates/newsletter/prepare.php`, `admin/templates/newsletter/preview.php`, `admin/templates/newsletter/email-templates-user.php`
- UPDATE - plik do usuniecia dodany w `updates/0.20/ver_0.258_files.txt`
<hr><b>ver. 0.257 - 12.02.2026</b><br />
- NEW - migracja modulu `Newsletter` do architektury Domain + DI (`Domain\\Newsletter\\NewsletterRepository`, `Domain\\Newsletter\\NewsletterPreviewRenderer`, `admin\\Controllers\\NewsletterController`)
- UPDATE - widoki `/admin/newsletter/*` przepiete z legacy `grid/gridEdit` na nowe komponenty (`components/table-list`, `components/form-edit`) + nowy endpoint `/admin/newsletter/preview/`
- UPDATE - routing DI (`admin\\Site`) rozszerzony o moduĹ `Newsletter`
- UPDATE - `admin\\factory\\Newsletter` dziala jako fasada do nowego repozytorium (backward compatibility)
- UPDATE - `front\\factory\\Newsletter` nie korzysta juz z `admin\\view\\Newsletter`
- CLEANUP - usuniete legacy klasy `autoload/admin/controls/class.Newsletter.php`, `autoload/admin/view/class.Newsletter.php`
<hr><b>ver. 0.256 - 12.02.2026</b><br />
- NEW - migracja modulu `Layouts` do architektury Domain + DI (`Domain\\Layouts\\LayoutsRepository`, `admin\\Controllers\\LayoutsController`)
- UPDATE - lista `/admin/layouts/view_list/` przepieta z legacy `grid` na `components/table-list` (filtry, sortowanie, paginacja)
- UPDATE - `layouts/layout-edit` korzysta z danych z repozytorium (menus/categories), bez wywolan legacy factory w widoku
- UPDATE - `Domain\\Languages\\LanguagesRepository` rozszerzone o wspolna metode `defaultLanguageId()`
- UPDATE - `admin\\Controllers\\ArticlesController` pobiera layouty przez `Domain\\Layouts\\LayoutsRepository` (DI)
- CLEANUP - usuniete legacy klasy `autoload/admin/controls/class.Layouts.php`, `autoload/admin/view/class.Layouts.php`
<hr><b>ver. 0.255 - 12.02.2026</b><br />
- UPDATE - kontrolery admin `Settings`, `Banners`, `Dictionaries`, `Articles` pobieraja liste jezykow przez `Domain\\Languages\\LanguagesRepository` (DI)
- UPDATE - routing DI (`admin\\Site`) przekazuje `LanguagesRepository` do kontrolerow `Articles`, `Banners`, `Settings`, `Dictionaries`
- UPDATE - aktywne legacy odwolania (`admin\\controls`, `admin\\factory\\Shop*`) przepiete z `admin\\factory\\Languages` na `LanguagesRepository`
- FIX - `autoload/admin/factory/class.Languages.php` uzywa pelnego znacznika PHP (zgodnosc z `short_open_tag=Off`)
<hr><b>ver. 0.254 - 12.02.2026</b><br />
- UPDATE - modul `Languages` w panelu admin przepiety na `Domain\\Languages\\LanguagesRepository` + `admin\\Controllers\\LanguagesController`
- UPDATE - migracja widokow languages (`languages-list`, `language-edit`, `translations-list`, `translation-edit`) na `components/table-list` i `components/form-edit`
- UPDATE - routing DI dla `Languages` w `admin\\Site` oraz kompatybilna fasada `admin\\factory\\Languages` delegujaca do repozytorium
- UPDATE - naprawiono zapis edycji jezyka (ID jezyka pobierane z URL przy edycji)
- UPDATE - globalne poprawki UX filtrĂłw w `components/table-list` (kompaktowe kolumny `Aktywny`/`Domyslny`, spacing i pelna szerokosc selecta)
- CLEANUP - usuniete legacy klasy: `autoload/admin/controls/class.Languages.php`, `autoload/admin/view/class.Languages.php`
<hr>
<b>ver. 0.253 - 12.02.2026</b><br />
- UPDATE - modul `Users` w panelu admin w pelni przepiety na `Domain\\User\\UserRepository` + `admin\\Controllers\\UsersController`
- UPDATE - migracja widokow users z `grid/gridEdit` na nowe komponenty (`components/table-list`, `components/form-edit`)
- UPDATE - dodana walidacja warunkowa: przy wlaczonym 2FA pole `E-mail do 2FA` jest wymagane
- UPDATE - globalne ulepszenia `components/table-list` (kompaktowe filtry select/status i odstepy w formularzu paginacji)
- CLEANUP - usuniete legacy klasy users: `autoload/admin/controls/class.Users.php`, `autoload/admin/factory/class.Users.php`, `autoload/admin/view/class.Users.php`
<hr>
<b>ver. 0.252 - 10.02.2026</b><br />
- UPDATE - migracja listy archiwum produktow do nowego komponentu tabeli (`components/table-list`) z filtrowaniem i paginacja
- UPDATE - banery i archiwum produktow: wydzielenie CSS/JS do osobnych widokow `*-custom-script.php`
- UPDATE - filemanager przepiety na nowy routing (`admin\\Controllers\\FilemanagerController`)
- FIX - naprawiono blad `Invalid Key` w widoku filemanagera po refaktoryzacji
- UPDATE - usunieto legacy klasy i stare szablony (`admin\\controls`, `admin\\view`, `admin/templates/product_archive`)
<hr>
<b>ver. 0.251 - 09.02.2026</b><br />
- NEW - migracja modulu Dictionaries do nowej architektury (Domain + admin Controller + DI)
- UPDATE - lista i formularz Dictionaries przepiete na nowe komponenty (`components/table-list`, `components/form-edit`)
- UPDATE - dodano globalne ograniczenie szerokosci pierwszej kolumny (Lp.) w `components/table-list`
- FIX - zapis tlumaczen jednostek obsluguje `lang_id` jako string (`pl`, `en`)
- UPDATE - usunieto legacy klasy Dictionaries: `admin\\controls`, `admin\\factory`, `front\\factory`
- UPDATE - przepieto uzycia na `Domain\\Dictionaries\\DictionariesRepository` (`shop-product`, `shop_product` admin)
<hr>
<b>ver. 0.250</b><br />
- UPDATE - refaktoryzacja Settings: `Domain\\Settings\\SettingsRepository` ma bezposredni dostep do bazy (bez delegacji do `admin\\factory\\Settings`)
- UPDATE - przepieto pozostale uzycia `admin\\factory\\Settings` na `Domain\\Settings\\SettingsRepository` (`admin\\controls\\Settings`, `admin\\controls\\Newsletter`, `front\\factory\\Newsletter`)
- UPDATE - DI dla SettingsController: repozytorium otrzymuje `$mdb` w `admin\\Site`
- UPDATE - Settings: widok edycji przeniesiony na nowy mechanizm formularza (`FormEditViewModel` + `components/form-edit`) jak w banerach
- UPDATE - usunieto nieuzywana legacy klase `autoload/admin/factory/class.Settings.php`
- UPDATE - usunieto legacy fallback kontrolera `autoload/admin/controls/class.Settings.php`
- UPDATE - usunieto nieuzywana klase widoku `autoload/admin/view/class.Settings.php`
<hr><b>ver. 0.249</b><br />
- FIX - banner edit: poprawiono zapisywanie danych jezykowych i synchronizacje CKEditor przed zapisem
- FIX - banner edit: naprawiono hash zakladek (usunieto `undefined` w URL)
- FIX - filemanager: przywrocono dzialanie popupa wyboru obrazka z banera
- UPDATE - komunikaty zapisu w nowym formularzu sa wyswietlane w stylu panelu (bez natywnego alertu JS)
- UPDATE - lista banerow: dodano kolumne miniatury oraz podglad duzego obrazka w popup po najechaniu
- UPDATE - usunieto nieuzywane legacy klasy banerow: `admin\\view\\Banners`, `admin\\factory\\Banners`
<hr><b>ver. 0.248</b><br />
- UPDATE - filtry w nowych tabelach dzialaja automatycznie na `onchange`
- UPDATE - `components/table-list`: auto-submit formularza filtrow po zmianie pola (select, date, text)
<hr><b>ver. 0.247</b><br />
- UPDATE - nowy dialog potwierdzenia usuwania w `components/table-list` (zamiast natywnego `confirm`)
- UPDATE - popup usuwania: wiekszy rozmiar i centrowanie na srodku ekranu
<hr><b>ver. 0.246</b><br />
- UPDATE - migracja listy banerow do nowego mechanizmu tabeli (`components/table-list`, filtrowanie, sortowanie, paginacja)
- UPDATE - `admin\Controllers\BannerController::list()` buduje `PaginatedTableViewModel`
- UPDATE - `Domain\Banner\BannerRepository::listForAdmin()` (bezpieczne filtrowanie i sortowanie)
- UPDATE - usunieto legacy kontroler `autoload/admin/controls/class.Banners.php`
- UPDATE - plik do usuniecia dodany w `updates/0.20/ver_0.246_files.txt`
<hr><b>ver. 0.245</b><br />
- UPDATE - refaktoryzacja listy artykulow: wspolny komponent `admin/templates/components/table-list.php` + `PaginatedTableViewModel`
- NEW - `admin\Support\TableListRequestFactory` (wspolna obsluga filtrow, sortowania i paginacji dla list)
- UPDATE - `Domain\Article\ArticleRepository::listForAdmin()` utwardzone pod katem bezpieczenstwa (whitelist sortowania, bind params, limit per_page)
- UPDATE - usunieto legacy `browse_list` dla modulu Articles
- UPDATE - usuniete pliki legacy sa wyszczegolnione w `updates/0.20/ver_0.245_files.txt`
- FIX - generator `.htaccess` i `libraries/htaccess.conf` (QSA dla `/admin/...`, komentarz niedozwolonych dyrektyw `SetHandler/AddHandler/ForceType`)
<hr><b>ver. 0.244</b><br />
- UPDATE - refaktoryzacja: article_save przeniesiony do Domain\Article\ArticleRepository::save() z prywatnymi helperami
- UPDATE - refaktoryzacja: article_delete przeniesiony do Domain\Article\ArticleRepository::archive()
- UPDATE - ArticlesController: nowe akcje save() i delete() z DI
- UPDATE - admin\factory\Articles::article_save() i articles_set_archive() delegujÄ… do repozytorium (kompatybilnoĹć)
<hr><b>ver. 0.243</b><br />
- UPDATE - refaktoryzacja: cleanup nieprzypisanych plikĂłw/zdjęć artykuĹĂłw przeniesiony do Domain\Article\ArticleRepository
- UPDATE - ArticlesController::edit() uĹĽywa repozytorium do cleanupu, a admin\factory\Articles zachowuje delegowanie (kompatybilnoĹć)
<hr><b>ver. 0.242</b><br />
- NEW - refaktoryzacja: Domain\Article\ArticleRepository + migracja article_edit do admin\Controllers\ArticlesController (DI)
- UPDATE - admin\factory\Articles::article_details() deleguje do nowego repozytorium (kompatybilnoĹć zachowana)
- UPDATE - metody przejęte przez nowe kontrolery oznaczone jako @deprecated w legacy kontrolerach admin\controls
<hr><b>ver. 0.241</b><br />
- NEW - refaktoryzacja: admin\Controllers\ProductArchiveController - archiwum produktĂłw z DI
- NEW - ProductRepository::archive(), unarchive() - operacje archiwizacji w repozytorium
- FIX - naprawiono SQL w liĹcie archiwum (puste wyszukiwanie filtrowaĹo wszystkie wyniki)
- FIX - naprawiono brakujÄ…cy filtr archive = 1 w zapytaniu bez wyszukiwania
- UPDATE - wyczyszczono szablony archiwum (usunięto zbędne funkcje: apilo, baselinker, duplikowanie)
<hr>
<b>ver. 0.240</b><br />
- NEW - refaktoryzacja: Domain\Settings\SettingsRepository + admin\Controllers\SettingsController (architektura Domain-Driven)
- NEW - refaktoryzacja: Domain\Cache\CacheRepository - czyszczenie cache z obsĹugÄ… Redis
- FIX - komunikat potwierdzenia zapisu ustawień w panelu administratora
- FIX - naprawiono element #content w layoucie admina (powiadomienia grid.js)
<hr><b>ver. 0.239</b><br />
- NEW - refaktoryzacja: Domain\Banner\BannerRepository + admin\Controllers\BannerController (peĹna migracja kontrolera)
- NEW - refaktoryzacja: Domain\Product\ProductRepository::getPrice(), getName() - migracja kolejnych metod
- NEW - router admin z obsĹugÄ… nowych kontrolerĂłw (fallback na stare)
- UPDATE - shop\Product::get_product_price(), get_product_name() uĹĽywajÄ… nowego repozytorium (kompatybilnoĹć zachowana)
<hr>
<b>ver. 0.238</b><br />
- NEW - refaktoryzacja: Domain\Product\ProductRepository - pierwsza klasa w nowej architekturze Domain-Driven
- NEW - Dependency Injection zamiast global variables
- UPDATE - shop\Product::get_product_quantity() uĹĽywa teraz nowego repozytorium (kompatybilnoĹć zachowana)
<hr>
<b>ver. 0.237</b><br />
- NEW - automatyczne czyszczenie cache produktu po aktualizacji przez CRON (Sellasist, Apilo, Baselinker)
- UPDATE - przycisk "WyczyĹć cache" w panelu administratora z obsĹugÄ… AJAX i komunikatami o postÄ™pie
<hr>
<b>ver. 0.236</b><br />
- FIX - zabezpieczenie przed duplikatami zamówień w Apilo - automatyczne pobieranie ID zamówienia przy ędzie "idExternal już wykorzystywany"
<hr>
<b>ver. 0.235</b><br />
- FIX - poprawka funkcji aktualizacji
<hr>
<b>ver. 0.234</b><br />
- NEW - przycisk zaznaczania zamĂłwienia jako wysĹane do trustmate.io
<hr>
<b>ver. 0.232</b><br />
- NEW - opcje GPSR
<hr>
<b>ver. 0.231</b><br />
- FIX - poprawki bezpieczeństwa + dwuetapowa weryfikacja logowania
<hr>
<b>ver. 0.230</b><br />
- FIX - poprawki bezpieczeństwa
<hr>
<b>ver. 0.229</b><br />
- NEW - pola dodatkowe z opcjÄ… wymagane/niewymagane
<hr>
<b>ver. 0.228</b><br />
- NEW - cron do wysyĹania zamĂłwieĹ„ do trustmate.io
<hr>
<b>ver. 0.227</b><br />
- NEW - historia kodĂłw rabatowych
<hr>
<b>ver. 0.226</b><br />
- NEW - dodanie opcji faktury do zamĂłwienia
<hr>
<b>ver. 0.225</b><br />
- NEW - przycisk czyszczenia cache<br>
- NEW - ponowne wysyĹanie zamĂłwienia do apilo
<hr>
<b>ver. 0.224</b><br />
- NEW - sortowanie form dostawy
<hr>
<b>ver. 0.223</b><br />
- FIX - integracja z Orlen Paczka
<hr>
<b>ver. 0.222</b><br />
- NEW - integracja z Orlen Paczka
<hr>
<b>ver. 0.221</b><br />
- NEW - Automatyczne przekierowania adresĂłw URL produktĂłw, zmiany w pliku htaccess
<hr>
<b>ver. 0.220</b><br />
- NEW - Dodanie moĹĽliwoĹci wyĹwietlenia na strone ostatnio dodane produkty [PRODUKTY_NEW] lub [PRODUKTY_NEW:10].<br>
- NEW - Dodanie moĹĽliwoĹci wyĹwietlenia na strone popularnych produktĂłw [PRODUKTY_TOP] lub [PRODUKTY_TOP:10].
<hr>
<b>ver. 0.219</b><br />
- NEW - Dodanie moĹĽliwoĹci zmiany daty w artykuĹach
<hr>
<b>ver. 0.218</b><br />
- NEW - indywidualny kod GTM
<hr>
<b>ver. 0.217</b><br />
- NEW - zwiÄ™kszenie obsĹugi REDIS
<hr>
<b>ver. 0.216</b><br />
- NEW - aktualizacja api i cron (apilo)
<hr>
<b>ver. 0.215</b><br />
- FIX - generowanie pliku .htaccess
<hr>
<b>ver. 0.214</b><br />
- NEW - dodanie API
<hr>
<b>ver. 0.213</b><br />
- FIX - wyliczenie darmowej dostawy
<hr>
<b>ver. 0.212</b><br />
- NEW - zmiany w zapisywaniu zamĂłwienia do apilo
<hr>
<b>ver. 0.211</b><br />
- NEW - Debugowanie apilo + wyĹwietlanie podkategorii
<hr>
<b>ver. 0.210</b><br />
- NEW - dodatkowe pola w widoku produktĂłw
<hr>
<b>ver. 0.209</b><br />
- NEW - zmiany w widoku produktĂłw (panel administratora)
<hr>
<b>ver. 0.208</b><br />
- NEW - zmiany w wyszukiwarce produktĂłw
<hr>
<b>ver. 0.204-0.207</b><br />
- NEW - htaccess update
<hr>
<b>ver. 0.204-0.206</b><br />
- NEW - wysyĹanie produktĂłw do apilo
<hr>
<b>ver. 0.203</b><br />
- NEW - zmiana sposobu wyliczania cen produkty z dodatkami
<hr>
<b>ver. 0.202</b><br />
- NEW - dodano "Ăłwne zdjÄ™cie" w edycji artykuĹu
<hr>
<b>ver. 0.201</b><br />
- FIX - aktualizacja statusĂłw na podstawie baselinkera
<hr>
<b>ver. 0.200</b><br />
- NEW - wysyĹanie produktĂłw do baselinker
<hr>
<b>ver. 0.199</b><br />
- NEW - usprawnienie edycji danych do XML
<hr>
<b>ver. 0.198</b><br />
- NEW - automatyczne generowanie kodĂłw SKU
<hr>
<b>ver. 0.197</b><br />
- FIX - poprawki w Dashboard
<hr>
<b>ver. 0.196</b><br />
- FIX - integracja z apilo.com
<hr>
<b>ver. 0.195</b><br />
- FIX - aktualizacja statusĂłw
<hr>
<b>ver. 0.194</b><br />
- UPDATE - integracja apilo
<hr>
<b>ver. 0.193</b><br />
- UPDATE - aktualizacja synchronizacji z baselinker
<hr>
<b>ver. 0.192</b><br />
- NEW - pobieranie statusĂłw z sellasist
<hr>
<b>ver. 0.191</b><br />
- NEW - integracja z selasist
<hr>
<b>ver. 0.190</b><br />
- FIX - produkty powiÄ…zane
<hr>
<b>ver. 0.189</b><br />
- FIX - ceny promocyjne produktĂłw z dodatkiem
<hr>
<b>ver. 0.188</b><br />
- NEW - widok listy produktĂłw
<hr>
<b>ver. 0.187</b><br />
- FIX - pobieranie cen z APILO
<hr>
<b>ver. 0.186</b><br />
- FIX - dodawanie do koszyka tych samych produktów ale z różną personalizacją
<hr>
<b>ver. 0.185</b><br />
- FIX - masowa edycja produktĂłw
<hr>
<b>ver. 0.184</b><br />
- NEW - druga czÄ™Ĺć integracji z apilo, masowa edycja produktĂłw
<hr>
<b>ver. 0.183</b><br />
- NEW - pierwsza czÄ™Ĺć integracji z apilo
<hr>
<b>ver. 0.182</b><br />
- FIX - layout
<hr>
<b>ver. 0.181</b><br />
- NEW - infinitescroll - opcja Ä…czy/wyĹÄ…cz
<hr>
<b>ver. 0.180</b><br />
- NEW - aktualizacja dashboard
<hr>
<b>ver. 0.179</b><br />
- NEW - obsĹuga EAN
<hr>
<b>ver. 0.177, 0.178</b><br />
- FIX - custom_label
<hr>
<b>ver. 0.176</b><br />
- NEW - custom_label
<hr>
<b>ver. 0.175</b><br />
- NEW - nowe statystyki
<hr>
<b>ver. 0.174</b><br />
- FIX - generowanie xml
<hr>
<b>ver. 0.173</b><br />
- NEW - duplikowanie produktu wraz z kombinacjami
- NEW - dodanie przechodzenia pomiędzy zamówienia (poprzednie/następne zamówienie)
<hr>
<b>ver. 0.172</b><br />
- FIX - poprawki w Cache
<hr>
<b>ver. 0.171</b><br />
- FIX - poprawki w Cache
<hr>
<b>ver. 0.170</b><br />
- NEW - usuwanie cache produktu przy zapisie
<hr>
<b>ver. 0.169</b><br />
- FIX - poprawki w liĹcie produktĂłw
<hr>
<b>ver. 0.168</b><br />
- NEW - archiwum produktĂłw
<hr>
<b>ver. 0.167</b><br />
- NEW - dodanie obsĹugi cen i stanĂłw magazynowych kombinacji produktĂłw
<hr>
<b>ver. 0.166</b><br />
- NEW - wspĂłĹpraca z GTM
<hr>
<b>ver. 0.164/5</b><br />
- FIX - ukrywanie produktĂłw nieaktywnych
<hr>
<b>ver. 0.163</b><br />
- NEW - automatyczne podpowiadanie produktĂłw do zestawu na podstawie wczeĹniejszych zakupĂłw klientĂłw
<hr>
<b>ver. 0.162</b><br />
- NEW - GA4
<hr>
<b>ver. 0.161</b><br />
- UPDATE - aktualizacja menu administratora
<hr>
<b>ver. 0.160</b><br />
- UPDATE - aktualizacja cron Baselinker
- NEW - waga i cena jednostkowa produktu
<hr>
<b>ver. 0.159</b><br />
- FIX - cron Baselinker
<hr>
<b>ver. 0.158</b><br />
- UPDATE - poprawa kolorystyki przyciskĂłw
<hr>
<b>ver. 0.157</b><br />
- NEW - szybka zmiana statusu produktu
<hr>
<b>ver. 0.156</b><br />
- NEW - dodanie szybkiej edycji google xml label
<hr>
<b>ver. 0.155</b><br />
- NEW - infinite scroll w widoku kategorii
<hr>
<b>ver. 0.154</b><br />
- FIX - atrybuty produktĂłw
<hr>
<b>ver. 0.153</b><br />
- FIX - atrybuty produktĂłw
<hr>
<b>ver. 0.152</b><br />
- FIX - tematy maili
<hr>
<b>ver. 0.151</b><br />
- FIX - tematy maili
<hr>
<b>ver. 0.150</b><br />
- NEW - domyĹlna forma transportu
<hr>
<b>ver. 0.149</b><br />
- NEW - tematy maili
<hr>
<b>ver. 0.148</b><br />
- FIX - cron-xml
<hr>
<b>ver. 0.147</b><br />
- FIX - cron-xml
<hr>
<b>ver. 0.146</b><br />
- NEW - cron-xml
<hr>
<b>ver. 0.145</b><br />
- NEW - omnibus ready
<hr>
<b>ver. 0.144</b><br />
- FIX - usunięcie adresu marianek.pl z kodu
<hr>
<b>ver. 0.143</b><br />
- FIX - poprawa generowania plikĂłw WEBP
<hr>
<b>ver. 0.142</b><br />
- FIX - poprawa adresu strony Ăłwnej

View File

@@ -1,5 +1,5 @@
<? <?
$current_ver = 302; $current_ver = 307;
for ($i = 1; $i <= $current_ver; $i++) for ($i = 1; $i <= $current_ver; $i++)
{ {