Passed
Push — master ( 4165df...b83769 )
by Théo
01:49
created

Configuration::getShebang()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 1
nc 1
nop 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of the box project.
7
 *
8
 * (c) Kevin Herrera <[email protected]>
9
 *     Théo Fidry <[email protected]>
10
 *
11
 * This source file is subject to the MIT license that is bundled
12
 * with this source code in the file LICENSE.
13
 */
14
15
namespace KevinGH\Box;
16
17
use ArrayIterator;
18
use Closure;
19
use Datetime;
20
use DateTimeImmutable;
21
use Herrera\Annotations\Tokenizer;
22
use Herrera\Box\Compactor\CompactorInterface;
23
use Herrera\Box\Compactor\Php;
24
use InvalidArgumentException;
25
use Phar;
26
use Phine\Path\Path;
27
use RuntimeException;
28
use SplFileInfo;
29
use stdClass;
30
use Symfony\Component\Finder\Finder;
31
use Symfony\Component\Process\Process;
32
33
final class Configuration
34
{
35
    private const DEFAULT_ALIAS = 'default.phar';
36
    private const DEFAULT_DATETIME_FORMAT = 'Y-m-d H:i:s';
37
    private const DEFAULT_REPLACEMENT_SIGIL = '@';
38
39
    private $fileMode;
40
    private $alias;
41
    private $basePath;
42
    private $basePathRegex;
43
    private $binaryDirectoriesIterator;
44
    private $binaryFilesIterator;
45
    private $binaryIterators;
46
    private $directoriesIterator;
47
    private $filesIterator;
48
    private $filesIterators;
49
    private $bootstrapFile;
50
    private $compactors;
51
    private $compressionAlgorithm;
52
    private $mainScriptPath;
53
    private $mainScriptContent;
54
    private $map;
55
    private $mapper;
56
    private $metadata;
57
    private $mimetypeMapping;
58
    private $mungVariables;
59
    private $notFoundScriptPath;
60
    private $outputPath;
61
    private $privateKeyPassphrase;
62
    private $privateKeyPath;
63
    private $isPrivateKeyPrompt;
64
    private $processedReplacements;
65
    private $shebang;
66
    private $signingAlgorithm;
67
    private $stubBanner;
68
    private $stubBannerPath;
69
    private $stubBannerFromFile;
70
    private $stubPath;
71
    private $isExtractable;
72
    private $isInterceptFileFuncs;
73
    private $isStubGenerated;
74
    private $isWebPhar;
75
76
    /**
77
     * @param string                     $alias                     TODO: description
78
     * @param string                     $basePath                  Path used as the base for all the relative paths used
79
     * @param string                     $basePathRegex             Base path escaped to be able to easily extract the relative path from a file path
80
     * @param iterable|SplFileInfo[]     $binaryDirectoriesIterator List of directories containing images or other binary data
81
     * @param iterable|SplFileInfo[]     $binaryFilesIterator       List of files containing images or other binary data
82
     * @param iterable[]|SplFileInfo[][] $binaryIterators           List of file iterators returning binary files
83
     * @param iterable|SplFileInfo[]     $directoriesIterator       List of directories
84
     * @param iterable|SplFileInfo[]     $filesIterator             List of files
85
     * @param iterable[]|SplFileInfo[][] $filesIterators            List of file iterators
86
     * @param null|string                $bootstrapFile             The bootstrap file path
87
     * @param CompactorInterface[]       $compactors                List of file contents compactors
88
     * @param null|int                   $compressionAlgorithm      Compression algorithm constant value. See the \Phar class constants
89
     * @param null|int                   $fileMode                  File mode in octal form
90
     * @param null|string                $mainScriptPath            The main script file path
91
     * @param null|string                $mainScriptContent         The processed content of the main script file
92
     * @param string[]                   $map                       The internal file path mapping
93
     * @param Closure                    $mapper                    Callable for the configured map
94
     * @param mixed                      $metadata                  The PHAR Metadata
95
     * @param array                      $mimetypeMapping           The file extension MIME type mapping
96
     * @param array                      $mungVariables             The list of server variables to modify for execution
97
     * @param null|string                $notFoundScriptPath        The file path to the script to execute when a file is not found
98
     * @param string                     $outputPath
99
     * @param null|string                $privateKeyPassphrase
100
     * @param null|string                $privateKeyPath
101
     * @param bool                       $isPrivateKeyPrompt        If the user should be prompted for the private key passphrase
102
     * @param array                      $processedReplacements     The processed list of replacement placeholders and their values
103
     * @param null|string                $shebang                   The shebang line
104
     * @param int                        $signingAlgorithm          The PHAR siging algorithm. See \Phar constants
105
     * @param null|string                $stubBanner                The stub banner comment
106
     * @param null|string                $stubBannerPath            The path to the stub banner comment file
107
     * @param null|string                $stubBannerFromFile        The stub banner comment from the fine
108
     * @param null|string                $stubPath                  The PHAR stub file path
109
     * @param bool                       $isExtractable             Wether or not StubGenerator::extract() should be used
110
     * @param bool                       $isInterceptFileFuncs      wether or not Phar::interceptFileFuncs() should be used
111
     * @param bool                       $isStubGenerated           Wether or not if the PHAR stub should be generated
112
     * @param bool                       $isWebPhar                 Wether or not the PHAR is going to be used for the web
113
     */
114
    private function __construct(
115
        string $alias,
116
        string $basePath,
117
        string $basePathRegex,
118
        ?iterable $binaryDirectoriesIterator,
119
        ?iterable $binaryFilesIterator,
120
        array $binaryIterators,
121
        ?iterable $directoriesIterator,
122
        ?iterable $filesIterator,
123
        array $filesIterators,
124
        ?string $bootstrapFile,
125
        array $compactors,
126
        ?int $compressionAlgorithm,
127
        ?int $fileMode,
128
        ?string $mainScriptPath,
129
        ?string $mainScriptContent,
130
        array $map,
131
        Closure $mapper,
132
        $metadata,
133
        array $mimetypeMapping,
134
        array $mungVariables,
135
        ?string $notFoundScriptPath,
136
        string $outputPath,
137
        ?string $privateKeyPassphrase,
138
        ?string $privateKeyPath,
139
        bool $isPrivateKeyPrompt,
140
        array $processedReplacements,
141
        ?string $shebang,
142
        int $signingAlgorithm,
143
        ?string $stubBanner,
144
        ?string $stubBannerPath,
145
        ?string $stubBannerFromFile,
146
        ?string $stubPath,
147
        bool $isExtractable,
148
        bool $isInterceptFileFuncs,
149
        bool $isStubGenerated,
150
        bool $isWebPhar
151
    ) {
152
        $this->alias = $alias;
153
        $this->basePath = $basePath;
154
        $this->basePathRegex = $basePathRegex;
155
        $this->binaryDirectoriesIterator = $binaryDirectoriesIterator;
156
        $this->binaryFilesIterator = $binaryFilesIterator;
157
        $this->binaryIterators = $binaryIterators;
158
        $this->directoriesIterator = $directoriesIterator;
159
        $this->filesIterator = $filesIterator;
160
        $this->filesIterators = $filesIterators;
161
        $this->bootstrapFile = $bootstrapFile;
162
        $this->compactors = $compactors;
163
        $this->compressionAlgorithm = $compressionAlgorithm;
164
        $this->fileMode = $fileMode;
165
        $this->mainScriptPath = $mainScriptPath;
166
        $this->mainScriptContent = $mainScriptContent;
167
        $this->map = $map;
168
        $this->mapper = $mapper;
169
        $this->metadata = $metadata;
170
        $this->mimetypeMapping = $mimetypeMapping;
171
        $this->mungVariables = $mungVariables;
172
        $this->notFoundScriptPath = $notFoundScriptPath;
173
        $this->outputPath = $outputPath;
174
        $this->privateKeyPassphrase = $privateKeyPassphrase;
175
        $this->privateKeyPath = $privateKeyPath;
176
        $this->isPrivateKeyPrompt = $isPrivateKeyPrompt;
177
        $this->processedReplacements = $processedReplacements;
178
        $this->shebang = $shebang;
179
        $this->signingAlgorithm = $signingAlgorithm;
180
        $this->stubBanner = $stubBanner;
181
        $this->stubBannerPath = $stubBannerPath;
182
        $this->stubBannerFromFile = $stubBannerFromFile;
183
        $this->stubPath = $stubPath;
184
        $this->isExtractable = $isExtractable;
185
        $this->isInterceptFileFuncs = $isInterceptFileFuncs;
186
        $this->isStubGenerated = $isStubGenerated;
187
        $this->isWebPhar = $isWebPhar;
188
    }
189
190
    public static function create(string $file, stdClass $raw): self
191
    {
192
        $alias = $raw->alias ?? self::DEFAULT_ALIAS;
193
194
        $basePath = self::retrieveBasePath($file, $raw);
195
        $basePathRegex = '/'.preg_quote($basePath.DIRECTORY_SEPARATOR, '/').'/';
196
197
        $blacklist = self::retrieveBlacklist($raw);
198
        $blacklistFilter = self::retrieveBlacklistFilter($basePath, $blacklist);
199
200
        $binaryDirectories = self::retrieveBinaryDirectories($raw, $basePath);
201
        $binaryDirectoriesIterator = self::retrieveBinaryDirectoriesIterator($binaryDirectories, $blacklistFilter);
202
203
        $binaryFiles = self::retrieveBinaryFiles($raw, $basePath);
204
        $binaryFilesIterator = self::retrieveBinaryFilesIterator($binaryFiles);
205
206
        $binaryIterators = self::retrieveBinaryIterators($raw, $basePath, $blacklistFilter);
207
208
        $directories = self::retrieveDirectories($raw, $basePath);
209
        $directoriesIterator = self::retrieveDirectoriesIterator($directories, $blacklistFilter);
210
211
        $files = self::retrieveFiles($raw, $basePath);
212
        $filesIterator = self::retrieveFilesIterator($files);
213
214
        $filesIterators = self::retrieveFilesIterators($raw, $basePath, $blacklistFilter);
215
216
        $bootstrapFile = self::retrieveBootstrapFile($raw, $basePath);
217
218
        $compactors = self::retrieveCompactors($raw);
219
        $compressionAlgorithm = self::retrieveCompressionAlgorithm($raw);
220
221
        $fileMode = self::retrieveFileMode($raw);
222
223
        $mainScriptPath = self::retrieveMainScriptPath($raw);
224
        $mainScriptContent = self::retrieveMainScriptContents($mainScriptPath, $basePath);
225
226
        $map = self::retrieveMap($raw);
227
        $mapper = self::retrieveMapper($map);
228
229
        $metadata = self::retrieveMetadata($raw);
230
231
        $mimeTypeMapping = self::retrieveMimetypeMapping($raw);
232
        $mungVariables = self::retrieveMungVariables($raw);
233
        $notFoundScriptPath = self::retrieveNotFoundScriptPath($raw);
234
        $outputPath = self::retrieveOutputPath($raw, $file);
235
236
        $privateKeyPassphrase = self::retrievePrivateKeyPassphrase($raw);
237
        $privateKeyPath = self::retrievePrivateKeyPath($raw);
238
        $isPrivateKeyPrompt = self::retrieveIsPrivateKeyPrompt($raw);
239
240
        $replacements = self::retrieveReplacements($raw);
241
        $processedReplacements = self::retrieveProcessedReplacements($replacements, $raw, $file);
242
243
        $shebang = self::retrieveShebang($raw);
244
245
        $signingAlgorithm = self::retrieveSigningAlgorithm($raw);
246
247
        $stubBanner = self::retrieveStubBanner($raw);
248
        $stubBannerPath = self::retrieveStubBannerPath($raw);
249
        $stubBannerFromFile = self::retrieveStubBannerFromFile($basePath, $stubBannerPath);
250
251
        $stubPath = self::retrieveStubPath($raw);
252
253
        $isExtractable = self::retrieveIsExtractable($raw);
254
        $isInterceptFileFuncs = self::retrieveIsInterceptFileFuncs($raw);
255
        $isStubGenerated = self::retrieveIsStubGenerated($raw);
256
        $isWebPhar = self::retrieveIsWebPhar($raw);
257
258
        return new self(
259
            $alias,
260
            $basePath,
261
            $basePathRegex,
262
            $binaryDirectoriesIterator,
263
            $binaryFilesIterator,
264
            $binaryIterators,
265
            $directoriesIterator,
266
            $filesIterator,
267
            $filesIterators,
268
            $bootstrapFile,
269
            $compactors,
270
            $compressionAlgorithm,
271
            $fileMode,
272
            $mainScriptPath,
273
            $mainScriptContent,
274
            $map,
275
            $mapper,
276
            $metadata,
277
            $mimeTypeMapping,
278
            $mungVariables,
279
            $notFoundScriptPath,
280
            $outputPath,
281
            $privateKeyPassphrase,
282
            $privateKeyPath,
283
            $isPrivateKeyPrompt,
284
            $processedReplacements,
285
            $shebang,
286
            $signingAlgorithm,
287
            $stubBanner,
288
            $stubBannerPath,
289
            $stubBannerFromFile,
290
            $stubPath,
291
            $isExtractable,
292
            $isInterceptFileFuncs,
293
            $isStubGenerated,
294
            $isWebPhar
295
        );
296
    }
297
298
    public function retrieveRelativeBasePath(string $path): string
299
    {
300
        return preg_replace(
301
            $this->basePathRegex,
302
            '',
303
            $path
304
        );
305
    }
306
307
    public function getAlias(): string
308
    {
309
        return $this->alias;
310
    }
311
312
    public function getBasePath(): string
313
    {
314
        return $this->basePath;
315
    }
316
317
    /**
318
     * @return null|iterable|SplFileInfo[]
319
     */
320
    public function getBinaryDirectoriesIterator(): ?iterable
321
    {
322
        return $this->binaryDirectoriesIterator;
323
    }
324
325
    /**
326
     * @return null|iterable|SplFileInfo[]
327
     */
328
    public function getBinaryFilesIterator(): ?iterable
329
    {
330
        return $this->binaryFilesIterator;
331
    }
332
333
    /**
334
     * @return iterable[]|SplFileInfo[][]
335
     */
336
    public function getBinaryIterators(): array
337
    {
338
        return $this->binaryIterators;
339
    }
340
341
    /**
342
     * @return null|iterable|SplFileInfo[]
343
     */
344
    public function getDirectoriesIterator(): ?iterable
345
    {
346
        return $this->directoriesIterator;
347
    }
348
349
    /**
350
     * @return null|iterable|SplFileInfo[]
351
     */
352
    public function getFilesIterator(): ?iterable
353
    {
354
        return $this->filesIterator;
355
    }
356
357
    public function getBootstrapFile(): ?string
358
    {
359
        return $this->bootstrapFile;
360
    }
361
362
    public function loadBootstrap(): void
363
    {
364
        $file = $this->bootstrapFile;
365
366
        if (null !== $file) {
367
            include $file;
368
        }
369
    }
370
371
    /**
372
     * @return CompactorInterface[] the list of compactors
373
     */
374
    public function getCompactors(): array
375
    {
376
        return $this->compactors;
377
    }
378
379
    public function getCompressionAlgorithm(): ?int
380
    {
381
        return $this->compressionAlgorithm;
382
    }
383
384
    public function getFileMode(): ?int
385
    {
386
        return $this->fileMode;
387
    }
388
389
    public function getMainScriptPath(): ?string
390
    {
391
        return $this->mainScriptPath;
392
    }
393
394
    public function getMainScriptContent(): ?string
395
    {
396
        return $this->mainScriptContent;
397
    }
398
399
    public function getMimetypeMapping(): array
400
    {
401
        return $this->mimetypeMapping;
402
    }
403
404
    public function getMungVariables(): array
405
    {
406
        return $this->mungVariables;
407
    }
408
409
    public function getNotFoundScriptPath(): ?string
410
    {
411
        return $this->notFoundScriptPath;
412
    }
413
414
    public function getOutputPath(): string
415
    {
416
        return $this->outputPath;
417
    }
418
419
    /**
420
     * @return string[]
421
     */
422
    public function getMap(): array
423
    {
424
        return $this->map;
425
    }
426
427
    public function getMapper(): Closure
428
    {
429
        return $this->mapper;
430
    }
431
432
    /**
433
     * @return mixed
434
     */
435
    public function getMetadata()
436
    {
437
        return $this->metadata;
438
    }
439
440
    /**
441
     * @return iterable[]|SplFileInfo[][]
442
     */
443
    public function getFilesIterators()
444
    {
445
        return $this->filesIterators;
446
    }
447
448
    public function getPrivateKeyPassphrase(): ?string
449
    {
450
        return $this->privateKeyPassphrase;
451
    }
452
453
    public function getPrivateKeyPath(): ?string
454
    {
455
        return $this->privateKeyPath;
456
    }
457
458
    public function isPrivateKeyPrompt(): bool
459
    {
460
        return $this->isPrivateKeyPrompt;
461
    }
462
463
    public function getProcessedReplacements(): array
464
    {
465
        return $this->processedReplacements;
466
    }
467
468
    public function getShebang(): ?string
469
    {
470
        return $this->shebang;
471
    }
472
473
    public function getSigningAlgorithm(): int
474
    {
475
        return $this->signingAlgorithm;
476
    }
477
478
    public function getStubBanner(): ?string
479
    {
480
        return $this->stubBanner;
481
    }
482
483
    public function getStubBannerPath(): ?string
484
    {
485
        return $this->stubBannerPath;
486
    }
487
488
    public function getStubBannerFromFile()
489
    {
490
        return $this->stubBannerFromFile;
491
    }
492
493
    public function getStubPath(): ?string
494
    {
495
        return $this->stubPath;
496
    }
497
498
    public function isExtractable(): bool
499
    {
500
        return $this->isExtractable;
501
    }
502
503
    public function isInterceptFileFuncs(): bool
504
    {
505
        return $this->isInterceptFileFuncs;
506
    }
507
508
    public function isStubGenerated(): bool
509
    {
510
        return $this->isStubGenerated;
511
    }
512
513
    public function isWebPhar(): bool
514
    {
515
        return $this->isWebPhar;
516
    }
517
518
    private static function retrieveBasePath(string $file, stdClass $raw): string
519
    {
520
        if (isset($raw->{'base-path'})) {
521
            if (false === is_dir($raw->{'base-path'})) {
522
                throw new InvalidArgumentException(
523
                    sprintf(
524
                        'The base path "%s" is not a directory or does not exist.',
525
                        $raw->{'base-path'}
526
                    )
527
                );
528
            }
529
530
            return realpath($raw->{'base-path'});
531
        }
532
533
        return realpath(dirname($file));
534
    }
535
536
    /**
537
     * @return array|SplFileInfo[]
538
     */
539
    private static function retrieveBinaryDirectories(stdClass $raw, string $basePath): array
540
    {
541
        if (isset($raw->{'directories-bin'})) {
542
            $directories = (array) $raw->{'directories-bin'};
543
544
            array_walk(
545
                $directories,
546
                function (&$directory) use ($basePath): void {
547
                    $directory = $basePath
548
                        .DIRECTORY_SEPARATOR
549
                        .Path::canonical($directory);
550
                }
551
            );
552
553
            return $directories;
554
        }
555
556
        return [];
557
    }
558
559
    /**
560
     * @param SplFileInfo[] $binaryDirectories
561
     * @param Closure       $blacklistFilter
562
     *
563
     * @return null|iterable|SplFileInfo[] the iterator
564
     */
565
    private static function retrieveBinaryDirectoriesIterator(array $binaryDirectories, Closure $blacklistFilter): ?iterable
566
    {
567
        if ([] !== $binaryDirectories) {
568
            return Finder::create()
0 ignored issues
show
Bug Best Practice introduced by
The expression return Symfony\Component...>in($binaryDirectories) returns the type Symfony\Component\Finder\Finder which is incompatible with the type-hinted return null|iterable.
Loading history...
569
                ->files()
570
                ->filter($blacklistFilter)
571
                ->ignoreVCS(true)
572
                ->in($binaryDirectories);
573
        }
574
575
        return null;
576
    }
577
578
    /**
579
     * @return SplFileInfo[] the list of paths
580
     */
581
    private static function retrieveBinaryFiles(stdClass $raw, string $basePath): array
582
    {
583
        if (isset($raw->{'files-bin'})) {
584
            $files = [];
585
586
            foreach ((array) $raw->{'files-bin'} as $file) {
587
                $files[] = new SplFileInfo(
588
                    $basePath.DIRECTORY_SEPARATOR.Path::canonical($file)
589
                );
590
            }
591
592
            return $files;
593
        }
594
595
        return [];
596
    }
597
598
    /**
599
     * @return null|iterable|SplFileInfo[] the iterator
600
     */
601
    private static function retrieveBinaryFilesIterator(array $binaryFiles): ?iterable
602
    {
603
        if ([] !== $binaryFiles) {
604
            return new ArrayIterator($binaryFiles);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new ArrayIterator($binaryFiles) returns the type ArrayIterator which is incompatible with the type-hinted return null|iterable.
Loading history...
605
        }
606
607
        return null;
608
    }
609
610
    /**
611
     * @return string[]
612
     */
613
    private static function retrieveBlacklist(stdClass $raw): array
614
    {
615
        if (isset($raw->blacklist)) {
616
            $blacklist = (array) $raw->blacklist;
617
618
            array_walk(
619
                $blacklist,
620
                function (&$file): void {
621
                    $file = Path::canonical($file);
622
                }
623
            );
624
625
            return $blacklist;
626
        }
627
628
        return [];
629
    }
630
631
    /**
632
     * @param string   $basePath
633
     * @param string[] $blacklist
634
     *
635
     * @return Closure
636
     */
637
    private static function retrieveBlacklistFilter(string $basePath, array $blacklist): Closure
638
    {
639
        $base = sprintf(
640
            '/^%s/',
641
            preg_quote($basePath.DIRECTORY_SEPARATOR, '/')
642
        );
643
644
        return function (SplFileInfo $file) use ($base, $blacklist): ?bool {
645
            $path = Path::canonical(
646
                preg_replace($base, '', $file->getPathname())
647
            );
648
649
            if (in_array($path, $blacklist, true)) {
650
                return false;
651
            }
652
653
            return null;
654
        };
655
    }
656
657
    /**
658
     * @param stdClass $raw
659
     * @param string   $basePath
660
     * @param Closure  $blacklistFilter
661
     *
662
     * @return iterable[]|SplFileInfo[][]
663
     */
664
    private static function retrieveBinaryIterators(stdClass $raw, string $basePath, Closure $blacklistFilter): array
665
    {
666
        if (isset($raw->{'finder-bin'})) {
667
            return self::processFinders($raw->{'finder-bin'}, $basePath, $blacklistFilter);
668
        }
669
670
        return [];
671
    }
672
673
    /**
674
     * @param stdClass $raw
675
     * @param string   $basePath
676
     *
677
     * @return string[]
678
     */
679
    private static function retrieveDirectories(stdClass $raw, string $basePath): array
680
    {
681
        if (isset($raw->directories)) {
682
            $directories = (array) $raw->directories;
683
684
            array_walk(
685
                $directories,
686
                function (&$directory) use ($basePath): void {
687
                    $directory = $basePath
688
                        .DIRECTORY_SEPARATOR
689
                        .rtrim(Path::canonical($directory), DIRECTORY_SEPARATOR);
690
                }
691
            );
692
693
            return $directories;
694
        }
695
696
        return [];
697
    }
698
699
    /**
700
     * @param string[] $directories
701
     * @param Closure  $blacklistFilter
702
     *
703
     * @return null|iterable|SplFileInfo[]
704
     */
705
    private static function retrieveDirectoriesIterator(array $directories, Closure $blacklistFilter): ?iterable
706
    {
707
        if ([] !== $directories) {
708
            return Finder::create()
0 ignored issues
show
Bug Best Practice introduced by
The expression return Symfony\Component...true)->in($directories) returns the type Symfony\Component\Finder\Finder which is incompatible with the type-hinted return null|iterable.
Loading history...
709
                ->files()
710
                ->filter($blacklistFilter)
711
                ->ignoreVCS(true)
712
                ->in($directories)
713
            ;
714
        }
715
716
        return null;
717
    }
