feat: WIP
This commit is contained in:
61
.github/workflows/main.yaml
vendored
Normal file
61
.github/workflows/main.yaml
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
name: Telegram Mini App Shop Builder
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
module-build:
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
name: Build module.
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Build module
|
||||||
|
run: |
|
||||||
|
bash scripts/ci/build.sh "${GITHUB_WORKSPACE}"
|
||||||
|
|
||||||
|
- name: Upload build artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: oc_telegram_shop.ocmod.zip
|
||||||
|
path: ./build/oc_telegram_shop.ocmod.zip
|
||||||
|
retention-days: 1
|
||||||
|
|
||||||
|
release:
|
||||||
|
if: github.ref == 'refs/heads/master'
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs: [module-build]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0 # to fetch tags
|
||||||
|
|
||||||
|
- name: Extract tag and set filename
|
||||||
|
id: meta
|
||||||
|
run: |
|
||||||
|
TAG=${GITHUB_REF#refs/tags/}
|
||||||
|
echo "tag=$TAG" >> $GITHUB_OUTPUT
|
||||||
|
echo "filename=oc_telegram_shop_${TAG}.ocmod.zip" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Download build artifact
|
||||||
|
uses: actions/download-artifact@v4
|
||||||
|
with:
|
||||||
|
name: oc_telegram_shop.ocmod.zip
|
||||||
|
path: ./build
|
||||||
|
|
||||||
|
- name: Rename artifact file
|
||||||
|
run: mv ./build/oc_telegram_shop.ocmod.zip ./build/${{ steps.meta.outputs.filename }}
|
||||||
|
|
||||||
|
- name: Create GitHub Release
|
||||||
|
uses: softprops/action-gh-release@v2
|
||||||
|
with:
|
||||||
|
tag_name: ${{ steps.meta.outputs.tag }}
|
||||||
|
files: ./build/${{ steps.meta.outputs.filename }}
|
||||||
|
generate_release_notes: true
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -24,4 +24,6 @@ dist-ssr
|
|||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
src/*
|
src/*
|
||||||
spa/node_modules
|
spa/node_modules
|
||||||
|
module/oc_telegram_shop/upload/oc_telegram_shop/vendor
|
||||||
|
module/oc_telegram_shop/upload/image
|
||||||
8
Makefile
8
Makefile
@@ -22,15 +22,11 @@ start:
|
|||||||
docker compose up -d
|
docker compose up -d
|
||||||
|
|
||||||
ssh:
|
ssh:
|
||||||
docker compose exec web bash
|
docker compose exec -w /module/oc_telegram_shop/upload/oc_telegram_shop web bash
|
||||||
|
|
||||||
link:
|
link:
|
||||||
docker compose exec web bash -c "php ./scripts/link.php"
|
docker compose exec web bash -c "php ./scripts/link.php"
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
$(MAKE) link && \
|
$(MAKE) link && \
|
||||||
cd frontend && npm run dev
|
cd spa && npm run dev
|
||||||
|
|
||||||
build:
|
|
||||||
docker build -t oc_layout_pro_build_prod -f docker/build.dockerfile . && \
|
|
||||||
docker run --rm -v "./build":/build oc_layout_pro_build_prod
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
web:
|
web:
|
||||||
image: webdevops/php-apache-dev:${PHP_VERSION}
|
image: webdevops/php-apache-dev:7.4
|
||||||
platform: linux/amd64
|
platform: linux/amd64
|
||||||
volumes:
|
volumes:
|
||||||
- "./src:/web"
|
- "./src:/web"
|
||||||
|
|||||||
72
module/oc_telegram_shop/upload/catalog/controller/tgshop/handle.php
Executable file
72
module/oc_telegram_shop/upload/catalog/controller/tgshop/handle.php
Executable file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\ApplicationFactory;
|
||||||
|
use Cart\Currency;
|
||||||
|
use Cart\Tax;
|
||||||
|
|
||||||
|
$sysLibPath = rtrim(DIR_SYSTEM, '/') . '/library/oc_telegram_shop';
|
||||||
|
$basePath = rtrim(DIR_APPLICATION, '/') . '/..';
|
||||||
|
if (is_readable($sysLibPath . '/oc_telegram_shop.phar')) {
|
||||||
|
require_once "phar://{$sysLibPath}/oc_telegram_shop.phar/vendor/autoload.php";
|
||||||
|
} elseif (is_dir("$basePath/oc_telegram_shop")) {
|
||||||
|
require_once "$basePath/oc_telegram_shop/vendor/autoload.php";
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException('Unable to locate bulk products directory.');
|
||||||
|
}
|
||||||
|
|
||||||
|
class Controllertgshophandle extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
$app = ApplicationFactory::create([
|
||||||
|
'oc_config_tax' => $this->config->get('config_tax'),
|
||||||
|
'oc_currency' => $this->session->data['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,
|
||||||
|
'db' => [
|
||||||
|
'host' => DB_HOSTNAME,
|
||||||
|
'database' => DB_DATABASE,
|
||||||
|
'username' => DB_USERNAME,
|
||||||
|
'password' => DB_PASSWORD,
|
||||||
|
'prefix' => DB_PREFIX,
|
||||||
|
],
|
||||||
|
'logs' => [
|
||||||
|
'path' => DIR_LOGS,
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
|
$app->bind(Url::class, function () {
|
||||||
|
return $this->url;
|
||||||
|
});
|
||||||
|
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->model_tool_image->resize('no_image.png', $width, $height);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->load->model('checkout/order');
|
||||||
|
|
||||||
|
$app->bind('model_checkout_order', function () {
|
||||||
|
return $this->model_checkout_order;
|
||||||
|
});
|
||||||
|
|
||||||
|
$app->bootAndHandleRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function spa()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
27
module/oc_telegram_shop/upload/oc_telegram_shop/composer.json
Executable file
27
module/oc_telegram_shop/upload/oc_telegram_shop/composer.json
Executable file
@@ -0,0 +1,27 @@
|
|||||||
|
{
|
||||||
|
"name": "nikitakiselev/oc_telegram_shop",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Openguru\\OpenCartFramework\\": "framework/",
|
||||||
|
"App\\": "src/"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"framework/Support/helpers.php"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Nikita Kiselev",
|
||||||
|
"email": "mail@nikitakiselev.ru"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": "^7.4",
|
||||||
|
"ext-pdo": "*",
|
||||||
|
"psr/container": "^2.0",
|
||||||
|
"ext-json": "*"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"roave/security-advisories": "dev-latest"
|
||||||
|
}
|
||||||
|
}
|
||||||
1033
module/oc_telegram_shop/upload/oc_telegram_shop/composer.lock
generated
Executable file
1033
module/oc_telegram_shop/upload/oc_telegram_shop/composer.lock
generated
Executable file
File diff suppressed because it is too large
Load Diff
113
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Application.php
Executable file
113
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Application.php
Executable file
@@ -0,0 +1,113 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
use Openguru\OpenCartFramework\Config\Settings;
|
||||||
|
use Openguru\OpenCartFramework\Container\Container;
|
||||||
|
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||||
|
use Openguru\OpenCartFramework\Http\Request;
|
||||||
|
use Openguru\OpenCartFramework\Logger\Logger;
|
||||||
|
use Openguru\OpenCartFramework\Router\Router;
|
||||||
|
use Openguru\OpenCartFramework\Support\ExecutionTimeProfiler;
|
||||||
|
|
||||||
|
class Application extends Container
|
||||||
|
{
|
||||||
|
private static Application $instance;
|
||||||
|
private static array $events = [];
|
||||||
|
private array $serviceProviders = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var ExecutionTimeProfiler
|
||||||
|
*/
|
||||||
|
private ExecutionTimeProfiler $profiler;
|
||||||
|
|
||||||
|
public static function getInstance(): Application
|
||||||
|
{
|
||||||
|
return static::$instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function bootKernelServices(): void
|
||||||
|
{
|
||||||
|
$this->singleton(ExecutionTimeProfiler::class, function () {
|
||||||
|
return new ExecutionTimeProfiler();
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->profiler = $this->get(ExecutionTimeProfiler::class);
|
||||||
|
$this->profiler->start();
|
||||||
|
|
||||||
|
$this->singleton(Container::class, function (Container $container) {
|
||||||
|
return $container;
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->singleton(Logger::class, function (Container $container) {
|
||||||
|
$path = $container->getConfigValue('logs.path') . '/oc_telegram_shop.log';
|
||||||
|
return new Logger($path, Logger::LEVEL_INFO);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->singleton(Settings::class, function (Container $container) {
|
||||||
|
return new Settings($container->getConfigValue());
|
||||||
|
});
|
||||||
|
|
||||||
|
$errorHandler = new ErrorHandler(
|
||||||
|
$this->get(Logger::class)
|
||||||
|
);
|
||||||
|
|
||||||
|
$errorHandler->register();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function boot(): Application
|
||||||
|
{
|
||||||
|
$this->bootKernelServices();
|
||||||
|
|
||||||
|
$this->initializeEventDispatcher(static::$events);
|
||||||
|
|
||||||
|
DependencyRegistration::register($this, $this->serviceProviders);
|
||||||
|
|
||||||
|
static::$instance = $this;
|
||||||
|
|
||||||
|
$this->profiler->addCheckpoint('Bootstrap Application');
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleRequest(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$this->bind(Request::class, function () use ($request) {
|
||||||
|
return $request;
|
||||||
|
});
|
||||||
|
|
||||||
|
$router = $this->get(Router::class);
|
||||||
|
$apiAction = $request->get('api_action');
|
||||||
|
$action = $router->resolve($apiAction);
|
||||||
|
|
||||||
|
[$controller, $method] = $action;
|
||||||
|
|
||||||
|
if (!class_exists($controller) || !method_exists($controller, $method)) {
|
||||||
|
throw new InvalidArgumentException('Invalid action: ' . $controller . '->' . $method);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->profiler->addCheckpoint('Handle Router.');
|
||||||
|
|
||||||
|
$response = $this->call($controller, $method);
|
||||||
|
|
||||||
|
$this->profiler->addCheckpoint('Handle HTTP request.');
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bootAndHandleRequest(): void
|
||||||
|
{
|
||||||
|
$this->boot();
|
||||||
|
$request = Request::createFromGlobals();
|
||||||
|
$response = $this->handleRequest($request);
|
||||||
|
$response->send();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function withServiceProviders(array $serviceProviders): Application
|
||||||
|
{
|
||||||
|
$this->serviceProviders = $serviceProviders;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Cache;
|
||||||
|
|
||||||
|
interface CacheInterface
|
||||||
|
{
|
||||||
|
public function get(string $key);
|
||||||
|
public function set(string $key, $value, ?int $ttlSeconds = null): void;
|
||||||
|
public function delete(string $key): void;
|
||||||
|
public function clear(): void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Cache;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Config\Settings;
|
||||||
|
use Openguru\OpenCartFramework\Container\Container;
|
||||||
|
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
||||||
|
|
||||||
|
class CacheServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->container->singleton(CacheInterface::class, function (Container $container) {
|
||||||
|
return new DatabaseCache(
|
||||||
|
$container->get(ConnectionInterface::class),
|
||||||
|
$container->get(Settings::class)->get('tables.cache'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Cache;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
||||||
|
use Openguru\BulkProducts\Modules\Shared\Cache\CacheInterface;
|
||||||
|
|
||||||
|
class DatabaseCache implements CacheInterface
|
||||||
|
{
|
||||||
|
private $connection;
|
||||||
|
private $table;
|
||||||
|
|
||||||
|
public function __construct(ConnectionInterface $connection, string $table)
|
||||||
|
{
|
||||||
|
$this->connection = $connection;
|
||||||
|
$this->table = $table;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $key)
|
||||||
|
{
|
||||||
|
$cache = $this->connection->select(
|
||||||
|
"
|
||||||
|
SELECT result
|
||||||
|
FROM $this->table
|
||||||
|
WHERE cache_key = :key AND (expires_at IS NULL OR expires_at > NOW())
|
||||||
|
",
|
||||||
|
[':key' => $key]
|
||||||
|
);
|
||||||
|
|
||||||
|
return $cache ? unserialize($cache[0]['result'], ['allowed_classes' => true]) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(string $key, $value, ?int $ttlSeconds = null): void
|
||||||
|
{
|
||||||
|
$this->connection->statement("DELETE FROM $this->table WHERE expires_at IS NOT NULL AND expires_at <= NOW()");
|
||||||
|
|
||||||
|
$expiresAt = $ttlSeconds ? date('Y-m-d H:i:s', time() + $ttlSeconds) : null;
|
||||||
|
|
||||||
|
$sql = <<<SQL
|
||||||
|
INSERT INTO $this->table (cache_key, result, expires_at)
|
||||||
|
VALUES (:key, :result, :expiresAt)
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
result = VALUES(result),
|
||||||
|
expires_at = VALUES(expires_at)
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
$this->connection->statement($sql, [
|
||||||
|
':key' => $key,
|
||||||
|
':result' => serialize($value),
|
||||||
|
':expiresAt' => $expiresAt,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function delete(string $key): void
|
||||||
|
{
|
||||||
|
$this->connection->statement("DELETE FROM $this->table WHERE cache_key = :key", [':key' => $key]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function clear(): void
|
||||||
|
{
|
||||||
|
$this->connection->truncateTable($this->table);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Config;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
|
||||||
|
class Settings
|
||||||
|
{
|
||||||
|
private $settings;
|
||||||
|
|
||||||
|
public function __construct(array $initialSettings = [])
|
||||||
|
{
|
||||||
|
$this->settings = $initialSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $key, $default = null)
|
||||||
|
{
|
||||||
|
return Arr::get($this->settings, $key, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function set(string $key, $value): void
|
||||||
|
{
|
||||||
|
Arr::set($this->settings, $key, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function has(string $key): bool
|
||||||
|
{
|
||||||
|
return Arr::get($this->settings, $key) !== null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function remove(string $key): void
|
||||||
|
{
|
||||||
|
Arr::unset($this->settings, $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAll(): array
|
||||||
|
{
|
||||||
|
return $this->settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setAll(array $settings): void
|
||||||
|
{
|
||||||
|
$this->settings = $settings;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Container;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Events\EventDispatcher;
|
||||||
|
use Openguru\OpenCartFramework\Exceptions\ContainerDependencyResolutionException;
|
||||||
|
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||||
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
use Phar;
|
||||||
|
use Psr\Container\ContainerInterface;
|
||||||
|
use ReflectionClass;
|
||||||
|
use ReflectionException;
|
||||||
|
use ReflectionMethod;
|
||||||
|
use ReflectionNamedType;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
if (!defined('BP_BASE_PATH')) {
|
||||||
|
$phar = Phar::running(false);
|
||||||
|
define('BP_BASE_PATH', $phar ? "phar://$phar" : dirname(__DIR__) . '/..');
|
||||||
|
}
|
||||||
|
|
||||||
|
class Container implements ContainerInterface
|
||||||
|
{
|
||||||
|
private array $factories = [];
|
||||||
|
private array $instances = [];
|
||||||
|
private array $config;
|
||||||
|
private $taggedAbstracts = [];
|
||||||
|
|
||||||
|
public function __construct(array $config)
|
||||||
|
{
|
||||||
|
$this->config = $config;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConfigValue(?string $key = null, $default = null)
|
||||||
|
{
|
||||||
|
if ($key === null) {
|
||||||
|
return $this->config;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arr::get($this->config, $key, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function has(string $id): bool
|
||||||
|
{
|
||||||
|
return array_key_exists($id, $this->factories);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function bind(string $abstract, callable $concrete, bool $singleton = false): void
|
||||||
|
{
|
||||||
|
$this->factories[$abstract] = [
|
||||||
|
'concrete' => $concrete,
|
||||||
|
'singleton' => $singleton,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function singleton(string $abstract, callable $concrete): void
|
||||||
|
{
|
||||||
|
$this->bind($abstract, $concrete, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param string $id
|
||||||
|
* @return T
|
||||||
|
* @psalm-param class-string<T>|string $id
|
||||||
|
* @psalm-suppress MoreSpecificImplementedParamType
|
||||||
|
*/
|
||||||
|
public function get(string $id)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->has($id)) {
|
||||||
|
if ($this->factories[$id]['singleton']) {
|
||||||
|
if (!array_key_exists($id, $this->instances)) {
|
||||||
|
$this->instances[$id] = $this->factories[$id]['concrete']($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->instances[$id];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->factories[$id]['concrete']($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (class_exists($id)) {
|
||||||
|
$reflection = new ReflectionClass($id);
|
||||||
|
$constructor = $reflection->getConstructor();
|
||||||
|
|
||||||
|
$arguments = [];
|
||||||
|
|
||||||
|
if ($constructor && $parameters = $constructor->getParameters()) {
|
||||||
|
foreach ($parameters as $parameter) {
|
||||||
|
if ($parameter->getType() instanceof ReflectionNamedType) {
|
||||||
|
$parameterType = $parameter->getType()->getName();
|
||||||
|
/** @psalm-suppress ArgumentTypeCoercion */
|
||||||
|
$arguments[] = $this->get($parameterType);
|
||||||
|
} else {
|
||||||
|
$arguments[] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @var T $instance */
|
||||||
|
$instance = $reflection->newInstanceArgs($arguments);
|
||||||
|
|
||||||
|
return $instance;
|
||||||
|
}
|
||||||
|
} catch (ReflectionException $exception) {
|
||||||
|
throw new ContainerDependencyResolutionException(
|
||||||
|
'Could not resolve the concrete: ' . $id . '. ' . $exception->getMessage()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ContainerDependencyResolutionException(
|
||||||
|
'Could not resolve the concrete: ' . $id . ' (class does not exist)'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param class-string $abstract
|
||||||
|
* @throws ReflectionException
|
||||||
|
*/
|
||||||
|
public function call(string $abstract, string $method): JsonResponse
|
||||||
|
{
|
||||||
|
if (!class_exists($abstract)) {
|
||||||
|
throw new ContainerDependencyResolutionException('Could not resolve the concrete: ' . $abstract);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!method_exists($abstract, $method)) {
|
||||||
|
throw new ContainerDependencyResolutionException('Method not found: ' . $abstract . '@' . $method);
|
||||||
|
}
|
||||||
|
|
||||||
|
$instance = $this->get($abstract);
|
||||||
|
|
||||||
|
$reflection = new ReflectionMethod($instance, $method);
|
||||||
|
|
||||||
|
$arguments = [];
|
||||||
|
foreach ($reflection->getParameters() as $parameter) {
|
||||||
|
if ($parameter->getType() instanceof ReflectionNamedType) {
|
||||||
|
$parameterType = $parameter->getType()->getName();
|
||||||
|
/** @psalm-suppress ArgumentTypeCoercion */
|
||||||
|
$arguments[] = $this->get($parameterType);
|
||||||
|
} else {
|
||||||
|
$arguments[] = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $instance->{$method}(...$arguments);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerEventListener(string $eventClass, string $listenerClass): void
|
||||||
|
{
|
||||||
|
$dispatcher = $this->get(EventDispatcher::class);
|
||||||
|
|
||||||
|
$dispatcher->registerListener($eventClass, $listenerClass);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function initializeEventDispatcher(array $listeners): void
|
||||||
|
{
|
||||||
|
$this->singleton(EventDispatcher::class, function (Container $container) {
|
||||||
|
return new EventDispatcher($container);
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach ($listeners as $eventClass => $listenerClasses) {
|
||||||
|
foreach ($listenerClasses as $listenerClass) {
|
||||||
|
$this->registerEventListener($eventClass, $listenerClass);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tag(string $tag, array $abstracts): void
|
||||||
|
{
|
||||||
|
if (array_key_exists($tag, $this->taggedAbstracts)) {
|
||||||
|
throw new RuntimeException('Tag already exists: ' . $tag);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->taggedAbstracts[$tag] = $abstracts;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Container;
|
||||||
|
|
||||||
|
abstract class ServiceProvider
|
||||||
|
{
|
||||||
|
/** @var Container $container */
|
||||||
|
protected $container;
|
||||||
|
|
||||||
|
public function __construct(Container $container)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract public function register(): void;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Contracts;
|
||||||
|
|
||||||
|
interface Arrayable
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Convert the object to an array.
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function toArray(): array;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Container\Container;
|
||||||
|
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||||
|
use Openguru\OpenCartFramework\Settings\DatabaseUserSettings;
|
||||||
|
use Openguru\OpenCartFramework\Settings\UserSettingsInterface;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class DependencyRegistration
|
||||||
|
{
|
||||||
|
public static function register(Container $container, array $serviceProviders = []): void
|
||||||
|
{
|
||||||
|
$container->singleton(UserSettingsInterface::class, function (Container $container) {
|
||||||
|
return $container->get(DatabaseUserSettings::class);
|
||||||
|
});
|
||||||
|
|
||||||
|
static::registerServiceProviders($container, $serviceProviders);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function registerServiceProviders(Container $container, array $serviceProviders): void
|
||||||
|
{
|
||||||
|
foreach ($serviceProviders as $serviceProvider) {
|
||||||
|
$provider = $container->get($serviceProvider);
|
||||||
|
if (!$provider instanceof ServiceProvider) {
|
||||||
|
throw new RuntimeException('ServiceProvider must extend ServiceProvider');
|
||||||
|
}
|
||||||
|
|
||||||
|
$provider->register();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
93
module/oc_telegram_shop/upload/oc_telegram_shop/framework/ErrorHandler.php
Executable file
93
module/oc_telegram_shop/upload/oc_telegram_shop/framework/ErrorHandler.php
Executable file
@@ -0,0 +1,93 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework;
|
||||||
|
|
||||||
|
use ErrorException;
|
||||||
|
use Openguru\OpenCartFramework\Exceptions\NonLoggableExceptionInterface;
|
||||||
|
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||||
|
use Openguru\OpenCartFramework\Http\Response;
|
||||||
|
use Openguru\OpenCartFramework\Logger\Logger;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @codeCoverageIgnore
|
||||||
|
*/
|
||||||
|
class ErrorHandler
|
||||||
|
{
|
||||||
|
private $logger;
|
||||||
|
|
||||||
|
public function __construct(Logger $logger)
|
||||||
|
{
|
||||||
|
$this->logger = $logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
set_error_handler([$this, 'handleError']);
|
||||||
|
set_exception_handler([$this, 'handleException']);
|
||||||
|
register_shutdown_function([$this, 'handleShutdown']);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleError(int $severity, string $message, string $file, int $line): bool
|
||||||
|
{
|
||||||
|
$this->logger->error('Handled PHP error: ' . implode(', ', compact('severity', 'message', 'file', 'line')));
|
||||||
|
|
||||||
|
// Convert warnings and notices to ErrorException
|
||||||
|
if (!(error_reporting() & $severity)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ErrorException($message, 0, $severity, $file, $line);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleException(Throwable $exception): void
|
||||||
|
{
|
||||||
|
if (!$exception instanceof NonLoggableExceptionInterface) {
|
||||||
|
$this->logger->logException($exception);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (PHP_SAPI === 'cli') {
|
||||||
|
echo $exception->getMessage() . PHP_EOL;
|
||||||
|
} else {
|
||||||
|
(new JsonResponse([
|
||||||
|
'exception' => get_class($exception),
|
||||||
|
'message' => $exception->getMessage(),
|
||||||
|
'file' => $exception->getFile(),
|
||||||
|
'line' => $exception->getLine(),
|
||||||
|
'trace' => $exception->getTrace(),
|
||||||
|
], Response::HTTP_INTERNAL_SERVER_ERROR))->send();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handleShutdown(): void
|
||||||
|
{
|
||||||
|
$error = error_get_last();
|
||||||
|
|
||||||
|
if ($error !== null && in_array($error['type'], [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR], true)) {
|
||||||
|
$message = sprintf(
|
||||||
|
"[%s] Fatal error: %s in %s:%d",
|
||||||
|
date('Y-m-d H:i:s'),
|
||||||
|
$error['message'],
|
||||||
|
$error['file'],
|
||||||
|
$error['line']
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->logger->error($message);
|
||||||
|
|
||||||
|
if (PHP_SAPI === 'cli') {
|
||||||
|
echo $message . PHP_EOL;
|
||||||
|
} else {
|
||||||
|
(new JsonResponse([
|
||||||
|
'message' => $message,
|
||||||
|
'file' => $error['file'],
|
||||||
|
'line' => $error['line'],
|
||||||
|
], Response::HTTP_INTERNAL_SERVER_ERROR))->send();
|
||||||
|
}
|
||||||
|
|
||||||
|
exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Events;
|
||||||
|
|
||||||
|
interface Event
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Events;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Container\Container;
|
||||||
|
|
||||||
|
class EventDispatcher
|
||||||
|
{
|
||||||
|
private $listeners = [];
|
||||||
|
private $container;
|
||||||
|
|
||||||
|
public function __construct(Container $container)
|
||||||
|
{
|
||||||
|
$this->container = $container;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function registerListener(string $eventClass, string $listenerClass): void
|
||||||
|
{
|
||||||
|
$this->listeners[$eventClass][] = $listenerClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dispatch(Event $event): void
|
||||||
|
{
|
||||||
|
$eventClass = get_class($event);
|
||||||
|
if (isset($this->listeners[$eventClass])) {
|
||||||
|
foreach ($this->listeners[$eventClass] as $listenerClass) {
|
||||||
|
$listener = $this->container->get($listenerClass);
|
||||||
|
$listener->handle($event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Events;
|
||||||
|
|
||||||
|
interface Listener
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ActionNotFoundException extends Exception implements NonLoggableExceptionInterface
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Exceptions;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
class ApplicationNotInstalledException extends Exception
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Exceptions;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class ContainerDependencyResolutionException extends RuntimeException
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Exceptions;
|
||||||
|
|
||||||
|
interface NonLoggableExceptionInterface
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Http;
|
||||||
|
|
||||||
|
class JsonResponse extends Response
|
||||||
|
{
|
||||||
|
private array $data;
|
||||||
|
private int $code;
|
||||||
|
|
||||||
|
public function __construct(array $data, int $code = self::HTTP_OK)
|
||||||
|
{
|
||||||
|
$this->data = $data;
|
||||||
|
$this->code = $code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getData(): array
|
||||||
|
{
|
||||||
|
return $this->data;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getCode(): int
|
||||||
|
{
|
||||||
|
return $this->code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function send(): void
|
||||||
|
{
|
||||||
|
$this->sendHeaders();
|
||||||
|
$this->sendContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendContent(): void
|
||||||
|
{
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'OPTIONS') {
|
||||||
|
http_response_code(204);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
echo json_encode($this->getData(), JSON_THROW_ON_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function sendHeaders(): void
|
||||||
|
{
|
||||||
|
http_response_code($this->getCode());
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
}
|
||||||
|
}
|
||||||
72
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Http/Request.php
Executable file
72
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Http/Request.php
Executable file
@@ -0,0 +1,72 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Http;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
use Openguru\OpenCartFramework\Support\Utils;
|
||||||
|
|
||||||
|
class Request
|
||||||
|
{
|
||||||
|
private $query;
|
||||||
|
private $request;
|
||||||
|
private $cookies;
|
||||||
|
private $files;
|
||||||
|
private $server;
|
||||||
|
private $content;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
array $query,
|
||||||
|
array $request,
|
||||||
|
array $cookies,
|
||||||
|
array $files,
|
||||||
|
array $server,
|
||||||
|
string $content = null
|
||||||
|
) {
|
||||||
|
$this->query = $query;
|
||||||
|
$this->request = $request;
|
||||||
|
$this->cookies = $cookies;
|
||||||
|
$this->files = $files;
|
||||||
|
$this->server = $server;
|
||||||
|
$this->content = $content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function createFromGlobals(): Request
|
||||||
|
{
|
||||||
|
return new static($_GET, $_POST, $_COOKIE, $_FILES, $_SERVER);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getContent(): string
|
||||||
|
{
|
||||||
|
if ($this->content === null || $this->content === '') {
|
||||||
|
$this->content = (string)file_get_contents('php://input');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function json(string $key = null, $default = null)
|
||||||
|
{
|
||||||
|
$content = $this->getContent();
|
||||||
|
|
||||||
|
if (!$content) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
$decoded = Utils::safeJsonDecode($content);
|
||||||
|
|
||||||
|
if ($key === null) {
|
||||||
|
return $decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Arr::get($decoded, $key, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $key = null, $default = null)
|
||||||
|
{
|
||||||
|
if ($key === null) {
|
||||||
|
return $this->query;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->query[$key] ?? $default;
|
||||||
|
}
|
||||||
|
}
|
||||||
70
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Http/Response.php
Executable file
70
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Http/Response.php
Executable file
@@ -0,0 +1,70 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Http;
|
||||||
|
|
||||||
|
class Response
|
||||||
|
{
|
||||||
|
public const HTTP_CONTINUE = 100;
|
||||||
|
public const HTTP_SWITCHING_PROTOCOLS = 101;
|
||||||
|
public const HTTP_PROCESSING = 102; // RFC2518
|
||||||
|
public const HTTP_EARLY_HINTS = 103; // RFC8297
|
||||||
|
public const HTTP_OK = 200;
|
||||||
|
public const HTTP_CREATED = 201;
|
||||||
|
public const HTTP_ACCEPTED = 202;
|
||||||
|
public const HTTP_NON_AUTHORITATIVE_INFORMATION = 203;
|
||||||
|
public const HTTP_NO_CONTENT = 204;
|
||||||
|
public const HTTP_RESET_CONTENT = 205;
|
||||||
|
public const HTTP_PARTIAL_CONTENT = 206;
|
||||||
|
public const HTTP_MULTI_STATUS = 207; // RFC4918
|
||||||
|
public const HTTP_ALREADY_REPORTED = 208; // RFC5842
|
||||||
|
public const HTTP_IM_USED = 226; // RFC3229
|
||||||
|
public const HTTP_MULTIPLE_CHOICES = 300;
|
||||||
|
public const HTTP_MOVED_PERMANENTLY = 301;
|
||||||
|
public const HTTP_FOUND = 302;
|
||||||
|
public const HTTP_SEE_OTHER = 303;
|
||||||
|
public const HTTP_NOT_MODIFIED = 304;
|
||||||
|
public const HTTP_USE_PROXY = 305;
|
||||||
|
public const HTTP_RESERVED = 306;
|
||||||
|
public const HTTP_TEMPORARY_REDIRECT = 307;
|
||||||
|
public const HTTP_PERMANENTLY_REDIRECT = 308; // RFC7238
|
||||||
|
public const HTTP_BAD_REQUEST = 400;
|
||||||
|
public const HTTP_UNAUTHORIZED = 401;
|
||||||
|
public const HTTP_PAYMENT_REQUIRED = 402;
|
||||||
|
public const HTTP_FORBIDDEN = 403;
|
||||||
|
public const HTTP_NOT_FOUND = 404;
|
||||||
|
public const HTTP_METHOD_NOT_ALLOWED = 405;
|
||||||
|
public const HTTP_NOT_ACCEPTABLE = 406;
|
||||||
|
public const HTTP_PROXY_AUTHENTICATION_REQUIRED = 407;
|
||||||
|
public const HTTP_REQUEST_TIMEOUT = 408;
|
||||||
|
public const HTTP_CONFLICT = 409;
|
||||||
|
public const HTTP_GONE = 410;
|
||||||
|
public const HTTP_LENGTH_REQUIRED = 411;
|
||||||
|
public const HTTP_PRECONDITION_FAILED = 412;
|
||||||
|
public const HTTP_REQUEST_ENTITY_TOO_LARGE = 413;
|
||||||
|
public const HTTP_REQUEST_URI_TOO_LONG = 414;
|
||||||
|
public const HTTP_UNSUPPORTED_MEDIA_TYPE = 415;
|
||||||
|
public const HTTP_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
|
||||||
|
public const HTTP_EXPECTATION_FAILED = 417;
|
||||||
|
public const HTTP_I_AM_A_TEAPOT = 418; // RFC2324
|
||||||
|
public const HTTP_MISDIRECTED_REQUEST = 421; // RFC7540
|
||||||
|
public const HTTP_UNPROCESSABLE_ENTITY = 422; // RFC4918
|
||||||
|
public const HTTP_LOCKED = 423; // RFC4918
|
||||||
|
public const HTTP_FAILED_DEPENDENCY = 424; // RFC4918
|
||||||
|
public const HTTP_TOO_EARLY = 425; // RFC-ietf-httpbis-replay-04
|
||||||
|
public const HTTP_UPGRADE_REQUIRED = 426; // RFC2817
|
||||||
|
public const HTTP_PRECONDITION_REQUIRED = 428; // RFC6585
|
||||||
|
public const HTTP_TOO_MANY_REQUESTS = 429; // RFC6585
|
||||||
|
public const HTTP_REQUEST_HEADER_FIELDS_TOO_LARGE = 431; // RFC6585
|
||||||
|
public const HTTP_UNAVAILABLE_FOR_LEGAL_REASONS = 451; // RFC7725
|
||||||
|
public const HTTP_INTERNAL_SERVER_ERROR = 500;
|
||||||
|
public const HTTP_NOT_IMPLEMENTED = 501;
|
||||||
|
public const HTTP_BAD_GATEWAY = 502;
|
||||||
|
public const HTTP_SERVICE_UNAVAILABLE = 503;
|
||||||
|
public const HTTP_GATEWAY_TIMEOUT = 504;
|
||||||
|
public const HTTP_VERSION_NOT_SUPPORTED = 505;
|
||||||
|
public const HTTP_VARIANT_ALSO_NEGOTIATES_EXPERIMENTAL = 506; // RFC2295
|
||||||
|
public const HTTP_INSUFFICIENT_STORAGE = 507; // RFC4918
|
||||||
|
public const HTTP_LOOP_DETECTED = 508; // RFC5842
|
||||||
|
public const HTTP_NOT_EXTENDED = 510; // RFC2774
|
||||||
|
public const HTTP_NETWORK_AUTHENTICATION_REQUIRED = 511; // RFC6585
|
||||||
|
}
|
||||||
88
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Logger/Logger.php
Executable file
88
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Logger/Logger.php
Executable file
@@ -0,0 +1,88 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Logger;
|
||||||
|
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
|
class Logger
|
||||||
|
{
|
||||||
|
private $logFile;
|
||||||
|
private $logLevel;
|
||||||
|
private $maxFileSize; // Максимальный размер файла в байтах
|
||||||
|
|
||||||
|
public const LEVEL_INFO = 1;
|
||||||
|
public const LEVEL_WARNING = 2;
|
||||||
|
public const LEVEL_ERROR = 3;
|
||||||
|
|
||||||
|
public function __construct($logFile, $logLevel = self::LEVEL_INFO, $maxFileSize = 1048576)
|
||||||
|
{
|
||||||
|
$this->logFile = $logFile;
|
||||||
|
$this->logLevel = $logLevel;
|
||||||
|
$this->maxFileSize = $maxFileSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function log($message, $level = self::LEVEL_INFO)
|
||||||
|
{
|
||||||
|
if ($level < $this->logLevel) {
|
||||||
|
return; // Не логируем, если уровень ниже установленного
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->rotateLogs();
|
||||||
|
|
||||||
|
$timestamp = date('Y-m-d H:i:s');
|
||||||
|
$levelStr = $this->getLevelString($level);
|
||||||
|
$logMessage = "[$timestamp] [$levelStr] $message\n";
|
||||||
|
|
||||||
|
file_put_contents($this->logFile, $logMessage, FILE_APPEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getLevelString($level): string
|
||||||
|
{
|
||||||
|
switch ($level) {
|
||||||
|
case self::LEVEL_INFO:
|
||||||
|
return 'INFO';
|
||||||
|
case self::LEVEL_WARNING:
|
||||||
|
return 'WARNING';
|
||||||
|
case self::LEVEL_ERROR:
|
||||||
|
return 'ERROR';
|
||||||
|
default:
|
||||||
|
return 'UNKNOWN';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function info(string $message): void
|
||||||
|
{
|
||||||
|
$this->log($message, self::LEVEL_INFO);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function warning(string $message): void
|
||||||
|
{
|
||||||
|
$this->log($message, self::LEVEL_WARNING);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function error(string $message): void
|
||||||
|
{
|
||||||
|
$this->log($message, self::LEVEL_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function rotateLogs(): void
|
||||||
|
{
|
||||||
|
if (is_file($this->logFile) && filesize($this->logFile) >= $this->maxFileSize) {
|
||||||
|
$newLogFile = $this->logFile . '.' . date('YmdHis');
|
||||||
|
rename($this->logFile, $newLogFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function logException(Throwable $exception): void
|
||||||
|
{
|
||||||
|
$this->error(
|
||||||
|
sprintf(
|
||||||
|
"Fatal error %s in %s on line %d\n%s",
|
||||||
|
$exception->getMessage(),
|
||||||
|
$exception->getFile(),
|
||||||
|
$exception->getLine(),
|
||||||
|
$exception->getTraceAsString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,429 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\QueryBuilder;
|
||||||
|
|
||||||
|
use Closure;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\Grammars\Grammar;
|
||||||
|
use Openguru\OpenCartFramework\Support\Utils;
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
class Builder
|
||||||
|
{
|
||||||
|
private $connection;
|
||||||
|
private $grammar;
|
||||||
|
|
||||||
|
public $columns = '*';
|
||||||
|
public $from;
|
||||||
|
public $joins = [];
|
||||||
|
public $wheres = [];
|
||||||
|
public $orders = [];
|
||||||
|
public $limit;
|
||||||
|
public $offset;
|
||||||
|
public $distinct = false;
|
||||||
|
|
||||||
|
public $bindings = [
|
||||||
|
'select' => [],
|
||||||
|
'from' => [],
|
||||||
|
'join' => [],
|
||||||
|
'where' => [],
|
||||||
|
'order' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(ConnectionInterface $connection, Grammar $grammar)
|
||||||
|
{
|
||||||
|
$this->connection = $connection;
|
||||||
|
$this->grammar = $grammar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function newQuery(): Builder
|
||||||
|
{
|
||||||
|
return new self($this->getConnection(), $this->getGrammar());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function select(array $columns): Builder
|
||||||
|
{
|
||||||
|
$this->columns = [];
|
||||||
|
$this->bindings['select'] = [];
|
||||||
|
|
||||||
|
if (!$columns) {
|
||||||
|
throw new InvalidArgumentException('Select columns not provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($columns as $key => $value) {
|
||||||
|
if ($value instanceof RawExpression) {
|
||||||
|
$this->columns[] = $value;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($key)) {
|
||||||
|
$this->columns[] = [
|
||||||
|
'column' => $key,
|
||||||
|
'as' => $value,
|
||||||
|
];
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->columns[] = [
|
||||||
|
'column' => $value,
|
||||||
|
'as' => null,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function from(string $table, ?string $as = null): Builder
|
||||||
|
{
|
||||||
|
$this->from = compact('table', 'as');
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereNull($column, $boolean = 'and'): Builder
|
||||||
|
{
|
||||||
|
$this->wheres[] = [
|
||||||
|
'type' => 'Null',
|
||||||
|
'column' => $column,
|
||||||
|
'boolean' => $boolean,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereNotNull($column, $boolean = 'and'): Builder
|
||||||
|
{
|
||||||
|
$this->wheres[] = [
|
||||||
|
'type' => 'NotNull',
|
||||||
|
'column' => $column,
|
||||||
|
'boolean' => $boolean,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereNested(Closure $callback, $boolean = 'and'): Builder
|
||||||
|
{
|
||||||
|
$callback($nestedQuery = $this->newQuery());
|
||||||
|
|
||||||
|
$this->wheres[] = [
|
||||||
|
'type' => 'Nested',
|
||||||
|
'query' => $nestedQuery,
|
||||||
|
'boolean' => $boolean,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->bindings['where'] = array_merge(
|
||||||
|
$this->bindings['where'],
|
||||||
|
$nestedQuery->getBindings('where')
|
||||||
|
);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereRaw(string $expression, $boolean = 'and'): Builder
|
||||||
|
{
|
||||||
|
$this->wheres[] = [
|
||||||
|
'type' => 'Raw',
|
||||||
|
'expression' => $expression,
|
||||||
|
'boolean' => $boolean,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function where($column, $operator, $value, $boolean = 'and'): Builder
|
||||||
|
{
|
||||||
|
$this->wheres[] = [
|
||||||
|
'type' => 'Basic',
|
||||||
|
'column' => $column,
|
||||||
|
'operator' => $operator,
|
||||||
|
'value' => $value,
|
||||||
|
'boolean' => $boolean,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->addBinding($value);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereIn($column, array $values, $not = false, $boolean = 'and'): Builder
|
||||||
|
{
|
||||||
|
$this->wheres[] = [
|
||||||
|
'type' => 'In',
|
||||||
|
'column' => $column,
|
||||||
|
'operator' => $not ? 'NOT IN' : 'IN',
|
||||||
|
'value' => $values,
|
||||||
|
'boolean' => $boolean,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->addBinding($values);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereNotIn($column, array $values, $boolean = 'and'): Builder
|
||||||
|
{
|
||||||
|
$this->wheres[] = [
|
||||||
|
'type' => 'In',
|
||||||
|
'column' => $column,
|
||||||
|
'operator' => 'NOT IN',
|
||||||
|
'value' => $values,
|
||||||
|
'boolean' => $boolean,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->addBinding($values);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereBetween(string $column, array $values, $boolean = 'and'): Builder
|
||||||
|
{
|
||||||
|
if (count($values) !== 2) {
|
||||||
|
throw new InvalidArgumentException('Invalid number of values provided.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->wheres[] = [
|
||||||
|
'type' => 'Between',
|
||||||
|
'column' => $column,
|
||||||
|
'values' => $values,
|
||||||
|
'boolean' => $boolean,
|
||||||
|
];
|
||||||
|
|
||||||
|
$this->addBinding($values[0]);
|
||||||
|
$this->addBinding($values[1]);
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orWhere($column, $operator, $value): Builder
|
||||||
|
{
|
||||||
|
return $this->where($column, $operator, $value, 'or');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function limit(int $limit): Builder
|
||||||
|
{
|
||||||
|
$this->limit = $limit;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function offset(int $offset): Builder
|
||||||
|
{
|
||||||
|
$this->offset = $offset;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function forPage(int $page = 0, int $perPage = 10): Builder
|
||||||
|
{
|
||||||
|
$offset = ($page - 1) * $perPage;
|
||||||
|
|
||||||
|
return $this->offset($offset)->limit($perPage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function orderBy(string $column, string $direction = 'ASC'): Builder
|
||||||
|
{
|
||||||
|
$direction = strtoupper($direction);
|
||||||
|
|
||||||
|
if (!in_array($direction, ['ASC', 'DESC'], true)) {
|
||||||
|
throw new InvalidArgumentException('Order direction must be "ASC" or "DESC".');
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->orders[] = [
|
||||||
|
'column' => $column,
|
||||||
|
'direction' => $direction,
|
||||||
|
];
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toSql(): string
|
||||||
|
{
|
||||||
|
return $this->grammar->compileComponents($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function toRawSql(): string
|
||||||
|
{
|
||||||
|
return $this->substituteBindingsIntoRawSql(
|
||||||
|
$this->toSql(),
|
||||||
|
$this->getBindings()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function substituteBindingsIntoRawSql(string $sql, array $bindings): string
|
||||||
|
{
|
||||||
|
$bindings = array_map(function ($value) {
|
||||||
|
return $this->connection->escape($value);
|
||||||
|
}, $bindings);
|
||||||
|
|
||||||
|
$query = '';
|
||||||
|
|
||||||
|
$isStringLiteral = false;
|
||||||
|
|
||||||
|
$sqlLen = strlen($sql);
|
||||||
|
|
||||||
|
for ($i = 0; $i < $sqlLen; $i++) {
|
||||||
|
$char = $sql[$i];
|
||||||
|
$nextChar = $sql[$i + 1] ?? null;
|
||||||
|
|
||||||
|
// Single quotes can be escaped as '' according to the SQL standard while
|
||||||
|
// MySQL uses \'. Postgres has operators like ?| that must get encoded
|
||||||
|
// in PHP like ??|. We should skip over the escaped characters here.
|
||||||
|
if (in_array($char . $nextChar, ["\'", "''", '??'])) {
|
||||||
|
$query .= $char . $nextChar;
|
||||||
|
++$i;
|
||||||
|
} elseif ($char === "'") { // Starting / leaving string literal...
|
||||||
|
$query .= $char;
|
||||||
|
$isStringLiteral = !$isStringLiteral;
|
||||||
|
} elseif ($char === '?' && !$isStringLiteral) { // Substitutable binding...
|
||||||
|
$query .= array_shift($bindings) ?? '?';
|
||||||
|
} else { // Normal character...
|
||||||
|
$query .= $char;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(): array
|
||||||
|
{
|
||||||
|
return $this->runSelect();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function firstOrNull(): ?array
|
||||||
|
{
|
||||||
|
$rows = $this->limit(1)->get();
|
||||||
|
|
||||||
|
return $rows[0] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function value(string $field)
|
||||||
|
{
|
||||||
|
$rows = $this->limit(1)->get();
|
||||||
|
|
||||||
|
return $rows[0][$field] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function pluck(string $column, string $index = null): array
|
||||||
|
{
|
||||||
|
$items = $this->runSelect();
|
||||||
|
|
||||||
|
return array_column($items, $column, $index);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function storeResultsToTable(string $destinationTable): bool
|
||||||
|
{
|
||||||
|
$sql = "INSERT INTO `$destinationTable` {$this->toSql()}";
|
||||||
|
|
||||||
|
$this->connection->truncateTable($destinationTable);
|
||||||
|
|
||||||
|
return $this->connection->statement($sql, $this->getBindings());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function runSelect(): array
|
||||||
|
{
|
||||||
|
return $this->connection->select(
|
||||||
|
$this->toSql(),
|
||||||
|
$this->getBindings()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBindings(string $type = null): array
|
||||||
|
{
|
||||||
|
if ($type === null) {
|
||||||
|
return Utils::arrayFlatten($this->bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->bindings[$type];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addBinding($value, $type = 'where'): Builder
|
||||||
|
{
|
||||||
|
if (!array_key_exists($type, $this->bindings)) {
|
||||||
|
throw new InvalidArgumentException("Invalid binding type: {$type}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->bindings[$type][] = $value;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dd(): void
|
||||||
|
{
|
||||||
|
Utils::dd($this->toSql(), $this->getBindings());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function join(string $table, Closure $joinClosure, $type = 'inner'): Builder
|
||||||
|
{
|
||||||
|
$joinClause = new JoinClause($this, $type, $table);
|
||||||
|
|
||||||
|
$joinClosure($joinClause);
|
||||||
|
|
||||||
|
$this->joins[] = $joinClause;
|
||||||
|
$this->bindings['join'][] = $joinClause->getBindings();
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function leftJoin(string $table, Closure $joinClosure): Builder
|
||||||
|
{
|
||||||
|
return $this->join($table, $joinClosure, 'left');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getConnection(): ConnectionInterface
|
||||||
|
{
|
||||||
|
return $this->connection;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getGrammar(): Grammar
|
||||||
|
{
|
||||||
|
return $this->grammar;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
$sql = 'SELECT COUNT(*) AS total_count FROM (' . $this->toSql() . ') AS x';
|
||||||
|
|
||||||
|
$result = $this->connection->select($sql, $this->getBindings());
|
||||||
|
|
||||||
|
return $result[0]['total_count'] ?? 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function distinct(): Builder
|
||||||
|
{
|
||||||
|
$this->distinct = true;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply a callback to the query if the given condition is true.
|
||||||
|
*
|
||||||
|
* @param bool $condition
|
||||||
|
* @param Closure $callback
|
||||||
|
* @param Closure|null $default
|
||||||
|
* @return $this
|
||||||
|
*/
|
||||||
|
public function when(bool $condition, Closure $callback, Closure $default = null): Builder
|
||||||
|
{
|
||||||
|
if ($condition) {
|
||||||
|
$callback($this);
|
||||||
|
} elseif ($default) {
|
||||||
|
$default($this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function hasJoinAlias(string $joinAlias): bool
|
||||||
|
{
|
||||||
|
$join = array_filter($this->joins, static function (JoinClause $join) use ($joinAlias) {
|
||||||
|
return strpos($join->table, $joinAlias) !== false;
|
||||||
|
});
|
||||||
|
|
||||||
|
return count($join) > 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\QueryBuilder\Connections;
|
||||||
|
|
||||||
|
interface ConnectionInterface
|
||||||
|
{
|
||||||
|
public function select(string $sql, array $bindings = []): array;
|
||||||
|
|
||||||
|
public function createTableFromSql(string $table, string $sql, array $bindings = []): bool;
|
||||||
|
|
||||||
|
public function insertIntoTableFromSql(string $table, string $sql, array $bindings = []): bool;
|
||||||
|
|
||||||
|
public function dropTable(string $table): bool;
|
||||||
|
|
||||||
|
public function dropTableIfExists(string $table): bool;
|
||||||
|
|
||||||
|
public function tableExists(string $table): bool;
|
||||||
|
|
||||||
|
public function escape($value): string;
|
||||||
|
|
||||||
|
public function statement(string $sql, array $bindings = []): bool;
|
||||||
|
|
||||||
|
public function beginTransaction(): bool;
|
||||||
|
|
||||||
|
public function commitTransaction(): bool;
|
||||||
|
|
||||||
|
public function rollBackTransaction(): bool;
|
||||||
|
|
||||||
|
public function truncateTable(string $table): bool;
|
||||||
|
|
||||||
|
public function lastInsertId($name = null): int;
|
||||||
|
|
||||||
|
public function getVersion(): string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\QueryBuilder\Connections;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Support\Utils;
|
||||||
|
use PDO;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class MySqlConnection implements ConnectionInterface
|
||||||
|
{
|
||||||
|
private $pdo;
|
||||||
|
|
||||||
|
public function __construct(PDO $pdo)
|
||||||
|
{
|
||||||
|
$this->pdo = $pdo;
|
||||||
|
|
||||||
|
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
|
||||||
|
|
||||||
|
$this->pdo->exec("SET NAMES 'utf8'");
|
||||||
|
$this->pdo->exec("SET CHARACTER SET utf8");
|
||||||
|
$this->pdo->exec("SET CHARACTER_SET_CONNECTION=utf8");
|
||||||
|
$this->pdo->exec("SET SQL_MODE = ''");
|
||||||
|
}
|
||||||
|
|
||||||
|
public function select(string $sql, array $bindings = []): array
|
||||||
|
{
|
||||||
|
$statement = $this->pdo->prepare($sql);
|
||||||
|
$statement->execute($bindings);
|
||||||
|
|
||||||
|
return $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function escape($value): string
|
||||||
|
{
|
||||||
|
switch (true) {
|
||||||
|
case $value === null:
|
||||||
|
return 'null';
|
||||||
|
|
||||||
|
case is_int($value) || is_float($value):
|
||||||
|
return (string)$value;
|
||||||
|
|
||||||
|
case is_bool($value):
|
||||||
|
return $value ? '1' : '0';
|
||||||
|
|
||||||
|
case is_array($value):
|
||||||
|
throw new RuntimeException('The database connection does not support escaping arrays.');
|
||||||
|
|
||||||
|
default:
|
||||||
|
if (Utils::strContains($value, "\00")) {
|
||||||
|
throw new RuntimeException(
|
||||||
|
'Strings with null bytes cannot be escaped. Use the binary escape option.'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preg_match('//u', $value) === false) {
|
||||||
|
throw new RuntimeException('Strings with invalid UTF-8 byte sequences cannot be escaped.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->pdo->quote($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createTableFromSql(string $table, string $sql, array $bindings = []): bool
|
||||||
|
{
|
||||||
|
$createSql = "CREATE TABLE IF NOT EXISTS `$table` AS ($sql)";
|
||||||
|
|
||||||
|
return $this->pdo->prepare($createSql)->execute($bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dropTable(string $table): bool
|
||||||
|
{
|
||||||
|
return $this->pdo->prepare("DROP TABLE `$table`")->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function dropTableIfExists(string $table): bool
|
||||||
|
{
|
||||||
|
return $this->pdo->prepare("DROP TABLE IF EXISTS `$table`")->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function tableExists(string $table): bool
|
||||||
|
{
|
||||||
|
$statement = $this->pdo->prepare("SHOW TABLES LIKE ?;");
|
||||||
|
$statement->execute([$table]);
|
||||||
|
|
||||||
|
return $statement->rowCount() > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function statement(string $sql, array $bindings = []): bool
|
||||||
|
{
|
||||||
|
return $this->pdo->prepare($sql)->execute($bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function beginTransaction(): bool
|
||||||
|
{
|
||||||
|
return $this->pdo->beginTransaction();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function commitTransaction(): bool
|
||||||
|
{
|
||||||
|
return $this->pdo->commit();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function rollBackTransaction(): bool
|
||||||
|
{
|
||||||
|
return $this->pdo->rollBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function insertIntoTableFromSql(string $table, string $sql, array $bindings = []): bool
|
||||||
|
{
|
||||||
|
$createSql = "INSERT INTO `{$table}` {$sql}";
|
||||||
|
|
||||||
|
return $this->pdo->prepare($createSql)->execute($bindings);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function truncateTable(string $table): bool
|
||||||
|
{
|
||||||
|
return $this->pdo->prepare("TRUNCATE TABLE {$table}")->execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function lastInsertId($name = null): int
|
||||||
|
{
|
||||||
|
return $this->pdo->lastInsertId($name);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getVersion(): string
|
||||||
|
{
|
||||||
|
return $this->pdo
|
||||||
|
->query('SELECT VERSION()')
|
||||||
|
->fetch(PDO::FETCH_COLUMN);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,188 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\QueryBuilder\Grammars;
|
||||||
|
|
||||||
|
use BadMethodCallException;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\RawExpression;
|
||||||
|
|
||||||
|
abstract class Grammar
|
||||||
|
{
|
||||||
|
protected $compiled = [
|
||||||
|
'columns' => [],
|
||||||
|
'from' => [],
|
||||||
|
'joins' => [],
|
||||||
|
'wheres' => [],
|
||||||
|
'orders' => [],
|
||||||
|
'limit' => [],
|
||||||
|
'offset' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
private function resetCompiled(): void
|
||||||
|
{
|
||||||
|
$this->compiled = [
|
||||||
|
'columns' => [],
|
||||||
|
'from' => [],
|
||||||
|
'joins' => [],
|
||||||
|
'wheres' => [],
|
||||||
|
'orders' => [],
|
||||||
|
'limit' => [],
|
||||||
|
'offset' => [],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compileComponents(Builder $builder): string
|
||||||
|
{
|
||||||
|
$this->resetCompiled();
|
||||||
|
|
||||||
|
foreach (array_keys($this->compiled) as $component) {
|
||||||
|
$method = 'compile' . ucfirst($component);
|
||||||
|
|
||||||
|
if (!method_exists($this, $method)) {
|
||||||
|
throw new BadMethodCallException("Method '{$method}' does not exist.");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! empty($builder->{$component})) {
|
||||||
|
$this->compiled[$component] = $this->{$method}(
|
||||||
|
$builder,
|
||||||
|
$builder->{$component},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->concatCompiled($this->compiled);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function concatCompiled(array $compiled): string
|
||||||
|
{
|
||||||
|
$array = [];
|
||||||
|
|
||||||
|
foreach (array_keys($this->compiled) as $component) {
|
||||||
|
$array[] = $compiled[$component] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(' ', array_filter($array));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compileColumns(Builder $builder, $columns): string
|
||||||
|
{
|
||||||
|
if ($columns === '*' || count($columns) === 0) {
|
||||||
|
return 'SELECT *';
|
||||||
|
}
|
||||||
|
|
||||||
|
$columns = array_map(static function ($item) {
|
||||||
|
if ($item instanceof RawExpression) {
|
||||||
|
return $item->getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $item['column'] . ($item['as'] !== null ? ' AS ' . $item['as'] : '');
|
||||||
|
}, $columns);
|
||||||
|
|
||||||
|
$distinct = $builder->distinct ? 'DISTINCT ' : '';
|
||||||
|
|
||||||
|
return "SELECT {$distinct}" . implode(', ', $columns);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compileWheres(Builder $builder, $wheres, string $prefix = 'WHERE '): string
|
||||||
|
{
|
||||||
|
if (!$wheres) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$compiledConditions = [];
|
||||||
|
|
||||||
|
$counter = 0;
|
||||||
|
foreach ($wheres as $condition) {
|
||||||
|
$compiledConditions[] = ($counter > 0 ? mb_strtoupper($condition['boolean']) . ' ' : '')
|
||||||
|
. $this->{'where' . $condition['type']}($condition);
|
||||||
|
$counter++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $prefix . implode(' ', $compiledConditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereBasic($condition): string
|
||||||
|
{
|
||||||
|
return $condition['column'] . ' ' . $condition['operator'] . ' ?';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereRaw($condition): string
|
||||||
|
{
|
||||||
|
return $condition['expression'];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereNull($condition): string
|
||||||
|
{
|
||||||
|
return $condition['column'] . ' IS NULL';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereNotNull($condition): string
|
||||||
|
{
|
||||||
|
return $condition['column'] . ' IS NOT NULL';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereNested($condition): string
|
||||||
|
{
|
||||||
|
return '(' . $this->compileWheres($condition['query'], $condition['query']->wheres, '') . ')';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereBetween($condition): string
|
||||||
|
{
|
||||||
|
return $condition['column'] . ' BETWEEN ? AND ?';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compileOrders(Builder $builder, array $orders): string
|
||||||
|
{
|
||||||
|
return 'ORDER BY ' . implode(
|
||||||
|
', ',
|
||||||
|
array_map(
|
||||||
|
static function ($order) {
|
||||||
|
return $order['column'] . ' ' . $order['direction'];
|
||||||
|
},
|
||||||
|
$orders
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compileFrom(Builder $builder, array $from): string
|
||||||
|
{
|
||||||
|
return 'FROM ' . $from['table'] . ($from['as'] !== null ? ' AS ' . $from['as'] : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compileLimit(Builder $builder, $limit): string
|
||||||
|
{
|
||||||
|
return "LIMIT $limit";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compileOffset(Builder $builder, $offset): string
|
||||||
|
{
|
||||||
|
return "OFFSET $offset";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function compileJoins(Builder $builder, $joins): string
|
||||||
|
{
|
||||||
|
return implode(' ', array_map(function (JoinClause $join) use ($builder) {
|
||||||
|
$conditions = '';
|
||||||
|
if ($join->wheres) {
|
||||||
|
$conditions = ' AND ' . $this->compileWheres($builder, $join->wheres, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return mb_strtoupper(
|
||||||
|
$join->type
|
||||||
|
) . " JOIN $join->table ON $join->first $join->operator $join->second" . $conditions;
|
||||||
|
}, $joins));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function whereIn($condition): string
|
||||||
|
{
|
||||||
|
if (! $condition['value']) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
$inValues = str_repeat('?, ', count($condition['value']) - 1) . '?';
|
||||||
|
$inOperator = $condition['operator'];
|
||||||
|
|
||||||
|
return $condition['column'] . " $inOperator (" . $inValues . ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\QueryBuilder\Grammars;
|
||||||
|
|
||||||
|
class MySqlGrammar extends Grammar
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\QueryBuilder;
|
||||||
|
|
||||||
|
class JoinClause extends Builder
|
||||||
|
{
|
||||||
|
public $table;
|
||||||
|
public $type;
|
||||||
|
public $first;
|
||||||
|
public $second;
|
||||||
|
public $operator;
|
||||||
|
|
||||||
|
public $bindings = [
|
||||||
|
'where' => [],
|
||||||
|
];
|
||||||
|
|
||||||
|
public function __construct(Builder $parent, string $type, string $table)
|
||||||
|
{
|
||||||
|
$this->type = $type;
|
||||||
|
$this->table = $table;
|
||||||
|
|
||||||
|
parent::__construct($parent->getConnection(), $parent->getGrammar());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function on($first, $operator, $second): JoinClause
|
||||||
|
{
|
||||||
|
$this->first = $first;
|
||||||
|
$this->operator = $operator;
|
||||||
|
$this->second = $second;
|
||||||
|
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\QueryBuilder;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class QueryBuilderException extends RuntimeException
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\QueryBuilder;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Container\Container;
|
||||||
|
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\Connections\MySqlConnection;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\Grammars\MySqlGrammar;
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
class QueryBuilderServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->container->bind(ConnectionInterface::class, function (Container $container) {
|
||||||
|
$host = $container->getConfigValue('db.host');
|
||||||
|
$username = $container->getConfigValue('db.username');
|
||||||
|
$password = $container->getConfigValue('db.password');
|
||||||
|
$port = $container->getConfigValue('db.port');
|
||||||
|
$dbName = $container->getConfigValue('db.database');
|
||||||
|
|
||||||
|
$dsn = "mysql:host=$host;port=$port;dbname=$dbName";
|
||||||
|
|
||||||
|
return new MySqlConnection(
|
||||||
|
new PDO($dsn, $username, $password)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
$this->container->bind(Builder::class, function (Container $container) {
|
||||||
|
return new Builder(
|
||||||
|
$container->get(ConnectionInterface::class),
|
||||||
|
$container->get(MySqlGrammar::class)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\QueryBuilder;
|
||||||
|
|
||||||
|
class QueryResult
|
||||||
|
{
|
||||||
|
private $row;
|
||||||
|
private $rows;
|
||||||
|
private $numRows;
|
||||||
|
|
||||||
|
public function __construct(
|
||||||
|
$row,
|
||||||
|
$rows,
|
||||||
|
$numRows
|
||||||
|
) {
|
||||||
|
$this->row = $row;
|
||||||
|
$this->rows = $rows;
|
||||||
|
$this->numRows = $numRows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getRow()
|
||||||
|
{
|
||||||
|
return $this->row;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getRows()
|
||||||
|
{
|
||||||
|
return $this->rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function getNumRows(): int
|
||||||
|
{
|
||||||
|
return $this->numRows;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\QueryBuilder;
|
||||||
|
|
||||||
|
class RawExpression
|
||||||
|
{
|
||||||
|
private $value;
|
||||||
|
|
||||||
|
public function __construct(string $value)
|
||||||
|
{
|
||||||
|
$this->value = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getValue(): string
|
||||||
|
{
|
||||||
|
return $this->value;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Router;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Container\Container;
|
||||||
|
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||||
|
|
||||||
|
class RouteServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->container->singleton(Router::class, function (Container $container) {
|
||||||
|
return new Router();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
38
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Router/Router.php
Executable file
38
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Router/Router.php
Executable file
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Router;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Exceptions\ActionNotFoundException;
|
||||||
|
|
||||||
|
class Router
|
||||||
|
{
|
||||||
|
private array $routes;
|
||||||
|
|
||||||
|
public function __construct(array $routes = [])
|
||||||
|
{
|
||||||
|
$this->routes = $routes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadRoutesFromFile(string $file): void
|
||||||
|
{
|
||||||
|
$this->routes = include $file;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param $action
|
||||||
|
* @return string[]
|
||||||
|
* @throws ActionNotFoundException
|
||||||
|
*/
|
||||||
|
public function resolve($action): array
|
||||||
|
{
|
||||||
|
if (!$action) {
|
||||||
|
throw new ActionNotFoundException('No action provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($this->routes[$action])) {
|
||||||
|
return $this->routes[$action];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ActionNotFoundException('Action "' . $action . '" not found.');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Settings;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
||||||
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
use JsonException;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class DatabaseUserSettings implements UserSettingsInterface
|
||||||
|
{
|
||||||
|
private $builder;
|
||||||
|
private $database;
|
||||||
|
private $allSettings = [];
|
||||||
|
|
||||||
|
public function __construct(Builder $builder, ConnectionInterface $database)
|
||||||
|
{
|
||||||
|
$this->builder = $builder;
|
||||||
|
$this->database = $database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getAll(): array
|
||||||
|
{
|
||||||
|
if ($this->allSettings) {
|
||||||
|
return $this->allSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
$row = $this->builder
|
||||||
|
->select(['settings_json'])
|
||||||
|
->from(config('tables.settings'))
|
||||||
|
->where('id', '=', 1)
|
||||||
|
->firstOrNull();
|
||||||
|
|
||||||
|
if (! $row || empty($row['settings_json'])) {
|
||||||
|
throw new RuntimeException('Database settings not found. Please reinstall the module.');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$decoded = json_decode($row['settings_json'], true, 512, JSON_THROW_ON_ERROR);
|
||||||
|
} catch (JsonException $e) {
|
||||||
|
throw new RuntimeException('Database settings invalid format: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->allSettings = $decoded;
|
||||||
|
|
||||||
|
return $decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(array $userSettings): void
|
||||||
|
{
|
||||||
|
$settingsTable = config('tables.settings');
|
||||||
|
try {
|
||||||
|
$currentSettings = $this->getAll();
|
||||||
|
$updatedSettings = array_replace_recursive($currentSettings, $userSettings);
|
||||||
|
|
||||||
|
$encoded = json_encode($updatedSettings, JSON_THROW_ON_ERROR);
|
||||||
|
|
||||||
|
$sql = <<<SQL
|
||||||
|
INSERT INTO `{$settingsTable}`
|
||||||
|
(id, settings_json)
|
||||||
|
VALUES
|
||||||
|
(1, '{$encoded}')
|
||||||
|
ON DUPLICATE KEY UPDATE
|
||||||
|
settings_json = VALUES(settings_json)
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
$this->database->statement($sql, [$encoded]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
throw new RuntimeException('Could not update settings: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function get(string $key, $default = null)
|
||||||
|
{
|
||||||
|
$all = $this->getAll();
|
||||||
|
|
||||||
|
return Arr::get($all, $key, $default);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function safeGet(string $key, $default = null)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
return $this->get($key, $default);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Settings;
|
||||||
|
|
||||||
|
interface UserSettingsInterface
|
||||||
|
{
|
||||||
|
public function get(string $key, $default = null);
|
||||||
|
public function getAll(): array;
|
||||||
|
public function update(array $userSettings): void;
|
||||||
|
public function safeGet(string $key, $default = null);
|
||||||
|
}
|
||||||
131
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Support/Arr.php
Executable file
131
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Support/Arr.php
Executable file
@@ -0,0 +1,131 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Support;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
class Arr
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Transform an array to use a specified field as keys.
|
||||||
|
*
|
||||||
|
* @param array $array Input array of items.
|
||||||
|
* @param string $keyField Field name to be used as keys.
|
||||||
|
* @return array Transformed array with specified field as keys.
|
||||||
|
* @throws InvalidArgumentException If the key field is missing in any item.
|
||||||
|
*/
|
||||||
|
public static function keyByField(array $array, string $keyField): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
foreach ($array as $item) {
|
||||||
|
if (!array_key_exists($keyField, $item)) {
|
||||||
|
throw new InvalidArgumentException("Key field '{$keyField}' is missing in one of the items.");
|
||||||
|
}
|
||||||
|
$result[$item[$keyField]] = $item;
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Group array by a specific key and collect values into sub-arrays.
|
||||||
|
*
|
||||||
|
* @param array $data Array to group.
|
||||||
|
* @param string $groupKey Key to group by.
|
||||||
|
* @param string $valueKey Key to collect values from.
|
||||||
|
* @return array Grouped array.
|
||||||
|
*/
|
||||||
|
public static function groupByKey(array $data, string $groupKey, string $valueKey): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
foreach ($data as $item) {
|
||||||
|
$result[$item[$groupKey]][] = $item[$valueKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get(array $items, string $key, $default = null)
|
||||||
|
{
|
||||||
|
if (!$items) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (array_key_exists($key, $items)) {
|
||||||
|
return $items[$key];
|
||||||
|
}
|
||||||
|
|
||||||
|
$segments = explode('.', $key);
|
||||||
|
foreach ($segments as $segment) {
|
||||||
|
if (!is_array($items) || !array_key_exists($segment, $items)) {
|
||||||
|
return $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
$items = &$items[$segment];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $items;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function set(array &$array, string $key, $value): void
|
||||||
|
{
|
||||||
|
$keys = explode('.', $key);
|
||||||
|
foreach ($keys as $k) {
|
||||||
|
if (!isset($array[$k]) || !is_array($array[$k])) {
|
||||||
|
$array[$k] = [];
|
||||||
|
}
|
||||||
|
$array = &$array[$k];
|
||||||
|
}
|
||||||
|
$array = $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function unset(array &$array, string $key): void
|
||||||
|
{
|
||||||
|
$keys = explode('.', $key);
|
||||||
|
while (count($keys) > 1) {
|
||||||
|
$k = array_shift($keys);
|
||||||
|
if (!isset($array[$k]) || !is_array($array[$k])) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$array = &$array[$k];
|
||||||
|
}
|
||||||
|
unset($array[array_shift($keys)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function find(array $array, callable $callback)
|
||||||
|
{
|
||||||
|
foreach ($array as $key => $value) {
|
||||||
|
if ($callback($value, $key) === true) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function pluck(array $value, string $field): array
|
||||||
|
{
|
||||||
|
return array_map(
|
||||||
|
static function ($item) use ($field) {
|
||||||
|
if (is_object($item)) {
|
||||||
|
return $item->{$field};
|
||||||
|
}
|
||||||
|
|
||||||
|
return $item[$field];
|
||||||
|
},
|
||||||
|
$value
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function mergeArraysRecursively(array $base, array $override): array
|
||||||
|
{
|
||||||
|
foreach ($override as $key => $value) {
|
||||||
|
if (isset($base[$key]) && is_array($base[$key]) && is_array($value)) {
|
||||||
|
$base[$key] = static::mergeArraysRecursively($base[$key], $value);
|
||||||
|
} else {
|
||||||
|
$base[$key] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $base;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Support;
|
||||||
|
|
||||||
|
class ExecutionTimeProfiler
|
||||||
|
{
|
||||||
|
private $startTime;
|
||||||
|
private $checkpoints = [];
|
||||||
|
|
||||||
|
public function start(): void
|
||||||
|
{
|
||||||
|
$this->startTime = microtime(true);
|
||||||
|
$this->checkpoints = [['label' => 'start', 'time' => $this->startTime]];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function addCheckpoint(string $label): void
|
||||||
|
{
|
||||||
|
$currentTime = microtime(true);
|
||||||
|
$this->checkpoints[] = [
|
||||||
|
'label' => $label,
|
||||||
|
'time' => $currentTime
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function stop(): array
|
||||||
|
{
|
||||||
|
$endTime = microtime(true);
|
||||||
|
$this->checkpoints[] = ['label' => 'End', 'time' => $endTime];
|
||||||
|
|
||||||
|
$result = [
|
||||||
|
'total_execution_time_ms' => round(($endTime - $this->startTime) * 1000, 2),
|
||||||
|
'checkpoints' => []
|
||||||
|
];
|
||||||
|
|
||||||
|
$total = count($this->checkpoints);
|
||||||
|
|
||||||
|
for ($i = 1; $i < $total; $i++) {
|
||||||
|
$prev = $this->checkpoints[$i - 1];
|
||||||
|
$current = $this->checkpoints[$i];
|
||||||
|
|
||||||
|
$result['checkpoints'][] = [
|
||||||
|
'label' => $current['label'],
|
||||||
|
'elapsed_since_previous_ms' => round(($current['time'] - $prev['time']) * 1000, 2)
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Support;
|
||||||
|
|
||||||
|
class PaginationHelper
|
||||||
|
{
|
||||||
|
public static function calculateLastPage(int $totalRecords, int $perPage): int
|
||||||
|
{
|
||||||
|
return (int)ceil($totalRecords / $perPage);
|
||||||
|
}
|
||||||
|
}
|
||||||
69
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Support/Utils.php
Executable file
69
module/oc_telegram_shop/upload/oc_telegram_shop/framework/Support/Utils.php
Executable file
@@ -0,0 +1,69 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Support;
|
||||||
|
|
||||||
|
use InvalidArgumentException;
|
||||||
|
|
||||||
|
class Utils
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array|string[]
|
||||||
|
*/
|
||||||
|
public static function ucsplit(string $string): array
|
||||||
|
{
|
||||||
|
return preg_split('/(?=\p{Lu})/u', $string, -1, PREG_SPLIT_NO_EMPTY) ?? [];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function safeJsonDecode(string $content)
|
||||||
|
{
|
||||||
|
$decoded = json_decode($content, true);
|
||||||
|
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
throw new InvalidArgumentException('JSON error: ' . json_last_error_msg());
|
||||||
|
}
|
||||||
|
|
||||||
|
return $decoded;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @psalm-return list<mixed>
|
||||||
|
*/
|
||||||
|
public static function arrayFlatten(array $array, $depth = INF): array
|
||||||
|
{
|
||||||
|
$result = [];
|
||||||
|
|
||||||
|
foreach ($array as $item) {
|
||||||
|
if (! is_array($item)) {
|
||||||
|
$result[] = $item;
|
||||||
|
} else {
|
||||||
|
$values = $depth === 1
|
||||||
|
? array_values($item)
|
||||||
|
: static::arrayFlatten($item, $depth - 1);
|
||||||
|
|
||||||
|
foreach ($values as $value) {
|
||||||
|
$result[] = $value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return never
|
||||||
|
*/
|
||||||
|
public static function dd()
|
||||||
|
{
|
||||||
|
$args = func_get_args();
|
||||||
|
echo '<pre>';
|
||||||
|
/** @psalm-suppress ForbiddenCode */
|
||||||
|
var_dump(...$args);
|
||||||
|
echo '</pre>';
|
||||||
|
die();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function strContains(string $haystack, string $needle): bool
|
||||||
|
{
|
||||||
|
return $needle === '' || strpos($haystack, $needle) !== false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Application;
|
||||||
|
use Openguru\OpenCartFramework\Config\Settings;
|
||||||
|
use Openguru\OpenCartFramework\Support\Utils;
|
||||||
|
|
||||||
|
if (!function_exists('table')) {
|
||||||
|
function db_table(string $name): string
|
||||||
|
{
|
||||||
|
$prefix = Application::getInstance()->getConfigValue('db.prefix');
|
||||||
|
|
||||||
|
return $prefix . $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('column')) {
|
||||||
|
function db_column($column): string
|
||||||
|
{
|
||||||
|
if (strpos($column, '.') !== false) {
|
||||||
|
[$table, $column] = explode('.', $column, 2);
|
||||||
|
|
||||||
|
if ($table === '' || $column === '') {
|
||||||
|
throw new InvalidArgumentException('Invalid column reference: ' . $column);
|
||||||
|
}
|
||||||
|
|
||||||
|
return db_table($table) . '.' . $column;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('resources_path')) {
|
||||||
|
function resources_path(string $path = ''): string
|
||||||
|
{
|
||||||
|
return BP_BASE_PATH . '/resources/' . $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('base_path')) {
|
||||||
|
function base_path(string $path = ''): string
|
||||||
|
{
|
||||||
|
return BP_BASE_PATH . '/' . $path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!function_exists('config')) {
|
||||||
|
function config(string $key, $default = null)
|
||||||
|
{
|
||||||
|
return Application::getInstance()->get(Settings::class)->get($key, $default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! function_exists('dd')) {
|
||||||
|
function dd(): void
|
||||||
|
{
|
||||||
|
Utils::dd(func_get_args());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Translator;
|
||||||
|
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class Translator implements TranslatorInterface
|
||||||
|
{
|
||||||
|
private $language;
|
||||||
|
private $translations;
|
||||||
|
|
||||||
|
public function __construct(string $language, array $translations = [])
|
||||||
|
{
|
||||||
|
$this->language = $language;
|
||||||
|
$this->translations = $translations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadTranslationsFromFolder(string $directory): void
|
||||||
|
{
|
||||||
|
if (!is_dir($directory)) {
|
||||||
|
throw new RuntimeException('Translations folder not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
$filename = $directory . "/{$this->language}.php";
|
||||||
|
if (!is_file($filename)) {
|
||||||
|
throw new RuntimeException("Translation file for language '{$this->language}' not found.");
|
||||||
|
}
|
||||||
|
|
||||||
|
$translations = include $filename;
|
||||||
|
$this->translations = array_merge($this->translations, $translations);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function translate(string $key, array $params = []): string
|
||||||
|
{
|
||||||
|
if (isset($this->translations[$key])) {
|
||||||
|
return $this->substitute($this->translations[$key], $params);
|
||||||
|
}
|
||||||
|
|
||||||
|
return $key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTranslations(): array
|
||||||
|
{
|
||||||
|
return $this->translations;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLanguage(): string
|
||||||
|
{
|
||||||
|
return $this->language;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function substitute($text, array $parameters): string
|
||||||
|
{
|
||||||
|
if (!$parameters) {
|
||||||
|
return $text;
|
||||||
|
}
|
||||||
|
|
||||||
|
$search = array_map(static function ($param) {
|
||||||
|
return '{' . $param . '}';
|
||||||
|
}, array_keys($parameters));
|
||||||
|
|
||||||
|
return str_replace($search, array_values($parameters), $text);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Translator;
|
||||||
|
|
||||||
|
interface TranslatorInterface
|
||||||
|
{
|
||||||
|
public function loadTranslationsFromFolder(string $directory): void;
|
||||||
|
|
||||||
|
public function translate(string $key, array $params = []): string;
|
||||||
|
|
||||||
|
public function getTranslations(): array;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Translator;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Container\Container;
|
||||||
|
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||||
|
|
||||||
|
class TranslatorServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->container->singleton(TranslatorInterface::class, function (Container $container) {
|
||||||
|
$language = $container->getConfigValue('lang');
|
||||||
|
$translator = new Translator($language);
|
||||||
|
$translator->loadTranslationsFromFolder(resources_path('/translations'));
|
||||||
|
return $translator;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Openguru\OpenCartFramework\Validator;
|
||||||
|
|
||||||
|
class Validator
|
||||||
|
{
|
||||||
|
private $input;
|
||||||
|
private $rules;
|
||||||
|
|
||||||
|
public function __construct(array $input, array $rules)
|
||||||
|
{
|
||||||
|
$this->input = $input;
|
||||||
|
$this->rules = $rules;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function validate(): bool
|
||||||
|
{
|
||||||
|
foreach ($this->rules as $name => $rule) {
|
||||||
|
$components = explode('|', $rule);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
26
module/oc_telegram_shop/upload/oc_telegram_shop/src/ApplicationFactory.php
Executable file
26
module/oc_telegram_shop/upload/oc_telegram_shop/src/ApplicationFactory.php
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App;
|
||||||
|
|
||||||
|
use App\ServiceProviders\AppServiceProvider;
|
||||||
|
use Openguru\OpenCartFramework\Application;
|
||||||
|
use Openguru\OpenCartFramework\Cache\CacheServiceProvider;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\QueryBuilderServiceProvider;
|
||||||
|
use Openguru\OpenCartFramework\Router\RouteServiceProvider;
|
||||||
|
use Openguru\OpenCartFramework\Support\Arr;
|
||||||
|
|
||||||
|
class ApplicationFactory
|
||||||
|
{
|
||||||
|
public static function create(array $config): Application
|
||||||
|
{
|
||||||
|
$defaultConfig = require __DIR__ . '/config.php';
|
||||||
|
|
||||||
|
return (new Application(Arr::mergeArraysRecursively($defaultConfig, $config)))
|
||||||
|
->withServiceProviders([
|
||||||
|
QueryBuilderServiceProvider::class,
|
||||||
|
CacheServiceProvider::class,
|
||||||
|
RouteServiceProvider::class,
|
||||||
|
AppServiceProvider::class
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Handlers;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\Builder;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
||||||
|
|
||||||
|
class HelloWorldHandler
|
||||||
|
{
|
||||||
|
private Builder $queryBuilder;
|
||||||
|
|
||||||
|
public function __construct(Builder $queryBuilder)
|
||||||
|
{
|
||||||
|
$this->queryBuilder = $queryBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(): JsonResponse
|
||||||
|
{
|
||||||
|
$languageId = 1;
|
||||||
|
|
||||||
|
$products = $this->queryBuilder->newQuery()
|
||||||
|
->select([
|
||||||
|
'products.product_id' => 'product_id',
|
||||||
|
'products.quantity' => 'product_quantity',
|
||||||
|
'product_description.name' => 'product_name',
|
||||||
|
'products.price' => 'product_price',
|
||||||
|
])
|
||||||
|
->from(db_table('product'), 'products')
|
||||||
|
->join(
|
||||||
|
db_table('product_description') . ' AS product_description',
|
||||||
|
function (JoinClause $join) use ($languageId) {
|
||||||
|
$join->on('products.product_id', '=', 'product_description.product_id')
|
||||||
|
->where('product_description.language_id', '=', $languageId);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'data' => array_map(function ($product) {
|
||||||
|
return [
|
||||||
|
'product_id' => (int) $product['product_id'],
|
||||||
|
'product_quantity' => (int) $product['product_quantity'],
|
||||||
|
'product_name' => $product['product_name'],
|
||||||
|
'product_price' => (float) $product['product_price'],
|
||||||
|
];
|
||||||
|
}, $products),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Handlers;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||||
|
use Openguru\OpenCartFramework\Http\Request;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
||||||
|
|
||||||
|
class OrderCreateHandler
|
||||||
|
{
|
||||||
|
private ConnectionInterface $database;
|
||||||
|
|
||||||
|
public function __construct(ConnectionInterface $database)
|
||||||
|
{
|
||||||
|
$this->database = $database;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$now = date('Y-m-d H:i:s');
|
||||||
|
$storeId = 0;
|
||||||
|
$storeName = 'Ваш магазин';
|
||||||
|
|
||||||
|
$sql = <<<SQL
|
||||||
|
INSERT INTO oc_order
|
||||||
|
(
|
||||||
|
store_id,
|
||||||
|
store_name,
|
||||||
|
firstname,
|
||||||
|
lastname,
|
||||||
|
shipping_code,
|
||||||
|
total,
|
||||||
|
order_status_id,
|
||||||
|
currency_code,
|
||||||
|
currency_value,
|
||||||
|
date_added,
|
||||||
|
date_modified
|
||||||
|
)
|
||||||
|
VALUES
|
||||||
|
(
|
||||||
|
$storeId,
|
||||||
|
'$storeName',
|
||||||
|
'John',
|
||||||
|
'Doe',
|
||||||
|
'flat.flat',
|
||||||
|
99.9999,
|
||||||
|
1,
|
||||||
|
'RUB',
|
||||||
|
1,
|
||||||
|
'$now',
|
||||||
|
'$now'
|
||||||
|
)
|
||||||
|
SQL;
|
||||||
|
|
||||||
|
$result = $this->database->statement($sql);
|
||||||
|
|
||||||
|
|
||||||
|
return new JsonResponse([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Handlers;
|
||||||
|
|
||||||
|
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\QueryBuilder\Builder;
|
||||||
|
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
|
||||||
|
|
||||||
|
class ProductsHandler
|
||||||
|
{
|
||||||
|
private Builder $queryBuilder;
|
||||||
|
private Currency $currency;
|
||||||
|
private Tax $tax;
|
||||||
|
private Settings $settings;
|
||||||
|
|
||||||
|
public function __construct(Builder $queryBuilder, Currency $currency, Tax $tax, Settings $settings)
|
||||||
|
{
|
||||||
|
$this->queryBuilder = $queryBuilder;
|
||||||
|
$this->currency = $currency;
|
||||||
|
$this->tax = $tax;
|
||||||
|
$this->settings = $settings;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function handle(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$languageId = 1;
|
||||||
|
$page = $request->get('page', 1);
|
||||||
|
$perPage = $request->get('perPage', 20);
|
||||||
|
|
||||||
|
$products = $this->queryBuilder->newQuery()
|
||||||
|
->select([
|
||||||
|
'products.product_id' => 'product_id',
|
||||||
|
'products.quantity' => 'product_quantity',
|
||||||
|
'product_description.name' => 'product_name',
|
||||||
|
'products.price' => 'price',
|
||||||
|
'products.image' => 'product_image',
|
||||||
|
'products.tax_class_id' => 'tax_class_id',
|
||||||
|
])
|
||||||
|
->from(db_table('product'), 'products')
|
||||||
|
->join(
|
||||||
|
db_table('product_description') . ' AS product_description',
|
||||||
|
function (JoinClause $join) use ($languageId) {
|
||||||
|
$join->on('products.product_id', '=', 'product_description.product_id')
|
||||||
|
->where('product_description.language_id', '=', $languageId);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
->forPage($page, $perPage)
|
||||||
|
->get();
|
||||||
|
|
||||||
|
/** @var Closure $resize */
|
||||||
|
$resize = Application::getInstance()->get('image_resize');
|
||||||
|
|
||||||
|
return new JsonResponse([
|
||||||
|
'data' => array_map(function ($product) use ($resize) {
|
||||||
|
if ($product['product_image']) {
|
||||||
|
$image = $resize($product['product_image'], 500, 500);
|
||||||
|
} else {
|
||||||
|
$image = $resize('placeholder.png', 500, 500);
|
||||||
|
}
|
||||||
|
|
||||||
|
$price = $this->currency->format(
|
||||||
|
$this->tax->calculate(
|
||||||
|
$product['price'],
|
||||||
|
$product['tax_class_id'],
|
||||||
|
$this->settings->get('oc_config_tax'),
|
||||||
|
),
|
||||||
|
$this->settings->get('oc_currency'),
|
||||||
|
);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'id' => (int)$product['product_id'],
|
||||||
|
'product_quantity' => (int)$product['product_quantity'],
|
||||||
|
'name' => $product['product_name'],
|
||||||
|
'price' => $price,
|
||||||
|
'image' => $image,
|
||||||
|
];
|
||||||
|
}, $products),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\ServiceProviders;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||||
|
use Openguru\OpenCartFramework\Router\Router;
|
||||||
|
|
||||||
|
class AppServiceProvider extends ServiceProvider
|
||||||
|
{
|
||||||
|
public function register(): void
|
||||||
|
{
|
||||||
|
$this->container->get(Router::class)->loadRoutesFromFile(__DIR__ . '/../routes.php');
|
||||||
|
}
|
||||||
|
}
|
||||||
42
module/oc_telegram_shop/upload/oc_telegram_shop/src/config.php
Executable file
42
module/oc_telegram_shop/upload/oc_telegram_shop/src/config.php
Executable file
@@ -0,0 +1,42 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
return [
|
||||||
|
|
||||||
|
'config_timezone' => 'UTC',
|
||||||
|
|
||||||
|
'lang' => 'en-gb',
|
||||||
|
'language_id' => 1,
|
||||||
|
'auth_user_id' => 0,
|
||||||
|
|
||||||
|
'base_url' => 'http://localhost',
|
||||||
|
|
||||||
|
'search_cache_seconds' => 60,
|
||||||
|
|
||||||
|
'chunk_size' => 100,
|
||||||
|
|
||||||
|
'retry_count' => 3,
|
||||||
|
|
||||||
|
'activity_log_retention_limit' => 5000,
|
||||||
|
|
||||||
|
'tables' => [
|
||||||
|
'selected_products' => 'bp_selected_products',
|
||||||
|
'simulation_results' => 'bp_simulation_results',
|
||||||
|
'activity_logs' => 'bp_activity_logs',
|
||||||
|
'cache' => 'bp_cache',
|
||||||
|
'task_progress' => 'bp_task_progress',
|
||||||
|
'task_steps' => 'bp_task_steps',
|
||||||
|
'search_results' => 'bp_search_results',
|
||||||
|
'settings' => 'bp_settings',
|
||||||
|
],
|
||||||
|
|
||||||
|
'db' => [
|
||||||
|
'host' => 'localhost',
|
||||||
|
'database' => 'not_set',
|
||||||
|
'username' => 'not_set',
|
||||||
|
'password' => 'not_set',
|
||||||
|
],
|
||||||
|
|
||||||
|
'logs' => [
|
||||||
|
'path' => 'not_set',
|
||||||
|
],
|
||||||
|
];
|
||||||
10
module/oc_telegram_shop/upload/oc_telegram_shop/src/routes.php
Executable file
10
module/oc_telegram_shop/upload/oc_telegram_shop/src/routes.php
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Handlers\HelloWorldHandler;
|
||||||
|
use App\Handlers\OrderCreateHandler;
|
||||||
|
use App\Handlers\ProductsHandler;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'products' => [ProductsHandler::class, 'handle'],
|
||||||
|
'order_create' => [OrderCreateHandler::class, 'handle'],
|
||||||
|
];
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
set -e
|
|
||||||
|
|
||||||
VERSION="1.0.0"
|
|
||||||
MODULE_NAME="oc_telegram_shop"
|
|
||||||
CURRENT_DIR="$(dirname "$0")"
|
|
||||||
|
|
||||||
echo "Current dir: ${CURRENT_DIR}"
|
|
||||||
node -v
|
|
||||||
npm -v
|
|
||||||
|
|
||||||
RED='\033[0;31m'
|
|
||||||
GREEN='\033[0;32m'
|
|
||||||
BLUE='\033[0;34m'
|
|
||||||
NC='\033[0m' # No Color
|
|
||||||
|
|
||||||
echo -e "${BLUE}🔨 Starting build process...${NC}"
|
|
||||||
|
|
||||||
echo -e "${BLUE}Build frontend...${NC}"
|
|
||||||
rm -rf /app/module/oc_telegram_shop/upload/admin/view/javascript
|
|
||||||
rm -rf /app/module/oc_telegram_shop/upload/admin/view/integration.js
|
|
||||||
cd /app/frontend
|
|
||||||
npm install
|
|
||||||
NODE_ENV=production npm run build
|
|
||||||
rm -rf /app/module/oc_telegram_shop/upload/admin/view/javascript/oc_telegram_shop/.vite
|
|
||||||
rm -rf /app/module/oc_telegram_shop/upload/admin/view/javascript/oc_telegram_shop/oc_telegram_shop.js.map
|
|
||||||
rm -rf /app/module/oc_telegram_shop/upload/admin/view/javascript/oc_telegram_shop/favicon.ico
|
|
||||||
|
|
||||||
echo -e "${BLUE}📦 Packaging into .ocmod.zip...${NC}"
|
|
||||||
cd /app/module/oc_telegram_shop
|
|
||||||
zip -rqq "${MODULE_NAME}.ocmod.zip" .
|
|
||||||
mv "${MODULE_NAME}.ocmod.zip" /build
|
|
||||||
|
|
||||||
FINAL_FILE="/build/${MODULE_NAME}.ocmod.zip"
|
|
||||||
|
|
||||||
if [ -f "$FINAL_FILE" ]; then
|
|
||||||
FILE_SIZE=$(du -h "$FINAL_FILE" | cut -f1)
|
|
||||||
echo -e "${GREEN}✅ Build completed successfully!${NC}"
|
|
||||||
echo -e "${GREEN}🎉 Created: $FINAL_FILE ($FILE_SIZE)${NC}"
|
|
||||||
else
|
|
||||||
echo "❌ Error: Final build file not found: $FINAL_FILE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "${BLUE}🎯 Build artifact ready for installation!${NC}"
|
|
||||||
78
scripts/ci/build.sh
Executable file
78
scripts/ci/build.sh
Executable file
@@ -0,0 +1,78 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
MODULE_NAME="oc_telegram_shop"
|
||||||
|
CURRENT_DIR="$(dirname "$0")"
|
||||||
|
|
||||||
|
echo "Current dir: ${CURRENT_DIR}"
|
||||||
|
composer --version
|
||||||
|
node -v
|
||||||
|
npm -v
|
||||||
|
|
||||||
|
SRC_PATH="$1"
|
||||||
|
BUILD_PATH="${CURRENT_DIR}/build"
|
||||||
|
|
||||||
|
echo "🔨 Starting build process..."
|
||||||
|
echo "ℹ️ Source path: ${SRC_PATH}"
|
||||||
|
echo "ℹ️ Build path: ${BUILD_PATH}"
|
||||||
|
|
||||||
|
if [ -z "$BUILD_PATH" ]; then
|
||||||
|
echo "❌ Error: BUILD_PATH is required"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "$SRC_PATH" ]; then
|
||||||
|
echo "❌ Error: SRC_PATH is required"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Build SPA..."
|
||||||
|
cd "${SRC_PATH}/spa"
|
||||||
|
npm install
|
||||||
|
npm run build
|
||||||
|
cd - > /dev/null
|
||||||
|
|
||||||
|
echo "Install Composer dependencies."
|
||||||
|
cd "${SRC_PATH}/module/oc_telegram_shop/upload/oc_telegram_shop"
|
||||||
|
composer install \
|
||||||
|
--prefer-dist \
|
||||||
|
--no-dev \
|
||||||
|
--optimize-autoloader \
|
||||||
|
--no-interaction \
|
||||||
|
--no-cache
|
||||||
|
cd - > /dev/null
|
||||||
|
|
||||||
|
echo "📜 Creating Phar archive..."
|
||||||
|
rm -rf \
|
||||||
|
"${SRC_PATH}/module/oc_telegram_shop/upload/oc_telegram_shop/tests" \
|
||||||
|
"${SRC_PATH}/module/oc_telegram_shop/upload/oc_telegram_shop/phpunit.xml" \
|
||||||
|
"${SRC_PATH}/module/oc_telegram_shop/upload/oc_telegram_shop/psalm.xml" \
|
||||||
|
"${SRC_PATH}/module/oc_telegram_shop/upload/oc_telegram_shop/psalm-classes.php" \
|
||||||
|
"${SRC_PATH}/module/oc_telegram_shop/upload/oc_telegram_shop/psalm-stubs.php"
|
||||||
|
|
||||||
|
php -d phar.readonly=0 scripts/ci/create-phar.php "${SRC_PATH}/module/oc_telegram_shop/upload/oc_telegram_shop" "${MODULE_NAME}.phar"
|
||||||
|
|
||||||
|
echo "📦 Packaging into .ocmod.zip..."
|
||||||
|
mkdir -p "${SRC_PATH}/module/oc_telegram_shop/upload/system/library/oc_telegram_shop"
|
||||||
|
mv "${MODULE_NAME}.phar" "${SRC_PATH}/module/oc_telegram_shop/upload/system/library/oc_telegram_shop"
|
||||||
|
|
||||||
|
rm -rf \
|
||||||
|
"${SRC_PATH}/module/oc_telegram_shop/upload/oc_telegram_shop"
|
||||||
|
|
||||||
|
cd "${SRC_PATH}/module/oc_telegram_shop/"
|
||||||
|
zip -rqq "${MODULE_NAME}.ocmod.zip" .
|
||||||
|
mv "${MODULE_NAME}.ocmod.zip" "${BUILD_PATH}"
|
||||||
|
|
||||||
|
FINAL_FILE="${BUILD_PATH}/${MODULE_NAME}.ocmod.zip"
|
||||||
|
|
||||||
|
if [ -f "$FINAL_FILE" ]; then
|
||||||
|
FILE_SIZE=$(du -h "$FINAL_FILE" | cut -f1)
|
||||||
|
echo -e "✅ Build completed successfully!"
|
||||||
|
echo -e "🎉 Created: $FINAL_FILE ($FILE_SIZE)"
|
||||||
|
else
|
||||||
|
echo "❌ Error: Final build file not found: $FINAL_FILE"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "🎯 Build artifact ready for installation!"
|
||||||
37
scripts/ci/create-phar.php
Normal file
37
scripts/ci/create-phar.php
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
$sourcePath = $argv[1] ?? null;
|
||||||
|
$pharFile = $argv[2] ?? null;
|
||||||
|
|
||||||
|
if (! $sourcePath) {
|
||||||
|
throw new InvalidArgumentException('Source path must be provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! $pharFile) {
|
||||||
|
throw new InvalidArgumentException('Phar file must be provided');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove old file if exists
|
||||||
|
if (file_exists($pharFile)) {
|
||||||
|
unlink($pharFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create new Phar archive
|
||||||
|
$phar = new Phar($pharFile);
|
||||||
|
|
||||||
|
// Start buffering
|
||||||
|
$phar->startBuffering();
|
||||||
|
|
||||||
|
// Add files from source directory
|
||||||
|
|
||||||
|
$phar->buildFromDirectory($sourcePath);
|
||||||
|
|
||||||
|
// (Optional) Set stub file — main entry point
|
||||||
|
// $phar->setStub($phar->createDefaultStub('index.php'));
|
||||||
|
|
||||||
|
$phar->compressFiles(Phar::GZ);
|
||||||
|
|
||||||
|
// Stop buffering and write
|
||||||
|
$phar->stopBuffering();
|
||||||
|
|
||||||
|
echo "Phar created: $pharFile\n";
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
$module = 'oc_layout_pro';
|
$module = 'oc_telegram_shop';
|
||||||
|
|
||||||
$basePath = dirname(__DIR__) . '/';
|
$basePath = dirname(__DIR__) . '/';
|
||||||
|
|
||||||
|
|||||||
30
spa/package-lock.json
generated
30
spa/package-lock.json
generated
@@ -10,6 +10,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/vue": "^2.2.0",
|
"@heroicons/vue": "^2.2.0",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
|
"ofetch": "^1.4.1",
|
||||||
"vue": "^3.5.17"
|
"vue": "^3.5.17"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -1321,6 +1322,12 @@
|
|||||||
"url": "https://github.com/saadeghi/daisyui?sponsor=1"
|
"url": "https://github.com/saadeghi/daisyui?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/destr": {
|
||||||
|
"version": "2.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
|
||||||
|
"integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/detect-libc": {
|
"node_modules/detect-libc": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.4",
|
||||||
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
|
||||||
@@ -1767,6 +1774,12 @@
|
|||||||
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/node-fetch-native": {
|
||||||
|
"version": "1.6.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.6.tgz",
|
||||||
|
"integrity": "sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/node-releases": {
|
"node_modules/node-releases": {
|
||||||
"version": "2.0.19",
|
"version": "2.0.19",
|
||||||
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.19.tgz",
|
||||||
@@ -1784,6 +1797,17 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ofetch": {
|
||||||
|
"version": "1.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.4.1.tgz",
|
||||||
|
"integrity": "sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"destr": "^2.0.3",
|
||||||
|
"node-fetch-native": "^1.6.4",
|
||||||
|
"ufo": "^1.5.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/picocolors": {
|
"node_modules/picocolors": {
|
||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
|
||||||
@@ -1933,6 +1957,12 @@
|
|||||||
"url": "https://github.com/sponsors/SuperchupuDev"
|
"url": "https://github.com/sponsors/SuperchupuDev"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/ufo": {
|
||||||
|
"version": "1.6.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz",
|
||||||
|
"integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/update-browserslist-db": {
|
"node_modules/update-browserslist-db": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@heroicons/vue": "^2.2.0",
|
"@heroicons/vue": "^2.2.0",
|
||||||
"@tailwindcss/vite": "^4.1.11",
|
"@tailwindcss/vite": "^4.1.11",
|
||||||
|
"ofetch": "^1.4.1",
|
||||||
"vue": "^3.5.17"
|
"vue": "^3.5.17"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,63 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="relative flex justify-center items-center p-3">
|
<ProductList/>
|
||||||
<h1 class="text-gray-950 text-lg font-bold truncate">Демо Телеграм-магазин</h1>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="p-3">
|
|
||||||
<button
|
|
||||||
class="btn"
|
|
||||||
@click="drawerVisible = true"
|
|
||||||
>
|
|
||||||
Категории
|
|
||||||
<ChevronDownIcon class="w-5"/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-4 p-3 sm:grid-cols-2 md:grid-cols-3">
|
|
||||||
<ProductCard/>
|
|
||||||
<ProductCard/>
|
|
||||||
<ProductCard/>
|
|
||||||
<ProductCard/>
|
|
||||||
<ProductCard/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<BottomDrawer v-model="drawerVisible">
|
|
||||||
<ul class="list bg-base-100 rounded-box shadow-md">
|
|
||||||
|
|
||||||
<li class="p-4 pb-2 text-xs opacity-60 tracking-wide">Категории</li>
|
|
||||||
|
|
||||||
<li class="list-row">
|
|
||||||
<div><img class="size-10 rounded-box" src="https://img.daisyui.com/images/profile/demo/1@94.webp"/></div>
|
|
||||||
<div class="flex items-center">Dio Lupa</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
<li class="list-row">
|
|
||||||
<div><img class="size-10 rounded-box" src="https://img.daisyui.com/images/profile/demo/1@94.webp"/></div>
|
|
||||||
<div class="flex items-center">Dio Lupa</div>
|
|
||||||
</li>
|
|
||||||
<li class="list-row">
|
|
||||||
<div><img class="size-10 rounded-box" src="https://img.daisyui.com/images/profile/demo/1@94.webp"/></div>
|
|
||||||
<div class="flex items-center">Dio Lupa</div>
|
|
||||||
</li>
|
|
||||||
<li class="list-row">
|
|
||||||
<div><img class="size-10 rounded-box" src="https://img.daisyui.com/images/profile/demo/1@94.webp"/></div>
|
|
||||||
<div class="flex items-center">Dio Lupa</div>
|
|
||||||
</li>
|
|
||||||
|
|
||||||
</ul>
|
|
||||||
</BottomDrawer>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import ProductCard from "./components/ProductCard.vue";
|
import ProductList from "./components/ProductList.vue";
|
||||||
import { ChevronDownIcon } from '@heroicons/vue/24/outline';
|
|
||||||
import BottomDrawer from "./components/BottomDrawer.vue";
|
|
||||||
import {ref} from "vue";
|
|
||||||
|
|
||||||
const drawerVisible = ref(false);
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
|
|
||||||
</style>
|
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="bg-white rounded-lg shadow p-3">
|
<div class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
||||||
<img src="https://placehold.co/300x300" alt="Футболка" class="w-full h-auto rounded-md mb-2" />
|
<a v-for="product in products" :key="product.id" :href="product.href" class="group">
|
||||||
<h2 class="font-semibold text-base">Базовая футболка</h2>
|
<img :src="product.imageSrc" :alt="product.imageAlt" class="aspect-square w-full rounded-lg bg-gray-200 object-cover group-hover:opacity-75 xl:aspect-7/8" />
|
||||||
<p class="text-sm text-gray-500">RUB 6,000</p>
|
<h3 class="mt-4 text-sm text-gray-700">{{ product.name }}</h3>
|
||||||
|
<p class="mt-1 text-lg font-medium text-gray-900">{{ product.price }}</p>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
28
spa/src/components/ProductList.vue
Normal file
28
spa/src/components/ProductList.vue
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<template>
|
||||||
|
<div class="bg-white">
|
||||||
|
<div class="mx-auto max-w-2xl px-4 py-16 sm:px-6 sm:py-24 lg:max-w-7xl lg:px-8">
|
||||||
|
<h2 class="sr-only">Products</h2>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 gap-x-6 gap-y-10 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
|
||||||
|
<a v-for="product in products" :key="product.id" class="group">
|
||||||
|
<img :src="product.image" :alt="product.name"
|
||||||
|
class="aspect-square w-full rounded-lg bg-gray-200 object-cover group-hover:opacity-75 xl:aspect-7/8"/>
|
||||||
|
<h3 class="mt-4 text-sm text-gray-700">{{ product.name }}</h3>
|
||||||
|
<p class="mt-1 text-lg font-medium text-gray-900">{{ product.price }}</p>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import {$fetch} from 'ofetch';
|
||||||
|
import {onMounted, ref} from "vue";
|
||||||
|
|
||||||
|
const products = ref([]);
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const {data} = await $fetch('http://localhost:8000/index.php?route=tgshop/handle&api_action=products');
|
||||||
|
products.value = data;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -4,4 +4,12 @@ import vue from "@vitejs/plugin-vue";
|
|||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [tailwindcss(), vue()],
|
plugins: [tailwindcss(), vue()],
|
||||||
});
|
|
||||||
|
base: '/telegram_shop_spa/',
|
||||||
|
build: {
|
||||||
|
outDir: '../module/oc_telegram_shop/upload/image/catalog/tgshopspa',
|
||||||
|
emptyOutDir: true,
|
||||||
|
sourcemap: true,
|
||||||
|
manifest: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user