Current File : /var/www/vinorea/modules/psxdesign/vendor/humbug/php-scoper/src/Whitelist.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 Countable;
use InvalidArgumentException;
use PhpParser\Node\Name\FullyQualified;
use function array_filter;
use function array_flip;
use function array_key_exists;
use function array_map;
use function array_pop;
use function array_unique;
use function array_values;
use function count;
use function explode;
use function implode;
use function preg_match;
use function sprintf;
use function str_replace;
use function strpos;
use function strtolower;
use function substr;
use function trim;

final class Whitelist implements Countable
{
    private $original;
    private $symbols;
    private $constants;
    private $namespaces;
    private $patterns;

    private $whitelistGlobalConstants;
    private $whitelistGlobalClasses;
    private $whitelistGlobalFunctions;

    private $whitelistedFunctions = [];
    private $whitelistedClasses = [];

    public static function create(
        bool $whitelistGlobalConstants,
        bool $whitelistGlobalClasses,
        bool $whitelistGlobalFunctions,
        string ...$elements
    ): self {
        $symbols = [];
        $constants = [];
        $namespaces = [];
        $patterns = [];
        $original = [];

        foreach ($elements as $element) {
            if (isset($element[0]) && '\\' === $element[0]) {
                $element = substr($element, 1);
            }

            if ('' === trim($element)) {
                throw new InvalidArgumentException(sprintf('Invalid whitelist element "%s": cannot accept an empty string', $element));
            }

            $original[] = $element;

            if ('\*' === substr($element, -2)) {
                $namespaces[] = strtolower(substr($element, 0, -2));
            } elseif ('*' === $element) {
                $namespaces[] = '';
            } elseif (false !== strpos($element, '*')) {
                self::assertValidPattern($element);

                $patterns[] = sprintf(
                    '/^%s$/u',
                    str_replace(
                        '\\',
                        '\\\\',
                        str_replace(
                            '*',
                            '.*',
                            $element
                        )
                    )
                );
            } else {
                $symbols[] = strtolower($element);
                $constants[] = self::lowerConstantName($element);
            }
        }

        return new self(
            $whitelistGlobalConstants,
            $whitelistGlobalClasses,
            $whitelistGlobalFunctions,
            array_unique($original),
            array_flip($symbols),
            array_flip($constants),
            array_unique($patterns),
            array_unique($namespaces)
        );
    }

    private static function assertValidPattern(string $element): void
    {
        if (1 !== preg_match('/^(([\p{L}_]+\\\\)+)?[\p{L}_]*\*$/u', $element)) {
            throw new InvalidArgumentException(sprintf('Invalid whitelist pattern "%s".', $element));
        }
    }

    /**
     * @param string[] $original
     * @param string[] $patterns
     * @param string[] $namespaces
     */
    private function __construct(
        bool $whitelistGlobalConstants,
        bool $whitelistGlobalClasses,
        bool $whitelistGlobalFunctions,
        array $original,
        array $symbols,
        array $constants,
        array $patterns,
        array $namespaces
    ) {
        $this->whitelistGlobalConstants = $whitelistGlobalConstants;
        $this->whitelistGlobalClasses = $whitelistGlobalClasses;
        $this->whitelistGlobalFunctions = $whitelistGlobalFunctions;
        $this->original = $original;
        $this->symbols = $symbols;
        $this->constants = $constants;
        $this->namespaces = $namespaces;
        $this->patterns = $patterns;
    }

    public function belongsToWhitelistedNamespace(string $name): bool
    {
        $nameNamespace = $this->retrieveNameNamespace($name);

        foreach ($this->namespaces as $namespace) {
            if ('' === $namespace || 0 === strpos($nameNamespace, $namespace)) {
                return true;
            }
        }

        return false;
    }

    public function isWhitelistedNamespace(string $name): bool
    {
        $name = strtolower($name);

        if (0 === strpos($name, '\\')) {
            $name = substr($name, 1);
        }

        foreach ($this->namespaces as $namespace) {
            if ('' === $namespace) {
                return true;
            }

            if ('' !== $namespace && 0 !== strpos($name, $namespace)) {
                continue;
            }

            $nameParts = explode('\\', $name);

            foreach (explode('\\', $namespace) as $index => $namespacePart) {
                if ($nameParts[$index] !== $namespacePart) {
                    return false;
                }
            }

            return true;
        }

        return false;
    }

