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

visavi / rotor / 27440345661

12 Jun 2026 08:11PM UTC coverage: 16.687% (+0.04%) from 16.644%
27440345661

push

github

visavi
Упростил Registry модулей, поправил метки в жалобах, улучшил подсветку комментария при переходе по якорю, улучшил отправку жалобы

6 of 41 new or added lines in 7 files covered. (14.63%)

2 existing lines in 2 files now uncovered.

960 of 5753 relevant lines covered (16.69%)

2.36 hits per line

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

7.45
/app/Http/Controllers/HomeController.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace App\Http\Controllers;
6

7
use App\Classes\Feed;
8
use App\Classes\Registry;
9
use App\Classes\Validator;
10
use App\Models\Ban;
11
use App\Models\Comment;
12
use App\Models\Search;
13
use Illuminate\Http\JsonResponse;
14
use Illuminate\Http\RedirectResponse;
15
use Illuminate\Http\Request;
16
use Illuminate\View\View;
17
use Mobicms\Captcha\Image as MobicmsCaptcha;
18
use Symfony\Component\HttpFoundation\Response;
19
use Visavi\Captcha\CaptchaBuilder as AnimatedCaptchaBuilder;
20
use Visavi\Captcha\PhraseBuilder as AnimatedPhraseBuilder;
21

22
class HomeController extends Controller
23
{
24
    /**
25
     * Главная страница
26
     */
27
    public function index(): View
×
28
    {
29
        return view('index');
×
30
    }
31

32
    /**
33
     * Лента (AJAX)
34
     */
35
    public function feed(Request $request): string|RedirectResponse
×
36
    {
37
        if (! $request->ajax()) {
×
38
            return redirect(url('/') . ($request->has('page') ? '?page=' . $request->input('page') : ''));
×
39
        }
40

41
        return (string) (new Feed())->getFeed();
×
42
    }
43

44
    /**
45
     * Закрытие сайта
46
     */
47
    public function closed(): Response
×
48
    {
49
        if (setting('closedsite') !== 2) {
×
50
            return redirect('/');
×
51
        }
52

53
        return response()->view('pages/closed', [], 503);
×
54
    }
55

56
    /**
57
     * Поиск по сайту
58
     */
59
    public function search(Request $request, Validator $validator): View|RedirectResponse
×
60
    {
61
        $posts = paginate([], 10);
×
62
        $query = (string) $request->input('query', $request->input('q', ''));
×
63
        $query = trim(preg_replace('/[^\p{L}\p{N}\s]/u', ' ', $query));
×
64
        $searchQuery = implode(' ', array_filter(explode(' ', $query), static fn ($w) => mb_strlen($w) >= 3));
×
65

66
        $types = Search::getRelateTypes();
×
67

68
        $sort = check($request->input('sort', 'relevance'));
×
69
        $order = match ($sort) {
×
70
            'date'     => ['created_at desc'],
×
71
            'date_asc' => ['created_at asc'],
×
72
            default    => ['match(text) against(? in boolean mode) desc', [$searchQuery . '*']],
×
73
        };
×
74

75
        $type = check($request->input('type'));
×
76
        $type = isset($types[$type]) ? $type : null;
×
77

78
        if ($query) {
×
79
            $validator->length($searchQuery, 3, 64, ['find' => __('main.request_length')]);
×
80

81
            if ($validator->isValid()) {
×
82
                $posts = Search::query()
×
UNCOV
83
                    ->whereIn('relate_type', array_keys($types))
×
84
                    ->when($type, function ($query) use ($type) {
×
85
                        $query->where('relate_type', $type);
×
86
                    })
×
NEW
87
                    ->where(function ($query) {
×
88
                        $query->where('relate_type', '!=', Comment::$morphName)
×
NEW
89
                            ->orWhereIn('relate_id', Comment::visible()->select('id'));
×
90
                    })
×
91
                    ->whereFullText('text', $searchQuery . '*', ['mode' => 'boolean'])
×
92
                    ->with('relate')
×
93
                    ->orderByRaw(...$order)
×
94
                    ->paginate(10)
×
95
                    ->appends(compact('query', 'sort', 'type'));
×
96

97
                $morphWith = array_filter(array_column(Registry::$search, 'with', 'class'));
×
98
                $posts->loadMorph('relate', [Comment::class => ['relate'], ...$morphWith]);
×
99
            } else {
100
                setInput($request->all());
×
101
                setFlash('danger', $validator->getErrors());
×
102
            }
103
        }
104

105
        return view('search/index', compact('posts', 'types', 'type', 'sort', 'query'));
×
106
    }
107

108
    /**
109
     * Бан по IP
110
     */
111
    public function ipban(Request $request): Response
3✔
112
    {
113
        $ban = Ban::query()
3✔
114
            ->where('ip', getIp())
3✔
115
            ->first();
3✔
116

117
        if (! $ban) {
3✔
118
            clearCache('ipBan');
3✔
119

120
            return redirect('/');
3✔
121
        }
122

123
        if (
124
            ! $ban->user_id
×
125
            && $ban->created_at < strtotime('-1 minute', SITETIME)
×
126
            && $request->isMethod('post')
×
127
            && captchaVerify()
×
128
        ) {
129
            $ban->delete();
×
130
            clearCache('ipBan');
×
131

132
            setFlash('success', __('pages.ip_success_unbanned'));
×
133

134
            return redirect('/');
×
135
        }
136

137
        return response()->view('pages/ipban', compact('ban'), 429);
×
138
    }
139

140
    /**
141
     * Защитная картинка
142
     */
143
    public function captcha(Request $request): Response
×
144
    {
145
        if (setting('captcha_type') === 'animated') {
×
146
            $phrase = new AnimatedPhraseBuilder();
×
147
            $phrase = $phrase->getPhrase(setting('captcha_maxlength'), setting('captcha_symbols'));
×
148

149
            $captcha = new AnimatedCaptchaBuilder($phrase);
×
150
            $captcha = $captcha->render();
×
151
        } else {
152
            $captcha = new MobicmsCaptcha();
×
153
            $captcha->imageWidth = 180;
×
154
            $captcha->imageHeight = 50;
×
155
            $captcha->lengthMax = setting('captcha_maxlength');
×
156
            $captcha->characterSet = (string) setting('captcha_symbols');
×
157
            $phrase = $captcha->getCode();
×
158
            $captcha = $captcha->build();
×
159
        }
160

161
        $request->session()->put('protect', $phrase);
×
162

163
        return response($captcha)
×
164
            ->header('Content-Type', setting('captcha_type') === 'animated' ? 'image/gif' : 'image/png')
×
165
            ->header('Cache-Control', 'no-store, no-cache, must-revalidate, max-age=0')
×
166
            ->header('Pragma', 'no-cache')
×
167
            ->header('Expires', 'Sat, 26 Jul 1997 05:00:00 GMT');
×
168
    }
169

170
    /**
171
     * Быстрое изменение языка
172
     */
173
    public function language(string $lang, Request $request): JsonResponse
×
174
    {
175
        $languages = getAvailableLanguages();
×
176

177
        if (preg_match('/^[a-z]+$/', $lang) && in_array($lang, $languages, true)) {
×
178
            if ($user = $request->user()) {
×
179
                $user->update([
×
180
                    'language' => $lang,
×
181
                ]);
×
182
            } else {
183
                $request->session()->put('language', $lang);
×
184
            }
185
        }
186

187
        return response()->json(['success' => true]);
×
188
    }
189

190
    public function error403(): View
×
191
    {
192
        abort(403);
×
193
    }
194

195
    public function error404(): View
×
196
    {
197
        abort(404);
×
198
    }
199
}
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