diff --git a/CLAUDE.md b/CLAUDE.md index f9e5272..586070c 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -36,7 +36,7 @@ composer test PHPUnit 9.6 via `phpunit.phar`. Bootstrap: `tests/bootstrap.php`. Config: `phpunit.xml`. -Current suite: **610 tests, 1816 assertions**. +Current suite: **610 tests, 1817 assertions**. ### Creating Updates See `docs/UPDATE_INSTRUCTIONS.md` for the full procedure. Updates are ZIP packages in `updates/0.XX/`. Never include `*.md` files, `updates/changelog.php`, or root `.htaccess` in update ZIPs. @@ -59,7 +59,7 @@ shopPRO/ │ │ ├── LayoutEngine.php # Layout engine (\front\LayoutEngine) │ │ ├── Controllers/ # DI controllers (\front\Controllers\) │ │ └── Views/ # Static views (\front\Views\) -│ └── shop/ # Legacy shop classes (Product, Order, etc.) +│ └── shop/ # EMPTY — all legacy classes migrated to Domain\ ├── admin/ # Admin panel │ ├── templates/ # Admin view templates │ └── layout/ # Admin CSS/JS/icons @@ -93,7 +93,7 @@ Custom autoloader in each entry point (not Composer autoload at runtime). Tries - `\admin\Controllers\` → `autoload/admin/Controllers/` (lowercase a — existing directory) - `\Shared\` → `autoload/Shared/` - `\front\` → `autoload/front/` -- `\shop\` → `autoload/shop/` +- ~~`\shop\`~~ → `autoload/shop/` (EMPTY — all classes migrated to `\Domain\`) - Do NOT use `\Admin\` (uppercase A) — the server directory is `admin/` (lowercase) ### Domain-Driven Architecture (migration complete for admin + frontend) @@ -187,6 +187,7 @@ Before starting implementation, review current state of docs (see AGENTS.md for - `docs/PROJECT_STRUCTURE.md` — detailed project structure and module status - `docs/REFACTORING_PLAN.md` — Domain migration plan and status - `docs/FRONTEND_REFACTORING_PLAN.md` — frontend migration plan (mostly complete) +- `docs/LEGACY_SHOP_REFACTORING_PLAN.md` — plan usunięcia 12 legacy klas z `autoload/shop/` (UKOŃCZONY — wszystkie 12 klas usunięte) - `docs/DATABASE_STRUCTURE.md` — full database schema - `docs/TESTING.md` — test suite status and history - `docs/FORM_EDIT_SYSTEM.md` — form system architecture diff --git a/admin/templates/dashboard/main-view.php b/admin/templates/dashboard/main-view.php index a835dee..fda8954 100644 --- a/admin/templates/dashboard/main-view.php +++ b/admin/templates/dashboard/main-view.php @@ -123,7 +123,7 @@ best_sales_products ) ): foreach ( $this -> best_sales_products as $row ):?> - defaultLanguage() );?> + findCached( (int)$row['parent_product_id'], ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->defaultLanguage() );?> '; ?> - language['name'];?> + zł @@ -158,7 +158,7 @@ most_view_products ) ): foreach ( $this -> most_view_products as $row ):?> - + findCached( $row['id'] );?> order['id'] ?? 0);
Kwota zamówienia order[ 'summary' ];?> zł
coupon ):?> -
Kod rabatowy: coupon -> name;?> - coupon -> amount;?> coupon -> type == 1 ? '%' : 'zł';?>
+
Kod rabatowy: coupon['name'];?> - coupon['amount'];?> coupon['type'] == 1 ? '%' : 'zł';?>

