From f588a792815c9223ad4425955cd2e9595d7cfc05 Mon Sep 17 00:00:00 2001 From: Serhii Korneliuk Date: Fri, 13 Dec 2024 10:32:34 +0200 Subject: [PATCH] [ADD] sCart. --- lang/en/global.php | 2 + lang/ru/global.php | 2 + lang/uk/global.php | 2 + src/Http/routes.php | 8 +- src/Models/sProduct.php | 30 ++-- src/sCart.php | 213 ++++++++++++++++++-------- src/sCommerce.php | 82 +++++++++- src/sCommerceServiceProvider.php | 23 ++- views/partials/scripts/cart.blade.php | 43 ++++++ 9 files changed, 309 insertions(+), 96 deletions(-) create mode 100644 views/partials/scripts/cart.blade.php diff --git a/lang/en/global.php b/lang/en/global.php index ecdfce1..2a88576 100644 --- a/lang/en/global.php +++ b/lang/en/global.php @@ -131,6 +131,8 @@ "product_link_rule_help" => "Choose a rule for forming a link to the product.", "product_link_rule_root" => "from the root of the site", "product_name" => "Product Name", + "product_title_is_out_of_stock" => "Product :title is out of stock.", + "product_with_id_not_found" => "Product with ID :id not found.", "products" => "Products", "products_help" => "List of all store products.", "products_icon" => "fa fa-store-alt", diff --git a/lang/ru/global.php b/lang/ru/global.php index 2d26f2f..5a6e9c8 100644 --- a/lang/ru/global.php +++ b/lang/ru/global.php @@ -131,6 +131,8 @@ "product_link_rule_help" => "Выберите правило для формирования ссылки на товар.", "product_link_rule_root" => "от корня сайта", "product_name" => "Название товара", + "product_title_is_out_of_stock" => "Продукт :title недостаточное количество на складе.", + "product_with_id_not_found" => "Товар с ID :id не найден.", "products" => "Товари", "products_help" => "Перечень всех товаров магазина.", "products_icon" => "fa fa-store-alt", diff --git a/lang/uk/global.php b/lang/uk/global.php index 26c531c..bc25476 100644 --- a/lang/uk/global.php +++ b/lang/uk/global.php @@ -131,6 +131,8 @@ "product_link_rule_help" => "Оберіть правило для формування посилання на товар.", "product_link_rule_root" => "від корня сайту", "product_name" => "Назва товару", + "product_title_is_out_of_stock" => "Продукт :title недостатня кількість на складі.", + "product_with_id_not_found" => "Товар із ID :id не знайдено.", "products" => "Товари", "products_help" => "Перелік всіх товарів магазину.", "products_icon" => "fa fa-store-alt", diff --git a/src/Http/routes.php b/src/Http/routes.php index 9843702..d0a03cd 100644 --- a/src/Http/routes.php +++ b/src/Http/routes.php @@ -1,8 +1,8 @@ prefix('scommerce/')->name('sCommerce.')->group(function () { - Route::get('/', [sCommerce::class, 'index'])->name('index'); -});*/ +Route::middleware('web')->prefix('scommerce/')->name('sCommerce.')->group(function () { + Route::post('add-to-cart', fn() => tap(sCart::addProduct(), fn($result) => response()->json($result, $result['success'] === true ? 200 : 404)))->name('addToCart'); +}); diff --git a/src/Models/sProduct.php b/src/Models/sProduct.php index 61ade91..d7368f7 100644 --- a/src/Models/sProduct.php +++ b/src/Models/sProduct.php @@ -33,7 +33,23 @@ class sProduct extends Model * * @var array */ - protected $appends = ['title', 'category', 'link', 'coverSrc', 'price', 'specialPrice']; + protected $appends = [ + 'title', + 'category', + 'link', + 'coverSrc', + 'price', + 'specialPrice' + ]; + + /** + * Get the attributes that should be cast. + * + * @var array + */ + protected $casts = [ + 'quantity' => 'integer', + ]; /** * Availability constants @@ -429,11 +445,7 @@ public function getCoverSrcAttribute(): string */ public function getPriceAttribute(): string { - if (!isset($_SESSION['currency'])) { - $_SESSION['currency'] = sCommerce::config('basic.main_currency', 'USD'); - } - - return $this->priceTo($_SESSION['currency']); + return $this->priceTo(sCommerce::currentCurrency()); } /** @@ -469,11 +481,7 @@ public function priceToNumber($currency): float */ public function getSpecialPriceAttribute(): string { - if (!isset($_SESSION['currency'])) { - $_SESSION['currency'] = sCommerce::config('basic.main_currency', 'USD'); - } - - return $this->specialPriceTo($_SESSION['currency']); + return $this->specialPriceTo(sCommerce::currentCurrency()); } /** diff --git a/src/sCart.php b/src/sCart.php index 614c9e4..a027d07 100644 --- a/src/sCart.php +++ b/src/sCart.php @@ -1,6 +1,8 @@ cartData = $this->loadCartData(); } - /** - * Load cart data from the session or database. - * - * @return array The cart data. - */ - protected function loadCartData(): array - { - // Load cart data from the session or database - // Implementation needed based on your storage method - return session('cart', []); - } - /** * Get a list of items in the cart with their IDs and quantities. * @@ -34,11 +24,14 @@ public function getItems(): array { $items = []; - foreach ($this->cartData as $productId => $quantity) { - $items[] = [ - 'product_id' => $productId, - 'quantity' => $quantity - ]; + foreach ($this->cartData as $productId => $optionId) { + foreach ($optionId as $quantity) { + $items[] = [ + 'productId' => $productId, + //'optionId' => $optionId, + 'quantity' => $quantity + ]; + } } return $items; @@ -57,77 +50,81 @@ public function getDetailedItems(): array { $items = []; - foreach ($this->cartData as $productId => $attributes) { - // Retrieve product details from the database + foreach ($this->cartData as $productId => $optionId) { $product = sProduct::find($productId); - // If product is not found, skip this item if (!$product) { continue; } - // Get attributes for the product in the cart - $productAttributes = $this->getProductAttributes($productId, $attributes); - $items[] = [ 'product_id' => $productId, 'product' => $product, - 'quantity' => $attributes['quantity'] ?? 1, // Default to 1 if quantity is not set - 'attributes' => $productAttributes + 'quantity' => $attributes['quantity'] ?? 1, ]; } return $items; } - /** - * Retrieve attributes for a given product ID from the cart data. - * - * @param int $productId The ID of the product. - * @param array $attributes The attributes associated with the product in the cart. - * @return array An array of attributes and their values for the product. - */ - private function getProductAttributes(int $productId, array $attributes): array - { - $productAttributes = []; - - foreach ($attributes['attributes'] ?? [] as $attributeId => $value) { - $attribute = sAttribute::find($attributeId); - - // Add attribute to the array if it exists - if ($attribute) { - $productAttributes[] = [ - 'attribute_id' => $attributeId, - 'attribute' => $attribute, - 'value' => $value - ]; - } - } - - return $productAttributes; - } - /** * Add a product to the cart. * * @param int $productId The ID of the product to add. - * @param array $attributes The attributes associated with the product. + * @param int $optionId The ID option associated with the product. * @param int $quantity The quantity of the product to add. * @return void */ - public function addProduct(int $productId, array $attributes = [], int $quantity = 1): void + public function addProduct(int $productId = 0, int $optionId = 0, int $quantity = 1): array { - if (!isset($this->cartData[$productId])) { - $this->cartData[$productId] = [ - 'quantity' => 0, - 'attributes' => [] + if ($productId === 0) { + $productId = request()->integer('productId'); + $optionId = request()->integer('optionId'); + $quantity = max(request()->integer('quantity'), 1); + } + + $product = $this->getProductDetails($productId); + + if (!$product) { + Log::warning('sCart - ' . __('sCommerce::global.product_with_id_not_found', ['id' => $productId])); + + return [ + 'success' => false, + 'message' => __('sCommerce::global.product_with_id_not_found', ['id' => $productId]), ]; } - $this->cartData[$productId]['quantity'] += $quantity; - $this->cartData[$productId]['attributes'] = array_merge($this->cartData[$productId]['attributes'], $attributes); + if (!isset($this->cartData[$productId][$optionId])) { + $this->cartData[$productId][$optionId] = 0; + } + if (sCommerce::config('product.quantity_on', 0)) { + if ($product->quantity < 0) { + Log::alert('sCart - ' . __('sCommerce::global.product_title_is_out_of_stock', ['title' => $product->title])); + + return [ + 'success' => false, + 'message' => __('sCommerce::global.product_title_is_out_of_stock', ['title' => $product->title]), + ]; + } else { + $quantity = min($quantity, $product->quantity); + } + } + + $this->cartData[$productId][$optionId] = ($quantity == 1 ? 1 : $quantity); $this->saveCartData(); + + $totalCartSum = $this->getTotalCartSum(); + + return [ + 'success' => true, + 'message' => "Product with ID {$productId} added to Cart.", + 'productId' => $productId, + 'product' => $this->getProductFields($product), + 'quantity' => $this->cartData[$productId][$optionId], + 'totalCartSum' => $totalCartSum, + 'totalCartSumFormatted' => sCommerce::convertPrice($totalCartSum), + ]; } /** @@ -158,21 +155,38 @@ public function updateQuantity(int $productId, int $quantity): void } /** - * Get the total number of items in the cart. + * Get the total sum of items in the cart in current Currency. * - * @return int The total number of items in the cart. + * @return float The total sum of items in the cart. */ - public function getTotalItems(): int + public function getTotalCartSum(): float { $total = 0; + $productIds = array_keys($this->cartData); + $products = sCommerce::getProducts($productIds); - foreach ($this->cartData as $item) { - $total += $item['quantity']; + foreach ($products as $product) { + foreach ($this->cartData[$product->id] as $optionId => $quantity) { + $price = $product->price_special > 0 ? $product->price_special : $product->price_regular; + $price = sCommerce::convertPriceNumber($price, $product->currency, sCommerce::currentCurrency()); + $total += $price * $quantity; + } } return $total; } + /** + * Load cart data from the session or database. + * + * @return array The cart data. + */ + protected function loadCartData(): array + { + //return session('sCart', []); + return $_SESSION['sCart'] ?? []; + } + /** * Save the cart data to the session or database. * @@ -182,6 +196,73 @@ protected function saveCartData(): void { // Save cart data to the session or database // Implementation needed based on your storage method - session(['cart' => $this->cartData]); + //session(['sCart' => $this->cartData]); + $_SESSION['sCart'] = $this->cartData; + } + + /** + * Get details of a product by its ID. + * + * This method retrieves detailed information about a product from the database. + * + * @param int $productId The ID of the product. + * @throws \UnexpectedValueException If the returned object is not a product. + */ + private function getProductDetails(int $productId): ?sProduct + { + $fields = ['id', 'title', 'link', 'coverSrc', 'category', 'sku', 'quantity', 'price', 'specialPrice']; + $product = sCommerce::getProduct($productId); + return $product && $product->id ? $product : null; + } + + /** + * Get details of a product by its ID with selected fields. + * + * This method retrieves detailed information about a product from the database. + * + * @param object $product The product. + * @throws \UnexpectedValueException If the returned object is not a product. + */ + private function getProductFields(object $product): ?array + { + $fields = [ + 'id', + 'title', + 'link', + 'coverSrc', + 'category', + 'sku', + 'quantity', + 'price', + 'specialPrice' + ]; + return $product->only($fields); + } + + /** + * Retrieve attributes for a given product ID from the cart data. + * + * @param int $productId The ID of the product. + * @param array $attributes The attributes associated with the product in the cart. + * @return array An array of attributes and their values for the product. + */ + private function getProductAttributes(int $productId, array $attributes): array + { + $productAttributes = []; + + foreach ($attributes['attributes'] ?? [] as $attributeId => $value) { + $attribute = sAttribute::find($attributeId); + + // Add attribute to the array if it exists + if ($attribute) { + $productAttributes[] = [ + 'attribute_id' => $attributeId, + 'attribute' => $attribute, + 'value' => $value + ]; + } + } + + return $productAttributes; } } diff --git a/src/sCommerce.php b/src/sCommerce.php index 169e565..6b5bccd 100644 --- a/src/sCommerce.php +++ b/src/sCommerce.php @@ -4,6 +4,7 @@ use EvolutionCMS\Models\SiteContent; use Illuminate\Support\Collection; use Illuminate\Support\Facades\Cache; +use Illuminate\Support\Facades\Cookie; use Illuminate\Support\Facades\DB; use Illuminate\Support\Str; use Seiger\sCommerce\Controllers\sCommerceController; @@ -28,6 +29,35 @@ class sCommerce */ protected $currencies; + /** + * The current currency for the session. + * Loaded lazily and stored statically to optimize retrieval across multiple calls. + * + * @var string + */ + protected static string $currentCurrency; + + /** + * Retrieve a paginated list of products based on provided product IDs. + * + * This method fetches products filtered by their IDs and active status, + * with optional language support and pagination settings. + * + * @param array $productIds An array of product IDs to fetch. + * @param string|null $lang The language to fetch the products in. Defaults to the current locale. + * @param int $perPage The number of products to return per page. Defaults to 1000. + * @return object The paginated list of products as a Laravel collection. + */ + + public function getProducts(array $productIds, string $lang = null, int $perPage = 1000): object + { + if (!$lang) { + $lang = evo()->getLocale(); + } + + return sProduct::lang($lang)->whereIn('id', $productIds)->active()->paginate($perPage); + } + /** * Retrieves the product based on the given ID and language. * @@ -103,7 +133,7 @@ public function getCategoryProducts(int $category = null, string $lang = null, i $categories = array_merge([$category], $sCommerceController->listAllActiveSubCategories($category, $dept)); $productIds = DB::table('s_product_category')->select(['product'])->whereIn('category', $categories)->get()->pluck('product')->toArray(); - return sProduct::lang($lang)->whereIn('id', $productIds)->active()->paginate($perPage); + return $this->getProducts($productIds, $lang, $perPage); } /** @@ -161,8 +191,10 @@ public function getCurrencies(null|array $where = null): Collection * @param string $currencyTo The currency to convert to. * @return string The converted price as a formatted string. */ - public function convertPrice($price, $currencyFrom, $currencyTo): string + public function convertPrice($price, $currencyFrom = null, $currencyTo = null): string { + $currencyFrom = $currencyFrom ?? static::loadCurrentCurrency(); + $currencyTo = $currencyTo ?? $currencyFrom; $curr = $this->getCurrencies([$currencyTo])->first(); $price = number_format( @@ -260,8 +292,52 @@ public function moduleUrl(): string * @param mixed $default (optional) The default value to return if the key does not exist. Default is null. * @return mixed The value retrieved from the config file or the default value if the key does not exist. */ - public function config(string $key, mixed $default = null): mixed + public static function config(string $key, mixed $default = null): mixed { return config('seiger.settings.sCommerce.' . $key, $default); } + + /** + * Loads the current currency from the session or the default configuration. + * This method is used internally to ensure the currency is properly initialized. + * + * @return string The currency code (e.g., USD, EUR, UAH). + */ + protected static function loadCurrentCurrency(): string + { + // Try to retrieve currency from session + $currency = $_SESSION['currency']; + + // Check cookies if currency is still not set + if (!$currency && Cookie::has('currency')) { + $currency = Cookie::get('currency'); + } + + // Fallback to default currency from configuration + if (!$currency) { + $currency = static::config('basic.main_currency', 'USD'); + } + + // Store currency in session if it is not already set + if (!$_SESSION['currency'] || $_SESSION['currency'] !== $currency) { + $_SESSION['currency'] = $currency; + } + + return $currency; + } + + /** + * Retrieves the current currency for the session. + * If the currency is not yet loaded, it initializes it using the loadCurrentCurrency method. + * + * @return string The currency code (e.g., USD, EUR). + */ + public static function currentCurrency(): string + { + if (!isset(static::$currentCurrency)) { + static::$currentCurrency = static::loadCurrentCurrency(); + } + + return static::$currentCurrency; + } } diff --git a/src/sCommerceServiceProvider.php b/src/sCommerceServiceProvider.php index 934336d..d68aafd 100644 --- a/src/sCommerceServiceProvider.php +++ b/src/sCommerceServiceProvider.php @@ -1,7 +1,6 @@ loadRoutes(); - // Migration for create tables $this->loadMigrations(); - // Views - $this->loadViews(); - - // MultiLang - $this->loadTranslations(); - // Files $this->publishFiles(); } + // Add custom routes for package + $this->loadRoutes(); + + // Views + $this->loadViews(); + + // MultiLang + $this->loadTranslations(); + // Check sCommerce configuration $this->mergeConfigFrom(dirname(__DIR__) . '/config/sCommerceCheck.php', 'cms.settings'); @@ -44,8 +43,8 @@ public function boot() $this->app->alias(sCommerce::class, 'sCommerce'); // Register the sCart class as a singleton - $this->app->singleton('sCart', fn($app) => sCart::getInstance()); - class_alias(sCartFacade::class, 'sCart'); + $this->app->singleton(sCart::class); + $this->app->alias(sCart::class, 'sCart'); } /** diff --git a/views/partials/scripts/cart.blade.php b/views/partials/scripts/cart.blade.php new file mode 100644 index 0000000..a75b703 --- /dev/null +++ b/views/partials/scripts/cart.blade.php @@ -0,0 +1,43 @@ +@php use Seiger\sCommerce\Facades\sCommerce; @endphp + \ No newline at end of file