Implement PSR3 and PHP Monolog (#19)

* feat: install monolog composer lib
* feat: implement psr3 and monolog
* feat: display logs in frontend
* fix: tests
* build: update cicd to run tests for PR
* build: add phpcs to cicd
* refactor: fix phpcs problems
This commit is contained in:
2025-11-17 15:00:54 +03:00
committed by GitHub
parent 770ec81fdc
commit d6db083dea
66 changed files with 466 additions and 337 deletions

View File

@@ -6,6 +6,11 @@ on:
- master
- 'issue/**'
- develop
pull_request:
types:
- opened
- synchronize
- reopened
permissions:
contents: write
@@ -51,6 +56,22 @@ jobs:
APP_ENV: testing
run: ./vendor/bin/phpunit --testdox tests/Unit tests/Telegram
phpcs:
name: Run PHP_CodeSniffer
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup PHP 7.4
uses: shivammathur/setup-php@v2
with:
php-version: '7.4'
tools: phpcs
- name: Run PHP_CodeSniffer
working-directory: module/oc_telegram_shop/upload/oc_telegram_shop
run: phpcs --standard=PSR12 bastion framework src
module-build:
name: Build module.
runs-on: ubuntu-latest

View File

@@ -46,6 +46,9 @@ dev-admin:
lint:
docker compose exec -w /module/oc_telegram_shop/upload/oc_telegram_shop web bash -c "./vendor/bin/phpstan analyse src framework"
phpcs:
docker compose exec -w /module/oc_telegram_shop/upload/oc_telegram_shop web bash -c "./vendor/bin/phpcs --standard=PSR12 bastion framework src"
test:
docker compose exec -w /module/oc_telegram_shop/upload/oc_telegram_shop web bash -c "./vendor/bin/phpunit --testdox tests/"

View File

@@ -29,6 +29,10 @@
<li :class="{active: route.name === 'mainpage'}">
<RouterLink :to="{name: 'mainpage'}">Главная страница</RouterLink>
</li>
<li :class="{active: route.name === 'logs'}">
<RouterLink :to="{name: 'logs'}">Журнал событий</RouterLink>
</li>
</ul>
<section class="form-horizontal tab-content">

View File

@@ -1,9 +1,6 @@
@layer theme, base, components, utilities;
@import "tailwindcss/theme.css" layer(theme) prefix(tw);
@import "tailwindcss/utilities.css" layer(utilities) prefix(tw);
@plugin "daisyui" {
prefix: 'd-'
}
@layer components {
.tw\:d-toggle {
@@ -24,7 +21,6 @@ html {
font-size: 14px;
}
.p-toast .p-toast-message-success {
color: #3c763d;
background-color: #dff0d8;

View File

@@ -0,0 +1,18 @@
<template>
<textarea v-text="rows" rows="40" class="tw:w-full"/>
</template>
<script setup>
import {onMounted, ref} from "vue";
import {apiGet} from "@/utils/http.js";
const rows = ref('');
onMounted(async () => {
const response = await apiGet('getLogs');
rows.value = response.data;
});
</script>
<style scoped>
</style>

View File

@@ -6,6 +6,7 @@ import TelegramView from "@/views/TelegramView.vue";
import MetricsView from "@/views/MetricsView.vue";
import StoreView from "@/views/StoreView.vue";
import MainPageView from "@/views/MainPageView.vue";
import LogsView from "@/views/LogsView.vue";
const router = createRouter({
history: createMemoryHistory(),
@@ -17,6 +18,7 @@ const router = createRouter({
{path: '/metrics', name: 'metrics', component: MetricsView},
{path: '/store', name: 'store', component: StoreView},
{path: '/mainpage', name: 'mainpage', component: MainPageView},
{path: '/logs', name: 'logs', component: LogsView},
],
});

View File

@@ -0,0 +1,8 @@
<template>
<LogsViewer/>
</template>
<script setup>
import LogsViewer from "@/components/LogsViewer.vue";
</script>

View File

@@ -2,14 +2,15 @@
use Bastion\ApplicationFactory;
use Cart\User;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Openguru\OpenCartFramework\Application;
use Openguru\OpenCartFramework\Http\Response as HttpResponse;
use Openguru\OpenCartFramework\ImageTool\ImageTool;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Openguru\OpenCartFramework\Logger\OpenCartLogAdapter;
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
use Openguru\OpenCartFramework\Support\Arr;
use Psr\Log\LoggerInterface;
$sysLibPath = rtrim(DIR_SYSTEM, '/') . '/library/oc_telegram_shop';
$basePath = rtrim(DIR_APPLICATION, '/') . '/..';
@@ -124,17 +125,18 @@ class ControllerExtensionModuleTgshop extends Controller
public function handle(): void
{
$logger = $this->createLogger();
try {
$this
->createApplication()
->createApplication($logger)
->bootAndHandleRequest();
} catch (Exception $e) {
$logger = new OpenCartLogAdapter($this->log, 'TeleCart');
$logger->logException($e);
} catch (Throwable $e) {
$logger->error($e->getMessage(), ['exception' => $e]);
http_response_code(HttpResponse::HTTP_INTERNAL_SERVER_ERROR);
header('Content-Type: application/json');
echo json_encode([
'error' => 'Server Error.',
'error' => getenv('APP_DEBUG') ? $e->getMessage() : 'Server Error.',
], JSON_THROW_ON_ERROR);
}
}
@@ -237,7 +239,7 @@ class ControllerExtensionModuleTgshop extends Controller
}
}
private function createApplication(): Application
private function createApplication(LoggerInterface $logger): Application
{
$json = $this->model_setting_setting->getSetting('module_telecart');
if (! isset($json['module_telecart_settings'])) {
@@ -250,7 +252,7 @@ class ControllerExtensionModuleTgshop extends Controller
'language_id' => (int) $this->config->get('config_language_id'),
],
'logs' => [
'path' => DIR_LOGS,
'path' => DIR_LOGS . '/telecart.log',
],
'database' => [
'host' => DB_HOSTNAME,
@@ -276,21 +278,32 @@ class ControllerExtensionModuleTgshop extends Controller
$app = ApplicationFactory::create($items);
$app->bind(OcRegistryDecorator::class, fn() => new OcRegistryDecorator($this->registry));
$app->bind(ImageToolInterface::class, fn() => new ImageTool(DIR_IMAGE, HTTPS_SERVER));
$app
->withLogger(fn() => new OpenCartLogAdapter(
$this->log,
'TeleCartAdmin',
$app->getConfigValue('app.app_debug')
? LoggerInterface::LEVEL_DEBUG
: LoggerInterface::LEVEL_INFO,
));
$app->setLogger($logger);
return $app;
}
private function runMaintenanceTasks(): void
{
$this->createApplication()->runMaintenanceTasks();
$logger = $this->createLogger();
try {
$this->createApplication($logger)->runMaintenanceTasks();
} catch (Throwable $exception) {
$logger->error($exception->getMessage(), ['exception' => $exception]);
}
}
private function createLogger(bool $debug = false): Logger
{
$log = new Logger('TeleCart_Admin');
$log->pushHandler(
new StreamHandler(
DIR_LOGS . '/telecart.log',
$debug ? Logger::DEBUG : Logger::INFO,
)
);
return $log;
}
}

View File

@@ -1,16 +1,6 @@
{{ header }}{{ column_left }}
<div id="content">
<div class="page-header">
<div class="container-fluid">
<h1>{{ heading_title }}</h1>
<ul class="breadcrumb">
{% for breadcrumb in breadcrumbs %}
<li><a href="{{ breadcrumb.href }}">{{ breadcrumb.text }}</a></li>
{% endfor %}
</ul>
</div>
</div>
<div class="container-fluid">
<div class="container-fluid" style="margin-top: 10px;">
{% if telecart_error_warning %}
<div class="alert alert-danger alert-dismissible"><i
class="fa fa-exclamation-circle"></i> {{ telecart_error_warning }}

View File

@@ -5,13 +5,14 @@ use App\ApplicationFactory;
use Cart\Cart;
use Cart\Currency;
use Cart\Tax;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Openguru\OpenCartFramework\Http\Response as HttpResponse;
use Openguru\OpenCartFramework\ImageTool\ImageTool;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Openguru\OpenCartFramework\Logger\OpenCartLogAdapter;
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
use Openguru\OpenCartFramework\Support\Arr;
use Psr\Log\LoggerInterface;
$sysLibPath = rtrim(DIR_SYSTEM, '/') . '/library/oc_telegram_shop';
$basePath = rtrim(DIR_APPLICATION, '/') . '/..';
@@ -40,6 +41,8 @@ class ControllerExtensionTgshopHandle extends Controller
public function index(): void
{
$bootstrapLogger = $this->createLogger();
try {
$this->session->data['language'] = $this->config->get('config_language');
@@ -91,17 +94,10 @@ class ControllerExtensionTgshopHandle extends Controller
$app->singleton(Log::class, fn() => $this->log);
$app
->withLogger(
fn() => new OpenCartLogAdapter(
$this->log,
'TeleCart',
$appDebug ? LoggerInterface::LEVEL_DEBUG : LoggerInterface::LEVEL_WARNING,
)
)
->withLogger(fn($app) => $this->createLogger($appDebug))
->bootAndHandleRequest();
} catch (Exception $e) {
$logger = new OpenCartLogAdapter($this->log, 'TeleCart');
$logger->logException($e);
} catch (Throwable $e) {
$bootstrapLogger->error($e->getMessage(), ['exception' => $e]);
http_response_code(HttpResponse::HTTP_INTERNAL_SERVER_ERROR);
header('Content-Type: application/json');
echo json_encode([
@@ -153,4 +149,17 @@ class ControllerExtensionTgshopHandle extends Controller
http_response_code(404);
}
}
private function createLogger(bool $appDebug = false, string $app = 'TeleCart'): LoggerInterface
{
$log = new Logger($app);
$log->pushHandler(
new StreamHandler(
DIR_LOGS . '/telecart.log',
$appDebug ? Logger::DEBUG : Logger::INFO,
)
);
return $log;
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Bastion\Handlers;
use Openguru\OpenCartFramework\Config\Settings;
use Openguru\OpenCartFramework\Http\JsonResponse;
use Psr\Log\LoggerInterface;
class LogsHandler
{
private Settings $settings;
private LoggerInterface $logger;
public function __construct(Settings $settings, LoggerInterface $logger)
{
$this->settings = $settings;
$this->logger = $logger;
}
public function getLogs(): JsonResponse
{
$logsPath = $this->settings->get('logs.path');
$data = implode(PHP_EOL, $this->readLastLogsRows($logsPath));
return new JsonResponse(compact('data'));
}
private function readLastLogsRows(string $path, int $lines = 1000, int $buffer = 4096): array
{
$f = fopen($path, 'rb');
if (! $f) {
return [];
}
$lineCount = 0;
$chunk = '';
fseek($f, 0, SEEK_END);
$filesize = ftell($f);
while ($filesize > 0 && $lineCount < $lines) {
$seek = max($filesize - $buffer, 0);
$readLength = $filesize - $seek;
fseek($f, $seek);
$chunk = fread($f, $readLength) . $chunk;
$filesize = $seek;
$lineCount = substr_count($chunk, "\n");
}
fclose($f);
$linesArray = explode("\n", $chunk);
return array_reverse(array_slice($linesArray, -$lines));
}
}

View File

@@ -33,20 +33,24 @@ class TelegramHandler
if (! $message) {
return new JsonResponse([
// phpcs:ignore Generic.Files.LineLength
'message' => 'Сообщение не найдено. Убедитесь что отправили кодовое слово в чат с ботом и повторите через 10 секунд. У Вас есть 60 секунд после отправки сообщения в чат, чтобы нажать на кнопку! Это сделано в целях безопасности.'
], Response::HTTP_UNPROCESSABLE_ENTITY);
}
$text = Arr::get($message, 'text');
if ($text !== 'opencart_get_chatid') {
return new JsonResponse(['message' => 'Последнее сообщение в чате не содержит кодовое слово.'],
Response::HTTP_UNPROCESSABLE_ENTITY);
return new JsonResponse(
['message' => 'Последнее сообщение в чате не содержит кодовое слово.'],
Response::HTTP_UNPROCESSABLE_ENTITY
);
}
$chatId = Arr::get($message, 'chat.id');
if (! $chatId) {
return new JsonResponse([
// phpcs:ignore Generic.Files.LineLength
'message' => 'ChatID не найден. Убедитесь что отправили кодовое слово в чат с ботом и повторите через 10 секунд.'
], Response::HTTP_UNPROCESSABLE_ENTITY);
}

View File

@@ -8,7 +8,7 @@ use App\Services\SettingsService;
use Bastion\Exceptions\BotTokenConfiguratorException;
use Exception;
use GuzzleHttp\Exception\GuzzleException;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
use Openguru\OpenCartFramework\Router\Router;
use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\Telegram\Exceptions\TelegramClientException;
@@ -66,7 +66,7 @@ class BotTokenConfigurator
}
throw new BotTokenConfiguratorException($exception->getMessage());
} catch (Exception|GuzzleException $exception) {
} catch (Exception | GuzzleException $exception) {
$this->logger->logException($exception);
throw new BotTokenConfiguratorException($exception->getMessage());
}

View File

@@ -2,6 +2,7 @@
use Bastion\Handlers\AutocompleteHandler;
use Bastion\Handlers\DictionariesHandler;
use Bastion\Handlers\LogsHandler;
use Bastion\Handlers\SettingsHandler;
use Bastion\Handlers\StatsHandler;
use Bastion\Handlers\TelegramHandler;
@@ -22,4 +23,5 @@ return [
'getAutocompleteCategories' => [AutocompleteHandler::class, 'getCategories'],
'getAutocompleteCategoriesFlat' => [AutocompleteHandler::class, 'getCategoriesFlat'],
'resetCache' => [SettingsHandler::class, 'resetCache'],
'getLogs' => [LogsHandler::class, 'getLogs'],
];

View File

@@ -27,13 +27,16 @@
"vlucas/phpdotenv": "^5.6",
"guzzlehttp/guzzle": "^7.9",
"symfony/cache": "^5.4",
"doctrine/dbal": "^3.10"
"doctrine/dbal": "^3.10",
"monolog/monolog": "^2.10",
"psr/log": "^1.1"
},
"require-dev": {
"roave/security-advisories": "dev-latest",
"phpstan/phpstan": "^2.1",
"phpunit/phpunit": "^9.6",
"doctrine/sql-formatter": "^1.3",
"mockery/mockery": "^1.6"
"mockery/mockery": "^1.6",
"squizlabs/php_codesniffer": "*"
}
}

View File

@@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "3429e02a0f2458ebcd70b11ef4c3d0df",
"content-hash": "07f450f44362653c6a294138d35f325c",
"packages": [
{
"name": "doctrine/dbal",
@@ -731,6 +731,108 @@
],
"time": "2022-05-21T17:30:32+00:00"
},
{
"name": "monolog/monolog",
"version": "2.10.0",
"source": {
"type": "git",
"url": "https://github.com/Seldaek/monolog.git",
"reference": "5cf826f2991858b54d5c3809bee745560a1042a7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/Seldaek/monolog/zipball/5cf826f2991858b54d5c3809bee745560a1042a7",
"reference": "5cf826f2991858b54d5c3809bee745560a1042a7",
"shasum": ""
},
"require": {
"php": ">=7.2",
"psr/log": "^1.0.1 || ^2.0 || ^3.0"
},
"provide": {
"psr/log-implementation": "1.0.0 || 2.0.0 || 3.0.0"
},
"require-dev": {
"aws/aws-sdk-php": "^2.4.9 || ^3.0",
"doctrine/couchdb": "~1.0@dev",
"elasticsearch/elasticsearch": "^7 || ^8",
"ext-json": "*",
"graylog2/gelf-php": "^1.4.2 || ^2@dev",
"guzzlehttp/guzzle": "^7.4",
"guzzlehttp/psr7": "^2.2",
"mongodb/mongodb": "^1.8",
"php-amqplib/php-amqplib": "~2.4 || ^3",
"phpspec/prophecy": "^1.15",
"phpstan/phpstan": "^1.10",
"phpunit/phpunit": "^8.5.38 || ^9.6.19",
"predis/predis": "^1.1 || ^2.0",
"rollbar/rollbar": "^1.3 || ^2 || ^3",
"ruflin/elastica": "^7",
"swiftmailer/swiftmailer": "^5.3|^6.0",
"symfony/mailer": "^5.4 || ^6",
"symfony/mime": "^5.4 || ^6"
},
"suggest": {
"aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB",
"doctrine/couchdb": "Allow sending log messages to a CouchDB server",
"elasticsearch/elasticsearch": "Allow sending log messages to an Elasticsearch server via official client",
"ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)",
"ext-curl": "Required to send log messages using the IFTTTHandler, the LogglyHandler, the SendGridHandler, the SlackWebhookHandler or the TelegramBotHandler",
"ext-mbstring": "Allow to work properly with unicode symbols",
"ext-mongodb": "Allow sending log messages to a MongoDB server (via driver)",
"ext-openssl": "Required to send log messages using SSL",
"ext-sockets": "Allow sending log messages to a Syslog server (via UDP driver)",
"graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server",
"mongodb/mongodb": "Allow sending log messages to a MongoDB server (via library)",
"php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib",
"rollbar/rollbar": "Allow sending log messages to Rollbar",
"ruflin/elastica": "Allow sending log messages to an Elastic Search server"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "2.x-dev"
}
},
"autoload": {
"psr-4": {
"Monolog\\": "src/Monolog"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Jordi Boggiano",
"email": "j.boggiano@seld.be",
"homepage": "https://seld.be"
}
],
"description": "Sends your logs to files, sockets, inboxes, databases and various web services",
"homepage": "https://github.com/Seldaek/monolog",
"keywords": [
"log",
"logging",
"psr-3"
],
"support": {
"issues": "https://github.com/Seldaek/monolog/issues",
"source": "https://github.com/Seldaek/monolog/tree/2.10.0"
},
"funding": [
{
"url": "https://github.com/Seldaek",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/monolog/monolog",
"type": "tidelift"
}
],
"time": "2024-11-12T12:43:37+00:00"
},
{
"name": "phpoption/phpoption",
"version": "1.9.3",
@@ -4878,6 +4980,85 @@
],
"time": "2020-09-28T06:39:44+00:00"
},
{
"name": "squizlabs/php_codesniffer",
"version": "4.0.1",
"source": {
"type": "git",
"url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git",
"reference": "0525c73950de35ded110cffafb9892946d7771b5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/0525c73950de35ded110cffafb9892946d7771b5",
"reference": "0525c73950de35ded110cffafb9892946d7771b5",
"shasum": ""
},
"require": {
"ext-simplexml": "*",
"ext-tokenizer": "*",
"ext-xmlwriter": "*",
"php": ">=7.2.0"
},
"require-dev": {
"phpunit/phpunit": "^8.4.0 || ^9.3.4 || ^10.5.32 || 11.3.3 - 11.5.28 || ^11.5.31"
},
"bin": [
"bin/phpcbf",
"bin/phpcs"
],
"type": "library",
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Greg Sherwood",
"role": "Former lead"
},
{
"name": "Juliette Reinders Folmer",
"role": "Current lead"
},
{
"name": "Contributors",
"homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors"
}
],
"description": "PHP_CodeSniffer tokenizes PHP files and detects violations of a defined set of coding standards.",
"homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
"keywords": [
"phpcs",
"standards",
"static analysis"
],
"support": {
"issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues",
"security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy",
"source": "https://github.com/PHPCSStandards/PHP_CodeSniffer",
"wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki"
},
"funding": [
{
"url": "https://github.com/PHPCSStandards",
"type": "github"
},
{
"url": "https://github.com/jrfnl",
"type": "github"
},
{
"url": "https://opencollective.com/php_codesniffer",
"type": "open_collective"
},
{
"url": "https://thanks.dev/u/gh/phpcsstandards",
"type": "thanks_dev"
}
],
"time": "2025-11-10T16:43:36+00:00"
},
{
"name": "theseer/tokenizer",
"version": "1.2.3",

View File

@@ -9,15 +9,15 @@ use Openguru\OpenCartFramework\Config\Settings;
use Openguru\OpenCartFramework\Container\Container;
use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\Logger\Logger;
use Openguru\OpenCartFramework\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;
use Psr\Log\LoggerAwareInterface;
use Psr\Log\LoggerInterface;
class Application extends Container
class Application extends Container implements LoggerAwareInterface
{
private static Application $instance;
private static array $events = [];
@@ -27,15 +27,6 @@ class Application extends Container
private LoggerInterface $logger;
public function __construct(array $config)
{
parent::__construct($config);
// Fallback logger
$path = rtrim($this->getConfigValue('logs.path'), '/') . '/oc_telegram_shop.log';
$this->logger = new Logger($path);
}
/**
* @var ExecutionTimeProfiler
*/
@@ -147,7 +138,7 @@ class Application extends Container
public function withLogger(Closure $closure): Application
{
$this->logger = $closure();
$this->setLogger($closure($this));
return $this;
}
@@ -175,4 +166,9 @@ class Application extends Container
$this->get(MaintenanceTasksService::class)->run();
}
public function setLogger(LoggerInterface $logger): void
{
$this->logger = $logger;
}
}