order[ 'transport' ] );?>: order[ 'transport_cost' ];?> zł
@@ -168,11 +168,11 @@ $orderId = (int)($this -> order['id'] ?? 0); - + - +
diff --git a/admin/templates/shop-order/order-edit.php b/admin/templates/shop-order/order-edit.php index a45295a..d478d43 100644 --- a/admin/templates/shop-order/order-edit.php +++ b/admin/templates/shop-order/order-edit.php @@ -177,11 +177,11 @@ $orderId = (int)($this -> order['id'] ?? 0); - + - +
diff --git a/admin/templates/shop-product/product-combination.php b/admin/templates/shop-product/product-combination.php index ac11a81..e3f9564 100644 --- a/admin/templates/shop-product/product-combination.php +++ b/admin/templates/shop-product/product-combination.php @@ -29,7 +29,8 @@ $attributes = explode( '|', $product['permutation_hash'] ); foreach ( $attributes as $attribute ): $attribute_tmp = explode( '-', $attribute ); - echo \shop\ProductAttribute::getAttributeName( (int)$attribute_tmp[0], $this -> default_language ) . ' - ' . \shop\ProductAttribute::get_value_name( (int)$attribute_tmp[1], $this -> default_language ) . ''; + $attrRepo = new \Domain\Attribute\AttributeRepository( $GLOBALS['mdb'] ); + echo $attrRepo->getAttributeNameById( (int)$attribute_tmp[0], $this -> default_language ) . ' - ' . $attrRepo->getAttributeValueById( (int)$attribute_tmp[1], $this -> default_language ) . ''; if ( $attribute != end( $attributes ) ) echo ', '; endforeach; diff --git a/autoload/Domain/Attribute/AttributeRepository.php b/autoload/Domain/Attribute/AttributeRepository.php index b9a1785..d0571ca 100644 --- a/autoload/Domain/Attribute/AttributeRepository.php +++ b/autoload/Domain/Attribute/AttributeRepository.php @@ -484,7 +484,16 @@ class AttributeRepository } $languageId = $langId !== null && trim($langId) !== '' ? trim($langId) : $this->defaultLanguageId(); - return (string)$this->db->get( + + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = "AttributeRepository::getAttributeValueById:{$valueId}:{$languageId}"; + $cached = $cacheHandler->get($cacheKey); + + if ($cached !== false && $cached !== null) { + return (string)unserialize($cached); + } + + $name = (string)$this->db->get( 'pp_shop_attributes_values_langs', 'name', [ @@ -494,6 +503,10 @@ class AttributeRepository ], ] ); + + $cacheHandler->set($cacheKey, $name); + + return $name; } /** @@ -939,4 +952,66 @@ class AttributeRepository { return round($value, $precision); } + + public function isValueDefault(int $valueId) + { + if ($valueId <= 0) { + return null; + } + + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = "attr_value_default:{$valueId}"; + $cached = $cacheHandler->get($cacheKey); + + if ($cached) { + return unserialize($cached); + } + + $isDefault = $this->db->get('pp_shop_attributes_values', 'is_default', ['id' => $valueId]); + $cacheHandler->set($cacheKey, $isDefault); + + return $isDefault; + } + + public function getAttributeOrder(int $attributeId) + { + if ($attributeId <= 0) { + return null; + } + + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = "attr_order:{$attributeId}"; + $cached = $cacheHandler->get($cacheKey); + + if ($cached) { + return unserialize($cached); + } + + $order = $this->db->get('pp_shop_attributes', 'o', ['id' => $attributeId]); + $cacheHandler->set($cacheKey, $order); + + return $order; + } + + public function getAttributeNameByValue(int $valueId, string $langId) + { + if ($valueId <= 0) { + return null; + } + + $stmt = $this->db->query( + 'SELECT name FROM pp_shop_attributes_langs AS psal ' + . 'INNER JOIN pp_shop_attributes_values AS psav ON psal.attribute_id = psav.attribute_id ' + . 'WHERE psav.id = :value_id AND lang_id = :lang_id', + [':value_id' => $valueId, ':lang_id' => $langId] + ); + + if (!$stmt) { + return null; + } + + $row = $stmt->fetch(\PDO::FETCH_ASSOC); + + return is_array($row) ? ($row['name'] ?? null) : null; + } } diff --git a/autoload/Domain/Basket/BasketCalculator.php b/autoload/Domain/Basket/BasketCalculator.php index 85082e0..f19c41b 100644 --- a/autoload/Domain/Basket/BasketCalculator.php +++ b/autoload/Domain/Basket/BasketCalculator.php @@ -31,14 +31,15 @@ class BasketCalculator global $lang_id; $summary = 0; + $productRepo = new \Domain\Product\ProductRepository($GLOBALS['mdb']); if (is_array($basket)) { foreach ($basket as $position) { - $product = \shop\Product::getFromCache((int)$position['product-id'], $lang_id); + $product = $productRepo->findCached((int)$position['product-id'], $lang_id); - $product_price_tmp = \shop\Product::calculate_basket_product_price( - (float)$product['price_brutto_promo'], - (float)$product['price_brutto'], + $product_price_tmp = self::calculateBasketProductPrice( + (float)($product['price_brutto_promo'] ?? 0), + (float)($product['price_brutto'] ?? 0), $coupon, $position ); @@ -59,4 +60,161 @@ class BasketCalculator } return $count; } + + public static function validateBasket($basket) + { + if ( !is_array( $basket ) ) + return array(); + + return $basket; + } + + public static function checkProductQuantityInStock($basket, bool $message = false) + { + $result = false; + $productRepo = new \Domain\Product\ProductRepository($GLOBALS['mdb']); + + foreach ( $basket as $key => $val ) + { + $permutation = null; + + if ( isset( $val['parent_id'] ) and (int)$val['parent_id'] and isset( $val['product-id'] ) ) + $permutation = $productRepo->getProductPermutationHash( (int)$val['product-id'] ); + + if ( !$permutation and isset( $val['attributes'] ) and is_array( $val['attributes'] ) and count( $val['attributes'] ) ) + $permutation = implode( '|', $val['attributes'] ); + + $quantity_options = $productRepo->getProductPermutationQuantityOptions( + $val['parent_id'] ? $val['parent_id'] : $val['product-id'], + $permutation + ); + + if ( + (int)$basket[ $key ][ 'quantity' ] < 1 + and ( (int)$quantity_options['quantity'] > 0 or (int)$quantity_options['stock_0_buy'] === 1 ) + ) + { + $basket[ $key ][ 'quantity' ] = 1; + $result = true; + } + + if ( ( $val[ 'quantity' ] > $quantity_options['quantity'] ) and !$quantity_options['stock_0_buy'] ) + { + $basket[ $key ][ 'quantity' ] = $quantity_options['quantity']; + if ( $message ) + \Shared\Helpers\Helpers::error( 'Ilość jednego lub więcej produktów została zmniejszona z powodu niestarczających stanów magazynowych. Sprawdź proszę koszyk.' ); + $result = true; + } + } + \Shared\Helpers\Helpers::set_session( 'basket', $basket ); + + return $result; + } + + /** + * Calculate product price in basket (with coupon + promotion discounts). + * Migrated from \shop\Product::calculate_basket_product_price() + */ + public static function calculateBasketProductPrice( float $price_brutto_promo, float $price_brutto, $coupon, $basket_position ) + { + $productRepo = new \Domain\Product\ProductRepository($GLOBALS['mdb']); + + // Produkty przecenione + if ( $price_brutto_promo ) + { + $price['price'] = $price_brutto; + $price['price_new'] = $price_brutto_promo; + + $coupon_type = is_array($coupon) ? ($coupon['type'] ?? null) : (is_object($coupon) ? $coupon->type : null); + $coupon_include_discounted = is_array($coupon) ? ($coupon['include_discounted_product'] ?? null) : (is_object($coupon) ? $coupon->include_discounted_product : null); + $coupon_categories = is_array($coupon) ? ($coupon['categories'] ?? null) : (is_object($coupon) ? $coupon->categories : null); + $coupon_amount = is_array($coupon) ? ($coupon['amount'] ?? 0) : (is_object($coupon) ? $coupon->amount : 0); + + if ( $coupon_type && $coupon_include_discounted ) + { + if ( $coupon_categories != null ) + { + $cats = is_string($coupon_categories) ? json_decode($coupon_categories) : $coupon_categories; + $product_categories = $productRepo->productCategories( (int)$basket_position['parent_id'] ? (int)$basket_position['parent_id'] : (int)$basket_position['product-id'] ); + if ( is_array( $cats ) ) foreach ( $cats as $category_tmp ) + { + if ( in_array( $category_tmp, $product_categories ) ) + { + $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal( $price['price_new'] - $price['price_new'] * $coupon_amount / 100 ); + break; + } + } + } + else + $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal( $price['price_new'] - $price['price_new'] * $coupon_amount / 100 ); + + if ( $basket_position['discount_amount'] && $basket_position['discount_include_coupon'] && $basket_position['include_product_promo'] ) + { + if ( $basket_position['discount_type'] == 3 ) + $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal( $basket_position['discount_amount'] ); + if ( $basket_position['discount_type'] == 1 ) + $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal($price['price_new'] - $price['price_new'] * $basket_position['discount_amount'] / 100 ); + } + } + else + { + if ( $basket_position['discount_amount'] && $basket_position['include_product_promo'] ) + { + if ( $basket_position['discount_type'] == 3 ) + $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal( $basket_position['discount_amount'] ); + if ( $basket_position['discount_type'] == 1 ) + $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal($price['price_new'] - $price['price_new'] * $basket_position['discount_amount'] / 100 ); + } + } + } + // Produkt nieprzeceniony + else + { + $price['price'] = $price_brutto; + $price['price_new'] = $price_brutto; + + $coupon_type = is_array($coupon) ? ($coupon['type'] ?? null) : (is_object($coupon) ? $coupon->type : null); + $coupon_categories = is_array($coupon) ? ($coupon['categories'] ?? null) : (is_object($coupon) ? $coupon->categories : null); + $coupon_amount = is_array($coupon) ? ($coupon['amount'] ?? 0) : (is_object($coupon) ? $coupon->amount : 0); + + if ( $coupon_type ) + { + if ( $coupon_categories != null ) + { + $cats = is_string($coupon_categories) ? json_decode($coupon_categories) : $coupon_categories; + $product_categories = $productRepo->productCategories( $basket_position['parent_id'] ? $basket_position['parent_id'] : $basket_position['product-id'] ); + if ( is_array( $cats ) ) foreach ( $cats as $category_tmp ) + { + if ( in_array( $category_tmp, $product_categories ) ) + { + $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal( $price['price_new'] - $price['price_new'] * $coupon_amount / 100 ); + break; + } + } + } + else + $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal($price['price'] - $price['price'] * $coupon_amount / 100 ); + + if ( $basket_position['discount_amount'] && $basket_position['discount_include_coupon'] && $basket_position['include_product_promo'] ) + { + if ( $basket_position['discount_type'] == 3 ) + $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal( $basket_position['discount_amount'] ); + if ( $basket_position['discount_type'] == 1 ) + $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal($price['price'] - $price['price'] * $basket_position['discount_amount'] / 100 ); + } + } + else + { + if ( $basket_position['discount_amount'] ) + { + if ( $basket_position['discount_type'] == 3 ) + $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal( $basket_position['discount_amount'] ); + if ( $basket_position['discount_type'] == 1 ) + $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal($price['price'] - $price['price'] * $basket_position['discount_amount'] / 100 ); + } + } + } + + return $price; + } } diff --git a/autoload/Domain/Category/CategoryRepository.php b/autoload/Domain/Category/CategoryRepository.php index 50990e6..ec74a88 100644 --- a/autoload/Domain/Category/CategoryRepository.php +++ b/autoload/Domain/Category/CategoryRepository.php @@ -763,4 +763,43 @@ class CategoryRepository return $fallback ? (string)$fallback : ''; } + + public function getCategoryProductIds(int $categoryId): array + { + if ($categoryId <= 0) { + return []; + } + + $result = $this->db->select('pp_shop_products_categories', 'product_id', ['category_id' => $categoryId]); + + return is_array($result) ? $result : []; + } + + public function subcategoriesLangCached(int $categoryId): array + { + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = "subcategories_lang:{$categoryId}"; + $cached = $cacheHandler->get($cacheKey); + + if ($cached) { + return unserialize($cached); + } + + $categories = $this->db->select('pp_shop_categories', '*', ['parent_id' => $categoryId]); + if (!is_array($categories)) { + return []; + } + + $result = []; + foreach ($categories as $cat) { + $lang = $this->db->get('pp_shop_categories_langs', '*', ['category_id' => $cat['id']]); + if (is_array($lang)) { + $result[] = $lang; + } + } + + $cacheHandler->set($cacheKey, $result); + + return $result; + } } diff --git a/autoload/Domain/Integrations/IntegrationsRepository.php b/autoload/Domain/Integrations/IntegrationsRepository.php index 7418bd2..73f6bcc 100644 --- a/autoload/Domain/Integrations/IntegrationsRepository.php +++ b/autoload/Domain/Integrations/IntegrationsRepository.php @@ -555,22 +555,22 @@ class IntegrationsRepository if ( !$accessToken ) return [ 'success' => false, 'message' => 'Brak tokenu Apilo.' ]; - $product = new \shop\Product( $productId ); + $product = ( new \Domain\Product\ProductRepository( $this->db ) )->findCached( $productId ); $params = [ - 'sku' => $product->sku, - 'ean' => $product->ean, - 'name' => $product->language['name'], - 'tax' => (int) $product->vat, + 'sku' => $product['sku'], + 'ean' => $product['ean'], + 'name' => $product['language']['name'], + 'tax' => (int) $product['vat'], 'status' => 1, - 'quantity' => (int) $product->quantity, - 'priceWithTax' => $product->price_brutto, - 'description' => $product->language['description'] . '
' . $product->language['short_description'], + 'quantity' => (int) $product['quantity'], + 'priceWithTax' => $product['price_brutto'], + 'description' => $product['language']['description'] . '
' . $product['language']['short_description'], 'shortDescription' => '', 'images' => [], ]; - foreach ( $product->images as $image ) + foreach ( $product['images'] as $image ) $params['images'][] = "https://" . $_SERVER['HTTP_HOST'] . $image['src']; $ch = curl_init( "https://projectpro.apilo.com/rest/api/warehouse/product/" ); diff --git a/autoload/Domain/Order/OrderAdminService.php b/autoload/Domain/Order/OrderAdminService.php index d1efbe7..24efc45 100644 --- a/autoload/Domain/Order/OrderAdminService.php +++ b/autoload/Domain/Order/OrderAdminService.php @@ -73,34 +73,90 @@ class OrderAdminService public function changeStatus(int $orderId, int $status, bool $sendEmail): array { - $order = new \shop\Order($orderId); - $response = $order->update_status($status, $sendEmail ? 1 : 0); + $order = $this->orders->findRawById($orderId); + if (!$order || (int)$order['status'] === $status) { + return ['result' => false]; + } - return is_array($response) ? $response : ['result' => false]; + $db = $this->orders->getDb(); + + if ($this->orders->updateOrderStatus($orderId, $status)) + { + $this->orders->insertStatusHistory($orderId, $status, $sendEmail ? 1 : 0); + + $response = ['result' => true]; + + if ($sendEmail) + { + $order['status'] = $status; + $response['email'] = $this->sendStatusChangeEmail($order); + } + + // Apilo status sync + $this->syncApiloStatusIfNeeded($order, $status); + + return $response; + } + + return ['result' => false]; } public function resendConfirmationEmail(int $orderId): bool { - $order = new \shop\Order($orderId); + global $settings; - return (bool)$order->order_resend_confirmation_email(); + $db = $this->orders->getDb(); + $order = $this->orders->orderDetailsFrontend($orderId); + if (!$order || !$order['id']) { + return false; + } + + $coupon = (int)$order['coupon_id'] ? (new \Domain\Coupon\CouponRepository($db))->find((int)$order['coupon_id']) : null; + + $mail_order = \Shared\Tpl\Tpl::view('shop-order/mail-summary', [ + 'settings' => $settings, + 'order' => $order, + 'coupon' => $coupon, + ]); + + $settings['ssl'] ? $base = 'https' : $base = 'http'; + + $regex = "-(]+src\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i"; + $mail_order = preg_replace($regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $mail_order); + + $regex = "-(]+href\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i"; + $mail_order = preg_replace($regex, "$1" . $base . "://" . $_SERVER['SERVER_NAME'] . "$2$4", $mail_order); + + \Shared\Helpers\Helpers::send_email($order['client_email'], \Shared\Helpers\Helpers::lang('potwierdzenie-zamowienia-ze-sklepu') . ' ' . $settings['firm_name'], $mail_order); + \Shared\Helpers\Helpers::send_email($settings['contact_email'], 'Nowe zamówienie / ' . $settings['firm_name'] . ' / ' . $order['number'] . ' - ' . $order['client_surname'] . ' ' . $order['client_name'], $mail_order); + + return true; } public function setOrderAsUnpaid(int $orderId): bool { - $order = new \shop\Order($orderId); - - return (bool)$order->set_as_unpaid(); + $this->orders->setAsUnpaid($orderId); + return true; } public function setOrderAsPaid(int $orderId, bool $sendMail): bool { - $order = new \shop\Order($orderId); - if (!$order->set_as_paid()) { + $order = $this->orders->findRawById($orderId); + if (!$order) { return false; } - $order->update_status(4, $sendMail ? 1 : 0); + // Apilo payment sync + $this->syncApiloPaymentIfNeeded($order); + + // Mark as paid + $this->orders->setAsPaid($orderId); + + // Set status to 1 (opłacone) without email + $this->changeStatus($orderId, 1, false); + + // Set status to 4 (przyjęte do realizacji) with email + $this->changeStatus($orderId, 4, $sendMail); return true; } @@ -132,7 +188,7 @@ class OrderAdminService curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ 'id' => (int)$order['apilo_order_id'], - 'status' => (int)( new \Domain\ShopStatus\ShopStatusRepository($this->db) )->getApiloStatusId( (int)$newStatus ), + 'status' => (int)( new \Domain\ShopStatus\ShopStatusRepository($mdb) )->getApiloStatusId( (int)$newStatus ), ])); curl_setopt($ch, CURLOPT_HTTPHEADER, [ 'Authorization: Bearer ' . $accessToken, @@ -195,4 +251,331 @@ class OrderAdminService { return $this->orders->deleteOrder($orderId); } + + // ========================================================================= + // Apilo sync queue (migrated from \shop\Order) + // ========================================================================= + + private const APILO_SYNC_QUEUE_FILE = '/temp/apilo-sync-queue.json'; + + public function processApiloSyncQueue(int $limit = 10): int + { + $queue = self::loadApiloSyncQueue(); + if (!\Shared\Helpers\Helpers::is_array_fix($queue)) { + return 0; + } + + $processed = 0; + + foreach ($queue as $key => $task) + { + if ($processed >= $limit) { + break; + } + + $order_id = (int)($task['order_id'] ?? 0); + if ($order_id <= 0) { + unset($queue[$key]); + continue; + } + + $order = $this->orders->findRawById($order_id); + if (!$order) { + unset($queue[$key]); + continue; + } + + $error = ''; + $sync_failed = false; + + $payment_pending = !empty($task['payment']) && (int)$order['paid'] === 1; + if ($payment_pending && (int)$order['apilo_order_id']) { + if (!$this->syncApiloPayment($order)) { + $sync_failed = true; + $error = 'payment_sync_failed'; + } + } + + $status_pending = isset($task['status']) && $task['status'] !== null && $task['status'] !== ''; + if (!$sync_failed && $status_pending && (int)$order['apilo_order_id']) { + if (!$this->syncApiloStatus($order, (int)$task['status'])) { + $sync_failed = true; + $error = 'status_sync_failed'; + } + } + + if ($sync_failed) { + $task['attempts'] = (int)($task['attempts'] ?? 0) + 1; + $task['last_error'] = $error; + $task['updated_at'] = date('Y-m-d H:i:s'); + $queue[$key] = $task; + } else { + unset($queue[$key]); + } + + $processed++; + } + + self::saveApiloSyncQueue($queue); + + return $processed; + } + + // ========================================================================= + // Private: email + // ========================================================================= + + private function sendStatusChangeEmail(array $order): bool + { + if (!$order['client_email']) { + return false; + } + + $db = $this->orders->getDb(); + $order_statuses = $this->orders->orderStatuses(); + $firm_name = (new \Domain\Settings\SettingsRepository($db))->getSingleValue('firm_name'); + + $status = (int)$order['status']; + $number = $order['number']; + + $subjects = [ + 0 => $firm_name . ' - zamówienie [NUMER] zostało złożone', + 1 => $firm_name . ' - zamówienie [NUMER] zostało opłacone', + 2 => $firm_name . ' - płatność za zamówienie [NUMER] została odrzucona', + 3 => $firm_name . ' - płatność za zamówienie [NUMER] jest sprawdzania ręcznie', + 4 => $firm_name . ' - zamówienie [NUMER] zostało przyjęte do realizacji', + 5 => $firm_name . ' - zamówienie [NUMER] zostało wysłane', + 6 => $firm_name . ' - zamówienie [NUMER] zostało zrealizowane', + 7 => $firm_name . ' - zamówienie [NUMER] zostało przygotowane go wysłania', + 8 => $firm_name . ' - zamówienie [NUMER] zostało anulowane', + ]; + + $subject = isset($subjects[$status]) ? str_replace('[NUMER]', $number, $subjects[$status]) : ''; + if (!$subject) { + return false; + } + + $email = new \Email(0); + $email->load_by_name('#sklep-zmiana-statusu-zamowienia'); + + $email->text = str_replace('[NUMER_ZAMOWIENIA]', $number, $email->text); + $email->text = str_replace('[DATA_ZAMOWIENIA]', date('Y/m/d', strtotime($order['date_order'])), $email->text); + $email->text = str_replace('[STATUS]', isset($order_statuses[$status]) ? $order_statuses[$status] : '', $email->text); + + return $email->send($order['client_email'], $subject, true); + } + + // ========================================================================= + // Private: Apilo sync + // ========================================================================= + + private function syncApiloPaymentIfNeeded(array $order): void + { + global $config; + + $db = $this->orders->getDb(); + $integrationsRepository = new \Domain\Integrations\IntegrationsRepository($db); + $apilo_settings = $integrationsRepository->getSettings('apilo'); + + if (!$apilo_settings['enabled'] || !$apilo_settings['access-token'] || !$apilo_settings['sync_orders']) { + return; + } + + if (isset($config['debug']['apilo']) && $config['debug']['apilo']) { + self::appendApiloLog("SET AS PAID\n" . print_r($order, true)); + } + + if ($order['apilo_order_id'] && !$this->syncApiloPayment($order)) { + self::queueApiloSync((int)$order['id'], true, null, 'payment_sync_failed'); + } + } + + private function syncApiloStatusIfNeeded(array $order, int $status): void + { + global $config; + + $db = $this->orders->getDb(); + $integrationsRepository = new \Domain\Integrations\IntegrationsRepository($db); + $apilo_settings = $integrationsRepository->getSettings('apilo'); + + if (!$apilo_settings['enabled'] || !$apilo_settings['access-token'] || !$apilo_settings['sync_orders']) { + return; + } + + if (isset($config['debug']['apilo']) && $config['debug']['apilo']) { + self::appendApiloLog("UPDATE STATUS\n" . print_r($order, true)); + } + + if ($order['apilo_order_id'] && !$this->syncApiloStatus($order, $status)) { + self::queueApiloSync((int)$order['id'], false, $status, 'status_sync_failed'); + } + } + + private function syncApiloPayment(array $order): bool + { + global $config; + + $db = $this->orders->getDb(); + $integrationsRepository = new \Domain\Integrations\IntegrationsRepository($db); + + if (!(int)$order['apilo_order_id']) { + return true; + } + + $payment_type = (int)(new \Domain\PaymentMethod\PaymentMethodRepository($db))->getApiloPaymentTypeId((int)$order['payment_method_id']); + if ($payment_type <= 0) { + $payment_type = 1; + } + + $payment_date = new \DateTime($order['date_order']); + $access_token = $integrationsRepository->apiloGetAccessToken(); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $order['apilo_order_id'] . '/payment/'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ + 'amount' => str_replace(',', '.', $order['summary']), + 'paymentDate' => $payment_date->format('Y-m-d\TH:i:s\Z'), + 'type' => $payment_type, + ])); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer " . $access_token, + "Accept: application/json", + "Content-Type: application/json", + ]); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); + curl_setopt($ch, CURLOPT_TIMEOUT, 15); + $apilo_response = curl_exec($ch); + $http_code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curl_error = curl_errno($ch) ? curl_error($ch) : ''; + curl_close($ch); + + if (isset($config['debug']['apilo']) && $config['debug']['apilo']) { + self::appendApiloLog("PAYMENT RESPONSE\nHTTP: " . $http_code . "\nCURL: " . $curl_error . "\n" . print_r($apilo_response, true)); + } + + if ($curl_error !== '') return false; + if ($http_code < 200 || $http_code >= 300) return false; + + return true; + } + + private function syncApiloStatus(array $order, int $status): bool + { + global $config; + + $db = $this->orders->getDb(); + $integrationsRepository = new \Domain\Integrations\IntegrationsRepository($db); + + if (!(int)$order['apilo_order_id']) { + return true; + } + + $access_token = $integrationsRepository->apiloGetAccessToken(); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $order['apilo_order_id'] . '/status/'); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PUT"); + curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([ + 'id' => $order['apilo_order_id'], + 'status' => (int)(new \Domain\ShopStatus\ShopStatusRepository($db))->getApiloStatusId($status), + ])); + curl_setopt($ch, CURLOPT_HTTPHEADER, [ + "Authorization: Bearer " . $access_token, + "Accept: application/json", + "Content-Type: application/json", + ]); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5); + curl_setopt($ch, CURLOPT_TIMEOUT, 15); + $apilo_result = curl_exec($ch); + $http_code = (int)curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curl_error = curl_errno($ch) ? curl_error($ch) : ''; + curl_close($ch); + + if (isset($config['debug']['apilo']) && $config['debug']['apilo']) { + self::appendApiloLog("STATUS RESPONSE\nHTTP: " . $http_code . "\nCURL: " . $curl_error . "\n" . print_r($apilo_result, true)); + } + + if ($curl_error !== '') return false; + if ($http_code < 200 || $http_code >= 300) return false; + + return true; + } + + // ========================================================================= + // Private: Apilo sync queue file helpers + // ========================================================================= + + private static function queueApiloSync(int $order_id, bool $payment, ?int $status, string $error): void + { + if ($order_id <= 0) return; + + $queue = self::loadApiloSyncQueue(); + $key = (string)$order_id; + $row = is_array($queue[$key] ?? null) ? $queue[$key] : []; + + $row['order_id'] = $order_id; + $row['payment'] = !empty($row['payment']) || $payment ? 1 : 0; + if ($status !== null) { + $row['status'] = $status; + } + + $row['attempts'] = (int)($row['attempts'] ?? 0) + 1; + $row['last_error'] = $error; + $row['updated_at'] = date('Y-m-d H:i:s'); + + $queue[$key] = $row; + self::saveApiloSyncQueue($queue); + } + + private static function apiloSyncQueuePath(): string + { + return dirname(__DIR__, 2) . self::APILO_SYNC_QUEUE_FILE; + } + + private static function loadApiloSyncQueue(): array + { + $path = self::apiloSyncQueuePath(); + if (!file_exists($path)) return []; + + $content = file_get_contents($path); + if (!$content) return []; + + $decoded = json_decode($content, true); + if (!is_array($decoded)) return []; + + return $decoded; + } + + private static function saveApiloSyncQueue(array $queue): void + { + $path = self::apiloSyncQueuePath(); + $dir = dirname($path); + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } + + file_put_contents($path, json_encode($queue, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES), LOCK_EX); + } + + private static function appendApiloLog(string $message): void + { + $base = isset($_SERVER['DOCUMENT_ROOT']) && $_SERVER['DOCUMENT_ROOT'] + ? rtrim($_SERVER['DOCUMENT_ROOT'], '/\\') + : dirname(__DIR__, 2); + + $dir = $base . '/logs'; + if (!is_dir($dir)) { + mkdir($dir, 0777, true); + } + + file_put_contents( + $dir . '/apilo.txt', + date('Y-m-d H:i:s') . ' --- ' . $message . "\n\n", + FILE_APPEND + ); + } } \ No newline at end of file diff --git a/autoload/Domain/Order/OrderRepository.php b/autoload/Domain/Order/OrderRepository.php index 1e52190..065da53 100644 --- a/autoload/Domain/Order/OrderRepository.php +++ b/autoload/Domain/Order/OrderRepository.php @@ -619,7 +619,8 @@ class OrderRepository if (is_array($basket)) { foreach ($basket as $basket_position) { $attributes = ''; - $product = \shop\Product::getFromCache($basket_position['product-id'], $lang_id); + $productRepo = new \Domain\Product\ProductRepository($this->db); + $product = $productRepo->findCached($basket_position['product-id'], $lang_id); if (is_array($basket_position['attributes'])) { foreach ($basket_position['attributes'] as $row) { @@ -640,7 +641,7 @@ class OrderRepository $product_custom_fields = ''; if (is_array($basket_position['custom_fields'])) { foreach ($basket_position['custom_fields'] as $key => $val) { - $custom_field = \shop\ProductCustomField::getFromCache($key); + $custom_field = (new \Domain\Product\ProductRepository($this->db))->findCustomFieldCached($key); if ($product_custom_fields) { $product_custom_fields .= '
'; } @@ -648,15 +649,15 @@ class OrderRepository } } - $product_price_tmp = \shop\Product::calculate_basket_product_price((float)$product['price_brutto_promo'], (float)$product['price_brutto'], $coupon, $basket_position); + $product_price_tmp = \Domain\Basket\BasketCalculator::calculateBasketProductPrice((float)$product['price_brutto_promo'], (float)$product['price_brutto'], $coupon, $basket_position); $this->db->insert('pp_shop_order_products', [ 'order_id' => $order_id, 'product_id' => $basket_position['product-id'], 'parent_product_id' => $basket_position['parent_id'] ? $basket_position['parent_id'] : $basket_position['product-id'], - 'name' => $product->language['name'], + 'name' => $product['language']['name'], 'attributes' => $attributes, - 'vat' => $product->vat, + 'vat' => $product['vat'], 'price_brutto' => $product_price_tmp['price'], 'price_brutto_promo' => $product_price_tmp['price_new'], 'quantity' => $basket_position['quantity'], @@ -664,7 +665,7 @@ class OrderRepository 'custom_fields' => $product_custom_fields, ]); - $product_quantity = \shop\Product::get_product_quantity($basket_position['product-id']); + $product_quantity = $productRepo->getQuantity($basket_position['product-id']); if ($product_quantity != null) { $this->db->update('pp_shop_products', ['quantity[-]' => $basket_position['quantity']], ['id' => $basket_position['product-id']]); } else { @@ -700,13 +701,72 @@ class OrderRepository // zmiana statusu w realizacji jeżeli płatność przy odbiorze if ($payment_id == 3) { - $order_tmp = new \shop\Order($order_id); - $order_tmp->update_status(4, true); + $this->updateOrderStatus($order_id, 4); + $this->insertStatusHistory($order_id, 4, 1); } return $order_id; } + // ========================================================================= + // Low-level helpers (used by OrderAdminService) + // ========================================================================= + + public function getDb() + { + return $this->db; + } + + public function findRawById(int $orderId): ?array + { + if ($orderId <= 0) return null; + $result = $this->db->get('pp_shop_orders', '*', ['id' => $orderId]); + return is_array($result) ? $result : null; + } + + public function findRawByHash(string $hash): ?array + { + if ($hash === '') return null; + $result = $this->db->get('pp_shop_orders', '*', ['hash' => $hash]); + return is_array($result) ? $result : null; + } + + public function findRawByPrzelewy24Hash(string $hash): ?array + { + if ($hash === '') return null; + $result = $this->db->get('pp_shop_orders', '*', ['przelewy24_hash' => $hash]); + return is_array($result) ? $result : null; + } + + public function setAsPaid(int $orderId): void + { + $this->db->update('pp_shop_orders', ['paid' => 1], ['id' => $orderId]); + } + + public function setAsUnpaid(int $orderId): void + { + $this->db->update('pp_shop_orders', ['paid' => 0], ['id' => $orderId]); + } + + public function updateOrderStatus(int $orderId, int $status): bool + { + return (bool)$this->db->update('pp_shop_orders', ['status' => $status], ['id' => $orderId]); + } + + public function insertStatusHistory(int $orderId, int $statusId, int $mail): void + { + $this->db->insert('pp_shop_order_statuses', [ + 'order_id' => $orderId, + 'status_id' => $statusId, + 'mail' => $mail, + ]); + } + + public function updateApiloStatusDate(int $orderId, string $date): void + { + $this->db->update('pp_shop_orders', ['apilo_order_status_date' => $date], ['id' => $orderId]); + } + private function nullableString(string $value): ?string { $value = trim($value); diff --git a/autoload/Domain/Product/ProductRepository.php b/autoload/Domain/Product/ProductRepository.php index 5bb123a..55d6492 100644 --- a/autoload/Domain/Product/ProductRepository.php +++ b/autoload/Domain/Product/ProductRepository.php @@ -1218,7 +1218,7 @@ class ProductRepository $vat = $this->db->get( 'pp_shop_products', 'vat', [ 'id' => $productId ] ); $attributeRepository = new \Domain\Attribute\AttributeRepository( $this->db ); - $permutations = \shop\Product::array_cartesian( $attributes ); + $permutations = self::arrayCartesian( $attributes ); if ( !\Shared\Helpers\Helpers::is_array_fix( $permutations ) ) { return true; } @@ -1544,11 +1544,12 @@ class ProductRepository if ( \Shared\Helpers\Helpers::is_array_fix( $rows ) ) { foreach ( $rows as $productId ) { - $product = \shop\Product::getFromCache( $productId, $lang_id ); + $product = $this->findCached( $productId, $lang_id ); + if ( !$product ) continue; - if ( is_array( $product->product_combinations ) && count( $product->product_combinations ) ) { - foreach ( $product->product_combinations as $productCombination ) { - if ( $productCombination->quantity !== null || $productCombination->stock_0_buy ) { + if ( is_array( $product['product_combinations'] ) && count( $product['product_combinations'] ) ) { + foreach ( $product['product_combinations'] as $productCombination ) { + if ( $productCombination['quantity'] !== null || $productCombination['stock_0_buy'] ) { $this->appendCombinationToXml( $doc, $channelNode, $product, $productCombination, $domainPrefix, $url ); } } @@ -1567,29 +1568,29 @@ class ProductRepository private function appendCombinationToXml(\DOMDocument $doc, \DOMElement $channelNode, $product, $combination, string $domainPrefix, string $url): void { $itemNode = $channelNode->appendChild( $doc->createElement( 'item' ) ); - $itemNode->appendChild( $doc->createElement( 'g:id', $combination->id ) ); - $itemNode->appendChild( $doc->createElement( 'g:item_group_id', $product->id ) ); + $itemNode->appendChild( $doc->createElement( 'g:id', $combination['id'] ) ); + $itemNode->appendChild( $doc->createElement( 'g:item_group_id', $product['id'] ) ); for ( $l = 0; $l <= 4; $l++ ) { $label = 'custom_label_' . $l; - if ( $product->$label ) { - $itemNode->appendChild( $doc->createElement( 'g:' . $label, $product->$label ) ); + if ( isset( $product[$label] ) && $product[$label] ) { + $itemNode->appendChild( $doc->createElement( 'g:' . $label, $product[$label] ) ); } } - $title = $product->language['xml_name'] ?: $product->language['name']; - $itemNode->appendChild( $doc->createElement( 'title', str_replace( '&', '&', $title ) . ' - ' . $product->generateSubtitleFromAttributes( $combination->permutation_hash ) ) ); + $title = $product['language']['xml_name'] ?: $product['language']['name']; + $itemNode->appendChild( $doc->createElement( 'title', str_replace( '&', '&', $title ) . ' - ' . $this->generateSubtitleFromAttributes( $combination['permutation_hash'] ) ) ); - $gtin = $product->ean ?: $this->generateEAN( $product->id ); + $gtin = $product['ean'] ?: $this->generateEAN( $product['id'] ); $itemNode->appendChild( $doc->createElement( 'g:gtin', $gtin ) ); - $desc = $product->language['short_description'] ?: $product->language['name']; + $desc = $product['language']['short_description'] ?: $product['language']['name']; $itemNode->appendChild( $doc->createElement( 'g:description', html_entity_decode( strip_tags( $desc ) ) ) ); - if ( $product->language['seo_link'] ) { - $link = $domainPrefix . '://' . $url . '/' . \Shared\Helpers\Helpers::seo( $product->language['seo_link'] ) . '/' . str_replace( '|', '/', $combination->permutation_hash ); + if ( $product['language']['seo_link'] ) { + $link = $domainPrefix . '://' . $url . '/' . \Shared\Helpers\Helpers::seo( $product['language']['seo_link'] ) . '/' . str_replace( '|', '/', $combination['permutation_hash'] ); } else { - $link = $domainPrefix . '://' . $url . '/p-' . $product->id . '-' . \Shared\Helpers\Helpers::seo( $product->language['name'] ) . '/' . str_replace( '|', '/', $combination->permutation_hash ); + $link = $domainPrefix . '://' . $url . '/p-' . $product['id'] . '-' . \Shared\Helpers\Helpers::seo( $product['language']['name'] ) . '/' . str_replace( '|', '/', $combination['permutation_hash'] ); } $itemNode->appendChild( $doc->createElement( 'link', $link ) ); @@ -1597,32 +1598,32 @@ class ProductRepository $itemNode->appendChild( $doc->createElement( 'g:condition', 'new' ) ); - if ( $combination->quantity !== null ) { - if ( $combination->quantity > 0 ) { + if ( $combination['quantity'] !== null ) { + if ( $combination['quantity'] > 0 ) { $itemNode->appendChild( $doc->createElement( 'g:availability', 'in stock' ) ); - $itemNode->appendChild( $doc->createElement( 'g:quantity', $combination->quantity ) ); + $itemNode->appendChild( $doc->createElement( 'g:quantity', $combination['quantity'] ) ); } else { - $itemNode->appendChild( $doc->createElement( 'g:availability', $combination->stock_0_buy ? 'in stock' : 'out of stock' ) ); + $itemNode->appendChild( $doc->createElement( 'g:availability', $combination['stock_0_buy'] ? 'in stock' : 'out of stock' ) ); } } else { - if ( $product->quantity > 0 ) { + if ( $product['quantity'] > 0 ) { $itemNode->appendChild( $doc->createElement( 'g:availability', 'in stock' ) ); - $itemNode->appendChild( $doc->createElement( 'g:quantity', $product->quantity ) ); + $itemNode->appendChild( $doc->createElement( 'g:quantity', $product['quantity'] ) ); } else { - $itemNode->appendChild( $doc->createElement( 'g:availability', $product->stock_0_buy ? 'in stock' : 'out of stock' ) ); - $itemNode->appendChild( $doc->createElement( 'g:quantity', $product->stock_0_buy ? 999 : 0 ) ); + $itemNode->appendChild( $doc->createElement( 'g:availability', $product['stock_0_buy'] ? 'in stock' : 'out of stock' ) ); + $itemNode->appendChild( $doc->createElement( 'g:quantity', $product['stock_0_buy'] ? 999 : 0 ) ); } } - if ( $combination->price_brutto ) { - $itemNode->appendChild( $doc->createElement( 'g:price', $combination->price_brutto . ' PLN' ) ); - if ( $combination->price_brutto_promo ) { - $itemNode->appendChild( $doc->createElement( 'g:sale_price', $combination->price_brutto_promo . ' PLN' ) ); + if ( $combination['price_brutto'] ) { + $itemNode->appendChild( $doc->createElement( 'g:price', $combination['price_brutto'] . ' PLN' ) ); + if ( $combination['price_brutto_promo'] ) { + $itemNode->appendChild( $doc->createElement( 'g:sale_price', $combination['price_brutto_promo'] . ' PLN' ) ); } } else { - $itemNode->appendChild( $doc->createElement( 'g:price', $product->price_brutto . ' PLN' ) ); - if ( $product->price_brutto_promo ) { - $itemNode->appendChild( $doc->createElement( 'g:sale_price', $product->price_brutto_promo . ' PLN' ) ); + $itemNode->appendChild( $doc->createElement( 'g:price', $product['price_brutto'] . ' PLN' ) ); + if ( $product['price_brutto_promo'] ) { + $itemNode->appendChild( $doc->createElement( 'g:sale_price', $product['price_brutto_promo'] . ' PLN' ) ); } } @@ -1635,33 +1636,33 @@ class ProductRepository private function appendProductToXml(\DOMDocument $doc, \DOMElement $channelNode, $product, string $domainPrefix, string $url): void { $itemNode = $channelNode->appendChild( $doc->createElement( 'item' ) ); - $itemNode->appendChild( $doc->createElement( 'g:id', $product->id ) ); - $itemNode->appendChild( $doc->createElement( 'g:item_group_id', $product->id ) ); + $itemNode->appendChild( $doc->createElement( 'g:id', $product['id'] ) ); + $itemNode->appendChild( $doc->createElement( 'g:item_group_id', $product['id'] ) ); - if ( $product->google_xml_label ) { - $itemNode->appendChild( $doc->createElement( 'g:custom_label_0', $product->google_xml_label ) ); + if ( isset( $product['google_xml_label'] ) && $product['google_xml_label'] ) { + $itemNode->appendChild( $doc->createElement( 'g:custom_label_0', $product['google_xml_label'] ) ); } - $title = $product->language['xml_name'] ?: $product->language['name']; + $title = $product['language']['xml_name'] ?: $product['language']['name']; $itemNode->appendChild( $doc->createElement( 'title', str_replace( '&', '&', $title ) ) ); - $gtin = $product->ean ?: $this->generateEAN( $product->id ); + $gtin = $product['ean'] ?: $this->generateEAN( $product['id'] ); $itemNode->appendChild( $doc->createElement( 'g:gtin', $gtin ) ); - $desc = $product->language['short_description'] ?: $product->language['name']; + $desc = $product['language']['short_description'] ?: $product['language']['name']; $itemNode->appendChild( $doc->createElement( 'g:description', html_entity_decode( strip_tags( $desc ) ) ) ); - if ( $product->language['seo_link'] ) { - $link = $domainPrefix . '://' . $url . '/' . \Shared\Helpers\Helpers::seo( $product->language['seo_link'] ); + if ( $product['language']['seo_link'] ) { + $link = $domainPrefix . '://' . $url . '/' . \Shared\Helpers\Helpers::seo( $product['language']['seo_link'] ); } else { - $link = $domainPrefix . '://' . $url . '/p-' . $product->id . '-' . \Shared\Helpers\Helpers::seo( $product->language['name'] ); + $link = $domainPrefix . '://' . $url . '/p-' . $product['id'] . '-' . \Shared\Helpers\Helpers::seo( $product['language']['name'] ); } $itemNode->appendChild( $doc->createElement( 'link', $link ) ); for ( $l = 0; $l <= 4; $l++ ) { $label = 'custom_label_' . $l; - if ( $product->$label ) { - $itemNode->appendChild( $doc->createElement( 'g:' . $label, $product->$label ) ); + if ( isset( $product[$label] ) && $product[$label] ) { + $itemNode->appendChild( $doc->createElement( 'g:' . $label, $product[$label] ) ); } } @@ -1669,11 +1670,11 @@ class ProductRepository $itemNode->appendChild( $doc->createElement( 'g:condition', 'new' ) ); - if ( $product->quantity ) { + if ( $product['quantity'] ) { $itemNode->appendChild( $doc->createElement( 'g:availability', 'in stock' ) ); - $itemNode->appendChild( $doc->createElement( 'g:quantity', $product->quantity ) ); + $itemNode->appendChild( $doc->createElement( 'g:quantity', $product['quantity'] ) ); } else { - if ( $product->stock_0_buy ) { + if ( $product['stock_0_buy'] ) { $itemNode->appendChild( $doc->createElement( 'g:availability', 'in stock' ) ); $itemNode->appendChild( $doc->createElement( 'g:quantity', 999 ) ); } else { @@ -1682,9 +1683,9 @@ class ProductRepository } } - $itemNode->appendChild( $doc->createElement( 'g:price', $product->price_brutto . ' PLN' ) ); - if ( $product->price_brutto_promo ) { - $itemNode->appendChild( $doc->createElement( 'g:sale_price', $product->price_brutto_promo . ' PLN' ) ); + $itemNode->appendChild( $doc->createElement( 'g:price', $product['price_brutto'] . ' PLN' ) ); + if ( $product['price_brutto_promo'] ) { + $itemNode->appendChild( $doc->createElement( 'g:sale_price', $product['price_brutto_promo'] . ' PLN' ) ); } $this->appendShippingToXml( $doc, $itemNode, $product ); @@ -1695,12 +1696,12 @@ class ProductRepository */ private function appendImagesToXml(\DOMDocument $doc, \DOMElement $itemNode, $product, string $domainPrefix, string $url): void { - if ( isset( $product->images[0] ) ) { - $itemNode->appendChild( $doc->createElement( 'g:image_link', $domainPrefix . '://' . $url . $product->images[0]['src'] ) ); + if ( isset( $product['images'][0] ) ) { + $itemNode->appendChild( $doc->createElement( 'g:image_link', $domainPrefix . '://' . $url . $product['images'][0]['src'] ) ); } - if ( count( $product->images ) > 1 ) { - for ( $i = 1; $i < count( $product->images ); ++$i ) { - $itemNode->appendChild( $doc->createElement( 'g:additional_image_link', $domainPrefix . '://' . $url . $product->images[$i]['src'] ) ); + if ( is_array( $product['images'] ) && count( $product['images'] ) > 1 ) { + for ( $i = 1; $i < count( $product['images'] ); ++$i ) { + $itemNode->appendChild( $doc->createElement( 'g:additional_image_link', $domainPrefix . '://' . $url . $product['images'][$i]['src'] ) ); } } } @@ -1714,7 +1715,7 @@ class ProductRepository $shippingNode->appendChild( $doc->createElement( 'g:country', 'PL' ) ); $shippingNode->appendChild( $doc->createElement( 'g:service', '1 dzień roboczy' ) ); $shippingNode->appendChild( $doc->createElement( 'g:price', - ( new \Domain\Transport\TransportRepository( $this->db ) )->lowestTransportPrice( (int) $product->wp ) . ' PLN' + ( new \Domain\Transport\TransportRepository( $this->db ) )->lowestTransportPrice( (int) $product['wp'] ) . ' PLN' ) ); } @@ -2243,4 +2244,603 @@ class ProductRepository 'AND' => ['product_id' => $productId, 'lang_id' => $langId], ]); } + + public function findCustomFieldCached(int $customFieldId) + { + if ($customFieldId <= 0) { + return null; + } + + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = 'custom_field:' . $customFieldId; + $cached = $cacheHandler->get($cacheKey); + + if ($cached) { + return unserialize($cached); + } + + $result = $this->db->get('pp_shop_products_custom_fields', '*', ['id_additional_field' => $customFieldId]); + if (!is_array($result)) { + return null; + } + + $cacheHandler->set($cacheKey, $result, 60 * 60 * 24); + + return $result; + } + + // ========================================================================= + // Migrated from \shop\Product + // ========================================================================= + + /** + * Replacement for $this->findCached() — returns array instead of Product object. + * Uses same Redis cache key pattern: shop\product:{id}:{lang}:{hash} + */ + public function findCached(int $productId, string $langId = null, string $permutationHash = null) + { + if ($productId <= 0) { + return null; + } + + if (!$langId) { + $langId = (new \Domain\Languages\LanguagesRepository($this->db))->defaultLanguage(); + } + + $cacheKey = "shop\\product:$productId:$langId:$permutationHash"; + + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cached = $cacheHandler->get($cacheKey); + + if ($cached) { + $data = unserialize($cached); + // Legacy cache may contain old \shop\Product objects — invalidate and re-fetch + if (is_object($data)) { + $cacheHandler->delete($cacheKey); + // Fall through to re-fetch from DB + } elseif (is_array($data)) { + return $data; + } + } + + $product = $this->db->get('pp_shop_products', '*', ['id' => $productId]); + if (!is_array($product)) { + return null; + } + + $effectiveId = $productId; + + // Combination — load data from parent + if ($product['parent_id']) { + $effectiveId = (int) $product['parent_id']; + + if (!$product['price_netto'] || !$product['price_brutto']) { + $parentPrices = $this->db->get('pp_shop_products', ['price_netto', 'price_brutto', 'price_netto_promo', 'price_brutto_promo', 'vat', 'wp'], ['id' => $effectiveId]); + if (is_array($parentPrices)) { + foreach ($parentPrices as $k => $v) { + $product[$k] = $v; + } + } + } + } + + // Language + $langRows = $this->db->select('pp_shop_products_langs', '*', ['AND' => ['product_id' => $effectiveId, 'lang_id' => $langId]]); + if (\Shared\Helpers\Helpers::is_array_fix($langRows)) { + foreach ($langRows as $row) { + if ($row['copy_from']) { + $copyRows = $this->db->select('pp_shop_products_langs', '*', ['AND' => ['product_id' => $effectiveId, 'lang_id' => $row['copy_from']]]); + if (is_array($copyRows)) { + foreach ($copyRows as $row2) { + $product['language'] = $row2; + } + } + } else { + $product['language'] = $row; + } + } + } + + // Images, files, categories, related, sets + $product['images'] = $this->db->select('pp_shop_products_images', '*', ['product_id' => $effectiveId, 'ORDER' => ['o' => 'ASC', 'id' => 'ASC']]); + $product['files'] = $this->db->select('pp_shop_products_files', '*', ['product_id' => $effectiveId]); + $product['categories'] = $this->db->select('pp_shop_products_categories', 'category_id', ['product_id' => $effectiveId]); + $product['products_related'] = $this->db->select('pp_shop_products_related', 'product_related_id', ['product_id' => $effectiveId]); + $product['products_sets'] = $this->db->select('pp_shop_product_sets_products', 'product_id', ['AND' => ['set_id' => (int) ($product['set_id'] ?? 0), 'product_id[!]' => $effectiveId]]); + + // Product combinations (only for main products) + if (!$product['parent_id']) { + $combIds = $this->db->select('pp_shop_products', 'id', ['parent_id' => $productId]); + if (\Shared\Helpers\Helpers::is_array_fix($combIds)) { + $combos = []; + foreach ($combIds as $combId) { + $combos[] = $this->findCached((int) $combId, $langId); + } + $product['product_combinations'] = $combos; + } + } + + // Producer + $producer = $this->db->get('pp_shop_producer', '*', ['id' => (int) ($product['producer_id'] ?? 0)]); + $producerLang = $this->db->get('pp_shop_producer_lang', '*', ['AND' => ['producer_id' => (int) ($product['producer_id'] ?? 0), 'lang_id' => $langId]]); + if (is_array($producer)) { + $producer['description'] = is_array($producerLang) ? ($producerLang['description'] ?? null) : null; + $producer['data'] = is_array($producerLang) ? ($producerLang['data'] ?? null) : null; + $producer['meta_title'] = is_array($producerLang) ? ($producerLang['meta_title'] ?? null) : null; + } + $product['producer'] = $producer; + + // Permutation price overrides + if ($permutationHash) { + $permPrices = $this->db->get('pp_shop_products', ['price_netto', 'price_brutto', 'price_netto_promo', 'price_brutto_promo'], ['AND' => ['permutation_hash' => $permutationHash, 'parent_id' => $productId]]); + if (is_array($permPrices)) { + if ($permPrices['price_netto'] !== null) $product['price_netto'] = $permPrices['price_netto']; + if ($permPrices['price_brutto'] !== null) $product['price_brutto'] = $permPrices['price_brutto']; + if ($permPrices['price_netto_promo'] !== null) $product['price_netto_promo'] = $permPrices['price_netto_promo']; + if ($permPrices['price_brutto_promo'] !== null) $product['price_brutto_promo'] = $permPrices['price_brutto_promo']; + } + } + + // Custom fields + $product['custom_fields'] = $this->db->select('pp_shop_products_custom_fields', '*', ['id_product' => $productId]); + + $cacheHandler->set($cacheKey, $product); + + return $product; + } + + public function isProductOnPromotion(int $productId): bool + { + return $this->db->get('pp_shop_products', 'price_netto_promo', ['id' => $productId]) !== null; + } + + public function productSetsWhenAddToBasket(int $productId): string + { + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = "ProductRepository::productSetsWhenAddToBasket:$productId"; + + $objectData = $cacheHandler->get($cacheKey); + + if (!$objectData) { + $parentId = $this->db->get('pp_shop_products', 'parent_id', ['id' => $productId]); + if ($parentId) { + $setId = $this->db->get('pp_shop_products', 'set_id', ['id' => $parentId]); + } else { + $setId = $this->db->get('pp_shop_products', 'set_id', ['id' => $productId]); + } + + $products = $this->db->select('pp_shop_product_sets_products', 'product_id', ['set_id' => $setId]); + if (!$products) { + $products_intersection = $this->db->select('pp_shop_orders_products_intersection', ['product_1_id', 'product_2_id'], ['OR' => ['product_1_id' => $productId, 'product_2_id' => $productId], 'ORDER' => ['count' => 'DESC'], 'LIMIT' => 5]); + if (!count($products_intersection)) { + $parentId2 = $this->db->get('pp_shop_products', 'parent_id', ['id' => $productId]); + $products_intersection = $this->db->select('pp_shop_orders_products_intersection', ['product_1_id', 'product_2_id'], ['OR' => ['product_1_id' => $parentId2, 'product_2_id' => $parentId2], 'ORDER' => ['count' => 'DESC'], 'LIMIT' => 5]); + } + + $products = []; + foreach ($products_intersection as $pi) { + if ($pi['product_1_id'] != $productId) { + $products[] = $pi['product_1_id']; + } else { + $products[] = $pi['product_2_id']; + } + } + $products = array_unique($products); + } + + $cacheHandler->set($cacheKey, $products); + } else { + $products = unserialize($objectData); + } + + if (is_array($products)) { + foreach ($products as $k => $pid) { + if (!$this->isProductActiveCached((int) $pid)) { + unset($products[$k]); + } + } + } + + return \Shared\Tpl\Tpl::view('shop-basket/alert-product-sets', [ + 'products' => $products, + ]); + } + + public function addVisit(int $productId): void + { + $this->db->update('pp_shop_products', ['visits[+]' => 1], ['id' => $productId]); + } + + public function getProductImg(int $productId) + { + $rows = $this->db->select('pp_shop_products_images', 'src', ['product_id' => $productId, 'ORDER' => ['o' => 'ASC']]); + if (\Shared\Helpers\Helpers::is_array_fix($rows)) { + foreach ($rows as $row) { + return $row; + } + } + return null; + } + + public function getProductUrl(int $productId): string + { + $langId = (new \Domain\Languages\LanguagesRepository($this->db))->defaultLanguage(); + + $langRows = $this->db->select('pp_shop_products_langs', '*', ['AND' => ['product_id' => $productId, 'lang_id' => $langId]]); + $language = null; + if (\Shared\Helpers\Helpers::is_array_fix($langRows)) { + foreach ($langRows as $row) { + if ($row['copy_from']) { + $copyRows = $this->db->select('pp_shop_products_langs', '*', ['AND' => ['product_id' => $productId, 'lang_id' => $row['copy_from']]]); + if (is_array($copyRows)) { + foreach ($copyRows as $row2) { + $language = $row2; + } + } + } else { + $language = $row; + } + } + } + + if ($language && $language['seo_link']) { + return '/' . $language['seo_link']; + } + return '/p-' . $productId . '-' . \Shared\Helpers\Helpers::seo($language ? $language['name'] : ''); + } + + public function searchProductsByNameCount(string $query, string $langId): int + { + $results = $this->db->query('SELECT COUNT(0) AS c FROM ( ' + . 'SELECT psp.id, ' + . '( CASE ' + . 'WHEN copy_from IS NULL THEN name ' + . 'WHEN copy_from IS NOT NULL THEN ( ' + . 'SELECT name FROM pp_shop_products_langs WHERE lang_id = pspl.copy_from AND product_id = psp.id ' + . ') ' + . 'END ) AS name ' + . 'FROM pp_shop_products AS psp ' + . 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id ' + . 'WHERE status = 1 AND name LIKE :query AND lang_id = :lang_id ' + . ') AS q1', [ + ':query' => '%' . $query . '%', + ':lang_id' => $langId, + ])->fetchAll(\PDO::FETCH_ASSOC); + + return (int) ($results[0]['c'] ?? 0); + } + + public function getProductsIdByName(string $query, string $langId, int $limit, int $from): array + { + $results = $this->db->query('SELECT psp.id, ' + . '( CASE ' + . 'WHEN copy_from IS NULL THEN name ' + . 'WHEN copy_from IS NOT NULL THEN ( ' + . 'SELECT name FROM pp_shop_products_langs WHERE lang_id = pspl.copy_from AND product_id = psp.id ' + . ') ' + . 'END ) AS name ' + . 'FROM pp_shop_products AS psp ' + . 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id ' + . 'WHERE status = 1 AND name LIKE :query AND lang_id = :lang_id ' + . 'ORDER BY name ASC ' + . 'LIMIT ' . (int) $from . ',' . (int) $limit, [ + ':query' => '%' . $query . '%', + ':lang_id' => $langId, + ])->fetchAll(\PDO::FETCH_ASSOC); + + $output = []; + if (is_array($results)) { + foreach ($results as $row) { + $output[] = $row['id']; + } + } + return $output; + } + + public function searchProductsByName(string $query, string $langId, int $page = 0): array + { + $count = $this->searchProductsByNameCount($query, $langId); + $ls = ceil($count / 12); + + if ($page < 1) { + $page = 1; + } elseif ($page > $ls) { + $page = (int) $ls; + } + + $from = 12 * ($page - 1); + if ($from < 0) { + $from = 0; + } + + return [ + 'products' => $this->getProductsIdByName($query, $langId, 12, $from), + 'count' => $count, + 'ls' => $ls, + ]; + } + + public function searchProductByNameAjax(string $query, string $langId): array + { + $results = $this->db->query( + 'SELECT product_id FROM pp_shop_products_langs AS pspl ' + . 'INNER JOIN pp_shop_products AS psp ON psp.id = pspl.product_id ' + . 'WHERE status = 1 AND lang_id = :lang_id AND LOWER(name) LIKE :query ' + . 'ORDER BY visits DESC LIMIT 12', + [':query' => '%' . $query . '%', ':lang_id' => $langId] + )->fetchAll(\PDO::FETCH_ASSOC); + + return is_array($results) ? $results : []; + } + + public function isStock0Buy(int $productId) + { + $parentId = $this->db->get('pp_shop_products', 'parent_id', ['id' => $productId]); + if ($parentId) { + return $this->db->get('pp_shop_products', 'stock_0_buy', ['id' => $parentId]); + } + return $this->db->get('pp_shop_products', 'stock_0_buy', ['id' => $productId]); + } + + public function getProductPermutationQuantityOptions(int $productId, $permutation) + { + global $settings; + + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = "ProductRepository::getProductPermutationQuantityOptions:v2:$productId:$permutation"; + + $objectData = $cacheHandler->get($cacheKey); + + if ($objectData) { + return unserialize($objectData); + } + + $result = []; + + if ($this->db->count('pp_shop_products', ['AND' => ['parent_id' => $productId, 'permutation_hash' => $permutation]])) { + $result['quantity'] = $this->db->get('pp_shop_products', 'quantity', ['AND' => ['parent_id' => $productId, 'permutation_hash' => $permutation]]); + $result['stock_0_buy'] = $this->db->get('pp_shop_products', 'stock_0_buy', ['AND' => ['parent_id' => $productId, 'permutation_hash' => $permutation]]); + + if ($result['quantity'] === null) { + $result['quantity'] = $this->db->get('pp_shop_products', 'quantity', ['id' => $productId]); + if ($result['stock_0_buy'] === null) { + $result['stock_0_buy'] = $this->db->get('pp_shop_products', 'stock_0_buy', ['id' => $productId]); + } + } + } else { + $result['quantity'] = $this->db->get('pp_shop_products', 'quantity', ['id' => $productId]); + $result['stock_0_buy'] = $this->db->get('pp_shop_products', 'stock_0_buy', ['id' => $productId]); + } + + $result['messages'] = $this->db->get('pp_shop_products_langs', ['warehouse_message_zero', 'warehouse_message_nonzero'], ['AND' => ['product_id' => $productId, 'lang_id' => 'pl']]); + if (!isset($result['messages']['warehouse_message_zero']) || !$result['messages']['warehouse_message_zero']) { + $result['messages']['warehouse_message_zero'] = isset($settings['warehouse_message_zero_pl']) ? $settings['warehouse_message_zero_pl'] : ''; + } + if (!isset($result['messages']['warehouse_message_nonzero']) || !$result['messages']['warehouse_message_nonzero']) { + $result['messages']['warehouse_message_nonzero'] = isset($settings['warehouse_message_nonzero_pl']) ? $settings['warehouse_message_nonzero_pl'] : ''; + } + + $cacheHandler->set($cacheKey, $result); + + return $result; + } + + public function getProductIdByAttributes(int $parentId, array $attributes) + { + return $this->db->get('pp_shop_products', 'id', ['AND' => ['parent_id' => $parentId, 'permutation_hash' => implode('|', $attributes)]]); + } + + public function getProductPermutationHash(int $productId) + { + return $this->db->get('pp_shop_products', 'permutation_hash', ['id' => $productId]); + } + + /** + * Build attribute list from product combinations (array of arrays with permutation_hash). + */ + public function getProductAttributes($products) + { + if (!is_array($products) || !count($products)) { + return false; + } + + $attrRepo = new \Domain\Attribute\AttributeRepository($this->db); + $attributes = []; + + foreach ($products as $product) { + $hash = is_array($product) ? ($product['permutation_hash'] ?? '') : (is_object($product) ? $product->permutation_hash : ''); + $permutations = explode('|', $hash); + foreach ($permutations as $permutation) { + $parts = explode('-', $permutation); + if (count($parts) < 2) continue; + + $value = []; + $value['id'] = $parts[1]; + $value['is_default'] = $attrRepo->isValueDefault((int) $parts[1]); + + if (array_search($parts[1], array_column($attributes, 'id')) === false) { + $attributes[$parts[0]][] = $value; + } + } + } + + $attributes = \Shared\Helpers\Helpers::removeDuplicates($attributes, 'id'); + + $sorted = []; + foreach ($attributes as $key => $val) { + $row = []; + $row['id'] = $key; + $row['values'] = $val; + $sorted[$attrRepo->getAttributeOrder((int) $key)] = $row; + } + + return $sorted; + } + + public function generateSkuCode(): string + { + $skus = $this->db->select('pp_shop_products', 'sku', ['sku[~]' => 'PP-']); + $codes = []; + if (is_array($skus)) { + foreach ($skus as $sku) { + $codes[] = (int) substr($sku, 3); + } + } + + if (!empty($codes)) { + return 'PP-' . str_pad(max($codes) + 1, 6, '0', STR_PAD_LEFT); + } + return 'PP-000001'; + } + + public function productMeta(int $productId): array + { + $result = $this->db->select( + 'pp_shop_products_categories (ppc)', + ['[>]pp_shop_categories_langs (pcl)' => ['ppc.category_id' => 'category_id']], + ['pcl.title', 'pcl.seo_link'], + ['ppc.product_id' => $productId] + ); + return is_array($result) ? $result : []; + } + + public function generateSubtitleFromAttributes(string $permutationHash, string $langId = null): string + { + if (!$langId) { + global $lang_id; + $langId = $lang_id; + } + + $subtitle = ''; + $attrRepo = new \Domain\Attribute\AttributeRepository($this->db); + $parts = explode('|', $permutationHash); + + foreach ($parts as $part) { + $attr = explode('-', $part); + if (count($attr) < 2) continue; + + if ($subtitle) { + $subtitle .= ', '; + } + $subtitle .= $attrRepo->getAttributeNameById((int) $attr[0], $langId) . ': ' . $attrRepo->getAttributeValueById((int) $attr[1], $langId); + } + + return $subtitle; + } + + public function getDefaultCombinationPrices(array $product): array + { + $prices = []; + if (!isset($product['product_combinations']) || !is_array($product['product_combinations'])) { + return $prices; + } + + $permutationHash = ''; + $attributes = $this->getProductAttributes($product['product_combinations']); + if (is_array($attributes)) { + foreach ($attributes as $attribute) { + foreach ($attribute['values'] as $value) { + if ($value['is_default']) { + if ($permutationHash) { + $permutationHash .= '|'; + } + $permutationHash .= $attribute['id'] . '-' . $value['id']; + } + } + } + } + + foreach ($product['product_combinations'] as $combo) { + $comboHash = is_array($combo) ? ($combo['permutation_hash'] ?? '') : (is_object($combo) ? $combo->permutation_hash : ''); + if ($comboHash == $permutationHash) { + $prices['price_netto'] = is_array($combo) ? ($combo['price_netto'] ?? null) : $combo->price_netto; + $prices['price_brutto'] = is_array($combo) ? ($combo['price_brutto'] ?? null) : $combo->price_brutto; + $prices['price_netto_promo'] = is_array($combo) ? ($combo['price_netto_promo'] ?? null) : $combo->price_netto_promo; + $prices['price_brutto_promo'] = is_array($combo) ? ($combo['price_brutto_promo'] ?? null) : $combo->price_brutto_promo; + } + } + + return $prices; + } + + public function getProductDataBySelectedAttributes(array $product, string $selectedAttribute): array + { + global $settings; + + $result = []; + + if (isset($product['product_combinations']) && is_array($product['product_combinations'])) { + foreach ($product['product_combinations'] as $combo) { + $comboHash = is_array($combo) ? ($combo['permutation_hash'] ?? '') : (is_object($combo) ? $combo->permutation_hash : ''); + if ($comboHash == $selectedAttribute) { + $comboQty = is_array($combo) ? ($combo['quantity'] ?? null) : $combo->quantity; + $comboS0B = is_array($combo) ? ($combo['stock_0_buy'] ?? null) : $combo->stock_0_buy; + + if ($comboQty !== null || $comboS0B) { + $result['quantity'] = $comboQty; + $result['stock_0_buy'] = $comboS0B; + $result['price_netto'] = \Shared\Helpers\Helpers::shortPrice(is_array($combo) ? $combo['price_netto'] : $combo->price_netto); + $result['price_brutto'] = \Shared\Helpers\Helpers::shortPrice(is_array($combo) ? $combo['price_brutto'] : $combo->price_brutto); + $pnp = is_array($combo) ? ($combo['price_netto_promo'] ?? null) : $combo->price_netto_promo; + $pbp = is_array($combo) ? ($combo['price_brutto_promo'] ?? null) : $combo->price_brutto_promo; + $result['price_netto_promo'] = $pnp ? \Shared\Helpers\Helpers::shortPrice($pnp) : null; + $result['price_brutto_promo'] = $pbp ? \Shared\Helpers\Helpers::shortPrice($pbp) : null; + } else { + $result['quantity'] = $product['quantity'] ?? null; + $result['stock_0_buy'] = $product['stock_0_buy'] ?? null; + $result['price_netto'] = \Shared\Helpers\Helpers::shortPrice($product['price_netto'] ?? 0); + $result['price_brutto'] = \Shared\Helpers\Helpers::shortPrice($product['price_brutto'] ?? 0); + $result['price_netto_promo'] = ($product['price_netto_promo'] ?? null) ? \Shared\Helpers\Helpers::shortPrice($product['price_netto_promo']) : null; + $result['price_brutto_promo'] = ($product['price_brutto_promo'] ?? null) ? \Shared\Helpers\Helpers::shortPrice($product['price_brutto_promo']) : null; + } + } + } + } + + $lang = isset($product['language']) ? $product['language'] : []; + $result['messages']['warehouse_message_zero'] = !empty($lang['warehouse_message_zero']) ? $lang['warehouse_message_zero'] : (isset($settings['warehouse_message_zero_pl']) ? $settings['warehouse_message_zero_pl'] : ''); + $result['messages']['warehouse_message_nonzero'] = !empty($lang['warehouse_message_nonzero']) ? $lang['warehouse_message_nonzero'] : (isset($settings['warehouse_message_nonzero_pl']) ? $settings['warehouse_message_nonzero_pl'] : ''); + $result['permutation_hash'] = $selectedAttribute; + + return $result; + } + + public function productCategories(int $productId): array + { + $result = $this->db->select('pp_shop_products_categories', 'category_id', ['product_id' => $productId]); + return is_array($result) ? $result : []; + } + + public static function arrayCartesian(array $input): array + { + $result = []; + + foreach ($input as $key => $values) { + if (empty($values)) { + continue; + } + + if (empty($result)) { + foreach ($values as $value) { + $result[] = [$key => $value]; + } + } else { + $append = []; + foreach ($result as &$product) { + $product[$key] = array_shift($values); + $copy = $product; + + foreach ($values as $item) { + $copy[$key] = $item; + $append[] = $copy; + } + + array_unshift($values, $product[$key]); + } + $result = array_merge($result, $append); + } + } + + return $result; + } } diff --git a/autoload/Domain/Promotion/PromotionRepository.php b/autoload/Domain/Promotion/PromotionRepository.php index f10e21d..784d934 100644 --- a/autoload/Domain/Promotion/PromotionRepository.php +++ b/autoload/Domain/Promotion/PromotionRepository.php @@ -8,6 +8,16 @@ class PromotionRepository private $db; private ?string $defaultLangId = null; + public static $condition_type = [ + 1 => 'Rabat procentowy na produkty z kategorii 1 jeżeli w koszyku jest produkt z kategorii 2', + 2 => 'Rabat procentowy na produkty z kategorii 1 i 2', + 3 => 'Najtańszy produkt w koszyku (z wybranych kategorii) za X zł', + 4 => 'Rabat procentowy na cały koszyk', + 5 => 'Rabat procentowy na produkty z kategorii 1 lub 2', + ]; + + public static $discount_type = [ 1 => 'Rabat procentowy' ]; + public function __construct($db) { $this->db = $db; @@ -413,13 +423,75 @@ class PromotionRepository try { $cache = new \Shared\Cache\CacheHandler(); if (method_exists($cache, 'delete')) { - $cache->delete('\shop\Promotion::get_active_promotions'); + $cache->delete('PromotionRepository::getActivePromotions'); } } catch (\Throwable $e) { // Cache invalidation should not block save/delete. } } + public function getActivePromotions() + { + $cacheHandler = new \Shared\Cache\CacheHandler(); + $cacheKey = "PromotionRepository::getActivePromotions"; + + $objectData = $cacheHandler->get( $cacheKey ); + + if ( !$objectData ) + { + $results = $this->db->select( 'pp_shop_promotion', 'id', [ 'AND' => [ 'status' => 1, 'OR #date_from' => [ 'date_from' => null, 'date_from[<=]' => date( 'Y-m-d' ) ], 'OR #date_to' => [ 'date_to' => null, 'date_to[>=]' => date( 'Y-m-d' ) ] ], 'ORDER' => [ 'id' => 'DESC' ] ] ); + + $cacheHandler->set( $cacheKey, $results ); + } + else + { + return unserialize( $objectData ); + } + + return $results; + } + + public function findPromotion( $basket ) + { + foreach ( $basket as $key => $val ) + { + unset( $basket[$key]['discount_type'] ); + unset( $basket[$key]['discount_amount'] ); + unset( $basket[$key]['discount_include_coupon'] ); + unset( $basket[$key]['include_product_promo'] ); + } + + $basket_tmp = $basket; + + $results = $this->getActivePromotions(); + if ( is_array( $results ) and count( $results ) ) + { + foreach ( $results as $row ) + { + $promotion = $this->find( (int)$row ); + + if ( $promotion['id'] <= 0 ) + continue; + + if ( $promotion['condition_type'] == 4 ) + return $this->applyTypeWholeBasket( $basket_tmp, $promotion ); + + if ( $promotion['condition_type'] == 3 ) + return $this->applyTypeCheapestProduct( $basket_tmp, $promotion ); + + if ( $promotion['condition_type'] == 5 ) + return $this->applyTypeCategoriesOr( $basket_tmp, $promotion ); + + if ( $promotion['condition_type'] == 2 ) + return $this->applyTypeCategoriesAnd( $basket_tmp, $promotion ); + + if ( $promotion['condition_type'] == 1 ) + return $this->applyTypeCategoryCondition( $basket_tmp, $promotion ); + } + } + return $basket; + } + // ========================================================================= // Frontend: basket promotion logic (migrated from front\factory\ShopPromotion) // ========================================================================= @@ -433,17 +505,17 @@ class PromotionRepository foreach ( $basket as $key => $val ) { - $product_promotion = \shop\Product::is_product_on_promotion( $val['product-id'] ); + $product_promotion = (new \Domain\Product\ProductRepository($this->db))->isProductOnPromotion( $val['product-id'] ); - if ( !$product_promotion or $product_promotion and $promotion->include_product_promo ) + if ( !$product_promotion or $product_promotion and $promotion['include_product_promo'] ) { $product_categories = $productRepo->productCategoriesFront( (int) $val['product-id'] ); foreach ( $product_categories as $category_tmp ) { - $basket[$key]['discount_type'] = $promotion->discount_type; - $basket[$key]['discount_amount'] = $promotion->amount; - $basket[$key]['discount_include_coupon'] = $promotion->include_coupon; - $basket[$key]['include_product_promo'] = $promotion->include_product_promo; + $basket[$key]['discount_type'] = $promotion['discount_type']; + $basket[$key]['discount_amount'] = $promotion['amount']; + $basket[$key]['discount_include_coupon'] = $promotion['include_coupon']; + $basket[$key]['include_product_promo'] = $promotion['include_product_promo']; } } } @@ -457,15 +529,15 @@ class PromotionRepository { $productRepo = new \Domain\Product\ProductRepository( $this->db ); $condition_1 = false; - $categories = json_decode( $promotion->categories ); + $categories = $promotion['categories']; if ( is_array( $categories ) and is_array( $categories ) ) { foreach ( $basket as $key => $val ) { - $product_promotion = \shop\Product::is_product_on_promotion( $val['product-id'] ); + $product_promotion = (new \Domain\Product\ProductRepository($this->db))->isProductOnPromotion( $val['product-id'] ); - if ( !$product_promotion or $product_promotion and $promotion->include_product_promo ) + if ( !$product_promotion or $product_promotion and $promotion['include_product_promo'] ) { $product_categories = $productRepo->productCategoriesFront( (int) $val['product-id'] ); foreach ( $product_categories as $category_tmp ) @@ -477,12 +549,12 @@ class PromotionRepository } } - if ( count( $condition_1 ) >= $promotion->min_product_count ) + if ( count( $condition_1 ) >= $promotion['min_product_count'] ) { $cheapest_position = false; foreach ( $basket as $key => $val ) { - $price = \shop\Product::get_product_price( $val['product-id'] ); + $price = (new \Domain\Product\ProductRepository($this->db))->getPrice( $val['product-id'] ); if ( !$cheapest_position or $cheapest_position['price'] > $price ) { $cheapest_position['price'] = $price; @@ -492,9 +564,9 @@ class PromotionRepository $basket[$cheapest_position['key']]['quantity'] = 1; $basket[$cheapest_position['key']]['discount_type'] = 3; - $basket[$cheapest_position['key']]['discount_amount'] = $promotion->price_cheapest_product; - $basket[$cheapest_position['key']]['discount_include_coupon'] = $promotion->include_coupon; - $basket[$cheapest_position['key']]['include_product_promo'] = $promotion->include_product_promo; + $basket[$cheapest_position['key']]['discount_amount'] = $promotion['price_cheapest_product']; + $basket[$cheapest_position['key']]['discount_include_coupon'] = $promotion['include_coupon']; + $basket[$cheapest_position['key']]['include_product_promo'] = $promotion['include_product_promo']; } return $basket; @@ -506,24 +578,24 @@ class PromotionRepository public function applyTypeCategoriesOr(array $basket, $promotion): array { $productRepo = new \Domain\Product\ProductRepository( $this->db ); - $categories = json_decode( $promotion->categories ); - $condition_categories = json_decode( $promotion->condition_categories ); + $categories = $promotion['categories']; + $condition_categories = $promotion['condition_categories']; foreach ( $basket as $key => $val ) { - $product_promotion = \shop\Product::is_product_on_promotion( $val['product-id'] ); + $product_promotion = (new \Domain\Product\ProductRepository($this->db))->isProductOnPromotion( $val['product-id'] ); - if ( !$product_promotion or $product_promotion and $promotion->include_product_promo ) + if ( !$product_promotion or $product_promotion and $promotion['include_product_promo'] ) { $product_categories = $productRepo->productCategoriesFront( (int) $val['product-id'] ); foreach ( $product_categories as $category_tmp ) { if ( in_array( $category_tmp['category_id'], $condition_categories ) or in_array( $category_tmp['category_id'], $categories ) ) { - $basket[$key]['discount_type'] = $promotion->discount_type; - $basket[$key]['discount_amount'] = $promotion->amount; - $basket[$key]['discount_include_coupon'] = $promotion->include_coupon; - $basket[$key]['include_product_promo'] = $promotion->include_product_promo; + $basket[$key]['discount_type'] = $promotion['discount_type']; + $basket[$key]['discount_amount'] = $promotion['amount']; + $basket[$key]['discount_include_coupon'] = $promotion['include_coupon']; + $basket[$key]['include_product_promo'] = $promotion['include_product_promo']; } } } @@ -540,8 +612,8 @@ class PromotionRepository $condition_1 = false; $condition_2 = false; - $categories = json_decode( $promotion->categories ); - $condition_categories = json_decode( $promotion->condition_categories ); + $categories = $promotion['categories']; + $condition_categories = $promotion['condition_categories']; if ( is_array( $condition_categories ) and is_array( $categories ) ) { @@ -577,10 +649,10 @@ class PromotionRepository { if ( in_array( $category_tmp['category_id'], $categories ) or in_array( $category_tmp['category_id'], $condition_categories ) ) { - $basket[$key]['discount_type'] = $promotion->discount_type; - $basket[$key]['discount_amount'] = $promotion->amount; - $basket[$key]['discount_include_coupon'] = $promotion->include_coupon; - $basket[$key]['include_product_promo'] = $promotion->include_product_promo; + $basket[$key]['discount_type'] = $promotion['discount_type']; + $basket[$key]['discount_amount'] = $promotion['amount']; + $basket[$key]['discount_include_coupon'] = $promotion['include_coupon']; + $basket[$key]['include_product_promo'] = $promotion['include_product_promo']; } } } @@ -595,8 +667,8 @@ class PromotionRepository { $productRepo = new \Domain\Product\ProductRepository( $this->db ); $condition = false; - $categories = json_decode( $promotion->categories ); - $condition_categories = json_decode( $promotion->condition_categories ); + $categories = $promotion['categories']; + $condition_categories = $promotion['condition_categories']; if ( is_array( $condition_categories ) and is_array( $categories ) ) { @@ -622,10 +694,10 @@ class PromotionRepository { if ( in_array( $category_tmp['category_id'], $categories ) ) { - $basket[$key]['discount_type'] = $promotion->discount_type; - $basket[$key]['discount_amount'] = $promotion->amount; - $basket[$key]['discount_include_coupon'] = $promotion->include_coupon; - $basket[$key]['include_product_promo'] = $promotion->include_product_promo; + $basket[$key]['discount_type'] = $promotion['discount_type']; + $basket[$key]['discount_amount'] = $promotion['amount']; + $basket[$key]['discount_include_coupon'] = $promotion['include_coupon']; + $basket[$key]['include_product_promo'] = $promotion['include_product_promo']; } } } diff --git a/autoload/Shared/Helpers/Helpers.php b/autoload/Shared/Helpers/Helpers.php index baeab72..a79079d 100644 --- a/autoload/Shared/Helpers/Helpers.php +++ b/autoload/Shared/Helpers/Helpers.php @@ -66,9 +66,9 @@ class Helpers // Wyczyść cache produktu dla wszystkich języków i permutacji $cacheHandler -> deletePattern( "shop\\product:$product_id:*" ); // Wyczyść cache związane z opcjami ilościowymi - $cacheHandler -> deletePattern( "\\shop\\Product::get_product_permutation_quantity_options:$product_id:*" ); + $cacheHandler -> deletePattern( "ProductRepository::getProductPermutationQuantityOptions:v2:$product_id:*" ); // Wyczyść cache zestawów produktów - $cacheHandler -> deletePattern( "\\shop\\Product::product_sets_when_add_to_basket:$product_id" ); + $cacheHandler -> deletePattern( "ProductRepository::productSetsWhenAddToBasket:$product_id" ); } catch (\Exception $e) { @@ -956,5 +956,20 @@ class Helpers } return false; } + + public static function shortPrice( $price ) + { + if ( self::isWholeNumber( $price ) ) + $price = round( $price, 0 ); + else + $price = self::decimal( $price ); + + return $price; + } + + public static function isWholeNumber( $value ) + { + return ( is_numeric( $value ) && ( round( $value, 3 ) == round( $value ) ) ); + } } diff --git a/autoload/admin/Controllers/ShopOrderController.php b/autoload/admin/Controllers/ShopOrderController.php index fa528f6..015c35a 100644 --- a/autoload/admin/Controllers/ShopOrderController.php +++ b/autoload/admin/Controllers/ShopOrderController.php @@ -167,7 +167,7 @@ class ShopOrderController $coupon = null; if (!empty($order) && !empty($order['coupon_id'])) { - $coupon = new \shop\Coupon((int)$order['coupon_id']); + $coupon = ( new \Domain\Coupon\CouponRepository( $GLOBALS['mdb'] ) )->find((int)$order['coupon_id']); } return \Shared\Tpl\Tpl::view('shop-order/order-details', [ @@ -191,7 +191,7 @@ class ShopOrderController return \Shared\Tpl\Tpl::view('shop-order/order-edit', [ 'order' => $this->service->details($orderId), 'order_statuses' => $this->service->statuses(), - 'transport' => \shop\Transport::transport_list(), + 'transport' => ( new \Domain\Transport\TransportRepository( $GLOBALS['mdb'] ) )->allActive(), 'payment_methods' => ( new \Domain\PaymentMethod\PaymentMethodRepository( $GLOBALS['mdb'] ) )->allActive(), ]); } diff --git a/autoload/admin/Controllers/ShopProductController.php b/autoload/admin/Controllers/ShopProductController.php index da98388..595424a 100644 --- a/autoload/admin/Controllers/ShopProductController.php +++ b/autoload/admin/Controllers/ShopProductController.php @@ -221,7 +221,7 @@ class ShopProductController $categories = ( new CategoryRepository( $db ) )->subcategories( null ); $layouts = $this->layoutsForProductEdit( $db ); $products = $this->repository->allProductsList(); - $sets = \shop\ProductSet::sets_list(); + $sets = ( new \Domain\ProductSet\ProductSetRepository( $db ) )->allSets(); $producers = ( new \Domain\Producer\ProducerRepository( $db ) )->allProducers(); $units = ( new \Domain\Dictionaries\DictionariesRepository( $db ) )->allUnits(); $dlang = $this->languagesRepository->defaultLanguage(); @@ -920,7 +920,7 @@ class ShopProductController */ public function ajax_product_url(): void { - echo json_encode( [ 'url' => \shop\Product::getProductUrl( (int) \Shared\Helpers\Helpers::get( 'product_id' ) ) ] ); + echo json_encode( [ 'url' => ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->getProductUrl( (int) \Shared\Helpers\Helpers::get( 'product_id' ) ) ] ); exit; } @@ -931,7 +931,7 @@ class ShopProductController { $response = [ 'status' => 'error', 'msg' => 'Podczas generowania kodu sku wystąpił błąd. Proszę spróbować ponownie.' ]; - $sku = \shop\Product::generate_sku_code( (int) \Shared\Helpers\Helpers::get( 'product_id' ) ); + $sku = ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->generateSkuCode(); if ( $sku ) { $response = [ 'status' => 'ok', 'sku' => $sku ]; } diff --git a/autoload/admin/Controllers/ShopPromotionController.php b/autoload/admin/Controllers/ShopPromotionController.php index e356310..7d4b06d 100644 --- a/autoload/admin/Controllers/ShopPromotionController.php +++ b/autoload/admin/Controllers/ShopPromotionController.php @@ -71,7 +71,7 @@ class ShopPromotionController 'lp' => $lp++ . '.', 'status' => $status === 1 ? 'tak' : 'nie', 'name' => '' . htmlspecialchars($name, ENT_QUOTES, 'UTF-8') . '', - 'condition_type' => htmlspecialchars((string)(\shop\Promotion::$condition_type[$conditionType] ?? '-'), ENT_QUOTES, 'UTF-8'), + 'condition_type' => htmlspecialchars((string)(\Domain\Promotion\PromotionRepository::$condition_type[$conditionType] ?? '-'), ENT_QUOTES, 'UTF-8'), 'date_from' => $dateFrom !== '' ? htmlspecialchars($dateFrom, ENT_QUOTES, 'UTF-8') : '-', 'date_to' => $dateTo !== '' ? htmlspecialchars($dateTo, ENT_QUOTES, 'UTF-8') : '-', '_actions' => [ @@ -248,13 +248,13 @@ class ShopPromotionController FormField::select('condition_type', [ 'label' => 'Warunki promocji', 'tab' => 'settings', - 'options' => \shop\Promotion::$condition_type, + 'options' => \Domain\Promotion\PromotionRepository::$condition_type, 'required' => true, ]), FormField::select('discount_type', [ 'label' => 'Typ rabatu', 'tab' => 'settings', - 'options' => \shop\Promotion::$discount_type, + 'options' => \Domain\Promotion\PromotionRepository::$discount_type, 'required' => true, ]), FormField::text('min_product_count', [ diff --git a/autoload/front/App.php b/autoload/front/App.php index 5db9b14..0ad3486 100644 --- a/autoload/front/App.php +++ b/autoload/front/App.php @@ -47,7 +47,7 @@ class App // wyświetlenie pojedynczego produktu if ( $product ) { - \shop\Product::add_visit( $product -> id ); + ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->addVisit( (int)$product['id'] ); return \Shared\Tpl\Tpl::view( 'shop-product/product', [ 'product' => $product, 'settings' => $settings, @@ -76,18 +76,6 @@ class App // stare klasy $class = '\front\controls\\' . $moduleName; - if ( class_exists( $class ) and method_exists( new $class, $action ) ) - return call_user_func_array( array( $class, $action ), array() ); - - // klasy sklepowe - $class = '\shop\\'; - - $results = explode( '_', \Shared\Helpers\Helpers::get( 'module' ) ); - if ( is_array( $results ) ) foreach ( $results as $row ) - $class .= ucfirst( $row ); - - $action = \Shared\Helpers\Helpers::get( 'action' ); - if ( class_exists( $class ) and method_exists( new $class, $action ) ) return call_user_func_array( array( $class, $action ), array() ); @@ -188,8 +176,10 @@ class App }, 'ShopOrder' => function() { global $mdb; + $orderRepo = new \Domain\Order\OrderRepository( $mdb ); return new \front\Controllers\ShopOrderController( - new \Domain\Order\OrderRepository( $mdb ) + $orderRepo, + new \Domain\Order\OrderAdminService( $orderRepo ) ); }, 'ShopProducer' => function() { @@ -204,6 +194,9 @@ class App new \Domain\Category\CategoryRepository( $mdb ) ); }, + 'Search' => function() { + return new \front\Controllers\SearchController(); + }, ]; } } diff --git a/autoload/front/Controllers/SearchController.php b/autoload/front/Controllers/SearchController.php new file mode 100644 index 0000000..d1209a3 --- /dev/null +++ b/autoload/front/Controllers/SearchController.php @@ -0,0 +1,49 @@ +searchProductsByName( \Shared\Helpers\Helpers::get( 'query' ), $lang_id, (int)$bs ); + + $out = \Shared\Tpl\Tpl::view( 'shop-search/products', [ + 'query' => \Shared\Helpers\Helpers::get( 'query' ), + 'products' => $results['products'] + ] ); + + if ( $results['ls'] > 1 ) + { + $tpl = new \Shared\Tpl\Tpl; + $tpl -> ls = $results['ls']; + $tpl -> bs = $bs ? $bs : 1; + $tpl -> link = 'wyszukiwarka/' . \Shared\Helpers\Helpers::get( 'query' ); + $out .= $tpl -> render( 'site/pager' ); + } + + return $out; + } + + public function searchProducts() + { + global $lang_id; + + $products = []; + $productRepo = new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ); + $results = $productRepo->searchProductByNameAjax( \Shared\Helpers\Helpers::get( 'query' ), $lang_id ); + if ( \Shared\Helpers\Helpers::is_array_fix( $results ) ) { + foreach ( $results as $row ) { + $products[] = \Shared\Tpl\Tpl::view( 'shop-search/product-search', [ + 'product' => $productRepo->findCached( $row['product_id'], $lang_id ) + ] ); + } + } + + echo json_encode( $products ); + exit; + } +} diff --git a/autoload/front/Controllers/ShopBasketController.php b/autoload/front/Controllers/ShopBasketController.php index d647101..ac96eb4 100644 --- a/autoload/front/Controllers/ShopBasketController.php +++ b/autoload/front/Controllers/ShopBasketController.php @@ -34,7 +34,7 @@ class ShopBasketController unset( $basket[ $product_hash ] ); - $basket = \shop\Promotion::find_promotion( $basket ); + $basket = (new \Domain\Promotion\PromotionRepository($GLOBALS['mdb']))->findPromotion( $basket ); \Shared\Helpers\Helpers::set_session( 'basket', $basket ); @@ -51,11 +51,11 @@ class ShopBasketController $basket_transport_method_id = \Shared\Helpers\Helpers::get_session( 'basket-transport-method-id' ); $basket[ $product_hash ][ 'quantity' ]++; - \shop\Basket::check_product_quantity_in_stock( $basket, false ); + \Domain\Basket\BasketCalculator::checkProductQuantityInStock( $basket, false ); $basket = \Shared\Helpers\Helpers::get_session( 'basket' ); - $basket = \shop\Promotion::find_promotion( $basket ); + $basket = (new \Domain\Promotion\PromotionRepository($GLOBALS['mdb']))->findPromotion( $basket ); \Shared\Helpers\Helpers::set_session( 'basket', $basket ); @@ -76,7 +76,7 @@ class ShopBasketController if ( $basket[ $product_hash ][ 'quantity' ] < 1 ) unset( $basket[ $product_hash ] ); - $basket = \shop\Promotion::find_promotion( $basket ); + $basket = (new \Domain\Promotion\PromotionRepository($GLOBALS['mdb']))->findPromotion( $basket ); \Shared\Helpers\Helpers::set_session( 'basket', $basket ); @@ -97,9 +97,9 @@ class ShopBasketController if ( $basket[ $product_hash ][ 'quantity' ] < 1 ) unset( $basket[ $product_hash ] ); - $basket = \shop\Promotion::find_promotion( $basket ); + $basket = (new \Domain\Promotion\PromotionRepository($GLOBALS['mdb']))->findPromotion( $basket ); - \shop\Basket::check_product_quantity_in_stock( $basket, false ); + \Domain\Basket\BasketCalculator::checkProductQuantityInStock( $basket, false ); $basket = \Shared\Helpers\Helpers::get_session( 'basket' ); @@ -116,7 +116,7 @@ class ShopBasketController public function basketAddProduct() { - $basket = \shop\Basket::validate_basket( \Shared\Helpers\Helpers::get_session( 'basket' ) ); + $basket = \Domain\Basket\BasketCalculator::validateBasket( \Shared\Helpers\Helpers::get_session( 'basket' ) ); $values_tmp = json_decode( \Shared\Helpers\Helpers::get( 'values' ), true ); $values = []; $attributes = []; @@ -144,7 +144,7 @@ class ShopBasketController if ( \Shared\Helpers\Helpers::is_array_fix( $attributes ) ) { $values['parent_id'] = $values[ 'product-id' ]; - $values['product-id'] = \shop\Product::get_product_id_by_attributes( $values[ 'product-id' ], $attributes ); + $values['product-id'] = ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->getProductIdByAttributes( $values[ 'product-id' ], $attributes ); $values['attributes'] = $attributes; } @@ -164,7 +164,7 @@ class ShopBasketController $basket[ $product_code ]['message'] = $values['product-message']; $basket[ $product_code ]['custom_fields'] = $custom_fields; - $basket = \shop\Promotion::find_promotion( $basket ); + $basket = (new \Domain\Promotion\PromotionRepository($GLOBALS['mdb']))->findPromotion( $basket ); \Shared\Helpers\Helpers::set_session( 'basket', $basket ); @@ -174,7 +174,7 @@ class ShopBasketController 'result' => 'ok', 'basket_mini_count' => \Domain\Basket\BasketCalculator::countProductsText( \Domain\Basket\BasketCalculator::countProducts( $basket ) ), 'basket_mini_value' => \Domain\Basket\BasketCalculator::summaryPrice( $basket, $coupon ), - 'product_sets' => \shop\Product::product_sets_when_add_to_basket( (int)$values['product-id'] ) + 'product_sets' => ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->productSetsWhenAddToBasket( (int)$values['product-id'] ) ] ); exit; } @@ -261,7 +261,7 @@ class ShopBasketController { global $lang_id, $settings; - if ( \shop\Basket::check_product_quantity_in_stock( \Shared\Helpers\Helpers::get_session( 'basket' ) ) ) + if ( \Domain\Basket\BasketCalculator::checkProductQuantityInStock( \Shared\Helpers\Helpers::get_session( 'basket' ) ) ) { header( 'Location: /koszyk' ); exit; @@ -286,7 +286,7 @@ class ShopBasketController { $client = \Shared\Helpers\Helpers::get_session( 'client' ); - if ( \shop\Basket::check_product_quantity_in_stock( \Shared\Helpers\Helpers::get_session( 'basket' ) ) ) + if ( \Domain\Basket\BasketCalculator::checkProductQuantityInStock( \Shared\Helpers\Helpers::get_session( 'basket' ) ) ) { header( 'Location: /koszyk' ); exit; @@ -357,13 +357,13 @@ class ShopBasketController $payment_method_id = \Shared\Helpers\Helpers::get_session( 'payment_method_id' ); $basket_transport_method_id = \Shared\Helpers\Helpers::get_session( 'basket-transport-method-id' ); - if ( \shop\Basket::check_product_quantity_in_stock( $basket ) ) + if ( \Domain\Basket\BasketCalculator::checkProductQuantityInStock( $basket ) ) { header( 'Location: /koszyk' ); exit; } - $basket = \shop\Promotion::find_promotion( $basket ); + $basket = (new \Domain\Promotion\PromotionRepository($GLOBALS['mdb']))->findPromotion( $basket ); return \Shared\Tpl\Tpl::view( 'shop-basket/basket', [ 'basket' => $basket, diff --git a/autoload/front/Controllers/ShopClientController.php b/autoload/front/Controllers/ShopClientController.php index fa58f07..de17696 100644 --- a/autoload/front/Controllers/ShopClientController.php +++ b/autoload/front/Controllers/ShopClientController.php @@ -140,7 +140,7 @@ class ShopClientController return \front\Views\ShopClient::clientOrders([ 'client' => $client, 'orders' => $this->clientRepo->clientOrders((int)$client['id']), - 'statuses' => \shop\Order::order_statuses(), + 'statuses' => ( new \Domain\Order\OrderRepository( $GLOBALS['mdb'] ) )->orderStatuses(), ]); } diff --git a/autoload/front/Controllers/ShopOrderController.php b/autoload/front/Controllers/ShopOrderController.php index b68cf4a..24f4977 100644 --- a/autoload/front/Controllers/ShopOrderController.php +++ b/autoload/front/Controllers/ShopOrderController.php @@ -2,14 +2,17 @@ namespace front\Controllers; use Domain\Order\OrderRepository; +use Domain\Order\OrderAdminService; class ShopOrderController { private $repository; + private $adminService; - public function __construct( OrderRepository $repository ) + public function __construct( OrderRepository $repository, OrderAdminService $adminService ) { $this->repository = $repository; + $this->adminService = $adminService; } public function paymentConfirmation() @@ -30,12 +33,11 @@ class ShopOrderController if ( \Shared\Helpers\Helpers::get( 'tr_status' ) == 'TRUE' && \Shared\Helpers\Helpers::get( 'tr_crc' ) ) { - $order = new \shop\Order( 0, \Shared\Helpers\Helpers::get( 'tr_crc' ) ); + $order = $this->repository->findRawByHash( \Shared\Helpers\Helpers::get( 'tr_crc' ) ); - if ( $order->id ) + if ( $order && $order['id'] ) { - $order->set_as_paid( true ); - $order->update_status( 4, true ); + $this->adminService->setOrderAsPaid( (int)$order['id'], true ); echo 'TRUE'; exit; } @@ -68,14 +70,13 @@ class ShopOrderController curl_setopt( $ch, CURLOPT_POSTFIELDS, http_build_query( $post ) ); $response = curl_exec( $ch ); - $order = new \shop\Order( 0, '', \Shared\Helpers\Helpers::get( 'p24_session_id' ) ); + $order = $this->repository->findRawByPrzelewy24Hash( \Shared\Helpers\Helpers::get( 'p24_session_id' ) ); - if ( $order['status'] == 0 && $order['summary'] * 100 == \Shared\Helpers\Helpers::get( 'p24_amount' ) ) + if ( $order && $order['status'] == 0 && $order['summary'] * 100 == \Shared\Helpers\Helpers::get( 'p24_amount' ) ) { if ( $order['id'] ) { - $order->set_as_paid( true ); - $order->update_status( 4, true ); + $this->adminService->setOrderAsPaid( (int)$order['id'], true ); } } @@ -88,9 +89,9 @@ class ShopOrderController if ( !empty( $_POST["KWOTA"] ) && !empty( $_POST["ID_PLATNOSCI"] ) && !empty( $_POST["ID_ZAMOWIENIA"] ) && !empty( $_POST["STATUS"] ) && !empty( $_POST["SEKRET"] ) && !empty( $_POST["HASH"] ) ) { - $order = new \shop\Order( $_POST['ID_ZAMOWIENIA'] ); + $order = $this->repository->orderDetailsFrontend( (int)$_POST['ID_ZAMOWIENIA'] ); - if ( $order['id'] ) + if ( $order && $order['id'] ) { if ( is_array( $order['products'] ) && count( $order['products'] ) ): $summary_tmp = 0; @@ -105,21 +106,20 @@ class ShopOrderController { if ( $_POST["STATUS"] == "SUCCESS" ) { - $order->set_as_paid( true ); - $order->update_status( 4, true ); + $this->adminService->setOrderAsPaid( (int)$order['id'], true ); echo \Shared\Helpers\Helpers::lang( 'zamowienie-zostalo-oplacone' ); } else if ( $_POST["STATUS"] == "FAILURE" ) { - $order->update_status( 2, true ); + $this->adminService->changeStatus( (int)$order['id'], 2, true ); echo \Shared\Helpers\Helpers::lang( 'platnosc-zostala-odrzucona' ); } } else { - $order->update_status( 3, true ); + $this->adminService->changeStatus( (int)$order['id'], 3, true ); echo \Shared\Helpers\Helpers::lang( 'zamowienie-zostalo-oplacone-reczne' ); } @@ -136,7 +136,7 @@ class ShopOrderController $order = $this->repository->orderDetailsFrontend( $this->repository->findIdByHash( \Shared\Helpers\Helpers::get( 'order_hash' ) ) ); - $coupon = (int)$order['coupon_id'] ? new \shop\Coupon( (int)$order['coupon_id'] ) : null; + $coupon = (int)$order['coupon_id'] ? ( new \Domain\Coupon\CouponRepository( $GLOBALS['mdb'] ) )->find( (int)$order['coupon_id'] ) : null; return \Shared\Tpl\Tpl::view( 'shop-order/order-details', [ 'order' => $order, diff --git a/autoload/front/Controllers/ShopProductController.php b/autoload/front/Controllers/ShopProductController.php index 5ee5072..fc2fd24 100644 --- a/autoload/front/Controllers/ShopProductController.php +++ b/autoload/front/Controllers/ShopProductController.php @@ -24,9 +24,10 @@ class ShopProductController (int)\Shared\Helpers\Helpers::get( 'offset' ) ); + $productRepo = new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ); if ( is_array( $products_ids ) ): foreach ( $products_ids as $product_id ): $output .= \Shared\Tpl\Tpl::view( 'shop-product/product-mini', [ - 'product' => \shop\Product::getFromCache( $product_id, $lang_id ) + 'product' => $productRepo->findCached( $product_id, $lang_id ) ] ); endforeach; endif; @@ -48,7 +49,29 @@ class ShopProductController $attributes[] = $val; } - $result = \shop\Product::getWarehouseMessage( $values['product-id'], $attributes, $lang_id ); + $productRepo = new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ); + $permutation = self::getPermutation( $attributes ); + $quantity = self::getPermutationQuantity( $values['product-id'], $permutation ); + global $settings; + + $result = []; + if ( $quantity ) + { + $msg = $productRepo->getWarehouseMessageNonzero( (int)$values['product-id'], $lang_id ); + if ( $msg ) + $result = [ 'msg' => $msg, 'quantity' => $quantity ]; + else if ( isset( $settings[ 'warehouse_message_nonzero_' . $lang_id ] ) && $settings[ 'warehouse_message_nonzero_' . $lang_id ] ) + $result = [ 'msg' => $settings[ 'warehouse_message_nonzero_' . $lang_id ], 'quantity' => $quantity ]; + } + else + { + $msg = $productRepo->getWarehouseMessageZero( (int)$values['product-id'], $lang_id ); + if ( $msg ) + $result = [ 'msg' => $msg, 'quantity' => $quantity ]; + else if ( isset( $settings[ 'warehouse_message_zero_' . $lang_id ] ) && $settings[ 'warehouse_message_zero_' . $lang_id ] ) + $result = [ 'msg' => $settings[ 'warehouse_message_zero_' . $lang_id ], 'quantity' => $quantity ]; + } + echo json_encode( $result ); exit; } @@ -68,10 +91,26 @@ class ShopProductController } $product_id = \Shared\Helpers\Helpers::get( 'product_id' ); - $product = \shop\Product::getFromCache( $product_id, $lang_id ); - $product_data = $product->getProductDataBySelectedAttributes( $combination ); + $productRepo = new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ); + $product = $productRepo->findCached( $product_id, $lang_id ); + $product_data = $productRepo->getProductDataBySelectedAttributes( $product, $combination ); echo json_encode( [ 'product_data' => $product_data ] ); exit; } + + private static function getPermutation( $attributes ) + { + if ( !is_array( $attributes ) || !count( $attributes ) ) return null; + return implode( '|', $attributes ); + } + + private static function getPermutationQuantity( $productId, $permutation ) + { + global $mdb; + if ( !$permutation ) return $mdb->get( 'pp_shop_products', 'quantity', [ 'id' => $productId ] ); + $qty = $mdb->get( 'pp_shop_products', 'quantity', [ 'AND' => [ 'parent_id' => $productId, 'permutation_hash' => $permutation ] ] ); + if ( $qty !== null ) return $qty; + return $mdb->get( 'pp_shop_products', 'quantity', [ 'id' => $productId ] ); + } } diff --git a/autoload/front/LayoutEngine.php b/autoload/front/LayoutEngine.php index f401298..f089852 100644 --- a/autoload/front/LayoutEngine.php +++ b/autoload/front/LayoutEngine.php @@ -1,6 +1,5 @@ findCached( \Shared\Helpers\Helpers::get( 'product' ), $lang_id, $_GET['permutation_hash'] ); if ( $product['language']['meta_title'] ) $page['language']['title'] = $product['language']['meta_title']; @@ -243,7 +242,7 @@ class LayoutEngine $html = str_replace( '[PRODUKT:' . $single_product[1] . ']', \Shared\Tpl\Tpl::view( 'shop-product/product-mini', [ - 'product' => \shop\Product::getFromCache( (int)$single_product[1], $lang_id ) + 'product' => ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->findCached( (int)$single_product[1], $lang_id ) ] ), $html ); @@ -259,7 +258,7 @@ class LayoutEngine $products_id = explode( ',', $products_box[1] ); foreach ( $products_id as $product_id ) - $products[] = Product::getFromCache( (int)$product_id, $lang_id ); + $products[] = ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->findCached( (int)$product_id, $lang_id ); $html = str_replace( @@ -288,7 +287,7 @@ class LayoutEngine $products_id_arr = ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->topProductIds( $limit ); foreach ( $products_id_arr as $product_id ){ - $top_products_arr[] = Product::getFromCache( (int)$product_id, $lang_id ); + $top_products_arr[] = ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->findCached( (int)$product_id, $lang_id ); } $html = str_replace( $pattern, @@ -317,7 +316,7 @@ class LayoutEngine foreach ( $products_id_arr as $product_id ){ - $top_products_arr[] = Product::getFromCache( (int)$product_id, $lang_id ); + $top_products_arr[] = ( new \Domain\Product\ProductRepository( $GLOBALS['mdb'] ) )->findCached( (int)$product_id, $lang_id ); } $html = str_replace( $pattern, @@ -334,7 +333,7 @@ class LayoutEngine $html = str_replace( '[META_DESCRIPTION]', $page['language']['meta_description'], $html ); $html = str_replace( '[JEZYKI]', \front\Views\Languages::render( ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->activeLanguages() ), $html ); $html = str_replace( '[TYTUL_STRONY]', self::title( $page['language']['title'], $page['show_title'], $page['language']['page_title'] ), $html ); - $html = str_replace( '[WYSZUKIWARKA]', \shop\Search::simple_form(), $html ); + $html = str_replace( '[WYSZUKIWARKA]', \front\Views\ShopSearch::simpleForm(), $html ); /* atrybut noindex */ if ( \Shared\Helpers\Helpers::get( 'article' ) ) diff --git a/autoload/front/Views/ShopSearch.php b/autoload/front/Views/ShopSearch.php new file mode 100644 index 0000000..5e7bf62 --- /dev/null +++ b/autoload/front/Views/ShopSearch.php @@ -0,0 +1,10 @@ + $val ) - { - $permutation = null; - - if ( isset( $val['parent_id'] ) and (int)$val['parent_id'] and isset( $val['product-id'] ) ) - $permutation = \shop\Product::get_product_permutation_hash( (int)$val['product-id'] ); - - if ( !$permutation and isset( $val['attributes'] ) and is_array( $val['attributes'] ) and count( $val['attributes'] ) ) - $permutation = implode( '|', $val['attributes'] ); - - $quantity_options = \shop\Product::get_product_permutation_quantity_options( - $val['parent_id'] ? $val['parent_id'] : $val['product-id'], - $permutation - ); - - if ( - (int)$basket[ $key ][ 'quantity' ] < 1 - and ( (int)$quantity_options['quantity'] > 0 or (int)$quantity_options['stock_0_buy'] === 1 ) - ) - { - $basket[ $key ][ 'quantity' ] = 1; - $result = true; - } - - if ( ( $val[ 'quantity' ] > $quantity_options['quantity'] ) and !$quantity_options['stock_0_buy'] ) - { - $basket[ $key ][ 'quantity' ] = $quantity_options['quantity']; - if ( $message ) - \Shared\Helpers\Helpers::error( 'Ilość jednego lub więcej produktów została zmniejszona z powodu niestarczających stanów magazynowych. Sprawdź proszę koszyk.' ); - $result = true; - } - } - \Shared\Helpers\Helpers::set_session( 'basket', $basket ); - - return $result; - } - - public function __get( $variable ) - { - if ( array_key_exists( $variable, $this -> data ) ) - return $this -> $variable; - } - - public function __set( $variable, $value ) - { - $this -> $variable = $value; - } - - public function offsetExists( $offset ) - { - return isset( $this -> $offset ); - } - - public function offsetGet( $offset ) - { - return $this -> $offset; - } - - public function offsetSet( $offset, $value ) - { - $this -> $offset = $value; - } - - public function offsetUnset( $offset ) - { - unset( $this -> $offset ); - } -} diff --git a/autoload/shop/class.Category.php b/autoload/shop/class.Category.php deleted file mode 100644 index d69046e..0000000 --- a/autoload/shop/class.Category.php +++ /dev/null @@ -1,84 +0,0 @@ - select( 'pp_shop_products_categories', 'product_id', [ 'category_id' => $category_id ] ); - } - - static public function get_category_name( int $category_id, $lang_id = null ) - { - global $mdb; - - if ( !$lang_id ) - $lang_id = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->defaultLanguage(); - - return $mdb -> get( 'pp_shop_categories_langs', 'title', [ 'AND' => [ 'category_id' => $category_id, 'lang_id' => $lang_id ] ] ); - } - - public function __get( $variable ) - { - if ( array_key_exists( $variable, $this -> data ) ) - return $this -> $variable; - } - - public function __set( $variable, $value ) - { - $this -> $variable = $value; - } - - public function offsetExists( $offset ) - { - return isset( $this -> $offset ); - } - - public function offsetGet( $offset ) - { - return $this -> $offset; - } - - public function offsetSet( $offset, $value ) - { - $this -> $offset = $value; - } - - public function offsetUnset( $offset ) - { - unset( $this -> $offset ); - } - - static public function get_subcategory_by_category( int $category_id ) - { - global $mdb; - - $cacheHandler = new \Shared\Cache\CacheHandler(); - $cacheKey = "\shop\Category::get_subcategory_by_category:$category_id"; - - $objectData = $cacheHandler -> get( $cacheKey ); - - if ( !$objectData ) - { - $shop_categories = $mdb -> select( 'pp_shop_categories', '*', ['parent_id' => $category_id] ); - $shop_categories_langs = array(); - foreach ($shop_categories as $shop_categorie) { - array_push($shop_categories_langs, $mdb -> get( 'pp_shop_categories_langs', '*', ['category_id' => $shop_categorie['id']] )); - } - - $cacheHandler -> set( $cacheKey, $shop_categories_langs ); - } - else - { - return unserialize( $objectData ); - } - - return $shop_categories_langs; - } -} \ No newline at end of file diff --git a/autoload/shop/class.Coupon.php b/autoload/shop/class.Coupon.php deleted file mode 100644 index 84af53a..0000000 --- a/autoload/shop/class.Coupon.php +++ /dev/null @@ -1,83 +0,0 @@ - get( 'pp_shop_coupon', '*', [ 'id' => $element_id ] ); - if ( is_array( $result ) ) - $this -> data = $result; - } - } - - public function load_from_db_by_name( string $name ) - { - global $mdb; - $result = $mdb -> get( 'pp_shop_coupon', '*', [ 'name' => $name ] ); - if ( is_array( $result ) ) - $this -> data = $result; - } - - public function is_one_time() - { - return (bool)( $this -> data['one_time'] ?? 0 ); - } - - public function is_available() - { - if ( !( $this -> data['id'] ?? 0 ) ) - return false; - - if ( !( $this -> data['status'] ?? 0 ) ) - return false; - - return !( $this -> data['used'] ?? 0 ); - } - - public function set_as_used() - { - $id = (int)( $this -> data['id'] ?? 0 ); - if ( $id > 0 ) - { - global $mdb; - $repo = new \Domain\Coupon\CouponRepository( $mdb ); - $repo -> markAsUsed( $id ); - $this -> data['used'] = 1; - } - } - - public function __get( $variable ) - { - return isset( $this -> data[$variable] ) ? $this -> data[$variable] : null; - } - - public function __set( $variable, $value ) - { - $this -> data[$variable] = $value; - } - - public function offsetExists( $offset ) - { - return isset( $this -> data[$offset] ); - } - - public function offsetGet( $offset ) - { - return isset( $this -> data[$offset] ) ? $this -> data[$offset] : null; - } - - public function offsetSet( $offset, $value ) - { - $this -> data[$offset] = $value; - } - - public function offsetUnset( $offset ) - { - unset( $this -> data[$offset] ); - } -} diff --git a/autoload/shop/class.Order.php b/autoload/shop/class.Order.php deleted file mode 100644 index ff82969..0000000 --- a/autoload/shop/class.Order.php +++ /dev/null @@ -1,562 +0,0 @@ - get( 'pp_shop_orders', '*', [ 'id' => $order_id ] ); - if ( \Shared\Helpers\Helpers::is_array_fix( $result ) ) foreach ( $result as $key => $val ) - $this -> $key = $val; - } - - if ( $hash ) - { - $result = $mdb -> get( 'pp_shop_orders', '*', [ 'hash' => $hash ] ); - if ( \Shared\Helpers\Helpers::is_array_fix( $result ) ) foreach ( $result as $key => $val ) - $this -> $key = $val; - } - - if ( $przelewy24_hash ) - { - $result = $mdb -> get( 'pp_shop_orders', '*', [ 'przelewy24_hash' => $przelewy24_hash ] ); - if ( \Shared\Helpers\Helpers::is_array_fix( $result ) ) foreach ( $result as $key => $val ) - $this -> $key = $val; - } - - $this -> products = $mdb -> select( 'pp_shop_order_products', '*', [ 'order_id' => $order_id ] ); - $this -> statuses = $mdb -> select( 'pp_shop_order_statuses', '*', [ 'order_id' => $order_id, 'ORDER' => [ 'id' => 'DESC' ] ] ); - } - - static public function notes_save( int $order_id, $notes ) - { - global $mdb; - return $mdb -> update( 'pp_shop_orders', [ 'notes' => $notes ], [ 'id' => $order_id ] ); - } - - public static function order_statuses() - { - global $mdb; - - $repository = new \Domain\ShopStatus\ShopStatusRepository( $mdb ); - return $repository -> allStatuses(); - } - - public function update_aplio_order_status_date( $date ) - { - global $mdb; - return $mdb -> update( 'pp_shop_orders', [ 'apilo_order_status_date' => $date ], [ 'id' => $this -> id ] ); - } - - // set_as_unpaid - public function set_as_unpaid() { - global $mdb; - - $mdb -> update( 'pp_shop_orders', [ 'paid' => 0 ], [ 'id' => $this -> id ] ); - return true; - } - - // set_as_paid - public function set_as_paid( $send_email = false ) - { - global $mdb, $config; - - $integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb ); - - // apilo - $apilo_settings = $integrationsRepository -> getSettings( 'apilo' ); - if ( $apilo_settings['enabled'] and $apilo_settings['access-token'] and $apilo_settings['sync_orders'] ) - { - // put data to file - if ( $config['debug']['apilo'] ) - { - file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', date( 'Y-m-d H:i:s' ) . " --- SET AS PAID\n\n", FILE_APPEND ); - file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', print_r( $this, true ) . "\n\n", FILE_APPEND ); - } - - if ( $this -> apilo_order_id and !$this -> sync_apilo_payment() ) - { - self::queue_apilo_sync( (int)$this -> id, true, null, 'payment_sync_failed' ); - } - } - - $mdb -> update( 'pp_shop_orders', [ 'paid' => 1 ], [ 'id' => $this -> id ] ); - $this -> update_status( 1, $send_email ); - - $dir_logs = $_SERVER['DOCUMENT_ROOT'] . '/logs/'; - if ( !is_dir( $dir_logs ) ) - mkdir( $dir_logs, 0777, true ); - - // $file = $dir_logs . date( 'Y-m-d' ) . '-order-set-as-paid.txt'; - // $content = date( 'Y-m-d H:i:s' ) . ' | ' . $this -> id . ' | ' . $this -> number . ' | ' . $this -> summary . ' | ' . $this -> client_email . "\n"; - // $content .= print_r( $apilo_response, true ) . "\n\n"; - // file_put_contents( $file, $content, FILE_APPEND ); - - return true; - } - - // zmiana statusu zamówienia - public function update_status( int $status, int $email_send ) - { - global $mdb, $config; - - $integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb ); - - if ( $this -> status == $status ) - return false; - - if ( $mdb -> update( 'pp_shop_orders', [ 'status' => $status ], [ 'id' => $this -> id ] ) ) - { - $mdb -> insert( 'pp_shop_order_statuses', [ - 'order_id' => $this -> id, - 'status_id' => $status, - 'mail' => $email_send - ] ); - - if ( $email_send ) - { - $this -> status = $status; - $response['email'] = $this -> send_status_change_email(); - } - - $response['result'] = true; - - // apilo - $apilo_settings = $integrationsRepository -> getSettings( 'apilo' ); - if ( $apilo_settings['enabled'] and $apilo_settings['access-token'] and $apilo_settings['sync_orders'] ) - { - // put data to file - if ( $config['debug']['apilo'] ) - { - file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', date( 'Y-m-d H:i:s' ) . " --- UPDATE STATUS\n\n", FILE_APPEND ); - file_put_contents( $_SERVER['DOCUMENT_ROOT'] . '/logs/apilo.txt', print_r( $this, true ) . "\n\n", FILE_APPEND ); - } - - if ( $this -> apilo_order_id and !$this -> sync_apilo_status( (int)$status ) ) - { - self::queue_apilo_sync( (int)$this -> id, false, (int)$status, 'status_sync_failed' ); - } - } - - return $response; - } - return false; - } - - // ponowne wysłanie maila o złożonym zamówieniu - public function order_resend_confirmation_email() - { - global $settings; - - global $mdb; - $order = ( new \Domain\Order\OrderRepository( $mdb ) )->orderDetailsFrontend( $this -> id ); - $coupon = (int)$order['coupon_id'] ? new \shop\Coupon( (int)$order['coupon_id'] ) : null; - - $mail_order = \Shared\Tpl\Tpl::view( 'shop-order/mail-summary', [ - 'settings' => $settings, - 'order' => $order, - 'coupon' => $coupon, - ] ); - - $settings[ 'ssl' ] ? $base = 'https' : $base = 'http'; - - $regex = "-(]+src\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i"; - $mail_order = preg_replace( $regex, "$1" . $base . "://" . $_SERVER[ 'SERVER_NAME' ] . "$2$4", $mail_order ); - - $regex = "-(]+href\s*=\s*['\"])(((?!'|\"|https?://).)*)(['\"][^>]*>)-i"; - $mail_order = preg_replace( $regex, "$1" . $base . "://" . $_SERVER[ 'SERVER_NAME' ] . "$2$4", $mail_order ); - - \Shared\Helpers\Helpers::send_email( $this -> client_email, \Shared\Helpers\Helpers::lang( 'potwierdzenie-zamowienia-ze-sklepu' ) . ' ' . $settings[ 'firm_name' ], $mail_order ); - \Shared\Helpers\Helpers::send_email( $settings[ 'contact_email' ], 'Nowe zamówienie / ' . $settings[ 'firm_name' ] . ' / ' . $order['number'] . ' - ' . $order['client_surname'] . ' ' . $order['client_name'], $mail_order ); - - return true; - } - - // wysłanie maila o zmianie statusu - public function send_status_change_email() - { - if ( !$this -> client_email ) - return false; - - $order_statuses = self::order_statuses(); - - $firm_name = ( new \Domain\Settings\SettingsRepository( $mdb ) )->getSingleValue( 'firm_name' ); - - switch( $this -> status ): - case 0: - $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - zamówienie [NUMER] zostało złożone' ); - break; - case 1: - $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - zamówienie [NUMER] zostało opłacone' ); - break; - case 2: - $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - płatność za zamówienie [NUMER] została odrzucona' ); - break; - case 3: - $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - płatność za zamówienie [NUMER] jest sprawdzania ręcznie' ); - break; - case 4: - $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - zamówienie [NUMER] zostało przyjęte do realizacji' ); - break; - case 5: - $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - zamówienie [NUMER] zostało wysłane' ); - break; - case 6: - $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - zamówienie [NUMER] zostało zrealizowane' ); - break; - case 7: - $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - zamówienie [NUMER] zostało przygotowane go wysłania' ); - break; - case 8: - $subject = str_replace( '[NUMER]', $this -> number, $firm_name . ' - zamówienie [NUMER] zostało anulowane' ); - break; - endswitch; - - $email = new \Email( 0 ); - $email -> load_by_name( '#sklep-zmiana-statusu-zamowienia' ); - - $email -> text = str_replace( '[NUMER_ZAMOWIENIA]', $this -> number, $email -> text ); - $email -> text = str_replace( '[DATA_ZAMOWIENIA]', date( 'Y/m/d', strtotime( $this -> date_order ) ), $email -> text ); - $email -> text = str_replace( '[STATUS]', $order_statuses[ $this -> status ], $email -> text ); - - return $email -> send( $this -> client_email, $subject, true ); - } - - // wyliczanie wartości zamówienia podczas edycji zamówienia - static public function calculate_order_summary_by_admin( int $order_id ) - { - global $mdb; - - $rows = $mdb -> select( 'pp_shop_order_products', '*', [ 'order_id' => $order_id ] ); - if ( \Shared\Helpers\Helpers::is_array_fix( $rows ) ) foreach ( $rows as $row ) - { - if ( $row['price_brutto_promo'] ) - $summary += $row['price_brutto_promo'] * $row['quantity']; - else - $summary += $row['price_brutto'] * $row['quantity']; - } - - return $summary + $mdb -> get( 'pp_shop_orders', 'transport_cost', [ 'id' => $order_id ] ); - } - - // ADMIN - usunięcie zamówienia - static public function order_delete( int $order_id ) - { - global $mdb; - return $mdb -> delete( 'pp_shop_orders', [ 'id' => $order_id ] ); - } - - // ADMIN - zmiana zamówienia - static public function order_save_by_admin( int $order_id, string $client_name, string $client_surname, string $client_street, string $client_postal_code, string $client_city, string $client_email, $firm_name = '', $firm_street = '', $firm_postal_code = '', $firm_city = '', $firm_nip = '', - int $transport_id, string $inpost_paczkomat, int $payment_method_id ) - { - global $mdb, $user; - - $mdb -> update( 'pp_shop_orders', [ - 'client_name' => $client_name, - 'client_surname' => $client_surname, - 'client_street' => $client_street, - 'client_postal_code' => $client_postal_code, - 'client_city' => $client_city, - 'client_email' => $client_email, - 'firm_name' => $firm_name ? $firm_name : null, - 'firm_street' => $firm_street ? $firm_street : null, - 'firm_postal_code' => $firm_postal_code ? $firm_postal_code : null, - 'firm_city' => $firm_city ? $firm_city : null, - 'firm_nip' => $firm_nip ? $firm_nip : null, - 'transport_id' => $transport_id, - 'transport' => $mdb -> get( 'pp_shop_transports', 'name_visible', [ 'id' => $transport_id ] ), - 'transport_cost' => $mdb -> get( 'pp_shop_transports', 'cost', [ 'id' => $transport_id ] ), - 'transport_description' => $mdb -> get( 'pp_shop_transports', 'description', [ 'id' => $transport_id ] ), - 'inpost_paczkomat' => $inpost_paczkomat, - 'payment_method_id' => $payment_method_id, - 'payment_method' => $mdb -> get( 'pp_shop_payment_methods', 'name', [ 'id' => $payment_method_id ] ), - ], [ - 'id' => $order_id - ] ); - - $mdb -> update( 'pp_shop_orders', [ - 'summary' => \shop\Order::calculate_order_summary_by_admin( $order_id ) - ], [ - 'id' => $order_id - ] ); - - return true; - } - - public function offsetExists( $offset ) - { - return isset( $this -> $offset ); - } - - public function offsetGet( $offset ) - { - return $this -> $offset; - } - - public function offsetSet( $offset, $value ) - { - $this -> $offset = $value; - } - - public function offsetUnset( $offset ) - { - unset( $this -> $offset ); - } - - private function sync_apilo_payment(): bool - { - global $config, $mdb; - - $integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb ); - - if ( !(int)$this -> apilo_order_id ) - return true; - - $payment_type = (int)( new \Domain\PaymentMethod\PaymentMethodRepository( $mdb ) )->getApiloPaymentTypeId( (int)$this -> payment_method_id ); - if ( $payment_type <= 0 ) - $payment_type = 1; - - $payment_date = new \DateTime( $this -> date_order ); - $access_token = $integrationsRepository -> apiloGetAccessToken(); - - $ch = curl_init(); - curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $this -> apilo_order_id . '/payment/' ); - curl_setopt( $ch, CURLOPT_POST, 1 ); - curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( [ - 'amount' => str_replace( ',', '.', $this -> summary ), - 'paymentDate' => $payment_date -> format('Y-m-d\TH:i:s\Z'), - 'type' => $payment_type - ] ) ); - curl_setopt( $ch, CURLOPT_HTTPHEADER, [ - "Authorization: Bearer " . $access_token, - "Accept: application/json", - "Content-Type: application/json" - ] ); - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); - curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 5 ); - curl_setopt( $ch, CURLOPT_TIMEOUT, 15 ); - $apilo_response = curl_exec( $ch ); - $http_code = (int)curl_getinfo( $ch, CURLINFO_HTTP_CODE ); - $curl_error = curl_errno( $ch ) ? curl_error( $ch ) : ''; - curl_close( $ch ); - - if ( $config['debug']['apilo'] ) - { - self::append_apilo_log( "PAYMENT RESPONSE\nHTTP: " . $http_code . "\nCURL: " . $curl_error . "\n" . print_r( $apilo_response, true ) . "\n" ); - } - - if ( $curl_error !== '' ) - return false; - - if ( $http_code < 200 or $http_code >= 300 ) - return false; - - return true; - } - - private function sync_apilo_status( int $status ): bool - { - global $config, $mdb; - - $integrationsRepository = new \Domain\Integrations\IntegrationsRepository( $mdb ); - - if ( !(int)$this -> apilo_order_id ) - return true; - - $access_token = $integrationsRepository -> apiloGetAccessToken(); - - $ch = curl_init(); - curl_setopt( $ch, CURLOPT_URL, "https://projectpro.apilo.com/rest/api/orders/" . $this -> apilo_order_id . '/status/' ); - curl_setopt( $ch, CURLOPT_POST, 1 ); - curl_setopt( $ch, CURLOPT_CUSTOMREQUEST, "PUT"); - curl_setopt( $ch, CURLOPT_POSTFIELDS, json_encode( [ - 'id' => $this -> apilo_order_id, - 'status' => (int)( new \Domain\ShopStatus\ShopStatusRepository( $mdb ) )->getApiloStatusId( (int)$status ) - ] ) ); - curl_setopt( $ch, CURLOPT_HTTPHEADER, [ - "Authorization: Bearer " . $access_token, - "Accept: application/json", - "Content-Type: application/json" - ] ); - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true ); - curl_setopt( $ch, CURLOPT_CONNECTTIMEOUT, 5 ); - curl_setopt( $ch, CURLOPT_TIMEOUT, 15 ); - $apilo_result = curl_exec( $ch ); - $http_code = (int)curl_getinfo( $ch, CURLINFO_HTTP_CODE ); - $curl_error = curl_errno( $ch ) ? curl_error( $ch ) : ''; - curl_close( $ch ); - - if ( $config['debug']['apilo'] ) - { - self::append_apilo_log( "STATUS RESPONSE\nHTTP: " . $http_code . "\nCURL: " . $curl_error . "\n" . print_r( $apilo_result, true ) . "\n" ); - } - - if ( $curl_error !== '' ) - return false; - - if ( $http_code < 200 or $http_code >= 300 ) - return false; - - return true; - } - - public static function process_apilo_sync_queue( int $limit = 10 ): int - { - $queue = self::load_apilo_sync_queue(); - if ( !\Shared\Helpers\Helpers::is_array_fix( $queue ) ) - return 0; - - $processed = 0; - - foreach ( $queue as $key => $task ) - { - if ( $processed >= $limit ) - break; - - $order_id = (int)( $task['order_id'] ?? 0 ); - if ( $order_id <= 0 ) - { - unset( $queue[$key] ); - continue; - } - - $order = new self( $order_id ); - if ( !(int)$order -> id ) - { - unset( $queue[$key] ); - continue; - } - - $error = ''; - $sync_failed = false; - - $payment_pending = !empty( $task['payment'] ) and (int)$order -> paid === 1; - if ( $payment_pending and (int)$order -> apilo_order_id ) - { - if ( !$order -> sync_apilo_payment() ) - { - $sync_failed = true; - $error = 'payment_sync_failed'; - } - } - - $status_pending = isset( $task['status'] ) and $task['status'] !== null and $task['status'] !== ''; - if ( !$sync_failed and $status_pending and (int)$order -> apilo_order_id ) - { - if ( !$order -> sync_apilo_status( (int)$task['status'] ) ) - { - $sync_failed = true; - $error = 'status_sync_failed'; - } - } - - if ( $sync_failed ) - { - $task['attempts'] = (int)( $task['attempts'] ?? 0 ) + 1; - $task['last_error'] = $error; - $task['updated_at'] = date( 'Y-m-d H:i:s' ); - $queue[$key] = $task; - } - else - { - unset( $queue[$key] ); - } - - $processed++; - } - - self::save_apilo_sync_queue( $queue ); - - return $processed; - } - - private static function queue_apilo_sync( int $order_id, bool $payment, ?int $status, string $error ): void - { - if ( $order_id <= 0 ) - return; - - $queue = self::load_apilo_sync_queue(); - $key = (string)$order_id; - $row = is_array( $queue[$key] ?? null ) ? $queue[$key] : []; - - $row['order_id'] = $order_id; - $row['payment'] = !empty( $row['payment'] ) || $payment ? 1 : 0; - if ( $status !== null ) - $row['status'] = $status; - - $row['attempts'] = (int)( $row['attempts'] ?? 0 ) + 1; - $row['last_error'] = $error; - $row['updated_at'] = date( 'Y-m-d H:i:s' ); - - $queue[$key] = $row; - self::save_apilo_sync_queue( $queue ); - } - - private static function apilo_sync_queue_path(): string - { - return dirname( __DIR__, 2 ) . self::APILO_SYNC_QUEUE_FILE; - } - - private static function load_apilo_sync_queue(): array - { - $path = self::apilo_sync_queue_path(); - if ( !file_exists( $path ) ) - return []; - - $content = file_get_contents( $path ); - if ( !$content ) - return []; - - $decoded = json_decode( $content, true ); - if ( !is_array( $decoded ) ) - return []; - - return $decoded; - } - - private static function save_apilo_sync_queue( array $queue ): void - { - $path = self::apilo_sync_queue_path(); - $dir = dirname( $path ); - if ( !is_dir( $dir ) ) - mkdir( $dir, 0777, true ); - - file_put_contents( $path, json_encode( $queue, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ), LOCK_EX ); - } - - private static function append_apilo_log( string $message ): void - { - $base = isset( $_SERVER['DOCUMENT_ROOT'] ) && $_SERVER['DOCUMENT_ROOT'] - ? rtrim( $_SERVER['DOCUMENT_ROOT'], '/\\' ) - : dirname( __DIR__, 2 ); - - $dir = $base . '/logs'; - if ( !is_dir( $dir ) ) - mkdir( $dir, 0777, true ); - - file_put_contents( - $dir . '/apilo.txt', - date( 'Y-m-d H:i:s' ) . ' --- ' . $message . "\n\n", - FILE_APPEND - ); - } -} diff --git a/autoload/shop/class.Product.php b/autoload/shop/class.Product.php deleted file mode 100644 index 8092da9..0000000 --- a/autoload/shop/class.Product.php +++ /dev/null @@ -1,952 +0,0 @@ -defaultLanguage(); - - $result = $mdb -> get( 'pp_shop_products', '*', [ 'id' => $product_id ] ); - if ( \Shared\Helpers\Helpers::is_array_fix( $result ) ) foreach ( $result as $key => $val ) - $this -> $key = $val; - - // kombinacja produktu - if ( $this -> parent_id ) - { - // pobranie wartości z produktu głównego - if ( !$this -> price_netto or !$this -> price_brutto ) - { - $result = $mdb -> get( 'pp_shop_products', [ 'price_netto', 'price_brutto', 'price_netto_promo', 'price_brutto_promo', 'vat', 'wp' ], [ 'id' => $this -> parent_id ] ); - if ( \Shared\Helpers\Helpers::is_array_fix( $result ) ) foreach ( $result as $key => $val ) - $this -> $key = $val; - } - - $results = $mdb -> select( 'pp_shop_products_langs', '*', [ 'AND' => [ 'product_id' => $this -> parent_id, 'lang_id' => $lang_id ] ] ); - if ( \Shared\Helpers\Helpers::is_array_fix( $results ) ) foreach ( $results as $row ) - { - if ( $row[ 'copy_from' ] ) - { - $results2 = $mdb -> select( 'pp_shop_products_langs', '*', [ 'AND' => [ 'product_id' => $this -> parent_id, 'lang_id' => $row[ 'copy_from' ] ] ] ); - if ( is_array( $results2 ) ) - foreach ( $results2 as $row2 ) - $this -> language = $row2; - } - else - $this -> language = $row; - } - - $this -> images = $mdb -> select( 'pp_shop_products_images', '*', [ 'product_id' => $this -> parent_id, 'ORDER' => [ 'o' => 'ASC', 'id' => 'ASC' ] ] ); - $this -> files = $mdb -> select( 'pp_shop_products_files', '*', [ 'product_id' => $this -> parent_id] ); - $this -> categories = $mdb -> select( 'pp_shop_products_categories', 'category_id', [ 'product_id' => $this -> parent_id ] ); - - $this -> products_related = $mdb -> select( 'pp_shop_products_related', 'product_related_id', [ 'product_id' => $this -> parent_id ] ); - $this -> products_sets = $mdb -> select( 'pp_shop_product_sets_products', 'product_id', [ 'AND' => [ 'set_id' => $this -> set_id, 'product_id[!]' => $this -> parent_id ] ] ); - } - else - { - $results = $mdb -> select( 'pp_shop_products_langs', '*', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $lang_id ] ] ); - if ( \Shared\Helpers\Helpers::is_array_fix( $results ) ) foreach ( $results as $row ) - { - if ( $row[ 'copy_from' ] ) - { - $results2 = $mdb -> select( 'pp_shop_products_langs', '*', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $row[ 'copy_from' ] ] ] ); - if ( is_array( $results2 ) ) - foreach ( $results2 as $row2 ) - $this -> language = $row2; - } - else - $this -> language = $row; - } - - $this -> images = $mdb -> select( 'pp_shop_products_images', '*', [ 'product_id' => $product_id, 'ORDER' => [ 'o' => 'ASC', 'id' => 'ASC' ] ] ); - $this -> files = $mdb -> select( 'pp_shop_products_files', '*', [ 'product_id' => $product_id ] ); - $this -> categories = $mdb -> select( 'pp_shop_products_categories', 'category_id', [ 'product_id' => $product_id ] ); - - $this -> products_related = $mdb -> select( 'pp_shop_products_related', 'product_related_id', [ 'product_id' => $product_id ] ); - $this -> products_sets = $mdb -> select( 'pp_shop_product_sets_products', 'product_id', [ 'AND' => [ 'set_id' => $this -> set_id, 'product_id[!]' => $product_id ] ] ); - - $results = $mdb -> select( 'pp_shop_products', 'id', [ 'parent_id' => $product_id ] ); - if ( \Shared\Helpers\Helpers::is_array_fix( $results ) ) - { - foreach ( $results as $row ) - $product_combinations[] = \shop\Product::getFromCache( $row, $lang_id ); - - $this -> product_combinations = $product_combinations; - } - } - - $producer = $mdb -> get( 'pp_shop_producer', '*', [ 'id' => (int) $this -> producer_id ] ); - $producer_languages = $mdb -> get( 'pp_shop_producer_lang', '*', [ 'AND' => [ 'producer_id' => (int) $this -> producer_id, 'lang_id' => $lang_id ] ] ); - $producer['description'] = $producer_languages['description']; - $producer['data'] = $producer_languages['data']; - $producer['meta_title'] = $producer_languages['meta_title']; - - $this -> producer = $producer; - - if ( $permutation_hash ) - { - $permutation_price = $mdb -> get( 'pp_shop_products', [ 'price_netto', 'price_brutto', 'price_netto_promo', 'price_brutto_promo' ], [ 'AND' => [ 'permutation_hash' => $permutation_hash, 'parent_id' => $product_id ] ] ); - if ( is_array( $permutation_price ) ) - { - if ( $permutation_price[ 'price_netto' ] != null ) - $this -> price_netto = $permutation_price[ 'price_netto' ]; - - if ( $permutation_price[ 'price_brutto' ] != null ) - $this -> price_brutto = $permutation_price[ 'price_brutto' ]; - - if ( $permutation_price[ 'price_netto_promo' ] != null ) - $this -> price_netto_promo = $permutation_price[ 'price_netto_promo' ]; - - if ( $permutation_price[ 'price_brutto_promo' ] != null ) - $this -> price_brutto_promo = $permutation_price[ 'price_brutto_promo' ]; - } - } - - $this -> custom_fields = $mdb -> select( 'pp_shop_products_custom_fields', '*', [ 'id_product' => $product_id ] ); - } - - /** - * Retrieves a product object from cache or creates a new instance if not found. - * - * @param int $product_id The ID of the product. - * @param int $lang_id The ID of the language. - * @param string $permutation_hash The permutation hash of the product. - * @return \shop\Product The product object. - */ - public static function getFromCache($product_id, $lang_id, $permutation_hash = null) - { - // Check if Redis extension is loaded - if (class_exists('Redis')) - { - try - { - // Get the Redis connection instance - $redis = \Shared\Cache\RedisConnection::getInstance()->getConnection(); - - // Check if Redis connection is valid - if ( $redis ) - { - // Try to retrieve the serialized product object from cache - $objectData = $redis->get("shop\product:$product_id:$lang_id:$permutation_hash"); - - if (!$objectData) - { - // Product not found in cache, create a new instance and store it in cache - $object = new self($product_id, $lang_id, $permutation_hash); - $redis->setex("shop\product:$product_id:$lang_id:$permutation_hash", 60 * 60 * 24, serialize($object)); - } - else - { - // Product found in cache, unserialize it - $object = unserialize($objectData); - } - } - else - { - // Redis connection failed, create a new instance - $object = new self($product_id, $lang_id, $permutation_hash); - } - } - catch (\Exception $e) - { - // Log the exception if needed - $object = new self($product_id, $lang_id, $permutation_hash); - } - } - else - { - // Redis extension not loaded, create a new instance - $object = new self($product_id, $lang_id, $permutation_hash); - } - - return $object; - } - - public function getDefaultCombinationPrices() - { - $permutation_hash = ''; - - $attributes = \shop\Product::get_product_attributes( $this -> product_combinations ); - foreach ( $attributes as $attribute ) - { - foreach ( $attribute['values'] as $value ) - { - if ( $value['is_default'] ) - { - if ( $permutation_hash ) - $permutation_hash .= '|'; - $permutation_hash .= $attribute['id'] . '-' . $value['id']; - } - } - } - - foreach ( $this -> product_combinations as $product_combination ) - { - if ( $product_combination -> permutation_hash == $permutation_hash ) - { - $prices['price_netto'] = $product_combination -> price_netto; - $prices['price_brutto'] = $product_combination -> price_brutto; - $prices['price_netto_promo'] = $product_combination -> price_netto_promo; - $prices['price_brutto_promo'] = $product_combination -> price_brutto_promo; - } - } - - return $prices; - } - - public function generateSubtitleFromAttributes( $permutation_hash ) - { - global $lang_id; - - $subtitle = ''; - - $attributes = explode( '|', $permutation_hash ); - foreach ( $attributes as $attribute ) - { - $attribute = explode( '-', $attribute ); - - if ( $subtitle ) - $subtitle .= ', '; - - $subtitle .= \shop\ProductAttribute::getAttributeName( $attribute[0], $lang_id ) . ': ' . \shop\ProductAttribute::get_value_name( $attribute[1], $lang_id ); - } - - return $subtitle; - } - - public function getProductDataBySelectedAttributes( $selected_attribute ) - { - global $settings; - - foreach ( $this -> product_combinations as $product_combination ) - { - if ( $product_combination -> permutation_hash == $selected_attribute ) - { - if ( $product_combination -> quantity !== null or $product_combination -> stock_0_buy ) - { - $result['quantity'] = $product_combination -> quantity; - $result['stock_0_buy'] = $product_combination -> stock_0_buy; - $result['price_netto'] = Shop::shortPrice( $product_combination -> price_netto ); - $result['price_brutto'] = Shop::shortPrice( $product_combination -> price_brutto ); - $result['price_netto_promo'] = $product_combination -> price_netto_promo ? Shop::shortPrice( $product_combination -> price_netto_promo ) : null; - $result['price_brutto_promo'] = $product_combination -> price_brutto_promo ? Shop::shortPrice( $product_combination -> price_brutto_promo ) : null; - } - else - { - $result['quantity'] = $this -> quantity; - $result['stock_0_buy'] = $this -> stock_0_buy; - $result['price_netto'] = Shop::shortPrice( $this -> price_netto ); - $result['price_brutto'] = Shop::shortPrice( $this -> price_brutto ); - $result['price_netto_promo'] = $this -> price_netto_promo ? Shop::shortPrice( $this -> price_netto_promo ) : null; - $result['price_brutto_promo'] = $this -> price_brutto_promo ? Shop::shortPrice( $this -> price_brutto_promo ) : null; - } - } - } - $result['messages']['warehouse_message_zero'] = $this -> language['warehouse_message_zero'] ? $this -> language['warehouse_message_zero'] : $settings['warehouse_message_zero_pl']; - $result['messages']['warehouse_message_nonzero'] = $this -> language['warehouse_message_nonzero'] ? $this -> language['warehouse_message_nonzero'] : $settings['warehouse_message_nonzero_pl']; - $result['permutation_hash'] = $selected_attribute; - return $result; - } - - // sprawdź czy produkt jest na promocji - static public function is_product_on_promotion( int $product_id ) - { - global $mdb; - - if ( $mdb -> get( 'pp_shop_products', 'price_netto_promo', [ 'id' => $product_id ] ) != null ) - return true; - - return false; - } - - // pobierz kod SKU - static public function get_product_sku( int $product_id ) - { - global $mdb; - - $result = $mdb -> get( 'pp_shop_products', [ 'parent_id', 'sku' ], [ 'id' => $product_id ] ); - if ( $result['sku'] ) - return $result['sku']; - else - return $mdb -> get( 'pp_shop_products', 'sku', [ 'id' => $result['parent_id'] ] ); - } - - // pobierz cenę produktu - // FASADA - wywołuje nową klasę Domain\Product\ProductRepository - static public function get_product_price( int $product_id ) - { - global $mdb; - $repository = new \Domain\Product\ProductRepository($mdb); - return $repository->getPrice($product_id); - } - - // pobierz nazwę produktu - // FASADA - wywołuje nową klasę Domain\Product\ProductRepository - static public function get_product_name( int $product_id, string $lang_id = null ) - { - global $mdb; - - if ( !$lang_id ) - $lang_id = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->defaultLanguage(); - - $repository = new \Domain\Product\ProductRepository($mdb); - return $repository->getName($product_id, $lang_id); - } - - // pobierz i wyświetl produktu do zestawu po dodaniu do koszyka - static public function product_sets_when_add_to_basket( int $product_id ) - { - global $mdb; - - $cacheHandler = new \Shared\Cache\CacheHandler(); - $cacheKey = "\shop\Product::product_sets_when_add_to_basket:$product_id"; - - $objectData = $cacheHandler -> get( $cacheKey ); - - if ( !$objectData ) - { - if ( $product_id_tmp = $mdb -> get( 'pp_shop_products', 'parent_id', [ 'id' => $product_id ] ) ) - $set_id = $mdb -> get( 'pp_shop_products', 'set_id', [ 'id' => $product_id_tmp ] ); - else - $set_id = $mdb -> get( 'pp_shop_products', 'set_id', [ 'id' => $product_id ] ); - - $products = $mdb -> select( 'pp_shop_product_sets_products', 'product_id', [ 'set_id' => $set_id ] ); - if ( !$products ) - { - $products_intersection = $mdb -> select( 'pp_shop_orders_products_intersection', [ 'product_1_id', 'product_2_id' ], [ 'OR' => [ 'product_1_id' => $product_id, 'product_2_id' => $product_id ], 'ORDER' => [ 'count' => 'DESC' ], 'LIMIT' => 5 ] ); - if ( !count( $products_intersection ) ) - { - $product_id = $mdb -> get( 'pp_shop_products', 'parent_id', [ 'id' => $product_id ] ); - $products_intersection = $mdb -> select( 'pp_shop_orders_products_intersection', [ 'product_1_id', 'product_2_id' ], [ 'OR' => [ 'product_1_id' => $product_id, 'product_2_id' => $product_id ], 'ORDER' => [ 'count' => 'DESC' ], 'LIMIT' => 5 ] ); - } - - foreach ( $products_intersection as $product_intersection ) - { - if ( $product_intersection['product_1_id'] != $product_id ) - $products[] = $product_intersection['product_1_id']; - else - $products[] = $product_intersection['product_2_id']; - } - array_unique( $products ); - } - - $cacheHandler -> set( $cacheKey, $products ); - } - else - { - $products = unserialize( $objectData ); - } - - foreach ( $products as $product_id ) - { - if ( !( new \Domain\Product\ProductRepository( $mdb ) )->isProductActiveCached( (int)$product_id ) ) - $products = array_diff( $products, [ $product_id ] ); - } - - return \Shared\Tpl\Tpl::view( 'shop-basket/alert-product-sets', [ - 'products' => $products - ] ); - } - - // dodaje 1 do licznika odwiedziń produktu - static public function add_visit( int $product_id ) - { - global $mdb; - return $mdb -> update( 'pp_shop_products', [ 'visits[+]' => 1 ], [ 'id' => $product_id ] ); - } - - //FIX:ME - do poprawy nazwa - // pobierz zdjęcie główne - static public function getProductImg( int $product_id ) - { - global $mdb; - - $results = $mdb -> select( 'pp_shop_products_images', 'src', [ 'product_id' => $product_id, 'ORDER' => [ 'o' => 'ASC' ] ] ); - if ( \Shared\Helpers\Helpers::is_array_fix( $results ) ) foreach ( $results as $row ) - return $row; - } - - //FIX:ME - do poprawy nazwa - // pobierz bezpośredni url produktu - static public function getProductUrl( int $product_id ) - { - global $mdb; - - $lang_id = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->defaultLanguage(); - - $results = $mdb -> select( 'pp_shop_products_langs', '*', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $lang_id ] ] ); - if ( \Shared\Helpers\Helpers::is_array_fix( $results ) ) foreach ( $results as $row ) - { - if ( $row[ 'copy_from' ] ) - { - $results2 = $mdb -> select( 'pp_shop_products_langs', '*', [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $row[ 'copy_from' ] ] ] ); - if ( is_array( $results2 ) ) - foreach ( $results2 as $row2 ) - $language = $row2; - } - else - $language = $row; - } - - $language['seo_link'] ? $url = '/' . $language['seo_link'] : $url = '/p-' . $product_id . '-' . \Shared\Helpers\Helpers::seo( $language['name'] ); - - return $url; - } - - // pobierz ilość wyników na podstawie wyszukiwanej nazwy - static public function searchProductsByNameCount( $query, $lang_id ) - { - global $mdb; - - $results = $mdb -> query( 'SELECT COUNT(0) AS c FROM ( ' - . 'SELECT psp.id, ' - . '( CASE ' - . 'WHEN copy_from IS NULL THEN name ' - . 'WHEN copy_from IS NOT NULL THEN ( ' - . 'SELECT ' - . 'name ' - . 'FROM ' - . 'pp_shop_products_langs ' - . 'WHERE ' - . 'lang_id = pspl.copy_from AND product_id = psp.id ' - . ') ' - . 'END ) AS name ' - . 'FROM ' - . 'pp_shop_products AS psp ' - . 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id ' - . 'WHERE ' - . 'status = 1 AND name LIKE :query AND lang_id = :lang_id ' - . ') AS q1', [ - ':query' => '%' . $query . '%', - ':lang_id' => $lang_id - ] ) -> fetchAll( \PDO::FETCH_ASSOC ); - return $results[0]['c']; - } - - // pobierz id produktów na podstawie wyszukiwanej nazwy - static public function getProductsIdByName( string $query, string $lang_id, int $products_limit, int $from ) - { - global $mdb; - - $results = $mdb -> query( 'SELECT ' - . 'psp.id, ' - . '( CASE ' - . 'WHEN copy_from IS NULL THEN name ' - . 'WHEN copy_from IS NOT NULL THEN ( ' - . 'SELECT ' - . 'name ' - . 'FROM ' - . 'pp_shop_products_langs ' - . 'WHERE ' - . 'lang_id = pspl.copy_from AND product_id = psp.id ' - . ') ' - . 'END ) AS name ' - . 'FROM ' - . 'pp_shop_products AS psp ' - . 'INNER JOIN pp_shop_products_langs AS pspl ON pspl.product_id = psp.id ' - . 'WHERE ' - . 'status = 1 AND name LIKE :query AND lang_id = :lang_id ' - . 'ORDER BY ' - . 'name ASC ' - . 'LIMIT ' - . $from . ',' . $products_limit, [ - ':query' => '%' . $query . '%', - ':lang_id' => $lang_id - ] ) -> fetchAll( \PDO::FETCH_ASSOC ); - if ( is_array( $results ) and !empty( $results ) ) foreach ( $results as $row ) - $output[] = $row['id']; - - return $output; - } - - static public function searchProductsByName( string $query, string $lang_id, int $bs = 0 ) - { - $count = \shop\Product::searchProductsByNameCount( $query, $lang_id ); - $ls = ceil( $count / 12 ); - - if ( $bs < 1 ) - $bs = 1; - else if ( $bs > $ls ) - $bs = $ls; - - $from = 12 * ( $bs - 1 ); - - if ( $from < 0 ) - $from = 0; - - $results['products'] = \shop\Product::getProductsIdByName( $query, $lang_id, 12, $from ); - $results['count'] = $count; - $results['ls'] = $ls; - - return $results; - - } - - static public function searchProductByNameAjax( string $query, string $lang_id ) - { - global $mdb; - return $mdb -> query( - 'SELECT - product_id - FROM - pp_shop_products_langs AS pspl - INNER JOIN pp_shop_products AS psp ON psp.id = pspl.product_id - WHERE - status = 1 AND lang_id = :lang_id AND LOWER(name) LIKE :query - ORDER BY visits DESC - LIMIT 12', [ - ':query' => '%' . $query . '%', - ':lang_id' => $lang_id - ] ) -> fetchAll( \PDO::FETCH_ASSOC ); - } - - public function permutations() - { - if ( is_array( $this -> attributes ) ) foreach ( $this -> attributes as $attribute ) - { - $attribute_obj = new \shop\ProductAttribute( $attribute['attribute_id'] ); - - if ( $attribute_obj['stock_influences'] ) - $attributes_tmp[ $attribute[ 'attribute_id' ] ][] = $attribute[ 'value_id' ]; - } - - if ( is_array( $attributes_tmp ) ) - return $this -> permutations = self::array_cartesian( $attributes_tmp ); - } - - /// sprawdź czy produkt można zamawiać przy stanie magazynowym zero - static public function is_stock_0_buy( int $product_id ) - { - global $mdb; - - // jeżeli jest to kombinacja produktu - if ( $parent_id = $mdb -> get( 'pp_shop_products', 'parent_id', [ 'id' => $product_id ] ) ) - return $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'id' => $parent_id ] ); - else - return $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'id' => $product_id ] ); - } - - // pobierz stan magazynowy i komunikaty dla kombinacji produktu - static public function get_product_permutation_quantity_options( int $product_id, $permutation ) - { - global $mdb, $settings; - - $cacheHandler = new \Shared\Cache\CacheHandler(); - $cacheKey = "\shop\Product::get_product_permutation_quantity_options:v2:$product_id:$permutation"; - - $objectData = $cacheHandler -> get( $cacheKey ); - - if ( !$objectData ) - { - if ( $mdb -> count( 'pp_shop_products', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation ] ] ) ) - { - $result['quantity'] = $mdb -> get( 'pp_shop_products', 'quantity', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation ] ] ); - $result['stock_0_buy'] = $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'AND' => [ 'parent_id' => $product_id, 'permutation_hash' => $permutation ] ] ); - - if ( $result['quantity'] == null ) - { - $result['quantity'] = $mdb -> get( 'pp_shop_products', 'quantity', [ 'id' => $product_id ] ); - - if ( $result['stock_0_buy'] == null ) - $result['stock_0_buy'] = $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'id' => $product_id] ); - - $result['messages'] = $mdb -> get( 'pp_shop_products_langs', [ 'warehouse_message_zero', 'warehouse_message_nonzero' ], [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => 'pl' ] ] ); - - if ( !$result['messages']['warehouse_message_zero'] ) - $result['messages']['warehouse_message_zero'] = $settings['warehouse_message_zero_pl']; - - if ( !$result['messages']['warehouse_message_nonzero'] ) - $result['messages']['warehouse_message_nonzero'] = $settings['warehouse_message_nonzero_pl']; - } - else - { - $result['messages'] = $mdb -> get( 'pp_shop_products_langs', [ 'warehouse_message_zero', 'warehouse_message_nonzero' ], [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => 'pl' ] ] ); - - if ( !$result['messages']['warehouse_message_zero'] ) - $result['messages']['warehouse_message_zero'] = $settings['warehouse_message_zero_pl']; - - if ( !$result['messages']['warehouse_message_nonzero'] ) - $result['messages']['warehouse_message_nonzero'] = $settings['warehouse_message_nonzero_pl']; - } - } - else - { - $result['quantity'] = $mdb -> get( 'pp_shop_products', 'quantity', [ 'id' => $product_id ] ); - $result['stock_0_buy'] = $mdb -> get( 'pp_shop_products', 'stock_0_buy', [ 'id' => $product_id] ); - $result['messages'] = $mdb -> get( 'pp_shop_products_langs', [ 'warehouse_message_zero', 'warehouse_message_nonzero' ], [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => 'pl' ] ] ); - - if ( !$result['messages']['warehouse_message_zero'] ) - $result['messages']['warehouse_message_zero'] = $settings['warehouse_message_zero_pl']; - - if ( !$result['messages']['warehouse_message_nonzero'] ) - $result['messages']['warehouse_message_nonzero'] = $settings['warehouse_message_nonzero_pl']; - } - - $cacheHandler -> set( $cacheKey, $result ); - } - else - { - return unserialize( $objectData ); - } - - return $result; - } - - // pobierz stan magazynowy produktu - // FASADA - wywołuje nową klasę Domain\Product\ProductRepository - static public function get_product_quantity( int $product_id ) - { - global $mdb; - $repository = new \Domain\Product\ProductRepository($mdb); - return $repository->getQuantity($product_id); - } - - public static function product_categories( int $product_id ) - { - global $mdb; - return $mdb -> select( 'pp_shop_products_categories', 'category_id', [ 'product_id' => $product_id ] ); - } - - public static function calculate_basket_product_price( float $price_brutto_promo, float $price_brutto, $coupon, $basket_position ) - { - // ? produkty przecenione - if ( $price_brutto_promo ) - { - $price['price'] = $price_brutto; - $price['price_new'] = $price_brutto_promo; - - // ? zastosuje kod rabatowy - if ( $coupon -> type && $coupon -> include_discounted_product ) - { - // ? ograniczony do wybranych kategorii - if ( $coupon -> categories != null ) - { - $coupon_categories = json_decode( $coupon -> categories ); - $product_categories = \shop\Product::product_categories( (int)$basket_position['parent_id'] ? (int)$basket_position['parent_id'] : (int)$basket_position['product-id'] ); - if ( is_array( $coupon_categories ) ) foreach ( $coupon_categories as $category_tmp ) - { - if ( in_array( $category_tmp, $product_categories ) ) - { - $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal( $price['price_new'] - $price['price_new'] * $coupon -> amount / 100 ); - break; - } - } - } - // ? nieograniczony kategoriami - else - $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal( $price['price_new'] - $price['price_new'] * $coupon -> amount / 100 ); - - // ? uwzględnij promocję jeżeli może łączyć się z rabatami i produktami przecenionymi - if ( $basket_position['discount_amount'] && $basket_position['discount_include_coupon'] && $basket_position['include_product_promo'] ) - { - // ? najtańszy produkt w koszyku (z wybranych kategorii) za X zł - if ( $basket_position['discount_type'] == 3 ) - $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal( $basket_position['discount_amount'] ); - - // ? rabat procentowy - if ( $basket_position['discount_type'] == 1 ) - $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal($price['price_new'] - $price['price_new'] * $basket_position['discount_amount'] / 100 ); - } - } - // ? brak kodu rabatowego - else - { - if ( $basket_position['discount_amount'] && $basket_position['include_product_promo'] ) - { - // ? Najtańszy produkt w koszyku (z wybranych kategorii) za X zł - if ( $basket_position['discount_type'] == 3 ) - $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal( $basket_position['discount_amount'] ); - - // ? rabat procentowy - if ( $basket_position['discount_type'] == 1 ) - $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal($price['price_new'] - $price['price_new'] * $basket_position['discount_amount'] / 100 ); - } - } - } - // ? produkt nieprzeceniony - else - { - $price['price'] = $price_brutto; - $price['price_new'] = $price_brutto; - - // ? zastosuj kod rabatowy - if ( $coupon -> type ) - { - // ? ograniczony do wybranych kategorii - if ( $coupon -> categories != null ) - { - $coupon_categories = json_decode( $coupon -> categories ); - $product_categories = \shop\Product::product_categories( $basket_position['parent_id'] ? $basket_position['parent_id'] : $basket_position['product-id'] ); - if ( is_array( $coupon_categories ) ) foreach ( $coupon_categories as $category_tmp ) - { - if ( in_array( $category_tmp, $product_categories ) ) - { - $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal( $price['price_new'] - $price['price_new'] * $coupon -> amount / 100 ); - break; - } - } - } - // ? nieograniczony - else - $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal($price['price'] - $price['price'] * $coupon -> amount / 100 ); - - // ? uwzględnij promocję jeżeli może łączyć się z rabatami i produktami przecenionymi - if ( $basket_position['discount_amount'] && $basket_position['discount_include_coupon'] && $basket_position['include_product_promo'] ) - { - // ? najtańszy produkt w koszyku (z wybranych kategorii) za X zł - if ( $basket_position['discount_type'] == 3 ) - $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal( $basket_position['discount_amount'] ); - - // ? rabat procentowy - if ( $basket_position['discount_type'] == 1 ) - $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal($price['price'] - $price['price'] * $basket_position['discount_amount'] / 100 ); - } - } - // ? bez kodu rabatowego - else - { - if ( $basket_position['discount_amount'] ) - { - // ? Najtańszy produkt w koszyku (z wybranych kategorii) za X zł - if ( $basket_position['discount_type'] == 3 ) - $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal( $basket_position['discount_amount'] ); - - // ? rabat procentowy - if ( $basket_position['discount_type'] == 1 ) - $price['price_new'] = \Shared\Helpers\Helpers::normalize_decimal($price['price'] - $price['price'] * $basket_position['discount_amount'] / 100 ); - } - } - } - - return $price; - } - - // pobierz wiadomość zależną od stanu magazynowego - // id_product - id produktu - // attributes - wybrane atrybuty przez użytkownika - public static function getWarehouseMessage( int $id_product, $attributes, string $lang_id ) - { - global $settings; - - $permutation = self::getPermutation( $attributes ); - - $quantity = self::getPermutationQuantity( $id_product, $permutation ); - - if ( $quantity ) - { - if ( $msg = ( new \Domain\Product\ProductRepository( $mdb ) )->getWarehouseMessageNonzero( (int)$id_product, $lang_id ) ) - $result = [ 'msg' => $msg, 'quantity' => $quantity ]; - else if ( $settings[ 'warehouse_message_nonzero_' . $lang_id ] ) - $result = [ 'msg' => $settings[ 'warehouse_message_nonzero_' . $lang_id ], 'quantity' => $quantity ]; - } - else - { - if ( $msg = ( new \Domain\Product\ProductRepository( $mdb ) )->getWarehouseMessageZero( (int)$id_product, $lang_id ) ) - $result = [ 'msg' => $msg, 'quantity' => $quantity ]; - else if ( $settings[ 'warehouse_message_zero_' . $lang_id ] ) - $result = [ 'msg' => $settings[ 'warehouse_message_zero_' . $lang_id ], 'quantity' => $quantity ]; - } - return $result; - } - - // pobranie id produktu wg wybranych parametrów - static public function get_product_id_by_attributes( int $parent_id, array $attributes ) - { - global $mdb; - - return $mdb -> get( 'pp_shop_products', 'id', [ 'AND' => [ 'parent_id' => $parent_id, 'permutation_hash' => implode( '|', $attributes ) ] ] ); - } - - // pobranie permutation_hash dla kombinacji produktu - static public function get_product_permutation_hash( int $product_id ) - { - global $mdb; - return $mdb -> get( 'pp_shop_products', 'permutation_hash', [ 'id' => $product_id ] ); - } - - // pobranie listy atrybutów z wybranymi wartościami - static public function get_product_attributes( $products ) - { - if ( !is_array( $products ) or !count( $products ) ) - return false; - - $attributes = array(); - - foreach ( $products as $product ) - { - $permutations = explode( '|', $product['permutation_hash'] ); - foreach ( $permutations as $permutation ) - { - $attribute = explode( '-', $permutation ); - - $value['id'] = $attribute[1]; - $value['is_default'] = \shop\ProductAttribute::is_value_default( $attribute[1] ); - - if ( array_search( $attribute[1], array_column( $attributes, 'id' ) ) === false ) - $attributes[ $attribute[0] ][] = $value; - } - } - - $attributes = \Shared\Helpers\Helpers::removeDuplicates( $attributes, 'id' ); - - foreach ( $attributes as $key => $val ) - { - $row['id'] = $key; - $row['values'] = $val; - - $attributes_sort[ \shop\ProductAttribute::get_attribute_order( $key ) ] = $row; - } - - return $attributes_sort; - } - - public static function array_cartesian( $input ) - { - $result = array(); - - foreach ( $input as $key => $values ) - { - if ( empty( $values ) ) - continue; - - if ( empty( $result ) ) - { - foreach ( $values as $value ) - { - $result[] = array( $key => $value ); - } - } - else - { - $append = array(); - - foreach ( $result as &$product ) - { - $product[$key] = array_shift( $values ); - $copy = $product; - - foreach ( $values as $item ) - { - $copy[$key] = $item; - $append[] = $copy; - } - - array_unshift( $values, $product[$key] ); - } - - $result = array_merge( $result, $append ); - } - } - - return $result; - } - - public function __get( $variable ) - { - if ( is_array( $this -> data ) and array_key_exists( $variable, $this -> data ) ) - return $this -> $variable; - } - - public function __set( $variable, $value ) - { - $this -> $variable = $value; - } - - public function offsetExists( $offset ) - { - return isset( $this -> $offset ); - } - - public function offsetGet( $offset ) - { - return $this -> $offset; - } - - public function offsetSet( $offset, $value ) - { - $this -> $offset = $value; - } - - public function offsetUnset( $offset ) - { - unset( $this -> $offset ); - } - - // generate_sku_code - public static function generate_sku_code( int $product_id ) - { - global $mdb; - // find product with sku like 'PP-000000' - $skus = $mdb -> select( 'pp_shop_products', 'sku', [ 'sku[~]' => 'PP-' ] ); - if ( is_array( $skus ) ) - { - foreach ( $skus as $sku ) - $sku_codes[] = (int)substr( $sku, 3 ); - } - - // find max sku - if ( is_array( $sku_codes ) ) - $sku = 'PP-' . str_pad( max( $sku_codes ) + 1, 6, '0', STR_PAD_LEFT ); - else - $sku = 'PP-000001'; - - return $sku; - } - - // product_xml_name_save - public static function product_xml_name_save( int $product_id, string $xml_name, string $lang_id ) - { - global $mdb; - return $mdb -> update( 'pp_shop_products_langs', [ 'xml_name' => $xml_name ], [ 'AND' => [ 'product_id' => $product_id, 'lang_id' => $lang_id ] ] ); - } - - // product_custom_label_suggestions - public static function product_custom_label_suggestions( string $custom_label, string $label_type ) - { - global $mdb; - - $results = $mdb -> query( 'SELECT DISTINCT - ' . $label_type . ' AS label - FROM - pp_shop_products - WHERE - ' . $label_type . ' LIKE :custom_label - LIMIT 10', [ ':custom_label' => '%' . $custom_label . '%' ] ) -> fetchAll( \PDO::FETCH_ASSOC ); - if ( is_array( $results ) ) - { - foreach ( $results as $row ) - $output[] = $row; - } - - return $output; - } - - // product_custom_label_save - public static function product_custom_label_save( int $product_id, string $custom_label, string $label_type ) - { - global $mdb; - return $mdb -> update( 'pp_shop_products', [ $label_type => $custom_label ], [ 'id' => $product_id ] ); - } - public static function product_meta (int $product_id) - { - global $mdb; - return $mdb->select( - "pp_shop_products_categories (ppc)", - [ - "[>]pp_shop_categories_langs (pcl)" => ["ppc.category_id" => "category_id"] - ], - [ - "pcl.title", - "pcl.seo_link" - ], - [ - "ppc.product_id" => $product_id - ] - ); - } -} diff --git a/autoload/shop/class.ProductAttribute.php b/autoload/shop/class.ProductAttribute.php deleted file mode 100644 index 4ed0e36..0000000 --- a/autoload/shop/class.ProductAttribute.php +++ /dev/null @@ -1,119 +0,0 @@ - get( $cacheKey ); - - if ( !$objectData ) - { - $is_default = $mdb -> get( 'pp_shop_attributes_values', 'is_default', [ 'id' => $value_id ] ); - $cacheHandler -> set( $cacheKey, $is_default ); - } - else - { - return unserialize( $objectData ); - } - - return $is_default; - } - - public function offsetExists($offset) { - return isset($this->$offset); - } - - public function offsetGet($offset) { - return $this->$offset; - } - - public function offsetSet($offset , $value) { - $this->$offset = $value; - } - - public function offsetUnset($offset) { - unset($this->$offset); - } - - public function __construct( int $attribute_id, $lang_id = null ) - { - global $mdb; - - if ( !$lang_id ) - $lang_id = ( new \Domain\Languages\LanguagesRepository( $mdb ) )->defaultLanguage(); - - $result = $mdb -> get( 'pp_shop_attributes', '*', [ 'id' => $attribute_id ] ); - if ( \Shared\Helpers\Helpers::is_array_fix( $result ) ) foreach ( $result as $key => $val ) - $this -> $key = $val; - - $results = $mdb -> select( 'pp_shop_attributes_langs', '*', [ 'AND' => [ 'attribute_id' => $attribute_id, 'lang_id' => $lang_id ] ] ); - if ( \Shared\Helpers\Helpers::is_array_fix( $results ) ) foreach ( $results as $row ) - $this -> language = $row; - } - - // pobierz kolejność atrybutu - static public function get_attribute_order( int $attribute_id ) - { - global $mdb; - - $cacheHandler = new \Shared\Cache\CacheHandler(); - $cacheKey = "\shop\ProductAttribute::get_attribute_order:$attribute_id"; - - $objectData = $cacheHandler -> get( $cacheKey ); - - if ( !$objectData ) - { - $order = $mdb -> get( 'pp_shop_attributes', 'o', [ 'id' => $attribute_id ] ); - $cacheHandler -> set( $cacheKey, $order ); - } - else - { - return unserialize($objectData); - } - - return $order; - } - - // pobierz nazwę atrybutu za pomocą wartości - static public function getAttributeNameByValue( int $value_id, string $lang_id ) - { - global $mdb; - return array_pop( $mdb -> query( 'SELECT name FROM pp_shop_attributes_langs AS psal INNER JOIN pp_shop_attributes_values AS psav ON psal.attribute_id = psav.attribute_id WHERE psav.id = ' . $value_id . ' AND lang_id = \'' . $lang_id . '\'' ) -> fetch( \PDO::FETCH_ASSOC ) ); - } - - // pobierz nazwę wartości - static public function get_value_name( int $value_id, string $lang_id ) - { - global $mdb; - - $cacheHandler = new \Shared\Cache\CacheHandler(); - $cacheKey = "\shop\ProductAttribute::get_value_name:$value_id:$lang_id"; - - $objectData = $cacheHandler -> get( $cacheKey ); - - if ( !$objectData ) - { - $value_name = $mdb -> get( 'pp_shop_attributes_values_langs', 'name', [ 'AND' => [ 'value_id' => (int)$value_id, 'lang_id' => $lang_id ] ] ); - $cacheHandler -> set( $cacheKey, $value_name ); - } - else - { - return unserialize($objectData); - } - return $value_name; - } - - // pobierz nazwę atrybutu - static public function getAttributeName( int $attribute_id, string $lang_id ) - { - global $mdb; - return $mdb -> get( 'pp_shop_attributes_langs', 'name', [ 'AND' => [ 'attribute_id' => (int)$attribute_id, 'lang_id' => $lang_id ] ] ); - } -} \ No newline at end of file diff --git a/autoload/shop/class.ProductCustomField.php b/autoload/shop/class.ProductCustomField.php deleted file mode 100644 index 0c4c750..0000000 --- a/autoload/shop/class.ProductCustomField.php +++ /dev/null @@ -1,74 +0,0 @@ - get( 'pp_shop_products_custom_fields', '*', [ 'id_additional_field' => $custom_field_id ] ); - if ( \Shared\Helpers\Helpers::is_array_fix( $result ) ) foreach ( $result as $key => $val ) - $this -> $key = $val; - } - - static public function getFromCache( int $custom_field_id ) - { - // Check if Redis extension is loaded - if ( class_exists( 'Redis' ) ) - { - try - { - $redis = \Shared\Cache\RedisConnection::getInstance() -> getConnection(); - - if ( $redis ) - { - $objectData = $redis -> get( "shop\ProductCustomField:$custom_field_id" ); - - if ( !$objectData ) - { - $object = new self( $custom_field_id ); - $redis -> setex( "shop\ProductCustomField:$custom_field_id", 60 * 60 * 24, serialize( $object ) ); - } - else - { - $object = unserialize( $objectData ); - } - } - else - { - // Log the error if needed - $object = new self( $custom_field_id ); - } - } - catch ( \Exception $e ) - { - // Log the exception if needed - $object = new self( $custom_field_id ); - } - } - else - { - // Redis extension not loaded, create a new instance - $object = new self( $custom_field_id ); - } - - return $object; - } - - public function offsetExists($offset) { - return isset($this->$offset); - } - - public function offsetGet($offset) { - return $this->$offset; - } - - public function offsetSet($offset , $value) { - $this->$offset = $value; - } - - public function offsetUnset($offset) { - unset($this->$offset); - } -} \ No newline at end of file diff --git a/autoload/shop/class.ProductSet.php b/autoload/shop/class.ProductSet.php deleted file mode 100644 index cca00cb..0000000 --- a/autoload/shop/class.ProductSet.php +++ /dev/null @@ -1,48 +0,0 @@ -$offset); - } - - public function offsetGet($offset) { - return $this->$offset; - } - - public function offsetSet($offset , $value) { - $this->$offset = $value; - } - - public function offsetUnset($offset) { - unset($this->$offset); - } - - public function __construct( int $set_id ) - { - global $mdb; - - $repo = new \Domain\ProductSet\ProductSetRepository( $mdb ); - $data = $repo->find( $set_id ); - - foreach ( $data as $key => $val ) - $this->$key = $val; - } - - //lista dostepnych kompletow (fasada do repozytorium) - static public function sets_list() - { - global $mdb; - $repo = new \Domain\ProductSet\ProductSetRepository( $mdb ); - return $repo->allSets(); - } - - // usuwanie kompletu produktow (fasada do repozytorium) - static public function set_delete( int $set_id ) - { - global $mdb; - $repo = new \Domain\ProductSet\ProductSetRepository( $mdb ); - return $repo->delete( $set_id ); - } -} diff --git a/autoload/shop/class.Promotion.php b/autoload/shop/class.Promotion.php deleted file mode 100644 index 8779333..0000000 --- a/autoload/shop/class.Promotion.php +++ /dev/null @@ -1,106 +0,0 @@ - 'Rabat procentowy na produkty z kategorii 1 jeżeli w koszyku jest produkt z kategorii 2', - 2 => 'Rabat procentowy na produkty z kategorii 1 i 2', - 3 => 'Najtańszy produkt w koszyku (z wybranych kategorii) za X zł', - 4 => 'Rabat procentowy na cały koszyk', - 5 => 'Rabat procentowy na produkty z kategorii 1 lub 2', - ]; - - public static $discount_type = [ 1 => 'Rabat procentowy' ]; - - public function __construct( $id ) - { - global $mdb; - - if ( $id ) - { - $result = $mdb->get( 'pp_shop_promotion', '*', [ 'id' => $id ] ); - if ( is_array( $result ) ) - $this->data = $result; - } - } - - public function __get( $variable ) - { - if ( isset( $this->data[$variable] ) ) - return $this->data[$variable]; - return null; - } - - public static function get_active_promotions() - { - global $mdb; - - $cacheHandler = new \Shared\Cache\CacheHandler(); - $cacheKey = "\shop\Promotion::get_active_promotions"; - - $objectData = $cacheHandler -> get( $cacheKey ); - - if ( !$objectData ) - { - $results = $mdb -> select( 'pp_shop_promotion', 'id', [ 'AND' => [ 'status' => 1, 'OR #date_from' => [ 'date_from' => null, 'date_from[<=]' => date( 'Y-m-d' ) ], 'OR #date_to' => [ 'date_to' => null, 'date_to[>=]' => date( 'Y-m-d' ) ] ], 'ORDER' => [ 'id' => 'DESC' ] ] ); - - $cacheHandler -> set( $cacheKey, $results ); - } - else - { - return unserialize( $objectData ); - } - - return $results; - } - - public static function find_promotion( $basket ) - { - global $mdb; - - foreach ( $basket as $key => $val ) - { - unset( $basket[$key]['discount_type'] ); - unset( $basket[$key]['discount_amount'] ); - unset( $basket[$key]['discount_include_coupon'] ); - unset( $basket[$key]['include_product_promo'] ); - } - - $basket_tmp = $basket; - - $results = self::get_active_promotions(); - if ( is_array( $results ) and count( $results ) ) - { - $promoRepo = new \Domain\Promotion\PromotionRepository( $mdb ); - - foreach ( $results as $row ) - { - $promotion = new \shop\Promotion( $row ); - - // Promocja na cały koszyk - if ( $promotion -> condition_type == 4 ) - return $promoRepo->applyTypeWholeBasket( $basket_tmp, $promotion ); - - // Najtańszy produkt w koszyku (z wybranych kategorii) za X zł - if ( $promotion -> condition_type == 3 ) - return $promoRepo->applyTypeCheapestProduct( $basket_tmp, $promotion ); - - // Rabat procentowy na produkty z kategorii 1 lub kategorii 2 - if ( $promotion -> condition_type == 5 ) - return $promoRepo->applyTypeCategoriesOr( $basket_tmp, $promotion ); - - // Rabat procentowy na produkty z kategorii I i kategorii II - if ( $promotion -> condition_type == 2 ) - return $promoRepo->applyTypeCategoriesAnd( $basket_tmp, $promotion ); - - // Rabat procentowy na produkty z kategorii I jeżeli w koszyku jest produkt z kategorii II - if ( $promotion -> condition_type == 1 ) - return $promoRepo->applyTypeCategoryCondition( $basket_tmp, $promotion ); - } - } - return $basket; - } -} diff --git a/autoload/shop/class.Search.php b/autoload/shop/class.Search.php deleted file mode 100644 index a34cd53..0000000 --- a/autoload/shop/class.Search.php +++ /dev/null @@ -1,70 +0,0 @@ - \Shared\Helpers\Helpers::get( 'query' ), - 'products' => $results['products'] - ]); - - if ( $results['ls'] > 1 ) - { - $tpl = new \Shared\Tpl\Tpl; - $tpl -> ls = $results['ls']; - $tpl -> bs = $bs ? $bs : 1; - $tpl -> link = 'wyszukiwarka/' . \Shared\Helpers\Helpers::get( 'query' ); - $out .= $tpl -> render( 'site/pager' ); - } - - return $out; - } - - static public function search_products() - { - global $lang_id; - - $results = \shop\Product::searchProductByNameAjax( \Shared\Helpers\Helpers::get( 'query' ), $lang_id ); - if ( \Shared\Helpers\Helpers::is_array_fix( $results ) ) foreach ( $results as $row ) - $products[] = \Shared\Tpl\Tpl::view( 'shop-search/product-search', [ - 'product' => Product::getFromCache( $row['product_id'], $lang_id ) - ] ); - - echo json_encode( $products ); - exit; - } - - public function offsetExists( $offset ) - { - return isset( $this -> $offset ); - } - - public function offsetGet( $offset ) - { - return $this -> $offset; - } - - public function offsetSet( $offset, $value ) - { - $this -> $offset = $value; - } - - public function offsetUnset( $offset ) - { - unset( $this -> $offset ); - } -} \ No newline at end of file diff --git a/autoload/shop/class.Shop.php b/autoload/shop/class.Shop.php deleted file mode 100644 index fd75a62..0000000 --- a/autoload/shop/class.Shop.php +++ /dev/null @@ -1,39 +0,0 @@ - $offset ); - } - - public function offsetGet( $offset ) - { - return $this -> $offset; - } - - public function offsetSet( $offset, $value ) - { - $this -> $offset = $value; - } - - public function offsetUnset( $offset ) - { - unset( $this -> $offset ); - } -} \ No newline at end of file diff --git a/autoload/shop/class.Transport.php b/autoload/shop/class.Transport.php deleted file mode 100644 index aa71607..0000000 --- a/autoload/shop/class.Transport.php +++ /dev/null @@ -1,31 +0,0 @@ - select( 'pp_shop_transports', '*', [ 'status' => 1 ], [ 'ORDER' => [ 'o' => 'ASC' ] ] ); - } - - public function offsetExists( $offset ) - { - return isset( $this -> $offset ); - } - - public function offsetGet( $offset ) - { - return $this -> $offset; - } - - public function offsetSet( $offset, $value ) - { - $this -> $offset = $value; - } - - public function offsetUnset( $offset ) - { - unset( $this -> $offset ); - } -} \ No newline at end of file diff --git a/cron-turstmate.php b/cron-turstmate.php index cb45e2a..bc39ac7 100644 --- a/cron-turstmate.php +++ b/cron-turstmate.php @@ -1,6 +1,4 @@ , 'name': '', - 'product_url': 'https://pomysloweprezenty.pl', + 'product_url': 'https://pomysloweprezenty.plfindCached( $product['product_id'], 'pl' ) );?>', "image_url": "https://pomysloweprezenty.plgetFirstImageCached( (int)$product['product_id'] );?>" } diff --git a/cron.php b/cron.php index 0c6b5fd..4f6a234 100644 --- a/cron.php +++ b/cron.php @@ -1,6 +1,4 @@ getSettings( 'apilo' ); if ( (int)($apilo_settings['enabled'] ?? 0) === 1 ) { $integrationsRepository -> apiloKeepalive( 300 ); $apilo_settings = $integrationsRepository -> getSettings( 'apilo' ); - Order::process_apilo_sync_queue( 10 ); + $orderRepo = new \Domain\Order\OrderRepository( $mdb ); + $orderAdminService = new \Domain\Order\OrderAdminService( $orderRepo ); + $orderAdminService->processApiloSyncQueue( 10 ); } function parsePaczkomatAddress($input) @@ -521,12 +521,10 @@ if ( $apilo_settings['enabled'] and $apilo_settings['sync_orders'] and $apilo_se { $shop_status_id = ( new \Domain\ShopStatus\ShopStatusRepository( $mdb ) )->getByIntegrationStatusId( 'apilo', (int)$responseData['status'] ); - $order_tmp = new Order( $order['id'] ); - if ( $shop_status_id ) - $order_tmp -> update_status( $shop_status_id, false ); + $orderAdminService->changeStatus( (int)$order['id'], $shop_status_id, false ); - $order_tmp -> update_aplio_order_status_date( date( 'Y-m-d H:i:s' ) ); + $orderRepo->updateApiloStatusDate( (int)$order['id'], date( 'Y-m-d H:i:s' ) ); echo '

Zaktualizowałem status zamówienia ' . $order['number'] . '

'; } } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 7c1b8e6..e8308a5 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,6 +4,35 @@ Logi zmian z migracji na Domain-Driven Architecture. Najnowsze na gorze. --- +## ver. 0.294 (2026-02-18) - Usuniecie autoload/shop/ — 12 legacy klas + +- **Faza 5.1: class.Order.php (~562 linii) USUNIETA** + - Logika Apilo sync przeniesiona do `OrderAdminService::processApiloSyncQueue()` + - Email zmiany statusu zintegrowany w `OrderAdminService::changeStatus()` + - `cron.php` przepiety na `OrderAdminService` + - `front\Controllers\ShopOrderController` — dodano `OrderAdminService` jako drugie DI + - `\shop\Order::order_statuses()` → `OrderRepository::orderStatuses()` (3 lokalizacje) +- **Faza 5.2: class.Product.php (~952 linii) USUNIETA — NAJWIEKSZA KLASA** + - ~20 metod przeniesionych do `ProductRepository`: `findCached()`, `isProductOnPromotion()`, `productSetsWhenAddToBasket()`, `addVisit()`, `getProductImg()`, `getProductUrl()`, `searchProductsByName()`, `searchProductByNameAjax()`, `searchProductsByNameCount()`, `isStock0Buy()`, `getProductPermutationQuantityOptions()`, `getProductIdByAttributes()`, `getProductPermutationHash()`, `getProductAttributes()`, `generateSkuCode()`, `productMeta()`, `generateSubtitleFromAttributes()`, `getDefaultCombinationPrices()`, `getProductDataBySelectedAttributes()`, `productCategories()`, `arrayCartesian()` + - `calculate_basket_product_price()` przeniesione do `BasketCalculator::calculateBasketProductPrice()` + - `BasketCalculator` przepisany: `summaryPrice()`, `checkProductQuantityInStock()` uzywaja `ProductRepository` + - XML generation (4 metody) — konwersja object→array access + - ~60+ callsite'ow zamienionych w kontrolerach, szablonach, repozytoriach i entry-pointach + - Cache key strings zaktualizowane z `\shop\Product::` na `ProductRepository::` +- **Cleanup:** + - Usunieto dead `\shop\` routing z `front\App::route()` + - Cache key `\shop\Promotion::get_active_promotions` → `PromotionRepository::getActivePromotions` + - Katalog `autoload/shop/` jest teraz pusty +- **Production hotfixes:** + - FIX: `findCached()` — stale Redis cache z obiektami `\shop\Product` powodowal ceny 0,00 zl (obiekty `__PHP_Incomplete_Class` po `(array)` cast maja zmanglowane klucze). Teraz invaliduje stary cache i re-fetchuje z DB + - FIX: `LayoutEngine.php` — 4 niekwalifikowane `Product::getFromCache()` resolwaly do `\front\Product` po usunieciu `use shop\Product` + - FIX: szablony (product-mini, product-search, product, product-warehouse-message, alert-product-sets, basket-details, main-view, promoted-products) — konwersja object access (`$product->field`) na array access (`$product['field']`) + - UPDATE: `AttributeRepository::getAttributeValueById()` — dodano Redis cache (zgodnosc ze starym `\shop\ProductAttribute::get_value_name()`) +- **PODSUMOWANIE CALEJ MIGRACJI:** 12 legacy klas, ~2363 linii kodu usunieto. Zero referencji `\shop\` w aktywnym kodzie. +- Testy: 610 OK, 1817 asercji + +--- + ## ver. 0.293 (2026-02-17) - front\controls\Site + front\view\Site → front\App + front\LayoutEngine - **front\controls\Site → front\App** — migracja routera na nowy namespace diff --git a/docs/LEGACY_SHOP_REFACTORING_PLAN.md b/docs/LEGACY_SHOP_REFACTORING_PLAN.md new file mode 100644 index 0000000..ae1b4a6 --- /dev/null +++ b/docs/LEGACY_SHOP_REFACTORING_PLAN.md @@ -0,0 +1,234 @@ +# Plan refaktoryzacji `autoload/shop/` — usunięcie legacy klas + +## Kontekst + +Katalog `autoload/shop/` zawierał **12 legacy klas** (~2 363 linii kodu), które pełniły rolę fasad/wrapperów nad warstwą `Domain\`. Wszystkie klasy zostały usunięte — logika przeniesiona do warstwy `Domain\` / `Shared\`, wywołania zaktualizowane. + +**Status: UKOŃCZONE** (wszystkie 12 klas usunięte, katalog `autoload/shop/` pusty) + +## Status + +| Faza | Klasy | Status | +|------|-------|--------| +| 1 | Transport, ProductSet, Coupon | ✅ Ukończona | +| 2 | Shop, Search, Basket | ✅ Ukończona | +| 3 | ProductCustomField, Category, ProductAttribute | ✅ Ukończona | +| 4 | Promotion | ✅ Ukończona | +| 5 | Order, Product | ✅ Ukończona | + +--- + +## Faza 1 — Trywialne fasady (0 logiki do migracji) + +### 1.1 `class.Transport.php` ✅ (~31 linii) +**Jedyne zewnętrzne użycie:** +- `admin\Controllers\ShopOrderController.php` → `\shop\Transport::transport_list()` + +**Plan:** +- Dodać metodę `allActiveOrdered()` do `Domain\Transport\TransportRepository` (odpowiednik `transport_list()`) +- Zamienić wywołanie w `ShopOrderController` +- Usunąć `class.Transport.php` + +### 1.2 `class.ProductSet.php` ✅ (~49 linii) +**Jedyne zewnętrzne użycie:** +- `admin\Controllers\ShopProductController.php:224` → `\shop\ProductSet::sets_list()` + +**Plan:** +- `ProductSetRepository::allSets()` już istnieje — zamienić wywołanie na `$this->productSetRepo->allSets()` +- Wstrzyknąć `ProductSetRepository` do `ShopProductController` (jeśli jeszcze nie jest) +- Usunąć `class.ProductSet.php` + +### 1.3 `class.Coupon.php` ✅ (~84 linii) +**Zewnętrzne użycia (3×):** +- `shop\class.Order.php:171` → `new \shop\Coupon($id)` (do przeniesienia w fazie Order) +- `front\Controllers\ShopOrderController.php:139` → `new \shop\Coupon($id)` +- `admin\Controllers\ShopOrderController.php:170` → `new \shop\Coupon($id)` + +**Plan:** +- `CouponRepository::findByName()` / `find()` już istnieje — dodać `findById(int $id): ?array` jeśli brak +- Zamienić `new \shop\Coupon($id)` → `$this->couponRepo->findById($id)` w obu kontrolerach +- Użycie w `class.Order.php` — rozwiąże się w fazie Order +- Usunąć `class.Coupon.php` + +--- + +## Faza 2 — Proste wrappery (minimalna logika do przeniesienia) + +### 2.1 `class.Shop.php` ✅ (~39 linii) — utility cenowe +**Zewnętrzne użycia (~24× w szablonach):** +- `templates/shop-product/product.php`, `product-mini.php` +- `templates/shop-search/product-search.php` +- `templates/shop-basket/alert-product-sets.php` +- `templates/controls/alert-product-sets.php` + +**Plan:** +- Przenieść `shortPrice()` i `isWholeNumber()` do `Shared\Helpers\Helpers` jako metody statyczne +- Zamienić `\shop\Shop::shortPrice()` → `\Shared\Helpers\Helpers::shortPrice()` we wszystkich szablonach (replace_all) +- Usunąć `class.Shop.php` + +### 2.2 `class.Search.php` ✅ (~70 linii) +**Jedyne zewnętrzne użycie:** +- `front\LayoutEngine.php:337` → `\shop\Search::simple_form()` + +**Plan:** +- Przenieść `simple_form()` do `front\Views\ShopSearch` (nowa klasa View, statyczna) +- Przenieść `search_results()` i `search_products()` do `front\Controllers\ShopSearchController` lub odpowiedniej istniejącej lokalizacji +- Zamienić wywołanie w `LayoutEngine` +- Usunąć `class.Search.php` +- Poprawić bug: `use shop\Produt;` (literówka — brakuje 'c') + +### 2.3 `class.Basket.php` ✅ (~92 linii) +**Zewnętrzne użycia (6× w `ShopBasketController`):** +- `validate_basket()` — prosta walidacja tablicy sesji +- `check_product_quantity_in_stock()` — sprawdzenie stanów magazynowych + +**Plan:** +- Przenieść obie metody do `Domain\Basket\BasketCalculator` (już istnieje) +- Zamienić wywołania w `ShopBasketController` +- Usunąć `class.Basket.php` + +--- + +## Faza 3 — Klasy z logiką cache/atrybutów + +### 3.1 `class.ProductCustomField.php` ✅ (~74 linii) +**Zewnętrzne użycia (2×):** +- `Domain\Order\OrderRepository.php:643` → `\shop\ProductCustomField::getFromCache()` +- `templates/shop-basket/_partials/product-custom-fields.php:3` → `\shop\ProductCustomField::getFromCache()` + +**Plan:** +- Dodać `findCached(int $id): ?array` do `Domain\Product\ProductRepository` (lub nowy `ProductCustomFieldRepository`) +- Zamienić wywołania w OrderRepository i szablonie +- Usunąć `class.ProductCustomField.php` + +### 3.2 `class.Category.php` ✅ (~84 linii) +**Zewnętrzne użycia (2×):** +- `index.php:225` → `\shop\Category::get_category_products_id()` (FB Pixel) +- `templates/shop-category/category.php:19` → `\shop\Category::get_subcategory_by_category()` + +**Plan:** +- `CategoryRepository::productsId()` prawdopodobnie istnieje — sprawdzić i użyć lub dodać +- Dodać `subcategoriesCached(int $categoryId): array` do `CategoryRepository` +- Zamienić wywołania +- Usunąć `class.Category.php` + +### 3.3 `class.ProductAttribute.php` ✅ (~119 linii) +**Zewnętrzne użycia (3 lokalizacje):** +- `shop\class.Product.php` — użycia wewnętrzne (rozwiążą się w fazie Product) +- `admin/templates/shop-product/product-combination.php:32` → `getAttributeName()`, `get_value_name()` +- `templates/shop-product/_partial/product-attribute.php:13` → `get_value_name()` + +**Plan:** +- `AttributeRepository::getAttributeNameById()` i `getAttributeValueById()` już istnieją +- Dodać brakujące metody do `AttributeRepository`: `isValueDefault()`, `getAttributeOrder()`, `getAttributeNameByValue()` (z Redis cache) +- Zamienić wywołania w szablonach +- Usunąć `class.ProductAttribute.php` + +--- + +## Faza 4 — Klasy z logiką promocji + +### 4.1 `class.Promotion.php` ✅ (~107 linii) +**Zewnętrzne użycia (3 lokalizacje):** +- `front\Controllers\ShopBasketController.php` (6×) → `\shop\Promotion::find_promotion()` +- `admin\Controllers\ShopPromotionController.php` → `::$condition_type`, `::$discount_type` + +**Plan:** +- Przenieść `get_active_promotions()` do `PromotionRepository` (z Redis cache) +- Przenieść `find_promotion()` do `PromotionRepository` — orkiestracja logiki applyType* +- Przenieść stałe `$condition_type` / `$discount_type` jako stałe klasowe do `PromotionRepository` +- Zamienić wywołania w `ShopBasketController` i `ShopPromotionController` +- Usunąć `class.Promotion.php` + +--- + +## Faza 5 — Duże klasy z wieloma zależnościami + +### 5.1 `class.Order.php` ✅ (~562 linii) +**Główna logika do migracji:** +- `OrderAdminService` ma już: `setOrderAsPaid()`, `setOrderAsUnpaid()`, `changeStatus()`, `resendConfirmationEmail()`, `saveOrderByAdmin()`, `deleteOrder()`, `saveNotes()` +- **Brakuje:** logika Apilo sync (`process_apilo_sync_queue()`, `sync_apilo_payment()`, `sync_apilo_status()`, queue management) +- **Brakuje:** `send_status_change_email()` (ale może być zintegrowane w `changeStatus()`) + +**Plan:** +1. Sprawdzić, czy `OrderAdminService` obsługuje już Apilo sync (metoda `sendOrderToApilo()` istnieje) +2. Przenieść `process_apilo_sync_queue()` + kolejkę Apilo do `Domain\Integrations\ApiloService` (lub do `IntegrationsRepository`) +3. Przenieść `send_status_change_email()` do `OrderAdminService` (jeśli nie jest częścią `changeStatus()`) +4. Usunąć referencję do `new \shop\Coupon()` — użyć `CouponRepository` (z fazy 1.3) +5. Zaktualizować `cron.php` (jeśli woła `process_apilo_sync_queue`) +6. Usunąć `class.Order.php` + +### 5.2 `class.Product.php` ✅ (~952 linii) — NAJWIĘKSZA KLASA +**Stan:** Większość logiki zmigowana do `ProductRepository`, ale klasa pełni rolę entity z ArrayAccess (używana w szablonach jako `$product['name']`). + +**Metody do przeniesienia:** +- `getFromCache()` — cache wrappera entity → przenieść do `ProductRepository::findCached()` +- `getDefaultCombinationPrices()` → `ProductRepository` +- `generateSubtitleFromAttributes()` → `ProductRepository` +- `getProductDataBySelectedAttributes()` → `ProductRepository` +- `is_product_on_promotion()` → `ProductRepository` +- `product_sets_when_add_to_basket()` → `ProductSetRepository` +- `add_visit()` → `ProductRepository` +- `getProductImg()` / `getProductUrl()` → `ProductRepository` +- `searchProductsByName()` / `searchProductByNameAjax()` / `searchProductsByNameCount()` → `ProductRepository` +- `is_stock_0_buy()` → `ProductRepository` +- `get_product_permutation_quantity_options()` → `ProductRepository` +- `calculate_basket_product_price()` → `Domain\Basket\BasketCalculator` +- `get_product_id_by_attributes()` / `get_product_permutation_hash()` / `get_product_attributes()` → `ProductRepository` lub `AttributeRepository` +- `array_cartesian()` / `permutations()` → `ProductRepository` (helper prywatny) +- `generate_sku_code()` → `ProductRepository` +- `product_meta()` → `ProductRepository` + +**Kluczowy problem:** szablony używają `$product['field']` przez ArrayAccess. Po migracji szablony będą dostawać zwykłe `array` z `ProductRepository` — to jest kompatybilne, bo `$product['field']` działa tak samo na tablicy. + +**Plan:** +1. Etapami przenosić metody statyczne do `ProductRepository` / `AttributeRepository` +2. Przenieść `calculate_basket_product_price()` do `BasketCalculator` +3. Zamienić `Product::getFromCache()` → `ProductRepository::findCached()` (zwraca tablicę) +4. Zaktualizować szablony i kontrolery +5. Usunąć `class.Product.php` + +--- + +## Pliki do modyfikacji (podsumowanie) + +| Plik | Zmiany | +|------|--------| +| `autoload/Domain/Transport/TransportRepository.php` | + `allActiveOrdered()` | +| `autoload/Domain/Basket/BasketCalculator.php` | + `validateBasket()`, `checkStock()`, `calculateProductPrice()` | +| `autoload/Domain/Promotion/PromotionRepository.php` | + `getActivePromotions()`, `findPromotion()`, stałe | +| `autoload/Domain/Product/ProductRepository.php` | + ~15 metod z class.Product.php | +| `autoload/Domain/Attribute/AttributeRepository.php` | + `isValueDefault()`, `getAttributeOrder()`, `getAttributeNameByValue()` | +| `autoload/Domain/Category/CategoryRepository.php` | + `subcategoriesCached()`, `categoryProductIds()` | +| `autoload/Domain/Order/OrderAdminService.php` | + email statusu, sprawdzenie Apilo | +| `autoload/Domain/Integrations/` | + `ApiloSyncService` (queue Apilo) | +| `autoload/Domain/Coupon/CouponRepository.php` | + `findById()` | +| `autoload/Shared/Helpers/Helpers.php` | + `shortPrice()`, `isWholeNumber()` | +| `autoload/front/Views/ShopSearch.php` | NOWY — `simple_form()` | +| `autoload/front/LayoutEngine.php` | zamiana `\shop\Search` → `\front\Views\ShopSearch` | +| `autoload/front/Controllers/ShopBasketController.php` | zamiana `\shop\Basket`, `\shop\Promotion` | +| `autoload/admin/Controllers/ShopOrderController.php` | zamiana `\shop\Coupon`, `\shop\Transport` | +| `autoload/admin/Controllers/ShopProductController.php` | zamiana `\shop\ProductSet` | +| `autoload/admin/Controllers/ShopPromotionController.php` | zamiana `::$condition_type`, `::$discount_type` | +| `autoload/front/Controllers/ShopOrderController.php` | zamiana `\shop\Coupon` | +| Szablony (`templates/`) | zamiana `\shop\Shop::shortPrice()`, `\shop\ProductAttribute::*`, `\shop\Category::*` | +| `index.php` | zamiana `\shop\Category::get_category_products_id()` | +| `autoload/Domain/Order/OrderRepository.php` | zamiana `\shop\ProductCustomField` | + +## Weryfikacja + +Po każdej fazie: +1. Uruchomić `./test.ps1` — wszystkie 610+ testów muszą przechodzić +2. Grep po `\shop\` w całym codebase — upewnić się, że usunięta klasa nie jest nigdzie używana +3. Po zakończeniu wszystkich faz: `grep -r "\\\\shop\\\\" autoload/ templates/ admin/ index.php ajax.php cron.php api.php` powinien zwracać 0 wyników + +## Szacunek złożoności + +| Faza | Klasy | Linii do usunięcia | Złożoność | +|------|-------|---------------------|-----------| +| 1 | Transport, ProductSet, Coupon | ~164 | Niska | +| 2 | Shop, Search, Basket | ~201 | Niska-średnia | +| 3 | ProductCustomField, Category, ProductAttribute | ~277 | Średnia | +| 4 | Promotion | ~107 | Średnia | +| 5 | Order, Product | ~1 514 | Wysoka | +| **Razem** | **12 klas** | **~2 363** | | diff --git a/docs/PROJECT_STRUCTURE.md b/docs/PROJECT_STRUCTURE.md index 851189f..d4c69bd 100644 --- a/docs/PROJECT_STRUCTURE.md +++ b/docs/PROJECT_STRUCTURE.md @@ -38,23 +38,23 @@ Dokumentacja struktury projektu shopPRO do szybkiego odniesienia. ``` shop\product:{product_id}:{lang_id}:{permutation_hash} ``` -- Przechowuje zserializowany obiekt produktu +- Przechowuje tablicę danych produktu (z kombinacjami, obrazkami, producentem itd.) - TTL: 24 godziny (86400 sekund) -- Klasa: `shop\Product::getFromCache()` - `autoload/shop/class.Product.php:121` +- Klasa: `Domain\Product\ProductRepository::findCached()` - `autoload/Domain/Product/ProductRepository.php` #### Opcje ilościowe produktu ``` -\shop\Product::get_product_permutation_quantity_options:{product_id}:{permutation} +ProductRepository::getProductPermutationQuantityOptions:v2:{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` +- Klasa: `Domain\Product\ProductRepository::getProductPermutationQuantityOptions()` - `autoload/Domain/Product/ProductRepository.php` #### Zestawy produktów ``` -\shop\Product::product_sets_when_add_to_basket:{product_id} +ProductRepository::productSetsWhenAddToBasket:{product_id} ``` - Przechowuje produkty często kupowane razem -- Klasa: `shop\Product::product_sets_when_add_to_basket()` - `autoload/shop/class.Product.php:316` +- Klasa: `Domain\Product\ProductRepository::productSetsWhenAddToBasket()` - `autoload/Domain/Product/ProductRepository.php` ## Integracje z systemami zewnętrznymi (CRON) @@ -179,12 +179,12 @@ Główna klasa helper (przeniesiona z `class.S.php`) z metodami: - ~~`\front\factory\`~~ - USUNIĘTY — wszystkie fabryki zmigrowane do Domain - ~~`\front\controls\`~~ - USUNIĘTY — router przeniesiony do `\front\App` - ~~`\front\view\`~~ - USUNIĘTY — layout engine przeniesiony do `\front\LayoutEngine` -- `\shop\` - klasy sklepu (Product, Order, itp.) +- ~~`\shop\`~~ - USUNIĘTY — wszystkie klasy zmigrowane do `\Domain\` ### Cachowanie produktów ```php // Pobranie produktu z cache -$product = \shop\Product::getFromCache($product_id, $lang_id, $permutation_hash); +$product = (new \Domain\Product\ProductRepository($mdb))->findCached($product_id, $lang_id, $permutation_hash); // Czyszczenie cache produktu \Shared\Helpers\Helpers::clear_product_cache($product_id); @@ -264,9 +264,9 @@ autoload/ - `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`. +- `OrderAdminService` zapisuje nieudane syncy Apilo (status/platnosc) do kolejki `temp/apilo-sync-queue.json`. +- `cron.php` automatycznie ponawia zalegle syncy (`OrderAdminService::processApiloSyncQueue()`). +- `OrderAdminService::setOrderAsPaid()` wysyla mapowany typ platnosci Apilo (z mapowania metody platnosci), bez stalej wartosci `type`. **Aktualizacja 2026-02-15 (ver. 0.276):** - Dodano modul domenowy `Domain/Order/OrderRepository.php`. diff --git a/docs/TESTING.md b/docs/TESTING.md index b54bf96..06a2e2a 100644 --- a/docs/TESTING.md +++ b/docs/TESTING.md @@ -33,10 +33,17 @@ Alternatywnie (Git Bash): ## Aktualny stan suite -Ostatnio zweryfikowano: 2026-02-17 +Ostatnio zweryfikowano: 2026-02-18 ```text -OK (610 tests, 1816 assertions) +OK (610 tests, 1817 assertions) +``` + +Aktualizacja po usunieciu autoload/shop/ — 12 legacy klas (2026-02-18, ver. 0.294): +```text +Pelny suite: OK (610 tests, 1817 assertions) +Zmodyfikowane testy: PromotionRepositoryTest (cache key \shop\Promotion → PromotionRepository) +Zmodyfikowane testy: ShopOrderControllerTest (zamiana \shop\Coupon → CouponRepository DI) ``` Aktualizacja po migracji front\controls\Site + front\view\Site (2026-02-17, ver. 0.293): diff --git a/docs/UPDATE_INSTRUCTIONS.md b/docs/UPDATE_INSTRUCTIONS.md index 7fb5012..bcdbffc 100644 --- a/docs/UPDATE_INSTRUCTIONS.md +++ b/docs/UPDATE_INSTRUCTIONS.md @@ -18,16 +18,16 @@ Aktualizacje znajdują się w folderze `updates/0.XX/` gdzie XX oznacza dziesią ## Procedura tworzenia nowej aktualizacji -## Status biezacej aktualizacji (ver. 0.293) +## Status biezacej aktualizacji (ver. 0.294) -- Wersja udostepniona: `0.293` (data: 2026-02-17). +- Wersja udostepniona: `0.294` (data: 2026-02-18). - Pliki publikacyjne: - - `temp/update_build/ver_0.293.zip`, `ver_0.293_files.txt` + - `temp/update_build/ver_0.294.zip`, `ver_0.294_files.txt` - Pliki metadanych aktualizacji: - - `updates/changelog.php` (dodany wpis `ver. 0.293`) - - `updates/versions.php` (`$current_ver = 293`) + - `updates/changelog.php` (dodany wpis `ver. 0.294`) + - `updates/versions.php` (`$current_ver = 294`) - Weryfikacja testow przed publikacja: - - `OK (610 tests, 1816 assertions)` + - `OK (610 tests, 1817 assertions)` ### 1. Określ numer wersji Sprawdź ostatnią wersję w `updates/` i zwiększ o 1. diff --git a/index.php b/index.php index 543f3fc..8888eae 100644 --- a/index.php +++ b/index.php @@ -222,7 +222,7 @@ if ( $settings[ 'piksel' ] ) "ViewCategory", { content_category: "kategoria", content_name: "' . htmlspecialchars( str_replace( '"', '', ( new \Domain\Category\CategoryRepository( $GLOBALS['mdb'] ) )->categoryName( (int)$category['id'], $lang_id ) ) ) . '", - content_ids: ["' . implode( ',', \shop\Category::get_category_products_id( $category['id'] ) ) . '"], + content_ids: ["' . implode( ',', ( new \Domain\Category\CategoryRepository( $GLOBALS['mdb'] ) )->getCategoryProductIds( (int)$category['id'] ) ) . '"], content_type: "product" });'; diff --git a/templates/controls/alert-product-sets.php b/templates/controls/alert-product-sets.php index cb698b2..1052623 100644 --- a/templates/controls/alert-product-sets.php +++ b/templates/controls/alert-product-sets.php @@ -3,29 +3,30 @@

Dobierz inne produkty do kompletu

+ products ) ): foreach ( $this -> products as $product_id ): - $product = new \shop\Product( $product_id ); + $product = $productRepo->findCached( $product_id, $lang_id ); - $product -> language['seo_link'] ? $url = '/' . $product -> language['seo_link'] : $url = '/p-' . $product['id'] . '-' . \Shared\Helpers\Helpers::seo( $product -> language['name'] ); + $product['language']['seo_link'] ? $url = '/' . $product['language']['seo_link'] : $url = '/p-' . $product['id'] . '-' . \Shared\Helpers\Helpers::seo( $product['language']['name'] ); if ( \Shared\Helpers\Helpers::get_session( 'current-lang' ) != ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->defaultLanguage() and $url != '#' ) $url = '/' . \Shared\Helpers\Helpers::get_session( 'current-lang' ) . $url; ?>
- new_to_date and $product -> new_to_date >= date( 'Y-m-d' ) ):?> + = date( 'Y-m-d' ) ):?>
- images[0]['src'], 1 ) ) ):?> + class == 'item' ):?> - <?= $product -> images[0]['alt'];?> + <?= $product['images'][0]['alt'];?> - <?= $product -> images[0]['alt'];?> - images[1]['src'], 1 ) ) ):?> - <?= $product -> images[1]['alt'];?> + <?= $product['images'][0]['alt'];?> + + <?= $product['images'][1]['alt'];?> - <?= $product -> images[0]['alt'];?> + <?= $product['images'][0]['alt'];?> @@ -36,20 +37,20 @@

