SearchAndReplaceAPI   F
last analyzed

Complexity

Total Complexity 101

Size/Duplication

Total Lines 746
Duplicated Lines 0 %

Importance

Changes 7
Bugs 0 Features 0
Metric Value
wmc 101
eloc 307
c 7
b 0
f 0
dl 0
loc 746
rs 2

35 Methods

Rating   Name   Duplication   Size   Complexity  
A testFileNameRequirements() 0 22 6
A setFileNameMustContain() 0 8 2
A setSearchPath() 0 5 1
A getClassNameOfFile() 0 38 3
A addToIgnoreFolderArray() 0 5 1
A setReplacementKey() 0 6 1
A addToOutput() 0 3 1
A appendToLog() 0 7 2
A getTotalTotalSearches() 0 3 1
A setComment() 0 5 1
A writeToFile() 0 14 3
A setReplacementHeader() 0 5 1
B getFullComment() 0 28 6
A getDebug() 0 3 1
A setExtensions() 0 5 1
A __construct() 0 4 1
A setBasePath() 0 6 1
A hasStringPresentInFile() 0 8 2
A getOutput() 0 6 1
A setDebug() 0 5 1
A setFileReplacementMaxCount() 0 5 1
A setIgnoreFolderArray() 0 5 1
A startSearchAndReplace() 0 15 4
A setFindAllExts() 0 5 1
A setIgnoreFileIfFound() 0 8 2
A testMustContain() 0 12 5
B showFormattedSearchTotals() 0 57 11
A setEndMarker() 0 5 1
A setSearchKey() 0 10 1
A setStartMarker() 0 5 1
A setIsReplacingEnabled() 0 5 1
A resetIgnoreFolderArray() 0 5 1
A getLog() 0 6 1
A isValidRegex() 0 19 3
F searchFileData() 0 142 30

How to fix   Complexity   

Complex Class

Complex classes like SearchAndReplaceAPI often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use SearchAndReplaceAPI, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace Sunnysideup\UpgradeToSilverstripe4\Api;
4
5
use Exception;
6
7
/**
8
 * @BasedOn  :  MA Razzaque Rupom <[email protected]>, <[email protected]>
9
 *             Moderator, phpResource Group(http://groups.yahoo.com/group/phpresource/)
10
 *             URL: http://rupom.wordpress.com
11
 */