View File

@@ -14,10 +14,12 @@ use ReflectionMethod;
use ReflectionNamedType;
use RuntimeException;
// phpcs:disable PSR1.Files.SideEffects
if (! defined('BP_BASE_PATH')) {
$phar = Phar::running(false);
define('BP_BASE_PATH', $phar ? "phar://$phar" : dirname(__DIR__) . '/..');
}
// phpcs:enable PSR1.Files.SideEffects
class Container implements ContainerInterface
{

View File

@@ -8,7 +8,7 @@ use Openguru\OpenCartFramework\Exceptions\ActionNotFoundException;
use Openguru\OpenCartFramework\Exceptions\NonLoggableExceptionInterface;
use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Response;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
use Throwable;
/**
@@ -34,7 +34,7 @@ class ErrorHandler
public function handleError(int $severity, string $message, string $file, int $line): bool
{
if (($severity & E_DEPRECATED|E_USER_DEPRECATED)) {
if (($severity & E_DEPRECATED | E_USER_DEPRECATED)) {
return true;
}
@@ -61,7 +61,7 @@ class ErrorHandler
}
if (! $exception instanceof NonLoggableExceptionInterface) {
$this->logger->logException($exception);
$this->logger->error($exception->getMessage(), []);
}
if ($exception instanceof ActionNotFoundException) {

View File

@@ -99,7 +99,7 @@ final class Request
*/
public function has($key): bool
{
if (is_array($key)) {
if (is_array($key)) {
foreach ($key as $k) {
if ($this->has($k)) {
return true;

View File

@@ -20,8 +20,13 @@ class ImageTool implements ImageToolInterface
$this->manager = new ImageManager(['driver' => $driver]);
}
public function resize(string $path, ?int $width = null, ?int $height = null, ?string $default = null, string $format = 'webp'): ?string
{
public function resize(
string $path,
?int $width = null,
?int $height = null,
?string $default = null,
string $format = 'webp'
): ?string {
$filename = is_file($this->imageDir . $path) ? $path : $default;
if (! $width && ! $height) {

View File

@@ -1,89 +0,0 @@
<?php
namespace Openguru\OpenCartFramework\Logger;
use Throwable;
class Logger implements LoggerInterface
{
private $logFile;
private $logLevel;
private $maxFileSize; // Максимальный размер файла в байтах
public function __construct($logFile, $logLevel = LoggerInterface::LEVEL_INFO, $maxFileSize = 1048576)
{
$this->logFile = $logFile;
$this->logLevel = $logLevel;
$this->maxFileSize = $maxFileSize;
}
public function log($message, $level = LoggerInterface::LEVEL_INFO)
{
if ($level < $this->logLevel) {
return; // Не логируем, если уровень ниже установленного
}
$this->rotateLogs();
$timestamp = date('Y-m-d H:i:s');
$levelStr = $this->getLevelString($level);
$logMessage = "[$timestamp] [$levelStr] $message\n";
file_put_contents($this->logFile, $logMessage, FILE_APPEND);
}
private function getLevelString($level): string
{
switch ($level) {
case LoggerInterface::LEVEL_INFO:
return 'INFO';
case LoggerInterface::LEVEL_WARNING:
return 'WARNING';
case LoggerInterface::LEVEL_ERROR:
return 'ERROR';
default:
return 'UNKNOWN';
}
}
public function info(string $message): void
{
$this->log($message, LoggerInterface::LEVEL_INFO);
}
public function warning(string $message): void
{
$this->log($message, LoggerInterface::LEVEL_WARNING);
}
public function error(string $message): void
{
$this->log($message, LoggerInterface::LEVEL_ERROR);
}
private function rotateLogs(): void
{
if (is_file($this->logFile) && filesize($this->logFile) >= $this->maxFileSize) {
$newLogFile = $this->logFile . '.' . date('YmdHis');
rename($this->logFile, $newLogFile);
}
}
public function logException(Throwable $exception): void
{
$this->error(
sprintf(
"Fatal error %s in %s on line %d\n%s",
$exception->getMessage(),
$exception->getFile(),
$exception->getLine(),
$exception->getTraceAsString()
)
);
}
public function debug(string $message): void
{
$this->log($message, LoggerInterface::LEVEL_DEBUG);
}
}

View File

@@ -1,25 +0,0 @@
<?php
namespace Openguru\OpenCartFramework\Logger;
use Throwable;
interface LoggerInterface
{
public const LEVEL_DEBUG = 0;
public const LEVEL_INFO = 1;
public const LEVEL_WARNING = 2;
public const LEVEL_ERROR = 3;
public function log($message, $level = self::LEVEL_INFO);
public function debug(string $message): void;
public function info(string $message): void;
public function warning(string $message): void;
public function error(string $message): void;
public function logException(Throwable $exception): void;
}

View File

@@ -1,85 +0,0 @@
<?php
namespace Openguru\OpenCartFramework\Logger;
use Log;
use Throwable;
class OpenCartLogAdapter implements LoggerInterface
{
private Log $ocLogger;
private string $namespace;
private int $minLogLevel;
public function __construct(Log $ocLogger, string $namespace, int $minLogLevel = LoggerInterface::LEVEL_WARNING)
{
$this->ocLogger = $ocLogger;
$this->namespace = $namespace;
$this->minLogLevel = $minLogLevel;
}
public function log($message, $level = LoggerInterface::LEVEL_INFO): void
{
if ($level < $this->minLogLevel) {
return;
}
$this->ocLogger->write(
sprintf(
"[%s] [%s] %s\n",
$this->namespace,
$this->getLevelString($level),
$message
)
);
}
public function info(string $message): void
{
$this->log($message);
}
public function warning(string $message): void
{
$this->log($message, LoggerInterface::LEVEL_WARNING);
}
public function error(string $message): void
{
$this->log($message, LoggerInterface::LEVEL_ERROR);
}
public function logException(Throwable $exception): void
{
$this->error(
sprintf(
"Fatal error %s in %s on line %d\n%s",
$exception->getMessage(),
$exception->getFile(),
$exception->getLine(),
$exception->getTraceAsString()
)
);
}
private function getLevelString($level): string
{
switch ($level) {
case LoggerInterface::LEVEL_DEBUG:
return 'DEBUG';
case LoggerInterface::LEVEL_INFO:
return 'INFO';
case LoggerInterface::LEVEL_WARNING:
return 'WARNING';
case LoggerInterface::LEVEL_ERROR:
return 'ERROR';
default:
return 'UNKNOWN';
}
}
public function debug(string $message): void
{
$this->log($message, LoggerInterface::LEVEL_DEBUG);
}
}

View File

@@ -2,7 +2,7 @@
namespace Openguru\OpenCartFramework\MaintenanceTasks;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
abstract class BaseMaintenanceTask implements MaintenanceTaskInterface
{

View File

@@ -2,7 +2,7 @@
namespace Openguru\OpenCartFramework\MaintenanceTasks;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
use Openguru\OpenCartFramework\Migrations\MigratorService;
class MaintenanceTasksService

View File

@@ -4,7 +4,7 @@ namespace Openguru\OpenCartFramework\MaintenanceTasks;
use Openguru\OpenCartFramework\Container\Container;
use Openguru\OpenCartFramework\Container\ServiceProvider;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
use Openguru\OpenCartFramework\Migrations\MigratorService;
class MaintenanceTasksServiceProvider extends ServiceProvider

View File

@@ -3,7 +3,7 @@
namespace Openguru\OpenCartFramework\Migrations;
use Openguru\OpenCartFramework\Application;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
abstract class Migration

View File

@@ -4,7 +4,7 @@ namespace Openguru\OpenCartFramework\Migrations;
use Openguru\OpenCartFramework\Container\Container;
use Openguru\OpenCartFramework\Container\ServiceProvider;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
use Openguru\OpenCartFramework\Support\WorkLogsBag;

View File

@@ -3,8 +3,8 @@
namespace Openguru\OpenCartFramework\Migrations;
use Exception;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
use Psr\Log\LoggerInterface;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use RuntimeException;
@@ -34,7 +34,7 @@ class MigratorService
$count = $this->applyMigrations($migrationsToApply);
if ($count > 0) {
$this->logger->log("$count migrations applied");
$this->logger->info("$count migrations applied");
}
}
@@ -52,7 +52,7 @@ class MigratorService
$this->createMigrationsTableSql()
);
$this->logger->log('Migrations table created.');
$this->logger->info('Migrations table created.');
}
}
@@ -103,13 +103,12 @@ SQL;
$migration->up();
$this->writeMigration($file);
$this->logger->log("Migrated $file");
$this->logger->info("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);
$this->logger->error("An error occurred while applying migration.", ['exception' => $e]);
}
}

View File

@@ -192,11 +192,6 @@ class Arr
* @param array $array Исходный массив
* @param array $keys Массив ключей в dot notation (например, ['app.name', 'app.logs.path', 'telegram.bot_token'])
* @return array Отфильтрованный массив с сохранением структуры
*
* @example
* $array = ['app' => ['name' => 'MyApp', 'logs' => ['path' => '/var/log']], 'telegram' => ['bot_token' => 'token']];
* Arr::getWithKeys($array, ['app.name', 'app.logs.path', 'telegram.bot_token'])
* // Вернет: ['app' => ['name' => 'MyApp', 'logs' => ['path' => '/var/log']], 'telegram' => ['bot_token' => 'token']]
*/
public static function getWithKeys(array $array, array $keys): array
{

View File

@@ -34,4 +34,3 @@ class Str
return html_entity_decode($string, ENT_QUOTES | ENT_HTML5, 'UTF-8');
}
}

View File

@@ -4,5 +4,4 @@ namespace Openguru\OpenCartFramework\Telegram;
class RequestValidator
{
}

View File

@@ -4,7 +4,7 @@ namespace Openguru\OpenCartFramework\Telegram;
use App\Services\SettingsService;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
use Openguru\OpenCartFramework\Telegram\Exceptions\TelegramInvalidSignatureException;
class SignatureValidator

View File

@@ -5,7 +5,7 @@ namespace Openguru\OpenCartFramework\Telegram;
use App\Services\SettingsService;
use Openguru\OpenCartFramework\Application;
use Openguru\OpenCartFramework\Container\ServiceProvider;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
class TelegramServiceProvider extends ServiceProvider
{

View File

@@ -4,5 +4,4 @@ namespace App\Adapters;
class OcCartAdapter
{
}

View File

@@ -33,4 +33,3 @@ final class MetricsDTO
];
}
}

View File

@@ -31,4 +31,3 @@ final class OrdersDTO
];
}
}