718
719
    /**
720
     * @return SplFileInfo[]
721
     */
722
    private static function retrieveFiles(stdClass $raw, string $basePath): array
723
    {
724
        if (false === isset($raw->files)) {
725
            return [];
726
        }
727
728
        $files = [];
729
730
        foreach ((array) $raw->files as $file) {
731
            $file = new SplFileInfo(
732
                $path = $basePath.DIRECTORY_SEPARATOR.Path::canonical($file)
733
            );
734
735
            if (false === $file->isFile()) {
736
                throw new InvalidArgumentException(
737
                    sprintf(
738
                        'The file "%s" does not exist or is not a file.',
739
                        $path
740
                    )
741
                );
742
            }
743
744
            $files[] = $file;
745
        }
746
747
        return $files;
748
    }
749
750
    /**
751
     * @param SplFileInfo[] $files
752
     *
753
     * @return null|iterable|SplFileInfo[]
754
     */
755
    private static function retrieveFilesIterator(array $files): ?iterable
756
    {
757
        if ([] !== $files) {
758
            return new ArrayIterator($files);
0 ignored issues
show
Bug Best Practice introduced by
The expression return new ArrayIterator($files) returns the type ArrayIterator which is incompatible with the type-hinted return null|iterable.
Loading history...
759
        }
760
761
        return null;
762
    }
