Вход Регистрация
Файл: app/Http/Controllers/Admin/UpgradeController.php
Строк: 202
<?php

declare(strict_types=1);

namespace 
AppHttpControllersAdmin;

use 
AppServicesGithubService;
use 
AppServicesMigrationService;
use 
AppServicesUpgradeService;
use 
IlluminateHttpJsonResponse;
use 
IlluminateHttpRequest;
use 
IlluminateSupportFacadesArtisan;
use 
IlluminateViewView;
use 
Throwable;

class 
UpgradeController extends AdminController
{
    public function 
__construct(
        private 
readonly MigrationService $migrations,
        private 
readonly UpgradeService $upgrade,
    ) {
    }

    
/**
     * Страница обновлений
     */
    
public function index(GithubService $githubService): View
    
{
        
$pendingMigrations $this->migrations->getPendingMigrations($this->migrationPaths());
        
$newReleases $this->upgrade->getNewReleases($githubService);

        
// Резолвим архив, который реально скачается (upgrade/full), чтобы кнопка
        // показывала честный размер и тип, а не первый попавшийся asset
        
foreach ($newReleases as &$release) {
            
$split $this->upgrade->splitAssets($release['assets'] ?? []);
            
$asset $this->upgrade->selectAsset($release['assets'] ?? [], $release['tag_name'] ?? '');
            
$release['asset'] = $asset;
            
$release['is_upgrade'] = $asset && str_ends_with($asset['name'] ?? '''_upgrade.zip');
            
// Полный архив как запасной вариант — только когда по умолчанию выбран upgrade
            
$release['full_asset'] = $release['is_upgrade'] ? $split['full'] : null;
        }
        unset(
$release);

        
// Переустановка текущей версии (скачать тот же релиз заново) — доступна,
        // пока текущий релиз ещё в списке последних релизов GitHub
        
$reinstall $this->reinstallInfo($githubService);

        
$permErrors = ($newReleases || $reinstall) ? $this->upgrade->checkPermissions() : [];

        return 
view('admin/upgrade/index'compact(
            
'pendingMigrations',
            
'newReleases',
            
'reinstall',
            
'permErrors',
        ));
    }

    
/**
     * Собирает данные для переустановки текущей версии
     */
    
private function reinstallInfo(GithubService $githubService): ?array
    {
        
$currentTag 'v' ROTOR_VERSION;

        foreach (
$githubService->getLatestReleases() as $release) {
            if ((
$release['tag_name'] ?? null) !== $currentTag || empty($release['assets'])) {
                continue;
            }

            
$split $this->upgrade->splitAssets($release['assets']);
            
$asset $this->upgrade->selectAsset($release['assets'], $currentTag);

            if (! 
$asset) {
                return 
null;
            }

            
$isUpgrade str_ends_with($asset['name'] ?? '''_upgrade.zip');

            return [
                
'tag'        => $currentTag,
                
'asset'      => $asset,
                
'full_asset' => $isUpgrade $split['full'] : null,
            ];
        }

        return 
null;
    }

    
/**
     * Скачивает и распаковывает архив релиза
     */
    
public function download(Request $requestGithubService $githubService): JsonResponse
    
{
        
$tag = (string) $request->input('tag');
        
$full = (bool) $request->input('full');
        
$asset $tag $this->upgrade->findAsset($githubService$tag$full) : null;

        if (! 
$asset) {
            return 
response()->json(['error' => __('admin.upgrade.invalid_params')], 422);
        }

        
ini_set('max_execution_time'0);
        
set_time_limit(0);

        try {
            
$this->upgrade->downloadRelease($tag$asset['browser_download_url']);
        } catch (
Throwable $e) {
            return 
response()->json(['error' => $e->getMessage()], 500);
        }

        return 
response()->json(['success' => true]);
    }

    
/**
     * Применяет скачанное обновление
     */
    
public function apply(Request $request): JsonResponse
    
{
        
$tag = (string) $request->input('tag');

        if (! 
$tag) {
            return 
response()->json(['error' => __('admin.upgrade.invalid_params')], 422);
        }

        
// Копирование тысяч файлов на shared-хостинге легко превышает max_execution_time,
        // а фатальный таймаут не выполнит finally и оставит сайт в maintenance mode
        
ini_set('max_execution_time'0);
        
set_time_limit(0);
        
ignore_user_abort(true);

        
Artisan::call('down');

        try {
            
$errors $this->upgrade->applyUpdate($tag);
            
$this->upgrade->cleanup($tag);
        } catch (
Throwable $e) {
            return 
response()->json(['error' => $e->getMessage()], 500);
        } finally {
            
Artisan::call('up');
        }

        
Artisan::call('cache:clear');
        
Artisan::call('route:clear');
        
Artisan::call('config:clear');

        
// Обновление затирает файлы ядра — восстанавливаем симлинки
        // и опубликованные файлы активных модулей
        
Artisan::call('module:sync');

        if (
function_exists('opcache_reset')) {
            
opcache_reset();
        }

        return 
response()->json([
            
'success' => true,
            
'errors'  => $errors,
        ]);
    }

    
/**
     * Выполняет одну следующую миграцию
     */
    
public function migrateNext(): JsonResponse
    
{
        
ini_set('max_execution_time'0);
        
set_time_limit(0);

        
$pending $this->migrations->getPendingMigrations($this->migrationPaths());

        if (empty(
$pending)) {
            
Artisan::call('cache:clear');
            
Artisan::call('route:clear');
            
Artisan::call('config:clear');

            return 
response()->json(['done' => true'migration' => null'output' => '']);
        }

        
$name $pending[0];
        
$file $this->migrations->findFile($name);

        if (! 
$file) {
            return 
response()->json(['error' => "Файл миграции не найден: {$name}"], 500);
        }

        
$remaining count($pending) - 1;

        try {
            
$output $this->migrations->runOne($file);
        } catch (
Throwable $e) {
            return 
response()->json([
                
'error' => "Ошибка миграции {$name}: " $e->getMessage(),
            ], 
500);
        }

        return 
response()->json([
            
'done'      => $remaining === 0,
            
'migration' => $name,
            
'output'    => $output,
            
'remaining' => $remaining,
        ]);
    }

    
/**
     * Пути к папкам с миграциями
     */
    
private function migrationPaths(): array
    {
        return [
database_path('migrations'), database_path('upgrades')];
    }
}
Онлайн: 0
Реклама