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

visavi / rotor / 28340133337

28 Jun 2026 11:47PM UTC coverage: 16.561% (+0.09%) from 16.474%
28340133337

push

github

visavi
Ядро и модули переведены на datetime, удалена константа SITETIME

18 of 95 new or added lines in 31 files covered. (18.95%)

7 existing lines in 6 files now uncovered.

989 of 5972 relevant lines covered (16.56%)

2.44 hits per line

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

35.71
/app/Http/Controllers/InstallController.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace App\Http\Controllers;
6

7
use App\Classes\Validator;
8
use App\Models\Setting;
9
use App\Models\User;
10
use App\Services\MigrationService;
11
use Illuminate\Http\JsonResponse;
12
use Illuminate\Http\RedirectResponse;
13
use Illuminate\Http\Request;
14
use Illuminate\Support\Facades\Artisan;
15
use Illuminate\Support\Facades\Auth;
16
use Illuminate\Support\Facades\DB;
17
use Illuminate\Support\Facades\Hash;
18
use Illuminate\Support\Facades\Lang;
19
use Illuminate\Support\Facades\Schema;
20
use Illuminate\Support\Str;
21
use Illuminate\View\View;
22

23
class InstallController extends Controller
24
{
25
    /**
26
     * Конструктор
27
     */
28
    public function __construct(
3✔
29
        Request $request,
30
        private readonly MigrationService $migrations
31
    ) {
32
        $lang = $request->input('lang', 'ru');
3✔
33

34
        Lang::setLocale($lang);
3✔
35

36
        view()->share('lang', $lang);
3✔
37
    }
38

39
    /**
40
     * Главная страница
41
     */
42
    public function index(): View
3✔
43
    {
44
        $keys = [
3✔
45
            'APP_ENV',
3✔
46
            'APP_DEBUG',
3✔
47
            'DB_CONNECTION',
3✔
48
            'DB_HOST',
3✔
49
            'DB_PORT',
3✔
50
            'DB_DATABASE',
3✔
51
            'DB_USERNAME',
3✔
52
            'APP_URL',
3✔
53
            'APP_EMAIL',
3✔
54
            'APP_ADMIN',
3✔
55
        ];
3✔
56

57
        $versions = [
3✔
58
            'php'   => '8.3.0',
3✔
59
            'mysql' => '5.7.8',
3✔
60
            'maria' => '10.2.7',
3✔
61
            'pgsql' => '9.2',
3✔
62
        ];
3✔
63

64
        $storage = glob(storage_path('{*,*/*,*/*/*}'), GLOB_BRACE | GLOB_ONLYDIR);
3✔
65
        $uploads = glob(public_path('uploads/*'), GLOB_ONLYDIR);
3✔
66
        $dirs = [public_path('assets/modules'), base_path('bootstrap/cache'), base_path('modules')];
3✔
67

68
        $dirs = array_merge($storage, $uploads, $dirs);
3✔
69
        $languages = getAvailableLanguages();
3✔
70

71
        $isUpdate = $this->isUpdate();
3✔
72
        $database = $this->databaseChecks($versions);
3✔
73

74
        return view('install/index', compact('keys', 'languages', 'versions', 'dirs', 'isUpdate', 'database'));
3✔
75
    }
76

77
    /**
78
     * Проверка подключения к БД и её версии (driver-aware, с защитой от падения)
79
     */
80
    private function databaseChecks(array $versions): array
3✔
81
    {
82
        $driver = (string) config('database.default');
3✔
83
        $pdoExt = $driver === 'pgsql' ? 'pdo_pgsql' : 'pdo_mysql';
3✔
84

85
        $database = [
3✔
86
            'driver'     => $driver,
3✔
87
            'pdoExt'     => $pdoExt,
3✔
88
            'pdoLoaded'  => extension_loaded($pdoExt),
3✔
89
            'connected'  => false,
3✔
90
            'error'      => null,
3✔
91
            'client'     => '',
3✔
92
            'mysqlnd'    => true,
3✔
93
            'version'    => '',
3✔
94
            'versionOk'  => false,
3✔
95
            'minVersion' => match ($driver) {
3✔
96
                'mariadb' => $versions['maria'],
×
97
                'pgsql'   => $versions['pgsql'],
×
98
                default   => $versions['mysql'],
3✔
99
            },
3✔
100
        ];
3✔
101

102
        try {
103
            $pdo = DB::connection()->getPdo();
3✔
104
            $database['connected'] = true;
3✔
105

106
            if (in_array($driver, ['mysql', 'mariadb'], true)) {
3✔
107
                $database['client'] = (string) ($pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION) ?: '');
3✔
108
                $database['mysqlnd'] = stripos($database['client'], 'mysqlnd') !== false;
3✔
109
            }
110

111
            $raw = DB::selectOne('SELECT VERSION() as version')->version ?? '';
3✔
112
            preg_match('/(\d+\.\d+(?:\.\d+)?)/', (string) $raw, $match);
3✔
113

114
            $database['version'] = (string) $raw;
3✔
115
            $database['versionOk'] = isset($match[1]) && version_compare($match[1], $database['minVersion'], '>=');
3✔
116
        } catch (\Throwable $e) {
×
117
            $database['error'] = $e->getMessage();
×
118
        }
119

120
        return $database;
3✔
121
    }
