Squashed commit message
Some checks failed
Telegram Mini App Shop Builder / Compute version metadata (push) Has been cancelled
Telegram Mini App Shop Builder / Run Frontend tests (push) Has been cancelled
Telegram Mini App Shop Builder / Run Backend tests (push) Has been cancelled
Telegram Mini App Shop Builder / Run PHP_CodeSniffer (push) Has been cancelled
Telegram Mini App Shop Builder / Build module. (push) Has been cancelled
Telegram Mini App Shop Builder / release (push) Has been cancelled

This commit is contained in:
2026-03-11 22:08:41 +03:00
commit 393bbb286b
585 changed files with 65605 additions and 0 deletions

View File

@@ -0,0 +1,306 @@
<?php
namespace Bastion\Handlers;
use Bastion\Exceptions\BotTokenConfiguratorException;
use Bastion\Services\BotTokenConfigurator;
use Bastion\Services\CronApiKeyRegenerator;
use Bastion\Services\SettingsService;
use Carbon\Carbon;
use Exception;
use Acme\ECommerceFramework\Cache\CacheInterface;
use Acme\ECommerceFramework\Config\Settings;
use Symfony\Component\HttpFoundation\JsonResponse;
use Acme\ECommerceFramework\Http\Request;
use Symfony\Component\HttpFoundation\Response;
use Acme\ECommerceFramework\QueryBuilder\Builder;
use Acme\ECommerceFramework\QueryBuilder\Connections\ConnectionInterface;
use Acme\ECommerceFramework\Scheduler\Models\ScheduledJob;
use Acme\ECommerceFramework\Support\Arr;
use Psr\Log\LoggerInterface;
class SettingsHandler
{
private BotTokenConfigurator $botTokenConfigurator;
private CronApiKeyRegenerator $cronApiKeyRegenerator;
private Settings $settings;
private SettingsService $settingsUpdateService;
private CacheInterface $cache;
private LoggerInterface $logger;
private Builder $builder;
private ConnectionInterface $connection;
private ScheduledJob $scheduledJob;
public function __construct(
BotTokenConfigurator $botTokenConfigurator,
CronApiKeyRegenerator $cronApiKeyRegenerator,
Settings $settings,
SettingsService $settingsUpdateService,
CacheInterface $cache,
LoggerInterface $logger,
Builder $builder,
ConnectionInterface $connection,
ScheduledJob $scheduledJob
) {
$this->botTokenConfigurator = $botTokenConfigurator;
$this->cronApiKeyRegenerator = $cronApiKeyRegenerator;
$this->settings = $settings;
$this->settingsUpdateService = $settingsUpdateService;
$this->cache = $cache;
$this->logger = $logger;
$this->builder = $builder;
$this->connection = $connection;
$this->scheduledJob = $scheduledJob;
}
/**
* Перегенерировать секретный ключ в URL для cron-job.org (сохраняет cron.api_key).
*/
public function regenerateCronScheduleUrl(Request $request): JsonResponse
{
$newApiKey = $this->cronApiKeyRegenerator->regenerate();
$scheduleUrl = $this->buildCronScheduleUrl(
$this->settings->get('app.shop_base_url', ''),
$newApiKey
);
return new JsonResponse(['api_key' => $newApiKey, 'schedule_url' => $scheduleUrl]);
}
public function configureBotToken(Request $request): JsonResponse
{
try {
$data = $this->botTokenConfigurator->configure(trim($request->json('botToken', '')));
return new JsonResponse($data);
} catch (BotTokenConfiguratorException $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_UNPROCESSABLE_ENTITY);
} catch (Exception $e) {
return new JsonResponse(['error' => $e->getMessage()], Response::HTTP_INTERNAL_SERVER_ERROR);
}
}
public function getSettingsForm(): JsonResponse
{
$data = Arr::getWithKeys($this->settings->getAll(), [
'app',
'telegram',
'metrics',
'store',
'orders',
'texts',
'sliders',
'mainpage_blocks',
'pulse',
'cron',
]);
if (!isset($data['cron']['mode'])) {
$data['cron']['mode'] = 'disabled';
}
$data['forms'] = [];
// Add CRON system details (read-only)
$data['cron']['cli_path'] = BP_REAL_BASE_PATH . '/cli.php';
$data['cron']['last_run'] = $this->getLastCronRunDate();
$data['cron']['schedule_url'] = $this->buildCronScheduleUrl(
$this->settings->get('app.shop_base_url', ''),
$this->settings->get('cron.api_key', '')
);
$data['scheduled_jobs'] = $this->scheduledJob->all();
$forms = $this->builder->newQuery()
->from('acmeshop_forms')
->get();
if ($forms) {
foreach ($forms as $form) {
try {
$schema = json_decode($form['schema'] ?? '[]', true, 512, JSON_THROW_ON_ERROR);
} catch (\JsonException $exception) {
$schema = [];
}
$data['forms'][$form['alias']] = [
'alias' => $form['alias'],
'friendly_name' => $form['friendly_name'],
'is_custom' => filter_var($form['is_custom'], FILTER_VALIDATE_BOOLEAN),
'schema' => $schema,
];
}
}
return new JsonResponse(compact('data'));
}
private function buildCronScheduleUrl(string $shopBaseUrl, string $apiKey): string
{
$base = rtrim($shopBaseUrl, '/');
if ($base === '') {
return '';
}
$params = http_build_query([
'route' => 'extension/tgshop/handle',
'api_action' => 'runSchedule',
'api_key' => $apiKey,
]);
return $base . '/index.php?' . $params;
}
public function saveSettingsForm(Request $request): JsonResponse
{
$input = $request->json();
$this->validate($input);
// Remove dynamic properties before saving
if (isset($input['cron'])) {
unset($input['cron']['cli_path']);
unset($input['cron']['last_run']);
unset($input['cron']['schedule_url']);
}
$this->settingsUpdateService->update(
Arr::getWithKeys($input, [
'app',
'telegram',
'metrics',
'store',
'orders',
'texts',
'sliders',
'mainpage_blocks',
'pulse',
'cron',
]),
);
// Update forms
$forms = Arr::get($input, 'forms', []);
foreach ($forms as $form) {
$schema = json_encode($form['schema'], JSON_THROW_ON_ERROR);
$this->builder->newQuery()
->where('alias', '=', $form['alias'])
->update('acmeshop_forms', [
'friendly_name' => $form['friendly_name'],
'is_custom' => $form['is_custom'],
'schema' => $schema,
]);
}
// Update scheduled jobs is_enabled and cron_expression
$scheduledJobs = Arr::get($input, 'scheduled_jobs', []);
foreach ($scheduledJobs as $job) {
$id = (int) ($job['id'] ?? 0);
if ($id <= 0) {
continue;
}
$isEnabled = filter_var($job['is_enabled'] ?? false, FILTER_VALIDATE_BOOLEAN);
if ($isEnabled) {
$this->scheduledJob->enable($id);
} else {
$this->scheduledJob->disable($id);
}
$cronExpression = trim((string) ($job['cron_expression'] ?? ''));
if ($cronExpression !== '') {
$this->scheduledJob->updateCronExpression($id, $cronExpression);
}
}
return new JsonResponse([], Response::HTTP_ACCEPTED);
}
private function validate(array $input): void
{
}
public function resetCache(): JsonResponse
{
$this->cache->clear();
$this->logger->info('Cache cleared manually.');
return new JsonResponse([], Response::HTTP_ACCEPTED);
}
private function getLastCronRunDate(): ?string
{
try {
// Since we are in SettingsHandler, we already have access to container or we can inject SchedulerService
// But SettingsHandler is constructed via DI. Let's add SchedulerService to constructor.
// For now, let's use global retrieval via cache if possible, or assume it's injected.
// But wait, getLastCronRunDate logic was in controller.
// SchedulerService stores last run in cache. We have $this->cache here.
$lastRunTimestamp = $this->cache->get("scheduler.global_last_run");
if ($lastRunTimestamp) {
return Carbon::createFromTimestamp($lastRunTimestamp)->toDateTimeString();
}
return null;
} catch (Exception $e) {
return null;
}
}
public function getSystemInfo(): JsonResponse
{
$info = [];
$info['PHP Version'] = PHP_VERSION;
$info['PHP SAPI'] = PHP_SAPI;
$info['PHP Memory Limit'] = ini_get('memory_limit');
$info['PHP Memory Usage'] = $this->formatBytes(memory_get_usage(true));
$info['PHP Peak Memory Usage'] = $this->formatBytes(memory_get_peak_usage(true));
$info['PHP Max Execution Time'] = ini_get('max_execution_time') . 's';
$info['PHP Upload Max Filesize'] = ini_get('upload_max_filesize');
$info['PHP Post Max Size'] = ini_get('post_max_size');
try {
$mysqlVersion = $this->connection->select('SELECT VERSION() as version');
$info['MySQL Version'] = $mysqlVersion[0]['version'] ?? 'Unknown';
} catch (Exception $e) {
$info['MySQL Version'] = 'Error: ' . $e->getMessage();
}
$cacheDriver = env('MEGAPAY_CACHE_DRIVER', 'mysql');
$cacheClass = get_class($this->cache);
$info['Cache Driver'] = $cacheDriver . ' (' . basename(str_replace('\\', '/', $cacheClass)) . ')';
$info['Module Version'] = module_version();
$info['ECommerce Version'] = defined('VERSION') ? VERSION : 'Unknown';
$info['ECommerce Core Version'] = defined('VERSION_CORE') ? VERSION_CORE : 'Unknown';
$info['Operating System'] = PHP_OS;
$info['Server Software'] = $_SERVER['SERVER_SOFTWARE'] ?? 'Unknown';
$info['Document Root'] = $_SERVER['DOCUMENT_ROOT'] ?? 'Unknown';
$info['PHP Timezone'] = date_default_timezone_get();
$info['Server Time'] = date('Y-m-d H:i:s');
$info['UTC Time'] = gmdate('Y-m-d H:i:s');
$info['Loaded PHP Extensions'] = implode(', ', get_loaded_extensions());
$infoText = '';
foreach ($info as $key => $value) {
$infoText .= $key . ': ' . $value . "\n";
}
return new JsonResponse(['data' => $infoText]);
}
private function formatBytes(int $bytes, int $precision = 2): string
{
$units = ['B', 'KB', 'MB', 'GB', 'TB'];
for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) {
$bytes /= 1024;
}
return round($bytes, $precision) . ' ' . $units[$i];
}
}