763
764
    /**
765
     * @param stdClass $raw
766
     * @param string   $basePath
767
     * @param Closure  $blacklistFilter
768
     *
769
     * @return iterable[]|SplFileInfo[][]
770
     */
771
    private static function retrieveFilesIterators(stdClass $raw, string $basePath, Closure $blacklistFilter): array
772
    {
773
        if (isset($raw->finder)) {
774
            return self::processFinders($raw->finder, $basePath, $blacklistFilter);
775
        }
776
777
        return [];
778
    }
779
780
    /**
781
     * @param array   $findersConfig   the configuration
782
     * @param string  $basePath
783
     * @param Closure $blacklistFilter
784
     *
785
     * @return Finder[]
786
     */
787
    private static function processFinders(array $findersConfig, string $basePath, Closure $blacklistFilter): array
788
    {
789
        $processFinderConfig = function ($methods) use ($basePath, $blacklistFilter): Finder {
790
            $finder = Finder::create()
791
                ->files()
792
                ->filter($blacklistFilter)
793
                ->ignoreVCS(true)
794
            ;
795
796
            if (isset($methods->in)) {
797
                $methods->in = (array) $methods->in;
798
799
                array_walk(
800
                    $methods->in,
801
                    function (&$directory) use ($basePath): void {
802
                        $directory = Path::canonical(
803
                            $basePath.DIRECTORY_SEPARATOR.$directory
804
                        );
805
                    }
806
                );
807
            }
808
809
            foreach ($methods as $method => $arguments) {
810
                if (false === method_exists($finder, $method)) {
811
                    throw new InvalidArgumentException(
812
                        sprintf(
813
                            'The method "Finder::%s" does not exist.',
814
                            $method
815
                        )
816
                    );
817
                }
818
819
                $arguments = (array) $arguments;
820
821
                foreach ($arguments as $argument) {
822
                    $finder->$method($argument);
823
                }
824
            }
825
826
            return $finder;
827
        };
828
829
        return array_map($processFinderConfig, $findersConfig);
830
    }
