• 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

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 Comment::$morphName:
×
42
                $model = Comment::query()->find($id);
×
43
                $path = $model?->getViewUrl(false);
×
44
                break;
×
45

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

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

57
        $validator
×
58
            ->true($model, __('main.message_not_found'))
×
59
            ->false($spam, __('ajax.complaint_already_sent'));
×
60

61
        if ($validator->isValid()) {
×
62
            Spam::query()->create([
×
63
                'relate_type' => $type,
×
64
                'relate_id'   => $model->id,
×
65
                'user_id'     => getUser('id'),
×
66
                'path'        => $path,
×
67
            ]);
×
68

69
            return response()->json(['success' => true]);
×
70
        }
71

72
        return response()->json([
×
73
            'success' => false,
×
74
            'message' => current($validator->getErrors()),
×
75
        ]);
×
76
    }
77

78
    /**
79
     * Связь голоса текущего пользователя (morph-имя relate единое по движку)
80
     *
81
     * @return MorphOne<Poll, Model>
82
     */
83
    private function pollRelation(Model $post): MorphOne
×
84
    {
85
        return $post->morphOne(Poll::class, 'relate')
×
86
            ->where('user_id', getUser('id'));
×
87
    }
88

89
    /**
90
     * Изменяет рейтинг
91
     */
92
    public function rating(Request $request): JsonResponse
×
93
    {
94
        $validTypes = array_merge([
×
95
            Comment::$morphName,
×
96
        ], Registry::$ratingTypes);
×
97

98
        $type = $request->input('type');
×
99
        $vote = $request->input('vote');
×
100

101
        if (! in_array($type, $validTypes, true)) {
×
102
            return response()->json(['success' => false, 'message' => 'Type invalid']);
×
103
        }
104

105
        if (! in_array($vote, ['+', '-'], true)) {
×
106
            return response()->json(['success' => false, 'message' => 'Invalid rating']);
×
107
        }
108

109
        $model = Relation::getMorphedModel($type);
×
110
        $post = $model::query()
×
111
            ->where('id', int($request->input('id')))
×
112
            ->where('user_id', '<>', getUser('id'))
×
113
            ->first();
×
114

115
        if (! $post) {
×
116
            return response()->json(['success' => false, 'message' => __('main.record_not_found')]);
×
117
        }
118

119
        $poll = $this->pollRelation($post)->firstOrNew();
×
120
        $isCancel = false;
×
121

122
        if ($poll->exists) {
×
123
            if ($poll->vote === $vote) {
×
124
                return response()->json(['success' => false]);
×
125
            }
126
            $isCancel = true;
×
127
            $poll->delete();
×
128
        }
129

130
        if (! $isCancel) {
×
131
            $this->pollRelation($post)->create([
×
NEW
132
                'user_id' => getUser('id'),
×
NEW
133
                'vote'    => $vote,
×
UNCOV
134
            ]);
×
135
        }
136

137
        $vote === '+' ? $post->increment('rating') : $post->decrement('rating');
×
138
        $post->refresh();
×
139

140
        return response()->json([
×
141
            'success' => true,
×
142
            'cancel'  => $isCancel,
×
143
            'rating'  => formatNum((int) $post->getAttribute('rating'))->toHtml(),
×
144
        ]);
×
145
    }
146

147
    /**
148
     * Загружает файлы
149
     */
150
    public function uploadFile(Request $request, Validator $validator): JsonResponse
×
151
    {
152
        $imageTypes = Registry::$mediaTypes;
×
153

154
        $fileTypes = array_merge([
×
155
            Comment::$morphName,
×
156
            Message::$morphName,
×
157
        ], Registry::$fileTypes);
×
158

159
        $id = int($request->input('id'));
×
160
        $file = $request->file('file');
×
161
        $type = $request->input('type');
×
162

163
        if (! in_array($type, array_merge($imageTypes, $fileTypes), true)) {
×
164
            return response()->json([
×
165
                'success' => false,
×
166
                'message' => 'Type invalid',
×
167
            ]);
×
168
        }
169

170
        $class = Relation::getMorphedModel($type);
×
171
        $isImageType = in_array($type, $imageTypes, true);
×
172

173
        if ($id) {
×
174
            $model = $class::query()->find($id);
×
175

176
            if (! $model) {
×
177
                return response()->json([
×
178
                    'success' => false,
×
179
                    'message' => 'Service not found',
×
180
                ]);
×
181
            }
182
        } else {
183
            $model = new $class();
×
184
        }
185

186
        $uploadedFiles = File::query()
×
187
            ->where('relate_type', $type)
×
188
            ->where('relate_id', $id)
×
189
            ->where('user_id', getUser('id'))
×
190
            ->get(['name']);
×
191

192
        $duplicate = $file && $uploadedFiles->contains(
×
193
            'name',
×
194
            Str::substr(getBodyName($file->getClientOriginalName()), 0, 50) . '.' . strtolower($file->getClientOriginalExtension())
×
195
        );
×
196

197
        $validator
×
198
            ->lt($uploadedFiles->count(), setting('maxfiles'), __('validator.files_max', ['max' => setting('maxfiles')]))
×
199
            ->false($duplicate, __('validator.file_duplicate'));
×
200

201
        if ($model->id) {
×
202
            $validator->true($model->user_id === getUser('id') || isAdmin(), __('ajax.record_not_author'));
×
203
        }
204

205
        if ($validator->isValid()) {
×
206
            $allowedExt = setting($isImageType ? 'media_extensions' : 'file_extensions');
×
207

208
            $rules = [
×
209
                'minweight'  => 100,
×
210
                'maxsize'    => setting('filesize'),
×
211
                'extensions' => explode(',', $allowedExt),
×
212
            ];
×
213

214
            $validator->file($file, $rules, __('validator.file_upload_failed'));
×
215
        }
216

217
        if ($validator->isValid()) {
×
218
            $fileData = $model->uploadFile($file);
×
219
            if (method_exists($model, 'convertVideo')) {
×
220
                $model->convertVideo($fileData);
×
221
            }
222

223
            if ($isImageType) {
×
224
                $data = [
×
225
                    'success' => true,
×
226
                    'id'      => $fileData['id'],
×
227
                    'path'    => $fileData['path'],
×
228
                    'type'    => $fileData['type'],
×
229
                ];
×
230
            } else {
231
                if (method_exists($model, 'addFileToArchive')) {
×
232
                    $model->addFileToArchive($fileData);
×
233
                }
234

235
                $data = [
×
236
                    'success' => true,
×
237
                    'id'      => $fileData['id'],
×
238
                    'path'    => $fileData['path'],
×
239
                    'name'    => $fileData['name'],
×
240
                    'size'    => $fileData['size'],
×
241
                    'type'    => $fileData['type'],
×
242
                ];
×
243
            }
244

245
            return response()->json($data);
×
246
        }
247

248
        return response()->json([
×
249
            'success' => false,
×
250
            'message' => current($validator->getErrors()),
×
251
        ]);
×
252
    }
