-
- - Treść
- - Ustawienia
- - SEO
-
-
-
-
-
- if ( is_array( $this -> languages ) ): foreach ( $this -> languages as $lg ):?>
- if ( $lg['status'] ):?>
- - if ( $lg['id'] == \front\factory\Languages::default_language() ) echo ' ';?>= $lg['name'];?>
- endif;?>
- endforeach; endif;?>
-
-
- if ( is_array( $this -> languages ) ): foreach ( $this -> languages as $lg ):?>
- if ( $lg['status'] ):?>
-
- = \Html::input(
- array(
- 'label' => 'Nazwa kategorii',
- 'name' => 'title[' . $lg['id'] . ']',
- 'id' => 'title_' . $lg['id'],
- 'value' => $this -> category[ 'languages' ][ $lg['id'] ]['title'],
- 'inline' => true
- )
- );?>
- = \Html::textarea(
- array(
- 'label' => 'Opis kategorii',
- 'name' => 'text[' . $lg['id'] . ']',
- 'id' => 'text_' . $lg['id'],
- 'value' => $this -> category['languages'][ $lg['id'] ]['text'],
- 'inline' => true
- )
- );?>
- = \Html::textarea(
- array(
- 'label' => 'Opis kategorii (rozwinięcie)',
- 'name' => 'text_hidden[' . $lg['id'] . ']',
- 'id' => 'text_hidden_' . $lg['id'],
- 'value' => $this -> category['languages'][ $lg['id'] ]['text_hidden'],
- 'inline' => true
- )
- );?>
- = \Html::textarea( [
- 'label' => 'Dodatkowy tekst (nad produktami)',
- 'name' => 'additional_text[' . $lg['id'] . ']',
- 'id' => 'additional_text_' . $lg['id'],
- 'value' => $this -> category['languages'][ $lg['id'] ]['additional_text'],
- 'inline' => true
- ] );?>
-
-
- endif;?>
- endforeach; endif;?>
-
-
-
-
-
- = \Html::input_switch(
- array(
- 'label' => 'Aktywna',
- 'name' => 'status',
- 'checked' => $this -> category['status'] == 1 or !$this -> category['id'] ? true : false
- )
- );?>
- = \Html::select(
- [
- 'label' => 'Sortowanie produktĂłw',
- 'name' => 'sort_type',
- 'id' => 'sort_type',
- 'values' => is_array( $this -> sort_types ) ? $this -> sort_types : [],
- 'value' => $this -> category['sort_type']
- ]
- );?>
- = \Html::input_switch(
- array(
- 'label' => 'Wyświetlić podkategorie',
- 'name' => 'view_subcategories',
- 'checked' => $this -> category['view_subcategories'] == 1 ? true : false
- )
- );?>
-
-
-
-
- if ( is_array( $this -> languages ) ): foreach ( $this -> languages as $lg ):?>
- if ( $lg['status'] ):?>
- - if ( $lg['id'] == \front\factory\Languages::default_language() ) echo ' ';?>= $lg['name'];?>
- endif;?>
- endforeach; endif;?>
-
-
- if ( is_array( $this -> languages ) ): foreach ( $this -> languages as $lg ):?>
- if ( $lg['status'] ):?>
-
- = \Html::input_icon(
- array(
- 'label' => 'Link SEO',
- 'name' => 'seo_link[' . $lg['id'] . ']',
- 'id' => 'seo_link_' . $lg['id'],
- 'value' => $this -> category['languages' ][ $lg['id'] ]['seo_link'],
- 'icon_content' => 'generuj',
- 'icon_js' => 'generate_seo_links( "' . $lg['id'] . '", $( "#title_' . $lg['id'] . '" ).val(), ' . (int)$this -> category['id'] . ' );'
- )
- );?>
- = \Html::input(
- array(
- 'label' => 'Tytuł kategorii (h1)',
- 'name' => 'category_title[' . $lg['id'] . ']',
- 'id' => 'category_title_' . $lg['id'],
- 'value' => $this -> category['languages' ][ $lg['id'] ]['category_title']
- )
- );?>
- = \Html::input(
- array(
- 'label' => 'Meta title',
- 'name' => 'meta_title[' . $lg['id'] . ']',
- 'id' => 'meta_title_' . $lg['id'],
- 'value' => $this -> category['languages'][ $lg['id'] ]['meta_title']
- )
- );?>
- = \Html::textarea(
- array(
- 'label' => 'Meta description',
- 'name' => 'meta_description[' . $lg['id'] . ']',
- 'id' => 'meta_description_' . $lg['id'],
- 'value' => $this -> category['languages'][ $lg['id'] ]['meta_description']
- )
- );?>
- = \Html::textarea(
- array(
- 'label' => 'Meta keywords',
- 'name' => 'meta_keywords[' . $lg['id'] . ']',
- 'id' => 'meta_keywords_' . $lg['id'],
- 'value' => $this -> category['languages'][ $lg['id'] ]['meta_keywords']
- )
- );?>
- = \Html::select(
- array(
- 'label' => 'Blokuj indeksacjÄ™',
- 'name' => 'noindex[' . $lg['id'] . ']',
- 'id' => 'noindex_' . $lg['id'],
- 'values' => array(
- 0 => 'nie', 1 => 'tak'
- ),
- 'value' => $this -> category['languages'][ $lg['id'] ]['noindex'] == 1 ? 1 : 0
- )
- );?>
-
- endif;?>
- endforeach; endif;?>
-
-
-
-
-
-
-
-$out = ob_get_clean();
-
-$grid = new \gridEdit;
-$grid -> id = 'category-edit';
-$grid -> gdb_opt = $gdb;
-$grid -> include_plugins = true;
-$grid -> title = 'Edycja kategorii';
-$grid -> fields = [
- [
- 'db' => 'id',
- 'type' => 'hidden',
- 'value' => $this -> category['id']
- ],
- [
- 'db' => 'parent_id',
- 'type' => 'hidden',
- 'value' => $this -> category['id'] ? $this -> category['parent_id'] : $this -> pid
- ]
- ];
-$grid -> actions = [
- 'save' => [ 'url' => '/admin/shop_category/save/', 'back_url' => '/admin/shop_category/view_list/' ],
- 'cancel' => [ 'url' => '/admin/shop_category/view_list/' ]
- ];
-$grid -> external_code = $out;
-$grid -> persist_edit = true;
-$grid -> id_param = 'id';
-
-echo $grid -> draw();
-?>
-
-= \Tpl::view( 'shop-category/category-edit-custom-script' ); ?>
-
diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-products-custom-script.php b/temp/update_build/tmp_0.275/admin/templates/shop-category/category-products-custom-script.php
deleted file mode 100644
index 3a43fa0..0000000
--- a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-products-custom-script.php
+++ /dev/null
@@ -1,62 +0,0 @@
-
-
-
diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-products.php b/temp/update_build/tmp_0.275/admin/templates/shop-category/category-products.php
deleted file mode 100644
index 7885fd8..0000000
--- a/temp/update_build/tmp_0.275/admin/templates/shop-category/category-products.php
+++ /dev/null
@@ -1,37 +0,0 @@
-
-global $gdb;
-
-ob_start();
-?>
-
-
- if ( is_array( $this -> products ) ) foreach ( $this -> products as $product )
- {
- ?>
- -
-
= $product['name'];?>
-
-
- }
- ?>
-
-
-$out = ob_get_clean();
-
-$grid = new \gridEdit;
-$grid -> gdb_opt = $gdb;
-$grid -> include_plugins = true;
-$grid -> default_buttons = false;
-$grid -> external_code = $out;
-$grid -> title = 'Lista produktów';
-$grid -> buttons = [
- [
- 'label' => 'Wstecz',
- 'url' => '/admin/shop_category/view_list/',
- 'icon' => 'fa-reply',
- 'class' => 'btn-dark'
- ]
- ];
-echo $grid -> draw();
-?>
-= \Tpl::view( 'shop-category/category-products-custom-script', [ 'category_id' => $this -> category_id ] ); ?>
\ No newline at end of file
diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-category/subcategories-list.php b/temp/update_build/tmp_0.275/admin/templates/shop-category/subcategories-list.php
deleted file mode 100644
index 739fe25..0000000
--- a/temp/update_build/tmp_0.275/admin/templates/shop-category/subcategories-list.php
+++ /dev/null
@@ -1,36 +0,0 @@
- if ( is_array( $this -> categories ) ):?>
-
- foreach ( $this -> categories as $category ):?>
- -
-
- = \Tpl::view( 'shop-category/subcategories-list', [
- 'categories' => ( new \Domain\Category\CategoryRepository( $GLOBALS['mdb'] ) )->subcategories( $category['id'] ),
- 'level' => $this -> level + 1,
- 'dlang' => $this -> dlang
- ] );?>
-
- endforeach;?>
-
- endif;?>
\ No newline at end of file
diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-category/subcategory-browse-list.php b/temp/update_build/tmp_0.275/admin/templates/shop-category/subcategory-browse-list.php
deleted file mode 100644
index 1fafb54..0000000
--- a/temp/update_build/tmp_0.275/admin/templates/shop-category/subcategory-browse-list.php
+++ /dev/null
@@ -1,25 +0,0 @@
- if ( is_array( $this -> categories ) ):?>
-
- foreach ( $this -> categories as $category ):?>
- -
-
- = \Tpl::view( 'shop-category/subcategory-browse-list', [
- 'categories' => ( new \Domain\Category\CategoryRepository( $GLOBALS['mdb'] ) )->subcategories( $category['id'] ),
- 'level' => $this -> level + 1,
- 'dlang' => $this -> dlang
- ] );?>
-
- endforeach;?>
-
- endif;?>
\ No newline at end of file
diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-product/mass-edit.php b/temp/update_build/tmp_0.275/admin/templates/shop-product/mass-edit.php
deleted file mode 100644
index 0823132..0000000
--- a/temp/update_build/tmp_0.275/admin/templates/shop-product/mass-edit.php
+++ /dev/null
@@ -1,114 +0,0 @@
-
-
-
-
-
-
-
- Masowa edycja produktów
-
-
-
-
-
-
-
-
-
- products ) ): foreach ( $this->products as $key => $product ): ?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-= \Tpl::view( 'shop-product/mass-edit-custom-script' ); ?>
diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-product/product-edit.php b/temp/update_build/tmp_0.275/admin/templates/shop-product/product-edit.php
deleted file mode 100644
index 8d1519c..0000000
--- a/temp/update_build/tmp_0.275/admin/templates/shop-product/product-edit.php
+++ /dev/null
@@ -1,1348 +0,0 @@
-
-
-
-
-global $db;
-
-$upload_token = bin2hex( random_bytes(24) );
-$_SESSION['upload_tokens'][$upload_token] = [
- 'user_id' => $this -> user['id'],
- 'expires' => time() + 60*20
-];
-
-$_SESSION['rfm_akey'] = bin2hex(random_bytes(16));
-$_SESSION['rfm_akey_expires'] = time() + 20*60;
-$_SESSION['can_use_rfm'] = true;
-$rfmAkeyJS = $_SESSION['rfm_akey'];
-
-ob_start();
-?>
-
-
-
- - Opis
- - Zakładki
- - Cena
- - Magazyn
- - Ustawienia
- - SEO
- - Wyświetlanie
- - Galeria
- - Załączniki
- - Produkty powiązane
- - XML
- - Dodatkowe pola
- - GPSR
-
-
-
-
-
- if (is_array($this->languages)) : foreach ($this->languages as $lg) : ?>
- if ($lg['status']) : ?>
- - if ($lg['id'] == \front\factory\Languages::default_language()) echo ' '; ?>= $lg['name']; ?>
- endif; ?>
-
- endforeach;
- endif;
- ?>
-
-
- if (is_array($this->languages)) : foreach ($this->languages as $lg) : ?>
-
- $languages = array();
-
- $languages[''] = '---- wersja językowa ----';
- if (is_array($this->languages))
- foreach ($this->languages as $lg_tmp)
- {
- if ($lg_tmp['id'] != $lg['id'])
- $languages[$lg_tmp['id']] = $lg_tmp['name'];
- }
- ?>
- if ($lg['status']) : ?>
-
- =
- \Html::select(
- array(
- 'label' => 'Wyświetlaj treść z wersji',
- 'name' => 'copy_from[' . $lg['id'] . ']',
- 'values' => $languages,
- 'value' => $this->product['languages'][$lg['id']]['copy_from'],
- )
- );
- ?>
- =
- \Html::input(
- array(
- 'label' => 'Nazwa',
- 'name' => 'name[' . $lg['id'] . ']',
- 'id' => 'name_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['name'],
- 'inline' => true
- )
- );
- ?>
- =
- \Html::input(
- array(
- 'label' => 'Komunikat gdy stan magazynowy równy 0',
- 'name' => 'warehouse_message_zero[' . $lg['id'] . ']',
- 'id' => 'warehouse_message_zero_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['warehouse_message_zero'],
- 'inline' => true
- )
- );
- ?>
- =
- \Html::input(
- array(
- 'label' => 'Komunikat gdy stan magazynowy większy niż 0',
- 'name' => 'warehouse_message_nonzero[' . $lg['id'] . ']',
- 'id' => 'warehouse_message_nonzero_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['warehouse_message_nonzero'],
- 'inline' => true
- )
- );
- ?>
- =
- \Html::textarea(
- array(
- 'label' => 'Krótki opis',
- 'name' => 'short_description[' . $lg['id'] . ']',
- 'id' => 'short_description_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['short_description'],
- 'inline' => true
- )
- );
- ?>
- =
- \Html::textarea(
- array(
- 'label' => 'Opis',
- 'name' => 'description[' . $lg['id'] . ']',
- 'id' => 'description_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['description'],
- 'inline' => true
- )
- );
- ?>
-
-
- endif; ?>
-
- endforeach;
- endif;
- ?>
-
-
-
-
-
-
-
- if (is_array($this->languages)) : foreach ($this->languages as $lg) : ?>
- if ($lg['status']) : ?>
- - if ($lg['id'] == \front\factory\Languages::default_language()) echo ' '; ?>= $lg['name']; ?>
- endif; ?>
-
- endforeach;
- endif;
- ?>
-
-
- if (is_array($this->languages)) : foreach ($this->languages as $lg) : ?>
-
- $languages = array();
-
- $languages[''] = '---- wersja językowa ----';
- if (is_array($this->languages))
- foreach ($this->languages as $lg_tmp)
- {
- if ($lg_tmp['id'] != $lg['id'])
- $languages[$lg_tmp['id']] = $lg_tmp['name'];
- }
- ?>
- if ($lg['status']) : ?>
-
- =
- \Html::input(
- array(
- 'label' => 'Nazwa zakładki (1)',
- 'name' => 'tab_name_1[' . $lg['id'] . ']',
- 'id' => 'tab_name_1_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['tab_name_1'],
- 'inline' => true
- )
- );
- ?>
- =
- \Html::textarea(
- array(
- 'label' => 'Zawartość zakładki (1)',
- 'name' => 'tab_description_1[' . $lg['id'] . ']',
- 'id' => 'tab_description_1_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['tab_description_1'],
- 'inline' => true
- )
- );
- ?>
- =
- \Html::input(
- array(
- 'label' => 'Nazwa zakładki (2)',
- 'name' => 'tab_name_2[' . $lg['id'] . ']',
- 'id' => 'tab_name_2_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['tab_name_2'],
- 'inline' => true
- )
- );
- ?>
- =
- \Html::textarea(
- array(
- 'label' => 'Zawartość zakładki (2)',
- 'name' => 'tab_description_2[' . $lg['id'] . ']',
- 'id' => 'tab_description_2_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['tab_description_2'],
- 'inline' => true
- )
- );
- ?>
-
-
- endif; ?>
-
- endforeach;
- endif;
- ?>
-
-
-
-
-
- =
- \Html::input(
- array(
- 'label' => 'VAT (%)',
- 'name' => 'vat',
- 'id' => 'vat',
- 'class' => 'int-format',
- 'value' => $this->product['id'] ? $this->product['vat'] : 23,
- 'onchange' => 'calculate_price_brutto(); return false;'
- )
- );
- ?>
- =
- \Html::input(
- array(
- 'label' => 'Cena netto (PLN)',
- 'name' => 'price_netto',
- 'id' => 'price_netto',
- 'class' => 'number-format',
- 'value' => $this->product['price_netto'],
- 'onchange' => 'calculate_price_brutto(); return false;'
- )
- );
- ?>
- =
- \Html::input(
- array(
- 'label' => 'Cena brutto (PLN)',
- 'name' => 'price_brutto',
- 'id' => 'price_brutto',
- 'class' => 'number-format',
- 'value' => $this->product['price_brutto'],
- 'onchange' => 'calculate_price_netto(); return false;'
- )
- );
- ?>
- =
- \Html::input(
- array(
- 'label' => 'Promocyjna cena netto (PLN)',
- 'name' => 'price_netto_promo',
- 'id' => 'price_netto_promo',
- 'class' => 'number-format',
- 'value' => $this->product['price_netto_promo'],
- 'onchange' => 'calculate_price_brutto_promo(); return false;'
- )
- );
- ?>
- =
- \Html::input(
- array(
- 'label' => 'Promocyjna cena brutto (PLN)',
- 'name' => 'price_brutto_promo',
- 'id' => 'price_brutto_promo',
- 'class' => 'number-format',
- 'value' => $this->product['price_brutto_promo'],
- 'onchange' => 'calculate_price_netto_promo(); return false;'
- )
- );
- ?>
-
-
- $units[] = '--- wybierze jednostkę miary ---';
- foreach ($this->units as $unit)
- $units[$unit['id']] = $unit['text'];
- ?>
- = \Html::select([
- 'label' => 'Jednostka miary',
- 'name' => 'product_unit',
- 'id' => 'product_unit',
- 'values' => $units,
- 'value' => $this->product['product_unit_id']
- ]); ?>
- = \Html::input([
- 'label' => 'Waga/pojemność',
- 'name' => 'weight',
- 'id' => 'weight',
- 'class' => 'number-format',
- 'value' => $this->product['weight']
- ]); ?>
-
-
- =
- \Html::input([
- 'label' => 'Stan magazynowy',
- 'name' => 'quantity',
- 'id' => 'quantity',
- 'class' => 'int-format',
- 'value' => $this->product['quantity']
- ]);
- ?>
- =
- \Html::input_switch([
- 'label' => 'Pozwól zamawiać gdy stan 0',
- 'name' => 'stock_0_buy',
- 'checked' => $this->product['stock_0_buy'] == 1 ? true : false
- ]);
- ?>
- =
- \Html::input([
- 'label' => 'Współczynnik WP',
- 'name' => 'wp',
- 'id' => 'wp',
- 'class' => 'number-format',
- 'value' => $this->product['wp']
- ]);
- ?>
- = \Html::input_icon([
- 'label' => 'Kod SKU',
- 'name' => 'sku',
- 'id' => 'sku',
- 'value' => $this->product['sku'],
- 'icon_content' => 'generuj',
- 'icon_js' => 'generate_sku_code( ' . (int)$this->product['id'] . ' );'
- ]); ?>
- = \Html::input([
- 'label' => 'EAN',
- 'name' => 'ean',
- 'id' => 'ean',
- 'value' => $this->product['ean']
- ]); ?>
-
-
- =
- \Html::input_switch([
- 'label' => 'Widoczny',
- 'name' => 'status',
- 'checked' => $this->product['status'] == 1 or !$this->product['id'] ? true : false
- ]);
- ?>
- =
- \Html::input_switch([
- 'label' => 'Promowany',
- 'name' => 'promoted',
- 'checked' => $this->product['promoted'] == 1 ? true : false
- ]);
- ?>
- =
- \Html::input([
- 'label' => 'Nowość do dnia',
- 'name' => 'new_to_date',
- 'id' => 'new_to_date',
- 'class' => 'date',
- 'value' => $this->product['new_to_date']
- ]);
- ?>
- = \Html::input_switch([
- 'label' => 'Wyświetlaj pole na dodatkową wiadomość',
- 'name' => 'additional_message',
- 'checked' => $this->product['additional_message'] == 1 ? true : false
- ]);
- ?>
- = \Html::input_switch([
- 'label' => 'Dodatkowa wiadomość jest wymagana',
- 'name' => 'additional_message_required',
- 'checked' => $this->product['additional_message_required'] == 1 ? true : false
- ]);
- ?>
- = \Html::input([
- 'label' => 'Dodatkowa wiadomość (treść komunikatu)',
- 'name' => 'additional_message_text',
- 'id' => 'additional_message_text',
- 'value' => $this->product['additional_message_text']
- ]);
- ?>
-
-
-
-
- if (is_array($this->languages)) : foreach ($this->languages as $lg) : ?>
- if ($lg['status']) : ?>
- - if ($lg['id'] == \front\factory\Languages::default_language()) echo ' '; ?>= $lg['name']; ?>
- endif; ?>
-
- endforeach;
- endif;
- ?>
-
-
- if (is_array($this->languages)) : foreach ($this->languages as $lg) : ?>
- if ($lg['status']) : ?>
-
- =
- \Html::input_icon(
- array(
- 'label' => 'Link SEO',
- 'name' => 'seo_link[' . $lg['id'] . ']',
- 'id' => 'seo_link_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['seo_link'],
- 'icon_content' => 'generuj',
- 'icon_js' => 'generate_seo_links( "' . $lg['id'] . '", $( "#name_' . $lg['id'] . '" ).val(), ' . (int)$this->product['id'] . ' );'
- )
- );
- ?>
- = \Html::input([
- 'label' => 'Meta title',
- 'name' => 'meta_title[' . $lg['id'] . ']',
- 'id' => 'meta_title_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['meta_title']
- ]); ?>
- =
- \Html::textarea(
- array(
- 'label' => 'Meta description',
- 'name' => 'meta_description[' . $lg['id'] . ']',
- 'id' => 'meta_description_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['meta_description']
- )
- );
- ?>
- =
- \Html::textarea(
- array(
- 'label' => 'Meta keywords',
- 'name' => 'meta_keywords[' . $lg['id'] . ']',
- 'id' => 'meta_keywords_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['meta_keywords']
- )
- );
- ?>
- = \Html::input([
- 'label' => 'Canonical',
- 'name' => 'canonical[' . $lg['id'] . ']',
- 'id' => 'canonical_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['canonical']
- ]); ?>
-
- endif; ?>
-
- endforeach;
- endif;
- ?>
-
-
-
-
-
-
- $layouts[''] = '---- szablon domyślny ----';
- if (is_array($this->layouts)) : foreach ($this->layouts as $layout) :
- $layouts[$layout['id']] = $layout['name'];
- endforeach;
- endif;
- ?>
- =
- \Html::select([
- 'label' => 'Szablon',
- 'name' => 'layout_id',
- 'id' => 'layout_id',
- 'values' => $layouts,
- 'value' => $this->product['layout_id']
- ]);
- ?>
-
-
-
-
-
You browser doesn't have Flash installed.
-
-
-
-
- $files_count = 0;
- if (is_array($this->product['files'])) : foreach ($this->product['files'] as $file) :
-
- if ($file['name'])
- $name = $file['name'];
- else
- {
- $name = explode('/', $file['src']);
- $name = $name[count($name) - 1];
- }
- ?>
- -
-
-
-
- $files_count++;
- endforeach;
- endif;
- ?>
-
-
You browser doesn't have Flash installed.
-
-
-
-
-
- $z = 0; ?>
- if (is_array($this->languages)) : foreach ($this->languages as $lg) : ?>
- if ($lg['status']) : ?>
- -
- = $lg['name']; ?>
-
- endif; ?>
- endforeach;
- endif; ?>
-
-
- $z = 0; ?>
- if (is_array($this->languages)) : foreach ($this->languages as $lg) : ?>
- if ($lg['status']) : ?>
-
-
-
-
-
-
- endif; ?>
- endforeach;
- endif; ?>
-
-
- = \Html::input([
- 'label' => 'Custom label 0',
- 'name' => 'custom_label_0',
- 'id' => 'custom_label_0',
- 'value' => $this->product['custom_label_0']
- ]);
- ?>
- = \Html::input([
- 'label' => 'Custom label 1',
- 'name' => 'custom_label_1',
- 'id' => 'custom_label_1',
- 'value' => $this->product['custom_label_1']
- ]);
- ?>
- = \Html::input([
- 'label' => 'Custom label 2',
- 'name' => 'custom_label_2',
- 'id' => 'custom_label_2',
- 'value' => $this->product['custom_label_2']
- ]);
- ?>
- = \Html::input([
- 'label' => 'Custom label 3',
- 'name' => 'custom_label_3',
- 'id' => 'custom_label_3',
- 'value' => $this->product['custom_label_3']
- ]);
- ?>
- = \Html::input([
- 'label' => 'Custom label 4',
- 'name' => 'custom_label_4',
- 'id' => 'custom_label_4',
- 'value' => $this->product['custom_label_4']
- ]);
- ?>
-
-
-
dodaj niestandardowe pole
-
- if ( is_array( $this->product['custom_fields'] ) ) : foreach ( $this->product['custom_fields'] as $field ):?>
- $isRequired = !empty($field['is_required']); ?>
-
- endforeach; endif;?>
-
-
-
-
- $producers[''] = '--- wybierz producenta ---';
- foreach ($this->producers as $producer)
- $producers[$producer['id']] = $producer['name'];
- ?>
- = \Html::select([
- 'label' => 'Producent',
- 'name' => 'producer_id',
- 'id' => 'producer_id',
- 'values' => $producers,
- 'value' => $this->product['producer_id']
- ]); ?>
-
-
- if ( is_array( $this -> languages ) ): foreach ( $this -> languages as $lg ):?>
- if ($lg['status']) : ?>
- - if ($lg['id'] == \front\factory\Languages::default_language()) echo ' '; ?>= $lg['name']; ?>
- endif; ?>
- endforeach; endif; ?>
-
-
- if ( is_array ($this -> languages ) ): foreach ( $this -> languages as $lg ):?>
- if ( $lg['status'] ):?>
-
- = \Html::textarea( [
- 'label' => 'Informacje o bezpieczeństwie ('.$lg['name'].')',
- 'name' => 'security_information[' . $lg['id'] . ']',
- 'id' => 'security_information_' . $lg['id'],
- 'value' => $this->product['languages'][$lg['id']]['security_information']
- ] );?>
-
-
- endif; ?>
- endforeach; endif;?>
-
-
-
-
-
-
-
-$out = ob_get_clean();
-
-$grid = new \gridEdit;
-$grid->id = 'product-edit';
-$grid->gdb_opt = $gdb;
-$grid->include_plugins = true;
-$grid->title = $this->product['id'] ? 'Edycja produktu:
' . $this->product['languages'][\front\factory\Languages::default_language()]['name'] . '' : 'Edycja produktu';
-$grid->fields = [
- [
- 'db' => 'id',
- 'type' => 'hidden',
- 'value' => $this->product['id']
- ]
-];
-$grid->actions = [
- 'save' => ['url' => '/admin/shop_product/save/', 'back_url' => '/admin/shop_product/view_list/'],
- 'cancel' => ['url' => '/admin/shop_product/view_list/']
-];
-$grid->buttons = [
- [
- 'label' => 'Podgląd',
- 'id' => 'product-preview',
- 'url' => '#',
- 'icon' => 'fa-search',
- 'class' => 'btn-primary'
- ]
-];
-$grid->external_code = $out;
-$grid->persist_edit = true;
-$grid->id_param = 'id';
-
-echo $grid->draw();
-?>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/temp/update_build/tmp_0.275/admin/templates/shop-product/subcategories-list.php b/temp/update_build/tmp_0.275/admin/templates/shop-product/subcategories-list.php
deleted file mode 100644
index 5c7f796..0000000
--- a/temp/update_build/tmp_0.275/admin/templates/shop-product/subcategories-list.php
+++ /dev/null
@@ -1,24 +0,0 @@
- if ( is_array( $this -> categories ) ):?>
-
- foreach ( $this -> categories as $category ):?>
- -
-
- =
- \Tpl::view( 'shop-product/subcategories-list', [
- 'categories' => ( new \Domain\Category\CategoryRepository( $GLOBALS['mdb'] ) )->subcategories( $category[ 'id' ] ),
- 'product_categories' => $this -> product_categories,
- 'dlang' => $this -> dlang,
- 'name' => $this -> name
- ] );
- ?>
-
- endforeach;?>
-
- endif;?>
\ No newline at end of file
diff --git a/temp/update_build/tmp_0.275/admin/templates/site/main-layout.php b/temp/update_build/tmp_0.275/admin/templates/site/main-layout.php
deleted file mode 100644
index e090e08..0000000
--- a/temp/update_build/tmp_0.275/admin/templates/site/main-layout.php
+++ /dev/null
@@ -1,273 +0,0 @@
- global $user, $settings;?>
-
-
-
-
shopPro
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- if ( $user[ 'name' ] or $user[ 'surname' ] )
- echo $user[ 'surname' ] . ' ' . $user[ 'name' ];
- else
- echo $user[ 'login' ];
- ?>
-

