getRawHtml()   F
last analyzed

Complexity

Conditions 22
Paths 3636

Size

Total Lines 83
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 22
eloc 52
c 0
b 0
f 0
nc 3636
nop 3
dl 0
loc 83
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
use App\Models\Release;
4
use Blacklight\NZB;
5
use Blacklight\utility\Utility;
6
use Blacklight\XXX;
7
use GuzzleHttp\Client;
8
use GuzzleHttp\Cookie\CookieJar;
9
use GuzzleHttp\Cookie\SetCookie;
10
use GuzzleHttp\Exception\RequestException;
11
use Illuminate\Support\Facades\DB;
12
use Illuminate\Support\Facades\Log;
13
use Illuminate\Support\Str;
14
use sspat\ESQuerySanitizer\Sanitizer;
15
use Symfony\Component\Process\Process;
16
use Zip as ZipStream;
17
18
if (! function_exists('getRawHtml')) {
19
    /**
20
     * @param  bool  $cookie
21
     * @return bool|mixed|string
22
     */
23
    function getRawHtml($url, $cookie = false, $postData = null)
24
    {
25
        // Check if this is an adult site that needs age verification
26
        $adultSites = [
27
            'adultdvdempire.com',
28
            'adultdvdmarketplace.com',
29
            'aebn.net',
30
            'hotmovies.com',
31
            'popporn.com',
32
        ];
33
34
        $isAdultSite = false;
35
        foreach ($adultSites as $site) {
36
            if (stripos($url, $site) !== false) {
37
                $isAdultSite = true;
38
                break;
39
            }
40
        }
41
42
        // For adult sites, use age verification manager if available
43
        if ($isAdultSite && class_exists('\Blacklight\processing\adult\AgeVerificationManager')) {
44
            try {
45
                static $ageVerificationManager = null;
46
                if ($ageVerificationManager === null) {
47
                    $ageVerificationManager = new \Blacklight\processing\adult\AgeVerificationManager;
48
                }
49
50
                if ($postData !== null) {
51
                    // Handle POST requests
52
                    $cookieJar = $ageVerificationManager->getCookieJar($url);
53
                    $client = new Client([
54
                        'cookies' => $cookieJar,
55
                        'headers' => ['User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'],
56
                    ]);
57
58
                    $response = $client->post($url, ['form_params' => $postData]);
59
                    $response = $response->getBody()->getContents();
60
                } else {
61
                    $response = $ageVerificationManager->makeRequest($url);
62
                }
63
64
                if ($response !== false) {
65
                    $jsonResponse = json_decode($response, true);
66
                    if (json_last_error() === JSON_ERROR_NONE) {
67
                        return $jsonResponse;
68
                    }
69
70
                    return $response;
71
                }
72
            } catch (\Exception $e) {
73
                // Fall through to standard method
74
                if (function_exists('config') && config('app.debug') === true) {
75
                    Log::error('Age verification failed, falling back to standard method: '.$e->getMessage());
76
                }
77
            }
78
        }
79
80
        // Standard method for non-adult sites or if age verification fails
81
        $cookieJar = new CookieJar;
82
        $client = new Client(['headers' => ['User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246']]);
83
        if ($cookie !== false && $cookie !== null && $cookie !== '') {
84
            $cookie = $cookieJar->setCookie(SetCookie::fromString($cookie));
0 ignored issues
show
Bug introduced by
$cookie of type true is incompatible with the type string expected by parameter $cookie of GuzzleHttp\Cookie\SetCookie::fromString(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

84
            $cookie = $cookieJar->setCookie(SetCookie::fromString(/** @scrutinizer ignore-type */ $cookie));
Loading history...
85
            $client = new Client(['cookies' => $cookie, 'headers' => ['User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.135 Safari/537.36 Edge/12.246']]);
86
        }
87
        try {
88
            $response = $client->get($url)->getBody()->getContents();
89
            $jsonResponse = json_decode($response, true);
90
            if (json_last_error() === JSON_ERROR_NONE) {
91
                $response = $jsonResponse;
92
            }
93
        } catch (RequestException $e) {
94
            if (function_exists('config') && config('app.debug') === true) {
95
                Log::error($e->getMessage());
96
            }
97
            $response = false;
98
        } catch (RuntimeException $e) {
99
            if (function_exists('config') && config('app.debug') === true) {
100
                Log::error($e->getMessage());
101
            }
102
            $response = false;
103
        }
104
105
        return $response;
106
    }
107
}
108
109
if (! function_exists('makeFieldLinks')) {
110
    /**
111
     * @return string
112
     *
113
     * @throws Exception
114
     */
115
    function makeFieldLinks($data, $field, $type)
116
    {
117
        $tmpArr = explode(', ', $data[$field]);
118
        $newArr = [];
119
        $i = 0;
120
        foreach ($tmpArr as $ta) {
121
            if (trim($ta) === '') {
122
                continue;
123
            }
124
            if ($type === 'xxx' && $field === 'genre') {
125
                $ta = (new XXX)->getGenres(true, $ta);
126
                $ta = $ta['title'] ?? '';
127
            }
128
            if ($i > 7) {
129
                break;
130
            }
131
            $newArr[] = '<a href="'.url('/'.ucfirst($type).'?'.$field.'='.urlencode($ta)).'" title="'.$ta.'">'.$ta.'</a>';
132
            $i++;
133
        }
134
135
        return implode(', ', $newArr);
136
    }
137
}
138
139
if (! function_exists('getUserBrowseOrder')) {
140
    /**
141
     * @param  string  $orderBy
142
     */
143
    function getUserBrowseOrder($orderBy): array
144
    {
145
        $order = ($orderBy === '' ? 'username_desc' : $orderBy);
146
        $orderArr = explode('_', $order);
147
        $orderField = match ($orderArr[0]) {
148
            'email' => 'email',
149
            'host' => 'host',
150
            'createdat' => 'created_at',
151
            'lastlogin' => 'lastlogin',
152
            'apiaccess' => 'apiaccess',
153
            'apirequests' => 'apirequests',
154
            'grabs' => 'grabs',
155
            'roles_id' => 'users_role_id',
156
            'rolechangedate' => 'rolechangedate',
157
            default => 'username',
158
        };
159
        $orderSort = (isset($orderArr[1]) && preg_match('/^asc|desc$/i', $orderArr[1])) ? $orderArr[1] : 'desc';
160
161
        return [$orderField, $orderSort];
162
    }
163
}
164
165
if (! function_exists('getUserBrowseOrdering')) {
166
    function getUserBrowseOrdering(): array
167
    {
168
        return [
169
            'username_asc',
170
            'username_desc',
171
            'email_asc',
172
            'email_desc',
173
            'host_asc',
174
            'host_desc',
175
            'createdat_asc',
176
            'createdat_desc',
177
            'lastlogin_asc',
178
            'lastlogin_desc',
179
            'apiaccess_asc',
180
            'apiaccess_desc',
181
            'apirequests_asc',
182
            'apirequests_desc',
183
            'grabs_asc',
184
            'grabs_desc',
185
            'role_asc',
186
            'role_desc',
187
            'rolechangedate_asc',
188
            'rolechangedate_desc',
189
            'verification_asc',
190
            'verification_desc',
191
        ];
192
    }
193
}
194
195
if (! function_exists('getSimilarName')) {
196
    /**
197
     * @param  string  $name
198
     */
199
    function getSimilarName($name): string
200
    {
201
        return implode(' ', \array_slice(str_word_count(str_replace(['.', '_', '-'], ' ', $name), 2), 0, 2));
0 ignored issues
show
Bug introduced by
It seems like str_word_count(str_repla..., '-'), ' ', $name), 2) can also be of type integer; however, parameter $array of array_slice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

201
        return implode(' ', \array_slice(/** @scrutinizer ignore-type */ str_word_count(str_replace(['.', '_', '-'], ' ', $name), 2), 0, 2));
Loading history...
202
    }