    /**
     * @internal
     */
    public function whitelistGlobalFunctions(): bool
    {
        return $this->whitelistGlobalFunctions;
    }

    public function isGlobalWhitelistedFunction(string $functionName): bool
    {
        return $this->whitelistGlobalFunctions && false === strpos($functionName, '\\');
    }

    public function recordWhitelistedFunction(FullyQualified $original, FullyQualified $alias): void
    {
        $this->whitelistedFunctions[(string) $original] = [(string) $original, (string) $alias];
    }

    public function getRecordedWhitelistedFunctions(): array
    {
        return array_values($this->whitelistedFunctions);
    }

    /**
     * @internal
     */
    public function whitelistGlobalConstants(): bool
    {
        return $this->whitelistGlobalConstants;
    }

    public function isGlobalWhitelistedConstant(string $constantName): bool
    {
        return $this->whitelistGlobalConstants && false === strpos($constantName, '\\');
    }

    /**
     * @internal
     */
    public function whitelistGlobalClasses(): bool
    {
        return $this->whitelistGlobalClasses;
    }

    public function isGlobalWhitelistedClass(string $className): bool
    {
        return $this->whitelistGlobalClasses && false === strpos($className, '\\');
    }

    public function recordWhitelistedClass(FullyQualified $original, FullyQualified $alias): void
    {
        $this->whitelistedClasses[(string) $original] = [(string) $original, (string) $alias];
    }

    public function getRecordedWhitelistedClasses(): array
    {
        return array_values($this->whitelistedClasses);
    }

    /**
     * Tells if a given symbol is whitelisted. Note however that it does not account for when:.
     *
     * - The symbol belongs to the global namespace and the symbols of the global namespace of this type are whitelisted
     * - Belongs to a whitelisted namespace
     *
     * @param bool $constant Unlike other symbols, constants _can_ be case insensitive but 99% are not so we leave out
     *                       the case where they are not case sensitive.
     */
    public function isSymbolWhitelisted(string $name, bool $constant = false): bool
    {
        if (false === $constant && array_key_exists(strtolower($name), $this->symbols)) {
            return true;
        }

        if ($constant && array_key_exists(self::lowerConstantName($name), $this->constants)) {
            return true;
        }

        foreach ($this->patterns as $pattern) {
            $pattern = false === $constant ? $pattern.'i' : $pattern;

            if (1 === preg_match($pattern, $name)) {
                return true;
            }
        }

        return false;
    }

    /**
     * @return string[]
     *
     * @deprecated To be replaced by getWhitelistedClasses
     */
    public function getClassWhitelistArray(): array
    {
        return array_filter(
            $this->original,
            static function (string $name): bool {
                return '*' !== $name && '\*' !== substr($name, -2);
            }
        );
    }

    public function toArray(): array
    {
        return $this->original;
    }

    /**
     * {@inheritdoc}
     */
    public function count(): int
    {
        return count($this->whitelistedFunctions) + count($this->whitelistedClasses);
    }

    /**
     * Transforms the constant FQ name "Acme\Foo\X" to "acme\foo\X" since the namespace remains case insensitive for
     * constants regardless of whether or not constants actually are case insensitive.
     */
    private static function lowerConstantName(string $name): string
    {
        $parts = explode('\\', $name);

        $lastPart = array_pop($parts);

        $parts = array_map('strtolower', $parts);

        $parts[] = $lastPart;

        return implode('\\', $parts);
    }

    private function retrieveNameNamespace(string $name): string
    {
        $name = strtolower($name);

        if (0 === strpos($name, '\\')) {
            $name = substr($name, 1);
        }

        $nameParts = explode('\\', $name);

        array_pop($nameParts);

        return [] === $nameParts ? '' : implode('\\', $nameParts);
    }
}