831
832
    private static function retrieveBootstrapFile(stdClass $raw, string $basePath): ?string
833
    {
834
        if (false === isset($raw->bootstrap)) {
835
            return null;
836
        }
837
838
        $file = $raw->bootstrap;
839
840
        if (false === Path::isAbsolute($file)) {
841
            $file = Path::canonical(
842
                $basePath.DIRECTORY_SEPARATOR.$file
843
            );
844
        }
845
846
        if (false === file_exists($file)) {
847
            throw new InvalidArgumentException(
848
                sprintf(
849
                    'The bootstrap path "%s" is not a file or does not exist.',
850
                    $file
851
                )
852
            );
853
        }
854
855
        return $file;
856
    }
857
858
    /**
859
     * @return CompactorInterface[]
860
     */
861
    private static function retrieveCompactors(stdClass $raw): array
862
    {
863
        if (false === isset($raw->compactors)) {
864
            return [];
865
        }
866
867
        $compactors = [];
868
869
        foreach ((array) $raw->compactors as $class) {
870
            if (false === class_exists($class)) {
871
                throw new InvalidArgumentException(
872
                    sprintf(
873
                        'The compactor class "%s" does not exist.',
874
                        $class
875
                    )
876
                );
877
            }
878
879
            $compactor = new $class();
880
881
            if (false === ($compactor instanceof CompactorInterface)) {
882
                throw new InvalidArgumentException(
883
                    sprintf(
884
                        'The class "%s" is not a compactor class.',
885
                        $class
886
                    )
887
                );
888
            }
889
890
            if ($compactor instanceof Php) {
891
                if (false === empty($raw->annotations)) {
892
                    $tokenizer = new Tokenizer();
893
894
                    if (isset($raw->annotations->ignore)) {
895
                        $tokenizer->ignore(
896
                            (array) $raw->annotations->ignore
897
                        );
898
                    }
899
900
                    $compactor->setTokenizer($tokenizer);
901
                }
902
            }
903
904
            $compactors[] = $compactor;
905
        }
906
907
        return $compactors;
908
    }