203
}
204
205
if (! function_exists('human_filesize')) {
206
    /**
207
     * @param  int  $decimals
208
     */
209
    function human_filesize($bytes, $decimals = 0): string
210
    {
211
        $size = ['B', 'kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
212
        $factor = floor((\strlen($bytes) - 1) / 3);
213
214
        return round(sprintf("%.{$decimals}f", $bytes / (1024 ** $factor)), $decimals).@$size[$factor];
0 ignored issues
show
Bug introduced by
sprintf('%.'.$decimals.'...ytes / 1024 ** $factor) of type string is incompatible with the type double|integer expected by parameter $num of round(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

214
        return round(/** @scrutinizer ignore-type */ sprintf("%.{$decimals}f", $bytes / (1024 ** $factor)), $decimals).@$size[$factor];
Loading history...
215
    }
216
}
217
218
if (! function_exists('bcdechex')) {
219
    /**
220
     * @return string
221
     */
222
    function bcdechex($dec)
223
    {
224
        $hex = '';
225
        do {
226
            $last = bcmod($dec, 16);
227
            $hex = dechex($last).$hex;
0 ignored issues
show
Bug introduced by
$last of type null|string is incompatible with the type integer expected by parameter $num of dechex(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

227
            $hex = dechex(/** @scrutinizer ignore-type */ $last).$hex;
Loading history...
228
            $dec = bcdiv(bcsub($dec, $last), 16);
229
        } while ($dec > 0);
230
231
        return $hex;
232
    }
233
}
234
235
if (! function_exists('runCmd')) {
236
    /**
237
     * Run CLI command.
238
     *
239
     *
240
     * @param  string  $command
241
     * @param  bool  $debug
242
     * @return string
243
     */
244
    function runCmd($command, $debug = false)
245
    {
246
        if ($debug) {
247
            echo '-Running Command: '.PHP_EOL.'   '.$command.PHP_EOL;
248
        }
249
250
        $process = Process::fromShellCommandline('exec '.$command);
251
        $process->setTimeout(1800);
252
        $process->run();
253
        $output = $process->getOutput();
254
255
        if ($debug) {
256
            echo '-Command Output: '.PHP_EOL.'   '.$output.PHP_EOL;
257
        }
258
259
        return $output;
260
    }
261
}
262
263
if (! function_exists('escapeString')) {
264
265
    function escapeString($string): string
266
    {
267
        return DB::connection()->getPdo()->quote($string);
268
    }
269
}
270
271
if (! function_exists('realDuration')) {
272
273
    function realDuration($milliseconds): string
274
    {
275
        $time = round($milliseconds / 1000);
276
277
        return sprintf('%02dh:%02dm:%02ds', floor($time / 3600), floor($time / 60 % 60), $time % 60);
278
    }
279
}
280
281
if (! function_exists('is_it_json')) {
282
    /**
283
     * @throws JsonException
284
     */
285
    function is_it_json($isIt): bool
286
    {
287
        if (is_array($isIt)) {
288
            return false;
289
        }
290
        json_decode($isIt, true, 512, JSON_THROW_ON_ERROR);
291
292
        return json_last_error() === JSON_ERROR_NONE;
293
    }
294
}
295
296
if (! function_exists('getStreamingZip')) {
297
    /**
298
     * @throws Exception
299
     */
300
    function getStreamingZip(array $guids = []): STS\ZipStream\Builder
301
    {
302
        $nzb = new NZB;
303
        $zipped = ZipStream::create(now()->format('Ymdhis').'.zip');
304
        foreach ($guids as $guid) {
305
            $nzbPath = $nzb->NZBPath($guid);
306
            if ($nzbPath) {
307
                $nzbContents = Utility::unzipGzipFile($nzbPath);
308
                if ($nzbContents) {
309
                    $filename = $guid;
310
                    $r = Release::query()->where('guid', $guid)->first();
311
                    if ($r) {
312
                        $filename = $r['searchname'];
313
                    }
314
                    $zipped->addRaw($nzbContents, $filename.'.nzb');
315
                }
316
            }
317
        }
318
319
        return $zipped;
320
    }
321
}
322
323
if (! function_exists('release_flag')) {
324
    // Function inspired by c0r3@newznabforums adds country flags on the browse page.
325
    /**
326
     * @param  string  $text  Text to match against.
327
     * @param  string  $page  Type of page. browse or search.
328
     */
329
    function release_flag(string $text, string $page): bool|string
330
    {
331
        $code = $language = '';
332
333
        switch (true) {
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/Italian|\bita\b/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/Japanese|\bjp\b/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/Tagalog|Filipino/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/\bCzech\b/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/Chinese|Man...in|\bc[hn]\b/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/Hungarian|\bhun\b/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/Korean|\bkr\b/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/Hebrew|Yiddish/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/German(bed)?|\bger\b/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/\bHindi\b/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/\bThai\b/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/French|Vostfr|Multi/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/\bGreek\b/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/Flemish|\b(...|nl)\b|NlSub/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
Bug Best Practice introduced by
It seems like you are loosely comparing preg_match('/Swe(dish|sub)/i', $text) of type integer to the boolean true. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
334
            case stripos($text, 'Arabic') !== false:
335
                $code = 'PK';
336
                $language = 'Arabic';
337
                break;
338
            case stripos($text, 'Cantonese') !== false:
339
                $code = 'TW';
340
                $language = 'Cantonese';
341
                break;
342
            case preg_match('/Chinese|Mandarin|\bc[hn]\b/i', $text):
343
                $code = 'CN';
344
                $language = 'Chinese';
345
                break;
346
            case preg_match('/\bCzech\b/i', $text):
347
                $code = 'CZ';
348
                $language = 'Czech';
349
                break;
350
            case stripos($text, 'Danish') !== false:
351
                $code = 'DK';
352
                $language = 'Danish';
353
                break;
354
            case stripos($text, 'Finnish') !== false:
355
                $code = 'FI';
356
                $language = 'Finnish';
357
                break;
358
            case preg_match('/Flemish|\b(Dutch|nl)\b|NlSub/i', $text):
359
                $code = 'NL';
360
                $language = 'Dutch';
361
                break;
362
            case preg_match('/French|Vostfr|Multi/i', $text):
363
                $code = 'FR';
364
                $language = 'French';
365
                break;
366
            case preg_match('/German(bed)?|\bger\b/i', $text):
367
                $code = 'DE';
368
                $language = 'German';
369
                break;
370
            case preg_match('/\bGreek\b/i', $text):
371
                $code = 'GR';
372
                $language = 'Greek';
373
                break;
374
            case preg_match('/Hebrew|Yiddish/i', $text):
375
                $code = 'IL';
376
                $language = 'Hebrew';
377
                break;
378
            case preg_match('/\bHindi\b/i', $text):
379
                $code = 'IN';
380
                $language = 'Hindi';
381
                break;
382
            case preg_match('/Hungarian|\bhun\b/i', $text):
383
                $code = 'HU';
384
                $language = 'Hungarian';
385
                break;
386
            case preg_match('/Italian|\bita\b/i', $text):
387
                $code = 'IT';
388
                $language = 'Italian';
389
                break;
390
            case preg_match('/Japanese|\bjp\b/i', $text):
391
                $code = 'JP';
392
                $language = 'Japanese';
393
                break;
394
            case preg_match('/Korean|\bkr\b/i', $text):
395
                $code = 'KR';
396
                $language = 'Korean';
397
                break;
398
            case stripos($text, 'Norwegian') !== false:
399
                $code = 'NO';
400
                $language = 'Norwegian';
401
                break;
402
            case stripos($text, 'Polish') !== false:
403
                $code = 'PL';
404
                $language = 'Polish';
405
                break;
406
            case stripos($text, 'Portuguese') !== false:
407
                $code = 'PT';
408
                $language = 'Portugese';
409
                break;
410
            case stripos($text, 'Romanian') !== false:
411
                $code = 'RO';
412
                $language = 'Romanian';
413
                break;
414
            case stripos($text, 'Spanish') !== false:
415
                $code = 'ES';
416
                $language = 'Spanish';
417
                break;
418
            case preg_match('/Swe(dish|sub)/i', $text):
419
                $code = 'SE';
420
                $language = 'Swedish';
421
                break;
422
            case preg_match('/Tagalog|Filipino/i', $text):
423
                $code = 'PH';
424
                $language = 'Tagalog|Filipino';
425
                break;
426
            case preg_match('/\bThai\b/i', $text):
427
                $code = 'TH';
428
                $language = 'Thai';
429
                break;
430
            case stripos($text, 'Turkish') !== false:
431
                $code = 'TR';
432
                $language = 'Turkish';
433
                break;
434
            case stripos($text, 'Russian') !== false:
435
                $code = 'RU';
436
                $language = 'Russian';
437
                break;
438
            case stripos($text, 'Vietnamese') !== false:
439
                $code = 'VN';
440
                $language = 'Vietnamese';
441
                break;
442
        }
443
444
        if ($code !== '' && $page === 'browse') {
445
            return '<img title="'.$language.'" alt="'.$language.'" src="'.asset('/assets/images/flags/'.$code.'.png').'"/>';
446
        }
447
448
        if ($page === 'search') {
449
            if ($code === '') {
450
                return false;
451
            }
452
453
            return $code;
454
        }
455
456
        return '';
457
    }
458
}
459
460
if (! function_exists('getReleaseCover')) {
461
    /**
462
     * Get the cover image URL for a release based on its type and ID
463
     *
464
     * @param  object|array  $release  The release object or array
465
     * @return string The cover image URL or placeholder if no cover exists
466
     */
467
    function getReleaseCover($release): string
468
    {
469
        $coverType = null;
470
        $coverId = null;
471
472
        // Helper function to get value from object or array
473
        $getValue = function ($data, $key) {
474
            if (is_array($data)) {
475
                return $data[$key] ?? null;
476
            } elseif (is_object($data)) {
477
                return $data->$key ?? null;
478
            }
479
480
            return null;
481
        };
482
483
        // Determine cover type and ID based on category
484
        $imdbid = $getValue($release, 'imdbid');
485
        $musicinfo_id = $getValue($release, 'musicinfo_id');
486
        $consoleinfo_id = $getValue($release, 'consoleinfo_id');
487
        $bookinfo_id = $getValue($release, 'bookinfo_id');
488
        $gamesinfo_id = $getValue($release, 'gamesinfo_id');
489
        $xxxinfo_id = $getValue($release, 'xxxinfo_id');
490
        $anidbid = $getValue($release, 'anidbid');
491
492
        if (! empty($imdbid) && $imdbid > 0) {
493
            $coverType = 'movies';
494
            $coverId = str_pad($imdbid, 7, '0', STR_PAD_LEFT);
495
        } elseif (! empty($musicinfo_id)) {
496
            $coverType = 'music';
497
            $coverId = $musicinfo_id;
498
        } elseif (! empty($consoleinfo_id)) {
499
            $coverType = 'console';
500
            $coverId = $consoleinfo_id;
501
        } elseif (! empty($bookinfo_id)) {
502
            $coverType = 'book';
503
            $coverId = $bookinfo_id;
504
        } elseif (! empty($gamesinfo_id)) {
505
            $coverType = 'games';
506
            $coverId = $gamesinfo_id;
507
        } elseif (! empty($xxxinfo_id)) {
508
            $coverType = 'xxx';
509
            $coverId = $xxxinfo_id;
510
        } elseif (! empty($anidbid) && $anidbid > 0) {
511
            $coverType = 'anime';
512
            $coverId = $anidbid;
513
        }
514
515
        // Return the cover URL if we have a type and ID
516
        // The CoverController will handle serving the file or returning a placeholder
517
        if ($coverType && $coverId) {
518
            return url("/covers/{$coverType}/{$coverId}-cover.jpg");
519
        }
520
521
        // Return placeholder image if no cover type/ID found
522
        return asset('assets/images/no-cover.png');
523
    }
524
}
525
526
if (! function_exists('sanitize')) {
527
    function sanitize(array|string $phrases, array $doNotSanitize = []): string
528
    {
529
        if (! is_array($phrases)) {
0 ignored issues
show
introduced by
The condition is_array($phrases) is always true.
Loading history...
530
            $wordArray = explode(' ', str_replace('.', ' ', $phrases));
531
        } else {
532
            $wordArray = $phrases;
533
        }
534
535
        $keywords = [];
536
        $tempWords = [];
537
        foreach ($wordArray as $words) {
538
            $words = preg_split('/\s+/', $words);
539
            foreach ($words as $st) {
540
                if (Str::startsWith($st, ['!', '+', '-', '?', '*']) && Str::length($st) > 1 && ! preg_match('/([!+?\-*]){2,}/', $st)) {
541
                    $str = $st;
542
                } elseif (Str::endsWith($st, ['+', '-', '?', '*']) && Str::length($st) > 1 && ! preg_match('/([!+?\-*]){2,}/', $st)) {
543
                    $str = $st;
544
                } else {
545
                    $str = Sanitizer::escape($st, $doNotSanitize);
546
                }
547
                $tempWords[] = $str;
548
            }
549
550
            $keywords = $tempWords;
551
        }
552
553
        return implode(' ', $keywords);
554
    }
555
}
556
557
if (! function_exists('formatBytes')) {
558
    /**
559
     * Format bytes into human-readable file size.
560
     *
561
     * @param  int|float|null  $bytes
562
     */
563
    function formatBytes($bytes = 0): string
564
    {
565
        $units = ['B', 'KB', 'MB', 'GB', 'TB'];
566
        $bytes = max((int) $bytes, 0);
567
        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
568
        $pow = min($pow, count($units) - 1);
569
570
        $bytes /= pow(1024, $pow);
571
572
        return round($bytes, 2).' '.$units[$pow];
573
    }
574
}
575
576
if (! function_exists('csp_nonce')) {
577
    /**
578
     * Generate a CSP nonce for inline scripts
579
     * This should be stored in the request and reused across the request lifecycle
580
     */
581
    function csp_nonce(): string
582
    {
583
        static $nonce = null;
584
585
        if ($nonce === null) {
586
            $nonce = base64_encode(random_bytes(16));
587
        }
588
589
        return $nonce;
590
    }
591
}
592
593
if (! function_exists('userDate')) {
594
    /**
595
     * Format a date/time string according to the authenticated user's timezone
596
     *
597
     * @param  string|null  $date  The date to format
598
     * @param  string  $format  The format string (default: 'M d, Y H:i')
599
     * @return string The formatted date in user's timezone
600
     */
601
    function userDate(?string $date, string $format = 'M d, Y H:i'): string
602
    {
603
        if (empty($date)) {
604
            return '';
605
        }
606
607
        try {
608
            // Parse the date in the app's timezone (which should be UTC)
609
            // If dates in DB are stored in server timezone, they'll be parsed correctly
610
            $appTimezone = config('app.timezone', 'UTC');
611
            $carbon = \Illuminate\Support\Carbon::parse($date, $appTimezone);
612
613
            // If user is authenticated and has a timezone set, convert to it
614
            if (\Illuminate\Support\Facades\Auth::check() && \Illuminate\Support\Facades\Auth::user()->timezone) {
0 ignored issues
show
Bug introduced by
Accessing timezone on the interface Illuminate\Contracts\Auth\Authenticatable suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
615
                $carbon->setTimezone(\Illuminate\Support\Facades\Auth::user()->timezone);
616
            }
617
618
            return $carbon->format($format);
619
        } catch (\Exception $e) {
620
            return $date;
621
        }
622
    }
623
}
624
625
if (! function_exists('userDateDiffForHumans')) {
626
    /**
627
     * Format a date/time string as a human-readable diff according to the authenticated user's timezone
628
     *
629
     * @param  string|null  $date  The date to format
630
     * @return string The formatted date diff in user's timezone
631
     */
632
    function userDateDiffForHumans(?string $date): string
633
    {
634
        if (empty($date)) {
635
            return '';
636
        }
637
638
        try {
639
            // Parse the date in the app's timezone (which should be UTC)
640
            // If dates in DB are stored in server timezone, they'll be parsed correctly
641
            $appTimezone = config('app.timezone', 'UTC');
642
            $carbon = \Illuminate\Support\Carbon::parse($date, $appTimezone);
643
644
            // If user is authenticated and has a timezone set, convert to it
645
            if (\Illuminate\Support\Facades\Auth::check() && \Illuminate\Support\Facades\Auth::user()->timezone) {
0 ignored issues
show
Bug introduced by
Accessing timezone on the interface Illuminate\Contracts\Auth\Authenticatable suggest that you code against a concrete implementation. How about adding an instanceof check?
Loading history...
646
                $carbon->setTimezone(\Illuminate\Support\Facades\Auth::user()->timezone);
647
            }
648
649
            return $carbon->diffForHumans();
650
        } catch (\Exception $e) {
651
            return $date;
652
        }
653
    }
654
}
655
656
if (! function_exists('getAvailableTimezones')) {
657
    /**
658
     * Get a list of available timezones grouped by region
659
     *
660
     * @return array Array of timezones grouped by region
661
     */
662
    function getAvailableTimezones(): array
663
    {
664
        $timezones = [];
665
        $regions = [
666
            'Africa' => \DateTimeZone::AFRICA,
667
            'America' => \DateTimeZone::AMERICA,
668
            'Antarctica' => \DateTimeZone::ANTARCTICA,
669
            'Arctic' => \DateTimeZone::ARCTIC,
670
            'Asia' => \DateTimeZone::ASIA,
671
            'Atlantic' => \DateTimeZone::ATLANTIC,
672
            'Australia' => \DateTimeZone::AUSTRALIA,
673
            'Europe' => \DateTimeZone::EUROPE,
674
            'Indian' => \DateTimeZone::INDIAN,
675
            'Pacific' => \DateTimeZone::PACIFIC,
676
        ];
677
678
        foreach ($regions as $name => $region) {
679
            $timezones[$name] = \DateTimeZone::listIdentifiers($region);
680
        }
681
682
        return $timezones;
683
    }
684
}
685