<?php
// +-------------------------------------------------+
// © 2002-2004 PMB Services / www.sigb.net pmb@sigb.net et contributeurs (voir www.sigb.net)
// +-------------------------------------------------+
// $Id: database_parser.class.php,v 1.1.2.2 2026/03/17 13:47:12 qvarin Exp $

if (stristr($_SERVER['REQUEST_URI'], ".class.php")) {
    die("no access");
}

class DatabaseParser
{
    public const DATABASE_HOST = 'SQL_SERVER';
    public const DATABASE_USER = 'USER_NAME';
    public const DATABASE_PASS = 'USER_PASS';
    public const DATABASE_NAME = 'DATA_BASE';
    public const DATABASE_SECRET = 'SECRET_PASS';

    public const DEFAULT_TARGET_DEFINE = [
        self::DATABASE_HOST,
        self::DATABASE_USER,
        self::DATABASE_PASS,
        self::DATABASE_NAME,
        self::DATABASE_SECRET,
    ];

    /**
     * Tokens du code PHP
     *
     * @var array<int, string|array>
     */
    private $tokens = [];

    /**
     * Index du token
     *
     * @var int
     */
    private $cursor = 0;

    /**
     * Taille du code PHP
     *
     * @var int
     */
    private $length = 0;

    /**
     * Liste des constantes a recuperer
     *
     * @var array
     */
    private array $targetDefines = [];

    /**
     * Liste des constantes par database
     *
     * @var array<string, array>
     */
    private array $result = [];

    /**
     * Database actuelle (case en cours de traitement)
     *
     * @var string|null
     */
    private ?string $currentCase = null;

    /**
     * Constructeur
     *
     * @param array $targetDefines (optionnel)
     */
    public function __construct(array $targetDefines = self::DEFAULT_TARGET_DEFINE)
    {
        $this->targetDefines = $targetDefines;
    }

    /**
     * Parse le code PHP (db_params.php) et retourne les constantes definis dans le switch case
     *
     * @param string $code
     * @return array
     */
    public function parse(string $code): array
    {
        $this->tokens = token_get_all($code);
        $this->length = count($this->tokens);
        $this->cursor = 0;

        $this->result = [];
        $this->currentCase = null;

        while ($this->cursor < $this->length) {
            $token = $this->current();

            if ($this->isCaseToken($token)) {
                $this->handleCaseToken();
                continue;
            }

            if ($this->isDefineToken($token)) {
                $this->handleDefineToken();
                continue;
            }

            if ($this->isBreakToken($token)) {
                $this->currentCase = null;
            }

            $this->next();
        }

        if (empty($this->result)) {
            trigger_error('[DatabaseParser] Invalid code PHP (no database found)', E_USER_WARNING);
        }

        return $this->result;
    }

    /**
     * Indique si le token est un case (ex: case 'database':)
     *
     * @param array|string $token
     * @return bool
     */
    private function isCaseToken($token): bool
    {
        return is_array($token) && $token[0] === T_CASE;
    }

    /**
     * Gere le token case (ex: case 'database':)
     *
     * @return void
     */
    private function handleCaseToken(): void
    {
        $this->next();
        while ($this->cursor < $this->length) {
            $token = $this->current();
            if (is_array($token) && $token[0] === T_CONSTANT_ENCAPSED_STRING) {
                $this->currentCase = $this->cleanEncapsedString($token[1]);
                $this->result[$this->currentCase] ??= [];
                break;
            }
            $this->next();
        }
        $this->next();
    }

    /**
     * Indique si le token est un define (ex: define('constant', 'value');)
     *
     * @param array|string $token
     * @return bool
     */
    private function isDefineToken($token): bool
    {
        return $this->currentCase !== null
            && is_array($token)
            && $token[0] === T_STRING
            && $token[1] === 'define';
    }

    /**
     * Gere le token define (ex: define('constant', 'value');)
     *
     * @return void
     */
    private function handleDefineToken(): void
    {
        $this->skipToOpeningParenthesis();
        $constName = $this->extractConstantName();

        if ($constName !== null && in_array($constName, $this->targetDefines)) {
            $this->skipToComma();
            $this->extractConstantValue($constName);
        }

        $this->next();
    }

    /**
     * Avance jusqu'a la parenthese ouvrante
     * Gere les espaces comme dans : define   ('constant', 'value');
     *
     * @return void
     */
    private function skipToOpeningParenthesis(): void
    {
        $this->next();
        while ($this->cursor < $this->length && $this->currentValue() !== '(') {
            $this->next();
        }
        $this->next();
    }

    /**
     * Extrait le nom de la constante
     * Gere les espaces comme dans : define(   'constant', 'value');
     *
     * @return string|null
     */
    private function extractConstantName(): ?string
    {
        while ($this->cursor < $this->length) {
            $token = $this->current();
            if (is_array($token) && $token[0] === T_CONSTANT_ENCAPSED_STRING) {
                return $this->cleanEncapsedString($token[1]);
            }
            $this->next();
        }
        return null;
    }

    /**
     * Avance jusqu'a la virgule
     * Gere les espaces comme dans : define('constant'   , 'value');
     *
     * @return void
     */
    private function skipToComma(): void
    {
        $this->next();
        while ($this->cursor < $this->length && $this->currentValue() !== ',') {
            $this->next();
        }
        $this->next();
    }

    /**
     * Extrait la valeur de la constante et la stocke dans le resultat
     * Gere les espaces comme dans : define('constant',   'value');
     *
     * @param string $constName
     * @return void
     */
    private function extractConstantValue(string $constName): void
    {
        while ($this->cursor < $this->length) {
            $token = $this->current();
            if (is_array($token) && $token[0] === T_CONSTANT_ENCAPSED_STRING) {
                $this->result[$this->currentCase][$constName] = $this->cleanEncapsedString($token[1]);
                break;
            }
            $this->next();
        }
    }

    /**
     * Indique si le token est un break (ex: break;)
     *
     * @param array|string $token
     * @return bool
     */
    private function isBreakToken($token): bool
    {
        return is_array($token) && $token[0] === T_BREAK;
    }

    /**
     * Nettoie une chaine encapsulee (enleve les guillemets)
     *
     * @param string $string
     * @return string
     */
    private function cleanEncapsedString(string $string): string
    {
        $quote = substr($string, 0, 1);
        $string = trim($string, $quote);
        $string = str_replace(addslashes($quote), $quote, $string);
        return $string;
    }

    /**
     * Token courant
     *
     * @return array|string|null
     */
    private function current()
    {
        return $this->tokens[$this->cursor] ?? null;
    }

    /**
     * Valeur du token courant
     *
     * @return string
     */
    private function currentValue(): string
    {
        $token = $this->current();
        return is_string($token) ? $token : $token[1];
    }

    /**
     * Passe au token suivant
     *
     * @return void
     */
    private function next(): void
    {
        if ($this->cursor < $this->length) {
            $this->cursor++;
        }
    }
}

