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 = '

'
. ''
. '' . $categories . ''
. 'SKU: ' . $sku . ', EAN: ' . $ean . '';
$priceHtml = '';
$promoHtml = '';
$promotedHtml = $product['promoted'] ? 'tak' : 'nie';
$statusHtml = $product['status'] ? 'tak' : 'nie';
$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' => '' . $quantity . '',
'combinations' => 'kombinacje (' . $combinationsCount . ')',
'_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'] = '' . $apiloShort . '...
'
. 'usuń powiązanie';
} else {
$row['apilo'] = 'nie przypisano ';
}
}
$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: ' . $this->escapeHtml( (string) ( $product['languages'][$dlang]['name'] ?? '' ) ) . ''
: '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 = '';
return $html;
}
private function renderGalleryBox( array $product ): string
{
$html = '';
$html .= 'Twoja przeglądarka nie obsługuje uploadu plików.
';
return $html;
}
private function renderFilesBox( array $product ): string
{
$html = '';
$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 .= '';
}
$html .= '
';
$html .= 'Twoja przeglądarka nie obsługuje uploadu plików.
';
return $html;
}
private function renderRelatedProducts( array $product, array $products, array $sets ): string
{
$productId = (int) ( $product['id'] ?? 0 );
$html = '';
$html .= '';
return $html;
}
private function renderCustomFieldsBox( array $product ): string
{
$html = ' dodaj niestandardowe pole';
$html .= '';
$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 .= '
';
}
$html .= '
';
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;
}
}