- language['name'];?> +

- price_brutto_promo ):?> +
- price_brutto );?> +
- price_brutto_promo );?> +
- price_brutto );?> +
diff --git a/templates/shop-basket/_partials/product-custom-fields.php b/templates/shop-basket/_partials/product-custom-fields.php index 0ecc77b..a9134c7 100644 --- a/templates/shop-basket/_partials/product-custom-fields.php +++ b/templates/shop-basket/_partials/product-custom-fields.php @@ -1,6 +1,6 @@ custom_fields ) : ?> custom_fields as $key => $val ) : ?> - + findCustomFieldCached( $key ); ?>
diff --git a/templates/shop-basket/alert-product-sets.php b/templates/shop-basket/alert-product-sets.php index a193df4..19eddde 100644 --- a/templates/shop-basket/alert-product-sets.php +++ b/templates/shop-basket/alert-product-sets.php @@ -3,29 +3,30 @@

Dobierz inne produkty do kompletu

+ products ) ): foreach ( $this -> products as $product_id ): - $product = \shop\Product::getFromCache( $product_id, $lang_id ); + $product = $productRepo->findCached( $product_id, $lang_id ); - $product -> language['seo_link'] ? $url = '/' . $product -> language['seo_link'] : $url = '/p-' . $product['id'] . '-' . \Shared\Helpers\Helpers::seo( $product -> language['name'] ); + $product['language']['seo_link'] ? $url = '/' . $product['language']['seo_link'] : $url = '/p-' . $product['id'] . '-' . \Shared\Helpers\Helpers::seo( $product['language']['name'] ); if ( \Shared\Helpers\Helpers::get_session( 'current-lang' ) != ( new \Domain\Languages\LanguagesRepository( $GLOBALS['mdb'] ) )->defaultLanguage() and $url != '#' ) $url = '/' . \Shared\Helpers\Helpers::get_session( 'current-lang' ) . $url; ?>
- new_to_date and $product -> new_to_date >= date( 'Y-m-d' ) ):?> + = date( 'Y-m-d' ) ):?>
- images[0]['src'], 1 ) ) ):?> + class == 'item' ):?> - <?= $product -> images[0]['alt'];?> + <?= $product['images'][0]['alt'];?> - <?= $product -> images[0]['alt'];?> - images[1]['src'], 1 ) ) ):?> - <?= $product -> images[1]['alt'];?> + <?= $product['images'][0]['alt'];?> + + <?= $product['images'][1]['alt'];?> - <?= $product -> images[0]['alt'];?> + <?= $product['images'][0]['alt'];?> @@ -36,39 +37,39 @@