253

254
    /**
255
     * Удаляет файлы
256
     */
257
    public function deleteFile(Request $request, Validator $validator): JsonResponse
×
258
    {
259
        $types = array_merge([
×
260
            Comment::$morphName,
×
261
            Message::$morphName,
×
262
        ], Registry::$mediaTypes, Registry::$fileTypes);
×
263

264
        $id = int($request->input('id'));
×
265
        $type = $request->input('type');
×
266

267
        if (! in_array($type, $types, true)) {
×
268
            return response()->json([
×
269
                'success' => false,
×
270
                'message' => 'Type invalid',
×
271
            ]);
×
272
        }
273

274
        $file = File::query()
×
275
            ->where('relate_type', $type)
×
276
            ->find($id);
×
277

278
        if (! $file) {
×
279
            return response()->json([
×
280
                'success' => false,
×
281
                'message' => 'File not found',
×
282
            ]);
×
283
        }
284

285
        $validator
×
286
            ->true($file->user_id === getUser('id') || isAdmin(), __('ajax.record_not_author'));
×
287

288
        if ($validator->isValid()) {
×
289
            $file->delete();
×
290

291
            return response()->json([
×
292
                'success' => true,
×
293
                'path'    => $file->path,
×
294
            ]);
×
295
        }
296

297
        return response()->json([
×
298
            'success' => false,
×
299
            'message' => current($validator->getErrors()),
×
300
        ]);
×
301
    }
302

303
    /**
304
     * Возвращает список стикеров
305
     */
306
    public function getStickers(): JsonResponse
×
307
    {
308
        $stickers = Sticker::query()
×
309
            ->with('category:id,name')
×
310
            ->orderBy(DB::raw('CHAR_LENGTH(code)'))
×
311
            ->orderBy('name')
×
312
            ->get(['id', 'category_id', 'code', 'name']);
×
313

314
        $grouped = $stickers
×
315
            ->groupBy('category_id')
×
316
            ->toBase()
×
317
            ->map(fn ($items, $categoryId) => [
×
318
                'id'       => (int) $categoryId,
×
319
                'name'     => $items->first()->category->name,
×
320
                'stickers' => $items->map(fn (Sticker $s) => ['code' => $s->code, 'name' => $s->name])->values()->all(),
×
321
            ])
×
322
            ->values();
×
323

324
        return response()->json($grouped);
×
325
    }
326

327
    /**
328
     * Резолв прямой ссылки на картинку через og:image
329
     */
330
    public function resolveImage(Request $request): JsonResponse
×
331
    {
332
        $url = filter_var((string) $request->input('url'), FILTER_VALIDATE_URL);
×
333

334
        if (! $url) {
×
335
            return response()->json(['image' => null]);
×
336
        }
337

338
        $ctx = stream_context_create(['http' => [
×
339
            'timeout'         => 5,
×
340
            'follow_location' => true,
×
341
            'user_agent'      => 'Mozilla/5.0',
×
342
        ]]);
×
343

344
        $html = @file_get_contents($url, false, $ctx);
×
345

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

350
        return response()->json(['image' => null]);
×
351
    }
352

353
    /**
354
     * Set theme
355
     */
356
    public function setTheme(Request $request): JsonResponse
×
357
    {
358
        cookie()->queue(
×
359
            cookie()->forever(
×
360
                'theme',
×
361
                $request->input('theme') === 'dark' ? 'dark' : 'light',
×
362
            )
×
363
        );
×
364

365
        return response()->json([
×
366
            'success' => true,
×
367
        ]);
×
368
    }
369
}
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