909
910
    private static function retrieveCompressionAlgorithm(stdClass $raw): ?int
911
    {
912
        if (false === isset($raw->compression)) {
913
            return null;
914
        }
915
916
        if (false === is_string($raw->compression)) {
917
            return $raw->compression;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $raw->compression returns the type string which is incompatible with the type-hinted return null|integer.
Loading history...
918
        }
919
920
        if (false === defined('Phar::'.$raw->compression)) {
921
            throw new InvalidArgumentException(
922
                sprintf(
923
                    'The compression algorithm "%s" is not supported.',
924
                    $raw->compression
925
                )
926
            );
927
        }
928
929
        $value = constant('Phar::'.$raw->compression);
930
931
        // Phar::NONE is not valid for compressFiles()
932
        if (Phar::NONE === $value) {
933
            return null;
934
        }
935
936
        return $value;
937
    }
938
939
    private static function retrieveFileMode(stdClass $raw): ?int
940
    {
941
        if (isset($raw->chmod)) {
942
            return intval($raw->chmod, 8);
943
        }
944
945
        return null;
946
    }
947
948
    private static function retrieveMainScriptPath(stdClass $raw): ?string
949
    {
950
        if (isset($raw->main)) {
951
            return Path::canonical($raw->main);
952
        }
953
954
        return null;
955
    }