122

123
    /**
124
     * Проверка статуса и выполнение миграций
125
     */
126
    public function status(): View
×
127
    {
128
        if (! Schema::hasTable('migrations')) {
×
129
            Artisan::call('migrate:install');
×
130
        }
131

132
        $isUpdate = $this->isUpdate();
×
133
        $pendingMigrations = $this->migrations->getPendingMigrations($this->paths());
×
134

135
        return view('install/status', compact('isUpdate', 'pendingMigrations'));
×
136
    }
137

138
    /**
139
     * Выполняет одну следующую миграцию
140
     */
141
    public function migrateNext(): JsonResponse
×
142
    {
143
        ini_set('max_execution_time', 0);
×
144
        set_time_limit(0);
×
145

146
        $pending = $this->migrations->getPendingMigrations($this->paths());
×
147

148
        if (empty($pending)) {
×
149
            if (! $this->isUpdate()) {
×
150
                Artisan::call('key:generate', ['--force' => true]);
×
151
            }
152

153
            Artisan::call('cache:clear');
×
154
            Artisan::call('route:clear');
×
155
            Artisan::call('config:clear');
×
156

157
            return response()->json(['done' => true, 'migration' => null, 'output' => '']);
×
158
        }
159

160
        $name = $pending[0];
×
161
        $file = $this->migrations->findFile($name);
×
162

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

167
        $remaining = count($pending) - 1;
×
168

169
        try {
170
            $output = $this->migrations->runOne($file);
×
171
        } catch (\Throwable $e) {
×
172
            return response()->json([
×
173
                'error' => "Ошибка миграции {$name}: " . $e->getMessage(),
×
174
            ], 500);
×
175
        }
176

177
        return response()->json([
×
178
            'done'      => $remaining === 0,
×
179
            'migration' => $name,
×
180
            'output'    => $output,
×
181
            'remaining' => $remaining,
×
182
        ]);
×
183
    }
184

185
    private function paths(): array
×
186
    {
187
        $paths = [database_path('migrations')];
×
188

189
        if ($this->isUpdate()) {
×
190
            $paths[] = database_path('upgrades');
×
191
        }
192

193
        return $paths;
×
194
    }
195

196
    /**
197
     * Заполнение БД
198
     */
199
    public function seed(): View
×
200
    {
201
        if (setting('app_installed')) {
×
202
            abort(403);
×
203
        }
204

205
        Artisan::call('db:seed', ['--force' => true]);
×
206
        $output = Artisan::output();
×
207

208
        Artisan::call('cache:clear');
×
209
        Artisan::call('route:clear');
×
210
        Artisan::call('config:clear');
×
211

212
        return view('install/seed', compact('output'));
×
213
    }
214

215
    /**
216
     * Создание администратора
217
     */