View File

@@ -69,4 +69,3 @@ final class StoreDTO
];
}
}

View File

@@ -60,4 +60,3 @@ final class TelegramDTO
];
}
}

View File

@@ -42,4 +42,3 @@ final class TextsDTO
];
}
}

View File

@@ -11,7 +11,7 @@ use Openguru\OpenCartFramework\Exceptions\EntityNotFoundException;
use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\Http\Response;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
use RuntimeException;
class ProductsHandler

View File

@@ -8,7 +8,7 @@ use Openguru\OpenCartFramework\Cache\CacheInterface;
use Openguru\OpenCartFramework\Container\Container;
use Openguru\OpenCartFramework\Http\JsonResponse;
use Openguru\OpenCartFramework\Http\Request;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\Telegram\Contracts\TelegramCommandInterface;
use Openguru\OpenCartFramework\Telegram\Exceptions\TelegramCommandNotFoundException;

View File

@@ -4,7 +4,7 @@ namespace App\Services;
use Openguru\OpenCartFramework\Cache\CacheInterface;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;
use RuntimeException;

View File

@@ -21,9 +21,13 @@ class CartService
$this->oc->load->language('checkout/cart');
if ($this->oc->cart->hasProducts()) {
if (! $this->oc->cart->hasStock() && (! $this->oc->config->get('config_stock_checkout') || $this->oc->config->get(
'config_stock_warning'
))) {
if (
! $this->oc->cart->hasStock()
&& (
! $this->oc->config->get('config_stock_checkout')
|| $this->oc->config->get('config_stock_warning')
)
) {
$data['error_warning'] = $this->oc->language->get('error_stock');
} elseif (isset($this->oc->session->data['error'])) {
$data['error_warning'] = $this->oc->session->data['error'];
@@ -154,19 +158,19 @@ class CartService
if ($product['recurring']['trial']) {
$recurring = sprintf(
$this->oc->language->get('text_trial_description'),
$this->oc->currency->format(
$this->oc->tax->calculate(
$product['recurring']['trial_price'] * $product['quantity'],
$product['tax_class_id'],
$this->oc->config->get('config_tax')
),
$this->oc->session->data['currency']
$this->oc->language->get('text_trial_description'),
$this->oc->currency->format(
$this->oc->tax->calculate(
$product['recurring']['trial_price'] * $product['quantity'],
$product['tax_class_id'],
$this->oc->config->get('config_tax')
),
$product['recurring']['trial_cycle'],
$frequencies[$product['recurring']['trial_frequency']],
$product['recurring']['trial_duration']
) . ' ';
$this->oc->session->data['currency']
),
$product['recurring']['trial_cycle'],
$frequencies[$product['recurring']['trial_frequency']],
$product['recurring']['trial_duration']
) . ' ';
}
if ($product['recurring']['duration']) {
@@ -212,8 +216,8 @@ class CartService
'recurring' => $recurring,
'quantity' => (int) $product['quantity'],
'stock' => $product['stock'] ? true : ! (! $this->oc->config->get(
'config_stock_checkout'
) || $this->oc->config->get('config_stock_warning')),
'config_stock_checkout'
) || $this->oc->config->get('config_stock_warning')),
'reward' => ($product['reward'] ? sprintf(
$this->oc->language->get('text_points'),
$product['reward']
@@ -283,7 +287,9 @@ class CartService
$lastTotal = $totals[count($totals) - 1] ?? false;
$data['total'] = $lastTotal ? $lastTotal['value'] : 0;
$data['total_text'] = $lastTotal ? $this->oc->currency->format($lastTotal['value'], $this->oc->session->data['currency']) : 0;
$data['total_text'] = $lastTotal
? $this->oc->currency->format($lastTotal['value'], $this->oc->session->data['currency'])
: 0;
$data['total_products_count'] = $this->oc->cart->countProducts();
} else {
$data['text_error'] = $this->oc->language->get('text_empty');

View File

@@ -7,7 +7,7 @@ namespace App\Services;
use App\Exceptions\OrderValidationFailedException;
use DateTime;
use Exception;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
use Openguru\OpenCartFramework\QueryBuilder\Connections\ConnectionInterface;
use Openguru\OpenCartFramework\Support\Arr;

View File

@@ -9,7 +9,7 @@ use Exception;
use Openguru\OpenCartFramework\CriteriaBuilder\CriteriaBuilder;
use Openguru\OpenCartFramework\Exceptions\EntityNotFoundException;
use Openguru\OpenCartFramework\ImageTool\ImageToolInterface;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
use Openguru\OpenCartFramework\OpenCart\Decorators\OcRegistryDecorator;
use Openguru\OpenCartFramework\QueryBuilder\Builder;
use Openguru\OpenCartFramework\QueryBuilder\JoinClause;

View File

@@ -231,15 +231,19 @@ class SettingsSerializerService
throw new InvalidArgumentException('telegram.chat_id must be an integer or null');
}
if (isset($data['owner_notification_template']) && ! is_string(
if (
isset($data['owner_notification_template']) && ! is_string(
$data['owner_notification_template']
)) {
)
) {
throw new InvalidArgumentException('telegram.owner_notification_template must be a string or null');
}
if (isset($data['customer_notification_template']) && ! is_string(
if (
isset($data['customer_notification_template']) && ! is_string(
$data['customer_notification_template']
)) {
)
) {
throw new InvalidArgumentException('telegram.customer_notification_template must be a string or null');
}

View File

@@ -5,7 +5,7 @@ namespace App\Telegram;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Exception\GuzzleException;
use JsonException;
use Openguru\OpenCartFramework\Logger\LoggerInterface;
use Psr\Log\LoggerInterface;
use Openguru\OpenCartFramework\Support\Arr;
use Openguru\OpenCartFramework\Telegram\Commands\TelegramCommand;
use Openguru\OpenCartFramework\Telegram\Enums\ChatAction;
@@ -41,7 +41,8 @@ class LinkCommand extends TelegramCommand
if (! $state) {
$greeting = $this->telegram->escapeTgSpecialCharacters(
<<<MARKDOWN
Это удобный инструмент, который поможет вам 📎 создать красивое сообщение с кнопкой для открытия вашего 🛒 Telecart магазина.
Это удобный инструмент, который поможет вам 📎 создать красивое
сообщение с кнопкой для открытия вашего 🛒 Telecart магазина.
📌 Такое сообщение можно закрепить в канале или группе.
📤 Переслать клиентам в личные сообщения.

View File

@@ -4,9 +4,12 @@ namespace Tests;
use App\ApplicationFactory;
use Mockery;
use Monolog\Handler\NullHandler;
use Monolog\Logger;
use Openguru\OpenCartFramework\Application;
use Openguru\OpenCartFramework\Container\Container;
use PHPUnit\Framework\TestCase as BaseTestCase;
use Psr\Log\LoggerInterface;
use Tests\Helpers\DatabaseHelpers;
class TestCase extends BaseTestCase
@@ -63,6 +66,8 @@ class TestCase extends BaseTestCase
],
]);
$app->setLogger($this->getNullLogger());
$app->boot();
$this->registerDependenciesForTests($app);
@@ -90,4 +95,12 @@ class TestCase extends BaseTestCase
return $url;
});
}
protected function getNullLogger(): LoggerInterface
{
$logger = new Logger('Telecart_Tests');
$logger->pushHandler(new NullHandler());
return $logger;
}
}

View File

@@ -14,6 +14,7 @@ use DirectoryIterator;
use InvalidArgumentException;
use JsonException;
use Mockery;
use Monolog\Logger;
use Openguru\OpenCartFramework\Application;
use Openguru\OpenCartFramework\Config\Settings;
use Openguru\OpenCartFramework\Container\Container;
@@ -57,6 +58,7 @@ class CriteriaBuilderTest extends TestCase
];
$config = Arr::mergeArraysRecursively($baseSettings, $settingsOverride);
$application = new Application($config);
$application->setLogger($this->getNullLogger());
$mysqlConnection = new MySqlConnection($this->getPdoMock());
$application->boot();