- language['name'];?> +

getDefaultCombinationPrices(); + $prices = $productRepo->getDefaultCombinationPrices( $product ); if ( $prices ) { if ( $prices['price_brutto_promo'] ):?>
- +
- +
- +
price_brutto_promo ):?> + if ( $product['price_brutto_promo'] ):?>
- price_brutto );?> +
- price_brutto_promo );?> +
- price_brutto );?> +
diff --git a/templates/shop-basket/basket-details.php b/templates/shop-basket/basket-details.php index a2acebe..8708c92 100644 --- a/templates/shop-basket/basket-details.php +++ b/templates/shop-basket/basket-details.php @@ -6,16 +6,16 @@ lang_id ); + $product = (new \Domain\Product\ProductRepository($GLOBALS['mdb']))->findCached( (int)$position['product-id'], $this -> lang_id ); $permutation = null; if ( isset( $position['parent_id'] ) and (int)$position['parent_id'] and isset( $position['product-id'] ) ) - $permutation = \shop\Product::get_product_permutation_hash( (int)$position['product-id'] ); + $permutation = (new \Domain\Product\ProductRepository($GLOBALS['mdb']))->getProductPermutationHash( (int)$position['product-id'] ); if ( !$permutation and isset( $position['attributes'] ) and is_array( $position['attributes'] ) and count( $position['attributes'] ) ) $permutation = implode( '|', $position['attributes'] ); - $quantity_options = \shop\Product::get_product_permutation_quantity_options( + $quantity_options = (new \Domain\Product\ProductRepository($GLOBALS['mdb']))->getProductPermutationQuantityOptions( (int)( $position['parent_id'] ? $position['parent_id'] : $position['product-id'] ), $permutation ); @@ -63,7 +63,7 @@ $position['custom_fields'] ] ); ?> - additional_message ):?> +
@@ -72,7 +72,7 @@
coupon, $position ); + $price_product = \Domain\Basket\BasketCalculator::calculateBasketProductPrice( (float)$product['price_brutto_promo'], (float)$product['price_brutto'], $this -> coupon, $position ); if ( $price_product['price_new'] ) echo \Shared\Helpers\Helpers::decimal( $price_product['price_new'] ) . ' zł'; diff --git a/templates/shop-basket/summary-view.php b/templates/shop-basket/summary-view.php index 9b80483..61305a8 100644 --- a/templates/shop-basket/summary-view.php +++ b/templates/shop-basket/summary-view.php @@ -4,7 +4,7 @@
basket ) and count( $this -> basket ) ):?> basket as $position_hash => $position ):?> - lang_id );?> + findCached( $position[ 'product-id' ], $this -> lang_id );?>
@@ -51,7 +51,7 @@
coupon, $position); + $price_product = \Domain\Basket\BasketCalculator::calculateBasketProductPrice((float)$product['price_brutto_promo'], (float)$product['price_brutto'], $this -> coupon, $position); if ($price_product['price_new']) echo \Shared\Helpers\Helpers::decimal($price_product['price_new']) . ' zł'; diff --git a/templates/shop-category/blog-category-products.php b/templates/shop-category/blog-category-products.php index 7ca0a9d..72dfd2d 100644 --- a/templates/shop-category/blog-category-products.php +++ b/templates/shop-category/blog-category-products.php @@ -2,7 +2,7 @@
products ) ): foreach ( $this -> products as $product_id ):?> \shop\Product::getFromCache( $product_id, $lang_id ), + 'product' => (new \Domain\Product\ProductRepository($GLOBALS['mdb']))->findCached( $product_id, $lang_id ), 'class' => 'col-12 col-sm-6 col-lg-3' ] );?> diff --git a/templates/shop-category/category-infinitescroll.php b/templates/shop-category/category-infinitescroll.php index 1352b16..4f74caf 100644 --- a/templates/shop-category/category-infinitescroll.php +++ b/templates/shop-category/category-infinitescroll.php @@ -14,7 +14,7 @@ endif;
products)) : foreach ($this->products as $product_id) : echo \Shared\Tpl\Tpl::view('shop-product/product-mini', [ - 'product' => \shop\Product::getFromCache( $product_id, $lang_id ) + 'product' => (new \Domain\Product\ProductRepository($GLOBALS['mdb']))->findCached( $product_id, $lang_id ) ]); endforeach; endif; ?> diff --git a/templates/shop-category/category.php b/templates/shop-category/category.php index cac1631..24412a9 100644 --- a/templates/shop-category/category.php +++ b/templates/shop-category/category.php @@ -16,7 +16,7 @@ endif; if ( $this -> category['view_subcategories'] ): echo \Shared\Tpl\Tpl::view( 'shop-category/_partials/category-subcategory-tiles', [ - 'categories' => \shop\Category::get_subcategory_by_category( $this -> category['id'] ), + 'categories' => ( new \Domain\Category\CategoryRepository( $GLOBALS['mdb'] ) )->subcategoriesLangCached( (int)$this -> category['id'] ), ] ); endif; @@ -25,7 +25,7 @@ endif;
products ) ): foreach ( $this -> products as $product_id ): echo \Shared\Tpl\Tpl::view( 'shop-product/product-mini', [ - 'product' => \shop\Product::getFromCache( $product_id, $lang_id ) + 'product' => (new \Domain\Product\ProductRepository($GLOBALS['mdb']))->findCached( $product_id, $lang_id ) ] ); endforeach; endif;?>
diff --git a/templates/shop-category/products.php b/templates/shop-category/products.php index 75ae448..18f44e8 100644 --- a/templates/shop-category/products.php +++ b/templates/shop-category/products.php @@ -5,10 +5,10 @@
+ products ) ): foreach ( $this -> products as $product_id ): - $product = new \shop\Product( $product_id ); echo \Shared\Tpl\Tpl::view( 'shop-product/product-mini', [ - 'product' => new \shop\Product( $product_id, $lang_id ) + 'product' => $productRepo->findCached( $product_id, $lang_id ) ] ); endforeach; endif;?>
\ No newline at end of file diff --git a/templates/shop-client/client-orders.php b/templates/shop-client/client-orders.php index 4f3a1c7..cb9bfed 100644 --- a/templates/shop-client/client-orders.php +++ b/templates/shop-client/client-orders.php @@ -9,7 +9,7 @@ orders ) ): foreach ( $this -> orders as $order ):?> $order, - 'statuses' => \shop\Order::order_statuses() + 'statuses' => ( new \Domain\Order\OrderRepository( $GLOBALS['mdb'] ) )->orderStatuses() ] );?>
\ No newline at end of file diff --git a/templates/shop-order/mail-summary.php b/templates/shop-order/mail-summary.php index 6f24164..52b28f1 100644 --- a/templates/shop-order/mail-summary.php +++ b/templates/shop-order/mail-summary.php @@ -64,7 +64,7 @@ echo $this -> settings['newsletter_header']; order['products'] ) ): foreach ( $this -> order['products'] as $position ):?> - + findCached( $position['product_id'], $lang_id );?> $this -> order, 'coupon' => $this -> coupon, - 'statuses' => \shop\Order::order_statuses() + 'statuses' => ( new \Domain\Order\OrderRepository( $GLOBALS['mdb'] ) )->orderStatuses() ] );?> order['status'] == 0 or $this -> order['status'] == 2 ):?>
diff --git a/templates/shop-order/order-simple.php b/templates/shop-order/order-simple.php index a5e06b9..6145c9e 100644 --- a/templates/shop-order/order-simple.php +++ b/templates/shop-order/order-simple.php @@ -39,7 +39,7 @@
order['products'] ) and count( $this -> order['products'] ) ):?> order['products'] as $product ):?> - + findCached( $product['product_id'], $lang['id'] );?>
@@ -90,7 +90,7 @@ coupon ):?>
- Kod rabatowy: coupon -> name;?> - coupon -> amount;?> coupon -> type == 1 ? '%' : 'zł';?> + Kod rabatowy: coupon['name'];?> - coupon['amount'];?> coupon['type'] == 1 ? '%' : 'zł';?>
diff --git a/templates/shop-producer/products.php b/templates/shop-producer/products.php index 8e5e871..1437bb0 100644 --- a/templates/shop-producer/products.php +++ b/templates/shop-producer/products.php @@ -2,7 +2,7 @@
products ) ): foreach ( $this -> products as $product_id ): echo \Shared\Tpl\Tpl::view( 'shop-product/product-mini', [ - 'product' => $product = \shop\Product::getFromCache( $product_id, $lang_id ) + 'product' => $product = (new \Domain\Product\ProductRepository($GLOBALS['mdb']))->findCached( $product_id, $lang_id ) ] ); endforeach; endif;?>
diff --git a/templates/shop-product/_partial/product-attribute.php b/templates/shop-product/_partial/product-attribute.php index c42ca62..0c2db97 100644 --- a/templates/shop-product/_partial/product-attribute.php +++ b/templates/shop-product/_partial/product-attribute.php @@ -10,7 +10,7 @@ if ( $attribute_details['type'] == 0 ) attribute['values'] as $value ):?>
checked="checked" require="true" value=" attribute['id'];?>-" name=""> - +
@@ -132,7 +132,7 @@ ]);?>
- product -> language['short_description'];?> + product['language']['short_description'];?>
@@ -142,20 +142,20 @@
    - product -> language['tab_name_1'] ):?> - + product['language']['tab_name_1'] ):?> +
- product -> language['description'];?> + product['language']['description'];?>
- product -> language['tab_description_1'] ):?> + product['language']['tab_description_1'] ):?>
-
product -> language['tab_name_1'];?>
- product -> language['tab_description_1'];?> +
product['language']['tab_name_1'];?>
+ product['language']['tab_description_1'];?>
@@ -202,11 +202,11 @@
- product -> language['tab_name_2'] ):?> + product['language']['tab_name_2'] ):?>
-
product -> language['tab_name_2'];?>
+
product['language']['tab_name_2'];?>
- product -> language['tab_description_2'];?> + product['language']['tab_description_2'];?>
@@ -221,8 +221,8 @@