• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

visavi / rotor / 29168968892

11 Jul 2026 09:32PM UTC coverage: 32.95%. Remained the same
29168968892

push

github

visavi
Добавил сброс opcache в фунцию очистки кеша

0 of 3 new or added lines in 2 files covered. (0.0%)

1864 of 5657 relevant lines covered (32.95%)

3.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

2.17
/app/Http/Controllers/Admin/UpgradeController.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace App\Http\Controllers\Admin;
6

7
use App\Services\GithubService;
8
use App\Services\MigrationService;
9
use App\Services\UpgradeService;
10
use Illuminate\Http\JsonResponse;
11
use Illuminate\Http\Request;
12
use Illuminate\Support\Facades\Artisan;
13
use Illuminate\View\View;
14
use Throwable;
15

16
class UpgradeController extends AdminController
17
{
18
    public function __construct(
3✔
19
        private readonly MigrationService $migrations,
20
        private readonly UpgradeService $upgrade,
21
    ) {
22
    }
3✔
23

24
    /**
25
     * Страница обновлений
26
     */
27
    public function index(GithubService $githubService): View
×
28
    {
29
        $pendingMigrations = $this->migrations->getPendingMigrations($this->migrationPaths());
×
30
        $newReleases = $this->upgrade->getNewReleases($githubService);
×
31

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

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

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

50
        return view('admin/upgrade/index', compact(
×
51
            'pendingMigrations',
×
52
            'newReleases',
×
53
            'reinstall',
×
54
            'permErrors',
×
55
        ));
×
56
    }
57

58
    /**
59
     * Собирает данные для переустановки текущей версии
60
     */
61
    private function reinstallInfo(GithubService $githubService): ?array
×
62
    {
63
        $currentTag = 'v' . ROTOR_VERSION;
×
64

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

70
            $split = $this->upgrade->splitAssets($release['assets']);
×
71
            $asset = $this->upgrade->selectAsset($release['assets'], $currentTag);
×
72

73
            if (! $asset) {
×
74
                return null;
×
75
            }
76

77
            $isUpgrade = str_ends_with($asset['name'] ?? '', '_upgrade.zip');
×
78

79
            return [
×
80
                'tag'        => $currentTag,
×
81
                'asset'      => $asset,
×
82
                'full_asset' => $isUpgrade ? $split['full'] : null,
×
83
            ];
×
84
        }
85

86
        return null;
×
87
    }
88

89
    /**
90
     * Скачивает и распаковывает архив релиза
91
     */
92
    public function download(Request $request, GithubService $githubService): JsonResponse
×
93
    {
94
        $tag = (string) $request->input('tag');
×
95
        $full = (bool) $request->input('full');
×
96
        $asset = $tag ? $this->upgrade->findAsset($githubService, $tag, $full) : null;
×
97

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

102
        ini_set('max_execution_time', 0);
×
103
        set_time_limit(0);
×
104

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

111
        return response()->json(['success' => true]);
×
112
    }
113

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

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

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

131
        Artisan::call('down');
×
132

133
        try {
134
            $errors = $this->upgrade->applyUpdate($tag);
×
135
            $this->upgrade->cleanup($tag);
×
136
        } catch (Throwable $e) {
×
137
            return response()->json(['error' => $e->getMessage()], 500);
×
138
        } finally {
139
            Artisan::call('up');
×
140
        }
141

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

NEW
146
        refreshCaches();
×
147

148
        return response()->json([
×
149
            'success' => true,
×
150
            'errors'  => $errors,
×
151
        ]);
×
152
    }
153

154
    /**
155
     * Выполняет одну следующую миграцию
156
     */
157
    public function migrateNext(): JsonResponse
×
158
    {
159
        ini_set('max_execution_time', 0);
×
160
        set_time_limit(0);
×
161

162
        $pending = $this->migrations->getPendingMigrations($this->migrationPaths());
×
163

164
        if (empty($pending)) {
×
165
            refreshCaches();
×
166

167
            return response()->json(['done' => true, 'migration' => null, 'output' => '']);
×
168
        }
169

170
        $name = $pending[0];
×
171
        $file = $this->migrations->findFile($name);
×
172

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

177
        $remaining = count($pending) - 1;
×
178

179
        try {
180
            $output = $this->migrations->runOne($file);
×
181
        } catch (\Throwable $e) {
×
182
            return response()->json([
×
183
                'error' => "Ошибка миграции {$name}: " . $e->getMessage(),
×
184
            ], 500);
×
185
        }
186

187
        return response()->json([
×
188
            'done'      => $remaining === 0,
×
189
            'migration' => $name,
×
190
            'output'    => $output,
×
191
            'remaining' => $remaining,
×
192
        ]);
×
193
    }
194

195
    /**
196
     * Пути к папкам с миграциями
197
     */
198
    private function migrationPaths(): array
×
199
    {
200
        return [database_path('migrations'), database_path('upgrades')];
×
201
    }
202
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc