true): один и тот же файл читается однажды. * * @var array */ private static array $loaded = array(); /** * Значения, прочитанные из файлов. * * Значения живут только в этом хранилище и в окружение процесса не * копируются: иначе секрет (ключ OpenAI) утёк бы в `getenv()`, `phpinfo()` * и дочерние процессы всего PHP-процесса, включая чужие плагины. * * @var array */ private static array $values = array(); /** * Из какого файла пришло каждое значение (имя переменной => путь к файлу). * * @var array */ private static array $sources = array(); /** * Защитная первая строка PHP-файла с секретами. */ public const GUARD = ''; /** * Защищаемые файлы, у которых защитной строки нет (пропущены). * * @var list */ private static array $unguarded = array(); /** * Читает файл и запоминает значения. * * Можно вызывать несколько раз с разными файлами: побеждает тот файл, что * загружен раньше, — уже заданное непустое значение не перезаписывается. * Так же не перезаписываются значения, заданные на уровне сервера (реальный * environment хостинга): серверная переменная приоритетнее любого файла. * Пустые значения (`KEY=`) пропускаются, чтобы пустая строка в первом файле * не скрывала настоящее значение из следующего. * * @param string $path Путь к файлу `.env`. */ public static function load( string $path ): void { if ( isset( self::$loaded[ $path ] ) ) { return; } self::$loaded[ $path ] = true; if ( ! is_readable( $path ) ) { return; } $contents = file_get_contents( $path ); if ( false === $contents ) { return; } foreach ( self::parse( $contents ) as $key => $value ) { if ( '' === $value || '' !== self::get( $key ) ) { continue; } self::$values[ $key ] = $value; self::$sources[ $key ] = $path; } } /** * Откуда взято значение переменной. * * @param string $key Имя переменной. * @return string|null Путь к файлу, `'process'` для переменной окружения * сервера или null, если значения нет. */ public static function sourceOf( string $key ): ?string { if ( isset( self::$sources[ $key ] ) ) { // Если у сервера задана своя переменная с другим значением, get() // вернёт именно её — источник должен это отражать. $current = getenv( $key ); if ( is_string( $current ) && '' !== $current && ( self::$values[ $key ] ?? '' ) !== $current ) { return 'process'; } return self::$sources[ $key ]; } return '' !== self::get( $key ) ? 'process' : null; } /** * Читает PHP-файл с защитной первой строкой (``). * * Файл внутри веб-корня (wp-content) доступен по прямой ссылке, поэтому его * первая строка обязана прерывать выполнение: тогда запрос к файлу вернёт * пустую страницу, а не секреты. Файл без такой строки не используется — * он запоминается, чтобы страница настроек могла предупредить владельца. * * @param string $path Путь к файлу `lingocanvas.env.php`. */ public static function loadGuarded( string $path ): void { if ( isset( self::$loaded[ $path ] ) ) { return; } if ( ! is_readable( $path ) ) { self::$loaded[ $path ] = true; return; } $contents = file_get_contents( $path ); if ( false === $contents ) { self::$loaded[ $path ] = true; return; } $firstLine = preg_split( '/\R/', ltrim( $contents, "\xEF\xBB\xBF" ), 2 ); if ( ! is_array( $firstLine ) || self::GUARD !== trim( $firstLine[0] ) ) { self::$loaded[ $path ] = true; self::$unguarded[] = $path; return; } self::load( $path ); } /** * Файлы `lingocanvas.env.php`, пропущенные из-за отсутствия защитной строки. * * @return list */ public static function unguardedFiles(): array { return self::$unguarded; } /** * Значение переменной окружения. * * @param string $key Имя переменной. * @param string $default Значение по умолчанию. */ public static function get( string $key, string $default = '' ): string { // Реальное окружение сервера (getenv) приоритетнее файла. $fromEnv = getenv( $key ); if ( is_string( $fromEnv ) && '' !== $fromEnv ) { return $fromEnv; } $fromFile = self::$values[ $key ] ?? ''; return '' !== $fromFile ? $fromFile : $default; } /** * Сбрасывает состояние. Нужен только тестам. */ public static function reset(): void { self::$loaded = array(); self::$values = array(); self::$sources = array(); self::$unguarded = array(); } /** * Разбирает содержимое `.env` в пары ключ-значение. Чистая функция. * * Поддерживает `KEY=value`, пустые строки, `# комментарии` и значения * в кавычках. Никакой интерполяции переменных — она не нужна для * плоского списка ключей этого плагина и добавляет риск инъекции. * * @param string $contents Содержимое файла. * @return array */ public static function parse( string $contents ): array { $values = array(); foreach ( preg_split( '/\R/', $contents ) ?: array() as $line ) { $line = trim( $line ); // Строки `= 2 && $first === $last && ( '"' === $first || "'" === $first ) ) { return trim( substr( $value, 1, -1 ) ); } return trim( $value ); } }