956
957
    private static function retrieveMainScriptContents(?string $mainScriptPath, string $basePath): ?string
958
    {
959
        if (null === $mainScriptPath) {
960
            return null;
961
        }
962
        $mainScriptPath = $basePath.DIRECTORY_SEPARATOR.$mainScriptPath;
963
964
        if (false === ($contents = @file_get_contents($mainScriptPath))) {
965
            $errors = error_get_last();
966
967
            if (null === $errors) {
968
                $errors = ['message' => 'Failed to get contents of "'.$mainScriptPath.'""'];
969
            }
970
971
            throw new InvalidArgumentException($errors['message']);
972
        }
973
974
        return preg_replace('/^#!.*\s*/', '', $contents);
975
    }
976
977
    /**
978
     * @return string[]
979
     */
980
    private static function retrieveMap(stdClass $raw): array
981
    {
982
        if (false === isset($raw->map)) {
983
            return [];
984
        }
985
986
        $map = [];
987
988
        foreach ((array) $raw->map as $item) {
989
            $processed = [];
990
991
            foreach ($item as $match => $replace) {
992
                $processed[Path::canonical($match)] = Path::canonical($replace);
993
            }
994
995
            if (isset($processed['_empty_'])) {
996
                $processed[''] = $processed['_empty_'];
997
998
                unset($processed['_empty_']);
999
            }
1000
1001
            $map[] = $processed;
1002
        }
1003
1004
        return $map;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $map returns an array which contains values of type array|string[] which are incompatible with the documented value type string.
Loading history...
1005
    }
1006
1007
    private static function retrieveMapper(array $map): Closure
1008
    {
1009
        return function (string $path) use ($map): ?string {
1010
            foreach ($map as $item) {
1011
                foreach ($item as $match => $replace) {
1012
                    if (empty($match)) {
1013
                        return $replace.$path;
1014
                    }
1015
1016
                    if (0 === strpos($path, $match)) {
1017
                        return preg_replace(
1018
                            '/^'.preg_quote($match, '/').'/',
1019
                            $replace,
1020
                            $path
1021
                        );
1022
                    }
1023
                }
1024
            }
1025
1026
            return null;
1027
        };
1028
    }
1029
1030
    /**
1031
     * @return mixed
1032
     */
1033
    private static function retrieveMetadata(stdClass $raw)
1034
    {
1035
        if (isset($raw->metadata)) {
1036
            if (is_object($raw->metadata)) {
1037
                return (array) $raw->metadata;
1038
            }
1039
1040
            return $raw->metadata;
1041
        }
1042
1043
        return null;
1044
    }
1045
1046
    private static function retrieveMimetypeMapping(stdClass $raw): array
1047
    {
1048
        if (isset($raw->mimetypes)) {
1049
            return (array) $raw->mimetypes;
1050
        }
1051
1052
        return [];
1053
    }
1054
1055
    private static function retrieveMungVariables(stdClass $raw): array
1056
    {
1057
        if (isset($raw->mung)) {
1058
            return (array) $raw->mung;
1059
        }
1060
1061
        return [];
1062
    }
1063
1064
    private static function retrieveNotFoundScriptPath(stdClass $raw): ?string
1065
    {
1066
        if (isset($raw->{'not-found'})) {
1067
            return $raw->{'not-found'};
1068
        }
1069
1070
        return null;
1071
    }
1072
1073
    private static function retrieveOutputPath(stdClass $raw, string $file): string