12
13
class SearchAndReplaceAPI
14
{
15
    //generic search settings
16
17
    private $debug = false;
18
19
    private $basePath = '';
20
21
    private $isReplacingEnabled = false;
22
23
    //specific search settings
24
25
    private $searchKey = '';
26
27
    private $replacementKey = '';
28
29
    private $comment = '';
30
31
    private $startMarker = '### @@@@ START REPLACEMENT @@@@ ###';
32
33
    private $endMarker = '### @@@@ STOP REPLACEMENT @@@@ ###';
34
35
    private $replacementHeader = '';
36
37
    private $replacementType = '';
38
39
    private $caseSensitive = true;
40
41
    private $isRegex = false;
42
43
    private $ignoreFrom = [
44
        '//',
45
        '#',
46
        '/**',
47
    ];
48
49
    private $fileReplacementMaxCount = 0;
50
51
    private $ignoreUntil = [
52
        '//',
53
        '#',
54
        '*/',
55
    ];
56
57
    private $ignoreFileIfFound = [];
58
59
    private $fileNameMustContain = [];
60
61
    // special stuff
62
63
    private $magicReplacers = [
64
        '[SEARCH_REPLACE_CLASS_NAME_GOES_HERE]' => 'classNameOfFile',
65
    ];
66
67
    // files
68
69
    private $fileFinder = null;
70
71
    //stats and reporting
72
73
    private $logString = ''; //details of one search
74
75
    private $errorText = ''; //details of one search
76
77
    private $totalFound = 0; //total matches in one search
78
79
    private $output = ''; //buffer of output, until it is retrieved
80
81
    // static counts
82
83
    private $searchKeyTotals = [];
84
85
    private $folderTotals = [];
86
87
    private $totalTotal = 0;
88
89
    /**
90
     * magic replacement functions
91
     */
92
    private static $class_name_cache = [];
93
94
    private static $finder = null;
95
96
    public function __construct($basePath = '')
97
    {
98
        $this->basePath = $basePath;
99
        $this->fileFinder = new FindFiles();
100
    }
101
102
    //================================================
103
    // Setters Before Run
104
    //================================================
105
106
    /**
107
     *   @return $this
108
     */
109
    public function setDebug($b)
110
    {
111
        $this->debug = $b;
112
113
        return $this;
114
    }
115
116
    /**
117
     *   @return $this
118
     */
119
    public function setIsReplacingEnabled($b)
120
    {
121
        $this->isReplacingEnabled = $b;
122
123
        return $this;
124
    }
125
126
    /**
127
     *   Sets folders to ignore
128
     *   @param array $ignoreFolderArray
129
     *   @return self
130
     */
131
    public function setIgnoreFolderArray($ignoreFolderArray = [])
132
    {
133
        $this->fileFinder->setIgnoreFolderArray($ignoreFolderArray);
134
135
        return $this;
136
    }
137
138
    /**
139
     *   Sets folders to ignore
140
     *   @param array $ignoreFolderArray
141
     *   @return self
142
     */
143
    public function addToIgnoreFolderArray($ignoreFolderArray = [])
144
    {
145
        $this->fileFinder->addToIgnoreFolderArray($ignoreFolderArray);
146
147
        return $this;
148
    }
149
150
    /**
151
     * remove ignore folders
152
     */
153
    public function resetIgnoreFolderArray()
154
    {
155
        $this->fileFinder->resetIgnoreFolderArray();
156
157
        return $this;
158
    }
159
160
    public function setBasePath($pathLocation)
161
    {
162
        $this->basePath = $pathLocation;
163
        $this->fileFinder->setSearchPath($pathLocation);
164
165
        return $this;
166
    }
167
168
    public function setSearchPath($pathLocation)
169
    {
170
        $this->fileFinder->setSearchPath($pathLocation);
171
172
        return $this;
173
    }
174
175
    /**
176
     *   Sets extensions to look
177
     *   @param array $extensions
178
     */
179
    public function setExtensions($extensions = [])
180
    {
181
        $this->fileFinder->setExtensions($extensions);
182
183
        return $this;
184
    }
185
186
    /**
187
     *   Sets extensions to look
188
     *   @param bool $boolean
189
     */
190
    public function setFindAllExts($boolean = true)
191
    {
192
        $this->fileFinder->setFindAllExts($boolean);
193
194
        return $this;
195
    }
196
197
    public function setStartMarker($s)
198
    {
199
        $this->startMarker = $s;
200
201
        return $this;
202
    }
203
204
    public function setEndMarker($s)
205
    {
206
        $this->endMarker = $s;
207
208
        return $this;
209
    }
210
211
    public function setReplacementHeader($s)
212
    {
213
        $this->replacementHeader = $s;
214
215
        return $this;
216
    }
217
218
    public function setIgnoreFileIfFound($a)
219
    {
220
        if (is_string($a)) {
221
            $a = [$a];
222
        }
223
        $this->ignoreFileIfFound = $a;
224
225
        return $this;
226
    }
227
228
    public function setFileNameMustContain($a)
229
    {
230
        if (is_string($a)) {
231
            $a = [$a];
232
        }
233
        $this->fileNameMustContain = $a;
234
235
        return $this;
236
    }
237
238
    public function setFileReplacementMaxCount($i)
239
    {
240
        $this->fileReplacementMaxCount = $i;
241
242
        return $this;
243
    }
244
245
    //================================================
246
    // Setters Before Every Search
247
    //================================================
248
249
    /**
250
     * Sets search key and case sensitivity
251
     * @param bool $caseSensitive
252
     */
253
    public function setSearchKey($searchKey, $caseSensitive = false, $replacementType = 'noType')
254
    {
255
        $this->searchKey = $searchKey;
256
        $this->caseSensitive = $caseSensitive;
257
        $this->isRegex = $this->isValidRegex($this->searchKey);
258
        $this->replacementType = $replacementType;
259
        //reset comment
260
        $this->comment = '';
261
262
        return $this;
263
    }
264
265
    /**
266
     *   Sets key to replace searchKey with
267
     *   @param string $replacementKey
268
     */
269
    public function setReplacementKey($replacementKey)
270
    {
271
        $this->replacementKey = $replacementKey;
272
        $this->setIsReplacingEnabled(true);
273
274
        return $this;
275
    }
276
277
    /**
278
     *   Sets a comment to go with the replacement.
279
     *   @param string $comment
280
     */
281
    public function setComment($comment)
282
    {
283
        $this->comment = $comment;
284
285
        return $this;
286
    }
287
288
    /**
289
     * makes a comment into a PHP proper comment (like this one)
290
     * @return string
291
     */
292
    public function getFullComment()
293
    {
294
        $string = '';
295
        if ($this->comment) {
296
            $string .=
297
                PHP_EOL .
298
                '/**' . PHP_EOL .
299
                '  * ' . $this->startMarker . PHP_EOL;
300
            if ($this->replacementHeader) {
301
                $string .= '  * WHY: ' . $this->replacementHeader . PHP_EOL;
302
            }
303
            $caseSensitiveStatement = ($this->caseSensitive ? '' : ' (ignore case)');
304
            $replacementTypeStatement = ($this->replacementType ? ' (' . $this->replacementType . ')' : '');
305
            $new = '';
306
            if ($this->searchKey !== $this->replacementKey) {
307
            } else {
308
                $new = '  * NEW: ' . $this->replacementKey . ' ... ' . $replacementTypeStatement . PHP_EOL;
309
            }
310
            $string .=
311
                '  * OLD: ' . $this->searchKey . $caseSensitiveStatement . PHP_EOL .
312
                $new .
313
                '  * EXP: ' . $this->comment . PHP_EOL .
314
                '  * ' . $this->endMarker . PHP_EOL .
315
                '  */' .
316
                PHP_EOL;
317
        }
318
319
        return $string;
320
    }
321
322
    //================================================
323
    // Get FINAL output
324
    //================================================
325
326
    /**
327
     * @return bool
328
     */
329
    public function getDebug()
330
    {
331
        return $this->debug;
332
    }
333
334
    /**
335
     * returns full output
336
     * and clears it.
337
     * @return string
338
     */
339
    public function getOutput()
340
    {
341
        $output = $this->output;
342
        $this->output = '';
343
344
        return $output;
345
    }
346
347
    /**
348
     * returns full log
349
     * and clears it.
350
     * @return string
351
     */
352
    public function getLog()
353
    {
354
        $logString = $this->logString;
355
        $this->logString = '';
356
357
        return $logString;
358
    }
359
360
    /**
361
     * returns the TOTAL TOTAL number of
362
     * found replacements
363
     */
364
    public function getTotalTotalSearches()
365
    {
366
        return $this->totalTotal;
367
    }
368
369
    /**
370
     * should be run at the end of an extension.
371
     */
372
    public function showFormattedSearchTotals($suppressOutput = false)
373
    {
374
        $totalSearches = 0;
375
        foreach ($this->searchKeyTotals as $searchKey => $total) {
376
            $totalSearches += $total;
377
        }
378
        if ($suppressOutput) {
379
            //do nothing
380
        } else {
381
            $flatArray = $this->fileFinder->getFlatFileArray();
382
            if ($flatArray && ! is_array($flatArray)) {
383
                $this->addToOutput("\n" . $flatArray . "\n");
384
            } else {
385
                $this->addToOutput("\n--------------\nFiles Searched\n--------------\n");
386
                foreach ($flatArray as $file) {
387
                    $strippedFile = str_replace($this->basePath, '', $file);
388
                    $this->addToOutput($strippedFile . "\n");
389
                }
390
            }
391
            $folderSimpleTotals = [];
392
            $realBase = (string) realpath($this->basePath);
393
            $this->addToOutput("\n--------------\nSummary: by search key\n--------------\n");
394
            arsort($this->searchKeyTotals);
395
            foreach ($this->searchKeyTotals as $searchKey => $total) {
396
                $this->addToOutput(sprintf("%d:\t %s\n", $total, $searchKey));
397
            }
398
            $this->addToOutput("\n--------------\nSummary: by directory\n--------------\n");
399
            arsort($this->folderTotals);
400
            foreach ($this->folderTotals as $folder => $total) {
401
                $path = str_replace($realBase, '', (string) realpath($folder));
402
                $pathArr = explode('/', $path);
403
                if (isset($pathArr[1])) {
404
                    $folderName = $pathArr[1] . '/';
405
                    if (! isset($folderSimpleTotals[$folderName])) {
406
                        $folderSimpleTotals[$folderName] = 0;
407
                    }
408
                    $folderSimpleTotals[$folderName] += $total;
409
                    $strippedFolder = str_replace($this->basePath, '', $folder);
410
                    $this->addToOutput(sprintf("%d:\t %s\n", $total, $strippedFolder));
411
                }
412
            }
413
            $strippedRealBase = '/';
414
            $this->addToOutput(
415
                sprintf("\n--------------\nSummary: by root directory (%s)\n--------------\n", $strippedRealBase)
416
            );
417
            arsort($folderSimpleTotals);
418
            foreach ($folderSimpleTotals as $folder => $total) {
419
                $strippedFolder = str_replace($this->basePath, '', $folder);
420
                $this->addToOutput(sprintf("%d:\t %s\n", $total, $strippedFolder));
421
            }
422
            $this->addToOutput(sprintf("\n--------------\nTotal replacements: %d\n--------------\n", $totalSearches));
423
        }
424
        //add to total total
425
        $this->totalTotal += $totalSearches;
426
427
        //return total
428
        return $totalSearches;
429
    }
430
431
    //================================================
432
    // Doers
433
    //================================================
434
435
    /**
436
     * Searches all the files and creates the logs
437
     *
438
     * @return self
439
     */
440
    public function startSearchAndReplace()
441
    {
442
        $flatArray = $this->fileFinder->getFlatFileArray();
443
        foreach ($flatArray as $file) {
444
            $this->searchFileData($file);
445
        }
446
        if ($this->totalFound) {
447
            $msg = $this->totalFound . ' matches (' . $this->replacementType . ') for: ' . $this->logString;
448
            $this->addToOutput($msg);
449
        }
450
        if ($this->errorText !== '') {
451
            $this->addToOutput("\t Error-----" . $this->errorText);
452
        }
453
454
        return $this;
455
    }
456
457
    /**
458
     * THE KEY METHOD!
459
     * Searches data, replaces (if enabled) with given key, prepares log
460
     * @param string $file - e.g. /var/www/mysite.co.nz/mysite/code/Page.php
461
     */
462
    private function searchFileData($file)
463
    {
464
        $foundInLineCount = 0;
465
        $myReplacementKey = $this->replacementKey;
466
        if ($this->isReplacingEnabled) {
467
            //prerequisites for file and content ...
468
            if ($this->testMustContain($file) === false) {
469
                return;
470
            }
471
            if ($this->testFileNameRequirements($file) === false) {
472
                return;
473
            }
474
475
            $magicalData = [];
476
            $magicalData['classNameOfFile'] = $this->getClassNameOfFile($file);
477
            foreach ($this->magicReplacers as $magicReplacerFind => $magicReplacerReplaceVariable) {
478
                $myReplacementKey = str_replace(
479
                    $magicReplacerFind,
480
                    $magicalData[$magicReplacerReplaceVariable],
481
                    $myReplacementKey
482
                );
483
            }
484
            $oldFileContentArray = (array) file($file) ?? [];
485
            $newFileContentArray = [];
486
487
            if ($this->isRegex === true) {
488
                $pattern = $this->searchKey;
489
            } else {
490
                $searchKey = preg_quote($this->searchKey, '/');
491
                $pattern = "/" . $searchKey . "/U";
492
                if (! $this->caseSensitive) {
493
                    $pattern .= 'i';
494
                }
495
            }
496
            $foundCount = 0;
497
            $insidePreviousReplaceComment = false;
498
            $insideIgnoreArea = false;
499
            $completedTask = false;
500
            foreach ($oldFileContentArray as $oldLineContent) {
501
                $newLineContent = (string) $oldLineContent . '';
502
503
                if ($completedTask === false) {
504
                    $testLine = (string) trim((string) $oldLineContent);
505
506
                    //check if it is actually already replaced ...
507
                    if (strpos((string) $oldLineContent, $this->startMarker) !== false) {
508
                        $insidePreviousReplaceComment = true;
509
                    }
510
                    foreach ($this->ignoreFrom as $ignoreStarter) {
511
                        if (strpos((string) $testLine, $ignoreStarter) === 0) {
512
                            $insideIgnoreArea = true;
513
                        }
514
                    }
515
                    if ($insidePreviousReplaceComment || $insideIgnoreArea) {
516
                        //do nothing ...
517
                    } else {
518
                        // echo PHP_EOL . $pattern . PHP_EOL;
519
                        // $pattern = '/\$First\b/U';
520
                        $foundInLineCount = preg_match_all(
521
                            $pattern,
522
                            (string) $oldLineContent,
523
                            $matches,
524
                            PREG_PATTERN_ORDER
525
                        );
526
                        if ($foundInLineCount) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $foundInLineCount of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
527
                            // if ($this->caseSensitive) {
528
                            //     if (strpos((string) $oldLineContent, (string) $searchKey) === false) {
529
                            //         user_error('Regex found it, but phrase does not exist: ' . $searchKey);
530
                            //     }
531
                            // } else {
532
                            //     if (stripos((string) $oldLineContent, $searchKey) === false) {
533
                            //         user_error('Regex found it, but phrase does not exist: ' . $searchKey);
534
                            //     }
535
                            // }
536
                            $foundCount += $foundInLineCount;
537
                            if ($this->isReplacingEnabled) {
538
                                $newLineContent = preg_replace($pattern, $myReplacementKey, (string) $oldLineContent);
539
                                if ($fullComment = $this->getFullComment()) {
540
                                    $newFileContentArray[] = $fullComment;
541
                                }
542
                            }
543
                        } else {
544
                            if ($this->caseSensitive) {
545
                                if (strpos((string) $oldLineContent, (string) $this->searchKey) !== false) {
546
                                    user_error('Should have found: ' . $this->searchKey);
547
                                }
548
                            } else {
549
                                if (stripos((string) $oldLineContent, (string) $this->searchKey) !== false) {
550
                                    user_error('Should have found: ' . $this->searchKey);
551
                                }
552
                            }
553
                        }
554
                    }
555
                    if (strpos((string) $oldLineContent, (string) $this->endMarker) !== false) {
556
                        $insidePreviousReplaceComment = false;
557
                    }
558
                    foreach ($this->ignoreUntil as $ignoreEnder) {
559
                        if (strpos((string) $testLine, (string) $ignoreEnder) === 0) {
560
                            $insideIgnoreArea = false;
561
                        }
562
                    }
563
                    if ($this->fileReplacementMaxCount > 0 && $foundCount >= $this->fileReplacementMaxCount) {
564
                        $completedTask = true;
565
                    }
566
                }
567
568
                $newFileContentArray[] = $newLineContent;
569
            }
570
            if ($foundCount) {
571
                $oldFileContent = implode($oldFileContentArray);
572
                $newFileContent = implode($newFileContentArray);
573
                if ($newFileContent !== $oldFileContent) {
574
                    $this->writeToFile($file, $newFileContent);
575
576
                    //stats
577
                    $this->totalFound += $foundInLineCount;
578
                    if (! isset($this->searchKeyTotals[$this->searchKey])) {
579
                        $this->searchKeyTotals[$this->searchKey] = 0;
580
                    }
581
                    $this->searchKeyTotals[$this->searchKey] += $foundCount;
582
583
                    if (! isset($this->folderTotals[dirname($file)])) {
584
                        $this->folderTotals[dirname($file)] = 0;
585
                    }
586
                    $this->folderTotals[dirname($file)] += $foundCount;
587
588
                    //log
589
                    $foundStr = "-- ${foundCount} x";
590
                    if ($this->fileReplacementMaxCount) {
591
                        $foundStr .= ' limited to ' . $this->fileReplacementMaxCount;
592
                    }
593
                    $this->appendToLog($file, $foundStr);
594
                } else {
595
                    $this->appendToLog(
596
                        $file,
597
                        '********** ERROR: NO REPLACEMENT DESPITE MATCHES - searched for: ' .
598
                            $pattern . ' and replaced with ' . $myReplacementKey . " \n"
599
                    );
600
                }
601
            }
602
        } else {
603
            $this->appendToLog($file, '********** ERROR: Replacement Text is not defined');
604
        }
605
    }
