feat: add migrations, mantenance tasks, database cache, blocks cache
This commit is contained in:
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import {computed, onMounted, ref, useId} from "vue";
|
import {computed, onMounted, ref, useId} from "vue";
|
||||||
|
import {getThumb} from "@/utils/helpers.js";
|
||||||
|
|
||||||
const id = useId();
|
const id = useId();
|
||||||
const model = defineModel();
|
const model = defineModel();
|
||||||
@@ -26,13 +27,7 @@ const emit = defineEmits(['update:modelValue']);
|
|||||||
const inputRef = ref(null);
|
const inputRef = ref(null);
|
||||||
const isLoaded = ref(false);
|
const isLoaded = ref(false);
|
||||||
|
|
||||||
const thumb = computed(() => {
|
const thumb = computed(() => getThumb(model.value));
|
||||||
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}`;
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const input = inputRef.value;
|
const input = inputRef.value;
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<SettingsItem :label="label">
|
<SettingsItem :label="label">
|
||||||
<template #default>
|
<template #default>
|
||||||
<OcImagePicker v-model="model"/>
|
<OcImagePicker v-model="model" class="tw:w-30"/>
|
||||||
</template>
|
</template>
|
||||||
<template #help><slot></slot></template>
|
<template #help><slot></slot></template>
|
||||||
</SettingsItem>
|
</SettingsItem>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export function getThumb(imageUrl) {
|
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 extIndex = imageUrl.lastIndexOf('.');
|
||||||
const ext = imageUrl.substring(extIndex);
|
const ext = imageUrl.substring(extIndex);
|
||||||
const filename = imageUrl.substring(0, extIndex);
|
const filename = imageUrl.substring(0, extIndex);
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use Bastion\ApplicationFactory;
|
use Bastion\ApplicationFactory;
|
||||||
use Cart\User;
|
use Cart\User;
|
||||||
|
use Openguru\OpenCartFramework\Application;
|
||||||
use Openguru\OpenCartFramework\Http\Response as HttpResponse;
|
use Openguru\OpenCartFramework\Http\Response as HttpResponse;
|
||||||
use Openguru\OpenCartFramework\Logger\LoggerInterface;
|
use Openguru\OpenCartFramework\Logger\LoggerInterface;
|
||||||
use Openguru\OpenCartFramework\Logger\OpenCartLogAdapter;
|
use Openguru\OpenCartFramework\Logger\OpenCartLogAdapter;
|
||||||
@@ -94,14 +95,12 @@ class ControllerExtensionModuleTgshop extends Controller
|
|||||||
|
|
||||||
public function index(): void
|
public function index(): void
|
||||||
{
|
{
|
||||||
$this->cleanUpOldAssets();
|
$this->runMaintenanceTasks();
|
||||||
$this->migrateFromOldSettings();
|
|
||||||
$this->removeLegacyFiles();
|
|
||||||
$this->injectVueJs();
|
$this->injectVueJs();
|
||||||
$this->config();
|
$this->showConfigPage();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function config(): void
|
private function showConfigPage(): void
|
||||||
{
|
{
|
||||||
$data = [];
|
$data = [];
|
||||||
$this->document->setTitle($this->language->get('heading_title'));
|
$this->document->setTitle($this->language->get('heading_title'));
|
||||||
@@ -124,51 +123,8 @@ class ControllerExtensionModuleTgshop extends Controller
|
|||||||
public function handle(): void
|
public function handle(): void
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$json = $this->model_setting_setting->getSetting('module_telecart');
|
$this
|
||||||
if (! isset($json['module_telecart_settings'])) {
|
->createApplication()
|
||||||
$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,
|
|
||||||
))
|
|
||||||
->bootAndHandleRequest();
|
->bootAndHandleRequest();
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
$this->log->write('[TELECART] Error: ' . $e->getMessage());
|
$this->log->write('[TELECART] Error: ' . $e->getMessage());
|
||||||
@@ -264,68 +220,6 @@ class ControllerExtensionModuleTgshop extends Controller
|
|||||||
return $map;
|
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
|
private function injectVueJs(): void
|
||||||
{
|
{
|
||||||
$appDir = rtrim(DIR_APPLICATION, '/');
|
$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');
|
$json = $this->model_setting_setting->getSetting('module_telecart');
|
||||||
if (! $legacySettings) {
|
if (! isset($json['module_telecart_settings'])) {
|
||||||
return;
|
$json['module_telecart_settings'] = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$newSettings = $this->model_setting_setting->getSetting('module_telecart');
|
$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/#/',
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
|
||||||
static $mapLegacyToNewSettings = [
|
$app = ApplicationFactory::create($items);
|
||||||
'module_tgshop_app_icon' => 'app.app_icon',
|
$app->bind(OcRegistryDecorator::class, fn() => new OcRegistryDecorator($this->registry));
|
||||||
'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) {
|
$app
|
||||||
$data = [];
|
->withLogger(fn() => new OpenCartLogAdapter(
|
||||||
Arr::set($data, 'app.app_icon', $legacySettings['module_tgshop_app_icon']);
|
$this->log,
|
||||||
|
'TeleCartAdmin',
|
||||||
|
$app->getConfigValue('app.app_debug')
|
||||||
|
? LoggerInterface::LEVEL_DEBUG
|
||||||
|
: LoggerInterface::LEVEL_INFO,
|
||||||
|
));
|
||||||
|
|
||||||
foreach ($mapLegacyToNewSettings as $key => $value) {
|
return $app;
|
||||||
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,
|
|
||||||
]);
|
|
||||||
|
|
||||||
$this->log->write('[TELECART] Выполнено обновление настроек с 1й версии модуля.');
|
|
||||||
$this->session->data['success'] = 'Выполнено обновление настроек с прошлой версии модуля.';
|
|
||||||
}
|
|
||||||
|
|
||||||
$this->model_setting_setting->deleteSetting('module_tgshop');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function removeLegacyFiles(): void
|
private function runMaintenanceTasks(): void
|
||||||
{
|
{
|
||||||
$legacyFilesToRemove = [
|
$this->createApplication()->runMaintenanceTasks();
|
||||||
DIR_TEMPLATE . '/extension/module/tgshop_init.twig',
|
|
||||||
];
|
|
||||||
|
|
||||||
foreach ($legacyFilesToRemove as $file) {
|
|
||||||
if (file_exists($file)) {
|
|
||||||
unlink($file);
|
|
||||||
$this->log->write('[TELECART] Удалён старый файл: ' . $file);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class ApplicationFactory
|
|||||||
{
|
{
|
||||||
public static function create(array $settings): Application
|
public static function create(array $settings): Application
|
||||||
{
|
{
|
||||||
$defaultConfig = require __DIR__ . '/../src/config.php';
|
$defaultConfig = require __DIR__ . '/../configs/app.php';
|
||||||
$routes = require __DIR__ . '/routes.php';
|
$routes = require __DIR__ . '/routes.php';
|
||||||
|
|
||||||
$merged = Arr::mergeArraysRecursively($defaultConfig, $settings);
|
$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",
|
"intervention/image": "^2.7",
|
||||||
"vlucas/phpdotenv": "^5.6",
|
"vlucas/phpdotenv": "^5.6",
|
||||||
"guzzlehttp/guzzle": "^7.9",
|
"guzzlehttp/guzzle": "^7.9",
|
||||||
"symfony/cache": "^5.4"
|
"symfony/cache": "^5.4",
|
||||||
|
"doctrine/dbal": "^3.10"
|
||||||
},
|
},
|
||||||
"require-dev": {
|
"require-dev": {
|
||||||
"roave/security-advisories": "dev-latest",
|
"roave/security-advisories": "dev-latest",
|
||||||
|
|||||||
@@ -4,8 +4,262 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "e8ed2d3d0e11eac86a27bb2972b115cd",
|
"content-hash": "3429e02a0f2458ebcd70b11ef4c3d0df",
|
||||||
"packages": [
|
"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",
|
"name": "graham-campbell/result-type",
|
||||||
"version": "v1.1.3",
|
"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\Http\Request;
|
||||||
use Openguru\OpenCartFramework\Logger\Logger;
|
use Openguru\OpenCartFramework\Logger\Logger;
|
||||||
use Openguru\OpenCartFramework\Logger\LoggerInterface;
|
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\Router\Router;
|
||||||
use Openguru\OpenCartFramework\Support\ExecutionTimeProfiler;
|
use Openguru\OpenCartFramework\Support\ExecutionTimeProfiler;
|
||||||
|
|
||||||
@@ -160,4 +163,16 @@ class Application extends Container
|
|||||||
{
|
{
|
||||||
return $this->routes;
|
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
|
interface CacheInterface
|
||||||
{
|
{
|
||||||
public function get(string $key);
|
public function get(string $key);
|
||||||
|
|
||||||
public function set(string $key, $value, ?int $ttlSeconds = null): void;
|
public function set(string $key, $value, ?int $ttlSeconds = null): void;
|
||||||
|
|
||||||
public function delete(string $key): void;
|
public function delete(string $key): void;
|
||||||
|
|
||||||
public function clear(): void;
|
public function clear(): void;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,32 @@
|
|||||||
|
|
||||||
namespace Openguru\OpenCartFramework\Cache;
|
namespace Openguru\OpenCartFramework\Cache;
|
||||||
|
|
||||||
|
use Openguru\OpenCartFramework\Container\Container;
|
||||||
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
use Openguru\OpenCartFramework\Container\ServiceProvider;
|
||||||
|
use Symfony\Component\Cache\Adapter\DoctrineDbalAdapter;
|
||||||
|
|
||||||
class CacheServiceProvider extends ServiceProvider
|
class CacheServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
$this->container->singleton(CacheInterface::class, function () {
|
$this->container->singleton(DoctrineDbalAdapter::class, function (Container $container) {
|
||||||
return new SymfonyFilesystemCache('app.cache', 0, DIR_CACHE);
|
$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\Container\ServiceProvider;
|
||||||
use Openguru\OpenCartFramework\Settings\DatabaseUserSettings;
|
use Openguru\OpenCartFramework\Settings\DatabaseUserSettings;
|
||||||
use Openguru\OpenCartFramework\Settings\UserSettingsInterface;
|
use Openguru\OpenCartFramework\Settings\UserSettingsInterface;
|
||||||
|
use Openguru\OpenCartFramework\Support\SupportServiceProvider;
|
||||||
use RuntimeException;
|
use RuntimeException;
|
||||||
|
|
||||||
class DependencyRegistration
|
class DependencyRegistration
|
||||||
@@ -16,6 +17,8 @@ class DependencyRegistration
|
|||||||
return $container->get(DatabaseUserSettings::class);
|
return $container->get(DatabaseUserSettings::class);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$container->get(SupportServiceProvider::class)->register();
|
||||||
|
|
||||||
static::registerServiceProviders($container, $serviceProviders);
|
static::registerServiceProviders($container, $serviceProviders);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,7 +26,7 @@ class DependencyRegistration
|
|||||||
{
|
{
|
||||||
foreach ($serviceProviders as $serviceProvider) {
|
foreach ($serviceProviders as $serviceProvider) {
|
||||||
$provider = $container->get($serviceProvider);
|
$provider = $container->get($serviceProvider);
|
||||||
if (!$provider instanceof ServiceProvider) {
|
if (! $provider instanceof ServiceProvider) {
|
||||||
throw new RuntimeException('ServiceProvider must extend 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
|
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')));
|
$this->logger->error('Handled PHP error: ' . implode(', ', compact('severity', 'message', 'file', 'line')));
|
||||||
|
|
||||||
// Convert warnings and notices to ErrorException
|
// Convert warnings and notices to ErrorException
|
||||||
if (!(error_reporting() & $severity)) {
|
if (! (error_reporting() & $severity)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,11 +55,12 @@ class ErrorHandler
|
|||||||
$response = $customHandler->respond($exception);
|
$response = $customHandler->respond($exception);
|
||||||
if ($response !== null) {
|
if ($response !== null) {
|
||||||
$response->send();
|
$response->send();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!$exception instanceof NonLoggableExceptionInterface) {
|
if (! $exception instanceof NonLoggableExceptionInterface) {
|
||||||
$this->logger->logException($exception);
|
$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')) {
|
if (! function_exists('resources_path')) {
|
||||||
function resources_path(string $path = ''): string
|
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')) {
|
if (! function_exists('base_path')) {
|
||||||
function base_path(string $path = ''): string
|
function base_path(string $path = ''): string
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ class ApplicationFactory
|
|||||||
{
|
{
|
||||||
public static function create(array $config): Application
|
public static function create(array $config): Application
|
||||||
{
|
{
|
||||||
$defaultConfig = require __DIR__ . '/config.php';
|
$defaultConfig = require __DIR__ . '/../configs/app.php';
|
||||||
$routes = require __DIR__ . '/routes.php';
|
$routes = require __DIR__ . '/routes.php';
|
||||||
|
|
||||||
return (new Application(Arr::mergeArraysRecursively($defaultConfig, $config)))
|
return (new Application(Arr::mergeArraysRecursively($defaultConfig, $config)))
|
||||||
|
|||||||
@@ -39,13 +39,24 @@ class BlocksService
|
|||||||
|
|
||||||
public function process(array $block): array
|
public function process(array $block): array
|
||||||
{
|
{
|
||||||
$method = self::$processors[$block['type']] ?? null;
|
$blockType = $block['type'];
|
||||||
|
$cacheKey = "block_$blockType";
|
||||||
|
$cacheTtlSeconds = 60;
|
||||||
|
|
||||||
if (! $method) {
|
$data = $this->cache->get($cacheKey);
|
||||||
throw new RuntimeException('Processor for block type ' . $block['type'] . ' does not exist');
|
if (! $data) {
|
||||||
|
$method = self::$processors[$block['type']] ?? null;
|
||||||
|
|
||||||
|
if (! $method) {
|
||||||
|
throw new RuntimeException('Processor for block type ' . $block['type'] . ' does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = call_user_func_array($method, [$block]);
|
||||||
|
|
||||||
|
$this->cache->set($cacheKey, $data, $cacheTtlSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
return call_user_func_array($method, [$block]);
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function processSlider(array $block): array
|
private function processSlider(array $block): array
|
||||||
|
|||||||
Reference in New Issue
Block a user