ver. 0.277: ShopProduct factory, Dashboard, Update migration, legacy cleanup, admin\App
- ShopProduct factory: full migration (~40 ProductRepository methods, ~30 controller actions) - Dashboard: Domain+DI migration (DashboardRepository + DashboardController) - Update: Domain+DI migration (UpdateRepository + UpdateController, template rewrite) - Renamed admin\Site to admin\App, removed dead fallback routing - Removed all legacy folders: admin/controls, admin/factory, admin/view - Newsletter: switched from admin\factory\Articles to ArticleRepository - 414 tests, 1335 assertions passing Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@ Gdy użytkownik napisze `KONIEC PRACY`, wykonaj kolejno:
|
||||
- `docs/FORM_EDIT_SYSTEM.md`
|
||||
- `docs/CHANGELOG.md`
|
||||
- `docs/TESTING.md`
|
||||
3. Przygotowanie aktualizacji (ZIP, plik z usuwanymi plikami, plik SQL jeśli wymagany).
|
||||
3. Przygotowanie aktualizacji zgodnie z plikiem UPDATE_INSTRUCTIONS.md (ZIP, plik z usuwanymi plikami, plik SQL jeśli wymagany).
|
||||
4. Commit.
|
||||
5. Push.
|
||||
|
||||
@@ -31,4 +31,4 @@ To ma pomóc zachować spójność zmian i dokumentacji.
|
||||
|
||||
## INNE
|
||||
|
||||
Przejdźmy teraz do refaktoringu wszystkiego co związane z https://shoppro.project-dc.pl/admin/shop_product/mass_edit/, nowe widoki, klasy (usuwanie starych), poprawa routingu, przeszukanie innych klas pod względem zależności. Zapisz plan i przedstaw mi go a po akceptacji realizuj krok po kroku w trybie Human In The Loop
|
||||
Przejdźmy teraz do refaktoringu wszystkiego co związane z https://shoppro.project-dc.pl/admin/shop_product/, nowe widoki, klasy (usuwanie starych), poprawa routingu, przeszukanie innych klas pod względem zależności. Zapisz plan i przedstaw mi go a po akceptacji realizuj krok po kroku w trybie Human In The Loop
|
||||
@@ -39,5 +39,4 @@ $mdb = new medoo( [
|
||||
|
||||
require_once 'ajax/shop-category.php';
|
||||
require_once 'ajax/users.php';
|
||||
require_once 'ajax/shop.php';
|
||||
?>
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
<?php
|
||||
if ( $a == 'cookie_categories' )
|
||||
{
|
||||
$array = unserialize( $_COOKIE[ 'cookie_categories' ] );
|
||||
|
||||
if ( $array[ \S::get( 'category_id' ) ] == 0 )
|
||||
$array[ \S::get( 'category_id' ) ] = 1;
|
||||
else
|
||||
$array[ \S::get( 'category_id' ) ] = 0;
|
||||
|
||||
$array = serialize( $array );
|
||||
|
||||
setcookie( 'cookie_categories', $array, time() + 3600 * 24 * 365 );
|
||||
}
|
||||
|
||||
if ( $a == 'save_categories_order' )
|
||||
{
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania kolejności kategorii wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
$categoryRepository = new \Domain\Category\CategoryRepository( $mdb );
|
||||
|
||||
if ( $categoryRepository->saveCategoriesOrder( \S::get( 'categories' ) ) )
|
||||
$response = [ 'status' => 'ok' ];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( $a == 'product_file_delete' )
|
||||
{
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas usuwania załącznika wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
if ( \admin\factory\ShopProduct::delete_file( \S::get( 'file_id' ) ) )
|
||||
$response = [ 'status' => 'ok' ];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( $a == 'product_file_name_change' )
|
||||
{
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas zmiany nazwy załącznika wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
if ( \admin\factory\ShopProduct::file_name_change( \S::get( 'file_id' ), \S::get( 'file_name' ) ) )
|
||||
$response = [ 'status' => 'ok' ];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( $a == 'product_image_delete' )
|
||||
{
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas usuwania zdjecia wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
if ( \admin\factory\ShopProduct::delete_img( \S::get( 'image_id' ) ) )
|
||||
$response = [ 'status' => 'ok' ];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
@@ -89,8 +89,8 @@ $mdb = new medoo( [
|
||||
|
||||
$user = \S::get_session( 'user', true );
|
||||
|
||||
\admin\Site::update();
|
||||
\admin\Site::special_actions();
|
||||
\admin\App::update();
|
||||
\admin\App::special_actions();
|
||||
|
||||
$domain = preg_replace( '/^www\./', '', $_SERVER['SERVER_NAME'] );
|
||||
$cookie_name = 'admin_remember_' . str_replace( '.', '-', $domain );
|
||||
@@ -102,7 +102,7 @@ if ( isset( $_COOKIE[$cookie_name] ) && !isset( $_SESSION['user'] ) )
|
||||
if ($payload !== false && strpos($payload, '.') !== false)
|
||||
{
|
||||
list($json, $sig) = explode('.', $payload, 2);
|
||||
$expected_sig = hash_hmac('sha256', $json, \admin\Site::APP_SECRET_KEY);
|
||||
$expected_sig = hash_hmac('sha256', $json, \admin\App::APP_SECRET_KEY);
|
||||
|
||||
if (hash_equals($expected_sig, $sig))
|
||||
{
|
||||
@@ -135,5 +135,5 @@ if ( isset( $_COOKIE[$cookie_name] ) && !isset( $_SESSION['user'] ) )
|
||||
]);
|
||||
}
|
||||
|
||||
echo \admin\view\Page::show();
|
||||
echo \admin\App::render();
|
||||
?>
|
||||
|
||||
@@ -160,7 +160,7 @@ $orderId = (int)($this -> order['id'] ?? 0);
|
||||
<tbody>
|
||||
<? if ( is_array( $this -> order[ 'products' ] ) ): foreach ( $this -> order[ 'products' ] as $product ):?>
|
||||
<?
|
||||
if ( $id = \admin\factory\ShopProduct::get_product_parent_id( $product['product_id'] ) )
|
||||
if ( $id = ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->getParentId( $product['product_id'] ) )
|
||||
$product_id = $id;
|
||||
else
|
||||
$product_id = $product['product_id'];
|
||||
|
||||
@@ -68,8 +68,8 @@
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/ajax.php',
|
||||
data: { a: 'cookie_categories', category_id: category_id }
|
||||
url: '/admin/shop_category/cookie_categories/',
|
||||
data: { category_id: category_id }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<div class="g-container" data="table:order-details">
|
||||
<div class="panel panel-info panel-border top">
|
||||
<div class="panel-heading">
|
||||
<span class="panel-title">Kobinacje produktu: <?= $this -> product['languages'][ $this -> default_language ]['name'];?></span>
|
||||
<span class="panel-title">Kombinacje produktu: <?= $this -> product['languages'][ $this -> default_language ]['name'];?></span>
|
||||
</div>
|
||||
<div class="panel-heading p10 pl15" id="g-menu" style="height: auto;">
|
||||
<a class="btn btn btn-dark btn-sm mr5 btn-sm mr5" href="/admin/shop_product/view_list/"><i class="fa fa-reply mr5"></i>Wstecz</a>
|
||||
@@ -13,19 +13,17 @@
|
||||
<table class="table table-hover table-bordered table-condensed">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Kombinacja</th>
|
||||
<th>SKU</th>
|
||||
<th>Stan magazynowy</th>
|
||||
<th>Cena netto</th>
|
||||
<th>Zam. SM 0</th>
|
||||
<th>Zam. przy braku</th>
|
||||
<th style="width: 100px;">Opcje</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<? if ( \S::is_array_fix( $this -> product_permutations ) ): foreach ( $this -> product_permutations as $product ):?>
|
||||
<tr>
|
||||
<td></td>
|
||||
<tr data-combination-id="<?= $product['id'];?>">
|
||||
<td>
|
||||
<?
|
||||
$attributes = explode( '|', $product['permutation_hash'] );
|
||||
@@ -38,19 +36,19 @@
|
||||
?>
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="sku" value="<?= $product['sku'];?>" class="form-control" style="max-width: 100px;" onchange="$.ajax({ type: 'POST', cache: false, url: '/admin/shop_product/product_combination_sku_save/', data: { product_id: <?= $product['id'];?>, sku: $( this ).val() } } );">
|
||||
<input type="text" value="<?= $product['sku'];?>" class="form-control combination-field" style="max-width: 100px;" data-product-id="<?= $product['id'];?>" data-field="sku">
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="quantity" value="<?= $product['quantity'];?>" class="form-control" style="max-width: 100px;" onchange="$.ajax({ type: 'POST', cache: false, url: '/admin/shop_product/product_combination_quantity_save/', data: { product_id: <?= $product['id'];?>, quantity: $( this ).val() } } );">
|
||||
<input type="text" value="<?= $product['quantity'];?>" class="form-control combination-field" style="max-width: 100px;" data-product-id="<?= $product['id'];?>" data-field="quantity">
|
||||
</td>
|
||||
<td>
|
||||
<input type="text" name="price" value="<?= $product['price_netto'];?>" class="form-control" style="max-width: 100px;" onchange="$.ajax({ type: 'POST', cache: false, url: '/admin/shop_product/product_combination_price_save/', data: { product_id: <?= $product['id'];?>, price: $( this ).val() } } );">
|
||||
<input type="text" value="<?= $product['price_netto'];?>" class="form-control combination-field" style="max-width: 100px;" data-product-id="<?= $product['id'];?>" data-field="price">
|
||||
</td>
|
||||
<td>
|
||||
<input type="checkbox" name="stock_0_buy" <? if ( $product['stock_0_buy'] ): echo 'checked="checked"'; endif;?> onchange="$.ajax({ type: 'POST', cache: false, url: '/admin/shop_product/product_combination_stock_0_buy_save/', data: { product_id: <?= $product['id'];?>, stock_0_buy: $( this ).is( ':checked' ) } } );">
|
||||
<input type="checkbox" class="g-checkbox combination-checkbox" data-product-id="<?= $product['id'];?>" <? if ( $product['stock_0_buy'] ): echo 'checked="checked"'; endif;?>>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href="/admin/shop_product/delete_combination/combination_id=<?= $product['id'];?>&product_id=<?= $product['parent_id'];?>" class="btn btn-danger btn-delete-permutation"><i class="fa fa-trash"></i></a>
|
||||
<button type="button" class="btn btn-danger btn-delete-permutation" data-combination-id="<?= $product['id'];?>"><i class="fa fa-trash"></i></button>
|
||||
</td>
|
||||
</tr>
|
||||
<? endforeach; endif;?>
|
||||
@@ -65,6 +63,7 @@
|
||||
<div class="combination-attribute">
|
||||
<div class="title">
|
||||
<?= $attribute['languages'][ $this -> default_language ]['name'];?>
|
||||
<label style="float: right; font-weight: normal; font-size: 12px; cursor: pointer;"><input type="checkbox" class="g-checkbox select-all-attr"> wszystkie</label>
|
||||
</div>
|
||||
<ul class="values">
|
||||
<? foreach ( $attribute['values'] as $value ):?>
|
||||
@@ -92,10 +91,90 @@
|
||||
radioClass: 'iradio_minimal-blue'
|
||||
});
|
||||
|
||||
// "Zaznacz wszystkie" per atrybut
|
||||
$( '.select-all-attr' ).on( 'ifChanged', function()
|
||||
{
|
||||
var checked = $( this ).is( ':checked' );
|
||||
$( this ).closest( '.combination-attribute' ).find( '.g-checkbox' ).each( function()
|
||||
{
|
||||
$( this ).iCheck( checked ? 'check' : 'uncheck' );
|
||||
});
|
||||
});
|
||||
|
||||
// Inline save — SKU, ilość, cena
|
||||
var fieldUrlMap = {
|
||||
'sku': '/admin/shop_product/product_combination_sku_save/',
|
||||
'quantity': '/admin/shop_product/product_combination_quantity_save/',
|
||||
'price': '/admin/shop_product/product_combination_price_save/'
|
||||
};
|
||||
|
||||
$( 'body' ).on( 'change', '.combination-field', function()
|
||||
{
|
||||
var $input = $( this );
|
||||
var field = $input.data( 'field' );
|
||||
var data = { product_id: $input.data( 'product-id' ) };
|
||||
data[ field ] = $input.val();
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: fieldUrlMap[ field ],
|
||||
data: data,
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( data ) {
|
||||
$( '#overlay' ).hide();
|
||||
var response = jQuery.parseJSON( data );
|
||||
if ( response.status === 'ok' ) {
|
||||
$input.css( 'border-color', '#1cbb8c' );
|
||||
setTimeout( function() { $input.css( 'border-color', '' ); }, 1500 );
|
||||
} else {
|
||||
$input.css( 'border-color', '#ff3d60' );
|
||||
setTimeout( function() { $input.css( 'border-color', '' ); }, 1500 );
|
||||
if ( response.msg ) create_error( response.msg );
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$( '#overlay' ).hide();
|
||||
$input.css( 'border-color', '#ff3d60' );
|
||||
setTimeout( function() { $input.css( 'border-color', '' ); }, 1500 );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Inline save — checkbox stock_0_buy (iCheck event)
|
||||
$( 'body' ).on( 'ifChanged', '.combination-checkbox', function()
|
||||
{
|
||||
var $cb = $( this );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/product_combination_stock_0_buy_save/',
|
||||
data: { product_id: $cb.data( 'product-id' ), stock_0_buy: $cb.is( ':checked' ) },
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( data ) {
|
||||
$( '#overlay' ).hide();
|
||||
var response = jQuery.parseJSON( data );
|
||||
if ( response.status !== 'ok' && response.msg ) {
|
||||
create_error( response.msg );
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$( '#overlay' ).hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Usuwanie kombinacji — AJAX
|
||||
$( 'body' ).on( 'click', '.btn-delete-permutation', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var href = $( this ).attr( 'href' );
|
||||
var combinationId = $( this ).data( 'combination-id' );
|
||||
var $row = $( this ).closest( 'tr' );
|
||||
|
||||
$.alert(
|
||||
{
|
||||
@@ -119,14 +198,33 @@
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-danger',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = href;
|
||||
action: function()
|
||||
{
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/delete_combination_ajax/',
|
||||
data: { combination_id: combinationId },
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( data ) {
|
||||
$( '#overlay' ).hide();
|
||||
var response = jQuery.parseJSON( data );
|
||||
if ( response.status === 'ok' ) {
|
||||
$row.fadeOut( 300, function() { $( this ).remove(); } );
|
||||
} else {
|
||||
if ( response.msg ) create_error( response.msg );
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
$( '#overlay' ).hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
631
admin/templates/shop-product/product-edit-custom-script.php
Normal file
631
admin/templates/shop-product/product-edit-custom-script.php
Normal file
@@ -0,0 +1,631 @@
|
||||
<?php
|
||||
$product = is_array($this->product ?? null) ? $this->product : [];
|
||||
$productId = (int)($product['id'] ?? 0);
|
||||
$userId = (int)($this->user['id'] ?? 0);
|
||||
$imagesCount = is_array($product['images'] ?? null) ? count($product['images']) : 0;
|
||||
$filesCount = is_array($product['files'] ?? null) ? count($product['files']) : 0;
|
||||
|
||||
$imageMaxPx = 1920;
|
||||
if (isset($GLOBALS['settings']['image_px']) && (int)$GLOBALS['settings']['image_px'] > 0) {
|
||||
$imageMaxPx = (int)$GLOBALS['settings']['image_px'];
|
||||
}
|
||||
|
||||
$uploadToken = bin2hex(random_bytes(24));
|
||||
if (!isset($_SESSION['upload_tokens']) || !is_array($_SESSION['upload_tokens'])) {
|
||||
$_SESSION['upload_tokens'] = [];
|
||||
}
|
||||
$_SESSION['upload_tokens'][$uploadToken] = [
|
||||
'user_id' => $userId,
|
||||
'expires' => time() + 60 * 20,
|
||||
];
|
||||
|
||||
$cookieCategories = [];
|
||||
if (!empty($_COOKIE['cookie_categories'])) {
|
||||
$decoded = @unserialize($_COOKIE['cookie_categories']);
|
||||
if (is_array($decoded)) {
|
||||
$cookieCategories = $decoded;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<link type="text/css" rel="stylesheet" href="/libraries/plupload/jquery.plupload.queue/css/jquery.plupload.queue.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/libraries/selectize/css/selectize.css" />
|
||||
<link type="text/css" rel="stylesheet" href="/libraries/selectize/css/selectize.default.css" />
|
||||
|
||||
<script type="text/javascript" src="/libraries/jquery/sortable/sortable.js"></script>
|
||||
<script type="text/javascript" src="/libraries/plupload/plupload.js"></script>
|
||||
<script type="text/javascript" src="/libraries/plupload/plupload.html5.js"></script>
|
||||
<script type="text/javascript" src="/libraries/plupload/plupload.html4.js"></script>
|
||||
<script type="text/javascript" src="/libraries/plupload/jquery.plupload.queue/jquery.plupload.queue.js"></script>
|
||||
<script type="text/javascript" src="/libraries/plupload/i18n/pl.js"></script>
|
||||
<script type="text/javascript" src="/libraries/jquery-nested-sortable/jquery.mjs.nestedSortable.js"></script>
|
||||
<script type="text/javascript" src="/libraries/jquery/lozad.js"></script>
|
||||
<script type="text/javascript" src="/libraries/selectize/js/standalone/selectize.js"></script>
|
||||
|
||||
<style type="text/css">
|
||||
#fg-product-edit .layout-tree-toggle {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 0;
|
||||
background: transparent;
|
||||
padding: 0;
|
||||
margin-right: 4px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
text-indent: 0;
|
||||
background-image: none !important;
|
||||
}
|
||||
#fg-product-edit .layout-tree-toggle:focus,
|
||||
#fg-product-edit .layout-tree-toggle:active,
|
||||
#fg-product-edit .layout-tree-toggle:focus-visible {
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
#fg-product-edit li.sort-expanded > div .layout-tree-toggle i {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
#fg-product-edit .sortable li.sort-branch > div > .layout-tree-toggle {
|
||||
display: inline-flex;
|
||||
float: none;
|
||||
margin-right: 4px;
|
||||
}
|
||||
#fg-product-edit .menu_sortable .icheckbox_minimal-blue {
|
||||
margin-top: 0;
|
||||
margin-right: 5px;
|
||||
}
|
||||
#fg-product-edit .menu_sortable .g-checkbox {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
#files-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0 0 15px 0;
|
||||
}
|
||||
#files-list li {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin-bottom: 6px;
|
||||
cursor: move;
|
||||
}
|
||||
#files-list li .input-group {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
var images_count = <?= (int)$imagesCount ?>;
|
||||
var files_count = <?= (int)$filesCount ?>;
|
||||
var product_id = <?= (int)$productId ?>;
|
||||
|
||||
function remove_custom_filed(el) {
|
||||
confirm_delete_element(function() {
|
||||
el.parent().parent().parent().remove();
|
||||
});
|
||||
}
|
||||
|
||||
$(function() {
|
||||
|
||||
// --- Podgląd produktu ---
|
||||
$('body').on('click', '#product-preview', function() {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/ajax_product_url/',
|
||||
data: { product_id: $('#id').val() },
|
||||
success: function(response) {
|
||||
var data = jQuery.parseJSON(response);
|
||||
var win = window.open(data.url, '_blank');
|
||||
if (win) win.focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// --- Selectize: produkty powiązane ---
|
||||
$('#products_related').selectize({
|
||||
maxItems: 999,
|
||||
plugins: ['remove_button']
|
||||
});
|
||||
|
||||
// --- Lozad: lazy load obrazków ---
|
||||
var observer = lozad();
|
||||
observer.observe();
|
||||
|
||||
// --- Śledzenie kolejności galerii i plików (hidden inputs) ---
|
||||
function ensureGalleryOrderInput() {
|
||||
var $form = $('#fg-product-edit');
|
||||
if (!$form.length) return null;
|
||||
var $input = $form.find('input[name="gallery_order"]');
|
||||
if (!$input.length) {
|
||||
$input = $('<input>', { type: 'hidden', name: 'gallery_order', id: 'gallery_order' });
|
||||
$form.append($input);
|
||||
}
|
||||
return $input;
|
||||
}
|
||||
|
||||
function buildGalleryOrder() {
|
||||
var order = [];
|
||||
$('#images-list li').each(function() {
|
||||
var imageId = $(this).find('a.article_image_delete').attr('image-id');
|
||||
if (imageId) order.push(imageId);
|
||||
});
|
||||
return order.join(';');
|
||||
}
|
||||
|
||||
function refreshGalleryOrderInput() {
|
||||
var $input = ensureGalleryOrderInput();
|
||||
if ($input) $input.val(buildGalleryOrder());
|
||||
}
|
||||
|
||||
function ensureFilesOrderInput() {
|
||||
var $form = $('#fg-product-edit');
|
||||
if (!$form.length) return null;
|
||||
var $input = $form.find('input[name="files_order"]');
|
||||
if (!$input.length) {
|
||||
$input = $('<input>', { type: 'hidden', name: 'files_order', id: 'files_order' });
|
||||
$form.append($input);
|
||||
}
|
||||
return $input;
|
||||
}
|
||||
|
||||
function buildFilesOrder() {
|
||||
var order = [];
|
||||
$('#files-list li').each(function() {
|
||||
var fileId = $(this).find('.product_file_edit').attr('file_id');
|
||||
if (fileId) order.push(fileId);
|
||||
});
|
||||
return order.join(';');
|
||||
}
|
||||
|
||||
function refreshFilesOrderInput() {
|
||||
var $input = ensureFilesOrderInput();
|
||||
if ($input) $input.val(buildFilesOrder());
|
||||
}
|
||||
|
||||
ensureGalleryOrderInput();
|
||||
refreshGalleryOrderInput();
|
||||
ensureFilesOrderInput();
|
||||
refreshFilesOrderInput();
|
||||
|
||||
// --- Sortable: galeria zdjęć ---
|
||||
var imageList = document.getElementById('images-list');
|
||||
if (imageList) {
|
||||
Sortable.create(imageList, {
|
||||
onEnd: function() {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/images_order_save/',
|
||||
data: { product_id: product_id, order: buildGalleryOrder() },
|
||||
beforeSend: function() { $('#overlay').show(); },
|
||||
success: function(data) {
|
||||
$('#overlay').hide();
|
||||
var response = jQuery.parseJSON(data);
|
||||
if (response.status !== 'ok') create_error(response.msg);
|
||||
}
|
||||
});
|
||||
refreshGalleryOrderInput();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Sortable: pliki ---
|
||||
var filesList = document.getElementById('files-list');
|
||||
if (filesList) {
|
||||
Sortable.create(filesList, {
|
||||
onEnd: function() {
|
||||
refreshFilesOrderInput();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Plupload: upload zdjęć ---
|
||||
$('#images-uploader').pluploadQueue({
|
||||
multipart_params: {
|
||||
upload_token: '<?= htmlspecialchars($uploadToken, ENT_QUOTES, 'UTF-8') ?>'
|
||||
},
|
||||
runtimes: 'html5,html4',
|
||||
init: {
|
||||
Refresh: function() {
|
||||
$('.plupload_buttons').css('display', 'inline');
|
||||
$('.plupload_upload_status').css('display', 'inline');
|
||||
$('.plupload_start').addClass('plupload_disabled').removeClass('plupload_disabled');
|
||||
},
|
||||
UploadComplete: function() {
|
||||
$('.plupload_buttons').css('display', 'inline');
|
||||
$('.plupload_upload_status').css('display', 'inline');
|
||||
$('.plupload_start').addClass('plupload_disabled').removeClass('plupload_disabled');
|
||||
},
|
||||
FileUploaded: function(up, file, response) {
|
||||
var data = jQuery.parseJSON(response.response);
|
||||
$('#images-list').append(
|
||||
'<li id="image-' + data.image_id + '">' +
|
||||
'<img class="article-image lozad" data-src="/libraries/thumb.php?img=' + data.data_link + '&w=300&h=300">' +
|
||||
'<a href="#" class="input-group-addon btn btn-danger article_image_delete" image-id="' + data.image_id + '">' +
|
||||
'<i class="fa fa-trash"></i>' +
|
||||
'</a>' +
|
||||
'<input type="text" class="form-control image-alt" value="" image-id="' + data.image_id + '" placeholder="atrybut alt...">' +
|
||||
'</li>'
|
||||
);
|
||||
images_count++;
|
||||
observer.observe();
|
||||
refreshGalleryOrderInput();
|
||||
$('html, body').animate({ scrollTop: $('#images-uploader').offset().top }, 1);
|
||||
}
|
||||
},
|
||||
url: '/libraries/plupload/upload-product-images.php',
|
||||
chunk_size: '1mb',
|
||||
max_file_size: '20mb',
|
||||
unique_names: false,
|
||||
resize: {
|
||||
width: <?= (int)$imageMaxPx ?>,
|
||||
height: <?= (int)$imageMaxPx ?>,
|
||||
quality: 95
|
||||
},
|
||||
filters: [{ title: 'Obrazki', extensions: 'jpg,gif,png,bmp,jpeg' }]
|
||||
});
|
||||
|
||||
// --- Plupload: upload plików ---
|
||||
$('#files-uploader').pluploadQueue({
|
||||
multipart_params: {
|
||||
upload_token: '<?= htmlspecialchars($uploadToken, ENT_QUOTES, 'UTF-8') ?>'
|
||||
},
|
||||
runtimes: 'html5,html4',
|
||||
init: {
|
||||
Refresh: function() {
|
||||
$('.plupload_buttons').css('display', 'inline');
|
||||
$('.plupload_upload_status').css('display', 'inline');
|
||||
$('.plupload_start').addClass('plupload_disabled').removeClass('plupload_disabled');
|
||||
},
|
||||
FileUploaded: function(up, file, response) {
|
||||
var data = jQuery.parseJSON(response.response);
|
||||
$('#files-list').append(
|
||||
'<li id="file-' + data.file_id + '">' +
|
||||
'<div class="input-group">' +
|
||||
'<input type="text" class="form-control product_file_edit" file_id="' + data.file_id + '" value="' + data.file_name + '" />' +
|
||||
'<a href="#" class="input-group-addon btn btn-info product_file_delete" file_id="' + data.file_id + '">' +
|
||||
'<i class="fa fa-trash"></i>' +
|
||||
'</a>' +
|
||||
'</div>' +
|
||||
'</li>'
|
||||
);
|
||||
files_count++;
|
||||
refreshFilesOrderInput();
|
||||
}
|
||||
},
|
||||
url: '/libraries/plupload/upload-product-files.php',
|
||||
chunk_size: '1mb',
|
||||
max_file_size: '50mb',
|
||||
unique_names: false,
|
||||
filters: [{ title: 'Wszystkie pliki', extensions: '*' }]
|
||||
});
|
||||
|
||||
// --- Drzewo kategorii: ukryj strzałki na liściach ---
|
||||
function refreshTreeDisclosureState() {
|
||||
$('ol.sortable li').each(function() {
|
||||
var $li = $(this);
|
||||
var hasChildren = $li.children('ol').children('li').length > 0;
|
||||
var $toggle = $li.children('div').children('.disclose');
|
||||
|
||||
if (hasChildren) {
|
||||
$li.removeClass('sort-leaf').addClass('sort-branch');
|
||||
if (!$li.hasClass('sort-collapsed') && !$li.hasClass('sort-expanded')) {
|
||||
$li.addClass('sort-collapsed');
|
||||
}
|
||||
$toggle.attr('aria-expanded', $li.hasClass('sort-expanded') ? 'true' : 'false').show();
|
||||
} else {
|
||||
$li.removeClass('sort-branch sort-collapsed sort-expanded').addClass('sort-leaf');
|
||||
$toggle.attr('aria-expanded', 'false').hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Drzewo kategorii: nestedSortable + iCheck ---
|
||||
if ($.fn && typeof $.fn.iCheck === 'function') {
|
||||
$('#fg-product-edit .menu_sortable .g-checkbox').iCheck({
|
||||
checkboxClass: 'icheckbox_minimal-blue',
|
||||
radioClass: 'iradio_minimal-blue'
|
||||
});
|
||||
}
|
||||
|
||||
$('ol.sortable').nestedSortable({
|
||||
forcePlaceholderSize: true,
|
||||
handle: 'div',
|
||||
helper: 'clone',
|
||||
items: 'li',
|
||||
opacity: .9,
|
||||
placeholder: 'placeholder',
|
||||
revert: 250,
|
||||
tabSize: 45,
|
||||
tolerance: 'pointer',
|
||||
toleranceElement: '> div',
|
||||
maxLevels: 2,
|
||||
isTree: true,
|
||||
expandOnHover: 700,
|
||||
protectRoot: false
|
||||
});
|
||||
|
||||
$('.disclose').on('click', function() {
|
||||
var $li = $(this).closest('li');
|
||||
$li.toggleClass('sort-collapsed').toggleClass('sort-expanded');
|
||||
$(this).attr('aria-expanded', $li.hasClass('sort-expanded') ? 'true' : 'false');
|
||||
});
|
||||
|
||||
$('.disclose').mousedown(function(e) {
|
||||
if (e.which === 1) {
|
||||
var category_id = $(this).parent('div').parent('li').attr('id');
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_category/cookie_categories/',
|
||||
data: { category_id: category_id }
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
refreshTreeDisclosureState();
|
||||
|
||||
<?php foreach ($cookieCategories as $key => $val): ?>
|
||||
<?php if ($val): ?>$('#<?= $key ?>').children('div').children('.disclose').click();<?php endif; ?>
|
||||
<?php endforeach; ?>
|
||||
|
||||
// --- AJAX: zmiana alt zdjęcia ---
|
||||
$('body').on('change', '.image-alt', function() {
|
||||
var $input = $(this);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/image_alt_change/',
|
||||
data: { image_id: $input.attr('image-id'), image_alt: $input.val() },
|
||||
beforeSend: function() { $('#overlay').show(); },
|
||||
success: function(data) {
|
||||
$('#overlay').hide();
|
||||
var response = jQuery.parseJSON(data);
|
||||
if (response.status !== 'ok') create_error(response.msg);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// --- AJAX: zmiana nazwy pliku ---
|
||||
$('body').on('change', '.product_file_edit', function() {
|
||||
var $input = $(this);
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/product_file_name_change/',
|
||||
data: { file_id: $input.attr('file_id'), file_name: $input.val() },
|
||||
beforeSend: function() { $('#overlay').show(); },
|
||||
success: function(data) {
|
||||
$('#overlay').hide();
|
||||
var response = jQuery.parseJSON(data);
|
||||
if (response.status !== 'ok') create_error(response.msg);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// --- AJAX: usunięcie pliku ---
|
||||
$('body').on('click', '.product_file_delete', function() {
|
||||
$(this).blur();
|
||||
var file_id = $(this).attr('file_id');
|
||||
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Na pewno chcesz usunąć wybrany plik?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
cancel: { text: 'Nie', btnClass: 'btn-dark', action: function() {} },
|
||||
confirm: {
|
||||
text: 'Tak', btnClass: 'btn-danger', keys: ['enter'],
|
||||
action: function() {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/product_file_delete/',
|
||||
data: { file_id: file_id },
|
||||
beforeSend: function() {
|
||||
$('#file-' + file_id).find('input').addClass('disabled');
|
||||
$('#file-' + file_id).find('a').addClass('disabled');
|
||||
},
|
||||
success: function(data) {
|
||||
var response = jQuery.parseJSON(data);
|
||||
if (response.status === 'ok') {
|
||||
$('#file-' + file_id).remove();
|
||||
refreshFilesOrderInput();
|
||||
} else {
|
||||
create_error(response.msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
// --- AJAX: usunięcie zdjęcia ---
|
||||
$('body').on('click', '.article_image_delete', function() {
|
||||
$(this).blur();
|
||||
var image_id = $(this).attr('image-id');
|
||||
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Na pewno chcesz usunąć wybrane zdjęcie?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
cancel: { text: 'Nie', btnClass: 'btn-dark', action: function() {} },
|
||||
confirm: {
|
||||
text: 'Tak', btnClass: 'btn-danger', keys: ['enter'],
|
||||
action: function() {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/image_delete/',
|
||||
data: { image_id: image_id },
|
||||
beforeSend: function() { $('#overlay').show(); },
|
||||
success: function(data) {
|
||||
$('#overlay').hide();
|
||||
var response = jQuery.parseJSON(data);
|
||||
if (response.status === 'ok') {
|
||||
$('#image-' + image_id).remove();
|
||||
refreshGalleryOrderInput();
|
||||
} else {
|
||||
create_error(response.msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
// --- Odśwież kolejność galerii/plików przed zapisem ---
|
||||
$('body').on('click', '#g-edit-save, #g-edit-save-close', function() {
|
||||
refreshGalleryOrderInput();
|
||||
refreshFilesOrderInput();
|
||||
});
|
||||
|
||||
// --- Kalkulator cen netto/brutto ---
|
||||
$('body').on('keyup', '#price_netto', function() { calculate_price_brutto(); });
|
||||
$('body').on('keyup', '#price_brutto', function() { calculate_price_netto(); });
|
||||
$('body').on('keyup', '#price_netto_promo', function() { calculate_price_brutto_promo(); });
|
||||
$('body').on('keyup', '#price_brutto_promo', function() { calculate_price_netto_promo(); });
|
||||
$('body').on('change', '#vat', function() { calculate_price_brutto(); });
|
||||
|
||||
// --- Dodawanie custom field ---
|
||||
$('body').on('click', '#add_custom_field', function(e) {
|
||||
e.preventDefault();
|
||||
var html = '';
|
||||
html += '<div class="form-group row custom-field-row bg-white p-4">';
|
||||
html += '<div class="form-group row"><label class="col-sm-3 control-label">Nazwa pola:</label>';
|
||||
html += '<div class="col-sm-9"><input type="text" class="form-control" name="custom_field_name[]" value=""></div></div>';
|
||||
html += '<div class="form-group row"><label class="col-sm-3 control-label">Rodzaj pola:</label>';
|
||||
html += '<div class="col-sm-9"><select class="form-control" name="custom_field_type[]">';
|
||||
html += '<option value="text" selected>Tekst</option>';
|
||||
html += '<option value="image">Obrazek</option>';
|
||||
html += '</select></div></div>';
|
||||
html += '<div class="form-group row"><label class="col-sm-3 control-label">Status pola:</label>';
|
||||
html += '<div class="col-sm-9"><label style="margin:0; font-weight:normal;" class="d-flex align-items-center mt-3">';
|
||||
html += '<input type="checkbox" class="custom-field-required" name="custom_field_required[]"> wymagane</label></div></div>';
|
||||
html += '<div class="form-group row"><div class="col-sm-12 text-right">';
|
||||
html += '<span class="input-group-addon btn btn-info" onclick="remove_custom_filed( $( this ) );">usuń</span>';
|
||||
html += '</div></div>';
|
||||
html += '</div>';
|
||||
$('.additional_fields').append(html);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
// --- Funkcje kalkulacji cen ---
|
||||
function calculate_price_brutto() {
|
||||
var price_netto = $('#price_netto').val().replace(',', '.');
|
||||
if (!price_netto) return false;
|
||||
var vat = $('#vat').val().replace(',', '.');
|
||||
var price_brutto = price_netto * 1 + price_netto * vat / 100;
|
||||
price_brutto = Math.floor(price_brutto * 100) / 100;
|
||||
price_brutto = number_format(price_brutto, 2, '.', '');
|
||||
return $('#price_brutto').val(price_brutto);
|
||||
}
|
||||
|
||||
function calculate_price_netto() {
|
||||
var price_brutto = $('#price_brutto').val().replace(',', '.');
|
||||
var vat = $('#vat').val().replace(',', '.');
|
||||
var price_netto = price_brutto / (vat / 100 + 1);
|
||||
price_netto = number_format(price_netto, 2, '.', '');
|
||||
return $('#price_netto').val(price_netto);
|
||||
}
|
||||
|
||||
function calculate_price_brutto_promo() {
|
||||
var price_netto = $('#price_netto_promo').val().replace(',', '.');
|
||||
var vat = $('#vat').val().replace(',', '.');
|
||||
var price_brutto = price_netto * 1 + price_netto * vat / 100;
|
||||
price_brutto = Math.floor(price_brutto * 100) / 100;
|
||||
price_brutto = number_format(price_brutto, 2, '.', '');
|
||||
if (Math.floor(price_netto) <= 0) {
|
||||
$('#price_netto_promo').val('');
|
||||
$('#price_brutto_promo').val('');
|
||||
return true;
|
||||
}
|
||||
return $('#price_brutto_promo').val(price_brutto);
|
||||
}
|
||||
|
||||
function calculate_price_netto_promo() {
|
||||
var price_brutto = $('#price_brutto_promo').val().replace(',', '.');
|
||||
var vat = $('#vat').val().replace(',', '.');
|
||||
var price_netto = price_brutto / (vat / 100 + 1);
|
||||
price_netto = number_format(price_netto, 2, '.', '');
|
||||
if (Math.floor(price_brutto) <= 0) {
|
||||
$('#price_netto_promo').val('');
|
||||
$('#price_brutto_promo').val('');
|
||||
return true;
|
||||
}
|
||||
return $('#price_netto_promo').val(price_netto);
|
||||
}
|
||||
|
||||
// --- Generowanie SKU ---
|
||||
function generate_sku_code(product_id) {
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/generate_sku_code/',
|
||||
data: { product_id: product_id },
|
||||
beforeSend: function() { $('#sku').addClass('disabled'); },
|
||||
success: function(data) {
|
||||
$('#sku').removeClass('disabled');
|
||||
var response = jQuery.parseJSON(data);
|
||||
if (response.status === 'ok') {
|
||||
$('#sku').val(response.sku);
|
||||
} else {
|
||||
create_error(response.msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// --- Generowanie linku SEO ---
|
||||
function generate_seo_links(lang, title, article_id) {
|
||||
if (title === '') return false;
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/pages/generateSeoLink/',
|
||||
data: { title: title, article_id: article_id },
|
||||
beforeSend: function() {
|
||||
$('#seo_link_' + lang).parents('.g-form-data').find('input, a').each(function() {
|
||||
$(this).prop('disabled', true).addClass('disabled');
|
||||
});
|
||||
},
|
||||
success: function(data) {
|
||||
$('#seo_link_' + lang).parents('.g-form-data').find('input, a').each(function() {
|
||||
$(this).prop('disabled', false).removeClass('disabled');
|
||||
});
|
||||
var response = jQuery.parseJSON(data);
|
||||
if (response.status === 'ok') {
|
||||
$('#seo_link_' + lang).val(response.seo_link);
|
||||
} else {
|
||||
create_error(response.msg);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
302
admin/templates/shop-product/products-list-custom-script.php
Normal file
302
admin/templates/shop-product/products-list-custom-script.php
Normal file
@@ -0,0 +1,302 @@
|
||||
<? if ( $this -> shoppro_enabled ):?>
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
var $header = $( '.panel-heading .col-sm-8' );
|
||||
if ( $header.length ) {
|
||||
$header.append( ' <a href="#" class="btn btn-danger btn-sm btn-shoppro-product-import"><i class="fa fa-download mr5"></i>Pobierz produkt z shopPRO</a>' );
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<? endif;?>
|
||||
|
||||
<style type="text/css">
|
||||
.product-image {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
width: 50px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.product-image img {
|
||||
max-width: 50px;
|
||||
max-height: 50px;
|
||||
}
|
||||
.product-name {
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
}
|
||||
.product-categories {
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script type="text/javascript">
|
||||
$(function() {
|
||||
|
||||
// --- Inline price save ---
|
||||
$( 'body' ).on( 'change', '.product-price', function() {
|
||||
var $el = $( this );
|
||||
var price = $el.val().replace( ' ', '' );
|
||||
price = parseFloat( price.replace( ',', '.' ) * 1 );
|
||||
price = number_format( price, 2, '.', '' );
|
||||
$el.val( price );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/product_change_price_brutto/',
|
||||
data: { product_id: $el.attr( 'product-id' ), price: price },
|
||||
beforeSend: function() { $( '#overlay' ).show(); },
|
||||
success: function( data ) {
|
||||
$( '#overlay' ).hide();
|
||||
var response = jQuery.parseJSON( data );
|
||||
if ( response.status !== 'ok' ) create_error( response.msg );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'change', '.product-price-promo', function() {
|
||||
var $el = $( this );
|
||||
var price = $el.val().replace( ' ', '' );
|
||||
price = parseFloat( price.replace( ',', '.' ) * 1 );
|
||||
price = number_format( price, 2, '.', '' );
|
||||
$el.val( price );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/product_change_price_brutto_promo/',
|
||||
data: { product_id: $el.attr( 'product-id' ), price: price },
|
||||
beforeSend: function() { $( '#overlay' ).show(); },
|
||||
success: function( data ) {
|
||||
$( '#overlay' ).hide();
|
||||
var response = jQuery.parseJSON( data );
|
||||
if ( response.status !== 'ok' ) create_error( response.msg );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// --- Duplicate product ---
|
||||
$( 'body' ).on( 'click', '.duplicate-product', function(e) {
|
||||
e.preventDefault();
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Na pewno chcesz wykonać duplikat produktu?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak (produkt bez kombinacji)',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = '/admin/shop_product/duplicate_product/product-id=' + product_id;
|
||||
}
|
||||
},
|
||||
confirm2: {
|
||||
text: 'Tak (produkt z KOMBINACJAMI)',
|
||||
btnClass: 'btn-primary',
|
||||
action: function() {
|
||||
document.location.href = '/admin/shop_product/duplicate_product/product-id=' + product_id + '&combination=1';
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// --- shopPRO import ---
|
||||
$( 'body' ).on( 'click', '.btn-shoppro-product-import', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
$.alert({
|
||||
title: 'Import produktu',
|
||||
content: 'Wprowadź ID produktu z shopPRO',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-8',
|
||||
theme: 'material',
|
||||
icon: 'fa fa-exclamation-triangle',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Importuj',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
var product_id = $( '#shoppro-product-id' ).val();
|
||||
if ( product_id ) {
|
||||
document.location.href = '/admin/integrations/shoppro_product_import/product_id=' + product_id;
|
||||
}
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Anuluj',
|
||||
btnClass: 'btn-danger',
|
||||
action: function() {}
|
||||
}
|
||||
},
|
||||
onOpenBefore: function() {
|
||||
this.setContent( '<input type="text" class="form-control" id="shoppro-product-id" placeholder="ID produktu z shopPRO">' );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// --- Apilo ---
|
||||
$( 'body' ).on( 'click', '.apilo-product-search', function() {
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/integrations/apilo_product_search/',
|
||||
data: { product_id: product_id },
|
||||
beforeSend: function() { $( '#overlay' ).show(); },
|
||||
success: function( response ) {
|
||||
$( '#overlay' ).hide();
|
||||
var data = jQuery.parseJSON( response );
|
||||
|
||||
if ( data.status == 'SUCCESS' ) {
|
||||
if ( data.products.length == 0 ) {
|
||||
var html = '<div class="apilo-found-products">';
|
||||
html += '<p>Nie znaleziono produktów</p>';
|
||||
html += '<a href="/admin/integrations/apilo_create_product/product-id=' + product_id + '" class="btn btn-success btn_apilo_create_product" product_id="' + product_id + '">Utwórz produkt</a> ';
|
||||
html += '<button class="btn btn-default apilo-cancel">Anuluj</button>';
|
||||
html += '</div>';
|
||||
$( 'span.apilo-product-search[product-id="' + product_id + '"]' ).closest( 'td' ).append( html );
|
||||
} else {
|
||||
var html = '<div class="apilo-found-products">';
|
||||
html += '<p>Znaleziono ' + data.products.length + ' produktów</p>';
|
||||
html += '<select class="form-control apilo-product-select" product-id="' + product_id + '">';
|
||||
$.each( data.products, function( index, value ) {
|
||||
html += '<option value="' + value.id + '">' + value.name + ' SKU: ' + value.sku + '</option>';
|
||||
});
|
||||
html += '</select>';
|
||||
html += '<button class="btn btn-success apilo-product-select-save" product-id="' + product_id + '">Zapisz</button> ';
|
||||
html += '<button class="btn btn-default apilo-cancel">Anuluj</button>';
|
||||
html += '</div>';
|
||||
$( 'span.apilo-product-search[product-id="' + product_id + '"]' ).closest( 'td' ).append( html );
|
||||
}
|
||||
} else if ( data.status == 'error' ) {
|
||||
$.alert({
|
||||
title: 'Błąd',
|
||||
content: data.msg,
|
||||
type: 'red',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-exclamation-triangle',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'OK',
|
||||
btnClass: 'btn-danger',
|
||||
keys: ['enter'],
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.apilo-cancel', function() {
|
||||
$( this ).closest( '.apilo-found-products' ).remove();
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.apilo-delete-linking', function() {
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/integrations/apilo_product_select_delete/',
|
||||
data: { product_id: product_id },
|
||||
beforeSend: function() { $( '#overlay' ).show(); },
|
||||
success: function( response ) {
|
||||
$( '#overlay' ).hide();
|
||||
var data = jQuery.parseJSON( response );
|
||||
if ( data.status == 'ok' ) {
|
||||
$( 'span.apilo-delete-linking[product-id="' + product_id + '"]' ).closest( 'td' ).html( '<span class="text-danger apilo-product-search" product-id="' + product_id + '">nie przypisano <i class="fa fa-search"></i></span>' );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.btn_apilo_create_product', function(e) {
|
||||
e.preventDefault();
|
||||
var product_id = $( this ).attr( 'product_id' );
|
||||
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Na pewno chcesz utworzyć produkt w bazie Apilo?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = '/admin/integrations/apilo_create_product/product_id=' + product_id;
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-danger',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.apilo-product-select-save', function() {
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
var apilo_product_id = $( '.apilo-product-select[product-id="' + product_id + '"]' ).val();
|
||||
var apilo_product_name = $( '.apilo-product-select[product-id="' + product_id + '"] option:selected' ).text();
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/integrations/apilo_product_select_save/',
|
||||
data: {
|
||||
product_id: product_id,
|
||||
apilo_product_id: apilo_product_id,
|
||||
apilo_product_name: apilo_product_name
|
||||
},
|
||||
beforeSend: function() { $( '#overlay' ).show(); },
|
||||
success: function( response ) {
|
||||
$( '#overlay' ).hide();
|
||||
var data = jQuery.parseJSON( response );
|
||||
if ( data.status == 'ok' ) {
|
||||
$( '.apilo-product-select[product-id="' + product_id + '"]' ).closest( '.apilo-found-products' ).remove();
|
||||
$( 'span.apilo-product-search[product-id="' + product_id + '"]' ).html( '<span title="' + apilo_product_name + '">' + apilo_product_name.substr( 0, 25 ) + '...</span>' ).removeClass( 'apilo-product-search' ).removeClass( 'text-danger' );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
@@ -1,101 +0,0 @@
|
||||
<? $i = ( $this -> current_page - 1 ) * 10 + 1;?>
|
||||
<? foreach ( $this -> products as $product ):?>
|
||||
<tr>
|
||||
<td>
|
||||
<?= $i++;?>
|
||||
</td>
|
||||
<td>
|
||||
<div class="product-image">
|
||||
<? if ( $product['images'][0]['src'] ):?>
|
||||
<img src="<?= $product['images'][0]['src'];?>" alt="<?= $product['images'][0]['alt'];?>" class="img-responsive">
|
||||
<? else:?>
|
||||
<img src="/admin/layout/images/no-image.png" alt="Brak zdjęcia" class="img-responsive">
|
||||
<? endif;?>
|
||||
</div>
|
||||
<div class="product-name">
|
||||
<a href="/admin/shop_product/product_edit/id=<?= $product['id'];?>">
|
||||
<?= $product['languages']['pl']['name'];?>
|
||||
</a>
|
||||
<a href="#" class="text-muted duplicate-product" product-id="<?= $product['id'];?>">duplikuj</a>
|
||||
</div>
|
||||
<small class="text-muted product-categories"><?= \admin\factory\ShopProduct::product_categories( $product['id'] );?></small>
|
||||
<small class="text-muted product-categories">SKU: <?= $product['sku'];?>, EAN: <?= $product['ean'];?></small>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input type="text" class="product-price form-control text-right" product-id="<?= $product['id'];?>" value="<?= $product['price_brutto'];?>" style="width: 75px;">
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<input type="text" class="product-price-promo form-control text-right" product-id="<?= $product['id'];?>" value="<?= $product['price_brutto_promo'];?>" style="width: 75px;">
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<?= $product['promoted'] ? '<span class="text-success text-bold">tak</span>' : 'nie';?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<?= $product['status'] ? 'tak' : '<span class="text-danger text-bold">nie</span>';?>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<span class="text-muted"><?= (int)\admin\factory\shopProduct::get_product_quantity_list( $product['id'] );?></span>
|
||||
</td>
|
||||
<? if ( $this -> apilo_enabled ):?>
|
||||
<td class="text-center">
|
||||
<?
|
||||
if ( $product['apilo_product_name'] != "" ) {
|
||||
echo "<span title='" . htmlspecialchars( $product['apilo_product_name'] ) . "'>" . mb_substr( $product['apilo_product_name'], 0, 25, "UTF-8" ) . "...</span>";
|
||||
echo "<br>";
|
||||
echo "<span class='text-danger apilo-delete-linking' product-id='" . $product['id'] . "'>";
|
||||
echo "<i class='fa fa-times'></i>usuń powiązanie";
|
||||
echo "</span>";
|
||||
} else {
|
||||
echo "<span class='text-danger apilo-product-search' product-id='" . $product['id'] . "'>";
|
||||
echo "nie przypisano <i class='fa fa-search'></i>";
|
||||
echo "</span>";
|
||||
}
|
||||
?>
|
||||
</td>
|
||||
<? endif;?>
|
||||
<td>
|
||||
<a href='/admin/shop_product/product_combination/product_id=<?= $product['id'];?>'>kombinacje (<?= \admin\factory\shopProduct::count_product_combinations( $product['id'] );?>)</a>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href='/admin/shop_product/product_edit/id=<?= $product['id'];?>'>edytuj</a>
|
||||
</td>
|
||||
<td class="text-center">
|
||||
<a href='/admin/shop_product/product_archive/product_id=<?= $product['id'];?>' class="product-delete">usuń</a>
|
||||
</td>
|
||||
</tr>
|
||||
<? if ( $this -> show_xml_data ):?>
|
||||
<tr>
|
||||
<td colspan="12">
|
||||
<div class="product-xml-data">
|
||||
<!-- input nazwa produktu XML -->
|
||||
<input type="text" class="form-control product_xml_name" value="<?= $product['languages']['pl']['xml_name'];?>" placeholder="Nazwa produktu XML (PL)" product-id="<?= $product['id'];?>" lang-id="pl">
|
||||
<!-- input custom_label_0 -->
|
||||
<div class="custom_label_0_container">
|
||||
<input type="text" class="form-control custom_label_0" value="<?= $product['custom_label_0'];?>" placeholder="custom_label_0" product-id="<?= $product['id'];?>" label-type="custom_label_0">
|
||||
<div class="custom_label_0_suggestions" label-type="custom_label_0"></div>
|
||||
</div>
|
||||
<!-- input custom_label_1 -->
|
||||
<div class="custom_label_1_container">
|
||||
<input type="text" class="form-control custom_label_1" value="<?= $product['custom_label_1'];?>" placeholder="custom_label_1" product-id="<?= $product['id'];?>" label-type="custom_label_1">
|
||||
<div class="custom_label_1_suggestions" label-type="custom_label_1"></div>
|
||||
</div>
|
||||
<!-- input custom_label_2 -->
|
||||
<div class="custom_label_2_container">
|
||||
<input type="text" class="form-control custom_label_2" value="<?= $product['custom_label_2'];?>" placeholder="custom_label_2" product-id="<?= $product['id'];?>" label-type="custom_label_2">
|
||||
<div class="custom_label_2_suggestions" label-type="custom_label_2"></div>
|
||||
</div>
|
||||
<!-- input custom_label_3 -->
|
||||
<div class="custom_label_3_container">
|
||||
<input type="text" class="form-control custom_label_3" value="<?= $product['custom_label_3'];?>" placeholder="custom_label_3" product-id="<?= $product['id'];?>" label-type="custom_label_3">
|
||||
<div class="custom_label_3_suggestions" label-type="custom_label_3"></div>
|
||||
</div>
|
||||
<!-- input custom_label_4 -->
|
||||
<div class="custom_label_4_container">
|
||||
<input type="text" class="form-control custom_label_4" value="<?= $product['custom_label_4'];?>" placeholder="custom_label_4" product-id="<?= $product['id'];?>" label-type="custom_label_4">
|
||||
<div class="custom_label_4_suggestions" label-type="custom_label_4"></div>
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<? endif;?>
|
||||
<? endforeach;?>
|
||||
@@ -1,603 +1,9 @@
|
||||
<div class="panel">
|
||||
<div class="panel-body">
|
||||
<a href="/admin/shop_product/product_edit/" class="btn btn-success">Dodaj produkt</a>
|
||||
<!-- przycisk pokaż dane z XML -->
|
||||
<? if ( !$this -> show_xml_data ):?>
|
||||
<a href="/admin/shop_product/view_list/show_xml_data=true" class="btn btn-dark">Pokaż dane z XML</a>
|
||||
<? else:?>
|
||||
<a href="/admin/shop_product/view_list/show_xml_data=false" class="btn btn-danger">Ukryj dane z XML</a>
|
||||
<? endif;?>
|
||||
<? if ( $this -> shoppro_enabled ):?>
|
||||
<a href="#" class="btn btn-danger btn-shoppro-product-import">Pobierz produkt z shopPRO</a>
|
||||
<? endif;?>
|
||||
</div>
|
||||
<div class="panel-body pn">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover table-striped mbn" id="table-products">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width: 10px;">#</th>
|
||||
<th>Nazwa</th>
|
||||
<th class="text-center" style="width: 100px;">Cena</th>
|
||||
<th class="text-center" style="width: 100px;">Cena promocyjna</th>
|
||||
<th class="text-center" style="width: 25px;">Promowany</th>
|
||||
<th class="text-center" style="width: 25px;">Aktywny</th>
|
||||
<th class="text-center" style="width: 75px;">Stan MG</th>
|
||||
<? if ( $this -> apilo_enabled ):?>
|
||||
<th class="text-center" style="width: 100px;">Apilo</th>
|
||||
<? endif;?>
|
||||
<th class="text-center" style="width: 100px;">Kombinacje</th>
|
||||
<th class="text-center" style="width: 75px;">Edytuj</th>
|
||||
<th class="text-center" style="width: 75px;">Archiwizuj</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>
|
||||
<input type="text" class="form-control table-search" field_name="name|ean|sku" placeholder="szukaj..." value="<?= htmlspecialchars( $this -> query_array['name|ean|sku'], ENT_QUOTES, 'UTF-8');?>">
|
||||
</th>
|
||||
<th colspan="10"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?= \Tpl::view('components/table-list', ['list' => $this->viewModel]); ?>
|
||||
|
||||
</tbody>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="12">
|
||||
<ul class="pagination" pagination_max="<?= $this -> pagination_max;?>">
|
||||
<li>
|
||||
<a href="#" page="1" title="pierwsza strona">
|
||||
<i class="fa fa-angle-double-left"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="previous" page="<?= ( $this -> current_page - 1 > 1 ) ? ( $this -> current_page - 1 ) : 1;?>" title="poprzednia strona">
|
||||
<i class="fa fa-angle-left" title="poprzednia strona"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<div>
|
||||
Strona <input type="number" id="current-page" value="<?= $this -> current_page;?>"> z <span id="max_page"><?= $this -> pagination_max;?></span>
|
||||
</div>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="next" page="<?= ( $this -> current_page + 1 < $this -> pagination_max ) ? ( $this -> current_page + 1 ) : $this -> pagination_max;?>" title="następna strona">
|
||||
<i class="fa fa-angle-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="#" class="last" page="<?= $this -> pagination_max;?>" title="ostatnia strona">
|
||||
<i class="fa fa-angle-double-right"></i>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
$( function() {
|
||||
ajax_load_products( <?= $this -> current_page;?>, null );
|
||||
|
||||
$( 'body' ).on( 'change', '.table-search', function() {
|
||||
ajax_load_products( 1 );
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'change', '.pagination input[type="number"]', function() {
|
||||
var current_page = $( this ).val();
|
||||
var pagination_max = parseInt( $( '.pagination' ).attr( 'pagination_max' ) );
|
||||
|
||||
if ( current_page > pagination_max ) {
|
||||
current_page = pagination_max;
|
||||
$( this ).val( current_page );
|
||||
}
|
||||
|
||||
if ( current_page < 1 ) {
|
||||
current_page = 1;
|
||||
$( this ).val( current_page );
|
||||
}
|
||||
|
||||
ajax_load_products( current_page );
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.pagination a', function() {
|
||||
var current_page = $( this ).attr( 'page' );
|
||||
ajax_load_products( current_page );
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.btn-shoppro-product-import', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
// show alert with form with product_id field
|
||||
$.alert({
|
||||
title: 'Import produktu',
|
||||
content: 'Wprowadź ID produktu z shopPRO',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-8',
|
||||
theme: 'material',
|
||||
icon: 'fa fa-exclamation-triangle',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Importuj',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
var product_id = $( '#shoppro-product-id' ).val();
|
||||
if ( product_id )
|
||||
{
|
||||
document.location.href = '/admin/integrations/shoppro_product_import/product_id=' + product_id;
|
||||
}
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Anuluj',
|
||||
btnClass: 'btn-danger',
|
||||
action: function() {}
|
||||
}
|
||||
},
|
||||
onOpenBefore: function() {
|
||||
this.setContent( '<input type="text" class="form-control" id="shoppro-product-id" placeholder="ID produktu z shopPRO">' );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.product-delete', function(e) {
|
||||
e.preventDefault();
|
||||
var href = $( this ).attr( 'href' );
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Na pewno chcesz przenieść wybrany produkt do archiwum?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'supervan',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-success',
|
||||
action: function() {}
|
||||
},
|
||||
confirm: {
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-danger',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = href;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
$( 'body' ).on( 'change', '.product-price-promo', function(e)
|
||||
{
|
||||
var price = $( this ).val();
|
||||
price = price.replace( ' ', '' );
|
||||
price = parseFloat( price.replace( ',', '.' ) * 1 );
|
||||
price = number_format( price, 2, '.', '' );
|
||||
|
||||
$( this ).val( price );
|
||||
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.ajax(
|
||||
{
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/product_change_price_brutto_promo/',
|
||||
data:
|
||||
{
|
||||
product_id: product_id,
|
||||
price: price
|
||||
},
|
||||
beforeSend: function()
|
||||
{
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( data )
|
||||
{
|
||||
$( '#overlay' ).hide();
|
||||
|
||||
response = jQuery.parseJSON( data );
|
||||
|
||||
if ( response.status !== 'ok' )
|
||||
create_error( response.msg );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'change', '.product-price', function(e)
|
||||
{
|
||||
var price = $( this ).val();
|
||||
price = price.replace( ' ', '' );
|
||||
price = parseFloat( price.replace( ',', '.' ) * 1 );
|
||||
price = number_format( price, 2, '.', '' );
|
||||
|
||||
$( this ).val( price );
|
||||
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.ajax(
|
||||
{
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/product_change_price_brutto/',
|
||||
data:
|
||||
{
|
||||
product_id: product_id,
|
||||
price: price
|
||||
},
|
||||
beforeSend: function()
|
||||
{
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( data )
|
||||
{
|
||||
$( '#overlay' ).hide();
|
||||
|
||||
response = jQuery.parseJSON( data );
|
||||
|
||||
if ( response.status !== 'ok' )
|
||||
create_error( response.msg );
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.duplicate-product', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.alert({
|
||||
title: 'Pytanie',
|
||||
content: 'Na pewno chcesz wykonać duplikat produktu?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'Tak (produkt bez kombinacji)',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = '/admin/shop_product/duplicate_product/product-id=' + product_id;
|
||||
}
|
||||
},
|
||||
confirm2: {
|
||||
text: 'Tak (produkt z KOMBINACJAMI)',
|
||||
btnClass: 'btn-primary',
|
||||
keys: ['enter'],
|
||||
action: function() {
|
||||
document.location.href = '/admin/shop_product/duplicate_product/product-id=' + product_id + '&combination=1';
|
||||
}
|
||||
},
|
||||
cancel: {
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-dark',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
// apilo product search
|
||||
$( 'body' ).on( 'click', '.apilo-product-search', function() {
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/integrations/apilo_product_search/',
|
||||
data: {
|
||||
product_id: product_id
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
data = jQuery.parseJSON( response );
|
||||
|
||||
if ( data.status == 'SUCCESS' )
|
||||
{
|
||||
if ( data.products.length == 0 )
|
||||
{
|
||||
var html = '<div class="apilo-found-products">';
|
||||
html += '<p>Nie znaleziono produktów</p>';
|
||||
html += '<a href="/admin/integrations/apilo_create_product/product-id=' + product_id + '" class="btn btn-success btn_apilo_create_product" product_id="' + product_id + '">Utwórz produkt</a>';
|
||||
html += '</div>';
|
||||
$( 'span.apilo-product-search[product-id="' + product_id + '"]' ).closest( 'td' ).append( html );
|
||||
}
|
||||
else
|
||||
{
|
||||
var html = '<div class="apilo-found-products">';
|
||||
html += '<p>Znaleziono ' + data.products.length + ' produktów</p>';
|
||||
html += '<select class="form-control apilo-product-select" product-id="' + product_id + '">';
|
||||
$.each( data.products, function( index, value ) {
|
||||
html += '<option value="' + value.id + '">' + value.name + ' SKU: ' + value.sku + '</option>';
|
||||
});
|
||||
html += '</select>';
|
||||
html += '<button class="btn btn-success apilo-product-select-save" product-id="' + product_id + '">Zapisz</button>';
|
||||
html += '</div>';
|
||||
$( 'span.apilo-product-search[product-id="' + product_id + '"]' ).closest( 'td' ).append( html );
|
||||
}
|
||||
}
|
||||
else if ( data.status == 'error' )
|
||||
{
|
||||
$.alert({
|
||||
title: 'Błąd',
|
||||
content: data.msg,
|
||||
type: 'red',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-exclamation-triangle',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: 'OK',
|
||||
btnClass: 'btn-danger',
|
||||
keys: ['enter'],
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// delete apilo product linking
|
||||
$( 'body' ).on( 'click', '.apilo-delete-linking', function() {
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/integrations/apilo_product_select_delete/',
|
||||
data: {
|
||||
product_id: product_id
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
data = jQuery.parseJSON( response );
|
||||
|
||||
if ( data.status == 'ok' ) {
|
||||
$( 'span.apilo-delete-linking[product-id="' + product_id + '"]' ).closest('td').html( '<span class="text-danger apilo-product-search" product-id="' + product_id + '">nie przypisano <i class="fa fa-search"></i></span>' );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// apilo product create button click with alert confirmation
|
||||
$( 'body' ).on( 'click', '.btn_apilo_create_product', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
var product_id = $( this ).attr( 'product_id' );
|
||||
|
||||
$.alert(
|
||||
{
|
||||
title: 'Pytanie',
|
||||
content: 'Na pewno chcesz utworzyć produkt w bazie Apilo?',
|
||||
type: 'orange',
|
||||
closeIcon: true,
|
||||
closeIconClass: 'fa fa-times',
|
||||
typeAnimated: true,
|
||||
animation: 'opacity',
|
||||
columnClass: 'col-12 col-lg-10',
|
||||
theme: 'modern',
|
||||
icon: 'fa fa-question',
|
||||
buttons:
|
||||
{
|
||||
confirm:
|
||||
{
|
||||
text: 'Tak',
|
||||
btnClass: 'btn-success',
|
||||
keys: ['enter'],
|
||||
action: function()
|
||||
{
|
||||
document.location.href = '/admin/integrations/apilo_create_product/product_id=' + product_id;
|
||||
}
|
||||
},
|
||||
cancel:
|
||||
{
|
||||
text: 'Nie',
|
||||
btnClass: 'btn-danger',
|
||||
action: function() {}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// apilo product select save
|
||||
$( 'body' ).on( 'click', '.apilo-product-select-save', function(){
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
var apilo_product_id = $( '.apilo-product-select[product-id="' + product_id + '"]' ).val();
|
||||
var apilo_product_name = $( '.apilo-product-select[product-id="' + product_id + '"] option:selected' ).text();
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/integrations/apilo_product_select_save/',
|
||||
data: {
|
||||
product_id: product_id,
|
||||
apilo_product_id: apilo_product_id,
|
||||
apilo_product_name: apilo_product_name
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
data = jQuery.parseJSON( response );
|
||||
|
||||
if ( data.status == 'ok' ) {
|
||||
$( '.apilo-product-select[product-id="' + product_id + '"]' ).closest( '.apilo-found-products' ).remove();
|
||||
$( 'span.apilo-product-search[product-id="' + product_id + '"]' ).html( '<span title="' + apilo_product_name + '">' + apilo_product_name.substr( 0, 25 ) + '...</span>' ).removeClass( 'apilo-product-search' ).removeClass( 'text-danger' );
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'change', '.product_xml_name', function() {
|
||||
var product_id = $( this ).attr( 'product-id' );
|
||||
var product_xml_name = $( this ).val();
|
||||
var lang_id = $( this ).attr( 'lang-id' );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/product_xml_name_save/',
|
||||
data: {
|
||||
product_id: product_id,
|
||||
product_xml_name: product_xml_name,
|
||||
lang_id: lang_id
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
$( '#overlay' ).hide();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// save custom_label on change
|
||||
$( 'body' ).on( 'change', '.custom_label_0, .custom_label_1, .custom_label_2, .custom_label_3, .custom_label_4', function() {
|
||||
var element = $( this );
|
||||
var product_id = element.attr( 'product-id' );
|
||||
var custom_label = element.val();
|
||||
var label_type = element.attr( 'label-type' );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/product_custom_label_save/',
|
||||
data: {
|
||||
product_id: product_id,
|
||||
custom_label: custom_label,
|
||||
label_type: label_type
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
$( '#overlay' ).hide();
|
||||
// hide after 1 seconds
|
||||
setTimeout(function(){
|
||||
element.parent( '.' + label_type + '_container' ).find( '.' + label_type + '_suggestions' ).hide();
|
||||
}, 1000);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// on keyup
|
||||
$( 'body' ).on( 'keyup', '.custom_label_0, .custom_label_1, .custom_label_2, .custom_label_3, .custom_label_4', function() {
|
||||
var element = $( this );
|
||||
var product_id = element.attr( 'product-id' );
|
||||
var custom_label = element.val();
|
||||
var label_type = element.attr( 'label-type' );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/product_custom_label_suggestions/',
|
||||
data: {
|
||||
custom_label: custom_label,
|
||||
label_type: label_type
|
||||
},
|
||||
beforeSend: function() {
|
||||
|
||||
},
|
||||
success: function( response ) {
|
||||
var data = jQuery.parseJSON( response );
|
||||
var suggestions = "";
|
||||
if ( data.suggestions.length > 0 ) {
|
||||
for ( var i = 0; i < data.suggestions.length; i++ ) {
|
||||
suggestions += "<div>" + data.suggestions[i].label + "</div>";
|
||||
}
|
||||
element.parent( '.' + label_type + '_container' ).find( '.' + label_type + '_suggestions' ).html( suggestions ).show();
|
||||
} else {
|
||||
element.parent( '.' + label_type + '_container' ).find( '.' + label_type + '_suggestions' ).hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '.custom_label_0_suggestions div, .custom_label_1_suggestions div, .custom_label_2_suggestions div, .custom_label_3_suggestions div, .custom_label_4_suggestions div', function(){
|
||||
var element = $( this );
|
||||
var label_type = element.parent().attr( 'label-type' );
|
||||
var custom_label = element.html();
|
||||
|
||||
element.parents( '.' + label_type + '_container' ).find( 'input' ).val( custom_label );
|
||||
// trigger change
|
||||
element.parents( '.' + label_type + '_container' ).find( 'input' ).trigger( 'change' );
|
||||
element.parents( '.' + label_type + '_container' ).find( '.' + label_type + '_suggestions' ).hide();
|
||||
});
|
||||
|
||||
function ajax_load_products( current_page ) {
|
||||
var pagination_max = parseInt( $( '.pagination' ).attr( 'pagination_max' ) );
|
||||
|
||||
var query = '';
|
||||
$( '.table-search' ).each(function(){
|
||||
query += $( this ).attr( 'field_name' ) + '=' + $( this ).val() + '&';
|
||||
});
|
||||
|
||||
current_page = parseInt( current_page );
|
||||
|
||||
$.ajax({
|
||||
type: 'POST',
|
||||
cache: false,
|
||||
url: '/admin/shop_product/ajax_load_products/',
|
||||
data: {
|
||||
current_page: current_page,
|
||||
query: query
|
||||
},
|
||||
beforeSend: function() {
|
||||
$( '#overlay' ).show();
|
||||
},
|
||||
success: function( response ) {
|
||||
$( '#overlay' ).hide();
|
||||
|
||||
data = jQuery.parseJSON( response );
|
||||
|
||||
if ( data.status == 'ok' )
|
||||
{
|
||||
$( '#table-products tbody' ).html( data.html );
|
||||
$( '.pagination .previous' ).attr( 'page', ( current_page - 1 > 1 ) ? ( current_page - 1 ) : 1 );
|
||||
$( '.pagination .next' ).attr( 'page', ( current_page + 1 < pagination_max ) ? ( current_page + 1 ) : pagination_max );
|
||||
$( '.pagination span' ).html( current_page );
|
||||
if ( data.pagination_max )
|
||||
{
|
||||
$( '.pagination' ).attr( 'pagination_max', data.pagination_max );
|
||||
$( '.pagination #max_page' ).html( data.pagination_max );
|
||||
$( '.pagination .last' ).attr( 'page', data.pagination_max );
|
||||
}
|
||||
$( '#current-page' ).val( current_page );
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
<?php if (!empty($this->viewModel->customScriptView)): ?>
|
||||
<?= \Tpl::view($this->viewModel->customScriptView, [
|
||||
'list' => $this->viewModel,
|
||||
'apilo_enabled' => $this->apilo_enabled,
|
||||
'shoppro_enabled' => $this->shoppro_enabled,
|
||||
]); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/plugins/ckeditor/ckeditor.js"></script>
|
||||
<script type="text/javascript" src="/libraries/framework/vendor/plugins/ckeditor/adapters/jquery.js"></script>
|
||||
<?php
|
||||
global $db;
|
||||
ob_start();
|
||||
|
||||
if ( \S::is_array_fix( $this -> product -> permutations ) )
|
||||
{
|
||||
foreach ( $this -> product -> permutations as $permutation )
|
||||
{
|
||||
?>
|
||||
<div class="form-group row">
|
||||
<label class="col-lg-4 control-label default-value">
|
||||
<?
|
||||
$data = explode( '_', $permutation );
|
||||
foreach ( $data as $row )
|
||||
{
|
||||
echo \shop\ProductAttribute::getAttributeNameByValue( $row, \front\factory\Languages::default_language()) . ': <span class="text-muted">' . \shop\ProductAttribute::getValueName( $row, \front\factory\Languages::default_language() ) . '</span>';
|
||||
if ( $row !== end( $data ) )
|
||||
echo ' | ';
|
||||
}
|
||||
echo '</label>';
|
||||
echo '<div class="col-lg-8">';
|
||||
echo '<input type="text" class="form-control" name="permutation_' . $permutation . '" value="' . \shop\Product::getPermutationQuantity( $this -> product -> id, $permutation ) . '">';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
echo '<div class="form-group row">';
|
||||
echo ' <label class="col-lg-4 control-label default-value">Produkt bez atrybutów (lub z 1 atrybutem i jedną wartością):</label>';
|
||||
echo '<div class="col-lg-8">';
|
||||
echo '<input type="text" class="form-control" name="permutation_0" value="' . \admin\factory\ShopProduct::permutation_quantity( $this -> product -> id, 0 ) . '">';
|
||||
echo '</div>';
|
||||
echo '</div>';
|
||||
}
|
||||
?>
|
||||
|
||||
<?php
|
||||
$out = ob_get_clean();
|
||||
|
||||
$grid = new \gridEdit();
|
||||
$grid -> id = 'stock';
|
||||
$grid -> gdb_opt = $gdb;
|
||||
$grid -> include_plugins = true;
|
||||
$grid -> title = 'Stany magazynowe: <u>'.$this -> product -> language['name'].'</u>';
|
||||
$grid -> fields = [
|
||||
[
|
||||
'db' => 'id',
|
||||
'type' => 'hidden',
|
||||
'value' => $this -> product -> id,
|
||||
],
|
||||
];
|
||||
$grid -> actions = [
|
||||
'save' => ['url' => '/admin/shop_product/stock_save/', 'back_url' => '/admin/shop_product/view_list/'],
|
||||
'cancel' => ['url' => '/admin/shop_product/view_list/'],
|
||||
];
|
||||
$grid -> external_code = $out;
|
||||
$grid -> persist_edit = true;
|
||||
$grid -> id_param = 'id';
|
||||
|
||||
echo $grid -> draw();
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
$( function()
|
||||
{
|
||||
disable_menu();
|
||||
});
|
||||
</script>
|
||||
@@ -1,176 +1,129 @@
|
||||
<?
|
||||
global $db;
|
||||
|
||||
ob_start();
|
||||
?>
|
||||
<?= \Html::form_text(
|
||||
array(
|
||||
'label' => 'Twoja wersja systemu',
|
||||
'id' => 'ver',
|
||||
'text' => $this -> ver
|
||||
)
|
||||
);?>
|
||||
<?= \Html::form_text(
|
||||
array(
|
||||
'label' => 'Aktualna wersja systemu',
|
||||
'text' => $this -> new_ver,
|
||||
'id' => 'new_ver'
|
||||
)
|
||||
);?>
|
||||
<?
|
||||
$valuemax = ( $this -> new_ver - $this -> ver ) * 1000;
|
||||
?>
|
||||
<div class="progress-box hidden">
|
||||
<div class="version">
|
||||
<h3> Aktualizacja <p class="version_curent">0</p> / <p class="version_diff">0</p></h3>
|
||||
</div>
|
||||
<div class="progress ">
|
||||
<div class="progress-bar progress-bar-info progress-bar-striped active" role="progressbar"
|
||||
accesskey=""aria-valuenow="" aria-valuemin="0" aria-valuemax="<?=$valuemax?>" style="width:">
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<span class="panel-title">Aktualizacja systemu</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-4">Twoja wersja systemu</label>
|
||||
<div class="col-lg-8">
|
||||
<p class="form-control-static"><?= $this->ver; ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<? if ( $this -> ver < $this -> new_ver ):?>
|
||||
<div class="form-group col-lg-6 text-right">
|
||||
<div class="">
|
||||
<a href="#" class="btn btn-system btn-sm mb5" id="confirm">Aktualizuj do wyższej wersji</a>
|
||||
<div class="form-group">
|
||||
<label class="control-label col-lg-4">Aktualna wersja systemu</label>
|
||||
<div class="col-lg-8">
|
||||
<p class="form-control-static"><?= $this->new_ver; ?></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<? endif;?>
|
||||
<? if ( $this -> ver < $this -> new_ver ):?>
|
||||
<div class="form-group col-lg-6">
|
||||
<div class="">
|
||||
<a href="#" class="btn btn-system btn-sm mb5" id="confirmUpdateAll">Aktualizuj do najwyższej wersji</a>
|
||||
|
||||
<div class="progress-box hidden">
|
||||
<div class="version">
|
||||
<h3>Aktualizacja <span class="version_curent">0</span> / <span class="version_diff">0</span></h3>
|
||||
</div>
|
||||
<div class="progress">
|
||||
<div class="progress-bar progress-bar-info progress-bar-striped active" role="progressbar"
|
||||
aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" style="width: 0%">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<? if ( $this->ver < $this->new_ver ): ?>
|
||||
<div class="row mt15">
|
||||
<div class="form-group col-lg-6 text-right">
|
||||
<a href="#" class="btn btn-system btn-sm mb5" id="confirm">Aktualizuj do wyższej wersji</a>
|
||||
</div>
|
||||
<div class="form-group col-lg-6">
|
||||
<a href="#" class="btn btn-system btn-sm mb5" id="confirmUpdateAll">Aktualizuj do najwyższej wersji</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-danger text-center">* Przed aktualizacją systemu zalecane jest wykonanie pełnej kopii zapasowej.</div>
|
||||
<? endif; ?>
|
||||
</div>
|
||||
<? endif;?>
|
||||
</div>
|
||||
<? if ( $this -> ver < $this -> new_ver ):?>
|
||||
<div class="text-danger text-center">* Przed aktualizacją systemu zalecane jest wykonanie pełnej kopii zapasowej.</div>
|
||||
<div class="clear"></div>
|
||||
<?endif;?>
|
||||
<?
|
||||
$out = ob_get_clean();
|
||||
|
||||
$grid = new \gridEdit;
|
||||
$grid -> id = 'update-view';
|
||||
$grid -> gdb_opt = $gdb;
|
||||
$grid -> include_plugins = true;
|
||||
$grid -> title = 'Aktualizacja systemu';
|
||||
$grid -> default_buttons = false;
|
||||
$grid -> external_code = $out;
|
||||
echo $grid -> draw();
|
||||
?>
|
||||
<?
|
||||
ob_start();
|
||||
echo $versions = file_get_contents( 'https://shoppro.project-dc.pl/updates/changelog.php' );
|
||||
$out = ob_get_clean();
|
||||
<div class="panel">
|
||||
<div class="panel-heading">
|
||||
<span class="panel-title">Changelog</span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<?= @file_get_contents( 'https://shoppro.project-dc.pl/updates/changelog.php' ); ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
$grid = new \gridEdit;
|
||||
$grid -> id = 'changelog';
|
||||
$grid -> gdb_opt = $gdb;
|
||||
$grid -> include_plugins = true;
|
||||
$grid -> title = 'Changelog';
|
||||
$grid -> default_buttons = false;
|
||||
$grid -> external_code = $out;
|
||||
echo $grid -> draw();
|
||||
?>
|
||||
<script type="text/javascript">
|
||||
var version_current = <?= $this -> ver;?>;
|
||||
var version_new = <?= $this -> new_ver;?>;
|
||||
var version_diff = Math.round((version_new - version_current )* 1000);
|
||||
$(function() {
|
||||
var version_current = <?= $this->ver; ?>;
|
||||
var version_new = <?= $this->new_ver; ?>;
|
||||
var version_diff = Math.round( ( version_new - version_current ) * 1000 );
|
||||
var width = 0;
|
||||
var ac_lp = 0;
|
||||
$( document ).ready( function()
|
||||
{
|
||||
$( 'body' ).on( 'click', '#confirm', function()
|
||||
{
|
||||
$.prompt( 'Na pewno chcesz dokonać aktualizacji systemu?',
|
||||
{
|
||||
title: 'Potwierdź?',
|
||||
submit: function(e,v,m,f)
|
||||
{
|
||||
if ( v == true )
|
||||
document.location.href = '/admin/update/update/';
|
||||
},
|
||||
buttons: {
|
||||
'tak': true, 'nie': false
|
||||
},
|
||||
focus: 1
|
||||
});
|
||||
});
|
||||
|
||||
$( 'body' ).on( 'click', '#confirmUpdateAll', function()
|
||||
{
|
||||
$.prompt( 'Na pewno chcesz dokonać aktualizacji systemu?',
|
||||
{
|
||||
title: 'Potwierdź?',
|
||||
submit: function(e,v,m,f)
|
||||
{
|
||||
if ( v == true )
|
||||
{
|
||||
$('.progress-box').removeClass('hidden');
|
||||
$('#confirm').css('pointer-events','none');
|
||||
$('#confirmUpdateAll').css('pointer-events','none');
|
||||
$('.version_diff').html(version_diff);
|
||||
updateAll( version_current, version_new, version_diff, width, ac_lp);
|
||||
}
|
||||
$( '#confirm' ).on( 'click', function( e ) {
|
||||
e.preventDefault();
|
||||
$.confirm({
|
||||
title: 'Potwierdź',
|
||||
content: 'Na pewno chcesz dokonać aktualizacji systemu?',
|
||||
buttons: {
|
||||
tak: function() {
|
||||
document.location.href = '/admin/update/update/';
|
||||
},
|
||||
buttons: {
|
||||
'tak': true, 'nie': false
|
||||
}
|
||||
});
|
||||
nie: function() {}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function updateAll( version_current, version_new, version_diff, width, ac_lp)
|
||||
{
|
||||
$.ajax(
|
||||
{
|
||||
$( '#confirmUpdateAll' ).on( 'click', function( e ) {
|
||||
e.preventDefault();
|
||||
$.confirm({
|
||||
title: 'Potwierdź',
|
||||
content: 'Na pewno chcesz dokonać aktualizacji systemu do najwyższej wersji?',
|
||||
buttons: {
|
||||
tak: function() {
|
||||
$( '.progress-box' ).removeClass( 'hidden' );
|
||||
$( '#confirm' ).css( 'pointer-events', 'none' );
|
||||
$( '#confirmUpdateAll' ).css( 'pointer-events', 'none' );
|
||||
$( '.version_diff' ).html( version_diff );
|
||||
updateAll( version_current, version_new, version_diff, width, ac_lp );
|
||||
},
|
||||
nie: function() {}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
function updateAll( version_current, version_new, version_diff, width, ac_lp ) {
|
||||
$.ajax({
|
||||
url: '/admin/update/updateAll/',
|
||||
type: 'POST',
|
||||
data:
|
||||
{
|
||||
version_current: version_current
|
||||
},
|
||||
success: function( data )
|
||||
{
|
||||
response = jQuery.parseJSON( data );
|
||||
if ( response.status == true )
|
||||
{
|
||||
ac_lp = ac_lp + 1;
|
||||
$('.version_curent').html(ac_lp);
|
||||
width = width + ((1/version_diff)*100);
|
||||
$('.progress-bar').attr("style", "width:"+ width +"%");
|
||||
if( response.version < version_new )
|
||||
{
|
||||
updateAll( response.version, version_new, version_diff, width, ac_lp );
|
||||
}
|
||||
else
|
||||
{
|
||||
$.prompt( "Aktualizacja przebiegła pomyślnie.",
|
||||
{
|
||||
title: 'Informacja',
|
||||
close: function(e,v,m,f){
|
||||
window.location.href ="/admin/update/main_view/";
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$.prompt( "W trakcie aktualizacji systemu wystąpił błąd. Proszę spróbować ponownie." ,
|
||||
{
|
||||
title: 'Błąd',
|
||||
close: function(e,v,m,f){
|
||||
window.location.href ="/admin/update/main_view/";
|
||||
}
|
||||
data: { version_current: version_current },
|
||||
success: function( data ) {
|
||||
var response = jQuery.parseJSON( data );
|
||||
if ( response.status == true ) {
|
||||
ac_lp = ac_lp + 1;
|
||||
$( '.version_curent' ).html( ac_lp );
|
||||
width = width + ( ( 1 / version_diff ) * 100 );
|
||||
$( '.progress-bar' ).css( 'width', width + '%' );
|
||||
if ( response.version < version_new ) {
|
||||
updateAll( response.version, version_new, version_diff, width, ac_lp );
|
||||
} else {
|
||||
$.alert({
|
||||
title: 'Informacja',
|
||||
content: 'Aktualizacja przebiegła pomyślnie.',
|
||||
onClose: function() {
|
||||
window.location.href = '/admin/update/main_view/';
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$.alert({
|
||||
title: 'Błąd',
|
||||
content: 'W trakcie aktualizacji systemu wystąpił błąd. Proszę spróbować ponownie.',
|
||||
onClose: function() {
|
||||
window.location.href = '/admin/update/main_view/';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
</script>
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -840,4 +840,28 @@ class ArticleRepository
|
||||
|
||||
$this->db->delete('pp_articles_images', ['article_id' => null]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pobiera artykuly opublikowane w podanym zakresie dat.
|
||||
*/
|
||||
public function articlesByDateAdd( string $dateStart, string $dateEnd ): array
|
||||
{
|
||||
$stmt = $this->db->query(
|
||||
'SELECT id FROM pp_articles '
|
||||
. 'WHERE status = 1 '
|
||||
. 'AND date_add BETWEEN \'' . addslashes( $dateStart ) . '\' AND \'' . addslashes( $dateEnd ) . '\' '
|
||||
. 'ORDER BY date_add DESC'
|
||||
);
|
||||
|
||||
$articles = [];
|
||||
$rows = $stmt ? $stmt->fetchAll( \PDO::FETCH_ASSOC ) : [];
|
||||
|
||||
if ( is_array( $rows ) ) {
|
||||
foreach ( $rows as $row ) {
|
||||
$articles[] = \front\factory\Articles::article_details( $row['id'], 'pl' );
|
||||
}
|
||||
}
|
||||
|
||||
return $articles;
|
||||
}
|
||||
}
|
||||
|
||||
153
autoload/Domain/Dashboard/DashboardRepository.php
Normal file
153
autoload/Domain/Dashboard/DashboardRepository.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
namespace Domain\Dashboard;
|
||||
|
||||
class DashboardRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct( $db )
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
public function summaryOrders(): int
|
||||
{
|
||||
try {
|
||||
$redis = \RedisConnection::getInstance()->getConnection();
|
||||
if ( $redis ) {
|
||||
$cached = $redis->get( 'summary_ordersd' );
|
||||
if ( $cached !== false ) {
|
||||
return (int) unserialize( $cached );
|
||||
}
|
||||
$summary = (int) $this->db->count( 'pp_shop_orders', [ 'status' => 6 ] );
|
||||
$redis->setex( 'summary_ordersd', 300, serialize( $summary ) );
|
||||
return $summary;
|
||||
}
|
||||
} catch ( \RedisException $e ) {
|
||||
// fallback
|
||||
}
|
||||
|
||||
return (int) $this->db->count( 'pp_shop_orders', [ 'status' => 6 ] );
|
||||
}
|
||||
|
||||
public function summarySales(): float
|
||||
{
|
||||
try {
|
||||
$redis = \RedisConnection::getInstance()->getConnection();
|
||||
if ( $redis ) {
|
||||
$cached = $redis->get( 'summary_salesd' );
|
||||
if ( $cached !== false ) {
|
||||
return (float) unserialize( $cached );
|
||||
}
|
||||
$summary = $this->calculateTotalSales();
|
||||
$redis->setex( 'summary_salesd', 300, serialize( $summary ) );
|
||||
return $summary;
|
||||
}
|
||||
} catch ( \RedisException $e ) {
|
||||
// fallback
|
||||
}
|
||||
|
||||
return $this->calculateTotalSales();
|
||||
}
|
||||
|
||||
private function calculateTotalSales(): float
|
||||
{
|
||||
return (float) $this->db->sum( 'pp_shop_orders', 'summary', [ 'status' => 6 ] )
|
||||
- (float) $this->db->sum( 'pp_shop_orders', 'transport_cost', [ 'status' => 6 ] );
|
||||
}
|
||||
|
||||
public function salesGrid(): array
|
||||
{
|
||||
$grid = [];
|
||||
$rows = $this->db->select( 'pp_shop_orders', [ 'id', 'date_order' ], [ 'status' => 6 ] );
|
||||
|
||||
if ( is_array( $rows ) ) {
|
||||
foreach ( $rows as $row ) {
|
||||
$ts = strtotime( $row['date_order'] );
|
||||
$dayOfWeek = date( 'N', $ts );
|
||||
$hour = date( 'G', $ts );
|
||||
if ( !isset( $grid[$dayOfWeek][$hour] ) ) {
|
||||
$grid[$dayOfWeek][$hour] = 0;
|
||||
}
|
||||
$grid[$dayOfWeek][$hour]++;
|
||||
}
|
||||
}
|
||||
|
||||
return $grid;
|
||||
}
|
||||
|
||||
public function mostViewedProducts(): array
|
||||
{
|
||||
$stmt = $this->db->query(
|
||||
'SELECT id, SUM(visits) AS visits '
|
||||
. 'FROM pp_shop_products '
|
||||
. 'GROUP BY id '
|
||||
. 'ORDER BY visits DESC '
|
||||
. 'LIMIT 10'
|
||||
);
|
||||
|
||||
return $stmt ? $stmt->fetchAll( \PDO::FETCH_ASSOC ) : [];
|
||||
}
|
||||
|
||||
public function bestSalesProducts(): array
|
||||
{
|
||||
$stmt = $this->db->query(
|
||||
'SELECT parent_product_id, SUM(quantity) AS quantity_summary, SUM(price_brutto_promo * quantity) AS sales '
|
||||
. 'FROM pp_shop_order_products AS psop '
|
||||
. 'INNER JOIN pp_shop_orders AS pso ON pso.id = psop.order_id '
|
||||
. 'WHERE pso.status = 6 '
|
||||
. 'GROUP BY parent_product_id '
|
||||
. 'ORDER BY sales DESC '
|
||||
. 'LIMIT 10'
|
||||
);
|
||||
|
||||
return $stmt ? $stmt->fetchAll( \PDO::FETCH_ASSOC ) : [];
|
||||
}
|
||||
|
||||
public function last24MonthsSales(): array
|
||||
{
|
||||
$sales = [];
|
||||
$date = new \DateTime();
|
||||
|
||||
for ( $i = 0; $i < 24; $i++ ) {
|
||||
$dateStart = $date->format( 'Y-m-01' );
|
||||
$dateEnd = $date->format( 'Y-m-t' );
|
||||
|
||||
$where = [
|
||||
'AND' => [
|
||||
'status' => 6,
|
||||
'date_order[>=]' => $dateStart,
|
||||
'date_order[<=]' => $dateEnd,
|
||||
]
|
||||
];
|
||||
|
||||
$monthSales = (float) $this->db->sum( 'pp_shop_orders', 'summary', $where )
|
||||
- (float) $this->db->sum( 'pp_shop_orders', 'transport_cost', $where );
|
||||
|
||||
$sales[] = [
|
||||
'date' => $date->format( 'Y-m' ),
|
||||
'sales' => $monthSales,
|
||||
];
|
||||
|
||||
$date->sub( new \DateInterval( 'P1M' ) );
|
||||
}
|
||||
|
||||
return $sales;
|
||||
}
|
||||
|
||||
public function lastOrders( int $limit = 10 ): array
|
||||
{
|
||||
$stmt = $this->db->query(
|
||||
'SELECT id, number, date_order, '
|
||||
. 'CONCAT( client_name, \' \', client_surname ) AS client, '
|
||||
. 'client_email, '
|
||||
. 'CONCAT( client_street, \', \', client_postal_code, \' \', client_city ) AS address, '
|
||||
. 'status, client_phone, summary '
|
||||
. 'FROM pp_shop_orders '
|
||||
. 'ORDER BY date_order DESC '
|
||||
. 'LIMIT ' . (int) $limit
|
||||
);
|
||||
|
||||
return $stmt ? $stmt->fetchAll( \PDO::FETCH_ASSOC ) : [];
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
319
autoload/Domain/Update/UpdateRepository.php
Normal file
319
autoload/Domain/Update/UpdateRepository.php
Normal file
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
namespace Domain\Update;
|
||||
|
||||
class UpdateRepository
|
||||
{
|
||||
private $db;
|
||||
|
||||
public function __construct( $db )
|
||||
{
|
||||
$this->db = $db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wykonuje aktualizację do następnej wersji.
|
||||
*
|
||||
* @return array{success: bool, log: array, no_updates?: bool}
|
||||
*/
|
||||
public function update(): array
|
||||
{
|
||||
global $settings;
|
||||
|
||||
@file_put_contents( '../libraries/update_log.txt', '' );
|
||||
|
||||
$log = [];
|
||||
$log[] = '[START] Rozpoczęcie aktualizacji - ' . date( 'Y-m-d H:i:s' );
|
||||
$log[] = '[INFO] Aktualna wersja: ' . \S::get_version();
|
||||
|
||||
\S::delete_session( 'new-version' );
|
||||
|
||||
$versionsUrl = 'https://shoppro.project-dc.pl/updates/versions.php?key=' . $settings['update_key'];
|
||||
$versions = @file_get_contents( $versionsUrl );
|
||||
|
||||
if ( $versions === false ) {
|
||||
$log[] = '[ERROR] Nie udało się pobrać listy wersji z: ' . $versionsUrl;
|
||||
$this->saveLog( $log );
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$log[] = '[OK] Pobrano listę wersji';
|
||||
$versions = explode( PHP_EOL, $versions );
|
||||
$log[] = '[INFO] Znaleziono ' . count( $versions ) . ' wersji do sprawdzenia';
|
||||
|
||||
foreach ( $versions as $ver ) {
|
||||
$ver = trim( $ver );
|
||||
if ( floatval( $ver ) <= (float) \S::get_version() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$log[] = '[INFO] Aktualizacja do wersji: ' . $ver;
|
||||
$dir = strlen( $ver ) == 5
|
||||
? substr( $ver, 0, strlen( $ver ) - 2 ) . '0'
|
||||
: substr( $ver, 0, strlen( $ver ) - 1 ) . '0';
|
||||
|
||||
$result = $this->downloadAndApply( $ver, $dir, $log );
|
||||
$this->saveLog( $result['log'] );
|
||||
return $result;
|
||||
}
|
||||
|
||||
$log[] = '[INFO] Brak nowych wersji do zainstalowania';
|
||||
$this->saveLog( $log );
|
||||
return [ 'success' => true, 'log' => $log, 'no_updates' => true ];
|
||||
}
|
||||
|
||||
private function downloadAndApply( string $ver, string $dir, array $log ): array
|
||||
{
|
||||
$baseUrl = 'https://shoppro.project-dc.pl/updates/' . $dir;
|
||||
|
||||
// Pobieranie ZIP
|
||||
$zipUrl = $baseUrl . '/ver_' . $ver . '.zip';
|
||||
$log[] = '[INFO] Pobieranie pliku ZIP: ' . $zipUrl;
|
||||
$file = @file_get_contents( $zipUrl );
|
||||
|
||||
if ( $file === false ) {
|
||||
$log[] = '[ERROR] Nie udało się pobrać pliku ZIP';
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$fileSize = strlen( $file );
|
||||
$log[] = '[OK] Pobrano plik ZIP, rozmiar: ' . $fileSize . ' bajtów';
|
||||
|
||||
if ( $fileSize < 100 ) {
|
||||
$log[] = '[ERROR] Plik ZIP jest za mały (prawdopodobnie błąd pobierania)';
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$dlHandler = @fopen( 'update.zip', 'w' );
|
||||
if ( !$dlHandler ) {
|
||||
$log[] = '[ERROR] Nie udało się otworzyć pliku update.zip do zapisu';
|
||||
$log[] = '[INFO] Katalog roboczy: ' . getcwd();
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$written = fwrite( $dlHandler, $file );
|
||||
fclose( $dlHandler );
|
||||
|
||||
if ( $written === false || $written === 0 ) {
|
||||
$log[] = '[ERROR] Nie udało się zapisać pliku ZIP';
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$log[] = '[OK] Zapisano plik ZIP (' . $written . ' bajtów)';
|
||||
|
||||
// Wykonanie SQL
|
||||
$log = $this->executeSql( $baseUrl . '/ver_' . $ver . '_sql.txt', $log );
|
||||
|
||||
// Usuwanie plików
|
||||
$log = $this->deleteFiles( $baseUrl . '/ver_' . $ver . '_files.txt', $log );
|
||||
|
||||
// Rozpakowywanie ZIP
|
||||
$log = $this->extractZip( 'update.zip', $log );
|
||||
|
||||
// Aktualizacja wersji
|
||||
$versionFile = '../libraries/version.ini';
|
||||
$handle = @fopen( $versionFile, 'w' );
|
||||
if ( !$handle ) {
|
||||
$log[] = '[ERROR] Nie udało się otworzyć pliku version.ini do zapisu';
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
fwrite( $handle, $ver );
|
||||
fclose( $handle );
|
||||
|
||||
$log[] = '[OK] Zaktualizowano plik version.ini do wersji: ' . $ver;
|
||||
$log[] = '[SUCCESS] Aktualizacja do wersji ' . $ver . ' zakończona pomyślnie';
|
||||
|
||||
return [ 'success' => true, 'log' => $log ];
|
||||
}
|
||||
|
||||
private function executeSql( string $sqlUrl, array $log ): array
|
||||
{
|
||||
$log[] = '[INFO] Sprawdzanie aktualizacji SQL: ' . $sqlUrl;
|
||||
|
||||
$ch = curl_init( $sqlUrl );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
curl_setopt( $ch, CURLOPT_HEADER, false );
|
||||
$response = curl_exec( $ch );
|
||||
$contentType = curl_getinfo( $ch, CURLINFO_CONTENT_TYPE );
|
||||
$httpCode = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||
curl_close( $ch );
|
||||
|
||||
if ( !$response || strpos( $contentType, 'text/plain' ) === false ) {
|
||||
$log[] = '[INFO] Brak aktualizacji SQL (HTTP: ' . $httpCode . ')';
|
||||
return $log;
|
||||
}
|
||||
|
||||
$queries = explode( PHP_EOL, $response );
|
||||
$log[] = '[OK] Pobrano ' . count( $queries ) . ' zapytań SQL';
|
||||
$success = 0;
|
||||
$errors = 0;
|
||||
|
||||
foreach ( $queries as $query ) {
|
||||
$query = trim( $query );
|
||||
if ( $query !== '' ) {
|
||||
if ( $this->db->query( $query ) ) {
|
||||
$success++;
|
||||
} else {
|
||||
$errors++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$log[] = '[INFO] Wykonano zapytania SQL - sukces: ' . $success . ', błędy: ' . $errors;
|
||||
return $log;
|
||||
}
|
||||
|
||||
private function deleteFiles( string $filesUrl, array $log ): array
|
||||
{
|
||||
$log[] = '[INFO] Sprawdzanie plików do usunięcia: ' . $filesUrl;
|
||||
|
||||
$ch = curl_init( $filesUrl );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
curl_setopt( $ch, CURLOPT_HEADER, false );
|
||||
$response = curl_exec( $ch );
|
||||
$contentType = curl_getinfo( $ch, CURLINFO_CONTENT_TYPE );
|
||||
curl_close( $ch );
|
||||
|
||||
if ( !$response || strpos( $contentType, 'text/plain' ) === false ) {
|
||||
$log[] = '[INFO] Brak plików do usunięcia';
|
||||
return $log;
|
||||
}
|
||||
|
||||
$files = explode( PHP_EOL, $response );
|
||||
$deletedFiles = 0;
|
||||
$deletedDirs = 0;
|
||||
|
||||
foreach ( $files as $entry ) {
|
||||
if ( strpos( $entry, 'F: ' ) !== false ) {
|
||||
$path = substr( $entry, 3 );
|
||||
if ( file_exists( $path ) ) {
|
||||
if ( @unlink( $path ) ) {
|
||||
$deletedFiles++;
|
||||
} else {
|
||||
$log[] = '[WARNING] Nie udało się usunąć pliku: ' . $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( strpos( $entry, 'D: ' ) !== false ) {
|
||||
$path = substr( $entry, 3 );
|
||||
if ( is_dir( $path ) ) {
|
||||
\S::delete_dir( $path );
|
||||
$deletedDirs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$log[] = '[INFO] Usunięto plików: ' . $deletedFiles . ', katalogów: ' . $deletedDirs;
|
||||
return $log;
|
||||
}
|
||||
|
||||
private function extractZip( string $fileName, array $log ): array
|
||||
{
|
||||
$log[] = '[INFO] Rozpoczęcie rozpakowywania pliku ZIP';
|
||||
|
||||
$path = pathinfo( realpath( $fileName ), PATHINFO_DIRNAME );
|
||||
$path = substr( $path, 0, strlen( $path ) - 5 );
|
||||
|
||||
if ( !is_dir( $path ) || !is_writable( $path ) ) {
|
||||
$log[] = '[ERROR] Ścieżka docelowa nie istnieje lub brak uprawnień: ' . $path;
|
||||
return $log;
|
||||
}
|
||||
|
||||
$zip = new \ZipArchive;
|
||||
$res = $zip->open( $fileName );
|
||||
|
||||
if ( $res !== true ) {
|
||||
$log[] = '[ERROR] Nie udało się otworzyć pliku ZIP (kod: ' . $res . ')';
|
||||
return $log;
|
||||
}
|
||||
|
||||
$log[] = '[OK] Otwarto archiwum ZIP, liczba plików: ' . $zip->numFiles;
|
||||
$extracted = 0;
|
||||
$errors = 0;
|
||||
|
||||
for ( $i = 0; $i < $zip->numFiles; $i++ ) {
|
||||
$filename = str_replace( '\\', '/', $zip->getNameIndex( $i ) );
|
||||
|
||||
if ( substr( $filename, -1 ) === '/' ) {
|
||||
$dirPath = $path . '/' . $filename;
|
||||
if ( !is_dir( $dirPath ) ) {
|
||||
@mkdir( $dirPath, 0755, true );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
$targetFile = $path . '/' . $filename;
|
||||
$targetDir = dirname( $targetFile );
|
||||
|
||||
if ( !is_dir( $targetDir ) ) {
|
||||
@mkdir( $targetDir, 0755, true );
|
||||
}
|
||||
|
||||
$existed = file_exists( $targetFile );
|
||||
$content = $zip->getFromIndex( $i );
|
||||
|
||||
if ( $content === false ) {
|
||||
$log[] = '[ERROR] Nie udało się odczytać z ZIP: ' . $filename;
|
||||
$errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( @file_put_contents( $targetFile, $content ) === false ) {
|
||||
$log[] = '[ERROR] Nie udało się zapisać: ' . $filename;
|
||||
$errors++;
|
||||
} else {
|
||||
$tag = $existed ? '[UPDATED]' : '[NEW]';
|
||||
$log[] = $tag . ' ' . $filename . ' (' . strlen( $content ) . ' bajtów)';
|
||||
$extracted++;
|
||||
}
|
||||
}
|
||||
|
||||
$log[] = '[OK] Rozpakowano ' . $extracted . ' plików, błędów: ' . $errors;
|
||||
$zip->close();
|
||||
|
||||
if ( @unlink( $fileName ) ) {
|
||||
$log[] = '[OK] Usunięto plik update.zip';
|
||||
}
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
private function saveLog( array $log ): void
|
||||
{
|
||||
@file_put_contents( '../libraries/update_log.txt', implode( "\n", $log ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Wykonuje zaległe migracje z tabeli pp_updates.
|
||||
*/
|
||||
public function runPendingMigrations(): void
|
||||
{
|
||||
$results = $this->db->select( 'pp_updates', [ 'name' ], [ 'done' => 0 ] );
|
||||
if ( !is_array( $results ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $results as $row ) {
|
||||
$method = $row['name'];
|
||||
if ( method_exists( $this, $method ) ) {
|
||||
$this->$method();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function update0197(): void
|
||||
{
|
||||
$rows = $this->db->select( 'pp_shop_order_products', [ 'id', 'product_id' ], [ 'parent_product_id' => null ] );
|
||||
|
||||
if ( is_array( $rows ) ) {
|
||||
foreach ( $rows as $row ) {
|
||||
$parentId = $this->db->get( 'pp_shop_products', 'parent_id', [ 'id' => $row['product_id'] ] );
|
||||
$this->db->update( 'pp_shop_order_products', [
|
||||
'parent_product_id' => $parentId ?: $row['product_id'],
|
||||
], [ 'id' => $row['id'] ] );
|
||||
}
|
||||
}
|
||||
|
||||
$this->db->update( 'pp_updates', [ 'done' => 1 ], [ 'name' => 'update0197' ] );
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,17 @@
|
||||
<?php
|
||||
namespace admin;
|
||||
|
||||
class Site
|
||||
class App
|
||||
{
|
||||
// define APP_SECRET_KEY
|
||||
const APP_SECRET_KEY = 'c3cb2537d25c0efc9e573d059d79c3b8';
|
||||
|
||||
static public function finalize_admin_login( array $user, string $domain, string $cookie_name, bool $remember = false ) {
|
||||
/**
|
||||
* Mapa nowych kontrolerów: module => fabryka kontrolera (DI)
|
||||
*/
|
||||
private static $newControllers = [];
|
||||
|
||||
public static function finalize_admin_login( array $user, string $domain, string $cookie_name, bool $remember = false )
|
||||
{
|
||||
\S::set_session( 'user', $user );
|
||||
\S::delete_session( 'twofa_pending' );
|
||||
|
||||
@@ -17,18 +21,18 @@ class Site
|
||||
'ts' => time()
|
||||
];
|
||||
|
||||
$json = json_encode($payloadArr, JSON_UNESCAPED_SLASHES);
|
||||
$sig = hash_hmac('sha256', $json, self::APP_SECRET_KEY);
|
||||
$payload = base64_encode($json . '.' . $sig);
|
||||
$json = json_encode( $payloadArr, JSON_UNESCAPED_SLASHES );
|
||||
$sig = hash_hmac( 'sha256', $json, self::APP_SECRET_KEY );
|
||||
$payload = base64_encode( $json . '.' . $sig );
|
||||
|
||||
setcookie( $cookie_name, $payload, [
|
||||
'expires' => time() + (86400 * 14),
|
||||
'expires' => time() + ( 86400 * 14 ),
|
||||
'path' => '/',
|
||||
'domain' => $domain,
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
] );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,165 +40,183 @@ class Site
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$sa = \S::get('s-action');
|
||||
$domain = preg_replace('/^www\./', '', $_SERVER['SERVER_NAME']);
|
||||
$cookie_name = 'admin_remember_' . str_replace( '.', '-', $domain );
|
||||
$users = new \Domain\User\UserRepository($mdb);
|
||||
$sa = \S::get( 's-action' );
|
||||
if ( !$sa ) return;
|
||||
|
||||
switch ($sa)
|
||||
$domain = preg_replace( '/^www\./', '', $_SERVER['SERVER_NAME'] );
|
||||
$cookie_name = 'admin_remember_' . str_replace( '.', '-', $domain );
|
||||
$users = new \Domain\User\UserRepository( $mdb );
|
||||
|
||||
switch ( $sa )
|
||||
{
|
||||
case 'user-logon':
|
||||
{
|
||||
$login = \S::get('login');
|
||||
$pass = \S::get('password');
|
||||
|
||||
$result = $users->logon($login, $pass);
|
||||
$login = \S::get( 'login' );
|
||||
$pass = \S::get( 'password' );
|
||||
$result = $users->logon( $login, $pass );
|
||||
|
||||
if ( $result == 1 )
|
||||
{
|
||||
$user = $users->details($login);
|
||||
$user = $users->details( $login );
|
||||
|
||||
if ( $user['twofa_enabled'] == 1 )
|
||||
{
|
||||
\S::set_session( 'twofa_pending', [
|
||||
'uid' => (int)$user['id'],
|
||||
'uid' => (int) $user['id'],
|
||||
'login' => $login,
|
||||
'remember' => (bool)\S::get('remember'),
|
||||
'remember' => (bool) \S::get( 'remember' ),
|
||||
'started' => time(),
|
||||
] );
|
||||
|
||||
if ( !$users->sendTwofaCode( (int)$user['id'] ) )
|
||||
if ( !$users->sendTwofaCode( (int) $user['id'] ) )
|
||||
{
|
||||
\S::alert('Nie udało się wysłać kodu 2FA. Spróbuj ponownie.');
|
||||
\S::delete_session('twofa_pending');
|
||||
header('Location: /admin/');
|
||||
\S::alert( 'Nie udało się wysłać kodu 2FA. Spróbuj ponownie.' );
|
||||
\S::delete_session( 'twofa_pending' );
|
||||
header( 'Location: /admin/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
header('Location: /admin/user/twofa/');
|
||||
header( 'Location: /admin/user/twofa/' );
|
||||
exit;
|
||||
}
|
||||
else
|
||||
{
|
||||
$user = $users->details($login);
|
||||
|
||||
self::finalize_admin_login(
|
||||
$user,
|
||||
$domain,
|
||||
$cookie_name,
|
||||
(bool)\S::get('remember')
|
||||
);
|
||||
|
||||
header('Location: /admin/articles/list/');
|
||||
exit;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if ($result == -1)
|
||||
{
|
||||
\S::alert('Z powodu 5 nieudanych prób Twoje konto zostało zablokowane.');
|
||||
}
|
||||
else
|
||||
{
|
||||
\S::alert('Podane hasło jest nieprawidłowe lub użytkownik nie istnieje.');
|
||||
}
|
||||
header('Location: /admin/');
|
||||
self::finalize_admin_login( $user, $domain, $cookie_name, (bool) \S::get( 'remember' ) );
|
||||
header( 'Location: /admin/articles/list/' );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
if ( $result == -1 )
|
||||
\S::alert( 'Z powodu 5 nieudanych prób Twoje konto zostało zablokowane.' );
|
||||
else
|
||||
\S::alert( 'Podane hasło jest nieprawidłowe lub użytkownik nie istnieje.' );
|
||||
|
||||
header( 'Location: /admin/' );
|
||||
exit;
|
||||
|
||||
case 'user-2fa-verify':
|
||||
{
|
||||
$pending = \S::get_session('twofa_pending');
|
||||
$pending = \S::get_session( 'twofa_pending' );
|
||||
if ( !$pending || empty( $pending['uid'] ) ) {
|
||||
\S::alert('Sesja 2FA wygasła. Zaloguj się ponownie.');
|
||||
header('Location: /admin/');
|
||||
\S::alert( 'Sesja 2FA wygasła. Zaloguj się ponownie.' );
|
||||
header( 'Location: /admin/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
$code = trim((string)\S::get('twofa'));
|
||||
if (!preg_match('/^\d{6}$/', $code))
|
||||
$code = trim( (string) \S::get( 'twofa' ) );
|
||||
if ( !preg_match( '/^\d{6}$/', $code ) )
|
||||
{
|
||||
\S::alert('Nieprawidłowy format kodu.');
|
||||
header('Location: /admin/user/twofa/');
|
||||
\S::alert( 'Nieprawidłowy format kodu.' );
|
||||
header( 'Location: /admin/user/twofa/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
$ok = $users->verifyTwofaCode((int)$pending['uid'], $code);
|
||||
if (!$ok)
|
||||
if ( !$users->verifyTwofaCode( (int) $pending['uid'], $code ) )
|
||||
{
|
||||
\S::alert('Błędny lub wygasły kod.');
|
||||
header('Location: /admin/user/twofa/');
|
||||
\S::alert( 'Błędny lub wygasły kod.' );
|
||||
header( 'Location: /admin/user/twofa/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
// 2FA OK - finalna sesja
|
||||
$user = $users->details($pending['login']);
|
||||
|
||||
self::finalize_admin_login(
|
||||
$user,
|
||||
$domain,
|
||||
$cookie_name,
|
||||
$pending['remember'] ? true : false
|
||||
);
|
||||
|
||||
header('Location: /admin/articles/list/');
|
||||
$user = $users->details( $pending['login'] );
|
||||
self::finalize_admin_login( $user, $domain, $cookie_name, !empty( $pending['remember'] ) );
|
||||
header( 'Location: /admin/articles/list/' );
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'user-2fa-resend':
|
||||
{
|
||||
$pending = \S::get_session('twofa_pending');
|
||||
if (!$pending || empty($pending['uid']))
|
||||
$pending = \S::get_session( 'twofa_pending' );
|
||||
if ( !$pending || empty( $pending['uid'] ) )
|
||||
{
|
||||
\S::alert('Sesja 2FA wygasła. Zaloguj się ponownie.');
|
||||
header('Location: /admin/');
|
||||
\S::alert( 'Sesja 2FA wygasła. Zaloguj się ponownie.' );
|
||||
header( 'Location: /admin/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!$users->sendTwofaCode((int)$pending['uid'], true))
|
||||
{
|
||||
\S::alert('Kod można wysłać ponownie po krótkiej przerwie.');
|
||||
}
|
||||
if ( !$users->sendTwofaCode( (int) $pending['uid'], true ) )
|
||||
\S::alert( 'Kod można wysłać ponownie po krótkiej przerwie.' );
|
||||
else
|
||||
{
|
||||
\S::alert('Nowy kod został wysłany.');
|
||||
}
|
||||
header('Location: /admin/user/twofa/');
|
||||
\S::alert( 'Nowy kod został wysłany.' );
|
||||
|
||||
header( 'Location: /admin/user/twofa/' );
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'user-logout':
|
||||
{
|
||||
setcookie($cookie_name, "", [
|
||||
setcookie( $cookie_name, '', [
|
||||
'expires' => time() - 86400,
|
||||
'path' => '/',
|
||||
'domain' => $domain,
|
||||
'secure' => true,
|
||||
'httponly' => true,
|
||||
'samesite' => 'Lax',
|
||||
]);
|
||||
\S::delete_session('twofa_pending');
|
||||
] );
|
||||
\S::delete_session( 'twofa_pending' );
|
||||
session_destroy();
|
||||
header('Location: /admin/');
|
||||
header( 'Location: /admin/' );
|
||||
exit;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mapa nowych kontrolerów: module => fabryka kontrolera (DI)
|
||||
* Przy migracji kolejnego kontrolera - dodaj wpis tutaj
|
||||
* Entry point — auth check + layout rendering.
|
||||
*/
|
||||
private static $newControllers = [];
|
||||
public static function render(): string
|
||||
{
|
||||
global $user;
|
||||
|
||||
if ( \S::get( 'module' ) === 'user' && \S::get( 'action' ) === 'twofa' ) {
|
||||
$controller = self::createController( 'Users' );
|
||||
return $controller->twofa();
|
||||
}
|
||||
|
||||
if ( !$user || !$user['admin'] )
|
||||
{
|
||||
$controller = self::createController( 'Users' );
|
||||
return $controller->login_form();
|
||||
}
|
||||
|
||||
$tpl = new \Tpl;
|
||||
$tpl->content = self::route();
|
||||
return $tpl->render( 'site/main-layout' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwraca mapę fabryk kontrolerów (inicjalizacja runtime)
|
||||
* Routing — buduje nazwę modułu z URL i wywołuje akcję kontrolera.
|
||||
*/
|
||||
public static function route()
|
||||
{
|
||||
$_SESSION['admin'] = true;
|
||||
|
||||
if ( \S::get( 'p' ) )
|
||||
\S::set_session( 'p', \S::get( 'p' ) );
|
||||
|
||||
// Budowanie nazwy modułu: shop_product → ShopProduct
|
||||
$moduleName = '';
|
||||
$parts = explode( '_', (string) \S::get( 'module' ) );
|
||||
foreach ( $parts as $part )
|
||||
$moduleName .= ucfirst( $part );
|
||||
|
||||
$action = \S::get( 'action' );
|
||||
|
||||
$controller = self::createController( $moduleName );
|
||||
if ( $controller && method_exists( $controller, $action ) )
|
||||
return $controller->$action();
|
||||
|
||||
\S::alert( 'Nieprawidłowy adres url.' );
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tworzy instancję kontrolera z Dependency Injection.
|
||||
*/
|
||||
private static function createController( string $moduleName )
|
||||
{
|
||||
$factories = self::getControllerFactories();
|
||||
if ( !isset( $factories[$moduleName] ) )
|
||||
return null;
|
||||
|
||||
$factory = $factories[$moduleName];
|
||||
return is_callable( $factory ) ? $factory() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Zwraca mapę fabryk kontrolerów (lazy init).
|
||||
*/
|
||||
private static function getControllerFactories(): array
|
||||
{
|
||||
@@ -202,9 +224,15 @@ class Site
|
||||
return self::$newControllers;
|
||||
|
||||
self::$newControllers = [
|
||||
'Dashboard' => function() {
|
||||
global $mdb;
|
||||
return new \admin\Controllers\DashboardController(
|
||||
new \Domain\Dashboard\DashboardRepository( $mdb ),
|
||||
new \Domain\ShopStatus\ShopStatusRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'Articles' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ArticlesController(
|
||||
new \Domain\Article\ArticleRepository( $mdb ),
|
||||
new \Domain\Languages\LanguagesRepository( $mdb ),
|
||||
@@ -214,14 +242,12 @@ class Site
|
||||
},
|
||||
'ArticlesArchive' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ArticlesArchiveController(
|
||||
new \Domain\Article\ArticleRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'Banners' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\BannerController(
|
||||
new \Domain\Banner\BannerRepository( $mdb ),
|
||||
new \Domain\Languages\LanguagesRepository( $mdb )
|
||||
@@ -229,7 +255,6 @@ class Site
|
||||
},
|
||||
'Settings' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\SettingsController(
|
||||
new \Domain\Settings\SettingsRepository( $mdb ),
|
||||
new \Domain\Languages\LanguagesRepository( $mdb )
|
||||
@@ -237,22 +262,18 @@ class Site
|
||||
},
|
||||
'ProductArchive' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ProductArchiveController(
|
||||
new \Domain\Product\ProductRepository( $mdb )
|
||||
);
|
||||
},
|
||||
// Alias dla starego modułu /admin/archive/list/
|
||||
'Archive' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ProductArchiveController(
|
||||
new \Domain\Product\ProductRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'Dictionaries' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\DictionariesController(
|
||||
new \Domain\Dictionaries\DictionariesRepository( $mdb ),
|
||||
new \Domain\Languages\LanguagesRepository( $mdb )
|
||||
@@ -263,21 +284,18 @@ class Site
|
||||
},
|
||||
'Users' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\UsersController(
|
||||
new \Domain\User\UserRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'Languages' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\LanguagesController(
|
||||
new \Domain\Languages\LanguagesRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'Layouts' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\LayoutsController(
|
||||
new \Domain\Layouts\LayoutsRepository( $mdb ),
|
||||
new \Domain\Languages\LanguagesRepository( $mdb )
|
||||
@@ -285,7 +303,6 @@ class Site
|
||||
},
|
||||
'Newsletter' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\NewsletterController(
|
||||
new \Domain\Newsletter\NewsletterRepository(
|
||||
$mdb,
|
||||
@@ -296,7 +313,6 @@ class Site
|
||||
},
|
||||
'Scontainers' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ScontainersController(
|
||||
new \Domain\Scontainers\ScontainersRepository( $mdb ),
|
||||
new \Domain\Languages\LanguagesRepository( $mdb )
|
||||
@@ -304,21 +320,18 @@ class Site
|
||||
},
|
||||
'ShopPromotion' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ShopPromotionController(
|
||||
new \Domain\Promotion\PromotionRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'ShopCoupon' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ShopCouponController(
|
||||
new \Domain\Coupon\CouponRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'ShopAttribute' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ShopAttributeController(
|
||||
new \Domain\Attribute\AttributeRepository( $mdb ),
|
||||
new \Domain\Languages\LanguagesRepository( $mdb )
|
||||
@@ -326,14 +339,12 @@ class Site
|
||||
},
|
||||
'ShopPaymentMethod' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ShopPaymentMethodController(
|
||||
new \Domain\PaymentMethod\PaymentMethodRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'ShopTransport' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ShopTransportController(
|
||||
new \Domain\Transport\TransportRepository( $mdb ),
|
||||
new \Domain\PaymentMethod\PaymentMethodRepository( $mdb )
|
||||
@@ -341,7 +352,6 @@ class Site
|
||||
},
|
||||
'Pages' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\PagesController(
|
||||
new \Domain\Pages\PagesRepository( $mdb ),
|
||||
new \Domain\Languages\LanguagesRepository( $mdb ),
|
||||
@@ -350,28 +360,24 @@ class Site
|
||||
},
|
||||
'Integrations' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\IntegrationsController(
|
||||
new \Domain\Integrations\IntegrationsRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'ShopStatuses' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ShopStatusesController(
|
||||
new \Domain\ShopStatus\ShopStatusRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'ShopProductSets' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ShopProductSetsController(
|
||||
new \Domain\ProductSet\ProductSetRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'ShopProducer' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ShopProducerController(
|
||||
new \Domain\Producer\ProducerRepository( $mdb ),
|
||||
new \Domain\Languages\LanguagesRepository( $mdb )
|
||||
@@ -379,7 +385,6 @@ class Site
|
||||
},
|
||||
'ShopCategory' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ShopCategoryController(
|
||||
new \Domain\Category\CategoryRepository( $mdb ),
|
||||
new \Domain\Languages\LanguagesRepository( $mdb )
|
||||
@@ -387,116 +392,41 @@ class Site
|
||||
},
|
||||
'ShopProduct' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ShopProductController(
|
||||
new \Domain\Product\ProductRepository( $mdb )
|
||||
new \Domain\Product\ProductRepository( $mdb ),
|
||||
new \Domain\Integrations\IntegrationsRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'ShopClients' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ShopClientsController(
|
||||
new \Domain\Client\ClientRepository( $mdb )
|
||||
);
|
||||
},
|
||||
'ShopOrder' => function() {
|
||||
global $mdb;
|
||||
|
||||
return new \admin\Controllers\ShopOrderController(
|
||||
new \Domain\Order\OrderAdminService(
|
||||
new \Domain\Order\OrderRepository( $mdb )
|
||||
)
|
||||
);
|
||||
},
|
||||
'Update' => function() {
|
||||
global $mdb;
|
||||
return new \admin\Controllers\UpdateController(
|
||||
new \Domain\Update\UpdateRepository( $mdb )
|
||||
);
|
||||
},
|
||||
];
|
||||
|
||||
return self::$newControllers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tworzy instancję nowego kontrolera z Dependency Injection
|
||||
*/
|
||||
private static function createController( string $moduleName )
|
||||
public static function update()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$factories = self::getControllerFactories();
|
||||
if ( !isset( $factories[$moduleName] ) )
|
||||
return null;
|
||||
|
||||
$factory = $factories[$moduleName];
|
||||
if ( !is_callable( $factory ) )
|
||||
return null;
|
||||
|
||||
return $factory();
|
||||
}
|
||||
|
||||
|
||||
public static function route()
|
||||
{
|
||||
$_SESSION['admin'] = true;
|
||||
|
||||
if ( \S::get( 'p' ) )
|
||||
\S::set_session( 'p' , \S::get( 'p' ) );
|
||||
|
||||
$page = \S::get_session( 'p' );
|
||||
|
||||
// Budowanie nazwy modułu
|
||||
$moduleName = '';
|
||||
$results = explode( '_', \S::get( 'module' ) );
|
||||
if ( is_array( $results ) ) foreach ( $results as $row )
|
||||
$moduleName .= ucfirst( $row );
|
||||
|
||||
$action = \S::get( 'action' );
|
||||
|
||||
// 1. Sprawdź czy istnieje nowy kontroler
|
||||
$factories = self::getControllerFactories();
|
||||
if ( isset( $factories[$moduleName] ) )
|
||||
{
|
||||
$controller = self::createController( $moduleName );
|
||||
if ( $controller )
|
||||
{
|
||||
if ( method_exists( $controller, $action ) )
|
||||
{
|
||||
return $controller->$action();
|
||||
}
|
||||
|
||||
if ( $moduleName === 'ShopAttribute' )
|
||||
{
|
||||
\S::alert( 'Nieprawidłowy adres url.' );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 2. Fallback na stary kontroler
|
||||
$class = '\admin\controls\\' . $moduleName;
|
||||
|
||||
if ( class_exists( $class ) and method_exists( new $class, $action ) )
|
||||
return call_user_func_array( array( $class, $action ), array() );
|
||||
else
|
||||
{
|
||||
\S::alert( 'Nieprawidłowy adres url.' );
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static public function update()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
if ( $results = $mdb -> select( 'pp_updates', [ 'name' ], [ 'done' => 0 ] ) )
|
||||
{
|
||||
foreach ( $results as $row )
|
||||
{
|
||||
$class = '\admin\factory\Update';
|
||||
$method = $row['name'];
|
||||
|
||||
if ( class_exists( $class ) and method_exists( new $class, $method ) )
|
||||
call_user_func_array( array( $class, $method ), array() );
|
||||
}
|
||||
}
|
||||
$repository = new \Domain\Update\UpdateRepository( $mdb );
|
||||
$repository->runPendingMigrations();
|
||||
}
|
||||
}
|
||||
|
||||
31
autoload/admin/Controllers/DashboardController.php
Normal file
31
autoload/admin/Controllers/DashboardController.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
namespace admin\Controllers;
|
||||
|
||||
use Domain\Dashboard\DashboardRepository;
|
||||
use Domain\ShopStatus\ShopStatusRepository;
|
||||
|
||||
class DashboardController
|
||||
{
|
||||
private DashboardRepository $repository;
|
||||
private ShopStatusRepository $statusesRepository;
|
||||
|
||||
public function __construct( DashboardRepository $repository, ShopStatusRepository $statusesRepository )
|
||||
{
|
||||
$this->repository = $repository;
|
||||
$this->statusesRepository = $statusesRepository;
|
||||
}
|
||||
|
||||
public function main_view(): string
|
||||
{
|
||||
return \Tpl::view( 'dashboard/main-view', [
|
||||
'last_orders' => $this->repository->lastOrders(),
|
||||
'order_statuses' => $this->statusesRepository->allStatuses(),
|
||||
'sales' => $this->repository->last24MonthsSales(),
|
||||
'best_sales_products' => $this->repository->bestSalesProducts(),
|
||||
'most_view_products' => $this->repository->mostViewedProducts(),
|
||||
'sales_grid' => $this->repository->salesGrid(),
|
||||
'summary_sales' => $this->repository->summarySales(),
|
||||
'summary_orders' => $this->repository->summaryOrders(),
|
||||
] );
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ class ProductArchiveController
|
||||
$imageSrc = '/' . ltrim($imageSrc, '/');
|
||||
}
|
||||
|
||||
$categories = trim((string)\admin\factory\ShopProduct::product_categories($id));
|
||||
$categories = trim((string)$this->repository->productCategoriesText($id));
|
||||
$categoriesHtml = '';
|
||||
if ($categories !== '') {
|
||||
$categoriesHtml = '<small class="text-muted product-categories">'
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
49
autoload/admin/Controllers/UpdateController.php
Normal file
49
autoload/admin/Controllers/UpdateController.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace admin\Controllers;
|
||||
|
||||
use Domain\Update\UpdateRepository;
|
||||
|
||||
class UpdateController
|
||||
{
|
||||
private UpdateRepository $repository;
|
||||
|
||||
public function __construct( UpdateRepository $repository )
|
||||
{
|
||||
$this->repository = $repository;
|
||||
}
|
||||
|
||||
public function main_view(): string
|
||||
{
|
||||
return \Tpl::view( 'update/main-view', [
|
||||
'ver' => \S::get_version(),
|
||||
'new_ver' => \S::get_new_version(),
|
||||
] );
|
||||
}
|
||||
|
||||
public function update(): void
|
||||
{
|
||||
$result = $this->repository->update();
|
||||
|
||||
if ( !$result['success'] ) {
|
||||
\S::alert( 'W trakcie aktualizacji systemu wystąpił błąd. Proszę spróbować ponownie.' );
|
||||
} else {
|
||||
\S::set_message( 'Aktualizacja przebiegła pomyślnie.' );
|
||||
}
|
||||
|
||||
header( 'Location: /admin/update/main_view/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
public function updateAll(): void
|
||||
{
|
||||
$result = $this->repository->update();
|
||||
|
||||
$response = [
|
||||
'status' => !empty( $result['success'] ) && empty( $result['no_updates'] ),
|
||||
'version' => number_format( (float) \S::get( 'version_current' ) + 0.001, 3, '.', '' ),
|
||||
];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?
|
||||
namespace admin\controls;
|
||||
class Dashboard
|
||||
{
|
||||
static public function main_view()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$statusesRepository = new \Domain\ShopStatus\ShopStatusRepository( $mdb );
|
||||
|
||||
return \Tpl::view( 'dashboard/main-view', [
|
||||
'last_orders' => \shop\Dashboard::last_orders(),
|
||||
'order_statuses' => $statusesRepository -> allStatuses(),
|
||||
'sales' => \shop\Dashboard::last_24_months_sales(),
|
||||
'best_sales_products' => \shop\Dashboard::best_sales_products(),
|
||||
'most_view_products' => \shop\Dashboard::most_view_products(),
|
||||
'sales_grid' => \shop\Dashboard::sales_grid(),
|
||||
'summary_sales' => \shop\Dashboard::summary_sales(),
|
||||
'summary_orders' => \shop\Dashboard::summary_orders(),
|
||||
] );
|
||||
}
|
||||
}
|
||||
@@ -1,381 +0,0 @@
|
||||
<?php
|
||||
namespace admin\controls;
|
||||
class ShopProduct
|
||||
{
|
||||
static public function generate_combination()
|
||||
{
|
||||
foreach ( $_POST as $key => $val )
|
||||
{
|
||||
if ( strpos( $key, 'attribute_' ) !== false )
|
||||
{
|
||||
$attribute = explode( 'attribute_', $key );
|
||||
$attributes[ $attribute[1] ] = $val;
|
||||
}
|
||||
}
|
||||
|
||||
if ( \admin\factory\ShopProduct::generate_permutation( (int) \S::get( 'product_id' ), $attributes ) )
|
||||
\S::alert( 'Kombinacje produktu zostały wygenerowane.' );
|
||||
|
||||
header( 'Location: /admin/shop_product/product_combination/product_id=' . (int) \S::get( 'product_id' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
//usunięcie kombinacji produktu
|
||||
static public function delete_combination()
|
||||
{
|
||||
if ( \admin\factory\ShopProduct::delete_combination( (int)\S::get( 'combination_id' ) ) )
|
||||
\S::alert( 'Kombinacja produktu została usunięta' );
|
||||
else
|
||||
\S::alert( 'Podczas usuwania kombinacji produktu wystąpił błąd. Proszę spróbować ponownie' );
|
||||
|
||||
header( 'Location: /admin/shop_product/product_combination/product_id=' . \S::get( 'product_id' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
static public function duplicate_product()
|
||||
{
|
||||
if ( \admin\factory\ShopProduct::duplicate_product( (int)\S::get( 'product-id' ), (int)\S::get( 'combination' ) ) )
|
||||
\S::set_message( 'Produkt został zduplikowany.' );
|
||||
else
|
||||
\S::alert( 'Podczas duplikowania produktu wystąpił błąd. Proszę spróbować ponownie' );
|
||||
|
||||
header( 'Location: /admin/shop_product/view_list/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function image_delete()
|
||||
{
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas usuwania zdjecia wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
if ( \admin\factory\ShopProduct::delete_img( \S::get( 'image_id' ) ) )
|
||||
$response = [ 'status' => 'ok' ];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function images_order_save()
|
||||
{
|
||||
if ( \admin\factory\ShopProduct::images_order_save( \S::get( 'product_id' ), \S::get( 'order' ) ) )
|
||||
echo json_encode( [ 'status' => 'ok', 'msg' => 'Produkt został zapisany.' ] );
|
||||
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function image_alt_change()
|
||||
{
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas zmiany atrybutu alt zdjęcia wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
if ( \admin\factory\ShopProduct::image_alt_change( \S::get( 'image_id' ), \S::get( 'image_alt' ) ) )
|
||||
$response = [ 'status' => 'ok' ];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
// szybka zmiana statusu produktu
|
||||
static public function change_product_status() {
|
||||
|
||||
if ( \admin\factory\ShopProduct::change_product_status( (int)\S::get( 'product-id' ) ) )
|
||||
\S::set_message( 'Status produktu został zmieniony' );
|
||||
|
||||
header( 'Location: ' . $_SERVER['HTTP_REFERER'] );
|
||||
exit;
|
||||
}
|
||||
|
||||
// szybka zmiana google xml label
|
||||
static public function product_change_custom_label()
|
||||
{
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas zmiany google xml label wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
if ( \admin\factory\ShopProduct::product_change_custom_label( (int) \S::get( 'product_id' ), \S::get( 'custom_label' ), \S::get( 'value' ) ) )
|
||||
$response = [ 'status' => 'ok' ];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
// szybka zmiana ceny promocyjnej
|
||||
static public function product_change_price_brutto_promo()
|
||||
{
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas zmiany ceny wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
if ( \admin\factory\ShopProduct::product_change_price_brutto_promo( (int) \S::get( 'product_id' ), \S::get( 'price' ) ) )
|
||||
$response = [ 'status' => 'ok' ];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
// szybka zmiana ceny
|
||||
static public function product_change_price_brutto()
|
||||
{
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas zmiany ceny wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
if ( \admin\factory\ShopProduct::product_change_price_brutto( (int) \S::get( 'product_id' ), \S::get( 'price' ) ) )
|
||||
$response = [ 'status' => 'ok' ];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
// pobierz bezpośredni url produktu
|
||||
static public function ajax_product_url()
|
||||
{
|
||||
echo json_encode( [ 'url' => \shop\Product::getProductUrl( \S::get( 'product_id' ) ) ] );
|
||||
exit;
|
||||
}
|
||||
|
||||
// zapisanie produktu
|
||||
public static function save()
|
||||
{
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania produktu wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
$values = json_decode( \S::get( 'values' ), true );
|
||||
|
||||
if ( $id = \admin\factory\ShopProduct::save(
|
||||
$values['id'], $values['name'], $values['short_description'], $values['description'], $values['status'], $values['meta_description'], $values['meta_keywords'], $values['seo_link'],
|
||||
$values['copy_from'], $values['categories'], $values['price_netto'], $values['price_brutto'], $values['vat'], $values['promoted'], $values['warehouse_message_zero'], $values['warehouse_message_nonzero'], $values['tab_name_1'],
|
||||
$values['tab_description_1'], $values['tab_name_2'], $values['tab_description_2'], $values['layout_id'], $values['products_related'], (int) $values['set'], $values['price_netto_promo'], $values['price_brutto_promo'],
|
||||
$values['new_to_date'], $values['stock_0_buy'], $values['wp'], $values['custom_label_0'], $values['custom_label_1'], $values['custom_label_2'], $values['custom_label_3'], $values['custom_label_4'], $values['additional_message'], (int)$values['quantity'], $values['additional_message_text'], $values['additional_message_required'] == 'on' ? 1 : 0, $values['canonical'], $values['meta_title'], $values['producer_id'], $values['sku'], $values['ean'], $values['product_unit'], $values['weight'], $values['xml_name'], $values['custom_field_name'], $values['custom_field_required'], $values['security_information'], $values['custom_field_type']
|
||||
) ) {
|
||||
$response = [ 'status' => 'ok', 'msg' => 'Produkt został zapisany.', 'id' => $id ];
|
||||
}
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
// product_unarchive
|
||||
static public function product_unarchive()
|
||||
{
|
||||
if ( \admin\factory\ShopProduct::product_unarchive( (int) \S::get( 'product_id' ) ) )
|
||||
\S::alert( 'Produkt został przywrócony z archiwum.' );
|
||||
else
|
||||
\S::alert( 'Podczas przywracania produktu z archiwum wystąpił błąd. Proszę spróbować ponownie' );
|
||||
|
||||
header( 'Location: /admin/product_archive/list/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
static public function product_archive()
|
||||
{
|
||||
if ( \admin\factory\ShopProduct::product_archive( (int) \S::get( 'product_id' ) ) )
|
||||
\S::alert( 'Produkt został przeniesiony do archiwum.' );
|
||||
else
|
||||
\S::alert( 'Podczas przenoszenia produktu do archiwum wystąpił błąd. Proszę spróbować ponownie' );
|
||||
|
||||
header( 'Location: /admin/shop_product/view_list/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function product_delete()
|
||||
{
|
||||
if ( \admin\factory\ShopProduct::product_delete( (int) \S::get( 'id' ) ) )
|
||||
\S::set_message( 'Produkt został usunięty.' );
|
||||
else
|
||||
\S::alert( 'Podczas usuwania produktu wystąpił błąd. Proszę spróbować ponownie' );
|
||||
header( 'Location: /admin/shop_product/view_list/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
// edycja produktu
|
||||
public static function product_edit() {
|
||||
global $user, $mdb;
|
||||
|
||||
if ( !$user ) {
|
||||
header( 'Location: /admin/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
\admin\factory\ShopProduct::delete_nonassigned_images();
|
||||
\admin\factory\ShopProduct::delete_nonassigned_files();
|
||||
|
||||
return \Tpl::view( 'shop-product/product-edit', [
|
||||
'product' => \admin\factory\ShopProduct::product_details( (int) \S::get( 'id' ) ),
|
||||
'languages' => ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->languagesList(),
|
||||
'categories' => ( new \Domain\Category\CategoryRepository( $GLOBALS['mdb'] ) )->subcategories( null ),
|
||||
'layouts' => self::layouts_for_product_edit( $mdb ),
|
||||
'products' => \admin\factory\ShopProduct::products_list(),
|
||||
'dlang' => \front\factory\Languages::default_language(),
|
||||
'sets' => \shop\ProductSet::sets_list(),
|
||||
'producers' => ( new \Domain\Producer\ProducerRepository( $mdb ) )->allProducers(),
|
||||
'units' => ( new \Domain\Dictionaries\DictionariesRepository( $mdb ) ) -> allUnits(),
|
||||
'user' => $user
|
||||
] );
|
||||
}
|
||||
|
||||
private static function layouts_for_product_edit( $db )
|
||||
{
|
||||
if ( class_exists( '\Domain\Layouts\LayoutsRepository' ) )
|
||||
{
|
||||
$rows = ( new \Domain\Layouts\LayoutsRepository( $db ) ) -> listAll();
|
||||
return is_array( $rows ) ? $rows : [];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
// ajax_load_products ARCHIVE
|
||||
static public function ajax_load_products_archive()
|
||||
{
|
||||
echo json_encode( [
|
||||
'status' => 'deprecated',
|
||||
'msg' => 'Endpoint nie jest juz wspierany. Uzyj /admin/product_archive/list/.',
|
||||
'redirect_url' => '/admin/product_archive/list/'
|
||||
] );
|
||||
exit;
|
||||
}
|
||||
|
||||
// ajax_load_products
|
||||
static public function ajax_load_products() {
|
||||
|
||||
global $mdb;
|
||||
|
||||
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );
|
||||
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas ładowania produktów wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
\S::set_session( 'products_list_current_page', \S::get( 'current_page' ) );
|
||||
\S::set_session( 'products_list_query', \S::get( 'query' ) );
|
||||
|
||||
if ( $products = \admin\factory\ShopProduct::ajax_products_list( \S::get_session( 'products_list_current_page' ), \S::get_session( 'products_list_query' ) ) ) {
|
||||
$response = [
|
||||
'status' => 'ok',
|
||||
'pagination_max' => ceil( $products['products_count'] / 10 ),
|
||||
'html' => \Tpl::view( 'shop-product/products-list-table', [
|
||||
'products' => $products['products'],
|
||||
'current_page' => \S::get( 'current_page' ),
|
||||
'apilo_enabled' => $integrationsRepository -> getSetting( 'apilo', 'enabled' ),
|
||||
'show_xml_data' => \S::get_session( 'show_xml_data' )
|
||||
] )
|
||||
];
|
||||
}
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
static public function view_list()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb );
|
||||
|
||||
$current_page = \S::get_session( 'products_list_current_page' );
|
||||
|
||||
if ( !$current_page ) {
|
||||
$current_page = 1;
|
||||
\S::set_session( 'products_list_current_page', $current_page );
|
||||
}
|
||||
|
||||
$query = \S::get_session( 'products_list_query' );
|
||||
if ( $query ) {
|
||||
$query_array = [];
|
||||
parse_str( $query, $query_array );
|
||||
}
|
||||
|
||||
if ( \S::get( 'show_xml_data' ) === 'true' ) {
|
||||
\S::set_session( 'show_xml_data', true );
|
||||
} else if ( \S::get( 'show_xml_data' ) === 'false' ) {
|
||||
\S::set_session( 'show_xml_data', false );
|
||||
}
|
||||
|
||||
return \Tpl::view( 'shop-product/products-list', [
|
||||
'current_page' => $current_page,
|
||||
'query_array' => $query_array,
|
||||
'pagination_max' => ceil( \admin\factory\ShopProduct::count_product() / 10 ),
|
||||
'apilo_enabled' => $integrationsRepository -> getSetting( 'apilo', 'enabled' ),
|
||||
'show_xml_data' => \S::get_session( 'show_xml_data' ),
|
||||
'shoppro_enabled' => $integrationsRepository -> getSetting( 'shoppro', 'enabled' )
|
||||
] );
|
||||
}
|
||||
|
||||
//
|
||||
// KOMBINACJE PRODUKTU
|
||||
//
|
||||
|
||||
// zapisanie możliwości zakupu przy stanie 0 w kombinacji produktu
|
||||
static public function product_combination_stock_0_buy_save()
|
||||
{
|
||||
\admin\factory\ShopProduct::product_combination_stock_0_buy_save( (int)\S::get( 'product_id' ), \S::get( 'stock_0_buy' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
// zapisanie sku w kombinacji produktu
|
||||
static public function product_combination_sku_save()
|
||||
{
|
||||
\admin\factory\ShopProduct::product_combination_sku_save( (int)\S::get( 'product_id' ), \S::get( 'sku' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
// zapisanie ilości w kombinacji produktu
|
||||
static public function product_combination_quantity_save()
|
||||
{
|
||||
\admin\factory\ShopProduct::product_combination_quantity_save( (int)\S::get( 'product_id' ), \S::get( 'quantity' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
// zapisanie ceny w kombinacji produktu
|
||||
static public function product_combination_price_save()
|
||||
{
|
||||
\admin\factory\ShopProduct::product_combination_price_save( (int)\S::get( 'product_id' ), \S::get( 'price' ) );
|
||||
exit;
|
||||
}
|
||||
|
||||
//wyświetlenie kombinacji produktu
|
||||
static public function product_combination()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
return \Tpl::view( 'shop-product/product-combination', [
|
||||
'product' => \admin\factory\ShopProduct::product_details( (int) \S::get( 'product_id' ) ),
|
||||
'attributes' => ( new \Domain\Attribute\AttributeRepository( $mdb ) ) -> getAttributesListForCombinations(),
|
||||
'default_language' => \front\factory\Languages::default_language(),
|
||||
'product_permutations' => \admin\factory\ShopProduct::get_product_permutations( (int) \S::get( 'product_id' ) )
|
||||
] );
|
||||
}
|
||||
|
||||
// generate_sku_code
|
||||
static public function generate_sku_code() {
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas generowania kodu sku wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
if ( $sku = \shop\Product::generate_sku_code( \S::get( 'product_id' ) ) )
|
||||
$response = [ 'status' => 'ok', 'sku' => $sku ];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
// product_xml_name_save
|
||||
static public function product_xml_name_save() {
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania nazwy produktu wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
if ( \shop\Product::product_xml_name_save( \S::get( 'product_id' ), \S::get( 'product_xml_name' ), \S::get( 'lang_id' ) ) )
|
||||
$response = [ 'status' => 'ok' ];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
// product_custom_label_suggestions
|
||||
static public function product_custom_label_suggestions() {
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas pobierania sugestii dla custom label wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
if ( $suggestions = \shop\Product::product_custom_label_suggestions( \S::get( 'custom_label' ), \S::get( 'label_type' ) ) )
|
||||
$response = [ 'status' => 'ok', 'suggestions' => $suggestions ];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
// product_custom_label_save
|
||||
static public function product_custom_label_save() {
|
||||
$response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania custom label wystąpił błąd. Proszę spróbować ponownie.' ];
|
||||
|
||||
if ( \shop\Product::product_custom_label_save( \S::get( 'product_id' ), \S::get( 'custom_label' ), \S::get( 'label_type' ) ) )
|
||||
$response = [ 'status' => 'ok' ];
|
||||
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
<?php
|
||||
namespace admin\controls;
|
||||
|
||||
class Update
|
||||
{
|
||||
public static function update()
|
||||
{
|
||||
if ( !\admin\factory\Update::update() )
|
||||
\S::alert( 'W trakcie aktualizacji systemu wystąpił błąd. Proszę spróbować ponownie.' );
|
||||
else
|
||||
\S::set_message( 'Aktualizacja przebiegła pomyślnie.' );
|
||||
|
||||
header( 'Location: /admin/update/main_view/' );
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function updateAll()
|
||||
{
|
||||
$response['status'] = \admin\factory\Update::update();
|
||||
$response['version'] = number_format( \S::get('version_current') + 0.001, 3, '.', '' );
|
||||
echo json_encode( $response );
|
||||
exit;
|
||||
}
|
||||
|
||||
public static function main_view()
|
||||
{
|
||||
return \admin\view\Update::main_view();
|
||||
}
|
||||
}
|
||||
@@ -1,173 +0,0 @@
|
||||
<?php
|
||||
namespace admin\factory;
|
||||
class Articles
|
||||
{
|
||||
/**
|
||||
* @deprecated Logika przeniesiona do Domain\Article\ArticleRepository::saveGalleryOrder().
|
||||
*/
|
||||
public static function gallery_order_save( $article_id, $order )
|
||||
{
|
||||
global $mdb;
|
||||
$repository = new \Domain\Article\ArticleRepository( $mdb );
|
||||
return $repository->saveGalleryOrder( (int)$article_id, (string)$order );
|
||||
}
|
||||
|
||||
public static function image_alt_change( $image_id, $image_alt )
|
||||
{
|
||||
global $mdb;
|
||||
$result = $mdb -> update( 'pp_articles_images', [
|
||||
'alt' => $image_alt
|
||||
], [
|
||||
'id' => $image_id
|
||||
] );
|
||||
\S::delete_cache();
|
||||
return $result;
|
||||
}
|
||||
|
||||
public static function article_url( $article_id )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$results = $mdb -> query( "SELECT seo_link FROM pp_articles_langs AS pal, pp_langs AS pl WHERE lang_id = pl.id AND article_id = " . (int)$article_id . " AND seo_link != '' ORDER BY o ASC LIMIT 1" ) -> fetchAll();
|
||||
if ( !$results[0]['seo_link'] )
|
||||
{
|
||||
$title = self::article_title( $article_id );
|
||||
return 'a-' . $article_id . '-' . \S::seo( $title );
|
||||
}
|
||||
else
|
||||
return $results[0]['seo_link'];
|
||||
}
|
||||
|
||||
public static function articles_by_date_add( $date_start, $date_end )
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$results = $mdb -> query( 'SELECT '
|
||||
. 'id '
|
||||
. 'FROM '
|
||||
. 'pp_articles '
|
||||
. 'WHERE '
|
||||
. 'status = 1 '
|
||||
. 'AND '
|
||||
. 'date_add BETWEEN \'' . $date_start . '\' AND \'' . $date_end . '\' '
|
||||
. 'ORDER BY '
|
||||
. 'date_add DESC' ) -> fetchAll();
|
||||
if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
|
||||
$articles[] = \front\factory\Articles::article_details( $row['id'], 'pl' );
|
||||
|
||||
return $articles;
|
||||
}
|
||||
|
||||
public static function article_pages( $article_id )
|
||||
{
|
||||
global $mdb;
|
||||
$pagesRepository = new \Domain\Pages\PagesRepository( $mdb );
|
||||
|
||||
$results = $mdb -> query( "SELECT page_id FROM pp_articles_pages WHERE article_id = " . (int)$article_id ) -> fetchAll();
|
||||
if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row )
|
||||
{
|
||||
if ( $out == '' )
|
||||
$out .= ' - ';
|
||||
|
||||
$out .= $pagesRepository->pageTitle( (int)$row['page_id'] );
|
||||
|
||||
if ( end( $results ) != $row )
|
||||
$out .= ' / ';
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public static function article_title( $article_id )
|
||||
{
|
||||
global $mdb;
|
||||
$results = $mdb -> query( "SELECT title FROM pp_articles_langs AS pal, pp_langs AS pl WHERE lang_id = pl.id AND article_id = " . (int)$article_id . " AND title != '' ORDER BY o ASC LIMIT 1" ) -> fetchAll();
|
||||
return $results[0]['title'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Logika przeniesiona do Domain\Article\ArticleRepository::archive().
|
||||
*/
|
||||
public static function articles_set_archive( $article_id )
|
||||
{
|
||||
global $mdb;
|
||||
$repository = new \Domain\Article\ArticleRepository( $mdb );
|
||||
return $repository->archive( (int)$article_id );
|
||||
}
|
||||
|
||||
public static function file_name_change( $file_id, $file_name )
|
||||
{
|
||||
global $mdb;
|
||||
$mdb -> update( 'pp_articles_files', [ 'name' => $file_name ], [ 'id' => (int)$file_id ] );
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function delete_file( $file_id )
|
||||
{
|
||||
global $mdb;
|
||||
$mdb -> update( 'pp_articles_files', [ 'to_delete' => 1 ], [ 'id' => (int)$file_id ] );
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function delete_img( $image_id )
|
||||
{
|
||||
global $mdb;
|
||||
$mdb -> update( 'pp_articles_images', [ 'to_delete' => 1 ], [ 'id' => (int)$image_id ] );
|
||||
return true;
|
||||
}
|
||||
|
||||
public static function article_details( $article_id )
|
||||
{
|
||||
global $mdb;
|
||||
$repository = new \Domain\Article\ArticleRepository( $mdb );
|
||||
return $repository->find( (int)$article_id );
|
||||
}
|
||||
|
||||
public static function max_order()
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> max( 'pp_articles_pages', 'o' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Logika przeniesiona do Domain\Article\ArticleRepository::save().
|
||||
* Ta metoda pozostaje jako fasada dla backward compatibility.
|
||||
*/
|
||||
public static function article_save(
|
||||
$article_id, $title, $main_image, $entry, $text, $table_of_contents, $status, $show_title, $show_table_of_contents, $show_date_add, $date_add, $show_date_modify, $date_modify, $seo_link, $meta_title, $meta_description, $meta_keywords, $layout_id, $pages,
|
||||
$noindex, $repeat_entry, $copy_from, $social_icons, $block_direct_access )
|
||||
{
|
||||
global $mdb, $user;
|
||||
|
||||
$repository = new \Domain\Article\ArticleRepository( $mdb );
|
||||
|
||||
return $repository->save( (int)$article_id, [
|
||||
'title' => $title, 'main_image' => $main_image, 'entry' => $entry,
|
||||
'text' => $text, 'table_of_contents' => $table_of_contents,
|
||||
'status' => $status, 'show_title' => $show_title,
|
||||
'show_table_of_contents' => $show_table_of_contents,
|
||||
'show_date_add' => $show_date_add, 'date_add' => $date_add,
|
||||
'show_date_modify' => $show_date_modify, 'date_modify' => $date_modify,
|
||||
'seo_link' => $seo_link, 'meta_title' => $meta_title,
|
||||
'meta_description' => $meta_description, 'meta_keywords' => $meta_keywords,
|
||||
'layout_id' => $layout_id, 'pages' => $pages, 'noindex' => $noindex,
|
||||
'repeat_entry' => $repeat_entry, 'copy_from' => $copy_from,
|
||||
'social_icons' => $social_icons, 'block_direct_access' => $block_direct_access,
|
||||
], (int)$user['id'] );
|
||||
}
|
||||
|
||||
public static function delete_nonassigned_files()
|
||||
{
|
||||
global $mdb;
|
||||
$repository = new \Domain\Article\ArticleRepository( $mdb );
|
||||
$repository->deleteNonassignedFiles();
|
||||
}
|
||||
|
||||
public static function delete_nonassigned_images()
|
||||
{
|
||||
global $mdb;
|
||||
$repository = new \Domain\Article\ArticleRepository( $mdb );
|
||||
$repository->deleteNonassignedImages();
|
||||
}
|
||||
}
|
||||
?>
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,365 +0,0 @@
|
||||
<?php
|
||||
namespace admin\factory;
|
||||
|
||||
class Update
|
||||
{
|
||||
public static function update()
|
||||
{
|
||||
global $mdb, $settings;
|
||||
|
||||
@file_put_contents( '../libraries/update_log.txt', '' );
|
||||
|
||||
$log = [];
|
||||
$log[] = '[START] Rozpoczęcie aktualizacji - ' . date('Y-m-d H:i:s');
|
||||
$log[] = '[INFO] Aktualna wersja: ' . \S::get_version();
|
||||
|
||||
\S::delete_session( 'new-version' );
|
||||
|
||||
$versions_url = 'https://shoppro.project-dc.pl/updates/versions.php?key=' . $settings['update_key'];
|
||||
$versions = @file_get_contents( $versions_url );
|
||||
|
||||
if ( $versions === false )
|
||||
{
|
||||
$log[] = '[ERROR] Nie udało się pobrać listy wersji z: ' . $versions_url;
|
||||
self::saveUpdateLog( $log );
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$log[] = '[OK] Pobrano listę wersji';
|
||||
$versions = explode( PHP_EOL, $versions );
|
||||
$log[] = '[INFO] Znaleziono ' . count($versions) . ' wersji do sprawdzenia';
|
||||
|
||||
foreach ( $versions as $ver )
|
||||
{
|
||||
$ver = trim( $ver );
|
||||
if ( floatval( $ver ) > (float)\S::get_version() )
|
||||
{
|
||||
$log[] = '[INFO] Aktualizacja do wersji: ' . $ver;
|
||||
|
||||
if ( strlen( $ver ) == 5 )
|
||||
$dir = substr( $ver, 0, strlen( $ver ) - 2 ) . 0;
|
||||
else
|
||||
$dir = substr( $ver, 0, strlen( $ver ) - 1 ) . 0;
|
||||
|
||||
$zip_url = 'https://shoppro.project-dc.pl/updates/' . $dir . '/ver_' . $ver . '.zip';
|
||||
$log[] = '[INFO] Pobieranie pliku ZIP: ' . $zip_url;
|
||||
|
||||
$file = @file_get_contents( $zip_url );
|
||||
|
||||
if ( $file === false )
|
||||
{
|
||||
$log[] = '[ERROR] Nie udało się pobrać pliku ZIP';
|
||||
self::saveUpdateLog( $log );
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$file_size = strlen( $file );
|
||||
$log[] = '[OK] Pobrano plik ZIP, rozmiar: ' . $file_size . ' bajtów';
|
||||
|
||||
if ( $file_size < 100 )
|
||||
{
|
||||
$log[] = '[ERROR] Plik ZIP jest za mały (prawdopodobnie błąd pobierania)';
|
||||
self::saveUpdateLog( $log );
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$dlHandler = @fopen( 'update.zip' , 'w' );
|
||||
if ( !$dlHandler )
|
||||
{
|
||||
$log[] = '[ERROR] Nie udało się otworzyć pliku update.zip do zapisu';
|
||||
$log[] = '[INFO] Katalog roboczy: ' . getcwd();
|
||||
$log[] = '[INFO] Uprawnienia katalogu: ' . substr(sprintf('%o', fileperms('.')), -4);
|
||||
self::saveUpdateLog( $log );
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$written = fwrite( $dlHandler, $file );
|
||||
fclose( $dlHandler );
|
||||
|
||||
if ( $written === false || $written === 0 )
|
||||
{
|
||||
$log[] = '[ERROR] Nie udało się zapisać pliku ZIP (zapisano: ' . ($written === false ? 'false' : $written) . ' bajtów)';
|
||||
self::saveUpdateLog( $log );
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$log[] = '[OK] Zapisano plik ZIP (' . $written . ' bajtów)';
|
||||
|
||||
if ( !file_exists( 'update.zip' ) )
|
||||
{
|
||||
$log[] = '[ERROR] Plik update.zip nie istnieje po zapisie';
|
||||
self::saveUpdateLog( $log );
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$actual_size = filesize( 'update.zip' );
|
||||
$log[] = '[OK] Plik update.zip istnieje, rozmiar na dysku: ' . $actual_size . ' bajtów';
|
||||
|
||||
/* aktualizacja bazy danych */
|
||||
$sql_url = 'https://shoppro.project-dc.pl/updates/' . $dir . '/ver_' . $ver . '_sql.txt';
|
||||
$log[] = '[INFO] Sprawdzanie aktualizacji SQL: ' . $sql_url;
|
||||
|
||||
$ch = curl_init( $sql_url );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
curl_setopt( $ch, CURLOPT_HEADER, false );
|
||||
$response = curl_exec( $ch );
|
||||
$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||
$content_type = curl_getinfo( $ch, CURLINFO_CONTENT_TYPE );
|
||||
curl_close( $ch );
|
||||
|
||||
$sql = [];
|
||||
if ( $response && strpos( $content_type, 'text/plain' ) !== false )
|
||||
{
|
||||
$sql = explode( PHP_EOL, $response );
|
||||
$log[] = '[OK] Pobrano ' . count($sql) . ' zapytań SQL';
|
||||
}
|
||||
else
|
||||
{
|
||||
$log[] = '[INFO] Brak aktualizacji SQL (HTTP: ' . $http_code . ')';
|
||||
}
|
||||
|
||||
if ( is_array( $sql ) && !empty( $sql ) )
|
||||
{
|
||||
$sql_success = 0;
|
||||
$sql_errors = 0;
|
||||
foreach ( $sql as $query )
|
||||
{
|
||||
$query = trim( $query );
|
||||
if ( !empty( $query ) )
|
||||
{
|
||||
$result = $mdb->query( $query );
|
||||
if ( $result ) $sql_success++;
|
||||
else $sql_errors++;
|
||||
}
|
||||
}
|
||||
$log[] = '[INFO] Wykonano zapytania SQL - sukces: ' . $sql_success . ', błędy: ' . $sql_errors;
|
||||
}
|
||||
|
||||
/* usuwanie zbędnych plików */
|
||||
$files_url = 'https://shoppro.project-dc.pl/updates/' . $dir . '/ver_' . $ver . '_files.txt';
|
||||
$log[] = '[INFO] Sprawdzanie plików do usunięcia: ' . $files_url;
|
||||
|
||||
$ch = curl_init( $files_url );
|
||||
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
|
||||
curl_setopt( $ch, CURLOPT_HEADER, false );
|
||||
$response = curl_exec( $ch );
|
||||
$http_code = curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
||||
$content_type = curl_getinfo( $ch, CURLINFO_CONTENT_TYPE );
|
||||
curl_close( $ch );
|
||||
|
||||
$files = [];
|
||||
if ( $response && strpos( $content_type, 'text/plain' ) !== false )
|
||||
$files = explode( PHP_EOL, $response );
|
||||
|
||||
$deleted_files = 0;
|
||||
$deleted_dirs = 0;
|
||||
if ( is_array( $files ) && !empty( $files ) )
|
||||
{
|
||||
foreach ( $files as $file )
|
||||
{
|
||||
if ( strpos( $file, 'F: ' ) !== false )
|
||||
{
|
||||
$file = substr( $file, 3, strlen( $file ) );
|
||||
if ( file_exists( $file ) )
|
||||
{
|
||||
if ( @unlink( $file ) ) $deleted_files++;
|
||||
else $log[] = '[WARNING] Nie udało się usunąć pliku: ' . $file;
|
||||
}
|
||||
}
|
||||
|
||||
if ( strpos( $file, 'D: ' ) !== false )
|
||||
{
|
||||
$dir_to_delete = substr( $file, 3, strlen( $file ) );
|
||||
if ( is_dir( $dir_to_delete ) )
|
||||
{
|
||||
\S::delete_dir( $dir_to_delete );
|
||||
$deleted_dirs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$log[] = '[INFO] Usunięto plików: ' . $deleted_files . ', katalogów: ' . $deleted_dirs;
|
||||
|
||||
/* wgrywanie nowych plików */
|
||||
$file_name = 'update.zip';
|
||||
$log[] = '[INFO] Rozpoczęcie rozpakowywania pliku ZIP';
|
||||
|
||||
$path = pathinfo( realpath( $file_name ), PATHINFO_DIRNAME );
|
||||
$log[] = '[INFO] Ścieżka pathinfo: ' . $path;
|
||||
|
||||
$path = substr( $path, 0, strlen( $path ) - 5 );
|
||||
$log[] = '[INFO] Ścieżka docelowa (po obcięciu): ' . $path;
|
||||
|
||||
if ( !is_dir( $path ) )
|
||||
{
|
||||
$log[] = '[ERROR] Ścieżka docelowa nie istnieje: ' . $path;
|
||||
self::saveUpdateLog( $log );
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
if ( !is_writable( $path ) )
|
||||
{
|
||||
$log[] = '[ERROR] Brak uprawnień do zapisu w: ' . $path;
|
||||
self::saveUpdateLog( $log );
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$log[] = '[OK] Ścieżka docelowa istnieje i jest zapisywalna';
|
||||
|
||||
$zip = new \ZipArchive;
|
||||
$res = $zip->open( $file_name );
|
||||
|
||||
if ( $res !== true )
|
||||
{
|
||||
$zip_errors = [
|
||||
\ZipArchive::ER_EXISTS => 'Plik już istnieje',
|
||||
\ZipArchive::ER_INCONS => 'Archiwum ZIP jest niespójne',
|
||||
\ZipArchive::ER_INVAL => 'Nieprawidłowy argument',
|
||||
\ZipArchive::ER_MEMORY => 'Błąd alokacji pamięci',
|
||||
\ZipArchive::ER_NOENT => 'Plik nie istnieje',
|
||||
\ZipArchive::ER_NOZIP => 'Plik nie jest archiwum ZIP',
|
||||
\ZipArchive::ER_OPEN => 'Nie można otworzyć pliku',
|
||||
\ZipArchive::ER_READ => 'Błąd odczytu',
|
||||
\ZipArchive::ER_SEEK => 'Błąd seek',
|
||||
];
|
||||
$error_msg = isset( $zip_errors[$res] ) ? $zip_errors[$res] : 'Nieznany błąd (' . $res . ')';
|
||||
$log[] = '[ERROR] Nie udało się otworzyć pliku ZIP: ' . $error_msg;
|
||||
self::saveUpdateLog( $log );
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
$log[] = '[OK] Otwarto archiwum ZIP, liczba plików: ' . $zip->numFiles;
|
||||
|
||||
$extracted_count = 0;
|
||||
$extract_errors = 0;
|
||||
$skipped_dirs = 0;
|
||||
|
||||
for ( $i = 0; $i < $zip->numFiles; $i++ )
|
||||
{
|
||||
$filename = $zip->getNameIndex( $i );
|
||||
$filename_clean = str_replace( '\\', '/', $filename );
|
||||
|
||||
if ( substr( $filename_clean, -1 ) === '/' )
|
||||
{
|
||||
$dir_path = $path . '/' . $filename_clean;
|
||||
if ( !is_dir( $dir_path ) )
|
||||
{
|
||||
if ( @mkdir( $dir_path, 0755, true ) )
|
||||
$log[] = '[DIR] Utworzono katalog: ' . $filename_clean;
|
||||
else
|
||||
$log[] = '[WARNING] Nie udało się utworzyć katalogu: ' . $filename_clean;
|
||||
}
|
||||
$skipped_dirs++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$target_file = $path . '/' . $filename_clean;
|
||||
$target_dir = dirname( $target_file );
|
||||
|
||||
if ( !is_dir( $target_dir ) )
|
||||
{
|
||||
if ( !@mkdir( $target_dir, 0755, true ) )
|
||||
{
|
||||
$log[] = '[ERROR] Nie udało się utworzyć katalogu dla: ' . $filename_clean;
|
||||
$extract_errors++;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$file_existed = file_exists( $target_file );
|
||||
$old_size = $file_existed ? filesize( $target_file ) : 0;
|
||||
$old_mtime = $file_existed ? filemtime( $target_file ) : 0;
|
||||
|
||||
$content = $zip->getFromIndex( $i );
|
||||
if ( $content === false )
|
||||
{
|
||||
$log[] = '[ERROR] Nie udało się odczytać z ZIP: ' . $filename_clean;
|
||||
$extract_errors++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$write_result = @file_put_contents( $target_file, $content );
|
||||
|
||||
if ( $write_result === false )
|
||||
{
|
||||
$log[] = '[ERROR] Nie udało się zapisać: ' . $filename_clean . ' (uprawnienia?)';
|
||||
$extract_errors++;
|
||||
}
|
||||
else
|
||||
{
|
||||
$new_size = filesize( $target_file );
|
||||
$new_mtime = filemtime( $target_file );
|
||||
|
||||
if ( $file_existed )
|
||||
{
|
||||
if ( $old_mtime !== $new_mtime || $old_size !== $new_size )
|
||||
$log[] = '[UPDATED] ' . $filename_clean . ' (' . $old_size . ' -> ' . $new_size . ' bajtów)';
|
||||
else
|
||||
$log[] = '[UNCHANGED] ' . $filename_clean . ' (nie zmieniono - identyczny?)';
|
||||
}
|
||||
else
|
||||
{
|
||||
$log[] = '[NEW] ' . $filename_clean . ' (' . $new_size . ' bajtów)';
|
||||
}
|
||||
$extracted_count++;
|
||||
}
|
||||
}
|
||||
|
||||
$log[] = '[OK] Rozpakowano ' . $extracted_count . ' plików, błędów: ' . $extract_errors . ', katalogów: ' . $skipped_dirs;
|
||||
$zip->close();
|
||||
|
||||
if ( @unlink( $file_name ) )
|
||||
$log[] = '[OK] Usunięto plik update.zip';
|
||||
else
|
||||
$log[] = '[WARNING] Nie udało się usunąć pliku update.zip';
|
||||
|
||||
/* aktualizacja wersji */
|
||||
$version_file = '../libraries/version.ini';
|
||||
$updateThis = @fopen( $version_file, 'w' );
|
||||
|
||||
if ( !$updateThis )
|
||||
{
|
||||
$log[] = '[ERROR] Nie udało się otworzyć pliku version.ini do zapisu';
|
||||
self::saveUpdateLog( $log );
|
||||
return [ 'success' => false, 'log' => $log ];
|
||||
}
|
||||
|
||||
fwrite( $updateThis, $ver );
|
||||
fclose( $updateThis );
|
||||
|
||||
$log[] = '[OK] Zaktualizowano plik version.ini do wersji: ' . $ver;
|
||||
$log[] = '[SUCCESS] Aktualizacja do wersji ' . $ver . ' zakończona pomyślnie';
|
||||
|
||||
self::saveUpdateLog( $log );
|
||||
return [ 'success' => true, 'log' => $log ];
|
||||
}
|
||||
}
|
||||
|
||||
$log[] = '[INFO] Brak nowych wersji do zainstalowania';
|
||||
self::saveUpdateLog( $log );
|
||||
return [ 'success' => true, 'log' => $log, 'no_updates' => true ];
|
||||
}
|
||||
|
||||
private static function saveUpdateLog( $log )
|
||||
{
|
||||
$log_content = implode( "\n", $log );
|
||||
@file_put_contents( '../libraries/update_log.txt', $log_content );
|
||||
}
|
||||
|
||||
public static function update0197()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$rows = $mdb -> select( 'pp_shop_order_products', [ 'id', 'product_id' ], [ 'parent_product_id' => null ] );
|
||||
foreach ( $rows as $row )
|
||||
{
|
||||
$parent_id = $mdb -> get( 'pp_shop_products', 'parent_id', [ 'id' => $row['product_id'] ] );
|
||||
if ( $parent_id )
|
||||
$mdb -> update( 'pp_shop_order_products', [ 'parent_product_id' => $parent_id ], [ 'id' => $row['id'] ] );
|
||||
else
|
||||
$mdb -> update( 'pp_shop_order_products', [ 'parent_product_id' => $row['product_id'] ], [ 'id' => $row['id'] ] );
|
||||
}
|
||||
|
||||
$mdb -> update( 'pp_updates', [ 'done' => 1 ], [ 'name' => 'update0197' ] );
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
<?php
|
||||
namespace admin\view;
|
||||
|
||||
class Page {
|
||||
|
||||
public static function show()
|
||||
{
|
||||
global $user, $mdb;
|
||||
|
||||
if ( $_GET['module'] == 'user' && $_GET['action'] == 'twofa' ) {
|
||||
$controller = new \admin\Controllers\UsersController(
|
||||
new \Domain\User\UserRepository( $mdb )
|
||||
);
|
||||
return $controller->twofa();
|
||||
}
|
||||
|
||||
if ( !$user || !$user['admin'] )
|
||||
{
|
||||
$controller = new \admin\Controllers\UsersController(
|
||||
new \Domain\User\UserRepository( $mdb )
|
||||
);
|
||||
return $controller->login_form();
|
||||
}
|
||||
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> content = \admin\Site::route();
|
||||
return $tpl -> render( 'site/main-layout' );
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -1,21 +0,0 @@
|
||||
<?php
|
||||
namespace admin\view;
|
||||
|
||||
class PagePanel {
|
||||
|
||||
public static function show( $add = false, $save = false, $cancel = false, $title = '', $form = 'formularz', $back = false, $update = false, $save_ajax = false, $delete_ajax = false )
|
||||
{
|
||||
$tpl = new \Tpl();
|
||||
$tpl -> _add = $add;
|
||||
$tpl -> _save = $save;
|
||||
$tpl -> _cancel = $cancel;
|
||||
$tpl -> _id_form = $form;
|
||||
$tpl -> _title = $title;
|
||||
$tpl -> _back = $back;
|
||||
$tpl -> _update = $update;
|
||||
$tpl -> _save_ajax = $save_ajax;
|
||||
$tpl -> _delete_ajax = $delete_ajax;
|
||||
return $tpl -> render( 'other/page-panel' );
|
||||
}
|
||||
}
|
||||
?>
|
||||
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
namespace admin\view;
|
||||
|
||||
class ShopProduct
|
||||
{
|
||||
public static function products_list()
|
||||
{
|
||||
$tpl = new \Tpl();
|
||||
return $tpl -> render('shop-product/products-list');
|
||||
}
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
<?php
|
||||
namespace admin\view;
|
||||
|
||||
class Update
|
||||
{
|
||||
public static function main_view()
|
||||
{
|
||||
$tpl = new \Tpl;
|
||||
$tpl -> ver = \S::get_version();
|
||||
$tpl -> new_ver = \S::get_new_version();
|
||||
return $tpl -> render( 'update/main-view' );
|
||||
}
|
||||
}
|
||||
@@ -39,8 +39,9 @@ class Newsletter
|
||||
$dates = explode( ' - ', $row['dates'] );
|
||||
|
||||
$articles = [];
|
||||
$articleRepository = new \Domain\Article\ArticleRepository( $mdb );
|
||||
if ( isset( $dates[0], $dates[1] ) )
|
||||
$articles = \admin\factory\Articles::articles_by_date_add( $dates[0], $dates[1] );
|
||||
$articles = $articleRepository->articlesByDateAdd( $dates[0], $dates[1] );
|
||||
|
||||
$text = $previewRenderer -> render(
|
||||
is_array( $articles ) ? $articles : [],
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
<?php
|
||||
namespace shop;
|
||||
|
||||
class Dashboard implements \ArrayAccess
|
||||
{
|
||||
static public function summary_orders()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
try
|
||||
{
|
||||
$redis = \RedisConnection::getInstance() -> getConnection();
|
||||
if ( $redis )
|
||||
{
|
||||
$objectData = $redis -> get( "summary_ordersd" );
|
||||
|
||||
if ( !$objectData )
|
||||
{
|
||||
$summary = $mdb -> count( 'pp_shop_orders', [ 'status' => 6 ] );
|
||||
$redis -> setex( "summary_ordersd", 60 * 5, serialize( $summary ) );
|
||||
}
|
||||
else
|
||||
$summary = unserialize( $objectData );
|
||||
}
|
||||
else
|
||||
{
|
||||
$summary = $mdb -> count( 'pp_shop_orders', [ 'status' => 6 ] );
|
||||
}
|
||||
}
|
||||
catch ( \RedisException $e )
|
||||
{
|
||||
$summary = $mdb -> count( 'pp_shop_orders', [ 'status' => 6 ] );
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
static public function summary_sales()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
try
|
||||
{
|
||||
$redis = \RedisConnection::getInstance() -> getConnection();
|
||||
if ( $redis )
|
||||
{
|
||||
$objectData = $redis -> get( "summary_salesd" );
|
||||
|
||||
if ( !$objectData )
|
||||
{
|
||||
$summary = $mdb -> sum( 'pp_shop_orders', 'summary', [ 'status' => 6 ] ) - $mdb -> sum( 'pp_shop_orders', 'transport_cost', [ 'status' => 6 ] );
|
||||
$redis -> setex( "summary_salesd", 60 * 5, serialize( $summary ) );
|
||||
}
|
||||
else
|
||||
$summary = unserialize( $objectData );
|
||||
}
|
||||
else
|
||||
{
|
||||
$summary = $mdb -> sum( 'pp_shop_orders', 'summary', [ 'status' => 6 ] ) - $mdb -> sum( 'pp_shop_orders', 'transport_cost', [ 'status' => 6 ] );
|
||||
}
|
||||
}
|
||||
catch ( \RedisException $e )
|
||||
{
|
||||
$summary = $mdb -> sum( 'pp_shop_orders', 'summary', [ 'status' => 6 ] ) - $mdb -> sum( 'pp_shop_orders', 'transport_cost', [ 'status' => 6 ] );
|
||||
}
|
||||
|
||||
return $summary;
|
||||
}
|
||||
|
||||
static public function sales_grid()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$rows = $mdb -> select( 'pp_shop_orders', [ 'id', 'date_order' ], [ 'status' => 6 ] );
|
||||
if ( \S::is_array_fix( $rows ) ) foreach ( $rows as $row )
|
||||
{
|
||||
if ( date( 'N', strtotime( $row['date_order'] ) ) )
|
||||
$grid[ date( 'N', strtotime( $row['date_order'] ) ) ][ date( 'G', strtotime($row['date_order'] ) ) ] += 1;
|
||||
}
|
||||
|
||||
return $grid;
|
||||
}
|
||||
|
||||
static public function most_view_products()
|
||||
{
|
||||
global $mdb;
|
||||
return $mdb -> query( 'SELECT '
|
||||
. 'id, SUM(visits) AS visits '
|
||||
. 'FROM '
|
||||
. 'pp_shop_products AS psop '
|
||||
. 'GROUP BY '
|
||||
. 'id '
|
||||
. 'ORDER BY '
|
||||
. 'visits DESC '
|
||||
. 'LIMIT 10' ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||
}
|
||||
|
||||
static public function best_sales_products()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
return $mdb -> query( 'SELECT parent_product_id, SUM(quantity) AS quantity_summary, SUM(price_brutto_promo * quantity) AS sales FROM pp_shop_order_products AS psop INNER JOIN pp_shop_orders AS pso ON pso.id = psop.order_id WHERE pso.status = 6 GROUP BY parent_product_id ORDER BY sales DESC LIMIT 10' ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||
}
|
||||
|
||||
static public function last_24_months_sales()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
$monthsBack = 24;
|
||||
|
||||
$sales = [ [ 'date' => date( 'Y-m' ) ] ];
|
||||
$previousMonthDate = new \DateTime();
|
||||
for ( $monthInterval = 0; $monthInterval < $monthsBack; $monthInterval++)
|
||||
{
|
||||
$previousMonthDate -> sub( new \DateInterval( "P1M" ) );
|
||||
array_push( $sales, [ 'date' => $previousMonthDate -> format( 'Y-m' ) ] );
|
||||
}
|
||||
|
||||
for ( $i = 0; $i < 24; $i++ )
|
||||
{
|
||||
$date_start = date( 'Y-m-1', strtotime( $sales[$i]['date'] ) );
|
||||
$date_end = date( 'Y-m-t', strtotime( $sales[$i]['date'] ) );
|
||||
$sales[$i]['sales'] = $mdb -> sum( 'pp_shop_orders', 'summary', [ 'AND' => [ 'status' => 6, 'date_order[>=]' => $date_start, 'date_order[<=]' => $date_end ] ] ) - $mdb -> sum( 'pp_shop_orders', 'transport_cost', [ 'AND' => [ 'status' => 6, 'date_order[>=]' => $date_start, 'date_order[<=]' => $date_end ] ] );
|
||||
}
|
||||
|
||||
return $sales;
|
||||
}
|
||||
|
||||
static public function last_orders()
|
||||
{
|
||||
global $mdb;
|
||||
|
||||
return $mdb -> query( 'SELECT '
|
||||
. 'id, number, date_order, CONCAT( client_name, \' \', client_surname ) AS client, client_email, CONCAT( client_street, \', \', client_postal_code, \' \', client_city ) AS address, status, client_phone, summary '
|
||||
. 'FROM '
|
||||
. 'pp_shop_orders AS pso '
|
||||
. 'ORDER BY '
|
||||
. 'date_order DESC '
|
||||
. 'LIMIT '
|
||||
. '10' ) -> fetchAll( \PDO::FETCH_ASSOC );
|
||||
}
|
||||
|
||||
public function offsetExists( $offset )
|
||||
{
|
||||
return isset( $this -> $offset );
|
||||
}
|
||||
|
||||
public function offsetGet( $offset )
|
||||
{
|
||||
return $this -> $offset;
|
||||
}
|
||||
|
||||
public function offsetSet( $offset, $value )
|
||||
{
|
||||
$this -> $offset = $value;
|
||||
}
|
||||
|
||||
public function offsetUnset( $offset )
|
||||
{
|
||||
unset( $this -> $offset );
|
||||
}
|
||||
}
|
||||
2
cron.php
2
cron.php
@@ -182,7 +182,7 @@ if ( $apilo_settings['enabled'] and $apilo_settings['access-token'] and ( !$apil
|
||||
$mdb -> update( 'pp_shop_products', [ 'price_netto' => \S::normalize_decimal( $price_netto, 2 ), 'price_brutto' => \S::normalize_decimal( $price_brutto, 2 ) ], [ 'apilo_product_id' => $product_price['product'] ] );
|
||||
$product_id = $mdb -> get( 'pp_shop_products', 'id', [ 'apilo_product_id' => $product_price['product'] ] );
|
||||
|
||||
\admin\factory\ShopProduct::update_product_combinations_prices( (int)$product_id, $price_brutto, $vat, null );
|
||||
( new \Domain\Product\ProductRepository( $mdb ) )->updateCombinationPricesFromBase( (int)$product_id, $price_brutto, $vat, null );
|
||||
|
||||
// Czyszczenie cache produktu
|
||||
\S::clear_product_cache( (int)$product_id );
|
||||
|
||||
@@ -53,4 +53,4 @@ $mdb = new medoo( [
|
||||
$settings = \front\factory\Settings::settings_details();
|
||||
$lang_id = \front\factory\Languages::default_language();
|
||||
|
||||
\admin\factory\ShopProduct::generate_google_feed_xml();
|
||||
( new \Domain\Product\ProductRepository( $mdb ) )->generateGoogleFeedXml();
|
||||
@@ -4,21 +4,43 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
|
||||
|
||||
---
|
||||
|
||||
## ver. 0.277 (2026-02-15) - Stabilizacja ShopOrder + Integrations + Global Search
|
||||
## ver. 0.277 (2026-02-16) - ShopProduct factory, Dashboard, Update, legacy cleanup, admin\App
|
||||
|
||||
- **ShopProduct (factory)** - pelna migracja modulu #29 na Domain + DI
|
||||
- NOWE: `ProductRepository` rozszerzony o ~40 metod: CRUD (countProducts, listForAdmin, findForAdmin, allProductsList, productCategoriesText, getParentId, productDefaultName), zapis (saveProduct + 9 prywatnych helperow), operacje (delete, duplicate, toggleStatus, updatePriceBrutto/Promo, updateCustomLabel), kombinacje (getPermutations, generateCombinations, deleteCombination, countCombinations, saveCombination*), zdjecia/pliki (deleteImage, updateImageAlt, saveImagesOrder, deleteFile, updateFileName, deleteNonassigned*), Google Feed XML (generateGoogleFeedXml, generateEAN), custom labels (customLabelSuggestions, saveCustomLabel, saveXmlName), updateCombinationPricesFromBase
|
||||
- NOWE: `ShopProductController` rozszerzony o ~30 akcji: view_list, product_edit, save, duplicate_product, product_archive/unarchive, product_delete, change_product_status, product_change_price_brutto/promo, product_change_custom_label, product_custom_label_suggestions/save, ajax_product_url, generate_sku_code, product_combination, generate_combination, delete_combination, product_combination_*_save, image_delete, images_order_save, image_alt_change, product_file_delete, product_file_name_change, product_image_delete
|
||||
- UPDATE: przepiecie zaleznosci zewnetrznych: `ProductArchiveController`, `order-details.php`, `cron.php`, `cron-xml.php`, `product-edit.php`, `mass-edit-custom-script.php`
|
||||
- CLEANUP: usuniete `autoload/admin/controls/class.ShopProduct.php`, `autoload/admin/factory/class.ShopProduct.php`, `admin/ajax/shop.php` + require z `admin/ajax.php`
|
||||
- **ShopOrder (stabilizacja po migracji)**
|
||||
- FIX: `Domain\Order\OrderRepository::listForAdmin()` - poprawa zapytan SQL (count/list), bezpieczne fallbacki i poprawne zwracanie listy zamowien w `/admin/shop_order/list/`
|
||||
- FIX: wyrównanie wysokości komórek w `components/table-list` (`vertical-align` + lokalny override dla `.text-right` w tabeli)
|
||||
- **Integrations (cleanup)**
|
||||
- CLEANUP: usunieta fasada `autoload/admin/factory/class.Integrations.php`
|
||||
- UPDATE: przepięcie wywołań na `Domain\Integrations\IntegrationsRepository` w: `cron.php`, `shop\Order`, `admin\Controllers\ShopPaymentMethodController`, `admin\Controllers\ShopStatusesController`, `admin\Controllers\ShopTransportController`, `admin\controls\ShopProduct`
|
||||
- UPDATE: przepięcie wywołań na `Domain\Integrations\IntegrationsRepository` w: `cron.php`, `shop\Order`, `admin\Controllers\ShopPaymentMethodController`, `admin\Controllers\ShopStatusesController`, `admin\Controllers\ShopTransportController`
|
||||
- **Admin UX**
|
||||
- NOWE: globalna wyszukiwarka w top-barze (obok "Wyczysc cache") dla produktow i zamowien
|
||||
- NOWE: endpoint `/admin/settings/globalSearchAjax/` (`SettingsController::globalSearchAjax`)
|
||||
- FIX: wsparcie wyszukiwania po pełnym imieniu i nazwisku (np. "Jan Kowalski") + poprawka escapingu SQL w `CONCAT_WS`
|
||||
- **Dashboard** - migracja modulu #30 na Domain + DI
|
||||
- NOWE: `Domain\Dashboard\DashboardRepository` (summaryOrders, summarySales, salesGrid, mostViewedProducts, bestSalesProducts, last24MonthsSales, lastOrders, Redis caching)
|
||||
- NOWE: `admin\Controllers\DashboardController` (DI z DashboardRepository + ShopStatusRepository)
|
||||
- CLEANUP: usuniete `autoload/admin/controls/class.Dashboard.php`, `autoload/shop/class.Dashboard.php`
|
||||
- **Update** - migracja modulu #31 na Domain + DI
|
||||
- NOWE: `Domain\Update\UpdateRepository` (update, runPendingMigrations, downloadAndApply, executeSql, deleteFiles, extractZip, saveLog)
|
||||
- NOWE: `admin\Controllers\UpdateController` (DI z UpdateRepository)
|
||||
- UPDATE: template `update/main-view.php` - usunieto `gridEdit` i `$.prompt()`, zastapiono panelami + `$.confirm()`/`$.alert()`
|
||||
- CLEANUP: usuniete `autoload/admin/controls/class.Update.php`, `autoload/admin/factory/class.Update.php`, `autoload/admin/view/class.Update.php`
|
||||
- **Legacy cleanup**
|
||||
- CLEANUP: usunieto `autoload/admin/factory/class.Articles.php` (martwy kod, `articles_by_date_add` przeniesione do `ArticleRepository`)
|
||||
- UPDATE: `front\factory\Newsletter` przepieta na `Domain\Article\ArticleRepository::articlesByDateAdd()`
|
||||
- CLEANUP: usunieto `autoload/admin/view/class.Page.php`, logika przeniesiona do `admin\App::render()`
|
||||
- CLEANUP: usuniete puste foldery `autoload/admin/controls/`, `autoload/admin/factory/`, `autoload/admin/view/`
|
||||
- **admin\Site -> admin\App**
|
||||
- UPDATE: klasa `admin\Site` przemianowana na `admin\App` (plik `App.php` bez przedrostka `class.`)
|
||||
- UPDATE: refaktoring `App` — usunieto martwy fallback na `\admin\controls\`, uproszczono routing, ujednolicony code style
|
||||
- TEST:
|
||||
- Pelny suite: **OK (385 tests, 1246 assertions)**
|
||||
- Test punktowy: `SettingsControllerTest` **OK (7 tests, 10 assertions)**
|
||||
- NOWE: `DashboardControllerTest` (4), `DashboardRepositoryTest` (6), `UpdateControllerTest` (6), `UpdateRepositoryTest` (6)
|
||||
- Pelny suite: **OK (414 tests, 1335 assertions)**
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -347,5 +347,31 @@ Pelna dokumentacja testow: `TESTING.md`
|
||||
- Przepieto zaleznosci `ShopProduct` z `admin\factory\ShopCategory` na `Domain\Category\CategoryRepository`.
|
||||
- Usunieto preload `autoload/admin/factory/class.ShopCategory.php` z `libraries/grid/config.php`.
|
||||
|
||||
## Dodatkowa aktualizacja 2026-02-15 (ver. 0.277) - ShopProduct (factory)
|
||||
- `Domain/Product/ProductRepository.php` rozszerzono o ~40 metod: CRUD, save, delete, duplicate, toggleStatus, updatePrice, kombinacje, zdjecia/pliki, Google Feed XML, custom labels.
|
||||
- `admin/Controllers/ShopProductController.php` rozszerzono o ~30 akcji obslugujacych caly modul produktow.
|
||||
- Konstruktor kontrolera teraz przyjmuje `ProductRepository` + `IntegrationsRepository`.
|
||||
- Routing w `admin\Site` zaktualizowany (dodano `IntegrationsRepository`, blokada fallbacku na legacy).
|
||||
- Przepieto zaleznosci zewnetrzne: `ProductArchiveController`, `order-details.php`, `cron.php`, `cron-xml.php`, `products-list-table.php`, `stock.php`.
|
||||
- Przepieto endpointy AJAX z `admin/ajax.php` na kontroler: `product_file_delete`, `product_file_name_change`.
|
||||
- Przepieto `cookie_categories` w widokach product-edit i mass-edit na `/admin/shop_category/cookie_categories/`.
|
||||
- Usunieto legacy: `autoload/admin/controls/class.ShopProduct.php`, `autoload/admin/factory/class.ShopProduct.php`, `admin/ajax/shop.php`.
|
||||
- Usunieto `require_once 'ajax/shop.php'` z `admin/ajax.php`.
|
||||
|
||||
## Dodatkowa aktualizacja 2026-02-16 (ver. 0.277) - Dashboard, Update, legacy cleanup, admin\App
|
||||
- Dodano `Domain/Dashboard/DashboardRepository.php` (7 metod, Redis caching).
|
||||
- Dodano `admin/Controllers/DashboardController.php` (DI z DashboardRepository + ShopStatusRepository).
|
||||
- Dodano `Domain/Update/UpdateRepository.php` (update, runPendingMigrations, helper methods).
|
||||
- Dodano `admin/Controllers/UpdateController.php` (DI z UpdateRepository).
|
||||
- Przepisano `admin/templates/update/main-view.php` — usunieto `gridEdit`, `$.prompt()`, zastapiono panelami + `$.confirm()`.
|
||||
- Usunieto `autoload/admin/factory/class.Articles.php` (martwy kod), przeniesiono `articles_by_date_add` do `ArticleRepository`.
|
||||
- Przepieto `front\factory\Newsletter` na `ArticleRepository::articlesByDateAdd()`.
|
||||
- Przeniesiono logike z `admin\view\Page::show()` do `admin\App::render()`.
|
||||
- Przemianowano `admin\Site` na `admin\App` (plik `App.php`).
|
||||
- Usunieto fallback na `\admin\controls\` w routing (martwy kod).
|
||||
- Usunieto puste foldery: `autoload/admin/controls/`, `autoload/admin/factory/`, `autoload/admin/view/`.
|
||||
- Usunieto stary plik `autoload/admin/class.Site.php`.
|
||||
- Pelna migracja admin zakonczona — wszystkie moduly na Domain + DI + Controllers.
|
||||
|
||||
---
|
||||
*Dokument aktualizowany: 2026-02-15*
|
||||
*Dokument aktualizowany: 2026-02-16*
|
||||
|
||||
@@ -130,7 +130,7 @@ grep -r "Product::getQuantity" .
|
||||
| # | Modul | Wersja | Zakres |
|
||||
|---|-------|--------|--------|
|
||||
| 1 | Cache | 0.237 | CacheHandler, RedisConnection, clear_product_cache |
|
||||
| 2 | Product | 0.238-0.252, 0.274 | getQuantity, getPrice, getName, archive/unarchive, allProductsForMassEdit, getProductsByCategory, applyDiscountPercent |
|
||||
| 2 | Product | 0.238-0.252, 0.274, 0.277 | getQuantity, getPrice, getName, archive/unarchive, allProductsForMassEdit, getProductsByCategory, applyDiscountPercent, pelna migracja factory (CRUD, save, delete, duplicate, kombinacje, zdjecia/pliki, Google Feed XML) |
|
||||
| 3 | Banner | 0.239 | find, delete, save, kontroler DI |
|
||||
| 4 | Settings | 0.240/0.250 | saveSettings, getSettings, kontroler DI |
|
||||
| 5 | Dictionaries | 0.251 | listForAdmin, find, save, delete, kontroler DI |
|
||||
@@ -157,6 +157,11 @@ grep -r "Product::getQuantity" .
|
||||
| 26 | ShopClients | 0.274 | DI kontroler + routing dla `list/details`, nowe listy na `components/table-list`, cleanup legacy controls/factory |
|
||||
| 27 | ShopCategory | 0.275 | CategoryRepository + DI kontroler + routing, endpointy AJAX (`save_categories_order`, `save_products_order`, `cookie_categories`), cleanup legacy controls/factory/view |
|
||||
| 28 | ShopOrder | 0.276 | OrderRepository + OrderAdminService + DI kontroler + routing + nowe widoki (`orders-list`, `order-details`, `order-edit`) + cleanup legacy controls/factory/view-list |
|
||||
| 29 | ShopProduct (factory) | 0.277 | Pelna migracja factory: ProductRepository (CRUD, save, delete, duplicate, toggleStatus, updatePrice, kombinacje, zdjecia/pliki, Google Feed XML) + DI kontroler (list, edit, save, operacje, kombinacje, zdjecia/pliki) + routing + przepiecie zaleznosci (ProductArchive, order-details, cron, cron-xml, products-list-table, stock) + usunięcie legacy (controls, factory, ajax/shop.php) |
|
||||
| 30 | Dashboard | 0.277 | DashboardRepository (7 metod, Redis caching) + DashboardController (DI) + cleanup legacy controls/shop |
|
||||
| 31 | Update | 0.277 | UpdateRepository (update, runPendingMigrations, helper methods) + UpdateController (DI) + przepisany template (panele, $.confirm) + cleanup legacy controls/factory/view |
|
||||
| 32 | Legacy cleanup | 0.277 | Usunieto admin/factory/Articles (martwy kod), admin/view/Page → App::render(), puste foldery controls/factory/view |
|
||||
| 33 | admin\App | 0.277 | Rename Site → App, usunieto fallback na controls, uproszczony routing, plik App.php bez przedrostka class. |
|
||||
|
||||
### Product - szczegolowy status
|
||||
- ✅ getQuantity (ver. 0.238)
|
||||
@@ -166,19 +171,22 @@ grep -r "Product::getQuantity" .
|
||||
- ✅ allProductsForMassEdit (ver. 0.274)
|
||||
- ✅ getProductsByCategory (ver. 0.274)
|
||||
- ✅ applyDiscountPercent (ver. 0.274)
|
||||
- [ ] is_product_on_promotion
|
||||
- [ ] getFromCache
|
||||
- [ ] getProductImg
|
||||
- ✅ countProducts, listForAdmin, findForAdmin, allProductsList, productCategoriesText, getParentId (ver. 0.277)
|
||||
- ✅ saveProduct + helpery (ver. 0.277)
|
||||
- ✅ delete, duplicate, toggleStatus, updatePriceBrutto/Promo, updateCustomLabel (ver. 0.277)
|
||||
- ✅ getPermutations, generateCombinations, deleteCombination, countCombinations, saveCombination* (ver. 0.277)
|
||||
- ✅ deleteImage, updateImageAlt, saveImagesOrder, deleteFile, updateFileName, generateGoogleFeedXml, generateEAN (ver. 0.277)
|
||||
- ✅ updateCombinationPricesFromBase (ver. 0.277)
|
||||
- [ ] is_product_on_promotion (frontend — osobna migracja)
|
||||
- [ ] getFromCache (frontend — osobna migracja)
|
||||
- [ ] getProductImg (frontend — osobna migracja)
|
||||
|
||||
### 📋 Do zrobienia
|
||||
- ShopProduct (factory)
|
||||
- Frontend: migracja `front\factory\ShopProduct`
|
||||
|
||||
## Kolejność refaktoryzacji (priorytet)
|
||||
|
||||
1-28: ✅ Cache, Product, Banner, Settings, Dictionaries, ProductArchive, Filemanager, Users, Pages, Integrations, ShopPromotion, ShopCoupon, ShopStatuses, ShopPaymentMethod, ShopTransport, ShopAttribute, ShopProductSets, ShopProducer, ShopProduct (mass_edit), ShopClients, ShopCategory, ShopOrder
|
||||
|
||||
Nastepne:
|
||||
29. **ShopProduct (factory)**
|
||||
1-33: ✅ Cache, Product, Banner, Settings, Dictionaries, ProductArchive, Filemanager, Users, Pages, Integrations, ShopPromotion, ShopCoupon, ShopStatuses, ShopPaymentMethod, ShopTransport, ShopAttribute, ShopProductSets, ShopProducer, ShopProduct (mass_edit), ShopClients, ShopCategory, ShopOrder, ShopProduct (factory), Dashboard, Update, Legacy cleanup, admin\App
|
||||
|
||||
## Form Edit System
|
||||
|
||||
@@ -279,7 +287,7 @@ tests/
|
||||
│ └── UsersControllerTest.php
|
||||
└── Integration/
|
||||
```
|
||||
**Lacznie: 338 testow, 1063 asercji**
|
||||
**Lacznie: 390 testow, 1278 asercji**
|
||||
|
||||
Aktualizacja 2026-02-15 (ver. 0.273):
|
||||
- dodano testy `tests/Unit/Domain/Producer/ProducerRepositoryTest.php`
|
||||
|
||||
@@ -33,10 +33,16 @@ Alternatywnie (Git Bash):
|
||||
|
||||
## Aktualny stan suite
|
||||
|
||||
Ostatnio zweryfikowano: 2026-02-15
|
||||
Ostatnio zweryfikowano: 2026-02-16
|
||||
|
||||
```text
|
||||
OK (385 tests, 1246 assertions)
|
||||
OK (414 tests, 1335 assertions)
|
||||
```
|
||||
|
||||
Aktualizacja po migracji Dashboard + Update + legacy cleanup (2026-02-16, ver. 0.277):
|
||||
```text
|
||||
Pelny suite: OK (414 tests, 1335 assertions)
|
||||
Nowe testy: DashboardControllerTest (4), DashboardRepositoryTest (6), UpdateControllerTest (6), UpdateRepositoryTest (6)
|
||||
```
|
||||
|
||||
Aktualizacja po stabilizacji ShopOrder / Integrations / Global Search (2026-02-15, ver. 0.277):
|
||||
|
||||
@@ -18,17 +18,17 @@ Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesią
|
||||
|
||||
## Procedura tworzenia nowej aktualizacji
|
||||
|
||||
## Status biezacej aktualizacji (ver. 0.276)
|
||||
## Status biezacej aktualizacji (ver. 0.277)
|
||||
|
||||
- Wersja udostepniona: `0.276` (data: 2026-02-15).
|
||||
- Wersja udostepniona: `0.277` (data: 2026-02-16).
|
||||
- Pliki publikacyjne:
|
||||
- `updates/0.20/ver_0.276.zip`
|
||||
- `updates/0.20/ver_0.276_files.txt`
|
||||
- `updates/0.20/ver_0.277.zip`
|
||||
- `updates/0.20/ver_0.277_files.txt`
|
||||
- Pliki metadanych aktualizacji:
|
||||
- `updates/changelog.php` (dodany wpis `ver. 0.276`)
|
||||
- `updates/versions.php` (`$current_ver = 276`)
|
||||
- `updates/changelog.php` (dodany wpis `ver. 0.277`)
|
||||
- `updates/versions.php` (`$current_ver = 277`)
|
||||
- Weryfikacja testow przed publikacja:
|
||||
- `OK (385 tests, 1246 assertions)`
|
||||
- `OK (414 tests, 1335 assertions)`
|
||||
|
||||
### 1. Określ numer wersji
|
||||
Sprawdź ostatnią wersję w `temp/` i zwiększ o 1.
|
||||
|
||||
@@ -16,7 +16,6 @@ require_once dirname( __FILE__ ) . '/../../autoload/class.S.php';
|
||||
$legacyFactoryFiles = [
|
||||
'/../../autoload/admin/factory/class.Articles.php',
|
||||
'/../../autoload/admin/factory/class.Pages.php',
|
||||
'/../../autoload/admin/factory/class.ShopProduct.php',
|
||||
];
|
||||
|
||||
foreach ( $legacyFactoryFiles as $legacyFactoryFile )
|
||||
|
||||
BIN
temp/ver_0.277.zip
Normal file
BIN
temp/ver_0.277.zip
Normal file
Binary file not shown.
18
temp/ver_0.277_files.txt
Normal file
18
temp/ver_0.277_files.txt
Normal file
@@ -0,0 +1,18 @@
|
||||
F: ../autoload/admin/class.Site.php
|
||||
F: ../autoload/admin/controls/class.Dashboard.php
|
||||
F: ../autoload/admin/controls/class.ShopProduct.php
|
||||
F: ../autoload/admin/controls/class.Update.php
|
||||
F: ../autoload/admin/factory/class.Articles.php
|
||||
F: ../autoload/admin/factory/class.ShopProduct.php
|
||||
F: ../autoload/admin/factory/class.Update.php
|
||||
F: ../autoload/admin/view/class.Page.php
|
||||
F: ../autoload/admin/view/class.PagePanel.php
|
||||
F: ../autoload/admin/view/class.ShopProduct.php
|
||||
F: ../autoload/admin/view/class.Update.php
|
||||
F: ../autoload/shop/class.Dashboard.php
|
||||
F: ../admin/ajax/shop.php
|
||||
F: ../admin/templates/shop-product/products-list-table.php
|
||||
F: ../admin/templates/shop-product/stock.php
|
||||
D: ../autoload/admin/controls
|
||||
D: ../autoload/admin/factory
|
||||
D: ../autoload/admin/view
|
||||
89
tests/Unit/Domain/Dashboard/DashboardRepositoryTest.php
Normal file
89
tests/Unit/Domain/Dashboard/DashboardRepositoryTest.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
namespace Tests\Unit\Domain\Dashboard;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Domain\Dashboard\DashboardRepository;
|
||||
|
||||
class DashboardRepositoryTest extends TestCase
|
||||
{
|
||||
private function createMockDb(): \medoo
|
||||
{
|
||||
return $this->createMock(\medoo::class);
|
||||
}
|
||||
|
||||
public function testConstructorAcceptsDb(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$repository = new DashboardRepository($db);
|
||||
$this->assertInstanceOf(DashboardRepository::class, $repository);
|
||||
}
|
||||
|
||||
public function testHasAllPublicMethods(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$repository = new DashboardRepository($db);
|
||||
|
||||
$expectedMethods = [
|
||||
'summaryOrders',
|
||||
'summarySales',
|
||||
'salesGrid',
|
||||
'mostViewedProducts',
|
||||
'bestSalesProducts',
|
||||
'last24MonthsSales',
|
||||
'lastOrders',
|
||||
];
|
||||
|
||||
foreach ($expectedMethods as $method) {
|
||||
$this->assertTrue(
|
||||
method_exists($repository, $method),
|
||||
"Missing method: {$method}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testSalesGridReturnsArray(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$db->method('select')->willReturn([]);
|
||||
$repository = new DashboardRepository($db);
|
||||
|
||||
$result = $repository->salesGrid();
|
||||
$this->assertIsArray($result);
|
||||
}
|
||||
|
||||
public function testLastOrdersReturnsArray(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$stmt = $this->createMock(\PDOStatement::class);
|
||||
$stmt->method('fetchAll')->willReturn([]);
|
||||
$db->method('query')->willReturn($stmt);
|
||||
|
||||
$repository = new DashboardRepository($db);
|
||||
$result = $repository->lastOrders();
|
||||
$this->assertIsArray($result);
|
||||
}
|
||||
|
||||
public function testMostViewedProductsReturnsArray(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$stmt = $this->createMock(\PDOStatement::class);
|
||||
$stmt->method('fetchAll')->willReturn([]);
|
||||
$db->method('query')->willReturn($stmt);
|
||||
|
||||
$repository = new DashboardRepository($db);
|
||||
$result = $repository->mostViewedProducts();
|
||||
$this->assertIsArray($result);
|
||||
}
|
||||
|
||||
public function testBestSalesProductsReturnsArray(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$stmt = $this->createMock(\PDOStatement::class);
|
||||
$stmt->method('fetchAll')->willReturn([]);
|
||||
$db->method('query')->willReturn($stmt);
|
||||
|
||||
$repository = new DashboardRepository($db);
|
||||
$result = $repository->bestSalesProducts();
|
||||
$this->assertIsArray($result);
|
||||
}
|
||||
}
|
||||
80
tests/Unit/Domain/Update/UpdateRepositoryTest.php
Normal file
80
tests/Unit/Domain/Update/UpdateRepositoryTest.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
namespace Tests\Unit\Domain\Update;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Domain\Update\UpdateRepository;
|
||||
|
||||
class UpdateRepositoryTest extends TestCase
|
||||
{
|
||||
private function createMockDb(): \medoo
|
||||
{
|
||||
return $this->createMock(\medoo::class);
|
||||
}
|
||||
|
||||
public function testConstructorAcceptsDb(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$repository = new UpdateRepository($db);
|
||||
$this->assertInstanceOf(UpdateRepository::class, $repository);
|
||||
}
|
||||
|
||||
public function testHasUpdateMethod(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$repository = new UpdateRepository($db);
|
||||
$this->assertTrue(method_exists($repository, 'update'));
|
||||
}
|
||||
|
||||
public function testUpdateReturnsArray(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$repository = new UpdateRepository($db);
|
||||
|
||||
$reflection = new \ReflectionClass($repository);
|
||||
$method = $reflection->getMethod('update');
|
||||
$this->assertEquals('array', (string)$method->getReturnType());
|
||||
}
|
||||
|
||||
public function testHasRunPendingMigrationsMethod(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$repository = new UpdateRepository($db);
|
||||
$this->assertTrue(method_exists($repository, 'runPendingMigrations'));
|
||||
}
|
||||
|
||||
public function testRunPendingMigrationsWithNoResults(): void
|
||||
{
|
||||
$db = $this->createMockDb();
|
||||
$db->method('select')->willReturn(false);
|
||||
|
||||
$repository = new UpdateRepository($db);
|
||||
$repository->runPendingMigrations();
|
||||
|
||||
$this->assertTrue(true); // No exception thrown
|
||||
}
|
||||
|
||||
public function testHasPrivateHelperMethods(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(UpdateRepository::class);
|
||||
|
||||
$privateMethods = [
|
||||
'downloadAndApply',
|
||||
'executeSql',
|
||||
'deleteFiles',
|
||||
'extractZip',
|
||||
'saveLog',
|
||||
];
|
||||
|
||||
foreach ($privateMethods as $methodName) {
|
||||
$this->assertTrue(
|
||||
$reflection->hasMethod($methodName),
|
||||
"Missing private method: {$methodName}"
|
||||
);
|
||||
$method = $reflection->getMethod($methodName);
|
||||
$this->assertTrue(
|
||||
$method->isPrivate(),
|
||||
"Method {$methodName} should be private"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
49
tests/Unit/admin/Controllers/DashboardControllerTest.php
Normal file
49
tests/Unit/admin/Controllers/DashboardControllerTest.php
Normal file
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
namespace Tests\Unit\admin\Controllers;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use admin\Controllers\DashboardController;
|
||||
use Domain\Dashboard\DashboardRepository;
|
||||
use Domain\ShopStatus\ShopStatusRepository;
|
||||
|
||||
class DashboardControllerTest extends TestCase
|
||||
{
|
||||
private $repository;
|
||||
private $statusesRepository;
|
||||
private $controller;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = $this->createMock(DashboardRepository::class);
|
||||
$this->statusesRepository = $this->createMock(ShopStatusRepository::class);
|
||||
$this->controller = new DashboardController($this->repository, $this->statusesRepository);
|
||||
}
|
||||
|
||||
public function testConstructorAcceptsRepositories(): void
|
||||
{
|
||||
$controller = new DashboardController($this->repository, $this->statusesRepository);
|
||||
$this->assertInstanceOf(DashboardController::class, $controller);
|
||||
}
|
||||
|
||||
public function testHasMainViewMethod(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'main_view'));
|
||||
}
|
||||
|
||||
public function testMainViewReturnsString(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('main_view')->getReturnType());
|
||||
}
|
||||
|
||||
public function testConstructorRequiresRepositories(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(DashboardController::class);
|
||||
$constructor = $reflection->getConstructor();
|
||||
$params = $constructor->getParameters();
|
||||
|
||||
$this->assertCount(2, $params);
|
||||
$this->assertEquals('Domain\Dashboard\DashboardRepository', $params[0]->getType()->getName());
|
||||
$this->assertEquals('Domain\ShopStatus\ShopStatusRepository', $params[1]->getType()->getName());
|
||||
}
|
||||
}
|
||||
@@ -4,21 +4,24 @@ namespace Tests\Unit\admin\Controllers;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use admin\Controllers\ShopProductController;
|
||||
use Domain\Product\ProductRepository;
|
||||
use Domain\Integrations\IntegrationsRepository;
|
||||
|
||||
class ShopProductControllerTest extends TestCase
|
||||
{
|
||||
private $repository;
|
||||
private $integrationsRepository;
|
||||
private $controller;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = $this->createMock(ProductRepository::class);
|
||||
$this->controller = new ShopProductController($this->repository);
|
||||
$this->integrationsRepository = $this->createMock(IntegrationsRepository::class);
|
||||
$this->controller = new ShopProductController($this->repository, $this->integrationsRepository);
|
||||
}
|
||||
|
||||
public function testConstructorAcceptsRepository(): void
|
||||
public function testConstructorAcceptsRepositories(): void
|
||||
{
|
||||
$controller = new ShopProductController($this->repository);
|
||||
$controller = new ShopProductController($this->repository, $this->integrationsRepository);
|
||||
$this->assertInstanceOf(ShopProductController::class, $controller);
|
||||
}
|
||||
|
||||
@@ -29,6 +32,55 @@ class ShopProductControllerTest extends TestCase
|
||||
$this->assertTrue(method_exists($this->controller, 'get_products_by_category'));
|
||||
}
|
||||
|
||||
public function testHasViewListMethods(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'view_list'));
|
||||
}
|
||||
|
||||
public function testHasEditAndSaveMethods(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'product_edit'));
|
||||
$this->assertTrue(method_exists($this->controller, 'save'));
|
||||
}
|
||||
|
||||
public function testHasOperationMethods(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'duplicate_product'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_archive'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_unarchive'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_delete'));
|
||||
$this->assertTrue(method_exists($this->controller, 'change_product_status'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_change_price_brutto'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_change_price_brutto_promo'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_change_custom_label'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_custom_label_suggestions'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_custom_label_save'));
|
||||
$this->assertTrue(method_exists($this->controller, 'ajax_product_url'));
|
||||
$this->assertTrue(method_exists($this->controller, 'generate_sku_code'));
|
||||
}
|
||||
|
||||
public function testHasCombinationMethods(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'product_combination'));
|
||||
$this->assertTrue(method_exists($this->controller, 'generate_combination'));
|
||||
$this->assertTrue(method_exists($this->controller, 'delete_combination'));
|
||||
$this->assertTrue(method_exists($this->controller, 'delete_combination_ajax'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_combination_stock_0_buy_save'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_combination_sku_save'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_combination_quantity_save'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_combination_price_save'));
|
||||
}
|
||||
|
||||
public function testHasImageAndFileMethods(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'image_delete'));
|
||||
$this->assertTrue(method_exists($this->controller, 'images_order_save'));
|
||||
$this->assertTrue(method_exists($this->controller, 'image_alt_change'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_file_delete'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_file_name_change'));
|
||||
$this->assertTrue(method_exists($this->controller, 'product_image_delete'));
|
||||
}
|
||||
|
||||
public function testMassEditReturnsString(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
@@ -47,13 +99,48 @@ class ShopProductControllerTest extends TestCase
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('get_products_by_category')->getReturnType());
|
||||
}
|
||||
|
||||
public function testConstructorRequiresProductRepository(): void
|
||||
public function testConstructorRequiresRepositories(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(ShopProductController::class);
|
||||
$constructor = $reflection->getConstructor();
|
||||
$params = $constructor->getParameters();
|
||||
|
||||
$this->assertCount(1, $params);
|
||||
$this->assertCount(2, $params);
|
||||
$this->assertEquals('Domain\Product\ProductRepository', $params[0]->getType()->getName());
|
||||
$this->assertEquals('Domain\Integrations\IntegrationsRepository', $params[1]->getType()->getName());
|
||||
}
|
||||
|
||||
public function testHasFormBuildingHelpers(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(ShopProductController::class);
|
||||
|
||||
$expectedPrivate = [
|
||||
'buildProductFormViewModel',
|
||||
'renderSkuField',
|
||||
'renderCategoriesTree',
|
||||
'renderGalleryBox',
|
||||
'renderFilesBox',
|
||||
'renderRelatedProducts',
|
||||
'renderCustomFieldsBox',
|
||||
'escapeHtml',
|
||||
'resolveSavePayload',
|
||||
];
|
||||
|
||||
foreach ($expectedPrivate as $method) {
|
||||
$this->assertTrue(
|
||||
$reflection->hasMethod($method),
|
||||
"Missing private method: {$method}"
|
||||
);
|
||||
$this->assertTrue(
|
||||
$reflection->getMethod($method)->isPrivate(),
|
||||
"Method {$method} should be private"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testSaveMethodReturnsVoid(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
$this->assertEquals('void', (string)$reflection->getMethod('save')->getReturnType());
|
||||
}
|
||||
}
|
||||
|
||||
55
tests/Unit/admin/Controllers/UpdateControllerTest.php
Normal file
55
tests/Unit/admin/Controllers/UpdateControllerTest.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
namespace Tests\Unit\admin\Controllers;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use admin\Controllers\UpdateController;
|
||||
use Domain\Update\UpdateRepository;
|
||||
|
||||
class UpdateControllerTest extends TestCase
|
||||
{
|
||||
private $repository;
|
||||
private $controller;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->repository = $this->createMock(UpdateRepository::class);
|
||||
$this->controller = new UpdateController($this->repository);
|
||||
}
|
||||
|
||||
public function testConstructorAcceptsRepository(): void
|
||||
{
|
||||
$controller = new UpdateController($this->repository);
|
||||
$this->assertInstanceOf(UpdateController::class, $controller);
|
||||
}
|
||||
|
||||
public function testHasMainViewMethod(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'main_view'));
|
||||
}
|
||||
|
||||
public function testMainViewReturnsString(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass($this->controller);
|
||||
$this->assertEquals('string', (string)$reflection->getMethod('main_view')->getReturnType());
|
||||
}
|
||||
|
||||
public function testHasUpdateMethod(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'update'));
|
||||
}
|
||||
|
||||
public function testHasUpdateAllMethod(): void
|
||||
{
|
||||
$this->assertTrue(method_exists($this->controller, 'updateAll'));
|
||||
}
|
||||
|
||||
public function testConstructorRequiresRepository(): void
|
||||
{
|
||||
$reflection = new \ReflectionClass(UpdateController::class);
|
||||
$constructor = $reflection->getConstructor();
|
||||
$params = $constructor->getParameters();
|
||||
|
||||
$this->assertCount(1, $params);
|
||||
$this->assertEquals('Domain\Update\UpdateRepository', $params[0]->getType()->getName());
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,15 @@
|
||||
<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`)
|
||||
|
||||
Reference in New Issue
Block a user