1074
    {
1075
        $base = getcwd().DIRECTORY_SEPARATOR;
1076
1077
        if (isset($raw->output)) {
1078
            $path = $raw->output;
1079
1080
            if (false === Path::isAbsolute($path)) {
1081
                $path = Path::canonical($base.$path);
1082
            }
1083
        } else {
1084
            $path = $base.self::DEFAULT_ALIAS;
1085
        }
1086
1087
        if (false !== strpos($path, '@'.'git-version@')) {
1088
            $gitVersion = self::retrieveGitVersion($file);
1089
1090
            $path = str_replace('@'.'git-version@', $gitVersion, $path);
1091
        }
1092
1093
        return $path;
1094
    }
1095
1096
    private static function retrievePrivateKeyPassphrase(stdClass $raw): ?string
1097
    {
1098
        if (isset($raw->{'key-pass'})
1099
            && is_string($raw->{'key-pass'})
1100
        ) {
1101
            return $raw->{'key-pass'};
1102
        }
1103
1104
        return null;
1105
    }
1106
1107
    private static function retrievePrivateKeyPath(stdClass $raw): ?string
1108
    {
1109
        if (isset($raw->key)) {
1110
            return $raw->key;
1111
        }
1112
1113
        return null;
1114
    }
1115
1116
    private static function retrieveReplacements(stdClass $raw): array
1117
    {
1118
        if (isset($raw->replacements)) {
1119
            return (array) $raw->replacements;
1120
        }
1121
1122
        return [];
1123
    }
1124
1125
    private static function retrieveProcessedReplacements(
1126
        array $replacements,
1127
        stdClass $raw,
1128
        string $file
1129
    ): array {
1130
        if (null !== ($git = self::retrieveGitHashPlaceholder($raw))) {
1131
            $replacements[$git] = self::retrieveGitHash($file);
1132
        }
1133
1134
        if (null !== ($git = self::retrieveGitShortHashPlaceholder($raw))) {
1135
            $replacements[$git] = self::retrieveGitHash($file, true);
1136
        }
1137
1138
        if (null !== ($git = self::retrieveGitTagPlaceholder($raw))) {
1139
            $replacements[$git] = self::retrieveGitTag($file);
1140
        }
1141
1142
        if (null !== ($git = self::retrieveGitVersionPlaceholder($raw))) {
1143
            $replacements[$git] = self::retrieveGitVersion($file);
1144
        }
1145
1146
        if (null !== ($date = self::retrieveDatetimeNowPlaceHolder($raw))) {
1147
            $replacements[$date] = self::retrieveDatetimeNow(
1148
                self::retrieveDatetimeFormat($raw)
1149
            );
1150
        }
1151
1152
        $sigil = self::retrieveReplacementSigil($raw);
1153
1154
        foreach ($replacements as $key => $value) {
1155
            unset($replacements[$key]);
1156
            $replacements["$sigil$key$sigil"] = $value;
1157
        }
1158
1159
        return $replacements;
1160
    }
1161
1162
    private static function retrieveGitHashPlaceholder(stdClass $raw): ?string
1163
    {
1164
        if (isset($raw->{'git-commit'})) {
1165
            return $raw->{'git-commit'};
1166
        }
1167
1168
        return null;
1169
    }
1170
1171
    /**
1172
     * @param string $file
1173
     * @param bool   $short Use the short version
1174
     *
1175
     * @return string the commit hash
1176
     */
1177
    private static function retrieveGitHash(string $file, bool $short = false): string
1178
    {
1179
        return self::runGitCommand(
1180
            sprintf(
1181
                'git log --pretty="%s" -n1 HEAD',
1182
                $short ? '%h' : '%H'
1183
            ),
1184
            $file
1185
        );
1186
    }
1187
1188
    private static function retrieveGitShortHashPlaceholder(stdClass $raw): ?string
1189
    {
1190
        if (isset($raw->{'git-commit-short'})) {
1191
            return $raw->{'git-commit-short'};
1192
        }
1193
1194
        return null;
1195
    }
1196
1197
    private static function retrieveGitTagPlaceholder(stdClass $raw): ?string
1198
    {
1199
        if (isset($raw->{'git-tag'})) {
1200
            return $raw->{'git-tag'};
1201
        }
1202
1203
        return null;
1204
    }
1205
1206
    private static function retrieveGitTag(string $file): ?string
1207
    {
1208
        return self::runGitCommand('git describe --tags HEAD', $file);
1209
    }
1210
1211
    private static function retrieveGitVersionPlaceholder(stdClass $raw): ?string
1212
    {
1213
        if (isset($raw->{'git-version'})) {
1214
            return $raw->{'git-version'};
1215
        }
1216
1217
        return null;
1218
    }
1219
1220
    private static function retrieveGitVersion(string $file): ?string
1221
    {
1222
        try {
1223
            return self::retrieveGitTag($file);
1224
        } catch (RuntimeException $exception) {
1225
            try {
1226
                return self::retrieveGitHash($file, true);
1227
            } catch (RuntimeException $exception) {
1228
                throw new RuntimeException(
1229
                    sprintf(
1230
                        'The tag or commit hash could not be retrieved from "%s": %s',
1231
                        dirname($file),
1232
                        $exception->getMessage()
1233
                    ),
1234
                    0,
1235
                    $exception
1236
                );
1237
            }
1238
        }
1239
    }
1240
1241
    private static function retrieveDatetimeNowPlaceHolder(stdClass $raw): ?string
1242
    {
1243
        if (isset($raw->{'datetime'})) {
1244
            return $raw->{'datetime'};
1245
        }
1246
1247
        return null;
1248
    }
1249
1250
    private static function retrieveDatetimeNow(string $format)
