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]; } }