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 = '
' . $imgAlt . '
' . '
' . '' . $name . ' ' . 'duplikuj' . '
' . '' . $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 = '
'; $html .= ''; $html .= '
'; $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 = ''; $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 .= ''; $html .= '
'; $html .= '
'; $html .= '
'; $html .= ''; $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 .= '
'; $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
'; $html .= '
'; $html .= 'usuń'; $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; } }