Completed
Branch dev (4bcb34)
by Darko
13:52
created

ReleaseRemover::removePasswordURL()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 18
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 11
nc 2
nop 0
dl 0
loc 18
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
namespace Blacklight;
4
5
use Blacklight\db\DB;
6
use App\Models\Category;
7
8
/**
9
 * Handles removing of various unwanted releases.
10
 *
11
 * Class ReleaseRemover
12
 */
13
class ReleaseRemover
14
{
15
    /**
16
     * @const New line.
17
     */
18
    private const N = PHP_EOL;
19
20
    /**
21
     * @var string
22
     */
23
    protected $blacklistID;
24
25
    /**
26
     * Is is run from the browser?
27
     *
28
     * @var bool
29
     */
30
    protected $browser;
31
32
    /**
33
     * @var \Blacklight\ConsoleTools
34
     */
35
    protected $consoleTools;
36
37
    /**
38
     * @var string
39
     */
40
    protected $crapTime = '';
41
42
    /**
43
     * @var bool
44
     */
45
    protected $delete;
46
47
    /**
48
     * @var int
49
     */
50
    protected $deletedCount = 0;
51
52
    /**
53
     * @var bool
54
     */
55
    protected $echoCLI;
56
57
    /**
58
     * If an error occurred, store it here.
59
     *
60
     * @var string
61
     */
62
    protected $error;
63
64
    /**
65
     * Ignore user check?
66
     *
67
     * @var bool
68
     */
69
    protected $ignoreUserCheck;
70
71
    /**
72
     * @var string
73
     */
74
    protected $method = '';
75
76
    /**
77
     * @var \Blacklight\db\DB
78
     */
79
    protected $pdo;
80
81
    /**
82
     * The query we will use to select unwanted releases.
83
     *
84
     * @var string
85
     */
86
    protected $query;
87
88
    /**
89
     * @var \Blacklight\Releases
90
     */
91
    protected $releases;
92
93
    /**
94
     * Result of the select query.
95
     *
96
     * @var array
97
     */
98
    protected $result;
99
100
    /**
101
     * Time we started.
102
     *
103
     * @var int
104
     */
105
    protected $timeStart;
106
107
    /**
108
     * @var \Blacklight\NZB
109
     */
110
    private $nzb;
111
112
    /**
113
     * @var \Blacklight\ReleaseImage
114
     */
115
    private $releaseImage;
116
117
    /**
118
     * Construct.
119
     *
120
     * @param array $options Class instances / various options.
121
     * @throws \Exception
122
     */
123
    public function __construct(array $options = [])
124
    {
125
        $defaults = [
126
            'Browser'      => false, // Are we coming from the web script.
127
            'ConsoleTools' => null,
128
            'Echo'         => true, // Echo to CLI?
129
            'NZB'          => null,
130
            'ReleaseImage' => null,
131
            'Releases'     => null,
132
            'Settings'     => null,
133
        ];
134
        $options += $defaults;
135
136
        $this->pdo = ($options['Settings'] instanceof DB ? $options['Settings'] : new DB());
137
        $this->consoleTools = ($options['ConsoleTools'] instanceof ConsoleTools ? $options['ConsoleTools'] : new ConsoleTools());
138
        $this->releases = ($options['Releases'] instanceof Releases ? $options['Releases'] : new Releases(['Settings' => $this->pdo]));
139
        $this->nzb = ($options['NZB'] instanceof NZB ? $options['NZB'] : new NZB());
140
        $this->releaseImage = ($options['ReleaseImage'] instanceof ReleaseImage ? $options['ReleaseImage'] : new ReleaseImage());
141
142
        $this->query = '';
143
        $this->error = '';
144
        $this->ignoreUserCheck = false;
145
        $this->browser = $options['Browser'];
146
        $this->echoCLI = (! $this->browser && config('nntmux.echocli') && $options['Echo']);
147
    }
148
149
    /**
150
     * Remove releases using user criteria.
151
     *
152
     * @param array $arguments Array of criteria used to delete unwanted releases.
153
     *                         Criteria muse look like this : columnName=modifier="content"
154
     *                         columnName is a column name from the releases table.
155
     *                         modifiers are : equals,like,bigger,smaller
156
     *                         content is what to change the column content to
157
     *
158
     * @return string|bool
159
     */
160
    public function removeByCriteria($arguments)
161
    {
162
        $this->delete = true;
163
        $this->ignoreUserCheck = false;
164
        // Time we started.
165
        $this->timeStart = time();
166
167
        // Start forming the query.
168
        $this->query = 'SELECT id, guid, searchname FROM releases WHERE 1=1';
169
170
        // Keep forming the query based on the user's criteria, return if any errors.
171
        foreach ($arguments as $arg) {
172
            $this->error = '';
173
            $string = $this->formatCriteriaQuery($arg);
174
            if ($string === false) {
175
                return $this->returnError();
176
            }
177
            $this->query .= $string;
178
        }
179
        $this->query = $this->cleanSpaces($this->query);
180
181
        // Check if the user wants to run the query.
182
        if ($this->checkUserResponse() === false) {
183
            return false;
184
        }
185
186
        // Check if the query returns results.
187
        if ($this->checkSelectQuery() === false) {
188
            return $this->returnError();
189
        }
190
191
        $this->method = 'userCriteria';
192
193
        $this->deletedCount = 0;
194
        // Delete the releases.
195
        $this->deleteReleases();
196
197
        if ($this->echoCLI) {
198
            echo ColorCLI::headerOver(($this->delete ? 'Deleted ' : 'Would have deleted ').$this->deletedCount.' release(s). This script ran for ');
199
            echo ColorCLI::header($this->consoleTools->convertTime(time() - $this->timeStart));
200
        }
201
202
        return $this->browser
203
            ?
204
            'Success! '.
205
            ($this->delete ? 'Deleted ' : 'Would have deleted ').
206
            $this->deletedCount.
207
            ' release(s) in '.
208
            $this->consoleTools->convertTime(time() - $this->timeStart)
209
            :
210
            true;
211
    }
212
213
    /**
214
     * Delete crap releases.
215
     *
216
     * @param bool       $delete                 Delete the release or just show the result?
217
     * @param int|string $time                   Time in hours (to select old releases) or 'full' for no time limit.
218
     * @param string     $type                   Type of query to run [blacklist, executable, gibberish, hashed, installbin, passworded,
219
     *                                           passwordurl, sample, scr, short, size, ''] ('' runs against all types)
220
     * @param string|int     $blacklistID
221
     *
222
     * @return string|bool
223
     */
224
    public function removeCrap($delete, $time, $type = '', $blacklistID = '')
225
    {
226
        $this->timeStart = time();
227
        $this->delete = $delete;
228
        $this->blacklistID = '';
229
230
        if ($blacklistID !== '' && is_numeric($blacklistID)) {
231
            $this->blacklistID = sprintf('AND id = %d', $blacklistID);
232
        }
233
234
        $time = trim($time);
235
        $this->crapTime = '';
236
        $type = strtolower(trim($type));
237
238
        if ($time === 'full') {
239
            if ($this->echoCLI) {
240
                echo ColorCLI::header('Removing '.($type === '' ? 'All crap releases ' : $type.' crap releases').' - no time limit.\n');
241
            }
242
        } else {
243
            if (! is_numeric($time)) {
244
                $this->error = 'Error, time must be a number or full.';
245
246
                return $this->returnError();
247
            }
248
            if ($this->echoCLI) {
249
                echo ColorCLI::header('Removing '.($type === '' ? 'All crap releases ' : $type.' crap releases').' from the past '.$time.' hour(s).\n');
250
            }
251
            $this->crapTime = ' AND r.adddate > (NOW() - INTERVAL '.$time.' HOUR)';
252
        }
253
254
        $this->deletedCount = 0;
255
        switch ($type) {
256
            case 'blacklist':
257
                $this->removeBlacklist();
258
                break;
259
            case 'blfiles':
260
                $this->removeBlacklistFiles();
261
                break;
262
            case 'executable':
263
                $this->removeExecutable();
264
                break;
265
            case 'gibberish':
266
                $this->removeGibberish();
267
                break;
268
            case 'hashed':
269
                $this->removeHashed();
270
                break;
271
            case 'installbin':
272
                $this->removeInstallBin();
273
                break;
274
            case 'passworded':
275
                $this->removePassworded();
276
                break;
277
            case 'passwordurl':
278
                $this->removePasswordURL();
279
                break;
280
            case 'sample':
281
                $this->removeSample();
282
                break;
283
            case 'scr':
284
                $this->removeSCR();
285
                break;
286
            case 'short':
287
                $this->removeShort();
288
                break;
289
            case 'size':
290
                $this->removeSize();
291
                break;
292
            case 'huge':
293
                $this->removeHuge();
294
                break;
295
            case 'nzb':
296
                $this->removeSingleNZB();
297
                break;
298
            case 'codec':
299
                $this->removeCodecPoster();
300
                break;
301
            case 'wmv_all':
302
                $this->removeWMV();
303
                break;
304
            case '':
305
                $this->removeBlacklist();
306
                $this->removeBlacklistFiles();
307
                $this->removeExecutable();
308
                $this->removeGibberish();
309
                $this->removeHashed();
310
                $this->removeInstallBin();
311
                $this->removePassworded();
312
                $this->removeSample();
313
                $this->removeSCR();
314
                $this->removeShort();
315
                $this->removeSize();
316
                $this->removeHuge();
317
                $this->removeSingleNZB();
318
                $this->removeCodecPoster();
319
                break;
320
            default:
321
                $this->error = 'Wrong type: '.$type;
322
323
                return $this->returnError();
324
        }
325
326
        if ($this->echoCLI) {
327
            echo ColorCLI::headerOver(($this->delete ? 'Deleted ' : 'Would have deleted ').$this->deletedCount.' release(s). This script ran for ');
328
            echo ColorCLI::header($this->consoleTools->convertTime(time() - $this->timeStart));
329
        }
330
331
        return $this->browser
332
            ?
333
            'Success! '.
334
            ($this->delete ? 'Deleted ' : 'Would have deleted ').
335
            $this->deletedCount.
336
            ' release(s) in '.
337
            $this->consoleTools->convertTime(time() - $this->timeStart)
338
            :
339
            true;
340
    }
341
342
    /**
343
     * Remove releases with 15 or more letters or numbers, nothing else.
344
     *
345
     * @return bool|string
346
     */
347
    protected function removeGibberish()
348
    {
349
        $this->method = 'Gibberish';
350
        $this->query = sprintf(
351
            "SELECT r.guid, r.searchname, r.id
352
			FROM releases r
353
			WHERE r.nfostatus = 0
354
			AND r.iscategorized = 1
355
			AND r.rarinnerfilecount = 0
356
			AND r.categories_id NOT IN (%d)
357
			AND r.searchname REGEXP '^[a-zA-Z0-9]{15,}$'
358
			%s",
359
            Category::OTHER_HASHED,
360
            $this->crapTime
361
        );
362
363
        if ($this->checkSelectQuery() === false) {
364
            return $this->returnError();
365
        }
366
367
        return $this->deleteReleases();
368
    }
369
370
    /**
371
     * Remove releases with 25 or more letters/numbers, probably hashed.
372
     *
373
     * @return bool|string
374
     */
375
    protected function removeHashed()
376
    {
377
        $this->method = 'Hashed';
378
        $this->query = sprintf(
379
            "SELECT r.guid, r.searchname, r.id
380
			FROM releases r
381
			WHERE r.nfostatus = 0
382
			AND r.iscategorized = 1
383
			AND r.rarinnerfilecount = 0
384
			AND r.categories_id NOT IN (%d, %d)
385
			AND r.searchname REGEXP '[a-zA-Z0-9]{25,}'
386
			%s",
387
            Category::OTHER_MISC,
388
            Category::OTHER_HASHED,
389
            $this->crapTime
390
        );
391
392
        if ($this->checkSelectQuery() === false) {
393
            return $this->returnError();
394
        }
395
396
        return $this->deleteReleases();
397
    }
398
399
    /**
400
     * Remove releases with 5 or less letters/numbers.
401
     *
402
     * @return bool|string
403
     */
404
    protected function removeShort()
405
    {
406
        $this->method = 'Short';
407
        $this->query = sprintf(
408
            "SELECT r.guid, r.searchname, r.id
409
			FROM releases r
410
			WHERE r.nfostatus = 0
411
			AND r.iscategorized = 1
412
			AND r.rarinnerfilecount = 0
413
			AND r.categories_id NOT IN (%d)
414
			AND r.searchname REGEXP '^[a-zA-Z0-9]{0,5}$'
415
			%s",
416
            Category::OTHER_MISC,
417
            $this->crapTime
418
        );
419
420
        if ($this->checkSelectQuery() === false) {
421
            return $this->returnError();
422
        }
423
424
        return $this->deleteReleases();
425
    }
426
427
    /**
428
     * Remove releases with an exe file not in other misc or pc apps/games.
429
     *
430
     * @return bool|string
431
     */
432
    protected function removeExecutable()
433
    {
434
        $this->method = 'Executable';
435
436
        $this->query = sprintf(
437
            'SELECT r.guid, r.searchname, r.id
438
			FROM releases r
439
			STRAIGHT_JOIN release_files rf ON r.id = rf.releases_id
440
			WHERE rf.name %s
441
			AND r.categories_id NOT IN (%d, %d, %d, %d) %s',
442
            $this->pdo->likeString('.exe', true, false),
443
            Category::PC_0DAY,
444
            Category::PC_GAMES,
445
            Category::OTHER_MISC,
446
            Category::OTHER_HASHED,
447
            $this->crapTime
448
        );
449
450
        if ($this->checkSelectQuery() === false) {
451
            return $this->returnError();
452
        }
453
454
        return $this->deleteReleases();
455
    }
456
457
    /**
458
     * Remove releases with an install.bin file.
459
     *
460
     * @return bool|string
461
     */
462
    protected function removeInstallBin()
463
    {
464
        $this->method = 'Install.bin';
465
466
        $this->query = sprintf(
467
            'SELECT r.guid, r.searchname, r.id
468
			FROM releases r
469
			STRAIGHT_JOIN release_files rf ON r.id = rf.releases_id
470
			WHERE rf.name %s %s',
471
            $this->pdo->likeString('install.bin', true, true),
472
            $this->crapTime
473
        );
474
475
        if ($this->checkSelectQuery() === false) {
476
            return $this->returnError();
477
        }
478
479
        return $this->deleteReleases();
480
    }
481
482
    /**
483
     * Remove releases with an password.url file.
484
     *
485
     * @return bool|string
486
     */
487
    protected function removePasswordURL()
488
    {
489
        $this->method = 'Password.url';
490
491
        $this->query = sprintf(
492
            'SELECT r.guid, r.searchname, r.id
493
			FROM releases r
494
			STRAIGHT_JOIN release_files rf ON r.id = rf.releases_id
495
			WHERE rf.name %s %s ',
496
            $this->pdo->likeString('password.url', true, true),
497
            $this->crapTime
498
        );
499
500
        if ($this->checkSelectQuery() === false) {
501
            return $this->returnError();
502
        }
503
504
        return $this->deleteReleases();
505
    }
506
507
    /**
508
     * Remove releases with password in the search name.
509
     *
510
     * @return bool|string
511
     */
512
    protected function removePassworded()
513
    {
514
        $this->method = 'Passworded';
515
516
        $this->query = sprintf(
517
            'SELECT r.guid, r.searchname, r.id
518
			FROM releases r
519
			WHERE r.searchname %s
520
			AND r.searchname NOT %s
521
			AND r.searchname NOT %s
522
			AND r.searchname NOT %s
523
			AND r.searchname NOT %s
524
			AND r.searchname NOT %s
525
			AND r.searchname NOT %s
526
			AND r.nzbstatus = 1
527
			AND r.categories_id NOT IN (%d, %d, %d, %d, %d, %d, %d, %d, %d) %s',
528
            // Matches passwort / passworded / etc also.
529
            $this->pdo->likeString('passwor', true, true),
530
            $this->pdo->likeString('advanced', true, true),
531
            $this->pdo->likeString('no password', true, true),
532
            $this->pdo->likeString('not password', true, true),
533
            $this->pdo->likeString('recovery', true, true),
534
            $this->pdo->likeString('reset', true, true),
535
            $this->pdo->likeString('unlocker', true, true),
536
            Category::PC_GAMES,
537
            Category::PC_0DAY,
538
            Category::PC_ISO,
539
            Category::PC_MAC,
540
            Category::PC_PHONE_ANDROID,
541
            Category::PC_PHONE_IOS,
542
            Category::PC_PHONE_OTHER,
543
            Category::OTHER_MISC,
544
            Category::OTHER_HASHED,
545
            $this->crapTime
546
        );
547
548
        if ($this->checkSelectQuery() === false) {
549
            return $this->returnError();
550
        }
551
552
        return $this->deleteReleases();
553
    }
554
555
    /**
556
     * Remove releases smaller than 2MB with 1 part not in MP3/books/misc section.
557
     *
558
     * @return bool|string
559
     */
560
    protected function removeSize()
561
    {
562
        $this->method = 'Size';
563
        $this->query = sprintf(
564
            'SELECT r.guid, r.searchname, r.id
565
			FROM releases r
566
			WHERE r.totalpart = 1
567
			AND r.size < 2097152
568
			AND r.categories_id NOT IN (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d) %s',
569
            Category::MUSIC_MP3,
570
            Category::BOOKS_COMICS,
571
            Category::BOOKS_EBOOK,
572
            Category::BOOKS_FOREIGN,
573
            Category::BOOKS_MAGAZINES,
574
            Category::BOOKS_TECHNICAL,
575
            Category::BOOKS_UNKNOWN,
576
            Category::PC_0DAY,
577
            Category::PC_GAMES,
578
            Category::OTHER_MISC,
579
            Category::OTHER_HASHED,
580
            $this->crapTime
581
        );
582
583
        if ($this->checkSelectQuery() === false) {
584
            return $this->returnError();
585
        }
586
587
        return $this->deleteReleases();
588
    }
589
590
    /**
591
     * Remove releases bigger than 200MB with just a single file.
592
     *
593
     * @return bool|string
594
     */
595
    protected function removeHuge()
596
    {
597
        $this->method = 'Huge';
598
        $this->query = sprintf(
599
            'SELECT r.guid, r.searchname, r.id
600
			FROM releases r
601
			WHERE r.totalpart = 1
602
			AND r.size > 209715200 %s',
603
            $this->crapTime
604
        );
605
606
        if ($this->checkSelectQuery() === false) {
607
            return $this->returnError();
608
        }
609
610
        return $this->deleteReleases();
611
    }
612
613
    /**
614
     * Remove releases that are just a single nzb file.
615
     *
616
     * @return bool|string
617
     */
618
    protected function removeSingleNZB()
619
    {
620
        $this->method = '.nzb';
621
        $this->query = sprintf(
622
            'SELECT r.guid, r.searchname, r.id
623
			FROM releases r
624
			STRAIGHT_JOIN release_files rf ON r.id = rf.releases_id
625
			WHERE r.totalpart = 1
626
			AND rf.name %s %s',
627
            $this->pdo->likeString('.nzb', true, false),
628
            $this->crapTime
629
        );
630
631
        if ($this->checkSelectQuery() === false) {
632
            return $this->returnError();
633
        }
634
635
        return $this->deleteReleases();
636
    }
637
638
    /**
639
     * Remove releases with more than 1 part, less than 40MB, sample in name. TV/Movie sections.
640
     *
641
     * @return bool|string
642
     */
643
    protected function removeSample()
644
    {
645
        $this->method = 'Sample';
646
647
        $this->query = sprintf(
648
            'SELECT r.guid, r.searchname, r.id
649
			FROM releases r
650
			WHERE r.totalpart > 1
651
			AND r.size < 40000000
652
			AND r.name %s
653
			AND r.categories_id IN (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d) %s',
654
            $this->pdo->likeString('sample', true, true),
655
            Category::TV_ANIME,
656
            Category::TV_DOCU,
657
            Category::TV_FOREIGN,
658
            Category::TV_HD,
659
            Category::TV_OTHER,
660
            Category::TV_SD,
661
            Category::TV_SPORT,
662
            Category::TV_WEBDL,
663
            Category::MOVIE_3D,
664
            Category::MOVIE_BLURAY,
665
            Category::MOVIE_DVD,
666
            Category::MOVIE_FOREIGN,
667
            Category::MOVIE_HD,
668
            Category::MOVIE_OTHER,
669
            Category::MOVIE_SD,
670
            $this->crapTime
671
        );
672
673
        if ($this->checkSelectQuery() === false) {
674
            return $this->returnError();
675
        }
676
677
        return $this->deleteReleases();
678
    }
679
680
    /**
681
     * Remove releases with a scr file in the filename/subject.
682
     *
683
     * @return bool|string
684
     */
685
    protected function removeSCR()
686
    {
687
        $this->method = '.scr';
688
689
        $this->query = sprintf(
690
            "SELECT r.guid, r.searchname, r.id
691
			FROM releases r
692
			STRAIGHT_JOIN release_files rf ON r.id = rf.releases_id
693
			WHERE (rf.name REGEXP '[.]scr[$ \"]' OR r.name REGEXP '[.]scr[$ \"]')
694
			%s",
695
            $this->crapTime
696
        );
697
698
        if ($this->checkSelectQuery() === false) {
699
            return $this->returnError();
700
        }
701
702
        return $this->deleteReleases();
703
    }
704
705
    /**
706
     * Remove releases using the site blacklist regexes.
707
     *
708
     * @return bool
709
     */
710
    protected function removeBlacklist()
711
    {
712
        $status = sprintf('AND status = %d', Binaries::BLACKLIST_ENABLED);
713
714
        if (! empty($this->blacklistID) && $this->delete === false) {
715
            $status = '';
716
        }
717
718
        $regexList = $this->pdo->query(
719
            sprintf(
720
                'SELECT regex, id, groupname, msgcol
721
				FROM binaryblacklist
722
				WHERE optype = %d
723
				AND msgcol IN (%d, %d) %s %s
724
				ORDER BY id ASC',
725
                Binaries::OPTYPE_BLACKLIST,
726
                Binaries::BLACKLIST_FIELD_SUBJECT,
727
                Binaries::BLACKLIST_FIELD_FROM,
728
                $this->blacklistID,
729
                $status
730
            )
731
        );
732
733
        if (\count($regexList) > 0) {
734
            foreach ($regexList as $regex) {
735
                $regexSQL = $ftMatch = $regexMatch = $opTypeName = '';
736
                $dbRegex = $this->pdo->escapeString($regex['regex']);
737
738
                if ($this->crapTime === '') {
739
                    $regexMatch = $this->extractSrchFromRegx($dbRegex);
740
                    if ($regexMatch !== '') {
741
                        $ftMatch = sprintf('rse.query = "@(name,searchname) %s;limit=1000000;maxmatches=1000000;mode=any" AND', str_replace('|', ' ', str_replace('"', '', $regexMatch)));
742
                    }
743
                }
744
745
                switch ((int) $regex['msgcol']) {
746
                    case Binaries::BLACKLIST_FIELD_SUBJECT:
747
                        $regexSQL = sprintf('WHERE %s (r.name REGEXP %s OR r.searchname REGEXP %2$s)', $ftMatch, $dbRegex);
748
                        $opTypeName = 'Subject';
749
                        break;
750
                    case Binaries::BLACKLIST_FIELD_FROM:
751
                        $regexSQL = 'WHERE r.fromname REGEXP '.$dbRegex;
752
                        $opTypeName = 'Poster';
753
                        break;
754
                }
755
756
                if ($regexSQL === '') {
757
                    continue;
758
                }
759
760
                // Get the group ID if the regex is set to work against a group.
761
                $groupID = '';
762
                if (strtolower($regex['groupname']) !== 'alt.binaries.*') {
763
                    $groupIDs = $this->pdo->query(
764
                        'SELECT id FROM groups WHERE name REGEXP '.
765
                        $this->pdo->escapeString($regex['groupname'])
766
                    );
767
768
                    $groupIDCount = \count($groupIDs);
769
                    if ($groupIDCount === 0) {
770
                        continue;
771
                    } elseif ($groupIDCount === 1) {
772
                        $groupIDs = $groupIDs[0]['id'];
773
                    } else {
774
                        $string = '';
775
                        foreach ($groupIDs as $ID) {
776
                            $string .= $ID['id'].',';
777
                        }
778
                        $groupIDs = substr($string, 0, -1);
779
                    }
780
781
                    $groupID = ' AND r.groups_id in ('.$groupIDs.') ';
782
                }
783
                $this->method = 'Blacklist ['.$regex['id'].']';
784
785
                // Check if using FT Match and declare for echo
786
                if ($ftMatch !== '' && $opTypeName === 'Subject') {
787
                    $blType = 'FULLTEXT match with REGEXP';
788
                    $ftUsing = 'Using ('.$regexMatch.') as interesting words.'.PHP_EOL;
789
                } else {
790
                    $blType = 'only REGEXP';
791
                    $ftUsing = PHP_EOL;
792
                }
793
794
                // Provide useful output of operations
795
                echo ColorCLI::header(
796
                    sprintf(
797
                    "Finding crap releases for %s: Using %s method against release %s.\n".
798
                        '%s',
799
                    $this->method,
800
                    $blType,
801
                    $opTypeName,
802
                    $ftUsing
803
                    )
804
                );
805
806
                if ($opTypeName === 'Subject') {
807
                    $join = 'INNER JOIN releases_se rse ON rse.id = r.id';
808
                } else {
809
                    $join = '';
810
                }
811
812
                $this->query = sprintf(
813
                    '
814
							SELECT r.guid, r.searchname, r.id
815
							FROM releases r %s %s %s %s',
816
                    $join,
817
                    $regexSQL,
818
                    $groupID,
819
                    $this->crapTime
820
                );
821
822
                if ($this->checkSelectQuery() === false) {
823
                    continue;
824
                }
825
                $this->deleteReleases();
826
            }
827
        } else {
828
            echo ColorCLI::error("No regular expressions were selected for blacklist removal. Make sure you have activated REGEXPs in Site Edit and you're specifying a valid ID.\n");
829
        }
830
831
        return true;
832
    }
833
834
    /**
835
     * Remove releases using the site blacklist regexes against file names.
836
     *
837
     * @return bool
838
     */
839
    protected function removeBlacklistFiles()
840
    {
841
        $allRegex = $this->pdo->query(
842
            sprintf(
843
                'SELECT regex, id, groupname
844
				FROM binaryblacklist
845
				WHERE status = %d
846
				AND optype = %d
847
				AND msgcol = %d
848
				ORDER BY id ASC',
849
                Binaries::BLACKLIST_ENABLED,
850
                Binaries::OPTYPE_BLACKLIST,
851
                Binaries::BLACKLIST_FIELD_SUBJECT
852
            )
853
        );
854
855
        if (\count($allRegex) > 0) {
856
            foreach ($allRegex as $regex) {
857
                $dbRegex = $this->pdo->escapeString($regex['regex']);
0 ignored issues
show
Unused Code introduced by
The assignment to $dbRegex is dead and can be removed.
Loading history...
858
859
                $regexSQL = sprintf(
860
                    'STRAIGHT_JOIN release_files rf ON r.id = rf.releases_id
861
				WHERE rf.name REGEXP %s ',
862
                    $this->pdo->escapeString($regex['regex'])
863
                );
864
865
                if ($regexSQL === '') {
866
                    continue;
867
                }
868
869
                // Get the group ID if the regex is set to work against a group.
870
                $groupID = '';
871
                if (strtolower($regex['groupname']) !== 'alt.binaries.*') {
872
                    $groupIDs = $this->pdo->query(
873
                        'SELECT id FROM groups WHERE name REGEXP '.
874
                        $this->pdo->escapeString($regex['groupname'])
875
                    );
876
                    $groupIDCount = \count($groupIDs);
877
                    if ($groupIDCount === 0) {
878
                        continue;
879
                    } elseif ($groupIDCount === 1) {
880
                        $groupIDs = $groupIDs[0]['id'];
881
                    } else {
882
                        $string = '';
883
                        foreach ($groupIDs as $fID) {
884
                            $string .= $fID['id'].',';
885
                        }
886
                        $groupIDs = substr($string, 0, -1);
887
                    }
888
889
                    $groupID = ' AND r.groups_id in ('.$groupIDs.') ';
890
                }
891
892
                $this->method = 'Blacklist Files '.$regex['id'];
893
894
                $blType = 'only REGEXP';
895
                $ftUsing = PHP_EOL;
896
897
                // Provide useful output of operations
898
                echo ColorCLI::header(
899
                    sprintf(
900
                    'Finding crap releases for %s: Using %s method against release filenames.'.PHP_EOL.
901
                        '%s',
902
                    $this->method,
903
                    $blType,
904
                    $ftUsing
905
                    )
906
                );
907
908
                $this->query = sprintf(
909
                    'SELECT DISTINCT r.id, r.guid, r.searchname
910
					FROM releases r %s %s %s',
911
                    $regexSQL,
912
                    $groupID,
913
                    $this->crapTime
914
                );
915
916
                if ($this->checkSelectQuery() === false) {
917
                    continue;
918
                }
919
920
                $this->deleteReleases();
921
            }
922
        }
923
924
        return true;
925
    }
926
927
    /**
928
     * Remove releases that contain .wmv file, aka that spam poster.
929
     * Thanks to dizant from nZEDb forums for the sql query.
930
     *
931
     * @return string|bool
932
     */
933
    protected function removeWMV()
934
    {
935
        $this->method = 'WMV_ALL';
936
        $this->query = "
937
			SELECT r.guid, r.searchname
938
			FROM releases r
939
			LEFT JOIN release_files rf ON (r.id = rf.releases_id)
940
			WHERE r.categories_id BETWEEN ' . Category::TV_ROOT . ' AND ' . Category::TV_OTHER . '
941
			AND rf.name REGEXP 'x264.*\.wmv$'
942
			GROUP BY r.id";
943
944
        if ($this->checkSelectQuery() === false) {
945
            return $this->returnError();
946
        }
947
948
        return $this->deleteReleases();
949
    }
950
951
    /**
952
     * Remove releases that contain .wmv files and Codec\Setup.exe files, aka that spam poster.
953
     * Thanks to dizant from nZEDb forums for parts of the sql query.
954
     *
955
     * @return string|bool
956
     */
957
    protected function removeCodecPoster()
958
    {
959
        $categories = sprintf(
960
            'r.categories_id IN (%d, %d, %d, %d, %d, %d, %d, %d, %d, %d, %d)',
961
            Category::MOVIE_3D,
962
            Category::MOVIE_BLURAY,
963
            Category::MOVIE_DVD,
964
            Category::MOVIE_FOREIGN,
965
            Category::MOVIE_HD,
966
            Category::MOVIE_OTHER,
967
            Category::MOVIE_SD,
968
            Category::XXX_WMV,
969
            Category::XXX_X264,
970
            Category::XXX_XVID,
971
            Category::XXX_OTHER
972
        );
973
974
        $regex =
975
            '\.*((DVDrip|BRRip)[. ].*[. ](R[56]|HQ)|720p[ .](DVDrip|HQ)|Webrip.*[. ](R[56]|Xvid|AC3|US)'.
976
            '|720p.*[. ]WEB-DL[. ]Xvid[. ]AC3[. ]US|HDRip.*[. ]Xvid[. ]DD5).*[. ]avi$';
977
978
        $this->query = "
979
			SELECT r.guid, r.searchname, r.id
980
			FROM releases r
981
			LEFT JOIN release_files rf ON r.id = rf.releases_id
982
			WHERE {$categories}
983
			AND (r.imdbid NOT IN ('0000000', 0) OR xxxinfo_id > 0)
984
			AND r.nfostatus = 1
985
			AND r.haspreview = 0
986
			AND r.jpgstatus = 0
987
			AND r.predb_id = 0
988
			AND r.videostatus = 0
989
			AND
990
			(
991
				rf.name REGEXP 'XviD-[a-z]{3}\\.(avi|mkv|wmv)$'
992
				OR rf.name REGEXP 'x264.*\\.(wmv|avi)$'
993
				OR rf.name REGEXP '{$regex}'
994
				OR rf.name LIKE '%\\Codec%Setup.exe%'
995
				OR rf.name LIKE '%\\Codec%Installer.exe%'
996
				OR rf.name LIKE '%\\Codec.exe%'
997
				OR rf.name LIKE '%If_you_get_error.txt%'
998
				OR rf.name LIKE '%read me if the movie not playing.txt%'
999
				OR rf.name LIKE '%Lisez moi si le film ne demarre pas.txt%'
1000
				OR rf.name LIKE '%lees me als de film niet spelen.txt%'
1001
				OR rf.name LIKE '%Lesen Sie mir wenn der Film nicht abgespielt.txt%'
1002
				OR rf.name LIKE '%Lesen Sie mir, wenn der Film nicht starten.txt%'
1003
			)
1004
			GROUP BY r.id {$this->crapTime}";
1005
1006
        if ($this->checkSelectQuery() === false) {
1007
            return $this->returnError();
1008
        }
1009
1010
        return $this->deleteReleases();
1011
    }
1012
1013
    /**
1014
     * Delete releases from the database.
1015
     */
1016
    protected function deleteReleases()
1017
    {
1018
        $deletedCount = 0;
1019
        foreach ($this->result as $release) {
1020
            if ($this->delete) {
1021
                $this->releases->deleteSingle(['g' => $release['guid'], 'i' => $release['id']], $this->nzb, $this->releaseImage);
1022
                if ($this->echoCLI) {
1023
                    echo ColorCLI::primary('Deleting: '.$this->method.': '.$release['searchname']);
1024
                }
1025
            } elseif ($this->echoCLI) {
1026
                echo ColorCLI::primary('Would be deleting: '.$this->method.': '.$release['searchname']);
1027
            }
1028
            $deletedCount++;
1029
        }
1030
1031
        $this->deletedCount += $deletedCount;
1032
1033
        return true;
1034
    }
1035
1036
    /**
1037
     * Verify if the query has any results.
1038
     *
1039
     * @return bool False on failure, true on success after setting a count of found releases.
1040
     */
1041
    protected function checkSelectQuery()
1042
    {
1043
        // Run the query, check if it picked up anything.
1044
        $result = $this->pdo->query($this->cleanSpaces($this->query));
1045
        if (\count($result) <= 0) {
1046
            $this->error = '';
1047
            if ($this->method === 'userCriteria') {
1048
                $this->error = 'No releases were found to delete, try changing your criteria.';
1049
            }
1050
1051
            return false;
1052
        }
1053
        $this->result = $result;
1054
1055
        return true;
1056
    }
1057
1058
    /**
1059
     * Go through user arguments and format part of the query.
1060
     *
1061
     * @param string $argument User argument.
1062
     *
1063
     * @return string|false
1064
     */
1065
    protected function formatCriteriaQuery($argument)
1066
    {
1067
        // Check if the user wants to ignore the check.
1068
        if ($argument === 'ignore') {
1069
            $this->ignoreUserCheck = true;
1070
1071
            return '';
1072
        }
1073
1074
        $this->error = 'Invalid argument supplied: '.$argument.self::N;
1075
        $args = explode('=', $argument);
1076
        if (\count($args) === 3) {
1077
            $args[0] = $this->cleanSpaces($args[0]);
1078
            $args[1] = $this->cleanSpaces($args[1]);
1079
            $args[2] = $this->cleanSpaces($args[2]);
1080
            switch ($args[0]) {
1081
                case 'categories_id':
1082
                    if ($args[1] === 'equals') {
1083
                        return ' AND categories_id = '.$args[2];
1084
                    }
1085
                    break;
1086
                case 'imdbid':
1087
                    if ($args[1] === 'equals') {
1088
                        if ($args[2] === 'NULL') {
1089
                            return ' AND imdbid IS NULL ';
1090
                        } else {
1091
                            return ' AND imdbid = '.$args[2];
1092
                        }
1093
                    }
1094
                    break;
1095
                case 'nzbstatus':
1096
                    if ($args[1] === 'equals') {
1097
                        return ' AND nzbstatus = '.$args[2];
1098
                    }
1099
                    break;
1100
                case 'videos_id':
1101
                    if ($args[1] === 'equals') {
1102
                        return ' AND videos_id = '.$args[2];
1103
                    }
1104
                    break;
1105
                case 'totalpart':
1106
                    switch ($args[1]) {
1107
                        case 'equals':
1108
                            return ' AND totalpart = '.$args[2];
1109
                        case 'bigger':
1110
                            return ' AND totalpart > '.$args[2];
1111
                        case 'smaller':
1112
                            return ' AND totalpart < '.$args[2];
1113
                        default:
1114
                            break;
1115
                    }
1116
                    break;
1117
                case 'fromname':
1118
                    switch ($args[1]) {
1119
                        case 'equals':
1120
                            return ' AND fromname = '.$this->pdo->escapeString($args[2]);
1121
                        case 'like':
1122
                            return ' AND fromname '.$this->formatLike($args[2], 'fromname');
1123
                    }
1124
                    break;
1125
                case 'groupname':
1126
                    switch ($args[1]) {
1127
                        case 'equals':
1128
                            $group = $this->pdo->queryOneRow('SELECT id FROM groups WHERE name = '.$this->pdo->escapeString($args[2]));
1129
                            if ($group === false) {
1130
                                $this->error = 'This group was not found in your database: '.$args[2].PHP_EOL;
1131
                                break;
1132
                            }
1133
1134
                            return ' AND groups_id = '.$group['id'];
1135
                        case 'like':
1136
                            $groups = $this->pdo->query('SELECT id FROM groups WHERE name '.$this->formatLike($args[2], 'name'));
1137
                            if (\count($groups) === 0) {
1138
                                $this->error = 'No groups were found with this pattern in your database: '.$args[2].PHP_EOL;
1139
                                break;
1140
                            }
1141
                            $gQuery = ' AND groups_id IN (';
1142
                            foreach ($groups as $group) {
1143
                                $gQuery .= $group['id'].',';
1144
                            }
1145
                            $gQuery = substr($gQuery, 0, -0).')';
1146
1147
                            return $gQuery;
1148
                        default:
1149
                            break;
1150
                    }
1151
                    break;
1152
                case 'guid':
1153
                    if ($args[1] === 'equals') {
1154
                        return ' AND guid = '.$this->pdo->escapeString($args[2]);
1155
                    }
1156
                    break;
1157
                case 'name':
1158
                    switch ($args[1]) {
1159
                        case 'equals':
1160
                            return ' AND name = '.$this->pdo->escapeString($args[2]);
1161
                        case 'like':
1162
                            return ' AND name '.$this->formatLike($args[2], 'name');
1163
                        default:
1164
                            break;
1165
                    }
1166
                    break;
1167
                case 'searchname':
1168
                    switch ($args[1]) {
1169
                        case 'equals':
1170
                            return ' AND searchname = '.$this->pdo->escapeString($args[2]);
1171
                        case 'like':
1172
                            return ' AND searchname '.$this->formatLike($args[2], 'searchname');
1173
                        default:
1174
                            break;
1175
                    }
1176
                    break;
1177
                case 'size':
1178
                    if (! is_numeric($args[2])) {
1179
                        break;
1180
                    }
1181
                    switch ($args[1]) {
1182
                        case 'equals':
1183
                            return ' AND size = '.$args[2];
1184
                        case 'bigger':
1185
                            return ' AND size > '.$args[2];
1186
                        case 'smaller':
1187
                            return ' AND size < '.$args[2];
1188
                        default:
1189
                            break;
1190
                    }
1191
                    break;
1192
                case 'adddate':
1193
                    if (! is_numeric($args[2])) {
1194
                        break;
1195
                    }
1196
                    switch ($args[1]) {
1197
                        case 'bigger':
1198
                            return ' AND adddate <  NOW() - INTERVAL '.$args[2].' HOUR';
1199
                        case 'smaller':
1200
                            return ' AND adddate >  NOW() - INTERVAL '.$args[2].' HOUR';
1201
                        default:
1202
                            break;
1203
                    }
1204
                    break;
1205
                case 'postdate':
1206
                    if (! is_numeric($args[2])) {
1207
                        break;
1208
                    }
1209
                    switch ($args[1]) {
1210
                        case 'bigger':
1211
                            return ' AND postdate <  NOW() - INTERVAL '.$args[2].' HOUR';
1212
                        case 'smaller':
1213
                            return ' AND postdate >  NOW() - INTERVAL '.$args[2].' HOUR';
1214
                        default:
1215
                            break;
1216
                    }
1217
                    break;
1218
                case 'completion':
1219
                    if (! is_numeric($args[2])) {
1220
                        break;
1221
                    }
1222
                    if ($args[1] === 'smaller') {
1223
                        return ' AND completion > 0 AND completion < '.$args[2];
1224
                    }
1225
            }
1226
        }
1227
1228
        return false;
1229
    }
1230
1231
    /**
1232
     * Check if the user wants to run the current query.
1233
     *
1234
     * @return bool
1235
     */
1236
    protected function checkUserResponse()
1237
    {
1238
        if ($this->ignoreUserCheck || $this->browser) {
1239
            return true;
1240
        }
1241
1242
        // Print the query to the user, ask them if they want to continue using it.
1243
        echo ColorCLI::primary(
1244
            'This is the query we have formatted using your criteria, you can run it in SQL to see if you like the results:'.
1245
            self::N.$this->query.';'.self::N.
1246
            'If you are satisfied, type yes and press enter. Anything else will exit.'
1247
        );
1248
1249
        // Check the users response.
1250
        $userInput = trim(fgets(fopen('php://stdin', 'brt')));
0 ignored issues
show
Bug introduced by
It seems like fopen('php://stdin', 'brt') can also be of type false; however, parameter $handle of fgets() does only seem to accept resource, 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

1250
        $userInput = trim(fgets(/** @scrutinizer ignore-type */ fopen('php://stdin', 'brt')));
Loading history...
1251
        if ($userInput !== 'yes') {
1252
            echo ColorCLI::primary('You typed: "'.$userInput.'", the program will exit.');
1253
1254
            return false;
1255
        }
1256
1257
        return true;
1258
    }
1259
1260
    /**
1261
     * Remove multiple spaces and trim leading spaces.
1262
     *
1263
     * @param string $string
1264
     *
1265
     * @return string
1266
     */
1267
    protected function cleanSpaces($string)
1268
    {
1269
        return trim(preg_replace('/\s{2,}/', ' ', $string));
1270
    }
1271
1272
    /**
1273
     * Format a "like" string. ie: "name LIKE '%test%' AND name LIKE '%123%'.
1274
     *
1275
     * @param string $string The string to format.
1276
     * @param string $type   The column name.
1277
     *
1278
     * @return string
1279
     */
1280
    protected function formatLike($string, $type)
1281
    {
1282
        $newString = explode(' ', $string);
1283
        if (\count($newString) > 1) {
1284
            $string = implode("%' AND {$type} LIKE '%", array_unique($newString));
1285
        }
1286
1287
        return " LIKE '%".$string."%' ";
1288
    }
1289
1290
    /**
1291
     * Echo the error and return false if on CLI.
1292
     * Return the error if on browser.
1293
     *
1294
     * @return bool/string
1295
     */
0 ignored issues
show
Documentation Bug introduced by
The doc comment bool/string at position 0 could not be parsed: Unknown type name 'bool/string' at position 0 in bool/string.
Loading history...
1296
    protected function returnError()
1297
    {
1298
        if ($this->browser) {
1299
            return $this->error.'<br />';
1300
        }
1301
1302
        if ($this->echoCLI && $this->error !== '') {
1303
            echo ColorCLI::error($this->error);
1304
        }
1305
1306
        return false;
1307
    }
1308
1309
    protected function extractSrchFromRegx($dbRegex = '')
1310
    {
1311
        $regexMatch = '';
1312
1313
        // Match Regex beginning for long running foreign search
1314
        if (substr($dbRegex, 2, 17) === 'brazilian|chinese') {
1315
            // Find first brazilian instance position in Regex, then find first closing parenthesis.
1316
            // Then substitute all pipes (|) with spaces for FT search and insert into query
1317
            $forBegin = strpos($dbRegex, 'brazilian');
1318
            $regexMatch =
1319
                substr(
1320
                    $dbRegex,
1321
                    $forBegin,
1322
                    strpos($dbRegex, ')') - $forBegin
1323
                );
1324
        } elseif (substr($dbRegex, 7, 11) === 'bl|cz|de|es') {
1325
            // Find first bl|cz instance position in Regex, then find first closing parenthesis.
1326
            $forBegin = strpos($dbRegex, 'bl|cz');
1327
            $regexMatch = '"'.
1328
                str_replace(
1329
                    '|',
1330
                    '" "',
1331
                    substr($dbRegex, $forBegin, strpos($dbRegex, ')') - $forBegin)
1332
                ).'"';
1333
        } elseif (substr($dbRegex, 8, 5) === '19|20') {
1334
            // Find first bl|cz instance position in Regex, then find last closing parenthesis as this is reversed.
1335
            $forBegin = strpos($dbRegex, 'bl|cz');
1336
            $regexMatch = '"'.
1337
                str_replace(
1338
                    '|',
1339
                    '" "',
1340
                    substr($dbRegex, $forBegin, strrpos($dbRegex, ')') - $forBegin)
1341
                ).'"';
1342
        } elseif (substr($dbRegex, 7, 14) === 'chinese.subbed') {
1343
            // Find first brazilian instance position in Regex, then find first closing parenthesis.
1344
            $forBegin = strpos($dbRegex, 'chinese');
1345
            $regexMatch =
1346
                str_replace(
1347
                    'nl  subed|bed|s',
1348
                    'nlsubs|nlsubbed|nlsubed',
1349
                    str_replace(
1350
                        '?',
1351
                        '',
1352
                        str_replace(
1353
                            '.',
1354
                            ' ',
1355
                            str_replace(
1356
                                ['-', '(', ')'],
1357
                                '',
1358
                                substr(
1359
                                    $dbRegex,
1360
                                    $forBegin,
1361
                                    strrpos($dbRegex, ')') - $forBegin
1362
                                )
1363
                            )
1364
                        )
1365
                    )
1366
                );
1367
        } elseif (substr($dbRegex, 8, 2) === '4u') {
1368
            // Find first 4u\.nl instance position in Regex, then find first closing parenthesis.
1369
            $forBegin = strpos($dbRegex, '4u');
1370
            $regexMatch =
1371
                str_replace(
1372
                    'nov[ a]+rip',
1373
                    'nova',
1374
                    str_replace(
1375
                        '4u.nl',
1376
                        '"4u" "nl"',
1377
                        substr($dbRegex, $forBegin, strpos($dbRegex, ')') - $forBegin)
1378
                    )
1379
                );
1380
        } elseif (substr($dbRegex, 8, 5) === 'bd|dl') {
1381
            // Find first bd|dl instance position in Regex, then find last closing parenthesis as this is reversed.
1382
            $forBegin = strpos($dbRegex, 'bd|dl');
1383
            $regexMatch =
1384
                str_replace(
1385
                    ['\\', ']', '['],
1386
                    '',
1387
                    str_replace(
1388
                        'bd|dl)mux',
1389
                        'bdmux|dlmux',
1390
                        substr(
1391
                            $dbRegex,
1392
                            $forBegin,
1393
                            strrpos($dbRegex, ')') - $forBegin
1394
                        )
1395
                    )
1396
                );
1397
        } elseif (substr($dbRegex, 7, 9) === 'imageset|') {
1398
            // Find first imageset| instance position in Regex, then find last closing parenthesis.
1399
            $forBegin = strpos($dbRegex, 'imageset');
1400
            $regexMatch = substr($dbRegex, $forBegin, strpos($dbRegex, ')') - $forBegin);
1401
        } elseif (substr($dbRegex, 1, 9) === 'hdnectar|') {
1402
            // Find first hdnectar| instance position in Regex.
1403
            $regexMatch = str_replace('\'', '', $dbRegex);
1404
        } elseif (substr($dbRegex, 1, 10) === 'Passworded') {
1405
            // Find first Passworded instance position esin Regex, then find last closing parenthesis.
1406
            $regexMatch = str_replace('\'', '', $dbRegex);
1407
        }
1408
1409
        return $regexMatch;
1410
    }
1411
}
1412