-
-
-
-
-
- if ( $alert = \S::get_session( 'alert' ) ):
- \S::alert( false );
- ?>
-
-
-
-
-
- = $alert;?>
-
-
-
- endif;?>
-
-
- = $this -> content;?>
-
-
-
-
-
-
-
-
diff --git a/temp/update_build/tmp_0.275/autoload/admin/Controllers/ShopCategoryController.php b/temp/update_build/tmp_0.275/autoload/admin/Controllers/ShopCategoryController.php
deleted file mode 100644
index 61d70b5..0000000
--- a/temp/update_build/tmp_0.275/autoload/admin/Controllers/ShopCategoryController.php
+++ /dev/null
@@ -1,163 +0,0 @@
-repository = $repository;
- $this->languagesRepository = $languagesRepository;
- }
-
- public function view_list(): string
- {
- return \Tpl::view('shop-category/categories-list', [
- 'categories' => $this->repository->subcategories(null),
- 'level' => 0,
- 'dlang' => \front\factory\Languages::default_language(),
- ]);
- }
-
- public function list(): string
- {
- return $this->view_list();
- }
-
- public function category_edit(): string
- {
- return \Tpl::view('shop-category/category-edit', [
- 'category' => $this->repository->categoryDetails(\S::get('id')),
- 'pid' => \S::get('pid'),
- 'languages' => $this->languagesRepository->languagesList(),
- 'sort_types' => $this->repository->sortTypes(),
- ]);
- }
-
- public function edit(): string
- {
- return $this->category_edit();
- }
-
- public function save(): void
- {
- $response = [
- 'status' => 'error',
- 'msg' => 'Podczas zapisywania kategorii wystąpił błąd. Proszę spróbować ponownie.',
- ];
-
- $values = json_decode((string)\S::get('values'), true);
- if (is_array($values)) {
- $savedId = $this->repository->save($values);
- if (!empty($savedId)) {
- $response = [
- 'status' => 'ok',
- 'msg' => 'Kategoria została zapisana.',
- 'id' => (int)$savedId,
- ];
- }
- }
-
- echo json_encode($response);
- exit;
- }
-
- public function category_delete(): void
- {
- if ($this->repository->categoryDelete(\S::get('id'))) {
- \S::set_message('Kategoria została usunięta.');
- } else {
- \S::alert('Podczas usuwania kategorii wystąpił błąd. Aby usunąć kategorię nie może ona posiadać przypiętych podkategorii.');
- }
-
- header('Location: /admin/shop_category/view_list/');
- exit;
- }
-
- public function delete(): void
- {
- $this->category_delete();
- }
-
- public function category_products(): string
- {
- return \Tpl::view('shop-category/category-products', [
- 'category_id' => \S::get('id'),
- 'products' => $this->repository->categoryProducts((int)\S::get('id')),
- ]);
- }
-
- public function products(): string
- {
- return $this->category_products();
- }
-
- public function category_url_browser(): void
- {
- echo \Tpl::view('shop-category/category-browse-list', [
- 'categories' => $this->repository->subcategories(null),
- 'level' => 0,
- 'dlang' => \front\factory\Languages::default_language(),
- ]);
- exit;
- }
-
- public function save_categories_order(): void
- {
- $response = [
- 'status' => 'error',
- 'msg' => 'Podczas zapisywania kolejności kategorii wystąpił błąd. Proszę spróbować ponownie.',
- ];
-
- if ( $this->repository->saveCategoriesOrder( \S::get( 'categories' ) ) ) {
- $response = [ 'status' => 'ok' ];
- }
-
- echo json_encode( $response );
- exit;
- }
-
- public function save_products_order(): void
- {
- $response = [
- 'status' => 'error',
- 'msg' => 'Podczas zapisywania kolejności wyświetlania produktów wystąpił błąd. Proszę spróbować ponownie.',
- ];
-
- if ( $this->repository->saveProductOrder( \S::get( 'category_id' ), \S::get( 'products' ) ) ) {
- $response = [ 'status' => 'ok' ];
- }
-
- echo json_encode( $response );
- exit;
- }
-
- public function cookie_categories(): void
- {
- $categoryId = (string) \S::get( 'category_id' );
- if ( $categoryId === '' ) {
- echo json_encode( [ 'status' => 'error' ] );
- exit;
- }
-
- $array = [];
- if ( isset( $_COOKIE['cookie_categories'] ) ) {
- $tmp = @unserialize( (string) $_COOKIE['cookie_categories'] );
- if ( is_array( $tmp ) ) {
- $array = $tmp;
- }
- }
-
- $array[$categoryId] = isset( $array[$categoryId] ) && (int) $array[$categoryId] === 1 ? 0 : 1;
-
- setcookie( 'cookie_categories', serialize( $array ), time() + 3600 * 24 * 365, '/' );
-
- echo json_encode( [ 'status' => 'ok' ] );
- exit;
- }
-}
diff --git a/temp/update_build/tmp_0.275/autoload/admin/Controllers/ShopProductController.php b/temp/update_build/tmp_0.275/autoload/admin/Controllers/ShopProductController.php
deleted file mode 100644
index 3cb3372..0000000
--- a/temp/update_build/tmp_0.275/autoload/admin/Controllers/ShopProductController.php
+++ /dev/null
@@ -1,73 +0,0 @@
-repository = $repository;
- }
-
- /**
- * Widok masowej edycji produktów.
- */
- public function mass_edit(): string
- {
- $categoryRepository = new CategoryRepository( $GLOBALS['mdb'] );
-
- return \Tpl::view( 'shop-product/mass-edit', [
- 'products' => $this->repository->allProductsForMassEdit(),
- 'categories' => $categoryRepository->subcategories( null ),
- 'dlang' => \front\factory\Languages::default_language()
- ] );
- }
-
- /**
- * AJAX: zastosowanie rabatu procentowego na zaznaczonych produktach.
- */
- public function mass_edit_save(): void
- {
- $discountPercent = \S::get( 'discount_percent' );
- $products = \S::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) \S::get( 'category_id' );
- $products = $this->repository->getProductsByCategory( $categoryId );
-
- echo json_encode( [ 'status' => 'ok', 'products' => $products ] );
- exit;
- }
-}
diff --git a/temp/update_build/tmp_0.275/autoload/admin/class.Site.php b/temp/update_build/tmp_0.275/autoload/admin/class.Site.php
deleted file mode 100644
index 2d6c1ad..0000000
--- a/temp/update_build/tmp_0.275/autoload/admin/class.Site.php
+++ /dev/null
@@ -1,493 +0,0 @@
- $user['login'],
- 'ts' => time()
- ];
-
- $json = json_encode($payloadArr, JSON_UNESCAPED_SLASHES);
- $sig = hash_hmac('sha256', $json, self::APP_SECRET_KEY);
- $payload = base64_encode($json . '.' . $sig);
-
- setcookie( $cookie_name, $payload, [
- 'expires' => time() + (86400 * 14),
- 'path' => '/',
- 'domain' => $domain,
- 'secure' => true,
- 'httponly' => true,
- 'samesite' => 'Lax',
- ]);
- }
- }
-
- public static function special_actions()
- {
- global $mdb;
-
- $sa = \S::get('s-action');
- $domain = preg_replace('/^www\./', '', $_SERVER['SERVER_NAME']);
- $cookie_name = 'admin_remember_' . str_replace( '.', '-', $domain );
- $users = new \Domain\User\UserRepository($mdb);
-
- switch ($sa)
- {
- case 'user-logon':
- {
- $login = \S::get('login');
- $pass = \S::get('password');
-
- $result = $users->logon($login, $pass);
-
- if ( $result == 1 )
- {
- $user = $users->details($login);
-
- if ( $user['twofa_enabled'] == 1 )
- {
- \S::set_session( 'twofa_pending', [
- 'uid' => (int)$user['id'],
- 'login' => $login,
- 'remember' => (bool)\S::get('remember'),
- 'started' => time(),
- ] );
-
- if ( !$users->sendTwofaCode( (int)$user['id'] ) )
- {
- \S::alert('Nie udało się wysłać kodu 2FA. Spróbuj ponownie.');
- \S::delete_session('twofa_pending');
- header('Location: /admin/');
- exit;
- }
-
- header('Location: /admin/user/twofa/');
- exit;
- }
- else
- {
- $user = $users->details($login);
-
- self::finalize_admin_login(
- $user,
- $domain,
- $cookie_name,
- (bool)\S::get('remember')
- );
-
- header('Location: /admin/articles/list/');
- exit;
- }
- }
- else
- {
- if ($result == -1)
- {
- \S::alert('Z powodu 5 nieudanych prób Twoje konto zostało zablokowane.');
- }
- else
- {
- \S::alert('Podane hasło jest nieprawidłowe lub użytkownik nie istnieje.');
- }
- header('Location: /admin/');
- exit;
- }
- }
- break;
-
- case 'user-2fa-verify':
- {
- $pending = \S::get_session('twofa_pending');
- if ( !$pending || empty( $pending['uid'] ) ) {
- \S::alert('Sesja 2FA wygasła. Zaloguj się ponownie.');
- header('Location: /admin/');
- exit;
- }
-
- $code = trim((string)\S::get('twofa'));
- if (!preg_match('/^\d{6}$/', $code))
- {
- \S::alert('Nieprawidłowy format kodu.');
- header('Location: /admin/user/twofa/');
- exit;
- }
-
- $ok = $users->verifyTwofaCode((int)$pending['uid'], $code);
- if (!$ok)
- {
- \S::alert('Błędny lub wygasły kod.');
- header('Location: /admin/user/twofa/');
- exit;
- }
-
- // 2FA OK - finalna sesja
- $user = $users->details($pending['login']);
-
- self::finalize_admin_login(
- $user,
- $domain,
- $cookie_name,
- $pending['remember'] ? true : false
- );
-
- header('Location: /admin/articles/list/');
- exit;
- }
- break;
-
- case 'user-2fa-resend':
- {
- $pending = \S::get_session('twofa_pending');
- if (!$pending || empty($pending['uid']))
- {
- \S::alert('Sesja 2FA wygasła. Zaloguj się ponownie.');
- header('Location: /admin/');
- exit;
- }
-
- if (!$users->sendTwofaCode((int)$pending['uid'], true))
- {
- \S::alert('Kod można wysłać ponownie po krótkiej przerwie.');
- }
- else
- {
- \S::alert('Nowy kod został wysłany.');
- }
- header('Location: /admin/user/twofa/');
- exit;
- }
- break;
-
- case 'user-logout':
- {
- setcookie($cookie_name, "", [
- 'expires' => time() - 86400,
- 'path' => '/',
- 'domain' => $domain,
- 'secure' => true,
- 'httponly' => true,
- 'samesite' => 'Lax',
- ]);
- \S::delete_session('twofa_pending');
- session_destroy();
- header('Location: /admin/');
- exit;
- }
- break;
- }
- }
-
- /**
- * Mapa nowych kontrolerów: module => fabryka kontrolera (DI)
- * Przy migracji kolejnego kontrolera - dodaj wpis tutaj
- */
- private static $newControllers = [];
-
- /**
- * Zwraca mapę fabryk kontrolerów (inicjalizacja runtime)
- */
- private static function getControllerFactories(): array
- {
- if ( !empty( self::$newControllers ) )
- return self::$newControllers;
-
- self::$newControllers = [
- 'Articles' => function() {
- global $mdb;
-
- return new \admin\Controllers\ArticlesController(
- new \Domain\Article\ArticleRepository( $mdb ),
- new \Domain\Languages\LanguagesRepository( $mdb ),
- new \Domain\Layouts\LayoutsRepository( $mdb ),
- new \Domain\Pages\PagesRepository( $mdb )
- );
- },
- 'ArticlesArchive' => function() {
- global $mdb;
-
- return new \admin\Controllers\ArticlesArchiveController(
- new \Domain\Article\ArticleRepository( $mdb )
- );
- },
- 'Banners' => function() {
- global $mdb;
-
- return new \admin\Controllers\BannerController(
- new \Domain\Banner\BannerRepository( $mdb ),
- new \Domain\Languages\LanguagesRepository( $mdb )
- );
- },
- 'Settings' => function() {
- global $mdb;
-
- return new \admin\Controllers\SettingsController(
- new \Domain\Settings\SettingsRepository( $mdb ),
- new \Domain\Languages\LanguagesRepository( $mdb )
- );
- },
- 'ProductArchive' => function() {
- global $mdb;
-
- return new \admin\Controllers\ProductArchiveController(
- new \Domain\Product\ProductRepository( $mdb )
- );
- },
- // Alias dla starego modułu /admin/archive/list/
- 'Archive' => function() {
- global $mdb;
-
- return new \admin\Controllers\ProductArchiveController(
- new \Domain\Product\ProductRepository( $mdb )
- );
- },
- 'Dictionaries' => function() {
- global $mdb;
-
- return new \admin\Controllers\DictionariesController(
- new \Domain\Dictionaries\DictionariesRepository( $mdb ),
- new \Domain\Languages\LanguagesRepository( $mdb )
- );
- },
- 'Filemanager' => function() {
- return new \admin\Controllers\FilemanagerController();
- },
- 'Users' => function() {
- global $mdb;
-
- return new \admin\Controllers\UsersController(
- new \Domain\User\UserRepository( $mdb )
- );
- },
- 'Languages' => function() {
- global $mdb;
-
- return new \admin\Controllers\LanguagesController(
- new \Domain\Languages\LanguagesRepository( $mdb )
- );
- },
- 'Layouts' => function() {
- global $mdb;
-
- return new \admin\Controllers\LayoutsController(
- new \Domain\Layouts\LayoutsRepository( $mdb ),
- new \Domain\Languages\LanguagesRepository( $mdb )
- );
- },
- 'Newsletter' => function() {
- global $mdb;
-
- return new \admin\Controllers\NewsletterController(
- new \Domain\Newsletter\NewsletterRepository(
- $mdb,
- new \Domain\Settings\SettingsRepository( $mdb )
- ),
- new \Domain\Newsletter\NewsletterPreviewRenderer()
- );
- },
- 'Scontainers' => function() {
- global $mdb;
-
- return new \admin\Controllers\ScontainersController(
- new \Domain\Scontainers\ScontainersRepository( $mdb ),
- new \Domain\Languages\LanguagesRepository( $mdb )
- );
- },
- 'ShopPromotion' => function() {
- global $mdb;
-
- return new \admin\Controllers\ShopPromotionController(
- new \Domain\Promotion\PromotionRepository( $mdb )
- );
- },
- 'ShopCoupon' => function() {
- global $mdb;
-
- return new \admin\Controllers\ShopCouponController(
- new \Domain\Coupon\CouponRepository( $mdb )
- );
- },
- 'ShopAttribute' => function() {
- global $mdb;
-
- return new \admin\Controllers\ShopAttributeController(
- new \Domain\Attribute\AttributeRepository( $mdb ),
- new \Domain\Languages\LanguagesRepository( $mdb )
- );
- },
- 'ShopPaymentMethod' => function() {
- global $mdb;
-
- return new \admin\Controllers\ShopPaymentMethodController(
- new \Domain\PaymentMethod\PaymentMethodRepository( $mdb )
- );
- },
- 'ShopTransport' => function() {
- global $mdb;
-
- return new \admin\Controllers\ShopTransportController(
- new \Domain\Transport\TransportRepository( $mdb ),
- new \Domain\PaymentMethod\PaymentMethodRepository( $mdb )
- );
- },
- 'Pages' => function() {
- global $mdb;
-
- return new \admin\Controllers\PagesController(
- new \Domain\Pages\PagesRepository( $mdb ),
- new \Domain\Languages\LanguagesRepository( $mdb ),
- new \Domain\Layouts\LayoutsRepository( $mdb )
- );
- },
- 'Integrations' => function() {
- global $mdb;
-
- return new \admin\Controllers\IntegrationsController(
- new \Domain\Integrations\IntegrationsRepository( $mdb )
- );
- },
- 'ShopStatuses' => function() {
- global $mdb;
-
- return new \admin\Controllers\ShopStatusesController(
- new \Domain\ShopStatus\ShopStatusRepository( $mdb )
- );
- },
- 'ShopProductSets' => function() {
- global $mdb;
-
- return new \admin\Controllers\ShopProductSetsController(
- new \Domain\ProductSet\ProductSetRepository( $mdb )
- );
- },
- 'ShopProducer' => function() {
- global $mdb;
-
- return new \admin\Controllers\ShopProducerController(
- new \Domain\Producer\ProducerRepository( $mdb ),
- new \Domain\Languages\LanguagesRepository( $mdb )
- );
- },
- 'ShopCategory' => function() {
- global $mdb;
-
- return new \admin\Controllers\ShopCategoryController(
- new \Domain\Category\CategoryRepository( $mdb ),
- new \Domain\Languages\LanguagesRepository( $mdb )
- );
- },
- 'ShopProduct' => function() {
- global $mdb;
-
- return new \admin\Controllers\ShopProductController(
- new \Domain\Product\ProductRepository( $mdb )
- );
- },
- 'ShopClients' => function() {
- global $mdb;
-
- return new \admin\Controllers\ShopClientsController(
- new \Domain\Client\ClientRepository( $mdb )
- );
- },
- ];
-
- return self::$newControllers;
- }
-
- /**
- * Tworzy instancję nowego kontrolera z Dependency Injection
- */
- private static function createController( string $moduleName )
- {
- global $mdb;
-
- $factories = self::getControllerFactories();
- if ( !isset( $factories[$moduleName] ) )
- return null;
-
- $factory = $factories[$moduleName];
- if ( !is_callable( $factory ) )
- return null;
-
- return $factory();
- }
-
-
- public static function route()
- {
- $_SESSION['admin'] = true;
-
- if ( \S::get( 'p' ) )
- \S::set_session( 'p' , \S::get( 'p' ) );
-
- $page = \S::get_session( 'p' );
-
- // Budowanie nazwy modułu
- $moduleName = '';
- $results = explode( '_', \S::get( 'module' ) );
- if ( is_array( $results ) ) foreach ( $results as $row )
- $moduleName .= ucfirst( $row );
-
- $action = \S::get( 'action' );
-
- // 1. Sprawdź czy istnieje nowy kontroler
- $factories = self::getControllerFactories();
- if ( isset( $factories[$moduleName] ) )
- {
- $controller = self::createController( $moduleName );
- if ( $controller )
- {
- if ( method_exists( $controller, $action ) )
- {
- return $controller->$action();
- }
-
- if ( $moduleName === 'ShopAttribute' )
- {
- \S::alert( 'Nieprawidłowy adres url.' );
- return false;
- }
- }
-
- }
-
- // 2. Fallback na stary kontroler
- $class = '\admin\controls\\' . $moduleName;
-
- if ( class_exists( $class ) and method_exists( new $class, $action ) )
- return call_user_func_array( array( $class, $action ), array() );
- else
- {
- \S::alert( 'Nieprawidłowy adres url.' );
- return false;
- }
- }
-
- static public function update()
- {
- global $mdb;
-
- if ( $results = $mdb -> select( 'pp_updates', [ 'name' ], [ 'done' => 0 ] ) )
- {
- foreach ( $results as $row )
- {
- $class = '\admin\factory\Update';
- $method = $row['name'];
-
- if ( class_exists( $class ) and method_exists( new $class, $method ) )
- call_user_func_array( array( $class, $method ), array() );
- }
- }
- }
-}
-
diff --git a/temp/update_build/tmp_0.275/autoload/admin/controls/class.ShopProduct.php b/temp/update_build/tmp_0.275/autoload/admin/controls/class.ShopProduct.php
deleted file mode 100644
index c7113d4..0000000
--- a/temp/update_build/tmp_0.275/autoload/admin/controls/class.ShopProduct.php
+++ /dev/null
@@ -1,373 +0,0 @@
- $val )
- {
- if ( strpos( $key, 'attribute_' ) !== false )
- {
- $attribute = explode( 'attribute_', $key );
- $attributes[ $attribute[1] ] = $val;
- }
- }
-
- if ( \admin\factory\ShopProduct::generate_permutation( (int) \S::get( 'product_id' ), $attributes ) )
- \S::alert( 'Kombinacje produktu zostały wygenerowane.' );
-
- header( 'Location: /admin/shop_product/product_combination/product_id=' . (int) \S::get( 'product_id' ) );
- exit;
- }
-
- //usunięcie kombinacji produktu
- static public function delete_combination()
- {
- if ( \admin\factory\ShopProduct::delete_combination( (int)\S::get( 'combination_id' ) ) )
- \S::alert( 'Kombinacja produktu została usunięta' );
- else
- \S::alert( 'Podczas usuwania kombinacji produktu wystąpił błąd. Proszę spróbować ponownie' );
-
- header( 'Location: /admin/shop_product/product_combination/product_id=' . \S::get( 'product_id' ) );
- exit;
- }
-
- static public function duplicate_product()
- {
- if ( \admin\factory\ShopProduct::duplicate_product( (int)\S::get( 'product-id' ), (int)\S::get( 'combination' ) ) )
- \S::set_message( 'Produkt został zduplikowany.' );
- else
- \S::alert( 'Podczas duplikowania produktu wystąpił błąd. Proszę spróbować ponownie' );
-
- header( 'Location: /admin/shop_product/view_list/' );
- exit;
- }
-
- public static function image_delete()
- {
- $response = [ 'status' => 'error', 'msg' => 'Podczas usuwania zdjecia wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( \admin\factory\ShopProduct::delete_img( \S::get( 'image_id' ) ) )
- $response = [ 'status' => 'ok' ];
-
- echo json_encode( $response );
- exit;
- }
-
- public static function images_order_save()
- {
- if ( \admin\factory\ShopProduct::images_order_save( \S::get( 'product_id' ), \S::get( 'order' ) ) )
- echo json_encode( [ 'status' => 'ok', 'msg' => 'Produkt został zapisany.' ] );
-
- exit;
- }
-
- public static function image_alt_change()
- {
- $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany atrybutu alt zdjęcia wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( \admin\factory\ShopProduct::image_alt_change( \S::get( 'image_id' ), \S::get( 'image_alt' ) ) )
- $response = [ 'status' => 'ok' ];
-
- echo json_encode( $response );
- exit;
- }
-
- // szybka zmiana statusu produktu
- static public function change_product_status() {
-
- if ( \admin\factory\ShopProduct::change_product_status( (int)\S::get( 'product-id' ) ) )
- \S::set_message( 'Status produktu został zmieniony' );
-
- header( 'Location: ' . $_SERVER['HTTP_REFERER'] );
- exit;
- }
-
- // szybka zmiana google xml label
- static public function product_change_custom_label()
- {
- $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany google xml label wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( \admin\factory\ShopProduct::product_change_custom_label( (int) \S::get( 'product_id' ), \S::get( 'custom_label' ), \S::get( 'value' ) ) )
- $response = [ 'status' => 'ok' ];
-
- echo json_encode( $response );
- exit;
- }
-
- // szybka zmiana ceny promocyjnej
- static public function product_change_price_brutto_promo()
- {
- $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany ceny wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( \admin\factory\ShopProduct::product_change_price_brutto_promo( (int) \S::get( 'product_id' ), \S::get( 'price' ) ) )
- $response = [ 'status' => 'ok' ];
-
- echo json_encode( $response );
- exit;
- }
-
- // szybka zmiana ceny
- static public function product_change_price_brutto()
- {
- $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany ceny wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( \admin\factory\ShopProduct::product_change_price_brutto( (int) \S::get( 'product_id' ), \S::get( 'price' ) ) )
- $response = [ 'status' => 'ok' ];
-
- echo json_encode( $response );
- exit;
- }
-
- // pobierz bezpośredni url produktu
- static public function ajax_product_url()
- {
- echo json_encode( [ 'url' => \shop\Product::getProductUrl( \S::get( 'product_id' ) ) ] );
- exit;
- }
-
- // zapisanie produktu
- public static function save()
- {
- $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania produktu wystąpił błąd. Proszę spróbować ponownie.' ];
- $values = json_decode( \S::get( 'values' ), true );
-
- if ( $id = \admin\factory\ShopProduct::save(
- $values['id'], $values['name'], $values['short_description'], $values['description'], $values['status'], $values['meta_description'], $values['meta_keywords'], $values['seo_link'],
- $values['copy_from'], $values['categories'], $values['price_netto'], $values['price_brutto'], $values['vat'], $values['promoted'], $values['warehouse_message_zero'], $values['warehouse_message_nonzero'], $values['tab_name_1'],
- $values['tab_description_1'], $values['tab_name_2'], $values['tab_description_2'], $values['layout_id'], $values['products_related'], (int) $values['set'], $values['price_netto_promo'], $values['price_brutto_promo'],
- $values['new_to_date'], $values['stock_0_buy'], $values['wp'], $values['custom_label_0'], $values['custom_label_1'], $values['custom_label_2'], $values['custom_label_3'], $values['custom_label_4'], $values['additional_message'], (int)$values['quantity'], $values['additional_message_text'], $values['additional_message_required'] == 'on' ? 1 : 0, $values['canonical'], $values['meta_title'], $values['producer_id'], $values['sku'], $values['ean'], $values['product_unit'], $values['weight'], $values['xml_name'], $values['custom_field_name'], $values['custom_field_required'], $values['security_information'], $values['custom_field_type']
- ) ) {
- $response = [ 'status' => 'ok', 'msg' => 'Produkt został zapisany.', 'id' => $id ];
- }
-
- echo json_encode( $response );
- exit;
- }
-
- // product_unarchive
- static public function product_unarchive()
- {
- if ( \admin\factory\ShopProduct::product_unarchive( (int) \S::get( 'product_id' ) ) )
- \S::alert( 'Produkt został przywrócony z archiwum.' );
- else
- \S::alert( 'Podczas przywracania produktu z archiwum wystąpił błąd. Proszę spróbować ponownie' );
-
- header( 'Location: /admin/product_archive/list/' );
- exit;
- }
-
- static public function product_archive()
- {
- if ( \admin\factory\ShopProduct::product_archive( (int) \S::get( 'product_id' ) ) )
- \S::alert( 'Produkt został przeniesiony do archiwum.' );
- else
- \S::alert( 'Podczas przenoszenia produktu do archiwum wystąpił błąd. Proszę spróbować ponownie' );
-
- header( 'Location: /admin/shop_product/view_list/' );
- exit;
- }
-
- public static function product_delete()
- {
- if ( \admin\factory\ShopProduct::product_delete( (int) \S::get( 'id' ) ) )
- \S::set_message( 'Produkt został usunięty.' );
- else
- \S::alert( 'Podczas usuwania produktu wystąpił błąd. Proszę spróbować ponownie' );
- header( 'Location: /admin/shop_product/view_list/' );
- exit;
- }
-
- // edycja produktu
- public static function product_edit() {
- global $user, $mdb;
-
- if ( !$user ) {
- header( 'Location: /admin/' );
- exit;
- }
-
- \admin\factory\ShopProduct::delete_nonassigned_images();
- \admin\factory\ShopProduct::delete_nonassigned_files();
-
- return \Tpl::view( 'shop-product/product-edit', [
- 'product' => \admin\factory\ShopProduct::product_details( (int) \S::get( 'id' ) ),
- 'languages' => ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->languagesList(),
- 'categories' => ( new \Domain\Category\CategoryRepository( $GLOBALS['mdb'] ) )->subcategories( null ),
- 'layouts' => self::layouts_for_product_edit( $mdb ),
- 'products' => \admin\factory\ShopProduct::products_list(),
- 'dlang' => \front\factory\Languages::default_language(),
- 'sets' => \shop\ProductSet::sets_list(),
- 'producers' => ( new \Domain\Producer\ProducerRepository( $mdb ) )->allProducers(),
- 'units' => ( new \Domain\Dictionaries\DictionariesRepository( $mdb ) ) -> allUnits(),
- 'user' => $user
- ] );
- }
-
- private static function layouts_for_product_edit( $db )
- {
- if ( class_exists( '\Domain\Layouts\LayoutsRepository' ) )
- {
- $rows = ( new \Domain\Layouts\LayoutsRepository( $db ) ) -> listAll();
- return is_array( $rows ) ? $rows : [];
- }
-
- return [];
- }
-
- // ajax_load_products ARCHIVE
- static public function ajax_load_products_archive()
- {
- echo json_encode( [
- 'status' => 'deprecated',
- 'msg' => 'Endpoint nie jest juz wspierany. Uzyj /admin/product_archive/list/.',
- 'redirect_url' => '/admin/product_archive/list/'
- ] );
- exit;
- }
-
- // ajax_load_products
- static public function ajax_load_products() {
-
- $response = [ 'status' => 'error', 'msg' => 'Podczas ładowania produktów wystąpił błąd. Proszę spróbować ponownie.' ];
-
- \S::set_session( 'products_list_current_page', \S::get( 'current_page' ) );
- \S::set_session( 'products_list_query', \S::get( 'query' ) );
-
- if ( $products = \admin\factory\ShopProduct::ajax_products_list( \S::get_session( 'products_list_current_page' ), \S::get_session( 'products_list_query' ) ) ) {
- $response = [
- 'status' => 'ok',
- 'pagination_max' => ceil( $products['products_count'] / 10 ),
- 'html' => \Tpl::view( 'shop-product/products-list-table', [
- 'products' => $products['products'],
- 'current_page' => \S::get( 'current_page' ),
- 'apilo_enabled' => \admin\factory\Integrations::apilo_settings( 'enabled' ),
- 'show_xml_data' => \S::get_session( 'show_xml_data' )
- ] )
- ];
- }
-
- echo json_encode( $response );
- exit;
- }
-
- static public function view_list()
- {
- $current_page = \S::get_session( 'products_list_current_page' );
-
- if ( !$current_page ) {
- $current_page = 1;
- \S::set_session( 'products_list_current_page', $current_page );
- }
-
- $query = \S::get_session( 'products_list_query' );
- if ( $query ) {
- $query_array = [];
- parse_str( $query, $query_array );
- }
-
- if ( \S::get( 'show_xml_data' ) === 'true' ) {
- \S::set_session( 'show_xml_data', true );
- } else if ( \S::get( 'show_xml_data' ) === 'false' ) {
- \S::set_session( 'show_xml_data', false );
- }
-
- return \Tpl::view( 'shop-product/products-list', [
- 'current_page' => $current_page,
- 'query_array' => $query_array,
- 'pagination_max' => ceil( \admin\factory\ShopProduct::count_product() / 10 ),
- 'apilo_enabled' => \admin\factory\Integrations::apilo_settings( 'enabled' ),
- 'show_xml_data' => \S::get_session( 'show_xml_data' ),
- 'shoppro_enabled' => \admin\factory\Integrations::shoppro_settings( 'enabled' )
- ] );
- }
-
- //
- // KOMBINACJE PRODUKTU
- //
-
- // zapisanie możliwości zakupu przy stanie 0 w kombinacji produktu
- static public function product_combination_stock_0_buy_save()
- {
- \admin\factory\ShopProduct::product_combination_stock_0_buy_save( (int)\S::get( 'product_id' ), \S::get( 'stock_0_buy' ) );
- exit;
- }
-
- // zapisanie sku w kombinacji produktu
- static public function product_combination_sku_save()
- {
- \admin\factory\ShopProduct::product_combination_sku_save( (int)\S::get( 'product_id' ), \S::get( 'sku' ) );
- exit;
- }
-
- // zapisanie ilości w kombinacji produktu
- static public function product_combination_quantity_save()
- {
- \admin\factory\ShopProduct::product_combination_quantity_save( (int)\S::get( 'product_id' ), \S::get( 'quantity' ) );
- exit;
- }
-
- // zapisanie ceny w kombinacji produktu
- static public function product_combination_price_save()
- {
- \admin\factory\ShopProduct::product_combination_price_save( (int)\S::get( 'product_id' ), \S::get( 'price' ) );
- exit;
- }
-
- //wyświetlenie kombinacji produktu
- static public function product_combination()
- {
- global $mdb;
-
- return \Tpl::view( 'shop-product/product-combination', [
- 'product' => \admin\factory\ShopProduct::product_details( (int) \S::get( 'product_id' ) ),
- 'attributes' => ( new \Domain\Attribute\AttributeRepository( $mdb ) ) -> getAttributesListForCombinations(),
- 'default_language' => \front\factory\Languages::default_language(),
- 'product_permutations' => \admin\factory\ShopProduct::get_product_permutations( (int) \S::get( 'product_id' ) )
- ] );
- }
-
- // generate_sku_code
- static public function generate_sku_code() {
- $response = [ 'status' => 'error', 'msg' => 'Podczas generowania kodu sku wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( $sku = \shop\Product::generate_sku_code( \S::get( 'product_id' ) ) )
- $response = [ 'status' => 'ok', 'sku' => $sku ];
-
- echo json_encode( $response );
- exit;
- }
-
- // product_xml_name_save
- static public function product_xml_name_save() {
- $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania nazwy produktu wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( \shop\Product::product_xml_name_save( \S::get( 'product_id' ), \S::get( 'product_xml_name' ), \S::get( 'lang_id' ) ) )
- $response = [ 'status' => 'ok' ];
-
- echo json_encode( $response );
- exit;
- }
-
- // product_custom_label_suggestions
- static public function product_custom_label_suggestions() {
- $response = [ 'status' => 'error', 'msg' => 'Podczas pobierania sugestii dla custom label wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( $suggestions = \shop\Product::product_custom_label_suggestions( \S::get( 'custom_label' ), \S::get( 'label_type' ) ) )
- $response = [ 'status' => 'ok', 'suggestions' => $suggestions ];
-
- echo json_encode( $response );
- exit;
- }
-
- // product_custom_label_save
- static public function product_custom_label_save() {
- $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania custom label wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( \shop\Product::product_custom_label_save( \S::get( 'product_id' ), \S::get( 'custom_label' ), \S::get( 'label_type' ) ) )
- $response = [ 'status' => 'ok' ];
-
- echo json_encode( $response );
- exit;
- }
-}
diff --git a/temp/update_build/tmp_0.275/autoload/admin/factory/class.ShopProduct.php b/temp/update_build/tmp_0.275/autoload/admin/factory/class.ShopProduct.php
deleted file mode 100644
index 633cf32..0000000
--- a/temp/update_build/tmp_0.275/autoload/admin/factory/class.ShopProduct.php
+++ /dev/null
@@ -1,1578 +0,0 @@
- count( 'pp_shop_products_langs', [
- 'AND' => [
- 'lang_id' => $lang_id,
- 'seo_link' => $seo_link,
- 'product_id[!]' => $product_id,
- ],
- ] );
- }
-
- private static function removeConflictingRedirectSources( int $product_id, string $lang_id, string $from ): void
- {
- global $mdb;
-
- if ( !$from )
- return;
-
- $mdb -> delete( 'pp_redirects', [
- 'AND' => [
- 'from' => $from,
- 'lang_id' => $lang_id,
- 'product_id[!]' => $product_id,
- ],
- ] );
- }
-
- // count_product
- static public function count_product( $where = null )
- {
- global $mdb;
-
- if ( $where )
- return $mdb -> count( 'pp_shop_products', $where );
- else
- return $mdb -> count( 'pp_shop_products', [ 'archive' => 0 ] );
- }
-
- static public function update_product_combinations_prices( int $product_id, $price_brutto, $vat, $price_brutto_promo )
- {
- global $mdb;
-
- $products = $mdb -> query( 'SELECT psp.id, parent_id '
- . 'FROM pp_shop_products AS psp '
- . 'INNER JOIN pp_shop_products_attributes AS pspa ON psp.id = pspa.product_id '
- . 'INNER JOIN pp_shop_attributes_values AS psav ON pspa.value_id = psav.id '
- . 'WHERE psav.impact_on_the_price > 0 AND psp.parent_id = :product_id', [ ':product_id' => $product_id ] ) -> fetchAll( \PDO::FETCH_ASSOC );
- foreach ( $products as $product )
- {
- $price_brutto_combination = $price_brutto;
- $price_brutto_promo_combination = $price_brutto_promo;
-
- $values = $mdb -> query( 'SELECT impact_on_the_price FROM pp_shop_attributes_values AS psav INNER JOIN pp_shop_products_attributes AS pspa ON pspa.value_id = psav.id WHERE impact_on_the_price IS NOT NULL AND product_id = :product_id', [ ':product_id' => $product['id'] ] ) -> fetchAll( \PDO::FETCH_ASSOC );
- foreach ( $values as $value )
- {
- $price_brutto_combination += $value['impact_on_the_price'];
- if ( $price_brutto_promo )
- $price_brutto_promo_combination += $value['impact_on_the_price'];
- else
- $price_brutto_promo_combination = null;
- }
-
- $price_netto_combination = \S::normalize_decimal( $price_brutto_combination / ( 100 + $vat ) * 100, 2 );
- if ( $price_brutto_promo_combination )
- $price_netto_promo_combination = \S::normalize_decimal( $price_brutto_promo_combination / ( 100 + $vat ) * 100, 2 );
- else
- $price_netto_promo_combination = null;
-
- $mdb -> update( 'pp_shop_products', [ 'price_netto' => $price_netto_combination, 'price_brutto' => $price_brutto_combination, 'price_netto_promo' => $price_netto_promo_combination, 'price_brutto_promo' => $price_brutto_promo_combination ], [ 'id' => $product['id'] ] );
- }
- }
-
- // szybka zmiana statusu produktu
- static public function change_product_status( int $product_id ) {
- global $mdb;
-
- $status = $mdb -> get( 'pp_shop_products', 'status', [ 'id' => $product_id ] );
- $status = $status == 1 ? 0 : 1;
- return $mdb -> update( 'pp_shop_products', [ 'status' => $status ], [ 'id' => $product_id ] );
- }
-
- // domyślna nazwa produktu
- static public function product_default_name( int $product_id ) {
- global $mdb;
-
- $default_lang = $mdb -> get( 'pp_langs', 'id', [ 'start' => 1 ] );
- return $mdb -> get( 'pp_shop_products_langs', 'name', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $default_lang ] ] );
- }
-
- // szybka zmiana google xml label
- static public function product_change_custom_label( int $product_id, $custom_label, $value )
- {
- global $mdb;
- return $mdb -> update( 'pp_shop_products', [ 'custom_label_' . $custom_label => $value ? $value : null ], [ 'id' => $product_id ] );
- }
-
- // szybka zmiana ceny promocyjnej
- static public function product_change_price_brutto_promo( int $product_id, $price )
- {
- global $mdb;
-
- $vat = $mdb -> get( 'pp_shop_products', 'vat', [ 'id' => $product_id ] );
- $price_netto = \S::normalize_decimal( (float)$price / ( 100 + (float)$vat ) * 100, 2 );
-
- return $mdb -> update( 'pp_shop_products', [ 'price_brutto_promo' => $price != 0.00 ? $price : null, 'price_netto_promo' => $price_netto != 0.00 ? $price : null ], [ 'id' => $product_id ] );
- }
-
- // szybka zmiana ceny
- static public function product_change_price_brutto( int $product_id, $price )
- {
- global $mdb;
-
- $vat = $mdb -> get( 'pp_shop_products', 'vat', [ 'id' => $product_id ] );
- $price_netto = \S::normalize_decimal( (float)$price / ( 100 + (float)$vat ) * 100, 2 );
-
- return $mdb -> update( 'pp_shop_products', [ 'price_brutto' => $price != 0.00 ? $price : null, 'price_netto' => $price_netto != 0.00 ? $price : null ], [ 'id' => $product_id ] );
- }
-
- // pobierz id produktu głównego
- static public function get_product_parent_id( int $product_id )
- {
- global $mdb;
- return $mdb -> get( 'pp_shop_products', 'parent_id', [ 'id' => $product_id ] );
- }
-
- // usunięcie kombinacji produktu
- static public function delete_combination( int $combination_id )
- {
- global $mdb;
-
- $mdb -> delete( 'pp_shop_products', [ 'id' => $combination_id ] );
- $mdb -> delete( 'pp_shop_products_attributes', [ 'product_id' => $combination_id ] );
-
- return true;
- }
-
- // pobranie permutacji produktu
- static public function get_product_permutations( int $product_id )
- {
- global $mdb;
-
- $results = $mdb -> select( 'pp_shop_products', 'id', [ 'parent_id' => $product_id ] );
- if ( \S::is_array_fix( $results ) ) foreach ( $results as $row )
- $products[] = \admin\factory\ShopProduct::product_details( $row );
-
- return $products;
- }
-
- // generowanie kombinacji produktu
- static public function generate_permutation( int $product_id, $attributes )
- {
- global $mdb;
-
- $vat = $mdb -> get( 'pp_shop_products', 'vat', [ 'id' => $product_id ] );
- $attributeRepository = new \Domain\Attribute\AttributeRepository( $mdb );
-
- $permutations = \shop\Product::array_cartesian( $attributes );
- if ( \S::is_array_fix( $permutations ) ) foreach ( $permutations as $permutation )
- {
- $product = null;
- ksort( $permutation );
-
- $permutation_hash = '';
-
- if ( \S::is_array_fix( $permutation ) ) foreach ( $permutation as $key => $val )
- {
- if ( $permutation_hash )
- $permutation_hash .= '|';
-
- $permutation_hash .= $key . '-' . $val;
-
- // sprawdzenie czy atrybut ma wpływ na cenę
- $value_details = $attributeRepository -> valueDetails( (int)$val );
- $impact_on_the_price = $value_details[ 'impact_on_the_price' ];
-
- if ( $impact_on_the_price > 0 )
- {
- if ( !$product )
- $product = \admin\factory\ShopProduct::product_details( $product_id );
-
- $product_price_brutto = $product['price_brutto'] + $impact_on_the_price;
- $product_price_netto = $product_price_brutto / ( 1 + ( $product['vat'] / 100 ) );
-
- if ( $product['price_brutto_promo'] )
- {
- $product_price_brutto_promo = $product['price_brutto_promo'] + $impact_on_the_price;
- $product_price_netto_promo = $product_price_brutto_promo / ( 1 + ( $product['vat'] / 100 ) );
- }
- else
- {
- $product_price_brutto_promo = null;
- $product_price_netto_promo = null;
- }
- }
-
- if ( $permutation_hash and !$mdb -> count( 'pp_shop_products', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation_hash ] ] ) )
- {
- if ( $mdb -> insert( 'pp_shop_products', [ 'parent_id' => $product_id, 'permutation_hash' => $permutation_hash, 'vat' => $vat ] ) )
- {
- $combination_id = $mdb -> id();
- if ( $product )
- {
- $mdb -> update( 'pp_shop_products', [ 'price_netto' => $product_price_netto, 'vat' => $product['vat'], 'price_brutto' => $product_price_brutto, 'price_netto_promo' => $product_price_netto_promo, 'price_brutto_promo' => $product_price_brutto_promo ], [ 'id' => $combination_id ] );
- }
-
- $permutation_hash_rev_rows = explode( '|', $permutation_hash );
- foreach ( $permutation_hash_rev_rows as $permutation_hash_rev )
- {
- $attribute_rev = explode( '-', $permutation_hash_rev );
- $mdb -> insert( 'pp_shop_products_attributes', [ 'product_id' => $combination_id, 'attribute_id' => $attribute_rev[0], 'value_id' => $attribute_rev[1] ] );
- }
- }
- }
- }
- }
- return true;
- }
-
- public static function product_name($product_id)
- {
- global $mdb;
-
- $results = $mdb -> query("SELECT pspl.name FROM pp_shop_products_langs AS pspl, pp_langs AS pl WHERE lang_id = pl.id AND product_id = :product_id AND pspl.name != '' ORDER BY o ASC LIMIT 1", [':product_id' => $product_id]) -> fetchAll();
-
- return $results[0]['name'];
- }
-
- public static function get_product_images($product_id)
- {
- global $mdb;
-
- return $mdb -> select('pp_shop_products_images', 'src', ['product_id' => (int) $product_id, 'ORDER' => ['o' => 'ASC', 'id' => 'ASC']]);
- }
-
- static public function generate_EAN( $number )
- {
- $code = '200' . str_pad($number, 9, '0');
- $weightflag = true;
- $sum = 0;
-
- for ($i = strlen($code) - 1; $i >= 0; $i--)
- {
- $sum += (int)$code[$i] * ($weightflag?3:1);
- $weightflag = !$weightflag;
- }
-
- $code .= (10 - ($sum % 10)) % 10;
- return $code;
- }
-
- static public function generate_google_feed_xml()
- {
- global $mdb, $lang_id;
-
- $settings = \front\factory\Settings::settings_details(true);
-
- $domain_prefix = 'https';
- $url = preg_replace('#^(http(s)?://)?w{3}\.#', '$1', $_SERVER['SERVER_NAME']);
-
- $main_language = \front\factory\Languages::default_language();
-
- $doc = new \DOMDocument('1.0', 'UTF-8');
- $xmlRoot = $doc -> createElement('rss');
- $xmlRoot = $doc -> appendChild($xmlRoot);
- $xmlRoot -> setAttribute('version', '2.0');
- $xmlRoot -> setAttributeNS('http://www.w3.org/2000/xmlns/', 'xmlns:g', 'http://base.google.com/ns/1.0');
- $channelNode = $xmlRoot -> appendChild($doc -> createElement('channel'));
- $channelNode -> appendChild( $doc -> createElement( 'title', $settings['firm_name']));
- $channelNode -> appendChild( $doc -> createElement( 'link', $domain_prefix . '://' . $url ) );
-
- $rows = $mdb -> select( 'pp_shop_products', 'id', [ 'AND' => [ 'status' => '1', 'archive' => 0, 'parent_id' => null ] ] );
- if ( \S::is_array_fix( $rows ) ) foreach ( $rows as $product_id )
- {
- $product = \shop\Product::getFromCache( $product_id, $lang_id );
-
- if ( is_array( $product -> product_combinations ) and count( $product -> product_combinations ) )
- {
- foreach ( $product -> product_combinations as $product_combination )
- {
- if ( $product_combination -> quantity !== null or $product_combination -> stock_0_buy )
- {
- $itemNode = $channelNode -> appendChild( $doc -> createElement( 'item' ) );
- $p_gid = $itemNode -> appendChild( $doc -> createElement('g:id', $product_combination -> id ) );
- $p_groupid = $itemNode -> appendChild( $doc -> createElement( 'g:item_group_id', $product -> id ) );
-
- if ( $product -> custom_label_0 )
- $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_0', $product -> custom_label_0 ) );
-
- if ( $product -> custom_label_1 )
- $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_1', $product -> custom_label_1 ) );
-
- if ( $product -> custom_label_2 )
- $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_2', $product -> custom_label_2 ) );
-
- if ( $product -> custom_label_3 )
- $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_3', $product -> custom_label_3 ) );
-
- if ( $product -> custom_label_4 )
- $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_4', $product -> custom_label_4 ) );
-
- if ( $product -> language['xml_name'] )
- $p_title = $itemNode -> appendChild( $doc -> createElement( 'title', str_replace( '&', '&', $product -> language['xml_name'] ) . ' - ' . $product -> generateSubtitleFromAttributes( $product_combination -> permutation_hash ) ) );
- else
- $p_title = $itemNode -> appendChild( $doc -> createElement( 'title', str_replace( '&', '&', $product -> language['name'] ) . ' - ' . $product -> generateSubtitleFromAttributes( $product_combination -> permutation_hash ) ) );
-
- if ( $product -> ean )
- $p_gtin = $itemNode -> appendChild( $doc -> createElement( 'g:gtin', $product -> ean ) );
- else
- $p_gtin = $itemNode -> appendChild( $doc -> createElement( 'g:gtin', self::generate_EAN( $product -> id ) ) );
-
- // opis produktu
- if ( $product -> language['short_description'] )
- $p_description = $itemNode -> appendChild( $doc -> createElement( 'g:description', html_entity_decode( strip_tags( $product -> language['short_description'] ) ) ) );
- else
- $p_description = $itemNode -> appendChild( $doc -> createElement( 'g:description', html_entity_decode( strip_tags( $product -> language['name'] ) ) ) );
-
- if ( $product -> language['seo_link'] )
- $link = $domain_prefix . '://' . $url . '/' . \S::seo( $product -> language['seo_link'] ) . '/' . str_replace( '|', '/', $product_combination -> permutation_hash );
- else
- $link = $domain_prefix . '://' . $url . '/' . 'p-' . $product -> id . '-' . \S::seo( $product -> language['name'] ) . '/' . str_replace( '|', '/', $product_combination -> permutation_hash );
-
- $p_link = $itemNode -> appendChild( $doc -> createElement( 'link', $link ) );
-
- if ( $product -> images[0] )
- $p_gimage_link = $itemNode -> appendChild( $doc -> createElement( 'g:image_link', $domain_prefix . '://' . $url . $product -> images[0]['src'] ) );
-
- if ( count( $product -> images ) > 1 )
- {
- for ( $i = 1; $i < count( $product -> images ); ++$i )
- $p_gimage_link = $itemNode -> appendChild( $doc -> createElement( 'g:additional_image_link', $domain_prefix . '://' . $url . $product -> images[$i]['src'] ) );
- }
-
- $p_gcondition = $itemNode -> appendChild( $doc -> createElement( 'g:condition', 'new' ) );
-
- if ( $product_combination -> quantity !== null )
- {
- if ( $product_combination -> quantity > 0 )
- {
- $p_gavailability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'in stock' ) );
- $p_gquantity = $itemNode -> appendChild( $doc -> createElement( 'g:quantity', $product_combination -> quantity ) );
- }
- else
- {
- if ( $product_combination -> stock_0_buy )
- $p_availability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'in stock' ) );
- else
- $p_availability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'out of stock' ) );
- }
- }
- else
- {
- if ( $product -> quantity > 0 )
- {
- $p_gavailability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'in stock' ) );
- $p_gquantity = $itemNode -> appendChild( $doc -> createElement( 'g:quantity', $product -> quantity ) );
- }
- else
- {
- if ( $product -> stock_0_buy )
- {
- $p_availability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'in stock' ) );
- $p_gquantity = $itemNode -> appendChild( $doc -> createElement( 'g:quantity', 999 ) );
- }
- else
- {
- $p_availability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'out of stock' ) );
- $p_gquantity = $itemNode -> appendChild( $doc -> createElement( 'g:quantity', 0 ) );
- }
- }
- }
-
- if ( $product_combination -> price_brutto )
- {
- $p_gprice = $itemNode -> appendChild( $doc -> createElement( 'g:price', $product_combination -> price_brutto . ' PLN' ) );
-
- if ( $product_combination -> price_brutto_promo )
- $p_gsale_price = $itemNode -> appendChild( $doc -> createElement( 'g:sale_price', $product_combination -> price_brutto_promo . ' PLN' ) );
- }
- else
- {
- $p_gprice = $itemNode -> appendChild( $doc -> createElement( 'g:price', $product -> price_brutto . ' PLN' ) );
-
- if ( $product -> price_brutto_promo )
- $p_gsale_price = $itemNode -> appendChild( $doc -> createElement( 'g:sale_price', $product -> price_brutto_promo . ' PLN' ) );
- }
-
- $p_gshipping = $itemNode -> appendChild( $doc -> createElement( 'g:shipping' ) );
- $p_gcountry = $p_gshipping -> appendChild( $doc -> createElement( 'g:country', 'PL' ) );
- $p_gservice = $p_gshipping -> appendChild( $doc -> createElement( 'g:service', '1 dzień roboczy' ) );
- $p_gprice = $p_gshipping -> appendChild( $doc -> createElement( 'g:price', ( new \Domain\Transport\TransportRepository( $mdb ) )->lowestTransportPrice( (int) $product -> wp ) . ' PLN' ) );
- }
- }
- }
- else
- {
- $itemNode = $channelNode -> appendChild( $doc -> createElement( 'item' ) );
- $p_gid = $itemNode -> appendChild( $doc -> createElement('g:id', $product -> id ) );
- $p_groupid = $itemNode -> appendChild( $doc -> createElement( 'g:item_group_id', $product -> id ) );
-
- if ( $product -> google_xml_label )
- $p_label = $itemNode -> appendChild($doc -> createElement('g:custom_label_0', $product -> google_xml_label ) );
-
- if ( $product -> language['xml_name'] )
- $p_title = $itemNode -> appendChild( $doc -> createElement( 'title', str_replace( '&', '&', $product -> language['xml_name'] ) ) );
- else
- $p_title = $itemNode -> appendChild( $doc -> createElement( 'title', str_replace( '&', '&', $product -> language['name'] ) ) );
-
- if ( $product -> ean )
- $p_gtin = $itemNode -> appendChild( $doc -> createElement( 'g:gtin', $product -> ean ) );
- else
- $p_gtin = $itemNode -> appendChild( $doc -> createElement( 'g:gtin', self::generate_EAN( $product -> id ) ) );
-
- // opis produktu
- if ( $product -> language['short_description'] )
- $p_description = $itemNode -> appendChild( $doc -> createElement( 'g:description', html_entity_decode( strip_tags( $product -> language['short_description'] ) ) ) );
- else
- $p_description = $itemNode -> appendChild( $doc -> createElement( 'g:description', html_entity_decode( strip_tags( $product -> language['name'] ) ) ) );
-
- if ( $product -> language['seo_link'] )
- $link = $domain_prefix . '://' . $url . '/' . \S::seo( $product -> language['seo_link'] );
- else
- $link = $domain_prefix . '://' . $url . '/' . 'p-' . $product -> id . '-' . \S::seo( $product -> language['name'] );
-
- $p_link = $itemNode -> appendChild( $doc -> createElement( 'link', $link ) );
-
- if ( $product -> custom_label_0 )
- $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_0', $product -> custom_label_0 ) );
-
- if ( $product -> custom_label_1 )
- $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_1', $product -> custom_label_1 ) );
-
- if ( $product -> custom_label_2 )
- $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_2', $product -> custom_label_2 ) );
-
- if ( $product -> custom_label_3 )
- $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_3', $product -> custom_label_3 ) );
-
- if ( $product -> custom_label_4 )
- $p_label = $itemNode -> appendChild( $doc -> createElement('g:custom_label_4', $product -> custom_label_4 ) );
-
- if ( $product -> images[0] )
- $p_gimage_link = $itemNode -> appendChild( $doc -> createElement( 'g:image_link', $domain_prefix . '://' . $url . $product -> images[0]['src'] ) );
-
- if ( count( $product -> images ) > 1 )
- {
- for ( $i = 1; $i < count( $product -> images ); ++$i )
- $p_gimage_link = $itemNode -> appendChild( $doc -> createElement( 'g:additional_image_link', $domain_prefix . '://' . $url . $product -> images[$i]['src'] ) );
- }
-
- $p_gcondition = $itemNode -> appendChild( $doc -> createElement( 'g:condition', 'new' ) );
-
- if ( $product -> quantity )
- {
- $p_gavailability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'in stock' ) );
- $p_gquantity = $itemNode -> appendChild( $doc -> createElement( 'g:quantity', $product -> quantity ) );
- }
- else
- {
- if ( $product -> stock_0_buy ) {
- $p_availability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'in stock' ) );
- $p_gquantity = $itemNode -> appendChild( $doc -> createElement( 'g:quantity', 999 ) );
- }
- else {
- $p_availability = $itemNode -> appendChild( $doc -> createElement( 'g:availability', 'out of stock' ) );
- $p_gquantity = $itemNode -> appendChild( $doc -> createElement( 'g:quantity', 0 ) );
- }
- }
-
- $p_gprice = $itemNode -> appendChild( $doc -> createElement( 'g:price', $product -> price_brutto . ' PLN' ) );
-
- if ( $product -> price_brutto_promo )
- $p_gsale_price = $itemNode -> appendChild( $doc -> createElement( 'g:sale_price', $product -> price_brutto_promo . ' PLN' ) );
-
- $p_gshipping = $itemNode -> appendChild( $doc -> createElement( 'g:shipping' ) );
- $p_gcountry = $p_gshipping -> appendChild( $doc -> createElement( 'g:country', 'PL' ) );
- $p_gservice = $p_gshipping -> appendChild( $doc -> createElement( 'g:service', '1 dzień roboczy' ) );
- $p_gprice = $p_gshipping -> appendChild( $doc -> createElement( 'g:price', ( new \Domain\Transport\TransportRepository( $mdb ) )->lowestTransportPrice( (int) $product -> wp ) . ' PLN' ) );
- }
- }
- file_put_contents('../google-feed.xml', $doc -> saveXML());
- }
-
- static public function count_product_combinations( int $product_id )
- {
- global $mdb;
-
- return $mdb -> count( 'pp_shop_products', [ 'parent_id' => $product_id ] );
- }
-
- // ajax_products_list
- static public function ajax_products_list_archive( $current_page = null, $query = null )
- {
- global $mdb;
-
- $search = '';
-
- if ( $query )
- {
- $query_array = [];
- parse_str( $query, $query_array );
-
- foreach ( $query_array as $key => $val ) {
- if ( $val !== '' )
- $search .= ' AND ' . $key . ' LIKE \'%' . $val . '%\'';
- }
- }
-
- $results = $mdb -> query( 'SELECT '
- . 'DISTINCT( psp.id )'
- . 'FROM '
- . 'pp_shop_products AS psp '
- . 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id '
- . 'WHERE archive = 1 AND parent_id IS NULL ' . $search . ' ORDER BY id DESC LIMIT ' . ( $current_page - 1 ) * 10 . ', 10' ) -> fetchAll( \PDO::FETCH_ASSOC );
- $results2 = $mdb -> query( 'SELECT '
- . 'COUNT( DISTINCT( psp.id ) ) AS products_count '
- . 'FROM '
- . 'pp_shop_products AS psp '
- . 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id '
- . 'WHERE archive = 1 AND parent_id IS NULL ' . $search ) -> fetchAll( \PDO::FETCH_ASSOC );
-
- if ( is_array( $results ) ) foreach ( $results as $row ) {
- $products[] = \admin\factory\ShopProduct::product_details( $row['id'] );
- }
-
- return [ 'products' => $products, 'products_count' => $results2[0]['products_count'] ];
- }
-
- // ajax_products_list
- static public function ajax_products_list( $current_page = null, $query = null )
- {
- global $mdb;
-
- if ( $query )
- {
- $search = '';
- $query_array = [];
-
- parse_str( $query, $query_array );
-
- foreach ( $query_array as $key => $val ) {
- if ( strpos( $key, '|' ) !== false )
- {
- $keys_tmp = explode( '|', $key );
- $search .= ' AND ( ';
- foreach ( $keys_tmp as $key_tmp )
- {
- if ( $key_tmp != reset( $keys_tmp ) )
- $search .= ' OR ' . $key_tmp . ' LIKE \'%' . $val . '%\'';
- else
- $search .= ' ' . $key_tmp . ' LIKE \'%' . $val . '%\'';
- }
- $search .= ' )';
- }
- else
- {
- $search .= ' AND ' . $key . ' LIKE \'%' . $val . '%\'';
- }
- }
-
- $results = $mdb -> query( 'SELECT '
- . 'DISTINCT( psp.id )'
- . 'FROM '
- . 'pp_shop_products AS psp '
- . 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id '
- . 'WHERE archive = 0 AND parent_id IS NULL ' . $search . ' ORDER BY id DESC LIMIT ' . ( $current_page - 1 ) * 10 . ', 10' ) -> fetchAll( \PDO::FETCH_ASSOC );
- $results2 = $mdb -> query( 'SELECT '
- . 'COUNT( DISTINCT( psp.id ) ) AS products_count '
- . 'FROM '
- . 'pp_shop_products AS psp '
- . 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id '
- . 'WHERE archive = 0 AND parent_id IS NULL ' . $search ) -> fetchAll( \PDO::FETCH_ASSOC );
- } else {
- $results = $mdb -> query( 'SELECT id FROM pp_shop_products WHERE parent_id IS NULL ORDER BY id DESC LIMIT ' . ( $current_page - 1 ) * 10 . ', 10' ) -> fetchAll( \PDO::FETCH_ASSOC );
- $results2 = $mdb -> query( 'SELECT COUNT( id ) AS products_count FROM pp_shop_products WHERE parent_id IS NULL' ) -> fetchAll( \PDO::FETCH_ASSOC );
- }
-
- if ( is_array( $results ) ) foreach ( $results as $row ) {
- $products[] = \admin\factory\ShopProduct::product_details( $row['id'] );
- }
-
- return [ 'products' => $products, 'products_count' => $results2[0]['products_count'] ];
- }
-
- public static function products_list()
- {
- global $mdb;
-
- $results = $mdb -> select( 'pp_shop_products', 'id', [ 'parent_id' => null ] );
- if ( is_array( $results ) ) foreach ( $results as $row )
- {
- $products[ $row ] = $mdb -> get ('pp_shop_products_langs', 'name', ['AND' => [ 'product_id' => $row, 'lang_id' => 'pl' ] ] );
- }
-
- return $products;
- }
-
- public static function images_order_save($product_id, $order)
- {
- global $mdb;
-
- $order = explode(';', $order);
- if (\is_array($order) && !empty($order))
- {
- foreach ($order as $image_id)
- {
- $mdb -> update('pp_shop_products_images', [
- 'o' => $i++,
- ], [
- 'AND' => [
- 'product_id' => $product_id,
- 'id' => $image_id,
- ],
- ]);
- }
- }
-
- return true;
- }
-
- public static function image_alt_change($image_id, $image_alt)
- {
- global $mdb;
- $result = $mdb -> update('pp_shop_products_images', [
- 'alt' => $image_alt,
- ], [
- 'id' => $image_id,
- ]);
- \S::delete_cache();
-
- return $result;
- }
-
- // product_unarchive
- static public function product_unarchive( int $product_id )
- {
- global $mdb;
-
- $mdb -> update( 'pp_shop_products', [ 'status' => 1, 'archive' => 0 ], [ 'id' => $product_id ] );
- $mdb -> update( 'pp_shop_products', [ 'status' => 1, 'archive' => 0 ], [ 'parent_id' => $product_id ] );
-
- return true;
- }
-
- static public function product_archive( int $product_id )
- {
- global $mdb;
-
- $mdb -> update( 'pp_shop_products', [ 'status' => 0, 'archive' => 1 ], [ 'id' => $product_id ] );
- $mdb -> update( 'pp_shop_products', [ 'status' => 0, 'archive' => 1 ], [ 'parent_id' => $product_id ] );
-
- return true;
- }
-
- public static function product_delete( int $product_id)
- {
- global $mdb;
-
- $mdb -> delete( 'pp_shop_products_categories', ['product_id' => $product_id ] );
- $mdb -> delete( 'pp_shop_products_langs', ['product_id' => $product_id ] );
- $mdb -> delete( 'pp_shop_products_images', ['product_id' => $product_id ] );
- $mdb -> delete( 'pp_shop_products_files', ['product_id' => $product_id ] );
- $mdb -> delete( 'pp_shop_products_attributes', ['product_id' => $product_id ] );
- $mdb -> delete( 'pp_shop_products', ['id' => $product_id ] );
- $mdb -> delete( 'pp_shop_product_sets_products', [ 'product_id' => $product_id ] );
- // pp_routes
- $mdb -> delete( 'pp_routes', [ 'product_id' => $product_id ] );
- // pp_redirects
- $mdb -> delete( 'pp_redirects', [ 'product_id' => $product_id ] );
-
- \S::delete_dir( '../upload/product_images/product_' . $product_id . '/' );
- \S::delete_dir( '../upload/product_files/product_' . $product_id . '/' );
-
- return true;
- }
-
- public static function product_categories($product_id)
- {
- global $mdb;
-
- $results = $mdb -> query('SELECT category_id FROM pp_shop_products_categories WHERE product_id = '.(int) $product_id) -> fetchAll();
- if (\is_array($results) && !empty($results))
- {
- foreach ($results as $row)
- {
- if ('' === $out)
- {
- $out .= ' - ';
- }
-
- $out .= ( new \Domain\Category\CategoryRepository( $mdb ) )->categoryTitle( (int) $row['category_id'] );
-
- if (end($results) !== $row)
- {
- $out .= ' / ';
- }
- }
- }
-
- return $out;
- }
-
- public static function max_order()
- {
- global $mdb;
-
- return $mdb -> max('pp_shop_products_categories', 'o');
- }
-
- public static function delete_img($image_id)
- {
- global $mdb;
- $mdb -> update('pp_shop_products_images', ['to_delete' => 1], ['id' => (int) $image_id]);
-
- return true;
- }
-
- public static function delete_nonassigned_images()
- {
- global $mdb;
-
- $results = $mdb -> select('pp_shop_products_images', '*', ['product_id' => null]);
- if (\is_array($results))
- {
- foreach ($results as $row)
- {
- if (file_exists('../'.$row['src']))
- {
- unlink('../'.$row['src']);
- }
- }
- }
-
- $mdb -> delete('pp_shop_products_images', ['product_id' => null]);
- }
-
- public static function file_name_change($file_id, $file_name)
- {
- global $mdb;
- $mdb -> update('pp_shop_products_files', ['name' => $file_name], ['id' => (int) $file_id]);
-
- return true;
- }
-
- public static function delete_file($file_id)
- {
- global $mdb;
- $mdb -> update('pp_shop_products_files', ['to_delete' => 1], ['id' => (int) $file_id]);
-
- return true;
- }
-
- public static function delete_nonassigned_files()
- {
- global $mdb;
-
- $results = $mdb -> select('pp_shop_products_files', '*', ['product_id' => null]);
- if (\is_array($results))
- {
- foreach ($results as $row)
- {
- if (file_exists('../'.$row['src']))
- {
- unlink('../'.$row['src']);
- }
- }
- }
-
- $mdb -> delete('pp_shop_products_files', ['product_id' => null]);
- }
-
- public static function save(
- $product_id, $name, $short_description, $description, $status, $meta_description, $meta_keywords, $seo_link, $copy_from, $categories, $price_netto, $price_brutto, $vat, $promoted, $warehouse_message_zero, $warehouse_message_nonzero, $tab_name_1, $tab_description_1, $tab_name_2, $tab_description_2, $layout_id, $products_related, int $set_id, $price_netto_promo, $price_brutto_promo, $new_to_date, $stock_0_buy, $wp, $custom_label_0, $custom_label_1, $custom_label_2, $custom_label_3, $custom_label_4, $additional_message, int $quantity, $additional_message_text, int $additional_message_required, $canonical, $meta_title, $producer_id, $sku, $ean, $product_unit, $weight, $xml_name, $custom_field_name, $custom_field_required, $security_information, $custom_field_type
- )
- {
- global $mdb, $user;
-
- if ( !$product_id )
- {
- $mdb -> insert('pp_shop_products', [
- 'date_add' => date('Y-m-d H:i:s'),
- 'date_modify' => date('Y-m-d H:i:s'),
- 'modify_by' => $user['id'],
- 'status' => 'on' === $status ? 1 : 0,
- 'price_netto' => ($price_netto && 0.00 !== $price_netto) ? $price_netto : null,
- 'price_brutto' => ($price_brutto && 0.00 !== $price_brutto) ? $price_brutto : null,
- 'vat' => $vat,
- 'promoted' => 'on' === $promoted ? 1 : 0,
- 'layout_id' => $layout_id ? $layout_id : null,
- 'price_netto_promo' => ($price_netto_promo && 0.00 !== $price_netto_promo) ? $price_netto_promo : null,
- 'price_brutto_promo' => ($price_brutto_promo && 0.00 !== $price_brutto_promo) ? $price_brutto_promo : null,
- 'new_to_date' => $new_to_date ? $new_to_date : null,
- 'stock_0_buy' => 'on' === $stock_0_buy ? 1 : 0,
- 'wp' => $wp ? $wp : null,
- 'sku' => $sku ? $sku : null,
- 'ean' => $ean ? $ean : null,
- 'custom_label_0' => $custom_label_0 ? $custom_label_0 : null,
- 'custom_label_1' => $custom_label_1 ? $custom_label_1 : null,
- 'custom_label_2' => $custom_label_2 ? $custom_label_2 : null,
- 'custom_label_3' => $custom_label_3 ? $custom_label_3 : null,
- 'custom_label_4' => $custom_label_4 ? $custom_label_4 : null,
- 'additional_message' => $additional_message == 'on' ? 1 : 0,
- 'set_id' => $set_id ? $set_id : null,
- 'quantity' => $quantity,
- 'additional_message_text' => $additional_message_text ? $additional_message_text : null,
- 'additional_message_required' => $additional_message_required,
- 'producer_id' => !empty( $producer_id ) ? $producer_id : null,
- 'product_unit_id' => !empty( $product_unit ) ? $product_unit : null,
- 'weight' => !empty( $weight ) ? $weight : null,
- ] );
-
- $id = $mdb -> id();
-
- if ( $id )
- {
- $langs = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->languagesList( true );
- foreach ( $langs as $lg )
- {
- $mdb -> insert( 'pp_shop_products_langs', [
- 'product_id' => (int) $id,
- 'lang_id' => $lg['id'],
- 'name' => $name[$lg['id']] ? $name[$lg['id']] : null,
- 'short_description' => $short_description[$lg['id']] ? $short_description[$lg['id']] : null,
- 'description' => $description[$lg['id']] ? $description[$lg['id']] : null,
- 'meta_description' => $meta_description[$lg['id']] ? $meta_description[$lg['id']] : null,
- 'meta_keywords' => $meta_keywords[$lg['id']] ? $meta_keywords[$lg['id']] : null,
- 'seo_link' => $seo_link[$lg['id']] ? \S::seo($seo_link[$lg['id']]) : null,
- 'copy_from' => $copy_from[$lg['id']] ? $copy_from[$lg['id']] : null,
- 'warehouse_message_zero' => $warehouse_message_zero[$lg['id']] ? $warehouse_message_zero[$lg['id']] : null,
- 'warehouse_message_nonzero' => $warehouse_message_nonzero[$lg['id']] ? $warehouse_message_nonzero[$lg['id']] : null,
- 'tab_name_1' => $tab_name_1[$lg['id']] ? $tab_name_1[$lg['id']] : null,
- 'tab_description_1' => $tab_description_1[$lg['id']] ? $tab_description_1[$lg['id']] : null,
- 'tab_name_2' => $tab_name_2[$lg['id']] ? $tab_name_2[$lg['id']] : null,
- 'tab_description_2' => $tab_description_2[$lg['id']] ? $tab_description_2[$lg['id']] : null,
- 'canonical' => $canonical[$lg['id']] ? $canonical[$lg['id']] : null,
- 'meta_title' => $meta_title[$lg['id']] ? $meta_title[$lg['id']] : null,
- 'xml_name' => $xml_name[$lg['id']] ? $xml_name[$lg['id']] : null,
- 'security_information' => $security_information[$lg['id']] ? $security_information[$lg['id']] : null,
- ] );
- }
-
- if ( is_array($categories))
- {
- foreach ($categories as $category)
- {
- $order = self::max_order() + 1;
-
- $mdb -> insert('pp_shop_products_categories', [
- 'product_id' => (int) $id,
- 'category_id' => (int) $category,
- 'o' => (int) $order,
- ]);
- }
- }
- elseif ($categories)
- {
- $order = self::max_order() + 1;
-
- $mdb -> insert('pp_shop_products_categories', [
- 'product_id' => (int) $id,
- 'category_id' => (int) $categories,
- 'o' => (int) $order,
- ]);
- }
-
- if (\is_array($products_related))
- {
- foreach ($products_related as $product_related )
- {
- $mdb -> insert('pp_shop_products_related', [
- 'product_id' => (int) $id,
- 'product_related_id' => (int) $product_related,
- ]);
- }
- }
- elseif ( $products_related )
- {
- $mdb -> insert('pp_shop_products_related', [
- 'product_id' => (int) $id,
- 'product_related_id' => (int) $products_related,
- ]);
- }
-
- $created = false;
-
- $results = $mdb -> select('pp_shop_products_files', '*', ['product_id' => null]);
- if (\is_array($results))
- {
- foreach ($results as $row)
- {
- $dir = '/upload/product_files/product_'.$id;
-
- $new_file_name = str_replace('/upload/product_files/tmp', $dir, $row['src']);
-
- if (file_exists('..'.$row['src']))
- {
- if (!is_dir('../'.$dir) && true !== $created)
- {
- if (mkdir('../'.$dir, 0755, true))
- {
- $created = true;
- }
- }
- rename('..'.$row['src'], '..'.$new_file_name);
- }
-
- $mdb -> update('pp_shop_products_files', ['src' => $new_file_name, 'product_id' => $id], ['id' => $row['id']]);
- }
- }
-
- $created = false;
-
- $results = $mdb -> select('pp_shop_products_images', '*', ['product_id' => null]);
- if (\is_array($results))
- {
- foreach ($results as $row)
- {
- $dir = '/upload/product_images/product_'.$id;
-
- $new_file_name = str_replace('/upload/product_images/tmp', $dir, $row['src']);
-
- if (file_exists('../'.$new_file_name))
- {
- $ext = strrpos($new_file_name, '.');
- $fileName_a = substr($new_file_name, 0, $ext);
- $fileName_b = substr($new_file_name, $ext);
-
- $count = 1;
-
- while (file_exists('../'.$fileName_a.'_'.$count.$fileName_b))
- {
- ++$count;
- }
-
- $new_file_name = $fileName_a.'_'.$count.$fileName_b;
- }
-
- if (file_exists('..'.$row['src']))
- {
- if (!is_dir('../'.$dir) && true !== $created)
- {
- if (mkdir('../'.$dir, 0755, true))
- {
- $created = true;
- }
- }
- rename('..'.$row['src'], '..'.$new_file_name);
- }
-
- $mdb -> update('pp_shop_products_images', ['src' => $new_file_name, 'product_id' => (int) $id], ['id' => $row['id']]);
- }
- }
-
- // dodatkowe pola
- for ( $i = 0; $i < count( $custom_field_name ); ++$i )
- {
- if ( !empty( $custom_field_name[$i] ) )
- {
- $custom_field = $custom_field_name[$i];
- $custom_field_type_data = $custom_field_type[$i];
- $custom_field_required = isset( $custom_field_required[$i] ) ? 1 : 0;
-
- $mdb -> insert( 'pp_shop_products_custom_fields', [
- 'id_product' => (int) $id,
- 'name' => $custom_field,
- 'type' => $custom_field_type_data,
- 'is_required' => $custom_field_required,
- ] );
- }
- }
-
- \S::htacces();
-
- \S::delete_dir('../temp/');
- \S::delete_dir('../thumbs/');
-
- return $id;
- }
- }
- else
- {
- $mdb -> update( 'pp_shop_products', [
- 'date_modify' => date('Y-m-d H:i:s'),
- 'modify_by' => $user['id'],
- 'status' => 'on' === $status ? 1 : 0,
- 'price_netto' => ($price_netto && 0.00 !== $price_netto) ? $price_netto : null,
- 'price_brutto' => ($price_brutto && 0.00 !== $price_brutto) ? $price_brutto : null,
- 'vat' => $vat,
- 'promoted' => 'on' === $promoted ? 1 : 0,
- 'layout_id' => $layout_id ? $layout_id : null,
- 'price_netto_promo' => ($price_netto_promo && 0.00 !== $price_netto_promo) ? $price_netto_promo : null,
- 'price_brutto_promo' => ($price_brutto_promo && 0.00 !== $price_brutto_promo) ? $price_brutto_promo : null,
- 'new_to_date' => $new_to_date ? $new_to_date : null,
- 'stock_0_buy' => 'on' === $stock_0_buy ? 1 : 0,
- 'wp' => $wp ? $wp : null,
- 'sku' => $sku ? $sku : null,
- 'ean' => $ean ? $ean : null,
- 'custom_label_0' => $custom_label_0 ? $custom_label_0 : null,
- 'custom_label_1' => $custom_label_1 ? $custom_label_1 : null,
- 'custom_label_2' => $custom_label_2 ? $custom_label_2 : null,
- 'custom_label_3' => $custom_label_3 ? $custom_label_3 : null,
- 'custom_label_4' => $custom_label_4 ? $custom_label_4 : null,
- 'additional_message' => $additional_message == 'on' ? 1 : 0,
- 'set_id' => $set_id ? $set_id : null,
- 'quantity' => $quantity,
- 'additional_message_text' => $additional_message_text ? $additional_message_text : null,
- 'additional_message_required' => $additional_message_required,
- 'producer_id' => !empty( $producer_id ) ? $producer_id : null,
- 'product_unit_id' => !empty( $product_unit ) ? $product_unit : null,
- 'weight' => !empty( $weight ) ? $weight : null,
- ], [
- 'id' => (int) $product_id,
- ] );
-
- $mdb -> update( 'pp_shop_products', [
- 'additional_message' => $additional_message == 'on' ? 1 : 0,
- ], [
- 'parent_id' => (int) $product_id,
- ] );
-
- \admin\factory\ShopProduct::update_product_combinations_prices( $product_id, $price_brutto, $vat, $price_brutto_promo );
-
- $langs = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->languagesList( true );
- foreach ( $langs as $lg )
- {
- if ( $translation_id = $mdb -> get( 'pp_shop_products_langs', 'id', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $lg['id'] ] ] ) )
- {
- $current_seo_link = $mdb -> get( 'pp_shop_products_langs', 'seo_link', [ 'id' => $translation_id ] );
-
- if ( $seo_link[$lg['id']] )
- $new_seo_link = \S::seo( $seo_link[$lg['id']] );
- else
- $new_seo_link = \S::seo( 'p-' . $product_id . '-' . $name[$lg['id']] );
-
- if ( $new_seo_link !== $current_seo_link and $current_seo_link != '' )
- {
- if ( $mdb -> count( 'pp_redirects', [ 'from' => $new_seo_link, 'to' => $current_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] ) )
- $mdb -> delete( 'pp_redirects', [ 'from' => $new_seo_link, 'to' => $current_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] );
-
- $mdb -> delete( 'pp_redirects', [
- 'AND' => [
- 'product_id' => $product_id,
- 'lang_id' => $lg['id'],
- 'from' => $current_seo_link,
- 'to[!]' => $new_seo_link,
- ],
- ] );
-
- if ( !self::seoLinkUsedByOtherProduct( (int) $product_id, (string) $lg['id'], (string) $current_seo_link ) )
- {
- self::removeConflictingRedirectSources( (int) $product_id, (string) $lg['id'], (string) $current_seo_link );
-
- if ( !$mdb -> count( 'pp_redirects', [ 'from' => $current_seo_link, 'to' => $new_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] ) )
- {
- if ( \S::canAddRedirect( $current_seo_link, $new_seo_link, $lg['id'] ) )
- $mdb -> insert( 'pp_redirects', [ 'from' => $current_seo_link, 'to' => $new_seo_link, 'lang_id' => $lg['id'], 'product_id' => $product_id ] );
- }
- }
- else
- $mdb -> delete( 'pp_redirects', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $lg['id'], 'from' => $current_seo_link ] ] );
- }
-
- $mdb -> update( 'pp_shop_products_langs', [
- 'name' => $name[$lg['id']] ? $name[$lg['id']] : null,
- 'short_description' => $short_description[$lg['id']] ? $short_description[$lg['id']] : null,
- 'description' => $description[$lg['id']] ? $description[$lg['id']] : null,
- 'meta_description' => $meta_description[$lg['id']] ? $meta_description[$lg['id']] : null,
- 'meta_keywords' => $meta_keywords[$lg['id']] ? $meta_keywords[$lg['id']] : null,
- 'seo_link' => $seo_link[$lg['id']] ? \S::seo($seo_link[$lg['id']]) : null,
- 'copy_from' => $copy_from[$lg['id']] ? $copy_from[$lg['id']] : null,
- 'warehouse_message_zero' => $warehouse_message_zero[$lg['id']] ? $warehouse_message_zero[$lg['id']] : null,
- 'warehouse_message_nonzero' => $warehouse_message_nonzero[$lg['id']] ? $warehouse_message_nonzero[$lg['id']] : null,
- 'tab_name_1' => $tab_name_1[$lg['id']] ? $tab_name_1[$lg['id']] : null,
- 'tab_description_1' => $tab_description_1[$lg['id']] ? $tab_description_1[$lg['id']] : null,
- 'tab_name_2' => $tab_name_2[$lg['id']] ? $tab_name_2[$lg['id']] : null,
- 'tab_description_2' => $tab_description_2[$lg['id']] ? $tab_description_2[$lg['id']] : null,
- 'canonical' => $canonical[$lg['id']] ? $canonical[$lg['id']] : null,
- 'meta_title' => $meta_title[$lg['id']] ? $meta_title[$lg['id']] : null,
- 'xml_name' => $xml_name[$lg['id']] ? $xml_name[$lg['id']] : null,
- 'security_information' => $security_information[$lg['id']] ? $security_information[$lg['id']] : null,
- ], [
- 'id' => $translation_id
- ] );
- }
- else
- {
- $mdb -> insert( 'pp_shop_products_langs', [
- 'product_id' => (int) $product_id,
- 'lang_id' => $lg['id'],
- 'name' => $name[$lg['id']] ? $name[$lg['id']] : null,
- 'short_description' => $short_description[$lg['id']] ? $short_description[$lg['id']] : null,
- 'description' => $description[$lg['id']] ? $description[$lg['id']] : null,
- 'meta_description' => $meta_description[$lg['id']] ? $meta_description[$lg['id']] : null,
- 'meta_keywords' => $meta_keywords[$lg['id']] ? $meta_keywords[$lg['id']] : null,
- 'seo_link' => $seo_link[$lg['id']] ? \S::seo($seo_link[$lg['id']]) : null,
- 'copy_from' => $copy_from[$lg['id']] ? $copy_from[$lg['id']] : null,
- 'warehouse_message_zero' => $warehouse_message_zero[$lg['id']] ? $warehouse_message_zero[$lg['id']] : null,
- 'warehouse_message_nonzero' => $warehouse_message_nonzero[$lg['id']] ? $warehouse_message_nonzero[$lg['id']] : null,
- 'tab_name_1' => $tab_name_1[$lg['id']] ? $tab_name_1[$lg['id']] : null,
- 'tab_description_1' => $tab_description_1[$lg['id']] ? $tab_description_1[$lg['id']] : null,
- 'tab_name_2' => $tab_name_2[$lg['id']] ? $tab_name_2[$lg['id']] : null,
- 'tab_description_2' => $tab_description_2[$lg['id']] ? $tab_description_2[$lg['id']] : null,
- 'canonical' => $canonical[$lg['id']] ? $canonical[$lg['id']] : null,
- 'meta_title' => $meta_title[$lg['id']] ? $meta_title[$lg['id']] : null,
- 'xml_name' => $xml_name[$lg['id']] ? $xml_name[$lg['id']] : null,
- 'security_information' => $security_information[$lg['id']] ? $security_information[$lg['id']] : null,
- ] );
- }
- }
-
- $not_in = [0];
-
- if (\is_array($categories))
- {
- foreach ($categories as $category)
- {
- $not_in[] = $category;
- }
- }
- elseif ($categories)
- {
- $not_in[] = $categories;
- }
-
- $mdb -> delete('pp_shop_products_categories', ['AND' => ['product_id' => (int) $product_id, 'category_id[!]' => $not_in]]);
-
- $categories_tmp = $mdb -> select('pp_shop_products_categories', 'category_id', ['product_id' => (int) $product_id]);
-
- if (!\is_array($categories))
- {
- $categories = [$categories];
- }
-
- $categories = array_diff($categories, $categories_tmp);
-
- if (\is_array($categories))
- {
- foreach ($categories as $category)
- {
- $order = self::max_order() + 1;
-
- if ( $product_id and $category )
- $mdb -> insert( 'pp_shop_products_categories', [
- 'product_id' => (int)$product_id,
- 'category_id' => (int)$category,
- 'o' => (int) $order,
- ] );
- }
- }
-
- // produkty powiązane
- $not_in = [0];
-
- if (\is_array($products_related))
- {
- foreach ($products_related as $product_related)
- {
- $not_in[] = $product_related;
- }
- }
- elseif ($products_related)
- {
- $not_in[] = $products_related;
- }
-
- $mdb -> delete('pp_shop_products_related', ['AND' => ['product_id' => (int) $product_id, 'product_related_id[!]' => $not_in]]);
-
- $products_related_tmp = $mdb -> select('pp_shop_products_related', 'product_related_id', ['product_id' => (int) $product_id]);
-
- if (!\is_array($products_related))
- {
- $products_related = [$products_related];
- }
-
- $products_related = array_diff($products_related, $products_related_tmp);
-
- if (\is_array($products_related))
- {
- foreach ($products_related as $product_related)
- {
- if ($product_id && $product_related)
- {
- $mdb -> insert('pp_shop_products_related', [
- 'product_id' => (int) $product_id,
- 'product_related_id' => (int) $product_related,
- ]);
- }
- }
- }
-
- $created = false;
-
- $results = $mdb -> select('pp_shop_products_files', '*', ['product_id' => null]);
- if (\is_array($results))
- {
- foreach ($results as $row)
- {
- $dir = '/upload/product_files/product_'.$product_id;
-
- $new_file_name = str_replace('/upload/product_files/tmp', $dir, $row['src']);
-
- if (file_exists('..'.$row['src']))
- {
- if (!is_dir('../'.$dir) && true !== $created)
- {
- if (mkdir('../'.$dir, 0755, true))
- {
- $created = true;
- }
- }
- rename('..'.$row['src'], '..'.$new_file_name);
- }
-
- $mdb -> update('pp_shop_products_files', ['src' => $new_file_name, 'product_id' => (int) $product_id], ['id' => $row['id']]);
- }
- }
-
- $results = $mdb -> select('pp_shop_products_files', '*', ['AND' => ['product_id' => (int) $product_id, 'to_delete' => 1]]);
- if (\is_array($results))
- {
- foreach ($results as $row)
- {
- if (file_exists('../'.$row['src']))
- {
- unlink('../'.$row['src']);
- }
- }
- }
-
- $mdb -> delete('pp_shop_products_files', ['AND' => ['product_id' => (int) $product_id, 'to_delete' => 1]]);
-
- $created = false;
-
- // zdjęcia
- $results = $mdb -> select('pp_shop_products_images', '*', ['product_id' => null]);
- if (\is_array($results))
- {
- foreach ($results as $row)
- {
- $dir = '/upload/product_images/product_'.$product_id;
-
- $new_file_name = str_replace('/upload/product_images/tmp', $dir, $row['src']);
-
- if (file_exists('../'.$new_file_name))
- {
- $ext = strrpos($new_file_name, '.');
- $fileName_a = substr($new_file_name, 0, $ext);
- $fileName_b = substr($new_file_name, $ext);
-
- $count = 1;
-
- while (file_exists('../'.$fileName_a.'_'.$count.$fileName_b))
- {
- ++$count;
- }
-
- $new_file_name = $fileName_a.'_'.$count.$fileName_b;
- }
-
- if (file_exists('..'.$row['src']))
- {
- if (!is_dir('../'.$dir) && true !== $created)
- {
- if (mkdir('../'.$dir, 0755, true))
- {
- $created = true;
- }
- }
- rename('..'.$row['src'], '..'.$new_file_name);
- }
-
- $mdb -> update('pp_shop_products_images', ['src' => $new_file_name, 'product_id' => (int) $product_id], ['id' => $row['id']]);
- }
- }
-
- $results = $mdb -> select('pp_shop_products_images', '*', ['AND' => ['product_id' => (int) $product_id, 'to_delete' => 1]]);
- if (\is_array($results))
- {
- foreach ($results as $row)
- {
- if (file_exists('../'.$row['src']))
- {
- unlink('../'.$row['src']);
- }
- }
- }
-
- $mdb -> delete('pp_shop_products_images', ['AND' => ['product_id' => (int) $product_id, 'to_delete' => 1]]);
-
- // dodatkowe pola
- // delete only custom fields that are not in the new list
- foreach ( $custom_field_name as $custom_field )
- {
- if ( !empty( $custom_field ) )
- {
- $exits_custom_ids[] = (int)$mdb -> get( 'pp_shop_products_custom_fields', 'id_additional_field', [ 'AND' => [ 'id_product' => $product_id, 'name' => $custom_field ] ] );
- }
- }
-
- $mdb -> delete( 'pp_shop_products_custom_fields', [ 'AND' => [ 'id_product' => $product_id, 'id_additional_field[!]' => $exits_custom_ids ] ] );
-
- // $custom_field_name i $custom_field_required
- foreach ( $custom_field_name as $i => $custom_field )
- {
- if ( !empty( $custom_field ) )
- {
- $custom_field_type_data = $custom_field_type[$i];
- $is_required = !empty( $custom_field_required[$i] ) ? 1 : 0;
-
- if ( !$mdb -> count( 'pp_shop_products_custom_fields', [ 'AND' => [ 'id_product' => $product_id, 'name' => $custom_field ] ] ) )
- {
- $mdb -> insert( 'pp_shop_products_custom_fields', [
- 'id_product' => $product_id,
- 'name' => $custom_field,
- 'type' => $custom_field_type_data,
- 'is_required' => $is_required
- ]);
- }
- else
- {
- $mdb -> update( 'pp_shop_products_custom_fields',
- [
- 'type' => $custom_field_type_data,
- 'is_required' => $is_required
- ],
- [ 'AND' => [ 'id_product' => $product_id, 'name' => $custom_field ] ]);
- }
- }
- }
-
- \S::htacces();
-
- \S::delete_dir( '../temp/' );
- \S::delete_dir( '../thumbs/' );
-
- $redis = \RedisConnection::getInstance() -> getConnection();
- if ( $redis )
- $redis -> flushAll();
-
- return $product_id;
- }
- }
-
- // pobierz prostą listę z ilościami produktu
- static public function get_product_quantity_list( int $product_id )
- {
- global $mdb;
-
- return $mdb -> get( 'pp_shop_products', 'quantity', [ 'id' => $product_id ] );
- }
-
- // ADMIN - szczególy produktu
- static public function product_details( int $product_id )
- {
- global $mdb;
-
- if ( $product = $mdb -> get( 'pp_shop_products', '*', [ 'id' => $product_id ] ) )
- {
- $results = $mdb -> select( 'pp_shop_products_langs', '*', [ 'product_id' => $product_id ] );
- if ( is_array( $results ) ) foreach ($results as $row)
- $product['languages'][ $row['lang_id'] ] = $row;
-
- $product['images'] = $mdb -> select( 'pp_shop_products_images', '*', [ 'product_id' => $product_id, 'ORDER' => [ 'o' => 'ASC', 'id' => 'ASC' ] ] );
- $product['files'] = $mdb -> select( 'pp_shop_products_files', '*', [ 'product_id' => $product_id ] );
- $product['categories'] = $mdb -> select( 'pp_shop_products_categories', 'category_id', [ 'product_id' => $product_id ] );
- $product['attributes'] = $mdb -> select( 'pp_shop_products_attributes', [ 'attribute_id', 'value_id' ], [ 'product_id' => $product_id ] );
- $product['products_related'] = $mdb -> select( 'pp_shop_products_related', 'product_related_id', [ 'product_id' => $product_id ] );
- $product['custom_fields'] = $mdb -> select( 'pp_shop_products_custom_fields', '*', [ 'id_product' => $product_id ] );
- }
-
- return $product;
- }
-
- // duplikowanie produktu w panelu administratora
- static public function duplicate_product( int $product_id, int $with_combinations = 0 )
- {
- global $mdb;
-
- $product = $mdb -> get( 'pp_shop_products', '*', [ 'id' => $product_id ] );
- if ( $product )
- {
- $mdb -> insert( 'pp_shop_products', [
- 'price_netto' => $product['price_netto'],
- 'price_brutto' => $product['price_brutto'],
- 'price_netto_promo' => $product['price_netto_promo'],
- 'price_brutto_promo' => $product['price_brutto_promo'],
- 'vat' => $product['vat'],
- 'promoted' => $product['promoted'],
- 'layout_id' => $product['layout_id'],
- 'new_to_date' => $product['new_to_date'],
- 'stock_0_buy' => $product['stock_0_buy'],
- 'wp' => $product['wp'],
- 'custom_label_0' => $product['custom_label_0'],
- 'custom_label_1' => $product['custom_label_1'],
- 'custom_label_2' => $product['custom_label_2'],
- 'custom_label_3' => $product['custom_label_3'],
- 'custom_label_4' => $product['custom_label_4'],
- 'additional_message' => $product['additional_message']
- ] );
-
- $new_product_id = $mdb -> id();
- if ( $new_product_id )
- {
- $attributes = $mdb -> select( 'pp_shop_products_attributes', '*', [ 'product_id' => $product_id ] );
- if ( \S::is_array_fix( $attributes ) ) foreach ( $attributes as $row )
- {
- $mdb -> insert( 'pp_shop_products_attributes', [
- 'product_id' => $new_product_id,
- 'attribute_id' => $row['attribute_id'],
- 'value_id' => $row['value_id']
- ] );
- }
-
- $categories = $mdb -> select( 'pp_shop_products_categories', '*', [ 'product_id' => $product_id ] );
- if ( \S::is_array_fix( $categories ) ) foreach ( $categories as $row )
- {
- $mdb -> insert( 'pp_shop_products_categories', [
- 'product_id' => $new_product_id,
- 'category_id' => $row['category_id'],
- 'o' => $row['o']
- ] );
- }
-
- $langs = $mdb -> select( 'pp_shop_products_langs', '*', [ 'product_id' => $product_id ] );
- if ( \S::is_array_fix( $langs ) ) foreach ( $langs as $row )
- {
- $mdb -> insert( 'pp_shop_products_langs', [
- 'product_id' => $new_product_id,
- 'lang_id' => $row['lang_id'],
- 'name' => $row['name'] . ' - kopia',
- 'short_description' => $row['short_description'],
- 'description' => $row['description'],
- 'tab_name_1' => $row['tab_name_1'],
- 'tab_description_1' => $row['tab_description_1'],
- 'tab_name_2' => $row['tab_name_2'],
- 'tab_description_2' => $row['tab_description_2'],
- 'meta_description' => $row['meta_description'],
- 'meta_keywords' => $row['meta_keywords'],
- 'copy_from' => $row['copy_from'],
- 'warehouse_message_zero' => $row['warehouse_message_zero'],
- 'warehouse_message_nonzero' => $row['warehouse_message_nonzero']
- ] );
- }
-
- // custom fields
- $custom_fields = $mdb -> select( 'pp_shop_products_custom_fields', '*', [ 'id_product' => $product_id ] );
- if ( \S::is_array_fix( $custom_fields ) ) foreach ( $custom_fields as $row )
- {
- $mdb -> insert( 'pp_shop_products_custom_fields', [
- 'id_product' => $new_product_id,
- 'name' => $row['name']
- ] );
- }
- }
-
- // duplikowanie kombinacji produktu
- if ( $with_combinations )
- {
- $product_combinations = $mdb -> select( 'pp_shop_products', '*', [ 'parent_id' => $product_id ] );
- foreach ( $product_combinations as $product_combination )
- {
- $mdb -> insert( 'pp_shop_products', [
- 'parent_id' => $new_product_id,
- 'permutation_hash' => $product_combination['permutation_hash'],
- 'price_netto' => $product_combination['price_netto'],
- 'price_brutto' => $product_combination['price_brutto'],
- 'price_netto_promo' => $product_combination['price_netto_promo'],
- 'price_brutto_promo' => $product_combination['price_brutto_promo'],
- 'vat' => $product_combination['vat'],
- 'stock_0_buy' => $product_combination['stock_0_buy'],
- 'quantity' => $product_combination['quantity'],
- 'wp' => $product_combination['wp'],
- 'additional_message' => $product_combination['additional_message'],
- 'additional_message_text' => $product_combination['additional_message_text'],
- 'additional_message_required' => $product_combination['additional_message_required']
- ] );
-
- $combination_id = $mdb -> id();
- if ( $combination_id )
- {
- $pp_shop_products_attributes = $mdb -> select( 'pp_shop_products_attributes', '*', [ 'product_id' => $product_combination['id'] ] );
- foreach ( $pp_shop_products_attributes as $pp_shop_products_attribute )
- {
- $mdb -> insert( 'pp_shop_products_attributes', [
- 'product_id' => $combination_id,
- 'attribute_id' => $pp_shop_products_attribute['attribute_id'],
- 'value_id' => $pp_shop_products_attribute['value_id']
- ] );
- }
- }
- }
- }
-
- return true;
- }
- return false;
- }
-
- //
- // KOMBINACJE PRODUKTU
- //
-
- static public function product_combination_stock_0_buy_save( int $product_id, $stock_0_buy )
- {
- global $mdb;
- return $mdb -> update( 'pp_shop_products', [ 'stock_0_buy' => $stock_0_buy == 'true' ? 1 : 0 ], [ 'id' => $product_id ] );
- }
-
- static public function product_combination_sku_save( int $product_id, $sku )
- {
- global $mdb;
- return $mdb -> update( 'pp_shop_products', [ 'sku' => $sku ], [ 'id' => $product_id ] );
- }
-
- static public function product_combination_quantity_save( int $product_id, $quantity )
- {
- global $mdb;
- return $mdb -> update( 'pp_shop_products', [ 'quantity' => $quantity == '' ? $quantity = null : $quantity = (int) $quantity ], [ 'id' => $product_id ] );
- }
-
- static public function product_combination_price_save( int $product_id, $price_netto )
- {
- global $mdb;
-
- $vat = $mdb -> get( 'pp_shop_products', 'vat', [ 'id' => $product_id ] );
-
- $price_brutto = $price_netto * ( 1 + ( $vat / 100 ) );
-
- return $mdb -> update( 'pp_shop_products', [ 'price_netto' => $price_netto == '' ? $price_netto = null : $price_netto = (float) $price_netto, 'price_brutto' => $price_brutto == '' ? $price_brutto = null : $price_brutto = (float) $price_brutto ], [ 'id' => $product_id ] );
- }
-
- // aktualizacja ceny produktu pod wpływem zmiany ceny wartości atrybutu
- static public function update_product_price_by_attribute_value_impact( $value_id, $impact_on_the_price )
- {
- global $mdb;
-
- $products = $mdb -> select( 'pp_shop_products_attributes', [ 'product_id' ], [ 'value_id' => $value_id ] );
- if ( is_array( $products ) ) foreach ( $products as $row )
- {
- $parent_id = $mdb -> get( 'pp_shop_products', 'parent_id', [ 'id' => $row['product_id'] ] );
- $product = $mdb -> get( 'pp_shop_products', '*', [ 'id' => $parent_id ] );
- if ( $product )
- {
- $price_brutto = $product['price_brutto'] + \S::normalize_decimal( $impact_on_the_price );
- $price_netto = \S::normalize_decimal( $price_brutto / ( 1 + ( $product['vat'] / 100 ) ) );
-
- if ( $product['price_netto_promo'] )
- {
- $price_brutto_promo = $product['price_brutto_promo'] + \S::normalize_decimal( $impact_on_the_price );
- $price_netto_promo = \S::normalize_decimal( $price_brutto_promo / ( 1 + ( $product['vat'] / 100 ) ) );
- }
- else
- {
- $price_netto_promo = null;
- $price_brutto_promo = null;
- }
-
- if ( $impact_on_the_price > 0 )
- {
- $mdb -> update( 'pp_shop_products', [ 'price_netto' => $price_netto, 'price_brutto' => $price_brutto, 'price_netto_promo' => $price_netto_promo, 'price_brutto_promo' => $price_brutto_promo, 'date_modify' => date( 'Y-m-d H:i:s' ) ], [ 'id' => $row['product_id'] ] );
- }
- else if ( isset( $impact_on_the_price ) && $impact_on_the_price == 0 && $impact_on_the_price != '' )
- {
- $mdb -> update( 'pp_shop_products', [ 'price_netto' => null, 'price_brutto' => null, 'price_netto_promo' => null, 'price_brutto_promo' => null, 'quantity' => null, 'stock_0_buy' => null, 'date_modify' => date( 'Y-m-d H:i:s' ) ], [ 'id' => $row['product_id'] ] );
- }
- }
- }
- }
-}
diff --git a/temp/update_build/tmp_0.275/docs/CHANGELOG.md b/temp/update_build/tmp_0.275/docs/CHANGELOG.md
deleted file mode 100644
index 2bc9e81..0000000
--- a/temp/update_build/tmp_0.275/docs/CHANGELOG.md
+++ /dev/null
@@ -1,458 +0,0 @@
-# Changelog shopPRO
-
-Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze.
-
----
-
-## ver. 0.275 (2026-02-15) - ShopCategory
-
-- **ShopCategory** - migracja `/admin/shop_category/*` na Domain + DI + nowe endpointy AJAX
- - NOWE: `Domain\Category\CategoryRepository` (`sortTypes`, `subcategories`, `categoryDetails`, `categoryProducts`, `save`, `categoryDelete`, `saveCategoriesOrder`, `saveProductOrder`, `categoryTitle`)
- - NOWE: `admin\Controllers\ShopCategoryController` (DI) z akcjami `list/view_list`, `edit/category_edit`, `save`, `delete/category_delete`, `products/category_products`, `category_url_browser`, `save_categories_order`, `save_products_order`, `cookie_categories`
- - UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ShopCategory`
- - UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_category/list/`
- - UPDATE: widoki `shop-category/*` - wydzielenie skryptow do `*-custom-script.php`, ujednolicone strzalki drzewa (`button + caret + aria-expanded`)
- - UPDATE: AJAX drzewek przepiety z `/admin/ajax.php?a=*` na `/admin/shop_category/*`
- - UPDATE: zaleznosci `ShopProduct` przepiete z `admin\factory\ShopCategory` na `Domain\Category\CategoryRepository`
- - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopCategory.php`, `autoload/admin/factory/class.ShopCategory.php`, `autoload/admin/view/class.ShopCategory.php`
- - CLEANUP: usuniety preload `class.ShopCategory.php` z `libraries/grid/config.php`
-- TEST:
- - NOWE: `tests/Unit/Domain/Category/CategoryRepositoryTest.php`
- - NOWE: `tests/Unit/admin/Controllers/ShopCategoryControllerTest.php`
- - Testy punktowe: **OK (16 tests, 72 assertions)**
-
----
-
-## ver. 0.274 (2026-02-15) - ShopProduct mass_edit + UI trees
-
-- **ShopProduct (mass_edit)** - migracja akcji masowej edycji na Domain + DI
- - NOWE: `admin\Controllers\ShopProductController` (DI) z akcjami `mass_edit`, `mass_edit_save`, `get_products_by_category`
- - UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ShopProduct`
- - UPDATE: `Domain\Product\ProductRepository` rozszerzone o metody `allProductsForMassEdit`, `getProductsByCategory`, `applyDiscountPercent` (+ aktualizacja cen kombinacji)
- - CLEANUP: usuniete legacy akcje `mass_edit`, `mass_edit_save`, `get_products_by_category` z `admin\controls\ShopProduct`
-- **ShopProduct mass_edit UI** - przebudowa widoku i skryptu
- - UPDATE: `admin/templates/shop-product/mass-edit.php` przepiety na nowy partial JS `mass-edit-custom-script`
- - NOWE: `admin/templates/shop-product/mass-edit-custom-script.php` (nestedSortable + iCheck + stabilizacja drzewka)
- - UPDATE: `admin/templates/shop-product/subcategories-list.php` ujednolicone strzalki (button + caret)
- - FIX: zaznaczenie kategorii w drzewku nie zaznacza automatycznie produktow na liscie
-- **Pages / Articles UI** - ujednolicenie drzewek
- - UPDATE: `/admin/pages/list/` - nowe strzalki drzewa + `aria-expanded` + odswiezanie stanu branch/leaf
- - UPDATE: `/admin/articles/edit/*` (zakladka wyswietlania) - nowe strzalki i checkboxy (iCheck) dla drzewka stron
-- **ShopClients** - migracja `/admin/shop_clients` na Domain + DI + nowe widoki
- - NOWE: `Domain\Client\ClientRepository` (`listForAdmin`, `ordersForClient`, `totalsForClient`)
- - NOWE: `admin\Controllers\ShopClientsController` (DI) z akcjami `list`, `details` + aliasy legacy `view_list`, `clients_details`
- - UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ShopClients`
- - UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_clients/list/`
- - UPDATE: widoki `shop-clients/view-list` i `shop-clients/clients-details` przepiete na `components/table-list`
- - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopClients.php`, `autoload/admin/factory/class.ShopClients.php`
-- TEST:
- - NOWE: `tests/Unit/admin/Controllers/ShopProductControllerTest.php`
- - NOWE: `tests/Unit/Domain/Client/ClientRepositoryTest.php`, `tests/Unit/admin/Controllers/ShopClientsControllerTest.php`
- - UPDATE: `tests/Unit/Domain/Product/ProductRepositoryTest.php` (nowe przypadki dla mass_edit)
- - UPDATE: `tests/bootstrap.php` (stub `S::normalize_decimal()`)
-- Testy: **OK (361 tests, 1125 assertions)**
-
----
-
-## ver. 0.273 (2026-02-15) - ShopProducer
-
-- **ShopProducer** - migracja `/admin/shop_producer` na Domain + DI + nowe widoki
- - NOWE: `Domain\Producer\ProducerRepository` (`listForAdmin`, `find`, `save`, `delete`, `allProducers`, `findForFrontend`, `producerProducts`, `allActiveIds`)
- - NOWE: `admin\Controllers\ShopProducerController` (DI) z akcjami `list`, `edit`, `save`, `delete`
- - UPDATE: modul `/admin/shop_producer/*` dziala na `components/table-list` i `components/form-edit` z zakladkami jezykowymi (Opis + SEO)
- - UPDATE: routing i menu admin na kanoniczny URL `/admin/shop_producer/list/`
- - UPDATE: `shop\Producer` przepiety na fasade do `Domain\Producer\ProducerRepository`
- - UPDATE: `admin\factory\ShopProduct` - 2 wywolania `admin\factory\ShopTransport` przepiete na `Domain\Transport\TransportRepository`
- - UPDATE: `admin\controls\ShopProduct` - usuniety fallback do `admin\factory\Layouts`
- - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopProducer.php`, `admin/templates/shop-producer/list.php`, `admin/templates/shop-producer/edit.php`
- - CLEANUP: usuniete 6 pustych factory facades: `admin\factory\Languages`, `admin\factory\Newsletter`, `admin\factory\Scontainers`, `admin\factory\ShopProducer`, `admin\factory\ShopTransport`, `admin\factory\Layouts`
- - TEST: dodane `tests/Unit/Domain/Producer/ProducerRepositoryTest.php` i `tests/Unit/admin/Controllers/ShopProducerControllerTest.php`
-- Testy: **OK (338 tests, 1063 assertions)**
-
----
-
-## ver. 0.272 (2026-02-15) - ShopProductSets
-
-- **ShopProductSets** - migracja `/admin/shop_product_sets` na Domain + DI + nowe widoki
- - NOWE: `Domain\ProductSet\ProductSetRepository` (`listForAdmin`, `find`, `save`, `delete`, `allSets`, `allProductsMap`)
- - NOWE: `admin\Controllers\ShopProductSetsController` (DI) z akcjami `list`, `edit`, `save`, `delete`
- - UPDATE: modul `/admin/shop_product_sets/*` dziala na `components/table-list` i `components/form-edit` + Selectize multi-select produktow
- - UPDATE: routing i menu admin na kanoniczny URL `/admin/shop_product_sets/list/`
- - UPDATE: `shop\ProductSet` przepiety na fasade do `Domain\ProductSet\ProductSetRepository`
- - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopProductSets.php`, `autoload/admin/factory/class.ShopProductSet.php`, `admin/templates/shop-product-sets/view-list.php`, `admin/templates/shop-product-sets/set-edit.php`
- - TEST: dodane `tests/Unit/Domain/ProductSet/ProductSetRepositoryTest.php` i `tests/Unit/admin/Controllers/ShopProductSetsControllerTest.php`
-- Testy: **OK (324 tests, 1000 assertions)**
-
----
-
-## ver. 0.271 (2026-02-14) - ShopAttribute
-
-- **ShopAttribute** - migracja `/admin/shop_attribute` na Domain + DI + nowe widoki
- - NOWE: `Domain\Attribute\AttributeRepository` (`listForAdmin`, `findAttribute`, `saveAttribute`, `deleteAttribute`, `findValues`, `saveValues`, `saveLegacyValues`, `valueDetails`)
- - NOWE: `admin\Controllers\ShopAttributeController` (DI) z akcjami `list`, `edit`, `save`, `delete`, `values`, `values_save`, `value_row_tpl`
- - UPDATE: modul `/admin/shop_attribute/*` dziala na `components/table-list` i `components/form-edit`
- - UPDATE: nowy edytor wartosci cechy (`values-edit`) z walidacja serwerowa i stabilnym `row_key` (bez indeksow do wyboru domyslnej wartosci)
- - UPDATE: routing i menu admin na kanoniczny URL `/admin/shop_attribute/list/` (bez aliasow legacy)
- - UPDATE: przepiecie zaleznosci kombinacji produktu (`admin\controls\ShopProduct`, `admin\factory\ShopProduct`, `admin/templates/shop-product/product-combination.php`) na `Domain\Attribute\AttributeRepository` i `shop\ProductAttribute`
- - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopAttribute.php`, `autoload/admin/factory/class.ShopAttribute.php`, `autoload/admin/view/class.ShopAttribute.php`, `admin/templates/shop-attribute/_partials/value.php`
- - TEST: dodane `tests/Unit/Domain/Attribute/AttributeRepositoryTest.php` i `tests/Unit/admin/Controllers/ShopAttributeControllerTest.php`
-- Testy: **OK (312 tests, 948 assertions)**
-
----
-
-## ver. 0.270 (2026-02-14) - Apilo payment/status sync hardening
-
-- **Shop/Order + Apilo** - utwardzenie synchronizacji platnosci i statusow zamowien
- - FIX: `shop\Order::set_as_paid()` wysyla do Apilo mapowany typ platnosci (`payment_method_id` -> `apilo_payment_type_id`) zamiast stalego `type = 1`
- - NOWE: retry queue dla chwilowej niedostepnosci Apilo (`temp/apilo-sync-queue.json`) dla sync platnosci i statusu
- - NOWE: `shop\Order::process_apilo_sync_queue()` przetwarza zalegle syncy
- - UPDATE: `cron.php` uruchamia przetwarzanie kolejki sync Apilo przy aktywnej integracji
- - UPDATE: rozszerzone logowanie debug (`logs/apilo.txt`) o HTTP code i bledy cURL dla sync platnosci/statusu
-- Testy: **OK (300 tests, 895 assertions)**
-
----
-
-## ver. 0.269 (2026-02-14) - ShopTransport
-
-- **ShopTransport** - migracja `/admin/shop_transport` na Domain + DI + nowe widoki
- - NOWE: `Domain\Transport\TransportRepository` (`listForAdmin`, `find`, `save`, `allActive`, `allForAdmin`, `findActiveById`, `getTransportCost`, `lowestTransportPrice`, `getApiloCarrierAccountId`)
- - NOWE: `admin\Controllers\ShopTransportController` (DI) z akcjami `list`, `edit`, `save`
- - NOWE: widoki `shop-transport/transports-list.php` i `shop-transport/transport-edit.php` + `transport-edit-custom-script.php`
- - UPDATE: routing i menu admin na kanoniczny URL `/admin/shop_transport/list/`
- - UPDATE: `admin\factory\ShopTransport`, `front\factory\ShopTransport` przepiete na nowe repozytorium
- - FIX: `save()` return type `?int` zamiast `int|bool` (spojnosc z PaymentMethod)
- - FIX: `toSwitchValue()` helper zamiast `=== 'on'` (obsluga '1', 'on', 'true', 'yes')
- - FIX: `\S::delete_dir()` przeniesione z repozytorium do kontrolera (DDD)
- - FIX: Medoo `select()` syntax - ORDER w WHERE zamiast 4-arg form
- - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopTransport.php`, `autoload/admin/view/class.ShopTransport.php`, `admin/templates/shop-transport/view-list.php`
- - FIX: `transports-list.php` - zmienna `'viewModel'` zmieniona na `'list'` (zgodnie z `table-list.php` komponentem)
-- Testy: **OK (300 tests, 895 assertions)**
-
----
-
-## ver. 0.268 (2026-02-14) - ShopPaymentMethod + Apilo token keepalive
-
-- **ShopPaymentMethod** - migracja `/admin/shop_payment_method` na Domain + DI + nowe widoki
- - NOWE: `Domain\PaymentMethod\PaymentMethodRepository` (`listForAdmin`, `find`, `save`, `allActive`, `allForAdmin`, `findActiveById`, `isActive`, `getApiloPaymentTypeId`, `forTransport`)
- - NOWE: `admin\Controllers\ShopPaymentMethodController` (DI) z akcjami `list`, `edit`, `save`
- - NOWE: widoki `shop-payment-method/payment-methods-list.php` i `shop-payment-method/payment-method-edit.php`
- - UPDATE: routing i menu admin na kanoniczny URL `/admin/shop_payment_method/list/`
- - UPDATE: `admin\controls\ShopTransport`, `front\factory\ShopPaymentMethod`, `shop\PaymentMethod` przepiete na nowe repozytorium
- - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopPaymentMethod.php`, `autoload/admin/factory/class.ShopPaymentMethod.php`, `autoload/admin/view/class.ShopPaymentMethod.php`, `admin/templates/shop-payment-method/view-list.php`
-- **Integrations/Apilo** - stabilizacja tokenu i lepszy feedback
- - NOWE: automatyczne odswiezanie tokenu Apilo przed wygasnieciem (`apiloKeepalive`, refresh lead time)
- - UPDATE: cron uruchamia keepalive i odswieza konfiguracje Apilo
- - UPDATE: bardziej szczegolowe komunikaty bledow dla przyciskow integracji Apilo (co zrobic dalej)
-- Testy: **OK (280 tests, 828 assertions)**
-
----
-
-## ver. 0.267 (2026-02-14) - ShopStatuses
-
-- **ShopStatuses** - migracja `/admin/shop_statuses` na Domain + DI + nowe widoki
- - NOWE: `Domain\ShopStatus\ShopStatusRepository` (`listForAdmin`, `find`, `save`, `getApiloStatusId`, `getByIntegrationStatusId`, `allStatuses`)
- - NOWE: `admin\Controllers\ShopStatusesController` (DI) z akcjami `list`, `edit`, `save` (bez aliasow legacy)
- - NOWE: typ pola `FormFieldType::COLOR` + `FormField::color()` + `FormFieldRenderer::renderColor()` (color picker HTML5 zsynchronizowany z polem tekstowym)
- - UPDATE: modul `/admin/shop_statuses/*` dziala na `components/table-list` i `components/form-edit`
- - UPDATE: `front\factory\ShopStatuses` jako fasada delegujaca do `Domain\ShopStatus\ShopStatusRepository`
- - UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_statuses/list/`
- - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopStatuses.php`, `autoload/admin/factory/class.ShopStatuses.php`
- - UWAGA: statusy maja ID od 0 - kluczowe dla walidacji (find/save uzywaja `$id < 0`)
-- Testy: **OK (254 tests, 736 assertions)**
-
----
-
-## ver. 0.266 (2026-02-13) - ShopCoupon
-
-- **ShopCoupon** - migracja `/admin/shop_coupon` na Domain + DI + nowe widoki
- - NOWE: `Domain\Coupon\CouponRepository` (`listForAdmin`, `find`, `save`, `delete`, `categoriesTree`)
- - NOWE: `admin\Controllers\ShopCouponController` (DI) z akcjami `list`, `edit`, `save`, `delete`
- - UPDATE: kompatybilnosc aliasow legacy (`view_list`, `coupon_edit`, `coupon_save`, `coupon_delete`) obslugiwana przez nowy kontroler
- - UPDATE: modul `/admin/shop_coupon/*` dziala na `components/table-list` i `components/form-edit`
- - NOWE: widoki/partiale `shop-coupon/coupons-list`, `shop-coupon/coupon-edit-new`, `shop-coupon/coupon-categories-selector`, `shop-coupon/coupon-categories-tree`, `shop-coupon/coupon-edit-custom-script`
- - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopCoupon.php`, `autoload/admin/factory/class.ShopCoupon.php`, `admin/templates/shop-coupon/view-list.php`, `admin/templates/shop-coupon/coupon-edit.php`
- - UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_coupon/list/`
- - FIX: ujednolicone UI drzewek i checkboxow miedzy kuponami i layoutami
-- Testy: **OK (235 tests, 682 assertions)**
-
----
-
-## ver. 0.265 (2026-02-13) - ShopPromotion poprawki
-
-- **ShopPromotion** - stabilizacja po migracji
- - UPDATE: dodane `date_from` w `Domain\Promotion\PromotionRepository` (save/find/list/sort)
- - UPDATE: `admin\Controllers\ShopPromotionController` rozszerzony o pole `Data od` na formularzu i kolumne `Data od` na liscie
- - UPDATE: `shop\Promotion::get_active_promotions()` filtruje aktywnosc po `date_from` i `date_to`
- - FIX: zapis edycji promocji nie tworzy nowego rekordu (hidden `id` + fallback `id` z URL)
- - TEST: rozszerzono `PromotionRepositoryTest` o asercje `date_from`
-- Testy: **OK (222 tests, 614 assertions)**
-
----
-
-## ver. 0.264 (2026-02-13) - ShopPromotion
-
-- **ShopPromotion** - migracja `/admin/shop_promotion` na Domain + DI + nowe widoki
- - NOWE: `Domain\Promotion\PromotionRepository` (`listForAdmin`, `find`, `save`, `delete`, `categoriesTree`, invalidacja cache aktywnych promocji)
- - NOWE: `admin\Controllers\ShopPromotionController` (DI) z akcjami `list`, `edit`, `save`, `delete`
- - UPDATE: routing DI (`admin\Site`) rozszerzony o modul `ShopPromotion`
- - UPDATE: modul `/admin/shop_promotion/*` dziala na `components/table-list` i `components/form-edit`
- - NOWE: widoki/partiale `shop-promotion/promotions-list`, `shop-promotion/promotion-edit`, `shop-promotion/promotion-categories-selector`, `shop-promotion/promotion-categories-tree`, `shop-promotion/promotion-edit-custom-script`
- - CLEANUP: usuniete legacy `autoload/admin/controls/class.ShopPromotion.php`, `autoload/admin/factory/class.ShopPromotion.php`, `admin/templates/shop-promotion/view-list.php`
- - UPDATE: menu admin przepiete na kanoniczny URL `/admin/shop_promotion/list/`
-- Testy: **OK (222 tests, 609 assertions)**
-
----
-
-## ver. 0.263 (2026-02-13) - Integrations + cleanup Sellasist/Baselinker
-
-- NOWE: `Domain\Integrations\IntegrationsRepository` (settings Apilo/ShopPRO, OAuth, product linking, API fetch)
-- NOWE: `admin\Controllers\IntegrationsController` (DI) dla akcji Apilo i ShopPRO
-- UPDATE: `admin\factory\Integrations` jako fasada delegujaca do repozytorium
-- **CLEANUP: usunieto integracje Sellasist i Baselinker z calego projektu:**
- - Usuniete klasy: `admin\controls\Integrations`, `admin\controls\Baselinker`, `admin\factory\Baselinker`, `front\factory\Shop`, `shop\ShopStatus`
- - Usuniete szablony: `integrations/sellasist-settings.php`, `integrations/baselinker-settings.php`, `admin/templates/baselinker/`
- - Wyczyszczone referencje w: `cron.php`, `cron/cron-xml.php`, `shop\Order`, kontrolery/factory/front Shop*
-- Testy: **OK (212 tests, 577 assertions)**
-
----
-
-## ver. 0.262 (2026-02-13) - Pages
-
-- NOWE: `Domain\Pages\PagesRepository` (CRUD menu/stron, drzewo stron, sortowanie, SEO)
-- NOWE: `admin\Controllers\PagesController` (DI) dla akcji menu/page/AJAX
-- UPDATE: widoki `admin/templates/pages/*` przepiete na dane z kontrolera/repozytorium
-- UPDATE: endpointy AJAX przepiete z `admin/ajax.php?a=*` na `/admin/pages/*`
-- CLEANUP: usuniete legacy `controls/Pages`, `view/Pages`, `factory/Pages`, `ajax/pages.php`
-- Testy: **OK (186 tests, 478 assertions)**
-
----
-
-## ver. 0.261 (2026-02-13) - Articles (dalsza refaktoryzacja)
-
-- UPDATE: `Domain\Article\ArticleRepository` rozszerzone o metody UI/admin i `saveFilesOrder()`
-- UPDATE: `admin\Controllers\ArticlesController` obsluguje AJAX: `article_image_alt_change`, `article_file_name_change`, `article_image_delete`, `article_file_delete`, `filesOrderSave`
-- UPDATE: lista artykulow nie korzysta juz z `admin\factory\Articles::article_pages()`
-- UPDATE: widok edycji przepiety z `/admin/ajax.php` na `/admin/articles/*`
-- UPDATE: drag&drop sortowania listy zalacznikow
-- CLEANUP: usuniete `autoload/admin/view/class.Articles.php` i `admin/ajax/articles.php`
-- Testy: **OK (178 tests, 443 assertions)**
-
----
-
-## ver. 0.260 (2026-02-12) - ArticlesArchive
-
-- NOWE: `admin\Controllers\ArticlesArchiveController` (DI)
-- UPDATE: `Domain\Article\ArticleRepository` rozszerzone o `listArchivedForAdmin()`, `restore()`, `deletePermanently()`
-- UPDATE: `/admin/articles_archive/view_list/` migrowane na `components/table-list`
-- CLEANUP: usuniete legacy `controls/ArticlesArchive`, `factory/ArticlesArchive`, `view/ArticlesArchive`
-- Testy: **OK (165 tests, 424 assertions)**
-
----
-
-## ver. 0.259 (2026-02-12) - Scontainers
-
-- NOWE: `Domain\Scontainers\ScontainersRepository` (listForAdmin, find, save, delete, detailsForLanguage)
-- NOWE: `admin\Controllers\ScontainersController` (DI)
-- UPDATE: `/admin/scontainers/*` migrowane na `components/table-list` i `components/form-edit`
-- UPDATE: `admin\factory\Scontainers` i `front\factory\Scontainers` jako fasady
-- CLEANUP: usuniete `controls/Scontainers`, `view/Scontainers`
-- Testy: **OK (158 tests, 397 assertions)**
-
----
-
-## ver. 0.258 (2026-02-12) - Newsletter (stabilizacja)
-
-- UPDATE: tymczasowo wylaczono flow `prepare/send/preview` (wymaga przebudowy)
-- UPDATE: tymczasowo wylaczono modul `Szablony uzytkownika`
-- UPDATE: aktywna obsluga tylko szablonow administracyjnych (`is_admin = 1`)
-- CLEANUP: usuniete nieuzywane widoki `prepare.php`, `preview.php`, `email-templates-user.php`
-
----
-
-## ver. 0.257 (2026-02-12) - Newsletter
-
-- NOWE: `Domain\Newsletter\NewsletterRepository` (subskrybenci, szablony, ustawienia, kolejka wysylki)
-- NOWE: `Domain\Newsletter\NewsletterPreviewRenderer` (render podgladu)
-- NOWE: `admin\Controllers\NewsletterController` (DI)
-- UPDATE: `/admin/newsletter/*` migrowane na `components/table-list` i `components/form-edit`
-- UPDATE: `admin\factory\Newsletter` jako fasada; `front\factory\Newsletter` bez `admin\view\Newsletter`
-- CLEANUP: usuniete `controls/Newsletter`, `view/Newsletter`
-- Testy: **OK (150 tests, 372 assertions)**
-
----
-
-## ver. 0.256 (2026-02-12) - Layouts
-
-- NOWE: `Domain\Layouts\LayoutsRepository` (find, save, delete, listForAdmin, menusWithPages, categoriesTree)
-- NOWE: `admin\Controllers\LayoutsController` (DI)
-- UPDATE: lista `/admin/layouts/view_list/` migrowana na `components/table-list`
-- UPDATE: widok `layouts/layout-edit` korzysta z danych z repozytorium
-- NOWE: partial `admin/templates/layouts/subcategories-list.php`
-- UPDATE: `Domain\Languages\LanguagesRepository::defaultLanguageId()` jako wspolna metoda
-- UPDATE: `ArticlesController` korzysta z `LayoutsRepository` (DI)
-- CLEANUP: usuniete `controls/Layouts`, `view/Layouts`; `factory/Layouts` jako fasada
-- Testy: **OK (141 tests, 336 assertions)**
-
----
-
-## ver. 0.255 (2026-02-12) - Languages DI cleanup
-
-- UPDATE: SettingsController, BannerController, DictionariesController, ArticlesController pobieraja liste jezykow przez `Domain/Languages/LanguagesRepository` (DI)
-- UPDATE: router DI przekazuje `LanguagesRepository` do kontrolerow
-- UPDATE: legacy `admin/controls`, `admin/factory/Shop*` przepiete na `LanguagesRepository`
-- FIX: `admin/factory/class.Languages.php` poprawione na ` | Kolumny dynamiczne per jezyk (np. pl, en) |
-
-**Uzywane w:** `Domain\\Languages\\LanguagesRepository`, `admin\\Controllers\\LanguagesController`, `front\\factory\\Languages`
-
-**Aktualizacja 2026-02-12:** modul jezykow i tlumaczen (`pp_langs`, `pp_langs_translations`) obslugiwany przez `Domain\\Languages\\LanguagesRepository`.
-
-## pp_layouts
-Szablony layoutow (HTML/CSS/JS + flagi domyslne).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| name | Nazwa szablonu |
-| html | Kod HTML |
-| css | Kod CSS |
-| js | Kod JS |
-| m_html | Kod HTML mobilny |
-| m_css | Kod CSS mobilny |
-| m_js | Kod JS mobilny |
-| status | Domyslny layout stron (0/1) |
-| categories_default | Domyslny layout kategorii (0/1) |
-
-**Uzywane w:** `Domain\\Layouts\\LayoutsRepository`, `admin\\Controllers\\LayoutsController`, `front\\factory\\Layouts`
-
-## pp_layouts_pages
-Przypisanie layoutow do stron CMS.
-
-| Kolumna | Opis |
-|---------|------|
-| layout_id | FK do pp_layouts |
-| page_id | FK do pp_pages |
-
-**Uzywane w:** `Domain\\Layouts\\LayoutsRepository`, `front\\factory\\Layouts`
-
-## pp_layouts_categories
-Przypisanie layoutow do kategorii sklepu.
-
-| Kolumna | Opis |
-|---------|------|
-| layout_id | FK do pp_layouts |
-| category_id | FK do pp_shop_categories |
-
-**Uzywane w:** `Domain\\Layouts\\LayoutsRepository`, `front\\factory\\Layouts`
-
-**Aktualizacja 2026-02-12 (ver. 0.256):** modul `/admin/layouts` korzysta z `Domain\\Layouts\\LayoutsRepository` (DI kontroler + fasada legacy).
-
-## pp_newsletter
-Adresy e-mail zapisane do newslettera.
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| email | Adres e-mail subskrybenta |
-| hash | Hash potwierdzenia/wypisu |
-| status | 1 = potwierdzony, 0 = oczekujacy |
-
-**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `front\\factory\\Newsletter`
-
-## pp_newsletter_send
-Kolejka wysylki newslettera.
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| email | Adres docelowy |
-| dates | Zakres dat artykulow (tekst) |
-| id_template | FK do `pp_newsletter_templates` (NULL gdy brak szablonu) |
-
-**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `front\\factory\\Newsletter::newsletter_send()`
-
-## pp_newsletter_templates
-Szablony tresci e-maili (uzytkownik + administracyjne/systemowe).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| name | Nazwa/klucz szablonu |
-| text | Tresc HTML szablonu |
-| is_admin | 1 = szablon administracyjny/systemowy, 0 = szablon uzytkownika |
-
-**Uzywane w:** `Domain\\Newsletter\\NewsletterRepository`, `admin\\Controllers\\NewsletterController`, `front\\factory\\Newsletter`
-
-**Aktualizacja 2026-02-12 (ver. 0.257):** modul `/admin/newsletter` korzysta z `Domain\\Newsletter\\NewsletterRepository` (DI kontroler + fasada legacy).
-
-## pp_scontainers
-Kontenery statyczne (modul /admin/scontainers).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| status | 1 = aktywny, 0 = nieaktywny |
-| show_title | 1 = pokaz tytul, 0 = ukryj tytul |
-
-**Uzywane w:** `Domain\Scontainers\ScontainersRepository`, `admin\Controllers\ScontainersController`, `front\factory\Scontainers`
-
-## pp_scontainers_langs
-Tlumaczenia kontenerow statycznych (per jezyk).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| container_id | FK do pp_scontainers |
-| lang_id | ID jezyka (np. pl, en) |
-| title | Tytul kontenera |
-| text | Tresc HTML kontenera |
-
-**Uzywane w:** `Domain\Scontainers\ScontainersRepository`, `front\factory\Scontainers`
-
-**Aktualizacja 2026-02-12 (ver. 0.259):** modul `/admin/scontainers` korzysta z `Domain\Scontainers\ScontainersRepository` (DI kontroler + fasada legacy).
-
-**Aktualizacja 2026-02-12 (ver. 0.260):** modul `/admin/articles_archive` korzysta z `Domain\Article\ArticleRepository` (`listArchivedForAdmin`, `restore`, `deletePermanently`) przez `admin\Controllers\ArticlesArchiveController`.
-
-## pp_shop_attributes
-Cechy produktu (modul `/admin/shop_attribute`).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| status | Status: 1 = aktywny, 0 = nieaktywny |
-| type | Typ cechy: 0 = tekst, 1 = kolor, 2 = wzor |
-| o | Kolejnosc wyswietlania |
-
-**Uzywane w:** `Domain\Attribute\AttributeRepository`, `admin\Controllers\ShopAttributeController`, `admin\controls\ShopProduct`, `admin\factory\ShopProduct`
-
-## pp_shop_attributes_langs
-Tlumaczenia cech produktu (per jezyk).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| attribute_id | FK do pp_shop_attributes |
-| lang_id | ID jezyka (np. pl, en) |
-| name | Nazwa cechy |
-
-**Uzywane w:** `Domain\Attribute\AttributeRepository`, `shop\ProductAttribute`
-
-## pp_shop_attributes_values
-Wartosci cech produktu.
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| attribute_id | FK do pp_shop_attributes |
-| is_default | Czy wartosc domyslna dla cechy (0/1) |
-| impact_on_the_price | Wplyw na cene wariantu (NULL = brak) |
-
-**Uzywane w:** `Domain\Attribute\AttributeRepository`, `admin\Controllers\ShopAttributeController`, `admin\factory\ShopProduct`
-
-## pp_shop_attributes_values_langs
-Tlumaczenia wartosci cech (per jezyk).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| value_id | FK do pp_shop_attributes_values |
-| lang_id | ID jezyka (np. pl, en) |
-| name | Nazwa wyswietlana |
-| value | Wewnetrzna wartosc techniczna (opcjonalna) |
-
-**Uzywane w:** `Domain\Attribute\AttributeRepository`, `shop\ProductAttribute`
-
-## pp_shop_products_attributes
-Powiazanie kombinacji produktow z wartosciami cech.
-
-| Kolumna | Opis |
-|---------|------|
-| product_id | FK do pp_shop_products (kombinacja) |
-| value_id | FK do pp_shop_attributes_values |
-
-**Uzywane w:** `Domain\Attribute\AttributeRepository::refreshCombinationPricesForValue()`, `admin\controls\ShopProduct`, `admin\factory\ShopProduct`
-
-**Aktualizacja 2026-02-14 (ver. 0.271):** modul `/admin/shop_attribute` korzysta z `Domain\Attribute\AttributeRepository` przez `admin\Controllers\ShopAttributeController`. Usunieto legacy klasy `admin\controls\ShopAttribute`, `admin\factory\ShopAttribute`, `admin\view\ShopAttribute`.
-
-## pp_shop_coupon
-Kody rabatowe sklepu (modul `/admin/shop_coupon`).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| name | Kod kuponu (UNIQUE) |
-| status | Status: 1 = aktywny, 0 = nieaktywny |
-| send | Czy kupon zostal wyslany (0/1) |
-| used | Czy kupon zostal wykorzystany (0/1) |
-| date_used | Data wykorzystania kuponu (NULL gdy brak) |
-| used_count | Licznik uzyc kuponu |
-| type | Typ kuponu (obecnie: 1 = rabat procentowy na koszyk) |
-| amount | Wartosc kuponu (np. procent) |
-| one_time | Czy kupon jednorazowy (0/1) |
-| include_discounted_product | Czy obejmuje rowniez produkty przecenione (0/1) |
-| categories | JSON z ID kategorii objetych kuponem (NULL = bez ograniczenia) |
-
-**Uzywane w:** `Domain\Coupon\CouponRepository`, `admin\Controllers\ShopCouponController`, `shop\Coupon`, `front\factory\ShopCoupon`, `front\factory\ShopOrder`
-
-**Aktualizacja 2026-02-13 (ver. 0.266):** modul `/admin/shop_coupon` korzysta z `Domain\Coupon\CouponRepository` przez `admin\Controllers\ShopCouponController`. Usunieto legacy klasy `admin\controls\ShopCoupon` i `admin\factory\ShopCoupon`.
-
-## pp_shop_promotion
-Promocje sklepu (modul `/admin/shop_promotion`).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| name | Nazwa promocji |
-| status | Status: 1 = aktywna, 0 = nieaktywna |
-| condition_type | Typ warunku promocji (slownik w `shop\Promotion::$condition_type`) |
-| discount_type | Typ rabatu (slownik w `shop\Promotion::$discount_type`) |
-| amount | Wartosc rabatu (np. procent) |
-| date_from | Data startu promocji (NULL = aktywna od razu) |
-| date_to | Data konca promocji (NULL = bez daty konca) |
-| categories | JSON z ID kategorii grupy I |
-| condition_categories | JSON z ID kategorii grupy II |
-| include_coupon | Czy laczyc z kuponami rabatowymi (0/1) |
-| include_product_promo | Czy uwzgledniac produkty przecenione (0/1) |
-| min_product_count | Minimalna liczba produktow (dla wybranych warunkow) |
-| price_cheapest_product | Cena najtanszego produktu (dla wybranych warunkow) |
-
-**Uzywane w:** `Domain\Promotion\PromotionRepository`, `admin\Controllers\ShopPromotionController`, `shop\Promotion`, `front\factory\ShopPromotion`
-
-**Aktualizacja 2026-02-13:** modul `/admin/shop_promotion` korzysta z `Domain\Promotion\PromotionRepository` przez `admin\Controllers\ShopPromotionController`. Usunieto legacy klasy `admin\controls\ShopPromotion` i `admin\factory\ShopPromotion`.
-
-**Aktualizacja 2026-02-13 (ver. 0.265):** dodano obsluge `date_from` (repozytorium, formularz admin, lista admin, filtr aktywnych promocji na froncie) oraz poprawke zapisu edycji promocji po `id`.
-
-## pp_shop_payment_methods
-Metody platnosci sklepu (modul `/admin/shop_payment_method`).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| name | Nazwa metody platnosci |
-| description | Opis metody platnosci (wyswietlany m.in. w checkout) |
-| status | Status: 1 = aktywna, 0 = nieaktywna |
-| apilo_payment_type_id | ID typu platnosci Apilo (NULL gdy brak mapowania) |
-| sellasist_payment_type_id | DEPRECATED (integracja Sellasist usunieta w ver. 0.263) |
-
-**Uzywane w:** `Domain\PaymentMethod\PaymentMethodRepository`, `admin\Controllers\ShopPaymentMethodController`, `front\factory\ShopPaymentMethod`, `shop\PaymentMethod`, `admin\controls\ShopTransport`, `cron.php`
-
-**Aktualizacja 2026-02-14 (ver. 0.268):** modul `/admin/shop_payment_method` korzysta z `Domain\PaymentMethod\PaymentMethodRepository` przez `admin\Controllers\ShopPaymentMethodController`. Usunieto legacy klasy `admin\controls\ShopPaymentMethod`, `admin\factory\ShopPaymentMethod`, `admin\view\ShopPaymentMethod` oraz widok `admin/templates/shop-payment-method/view-list.php`.
-
-## pp_shop_transports
-Rodzaje transportu sklepu (modul `/admin/shop_transport`).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| name | Nazwa (systemowa, readonly) |
-| name_visible | Nazwa widoczna dla klienta |
-| description | Opis metody transportu |
-| status | Status: 1 = aktywny, 0 = nieaktywny |
-| cost | Koszt dostawy (PLN) |
-| max_wp | Maksymalna waga paczki (NULL = bez limitu) |
-| default | Domyslna forma dostawy (0/1) |
-| delivery_free | Czy obsluguje darmowa dostawe (0/1) |
-| apilo_carrier_account_id | ID konta przewoznika w Apilo (NULL gdy brak mapowania) |
-| o | Kolejnosc wyswietlania |
-
-**Uzywane w:** `Domain\Transport\TransportRepository`, `admin\Controllers\ShopTransportController`, `front\factory\ShopTransport`
-
-## pp_shop_transport_payment_methods
-Powiazanie metod transportu z metodami platnosci (tabela lacznikowa).
-
-| Kolumna | Opis |
-|---------|------|
-| id_transport | FK do pp_shop_transports |
-| id_payment_method | FK do pp_shop_payment_methods |
-
-**Uzywane w:** `Domain\Transport\TransportRepository`, `Domain\PaymentMethod\PaymentMethodRepository::forTransport()`
-
-**Aktualizacja 2026-02-14 (ver. 0.269):** modul `/admin/shop_transport` korzysta z `Domain\Transport\TransportRepository` przez `admin\Controllers\ShopTransportController`. Usunieto legacy klasy `admin\controls\ShopTransport`, `admin\view\ShopTransport` oraz widok `admin/templates/shop-transport/view-list.php`.
-
-## pp_shop_apilo_settings
-Ustawienia integracji Apilo (key-value).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| name | Klucz ustawienia (np. client-id, access-token) |
-| value | Wartosc ustawienia |
-
-**Uzywane w:** `Domain\Integrations\IntegrationsRepository`, `admin\Controllers\IntegrationsController`, `admin\factory\Integrations`
-
-## pp_shop_shoppro_settings
-Ustawienia integracji ShopPRO (key-value).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| name | Klucz ustawienia (np. domain, db_name) |
-| value | Wartosc ustawienia |
-
-**Uzywane w:** `Domain\Integrations\IntegrationsRepository`, `admin\Controllers\IntegrationsController`, `admin\factory\Integrations`
-
-**Aktualizacja 2026-02-13:** modul `/admin/integrations/` korzysta z `Domain\Integrations\IntegrationsRepository` (DI kontroler + fasada legacy). Usunieto integracje Sellasist i Baselinker.
-
-## pp_shop_statuses
-Statusy zamowien sklepu (modul `/admin/shop_statuses`). Statusy sa predefiniowane - brak dodawania/usuwania, mozliwa edycja koloru i mapowania Apilo.
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK (zaczyna sie od 0!) |
-| status | Nazwa statusu (read-only) |
-| color | Kolor statusu (hex, np. #ff0000) |
-| o | Kolejnosc wyswietlania |
-| apilo_status_id | ID statusu w Apilo (NULL gdy brak mapowania) |
-| baselinker_status_id | DEPRECATED (usuniety w ver. 0.263) |
-| sellasist_status_id | DEPRECATED (usuniety w ver. 0.263) |
-
-**Uzywane w:** `Domain\ShopStatus\ShopStatusRepository`, `admin\Controllers\ShopStatusesController`, `front\factory\ShopStatuses`, `shop\Order`, `cron.php`
-
-**Aktualizacja 2026-02-14 (ver. 0.267):** modul `/admin/shop_statuses` korzysta z `Domain\ShopStatus\ShopStatusRepository` przez `admin\Controllers\ShopStatusesController`. Usunieto legacy klasy `admin\controls\ShopStatuses` i `admin\factory\ShopStatuses`. `front\factory\ShopStatuses` dziala jako fasada do repozytorium.
-
-## pp_shop_product_sets
-Komplety produktow (modul `/admin/shop_product_sets`).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| name | Nazwa kompletu |
-| status | Status: 1 = aktywny, 0 = nieaktywny |
-
-**Uzywane w:** `Domain\ProductSet\ProductSetRepository`, `admin\Controllers\ShopProductSetsController`, `shop\ProductSet`, `shop\Product`
-
-## pp_shop_product_sets_products
-Powiazanie kompletow z produktami (tabela lacznikowa).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| set_id | FK do pp_shop_product_sets |
-| product_id | FK do pp_shop_products |
-
-**Uzywane w:** `Domain\ProductSet\ProductSetRepository`, `shop\Product`, `front\factory\ShopProduct`, `admin\factory\ShopProduct`
-
-**Aktualizacja 2026-02-15 (ver. 0.272):** modul `/admin/shop_product_sets` korzysta z `Domain\ProductSet\ProductSetRepository` przez `admin\Controllers\ShopProductSetsController`. Usunieto legacy klasy `admin\controls\ShopProductSets` i `admin\factory\ShopProductSet`. `shop\ProductSet` dziala jako fasada do repozytorium.
-
-## pp_shop_producer
-Producenci produktow (modul `/admin/shop_producer`).
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| name | Nazwa producenta |
-| status | Status: 1 = aktywny, 0 = nieaktywny |
-| img | Sciezka do logo producenta (NULL gdy brak) |
-
-**Uzywane w:** `Domain\Producer\ProducerRepository`, `admin\Controllers\ShopProducerController`, `shop\Producer`, `shop\Product`, `front\controls\ShopProducer`
-
-## pp_shop_producer_lang
-Tlumaczenia producentow (per jezyk). FK kaskadowe ON DELETE CASCADE.
-
-| Kolumna | Opis |
-|---------|------|
-| id | PK |
-| producer_id | FK do pp_shop_producer |
-| lang_id | ID jezyka (np. pl, en) |
-| description | Opis producenta (TEXT) |
-| data | Dane producenta (TEXT, HTML) |
-| meta_title | Meta title SEO (VARCHAR 255) |
-
-**Uzywane w:** `Domain\Producer\ProducerRepository`, `shop\Producer`, `shop\Product`
-
-**Aktualizacja 2026-02-15 (ver. 0.273):** modul `/admin/shop_producer` korzysta z `Domain\Producer\ProducerRepository` przez `admin\Controllers\ShopProducerController`. Usunieto legacy `admin\controls\ShopProducer` i `admin\factory\ShopProducer`. `shop\Producer` dziala jako fasada do repozytorium.
diff --git a/temp/update_build/tmp_0.275/docs/PROJECT_STRUCTURE.md b/temp/update_build/tmp_0.275/docs/PROJECT_STRUCTURE.md
deleted file mode 100644
index f3de582..0000000
--- a/temp/update_build/tmp_0.275/docs/PROJECT_STRUCTURE.md
+++ /dev/null
@@ -1,338 +0,0 @@
-# Struktura Projektu shopPRO
-
-Dokumentacja struktury projektu shopPRO do szybkiego odniesienia.
-
-## System Cache (Redis)
-
-### Klasy odpowiedzialne za cache
-
-#### RedisConnection
-- **Plik:** `autoload/class.RedisConnection.php`
-- **Opis:** Singleton zarządzający połączeniem z Redis
-- **Metody:**
- - `getInstance()` - pobiera instancję połączenia
- - `getConnection()` - zwraca obiekt Redis
-
-#### CacheHandler
-- **Plik:** `autoload/class.CacheHandler.php`
-- **Opis:** Handler do obsługi cache Redis
-- **Metody:**
- - `get($key)` - pobiera wartość z cache
- - `set($key, $value, $ttl = 86400)` - zapisuje wartość do cache
- - `exists($key)` - sprawdza czy klucz istnieje
- - `delete($key)` - usuwa pojedynczy klucz
- - `deletePattern($pattern)` - usuwa klucze według wzorca
-
-#### Klasa S (pomocnicza)
-- **Plik:** `autoload/class.S.php`
-- **Metody cache:**
- - `clear_redis_cache()` - czyści cały cache Redis (flushAll)
- - `clear_product_cache(int $product_id)` - czyści cache konkretnego produktu
-
-### Wzorce kluczy Redis
-
-#### Produkty
-```
-shop\product:{product_id}:{lang_id}:{permutation_hash}
-```
-- Przechowuje zserializowany obiekt produktu
-- TTL: 24 godziny (86400 sekund)
-- Klasa: `shop\Product::getFromCache()` - `autoload/shop/class.Product.php:121`
-
-#### Opcje ilościowe produktu
-```
-\shop\Product::get_product_permutation_quantity_options:{product_id}:{permutation}
-```
-- Przechowuje informacje o ilości i komunikatach magazynowych
-- Klasa: `shop\Product::get_product_permutation_quantity_options()` - `autoload/shop/class.Product.php:549`
-
-#### Zestawy produktów
-```
-\shop\Product::product_sets_when_add_to_basket:{product_id}
-```
-- Przechowuje produkty często kupowane razem
-- Klasa: `shop\Product::product_sets_when_add_to_basket()` - `autoload/shop/class.Product.php:316`
-
-## Integracje z systemami zewnętrznymi (CRON)
-
-### Plik: `cron.php`
-
-#### Apilo
-- **Aktualizacja pojedynczego produktu:** synchronizacja cen i stanow
- - Czestotliwosc: Co 10 minut
-- **Synchronizacja cennika:** masowa aktualizacja cen z Apilo
- - Czestotliwosc: Co 1 godzine
-- **Synchronizacja zaleglych syncow platnosci/statusow:** kolejka retry dla chwilowej niedostepnosci Apilo (`temp/apilo-sync-queue.json`)
- - Przetwarzanie: przy kazdym uruchomieniu `cron.php` (limit wsadowy)
-
-**Uwaga:** Integracje Sellasist i Baselinker zostaly usuniete w ver. 0.263.
-
-## Panel Administratora
-
-### Routing
-- Główny katalog: `admin/`
-- Template główny: `admin/templates/site/main-layout.php`
-- Kontrolery (nowe): `autoload/admin/Controllers/`
-- Kontrolery legacy (fallback): `autoload/admin/controls/`
-
-### Przycisk "Wyczyść cache"
-- **Lokalizacja UI:** `admin/templates/site/main-layout.php:172`
-- **JavaScript:** `admin/templates/site/main-layout.php:235-274`
-- **Endpoint AJAX:** `/admin/settings/clear_cache_ajax/`
-- **Kontroler:** `autoload/admin/Controllers/SettingsController.php:43-60`
-- **Działanie:**
- 1. Pokazuje spinner "Czyszczę cache..."
- 2. Czyści katalogi: `temp/`, `thumbs/`
- 3. Wykonuje `flushAll()` na Redis
- 4. Pokazuje "Cache wyczyszczony!" przez 2 sekundy
- 5. Przywraca stan początkowy
-
-## Struktura katalogów
-
-```
-shopPRO/
-├── admin/ # Panel administratora
-│ ├── templates/ # Szablony widoków
-│ └── layout/ # Zasoby CSS/JS/ikony
-├── autoload/ # Klasy autoloadowane
-│ ├── admin/ # Klasy panelu admin
-│ │ ├── Controllers/ # Nowe kontrolery DI
-│ │ ├── controls/ # Kontrolery legacy (fallback)
-│ │ └── factory/ # Fabryki/helpery
-│ ├── Domain/ # Repozytoria/logika domenowa
-│ ├── front/ # Klasy frontendu
-│ │ └── factory/ # Fabryki/helpery
-│ └── shop/ # Klasy sklepu
-├── docs/ # Dokumentacja techniczna
-├── libraries/ # Biblioteki zewnętrzne
-├── temp/ # Cache tymczasowy
-├── thumbs/ # Miniatury zdjęć
-└── cron.php # Zadania CRON
-```
-
-## Baza danych
-
-### Główne tabele produktów
-- `pp_shop_products` - produkty główne
-- `pp_shop_products_langs` - tłumaczenia produktów
-- `pp_shop_products_images` - zdjęcia produktów
-- `pp_shop_products_categories` - kategorie produktów
-- `pp_shop_products_custom_fields` - pola własne produktów
-
-### Tabele integracji
-- Kolumny w `pp_shop_products`:
- - `apilo_product_id`, `apilo_product_name`, `apilo_get_data_date`
-- Tabele ustawien:
- - `pp_shop_apilo_settings` (key-value)
- - `pp_shop_shoppro_settings` (key-value)
-
-### Tabele checkout
-- `pp_shop_payment_methods` - metody platnosci sklepu (mapowanie `apilo_payment_type_id`)
-- `pp_shop_transports` - rodzaje transportu sklepu (mapowanie `apilo_carrier_account_id`)
-- `pp_shop_transport_payment_methods` - powiazanie metod transportu i platnosci
-
-Pelna dokumentacja tabel: `DATABASE_STRUCTURE.md`
-
-## Konfiguracja
-
-### Redis
-- Konfiguracja: `config.php` (zmienna `$config['redis']`)
-- Parametry: host, port, password
-
-### Autoload
-- Funkcja: `__autoload_my_classes()` w `cron.php:6`
-- Wzorzec: `autoload/{namespace}/class.{ClassName}.php`
-
-## Klasy pomocnicze
-
-### \S (autoload/class.S.php)
-Główna klasa helper z metodami:
-- `seo($val)` - generowanie URL SEO
-- `normalize_decimal($val, $precision)` - normalizacja liczb
-- `send_email()` - wysyłanie emaili
-- `delete_dir($dir)` - usuwanie katalogów
-- `htacces()` - generowanie .htaccess i sitemap.xml
-
-### Medoo
-- Plik: `libraries/medoo/medoo.php`
-- Zmienna: `$mdb`
-- ORM do operacji na bazie danych
-
-## Najważniejsze wzorce
-
-### Namespace'y
-- `\admin\Controllers\` - nowe kontrolery panelu admin (DI)
-- `\admin\controls\` - kontrolery legacy (fallback)
-- `\Domain\` - repozytoria/logika domenowa
-- `\admin\factory\` - helpery/fabryki admin
-- `\front\factory\` - helpery/fabryki frontend
-- `\shop\` - klasy sklepu (Product, Order, itp.)
-
-### Cachowanie produktów
-```php
-// Pobranie produktu z cache
-$product = \shop\Product::getFromCache($product_id, $lang_id, $permutation_hash);
-
-// Czyszczenie cache produktu
-\S::clear_product_cache($product_id);
-
-// Czyszczenie całego cache
-\S::clear_redis_cache();
-```
-
-## Refaktoryzacja do Domain-Driven Architecture
-
-### Nowa struktura (w trakcie migracji)
-```
-autoload/
-├── Domain/ # Nowa warstwa biznesowa (namespace \Domain\)
-│ ├── Product/
-│ │ └── ProductRepository.php
-│ ├── Banner/
-│ │ └── BannerRepository.php
-│ ├── Settings/
-│ │ └── SettingsRepository.php
-│ ├── Cache/
-│ │ └── CacheRepository.php
-│ ├── Article/
-│ │ └── ArticleRepository.php
-│ ├── User/
-│ │ └── UserRepository.php
-│ ├── Languages/
-│ │ └── LanguagesRepository.php
-│ ├── Layouts/
-│ │ └── LayoutsRepository.php
-│ ├── Newsletter/
-│ │ └── NewsletterRepository.php
-│ ├── Scontainers/
-│ │ └── ScontainersRepository.php
-│ ├── Dictionaries/
-│ │ └── DictionariesRepository.php
-│ ├── Pages/
-│ │ └── PagesRepository.php
-│ ├── Integrations/
-│ │ └── IntegrationsRepository.php
-│ ├── Promotion/
-│ │ └── PromotionRepository.php
-│ ├── Coupon/
-│ │ └── CouponRepository.php
-│ ├── ShopStatus/
-│ │ └── ShopStatusRepository.php
-│ ├── Transport/
-│ │ └── TransportRepository.php
-│ ├── ProductSet/
-│ │ └── ProductSetRepository.php
-│ ├── Producer/
-│ │ └── ProducerRepository.php
-│ └── ...
-├── admin/
-│ ├── Controllers/ # Nowe kontrolery (namespace \admin\Controllers\)
-│ ├── class.Site.php # Router: nowy kontroler → fallback stary
-│ ├── controls/ # Stare kontrolery (niezależny fallback)
-│ ├── factory/ # Stare helpery (niezależny fallback)
-│ └── view/ # Widoki (statyczne - bez zmian)
-├── shop/ # Legacy - fasady do Domain
-└── front/factory/ # Legacy - stopniowo migrowane
-```
-
-**Aktualizacja 2026-02-14 (ver. 0.268):**
-- Dodano modul domenowy `Domain/PaymentMethod/PaymentMethodRepository.php`.
-- Dodano kontroler DI `admin/Controllers/ShopPaymentMethodController.php`.
-- Modul `/admin/shop_payment_method/*` dziala na nowych widokach (`payment-methods-list`, `payment-method-edit`).
-- Usunieto legacy: `autoload/admin/controls/class.ShopPaymentMethod.php`, `autoload/admin/factory/class.ShopPaymentMethod.php`, `autoload/admin/view/class.ShopPaymentMethod.php`, `admin/templates/shop-payment-method/view-list.php`.
-
-**Aktualizacja 2026-02-14 (ver. 0.269):**
-- Dodano modul domenowy `Domain/Transport/TransportRepository.php`.
-- Dodano kontroler DI `admin/Controllers/ShopTransportController.php`.
-- Modul `/admin/shop_transport/*` dziala na nowych widokach (`transports-list`, `transport-edit`).
-- Usunieto legacy: `autoload/admin/controls/class.ShopTransport.php`, `autoload/admin/view/class.ShopTransport.php`, `admin/templates/shop-transport/view-list.php`.
-- `admin\factory\ShopTransport` i `front\factory\ShopTransport` przepiete na repozytorium.
-
-**Aktualizacja 2026-02-14 (ver. 0.270):**
-- `shop\Order` zapisuje nieudane syncy Apilo (status/platnosc) do kolejki `temp/apilo-sync-queue.json`.
-- `cron.php` automatycznie ponawia zalegle syncy (`Order::process_apilo_sync_queue()`).
-- `shop\Order::set_as_paid()` wysyla mapowany typ platnosci Apilo (z mapowania metody platnosci), bez stalej wartosci `type`.
-
-### Routing admin (admin\Site::route())
-1. Sprawdź mapę `$newControllers` → utwórz instancję z DI → wywołaj
-2. Jeśli nowy kontroler nie istnieje (`class_exists()` = false) → fallback na `admin\controls\`
-3. Stary kontroler jest NIEZALEŻNY od nowych klas (bezpieczny fallback)
-
-### Dependency Injection
-Nowe klasy używają **Dependency Injection** zamiast `global` variables:
-```php
-// STARE
-global $mdb;
-$quantity = $mdb->get('pp_shop_products', 'quantity', ['id' => $id]);
-
-// NOWE
-$repository = new \Domain\Product\ProductRepository($mdb);
-$quantity = $repository->getQuantity($id);
-```
-
-## Testowanie (tylko dla deweloperów)
-
-**UWAGA:** Pliki testów NIE są częścią aktualizacji dla klientów!
-
-### Narzędzia
-- **PHPUnit 9.6.34** - framework testowy
-- **test.bat** - uruchamianie testów
-- **composer.json** - autoloading PSR-4
-
-Pelna dokumentacja testow: `TESTING.md`
-
-## Dodatkowa aktualizacja 2026-02-14 (ver. 0.271)
-- Dodano modul domenowy `Domain/Attribute/AttributeRepository.php`.
-- Dodano kontroler DI `admin/Controllers/ShopAttributeController.php`.
-- Modul `/admin/shop_attribute/*` zostal przepiety na nowe widoki (`attributes-list`, `attribute-edit`, `values-edit`).
-- Usunieto legacy: `autoload/admin/controls/class.ShopAttribute.php`, `autoload/admin/factory/class.ShopAttribute.php`, `autoload/admin/view/class.ShopAttribute.php`, `admin/templates/shop-attribute/_partials/value.php`.
-- Przepieto zaleznosci kombinacji produktu na `Domain\Attribute\AttributeRepository` i `shop\ProductAttribute`.
-- Dla `ShopAttribute` routing celowo nie wykonuje fallbacku akcji do legacy kontrolera.
-
-## Dodatkowa aktualizacja 2026-02-15 (ver. 0.272)
-- Dodano modul domenowy `Domain/ProductSet/ProductSetRepository.php`.
-- Dodano kontroler DI `admin/Controllers/ShopProductSetsController.php`.
-- Modul `/admin/shop_product_sets/*` dziala na nowych widokach (`product-sets-list`, `product-set-edit`).
-- Usunieto legacy: `autoload/admin/controls/class.ShopProductSets.php`, `autoload/admin/factory/class.ShopProductSet.php`, `admin/templates/shop-product-sets/view-list.php`, `admin/templates/shop-product-sets/set-edit.php`.
-- `shop\ProductSet` przepiety na fasade do `Domain\ProductSet\ProductSetRepository`.
-
-## Dodatkowa aktualizacja 2026-02-15 (ver. 0.273)
-- Dodano modul domenowy `Domain/Producer/ProducerRepository.php`.
-
-## Dodatkowa aktualizacja 2026-02-15 (ver. 0.274)
-- Dodano modul domenowy `Domain/Client/ClientRepository.php`.
-- Dodano kontroler DI `admin/Controllers/ShopClientsController.php`.
-- Modul `/admin/shop_clients/*` dziala na nowych widokach opartych o `components/table-list`.
-- Usunieto legacy: `autoload/admin/controls/class.ShopClients.php`, `autoload/admin/factory/class.ShopClients.php`.
-- Routing i menu admin przepiete na kanoniczny URL `/admin/shop_clients/list/`.
-- Dodano kontroler DI `admin/Controllers/ShopProducerController.php`.
-- Modul `/admin/shop_producer/*` dziala na nowych widokach (`producers-list`, `producer-edit`).
-- Usunieto legacy: `autoload/admin/controls/class.ShopProducer.php`, `admin/templates/shop-producer/list.php`, `admin/templates/shop-producer/edit.php`.
-- `shop\Producer` przepiety na fasade do `Domain\Producer\ProducerRepository`.
-- `admin\controls\ShopProduct` uzywa `ProducerRepository::allProducers()`.
-- Usunieto 6 pustych factory facades: `admin\factory\Languages`, `admin\factory\Newsletter`, `admin\factory\Scontainers`, `admin\factory\ShopProducer`, `admin\factory\ShopTransport`, `admin\factory\Layouts`.
-- Przepieto 2 wywolania `admin\factory\ShopTransport` w `admin\factory\ShopProduct` na `Domain\Transport\TransportRepository`.
-- Usuniety fallback do `admin\factory\Layouts` w `admin\controls\ShopProduct`.
-
-## Dodatkowa aktualizacja 2026-02-15 (ver. 0.274)
-- Dodano kontroler DI `admin/Controllers/ShopProductController.php` (akcje `mass_edit`, `mass_edit_save`, `get_products_by_category`).
-- Routing `admin\Site` rozszerzono o mapowanie `ShopProduct` do nowego kontrolera.
-- `Domain/Product/ProductRepository.php` rozszerzono o metody dla mass-edit: `allProductsForMassEdit`, `getProductsByCategory`, `applyDiscountPercent`.
-- Usunieto legacy akcje mass-edit z `autoload/admin/controls/class.ShopProduct.php`.
-- Widok `/admin/shop_product/mass_edit/` przepiety na nowy partial `admin/templates/shop-product/mass-edit-custom-script.php`.
-- Ujednolicono UI drzewek (strzalki/expand) w:
- - `admin/templates/pages/pages-list.php` + `admin/templates/pages/subpages-list.php`
- - `admin/templates/articles/subpages-list.php` + `admin/templates/articles/article-edit-custom-script.php`
-
-## Dodatkowa aktualizacja 2026-02-15 (ver. 0.275)
-- Dodano modul domenowy `Domain/Category/CategoryRepository.php`.
-- Dodano kontroler DI `admin/Controllers/ShopCategoryController.php`.
-- Modul `/admin/shop_category/*` dziala przez DI i kanoniczny URL `/admin/shop_category/list/` (z zachowaniem aliasu `view_list`).
-- Widoki `shop-category/*` maja wydzielone skrypty `*-custom-script.php` i ujednolicone strzalki drzewa (`button + caret + aria-expanded`).
-- Endpointy AJAX dla drzewka kategorii i kolejnosci produktow przepiete na `/admin/shop_category/save_categories_order/`, `/admin/shop_category/save_products_order/`, `/admin/shop_category/cookie_categories/`.
-- Usunieto legacy: `autoload/admin/controls/class.ShopCategory.php`, `autoload/admin/factory/class.ShopCategory.php`, `autoload/admin/view/class.ShopCategory.php`.
-- Przepieto zaleznosci `ShopProduct` z `admin\factory\ShopCategory` na `Domain\Category\CategoryRepository`.
-- Usunieto preload `autoload/admin/factory/class.ShopCategory.php` z `libraries/grid/config.php`.
-
----
-*Dokument aktualizowany: 2026-02-15*
diff --git a/temp/update_build/tmp_0.275/docs/REFACTORING_PLAN.md b/temp/update_build/tmp_0.275/docs/REFACTORING_PLAN.md
deleted file mode 100644
index 257c0f6..0000000
--- a/temp/update_build/tmp_0.275/docs/REFACTORING_PLAN.md
+++ /dev/null
@@ -1,297 +0,0 @@
-# Plan Refaktoryzacji shopPRO - Domain-Driven Architecture
-
-## Cel
-Stopniowe przeniesienie logiki biznesowej do architektury warstwowej:
-- **Domain/** - logika biznesowa (core)
-- **Admin/** - warstwa administratora
-- **Frontend/** - warstwa użytkownika
-- **Shared/** - współdzielone narzędzia
-
-## Docelowa struktura
-
-```
-autoload/
-├── Domain/ # Logika biznesowa (CORE) - namespace \Domain\
-│ ├── Product/
-│ │ ├── ProductRepository.php
-│ │ ├── ProductService.php # (przyszłość)
-│ │ └── ProductCacheService.php # (przyszłość)
-│ ├── Banner/
-│ │ └── BannerRepository.php
-│ ├── Settings/
-│ │ └── SettingsRepository.php
-│ ├── Cache/
-│ │ └── CacheRepository.php
-│ ├── Order/
-│ ├── Category/
-│ └── ...
-│
-├── admin/ # Warstwa administratora (istniejący katalog!)
-│ ├── Controllers/ # Nowe kontrolery - namespace \admin\Controllers\
-│ ├── controls/ # Stare kontrolery (legacy fallback)
-│ ├── factory/ # Stare helpery (legacy)
-│ └── view/ # Widoki (statyczne - OK bez zmian)
-│
-├── Frontend/ # Warstwa użytkownika (przyszłość)
-│ ├── Controllers/
-│ └── Services/
-│
-├── Shared/ # Współdzielone narzędzia
-│ ├── Cache/
-│ │ ├── CacheHandler.php
-│ │ └── RedisConnection.php
-│ └── Helpers/
-│ └── S.php
-│
-└── [LEGACY] # Stare klasy (stopniowo deprecated)
- ├── shop/
- ├── admin/factory/
- └── front/factory/
-```
-
-### WAŻNE: Konwencja namespace → katalog (Linux case-sensitive!)
-- `\Domain\` → `autoload/Domain/` (duże D - nowy katalog)
-- `\admin\Controllers\` → `autoload/admin/Controllers/` (małe a - istniejący katalog)
-- NIE używać `\Admin\` (duże A) bo na serwerze Linux katalog to `admin/` (małe a)
-
-## Zasady migracji
-
-### 1. Stopniowość
-- Przenosimy **jedną funkcję na raz**
-- Zachowujemy kompatybilność wsteczną
-- Stare klasy działają jako fasady do nowych
-
-### 2. Dependency Injection zamiast statycznych metod
-```php
-// ❌ STARE - statyczne
-class Product {
- public static function getQuantity($id) {
- global $mdb;
- return $mdb->get('pp_shop_products', 'quantity', ['id' => $id]);
- }
-}
-
-// ✅ NOWE - instancje z DI
-class ProductRepository {
- private $db;
-
- public function __construct($db) {
- $this->db = $db;
- }
-
- public function getQuantity($id) {
- return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
- }
-}
-```
-
-### 3. Fasady dla kompatybilności
-```php
-// Stara klasa wywołuje nową
-namespace shop;
-
-class Product {
- public static function getQuantity($id) {
- global $mdb;
- $repo = new \Domain\Product\ProductRepository($mdb);
- return $repo->getQuantity($id);
- }
-}
-```
-
-## Proces migracji funkcji
-
-### Krok 1: Wybór funkcji
-- Wybierz prostą funkcję statyczną
-- Sprawdź jej zależności
-- Przeanalizuj gdzie jest używana
-
-### Krok 2: Stworzenie nowej struktury
-- Utwórz folder `Domain/{Module}/`
-- Stwórz odpowiednią klasę (Repository/Service/Entity)
-- Przenieś logikę
-
-### Krok 3: Znalezienie użyć
-```bash
-grep -r "Product::getQuantity" .
-```
-
-### Krok 4: Aktualizacja wywołań
-- Opcja A: Bezpośrednie wywołanie nowej klasy
-- Opcja B: Fasada w starej klasie (zalecane na początek)
-
-### Krok 5: Testy
-- Napisz test jednostkowy dla nowej funkcji
-- Sprawdź czy stare wywołania działają
-
-## Status migracji
-
-### ✅ Zmigrowane moduły
-| # | Modul | Wersja | Zakres |
-|---|-------|--------|--------|
-| 1 | Cache | 0.237 | CacheHandler, RedisConnection, clear_product_cache |
-| 2 | Product | 0.238-0.252, 0.274 | getQuantity, getPrice, getName, archive/unarchive, allProductsForMassEdit, getProductsByCategory, applyDiscountPercent |
-| 3 | Banner | 0.239 | find, delete, save, kontroler DI |
-| 4 | Settings | 0.240/0.250 | saveSettings, getSettings, kontroler DI |
-| 5 | Dictionaries | 0.251 | listForAdmin, find, save, delete, kontroler DI |
-| 6 | ProductArchive | 0.252 | kontroler DI, table-list |
-| 7 | Filemanager | 0.252 | kontroler DI, fix Invalid Key |
-| 8 | Users | 0.253 | CRUD, logon, 2FA, kontroler DI |
-| 9 | Languages | 0.254 | languages + translations, kontroler DI |
-| 10 | Layouts | 0.256 | find, save, delete, menusWithPages, categoriesTree |
-| 11 | Newsletter | 0.257-0.258 | subskrybenci, szablony, ustawienia |
-| 12 | Scontainers | 0.259 | listForAdmin, find, save, delete |
-| 13 | ArticlesArchive | 0.260 | restore, deletePermanently |
-| 14 | Articles | 0.261 | pelna migracja (CRUD, AJAX, galeria, pliki) |
-| 15 | Pages | 0.262 | menu/page CRUD, drzewo stron, AJAX |
-| 16 | Integrations | 0.263 | Apilo/ShopPRO, cleanup Sellasist/Baselinker |
-| 17 | ShopPromotion | 0.264-0.265 | listForAdmin, find, save, delete, categoriesTree |
-| 18 | ShopCoupon | 0.266 | listForAdmin, find, save, delete, categoriesTree |
-| 19 | ShopStatuses | 0.267 | listForAdmin, find, save, color picker |
-| 20 | ShopPaymentMethod | 0.268 | listForAdmin, find, save, allActive, mapowanie Apilo, DI kontroler |
-| 21 | ShopTransport | 0.269 | listForAdmin, find, save, allActive, allForAdmin, findActiveById, getTransportCost, lowestTransportPrice, getApiloCarrierAccountId, powiazanie z PaymentMethod, DI kontroler |
-| 22 | ShopAttribute | 0.271 | list/edit/save/delete/values, nowy edytor wartosci, cleanup legacy, przepiecie zaleznosci kombinacji |
-| 23 | ShopProductSets | 0.272 | listForAdmin, find, save, delete, allSets, allProductsMap, multi-select Selectize, DI kontroler |
-| 24 | ShopProducer | 0.273 | listForAdmin, find, save, delete, allProducers, producerProducts, fasada shop\Producer, DI kontroler |
-| 25 | ShopProduct (mass_edit) | 0.274 | DI kontroler + routing dla `mass_edit`, `mass_edit_save`, `get_products_by_category`, cleanup legacy akcji |
-| 26 | ShopClients | 0.274 | DI kontroler + routing dla `list/details`, nowe listy na `components/table-list`, cleanup legacy controls/factory |
-| 27 | ShopCategory | 0.275 | CategoryRepository + DI kontroler + routing, endpointy AJAX (`save_categories_order`, `save_products_order`, `cookie_categories`), cleanup legacy controls/factory/view |
-
-### Product - szczegolowy status
-- ✅ getQuantity (ver. 0.238)
-- ✅ getPrice (ver. 0.239)
-- ✅ getName (ver. 0.239)
-- ✅ archive / unarchive (ver. 0.241/0.252)
-- ✅ allProductsForMassEdit (ver. 0.274)
-- ✅ getProductsByCategory (ver. 0.274)
-- ✅ applyDiscountPercent (ver. 0.274)
-- [ ] is_product_on_promotion
-- [ ] getFromCache
-- [ ] getProductImg
-
-### 📋 Do zrobienia
-- Order
-- ShopProduct (factory)
-
-## Kolejność refaktoryzacji (priorytet)
-
-1-27: ✅ Cache, Product, Banner, Settings, Dictionaries, ProductArchive, Filemanager, Users, Pages, Integrations, ShopPromotion, ShopCoupon, ShopStatuses, ShopPaymentMethod, ShopTransport, ShopAttribute, ShopProductSets, ShopProducer, ShopProduct (mass_edit), ShopClients, ShopCategory
-
-Nastepne:
-28. **Order**
-
-## Form Edit System
-
-Nowy uniwersalny system formularzy edycji:
-- ✅ Klasy ViewModel: `FormFieldType`, `FormField`, `FormTab`, `FormAction`, `FormEditViewModel`
-- ✅ Walidacja: `FormValidator` z obsługą reguł per pole i sekcje językowe
-- ✅ Persist: `FormRequestHandler` - zapamiętywanie danych przy błędzie walidacji
-- ✅ Renderer: `FormFieldRenderer` - renderowanie wszystkich typów pól
-- ✅ Szablon: `admin/templates/components/form-edit.php` - uniwersalny layout
-- Wspierane typy pól: text, number, email, password, date, datetime, switch, select, textarea, editor, image, file, hidden, lang_section, color
-- Obsługa zakładek (vertical) i sekcji językowych (horizontal)
-- **Do zrobienia**: Przerobić pozostałe kontrolery/formularze (Product, Category, Pages, itd.)
-
-Pelna dokumentacja: `docs/FORM_EDIT_SYSTEM.md`
-
-## Zasady kodu
-
-### 1. SOLID Principles
-- **S**ingle Responsibility - jedna klasa = jedna odpowiedzialność
-- **O**pen/Closed - otwarty na rozszerzenia, zamknięty na modyfikacje
-- **L**iskov Substitution - podklasy mogą zastąpić nadklasy
-- **I**nterface Segregation - wiele małych interfejsów
-- **D**ependency Inversion - zależności od abstrakcji
-
-### 2. Nazewnictwo
-- **Entity** - `Product.php` (reprezentuje obiekt domenowy)
-- **Repository** - `ProductRepository.php` (dostęp do danych)
-- **Service** - `ProductService.php` (logika biznesowa)
-- **Controller** - `ProductController.php` (obsługa requestów)
-
-### 3. Type Hinting
-```php
-// ✅ DOBRE
-public function getQuantity(int $id): ?int {
- return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
-}
-
-// ❌ ZŁE
-public function getQuantity($id) {
- return $this->db->get('pp_shop_products', 'quantity', ['id' => $id]);
-}
-```
-
-## Narzędzia pomocnicze
-
-### Autoloader (produkcja)
-Autoloader w 9 entry pointach obsługuje dwie konwencje:
-1. `autoload/{namespace}/class.{ClassName}.php` (legacy)
-2. `autoload/{namespace}/{ClassName}.php` (PSR-4, fallback)
-
-Entry pointy: `index.php`, `ajax.php`, `api.php`, `cron.php`, `cron-turstmate.php`, `download.php`, `admin/index.php`, `admin/ajax.php`, `cron/cron-xml.php`
-
-### Static Analysis
-```bash
-composer require --dev phpstan/phpstan
-vendor/bin/phpstan analyse autoload/Domain
-```
-
-## Testowanie
-
-### Framework: PHPUnit
-```bash
-composer test
-```
-
-### Struktura testów
-```
-tests/
-├── Unit/
-│ ├── Domain/
-│ │ ├── Article/ArticleRepositoryTest.php
-│ │ ├── Banner/BannerRepositoryTest.php
-│ │ ├── Cache/CacheRepositoryTest.php
-│ │ ├── Coupon/CouponRepositoryTest.php
-│ │ ├── Dictionaries/DictionariesRepositoryTest.php
-│ │ ├── Integrations/IntegrationsRepositoryTest.php
-│ │ ├── PaymentMethod/PaymentMethodRepositoryTest.php
-│ │ ├── Producer/ProducerRepositoryTest.php
-│ │ ├── Product/ProductRepositoryTest.php
-│ │ ├── ProductSet/ProductSetRepositoryTest.php
-│ │ ├── Promotion/PromotionRepositoryTest.php
-│ │ ├── Settings/SettingsRepositoryTest.php
-│ │ ├── ShopStatus/ShopStatusRepositoryTest.php
-│ │ └── User/UserRepositoryTest.php
-│ └── admin/
-│ └── Controllers/
-│ ├── ArticlesControllerTest.php
-│ ├── DictionariesControllerTest.php
-│ ├── IntegrationsControllerTest.php
-│ ├── ProductArchiveControllerTest.php
-│ ├── SettingsControllerTest.php
-│ ├── ShopCouponControllerTest.php
-│ ├── ShopPaymentMethodControllerTest.php
-│ ├── ShopProducerControllerTest.php
-│ ├── ShopProductSetsControllerTest.php
-│ ├── ShopPromotionControllerTest.php
-│ ├── ShopStatusesControllerTest.php
-│ └── UsersControllerTest.php
-└── Integration/
-```
-**Lacznie: 338 testow, 1063 asercji**
-
-Aktualizacja 2026-02-15 (ver. 0.273):
-- dodano testy `tests/Unit/Domain/Producer/ProducerRepositoryTest.php`
-- dodano testy `tests/Unit/admin/Controllers/ShopProducerControllerTest.php`
-
-Aktualizacja 2026-02-14 (ver. 0.271):
-- dodano testy `tests/Unit/Domain/Attribute/AttributeRepositoryTest.php`
-- dodano testy `tests/Unit/admin/Controllers/ShopAttributeControllerTest.php`
-
-Pelna dokumentacja testow: `TESTING.md`
-
----
-*Rozpoczęto: 2025-02-05*
-*Ostatnia aktualizacja: 2026-02-15*
-*Changelog zmian: `docs/CHANGELOG.md`*
diff --git a/temp/update_build/tmp_0.275/docs/TESTING.md b/temp/update_build/tmp_0.275/docs/TESTING.md
deleted file mode 100644
index bcaefc2..0000000
--- a/temp/update_build/tmp_0.275/docs/TESTING.md
+++ /dev/null
@@ -1,456 +0,0 @@
-# Testowanie shopPRO
-
-## Szybki start
-
-### Pelny zestaw testow
-```bash
-composer test
-```
-
-Alternatywnie (Windows):
-```bash
-./test.ps1
-./test.bat
-./test-simple.bat
-./test-debug.bat
-```
-
-Alternatywnie (Git Bash):
-```bash
-./test.sh
-```
-
-### Konkretny plik testowy
-```bash
-./test.ps1 tests/Unit/Domain/Product/ProductRepositoryTest.php
-./test.ps1 tests/Unit/admin/Controllers/ArticlesControllerTest.php
-```
-
-### Konkretny test (`--filter`)
-```bash
-./test.ps1 --filter testGetQuantityReturnsCorrectValue
-```
-
-## Aktualny stan suite
-
-Ostatnio zweryfikowano: 2026-02-15
-
-```text
-OK (351 tests, 1091 assertions)
-```
-
-Aktualizacja po migracji ShopClients (2026-02-15, ver. 0.274) - testy punktowe:
-```text
-OK (10 tests, 34 assertions)
-```
-
-Aktualizacja po migracji ShopCategory (2026-02-15, ver. 0.275) - testy punktowe:
-```text
-OK (16 tests, 72 assertions)
-```
-
-Nowe testy dodane 2026-02-15:
-- `tests/Unit/Domain/Client/ClientRepositoryTest.php`
-- `tests/Unit/admin/Controllers/ShopClientsControllerTest.php`
-- `tests/Unit/Domain/Category/CategoryRepositoryTest.php`
-- `tests/Unit/admin/Controllers/ShopCategoryControllerTest.php`
-
-## Struktura testow
-
-```text
-tests/
-|-- bootstrap.php
-|-- Unit/
-| |-- Domain/
-| | |-- Article/ArticleRepositoryTest.php
-| | |-- Attribute/AttributeRepositoryTest.php
-| | |-- Banner/BannerRepositoryTest.php
-| | |-- Cache/CacheRepositoryTest.php
-| | |-- Coupon/CouponRepositoryTest.php
-| | |-- Category/CategoryRepositoryTest.php
-| | |-- Dictionaries/DictionariesRepositoryTest.php
-| | |-- Integrations/IntegrationsRepositoryTest.php
-| | |-- PaymentMethod/PaymentMethodRepositoryTest.php
-| | |-- Producer/ProducerRepositoryTest.php
-| | |-- Product/ProductRepositoryTest.php
-| | |-- ProductSet/ProductSetRepositoryTest.php
-| | |-- Promotion/PromotionRepositoryTest.php
-| | |-- Settings/SettingsRepositoryTest.php
-| | |-- ShopStatus/ShopStatusRepositoryTest.php
-| | |-- Transport/TransportRepositoryTest.php
-| | `-- User/UserRepositoryTest.php
-| `-- admin/
-| `-- Controllers/
-| |-- ArticlesControllerTest.php
-| |-- DictionariesControllerTest.php
-| |-- IntegrationsControllerTest.php
-| |-- ProductArchiveControllerTest.php
-| |-- SettingsControllerTest.php
-| |-- ShopAttributeControllerTest.php
-| |-- ShopCategoryControllerTest.php
-| |-- ShopCouponControllerTest.php
-| |-- ShopPaymentMethodControllerTest.php
-| |-- ShopProducerControllerTest.php
-| |-- ShopProductControllerTest.php
-| |-- ShopProductSetsControllerTest.php
-| |-- ShopPromotionControllerTest.php
-| |-- ShopStatusesControllerTest.php
-| |-- ShopTransportControllerTest.php
-| `-- UsersControllerTest.php
-`-- Integration/
-```
-
-## Tryby uruchamiania
-
-### 1. TestDox (czytelna lista)
-```bash
-./test.bat
-```
-Uruchamia:
-```bash
-C:\xampp\php\php.exe phpunit.phar --testdox
-```
-
-### 2. Standard (kropki)
-```bash
-./test-simple.bat
-```
-Uruchamia:
-```bash
-C:\xampp\php\php.exe phpunit.phar
-```
-
-### 3. Debug (pelne logowanie)
-```bash
-./test-debug.bat
-```
-Uruchamia:
-```bash
-C:\xampp\php\php.exe phpunit.phar --debug
-```
-
-### 4. PowerShell (najbardziej niezawodne)
-```bash
-./test.ps1
-```
-- najpierw probuje `php` z PATH
-- jesli brak, probuje m.in. `C:\xampp\php\php.exe`
-- zawsze dodaje `--do-not-cache-result`
-
-## Interpretacja wynikow
-
-```text
-. = test przeszedl
-E = error (blad wykonania)
-F = failure (niezgodna asercja)
-```
-
-Przyklad sukcesu:
-```text
-................................................................. 65 / 82 ( 79%)
-................. 82 / 82 (100%)
-
-OK (82 tests, 181 assertions)
-```
-
-## Dodawanie nowych testow
-
-1. Dodaj plik w odpowiednim module, np. `tests/Unit/Domain/
/Test.php`.
-2. Rozszerz `PHPUnit\Framework\TestCase`.
-3. Nazwy metod zaczynaj od `test`.
-4. Trzymaj sie wzorca AAA: Arrange, Act, Assert.
-
-## Mockowanie (przyklad)
-
-```php
-$mockDb = $this->createMock(\medoo::class);
-$mockDb->method('get')->willReturn(42);
-
-$repo = new ProductRepository($mockDb);
-$value = $repo->getQuantity(123);
-
-$this->assertEquals(42, $value);
-```
-
-## Przydatne informacje
-
-- Konfiguracja PHPUnit: `phpunit.xml`
-- Bootstrap testow: `tests/bootstrap.php`
-- Dodatkowy opis: `tests/README.md`
-
-## Aktualizacja suite
-
-Ostatnio zweryfikowano: 2026-02-12
-
-```text
-OK (119 tests, 256 assertions)
-```
-
-Nowe testy dodane 2026-02-12:
-- `tests/Unit/Domain/User/UserRepositoryTest.php` (25 testow: CRUD, logon, 2FA verify/send, checkLogin, updateById)
-- `tests/Unit/admin/Controllers/UsersControllerTest.php` (12 testow: kontrakty + normalizeUser)
-
-Aktualizacja po migracji widokow Users (2026-02-12):
-```text
-OK (120 tests, 262 assertions)
-```
-
-## Aktualizacja suite (finalizacja Users)
-Ostatnio zweryfikowano: 2026-02-12
-
-```text
-OK (120 tests, 262 assertions)
-```
-
-Aktualizacja po migracji Languages (2026-02-12):
-```text
-OK (130 tests, 301 assertions)
-```
-
-Nowe testy dodane 2026-02-12:
-- `tests/Unit/Domain/Languages/LanguagesRepositoryTest.php`
-- `tests/Unit/admin/Controllers/LanguagesControllerTest.php`
-
-## Aktualizacja suite (release 0.254)
-Ostatnio zweryfikowano: 2026-02-12
-
-```text
-OK (130 tests, 301 assertions)
-```
-
-Nowe testy dodane 2026-02-12:
-- `tests/Unit/Domain/Languages/LanguagesRepositoryTest.php`
-- `tests/Unit/admin/Controllers/LanguagesControllerTest.php`
-
-## Aktualizacja suite (release 0.255)
-Ostatnio zweryfikowano: 2026-02-12
-
-```text
-OK (130 tests, 303 assertions)
-```
-
-## Aktualizacja suite (release 0.256)
-Ostatnio zweryfikowano: 2026-02-12
-
-```text
-OK (141 tests, 336 assertions)
-```
-
-Nowe testy dodane 2026-02-12:
-- `tests/Unit/Domain/Layouts/LayoutsRepositoryTest.php`
-- `tests/Unit/admin/Controllers/LayoutsControllerTest.php`
-
-Zaktualizowane testy 2026-02-12:
-- `tests/Unit/Domain/Languages/LanguagesRepositoryTest.php` (defaultLanguageId)
-- `tests/Unit/admin/Controllers/ArticlesControllerTest.php` (konstruktor + LayoutsRepository)
-
-## Aktualizacja suite (release 0.257)
-Ostatnio zweryfikowano: 2026-02-12
-
-```text
-OK (150 tests, 372 assertions)
-```
-
-Nowe testy dodane 2026-02-12:
-- `tests/Unit/Domain/Newsletter/NewsletterRepositoryTest.php`
-- `tests/Unit/admin/Controllers/NewsletterControllerTest.php`
-
-## Aktualizacja suite (release 0.258)
-Ostatnio zweryfikowano: 2026-02-12
-
-```text
-OK (150 tests, 372 assertions)
-```
-
-## Aktualizacja suite (release 0.259)
-Ostatnio zweryfikowano: 2026-02-12
-
-```text
-OK (158 tests, 397 assertions)
-```
-
-Nowe testy dodane 2026-02-12:
-- `tests/Unit/Domain/Scontainers/ScontainersRepositoryTest.php`
-- `tests/Unit/admin/Controllers/ScontainersControllerTest.php`
-
-## Aktualizacja suite (release 0.260)
-Ostatnio zweryfikowano: 2026-02-12
-
-```text
-OK (165 tests, 424 assertions)
-```
-
-Nowe testy dodane 2026-02-12:
-- `tests/Unit/Domain/Article/ArticleRepositoryTest.php` (rozszerzenie o testy `restore`, `deletePermanently`, `listArchivedForAdmin`)
-- `tests/Unit/admin/Controllers/ArticlesArchiveControllerTest.php`
-
-## Aktualizacja suite (release 0.261)
-Ostatnio zweryfikowano: 2026-02-13
-
-```text
-OK (176 tests, 439 assertions)
-```
-
-Nowe testy/rozszerzenia 2026-02-13:
-- `tests/Unit/Domain/Article/ArticleRepositoryTest.php` (nowe przypadki dla `pagesSummaryForArticles`, `updateImageAlt`, `markFileToDelete`)
-- `tests/Unit/admin/Controllers/ArticlesControllerTest.php` (nowe kontrakty dla akcji `imageAltChange`, `fileNameChange`, `imageDelete`, `fileDelete`)
-
-## Aktualizacja suite (release 0.261)
-Ostatnio zweryfikowano: 2026-02-13
-
-```text
-OK (178 tests, 443 assertions)
-```
-
-Nowe testy/rozszerzenia 2026-02-13:
-- `tests/Unit/Domain/Article/ArticleRepositoryTest.php` (nowe przypadki dla `saveFilesOrder`)
-
-## Aktualizacja suite (Pages migration)
-Ostatnio zweryfikowano: 2026-02-13
-
-```text
-OK (186 tests, 478 assertions)
-```
-
-Nowe testy dodane 2026-02-13:
-- `tests/Unit/Domain/Pages/PagesRepositoryTest.php`
-- `tests/Unit/admin/Controllers/PagesControllerTest.php`
-
-Zaktualizowane testy 2026-02-13:
-- `tests/Unit/admin/Controllers/ArticlesControllerTest.php` (konstruktor z `Domain\\Pages\\PagesRepository`)
-
-## Aktualizacja suite (Integrations refactor, ver. 0.263)
-Ostatnio zweryfikowano: 2026-02-13
-
-```text
-OK (212 tests, 577 assertions)
-```
-
-Nowe testy dodane 2026-02-13:
-- `tests/Unit/Domain/Integrations/IntegrationsRepositoryTest.php` (16 testow: getSettings, getSetting, saveSetting, linkProduct, unlinkProduct, getProductSku, apiloGetAccessToken, invalid provider, settings table mapping)
-- `tests/Unit/admin/Controllers/IntegrationsControllerTest.php` (10 testow: kontrakty metod, return types, brak metod sellasist/baselinker)
-
-Zaktualizowane pliki:
-- `tests/bootstrap.php` (dodany stub `S::remove_special_chars()`)
-
-## Aktualizacja suite (ShopPromotion refactor, ver. 0.264)
-Ostatnio zweryfikowano: 2026-02-13
-
-```text
-OK (222 tests, 609 assertions)
-```
-
-Nowe testy dodane 2026-02-13:
-- `tests/Unit/Domain/Promotion/PromotionRepositoryTest.php` (6 testow: find default, save insert, delete, whitelist sortowania, drzewo kategorii)
-- `tests/Unit/admin/Controllers/ShopPromotionControllerTest.php` (4 testy: kontrakty metod i DI konstruktora)
-
-## Aktualizacja suite (ShopPromotion fix + date_from, ver. 0.265)
-Ostatnio zweryfikowano: 2026-02-13
-
-```text
-OK (222 tests, 614 assertions)
-```
-
-Zmiany testowe 2026-02-13:
-- rozszerzenie `tests/Unit/Domain/Promotion/PromotionRepositoryTest.php` o asercje `date_from`
-
-## Aktualizacja suite (ShopCoupon refactor, ver. 0.266)
-Ostatnio zweryfikowano: 2026-02-13
-
-```text
-OK (235 tests, 682 assertions)
-```
-
-Nowe testy dodane 2026-02-13:
-- `tests/Unit/Domain/Coupon/CouponRepositoryTest.php` (8 testow: find default/normalize, save insert/update, delete, whitelist sortowania, drzewo kategorii)
-- `tests/Unit/admin/Controllers/ShopCouponControllerTest.php` (5 testow: kontrakty metod, aliasy legacy, DI konstruktora)
-
-Ponowna weryfikacja po poprawkach UI (drzewko + checkboxy): 2026-02-13
-- `OK (235 tests, 682 assertions)`
-
-## Aktualizacja suite (ShopStatuses refactor, ver. 0.267)
-Ostatnio zweryfikowano: 2026-02-14
-
-```text
-OK (254 tests, 736 assertions)
-```
-
-Nowe testy dodane 2026-02-14:
-- `tests/Unit/Domain/ShopStatus/ShopStatusRepositoryTest.php` (9 testow: find z ID=0, find null apilo, save update, save z ID=0, empty apilo sets null, reject negative ID, getApiloStatusId, getByIntegrationStatusId, allStatuses, whitelist sortowania)
-- `tests/Unit/admin/Controllers/ShopStatusesControllerTest.php` (5 testow: kontrakty metod, brak aliasow legacy, return types, DI konstruktora)
-
-## Aktualizacja suite (ShopPaymentMethod refactor, ver. 0.268)
-Ostatnio zweryfikowano: 2026-02-14
-
-```text
-OK (280 tests, 828 assertions)
-```
-
-Nowe testy dodane 2026-02-14:
-- `tests/Unit/Domain/PaymentMethod/PaymentMethodRepositoryTest.php` (14 testow: find invalid/null/normalize, save update/null/non-numeric apilo, listForAdmin whitelist, allActive, allForAdmin, findActiveById, isActive, getApiloPaymentTypeId, forTransport)
-- `tests/Unit/admin/Controllers/ShopPaymentMethodControllerTest.php` (5 testow: kontrakty metod, brak aliasow legacy, return types, DI konstruktora)
-
-## Aktualizacja suite (ShopTransport refactor, ver. 0.269)
-Ostatnio zweryfikowano: 2026-02-14
-
-```text
-OK (300 tests, 895 assertions)
-```
-
-Nowe testy dodane 2026-02-14:
-- `tests/Unit/Domain/Transport/TransportRepositoryTest.php` (14 testow: find invalid/null/normalize/nullables, save insert/update/failure/default reset/switch normalization, listForAdmin whitelist, allActive, getApiloCarrierAccountId, getTransportCost, allForAdmin)
-- `tests/Unit/admin/Controllers/ShopTransportControllerTest.php` (5 testow: kontrakty metod, brak aliasow legacy, return types, DI konstruktora z 2 repo)
-
-## Aktualizacja suite (Apilo sync hardening, ver. 0.270)
-Ostatnio zweryfikowano: 2026-02-14
-
-```text
-OK (300 tests, 895 assertions)
-```
-
-Zmiany testowe 2026-02-14:
-- brak nowych testow; pelna regresja po zmianach sync Apilo (TPAY -> Apilo) przeszla bez bledow
-
-## Aktualizacja suite (ShopAttribute refactor, ver. 0.271)
-Ostatnio zweryfikowano: 2026-02-14
-
-```text
-OK (312 tests, 948 assertions)
-```
-
-Nowe testy dodane 2026-02-14:
-- `tests/Unit/Domain/Attribute/AttributeRepositoryTest.php` (5 testow: domyslne dane cechy, whitelist sortowania/paginacji, zapis wartosci i domyslnej, usuwanie pustych tlumaczen, jezyk domyslny)
-- `tests/Unit/admin/Controllers/ShopAttributeControllerTest.php` (7 testow: kontrakty metod, brak aliasow legacy, return types, DI konstruktora, walidacja `validateValuesRows`)
-
-## Aktualizacja suite (ShopProductSets refactor, ver. 0.272)
-Ostatnio zweryfikowano: 2026-02-15
-
-```text
-OK (324 tests, 1000 assertions)
-```
-
-Nowe testy dodane 2026-02-15:
-- `tests/Unit/Domain/ProductSet/ProductSetRepositoryTest.php` (7 testow: find default/normalize, save insert/update, delete invalid, whitelist sortowania/paginacji, allSets)
-- `tests/Unit/admin/Controllers/ShopProductSetsControllerTest.php` (5 testow: kontrakty metod, aliasy legacy, return types, DI konstruktora)
-
-## Aktualizacja suite (ShopProducer refactor, ver. 0.273)
-Ostatnio zweryfikowano: 2026-02-15
-
-```text
-OK (338 tests, 1063 assertions)
-```
-
-Nowe testy dodane 2026-02-15:
-- `tests/Unit/Domain/Producer/ProducerRepositoryTest.php` (9 testow: find default/normalize, save insert/update, delete invalid/success, whitelist sortowania/paginacji, allProducers, producerProducts)
-- `tests/Unit/admin/Controllers/ShopProducerControllerTest.php` (5 testow: kontrakty metod, aliasy legacy, return types, DI konstruktora)
-
-## Aktualizacja suite (ShopProduct mass_edit, ver. 0.274)
-Ostatnio zweryfikowano: 2026-02-15
-
-```text
-OK (351 tests, 1091 assertions)
-```
-
-Nowe testy dodane 2026-02-15:
-- `tests/Unit/Domain/Product/ProductRepositoryTest.php` (rozszerzenie: `allProductsForMassEdit`, `getProductsByCategory`, `applyDiscountPercent`)
-- `tests/Unit/admin/Controllers/ShopProductControllerTest.php` (7 testow: kontrakty metod, return types, DI konstruktora)
diff --git a/temp/update_build/tmp_0.275/libraries/grid/config.php b/temp/update_build/tmp_0.275/libraries/grid/config.php
deleted file mode 100644
index 238b9c9..0000000
--- a/temp/update_build/tmp_0.275/libraries/grid/config.php
+++ /dev/null
@@ -1,48 +0,0 @@
- 'mysql',
- 'database_name' => $database['name'],
- 'server' => $database['host'],
- 'username' => $database['user'],
- 'password' => $database['password'],
- 'charset' => 'utf8'
- );
-
-$mdb = new medoo( [
- 'database_type' => 'mysql',
- 'database_name' => $database['name'],
- 'server' => $database['host'],
- 'username' => $database['user'],
- 'password' => $database['password'],
- 'charset' => 'utf8'
- ] );
diff --git a/temp/update_build/tmp_0.275/tests/Unit/admin/Controllers/ShopCategoryControllerTest.php b/temp/update_build/tmp_0.275/tests/Unit/admin/Controllers/ShopCategoryControllerTest.php
deleted file mode 100644
index 679731c..0000000
--- a/temp/update_build/tmp_0.275/tests/Unit/admin/Controllers/ShopCategoryControllerTest.php
+++ /dev/null
@@ -1,80 +0,0 @@
-repository = $this->createMock(CategoryRepository::class);
- $this->languagesRepository = $this->createMock(LanguagesRepository::class);
- $this->controller = new ShopCategoryController($this->repository, $this->languagesRepository);
- }
-
- public function testConstructorAcceptsDependencies(): void
- {
- $controller = new ShopCategoryController($this->repository, $this->languagesRepository);
- $this->assertInstanceOf(ShopCategoryController::class, $controller);
- }
-
- public function testHasExpectedActionMethods(): void
- {
- $this->assertTrue(method_exists($this->controller, 'view_list'));
- $this->assertTrue(method_exists($this->controller, 'list'));
- $this->assertTrue(method_exists($this->controller, 'category_edit'));
- $this->assertTrue(method_exists($this->controller, 'edit'));
- $this->assertTrue(method_exists($this->controller, 'save'));
- $this->assertTrue(method_exists($this->controller, 'category_delete'));
- $this->assertTrue(method_exists($this->controller, 'delete'));
- $this->assertTrue(method_exists($this->controller, 'category_products'));
- $this->assertTrue(method_exists($this->controller, 'products'));
- $this->assertTrue(method_exists($this->controller, 'category_url_browser'));
- $this->assertTrue(method_exists($this->controller, 'save_categories_order'));
- $this->assertTrue(method_exists($this->controller, 'save_products_order'));
- $this->assertTrue(method_exists($this->controller, 'cookie_categories'));
- }
-
- public function testViewActionsReturnString(): void
- {
- $reflection = new \ReflectionClass($this->controller);
-
- $this->assertEquals('string', (string)$reflection->getMethod('view_list')->getReturnType());
- $this->assertEquals('string', (string)$reflection->getMethod('list')->getReturnType());
- $this->assertEquals('string', (string)$reflection->getMethod('category_edit')->getReturnType());
- $this->assertEquals('string', (string)$reflection->getMethod('edit')->getReturnType());
- $this->assertEquals('string', (string)$reflection->getMethod('category_products')->getReturnType());
- $this->assertEquals('string', (string)$reflection->getMethod('products')->getReturnType());
- }
-
- public function testMutationActionsReturnVoid(): void
- {
- $reflection = new \ReflectionClass($this->controller);
-
- $this->assertEquals('void', (string)$reflection->getMethod('save')->getReturnType());
- $this->assertEquals('void', (string)$reflection->getMethod('category_delete')->getReturnType());
- $this->assertEquals('void', (string)$reflection->getMethod('delete')->getReturnType());
- $this->assertEquals('void', (string)$reflection->getMethod('category_url_browser')->getReturnType());
- $this->assertEquals('void', (string)$reflection->getMethod('save_categories_order')->getReturnType());
- $this->assertEquals('void', (string)$reflection->getMethod('save_products_order')->getReturnType());
- $this->assertEquals('void', (string)$reflection->getMethod('cookie_categories')->getReturnType());
- }
-
- public function testConstructorRequiresCategoryAndLanguagesRepositories(): void
- {
- $reflection = new \ReflectionClass(ShopCategoryController::class);
- $constructor = $reflection->getConstructor();
- $params = $constructor->getParameters();
-
- $this->assertCount(2, $params);
- $this->assertEquals('Domain\\Category\\CategoryRepository', $params[0]->getType()->getName());
- $this->assertEquals('Domain\\Languages\\LanguagesRepository', $params[1]->getType()->getName());
- }
-}
diff --git a/temp/update_build/update_0.273.zip b/temp/update_build/update_0.273.zip
deleted file mode 100644
index b277f34..0000000
Binary files a/temp/update_build/update_0.273.zip and /dev/null differ
diff --git a/temp/update_build/update_0.274.zip b/temp/update_build/update_0.274.zip
deleted file mode 100644
index 1ecb78b..0000000
Binary files a/temp/update_build/update_0.274.zip and /dev/null differ
diff --git a/temp/update_build/update_0.275.zip b/temp/update_build/update_0.275.zip
deleted file mode 100644
index ab9d403..0000000
Binary files a/temp/update_build/update_0.275.zip and /dev/null differ
diff --git a/temp/update_build/ver_0.200_20260211_000158/admin/templates/banners/banners-list-custom-script.php b/temp/update_build/ver_0.200_20260211_000158/admin/templates/banners/banners-list-custom-script.php
deleted file mode 100644
index 56ab843..0000000
--- a/temp/update_build/ver_0.200_20260211_000158/admin/templates/banners/banners-list-custom-script.php
+++ /dev/null
@@ -1,100 +0,0 @@
-
-
-
diff --git a/temp/update_build/ver_0.200_20260211_000158/admin/templates/banners/banners-list.php b/temp/update_build/ver_0.200_20260211_000158/admin/templates/banners/banners-list.php
deleted file mode 100644
index 3e70c9a..0000000
--- a/temp/update_build/ver_0.200_20260211_000158/admin/templates/banners/banners-list.php
+++ /dev/null
@@ -1,5 +0,0 @@
-= \Tpl::view('components/table-list', ['list' => $this->viewModel]); ?>
-
-viewModel->customScriptView)): ?>
- = \Tpl::view($this->viewModel->customScriptView, ['list' => $this->viewModel]); ?>
-
diff --git a/temp/update_build/ver_0.200_20260211_000158/admin/templates/filemanager/filemanager.php b/temp/update_build/ver_0.200_20260211_000158/admin/templates/filemanager/filemanager.php
deleted file mode 100644
index dc2d008..0000000
--- a/temp/update_build/ver_0.200_20260211_000158/admin/templates/filemanager/filemanager.php
+++ /dev/null
@@ -1,4 +0,0 @@
-filemanager_url ?? '/libraries/filemanager-9.14.2/dialog.php'));
-?>
-
diff --git a/temp/update_build/ver_0.200_20260211_000158/admin/templates/product-archive/products-list-custom-script.php b/temp/update_build/ver_0.200_20260211_000158/admin/templates/product-archive/products-list-custom-script.php
deleted file mode 100644
index aacad98..0000000
--- a/temp/update_build/ver_0.200_20260211_000158/admin/templates/product-archive/products-list-custom-script.php
+++ /dev/null
@@ -1,100 +0,0 @@
-
-
-
diff --git a/temp/update_build/ver_0.200_20260211_000158/admin/templates/product-archive/products-list.php b/temp/update_build/ver_0.200_20260211_000158/admin/templates/product-archive/products-list.php
deleted file mode 100644
index 3e70c9a..0000000
--- a/temp/update_build/ver_0.200_20260211_000158/admin/templates/product-archive/products-list.php
+++ /dev/null
@@ -1,5 +0,0 @@
-= \Tpl::view('components/table-list', ['list' => $this->viewModel]); ?>
-
-viewModel->customScriptView)): ?>
- = \Tpl::view($this->viewModel->customScriptView, ['list' => $this->viewModel]); ?>
-
diff --git a/temp/update_build/ver_0.200_20260211_000158/autoload/Domain/Product/ProductRepository.php b/temp/update_build/ver_0.200_20260211_000158/autoload/Domain/Product/ProductRepository.php
deleted file mode 100644
index e397697..0000000
--- a/temp/update_build/ver_0.200_20260211_000158/autoload/Domain/Product/ProductRepository.php
+++ /dev/null
@@ -1,247 +0,0 @@
-db = $db;
- }
-
- /**
- * Pobiera stan magazynowy produktu
- *
- * @param int $productId ID produktu
- * @return int|null Ilość produktu lub null jeśli nie znaleziono
- */
- public function getQuantity(int $productId): ?int
- {
- $quantity = $this->db->get('pp_shop_products', 'quantity', ['id' => $productId]);
-
- // Medoo zwraca false jeśli nie znaleziono
- return $quantity !== false ? (int)$quantity : null;
- }
-
- /**
- * Pobiera produkt po ID
- *
- * @param int $productId ID produktu
- * @return array|null Dane produktu lub null
- */
- public function find(int $productId): ?array
- {
- $product = $this->db->get('pp_shop_products', '*', ['id' => $productId]);
- return $product ?: null;
- }
-
- /**
- * Zwraca liste produktow z archiwum do panelu admin.
- *
- * @return array{items: array>, total: int}
- */
- public function listArchivedForAdmin(
- array $filters,
- string $sortColumn = 'id',
- string $sortDir = 'DESC',
- int $page = 1,
- int $perPage = 10
- ): array {
- $allowedSortColumns = [
- 'id' => 'psp.id',
- 'name' => 'name',
- 'price_brutto' => 'psp.price_brutto',
- 'price_brutto_promo' => 'psp.price_brutto_promo',
- 'quantity' => 'psp.quantity',
- 'combinations' => 'combinations',
- ];
-
- $sortSql = $allowedSortColumns[$sortColumn] ?? 'psp.id';
- $sortDir = strtoupper(trim($sortDir)) === 'ASC' ? 'ASC' : 'DESC';
- $page = max(1, $page);
- $perPage = min(self::MAX_PER_PAGE, max(1, $perPage));
- $offset = ($page - 1) * $perPage;
-
- $where = ['psp.archive = 1', 'psp.parent_id IS NULL'];
- $params = [];
-
- $phrase = trim((string)($filters['phrase'] ?? ''));
- if (strlen($phrase) > 255) {
- $phrase = substr($phrase, 0, 255);
- }
-
- if ($phrase !== '') {
- $where[] = '(
- psp.ean LIKE :phrase
- OR psp.sku LIKE :phrase
- OR EXISTS (
- SELECT 1
- FROM pp_shop_products_langs AS pspl2
- WHERE pspl2.product_id = psp.id
- AND pspl2.name LIKE :phrase
- )
- )';
- $params[':phrase'] = '%' . $phrase . '%';
- }
-
- $whereSql = implode(' AND ', $where);
-
- $sqlCount = "
- SELECT COUNT(0)
- FROM pp_shop_products AS psp
- WHERE {$whereSql}
- ";
-
- $stmtCount = $this->db->query($sqlCount, $params);
- $countRows = $stmtCount ? $stmtCount->fetchAll() : [];
- $total = isset($countRows[0][0]) ? (int)$countRows[0][0] : 0;
-
- $sql = "
- SELECT
- psp.id,
- psp.price_brutto,
- psp.price_brutto_promo,
- psp.quantity,
- psp.sku,
- psp.ean,
- (
- SELECT pspl.name
- FROM pp_shop_products_langs AS pspl
- INNER JOIN pp_langs AS pl ON pl.id = pspl.lang_id
- WHERE pspl.product_id = psp.id
- AND pspl.name <> ''
- ORDER BY pl.o ASC
- LIMIT 1
- ) AS name,
- (
- SELECT pspi.src
- FROM pp_shop_products_images AS pspi
- WHERE pspi.product_id = psp.id
- ORDER BY pspi.o ASC, pspi.id ASC
- LIMIT 1
- ) AS image_src,
- (
- SELECT pspi.alt
- FROM pp_shop_products_images AS pspi
- WHERE pspi.product_id = psp.id
- ORDER BY pspi.o ASC, pspi.id ASC
- LIMIT 1
- ) AS image_alt,
- (
- SELECT COUNT(0)
- FROM pp_shop_products AS pspc
- WHERE pspc.parent_id = psp.id
- ) AS combinations
- FROM pp_shop_products AS psp
- WHERE {$whereSql}
- ORDER BY {$sortSql} {$sortDir}, psp.id {$sortDir}
- LIMIT {$perPage} OFFSET {$offset}
- ";
-
- $stmt = $this->db->query($sql, $params);
- $items = $stmt ? $stmt->fetchAll() : [];
-
- return [
- 'items' => is_array($items) ? $items : [],
- 'total' => $total,
- ];
- }
-
- /**
- * Pobiera cenę produktu (promocyjną jeśli jest niższa, w przeciwnym razie regularną)
- *
- * @param int $productId ID produktu
- * @return float|null Cena brutto lub null jeśli nie znaleziono
- */
- public function getPrice(int $productId): ?float
- {
- $prices = $this->db->get('pp_shop_products', ['price_brutto', 'price_brutto_promo'], ['id' => $productId]);
-
- if (!$prices) {
- return null;
- }
-
- if ($prices['price_brutto_promo'] != '' && $prices['price_brutto_promo'] < $prices['price_brutto']) {
- return (float)$prices['price_brutto_promo'];
- }
-
- return (float)$prices['price_brutto'];
- }
-
- /**
- * Pobiera nazwę produktu w danym języku
- *
- * @param int $productId ID produktu
- * @param string $langId ID języka
- * @return string|null Nazwa produktu lub null jeśli nie znaleziono
- */
- public function getName(int $productId, string $langId): ?string
- {
- $name = $this->db->get('pp_shop_products_langs', 'name', ['AND' => ['product_id' => $productId, 'lang_id' => $langId]]);
-
- return $name ?: null;
- }
-
- /**
- * Aktualizuje ilość produktu
- *
- * @param int $productId ID produktu
- * @param int $quantity Nowa ilość
- * @return bool Czy aktualizacja się powiodła
- */
- public function updateQuantity(int $productId, int $quantity): bool
- {
- $result = $this->db->update(
- 'pp_shop_products',
- ['quantity' => $quantity],
- ['id' => $productId]
- );
-
- return $result !== false;
- }
-
- /**
- * Przywraca produkt z archiwum (wraz z kombinacjami)
- *
- * @param int $productId ID produktu
- * @return bool Czy operacja się powiodła
- */
- public function unarchive(int $productId): bool
- {
- $this->db->update( 'pp_shop_products', [ 'status' => 1, 'archive' => 0 ], [ 'id' => $productId ] );
- $this->db->update( 'pp_shop_products', [ 'status' => 1, 'archive' => 0 ], [ 'parent_id' => $productId ] );
-
- return true;
- }
-
- /**
- * Przenosi produkt do archiwum (wraz z kombinacjami)
- *
- * @param int $productId ID produktu
- * @return bool Czy operacja się powiodła
- */
- public function archive(int $productId): bool
- {
- $this->db->update( 'pp_shop_products', [ 'status' => 0, 'archive' => 1 ], [ 'id' => $productId ] );
- $this->db->update( 'pp_shop_products', [ 'status' => 0, 'archive' => 1 ], [ 'parent_id' => $productId ] );
-
- return true;
- }
-}
diff --git a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/BannerController.php b/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/BannerController.php
deleted file mode 100644
index 666a64c..0000000
--- a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/BannerController.php
+++ /dev/null
@@ -1,337 +0,0 @@
-repository = $repository;
- $this->formHandler = new FormRequestHandler();
- }
-
- /**
- * Lista banerow
- */
- public function list(): string
- {
- $sortableColumns = ['name', 'status', 'home_page', 'date_start', 'date_end'];
-
- $filterDefinitions = [
- [
- 'key' => 'name',
- 'label' => 'Nazwa',
- 'type' => 'text',
- ],
- [
- 'key' => 'status',
- 'label' => 'Aktywny',
- 'type' => 'select',
- 'options' => [
- '' => '- aktywny -',
- '1' => 'tak',
- '0' => 'nie',
- ],
- ],
- ];
-
- $listRequest = \admin\Support\TableListRequestFactory::fromRequest(
- $filterDefinitions,
- $sortableColumns,
- 'name'
- );
-
- // Historycznie lista banerow domyslnie byla sortowana rosnaco po nazwie.
- $sortDir = $listRequest['sortDir'];
- if (trim((string)\S::get('sort')) === '') {
- $sortDir = 'ASC';
- }
-
- $result = $this->repository->listForAdmin(
- $listRequest['filters'],
- $listRequest['sortColumn'],
- $sortDir,
- $listRequest['page'],
- $listRequest['perPage']
- );
-
- $rows = [];
- $lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1;
- foreach ($result['items'] as $item) {
- $id = (int)$item['id'];
- $name = (string)($item['name'] ?? '');
- $homePage = (int)($item['home_page'] ?? 0);
- $isActive = (int)($item['status'] ?? 0) === 1;
- $thumbnailSrc = trim((string)($item['thumbnail_src'] ?? ''));
- if ($thumbnailSrc !== '' && !preg_match('#^(https?:)?//#i', $thumbnailSrc) && strpos($thumbnailSrc, '/') !== 0) {
- $thumbnailSrc = '/' . ltrim($thumbnailSrc, '/');
- }
-
- $thumbnail = '-';
- if ($thumbnailSrc !== '') {
- $thumbnail = ''
- . '
 . ')
'
- . '
';
- }
-
- $rows[] = [
- 'lp' => $lp++ . '.',
- 'thumbnail' => $thumbnail,
- 'name' => '' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '',
- 'status' => $isActive ? 'tak' : 'nie',
- 'home_page' => $homePage === 1 ? 'tak' : 'nie',
- 'slider' => $homePage === 1 ? 'nie' : 'tak',
- 'date_start' => !empty($item['date_start']) ? date('Y-m-d', strtotime((string)$item['date_start'])) : '-',
- 'date_end' => !empty($item['date_end']) ? date('Y-m-d', strtotime((string)$item['date_end'])) : '-',
- '_actions' => [
- [
- 'label' => 'Edytuj',
- 'url' => '/admin/banners/banner_edit/id=' . $id,
- 'class' => 'btn btn-xs btn-primary',
- ],
- [
- 'label' => 'Usun',
- 'url' => '/admin/banners/banner_delete/id=' . $id,
- 'class' => 'btn btn-xs btn-danger',
- 'confirm' => 'Na pewno chcesz usunac wybrany element?',
- ],
- ],
- ];
- }
-
- $total = (int)$result['total'];
- $totalPages = max(1, (int)ceil($total / $listRequest['perPage']));
-
- $viewModel = new \admin\ViewModels\Common\PaginatedTableViewModel(
- [
- ['key' => 'lp', 'label' => 'Lp.', 'class' => 'text-center', 'sortable' => false],
- ['key' => 'thumbnail', 'label' => 'Miniatura', 'class' => 'text-center', 'sortable' => false, 'raw' => true],
- ['key' => 'name', 'sort_key' => 'name', 'label' => 'Nazwa', 'sortable' => true, 'raw' => true],
- ['key' => 'status', 'sort_key' => 'status', 'label' => 'Aktywny', 'class' => 'text-center', 'sortable' => true, 'raw' => true],
- ['key' => 'home_page', 'sort_key' => 'home_page', 'label' => 'Strona glowna', 'class' => 'text-center', 'sortable' => true, 'raw' => true],
- ['key' => 'slider', 'label' => 'Slajder', 'class' => 'text-center', 'sortable' => false, 'raw' => true],
- ['key' => 'date_start', 'sort_key' => 'date_start', 'label' => 'Data rozpoczecia', 'class' => 'text-center', 'sortable' => true],
- ['key' => 'date_end', 'sort_key' => 'date_end', 'label' => 'Data zakonczenia', 'class' => 'text-center', 'sortable' => true],
- ],
- $rows,
- $listRequest['viewFilters'],
- [
- 'column' => $listRequest['sortColumn'],
- 'dir' => $sortDir,
- ],
- [
- 'page' => $listRequest['page'],
- 'per_page' => $listRequest['perPage'],
- 'total' => $total,
- 'total_pages' => $totalPages,
- ],
- array_merge($listRequest['queryFilters'], [
- 'sort' => $listRequest['sortColumn'],
- 'dir' => $sortDir,
- 'per_page' => $listRequest['perPage'],
- ]),
- $listRequest['perPageOptions'],
- $sortableColumns,
- '/admin/banners/view_list/',
- 'Brak danych w tabeli.',
- '/admin/banners/banner_edit/',
- 'Dodaj baner',
- 'banners/banners-list-custom-script'
- );
-
- return \Tpl::view('banners/banners-list', [
- 'viewModel' => $viewModel,
- ]);
- }
-
- /**
- * Edycja banera
- */
- public function edit(): string
- {
- $bannerId = (int)\S::get('id');
- $banner = $this->repository->find($bannerId);
- $languages = \admin\factory\Languages::languages_list();
-
- // Sprawdź czy są błędy walidacji z poprzedniego requestu
- $validationErrors = $_SESSION['form_errors'][$this->getFormId()] ?? null;
- if ($validationErrors) {
- unset($_SESSION['form_errors'][$this->getFormId()]);
- }
-
- $viewModel = $this->buildFormViewModel($banner, $languages, $validationErrors);
-
- return \Tpl::view('components/form-edit', ['form' => $viewModel]);
- }
-
- /**
- * Zapisanie banera (AJAX)
- */
- public function save(): void
- {
- $response = ['success' => false, 'errors' => []];
-
- $bannerId = (int)\S::get('id');
- $banner = $this->repository->find($bannerId);
- $languages = \admin\factory\Languages::languages_list();
-
- $viewModel = $this->buildFormViewModel($banner, $languages);
-
- // Przetwórz dane z POST
- $result = $this->formHandler->handleSubmit($viewModel, $_POST);
-
- if (!$result['success']) {
- // Zapisz błędy w sesji i zwróć jako JSON
- $_SESSION['form_errors'][$this->getFormId()] = $result['errors'];
- $response['errors'] = $result['errors'];
- echo json_encode($response);
- exit;
- }
-
- // Zapisz dane
- $data = $result['data'];
- $data['id'] = $bannerId ?: null;
-
- $savedId = $this->repository->save($data);
-
- if ($savedId) {
- \S::delete_dir('../temp/');
- $response = [
- 'success' => true,
- 'id' => $savedId,
- 'message' => 'Baner został zapisany.'
- ];
- } else {
- $response['errors'] = ['general' => 'Błąd podczas zapisywania do bazy.'];
- }
-
- echo json_encode($response);
- exit;
- }
-
- /**
- * Usuniecie banera
- */
- public function delete(): void
- {
- $bannerId = (int)\S::get('id');
- if ($this->repository->delete($bannerId)) {
- \S::delete_dir('../temp/');
- \S::alert('Baner zostal usuniety.');
- }
-
- header('Location: /admin/banners/view_list/');
- exit;
- }
-
- /**
- * Buduje model widoku formularza
- */
- private function buildFormViewModel(array $banner, array $languages, ?array $errors = null): FormEditViewModel
- {
- $bannerId = $banner['id'] ?? 0;
- $isNew = empty($bannerId);
-
- // Domyślne wartości dla nowego banera
- if ($isNew) {
- $banner['status'] = 1;
- $banner['home_page'] = 0;
- }
-
- $tabs = [
- new FormTab('settings', 'Ustawienia', 'fa-wrench'),
- new FormTab('content', 'Zawartość', 'fa-file'),
- ];
-
- $fields = [
- // Zakładka Ustawienia
- FormField::text('name', [
- 'label' => 'Nazwa',
- 'tab' => 'settings',
- 'required' => true,
- ]),
- FormField::switch('status', [
- 'label' => 'Aktywny',
- 'tab' => 'settings',
- 'value' => ($banner['status'] ?? 1) == 1,
- ]),
- FormField::date('date_start', [
- 'label' => 'Data rozpoczęcia',
- 'tab' => 'settings',
- ]),
- FormField::date('date_end', [
- 'label' => 'Data zakończenia',
- 'tab' => 'settings',
- ]),
- FormField::switch('home_page', [
- 'label' => 'Slajder / Strona główna',
- 'tab' => 'settings',
- 'value' => ($banner['home_page'] ?? 0) == 1,
- ]),
-
- // Sekcja językowa w zakładce Zawartość
- FormField::langSection('translations', 'content', [
- FormField::image('src', [
- 'label' => 'Obraz',
- 'filemanager' => true,
- ]),
- FormField::text('url', [
- 'label' => 'Url',
- ]),
- FormField::textarea('html', [
- 'label' => 'Kod HTML',
- 'rows' => 6,
- ]),
- FormField::editor('text', [
- 'label' => 'Treść',
- 'toolbar' => 'MyTool',
- 'height' => 300,
- ]),
- ]),
- ];
-
- $actions = [
- FormAction::save(
- '/admin/banners/banner_save/' . ($isNew ? '' : 'id=' . $bannerId),
- '/admin/banners/view_list/'
- ),
- FormAction::cancel('/admin/banners/view_list/'),
- ];
-
- return new FormEditViewModel(
- $this->getFormId(),
- $isNew ? 'Nowy baner' : 'Edycja banera',
- $banner,
- $fields,
- $tabs,
- $actions,
- 'POST',
- '/admin/banners/banner_save/' . ($isNew ? '' : 'id=' . $bannerId),
- '/admin/banners/view_list/',
- true,
- ['id' => $bannerId],
- $languages,
- $errors
- );
- }
-
- /**
- * Zwraca identyfikator formularza
- */
- private function getFormId(): string
- {
- return 'banner-edit';
- }
-}
diff --git a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/FilemanagerController.php b/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/FilemanagerController.php
deleted file mode 100644
index ba2535e..0000000
--- a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/FilemanagerController.php
+++ /dev/null
@@ -1,46 +0,0 @@
-ensureFilemanagerAccessKey();
- $filemanagerUrl = $this->buildFilemanagerUrl($akey);
-
- return \Tpl::view('filemanager/filemanager', [
- 'filemanager_url' => $filemanagerUrl,
- ]);
- }
-
- private function ensureFilemanagerAccessKey(): string
- {
- $expiresAt = (int)($_SESSION['rfm_akey_expires'] ?? 0);
- $existingKey = trim((string)($_SESSION['rfm_akey'] ?? ''));
-
- if ($existingKey !== '' && $expiresAt >= time()) {
- $_SESSION['rfm_akey_expires'] = time() + self::RFM_KEY_TTL;
- return $existingKey;
- }
-
- try {
- $newKey = bin2hex(random_bytes(16));
- } catch (\Throwable $e) {
- $newKey = sha1(uniqid('rfm', true));
- }
-
- $_SESSION['rfm_akey'] = $newKey;
- $_SESSION['rfm_akey_expires'] = time() + self::RFM_KEY_TTL;
-
- return $newKey;
- }
-
- private function buildFilemanagerUrl(string $akey): string
- {
- return self::FILEMANAGER_DIALOG_PATH . '?akey=' . rawurlencode($akey);
- }
-}
-
diff --git a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/ProductArchiveController.php b/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/ProductArchiveController.php
deleted file mode 100644
index 5883b59..0000000
--- a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/Controllers/ProductArchiveController.php
+++ /dev/null
@@ -1,165 +0,0 @@
-productRepository = $productRepository;
- }
-
- public function list(): string
- {
- $sortableColumns = ['id', 'name', 'price_brutto', 'price_brutto_promo', 'quantity'];
-
- $filterDefinitions = [
- [
- 'key' => 'phrase',
- 'label' => 'Nazwa / EAN / SKU',
- 'type' => 'text',
- ],
- ];
-
- $listRequest = \admin\Support\TableListRequestFactory::fromRequest(
- $filterDefinitions,
- $sortableColumns,
- 'id',
- [10, 15, 25, 50, 100],
- 10
- );
-
- $result = $this->productRepository->listArchivedForAdmin(
- $listRequest['filters'],
- $listRequest['sortColumn'],
- $listRequest['sortDir'],
- $listRequest['page'],
- $listRequest['perPage']
- );
-
- $rows = [];
- $lp = ($listRequest['page'] - 1) * $listRequest['perPage'] + 1;
- foreach ($result['items'] as $item) {
- $id = (int)($item['id'] ?? 0);
- $name = trim((string)($item['name'] ?? ''));
- $sku = trim((string)($item['sku'] ?? ''));
- $ean = trim((string)($item['ean'] ?? ''));
- $imageSrc = trim((string)($item['image_src'] ?? ''));
- $imageAlt = trim((string)($item['image_alt'] ?? ''));
- $priceBrutto = (string)($item['price_brutto'] ?? '');
- $priceBruttoPromo = (string)($item['price_brutto_promo'] ?? '');
- $quantity = (int)($item['quantity'] ?? 0);
- $combinations = (int)($item['combinations'] ?? 0);
-
- if ($imageSrc === '') {
- $imageSrc = '/admin/layout/images/no-image.png';
- } elseif (!preg_match('#^(https?:)?//#i', $imageSrc) && strpos($imageSrc, '/') !== 0) {
- $imageSrc = '/' . ltrim($imageSrc, '/');
- }
-
- $categories = trim((string)\admin\factory\ShopProduct::product_categories($id));
- $categoriesHtml = '';
- if ($categories !== '') {
- $categoriesHtml = ''
- . htmlspecialchars($categories, ENT_QUOTES, 'UTF-8')
- . '';
- }
-
- $skuEanParts = [];
- if ($sku !== '') {
- $skuEanParts[] = 'SKU: ' . htmlspecialchars($sku, ENT_QUOTES, 'UTF-8');
- }
- if ($ean !== '') {
- $skuEanParts[] = 'EAN: ' . htmlspecialchars($ean, ENT_QUOTES, 'UTF-8');
- }
- $skuEanHtml = '';
- if (!empty($skuEanParts)) {
- $skuEanHtml = '' . implode(', ', $skuEanParts) . '';
- }
-
- $productCell = ''
- . '
 . ')
'
- . '
'
- . ''
- . $categoriesHtml
- . $skuEanHtml;
-
- $rows[] = [
- 'lp' => $lp++ . '.',
- 'product' => $productCell,
- 'price_brutto' => $priceBrutto !== '' ? $priceBrutto : '-',
- 'price_brutto_promo' => $priceBruttoPromo !== '' ? $priceBruttoPromo : '-',
- 'quantity' => (string)$quantity,
- '_actions' => [
- [
- 'label' => 'Przywroc',
- 'url' => '/admin/product_archive/unarchive/product_id=' . $id,
- 'class' => 'btn btn-xs btn-success',
- 'confirm' => 'Na pewno chcesz przywrocic wybrany produkt z archiwum?',
- 'confirm_ok' => 'Przywroc',
- 'confirm_cancel' => 'Anuluj',
- ],
- ],
- ];
- }
-
- $total = (int)$result['total'];
- $totalPages = max(1, (int)ceil($total / $listRequest['perPage']));
-
- $viewModel = new \admin\ViewModels\Common\PaginatedTableViewModel(
- [
- ['key' => 'lp', 'label' => 'Lp.', 'class' => 'text-center', 'sortable' => false],
- ['key' => 'product', 'sort_key' => 'name', 'label' => 'Nazwa', 'sortable' => true, 'raw' => true],
- ['key' => 'price_brutto', 'sort_key' => 'price_brutto', 'label' => 'Cena', 'class' => 'text-center', 'sortable' => true],
- ['key' => 'price_brutto_promo', 'sort_key' => 'price_brutto_promo', 'label' => 'Cena promocyjna', 'class' => 'text-center', 'sortable' => true],
- ['key' => 'quantity', 'sort_key' => 'quantity', 'label' => 'Stan MG', 'class' => 'text-center', 'sortable' => true]
- ],
- $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/product_archive/products_list/',
- 'Brak danych w tabeli.',
- null,
- null,
- 'product-archive/products-list-custom-script'
- );
-
- return \Tpl::view('product-archive/products-list', [
- 'viewModel' => $viewModel,
- ]);
- }
-
- public function unarchive(): void
- {
- if ( $this->productRepository->unarchive( (int) \S::get( 'product_id' ) ) )
- \S::alert( 'Produkt został przywrócony z archiwum.' );
- else
- \S::alert( 'Podczas przywracania produktu z archiwum wystąpił błąd. Proszę spróbować ponownie' );
-
- header( 'Location: /admin/product_archive/products_list/' );
- exit;
- }
-}
diff --git a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/class.Site.php b/temp/update_build/ver_0.200_20260211_000158/autoload/admin/class.Site.php
deleted file mode 100644
index 7d196bd..0000000
--- a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/class.Site.php
+++ /dev/null
@@ -1,356 +0,0 @@
- $user['login'],
- 'ts' => time()
- ];
-
- $json = json_encode($payloadArr, JSON_UNESCAPED_SLASHES);
- $sig = hash_hmac('sha256', $json, self::APP_SECRET_KEY);
- $payload = base64_encode($json . '.' . $sig);
-
- setcookie( $cookie_name, $payload, [
- 'expires' => time() + (86400 * 14),
- 'path' => '/',
- 'domain' => $domain,
- 'secure' => true,
- 'httponly' => true,
- 'samesite' => 'Lax',
- ]);
- }
- }
-
- public static function special_actions()
- {
- $sa = \S::get('s-action');
- $domain = preg_replace('/^www\./', '', $_SERVER['SERVER_NAME']);
- $cookie_name = 'admin_remember_' . str_replace( '.', '-', $domain );
-
- switch ($sa)
- {
- case 'user-logon':
- {
- $login = \S::get('login');
- $pass = \S::get('password');
-
- $result = \admin\factory\Users::logon($login, $pass);
-
- if ( $result == 1 )
- {
- $user = \admin\factory\Users::details($login);
-
- if ( $user['twofa_enabled'] == 1 )
- {
- \S::set_session( 'twofa_pending', [
- 'uid' => (int)$user['id'],
- 'login' => $login,
- 'remember' => (bool)\S::get('remember'),
- 'started' => time(),
- ] );
-
- if ( !\admin\factory\Users::send_twofa_code( (int)$user['id'] ) )
- {
- \S::alert('Nie udało się wysłać kodu 2FA. Spróbuj ponownie.');
- \S::delete_session('twofa_pending');
- header('Location: /admin/');
- exit;
- }
-
- header('Location: /admin/user/twofa/');
- exit;
- }
- else
- {
- $user = \admin\factory\Users::details($login);
-
- self::finalize_admin_login(
- $user,
- $domain,
- $cookie_name,
- (bool)\S::get('remember')
- );
-
- header('Location: /admin/articles/view_list/');
- exit;
- }
- }
- else
- {
- if ($result == -1)
- {
- \S::alert('Z powodu 5 nieudanych prób Twoje konto zostało zablokowane.');
- }
- else
- {
- \S::alert('Podane hasło jest nieprawidłowe lub użytkownik nie istnieje.');
- }
- header('Location: /admin/');
- exit;
- }
- }
- break;
-
- case 'user-2fa-verify':
- {
- $pending = \S::get_session('twofa_pending');
- if ( !$pending || empty( $pending['uid'] ) ) {
- \S::alert('Sesja 2FA wygasła. Zaloguj się ponownie.');
- header('Location: /admin/');
- exit;
- }
-
- $code = trim((string)\S::get('twofa'));
- if (!preg_match('/^\d{6}$/', $code))
- {
- \S::alert('Nieprawidłowy format kodu.');
- header('Location: /admin/user/twofa/');
- exit;
- }
-
- $ok = \admin\factory\Users::verify_twofa_code((int)$pending['uid'], $code);
- if (!$ok)
- {
- \S::alert('Błędny lub wygasły kod.');
- header('Location: /admin/user/twofa/');
- exit;
- }
-
- // 2FA OK — finalna sesja
- $user = \admin\factory\Users::details($pending['login']);
-
- self::finalize_admin_login(
- $user,
- $domain,
- $cookie_name,
- $pending['remember'] ? true : false
- );
-
- header('Location: /admin/articles/view_list/');
- exit;
- }
- break;
-
- case 'user-2fa-resend':
- {
- $pending = \S::get_session('twofa_pending');
- if (!$pending || empty($pending['uid']))
- {
- \S::alert('Sesja 2FA wygasła. Zaloguj się ponownie.');
- header('Location: /admin/');
- exit;
- }
-
- if (!\admin\factory\Users::send_twofa_code((int)$pending['uid'], true))
- {
- \S::alert('Kod można wysłać ponownie po krótkiej przerwie.');
- }
- else
- {
- \S::alert('Nowy kod został wysłany.');
- }
- header('Location: /admin/user/twofa/');
- exit;
- }
- break;
-
- case 'user-logout':
- {
- setcookie($cookie_name, "", [
- 'expires' => time() - 86400,
- 'path' => '/',
- 'domain' => $domain,
- 'secure' => true,
- 'httponly' => true,
- 'samesite' => 'Lax',
- ]);
- \S::delete_session('twofa_pending');
- session_destroy();
- header('Location: /admin/');
- exit;
- }
- break;
- }
- }
-
- /**
- * Mapa nowych kontrolerów: module => fabryka kontrolera (DI)
- * Przy migracji kolejnego kontrolera - dodaj wpis tutaj
- */
- private static $newControllers = [];
-
- /**
- * Zwraca mapę fabryk kontrolerów (inicjalizacja runtime)
- */
- private static function getControllerFactories(): array
- {
- if ( !empty( self::$newControllers ) )
- return self::$newControllers;
-
- self::$newControllers = [
- 'Articles' => function() {
- global $mdb;
-
- return new \admin\Controllers\ArticlesController(
- new \Domain\Article\ArticleRepository( $mdb )
- );
- },
- 'Banners' => function() {
- global $mdb;
-
- return new \admin\Controllers\BannerController(
- new \Domain\Banner\BannerRepository( $mdb )
- );
- },
- 'Settings' => function() {
- global $mdb;
-
- return new \admin\Controllers\SettingsController(
- new \Domain\Settings\SettingsRepository( $mdb )
- );
- },
- 'ProductArchive' => function() {
- global $mdb;
-
- return new \admin\Controllers\ProductArchiveController(
- new \Domain\Product\ProductRepository( $mdb )
- );
- },
- // Alias dla starego modułu /admin/archive/products_list/
- 'Archive' => function() {
- global $mdb;
-
- return new \admin\Controllers\ProductArchiveController(
- new \Domain\Product\ProductRepository( $mdb )
- );
- },
- 'Dictionaries' => function() {
- global $mdb;
-
- return new \admin\Controllers\DictionariesController(
- new \Domain\Dictionaries\DictionariesRepository( $mdb )
- );
- },
- 'Filemanager' => function() {
- return new \admin\Controllers\FilemanagerController();
- },
- ];
-
- return self::$newControllers;
- }
-
- /**
- * Tworzy instancję nowego kontrolera z Dependency Injection
- */
- private static function createController( string $moduleName )
- {
- global $mdb;
-
- $factories = self::getControllerFactories();
- if ( !isset( $factories[$moduleName] ) )
- return null;
-
- $factory = $factories[$moduleName];
- if ( !is_callable( $factory ) )
- return null;
-
- return $factory();
- }
-
- /**
- * Mapowanie nazw akcji: stara_nazwa => nowa_nazwa
- * Potrzebne gdy stary routing używa innej konwencji nazw
- */
- private static $actionMap = [
- 'gallery_order_save' => 'galleryOrderSave',
- 'view_list' => 'list',
- 'article_edit' => 'edit',
- 'article_save' => 'save',
- 'article_delete' => 'delete',
- 'banner_edit' => 'edit',
- 'banner_save' => 'save',
- 'banner_delete' => 'delete',
- 'clear_cache' => 'clearCache',
- 'clear_cache_ajax' => 'clearCacheAjax',
- 'settings_save' => 'save',
- 'products_list' => 'list',
- 'unit_edit' => 'edit',
- 'unit_save' => 'save',
- 'unit_delete' => 'delete',
- ];
-
- public static function route()
- {
- $_SESSION['admin'] = true;
-
- if ( \S::get( 'p' ) )
- \S::set_session( 'p' , \S::get( 'p' ) );
-
- $page = \S::get_session( 'p' );
-
- // Budowanie nazwy modułu
- $moduleName = '';
- $results = explode( '_', \S::get( 'module' ) );
- if ( is_array( $results ) ) foreach ( $results as $row )
- $moduleName .= ucfirst( $row );
-
- $action = \S::get( 'action' );
-
- // 1. Sprawdź czy istnieje nowy kontroler
- $factories = self::getControllerFactories();
- if ( isset( $factories[$moduleName] ) )
- {
- $controller = self::createController( $moduleName );
- if ( $controller )
- {
- // Mapuj nazwę akcji (stara → nowa) lub użyj oryginalnej
- $newAction = self::$actionMap[$action] ?? $action;
-
- if ( method_exists( $controller, $newAction ) )
- {
- return $controller->$newAction();
- }
- }
-
- }
-
- // 2. Fallback na stary kontroler
- $class = '\admin\controls\\' . $moduleName;
-
- if ( class_exists( $class ) and method_exists( new $class, $action ) )
- return call_user_func_array( array( $class, $action ), array() );
- else
- {
- \S::alert( 'Nieprawidłowy adres url.' );
- return false;
- }
- }
-
- static public function update()
- {
- global $mdb;
-
- if ( $results = $mdb -> select( 'pp_updates', [ 'name' ], [ 'done' => 0 ] ) )
- {
- foreach ( $results as $row )
- {
- $class = '\admin\factory\Update';
- $method = $row['name'];
-
- if ( class_exists( $class ) and method_exists( new $class, $method ) )
- call_user_func_array( array( $class, $method ), array() );
- }
- }
- }
-}
diff --git a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/controls/class.ShopProduct.php b/temp/update_build/ver_0.200_20260211_000158/autoload/admin/controls/class.ShopProduct.php
deleted file mode 100644
index e076736..0000000
--- a/temp/update_build/ver_0.200_20260211_000158/autoload/admin/controls/class.ShopProduct.php
+++ /dev/null
@@ -1,414 +0,0 @@
- update( 'pp_shop_products', [ 'price_brutto_promo' => $price_brutto_promo, 'price_netto_promo' => $price_netto_promo ], [ 'id' => \S::get( 'products' )[0] ] );
-
- \admin\factory\ShopProduct::update_product_combinations_prices( \S::get( 'products' )[0], $price_netto, $vat, $price_netto_promo );
-
- echo json_encode( [ 'status' => 'ok', 'price_brutto_promo' => $price_brutto_promo, 'price_brutto' => $price_brutto ] );
- exit;
- }
- echo json_encode( [ 'status' => 'error' ] );
- exit;
- }
-
- // get_products_by_category
- static public function get_products_by_category() {
- global $mdb;
-
- $products = $mdb -> select( 'pp_shop_products_categories', 'product_id', [ 'category_id' => \S::get( 'category_id' ) ] );
-
- echo json_encode( [ 'status' => 'ok', 'products' => $products ] );
- exit;
- }
-
- static public function mass_edit()
- {
- return \Tpl::view( 'shop-product/mass-edit', [
- 'products' => \admin\factory\ShopProduct::products_list(),
- 'categories' => \admin\factory\ShopCategory::subcategories( null ),
- 'dlang' => \front\factory\Languages::default_language()
- ] );
- }
-
- static public function generate_combination()
- {
- foreach ( $_POST as $key => $val )
- {
- if ( strpos( $key, 'attribute_' ) !== false )
- {
- $attribute = explode( 'attribute_', $key );
- $attributes[ $attribute[1] ] = $val;
- }
- }
-
- if ( \admin\factory\ShopProduct::generate_permutation( (int) \S::get( 'product_id' ), $attributes ) )
- \S::alert( 'Kombinacje produktu zostały wygenerowane.' );
-
- header( 'Location: /admin/shop_product/product_combination/product_id=' . (int) \S::get( 'product_id' ) );
- exit;
- }
-
- //usunięcie kombinacji produktu
- static public function delete_combination()
- {
- if ( \admin\factory\ShopProduct::delete_combination( (int)\S::get( 'combination_id' ) ) )
- \S::alert( 'Kombinacja produktu została usunięta' );
- else
- \S::alert( 'Podczas usuwania kombinacji produktu wystąpił błąd. Proszę spróbować ponownie' );
-
- header( 'Location: /admin/shop_product/product_combination/product_id=' . \S::get( 'product_id' ) );
- exit;
- }
-
- static public function duplicate_product()
- {
- if ( \admin\factory\ShopProduct::duplicate_product( (int)\S::get( 'product-id' ), (int)\S::get( 'combination' ) ) )
- \S::set_message( 'Produkt został zduplikowany.' );
- else
- \S::alert( 'Podczas duplikowania produktu wystąpił błąd. Proszę spróbować ponownie' );
-
- header( 'Location: /admin/shop_product/view_list/' );
- exit;
- }
-
- public static function image_delete()
- {
- $response = [ 'status' => 'error', 'msg' => 'Podczas usuwania zdjecia wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( \admin\factory\ShopProduct::delete_img( \S::get( 'image_id' ) ) )
- $response = [ 'status' => 'ok' ];
-
- echo json_encode( $response );
- exit;
- }
-
- public static function images_order_save()
- {
- if ( \admin\factory\ShopProduct::images_order_save( \S::get( 'product_id' ), \S::get( 'order' ) ) )
- echo json_encode( [ 'status' => 'ok', 'msg' => 'Produkt został zapisany.' ] );
-
- exit;
- }
-
- public static function image_alt_change()
- {
- $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany atrybutu alt zdjęcia wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( \admin\factory\ShopProduct::image_alt_change( \S::get( 'image_id' ), \S::get( 'image_alt' ) ) )
- $response = [ 'status' => 'ok' ];
-
- echo json_encode( $response );
- exit;
- }
-
- // szybka zmiana statusu produktu
- static public function change_product_status() {
-
- if ( \admin\factory\ShopProduct::change_product_status( (int)\S::get( 'product-id' ) ) )
- \S::set_message( 'Status produktu został zmieniony' );
-
- header( 'Location: ' . $_SERVER['HTTP_REFERER'] );
- exit;
- }
-
- // szybka zmiana google xml label
- static public function product_change_custom_label()
- {
- $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany google xml label wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( \admin\factory\ShopProduct::product_change_custom_label( (int) \S::get( 'product_id' ), \S::get( 'custom_label' ), \S::get( 'value' ) ) )
- $response = [ 'status' => 'ok' ];
-
- echo json_encode( $response );
- exit;
- }
-
- // szybka zmiana ceny promocyjnej
- static public function product_change_price_brutto_promo()
- {
- $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany ceny wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( \admin\factory\ShopProduct::product_change_price_brutto_promo( (int) \S::get( 'product_id' ), \S::get( 'price' ) ) )
- $response = [ 'status' => 'ok' ];
-
- echo json_encode( $response );
- exit;
- }
-
- // szybka zmiana ceny
- static public function product_change_price_brutto()
- {
- $response = [ 'status' => 'error', 'msg' => 'Podczas zmiany ceny wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( \admin\factory\ShopProduct::product_change_price_brutto( (int) \S::get( 'product_id' ), \S::get( 'price' ) ) )
- $response = [ 'status' => 'ok' ];
-
- echo json_encode( $response );
- exit;
- }
-
- // pobierz bezpośredni url produktu
- static public function ajax_product_url()
- {
- echo json_encode( [ 'url' => \shop\Product::getProductUrl( \S::get( 'product_id' ) ) ] );
- exit;
- }
-
- // zapisanie produktu
- public static function save()
- {
- $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania produktu wystąpił błąd. Proszę spróbować ponownie.' ];
- $values = json_decode( \S::get( 'values' ), true );
-
- if ( $id = \admin\factory\ShopProduct::save(
- $values['id'], $values['name'], $values['short_description'], $values['description'], $values['status'], $values['meta_description'], $values['meta_keywords'], $values['seo_link'],
- $values['copy_from'], $values['categories'], $values['price_netto'], $values['price_brutto'], $values['vat'], $values['promoted'], $values['warehouse_message_zero'], $values['warehouse_message_nonzero'], $values['tab_name_1'],
- $values['tab_description_1'], $values['tab_name_2'], $values['tab_description_2'], $values['layout_id'], $values['products_related'], (int) $values['set'], $values['price_netto_promo'], $values['price_brutto_promo'],
- $values['new_to_date'], $values['stock_0_buy'], $values['wp'], $values['custom_label_0'], $values['custom_label_1'], $values['custom_label_2'], $values['custom_label_3'], $values['custom_label_4'], $values['additional_message'], (int)$values['quantity'], $values['additional_message_text'], $values['additional_message_required'] == 'on' ? 1 : 0, $values['canonical'], $values['meta_title'], $values['producer_id'], $values['sku'], $values['ean'], $values['product_unit'], $values['weight'], $values['xml_name'], $values['custom_field_name'], $values['custom_field_required'], $values['security_information'], $values['custom_field_type']
- ) ) {
- $response = [ 'status' => 'ok', 'msg' => 'Produkt został zapisany.', 'id' => $id ];
- }
-
- echo json_encode( $response );
- exit;
- }
-
- // product_unarchive
- static public function product_unarchive()
- {
- if ( \admin\factory\ShopProduct::product_unarchive( (int) \S::get( 'product_id' ) ) )
- \S::alert( 'Produkt został przywrócony z archiwum.' );
- else
- \S::alert( 'Podczas przywracania produktu z archiwum wystąpił błąd. Proszę spróbować ponownie' );
-
- header( 'Location: /admin/product_archive/products_list/' );
- exit;
- }
-
- static public function product_archive()
- {
- if ( \admin\factory\ShopProduct::product_archive( (int) \S::get( 'product_id' ) ) )
- \S::alert( 'Produkt został przeniesiony do archiwum.' );
- else
- \S::alert( 'Podczas przenoszenia produktu do archiwum wystąpił błąd. Proszę spróbować ponownie' );
-
- header( 'Location: /admin/shop_product/view_list/' );
- exit;
- }
-
- public static function product_delete()
- {
- if ( \admin\factory\ShopProduct::product_delete( (int) \S::get( 'id' ) ) )
- \S::set_message( 'Produkt został usunięty.' );
- else
- \S::alert( 'Podczas usuwania produktu wystąpił błąd. Proszę spróbować ponownie' );
- header( 'Location: /admin/shop_product/view_list/' );
- exit;
- }
-
- // edycja produktu
- public static function product_edit() {
- global $user, $mdb;
-
- if ( !$user ) {
- header( 'Location: /admin/' );
- exit;
- }
-
- \admin\factory\ShopProduct::delete_nonassigned_images();
- \admin\factory\ShopProduct::delete_nonassigned_files();
-
- return \Tpl::view( 'shop-product/product-edit', [
- 'product' => \admin\factory\ShopProduct::product_details( (int) \S::get( 'id' ) ),
- 'languages' => \admin\factory\Languages::languages_list(),
- 'categories' => \admin\factory\ShopCategory::subcategories( null ),
- 'layouts' => \admin\factory\Layouts::layouts_list(),
- 'products' => \admin\factory\ShopProduct::products_list(),
- 'dlang' => \front\factory\Languages::default_language(),
- 'sets' => \shop\ProductSet::sets_list(),
- 'producers' => \admin\factory\ShopProducer::all(),
- 'units' => ( new \Domain\Dictionaries\DictionariesRepository( $mdb ) ) -> allUnits(),
- 'user' => $user
- ] );
- }
-
- // ajax_load_products ARCHIVE
- static public function ajax_load_products_archive()
- {
- echo json_encode( [
- 'status' => 'deprecated',
- 'msg' => 'Endpoint nie jest juz wspierany. Uzyj /admin/product_archive/products_list/.',
- 'redirect_url' => '/admin/product_archive/products_list/'
- ] );
- exit;
- }
-
- // ajax_load_products
- static public function ajax_load_products() {
-
- $response = [ 'status' => 'error', 'msg' => 'Podczas ładowania produktów wystąpił błąd. Proszę spróbować ponownie.' ];
-
- \S::set_session( 'products_list_current_page', \S::get( 'current_page' ) );
- \S::set_session( 'products_list_query', \S::get( 'query' ) );
-
- if ( $products = \admin\factory\ShopProduct::ajax_products_list( \S::get_session( 'products_list_current_page' ), \S::get_session( 'products_list_query' ) ) ) {
- $response = [
- 'status' => 'ok',
- 'pagination_max' => ceil( $products['products_count'] / 10 ),
- 'html' => \Tpl::view( 'shop-product/products-list-table', [
- 'products' => $products['products'],
- 'current_page' => \S::get( 'current_page' ),
- 'baselinker_enabled' => \admin\factory\Integrations::baselinker_settings( 'enabled' ),
- 'apilo_enabled' => \admin\factory\Integrations::apilo_settings( 'enabled' ),
- 'sellasist_enabled' => \admin\factory\Integrations::sellasist_settings( 'enabled' ),
- 'show_xml_data' => \S::get_session( 'show_xml_data' )
- ] )
- ];
- }
-
- echo json_encode( $response );
- exit;
- }
-
- static public function view_list()
- {
- $current_page = \S::get_session( 'products_list_current_page' );
-
- if ( !$current_page ) {
- $current_page = 1;
- \S::set_session( 'products_list_current_page', $current_page );
- }
-
- $query = \S::get_session( 'products_list_query' );
- if ( $query ) {
- $query_array = [];
- parse_str( $query, $query_array );
- }
-
- if ( \S::get( 'show_xml_data' ) === 'true' ) {
- \S::set_session( 'show_xml_data', true );
- } else if ( \S::get( 'show_xml_data' ) === 'false' ) {
- \S::set_session( 'show_xml_data', false );
- }
-
- return \Tpl::view( 'shop-product/products-list', [
- 'current_page' => $current_page,
- 'query_array' => $query_array,
- 'pagination_max' => ceil( \admin\factory\ShopProduct::count_product() / 10 ),
- 'baselinker_enabled' => \admin\factory\Integrations::baselinker_settings( 'enabled' ),
- 'apilo_enabled' => \admin\factory\Integrations::apilo_settings( 'enabled' ),
- 'sellasist_enabled' => \admin\factory\Integrations::sellasist_settings( 'enabled' ),
- 'show_xml_data' => \S::get_session( 'show_xml_data' ),
- 'shoppro_enabled' => \admin\factory\Integrations::shoppro_settings( 'enabled' )
- ] );
- }
-
- //
- // KOMBINACJE PRODUKTU
- //
-
- // zapisanie możliwości zakupu przy stanie 0 w kombinacji produktu
- static public function product_combination_stock_0_buy_save()
- {
- \admin\factory\ShopProduct::product_combination_stock_0_buy_save( (int)\S::get( 'product_id' ), \S::get( 'stock_0_buy' ) );
- exit;
- }
-
- // zapisanie sku w kombinacji produktu
- static public function product_combination_sku_save()
- {
- \admin\factory\ShopProduct::product_combination_sku_save( (int)\S::get( 'product_id' ), \S::get( 'sku' ) );
- exit;
- }
-
- // zapisanie ilości w kombinacji produktu
- static public function product_combination_quantity_save()
- {
- \admin\factory\ShopProduct::product_combination_quantity_save( (int)\S::get( 'product_id' ), \S::get( 'quantity' ) );
- exit;
- }
-
- // zapisanie ceny w kombinacji produktu
- static public function product_combination_price_save()
- {
- \admin\factory\ShopProduct::product_combination_price_save( (int)\S::get( 'product_id' ), \S::get( 'price' ) );
- exit;
- }
-
- //wyświetlenie kombinacji produktu
- static public function product_combination()
- {
- return \Tpl::view( 'shop-product/product-combination', [
- 'product' => \admin\factory\ShopProduct::product_details( (int) \S::get( 'product_id' ) ),
- 'attributes' => \admin\factory\ShopAttribute::get_attributes_list(),
- 'default_language' => \front\factory\Languages::default_language(),
- 'product_permutations' => \admin\factory\ShopProduct::get_product_permutations( (int) \S::get( 'product_id' ) )
- ] );
- }
-
- // generate_sku_code
- static public function generate_sku_code() {
- $response = [ 'status' => 'error', 'msg' => 'Podczas generowania kodu sku wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( $sku = \shop\Product::generate_sku_code( \S::get( 'product_id' ) ) )
- $response = [ 'status' => 'ok', 'sku' => $sku ];
-
- echo json_encode( $response );
- exit;
- }
-
- // product_xml_name_save
- static public function product_xml_name_save() {
- $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania nazwy produktu wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( \shop\Product::product_xml_name_save( \S::get( 'product_id' ), \S::get( 'product_xml_name' ), \S::get( 'lang_id' ) ) )
- $response = [ 'status' => 'ok' ];
-
- echo json_encode( $response );
- exit;
- }
-
- // product_custom_label_suggestions
- static public function product_custom_label_suggestions() {
- $response = [ 'status' => 'error', 'msg' => 'Podczas pobierania sugestii dla custom label wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( $suggestions = \shop\Product::product_custom_label_suggestions( \S::get( 'custom_label' ), \S::get( 'label_type' ) ) )
- $response = [ 'status' => 'ok', 'suggestions' => $suggestions ];
-
- echo json_encode( $response );
- exit;
- }
-
- // product_custom_label_save
- static public function product_custom_label_save() {
- $response = [ 'status' => 'error', 'msg' => 'Podczas zapisywania custom label wystąpił błąd. Proszę spróbować ponownie.' ];
-
- if ( \shop\Product::product_custom_label_save( \S::get( 'product_id' ), \S::get( 'custom_label' ), \S::get( 'label_type' ) ) )
- $response = [ 'status' => 'ok' ];
-
- echo json_encode( $response );
- exit;
- }
-}
diff --git a/temp/update_build/ver_0.290.zip b/temp/update_build/ver_0.290.zip
deleted file mode 100644
index 3577bf8..0000000
Binary files a/temp/update_build/ver_0.290.zip and /dev/null differ
diff --git a/temp/update_build/ver_0.290_files.txt b/temp/update_build/ver_0.290_files.txt
deleted file mode 100644
index 5c689ac..0000000
--- a/temp/update_build/ver_0.290_files.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-F: ../autoload/front/controls/class.ShopCoupon.php
-F: ../autoload/front/factory/class.ShopCoupon.php
-F: ../autoload/front/controls/class.ShopOrder.php
-F: ../autoload/front/factory/class.ShopOrder.php
-F: ../autoload/front/view/class.ShopOrder.php
diff --git a/temp/update_build/ver_0.291.zip b/temp/update_build/ver_0.291.zip
deleted file mode 100644
index ca67eaa..0000000
Binary files a/temp/update_build/ver_0.291.zip and /dev/null differ
diff --git a/temp/update_build/ver_0.291_files.txt b/temp/update_build/ver_0.291_files.txt
deleted file mode 100644
index 715550b..0000000
--- a/temp/update_build/ver_0.291_files.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-F: ../autoload/front/controls/class.ShopProducer.php
-F: ../autoload/shop/class.Producer.php
diff --git a/temp/update_build/ver_0.292.zip b/temp/update_build/ver_0.292.zip
deleted file mode 100644
index e992110..0000000
Binary files a/temp/update_build/ver_0.292.zip and /dev/null differ
diff --git a/temp/update_build/ver_0.292_files.txt b/temp/update_build/ver_0.292_files.txt
deleted file mode 100644
index 0993463..0000000
--- a/temp/update_build/ver_0.292_files.txt
+++ /dev/null
@@ -1,9 +0,0 @@
-F: ../autoload/front/controls/class.ShopProduct.php
-F: ../autoload/front/factory/class.ShopPaymentMethod.php
-F: ../autoload/front/factory/class.ShopProduct.php
-F: ../autoload/front/factory/class.ShopPromotion.php
-F: ../autoload/front/factory/class.ShopStatuses.php
-F: ../autoload/front/factory/class.ShopTransport.php
-F: ../autoload/front/view/class.ShopPaymentMethod.php
-F: ../autoload/front/view/class.ShopTransport.php
-F: ../autoload/shop/class.PaymentMethod.php
diff --git a/temp/update_build/ver_0.293.zip b/temp/update_build/ver_0.293.zip
deleted file mode 100644
index e832633..0000000
Binary files a/temp/update_build/ver_0.293.zip and /dev/null differ
diff --git a/temp/update_build/ver_0.293_files.txt b/temp/update_build/ver_0.293_files.txt
deleted file mode 100644
index 0a506e8..0000000
--- a/temp/update_build/ver_0.293_files.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-F: ../autoload/front/controls/class.Site.php
-F: ../autoload/front/view/class.Site.php
diff --git a/temp/ver_0.294.zip b/temp/ver_0.294.zip
deleted file mode 100644
index 80fefac..0000000
Binary files a/temp/ver_0.294.zip and /dev/null differ
diff --git a/temp/ver_0.294_files.txt b/temp/ver_0.294_files.txt
deleted file mode 100644
index 5b811be..0000000
--- a/temp/ver_0.294_files.txt
+++ /dev/null
@@ -1,12 +0,0 @@
-F: ../autoload/shop/class.Basket.php
-F: ../autoload/shop/class.Category.php
-F: ../autoload/shop/class.Coupon.php
-F: ../autoload/shop/class.Order.php
-F: ../autoload/shop/class.Product.php
-F: ../autoload/shop/class.ProductAttribute.php
-F: ../autoload/shop/class.ProductCustomField.php
-F: ../autoload/shop/class.ProductSet.php
-F: ../autoload/shop/class.Promotion.php
-F: ../autoload/shop/class.Search.php
-F: ../autoload/shop/class.Shop.php
-F: ../autoload/shop/class.Transport.php
diff --git a/tests/Unit/Domain/Order/OrderAdminServiceTest.php b/tests/Unit/Domain/Order/OrderAdminServiceTest.php
new file mode 100644
index 0000000..569a0da
--- /dev/null
+++ b/tests/Unit/Domain/Order/OrderAdminServiceTest.php
@@ -0,0 +1,230 @@
+createMock(OrderRepository::class);
+ }
+
+ return new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
+ }
+
+ public function testConstructorAcceptsOnlyOrderRepository(): void
+ {
+ $orderRepo = $this->createMock(OrderRepository::class);
+ $service = new OrderAdminService($orderRepo);
+ $this->assertInstanceOf(OrderAdminService::class, $service);
+ }
+
+ public function testConstructorAcceptsAllDependencies(): void
+ {
+ $orderRepo = $this->createMock(OrderRepository::class);
+ $productRepo = $this->createMock(ProductRepository::class);
+ $settingsRepo = $this->createMock(SettingsRepository::class);
+ $transportRepo = $this->createMock(TransportRepository::class);
+
+ $service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
+ $this->assertInstanceOf(OrderAdminService::class, $service);
+ }
+
+ public function testSearchProductsReturnsEmptyForEmptyQuery(): void
+ {
+ $productRepo = $this->createMock(ProductRepository::class);
+ $productRepo->expects($this->never())->method('searchProductByNameAjax');
+
+ $service = $this->createService(null, $productRepo);
+ $result = $service->searchProducts('', 'pl');
+
+ $this->assertSame([], $result);
+ }
+
+ public function testSearchProductsReturnsEmptyWithoutProductRepo(): void
+ {
+ $service = $this->createService();
+ $result = $service->searchProducts('test', 'pl');
+
+ $this->assertSame([], $result);
+ }
+
+ public function testSearchProductsReturnsFormattedResults(): void
+ {
+ $productRepo = $this->createMock(ProductRepository::class);
+ $productRepo->method('searchProductByNameAjax')
+ ->with('koszulka', 'pl')
+ ->willReturn([
+ ['product_id' => 10],
+ ['product_id' => 20],
+ ]);
+
+ $productRepo->method('findCached')
+ ->willReturnCallback(function ($id) {
+ if ($id === 10) {
+ return [
+ 'language' => ['name' => 'Koszulka biała'],
+ 'sku' => 'KB-001',
+ 'ean' => '',
+ 'price_brutto' => 49.99,
+ 'price_brutto_promo' => 39.99,
+ 'vat' => 23,
+ 'quantity' => 15,
+ 'parent_id' => 0,
+ ];
+ }
+ return null; // product 20 not found
+ });
+
+ $productRepo->method('getProductImg')
+ ->willReturn('/images/products/test.jpg');
+
+ $service = $this->createService(null, $productRepo);
+ $results = $service->searchProducts('koszulka', 'pl');
+
+ $this->assertCount(1, $results);
+ $this->assertSame(10, $results[0]['product_id']);
+ $this->assertSame('Koszulka biała', $results[0]['name']);
+ $this->assertSame('KB-001', $results[0]['sku']);
+ $this->assertSame(49.99, $results[0]['price_brutto']);
+ $this->assertSame(39.99, $results[0]['price_brutto_promo']);
+ $this->assertSame(15, $results[0]['quantity']);
+ }
+
+ public function testSaveOrderProductsReturnsFalseForInvalidOrderId(): void
+ {
+ $service = $this->createService();
+ $this->assertFalse($service->saveOrderProducts(0, []));
+ }
+
+ public function testSaveOrderProductsDeletesRemovedProducts(): void
+ {
+ $orderRepo = $this->createMock(OrderRepository::class);
+ $productRepo = $this->createMock(ProductRepository::class);
+ $settingsRepo = $this->createMock(SettingsRepository::class);
+ $transportRepo = $this->createMock(TransportRepository::class);
+
+ // Existing products
+ $orderRepo->method('orderProducts')
+ ->with(1)
+ ->willReturn([
+ ['id' => 100, 'product_id' => 5, 'quantity' => 2, 'price_brutto' => 10, 'price_brutto_promo' => 0],
+ ['id' => 101, 'product_id' => 6, 'quantity' => 1, 'price_brutto' => 20, 'price_brutto_promo' => 0],
+ ]);
+
+ // Product 100 is submitted with delete flag
+ // Product 101 is not submitted at all (also deleted)
+ $orderRepo->expects($this->exactly(2))->method('deleteOrderProduct');
+ $orderRepo->method('findRawById')->willReturn(['id' => 1, 'transport_id' => 1]);
+ $transportRepo->method('findActiveById')->willReturn(null);
+
+ // Stock should be returned for both deleted products
+ $productRepo->method('getQuantity')->willReturn(10);
+ $productRepo->expects($this->exactly(2))->method('updateQuantity');
+
+ $service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
+ $result = $service->saveOrderProducts(1, [
+ ['order_product_id' => 100, 'delete' => '1', 'quantity' => 2],
+ ]);
+
+ $this->assertTrue($result);
+ }
+
+ public function testSaveOrderProductsUpdatesQuantityAndAdjustsStock(): void
+ {
+ $orderRepo = $this->createMock(OrderRepository::class);
+ $productRepo = $this->createMock(ProductRepository::class);
+ $settingsRepo = $this->createMock(SettingsRepository::class);
+ $transportRepo = $this->createMock(TransportRepository::class);
+
+ // Existing: qty=3
+ $orderRepo->method('orderProducts')
+ ->willReturn([
+ ['id' => 100, 'product_id' => 5, 'quantity' => 3, 'price_brutto' => 10, 'price_brutto_promo' => 0],
+ ]);
+
+ // Submit: qty=5 (increased by 2 → stock decreases by 2)
+ $orderRepo->expects($this->once())->method('updateOrderProduct')
+ ->with(100, $this->callback(function ($data) {
+ return $data['quantity'] === 5;
+ }));
+
+ $orderRepo->method('findRawById')->willReturn(['id' => 1, 'transport_id' => 1]);
+ $transportRepo->method('findActiveById')->willReturn(null);
+
+ $productRepo->method('getQuantity')->with(5)->willReturn(20);
+ $productRepo->expects($this->once())->method('updateQuantity')
+ ->with(5, 18); // 20 + (3 - 5) = 18
+
+ $service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
+ $service->saveOrderProducts(1, [
+ ['order_product_id' => 100, 'product_id' => 5, 'quantity' => 5, 'price_brutto' => 10, 'price_brutto_promo' => 0],
+ ]);
+ }
+
+ public function testSaveOrderProductsAddsNewProductAndDecreasesStock(): void
+ {
+ $orderRepo = $this->createMock(OrderRepository::class);
+ $productRepo = $this->createMock(ProductRepository::class);
+ $settingsRepo = $this->createMock(SettingsRepository::class);
+ $transportRepo = $this->createMock(TransportRepository::class);
+
+ $orderRepo->method('orderProducts')->willReturn([]);
+
+ $orderRepo->expects($this->once())->method('addOrderProduct')
+ ->with(1, $this->callback(function ($data) {
+ return $data['product_id'] === 10
+ && $data['name'] === 'New Product'
+ && $data['quantity'] === 2;
+ }));
+
+ $orderRepo->method('findRawById')->willReturn(['id' => 1, 'transport_id' => 1]);
+ $transportRepo->method('findActiveById')->willReturn(null);
+
+ $productRepo->method('getQuantity')->with(10)->willReturn(15);
+ $productRepo->expects($this->once())->method('updateQuantity')
+ ->with(10, 13); // 15 - 2
+
+ $service = new OrderAdminService($orderRepo, $productRepo, $settingsRepo, $transportRepo);
+ $service->saveOrderProducts(1, [
+ [
+ 'order_product_id' => 0,
+ 'product_id' => 10,
+ 'parent_product_id' => 10,
+ 'name' => 'New Product',
+ 'vat' => 23,
+ 'price_brutto' => 50,
+ 'price_brutto_promo' => 0,
+ 'quantity' => 2,
+ ],
+ ]);
+ }
+
+ public function testGetFreeDeliveryThresholdReturnsZeroWithoutSettingsRepo(): void
+ {
+ $service = $this->createService();
+ $this->assertSame(0.0, $service->getFreeDeliveryThreshold());
+ }
+
+ public function testGetFreeDeliveryThresholdReturnsValue(): void
+ {
+ $settingsRepo = $this->createMock(SettingsRepository::class);
+ $settingsRepo->method('getSingleValue')
+ ->with('free_delivery')
+ ->willReturn('150.00');
+
+ $service = $this->createService(null, null, $settingsRepo);
+ $this->assertSame(150.0, $service->getFreeDeliveryThreshold());
+ }
+}
diff --git a/tests/Unit/Domain/Order/OrderRepositoryTest.php b/tests/Unit/Domain/Order/OrderRepositoryTest.php
index 98cac9b..bc93a8b 100644
--- a/tests/Unit/Domain/Order/OrderRepositoryTest.php
+++ b/tests/Unit/Domain/Order/OrderRepositoryTest.php
@@ -209,6 +209,138 @@ class OrderRepositoryTest extends TestCase
$this->assertSame($expectedPrefix . '006', $number);
}
+ // --- Order product CRUD tests ---
+
+ public function testGetOrderProductReturnsNullForInvalidId(): void
+ {
+ $mockDb = $this->createMock(\medoo::class);
+ $mockDb->expects($this->never())->method('get');
+
+ $repository = new OrderRepository($mockDb);
+ $this->assertNull($repository->getOrderProduct(0));
+ $this->assertNull($repository->getOrderProduct(-1));
+ }
+
+ public function testGetOrderProductReturnsArray(): void
+ {
+ $mockDb = $this->createMock(\medoo::class);
+ $mockDb->method('get')
+ ->with('pp_shop_order_products', '*', ['id' => 5])
+ ->willReturn(['id' => 5, 'order_id' => 1, 'name' => 'Test']);
+
+ $repository = new OrderRepository($mockDb);
+ $result = $repository->getOrderProduct(5);
+
+ $this->assertIsArray($result);
+ $this->assertSame(5, $result['id']);
+ }
+
+ public function testAddOrderProductReturnsNullForInvalidOrderId(): void
+ {
+ $mockDb = $this->createMock(\medoo::class);
+ $mockDb->expects($this->never())->method('insert');
+
+ $repository = new OrderRepository($mockDb);
+ $this->assertNull($repository->addOrderProduct(0, ['name' => 'Test']));
+ }
+
+ public function testAddOrderProductInsertsAndReturnsId(): void
+ {
+ $mockDb = $this->createMock(\medoo::class);
+ $mockDb->expects($this->once())->method('insert')
+ ->with('pp_shop_order_products', $this->callback(function ($data) {
+ return $data['order_id'] === 10
+ && $data['product_id'] === 5
+ && $data['name'] === 'Test Product'
+ && $data['quantity'] === 2;
+ }));
+ $mockDb->method('id')->willReturn('99');
+
+ $repository = new OrderRepository($mockDb);
+ $result = $repository->addOrderProduct(10, [
+ 'product_id' => 5,
+ 'name' => 'Test Product',
+ 'quantity' => 2,
+ 'price_brutto' => 19.99,
+ ]);
+
+ $this->assertSame(99, $result);
+ }
+
+ public function testUpdateOrderProductReturnsFalseForInvalidId(): void
+ {
+ $mockDb = $this->createMock(\medoo::class);
+ $mockDb->expects($this->never())->method('update');
+
+ $repository = new OrderRepository($mockDb);
+ $this->assertFalse($repository->updateOrderProduct(0, ['quantity' => 3]));
+ }
+
+ public function testUpdateOrderProductUpdatesFields(): void
+ {
+ $mockDb = $this->createMock(\medoo::class);
+ $mockDb->expects($this->once())->method('update')
+ ->with('pp_shop_order_products', $this->callback(function ($data) {
+ return $data['quantity'] === 3
+ && $data['price_brutto'] === 25.50;
+ }), ['id' => 7]);
+
+ $repository = new OrderRepository($mockDb);
+ $result = $repository->updateOrderProduct(7, [
+ 'quantity' => 3,
+ 'price_brutto' => 25.50,
+ ]);
+
+ $this->assertTrue($result);
+ }
+
+ public function testUpdateOrderProductReturnsFalseForEmptyData(): void
+ {
+ $mockDb = $this->createMock(\medoo::class);
+ $mockDb->expects($this->never())->method('update');
+
+ $repository = new OrderRepository($mockDb);
+ $this->assertFalse($repository->updateOrderProduct(7, []));
+ }
+
+ public function testDeleteOrderProductReturnsFalseForInvalidId(): void
+ {
+ $mockDb = $this->createMock(\medoo::class);
+ $mockDb->expects($this->never())->method('delete');
+
+ $repository = new OrderRepository($mockDb);
+ $this->assertFalse($repository->deleteOrderProduct(0));
+ }
+
+ public function testDeleteOrderProductCallsDelete(): void
+ {
+ $mockDb = $this->createMock(\medoo::class);
+ $mockDb->expects($this->once())->method('delete')
+ ->with('pp_shop_order_products', ['id' => 12]);
+
+ $repository = new OrderRepository($mockDb);
+ $this->assertTrue($repository->deleteOrderProduct(12));
+ }
+
+ public function testUpdateTransportCostDoesNothingForInvalidId(): void
+ {
+ $mockDb = $this->createMock(\medoo::class);
+ $mockDb->expects($this->never())->method('update');
+
+ $repository = new OrderRepository($mockDb);
+ $repository->updateTransportCost(0, 15.0);
+ }
+
+ public function testUpdateTransportCostUpdatesOrder(): void
+ {
+ $mockDb = $this->createMock(\medoo::class);
+ $mockDb->expects($this->once())->method('update')
+ ->with('pp_shop_orders', ['transport_cost' => 12.50], ['id' => 5]);
+
+ $repository = new OrderRepository($mockDb);
+ $repository->updateTransportCost(5, 12.50);
+ }
+
public function testGenerateOrderNumberStartsAt001(): void
{
$mockDb = $this->createMock(\medoo::class);
diff --git a/tests/Unit/admin/Controllers/ShopOrderControllerTest.php b/tests/Unit/admin/Controllers/ShopOrderControllerTest.php
index f055c68..2b72a32 100644
--- a/tests/Unit/admin/Controllers/ShopOrderControllerTest.php
+++ b/tests/Unit/admin/Controllers/ShopOrderControllerTest.php
@@ -41,6 +41,7 @@ class ShopOrderControllerTest extends TestCase
$this->assertTrue(method_exists($this->controller, 'toggle_trustmate_send'));
$this->assertTrue(method_exists($this->controller, 'delete'));
$this->assertTrue(method_exists($this->controller, 'order_delete'));
+ $this->assertTrue(method_exists($this->controller, 'search_products_ajax'));
}
public function testViewActionsReturnString(): void
@@ -70,6 +71,7 @@ class ShopOrderControllerTest extends TestCase
$this->assertEquals('void', (string)$reflection->getMethod('toggle_trustmate_send')->getReturnType());
$this->assertEquals('void', (string)$reflection->getMethod('delete')->getReturnType());
$this->assertEquals('void', (string)$reflection->getMethod('order_delete')->getReturnType());
+ $this->assertEquals('void', (string)$reflection->getMethod('search_products_ajax')->getReturnType());
}
public function testConstructorRequiresOrderAdminService(): void
@@ -78,7 +80,9 @@ class ShopOrderControllerTest extends TestCase
$constructor = $reflection->getConstructor();
$params = $constructor->getParameters();
- $this->assertCount(1, $params);
+ $this->assertCount(2, $params);
$this->assertEquals('Domain\\Order\\OrderAdminService', $params[0]->getType()->getName());
+ $this->assertEquals('Domain\\Product\\ProductRepository', $params[1]->getType()->getName());
+ $this->assertTrue($params[1]->isOptional());
}
}
diff --git a/updates/0.20/ver_0.295.zip b/updates/0.20/ver_0.295.zip
new file mode 100644
index 0000000..6aa0d93
Binary files /dev/null and b/updates/0.20/ver_0.295.zip differ
diff --git a/updates/changelog.php b/updates/changelog.php
index a34c25c..6bc7f2b 100644
--- a/updates/changelog.php
+++ b/updates/changelog.php
@@ -1,3 +1,9 @@
+ver. 0.295 - 19.02.2026
+- NEW - Edycja produktów w zamówieniu z panelu admina (dodawanie, usuwanie, zmiana ilości/cen)
+- NEW - Wyszukiwarka produktów AJAX w formularzu edycji zamówienia
+- NEW - Automatyczna korekta stanów magazynowych i przeliczanie kosztu dostawy
+- FIX - Cena promo w zamówieniu = 0 gdy identyczna z ceną bazową
+
ver. 0.294 - 19.02.2026
- FIX - Code review zakończony (96/96 klas, ~1144 metod): 27 fixów across all layers
- FIX - Domain: null guard na query()->fetchAll() w 8 repozytoriach, redundancja DI w PromotionRepository
diff --git a/updates/versions.php b/updates/versions.php
index 6de390c..2b5c3dd 100644
--- a/updates/versions.php
+++ b/updates/versions.php
@@ -1,5 +1,5 @@
-$current_ver = 294;
+$current_ver = 295;
for ($i = 1; $i <= $current_ver; $i++)
{