606
607
    /**
608
     * Writes new data (after the replacement) to file
609
     * @param string $data
610
     */
611
    private function writeToFile($file, $data)
612
    {
613
        if (is_writable($file)) {
614
            $fp = fopen($file, 'w');
615
            if ($fp) {
0 ignored issues
show
introduced by
$fp is of type resource, thus it always evaluated to false.
Loading history...
616
                fwrite($fp, $data);
617
                fclose($fp);
618
            } else {
619
                user_error('Could not open ' . $file);
620
            }
621
        } else {
622
            user_error(
623
                "********** ERROR: Can not replace text. File ${file} is not writable.",
624
                "\nPlease make it writable\n"
0 ignored issues
show
Bug introduced by
' Please make it writable ' of type string is incompatible with the type integer expected by parameter $error_level of user_error(). ( Ignorable by Annotation )

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

624
                /** @scrutinizer ignore-type */ "\nPlease make it writable\n"
Loading history...
625
            );
626
        }
627
    }
628
629
    /**
630
     * Appends log data to previous log data
631
     * @param string $file
632
     * @param string $matchStr
633
     */
634
    private function appendToLog($file, $matchStr)
635
    {
636
        if ($this->logString === '') {
637
            $this->logString = "'" . $this->searchKey . "'\n";
638
        }
639
        $file = basename($file);
640
        $this->logString .= "   ${matchStr} IN ${file}\n";
641
    }