1251
    {
1252
        $now = new DateTimeImmutable('now');
1253
1254
        $datetime = $now->format($format);
1255
1256
        if (!$datetime) {
1257
            throw new InvalidArgumentException(
1258
                sprintf(
1259
                    '""%s" is not a valid PHP date format',
1260
                    $format
1261
                )
1262
            );
1263
        }
1264
1265
        return $datetime;
1266
    }
1267
1268
    private static function retrieveDatetimeFormat(stdClass $raw): string
1269
    {
1270
        if (isset($raw->{'datetime_format'})) {
1271
            return $raw->{'datetime_format'};
1272
        }
1273
1274
        return self::DEFAULT_DATETIME_FORMAT;
1275
    }
1276
1277
    private static function retrieveReplacementSigil(stdClass $raw)
1278
    {
1279
        if (isset($raw->{'replacement-sigil'})) {
1280
            return $raw->{'replacement-sigil'};
1281
        }
1282
1283
        return self::DEFAULT_REPLACEMENT_SIGIL;
1284
    }
1285
1286
    private static function retrieveShebang(stdClass $raw): ?string
1287
    {
1288
        if (false === isset($raw->shebang)) {
1289
            return null;
1290
        }
1291
1292
        if (('' === $raw->shebang) || (false === $raw->shebang)) {
1293
            return '';
1294
        }
1295
1296
        $shebang = trim($raw->shebang);
1297
1298
        if ('#!' !== substr($shebang, 0, 2)) {
1299
            throw new InvalidArgumentException(
1300
                sprintf(
1301
                    'The shebang line must start with "#!": %s',
1302
                    $shebang
1303
                )
1304
            );
1305
        }
1306
1307
        return $shebang;
1308
    }
1309
1310
    private static function retrieveSigningAlgorithm(stdClass $raw): int
1311
    {
1312
        if (false === isset($raw->algorithm)) {
1313
            return Phar::SHA1;
1314
        }
1315
1316
        if (is_string($raw->algorithm)) {
1317
            if (false === defined('Phar::'.$raw->algorithm)) {
1318
                throw new InvalidArgumentException(
1319
                    sprintf(
1320
                        'The signing algorithm "%s" is not supported.',
1321
                        $raw->algorithm
1322
                    )
1323
                );
1324
            }
1325
1326
            return constant('Phar::'.$raw->algorithm);
1327
        }
1328
1329
        return $raw->algorithm;
1330
    }
1331
1332
    private static function retrieveStubBanner(stdClass $raw): ?string
1333
    {
1334
        if (isset($raw->{'banner'})) {
1335
            return $raw->{'banner'};
1336
        }
1337
1338
        return null;
1339
    }
1340
1341
    private static function retrieveStubBannerPath(stdClass $raw): ?string
1342
    {
1343
        if (isset($raw->{'banner-file'})) {
1344
            return Path::canonical($raw->{'banner-file'});
1345
        }
1346
1347
        return null;
1348
    }
1349
1350
    private static function retrieveStubBannerFromFile(string $basePath, ?string $stubBannerPath): ?string
1351
    {
1352
        if (null == $stubBannerPath) {
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $stubBannerPath of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1353
            return null;
1354
        }
1355
1356
        $stubBannerPath = $basePath.DIRECTORY_SEPARATOR.$stubBannerPath;
1357
1358
        if (false === ($contents = @file_get_contents($stubBannerPath))) {
1359
            $errors = error_get_last();
1360
1361
            if (null === $errors) {
1362
                $errors = ['message' => 'failed to get contents of "'.$stubBannerPath.'""'];
1363
            }
1364
1365
            throw new InvalidArgumentException($errors['message']);
1366
        }
1367
1368
        return $contents;
1369
    }
1370
1371
    private static function retrieveStubPath(stdClass $raw): ?string
1372
    {
1373
        if (isset($raw->stub) && is_string($raw->stub)) {
1374
            return $raw->stub;
1375
        }
1376
1377
        return null;
1378
    }
1379
1380
    private static function retrieveIsExtractable(stdClass $raw): bool
1381
    {
1382
        if (isset($raw->extract)) {
1383
            return $raw->extract;
1384
        }
1385
1386
        return false;
1387
    }
1388
1389
    private static function retrieveIsInterceptFileFuncs(stdClass $raw): bool
1390
    {
1391
        if (isset($raw->intercept)) {
1392
            return $raw->intercept;
1393
        }
1394
1395
        return false;
1396
    }
1397
1398
    private static function retrieveIsPrivateKeyPrompt(stdClass $raw): bool
1399
    {
1400
        if (isset($raw->{'key-pass'})
1401
            && (true === $raw->{'key-pass'})) {
1402
            return true;
1403
        }
1404
1405
        return false;
1406
    }
1407
1408
    private static function retrieveIsStubGenerated(stdClass $raw): bool
1409
    {
1410
        if (isset($raw->stub) && (true === $raw->stub)) {
1411
            return true;
1412
        }
1413
1414
        return false;
1415
    }
1416
1417
    private static function retrieveIsWebPhar(stdClass $raw): bool
1418
    {
1419
        if (isset($raw->web)) {
1420
            return $raw->web;
1421
        }
1422
1423
        return false;
1424
    }
1425
1426
    /**
1427
     * Runs a Git command on the repository.
1428
     *
1429
     * @param string $command the command
1430
     *
1431
     * @return string the trimmed output from the command
1432
     */
1433
    private static function runGitCommand(string $command, string $file): string
1434
    {
1435
        $path = dirname($file);
1436
1437
        $process = new Process($command, $path);
1438
1439
        if (0 === $process->run()) {
1440
            return trim($process->getOutput());
1441
        }
1442
1443
        throw new RuntimeException(
1444
            sprintf(
1445
                'The tag or commit hash could not be retrieved from "%s": %s',
1446
                $path,
1447
                $process->getErrorOutput()
1448
            )
1449
        );
1450
    }
1451
}
1452