File manager - Edit - /home/ferretapmx/public_html/plugins/system/helixultimate/vendor/scssphp/scssphp/src/Function/ColorFunctions.php
Back
<?php /** * SCSSPHP * * @copyright 2012-2020 Leaf Corcoran * * @license http://opensource.org/licenses/MIT MIT * * @link http://scssphp.github.io/scssphp */ namespace ScssPhp\ScssPhp\Function; use ScssPhp\ScssPhp\Deprecation; use ScssPhp\ScssPhp\Exception\SassScriptException; use ScssPhp\ScssPhp\Util\IterableUtil; use ScssPhp\ScssPhp\Util\NumberUtil; use ScssPhp\ScssPhp\Util\StringUtil; use ScssPhp\ScssPhp\Value\ColorFormatEnum; use ScssPhp\ScssPhp\Value\ListSeparator; use ScssPhp\ScssPhp\Value\SassArgumentList; use ScssPhp\ScssPhp\Value\SassColor; use ScssPhp\ScssPhp\Value\SassNumber; use ScssPhp\ScssPhp\Value\SassString; use ScssPhp\ScssPhp\Value\Value; use ScssPhp\ScssPhp\Warn; /** * @internal */ class ColorFunctions { /** * @param list<Value> $arguments */ public static function rgb(array $arguments): Value { return self::rgbImpl('rgb', $arguments); } /** * @param list<Value> $arguments */ public static function rgbTwoArgs(array $arguments): Value { return self::rgbTwoArgsImpl('rgb', $arguments); } /** * @param list<Value> $arguments */ public static function rgbOneArgs(array $arguments): Value { $parsed = self::parseChannels('rgb', ['$red', '$green', '$blue'], $arguments[0]); return $parsed instanceof SassString ? $parsed : self::rgbImpl('rgb', $parsed); } /** * @param list<Value> $arguments */ public static function rgba(array $arguments): Value { return self::rgbImpl('rgba', $arguments); } /** * @param list<Value> $arguments */ public static function rgbaTwoArgs(array $arguments): Value { return self::rgbTwoArgsImpl('rgba', $arguments); } /** * @param list<Value> $arguments */ public static function rgbaOneArgs(array $arguments): Value { $parsed = self::parseChannels('rgba', ['$red', '$green', '$blue'], $arguments[0]); return $parsed instanceof SassString ? $parsed : self::rgbImpl('rgba', $parsed); } /** * @param list<Value> $arguments */ public static function invert(array $arguments): Value { $weight = $arguments[1]->assertNumber('weight'); if ($arguments[0] instanceof SassNumber || $arguments[0]->isSpecialNumber()) { if ($weight->getValue() !== 100.0 || !$weight->hasUnit('%')) { throw new SassScriptException('Only one argument may be passed to the plain-CSS invert() function.'); } // Use the native CSS `invert` filter function. return self::functionString('invert', [$arguments[0]]); } $color = $arguments[0]->assertColor('color'); $inverse = $color->changeRgb(255 - $color->getRed(), 255 - $color->getGreen(), 255 - $color->getBlue()); return self::mixColors($inverse, $color, $weight); } /** * @param list<Value> $arguments */ public static function hsl(array $arguments): Value { return self::hslImpl('hsl', $arguments); } /** * @param list<Value> $arguments */ public static function hslTwoArgs(array $arguments): Value { // hsl(123, var(--foo)) is valid CSS because --foo might be `10%, 20%` and // functions are parsed after variable substitution. if ($arguments[0]->isVar() || $arguments[1]->isVar()) { return self::functionString('hsl', $arguments); } throw new SassScriptException('Missing argument $lightness.'); } /** * @param list<Value> $arguments */ public static function hslOneArgs(array $arguments): Value { $parsed = self::parseChannels('hsl', ['$hue', '$saturation', '$lightness'], $arguments[0]); return $parsed instanceof SassString ? $parsed : self::hslImpl('hsl', $parsed); } /** * @param list<Value> $arguments */ public static function hsla(array $arguments): Value { return self::hslImpl('hsla', $arguments); } /** * @param list<Value> $arguments */ public static function hslaTwoArgs(array $arguments): Value { // hsl(123, var(--foo)) is valid CSS because --foo might be `10%, 20%` and // functions are parsed after variable substitution. if ($arguments[0]->isVar() || $arguments[1]->isVar()) { return self::functionString('hsla', $arguments); } throw new SassScriptException('Missing argument $lightness.'); } /** * @param list<Value> $arguments */ public static function hslaOneArgs(array $arguments): Value { $parsed = self::parseChannels('hsla', ['$hue', '$saturation', '$lightness'], $arguments[0]); return $parsed instanceof SassString ? $parsed : self::hslImpl('hsla', $parsed); } /** * @param list<Value> $arguments */ public static function grayscale(array $arguments): Value { if ($arguments[0] instanceof SassNumber || $arguments[0]->isSpecialNumber()) { // Use the native CSS `grayscale` filter function. return self::functionString('grayscale', $arguments); } $color = $arguments[0]->assertColor('color'); return $color->changeHsl(saturation: 0); } /** * @param list<Value> $arguments */ public static function adjustHue(array $arguments): Value { $color = $arguments[0]->assertColor('color'); $degrees = self::angleValue($arguments[1], 'degrees'); return $color->changeHsl(hue: $color->getHue() + $degrees); } /** * @param list<Value> $arguments */ public static function lighten(array $arguments): Value { $color = $arguments[0]->assertColor('color'); $amount = $arguments[1]->assertNumber('amount'); return $color->changeHsl(lightness: NumberUtil::clamp($color->getLightness() + $amount->valueInRange(0, 100, 'amount'), 0, 100)); } /** * @param list<Value> $arguments */ public static function darken(array $arguments): Value { $color = $arguments[0]->assertColor('color'); $amount = $arguments[1]->assertNumber('amount'); return $color->changeHsl(lightness: NumberUtil::clamp($color->getLightness() - $amount->valueInRange(0, 100, 'amount'), 0, 100)); } /** * @param list<Value> $arguments */ public static function saturateCss(array $arguments): Value { if ($arguments[0] instanceof SassNumber || $arguments[0]->isSpecialNumber()) { // Use the native CSS `saturate` filter function. return self::functionString('saturate', $arguments); } $number = $arguments[0]->assertNumber('amount'); return new SassString('saturate(' . $number->toCssString() . ')', false); } /** * @param list<Value> $arguments */ public static function saturate(array $arguments): Value { $color = $arguments[0]->assertColor('color'); $amount = $arguments[1]->assertNumber('amount'); return $color->changeHsl(saturation: NumberUtil::clamp($color->getSaturation() + $amount->valueInRange(0, 100, 'amount'), 0, 100)); } /** * @param list<Value> $arguments */ public static function desaturate(array $arguments): Value { $color = $arguments[0]->assertColor('color'); $amount = $arguments[1]->assertNumber('amount'); return $color->changeHsl(saturation: NumberUtil::clamp($color->getSaturation() - $amount->valueInRange(0, 100, 'amount'), 0, 100)); } /** * @param list<Value> $arguments */ public static function alpha(array $arguments): Value { $argument = $arguments[0]; if ($argument instanceof SassString && !$argument->hasQuotes() && preg_match('/^[a-zA-Z]+\s*=/', $argument->getText())) { // Support the proprietary Microsoft alpha() function. return self::functionString('alpha', $arguments); } $color = $arguments[0]->assertColor('color'); return SassNumber::create($color->getAlpha()); } /** * @param list<Value> $arguments */ public static function alphaMicrosoft(array $arguments): Value { $argList = $arguments[0]->asList(); $argumentCount = \count($argList); if ($argumentCount > 0 && IterableUtil::every($argList, fn($argument) => $argument instanceof SassString && !$argument->hasQuotes() && preg_match('/^[a-zA-Z]+\s*=/', $argument->getText()))) { // Support the proprietary Microsoft alpha() function. return self::functionString('alpha', $arguments); } \assert($argumentCount !== 1); if ($argumentCount === 0) { throw new SassScriptException('Missing argument $color.'); } throw new SassScriptException("Only 1 argument allowed, but $argumentCount were passed."); } /** * @param list<Value> $arguments */ public static function opacity(array $arguments): Value { if ($arguments[0] instanceof SassNumber || $arguments[0]->isSpecialNumber()) { // Use the native CSS `opacity` filter function. return self::functionString('opacity', $arguments); } $color = $arguments[0]->assertColor('color'); return SassNumber::create($color->getAlpha()); } /** * @param list<Value> $arguments */ public static function red(array $arguments): Value { return SassNumber::create($arguments[0]->assertColor('color')->getRed()); } /** * @param list<Value> $arguments */ public static function green(array $arguments): Value { return SassNumber::create($arguments[0]->assertColor('color')->getGreen()); } /** * @param list<Value> $arguments */ public static function blue(array $arguments): Value { return SassNumber::create($arguments[0]->assertColor('color')->getBlue()); } /** * @param list<Value> $arguments */ public static function mix(array $arguments): Value { $color1 = $arguments[0]->assertColor('color1'); $color2 = $arguments[1]->assertColor('color2'); $weight = $arguments[2]->assertNumber('weight'); return self::mixColors($color1, $color2, $weight); } /** * @param list<Value> $arguments */ public static function hue(array $arguments): Value { return SassNumber::create($arguments[0]->assertColor('color')->getHue(), 'deg'); } /** * @param list<Value> $arguments */ public static function saturation(array $arguments): Value { return SassNumber::create($arguments[0]->assertColor('color')->getSaturation(), '%'); } /** * @param list<Value> $arguments */ public static function lightness(array $arguments): Value { return SassNumber::create($arguments[0]->assertColor('color')->getLightness(), '%'); } /** * @param list<Value> $arguments */ public static function complement(array $arguments): Value { $color = $arguments[0]->assertColor('color'); return $color->changeHsl(hue: $color->getHue() + 180); } /** * @param list<Value> $arguments */ public static function adjust(array $arguments): Value { return self::updateComponents($arguments, adjust: true); } /** * @param list<Value> $arguments */ public static function scale(array $arguments): Value { return self::updateComponents($arguments, scale: true); } /** * @param list<Value> $arguments */ public static function change(array $arguments): Value { return self::updateComponents($arguments, change: true); } /** * @param list<Value> $arguments */ public static function ieHexStr(array $arguments): Value { $color = $arguments[0]->assertColor('color'); return new SassString('#' . self::hexString(NumberUtil::fuzzyRound($color->getAlpha() * 255)) . self::hexString($color->getRed()) . self::hexString($color->getGreen()) . self::hexString($color->getBlue()), false); } private static function hexString(int $component): string { return strtoupper(str_pad(dechex($component), 2, '0', STR_PAD_LEFT)); } /** * @param list<Value> $arguments */ private static function updateComponents(array $arguments, bool $change = false, bool $adjust = false, bool $scale = false): SassColor { \assert(\count(array_filter([$change, $adjust, $scale])) === 1); $color = $arguments[0]->assertColor('color'); $argumentList = $arguments[1]; \assert($argumentList instanceof SassArgumentList); if (\count($argumentList->asList()) > 0) { throw new SassScriptException('Only one positional argument is allowed. All other arguments must be passed by name.'); } $keywords = $argumentList->getKeywords(); $getParam = function (string $name, float $max, bool $checkPercent = false, bool $assertPercent = false, bool $checkUnitless = false) use (&$keywords, $change, $scale): ?float { $number = ($keywords[$name] ?? null)?->assertNumber($name); unset($keywords[$name]); if ($number === null) { return null; } if (!$scale && $checkUnitless) { if ($number->hasUnits()) { Warn::forDeprecation( <<<TXT \$$name: Passing a number with unit {$number->getUnitString()} is deprecated. To preserve current behavior: {$number->unitSuggestion($name)} More info: https://sass-lang.com/d/function-units TXT, Deprecation::functionUnits ); } } if (!$scale && $checkPercent) { self::checkPercent($number, $name); } if ($scale || $assertPercent) { $number->assertUnit('%', $name); } if ($scale) { $max = 100; } return $scale || $assertPercent ? $number->valueInRange($change ? 0 : -$max, $max, $name) : $number->valueInRangeWithUnit($change ? 0 : -$max, $max, $name, $checkPercent ? '%' : ''); }; $alpha = $getParam('alpha', 1, checkUnitless: true); $red = $getParam('red', 255); $green = $getParam('green', 255); $blue = $getParam('blue', 255); if ($scale) { $hue = null; } else { $hueValue = $keywords['hue'] ?? null; unset($keywords['hue']); $hue = $hueValue === null ? null : self::angleValue($hueValue, 'hue'); } $saturation = $getParam('saturation', 100, checkPercent: true); $lightness = $getParam('lightness', 100, checkPercent: true); $whiteness = $getParam('whiteness', 100, assertPercent: true); $blackness = $getParam('blackness', 100, assertPercent: true); if (\count($keywords) > 0) { throw new SassScriptException(sprintf( 'No %s named %s.', StringUtil::pluralize('argument', \count($keywords)), StringUtil::toSentence(array_map(fn($name) => "\$$name", array_keys($keywords)), 'or') )); } $hasRgb = $red !== null || $green !== null || $blue !== null; $hasSL = $saturation !== null || $lightness !== null; $hasWB = $whiteness !== null || $blackness !== null; if ($hasRgb && ($hasSL || $hasWB || $hue !== null)) { $format = $hasWB ? 'HWB' : 'HSL'; throw new SassScriptException("RGB parameters may not be passed along with $format parameters."); } if ($hasSL && $hasWB) { throw new SassScriptException('HSL parameters may not be passed along with HWB parameters.'); } $updateValue = function (float $current, ?float $param, float $max) use ($change, $adjust): float { if ($param === null) { return $current; } if ($change) { return $param; } if ($adjust) { return NumberUtil::clamp($current + $param, 0, $max); } return $current + ($param > 0 ? $max - $current : $current) * $param / 100; }; $updateRgb = function (int $current, ?float $param) use ($updateValue): int { return NumberUtil::fuzzyRound($updateValue($current, $param, 255)); }; if ($hasRgb) { return $color->changeRgb( $updateRgb($color->getRed(), $red), $updateRgb($color->getGreen(), $green), $updateRgb($color->getBlue(), $blue), $updateValue($color->getAlpha(), $alpha, 1) ); } if ($hasWB) { return $color->changeHwb( $change ? $hue : $color->getHue() + ($hue ?? 0), $updateValue($color->getWhiteness(), $whiteness, 100), $updateValue($color->getBlackness(), $blackness, 100), $updateValue($color->getAlpha(), $alpha, 1) ); } if ($hue !== null || $hasSL) { return $color->changeHsl( $change ? $hue : $color->getHue() + ($hue ?? 0), $updateValue($color->getSaturation(), $saturation, 100), $updateValue($color->getLightness(), $lightness, 100), $updateValue($color->getAlpha(), $alpha, 1) ); } if ($alpha !== null) { return $color->changeAlpha($updateValue($color->getAlpha(), $alpha, 1)); } return $color; } /** * Returns a string representation of $name called with $arguments, as though * it were a plain CSS function. * * @param Value[] $arguments */ private static function functionString(string $name, array $arguments): SassString { return new SassString($name . '(' . implode(', ', array_map(fn(Value $argument) => $argument->toCssString(), $arguments)) . ')', false); } /** * @param list<Value> $arguments */ private static function rgbImpl(string $name, array $arguments): Value { $alpha = $arguments[3] ?? null; if ($arguments[0]->isSpecialNumber() || $arguments[1]->isSpecialNumber() || $arguments[2]->isSpecialNumber() || ($alpha?->isSpecialNumber() ?? false)) { return self::functionString($name, $arguments); } $red = $arguments[0]->assertNumber('red'); $green = $arguments[1]->assertNumber('green'); $blue = $arguments[2]->assertNumber('blue'); return SassColor::rgbInternal( NumberUtil::fuzzyRound(self::percentageOrUnitless($red, 255, 'red')), NumberUtil::fuzzyRound(self::percentageOrUnitless($green, 255, 'green')), NumberUtil::fuzzyRound(self::percentageOrUnitless($blue, 255, 'blue')), $alpha !== null ? self::percentageOrUnitless($alpha->assertNumber('alpha'), 1, 'alpha') : 1, ColorFormatEnum::rgbFunction ); } /** * @param list<Value> $arguments */ private static function rgbTwoArgsImpl(string $name, array $arguments): Value { // rgba(var(--foo), 0.5) is valid CSS because --foo might be `123, 456, 789` // and functions are parsed after variable substitution. if ($arguments[0]->isVar() || (!$arguments[0] instanceof SassColor && $arguments[1]->isVar())) { return self::functionString($name, $arguments); } if ($arguments[1]->isSpecialNumber()) { $color = $arguments[0]->assertColor('color'); return new SassString("$name({$color->getRed()}, {$color->getGreen()}, {$color->getBlue()}, {$arguments[1]->toCssString()})", false); } $color = $arguments[0]->assertColor('color'); $alpha = $arguments[1]->assertNumber('alpha'); return $color->changeAlpha(self::percentageOrUnitless($alpha, 1, 'alpha')); } /** * @param list<Value> $arguments */ private static function hslImpl(string $name, array $arguments): Value { $alpha = $arguments[3] ?? null; if ($arguments[0]->isSpecialNumber() || $arguments[1]->isSpecialNumber() || $arguments[2]->isSpecialNumber() || ($alpha?->isSpecialNumber() ?? false)) { return self::functionString($name, $arguments); } $hue = self::angleValue($arguments[0], 'hue'); $saturation = $arguments[1]->assertNumber('saturation'); $lightness = $arguments[2]->assertNumber('lightness'); self::checkPercent($saturation, 'saturation'); self::checkPercent($lightness, 'lightness'); return SassColor::hslInternal( $hue, NumberUtil::clamp($saturation->getValue(), 0, 100), NumberUtil::clamp($lightness->getValue(), 0, 100), $alpha !== null ? self::percentageOrUnitless($alpha->assertNumber('alpha'), 1, 'alpha') : 1, ColorFormatEnum::hslFunction ); } /** * Asserts that $angle is a number and returns its value in degrees. * * Prints a deprecation warning if $angle has a non-angle unit. */ private static function angleValue(Value $angleValue, string $name): float { $angle = $angleValue->assertNumber($name); if ($angle->compatibleWithUnit('deg')) { return $angle->coerceValueToUnit('deg'); } Warn::forDeprecation( <<<TXT \$$name: Passing a unit other than deg ($angle) is deprecated. To preserve current behavior: {$angle->unitSuggestion($name)} See https://sass-lang.com/d/function-units TXT, Deprecation::functionUnits ); return $angle->getValue(); } private static function checkPercent(SassNumber $number, string $name): void { if ($number->hasUnit('%')) { return; } Warn::forDeprecation( <<<TXT \$$name: Passing a number without unit % ($number) is deprecated. To preserve current behavior: {$number->unitSuggestion($name, '%')} More info: https://sass-lang.com/d/function-units TXT, Deprecation::functionUnits ); } /** * @param list<string> $argumentNames * * @return SassString|list<Value> */ private static function parseChannels(string $name, array $argumentNames, Value $channels): SassString|array { if ($channels->isVar()) { return self::functionString($name, [$channels]); } $originalChannels = $channels; $alphaFromSlashList = null; if ($channels->getSeparator() === ListSeparator::SLASH) { $list = $channels->asList(); if (\count($list) !== 2) { throw new SassScriptException(sprintf( 'Only 2 slash-separated elements allowed, but %s %s passed.', \count($list), StringUtil::pluralize('was', \count($list), 'were') )); } $channels = $list[0]; $alphaFromSlashList = $list[1]; if (!$alphaFromSlashList->isSpecialNumber()) { $alphaFromSlashList->assertNumber('alpha'); } if ($list[0]->isVar()) { return self::functionString($name, [$originalChannels]); } } $isCommaSeparated = $channels->getSeparator() === ListSeparator::COMMA; $isBracketed = $channels->hasBrackets(); if ($isCommaSeparated || $isBracketed) { $buffer = '$channels must be'; if ($isBracketed) { $buffer .= ' an unbracketed'; } if ($isCommaSeparated) { $buffer .= $isBracketed ? ',' : ' a'; $buffer .= ' space-separated'; } $buffer .= ' list.'; throw new SassScriptException($buffer); } $list = $channels->asList(); if (\count($list) >= 2 && $list[0] instanceof SassString && !$list[0]->hasQuotes() && StringUtil::equalsIgnoreCase($list[0]->getText(), 'from')) { return self::functionString($name, [$originalChannels]); } if (\count($list) > 3) { throw new SassScriptException(sprintf( 'Only 3 elements allowed, but %s were passed.', \count($list) )); } if (\count($list) < 3) { if (IterableUtil::any($list, fn (Value $value) => $value->isVar()) || (\count($list) > 0 && self::isVarSlash($list[0]))) { return self::functionString($name, [$originalChannels]); } $argument = $argumentNames[\count($list)]; throw new SassScriptException("Missing element $argument."); } if ($alphaFromSlashList !== null) { return [...$list, $alphaFromSlashList]; } if ($list[2] instanceof SassNumber && $list[2]->getAsSlash() !== null) { [$channel3, $alpha] = $list[2]->getAsSlash(); return [$list[0], $list[1], $channel3, $alpha]; } if ($list[2] instanceof SassString && !$list[2]->hasQuotes() && str_contains($list[2]->getText(), '/')) { return self::functionString($name, [$channels]); } return $list; } /** * Returns whether $value is an unquoted string that start with `var(` and * contains `/`. */ private static function isVarSlash(Value $value): bool { return $value instanceof SassString && $value->hasQuotes() && StringUtil::startsWithIgnoreCase($value->getText(), 'var(') && str_contains($value->getText(), '/'); } /** * Asserts that $number is a percentage or has no units, and normalizes the * value. * * If $number has no units, its value is clamped to be greater than `0` or * less than $max and returned. If $number is a percentage, it's scaled to be * within `0` and $max. Otherwise, this throws a {@see SassScriptException}. * * $name is used to identify the argument in the error message. */ private static function percentageOrUnitless(SassNumber $number, float $max, string $name): float { if (!$number->hasUnits()) { $value = $number->getValue(); } elseif ($number->hasUnit('%')) { $value = $max * $number->getValue() / 100; } else { throw new SassScriptException("\$$name: Expected $number to have unit \"%\" or no units."); } return NumberUtil::clamp($value, 0, $max); } private static function mixColors(SassColor $color1, SassColor $color2, SassNumber $weight): SassColor { self::checkPercent($weight, 'weight'); // This algorithm factors in both the user-provided weight (w) and the // difference between the alpha values of the two colors (a) to decide how // to perform the weighted average of the two RGB values. // // It works by first normalizing both parameters to be within [-1, 1], where // 1 indicates "only use color1", -1 indicates "only use color2", and all // values in between indicated a proportionately weighted average. // // Once we have the normalized variables w and a, we apply the formula // (w + a)/(1 + w*a) to get the combined weight (in [-1, 1]) of color1. This // formula has two especially nice properties: // // * When either w or a are -1 or 1, the combined weight is also that // number (cases where w * a == -1 are undefined, and handled as a // special case). // // * When a is 0, the combined weight is w, and vice versa. // // Finally, the weight of color1 is renormalized to be within [0, 1] and the // weight of color2 is given by 1 minus the weight of color1. $weightScale = $weight->valueInRange(0, 100, 'weight') / 100; $normalizedWeight = $weightScale * 2 - 1; $alphaDistance = $color1->getAlpha() - $color2->getAlpha(); $combinedWeight1 = $normalizedWeight * $alphaDistance == -1 ? $normalizedWeight : ($normalizedWeight + $alphaDistance) / (1 + $normalizedWeight * $alphaDistance); $weight1 = ($combinedWeight1 + 1) / 2; $weight2 = 1 - $weight1; return SassColor::rgb( NumberUtil::fuzzyRound($color1->getRed() * $weight1 + $color2->getRed() * $weight2), NumberUtil::fuzzyRound($color1->getGreen() * $weight1 + $color2->getGreen() * $weight2), NumberUtil::fuzzyRound($color1->getBlue() * $weight1 + $color2->getBlue() * $weight2), $color1->getAlpha() * $weightScale + $color2->getAlpha() * (1 - $weightScale) ); } /** * @param list<Value> $arguments */ public static function opacify(array $arguments): SassColor { $color = $arguments[0]->assertColor('color'); $amount = $arguments[1]->assertNumber('amount'); return $color->changeAlpha(NumberUtil::clamp($color->getAlpha() + $amount->valueInRangeWithUnit(0, 1, 'amount', ''), 0, 1)); } /** * @param list<Value> $arguments */ public static function transparentize(array $arguments): SassColor { $color = $arguments[0]->assertColor('color'); $amount = $arguments[1]->assertNumber('amount'); return $color->changeAlpha(NumberUtil::clamp($color->getAlpha() - $amount->valueInRangeWithUnit(0, 1, 'amount', ''), 0, 1)); } }
| ver. 1.4 |
Github
|
.
| PHP 8.2.32 | Generation time: 0.06 |
proxy
|
phpinfo
|
Settings