642
643
    /**
644
     * returns full output
645
     * and clears it.
646
     */
647
    private function addToOutput($s): void
648
    {
649
        $this->output .= $s;
650
    }
651
652
    private function getClassNameOfFile($filePath)
653
    {
654
        if (! self::$finder) {
655
            self::$finder = new FileNameToClass();
656
        }
657
        if (! isset(self::$class_name_cache[$filePath])) {
658
            $class = self::$finder->getClassNameFromFile($filePath);
659
            //see: https://stackoverflow.com/questions/7153000/get-class-name-from-file/44654073
660
            // $file = 'class.php'; # contains class Foo
661
            // $class = shell_exec("php -r \"include('$file'); echo end(get_declared_classes());\"");
662
            //see: https://stackoverflow.com/questions/7153000/get-class-name-from-file/44654073
663
            // $fp = fopen($filePath, 'r');
664
            // $class = $buffer = '';
665
            // $i = 0;
666
            // while (!$class) {
667
            //     if (feof($fp)) {
668
            //         break;
669
            //     }
670
            //
671
            //     $buffer .= fread($fp, 512);
672
            //     @$tokens = token_get_all($buffer);
673
            //
674
            //     if (strpos($buffer, '{') === false) continue;
675
            //
676
            //     for (;$i<count($tokens);$i++) {
677
            //         if ($tokens[$i][0] === T_CLASS) {
678
            //             for ($j=$i+1;$j<count($tokens);$j++) {
679
            //                 if ($tokens[$j] === '{') {
680
            //                     $class = $tokens[$i+2][1];
681
            //                     break 2;
682
            //                 }
683
            //             }
684
            //         }
685
            //     }
686
            // }
687
            self::$class_name_cache[$filePath] = $class;
688
        }
689
        return self::$class_name_cache[$filePath];
690
    }
