feat: add migrations, mantenance tasks, database cache, blocks cache
This commit is contained in:
@@ -19,6 +19,7 @@
|
||||
|
||||
<script setup>
|
||||
import {computed, onMounted, ref, useId} from "vue";
|
||||
import {getThumb} from "@/utils/helpers.js";
|
||||
|
||||
const id = useId();
|
||||
const model = defineModel();
|
||||
@@ -26,13 +27,7 @@ const emit = defineEmits(['update:modelValue']);
|
||||
const inputRef = ref(null);
|
||||
const isLoaded = ref(false);
|
||||
|
||||
const thumb = computed(() => {
|
||||
if (!model.value) return '/image/cache/no_image-100x100.png';
|
||||
const extIndex = model.value.lastIndexOf('.');
|
||||
const ext = model.value.substring(extIndex);
|
||||
const filename = model.value.substring(0, extIndex);
|
||||
return `/image/cache/${filename}-100x100${ext}`;
|
||||
});
|
||||
const thumb = computed(() => getThumb(model.value));
|
||||
|
||||
onMounted(() => {
|
||||
const input = inputRef.value;
|
||||
|
||||
@@ -1,11 +1,10 @@
|
||||
<template>
|
||||
<SettingsItem :label="label">
|
||||
<template #default>
|
||||
<OcImagePicker v-model="model"/>
|
||||
<OcImagePicker v-model="model" class="tw:w-30"/>
|
||||
</template>
|
||||
<template #help><slot></slot></template>
|
||||
</SettingsItem>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export function getThumb(imageUrl) {
|
||||
if (!imageUrl) return '/image/cache/no_image-100x100.png';
|
||||
if (!imageUrl) return '/image/no_image.png';
|
||||
const extIndex = imageUrl.lastIndexOf('.');
|
||||
const ext = imageUrl.substring(extIndex);
|
||||
const filename = imageUrl.substring(0, extIndex);
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
use Bastion\ApplicationFactory;
|
||||
use Cart\User;
|
||||
use Openguru\OpenCartFramework\Application;
|
||||
use Openguru\OpenCartFramework\Http\Response as HttpResponse;
|
||||
use Openguru\OpenCartFramework\Logger\LoggerInterface;
|
||||
use Openguru\OpenCartFramework\Logger\OpenCartLogAdapter;
|
||||
@@ -94,14 +95,12 @@ class ControllerExtensionModuleTgshop extends Controller
|
||||
|
||||
public function index(): void
|
||||
{
|
||||
$this->cleanUpOldAssets();
|
||||
$this->migrateFromOldSettings();
|
||||
$this->removeLegacyFiles();
|
||||
$this->runMaintenanceTasks();
|
||||
$this->injectVueJs();
|
||||
$this->config();
|
||||
$this->showConfigPage();
|
||||
}
|
||||
|
||||
private function config(): void
|
||||
private function showConfigPage(): void
|
||||
{
|
||||
$data = [];
|
||||
$this->document->setTitle($this->language->get('heading_title'));
|
||||
@@ -124,51 +123,8 @@ class ControllerExtensionModuleTgshop extends Controller
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
$json = $this->model_setting_setting->getSetting('module_telecart');
|
||||
if (! isset($json['module_telecart_settings'])) {
|
||||
$json['module_telecart_settings'] = [];
|
||||
}
|
||||
|
||||
$items = Arr::mergeArraysRecursively($json['module_telecart_settings'], [
|
||||
'app' => [
|
||||
'shop_base_url' => HTTPS_CATALOG, // for catalog: HTTPS_SERVER, for admin: HTTPS_CATALOG
|
||||
'language_id' => (int) $this->config->get('config_language_id'),
|
||||
],
|
||||
'logs' => [
|
||||
'path' => DIR_LOGS,
|
||||
],
|
||||
'database' => [
|
||||
'host' => DB_HOSTNAME,
|
||||
'database' => DB_DATABASE,
|
||||
'username' => DB_USERNAME,
|
||||
'password' => DB_PASSWORD,
|
||||
'prefix' => DB_PREFIX,
|
||||
'port' => (int) DB_PORT,
|
||||
],
|
||||
'store' => [
|
||||
'oc_store_id' => 0,
|
||||
'oc_default_currency' => $this->config->get('config_currency'),
|
||||
'oc_config_tax' => filter_var($this->config->get('config_tax'), FILTER_VALIDATE_BOOLEAN),
|
||||
],
|
||||
'orders' => [
|
||||
'oc_customer_group_id' => (int) $this->config->get('config_customer_group_id'),
|
||||
],
|
||||
'telegram' => [
|
||||
'mini_app_url' => rtrim(HTTPS_CATALOG, '/') . '/image/catalog/tgshopspa/#/',
|
||||
],
|
||||
]);
|
||||
|
||||
$app = ApplicationFactory::create($items);
|
||||
$app->bind(OcRegistryDecorator::class, fn() => new OcRegistryDecorator($this->registry));
|
||||
|
||||
$app
|
||||
->withLogger(fn() => new OpenCartLogAdapter(
|
||||
$this->log,
|
||||
'TeleCartAdmin',
|
||||
$app->getConfigValue('app.app_debug')
|
||||
? LoggerInterface::LEVEL_DEBUG
|
||||
: LoggerInterface::LEVEL_WARNING,
|
||||
))
|
||||
$this
|
||||
->createApplication()
|
||||
->bootAndHandleRequest();
|
||||
} catch (Exception $e) {
|
||||
$this->log->write('[TELECART] Error: ' . $e->getMessage());
|
||||
@@ -264,68 +220,6 @@ class ControllerExtensionModuleTgshop extends Controller
|
||||
return $map;
|
||||
}
|
||||
|
||||
private function cleanUpOldAssets(): void
|
||||
{
|
||||
$spaPath = rtrim(DIR_IMAGE, '/') . '/catalog/tgshopspa';
|
||||
$assetsPath = $spaPath . '/assets';
|
||||
$manifestPath = $spaPath . '/manifest.json';
|
||||
if (! file_exists($manifestPath)) {
|
||||
$this->log->write('[TELECART] Файл manifest.json не найден — очистка пропущена.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$contents = json_decode(file_get_contents($manifestPath), true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
$entry = $contents['index.html'] ?? null;
|
||||
if (! $entry) {
|
||||
throw new RuntimeException('[TELECART] Некорректный manifest.json — отсутствует ключ index.html.');
|
||||
}
|
||||
|
||||
$keep = [$entry['file']];
|
||||
if (! empty($entry['css'])) {
|
||||
foreach ($entry['css'] as $css) {
|
||||
$keep[] = $css;
|
||||
}
|
||||
}
|
||||
|
||||
$deletedFiles = 0;
|
||||
$keptFiles = 0;
|
||||
|
||||
foreach (glob($assetsPath . '/*') as $file) {
|
||||
$ext = pathinfo($file, PATHINFO_EXTENSION);
|
||||
if (! in_array($ext, ['js', 'css', 'map'])) {
|
||||
continue;
|
||||
}
|
||||
$relative = 'assets/' . basename($file);
|
||||
if (in_array($relative, $keep, true)) {
|
||||
$keptFiles++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_file($file)) {
|
||||
unlink($file);
|
||||
$deletedFiles++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($deletedFiles > 0) {
|
||||
$this->log->write(
|
||||
sprintf(
|
||||
'[TELECART] Очистка assets завершена. Удалено: %d, оставлено: %d',
|
||||
$deletedFiles,
|
||||
$keptFiles
|
||||
)
|
||||
);
|
||||
}
|
||||
} catch (JsonException $e) {
|
||||
$this->log->write('[TELECART] Ошибка декодирования файла manifest.json: ' . $e->getMessage());
|
||||
} catch (Exception $e) {
|
||||
$this->log->write('[TELECART] Ошибка удаления старых assets: ' . $e->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private function injectVueJs(): void
|
||||
{
|
||||
$appDir = rtrim(DIR_APPLICATION, '/');
|
||||
@@ -340,92 +234,59 @@ class ControllerExtensionModuleTgshop extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
private function migrateFromOldSettings(): void
|
||||
private function createApplication(): Application
|
||||
{
|
||||
$legacySettings = $this->model_setting_setting->getSetting('module_tgshop');
|
||||
if (! $legacySettings) {
|
||||
return;
|
||||
$json = $this->model_setting_setting->getSetting('module_telecart');
|
||||
if (! isset($json['module_telecart_settings'])) {
|
||||
$json['module_telecart_settings'] = [];
|
||||
}
|
||||
|
||||
$newSettings = $this->model_setting_setting->getSetting('module_telecart');
|
||||
|
||||
static $mapLegacyToNewSettings = [
|
||||
'module_tgshop_app_icon' => 'app.app_icon',
|
||||
'module_tgshop_theme_light' => 'app.theme_light',
|
||||
'module_tgshop_bot_token' => 'telegram.bot_token',
|
||||
'module_tgshop_status' => 'app.app_enabled',
|
||||
'module_tgshop_app_name' => 'app.app_name',
|
||||
'module_tgshop_theme_dark' => 'app.theme_dark',
|
||||
'module_tgshop_debug' => 'app.app_debug',
|
||||
'module_tgshop_chat_id' => 'telegram.chat_id',
|
||||
'module_tgshop_owner_notification_template' => 'telegram.owner_notification_template',
|
||||
'module_tgshop_text_order_created_success' => 'texts.text_order_created_success',
|
||||
'module_tgshop_enable_store' => 'store.enable_store',
|
||||
'module_tgshop_yandex_metrika' => 'metrics.yandex_metrika_counter',
|
||||
'module_tgshop_customer_notification_template' => 'telegram.customer_notification_template',
|
||||
'module_tgshop_feature_vouchers' => 'store.feature_vouchers',
|
||||
'module_tgshop_order_default_status_id' => 'orders.order_default_status_id',
|
||||
'module_tgshop_feature_coupons' => 'store.feature_coupons',
|
||||
'module_tgshop_text_no_more_products' => 'texts.text_no_more_products',
|
||||
'module_tgshop_text_empty_cart' => 'texts.text_empty_cart',
|
||||
];
|
||||
|
||||
if (! $newSettings) {
|
||||
$data = [];
|
||||
Arr::set($data, 'app.app_icon', $legacySettings['module_tgshop_app_icon']);
|
||||
|
||||
foreach ($mapLegacyToNewSettings as $key => $value) {
|
||||
if (array_key_exists($key, $legacySettings)) {
|
||||
if ($key === 'module_tgshop_status') {
|
||||
$newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
|
||||
} elseif ($key === 'module_tgshop_debug') {
|
||||
$newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
|
||||
} elseif ($key === 'module_tgshop_chat_id') {
|
||||
$newValue = (int) $legacySettings[$key];
|
||||
} elseif ($key === 'module_tgshop_enable_store') {
|
||||
$newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
|
||||
} elseif ($key === 'module_tgshop_order_default_status_id') {
|
||||
$newValue = (int) $legacySettings[$key];
|
||||
} elseif ($key === 'module_tgshop_feature_vouchers') {
|
||||
$newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
|
||||
} elseif ($key === 'module_tgshop_feature_coupons') {
|
||||
$newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
|
||||
} else {
|
||||
$newValue = $legacySettings[$key];
|
||||
}
|
||||
|
||||
Arr::set($data, $value, $newValue);
|
||||
}
|
||||
}
|
||||
|
||||
Arr::set(
|
||||
$data,
|
||||
'metrics.yandex_metrika_enabled',
|
||||
! empty(trim($legacySettings['module_tgshop_yandex_metrika']))
|
||||
);
|
||||
|
||||
$this->model_setting_setting->editSetting('module_telecart', [
|
||||
'module_telecart_settings' => $data,
|
||||
$items = Arr::mergeArraysRecursively($json['module_telecart_settings'], [
|
||||
'app' => [
|
||||
'shop_base_url' => HTTPS_CATALOG, // for catalog: HTTPS_SERVER, for admin: HTTPS_CATALOG
|
||||
'language_id' => (int) $this->config->get('config_language_id'),
|
||||
],
|
||||
'logs' => [
|
||||
'path' => DIR_LOGS,
|
||||
],
|
||||
'database' => [
|
||||
'host' => DB_HOSTNAME,
|
||||
'database' => DB_DATABASE,
|
||||
'username' => DB_USERNAME,
|
||||
'password' => DB_PASSWORD,
|
||||
'prefix' => DB_PREFIX,
|
||||
'port' => (int) DB_PORT,
|
||||
],
|
||||
'store' => [
|
||||
'oc_store_id' => 0,
|
||||
'oc_default_currency' => $this->config->get('config_currency'),
|
||||
'oc_config_tax' => filter_var($this->config->get('config_tax'), FILTER_VALIDATE_BOOLEAN),
|
||||
],
|
||||
'orders' => [
|
||||
'oc_customer_group_id' => (int) $this->config->get('config_customer_group_id'),
|
||||
],
|
||||
'telegram' => [
|
||||
'mini_app_url' => rtrim(HTTPS_CATALOG, '/') . '/image/catalog/tgshopspa/#/',
|
||||
],
|
||||
]);
|
||||
|
||||
$this->log->write('[TELECART] Выполнено обновление настроек с 1й версии модуля.');
|
||||
$this->session->data['success'] = 'Выполнено обновление настроек с прошлой версии модуля.';
|
||||
$app = ApplicationFactory::create($items);
|
||||
$app->bind(OcRegistryDecorator::class, fn() => new OcRegistryDecorator($this->registry));
|
||||
|
||||
$app
|
||||
->withLogger(fn() => new OpenCartLogAdapter(
|
||||
$this->log,
|
||||
'TeleCartAdmin',
|
||||
$app->getConfigValue('app.app_debug')
|
||||
? LoggerInterface::LEVEL_DEBUG
|
||||
: LoggerInterface::LEVEL_INFO,
|
||||
));
|
||||
|
||||
return $app;
|
||||
}
|
||||
|
||||
$this->model_setting_setting->deleteSetting('module_tgshop');
|
||||
}
|
||||
|
||||
private function removeLegacyFiles(): void
|
||||
private function runMaintenanceTasks(): void
|
||||
{
|
||||
$legacyFilesToRemove = [
|
||||
DIR_TEMPLATE . '/extension/module/tgshop_init.twig',
|
||||
];
|
||||
|
||||
foreach ($legacyFilesToRemove as $file) {
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
$this->log->write('[TELECART] Удалён старый файл: ' . $file);
|
||||
}
|
||||
}
|
||||
$this->createApplication()->runMaintenanceTasks();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class ApplicationFactory
|
||||
{
|
||||
public static function create(array $settings): Application
|
||||
{
|
||||
$defaultConfig = require __DIR__ . '/../src/config.php';
|
||||
$defaultConfig = require __DIR__ . '/../configs/app.php';
|
||||
$routes = require __DIR__ . '/routes.php';
|
||||
|
||||
$merged = Arr::mergeArraysRecursively($defaultConfig, $settings);
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace Bastion\Tasks;
|
||||
|
||||
use Exception;
|
||||
use JsonException;
|
||||
use Openguru\OpenCartFramework\MaintenanceTasks\BaseMaintenanceTask;
|
||||
use RuntimeException;
|
||||
|
||||
class CleanUpOldAssets extends BaseMaintenanceTask
|
||||
{
|
||||
public function handle(): void
|
||||
{
|
||||
$spaPath = rtrim(DIR_IMAGE, '/') . '/catalog/tgshopspa';
|
||||
$assetsPath = $spaPath . '/assets';
|
||||
$manifestPath = $spaPath . '/manifest.json';
|
||||
if (! file_exists($manifestPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$contents = json_decode(file_get_contents($manifestPath), true, 512, JSON_THROW_ON_ERROR);
|
||||
|
||||
$entry = $contents['index.html'] ?? null;
|
||||
if (! $entry) {
|
||||
throw new RuntimeException('Некорректный manifest.json — отсутствует ключ index.html.');
|
||||
}
|
||||
|
||||
$keep = [$entry['file']];
|
||||
if (! empty($entry['css'])) {
|
||||
foreach ($entry['css'] as $css) {
|
||||
$keep[] = $css;
|
||||
}
|
||||
}
|
||||
|
||||
$deletedFiles = 0;
|
||||
$keptFiles = 0;
|
||||
|
||||
foreach (glob($assetsPath . '/*') as $file) {
|
||||
$ext = pathinfo($file, PATHINFO_EXTENSION);
|
||||
if (! in_array($ext, ['js', 'css', 'map'])) {
|
||||
continue;
|
||||
}
|
||||
$relative = 'assets/' . basename($file);
|
||||
if (in_array($relative, $keep, true)) {
|
||||
$keptFiles++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (is_file($file)) {
|
||||
unlink($file);
|
||||
$deletedFiles++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($deletedFiles > 0) {
|
||||
$this->logger->info(
|
||||
sprintf('Очистка assets завершена. Удалено: %d, оставлено: %d', $deletedFiles, $keptFiles)
|
||||
);
|
||||
}
|
||||
} catch (JsonException $e) {
|
||||
$this->logger->error('Ошибка декодирования файла manifest.json: ' . $e->getMessage());
|
||||
} catch (Exception $e) {
|
||||
$this->logger->error('Ошибка удаления старых assets: ' . $e->getMessage());
|
||||
$this->logger->logException($e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -26,7 +26,8 @@
|
||||
"intervention/image": "^2.7",
|
||||
"vlucas/phpdotenv": "^5.6",
|
||||
"guzzlehttp/guzzle": "^7.9",
|
||||
"symfony/cache": "^5.4"
|
||||
"symfony/cache": "^5.4",
|
||||
"doctrine/dbal": "^3.10"
|
||||
},
|
||||
"require-dev": {
|
||||
"roave/security-advisories": "dev-latest",
|
||||
|
||||
@@ -4,8 +4,262 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "e8ed2d3d0e11eac86a27bb2972b115cd",
|
||||
"content-hash": "3429e02a0f2458ebcd70b11ef4c3d0df",
|
||||
"packages": [
|
||||
{
|
||||
"name": "doctrine/dbal",
|
||||
"version": "3.10.3",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/dbal.git",
|
||||
"reference": "65edaca19a752730f290ec2fb89d593cb40afb43"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/dbal/zipball/65edaca19a752730f290ec2fb89d593cb40afb43",
|
||||
"reference": "65edaca19a752730f290ec2fb89d593cb40afb43",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"composer-runtime-api": "^2",
|
||||
"doctrine/deprecations": "^0.5.3|^1",
|
||||
"doctrine/event-manager": "^1|^2",
|
||||
"php": "^7.4 || ^8.0",
|
||||
"psr/cache": "^1|^2|^3",
|
||||
"psr/log": "^1|^2|^3"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/cache": "< 1.11"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/cache": "^1.11|^2.0",
|
||||
"doctrine/coding-standard": "14.0.0",
|
||||
"fig/log-test": "^1",
|
||||
"jetbrains/phpstorm-stubs": "2023.1",
|
||||
"phpstan/phpstan": "2.1.30",
|
||||
"phpstan/phpstan-strict-rules": "^2",
|
||||
"phpunit/phpunit": "9.6.29",
|
||||
"slevomat/coding-standard": "8.24.0",
|
||||
"squizlabs/php_codesniffer": "4.0.0",
|
||||
"symfony/cache": "^5.4|^6.0|^7.0",
|
||||
"symfony/console": "^4.4|^5.4|^6.0|^7.0"
|
||||
},
|
||||
"suggest": {
|
||||
"symfony/console": "For helpful console commands such as SQL execution and import of files."
|
||||
},
|
||||
"bin": [
|
||||
"bin/doctrine-dbal"
|
||||
],
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\DBAL\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.",
|
||||
"homepage": "https://www.doctrine-project.org/projects/dbal.html",
|
||||
"keywords": [
|
||||
"abstraction",
|
||||
"database",
|
||||
"db2",
|
||||
"dbal",
|
||||
"mariadb",
|
||||
"mssql",
|
||||
"mysql",
|
||||
"oci8",
|
||||
"oracle",
|
||||
"pdo",
|
||||
"pgsql",
|
||||
"postgresql",
|
||||
"queryobject",
|
||||
"sasql",
|
||||
"sql",
|
||||
"sqlite",
|
||||
"sqlserver",
|
||||
"sqlsrv"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/dbal/issues",
|
||||
"source": "https://github.com/doctrine/dbal/tree/3.10.3"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.doctrine-project.org/sponsorship.html",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpdoctrine",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2025-10-09T09:05:12+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/deprecations",
|
||||
"version": "1.1.5",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/deprecations.git",
|
||||
"reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38",
|
||||
"reference": "459c2f5dd3d6a4633d3b5f46ee2b1c40f57d3f38",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"phpunit/phpunit": "<=7.5 || >=13"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^9 || ^12 || ^13",
|
||||
"phpstan/phpstan": "1.4.10 || 2.1.11",
|
||||
"phpstan/phpstan-phpunit": "^1.0 || ^2",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.6 || ^10.5 || ^11.5 || ^12",
|
||||
"psr/log": "^1 || ^2 || ^3"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Deprecations\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
|
||||
"homepage": "https://www.doctrine-project.org/",
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/deprecations/issues",
|
||||
"source": "https://github.com/doctrine/deprecations/tree/1.1.5"
|
||||
},
|
||||
"time": "2025-04-07T20:06:18+00:00"
|
||||
},
|
||||
{
|
||||
"name": "doctrine/event-manager",
|
||||
"version": "1.2.0",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/doctrine/event-manager.git",
|
||||
"reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/doctrine/event-manager/zipball/95aa4cb529f1e96576f3fda9f5705ada4056a520",
|
||||
"reference": "95aa4cb529f1e96576f3fda9f5705ada4056a520",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"doctrine/deprecations": "^0.5.3 || ^1",
|
||||
"php": "^7.1 || ^8.0"
|
||||
},
|
||||
"conflict": {
|
||||
"doctrine/common": "<2.9"
|
||||
},
|
||||
"require-dev": {
|
||||
"doctrine/coding-standard": "^9 || ^10",
|
||||
"phpstan/phpstan": "~1.4.10 || ^1.8.8",
|
||||
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
|
||||
"vimeo/psalm": "^4.24"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Doctrine\\Common\\": "src"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "Guilherme Blanco",
|
||||
"email": "guilhermeblanco@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Roman Borschel",
|
||||
"email": "roman@code-factory.org"
|
||||
},
|
||||
{
|
||||
"name": "Benjamin Eberlei",
|
||||
"email": "kontakt@beberlei.de"
|
||||
},
|
||||
{
|
||||
"name": "Jonathan Wage",
|
||||
"email": "jonwage@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Johannes Schmitt",
|
||||
"email": "schmittjoh@gmail.com"
|
||||
},
|
||||
{
|
||||
"name": "Marco Pivetta",
|
||||
"email": "ocramius@gmail.com"
|
||||
}
|
||||
],
|
||||
"description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.",
|
||||
"homepage": "https://www.doctrine-project.org/projects/event-manager.html",
|
||||
"keywords": [
|
||||
"event",
|
||||
"event dispatcher",
|
||||
"event manager",
|
||||
"event system",
|
||||
"events"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://github.com/doctrine/event-manager/issues",
|
||||
"source": "https://github.com/doctrine/event-manager/tree/1.2.0"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
"url": "https://www.doctrine-project.org/sponsorship.html",
|
||||
"type": "custom"
|
||||
},
|
||||
{
|
||||
"url": "https://www.patreon.com/phpdoctrine",
|
||||
"type": "patreon"
|
||||
},
|
||||
{
|
||||
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager",
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2022-10-12T20:51:15+00:00"
|
||||
},
|
||||
{
|
||||
"name": "graham-campbell/result-type",
|
||||
"version": "v1.1.3",
|
||||
|
||||
@@ -76,4 +76,12 @@ TEXT,
|
||||
],
|
||||
],
|
||||
],
|
||||
|
||||
'cache' => [
|
||||
'namespace' => 'telecart',
|
||||
'default_lifetime' => 60 * 60 * 24,
|
||||
'options' => [
|
||||
'db_table' => 'telecart_cache_items',
|
||||
],
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
use Bastion\Tasks\CleanUpOldAssets;
|
||||
|
||||
return [
|
||||
'tasks' => [
|
||||
CleanUpOldAssets::class,
|
||||
],
|
||||
];
|
||||
@@ -0,0 +1,77 @@
|
||||
<?php
|
||||
|
||||
use Openguru\OpenCartFramework\Migrations\Migration;
|
||||
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
|
||||
use Openguru\OpenCartFramework\Support\Arr;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
$opencart = $this->app->get(OcRegistryDecorator::class);
|
||||
$opencart->load->model('setting/setting');
|
||||
|
||||
$legacySettings = $opencart->model_setting_setting->getSetting('module_tgshop');
|
||||
if (! $legacySettings) {
|
||||
return;
|
||||
}
|
||||
|
||||
$newSettings = $opencart->model_setting_setting->getSetting('module_telecart');
|
||||
|
||||
static $mapLegacyToNewSettings = [
|
||||
'module_tgshop_app_icon' => 'app.app_icon',
|
||||
'module_tgshop_theme_light' => 'app.theme_light',
|
||||
'module_tgshop_bot_token' => 'telegram.bot_token',
|
||||
'module_tgshop_status' => 'app.app_enabled',
|
||||
'module_tgshop_app_name' => 'app.app_name',
|
||||
'module_tgshop_theme_dark' => 'app.theme_dark',
|
||||
'module_tgshop_debug' => 'app.app_debug',
|
||||
'module_tgshop_chat_id' => 'telegram.chat_id',
|
||||
'module_tgshop_owner_notification_template' => 'telegram.owner_notification_template',
|
||||
'module_tgshop_text_order_created_success' => 'texts.text_order_created_success',
|
||||
'module_tgshop_enable_store' => 'store.enable_store',
|
||||
'module_tgshop_yandex_metrika' => 'metrics.yandex_metrika_counter',
|
||||
'module_tgshop_customer_notification_template' => 'telegram.customer_notification_template',
|
||||
'module_tgshop_feature_vouchers' => 'store.feature_vouchers',
|
||||
'module_tgshop_order_default_status_id' => 'orders.order_default_status_id',
|
||||
'module_tgshop_feature_coupons' => 'store.feature_coupons',
|
||||
'module_tgshop_text_no_more_products' => 'texts.text_no_more_products',
|
||||
'module_tgshop_text_empty_cart' => 'texts.text_empty_cart',
|
||||
];
|
||||
|
||||
if (! $newSettings) {
|
||||
$data = [];
|
||||
|
||||
foreach ($mapLegacyToNewSettings as $key => $value) {
|
||||
if (array_key_exists($key, $legacySettings)) {
|
||||
if ($key === 'module_tgshop_status') {
|
||||
$newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
|
||||
} elseif ($key === 'module_tgshop_debug') {
|
||||
$newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
|
||||
} elseif ($key === 'module_tgshop_chat_id') {
|
||||
$newValue = (int) $legacySettings[$key];
|
||||
} elseif ($key === 'module_tgshop_enable_store') {
|
||||
$newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
|
||||
} elseif ($key === 'module_tgshop_order_default_status_id') {
|
||||
$newValue = (int) $legacySettings[$key];
|
||||
} elseif ($key === 'module_tgshop_feature_vouchers') {
|
||||
$newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
|
||||
} elseif ($key === 'module_tgshop_feature_coupons') {
|
||||
$newValue = filter_var($legacySettings[$key], FILTER_VALIDATE_BOOLEAN);
|
||||
} else {
|
||||
$newValue = $legacySettings[$key];
|
||||
}
|
||||
|
||||
Arr::set($data, $value, $newValue);
|
||||
}
|
||||
}
|
||||
|
||||
$opencart->model_setting_setting->editSetting('module_telecart', [
|
||||
'module_telecart_settings' => $data,
|
||||
]);
|
||||
|
||||
$this->logger->info('Выполнено обновление настроек с 1й версии модуля.');
|
||||
}
|
||||
|
||||
$opencart->model_setting_setting->deleteSetting('module_tgshop');
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
use Openguru\OpenCartFramework\Migrations\Migration;
|
||||
use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
$this->app->get(DoctrineDbalAdapter::class)->createTable();
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
use Openguru\OpenCartFramework\Migrations\Migration;
|
||||
|
||||
return new class extends Migration {
|
||||
public function up(): void
|
||||
{
|
||||
$legacyFilesToRemove = [
|
||||
DIR_TEMPLATE . '/extension/module/tgshop_init.twig',
|
||||
];
|
||||
|
||||
foreach ($legacyFilesToRemove as $file) {
|
||||
if (file_exists($file)) {
|
||||
unlink($file);
|
||||
$this->logger->info('Удалён старый файл: ' . $file);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -11,6 +11,9 @@ use Openguru\OpenCartFramework\Http\JsonResponse;
|
||||
use Openguru\OpenCartFramework\Http\Request;
|
||||
use Openguru\OpenCartFramework\Logger\Logger;
|
||||
use Openguru\OpenCartFramework\Logger\LoggerInterface;
|
||||
use Openguru\OpenCartFramework\MaintenanceTasks\MaintenanceTasksService;
|
||||
use Openguru\OpenCartFramework\MaintenanceTasks\MaintenanceTasksServiceProvider;
|
||||
use Openguru\OpenCartFramework\Migrations\MigrationsServiceProvider;
|
||||
use Openguru\OpenCartFramework\Router\Router;
|
||||
use Openguru\OpenCartFramework\Support\ExecutionTimeProfiler;
|
||||
|
||||
@@ -160,4 +163,16 @@ class Application extends Container
|
||||
{
|
||||
return $this->routes;
|
||||
}
|
||||
|
||||
public function runMaintenanceTasks(): void
|
||||
{
|
||||
$this->serviceProviders = array_merge($this->serviceProviders, [
|
||||
MigrationsServiceProvider::class,
|
||||
MaintenanceTasksServiceProvider::class,
|
||||
]);
|
||||
|
||||
$this->boot();
|
||||
|
||||
$this->get(MaintenanceTasksService::class)->run();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,10 @@ 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;
|
||||
}
|
||||
|
||||
@@ -2,14 +2,32 @@
|
||||
|
||||
namespace Openguru\OpenCartFramework\Cache;
|
||||
|
||||
use Openguru\OpenCartFramework\Container\Container;
|
||||
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||
use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter;
|
||||
|
||||
class CacheServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
$this->container->singleton(CacheInterface::class, function () {
|
||||
return new SymfonyFilesystemCache('app.cache', 0, DIR_CACHE);
|
||||
$this->container->singleton(DoctrineDbalAdapter::class, function (Container $container) {
|
||||
$host = $container->getConfigValue('database.host');
|
||||
$username = $container->getConfigValue('database.username');
|
||||
$password = $container->getConfigValue('database.password');
|
||||
$port = (int) $container->getConfigValue('database.port');
|
||||
$dbName = $container->getConfigValue('database.database');
|
||||
|
||||
$dsn = "mysql://$username:$password@$host:$port/$dbName?charset=utf8mb4";
|
||||
|
||||
$namespace = $container->getConfigValue('cache.namespace');
|
||||
$defaultLifetime = $container->getConfigValue('cache.default_lifetime');
|
||||
$options = $container->getConfigValue('cache.options');
|
||||
|
||||
return new DoctrineDbalAdapter($dsn, $namespace, $defaultLifetime, $options);
|
||||
});
|
||||
|
||||
$this->container->singleton(CacheInterface::class, function (Container $container) {
|
||||
return new SymfonyMySqlCache($container->get(DoctrineDbalAdapter::class));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace Openguru\OpenCartFramework\Cache;
|
||||
|
||||
use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter;
|
||||
|
||||
class SymfonyMySqlCache implements CacheInterface
|
||||
{
|
||||
protected DoctrineDbalAdapter $cache;
|
||||
|
||||
public function __construct(DoctrineDbalAdapter $adapter)
|
||||
{
|
||||
$this->cache = $adapter;
|
||||
}
|
||||
|
||||
public function get(string $key)
|
||||
{
|
||||
$item = $this->cache->getItem($key);
|
||||
|
||||
return $item->isHit() ? $item->get() : null;
|
||||
}
|
||||
|
||||
public function set(string $key, $value, ?int $ttlSeconds = null): void
|
||||
{
|
||||
$item = $this->cache->getItem($key);
|
||||
$item->set($value);
|
||||
|
||||
if ($ttlSeconds) {
|
||||
$item->expiresAfter($ttlSeconds);
|
||||
}
|
||||
|
||||
$this->cache->save($item);
|
||||
}
|
||||
|
||||
public function delete(string $key): void
|
||||
{
|
||||
$this->cache->deleteItem($key);
|
||||
}
|
||||
|
||||
public function clear(): void
|
||||
{
|
||||
$this->cache->clear();
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use Openguru\OpenCartFramework\Container\Container;
|
||||
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||
use Openguru\OpenCartFramework\Settings\DatabaseUserSettings;
|
||||
use Openguru\OpenCartFramework\Settings\UserSettingsInterface;
|
||||
use Openguru\OpenCartFramework\Support\SupportServiceProvider;
|
||||
use RuntimeException;
|
||||
|
||||
class DependencyRegistration
|
||||
@@ -16,6 +17,8 @@ class DependencyRegistration
|
||||
return $container->get(DatabaseUserSettings::class);
|
||||
});
|
||||
|
||||
$container->get(SupportServiceProvider::class)->register();
|
||||
|
||||
static::registerServiceProviders($container, $serviceProviders);
|
||||
}
|
||||
|
||||
@@ -23,7 +26,7 @@ class DependencyRegistration
|
||||
{
|
||||
foreach ($serviceProviders as $serviceProvider) {
|
||||
$provider = $container->get($serviceProvider);
|
||||
if (!$provider instanceof ServiceProvider) {
|
||||
if (! $provider instanceof ServiceProvider) {
|
||||
throw new RuntimeException('ServiceProvider must extend ServiceProvider');
|
||||
}
|
||||
|
||||
|
||||
@@ -34,10 +34,14 @@ class ErrorHandler
|
||||
|
||||
public function handleError(int $severity, string $message, string $file, int $line): bool
|
||||
{
|
||||
if (($severity & E_DEPRECATED|E_USER_DEPRECATED)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->logger->error('Handled PHP error: ' . implode(', ', compact('severity', 'message', 'file', 'line')));
|
||||
|
||||
// Convert warnings and notices to ErrorException
|
||||
if (!(error_reporting() & $severity)) {
|
||||
if (! (error_reporting() & $severity)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -51,11 +55,12 @@ class ErrorHandler
|
||||
$response = $customHandler->respond($exception);
|
||||
if ($response !== null) {
|
||||
$response->send();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$exception instanceof NonLoggableExceptionInterface) {
|
||||
if (! $exception instanceof NonLoggableExceptionInterface) {
|
||||
$this->logger->logException($exception);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Openguru\OpenCartFramework\MaintenanceTasks;
|
||||
|
||||
use Openguru\OpenCartFramework\Logger\LoggerInterface;
|
||||
|
||||
abstract class BaseMaintenanceTask implements MaintenanceTaskInterface
|
||||
{
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
public function __construct(LoggerInterface $logger)
|
||||
{
|
||||
$this->logger = $logger;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Openguru\OpenCartFramework\MaintenanceTasks;
|
||||
|
||||
interface MaintenanceTaskInterface
|
||||
{
|
||||
public function handle(): void;
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace Openguru\OpenCartFramework\MaintenanceTasks;
|
||||
|
||||
use Openguru\OpenCartFramework\Logger\LoggerInterface;
|
||||
use Openguru\OpenCartFramework\Migrations\MigratorService;
|
||||
|
||||
class MaintenanceTasksService
|
||||
{
|
||||
private MigratorService $migrator;
|
||||
private LoggerInterface $logger;
|
||||
private array $maintenanceTasks;
|
||||
|
||||
public function __construct(
|
||||
MigratorService $migrator,
|
||||
LoggerInterface $logger,
|
||||
array $maintenanceTasks = []
|
||||
) {
|
||||
$this->migrator = $migrator;
|
||||
$this->logger = $logger;
|
||||
$this->maintenanceTasks = $maintenanceTasks;
|
||||
}
|
||||
|
||||
public function run(): void
|
||||
{
|
||||
$this->migrator->migrate();
|
||||
$this->performMaintenanceTasks();
|
||||
}
|
||||
|
||||
private function performMaintenanceTasks(): void
|
||||
{
|
||||
foreach ($this->maintenanceTasks as $maintenanceTask) {
|
||||
$startTime = microtime(true);
|
||||
/** @var MaintenanceTaskInterface $instance */
|
||||
$instance = $maintenanceTask();
|
||||
$instance->handle();
|
||||
$endTime = microtime(true);
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
'Maintenance task %s executed by %d seconds',
|
||||
get_class($instance),
|
||||
$endTime - $startTime
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
namespace Openguru\OpenCartFramework\MaintenanceTasks;
|
||||
|
||||
use Openguru\OpenCartFramework\Container\Container;
|
||||
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||
use Openguru\OpenCartFramework\Logger\LoggerInterface;
|
||||
use Openguru\OpenCartFramework\Migrations\MigratorService;
|
||||
|
||||
class MaintenanceTasksServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
$this->container->singleton(MaintenanceTasksService::class, function (Container $container) {
|
||||
$config = require configs_path('maintenance.php');
|
||||
$classes = $config['tasks'];
|
||||
$tasks = array_map(static fn(string $class) => static fn() => $container->get($class), $classes);
|
||||
|
||||
return new MaintenanceTasksService(
|
||||
$container->get(MigratorService::class),
|
||||
$container->get(LoggerInterface::class),
|
||||
$tasks,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Openguru\OpenCartFramework\Migrations;
|
||||
|
||||
use Openguru\OpenCartFramework\Application;
|
||||
use Openguru\OpenCartFramework\Logger\LoggerInterface;
|
||||
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
||||
|
||||
abstract class Migration
|
||||
{
|
||||
protected Application $app;
|
||||
protected ConnectionInterface $database;
|
||||
protected LoggerInterface $logger;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->app = Application::getInstance();
|
||||
$this->database = $this->app->get(ConnectionInterface::class);
|
||||
$this->logger = $this->app->get(LoggerInterface::class);
|
||||
}
|
||||
|
||||
abstract public function up(): void;
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace Openguru\OpenCartFramework\Migrations;
|
||||
|
||||
use Openguru\OpenCartFramework\Container\Container;
|
||||
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||
use Openguru\OpenCartFramework\Logger\LoggerInterface;
|
||||
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
||||
use Openguru\OpenCartFramework\Support\WorkLogsBag;
|
||||
|
||||
class MigrationsServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
$this->container->singleton(MigratorService::class, function (Container $app) {
|
||||
return new MigratorService(
|
||||
$app->get(ConnectionInterface::class),
|
||||
$app->get(LoggerInterface::class),
|
||||
database_path('migrations'),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace Openguru\OpenCartFramework\Migrations;
|
||||
|
||||
use Exception;
|
||||
use Openguru\OpenCartFramework\Logger\LoggerInterface;
|
||||
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
|
||||
use RuntimeException;
|
||||
|
||||
class MigratorService
|
||||
{
|
||||
private ConnectionInterface $connection;
|
||||
private LoggerInterface $logger;
|
||||
private string $migrationsPath;
|
||||
|
||||
public function __construct(
|
||||
ConnectionInterface $connection,
|
||||
LoggerInterface $logger,
|
||||
string $migrationsPath
|
||||
) {
|
||||
$this->connection = $connection;
|
||||
$this->logger = $logger;
|
||||
$this->migrationsPath = $migrationsPath;
|
||||
}
|
||||
|
||||
public function migrate(): void
|
||||
{
|
||||
$this->ensureMigrationsTableExists();
|
||||
|
||||
$applied = $this->getAppliedMigrations();
|
||||
$migrationsToApply = $this->findMigrationsToApply($applied);
|
||||
$count = $this->applyMigrations($migrationsToApply);
|
||||
|
||||
if ($count > 0) {
|
||||
$this->logger->log("$count migrations applied");
|
||||
}
|
||||
}
|
||||
|
||||
private function getMigrationsTableName(): string
|
||||
{
|
||||
return 'telecart_migrations';
|
||||
}
|
||||
|
||||
private function ensureMigrationsTableExists(): void
|
||||
{
|
||||
$isExists = $this->connection->tableExists($this->getMigrationsTableName());
|
||||
|
||||
if (! $isExists) {
|
||||
$this->connection->statement(
|
||||
$this->createMigrationsTableSql()
|
||||
);
|
||||
|
||||
$this->logger->log('Migrations table created.');
|
||||
}
|
||||
}
|
||||
|
||||
private function createMigrationsTableSql(): string
|
||||
{
|
||||
return <<<SQL
|
||||
CREATE TABLE IF NOT EXISTS `{$this->getMigrationsTableName()}` (
|
||||
migration VARCHAR(191) NOT NULL,
|
||||
executed_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
PRIMARY KEY (migration)
|
||||
) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB;
|
||||
SQL;
|
||||
}
|
||||
|
||||
private function findMigrationsToApply(array $applied): array
|
||||
{
|
||||
$migrations = [];
|
||||
foreach (glob($this->migrationsPath . '/*.php') as $file) {
|
||||
$migrations[] = basename($file);
|
||||
}
|
||||
|
||||
return array_diff($migrations, $applied);
|
||||
}
|
||||
|
||||
private function getAppliedMigrations(): array
|
||||
{
|
||||
$migrations = $this->connection->select("SELECT migration FROM {$this->getMigrationsTableName()}");
|
||||
|
||||
return array_column($migrations, 'migration');
|
||||
}
|
||||
|
||||
private function applyMigrations(array $files): int
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
foreach ($files as $file) {
|
||||
try {
|
||||
$this->connection->beginTransaction();
|
||||
$migration = require $this->migrationsPath . '/' . $file;
|
||||
|
||||
if (! is_object($migration) || ! method_exists($migration, 'up')) {
|
||||
throw new RuntimeException("Migration file $file is invalid.");
|
||||
}
|
||||
|
||||
$migration->up();
|
||||
$this->writeMigration($file);
|
||||
$this->logger->log("Migrated $file");
|
||||
$count++;
|
||||
$this->connection->commitTransaction();
|
||||
} catch (Exception $e) {
|
||||
$this->connection->rollbackTransaction();
|
||||
$this->logger->error("An error occurred while applying migration.");
|
||||
$this->logger->logException($e);
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function writeMigration(string $file): void
|
||||
{
|
||||
$this->connection->insert($this->getMigrationsTableName(), [
|
||||
'migration' => $file,
|
||||
'executed_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace Openguru\OpenCartFramework\Support;
|
||||
|
||||
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||
|
||||
class SupportServiceProvider extends ServiceProvider
|
||||
{
|
||||
public function register(): void
|
||||
{
|
||||
$this->container->bind(WorkLogsBag::class, function () {
|
||||
return new WorkLogsBag([]);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Openguru\OpenCartFramework\Support;
|
||||
|
||||
class WorkLogsBag
|
||||
{
|
||||
private array $items;
|
||||
|
||||
public function __construct(array $items = [])
|
||||
{
|
||||
$this->items = $items;
|
||||
}
|
||||
|
||||
public function push(string $message): void
|
||||
{
|
||||
$this->items[] = sprintf('[%s] %s', date('Y-m-d H:i:s'), $message);
|
||||
}
|
||||
|
||||
public function merge(array $items): void
|
||||
{
|
||||
$this->items = array_merge($this->items, $items);
|
||||
}
|
||||
|
||||
public function getItems(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
public function formatAsList(): string
|
||||
{
|
||||
if (empty($this->items)) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$list = '<ul>';
|
||||
foreach ($this->items as $item) {
|
||||
$list .= sprintf('<li>%s</li>', $item);
|
||||
}
|
||||
$list .= '</ul>';
|
||||
|
||||
return $list;
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,13 @@ if (! function_exists('column')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('configs_path')) {
|
||||
function configs_path(string $path = ''): string
|
||||
{
|
||||
return BP_BASE_PATH . '/configs/' . $path;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('resources_path')) {
|
||||
function resources_path(string $path = ''): string
|
||||
{
|
||||
@@ -37,6 +44,13 @@ if (! function_exists('resources_path')) {
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('database_path')) {
|
||||
function database_path(string $path = ''): string
|
||||
{
|
||||
return BP_BASE_PATH . '/database/' . $path;
|
||||
}
|
||||
}
|
||||
|
||||
if (! function_exists('base_path')) {
|
||||
function base_path(string $path = ''): string
|
||||
{
|
||||
|
||||
@@ -17,7 +17,7 @@ class ApplicationFactory
|
||||
{
|
||||
public static function create(array $config): Application
|
||||
{
|
||||
$defaultConfig = require __DIR__ . '/config.php';
|
||||
$defaultConfig = require __DIR__ . '/../configs/app.php';
|
||||
$routes = require __DIR__ . '/routes.php';
|
||||
|
||||
return (new Application(Arr::mergeArraysRecursively($defaultConfig, $config)))
|
||||
|
||||
@@ -39,13 +39,24 @@ class BlocksService
|
||||
|
||||
public function process(array $block): array
|
||||
{
|
||||
$blockType = $block['type'];
|
||||
$cacheKey = "block_$blockType";
|
||||
$cacheTtlSeconds = 60;
|
||||
|
||||
$data = $this->cache->get($cacheKey);
|
||||
if (! $data) {
|
||||
$method = self::$processors[$block['type']] ?? null;
|
||||
|
||||
if (! $method) {
|
||||
throw new RuntimeException('Processor for block type ' . $block['type'] . ' does not exist');
|
||||
}
|
||||
|
||||
return call_user_func_array($method, [$block]);
|
||||
$data = call_user_func_array($method, [$block]);
|
||||
|
||||
$this->cache->set($cacheKey, $data, $cacheTtlSeconds);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function processSlider(array $block): array
|
||||
|
||||
Reference in New Issue
Block a user