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

visavi / rotor / 27388066632

12 Jun 2026 01:15AM UTC coverage: 14.172%. Remained the same
27388066632

push

github

visavi
Исправил ошибки phpstan

0 of 36 new or added lines in 9 files covered. (0.0%)

4 existing lines in 3 files now uncovered.

816 of 5758 relevant lines covered (14.17%)

1.64 hits per line

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

0.0
/app/Http/Controllers/AjaxController.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace App\Http\Controllers;
6

7
use App\Classes\Registry;
8
use App\Classes\Validator;
9
use App\Models\Comment;
10
use App\Models\File;
11
use App\Models\Message;
12
use App\Models\Poll;
13
use App\Models\Spam;
14
use App\Models\Sticker;
15
use Illuminate\Database\Eloquent\Model;
16
use Illuminate\Database\Eloquent\Relations\MorphOne;
17
use Illuminate\Database\Eloquent\Relations\Relation;
18
use Illuminate\Http\JsonResponse;
19
use Illuminate\Http\Request;
20
use Illuminate\Support\Facades\DB;
21
use Illuminate\Support\Str;
22

23
class AjaxController extends Controller
24
{
25
    /**
26
     * Отправляет жалобу на сообщение
27
     */
28
    public function complaint(Request $request, Validator $validator): JsonResponse
×
29
    {
30
        $path = null;
×
31
        $model = false;
×
32
        $id = int($request->input('id'));
×
33
        $type = $request->input('type');
×
34
        $page = $request->input('page');
×
35

36
        switch ($type) {
37
            case Message::$morphName:
×
38
                $model = Message::query()->find($id);
×
39
                break;
×
40

41
            case 'articles':
×
42
            case 'photos':
×
43
            case 'offers':
×
44
            case 'downs':
×
45
                $model = Comment::query()->find($id);
×
46
                $path = $model?->getViewUrl(false);
×
47
                $type = 'comments';
×
48
                break;
×
49

50
            default:
51
                if (isset(Registry::$complaintTypes[$type])) {
×
52
                    $result = (Registry::$complaintTypes[$type])($id, $page);
×
53
                    $model = $result['model'] ?? null;
×
54
                    $path = $result['path'] ?? null;
×
55
                }
56
                break;
×
57
        }
58

59
        $spam = Spam::query()->where(['relate_type' => $type, 'relate_id' => $id])->first();
×
60

61
        $validator
×
62
            ->true($model, __('main.message_not_found'))
×
63
            ->false($spam, __('ajax.complaint_already_sent'));
×
64

65
        if ($validator->isValid()) {
×
66
            Spam::query()->create([
×
67
                'relate_type' => $type,
×
68
                'relate_id'   => $model->id,
×
69
                'user_id'     => getUser('id'),
×
70
                'path'        => $path,
×
71
                'created_at'  => SITETIME,
×
72
            ]);
×
73

74
            return response()->json(['success' => true]);
×
75
        }
76

77
        return response()->json([
×
78
            'success' => false,
×
79
            'message' => current($validator->getErrors()),
×
80
        ]);
×
81
    }
82

83
    /**
84
     * Изменяет рейтинг
85
     */
86
    /**
87
     * Связь голоса текущего пользователя (morph-имя relate единое по движку)
88
     *
89
     * @return MorphOne<Poll, Model>
90
     */
NEW
91
    private function pollRelation(Model $post): MorphOne
×
92
    {
NEW
93
        return $post->morphOne(Poll::class, 'relate')
×
NEW
94
            ->where('user_id', getUser('id'));
×
95
    }
96

UNCOV
97
    public function rating(Request $request): JsonResponse
×
98
    {
99
        $validTypes = array_merge([
×
100
            Comment::$morphName,
×
101
        ], Registry::$ratingTypes);
×
102

103
        $type = $request->input('type');
×
104
        $vote = $request->input('vote');
×
105

106
        if (! in_array($type, $validTypes, true)) {
×
107
            return response()->json(['success' => false, 'message' => 'Type invalid']);
×
108
        }
109

110
        if (! in_array($vote, ['+', '-'], true)) {
×
111
            return response()->json(['success' => false, 'message' => 'Invalid rating']);
×
112
        }
113

114
        $model = Relation::getMorphedModel($type);
×
115
        $post = $model::query()
×
116
            ->where('id', int($request->input('id')))
×
117
            ->where('user_id', '<>', getUser('id'))
×
118
            ->first();
×
119

120
        if (! $post) {
×
121
            return response()->json(['success' => false, 'message' => __('main.record_not_found')]);
×
122
        }
123

NEW
124
        $poll = $this->pollRelation($post)->firstOrNew();
×
125
        $isCancel = false;
×
126

127
        if ($poll->exists) {
×
128
            if ($poll->vote === $vote) {
×
129
                return response()->json(['success' => false]);
×
130
            }
131
            $isCancel = true;
×
132
            $poll->delete();
×
133
        }
134

135
        if (! $isCancel) {
×
NEW
136
            $this->pollRelation($post)->create([
×
137
                'user_id'    => getUser('id'),
×
138
                'vote'       => $vote,
×
139
                'created_at' => SITETIME,
×
140
            ]);
×
141
        }
142

143
        $vote === '+' ? $post->increment('rating') : $post->decrement('rating');
×
144
        $post->refresh();
×
145

146
        return response()->json([
×
147
            'success' => true,
×
148
            'cancel'  => $isCancel,
×
NEW
149
            'rating'  => formatNum((int) $post->getAttribute('rating'))->toHtml(),
×
150
        ]);
×
151
    }
152

153
    /**
154
     * Загружает файлы
155
     */
156
    public function uploadFile(Request $request, Validator $validator): JsonResponse
×
157
    {
158
        $imageTypes = array_merge(Registry::$mediaTypes);
×
159

160
        $fileTypes = array_merge([
×
161
            Comment::$morphName,
×
162
            Message::$morphName,
×
163
        ], Registry::$fileTypes);
×
164

165
        $id = int($request->input('id'));
×
166
        $file = $request->file('file');
×
167
        $type = $request->input('type');
×
168

169
        if (! in_array($type, array_merge($imageTypes, $fileTypes), true)) {
×
170
            return response()->json([
×
171
                'success' => false,
×
172
                'message' => 'Type invalid',
×
173
            ]);
×
174
        }
175

176
        $class = Relation::getMorphedModel($type);
×
177
        $isImageType = in_array($type, $imageTypes, true);
×
178

179
        if ($id) {
×
180
            $model = $class::query()->find($id);
×
181

182
            if (! $model) {
×
183
                return response()->json([
×
184
                    'success' => false,
×
185
                    'message' => 'Service not found',
×
186
                ]);
×
187
            }
188
        } else {
189
            $model = new $class();
×
190
        }
191

192
        $countFiles = File::query()
×
193
            ->where('relate_type', $type)
×
194
            ->where('relate_id', $id)
×
195
            ->where('user_id', getUser('id'))
×
196
            ->count();
×
197

198
        $duplicate = $file && File::query()
×
199
            ->where('relate_type', $type)
×
200
            ->where('relate_id', $id)
×
201
            ->where('user_id', getUser('id'))
×
202
            ->where('name', Str::substr(getBodyName($file->getClientOriginalName()), 0, 50) . '.' . strtolower($file->getClientOriginalExtension()))
×
203
            ->exists();
×
204

205
        $validator
×
206
            ->lt($countFiles, setting('maxfiles'), __('validator.files_max', ['max' => setting('maxfiles')]))
×
207
            ->false($duplicate, __('validator.file_duplicate'));
×
208

209
        if ($model->id) {
×
210
            $validator->true($model->user_id === getUser('id') || isAdmin(), __('ajax.record_not_author'));
×
211
        }
212

213
        if ($validator->isValid()) {
×
214
            $allowedExt = setting($isImageType ? 'media_extensions' : 'file_extensions');
×
215

216
            $rules = [
×
217
                'minweight'  => 100,
×
218
                'maxsize'    => setting('filesize'),
×
219
                'extensions' => explode(',', $allowedExt),
×
220
            ];
×
221

222
            $validator->file($file, $rules, __('validator.file_upload_failed'));
×
223
        }
224

225
        if ($validator->isValid()) {
×
226
            $fileData = $model->uploadFile($file);
×
227
            if (method_exists($model, 'convertVideo')) {
×
228
                $model->convertVideo($fileData);
×
229
            }
230

231
            if ($isImageType) {
×
232
                $data = [
×
233
                    'success' => true,
×
234
                    'id'      => $fileData['id'],
×
235
                    'path'    => $fileData['path'],
×
236
                    'type'    => $fileData['type'],
×
237
                ];
×
238
            } else {
239
                if (method_exists($model, 'addFileToArchive')) {
×
240
                    $model->addFileToArchive($fileData);
×
241
                }
242

243
                $data = [
×
244
                    'success' => true,
×
245
                    'id'      => $fileData['id'],
×
246
                    'path'    => $fileData['path'],
×
247
                    'name'    => $fileData['name'],
×
248
                    'size'    => $fileData['size'],
×
249
                    'type'    => $fileData['type'],
×
250
                ];
×
251
            }
252

253
            return response()->json($data);
×
254
        }
255

256
        return response()->json([
×
257
            'success' => false,
×
258
            'message' => current($validator->getErrors()),
×
259
        ]);
×
260
    }
261

262
    /**
263
     * Удаляет файлы
264
     */
265
    public function deleteFile(Request $request, Validator $validator): JsonResponse
×
266
    {
267
        $types = array_merge([
×
268
            Comment::$morphName,
×
269
            Message::$morphName,
×
270
        ], Registry::$mediaTypes, Registry::$fileTypes);
×
271

272
        $id = int($request->input('id'));
×
273
        $type = $request->input('type');
×
274

275
        if (! in_array($type, $types, true)) {
×
276
            return response()->json([
×
277
                'success' => false,
×
278
                'message' => 'Type invalid',
×
279
            ]);
×
280
        }
281

282
        $file = File::query()
×
283
            ->where('relate_type', $type)
×
284
            ->find($id);
×
285

286
        if (! $file) {
×
287
            return response()->json([
×
288
                'success' => false,
×
289
                'message' => 'File not found',
×
290
            ]);
×
291
        }
292

293
        $validator
×
294
            ->true($file->user_id === getUser('id') || isAdmin(), __('ajax.record_not_author'));
×
295

296
        if ($validator->isValid()) {
×
297
            $file->delete();
×
298

299
            return response()->json([
×
300
                'success' => true,
×
301
                'path'    => $file->path,
×
302
            ]);
×
303
        }
304

305
        return response()->json([
×
306
            'success' => false,
×
307
            'message' => current($validator->getErrors()),
×
308
        ]);
×
309
    }
310

311
    /**
312
     * Возвращает список стикеров
313
     */
314
    public function getStickers(): JsonResponse
×
315
    {
316
        $stickers = Sticker::query()
×
317
            ->with('category:id,name')
×
318
            ->orderBy(DB::raw('CHAR_LENGTH(code)'))
×
319
            ->orderBy('name')
×
320
            ->get(['id', 'category_id', 'code', 'name']);
×
321

322
        $grouped = $stickers
×
323
            ->groupBy('category_id')
×
NEW
324
            ->toBase()
×
325
            ->map(fn ($items, $categoryId) => [
×
NEW
326
                'id'       => (int) $categoryId,
×
327
                'name'     => $items->first()->category->name,
×
NEW
328
                'stickers' => $items->map(fn (Sticker $s) => ['code' => $s->code, 'name' => $s->name])->values()->all(),
×
329
            ])
×
330
            ->values();
×
331

332
        return response()->json($grouped);
×
333
    }
334

335
    /**
336
     * Резолв прямой ссылки на картинку через og:image
337
     */
338
    public function resolveImage(Request $request): JsonResponse
×
339
    {
340
        $url = filter_var((string) $request->input('url'), FILTER_VALIDATE_URL);
×
341

342
        if (! $url) {
×
343
            return response()->json(['image' => null]);
×
344
        }
345

346
        $ctx = stream_context_create(['http' => [
×
347
            'timeout'         => 5,
×
348
            'follow_location' => true,
×
349
            'user_agent'      => 'Mozilla/5.0',
×
350
        ]]);
×
351

352
        $html = @file_get_contents($url, false, $ctx);
×
353

354
        if ($html && preg_match('/<meta[^>]+property=["\']og:image["\'][^>]+content=["\'](https?:[^"\']+)["\']/i', $html, $m)) {
×
355
            return response()->json(['image' => $m[1]]);
×
356
        }
357

358
        return response()->json(['image' => null]);
×
359
    }
360

361
    /**
362
     * Set theme
363
     */
364
    public function setTheme(Request $request): JsonResponse
×
365
    {
366
        cookie()->queue(
×
367
            cookie()->forever(
×
368
                'theme',
×
369
                $request->input('theme') === 'dark' ? 'dark' : 'light',
×
370
            )
×
371
        );
×
372

373
        return response()->json([
×
374
            'success' => true,
×
375
        ]);
×
376
    }
377
}
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