691
692
    private function testMustContain($fileName)
693
    {
694
        if (is_array($this->ignoreFileIfFound) && count($this->ignoreFileIfFound)) {
695
            foreach ($this->ignoreFileIfFound as $ignoreString) {
696
                if ($this->hasStringPresentInFile($fileName, $ignoreString)) {
697
                    $this->appendToLog($fileName, '********** Ignoring file, as ignore string found: ' . $ignoreString);
698
699
                    return false;
700
                }
701
            }
702
        }
703
        return true;
704
    }
705
706
    private function testFileNameRequirements($fileName)
707
    {
708
        if (is_array($this->fileNameMustContain) && count($this->fileNameMustContain)) {
709
            $passed = false;
710
            $fileBaseName = basename($fileName);
711
            foreach ($this->fileNameMustContain as $fileNameMustContainString) {
712
                if (stripos($fileBaseName, $fileNameMustContainString) !== false) {
713
                    $passed = true;
714
                }
715
            }
716
            if ($passed === false) {
717
                $this->appendToLog(
718
                    $fileName,
719
                    "********** skipping file (${fileBaseName}), as it does not contain: " .
720
                        implode(', ', $this->fileNameMustContain)
721
                );
722
723
                return false;
724
            }
725
        }
726
727
        return true;
728
    }
729
730
    private function hasStringPresentInFile(string $fileName, string $string): bool
731
    {
732
        // get the file contents, assuming the file to be readable (and exist)
733
        $contents = file_get_contents($fileName);
734
        if (strpos((string) $contents, (string) $string) !== false) {
735
            return true;
736
        }
737
        return false;
738
    }
739
740
    protected function isValidRegex(string $pattern): bool
741
    {
742
        // Check if the pattern looks like a regex
743
        if (! preg_match('/^\/.*\/[a-zA-Z]*$/', $pattern)) {
744
            return false;
745
        }
746
747
        // Attempt to validate the regex pattern
748
        set_error_handler(function () {
749
            throw new Exception('Invalid regex.');
750
        });
751
752
        try {
753
            preg_match($pattern, '');
754
            restore_error_handler();
755
            return true; // The pattern is valid
756
        } catch (Exception $e) {
757
            restore_error_handler();
758
            return false; // The pattern is invalid
759
        }
760
    }
761
}
762