218
    public function account(Request $request, Validator $validator): View|RedirectResponse
×
219
    {
220
        if (setting('app_installed')) {
×
221
            abort(403);
×
222
        }
223

224
        $lang = $request->input('lang', 'ru');
×
225
        $login = (string) $request->input('login');
×
226
        $password = $request->input('password');
×
227
        $password2 = $request->input('password2');
×
228
        $email = strtolower((string) $request->input('email'));
×
229

230
        if ($request->isMethod('post')) {
×
231
            $validator->regex($login, '|^[a-z0-9\-]+$|i', ['login' => __('validator.login')])
×
232
                ->regex(Str::substr($login, 0, 1), '|^[a-z0-9]+$|i', ['login' => __('users.login_begin_requirements')])
×
233
                ->email($email, ['email' => __('validator.email')])
×
234
                ->length($login, 3, 20, ['login' => __('users.login_length_requirements')])
×
235
                ->length($password, 6, 20, ['password' => __('users.password_length_requirements')])
×
236
                ->equal($password, $password2, ['password2' => __('users.passwords_different')])
×
237
                ->false(ctype_digit($login), ['login' => __('users.field_characters_requirements')])
×
238
                ->false(ctype_digit($password), ['password' => __('users.field_characters_requirements')])
×
239
                ->false(substr_count($login, '-') > 2, ['login' => __('users.login_hyphens_requirements')]);
×
240

241
            if ($validator->isValid()) {
×
242
                // Проверка логина на существование
243
                $checkLogin = User::query()->where('login', $login)->exists();
×
244
                $validator->false($checkLogin, ['login' => __('users.login_already_exists')]);
×
245

246
                // Проверка email на существование
247
                $checkMail = User::query()->where('email', $email)->exists();
×
248
                $validator->false($checkMail, ['email' => __('users.email_already_exists')]);
×
249
            }
250

251
            if ($validator->isValid()) {
×
252
                $user = User::query()->create([
×
NEW
253
                    'login'    => $login,
×
NEW
254
                    'password' => Hash::make($password),
×
NEW
255
                    'email'    => $email,
×
NEW
256
                    'level'    => User::BOSS,
×
NEW
257
                    'gender'   => User::MALE,
×
NEW
258
                    'themes'   => 'default',
×
NEW
259
                    'point'    => 500,
×
NEW
260
                    'money'    => 100000,
×
NEW
261
                    'status'   => 'Boss',
×
NEW
262
                    'language' => $lang,
×
UNCOV
263
                ]);
×
264

265
                // ------------- Авторизация -----------//
266
                Auth::login($user, true);
×
267

268
                // -------------- Приват ---------------//
269
                $text = __('install.text_message', ['login' => $login]);
×
270
                $user->sendMessage(null, $text);
×
271

272
                return redirect('/install/finish');
×
273
            }
274

275
            setInput($request->all());
×
276
            setFlash('danger', $validator->getErrors());
×
277
        }
278

279
        return view('install/account', compact('login', 'email'));
×
280
    }
281

282
    /**
283
     * Завершение установки
284
     */
285
    public function finish(): View
×
286
    {
287
        if ($this->isUpdate()) {
×
288
            abort(403);
×
289
        }
290

291
        // Помечаем все апгрейды как выполненные — свежая схема уже содержит все изменения
292
        $batch = DB::table('migrations')->max('batch') + 1;
×
293
        foreach (glob(database_path('upgrades/*.php')) as $file) {
×
294
            DB::table('migrations')->insertOrIgnore([
×
295
                'migration' => pathinfo($file, PATHINFO_FILENAME),
×
296
                'batch'     => $batch,
×
297
            ]);
×
298
        }
299

300
        Setting::query()
×
301
            ->where('name', 'app_installed')
×
302
            ->update(['value' => 1]);
×
303

304
        clearCache('settings');
×
305

306
        return view('install/finish');
×
307
    }
308

309
    private function isUpdate(): bool
3✔
310
    {
311
        return (bool) setting('app_installed');
3✔
312
    }
313
}
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