Current File : /var/www/vinorea/modules/psxdesign/vendor/humbug/php-scoper/src/Configuration.php
<?php

declare(strict_types=1);

/*
 * This file is part of the humbug/php-scoper package.
 *
 * Copyright (c) 2017 Théo FIDRY <theo.fidry@gmail.com>,
 *                    Pádraic Brady <padraic.brady@gmail.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

namespace Humbug\PhpScoper;

use Humbug\PhpScoper\Patcher\SymfonyPatcher;
use InvalidArgumentException;
use Iterator;
use RuntimeException;
use SplFileInfo;
use Symfony\Component\Filesystem\Filesystem;
use Symfony\Component\Finder\Finder;
use function array_filter;
use function array_key_exists;
use function array_keys;
use function array_map;
use function array_merge;
use function array_reduce;
use function array_unique;
use function array_unshift;
use function dirname;
use function file_exists;
use function file_get_contents;
use function gettype;
use function in_array;
use function is_array;
use function is_bool;
use function is_callable;
use function is_dir;
use function is_file;
use function is_link;
use function is_readable;
use function is_string;
use function iterator_to_array;
use function readlink;
use function realpath;
use function sprintf;
use function trim;
use const DIRECTORY_SEPARATOR;

final class Configuration
{
    private const PREFIX_KEYWORD = 'prefix';
    private const WHITELISTED_FILES_KEYWORD = 'files-whitelist';
    private const FINDER_KEYWORD = 'finders';
    private const PATCHERS_KEYWORD = 'patchers';
    private const WHITELIST_KEYWORD = 'whitelist';
    private const WHITELIST_GLOBAL_CONSTANTS_KEYWORD = 'whitelist-global-constants';
    private const WHITELIST_GLOBAL_CLASSES_KEYWORD = 'whitelist-global-classes';
    private const WHITELIST_GLOBAL_FUNCTIONS_KEYWORD = 'whitelist-global-functions';

    private const KEYWORDS = [
        self::PREFIX_KEYWORD,
        self::WHITELISTED_FILES_KEYWORD,
        self::FINDER_KEYWORD,
        self::PATCHERS_KEYWORD,
        self::WHITELIST_KEYWORD,
        self::WHITELIST_GLOBAL_CONSTANTS_KEYWORD,
        self::WHITELIST_GLOBAL_CLASSES_KEYWORD,
        self::WHITELIST_GLOBAL_FUNCTIONS_KEYWORD,
    ];

    private $path;
    private $prefix;
    private $filesWithContents;
    private $patchers;
    private $whitelist;
    private $whitelistedFiles;

    /**
     * @param string|null $path  Absolute path to the configuration file.
     * @param string[]    $paths List of paths to append besides the one configured
     */
    public static function load(string $path = null, array $paths = []): self
    {
        if (null === $path) {
            $config = [];
        } else {
            if (false === (new Filesystem())->isAbsolutePath($path)) {
                throw new InvalidArgumentException(
                    sprintf(
                        'Expected the path of the configuration file to load to be an absolute path, got "%s" '
                        .'instead',
                        $path
                    )
                );
            }

            if (false === file_exists($path)) {
                throw new InvalidArgumentException(
                    sprintf(
                        'Expected the path of the configuration file to exists but the file "%s" could not be '
                        .'found',
                        $path
                    )
                );
            }

            if (false === is_file($path) && false === (is_link($path) && false !== readlink($path) && is_file(readlink($path)))) {
                throw new InvalidArgumentException(
                    sprintf(
                        'Expected the path of the configuration file to be a file but "%s" appears to be a '
                        .'directory.',
                        $path
                    )
                );
            }

            $config = include $path;

            if (false === is_array($config)) {
                throw new InvalidArgumentException(
                    sprintf(
                        'Expected configuration to be an array, found "%s" instead.',
                        gettype($config)
                    )
                );
            }
        }

        self::validateConfigKeys($config);

        $prefix = self::retrievePrefix($config);

        $whitelistedFiles = null === $path ? [] : self::retrieveWhitelistedFiles(dirname($path), $config);

        $patchers = self::retrievePatchers($config);

        array_unshift($patchers, new SymfonyPatcher());

        $whitelist = self::retrieveWhitelist($config);

        $finders = self::retrieveFinders($config);
        $filesFromPaths = self::retrieveFilesFromPaths($paths);
        $filesWithContents = self::retrieveFilesWithContents(chain($filesFromPaths, ...$finders));

        return new self($path, $prefix, $filesWithContents, $patchers, $whitelist, $whitelistedFiles);
    }

    /**
     * @param string|null $path              Absolute path to the configuration file loaded.
     * @param string|null $prefix            The prefix applied.
     * @param string[][]  $filesWithContents Array of tuple with the first argument being the file path and the second its contents
     * @param callable[]  $patchers          List of closures which can alter the content of the files being
     *                                       scoped.
     * @param Whitelist   $whitelist         List of classes that will not be scoped.
     *                                       returning a boolean which if `true` means the class should be scoped
     *                                       (i.e. is ignored) or scoped otherwise.
     * @param string[]    $whitelistedFiles  List of absolute paths of files to completely ignore
     */
    private function __construct(
        ?string $path,
        ?string $prefix,
        array $filesWithContents,
        array $patchers,
        Whitelist $whitelist,
        array $whitelistedFiles
    ) {
        $this->path = $path;
        $this->prefix = $prefix;
        $this->filesWithContents = $filesWithContents;
        $this->patchers = $patchers;
        $this->whitelist = $whitelist;
        $this->whitelistedFiles = $whitelistedFiles;
    }

    public function withPaths(array $paths): self
    {
        $filesWithContents = self::retrieveFilesWithContents(
            chain(
                self::retrieveFilesFromPaths(
                    array_unique($paths)
                )
            )
        );

        return new self(
            $this->path,
            $this->prefix,
            array_merge($this->filesWithContents, $filesWithContents),
            $this->patchers,
            $this->whitelist,
            $this->whitelistedFiles
        );
    }

    public function withPrefix(?string $prefix): self
    {
        $prefix = self::retrievePrefix([self::PREFIX_KEYWORD => $prefix]);

        return new self(
            $this->path,
            $prefix,
            $this->filesWithContents,
            $this->patchers,
            $this->whitelist,
            $this->whitelistedFiles
        );
    }

    public function getPath(): ?string
    {
        return $this->path;
    }

    public function getPrefix(): ?string
    {
        return $this->prefix;
    }

    public function getFilesWithContents(): array
    {
        return $this->filesWithContents;
    }

    /**
     * @return callable[]
     */
    public function getPatchers(): array
    {
        return $this->patchers;
    }

    public function getWhitelist(): Whitelist
    {
        return $this->whitelist;
    }

    /**
     * @return string[]
     */
    public function getWhitelistedFiles(): array
    {
        return $this->whitelistedFiles;
    }

    private static function validateConfigKeys(array $config): void
    {
        array_map(
            [self::class, 'validateConfigKey'],
            array_keys($config)
        );
    }

    private static function validateConfigKey(string $key): void
    {
        if (false === in_array($key, self::KEYWORDS, true)) {
            throw new InvalidArgumentException(
                sprintf(
                    'Invalid configuration key value "%s" found.',
                    $key
                )
            );
        }
    }

    /**
     * If the prefix is set to null in the config file/argument then a random prefix is being used. However if set to
     * empty, the configuration will use a null prefix.
     *
     * TL:DR; setting the prefix is a big confusing because it is not properly split in "set prefix" & prefix strategy".
     */
    private static function retrievePrefix(array $config): ?string
    {
        $prefix = $config[self::PREFIX_KEYWORD] ?? null;

        if (null === $prefix) {
            return null;
        }

        $prefix = trim($prefix);

        return '' === $prefix ? null : $prefix;
    }

    private static function retrievePatchers(array $config): array
    {
        if (false === array_key_exists(self::PATCHERS_KEYWORD, $config)) {
            return [];
        }

        $patchers = $config[self::PATCHERS_KEYWORD];

        if (false === is_array($patchers)) {
            throw new InvalidArgumentException(
                sprintf(
                    'Expected patchers to be an array of callables, found "%s" instead.',
                    gettype($patchers)
                )
            );
        }

        foreach ($patchers as $index => $patcher) {
            if (is_callable($patcher)) {
                continue;
            }

            throw new InvalidArgumentException(
                sprintf(
                    'Expected patchers to be an array of callables, the "%d" element is not.',
                    $index
                )
            );
        }

        return $patchers;
    }

    private static function retrieveWhitelist(array $config): Whitelist
    {
        if (false === array_key_exists(self::WHITELIST_KEYWORD, $config)) {
            $whitelist = [];
        } else {
            $whitelist = $config[self::WHITELIST_KEYWORD];

            if (false === is_array($whitelist)) {
                throw new InvalidArgumentException(
                    sprintf(
                        'Expected whitelist to be an array of strings, found "%s" instead.',
                        gettype($whitelist)
                    )
                );
            }

            foreach ($whitelist as $index => $className) {
                if (is_string($className)) {
                    continue;
                }

                throw new InvalidArgumentException(
                    sprintf(
                        'Expected whitelist to be an array of string, the "%d" element is not.',
                        $index
                    )
                );
            }
        }

        if (false === array_key_exists(self::WHITELIST_GLOBAL_CONSTANTS_KEYWORD, $config)) {
            $whitelistGlobalConstants = true;
        } else {
            $whitelistGlobalConstants = $config[self::WHITELIST_GLOBAL_CONSTANTS_KEYWORD];

            if (false === is_bool($whitelistGlobalConstants)) {
                throw new InvalidArgumentException(
                    sprintf(
                        'Expected %s to be a boolean, found "%s" instead.',
                        self::WHITELIST_GLOBAL_CONSTANTS_KEYWORD,
                        gettype($whitelistGlobalConstants)
                    )
                );
            }
        }

        if (false === array_key_exists(self::WHITELIST_GLOBAL_CLASSES_KEYWORD, $config)) {
            $whitelistGlobalClasses = true;
        } else {
            $whitelistGlobalClasses = $config[self::WHITELIST_GLOBAL_CLASSES_KEYWORD];

            if (false === is_bool($whitelistGlobalClasses)) {
                throw new InvalidArgumentException(
                    sprintf(
                        'Expected %s to be a boolean, found "%s" instead.',
                        self::WHITELIST_GLOBAL_CLASSES_KEYWORD,
                        gettype($whitelistGlobalClasses)
                    )
                );
            }
        }

        if (false === array_key_exists(self::WHITELIST_GLOBAL_FUNCTIONS_KEYWORD, $config)) {
            $whitelistGlobalFunctions = true;
        } else {
            $whitelistGlobalFunctions = $config[self::WHITELIST_GLOBAL_FUNCTIONS_KEYWORD];

            if (false === is_bool($whitelistGlobalFunctions)) {
                throw new InvalidArgumentException(
                    sprintf(
                        'Expected %s to be a boolean, found "%s" instead.',
                        self::WHITELIST_GLOBAL_FUNCTIONS_KEYWORD,
                        gettype($whitelistGlobalFunctions)
                    )
                );
            }
        }

        return Whitelist::create($whitelistGlobalConstants, $whitelistGlobalClasses, $whitelistGlobalFunctions, ...$whitelist);
    }

    /**
     * @return string[] Absolute paths
     */
    private static function retrieveWhitelistedFiles(string $dirPath, array $config): array
    {
        if (false === array_key_exists(self::WHITELISTED_FILES_KEYWORD, $config)) {
            return [];
        }

        $whitelistedFiles = $config[self::WHITELISTED_FILES_KEYWORD];

        if (false === is_array($whitelistedFiles)) {
            throw new InvalidArgumentException(
                sprintf(
                    'Expected whitelisted files to be an array of strings, found "%s" instead.',
                    gettype($whitelistedFiles)
                )
            );
        }

        foreach ($whitelistedFiles as $index => $file) {
            if (false === is_string($file)) {
                throw new InvalidArgumentException(
                    sprintf(
                        'Expected whitelisted files to be an array of string, the "%d" element is not.',
                        $index
                    )
                );
            }

            if (false === (new Filesystem())->isAbsolutePath($file)) {
                $file = $dirPath.DIRECTORY_SEPARATOR.$file;
            }

            $whitelistedFiles[$index] = realpath($file);
        }

        return array_filter($whitelistedFiles);
    }

    private static function retrieveFinders(array $config): array
    {
        if (false === array_key_exists(self::FINDER_KEYWORD, $config)) {
            return [];
        }

        $finders = $config[self::FINDER_KEYWORD];

        if (false === is_array($finders)) {
            throw new InvalidArgumentException(
                sprintf(
                    'Expected finders to be an array of "%s", found "%s" instead.',
                    Finder::class,
                    gettype($finders)
                )
            );
        }

        foreach ($finders as $index => $finder) {
            if ($finder instanceof Finder) {
                continue;
            }

            throw new InvalidArgumentException(
                sprintf(
                    'Expected finders to be an array of "%s", the "%d" element is not.',
                    Finder::class,
                    $index
                )
            );
        }

        return $finders;
    }

    /**
     * @param string[] $paths
     */
    private static function retrieveFilesFromPaths(array $paths): iterable
    {
        if ([] === $paths) {
            return [];
        }

        $pathsToSearch = [];
        $filesToAppend = [];

        foreach ($paths as $path) {
            if (false === file_exists($path)) {
                throw new RuntimeException(
                    sprintf(
                        'Could not find the file "%s".',
                        $path
                    )
                );
            }

            if (is_dir($path)) {
                $pathsToSearch[] = $path;
            } else {
                $filesToAppend[] = $path;
            }
        }

        $finder = new Finder();

        $finder->files()
            ->in($pathsToSearch)
            ->append($filesToAppend)
            ->filter(static function (SplFileInfo $fileInfo): ?bool {
                if ($fileInfo->isLink()) {
                    return false;
                }

                return null;
            })
            ->sortByName()
        ;

        return $finder;
    }

    /**
     * @return string[][] Array of tuple with the first argument being the file path and the second its contents
     */
    private static function retrieveFilesWithContents(Iterator $files): array
    {
        return array_reduce(
            iterator_to_array($files, false),
            static function (array $files, SplFileInfo $fileInfo): array {
                $file = $fileInfo->getRealPath();

                if (false === $file) {
                    throw new RuntimeException(
                        sprintf(
                            'Could not find the file "%s".',
                            (string) $fileInfo
                        )
                    );
                }

                if (false === is_readable($file)) {
                    throw new RuntimeException(
                        sprintf(
                            'Could not read the file "%s".',
                            $file
                        )
                    );
                }

                $files[$fileInfo->getRealPath()] = [$fileInfo->getRealPath(), file_get_contents($file)];

                return $files;
            },
            []
        );
    }
}