- Implemented CronJobProcessor for managing scheduled jobs and processing job queues. - Created CronJobRepository for database interactions related to cron jobs. - Defined CronJobType for job types, statuses, and backoff calculations. - Added ApiloLogger for logging actions related to API interactions. - Enhanced UpdateController to check for updates and display update logs. - Updated FormAction to include a preview action for forms. - Modified ApiRouter to handle new dependencies for OrderAdminService and ProductsApiController. - Extended DictionariesApiController to manage attributes and producers. - Enhanced ProductsApiController with variant management and image upload functionality. - Updated ShopBasketController and ShopProductController to sort attributes and handle custom fields. - Added configuration for cron jobs in config.php. - Initialized apilo-sync-queue.json for managing sync tasks.
1200 lines
50 KiB
PHP
1200 lines
50 KiB
PHP
<?php
|
|
namespace admin\Controllers;
|
|
|
|
use Domain\Product\ProductRepository;
|
|
use Domain\Category\CategoryRepository;
|
|
use Domain\Integrations\IntegrationsRepository;
|
|
use Domain\Languages\LanguagesRepository;
|
|
use admin\ViewModels\Forms\FormEditViewModel;
|
|
use admin\ViewModels\Forms\FormField;
|
|
use admin\ViewModels\Forms\FormTab;
|
|
use admin\ViewModels\Forms\FormAction;
|
|
use admin\ViewModels\Common\PaginatedTableViewModel;
|
|
use admin\Support\TableListRequestFactory;
|
|
|
|
/**
|
|
* Kontroler produktów w panelu administratora.
|
|
* Obsługuje: listę produktów, edycję, zapis, operacje, kombinacje, zdjęcia, pliki, masową edycję.
|
|
*/
|
|
class ShopProductController
|
|
{
|
|
private ProductRepository $repository;
|
|
private IntegrationsRepository $integrationsRepository;
|
|
private LanguagesRepository $languagesRepository;
|
|
|
|
public function __construct(ProductRepository $repository, IntegrationsRepository $integrationsRepository, LanguagesRepository $languagesRepository)
|
|
{
|
|
$this->repository = $repository;
|
|
$this->integrationsRepository = $integrationsRepository;
|
|
$this->languagesRepository = $languagesRepository;
|
|
}
|
|
|
|
// ─── Krok 6: Lista / widok ───────────────────────────────────────
|
|
|
|
/**
|
|
* Lista produktów.
|
|
*/
|
|
public function view_list(): string
|
|
{
|
|
$apiloEnabled = $this->integrationsRepository->getSetting( 'apilo', 'enabled' );
|
|
$shopproEnabled = $this->integrationsRepository->getSetting( 'shoppro', 'enabled' );
|
|
$dlang = $this->languagesRepository->defaultLanguage();
|
|
|
|
$sortableColumns = [ 'id', 'name', 'price_brutto', 'status', 'promoted', 'quantity' ];
|
|
|
|
$filterDefinitions = [
|
|
[
|
|
'key' => 'search',
|
|
'label' => 'Szukaj (nazwa, EAN, SKU)',
|
|
'type' => 'text',
|
|
],
|
|
[
|
|
'key' => 'status',
|
|
'label' => 'Aktywny',
|
|
'type' => 'select',
|
|
'options' => [ '' => '- aktywny -', '1' => 'tak', '0' => 'nie' ],
|
|
],
|
|
[
|
|
'key' => 'promoted',
|
|
'label' => 'Promowany',
|
|
'type' => 'select',
|
|
'options' => [ '' => '- promowany -', '1' => 'tak', '0' => 'nie' ],
|
|
],
|
|
];
|
|
|
|
$listRequest = TableListRequestFactory::fromRequest(
|
|
$filterDefinitions,
|
|
$sortableColumns,
|
|
'id'
|
|
);
|
|
|
|
$result = $this->repository->listForAdmin(
|
|
$listRequest['filters'],
|
|
$listRequest['sortColumn'],
|
|
$listRequest['sortDir'],
|
|
$listRequest['page'],
|
|
$listRequest['perPage']
|
|
);
|
|
|
|
$rows = [];
|
|
$lp = ( $listRequest['page'] - 1 ) * $listRequest['perPage'] + 1;
|
|
|
|
foreach ( $result['items'] as $product ) {
|
|
$id = (int) $product['id'];
|
|
$name = htmlspecialchars( (string) ( $product['languages'][$dlang]['name'] ?? '' ), ENT_QUOTES, 'UTF-8' );
|
|
$imgSrc = !empty( $product['images'][0]['src'] )
|
|
? $product['images'][0]['src']
|
|
: '/admin/layout/images/no-image.png';
|
|
$imgAlt = htmlspecialchars( (string) ( $product['images'][0]['alt'] ?? '' ), ENT_QUOTES, 'UTF-8' );
|
|
$categories = htmlspecialchars( $this->repository->productCategoriesText( $id ), ENT_QUOTES, 'UTF-8' );
|
|
$sku = htmlspecialchars( (string) ( $product['sku'] ?? '' ), ENT_QUOTES, 'UTF-8' );
|
|
$ean = htmlspecialchars( (string) ( $product['ean'] ?? '' ), ENT_QUOTES, 'UTF-8' );
|
|
|
|
$nameHtml = '<div class="product-image"><img src="' . $imgSrc . '" alt="' . $imgAlt . '" class="img-responsive"></div>'
|
|
. '<div class="product-name">'
|
|
. '<a href="/admin/shop_product/product_edit/id=' . $id . '">' . $name . '</a> '
|
|
. '<a href="#" class="text-muted duplicate-product" product-id="' . $id . '">duplikuj</a>'
|
|
. '</div>'
|
|
. '<small class="text-muted product-categories product-categories--cats" title="' . $categories . '">' . $categories . '</small>'
|
|
. '<small class="text-muted product-categories">SKU: ' . $sku . ', EAN: ' . $ean . '</small>';
|
|
|
|
$priceHtml = '<input type="text" class="product-price form-control text-right" product-id="' . $id . '" value="' . htmlspecialchars( (string) $product['price_brutto'], ENT_QUOTES, 'UTF-8' ) . '" style="width: 75px;">';
|
|
$promoHtml = '<input type="text" class="product-price-promo form-control text-right" product-id="' . $id . '" value="' . htmlspecialchars( (string) $product['price_brutto_promo'], ENT_QUOTES, 'UTF-8' ) . '" style="width: 75px;">';
|
|
$promotedHtml = $product['promoted'] ? '<span class="text-success text-bold">tak</span>' : 'nie';
|
|
$statusHtml = $product['status'] ? 'tak' : '<span class="text-danger text-bold">nie</span>';
|
|
$quantity = (int) $this->repository->getQuantity( $id );
|
|
$combinationsCount = $this->repository->countCombinations( $id );
|
|
|
|
$row = [
|
|
'lp' => $lp++ . '.',
|
|
'name' => $nameHtml,
|
|
'price' => $priceHtml,
|
|
'price_promo' => $promoHtml,
|
|
'promoted' => $promotedHtml,
|
|
'status' => $statusHtml,
|
|
'quantity' => '<span class="text-muted">' . $quantity . '</span>',
|
|
'combinations' => '<a href="/admin/shop_product/product_combination/product_id=' . $id . '">kombinacje (' . $combinationsCount . ')</a>',
|
|
'_actions' => [
|
|
[
|
|
'label' => 'Edytuj',
|
|
'url' => '/admin/shop_product/product_edit/id=' . $id,
|
|
'class' => 'btn btn-xs btn-primary',
|
|
],
|
|
[
|
|
'label' => 'Archiwizuj',
|
|
'url' => '/admin/shop_product/product_archive/product_id=' . $id,
|
|
'class' => 'btn btn-xs btn-danger',
|
|
'confirm' => 'Na pewno chcesz przenieść wybrany produkt do archiwum?',
|
|
],
|
|
],
|
|
];
|
|
|
|
if ( $apiloEnabled ) {
|
|
if ( !empty( $product['apilo_product_name'] ) ) {
|
|
$apiloName = htmlspecialchars( (string) $product['apilo_product_name'], ENT_QUOTES, 'UTF-8' );
|
|
$apiloShort = htmlspecialchars( mb_substr( (string) $product['apilo_product_name'], 0, 25, 'UTF-8' ), ENT_QUOTES, 'UTF-8' );
|
|
$row['apilo'] = '<span title="' . $apiloName . '">' . $apiloShort . '...</span><br>'
|
|
. '<span class="text-danger apilo-delete-linking" product-id="' . $id . '"><i class="fa fa-times"></i>usuń powiązanie</span>';
|
|
} else {
|
|
$row['apilo'] = '<span class="text-danger apilo-product-search" product-id="' . $id . '">nie przypisano <i class="fa fa-search"></i></span>';
|
|
}
|
|
}
|
|
|
|
|
|
$rows[] = $row;
|
|
}
|
|
|
|
$total = (int) $result['total'];
|
|
$totalPages = max( 1, (int) ceil( $total / $listRequest['perPage'] ) );
|
|
|
|
$columns = [
|
|
[ 'key' => 'lp', 'label' => '#', 'class' => 'text-center', 'sortable' => false ],
|
|
[ 'key' => 'name', 'sort_key' => 'name', 'label' => 'Nazwa', 'sortable' => true, 'raw' => true ],
|
|
[ 'key' => 'price', 'sort_key' => 'price_brutto', 'label' => 'Cena', 'class' => 'text-center', 'sortable' => true, 'raw' => true ],
|
|
[ 'key' => 'price_promo', 'label' => 'Cena promocyjna', 'class' => 'text-center', 'sortable' => false, 'raw' => true ],
|
|
[ 'key' => 'promoted', 'sort_key' => 'promoted', 'label' => 'Promowany', 'class' => 'text-center', 'sortable' => true, 'raw' => true ],
|
|
[ 'key' => 'status', 'sort_key' => 'status', 'label' => 'Aktywny', 'class' => 'text-center', 'sortable' => true, 'raw' => true ],
|
|
[ 'key' => 'quantity', 'sort_key' => 'quantity', 'label' => 'Stan MG', 'class' => 'text-center', 'sortable' => true, 'raw' => true ],
|
|
];
|
|
|
|
if ( $apiloEnabled ) {
|
|
$columns[] = [ 'key' => 'apilo', 'label' => 'Apilo', 'class' => 'text-center', 'sortable' => false, 'raw' => true ];
|
|
}
|
|
|
|
$columns[] = [ 'key' => 'combinations', 'label' => 'Kombinacje', 'class' => 'text-center', 'sortable' => false, 'raw' => true ];
|
|
|
|
$viewModel = new PaginatedTableViewModel(
|
|
$columns,
|
|
$rows,
|
|
$listRequest['viewFilters'],
|
|
[
|
|
'column' => $listRequest['sortColumn'],
|
|
'dir' => $listRequest['sortDir'],
|
|
],
|
|
[
|
|
'page' => $listRequest['page'],
|
|
'per_page' => $listRequest['perPage'],
|
|
'total' => $total,
|
|
'total_pages' => $totalPages,
|
|
],
|
|
array_merge( $listRequest['queryFilters'], [
|
|
'sort' => $listRequest['sortColumn'],
|
|
'dir' => $listRequest['sortDir'],
|
|
'per_page' => $listRequest['perPage'],
|
|
] ),
|
|
$listRequest['perPageOptions'],
|
|
$sortableColumns,
|
|
'/admin/shop_product/view_list/',
|
|
'Brak produktów.',
|
|
'/admin/shop_product/product_edit/',
|
|
'Dodaj produkt',
|
|
'shop-product/products-list-custom-script'
|
|
);
|
|
|
|
return \Shared\Tpl\Tpl::view( 'shop-product/products-list', [
|
|
'viewModel' => $viewModel,
|
|
'apilo_enabled' => $apiloEnabled,
|
|
'shoppro_enabled' => $shopproEnabled,
|
|
] );
|
|
}
|
|
|
|
// ─── Krok 7: Edycja i zapis ─────────────────────────────────────
|
|
|
|
/**
|
|
* Formularz edycji produktu.
|
|
*/
|
|
public function product_edit(): string
|
|
{
|
|
global $user;
|
|
|
|
if ( !$user ) {
|
|
header( 'Location: /admin/' );
|
|
exit;
|
|
}
|
|
|
|
$this->repository->deleteNonassignedImages();
|
|
$this->repository->deleteNonassignedFiles();
|
|
|
|
$db = $GLOBALS['mdb'];
|
|
|
|
$product = $this->repository->findForAdmin( (int) \Shared\Helpers\Helpers::get( 'id' ) ) ?: [];
|
|
$languages = $this->languagesRepository->languagesList();
|
|
$categories = ( new CategoryRepository( $db ) )->subcategories( null );
|
|
$layouts = $this->layoutsForProductEdit( $db );
|
|
$products = $this->repository->allProductsList();
|
|
$sets = ( new \Domain\ProductSet\ProductSetRepository( $db ) )->allSets();
|
|
$producers = ( new \Domain\Producer\ProducerRepository( $db ) )->allProducers();
|
|
$units = ( new \Domain\Dictionaries\DictionariesRepository( $db ) )->allUnits();
|
|
$dlang = $this->languagesRepository->defaultLanguage();
|
|
|
|
$viewModel = $this->buildProductFormViewModel(
|
|
$product, $languages, $categories, $layouts, $products, $sets, $producers, $units, $dlang
|
|
);
|
|
|
|
return \Shared\Tpl\Tpl::view( 'shop-product/product-edit', [
|
|
'form' => $viewModel,
|
|
'product' => $product,
|
|
'user' => $user,
|
|
] );
|
|
}
|
|
|
|
private function layoutsForProductEdit($db): array
|
|
{
|
|
if ( class_exists( '\Domain\Layouts\LayoutsRepository' ) ) {
|
|
$rows = ( new \Domain\Layouts\LayoutsRepository( $db ) )->listAll();
|
|
return is_array( $rows ) ? $rows : [];
|
|
}
|
|
return [];
|
|
}
|
|
|
|
private function buildProductFormViewModel(
|
|
array $product,
|
|
array $languages,
|
|
array $categories,
|
|
array $layouts,
|
|
array $products,
|
|
array $sets,
|
|
array $producers,
|
|
array $units,
|
|
$dlang
|
|
): FormEditViewModel {
|
|
$productId = (int) ( $product['id'] ?? 0 );
|
|
$title = $productId > 0
|
|
? 'Edycja produktu: <u>' . $this->escapeHtml( (string) ( $product['languages'][$dlang]['name'] ?? '' ) ) . '</u>'
|
|
: 'Edycja produktu';
|
|
|
|
// Opcje select: copy_from
|
|
$copyFromOptions = [ '' => '---- wersja językowa ----' ];
|
|
foreach ( $languages as $lg ) {
|
|
if ( !empty( $lg['id'] ) ) {
|
|
$copyFromOptions[(string) $lg['id']] = (string) $lg['name'];
|
|
}
|
|
}
|
|
|
|
// Opcje select: jednostka miary
|
|
$unitOptions = [ '' => '--- wybierz jednostkę miary ---' ];
|
|
foreach ( $units as $unit ) {
|
|
$unitOptions[(string) $unit['id']] = (string) $unit['text'];
|
|
}
|
|
|
|
// Opcje select: layout
|
|
$layoutOptions = [ '' => '---- szablon domyślny ----' ];
|
|
foreach ( $layouts as $layout ) {
|
|
$layoutOptions[(string) $layout['id']] = (string) $layout['name'];
|
|
}
|
|
|
|
// Opcje select: producent
|
|
$producerOptions = [ '' => '--- wybierz producenta ---' ];
|
|
foreach ( $producers as $producer ) {
|
|
$producerOptions[(string) $producer['id']] = (string) $producer['name'];
|
|
}
|
|
|
|
$tabs = [
|
|
new FormTab( 'description', 'Opis', 'fa-file' ),
|
|
new FormTab( 'tabs', 'Zakładki', 'fa-file' ),
|
|
new FormTab( 'price', 'Cena', 'fa-dollar' ),
|
|
new FormTab( 'stock', 'Magazyn', 'fa-home' ),
|
|
new FormTab( 'settings', 'Ustawienia', 'fa-wrench' ),
|
|
new FormTab( 'seo', 'SEO', 'fa-globe' ),
|
|
new FormTab( 'display', 'Wyświetlanie', 'fa-share-alt' ),
|
|
new FormTab( 'gallery', 'Galeria', 'fa-file-image-o' ),
|
|
new FormTab( 'attachments', 'Załączniki', 'fa-file-archive-o' ),
|
|
new FormTab( 'related', 'Produkty powiązane', 'fa-exchange' ),
|
|
new FormTab( 'xml', 'XML', 'fa-file-excel-o' ),
|
|
new FormTab( 'custom_fields', 'Dodatkowe pola', 'fa-file-o' ),
|
|
new FormTab( 'gpsr', 'GPSR', 'fa-file-o' ),
|
|
];
|
|
|
|
$fields = [
|
|
FormField::hidden( 'id', $productId ),
|
|
|
|
// Tab: Opis — sekcja językowa
|
|
FormField::langSection( 'product_description', 'description', [
|
|
FormField::select( 'copy_from', [
|
|
'label' => 'Wyświetlaj treść z wersji',
|
|
'options' => $copyFromOptions,
|
|
] ),
|
|
FormField::text( 'name', [
|
|
'label' => 'Nazwa',
|
|
'attributes' => [ 'id' => 'name' ],
|
|
] ),
|
|
FormField::text( 'warehouse_message_zero', [
|
|
'label' => 'Komunikat gdy stan magazynowy równy 0',
|
|
'attributes' => [ 'id' => 'warehouse_message_zero' ],
|
|
] ),
|
|
FormField::text( 'warehouse_message_nonzero', [
|
|
'label' => 'Komunikat gdy stan magazynowy większy niż 0',
|
|
'attributes' => [ 'id' => 'warehouse_message_nonzero' ],
|
|
] ),
|
|
FormField::editor( 'short_description', [
|
|
'label' => 'Krótki opis',
|
|
'toolbar' => 'MyToolbar',
|
|
'height' => 250,
|
|
'attributes' => [ 'id' => 'short_description' ],
|
|
] ),
|
|
FormField::editor( 'description', [
|
|
'label' => 'Opis',
|
|
'toolbar' => 'MyToolbar',
|
|
'height' => 250,
|
|
'attributes' => [ 'id' => 'description' ],
|
|
] ),
|
|
] ),
|
|
|
|
// Tab: Zakładki — sekcja językowa
|
|
FormField::langSection( 'product_tabs', 'tabs', [
|
|
FormField::text( 'tab_name_1', [
|
|
'label' => 'Nazwa zakładki (1)',
|
|
'attributes' => [ 'id' => 'tab_name_1' ],
|
|
] ),
|
|
FormField::editor( 'tab_description_1', [
|
|
'label' => 'Zawartość zakładki (1)',
|
|
'toolbar' => 'MyToolbar',
|
|
'height' => 250,
|
|
'attributes' => [ 'id' => 'tab_description_1' ],
|
|
] ),
|
|
FormField::text( 'tab_name_2', [
|
|
'label' => 'Nazwa zakładki (2)',
|
|
'attributes' => [ 'id' => 'tab_name_2' ],
|
|
] ),
|
|
FormField::editor( 'tab_description_2', [
|
|
'label' => 'Zawartość zakładki (2)',
|
|
'toolbar' => 'MyToolbar',
|
|
'height' => 250,
|
|
'attributes' => [ 'id' => 'tab_description_2' ],
|
|
] ),
|
|
] ),
|
|
|
|
// Tab: Cena
|
|
FormField::text( 'vat', [
|
|
'label' => 'VAT (%)',
|
|
'tab' => 'price',
|
|
'value' => $productId ? $product['vat'] : 23,
|
|
'attributes' => [ 'id' => 'vat', 'class' => 'int-format' ],
|
|
] ),
|
|
FormField::text( 'price_netto', [
|
|
'label' => 'Cena netto (PLN)',
|
|
'tab' => 'price',
|
|
'value' => $product['price_netto'] ?? '',
|
|
'attributes' => [ 'id' => 'price_netto', 'class' => 'number-format' ],
|
|
] ),
|
|
FormField::text( 'price_brutto', [
|
|
'label' => 'Cena brutto (PLN)',
|
|
'tab' => 'price',
|
|
'value' => $product['price_brutto'] ?? '',
|
|
'attributes' => [ 'id' => 'price_brutto', 'class' => 'number-format' ],
|
|
] ),
|
|
FormField::text( 'price_netto_promo', [
|
|
'label' => 'Promocyjna cena netto (PLN)',
|
|
'tab' => 'price',
|
|
'value' => $product['price_netto_promo'] ?? '',
|
|
'attributes' => [ 'id' => 'price_netto_promo', 'class' => 'number-format' ],
|
|
] ),
|
|
FormField::text( 'price_brutto_promo', [
|
|
'label' => 'Promocyjna cena brutto (PLN)',
|
|
'tab' => 'price',
|
|
'value' => $product['price_brutto_promo'] ?? '',
|
|
'attributes' => [ 'id' => 'price_brutto_promo', 'class' => 'number-format' ],
|
|
] ),
|
|
FormField::select( 'product_unit', [
|
|
'label' => 'Jednostka miary',
|
|
'tab' => 'price',
|
|
'options' => $unitOptions,
|
|
'value' => $product['product_unit_id'] ?? '',
|
|
] ),
|
|
FormField::text( 'weight', [
|
|
'label' => 'Waga/pojemność',
|
|
'tab' => 'price',
|
|
'value' => $product['weight'] ?? '',
|
|
'attributes' => [ 'id' => 'weight', 'class' => 'number-format' ],
|
|
] ),
|
|
|
|
// Tab: Magazyn
|
|
FormField::text( 'quantity', [
|
|
'label' => 'Stan magazynowy',
|
|
'tab' => 'stock',
|
|
'value' => $product['quantity'] ?? '',
|
|
'attributes' => [ 'id' => 'quantity', 'class' => 'int-format' ],
|
|
] ),
|
|
FormField::switch( 'stock_0_buy', [
|
|
'label' => 'Pozwól zamawiać gdy stan 0',
|
|
'tab' => 'stock',
|
|
'value' => (int) ( $product['stock_0_buy'] ?? 0 ) === 1,
|
|
] ),
|
|
FormField::text( 'wp', [
|
|
'label' => 'Współczynnik WP',
|
|
'tab' => 'stock',
|
|
'value' => $product['wp'] ?? '',
|
|
'attributes' => [ 'id' => 'wp', 'class' => 'number-format' ],
|
|
] ),
|
|
FormField::custom( 'sku_field', $this->renderSkuField( $product ), [ 'tab' => 'stock' ] ),
|
|
FormField::text( 'ean', [
|
|
'label' => 'EAN',
|
|
'tab' => 'stock',
|
|
'value' => $product['ean'] ?? '',
|
|
'attributes' => [ 'id' => 'ean' ],
|
|
] ),
|
|
|
|
// Tab: Ustawienia
|
|
FormField::switch( 'status', [
|
|
'label' => 'Widoczny',
|
|
'tab' => 'settings',
|
|
'value' => ( (int) ( $product['status'] ?? 0 ) === 1 ) || $productId === 0,
|
|
] ),
|
|
FormField::switch( 'promoted', [
|
|
'label' => 'Promowany',
|
|
'tab' => 'settings',
|
|
'value' => (int) ( $product['promoted'] ?? 0 ) === 1,
|
|
] ),
|
|
FormField::date( 'new_to_date', [
|
|
'label' => 'Nowość do dnia',
|
|
'tab' => 'settings',
|
|
'value' => $product['new_to_date'] ?? '',
|
|
] ),
|
|
FormField::switch( 'additional_message', [
|
|
'label' => 'Wyświetlaj pole na dodatkową wiadomość',
|
|
'tab' => 'settings',
|
|
'value' => (int) ( $product['additional_message'] ?? 0 ) === 1,
|
|
] ),
|
|
FormField::switch( 'additional_message_required', [
|
|
'label' => 'Dodatkowa wiadomość jest wymagana',
|
|
'tab' => 'settings',
|
|
'value' => (int) ( $product['additional_message_required'] ?? 0 ) === 1,
|
|
] ),
|
|
FormField::text( 'additional_message_text', [
|
|
'label' => 'Dodatkowa wiadomość (treść komunikatu)',
|
|
'tab' => 'settings',
|
|
'value' => $product['additional_message_text'] ?? '',
|
|
'attributes' => [ 'id' => 'additional_message_text' ],
|
|
] ),
|
|
|
|
// Tab: SEO — sekcja językowa
|
|
FormField::langSection( 'product_seo', 'seo', [
|
|
FormField::text( 'seo_link', [
|
|
'label' => 'Link SEO',
|
|
'attributes' => [
|
|
'id' => 'seo_link',
|
|
'icon_content' => 'generuj',
|
|
'icon_js' => 'generate_seo_links( "{lang}", $( "#name_{lang}" ).val(), ' . $productId . ' );',
|
|
],
|
|
] ),
|
|
FormField::text( 'meta_title', [
|
|
'label' => 'Meta title',
|
|
'attributes' => [ 'id' => 'meta_title' ],
|
|
] ),
|
|
FormField::textarea( 'meta_description', [
|
|
'label' => 'Meta description',
|
|
'attributes' => [ 'id' => 'meta_description' ],
|
|
] ),
|
|
FormField::textarea( 'meta_keywords', [
|
|
'label' => 'Meta keywords',
|
|
'attributes' => [ 'id' => 'meta_keywords' ],
|
|
] ),
|
|
FormField::text( 'canonical', [
|
|
'label' => 'Canonical',
|
|
'attributes' => [ 'id' => 'canonical' ],
|
|
] ),
|
|
] ),
|
|
|
|
// Tab: Wyświetlanie
|
|
FormField::select( 'layout_id', [
|
|
'label' => 'Szablon',
|
|
'tab' => 'display',
|
|
'options' => $layoutOptions,
|
|
'value' => $product['layout_id'] ?? '',
|
|
] ),
|
|
FormField::custom( 'categories_tree', $this->renderCategoriesTree( $categories, $product, $dlang ), [ 'tab' => 'display' ] ),
|
|
|
|
// Tab: Galeria
|
|
FormField::custom( 'gallery_box', $this->renderGalleryBox( $product ), [ 'tab' => 'gallery' ] ),
|
|
|
|
// Tab: Załączniki
|
|
FormField::custom( 'files_box', $this->renderFilesBox( $product ), [ 'tab' => 'attachments' ] ),
|
|
|
|
// Tab: Produkty powiązane
|
|
FormField::custom( 'related_products', $this->renderRelatedProducts( $product, $products, $sets ), [ 'tab' => 'related' ] ),
|
|
|
|
// Tab: XML
|
|
FormField::text( 'custom_label_0', [ 'label' => 'Custom label 0', 'tab' => 'xml', 'value' => $product['custom_label_0'] ?? '' ] ),
|
|
FormField::text( 'custom_label_1', [ 'label' => 'Custom label 1', 'tab' => 'xml', 'value' => $product['custom_label_1'] ?? '' ] ),
|
|
FormField::text( 'custom_label_2', [ 'label' => 'Custom label 2', 'tab' => 'xml', 'value' => $product['custom_label_2'] ?? '' ] ),
|
|
FormField::text( 'custom_label_3', [ 'label' => 'Custom label 3', 'tab' => 'xml', 'value' => $product['custom_label_3'] ?? '' ] ),
|
|
FormField::text( 'custom_label_4', [ 'label' => 'Custom label 4', 'tab' => 'xml', 'value' => $product['custom_label_4'] ?? '' ] ),
|
|
|
|
// Tab: Dodatkowe pola
|
|
FormField::custom( 'custom_fields_box', $this->renderCustomFieldsBox( $product ), [ 'tab' => 'custom_fields' ] ),
|
|
|
|
// Tab: GPSR
|
|
FormField::select( 'producer_id', [
|
|
'label' => 'Producent',
|
|
'tab' => 'gpsr',
|
|
'options' => $producerOptions,
|
|
'value' => $product['producer_id'] ?? '',
|
|
] ),
|
|
FormField::langSection( 'product_gpsr', 'gpsr', [
|
|
FormField::editor( 'security_information', [
|
|
'label' => 'Informacje o bezpieczeństwie',
|
|
'toolbar' => 'MyToolbar',
|
|
'height' => 250,
|
|
'attributes' => [ 'id' => 'security_information' ],
|
|
] ),
|
|
] ),
|
|
];
|
|
|
|
$saveUrl = '/admin/shop_product/save/' . ( $productId > 0 ? 'id=' . $productId : '' );
|
|
$backUrl = '/admin/shop_product/view_list/';
|
|
|
|
$actions = [
|
|
FormAction::save( $saveUrl, $backUrl ),
|
|
FormAction::cancel( $backUrl ),
|
|
];
|
|
|
|
if ( $productId > 0 ) {
|
|
$previewUrl = $this->repository->getProductUrl( $productId );
|
|
$actions[] = FormAction::preview( $previewUrl );
|
|
}
|
|
|
|
return new FormEditViewModel(
|
|
'product-edit',
|
|
$title,
|
|
$product,
|
|
$fields,
|
|
$tabs,
|
|
$actions,
|
|
'POST',
|
|
$saveUrl,
|
|
$backUrl,
|
|
true,
|
|
[ 'id' => $productId ],
|
|
$languages
|
|
);
|
|
}
|
|
|
|
private function renderSkuField( array $product ): string
|
|
{
|
|
$productId = (int) ( $product['id'] ?? 0 );
|
|
$sku = $this->escapeHtml( (string) ( $product['sku'] ?? '' ) );
|
|
|
|
return \Shared\Html\Html::input_icon( [
|
|
'label' => 'Kod SKU',
|
|
'name' => 'sku',
|
|
'id' => 'sku',
|
|
'value' => $sku,
|
|
'icon_content' => 'generuj',
|
|
'icon_js' => 'generate_sku_code( ' . $productId . ' );',
|
|
] );
|
|
}
|
|
|
|
private function renderCategoriesTree( array $categories, array $product, $dlang ): string
|
|
{
|
|
$html = '<div class="form-group row">';
|
|
$html .= '<label class="col-md-4 control-label">Wyświetlaj w:</label>';
|
|
$html .= '<div class="col-md-8">';
|
|
$html .= '<div class="menu_sortable">';
|
|
$html .= '<ol class="sortable" id="sortable">';
|
|
|
|
foreach ( $categories as $category ) {
|
|
$catId = (int) $category['id'];
|
|
$catTitle = $this->escapeHtml( (string) ( $category['languages'][$dlang]['title'] ?? '' ) );
|
|
$checked = is_array( $product['categories'] ?? null ) && in_array( $catId, $product['categories'] );
|
|
|
|
$html .= '<li id="list_' . $catId . '" class="category_' . $catId . '" category="' . $catId . '">';
|
|
$html .= '<div class="context_0 content content_menu">';
|
|
$html .= '<button type="button" class="disclose layout-tree-toggle" aria-expanded="false" title="Rozwiń / zwiń"><i class="fa fa-caret-right"></i></button>';
|
|
if ( empty( $category['status'] ) ) {
|
|
$html .= '<i class="fa fa-ban fa-lg text-danger" title="Kategoria nieaktywna"></i>';
|
|
}
|
|
$html .= '<input type="checkbox" class="g-checkbox" name="categories[]" value="' . $catId . '"' . ( $checked ? ' checked="checked"' : '' ) . ' />';
|
|
$html .= '<b>' . $catTitle . '</b>';
|
|
$html .= '</div>';
|
|
$html .= \Shared\Tpl\Tpl::view( 'shop-product/subcategories-list', [
|
|
'categories' => ( new CategoryRepository( $GLOBALS['mdb'] ) )->subcategories( $catId ),
|
|
'product_categories' => $product['categories'] ?? [],
|
|
'dlang' => $dlang,
|
|
'name' => null,
|
|
] );
|
|
$html .= '</li>';
|
|
}
|
|
|
|
$html .= '</ol></div></div><div class="clear"></div></div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function renderGalleryBox( array $product ): string
|
|
{
|
|
$html = '<ul id="images-list">';
|
|
$images = is_array( $product['images'] ?? null ) ? $product['images'] : [];
|
|
foreach ( $images as $img ) {
|
|
$id = (int) ( $img['id'] ?? 0 );
|
|
$src = $this->escapeHtml( (string) ( $img['src'] ?? '' ) );
|
|
$alt = $this->escapeHtml( (string) ( $img['alt'] ?? '' ) );
|
|
|
|
$html .= '<li id="image-' . $id . '">';
|
|
$html .= '<img class="article-image lozad" data-src="/libraries/thumb.php?img=' . $src . '&w=300&h=300">';
|
|
$html .= '<a href="#" class="input-group-addon btn btn-danger article_image_delete" image-id="' . $id . '"><i class="fa fa-trash"></i></a>';
|
|
$html .= '<input type="text" class="form-control image-alt" value="' . $alt . '" image-id="' . $id . '" placeholder="atrybut alt...">';
|
|
$html .= '</li>';
|
|
}
|
|
$html .= '</ul>';
|
|
$html .= '<div id="images-uploader">Twoja przeglądarka nie obsługuje uploadu plików.</div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function renderFilesBox( array $product ): string
|
|
{
|
|
$html = '<ul id="files-list">';
|
|
$files = is_array( $product['files'] ?? null ) ? $product['files'] : [];
|
|
foreach ( $files as $file ) {
|
|
$id = (int) ( $file['id'] ?? 0 );
|
|
$src = (string) ( $file['src'] ?? '' );
|
|
$name = trim( (string) ( $file['name'] ?? '' ) );
|
|
if ( $name === '' ) {
|
|
$parts = explode( '/', $src );
|
|
$name = (string) end( $parts );
|
|
}
|
|
$name = $this->escapeHtml( $name );
|
|
|
|
$html .= '<li id="file-' . $id . '"><div class="input-group">';
|
|
$html .= '<input type="text" class="product_file_edit form-control" file_id="' . $id . '" value="' . $name . '" />';
|
|
$html .= '<a href="#" class="input-group-addon btn btn-info product_file_delete" file_id="' . $id . '"><i class="fa fa-trash"></i></a>';
|
|
$html .= '</div></li>';
|
|
}
|
|
$html .= '</ul>';
|
|
$html .= '<div id="files-uploader">Twoja przeglądarka nie obsługuje uploadu plików.</div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function renderRelatedProducts( array $product, array $products, array $sets ): string
|
|
{
|
|
$productId = (int) ( $product['id'] ?? 0 );
|
|
|
|
$html = '<div class="form-group row">';
|
|
$html .= '<label class="col-lg-4 control-label">Wybierz zdefiniowany komplet produktów:</label>';
|
|
$html .= '<div class="col-lg-8">';
|
|
$html .= '<select id="set" class="form-control" name="set">';
|
|
$html .= '<option value="">wybierz komplet...</option>';
|
|
foreach ( $sets as $set ) {
|
|
$selected = ( ( $product['set_id'] ?? '' ) == $set['id'] ) ? ' selected="selected"' : '';
|
|
$html .= '<option value="' . (int) $set['id'] . '"' . $selected . '>' . $this->escapeHtml( $set['name'] ) . '</option>';
|
|
}
|
|
$html .= '</select></div></div>';
|
|
|
|
$html .= '<div class="form-group row">';
|
|
$html .= '<label class="col-lg-4 control-label">Produkty powiązane:</label>';
|
|
$html .= '<div class="col-lg-8">';
|
|
$html .= '<select id="products_related" multiple name="products_related[]" placeholder="produkty powiązane">';
|
|
$html .= '<option value="">wybierz produkt...</option>';
|
|
foreach ( $products as $key => $val ) {
|
|
if ( (int) $key !== $productId ) {
|
|
$selected = ( is_array( $product['products_related'] ?? null ) && in_array( $key, $product['products_related'] ) ) ? ' selected' : '';
|
|
$html .= '<option value="' . (int) $key . '"' . $selected . '>' . $this->escapeHtml( (string) $val ) . '</option>';
|
|
}
|
|
}
|
|
$html .= '</select></div></div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function renderCustomFieldsBox( array $product ): string
|
|
{
|
|
$html = '<a href="#" class="btn btn-success" id="add_custom_field"><i class="fa fa-plus"></i> dodaj niestandardowe pole</a>';
|
|
$html .= '<div class="additional_fields pt-3">';
|
|
|
|
$customFields = is_array( $product['custom_fields'] ?? null ) ? $product['custom_fields'] : [];
|
|
foreach ( $customFields as $field ) {
|
|
$isRequired = !empty( $field['is_required'] );
|
|
$fieldName = $this->escapeHtml( (string) ( $field['name'] ?? '' ) );
|
|
$fieldType = (string) ( $field['type'] ?? 'text' );
|
|
|
|
$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="' . $fieldName . '"></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"' . ( $fieldType === 'text' ? ' selected' : '' ) . '>Tekst</option>';
|
|
$html .= '<option value="image"' . ( $fieldType === 'image' ? ' selected' : '' ) . '>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 mt-0 mr-3" name="custom_field_required[]"' . ( $isRequired ? ' checked' : '' ) . '> wymagane';
|
|
$html .= '</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></div>';
|
|
}
|
|
|
|
$html .= '</div>';
|
|
|
|
return $html;
|
|
}
|
|
|
|
private function escapeHtml( string $value ): string
|
|
{
|
|
return htmlspecialchars( $value, ENT_QUOTES, 'UTF-8' );
|
|
}
|
|
|
|
private function resolveSavePayload(): array
|
|
{
|
|
$legacyRaw = \Shared\Helpers\Helpers::get( 'values' );
|
|
if ( $legacyRaw !== null && $legacyRaw !== '' ) {
|
|
$legacy = json_decode( (string) $legacyRaw, true );
|
|
if ( is_array( $legacy ) ) {
|
|
return $legacy;
|
|
}
|
|
}
|
|
|
|
$payload = $_POST;
|
|
unset( $payload['_form_id'] );
|
|
|
|
return is_array( $payload ) ? $payload : [];
|
|
}
|
|
|
|
/**
|
|
* AJAX: zapis produktu.
|
|
*/
|
|
public function save(): void
|
|
{
|
|
$response = [ 'success' => false, 'status' => 'error', 'msg' => 'Podczas zapisywania produktu wystąpił błąd. Proszę spróbować ponownie.' ];
|
|
$values = $this->resolveSavePayload();
|
|
|
|
if ( $values ) {
|
|
$id = $this->repository->saveProduct( $values );
|
|
if ( $id ) {
|
|
$response = [ 'success' => true, 'status' => 'ok', 'message' => 'Produkt został zapisany.', 'msg' => 'Produkt został zapisany.', 'id' => $id ];
|
|
}
|
|
}
|
|
|
|
echo json_encode( $response );
|
|
exit;
|
|
}
|
|
|
|
// ─── Krok 8: Operacje na produktach ──────────────────────────────
|
|
|
|
/**
|
|
* Duplikowanie produktu.
|
|
*/
|
|
public function duplicate_product(): void
|
|
{
|
|
if ( $this->repository->duplicate( (int) \Shared\Helpers\Helpers::get( 'product-id' ), (bool) (int) \Shared\Helpers\Helpers::get( 'combination' ) ) ) {
|
|
\Shared\Helpers\Helpers::set_message( 'Produkt został zduplikowany.' );
|
|
} else {
|
|
\Shared\Helpers\Helpers::alert( 'Podczas duplikowania produktu wystąpił błąd. Proszę spróbować ponownie' );
|
|
}
|
|
|
|
header( 'Location: /admin/shop_product/view_list/' );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Archiwizacja produktu.
|
|
*/
|
|
public function product_archive(): void
|
|
{
|
|
if ( $this->repository->archive( (int) \Shared\Helpers\Helpers::get( 'product_id' ) ) ) {
|
|
\Shared\Helpers\Helpers::alert( 'Produkt został przeniesiony do archiwum.' );
|
|
} else {
|
|
\Shared\Helpers\Helpers::alert( 'Podczas przenoszenia produktu do archiwum wystąpił błąd. Proszę spróbować ponownie' );
|
|
}
|
|
|
|
header( 'Location: /admin/shop_product/view_list/' );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Przywrócenie z archiwum.
|
|
*/
|
|
public function product_unarchive(): void
|
|
{
|
|
if ( $this->repository->unarchive( (int) \Shared\Helpers\Helpers::get( 'product_id' ) ) ) {
|
|
\Shared\Helpers\Helpers::alert( 'Produkt został przywrócony z archiwum.' );
|
|
} else {
|
|
\Shared\Helpers\Helpers::alert( 'Podczas przywracania produktu z archiwum wystąpił błąd. Proszę spróbować ponownie' );
|
|
}
|
|
|
|
header( 'Location: /admin/product_archive/list/' );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Usunięcie produktu.
|
|
*/
|
|
public function product_delete(): void
|
|
{
|
|
if ( $this->repository->delete( (int) \Shared\Helpers\Helpers::get( 'id' ) ) ) {
|
|
\Shared\Helpers\Helpers::set_message( 'Produkt został usunięty.' );
|
|
} else {
|
|
\Shared\Helpers\Helpers::alert( 'Podczas usuwania produktu wystąpił błąd. Proszę spróbować ponownie' );
|
|
}
|
|
|
|
header( 'Location: /admin/shop_product/view_list/' );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Zmiana statusu produktu (aktywny/nieaktywny).
|
|
*/
|
|
public function change_product_status(): void
|
|
{
|
|
if ( $this->repository->toggleStatus( (int) \Shared\Helpers\Helpers::get( 'product-id' ) ) ) {
|
|
\Shared\Helpers\Helpers::set_message( 'Status produktu został zmieniony' );
|
|
}
|
|
|
|
header( 'Location: ' . $_SERVER['HTTP_REFERER'] );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: szybka zmiana ceny brutto.
|
|
*/
|
|
public function product_change_price_brutto(): void
|
|
{
|
|
$response = [ 'status' => 'error', 'msg' => 'Podczas zmiany ceny wystąpił błąd. Proszę spróbować ponownie.' ];
|
|
|
|
if ( $this->repository->updatePriceBrutto( (int) \Shared\Helpers\Helpers::get( 'product_id' ), \Shared\Helpers\Helpers::get( 'price' ) ) ) {
|
|
$response = [ 'status' => 'ok' ];
|
|
}
|
|
|
|
echo json_encode( $response );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: szybka zmiana ceny promocyjnej brutto.
|
|
*/
|
|
public function product_change_price_brutto_promo(): void
|
|
{
|
|
$response = [ 'status' => 'error', 'msg' => 'Podczas zmiany ceny wystąpił błąd. Proszę spróbować ponownie.' ];
|
|
|
|
if ( $this->repository->updatePriceBruttoPromo( (int) \Shared\Helpers\Helpers::get( 'product_id' ), \Shared\Helpers\Helpers::get( 'price' ) ) ) {
|
|
$response = [ 'status' => 'ok' ];
|
|
}
|
|
|
|
echo json_encode( $response );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: szybka zmiana custom label (Google XML).
|
|
*/
|
|
public function product_change_custom_label(): void
|
|
{
|
|
$response = [ 'status' => 'error', 'msg' => 'Podczas zmiany google xml label wystąpił błąd. Proszę spróbować ponownie.' ];
|
|
|
|
if ( $this->repository->updateCustomLabel( (int) \Shared\Helpers\Helpers::get( 'product_id' ), \Shared\Helpers\Helpers::get( 'custom_label' ), \Shared\Helpers\Helpers::get( 'value' ) ) ) {
|
|
$response = [ 'status' => 'ok' ];
|
|
}
|
|
|
|
echo json_encode( $response );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: sugestie custom label.
|
|
*/
|
|
public function product_custom_label_suggestions(): void
|
|
{
|
|
$response = [ 'status' => 'error', 'msg' => 'Podczas pobierania sugestii dla custom label wystąpił błąd. Proszę spróbować ponownie.' ];
|
|
|
|
$suggestions = $this->repository->customLabelSuggestions( \Shared\Helpers\Helpers::get( 'custom_label' ), \Shared\Helpers\Helpers::get( 'label_type' ) );
|
|
if ( $suggestions ) {
|
|
$response = [ 'status' => 'ok', 'suggestions' => $suggestions ];
|
|
}
|
|
|
|
echo json_encode( $response );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: zapis custom label produktu.
|
|
*/
|
|
public function product_custom_label_save(): void
|
|
{
|
|
$response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania custom label wystąpił błąd. Proszę spróbować ponownie.' ];
|
|
|
|
if ( $this->repository->saveCustomLabel( (int) \Shared\Helpers\Helpers::get( 'product_id' ), \Shared\Helpers\Helpers::get( 'custom_label' ), \Shared\Helpers\Helpers::get( 'label_type' ) ) ) {
|
|
$response = [ 'status' => 'ok' ];
|
|
}
|
|
|
|
echo json_encode( $response );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: pobierz bezpośredni URL produktu na frontendzie.
|
|
*/
|
|
public function ajax_product_url(): void
|
|
{
|
|
echo json_encode( [ 'url' => $this->repository->getProductUrl( (int) \Shared\Helpers\Helpers::get( 'product_id' ) ) ] );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: generowanie kodu SKU.
|
|
*/
|
|
public function generate_sku_code(): void
|
|
{
|
|
$response = [ 'status' => 'error', 'msg' => 'Podczas generowania kodu sku wystąpił błąd. Proszę spróbować ponownie.' ];
|
|
|
|
$sku = $this->repository->generateSkuCode();
|
|
if ( $sku ) {
|
|
$response = [ 'status' => 'ok', 'sku' => $sku ];
|
|
}
|
|
|
|
echo json_encode( $response );
|
|
exit;
|
|
}
|
|
|
|
// ─── Krok 9: Kombinacje ─────────────────────────────────────────
|
|
|
|
/**
|
|
* Widok kombinacji produktu.
|
|
*/
|
|
public function product_combination(): string
|
|
{
|
|
$db = $GLOBALS['mdb'];
|
|
|
|
return \Shared\Tpl\Tpl::view( 'shop-product/product-combination', [
|
|
'product' => $this->repository->findForAdmin( (int) \Shared\Helpers\Helpers::get( 'product_id' ) ),
|
|
'attributes' => ( new \Domain\Attribute\AttributeRepository( $db ) )->getAttributesListForCombinations(),
|
|
'default_language' => $this->languagesRepository->defaultLanguage(),
|
|
'product_permutations' => $this->repository->getCombinationsForTable( (int) \Shared\Helpers\Helpers::get( 'product_id' ) ),
|
|
] );
|
|
}
|
|
|
|
/**
|
|
* Generowanie kombinacji.
|
|
*/
|
|
public function generate_combination(): void
|
|
{
|
|
$attributes = [];
|
|
foreach ( $_POST as $key => $val ) {
|
|
if ( strpos( $key, 'attribute_' ) !== false ) {
|
|
$attribute = explode( 'attribute_', $key );
|
|
$attributes[ $attribute[1] ] = $val;
|
|
}
|
|
}
|
|
|
|
if ( $this->repository->generateCombinations( (int) \Shared\Helpers\Helpers::get( 'product_id' ), $attributes ) ) {
|
|
\Shared\Helpers\Helpers::alert( 'Kombinacje produktu zostały wygenerowane.' );
|
|
}
|
|
|
|
header( 'Location: /admin/shop_product/product_combination/product_id=' . (int) \Shared\Helpers\Helpers::get( 'product_id' ) );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* Usunięcie kombinacji.
|
|
*/
|
|
public function delete_combination(): void
|
|
{
|
|
if ( $this->repository->deleteCombination( (int) \Shared\Helpers\Helpers::get( 'combination_id' ) ) ) {
|
|
\Shared\Helpers\Helpers::alert( 'Kombinacja produktu została usunięta' );
|
|
} else {
|
|
\Shared\Helpers\Helpers::alert( 'Podczas usuwania kombinacji produktu wystąpił błąd. Proszę spróbować ponownie' );
|
|
}
|
|
|
|
header( 'Location: /admin/shop_product/product_combination/product_id=' . \Shared\Helpers\Helpers::get( 'product_id' ) );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: zapis stock_0_buy kombinacji.
|
|
*/
|
|
public function product_combination_stock_0_buy_save(): void
|
|
{
|
|
$this->repository->saveCombinationStock0Buy( (int) \Shared\Helpers\Helpers::get( 'product_id' ), \Shared\Helpers\Helpers::get( 'stock_0_buy' ) );
|
|
echo json_encode( [ 'status' => 'ok' ] );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: zapis SKU kombinacji.
|
|
*/
|
|
public function product_combination_sku_save(): void
|
|
{
|
|
$this->repository->saveCombinationSku( (int) \Shared\Helpers\Helpers::get( 'product_id' ), \Shared\Helpers\Helpers::get( 'sku' ) );
|
|
echo json_encode( [ 'status' => 'ok' ] );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: zapis ilości kombinacji.
|
|
*/
|
|
public function product_combination_quantity_save(): void
|
|
{
|
|
$this->repository->saveCombinationQuantity( (int) \Shared\Helpers\Helpers::get( 'product_id' ), \Shared\Helpers\Helpers::get( 'quantity' ) );
|
|
echo json_encode( [ 'status' => 'ok' ] );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: zapis ceny kombinacji.
|
|
*/
|
|
public function product_combination_price_save(): void
|
|
{
|
|
$this->repository->saveCombinationPrice( (int) \Shared\Helpers\Helpers::get( 'product_id' ), \Shared\Helpers\Helpers::get( 'price' ) );
|
|
echo json_encode( [ 'status' => 'ok' ] );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: usunięcie kombinacji bez przeładowania strony.
|
|
*/
|
|
public function delete_combination_ajax(): void
|
|
{
|
|
$response = [ 'status' => 'error', 'msg' => 'Podczas usuwania kombinacji wystąpił błąd.' ];
|
|
|
|
if ( $this->repository->deleteCombination( (int) \Shared\Helpers\Helpers::get( 'combination_id' ) ) ) {
|
|
$response = [ 'status' => 'ok' ];
|
|
}
|
|
|
|
echo json_encode( $response );
|
|
exit;
|
|
}
|
|
|
|
// ─── Krok 10: Zdjęcia i pliki ───────────────────────────────────
|
|
|
|
/**
|
|
* AJAX: usunięcie zdjęcia produktu.
|
|
*/
|
|
public function image_delete(): void
|
|
{
|
|
$response = [ 'status' => 'error', 'msg' => 'Podczas usuwania zdjecia wystąpił błąd. Proszę spróbować ponownie.' ];
|
|
|
|
if ( $this->repository->deleteImage( (int) \Shared\Helpers\Helpers::get( 'image_id' ) ) ) {
|
|
$response = [ 'status' => 'ok' ];
|
|
}
|
|
|
|
echo json_encode( $response );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: zapis kolejności zdjęć.
|
|
*/
|
|
public function images_order_save(): void
|
|
{
|
|
if ( $this->repository->saveImagesOrder( (int) \Shared\Helpers\Helpers::get( 'product_id' ), \Shared\Helpers\Helpers::get( 'order' ) ) ) {
|
|
echo json_encode( [ 'status' => 'ok', 'msg' => 'Produkt został zapisany.' ] );
|
|
}
|
|
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: zmiana alt zdjęcia.
|
|
*/
|
|
public function image_alt_change(): void
|
|
{
|
|
$response = [ 'status' => 'error', 'msg' => 'Podczas zmiany atrybutu alt zdjęcia wystąpił błąd. Proszę spróbować ponownie.' ];
|
|
|
|
if ( $this->repository->updateImageAlt( (int) \Shared\Helpers\Helpers::get( 'image_id' ), \Shared\Helpers\Helpers::get( 'image_alt' ) ) ) {
|
|
$response = [ 'status' => 'ok' ];
|
|
}
|
|
|
|
echo json_encode( $response );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: usunięcie pliku produktu (migracja z ajax/shop.php).
|
|
*/
|
|
public function product_file_delete(): void
|
|
{
|
|
$response = [ 'status' => 'error', 'msg' => 'Podczas usuwania pliku wystąpił błąd.' ];
|
|
|
|
if ( $this->repository->deleteFile( (int) \Shared\Helpers\Helpers::get( 'file_id' ) ) ) {
|
|
$response = [ 'status' => 'ok' ];
|
|
}
|
|
|
|
echo json_encode( $response );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: zmiana nazwy pliku (migracja z ajax/shop.php).
|
|
*/
|
|
public function product_file_name_change(): void
|
|
{
|
|
$response = [ 'status' => 'error', 'msg' => 'Podczas zmiany nazwy pliku wystąpił błąd.' ];
|
|
|
|
if ( $this->repository->updateFileName( (int) \Shared\Helpers\Helpers::get( 'file_id' ), \Shared\Helpers\Helpers::get( 'file_name' ) ) ) {
|
|
$response = [ 'status' => 'ok' ];
|
|
}
|
|
|
|
echo json_encode( $response );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: usunięcie zdjęcia produktu (migracja z ajax/shop.php).
|
|
*/
|
|
public function product_image_delete(): void
|
|
{
|
|
$response = [ 'status' => 'error', 'msg' => 'Podczas usuwania zdjęcia wystąpił błąd.' ];
|
|
|
|
if ( $this->repository->deleteImage( (int) \Shared\Helpers\Helpers::get( 'image_id' ) ) ) {
|
|
$response = [ 'status' => 'ok' ];
|
|
}
|
|
|
|
echo json_encode( $response );
|
|
exit;
|
|
}
|
|
|
|
// ─── Masowa edycja (istniejące) ──────────────────────────────────
|
|
|
|
/**
|
|
* Widok masowej edycji produktów.
|
|
*/
|
|
public function mass_edit(): string
|
|
{
|
|
$categoryRepository = new CategoryRepository( $GLOBALS['mdb'] );
|
|
|
|
return \Shared\Tpl\Tpl::view( 'shop-product/mass-edit', [
|
|
'products' => $this->repository->allProductsForMassEdit(),
|
|
'categories' => $categoryRepository->subcategories( null ),
|
|
'dlang' => $this->languagesRepository->defaultLanguage(),
|
|
] );
|
|
}
|
|
|
|
/**
|
|
* AJAX: zastosowanie rabatu procentowego na zaznaczonych produktach.
|
|
*/
|
|
public function mass_edit_save(): void
|
|
{
|
|
$discountPercent = \Shared\Helpers\Helpers::get( 'discount_percent' );
|
|
$products = \Shared\Helpers\Helpers::get( 'products' );
|
|
|
|
if ( $discountPercent != '' && $products && is_array( $products ) && count( $products ) > 0 ) {
|
|
$productId = (int) $products[0];
|
|
$result = $this->repository->applyDiscountPercent( $productId, (float) $discountPercent );
|
|
|
|
if ( $result !== null ) {
|
|
echo json_encode( [
|
|
'status' => 'ok',
|
|
'price_brutto_promo' => $result['price_brutto_promo'],
|
|
'price_brutto' => $result['price_brutto'],
|
|
] );
|
|
exit;
|
|
}
|
|
}
|
|
|
|
echo json_encode( [ 'status' => 'error' ] );
|
|
exit;
|
|
}
|
|
|
|
/**
|
|
* AJAX: pobranie ID produktów z danej kategorii.
|
|
*/
|
|
public function get_products_by_category(): void
|
|
{
|
|
$categoryId = (int) \Shared\Helpers\Helpers::get( 'category_id' );
|
|
$products = $this->repository->getProductsByCategory( $categoryId );
|
|
|
|
echo json_encode( [ 'status' => 'ok', 'products' => $products ] );
|
|
exit;
|
|
}
|
|
}
|