feat: display product options
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
<?php
|
||||
|
||||
use App\Adapters\OcImageTool;
|
||||
use App\Adapters\OcModelCatalogProductAdapter;
|
||||
use App\ApplicationFactory;
|
||||
use Cart\Currency;
|
||||
use Cart\Tax;
|
||||
@@ -20,9 +22,8 @@ class Controllerextensiontgshophandle extends Controller
|
||||
{
|
||||
$app = ApplicationFactory::create([
|
||||
'oc_config_tax' => $this->config->get('config_tax'),
|
||||
'oc_currency' => $this->session->data['currency'],
|
||||
'oc_default_currency' => $this->config->get('config_currency'),
|
||||
'timezone' => $this->config->get('config_timezone', 'UTC'),
|
||||
'lang' => $this->config->get('config_admin_language'),
|
||||
'language_id' => (int)$this->config->get('config_language_id'),
|
||||
'shop_base_url' => HTTPS_SERVER,
|
||||
'dir_image' => DIR_IMAGE,
|
||||
@@ -42,18 +43,21 @@ class Controllerextensiontgshophandle extends Controller
|
||||
return $this->url;
|
||||
});
|
||||
|
||||
$app->bind(Currency::class, function () { return $this->currency; });
|
||||
$app->bind(Tax::class, function () { return $this->tax; });
|
||||
$app->bind(Currency::class, function () {
|
||||
return $this->currency;
|
||||
});
|
||||
$app->bind(Tax::class, function () {
|
||||
return $this->tax;
|
||||
});
|
||||
|
||||
$this->load->model('tool/image');
|
||||
$app->bind('image_resize', function () {
|
||||
return function ($path, $width, $height) {
|
||||
if (is_file(DIR_IMAGE . $path)) {
|
||||
return $this->model_tool_image->resize($path, $width, $height);
|
||||
}
|
||||
$app->bind(OcModelCatalogProductAdapter::class, function () {
|
||||
$this->load->model('catalog/product');
|
||||
return new OcModelCatalogProductAdapter($this->model_catalog_product);
|
||||
});
|
||||
|
||||
return $this->model_tool_image->resize('no_image.png', $width, $height);
|
||||
};
|
||||
$app->bind(OcImageTool::class, function () {
|
||||
$this->load->model('tool/image');
|
||||
return new OcImageTool($this->model_tool_image);
|
||||
});
|
||||
|
||||
$this->load->model('checkout/order');
|
||||
@@ -62,6 +66,8 @@ class Controllerextensiontgshophandle extends Controller
|
||||
return $this->model_checkout_order;
|
||||
});
|
||||
|
||||
$this->session->data['language'] = $this->config->get('config_language');
|
||||
|
||||
$app->bootAndHandleRequest();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,295 @@
|
||||
<?php
|
||||
|
||||
namespace Openguru\OpenCartFramework\Collection;
|
||||
|
||||
use ArrayAccess;
|
||||
use InvalidArgumentException;
|
||||
use ArrayIterator;
|
||||
use IteratorAggregate;
|
||||
use Countable;
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* A generic collection for managing objects of a specific type.
|
||||
*
|
||||
* @template T
|
||||
* @implements IteratorAggregate<T>
|
||||
*/
|
||||
class Collection implements IteratorAggregate, Countable, ArrayAccess, JsonSerializable
|
||||
{
|
||||
/** @var array<T> */
|
||||
protected array $items;
|
||||
|
||||
/** @var string|null */
|
||||
protected ?string $expectedClass;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $items Initial items for the collection.
|
||||
* @param string|null $expectedClass The expected class type for the items.
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function __construct(array $items = [], ?string $expectedClass = null)
|
||||
{
|
||||
$this->expectedClass = $expectedClass;
|
||||
|
||||
foreach ($items as $item) {
|
||||
$this->validateItem($item);
|
||||
}
|
||||
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an item to the collection.
|
||||
*
|
||||
* @param mixed $item
|
||||
* @return self
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function add($item): self
|
||||
{
|
||||
$this->validateItem($item);
|
||||
$this->items[] = $item;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all items.
|
||||
*
|
||||
* @return array<T>
|
||||
*/
|
||||
public function all(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter items based on a callback.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return self
|
||||
*/
|
||||
public function filter(callable $callback): self
|
||||
{
|
||||
return new self(array_values(array_filter($this->items, $callback)), $this->expectedClass);
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply a callback to each item and return a new collection.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @return array
|
||||
*/
|
||||
public function map(callable $callback): array
|
||||
{
|
||||
return array_map($callback, $this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce the collection to a single value.
|
||||
*
|
||||
* @param callable $callback
|
||||
* @param mixed $initial
|
||||
* @return mixed
|
||||
*/
|
||||
public function reduce(callable $callback, $initial)
|
||||
{
|
||||
return array_reduce($this->items, $callback, $initial);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find an item by a specific property and value.
|
||||
*
|
||||
* @param string $property
|
||||
* @param mixed $value
|
||||
* @return T|null
|
||||
*/
|
||||
public function findByProperty(string $property, $value)
|
||||
{
|
||||
foreach ($this->items as $item) {
|
||||
if (is_object($item) && property_exists($item, $property) && $item->$property === $value) {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an item exists with a specific property and value.
|
||||
*
|
||||
* @param string $property
|
||||
* @param mixed $value
|
||||
* @return bool
|
||||
*/
|
||||
public function hasValue(string $property, $value): bool
|
||||
{
|
||||
return $this->findByProperty($property, $value) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate an item type.
|
||||
*
|
||||
* @param mixed $item
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
private function validateItem($item): void
|
||||
{
|
||||
if ($this->expectedClass !== null && !($item instanceof $this->expectedClass)) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Item must be an instance of %s, %s given.',
|
||||
$this->expectedClass,
|
||||
is_object($item) ? get_class($item) : gettype($item)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an iterator for the collection.
|
||||
*/
|
||||
public function getIterator(): ArrayIterator
|
||||
{
|
||||
return new ArrayIterator($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count the number of items in the collection.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->items);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an offset exists.
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @return bool
|
||||
*/
|
||||
public function offsetExists($offset): bool
|
||||
{
|
||||
return isset($this->items[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an item at a specific offset.
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @return mixed
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->items[$offset] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set an item at a specific offset.
|
||||
*
|
||||
* @param mixed $offset
|
||||
* @param mixed $value
|
||||
*/
|
||||
public function offsetSet($offset, $value): void
|
||||
{
|
||||
$this->validateItem($value);
|
||||
|
||||
if ($offset === null) {
|
||||
$this->items[] = $value;
|
||||
} else {
|
||||
$this->items[$offset] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unset an item at a specific offset.
|
||||
*
|
||||
* @param mixed $offset
|
||||
*/
|
||||
public function offsetUnset($offset): void
|
||||
{
|
||||
unset($this->items[$offset]);
|
||||
}
|
||||
|
||||
public function getValueByProperty(string $property, $propertyValue)
|
||||
{
|
||||
$item = $this->findByProperty($property, $propertyValue);
|
||||
|
||||
return $item ? $item->getValue() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return T|null
|
||||
*/
|
||||
public function first()
|
||||
{
|
||||
return $this->items[0] ?? null;
|
||||
}
|
||||
|
||||
public function getFirstItemValue(string $field, $default = null)
|
||||
{
|
||||
if ($this->first() === null) {
|
||||
return $default;
|
||||
}
|
||||
|
||||
return $this->first()->{$field};
|
||||
}
|
||||
|
||||
public function groupBy(callable $callback): array
|
||||
{
|
||||
$grouped = [];
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
$key = $callback($item);
|
||||
$grouped[$key][] = $item;
|
||||
}
|
||||
|
||||
return $grouped;
|
||||
}
|
||||
|
||||
public function keyByField(callable $callback): array
|
||||
{
|
||||
$items = [];
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
$key = $callback($item);
|
||||
$items[$key] = $item;
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->all();
|
||||
}
|
||||
|
||||
public function pluck(string $field): array
|
||||
{
|
||||
$result = [];
|
||||
|
||||
foreach ($this->items as $item) {
|
||||
if (is_object($item)) {
|
||||
$result[] = $item->$field;
|
||||
} else {
|
||||
$result[] = $item[$field];
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->all();
|
||||
}
|
||||
|
||||
public function append(array $items): Collection
|
||||
{
|
||||
return new Collection(array_merge($this->items, $items), $this->expectedClass);
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,7 @@ if (!function_exists('config')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('dd')) {
|
||||
if (!function_exists('dd')) {
|
||||
function dd(): void
|
||||
{
|
||||
Utils::dd(func_get_args());
|
||||
|
||||
30
module/oc_telegram_shop/upload/oc_telegram_shop/src/Adapters/OcImageTool.php
Executable file
30
module/oc_telegram_shop/upload/oc_telegram_shop/src/Adapters/OcImageTool.php
Executable file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace App\Adapters;
|
||||
|
||||
use Proxy;
|
||||
use RuntimeException;
|
||||
|
||||
class OcImageTool
|
||||
{
|
||||
private Proxy $image;
|
||||
|
||||
public function __construct(Proxy $image)
|
||||
{
|
||||
$this->image = $image;
|
||||
}
|
||||
|
||||
public function resize(string $path, int $width, int $height, ?string $default = null): ?string
|
||||
{
|
||||
if ($path && is_file(DIR_IMAGE . $path)) {
|
||||
$image = $path;
|
||||
return $this->image->resize($image, $width, $height);
|
||||
}
|
||||
|
||||
if ($default && is_file(DIR_IMAGE . $default)) {
|
||||
return $this->image->resize($default, $width, $height);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace App\Adapters;
|
||||
|
||||
use ModelCatalogProduct;
|
||||
use Proxy;
|
||||
|
||||
class OcModelCatalogProductAdapter
|
||||
{
|
||||
/** @var Proxy|ModelCatalogProduct */
|
||||
private Proxy $model;
|
||||
|
||||
public function __construct(Proxy $model)
|
||||
{
|
||||
$this->model = $model;
|
||||
}
|
||||
|
||||
public function getProductOptions(int $productId): array
|
||||
{
|
||||
return $this->model->getProductOptions($productId);
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,7 @@
|
||||
|
||||
namespace App\Handlers;
|
||||
|
||||
use Closure;
|
||||
use Openguru\OpenCartFramework\Application;
|
||||
use App\Adapters\OcImageTool;
|
||||
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||
use Openguru\OpenCartFramework\Http\Request;
|
||||
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
||||
@@ -12,10 +11,12 @@ use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
||||
class CategoriesHandler
|
||||
{
|
||||
private Builder $queryBuilder;
|
||||
private OcImageTool $imageTool;
|
||||
|
||||
public function __construct(Builder $queryBuilder)
|
||||
public function __construct(Builder $queryBuilder, OcImageTool $ocImageTool)
|
||||
{
|
||||
$this->queryBuilder = $queryBuilder;
|
||||
$this->ocImageTool = $ocImageTool;
|
||||
}
|
||||
|
||||
public function index(Request $request): JsonResponse
|
||||
@@ -47,16 +48,9 @@ class CategoriesHandler
|
||||
->limit($perPage)
|
||||
->get();
|
||||
|
||||
/** @var Closure $resize */
|
||||
$resize = Application::getInstance()->get('image_resize');
|
||||
|
||||
return new JsonResponse([
|
||||
'data' => array_map(static function ($category) use ($resize, $imageWidth, $imageHeight) {
|
||||
if ($category['image']) {
|
||||
$image = $resize($category['image'], $imageWidth, $imageHeight);
|
||||
} else {
|
||||
$image = $resize('no_image.png', $imageWidth, $imageHeight);
|
||||
}
|
||||
'data' => array_map(function ($category) use ($imageWidth, $imageHeight) {
|
||||
$image = $this->ocImageTool->resize($category['image'], $imageWidth, $imageHeight, 'no_image.png');
|
||||
|
||||
return [
|
||||
'id' => (int)$category['id'],
|
||||
|
||||
@@ -2,13 +2,14 @@
|
||||
|
||||
namespace App\Handlers;
|
||||
|
||||
use App\Adapters\OcImageTool;
|
||||
use App\Adapters\OcModelCatalogProductAdapter;
|
||||
use Cart\Currency;
|
||||
use Cart\Tax;
|
||||
use Closure;
|
||||
use Openguru\OpenCartFramework\Application;
|
||||
use Openguru\OpenCartFramework\Config\Settings;
|
||||
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||
use Openguru\OpenCartFramework\Http\Request;
|
||||
use Openguru\OpenCartFramework\Http\Response;
|
||||
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
||||
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
||||
use Openguru\OpenCartFramework\Support\Arr;
|
||||
@@ -19,13 +20,23 @@ class ProductsHandler
|
||||
private Currency $currency;
|
||||
private Tax $tax;
|
||||
private Settings $settings;
|
||||
private OcModelCatalogProductAdapter $ocModelCatalogProduct;
|
||||
private OcImageTool $ocImageTool;
|
||||
|
||||
public function __construct(Builder $queryBuilder, Currency $currency, Tax $tax, Settings $settings)
|
||||
{
|
||||
public function __construct(
|
||||
Builder $queryBuilder,
|
||||
Currency $currency,
|
||||
Tax $tax,
|
||||
Settings $settings,
|
||||
OcModelCatalogProductAdapter $ocModelCatalogProduct,
|
||||
OcImageTool $ocImageTool
|
||||
) {
|
||||
$this->queryBuilder = $queryBuilder;
|
||||
$this->currency = $currency;
|
||||
$this->tax = $tax;
|
||||
$this->settings = $settings;
|
||||
$this->ocModelCatalogProduct = $ocModelCatalogProduct;
|
||||
$this->ocImageTool = $ocImageTool;
|
||||
}
|
||||
|
||||
public function handle(Request $request): JsonResponse
|
||||
@@ -93,26 +104,24 @@ class ProductsHandler
|
||||
->get();
|
||||
}
|
||||
|
||||
|
||||
/** @var Closure $resize */
|
||||
$resize = Application::getInstance()->get('image_resize');
|
||||
|
||||
$productsImagesMap = [];
|
||||
foreach ($productsImages as $item) {
|
||||
$productsImagesMap[$item['product_id']][] = [
|
||||
'url' => $resize($item['image'], $imageWidth, $imageHeight),
|
||||
'url' => $this->ocImageTool->resize($item['image'], $imageWidth, $imageHeight, 'placeholder.png'),
|
||||
'alt' => 'Product Image',
|
||||
];
|
||||
}
|
||||
|
||||
return new JsonResponse([
|
||||
'data' => array_map(function ($product) use ($resize, $productsImagesMap, $imageWidth, $imageHeight) {
|
||||
'data' => array_map(function ($product) use ($productsImagesMap, $imageWidth, $imageHeight) {
|
||||
$allImages = [];
|
||||
if ($product['product_image']) {
|
||||
$image = $resize($product['product_image'], $imageWidth, $imageHeight);
|
||||
} else {
|
||||
$image = $resize('placeholder.png', $imageWidth, $imageHeight);
|
||||
}
|
||||
|
||||
$image = $this->ocImageTool->resize(
|
||||
$product['product_image'],
|
||||
$imageWidth,
|
||||
$imageHeight,
|
||||
'placeholder.png'
|
||||
);
|
||||
|
||||
$allImages[] = [
|
||||
'url' => $image,
|
||||
@@ -125,7 +134,7 @@ class ProductsHandler
|
||||
$product['tax_class_id'],
|
||||
$this->settings->get('oc_config_tax'),
|
||||
),
|
||||
$this->settings->get('oc_currency'),
|
||||
$this->settings->get('oc_default_currency'),
|
||||
);
|
||||
|
||||
if (!empty($productsImagesMap[$product['product_id']])) {
|
||||
@@ -173,7 +182,7 @@ class ProductsHandler
|
||||
->where('product_description.language_id', '=', $languageId);
|
||||
}
|
||||
)
|
||||
->join(
|
||||
->leftJoin(
|
||||
db_table('manufacturer') . ' AS manufacturer',
|
||||
function (JoinClause $join) use ($languageId) {
|
||||
$join->on('products.manufacturer_id', '=', 'manufacturer.manufacturer_id');
|
||||
@@ -183,6 +192,10 @@ class ProductsHandler
|
||||
->limit(1)
|
||||
->firstOrNull();
|
||||
|
||||
if (!$product) {
|
||||
return new JsonResponse([], Response::HTTP_NOT_FOUND);
|
||||
}
|
||||
|
||||
$productsImages = $this->queryBuilder->newQuery()
|
||||
->select([
|
||||
'products_images.product_id' => 'product_id',
|
||||
@@ -193,17 +206,20 @@ class ProductsHandler
|
||||
->where('products_images.product_id', '=', $productId)
|
||||
->get();
|
||||
|
||||
/** @var Closure $resize */
|
||||
$resize = Application::getInstance()->get('image_resize');
|
||||
|
||||
$images = [];
|
||||
$images[] = [
|
||||
'url' => $resize($product['product_image'], $imageWidth, $imageHeight),
|
||||
'url' => $this->ocImageTool->resize(
|
||||
$product['product_image'],
|
||||
$imageWidth,
|
||||
$imageHeight,
|
||||
'placeholder.png'
|
||||
),
|
||||
'alt' => $product['product_name'],
|
||||
];
|
||||
|
||||
foreach ($productsImages as $item) {
|
||||
$images[] = [
|
||||
'url' => $resize($item['image'], $imageWidth, $imageHeight),
|
||||
'url' => $this->ocImageTool->resize($item['image'], $imageWidth, $imageHeight, 'placeholder.png'),
|
||||
'alt' => $product['product_name'],
|
||||
];
|
||||
}
|
||||
@@ -214,7 +230,7 @@ class ProductsHandler
|
||||
$product['tax_class_id'],
|
||||
$this->settings->get('oc_config_tax'),
|
||||
),
|
||||
$this->settings->get('oc_currency'),
|
||||
$this->settings->get('oc_default_currency'),
|
||||
);
|
||||
|
||||
$data = [
|
||||
@@ -224,10 +240,64 @@ class ProductsHandler
|
||||
'manufacturer' => $product['product_manufacturer'],
|
||||
'price' => $price,
|
||||
'images' => $images,
|
||||
'options' => $this->loadProductOptions($product),
|
||||
];
|
||||
|
||||
return new JsonResponse([
|
||||
'data' => $data,
|
||||
]);
|
||||
}
|
||||
|
||||
private function loadProductOptions($product): array
|
||||
{
|
||||
$result = [];
|
||||
$productId = $product['product_id'];
|
||||
$taxClassId = $product['tax_class_id'];
|
||||
|
||||
$options = $this->ocModelCatalogProduct->getProductOptions($productId);
|
||||
$ocConfigTax = $this->settings->get('oc_config_tax');
|
||||
$ocDefaultCurrency = $this->settings->get('oc_default_currency');
|
||||
|
||||
foreach ($options as $option) {
|
||||
$product_option_value_data = [];
|
||||
|
||||
foreach ($option['product_option_value'] as $option_value) {
|
||||
if (!$option_value['subtract'] || ($option_value['quantity'] > 0)) {
|
||||
if ((float)$option_value['price']) {
|
||||
$priceWithTax = $this->tax->calculate(
|
||||
$option_value['price'],
|
||||
$taxClassId,
|
||||
$ocConfigTax ? 'Р' : false,
|
||||
);
|
||||
|
||||
$price = $this->currency->format($priceWithTax, $ocDefaultCurrency);
|
||||
} else {
|
||||
$price = false;
|
||||
}
|
||||
|
||||
$product_option_value_data[] = [
|
||||
'product_option_value_id' => (int)$option_value['product_option_value_id'],
|
||||
'option_value_id' => (int)$option_value['option_value_id'],
|
||||
'name' => $option_value['name'],
|
||||
'image' => $this->ocImageTool->resize($option_value['image'], 50, 50),
|
||||
'price' => $price,
|
||||
'price_prefix' => $option_value['price_prefix'],
|
||||
'selected' => false,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
$result[] = [
|
||||
'product_option_id' => (int)$option['product_option_id'],
|
||||
'values' => $product_option_value_data,
|
||||
'option_id' => (int)$option['option_id'],
|
||||
'name' => $option['name'],
|
||||
'type' => $option['type'],
|
||||
'value' => $option['value'],
|
||||
'required' => filter_var($option['required'], FILTER_VALIDATE_BOOLEAN),
|
||||
];
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user