[' . $currencyCodes . ']) *))$~ui';
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php
index 266f1b2bb..59cc3e3d2 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Operands/StructuredReference.php
@@ -190,8 +190,8 @@ final class StructuredReference implements Operand
{
if ($columnName !== '') {
$cellReference = $columnId . $cell->getRow();
- $pattern1 = '/\[' . preg_quote($columnName) . '\]/miu';
- $pattern2 = '/@' . preg_quote($columnName) . '/miu';
+ $pattern1 = '/\[' . preg_quote($columnName, '/') . '\]/miu';
+ $pattern2 = '/@' . preg_quote($columnName, '/') . '/miu';
if (preg_match($pattern1, $reference) === 1) {
$reference = preg_replace($pattern1, $cellReference, $reference);
} elseif (preg_match($pattern2, $reference) === 1) {
@@ -328,7 +328,7 @@ final class StructuredReference implements Operand
$cellFrom = "{$columnId}{$startRow}";
$cellTo = "{$columnId}{$endRow}";
$cellReference = ($cellFrom === $cellTo) ? $cellFrom : "{$cellFrom}:{$cellTo}";
- $pattern = '/\[' . preg_quote($columnName) . '\]/mui';
+ $pattern = '/\[' . preg_quote($columnName, '/') . '\]/mui';
if (preg_match($pattern, $reference) === 1) {
$columnsSelected = true;
$reference = preg_replace($pattern, $cellReference, $reference);
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php
index e3a2bd65c..fdee7503e 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php
@@ -20,28 +20,6 @@ class Engineering
*/
public const EULER = 2.71828182845904523536;
- /**
- * parseComplex.
- *
- * Parses a complex number into its real and imaginary parts, and an I or J suffix
- *
- * @deprecated 1.12.0 No longer used by internal code. Please use the \Complex\Complex class instead
- *
- * @param string $complexNumber The complex number
- *
- * @return mixed[] Indexed on "real", "imaginary" and "suffix"
- */
- public static function parseComplex($complexNumber)
- {
- $complex = new Complex($complexNumber);
-
- return [
- 'real' => $complex->getReal(),
- 'imaginary' => $complex->getImaginary(),
- 'suffix' => $complex->getSuffix(),
- ];
- }
-
/**
* BESSELI.
*
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php
index 691de8b70..f7ec02d43 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/Complex.php
@@ -49,7 +49,7 @@ class Complex
return $e->getMessage();
}
- if (($suffix == 'i') || ($suffix == 'j') || ($suffix == '')) {
+ if (($suffix === 'i') || ($suffix === 'j') || ($suffix === '')) {
$complex = new ComplexObject($realNumber, $imaginary, $suffix);
return (string) $complex;
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php
index 4741f3010..04bf3e506 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ConvertBinary.php
@@ -40,7 +40,7 @@ class ConvertBinary extends ConvertBase
return $e->getMessage();
}
- if (strlen($value) == 10) {
+ if (strlen($value) == 10 && $value[0] === '1') {
// Two's Complement
$value = substr($value, -9);
@@ -91,7 +91,7 @@ class ConvertBinary extends ConvertBase
return $e->getMessage();
}
- if (strlen($value) == 10) {
+ if (strlen($value) == 10 && $value[0] === '1') {
$high2 = substr($value, 0, 2);
$low8 = substr($value, 2);
$xarr = ['00' => '00000000', '01' => '00000001', '10' => 'FFFFFFFE', '11' => 'FFFFFFFF'];
@@ -144,7 +144,7 @@ class ConvertBinary extends ConvertBase
return $e->getMessage();
}
- if (strlen($value) == 10 && substr($value, 0, 1) === '1') { // Two's Complement
+ if (strlen($value) == 10 && $value[0] === '1') { // Two's Complement
return str_repeat('7', 6) . strtoupper(decoct((int) bindec("11$value")));
}
$octVal = (string) decoct((int) bindec($value));
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php
index e2d27bde2..bc425f873 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php
@@ -27,7 +27,7 @@ class HLookup extends LookupBase
*/
public static function lookup($lookupValue, $lookupArray, $indexNumber, $notExactMatch = true)
{
- if (is_array($lookupValue)) {
+ if (is_array($lookupValue) || is_array($indexNumber)) {
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $indexNumber, $notExactMatch);
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php
index edeb1aa8c..badd10573 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/VLookup.php
@@ -26,7 +26,7 @@ class VLookup extends LookupBase
*/
public static function lookup($lookupValue, $lookupArray, $indexNumber, $notExactMatch = true)
{
- if (is_array($lookupValue)) {
+ if (is_array($lookupValue) || is_array($indexNumber)) {
return self::evaluateArrayArgumentsIgnore([self::class, __FUNCTION__], 1, $lookupValue, $lookupArray, $indexNumber, $notExactMatch);
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php
index 1a797c8a2..56b0861c6 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Sum.php
@@ -66,8 +66,8 @@ class Sum
$returnValue += (int) $arg;
} elseif (ErrorValue::isError($arg)) {
return $arg;
- // ignore non-numerics from cell, but fail as literals (except null)
} elseif ($arg !== null && !Functions::isCellValue($k)) {
+ // ignore non-numerics from cell, but fail as literals (except null)
return ExcelError::VALUE();
}
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php
index 519607c08..24ddff2ec 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php
@@ -261,7 +261,7 @@ class Extract
$delimiter = Functions::flattenArray($delimiter);
$quotedDelimiters = array_map(
function ($delimiter) {
- return preg_quote($delimiter ?? '');
+ return preg_quote($delimiter ?? '', '/');
},
$delimiter
);
@@ -270,7 +270,7 @@ class Extract
return '(' . $delimiters . ')';
}
- return '(' . preg_quote($delimiter ?? '') . ')';
+ return '(' . preg_quote($delimiter ?? '', '/') . ')';
}
private static function matchFlags(int $matchMode): string
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php
index 93e728202..57d331663 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php
@@ -129,7 +129,7 @@ class Format
$format = Helpers::extractString($format);
if (!is_numeric($value) && Date::isDateTimeFormatCode($format)) {
- $value = DateTimeExcel\DateValue::fromString($value);
+ $value = DateTimeExcel\DateValue::fromString($value) + DateTimeExcel\TimeValue::fromString($value);
}
return (string) NumberFormat::toFormattedString($value, $format);
@@ -140,7 +140,7 @@ class Format
*
* @return mixed
*/
- private static function convertValue($value)
+ private static function convertValue($value, bool $spacesMeanZero = false)
{
$value = $value ?? 0;
if (is_bool($value)) {
@@ -150,6 +150,12 @@ class Format
throw new CalcExp(ExcelError::VALUE());
}
}
+ if (is_string($value)) {
+ $value = trim($value);
+ if ($spacesMeanZero && $value === '') {
+ $value = 0;
+ }
+ }
return $value;
}
@@ -181,6 +187,9 @@ class Format
'',
trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode())
);
+ if ($numberValue === '') {
+ return ExcelError::VALUE();
+ }
if (is_numeric($numberValue)) {
return (float) $numberValue;
}
@@ -277,7 +286,7 @@ class Format
}
try {
- $value = self::convertValue($value);
+ $value = self::convertValue($value, true);
$decimalSeparator = self::getDecimalSeparator($decimalSeparator);
$groupSeparator = self::getGroupSeparator($groupSeparator);
} catch (CalcExp $e) {
@@ -285,12 +294,12 @@ class Format
}
if (!is_numeric($value)) {
- $decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator) . '/', $value, $matches, PREG_OFFSET_CAPTURE);
+ $decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator, '/') . '/', $value, $matches, PREG_OFFSET_CAPTURE);
if ($decimalPositions > 1) {
return ExcelError::VALUE();
}
- $decimalOffset = array_pop($matches[0])[1]; // @phpstan-ignore-line
- if (strpos($value, $groupSeparator, $decimalOffset) !== false) {
+ $decimalOffset = array_pop($matches[0])[1] ?? null;
+ if ($decimalOffset === null || strpos($value, $groupSeparator, $decimalOffset) !== false) {
return ExcelError::VALUE();
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Text.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Text.php
index 8e6a575a2..b8a730767 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Text.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Text.php
@@ -193,7 +193,7 @@ class Text
if (is_array($delimiter) && count($valueSet) > 1) {
$quotedDelimiters = array_map(
function ($delimiter) {
- return preg_quote($delimiter ?? '');
+ return preg_quote($delimiter ?? '', '/');
},
$valueSet
);
@@ -202,7 +202,7 @@ class Text
return '(' . $delimiters . ')';
}
- return '(' . preg_quote(/** @scrutinizer ignore-type */ Functions::flattenSingleValue($delimiter)) . ')';
+ return '(' . preg_quote(/** @scrutinizer ignore-type */ Functions::flattenSingleValue($delimiter), '/') . ')';
}
private static function matchFlags(bool $matchMode): string
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php
index 1bf73ba82..c0fb38775 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/AdvancedValueBinder.php
@@ -51,8 +51,9 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder
return $this->setImproperFraction($matches, $cell);
}
- $decimalSeparator = preg_quote(StringHelper::getDecimalSeparator());
- $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator());
+ $decimalSeparatorNoPreg = StringHelper::getDecimalSeparator();
+ $decimalSeparator = preg_quote($decimalSeparatorNoPreg, '/');
+ $thousandsSeparator = preg_quote(StringHelper::getThousandsSeparator(), '/');
// Check for percentage
if (preg_match('/^\-?\d*' . $decimalSeparator . '?\d*\s?\%$/', preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value))) {
@@ -64,7 +65,7 @@ class AdvancedValueBinder extends DefaultValueBinder implements IValueBinder
// Convert value to number
$sign = ($matches['PrefixedSign'] ?? $matches['PrefixedSign2'] ?? $matches['PostfixedSign']) ?? null;
$currencyCode = $matches['PrefixedCurrency'] ?? $matches['PostfixedCurrency'];
- $value = (float) ($sign . trim(str_replace([$decimalSeparator, $currencyCode, ' ', '-'], ['.', '', '', ''], preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value)))); // @phpstan-ignore-line
+ $value = (float) ($sign . trim(str_replace([$decimalSeparatorNoPreg, $currencyCode, ' ', '-'], ['.', '', '', ''], preg_replace('/(\d)' . $thousandsSeparator . '(\d)/u', '$1$2', $value)))); // @phpstan-ignore-line
return $this->setCurrency($value, $cell, $currencyCode); // @phpstan-ignore-line
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php
index a53294e53..e9e41d7c7 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/Cell.php
@@ -71,6 +71,9 @@ class Cell
*/
private $formulaAttributes;
+ /** @var IgnoredErrors */
+ private $ignoredErrors;
+
/**
* Update the cell into the cell collection.
*
@@ -119,6 +122,7 @@ class Cell
} elseif (self::getValueBinder()->bindValue($this, $value) === false) {
throw new Exception('Value could not be bound to cell.');
}
+ $this->ignoredErrors = new IgnoredErrors();
}
/**
@@ -391,7 +395,9 @@ class Cell
}
throw new \PhpOffice\PhpSpreadsheet\Calculation\Exception(
- $this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage()
+ $this->getWorksheet()->getTitle() . '!' . $this->getCoordinate() . ' -> ' . $ex->getMessage(),
+ $ex->getCode(),
+ $ex
);
}
@@ -794,4 +800,9 @@ class Cell
{
return (string) $this->getValue();
}
+
+ public function getIgnoredErrors(): IgnoredErrors
+ {
+ return $this->ignoredErrors;
+ }
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php
index 0e395a7ff..692f316ec 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/DataValidator.php
@@ -20,7 +20,7 @@ class DataValidator
*/
public function isValid(Cell $cell)
{
- if (!$cell->hasDataValidation()) {
+ if (!$cell->hasDataValidation() || $cell->getDataValidation()->getType() === DataValidation::TYPE_NONE) {
return true;
}
@@ -31,13 +31,55 @@ class DataValidator
return false;
}
- // TODO: write check on all cases
- switch ($dataValidation->getType()) {
- case DataValidation::TYPE_LIST:
- return $this->isValueInList($cell);
+ $returnValue = false;
+ $type = $dataValidation->getType();
+ if ($type === DataValidation::TYPE_LIST) {
+ $returnValue = $this->isValueInList($cell);
+ } elseif ($type === DataValidation::TYPE_WHOLE) {
+ if (!is_numeric($cellValue) || fmod((float) $cellValue, 1) != 0) {
+ $returnValue = false;
+ } else {
+ $returnValue = $this->numericOperator($dataValidation, (int) $cellValue);
+ }
+ } elseif ($type === DataValidation::TYPE_DECIMAL || $type === DataValidation::TYPE_DATE || $type === DataValidation::TYPE_TIME) {
+ if (!is_numeric($cellValue)) {
+ $returnValue = false;
+ } else {
+ $returnValue = $this->numericOperator($dataValidation, (float) $cellValue);
+ }
+ } elseif ($type === DataValidation::TYPE_TEXTLENGTH) {
+ $returnValue = $this->numericOperator($dataValidation, mb_strlen((string) $cellValue));
}
- return false;
+ return $returnValue;
+ }
+
+ /** @param float|int $cellValue */
+ private function numericOperator(DataValidation $dataValidation, $cellValue): bool
+ {
+ $operator = $dataValidation->getOperator();
+ $formula1 = $dataValidation->getFormula1();
+ $formula2 = $dataValidation->getFormula2();
+ $returnValue = false;
+ if ($operator === DataValidation::OPERATOR_BETWEEN) {
+ $returnValue = $cellValue >= $formula1 && $cellValue <= $formula2;
+ } elseif ($operator === DataValidation::OPERATOR_NOTBETWEEN) {
+ $returnValue = $cellValue < $formula1 || $cellValue > $formula2;
+ } elseif ($operator === DataValidation::OPERATOR_EQUAL) {
+ $returnValue = $cellValue == $formula1;
+ } elseif ($operator === DataValidation::OPERATOR_NOTEQUAL) {
+ $returnValue = $cellValue != $formula1;
+ } elseif ($operator === DataValidation::OPERATOR_LESSTHAN) {
+ $returnValue = $cellValue < $formula1;
+ } elseif ($operator === DataValidation::OPERATOR_LESSTHANOREQUAL) {
+ $returnValue = $cellValue <= $formula1;
+ } elseif ($operator === DataValidation::OPERATOR_GREATERTHAN) {
+ $returnValue = $cellValue > $formula1;
+ } elseif ($operator === DataValidation::OPERATOR_GREATERTHANOREQUAL) {
+ $returnValue = $cellValue >= $formula1;
+ }
+
+ return $returnValue;
}
/**
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IgnoredErrors.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IgnoredErrors.php
new file mode 100644
index 000000000..ee4b51562
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IgnoredErrors.php
@@ -0,0 +1,66 @@
+numberStoredAsText = $value;
+
+ return $this;
+ }
+
+ public function getNumberStoredAsText(): bool
+ {
+ return $this->numberStoredAsText;
+ }
+
+ public function setFormula(bool $value): self
+ {
+ $this->formula = $value;
+
+ return $this;
+ }
+
+ public function getFormula(): bool
+ {
+ return $this->formula;
+ }
+
+ public function setTwoDigitTextYear(bool $value): self
+ {
+ $this->twoDigitTextYear = $value;
+
+ return $this;
+ }
+
+ public function getTwoDigitTextYear(): bool
+ {
+ return $this->twoDigitTextYear;
+ }
+
+ public function setEvalError(bool $value): self
+ {
+ $this->evalError = $value;
+
+ return $this;
+ }
+
+ public function getEvalError(): bool
+ {
+ return $this->evalError;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/CellReferenceHelper.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/CellReferenceHelper.php
index 24694d5c6..0e164543b 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/CellReferenceHelper.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/CellReferenceHelper.php
@@ -118,7 +118,7 @@ class CellReferenceHelper
{
$newColumn = Coordinate::stringFromColumnIndex(min($newColumnIndex + $this->numberOfColumns, AddressRange::MAX_COLUMN_INT));
- return $absoluteColumn . $newColumn;
+ return "{$absoluteColumn}{$newColumn}";
}
protected function updateRowReference(int $newRowIndex, string $absoluteRow): string
@@ -126,6 +126,6 @@ class CellReferenceHelper
$newRow = $newRowIndex + $this->numberOfRows;
$newRow = ($newRow > AddressRange::MAX_ROW) ? AddressRange::MAX_ROW : $newRow;
- return $absoluteRow . (string) $newRow;
+ return "{$absoluteRow}{$newRow}";
}
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php
index ade7b99d8..3d4813468 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Axis.php
@@ -52,6 +52,9 @@ class Axis extends Properties
/** @var string */
private $axisType = '';
+ /** @var ?AxisText */
+ private $axisText;
+
/**
* Axis Options.
*
@@ -88,6 +91,9 @@ class Axis extends Properties
Properties::FORMAT_CODE_DATE_ISO8601,
];
+ /** @var bool */
+ private $noFill = false;
+
/**
* Get Series Data Type.
*
@@ -183,6 +189,14 @@ class Axis extends Properties
*/
public function getAxisOptionsProperty($property)
{
+ if ($property === 'textRotation') {
+ if ($this->axisText !== null) {
+ if ($this->axisText->getRotation() !== null) {
+ return (string) $this->axisText->getRotation();
+ }
+ }
+ }
+
return $this->axisOptions[$property];
}
@@ -295,4 +309,28 @@ class Axis extends Properties
return $this;
}
+
+ public function getAxisText(): ?AxisText
+ {
+ return $this->axisText;
+ }
+
+ public function setAxisText(?AxisText $axisText): self
+ {
+ $this->axisText = $axisText;
+
+ return $this;
+ }
+
+ public function setNoFill(bool $noFill): self
+ {
+ $this->noFill = $noFill;
+
+ return $this;
+ }
+
+ public function getNoFill(): bool
+ {
+ return $this->noFill;
+ }
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php
new file mode 100644
index 000000000..cd9ba2ce2
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/AxisText.php
@@ -0,0 +1,56 @@
+font = new Font();
+ $this->font->setSize(null, true);
+ }
+
+ public function setRotation(?int $rotation): self
+ {
+ $this->rotation = $rotation;
+
+ return $this;
+ }
+
+ public function getRotation(): ?int
+ {
+ return $this->rotation;
+ }
+
+ public function getFillColorObject(): ChartColor
+ {
+ $fillColor = $this->font->getChartColor();
+ if ($fillColor === null) {
+ $fillColor = new ChartColor();
+ $this->font->setChartColorFromObject($fillColor);
+ }
+
+ return $fillColor;
+ }
+
+ public function getFont(): Font
+ {
+ return $this->font;
+ }
+
+ public function setFont(Font $font): self
+ {
+ $this->font = $font;
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php
index 2ff22a344..38c69a463 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Chart.php
@@ -150,6 +150,12 @@ class Chart
/** @var bool */
private $roundedCorners = false;
+ /** @var GridLines */
+ private $borderLines;
+
+ /** @var ChartColor */
+ private $fillColor;
+
/**
* Create a new Chart.
* majorGridlines and minorGridlines are deprecated, moved to Axis.
@@ -176,6 +182,8 @@ class Chart
if ($minorGridlines !== null) {
$this->yAxis->setMinorGridlines($minorGridlines);
}
+ $this->fillColor = new ChartColor();
+ $this->borderLines = new GridLines();
}
/**
@@ -786,4 +794,21 @@ class Chart
return $this;
}
+
+ public function getBorderLines(): GridLines
+ {
+ return $this->borderLines;
+ }
+
+ public function setBorderLines(GridLines $borderLines): self
+ {
+ $this->borderLines = $borderLines;
+
+ return $this;
+ }
+
+ public function getFillColor(): ChartColor
+ {
+ return $this->fillColor;
+ }
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php
index 0018d79d7..ac36c25c9 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php
@@ -2,6 +2,8 @@
namespace PhpOffice\PhpSpreadsheet\Chart;
+use PhpOffice\PhpSpreadsheet\Style\Font;
+
class Layout
{
/**
@@ -127,8 +129,11 @@ class Layout
/** @var ?ChartColor */
private $labelBorderColor;
- /** @var ?ChartColor */
- private $labelFontColor;
+ /** @var ?Font */
+ private $labelFont;
+
+ /** @var Properties */
+ private $labelEffects;
/**
* Create a new Layout.
@@ -172,7 +177,18 @@ class Layout
$this->initBoolean($layout, 'numFmtLinked');
$this->initColor($layout, 'labelFillColor');
$this->initColor($layout, 'labelBorderColor');
- $this->initColor($layout, 'labelFontColor');
+ $labelFont = $layout['labelFont'] ?? null;
+ if ($labelFont instanceof Font) {
+ $this->labelFont = $labelFont;
+ }
+ $labelFontColor = $layout['labelFontColor'] ?? null;
+ if ($labelFontColor instanceof ChartColor) {
+ $this->setLabelFontColor($labelFontColor);
+ }
+ $labelEffects = $layout['labelEffects'] ?? null;
+ if ($labelEffects instanceof Properties) {
+ $this->labelEffects = $labelEffects;
+ }
}
private function initBoolean(array $layout, string $name): void
@@ -493,14 +509,32 @@ class Layout
return $this;
}
+ public function getLabelFont(): ?Font
+ {
+ return $this->labelFont;
+ }
+
+ public function getLabelEffects(): ?Properties
+ {
+ return $this->labelEffects;
+ }
+
public function getLabelFontColor(): ?ChartColor
{
- return $this->labelFontColor;
+ if ($this->labelFont === null) {
+ return null;
+ }
+
+ return $this->labelFont->getChartColor();
}
public function setLabelFontColor(?ChartColor $chartColor): self
{
- $this->labelFontColor = $chartColor;
+ if ($this->labelFont === null) {
+ $this->labelFont = new Font();
+ $this->labelFont->setSize(null, true);
+ }
+ $this->labelFont->setChartColorFromObject($chartColor);
return $this;
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php
index edd87015a..04040aed6 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php
@@ -48,6 +48,15 @@ class Legend
*/
private $layout;
+ /** @var GridLines */
+ private $borderLines;
+
+ /** @var ChartColor */
+ private $fillColor;
+
+ /** @var ?AxisText */
+ private $legendText;
+
/**
* Create a new Legend.
*
@@ -60,6 +69,13 @@ class Legend
$this->setPosition($position);
$this->layout = $layout;
$this->setOverlay($overlay);
+ $this->borderLines = new GridLines();
+ $this->fillColor = new ChartColor();
+ }
+
+ public function getFillColor(): ChartColor
+ {
+ return $this->fillColor;
}
/**
@@ -148,4 +164,28 @@ class Legend
{
return $this->layout;
}
+
+ public function getLegendText(): ?AxisText
+ {
+ return $this->legendText;
+ }
+
+ public function setLegendText(?AxisText $legendText): self
+ {
+ $this->legendText = $legendText;
+
+ return $this;
+ }
+
+ public function getBorderLines(): GridLines
+ {
+ return $this->borderLines;
+ }
+
+ public function setBorderLines(GridLines $borderLines): self
+ {
+ $this->borderLines = $borderLines;
+
+ return $this;
+ }
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php
index ccde4bb2a..2b78d9a4e 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/PlotArea.php
@@ -163,4 +163,49 @@ class PlotArea
{
return $this->gradientFillStops;
}
+
+ /** @var ?int */
+ private $gapWidth;
+
+ /** @var bool */
+ private $useUpBars = false;
+
+ /** @var bool */
+ private $useDownBars = false;
+
+ public function getGapWidth(): ?int
+ {
+ return $this->gapWidth;
+ }
+
+ public function setGapWidth(?int $gapWidth): self
+ {
+ $this->gapWidth = $gapWidth;
+
+ return $this;
+ }
+
+ public function getUseUpBars(): bool
+ {
+ return $this->useUpBars;
+ }
+
+ public function setUseUpBars(bool $useUpBars): self
+ {
+ $this->useUpBars = $useUpBars;
+
+ return $this;
+ }
+
+ public function getUseDownBars(): bool
+ {
+ return $this->useDownBars;
+ }
+
+ public function setUseDownBars(bool $useDownBars): self
+ {
+ $this->useDownBars = $useDownBars;
+
+ return $this;
+ }
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php
index cb9b544b8..d676f1d33 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/JpGraphRendererBase.php
@@ -434,12 +434,33 @@ abstract class JpGraphRendererBase implements IRenderer
// Loop through each data series in turn
for ($i = 0; $i < $seriesCount; ++$i) {
- $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
+ $plotCategoryByIndex = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i);
+ if ($plotCategoryByIndex === false) {
+ $plotCategoryByIndex = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0);
+ }
+ $dataValuesY = $plotCategoryByIndex->getDataValues();
$dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
- foreach ($dataValuesY as $k => $dataValueY) {
- $dataValuesY[$k] = $k;
+ $redoDataValuesY = true;
+ if ($bubble) {
+ if (!$bubbleSize) {
+ $bubbleSize = '10';
+ }
+ $redoDataValuesY = false;
+ foreach ($dataValuesY as $dataValueY) {
+ if (!is_int($dataValueY) && !is_float($dataValueY)) {
+ $redoDataValuesY = true;
+
+ break;
+ }
+ }
}
+ if ($redoDataValuesY) {
+ foreach ($dataValuesY as $k => $dataValueY) {
+ $dataValuesY[$k] = $k;
+ }
+ }
+ //var_dump($dataValuesY, $dataValuesX, $bubbleSize);
$seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY);
if ($scatterStyle == 'lineMarker') {
@@ -483,7 +504,7 @@ abstract class JpGraphRendererBase implements IRenderer
$dataValues = [];
foreach ($dataValuesY as $k => $dataValueY) {
- $dataValues[$k] = implode(' ', array_reverse($dataValueY));
+ $dataValues[$k] = is_array($dataValueY) ? implode(' ', array_reverse($dataValueY)) : $dataValueY;
}
$tmp = array_shift($dataValues);
$dataValues[] = $tmp;
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php
index e1f0f90ad..b5e70d3a1 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/MtJpGraphRenderer.php
@@ -3,12 +3,12 @@
namespace PhpOffice\PhpSpreadsheet\Chart\Renderer;
/**
- * Jpgraph is not oficially maintained in Composer.
+ * Jpgraph is not officially maintained by Composer at packagist.org.
*
* This renderer implementation uses package
* https://packagist.org/packages/mitoteam/jpgraph
*
- * This package is up to date for August 2022 and has PHP 8.1 support.
+ * This package is up to date for June 2023 and has PHP 8.2 support.
*/
class MtJpGraphRenderer extends JpGraphRendererBase
{
@@ -29,7 +29,7 @@ class MtJpGraphRenderer extends JpGraphRendererBase
'regstat',
'scatter',
'stock',
- ]);
+ ], true); // enable Extended mode
$loaded = true;
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php
index afdeea996..302afee79 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Document/Properties.php
@@ -107,6 +107,8 @@ class Properties
*/
private $customProperties = [];
+ private string $hyperlinkBase = '';
+
/**
* Create a new Document Properties instance.
*/
@@ -434,7 +436,7 @@ class Properties
*
* @param mixed $propertyValue
* @param string $propertyType
- * 'i' : Integer
+ * 'i' : Integer
* 'f' : Floating Point
* 's' : String
* 'd' : Date/Time
@@ -534,4 +536,16 @@ class Properties
{
return self::PROPERTY_TYPE_ARRAY[$propertyType] ?? self::PROPERTY_TYPE_UNKNOWN;
}
+
+ public function getHyperlinkBase(): string
+ {
+ return $this->hyperlinkBase;
+ }
+
+ public function setHyperlinkBase(string $hyperlinkBase): self
+ {
+ $this->hyperlinkBase = $hyperlinkBase;
+
+ return $this;
+ }
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Downloader.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Downloader.php
new file mode 100644
index 000000000..e66ae4258
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Downloader.php
@@ -0,0 +1,89 @@
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
+ 'xls' => 'application/vnd.ms-excel',
+ 'ods' => 'application/vnd.oasis.opendocument.spreadsheet',
+ 'csv' => 'text/csv',
+ 'html' => 'text/html',
+ 'pdf' => 'application/pdf',
+ ];
+
+ public function __construct(string $folder, string $filename, ?string $filetype = null)
+ {
+ if ((is_dir($folder) === false) || (is_readable($folder) === false)) {
+ throw new Exception("Folder {$folder} is not accessable");
+ }
+ $filepath = "{$folder}/{$filename}";
+ $this->filepath = (string) realpath($filepath);
+ $this->filename = basename($filepath);
+ if ((file_exists($this->filepath) === false) || (is_readable($this->filepath) === false)) {
+ throw new Exception("{$this->filename} not found, or cannot be read");
+ }
+
+ $filetype ??= pathinfo($filename, PATHINFO_EXTENSION);
+ if (array_key_exists(strtolower($filetype), self::CONTENT_TYPES) === false) {
+ throw new Exception("Invalid filetype: {$filetype} cannot be downloaded");
+ }
+ $this->filetype = strtolower($filetype);
+ }
+
+ public function download(): void
+ {
+ $this->headers();
+
+ readfile($this->filepath);
+ }
+
+ public function headers(): void
+ {
+ ob_clean();
+
+ $this->contentType();
+ $this->contentDisposition();
+ $this->cacheHeaders();
+ $this->fileSize();
+
+ flush();
+ }
+
+ protected function contentType(): void
+ {
+ header('Content-Type: ' . self::CONTENT_TYPES[$this->filetype]);
+ }
+
+ protected function contentDisposition(): void
+ {
+ header('Content-Disposition: attachment;filename="' . $this->filename . '"');
+ }
+
+ protected function cacheHeaders(): void
+ {
+ header('Cache-Control: max-age=0');
+ // If you're serving to IE 9, then the following may be needed
+ header('Cache-Control: max-age=1');
+
+ // If you're serving to IE over SSL, then the following may be needed
+ header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
+ header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); // always modified
+ header('Cache-Control: cache, must-revalidate'); // HTTP/1.1
+ header('Pragma: public'); // HTTP/1.0
+ }
+
+ protected function fileSize(): void
+ {
+ header('Content-Length: ' . filesize($this->filepath));
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Handler.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Handler.php
new file mode 100644
index 000000000..d05197cef
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Handler.php
@@ -0,0 +1,46 @@
+attributes;
if ($attrs !== null) {
@@ -737,72 +737,72 @@ class Html
}
}
- private function endFontTag(): void
+ protected function endFontTag(): void
{
$this->face = $this->size = $this->color = null;
}
- private function startBoldTag(): void
+ protected function startBoldTag(): void
{
$this->bold = true;
}
- private function endBoldTag(): void
+ protected function endBoldTag(): void
{
$this->bold = false;
}
- private function startItalicTag(): void
+ protected function startItalicTag(): void
{
$this->italic = true;
}
- private function endItalicTag(): void
+ protected function endItalicTag(): void
{
$this->italic = false;
}
- private function startUnderlineTag(): void
+ protected function startUnderlineTag(): void
{
$this->underline = true;
}
- private function endUnderlineTag(): void
+ protected function endUnderlineTag(): void
{
$this->underline = false;
}
- private function startSubscriptTag(): void
+ protected function startSubscriptTag(): void
{
$this->subscript = true;
}
- private function endSubscriptTag(): void
+ protected function endSubscriptTag(): void
{
$this->subscript = false;
}
- private function startSuperscriptTag(): void
+ protected function startSuperscriptTag(): void
{
$this->superscript = true;
}
- private function endSuperscriptTag(): void
+ protected function endSuperscriptTag(): void
{
$this->superscript = false;
}
- private function startStrikethruTag(): void
+ protected function startStrikethruTag(): void
{
$this->strikethrough = true;
}
- private function endStrikethruTag(): void
+ protected function endStrikethruTag(): void
{
$this->strikethrough = false;
}
- private function breakTag(): void
+ protected function breakTag(): void
{
$this->stringData .= "\n";
}
@@ -826,8 +826,9 @@ class Html
if (isset($callbacks[$callbackTag])) {
$elementHandler = $callbacks[$callbackTag];
if (method_exists($this, $elementHandler)) {
- /** @phpstan-ignore-next-line */
- call_user_func([$this, $elementHandler], $element);
+ /** @var callable */
+ $callable = [$this, $elementHandler];
+ call_user_func($callable, $element);
}
}
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php
index 5ca546e07..6244375fe 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Sample.php
@@ -2,7 +2,9 @@
namespace PhpOffice\PhpSpreadsheet\Helper;
+use PhpOffice\PhpSpreadsheet\Chart\Chart;
use PhpOffice\PhpSpreadsheet\IOFactory;
+use PhpOffice\PhpSpreadsheet\Settings;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
use PhpOffice\PhpSpreadsheet\Writer\IWriter;
@@ -12,6 +14,7 @@ use RecursiveRegexIterator;
use ReflectionClass;
use RegexIterator;
use RuntimeException;
+use Throwable;
/**
* Helper class to be used in sample code.
@@ -120,7 +123,7 @@ class Sample
* @param string $filename
* @param string[] $writers
*/
- public function write(Spreadsheet $spreadsheet, $filename, array $writers = ['Xlsx', 'Xls']): void
+ public function write(Spreadsheet $spreadsheet, $filename, array $writers = ['Xlsx', 'Xls'], bool $withCharts = false, ?callable $writerCallback = null): void
{
// Set active sheet index to the first sheet, so Excel opens this as the first sheet
$spreadsheet->setActiveSheetIndex(0);
@@ -129,9 +132,16 @@ class Sample
foreach ($writers as $writerType) {
$path = $this->getFilename($filename, mb_strtolower($writerType));
$writer = IOFactory::createWriter($spreadsheet, $writerType);
+ $writer->setIncludeCharts($withCharts);
+ if ($writerCallback !== null) {
+ $writerCallback($writer);
+ }
$callStartTime = microtime(true);
$writer->save($path);
$this->logWrite($writer, $path, /** @scrutinizer ignore-type */ $callStartTime);
+ if ($this->isCli() === false) {
+ echo 'Download ' . basename($path) . '
';
+ }
}
$this->logEndingNotes();
@@ -147,7 +157,7 @@ class Sample
*
* @return string
*/
- private function getTemporaryFolder()
+ public function getTemporaryFolder()
{
$tempFolder = sys_get_temp_dir() . '/phpspreadsheet';
if (!$this->isDirOrMkdir($tempFolder)) {
@@ -162,10 +172,8 @@ class Sample
*
* @param string $filename
* @param string $extension
- *
- * @return string
*/
- public function getFilename($filename, $extension = 'xlsx')
+ public function getFilename($filename, $extension = 'xlsx'): string
{
$originalExtension = pathinfo($filename, PATHINFO_EXTENSION);
@@ -195,7 +203,29 @@ class Sample
public function log(string $message): void
{
$eol = $this->isCli() ? PHP_EOL : '
';
- echo date('H:i:s ') . $message . $eol;
+ echo($this->isCli() ? date('H:i:s ') : '') . $message . $eol;
+ }
+
+ public function renderChart(Chart $chart, string $fileName): void
+ {
+ if ($this->isCli() === true) {
+ return;
+ }
+
+ Settings::setChartRenderer(\PhpOffice\PhpSpreadsheet\Chart\Renderer\MtJpGraphRenderer::class);
+
+ $fileName = $this->getFilename($fileName, 'png');
+
+ try {
+ $chart->render($fileName);
+ $this->log('Rendered image: ' . $fileName);
+ $imageData = file_get_contents($fileName);
+ if ($imageData !== false) {
+ echo '';
+ }
+ } catch (Throwable $e) {
+ $this->log('Error rendering chart: ' . $e->getMessage() . PHP_EOL);
+ }
}
public function titles(string $category, string $functionName, ?string $description = null): void
@@ -246,7 +276,10 @@ class Sample
$callTime = $callEndTime - $callStartTime;
$reflection = new ReflectionClass($writer);
$format = $reflection->getShortName();
- $message = "Write {$format} format to {$path}
in " . sprintf('%.4f', $callTime) . ' seconds';
+
+ $message = ($this->isCli() === true)
+ ? "Write {$format} format to {$path} in " . sprintf('%.4f', $callTime) . ' seconds'
+ : "Write {$format} format to {$path}
in " . sprintf('%.4f', $callTime) . ' seconds';
$this->log($message);
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/TextGrid.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/TextGrid.php
index ed146a55d..3a4a98f02 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/TextGrid.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/TextGrid.php
@@ -48,17 +48,17 @@ class TextGrid
public function render(): string
{
- $this->gridDisplay = $this->isCli ? '' : '';
+ $this->gridDisplay = $this->isCli ? '' : '';
$maxRow = max($this->rows);
- $maxRowLength = strlen((string) $maxRow) + 1;
+ $maxRowLength = mb_strlen((string) $maxRow) + 1;
$columnWidths = $this->getColumnWidths();
$this->renderColumnHeader($maxRowLength, $columnWidths);
$this->renderRows($maxRowLength, $columnWidths);
$this->renderFooter($maxRowLength, $columnWidths);
- $this->gridDisplay .= $this->isCli ? '' : '
';
+ $this->gridDisplay .= $this->isCli ? '' : '';
return $this->gridDisplay;
}
@@ -75,9 +75,9 @@ class TextGrid
private function renderCells(array $rowData, array $columnWidths): void
{
foreach ($rowData as $column => $cell) {
- $cell = ($this->isCli) ? (string) $cell : htmlentities((string) $cell);
+ $displayCell = ($this->isCli) ? (string) $cell : htmlentities((string) $cell);
$this->gridDisplay .= '| ';
- $this->gridDisplay .= str_pad($cell, $columnWidths[$column] + 1, ' ');
+ $this->gridDisplay .= $displayCell . str_repeat(' ', $columnWidths[$column] - mb_strlen($cell ?? '') + 1);
}
}
@@ -126,12 +126,12 @@ class TextGrid
foreach ($columnData as $columnValue) {
if (is_string($columnValue)) {
- $columnWidth = max($columnWidth, strlen($columnValue));
+ $columnWidth = max($columnWidth, mb_strlen($columnValue));
} elseif (is_bool($columnValue)) {
- $columnWidth = max($columnWidth, strlen($columnValue ? 'TRUE' : 'FALSE'));
+ $columnWidth = max($columnWidth, mb_strlen($columnValue ? 'TRUE' : 'FALSE'));
}
- $columnWidth = max($columnWidth, strlen((string) $columnWidth));
+ $columnWidth = max($columnWidth, mb_strlen((string) $columnWidth));
}
return $columnWidth;
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php
index 174553225..99e4d6ad6 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric.php
@@ -80,17 +80,15 @@ class Gnumeric extends BaseReader
*/
public function canRead(string $filename): bool
{
- // Check if gzlib functions are available
- if (File::testFileNoThrow($filename) && function_exists('gzread')) {
- // Read signature data (first 3 bytes)
- $fh = fopen($filename, 'rb');
- if ($fh !== false) {
- $data = fread($fh, 2);
- fclose($fh);
+ $data = null;
+ if (File::testFileNoThrow($filename)) {
+ $data = $this->gzfileGetContents($filename);
+ if (strpos($data, self::NAMESPACE_GNM) === false) {
+ $data = '';
}
}
- return isset($data) && $data === chr(0x1F) . chr(0x8B);
+ return !empty($data);
}
private static function matchXml(XMLReader $xml, string $expectedLocalName): bool
@@ -110,9 +108,13 @@ class Gnumeric extends BaseReader
public function listWorksheetNames($filename)
{
File::assertFile($filename);
+ if (!$this->canRead($filename)) {
+ throw new Exception($filename . ' is an invalid Gnumeric file.');
+ }
$xml = new XMLReader();
- $xml->xml($this->getSecurityScannerOrThrow()->scanFile('compress.zlib://' . realpath($filename)), null, Settings::getLibXmlLoaderOptions());
+ $contents = $this->gzfileGetContents($filename);
+ $xml->xml($contents, null, Settings::getLibXmlLoaderOptions());
$xml->setParserProperty(2, true);
$worksheetNames = [];
@@ -139,9 +141,13 @@ class Gnumeric extends BaseReader
public function listWorksheetInfo($filename)
{
File::assertFile($filename);
+ if (!$this->canRead($filename)) {
+ throw new Exception($filename . ' is an invalid Gnumeric file.');
+ }
$xml = new XMLReader();
- $xml->xml($this->getSecurityScannerOrThrow()->scanFile('compress.zlib://' . realpath($filename)), null, Settings::getLibXmlLoaderOptions());
+ $contents = $this->gzfileGetContents($filename);
+ $xml->xml($contents, null, Settings::getLibXmlLoaderOptions());
$xml->setParserProperty(2, true);
$worksheetInfo = [];
@@ -185,13 +191,23 @@ class Gnumeric extends BaseReader
*/
private function gzfileGetContents($filename)
{
- $file = @gzopen($filename, 'rb');
$data = '';
- if ($file !== false) {
- while (!gzeof($file)) {
- $data .= gzread($file, 1024);
+ $contents = @file_get_contents($filename);
+ if ($contents !== false) {
+ if (substr($contents, 0, 2) === "\x1f\x8b") {
+ // Check if gzlib functions are available
+ if (function_exists('gzdecode')) {
+ $contents = @gzdecode($contents);
+ if ($contents !== false) {
+ $data = $contents;
+ }
+ }
+ } else {
+ $data = $contents;
}
- gzclose($file);
+ }
+ if ($data !== '') {
+ $data = $this->getSecurityScannerOrThrow()->scan($data);
}
return $data;
@@ -245,10 +261,13 @@ class Gnumeric extends BaseReader
{
$this->spreadsheet = $spreadsheet;
File::assertFile($filename);
+ if (!$this->canRead($filename)) {
+ throw new Exception($filename . ' is an invalid Gnumeric file.');
+ }
$gFileData = $this->gzfileGetContents($filename);
- $xml2 = simplexml_load_string($this->getSecurityScannerOrThrow()->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions());
+ $xml2 = simplexml_load_string($gFileData, 'SimpleXMLElement', Settings::getLibXmlLoaderOptions());
$xml = self::testSimpleXml($xml2);
$gnmXML = $xml->children(self::NAMESPACE_GNM);
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php
index b165b6c17..bfb52401a 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php
@@ -7,6 +7,8 @@ use DOMElement;
use DOMNode;
use DOMText;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Document\Properties;
use PhpOffice\PhpSpreadsheet\Helper\Dimension as CssDimension;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
@@ -283,15 +285,35 @@ class Html extends BaseReader
* @param int|string $row
* @param mixed $cellContent
*/
- protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent): void
+ protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent, array $attributeArray): void
{
if (is_string($cellContent)) {
// Simple String content
if (trim($cellContent) > '') {
// Only actually write it if there's content in the string
// Write to worksheet to be done here...
- // ... we return the cell so we can mess about with styles more easily
- $sheet->setCellValue($column . $row, $cellContent);
+ // ... we return the cell, so we can mess about with styles more easily
+
+ // Set cell value explicitly if there is data-type attribute
+ if (isset($attributeArray['data-type'])) {
+ $datatype = $attributeArray['data-type'];
+ if (in_array($datatype, [DataType::TYPE_STRING, DataType::TYPE_STRING2, DataType::TYPE_INLINE])) {
+ //Prevent to Excel treat string with beginning equal sign or convert big numbers to scientific number
+ if (substr($cellContent, 0, 1) === '=') {
+ $sheet->getCell($column . $row)
+ ->getStyle()
+ ->setQuotePrefix(true);
+ }
+ }
+ //catching the Exception and ignoring the invalid data types
+ try {
+ $sheet->setCellValueExplicit($column . $row, $cellContent, $attributeArray['data-type']);
+ } catch (\PhpOffice\PhpSpreadsheet\Exception $exception) {
+ $sheet->setCellValue($column . $row, $cellContent);
+ }
+ } else {
+ $sheet->setCellValue($column . $row, $cellContent);
+ }
$this->dataArray[$row][$column] = $cellContent;
}
} else {
@@ -305,7 +327,7 @@ class Html extends BaseReader
private function processDomElementBody(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child): void
{
$attributeArray = [];
- foreach (($child->attributes ?? []) as $attribute) {
+ foreach ($child->attributes as $attribute) {
$attributeArray[$attribute->name] = $attribute->value;
}
@@ -355,7 +377,7 @@ class Html extends BaseReader
private function processDomElementHr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
if ($child->nodeName === 'hr') {
- $this->flushCell($sheet, $column, $row, $cellContent);
+ $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
++$row;
if (isset($this->formats[$child->nodeName])) {
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
@@ -375,7 +397,7 @@ class Html extends BaseReader
$sheet->getStyle($column . $row)->getAlignment()->setWrapText(true);
} else {
// Otherwise flush our existing content and move the row cursor on
- $this->flushCell($sheet, $column, $row, $cellContent);
+ $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
++$row;
}
} else {
@@ -421,11 +443,11 @@ class Html extends BaseReader
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
} else {
if ($cellContent > '') {
- $this->flushCell($sheet, $column, $row, $cellContent);
+ $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
++$row;
}
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
- $this->flushCell($sheet, $column, $row, $cellContent);
+ $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
if (isset($this->formats[$child->nodeName])) {
$sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
@@ -448,11 +470,11 @@ class Html extends BaseReader
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
} else {
if ($cellContent > '') {
- $this->flushCell($sheet, $column, $row, $cellContent);
+ $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
}
++$row;
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
- $this->flushCell($sheet, $column, $row, $cellContent);
+ $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
$column = 'A';
}
} else {
@@ -469,10 +491,13 @@ class Html extends BaseReader
}
}
+ private string $currentColumn = 'A';
+
private function processDomElementTable(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
if ($child->nodeName === 'table') {
- $this->flushCell($sheet, $column, $row, $cellContent);
+ $this->currentColumn = 'A';
+ $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
$column = $this->setTableStartColumn($column);
if ($this->tableLevel > 1 && $row > 1) {
--$row;
@@ -491,7 +516,10 @@ class Html extends BaseReader
private function processDomElementTr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
{
- if ($child->nodeName === 'tr') {
+ if ($child->nodeName === 'col') {
+ $this->applyInlineStyle($sheet, -1, $this->currentColumn, $attributeArray);
+ ++$this->currentColumn;
+ } elseif ($child->nodeName === 'tr') {
$column = $this->getTableStartColumn();
$cellContent = '';
$this->processDomElement($child, $sheet, $row, $column, $cellContent);
@@ -574,7 +602,7 @@ class Html extends BaseReader
// apply inline style
$this->applyInlineStyle($sheet, $row, $column, $attributeArray);
- $this->flushCell($sheet, $column, $row, $cellContent);
+ $this->flushCell($sheet, $column, $row, $cellContent, $attributeArray);
$this->processDomElementBgcolor($sheet, $row, $column, $attributeArray);
$this->processDomElementWidth($sheet, $column, $attributeArray);
@@ -664,10 +692,94 @@ class Html extends BaseReader
if ($loaded === false) {
throw new Exception('Failed to load ' . $filename . ' as a DOM Document', 0, $e ?? null);
}
+ self::loadProperties($dom, $spreadsheet);
return $this->loadDocument($dom, $spreadsheet);
}
+ private static function loadProperties(DOMDocument $dom, Spreadsheet $spreadsheet): void
+ {
+ $properties = $spreadsheet->getProperties();
+ foreach ($dom->getElementsByTagName('meta') as $meta) {
+ $metaContent = (string) $meta->getAttribute('content');
+ if ($metaContent !== '') {
+ $metaName = (string) $meta->getAttribute('name');
+ switch ($metaName) {
+ case 'author':
+ $properties->setCreator($metaContent);
+
+ break;
+ case 'category':
+ $properties->setCategory($metaContent);
+
+ break;
+ case 'company':
+ $properties->setCompany($metaContent);
+
+ break;
+ case 'created':
+ $properties->setCreated($metaContent);
+
+ break;
+ case 'description':
+ $properties->setDescription($metaContent);
+
+ break;
+ case 'keywords':
+ $properties->setKeywords($metaContent);
+
+ break;
+ case 'lastModifiedBy':
+ $properties->setLastModifiedBy($metaContent);
+
+ break;
+ case 'manager':
+ $properties->setManager($metaContent);
+
+ break;
+ case 'modified':
+ $properties->setModified($metaContent);
+
+ break;
+ case 'subject':
+ $properties->setSubject($metaContent);
+
+ break;
+ case 'title':
+ $properties->setTitle($metaContent);
+
+ break;
+ default:
+ if (preg_match('/^custom[.](bool|date|float|int|string)[.](.+)$/', $metaName, $matches) === 1) {
+ switch ($matches[1]) {
+ case 'bool':
+ $properties->setCustomProperty($matches[2], (bool) $metaContent, Properties::PROPERTY_TYPE_BOOLEAN);
+
+ break;
+ case 'float':
+ $properties->setCustomProperty($matches[2], (float) $metaContent, Properties::PROPERTY_TYPE_FLOAT);
+
+ break;
+ case 'int':
+ $properties->setCustomProperty($matches[2], (int) $metaContent, Properties::PROPERTY_TYPE_INTEGER);
+
+ break;
+ case 'date':
+ $properties->setCustomProperty($matches[2], $metaContent, Properties::PROPERTY_TYPE_DATE);
+
+ break;
+ default: // string
+ $properties->setCustomProperty($matches[2], $metaContent, Properties::PROPERTY_TYPE_STRING);
+ }
+ }
+ }
+ }
+ }
+ if (!empty($dom->baseURI)) {
+ $properties->setHyperlinkBase($dom->baseURI);
+ }
+ }
+
private static function replaceNonAscii(array $matches): string
{
return '' . mb_ord($matches[0], 'UTF-8') . ';';
@@ -698,8 +810,10 @@ class Html extends BaseReader
if ($loaded === false) {
throw new Exception('Failed to load content as a DOM Document', 0, $e ?? null);
}
+ $spreadsheet = $spreadsheet ?? new Spreadsheet();
+ self::loadProperties($dom, $spreadsheet);
- return $this->loadDocument($dom, $spreadsheet ?? new Spreadsheet());
+ return $this->loadDocument($dom, $spreadsheet);
}
/**
@@ -769,7 +883,9 @@ class Html extends BaseReader
return;
}
- if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
+ if ($row <= 0 || $column === '') {
+ $cellStyle = new Style();
+ } elseif (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
$columnTo = $column;
for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
++$columnTo;
@@ -901,16 +1017,20 @@ class Html extends BaseReader
break;
case 'width':
- $sheet->getColumnDimension($column)->setWidth(
- (new CssDimension($styleValue ?? ''))->width()
- );
+ if ($column !== '') {
+ $sheet->getColumnDimension($column)->setWidth(
+ (new CssDimension($styleValue ?? ''))->width()
+ );
+ }
break;
case 'height':
- $sheet->getRowDimension($row)->setRowHeight(
- (new CssDimension($styleValue ?? ''))->height()
- );
+ if ($row > 0) {
+ $sheet->getRowDimension($row)->setRowHeight(
+ (new CssDimension($styleValue ?? ''))->height()
+ );
+ }
break;
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php
index 250815400..9913f3325 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods.php
@@ -8,6 +8,7 @@ use DOMElement;
use DOMNode;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
+use PhpOffice\PhpSpreadsheet\Helper\Dimension as HelperDimension;
use PhpOffice\PhpSpreadsheet\Reader\Ods\AutoFilter;
use PhpOffice\PhpSpreadsheet\Reader\Ods\DefinedNames;
use PhpOffice\PhpSpreadsheet\Reader\Ods\FormulaTranslator;
@@ -295,11 +296,29 @@ class Ods extends BaseReader
$tableNs = $dom->lookupNamespaceUri('table');
$textNs = $dom->lookupNamespaceUri('text');
$xlinkNs = $dom->lookupNamespaceUri('xlink');
+ $styleNs = $dom->lookupNamespaceUri('style');
$pageSettings->readStyleCrossReferences($dom);
$autoFilterReader = new AutoFilter($spreadsheet, $tableNs);
$definedNameReader = new DefinedNames($spreadsheet, $tableNs);
+ $columnWidths = [];
+ $automaticStyle0 = $dom->getElementsByTagNameNS($officeNs, 'automatic-styles')->item(0);
+ $automaticStyles = ($automaticStyle0 === null) ? [] : $automaticStyle0->getElementsByTagNameNS($styleNs, 'style');
+ foreach ($automaticStyles as $automaticStyle) {
+ $styleName = $automaticStyle->getAttributeNS($styleNs, 'name');
+ $styleFamily = $automaticStyle->getAttributeNS($styleNs, 'family');
+ if ($styleFamily === 'table-column') {
+ $tcprops = $automaticStyle->getElementsByTagNameNS($styleNs, 'table-column-properties');
+ if ($tcprops !== null) {
+ $tcprop = $tcprops->item(0);
+ if ($tcprop !== null) {
+ $columnWidth = $tcprop->getAttributeNs($styleNs, 'column-width');
+ $columnWidths[$styleName] = $columnWidth;
+ }
+ }
+ }
+ }
// Content
$item0 = $dom->getElementsByTagNameNS($officeNs, 'body')->item(0);
@@ -340,6 +359,7 @@ class Ods extends BaseReader
// Go through every child of table element
$rowID = 1;
+ $tableColumnIndex = 1;
foreach ($worksheetDataSet->childNodes as $childNode) {
/** @var DOMElement $childNode */
@@ -366,6 +386,26 @@ class Ods extends BaseReader
// $rowData = $cellData;
// break;
// }
+ break;
+ case 'table-column':
+ if ($childNode->hasAttributeNS($tableNs, 'number-columns-repeated')) {
+ $rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-columns-repeated');
+ } else {
+ $rowRepeats = 1;
+ }
+ $tableStyleName = $childNode->getAttributeNS($tableNs, 'style-name');
+ if (isset($columnWidths[$tableStyleName])) {
+ $columnWidth = new HelperDimension($columnWidths[$tableStyleName]);
+ $tableColumnString = Coordinate::stringFromColumnIndex($tableColumnIndex);
+ for ($rowRepeats2 = $rowRepeats; $rowRepeats2 > 0; --$rowRepeats2) {
+ $spreadsheet->getActiveSheet()
+ ->getColumnDimension($tableColumnString)
+ ->setWidth($columnWidth->toUnit('cm'), 'cm');
+ ++$tableColumnString;
+ }
+ }
+ $tableColumnIndex += $rowRepeats;
+
break;
case 'table-row':
if ($childNode->hasAttributeNS($tableNs, 'number-rows-repeated')) {
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php
index ad898ae41..f8eaf39d0 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Security/XmlScanner.php
@@ -151,7 +151,7 @@ class XmlScanner
throw new Reader\Exception('Detected use of ENTITY in XML, spreadsheet file load() aborted to prevent XXE/XEE attacks');
}
- if ($this->callback !== null && is_callable($this->callback)) {
+ if ($this->callback !== null) {
$xml = call_user_func($this->callback, $xml);
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php
index f35e8c3b2..816e7698d 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php
@@ -430,7 +430,7 @@ class Xls extends BaseReader
*/
public function canRead(string $filename): bool
{
- if (!File::testFileNoThrow($filename)) {
+ if (File::testFileNoThrow($filename) === false) {
return false;
}
@@ -440,6 +440,9 @@ class Xls extends BaseReader
// get excel data
$ole->read($filename);
+ if ($ole->wrkbook === null) {
+ throw new Exception('The filename ' . $filename . ' is not recognised as a Spreadsheet file');
+ }
return true;
} catch (PhpSpreadsheetException $e) {
@@ -449,7 +452,7 @@ class Xls extends BaseReader
public function setCodepage(string $codepage): void
{
- if (!CodePage::validate($codepage)) {
+ if (CodePage::validate($codepage) === false) {
throw new PhpSpreadsheetException('Unknown codepage: ' . $codepage);
}
@@ -1097,7 +1100,7 @@ class Xls extends BaseReader
// treat OBJ records
foreach ($this->objs as $n => $obj) {
// the first shape container never has a corresponding OBJ record, hence $n + 1
- if (isset($allSpContainers[$n + 1]) && is_object($allSpContainers[$n + 1])) {
+ if (isset($allSpContainers[$n + 1])) {
$spContainer = $allSpContainers[$n + 1];
// we skip all spContainers that are a part of a group shape since we cannot yet handle those
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php
index bf8908470..1c33fd681 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx.php
@@ -16,6 +16,7 @@ use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Hyperlinks;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\PageSetup;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Properties as PropertyReader;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SharedFormula;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViewOptions;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\SheetViews;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Styles;
@@ -61,6 +62,11 @@ class Xlsx extends BaseReader
/** @var Styles */
private $styleReader;
+ /**
+ * @var array
+ */
+ private $sharedFormulae = [];
+
/**
* Create a new Xlsx Reader instance.
*/
@@ -128,7 +134,7 @@ class Xlsx extends BaseReader
if ($replaceUnclosedBr) {
$contents = str_replace('
', '
', $contents);
}
- $rels = simplexml_load_string(
+ $rels = @simplexml_load_string(
$this->getSecurityScannerOrThrow()->scan($contents),
'SimpleXMLElement',
Settings::getLibXmlLoaderOptions(),
@@ -246,6 +252,7 @@ class Xlsx extends BaseReader
$xmlWorkbook = $this->loadZip($relTarget, $mainNS);
if ($xmlWorkbook->sheets) {
$dir = dirname($relTarget);
+
/** @var SimpleXMLElement $eleSheet */
foreach ($xmlWorkbook->sheets->sheet as $eleSheet) {
$tmpInfo = [
@@ -261,8 +268,8 @@ class Xlsx extends BaseReader
$xml = new XMLReader();
$xml->xml(
- $this->getSecurityScannerOrThrow()->scanFile(
- 'zip://' . File::realpath($filename) . '#' . $fileWorksheetPath
+ $this->getSecurityScannerOrThrow()->scan(
+ $this->getFromZipArchive($this->zip, $fileWorksheetPath)
),
null,
Settings::getLibXmlLoaderOptions()
@@ -324,13 +331,13 @@ class Xlsx extends BaseReader
* @param mixed $value
* @param mixed $calculatedValue
*/
- private function castToFormula(?SimpleXMLElement $c, string $r, string &$cellDataType, &$value, &$calculatedValue, array &$sharedFormulas, string $castBaseType): void
+ private function castToFormula(?SimpleXMLElement $c, string $r, string &$cellDataType, &$value, &$calculatedValue, string $castBaseType, bool $updateSharedCells = true): void
{
if ($c === null) {
return;
}
$attr = $c->f->attributes();
- $cellDataType = 'f';
+ $cellDataType = DataType::TYPE_FORMULA;
$value = "={$c->f}";
$calculatedValue = self::$castBaseType($c);
@@ -338,17 +345,19 @@ class Xlsx extends BaseReader
if (isset($attr['t']) && strtolower((string) $attr['t']) == 'shared') {
$instance = (string) $attr['si'];
- if (!isset($sharedFormulas[(string) $attr['si']])) {
- $sharedFormulas[$instance] = ['master' => $r, 'formula' => $value];
- } else {
- $master = Coordinate::indexesFromString($sharedFormulas[$instance]['master']);
+ if (!isset($this->sharedFormulae[(string) $attr['si']])) {
+ $this->sharedFormulae[$instance] = new SharedFormula($r, $value);
+ } elseif ($updateSharedCells === true) {
+ // It's only worth the overhead of adjusting the shared formula for this cell if we're actually loading
+ // the cell, which may not be the case if we're using a read filter.
+ $master = Coordinate::indexesFromString($this->sharedFormulae[$instance]->master());
$current = Coordinate::indexesFromString($r);
$difference = [0, 0];
$difference[0] = $current[0] - $master[0];
$difference[1] = $current[1] - $master[1];
- $value = $this->referenceHelper->updateFormulaReferences($sharedFormulas[$instance]['formula'], 'A1', $difference[0], $difference[1]);
+ $value = $this->referenceHelper->updateFormulaReferences($this->sharedFormulae[$instance]->formula(), 'A1', $difference[0], $difference[1]);
}
}
}
@@ -395,12 +404,18 @@ class Xlsx extends BaseReader
// Sadly, some 3rd party xlsx generators don't use consistent case for filenaming
// so we need to load case-insensitively from the zip file
- // Apache POI fixes
$contents = $archive->getFromName($fileName, 0, ZipArchive::FL_NOCASE);
+
+ // Apache POI fixes
if ($contents === false) {
$contents = $archive->getFromName(substr($fileName, 1), 0, ZipArchive::FL_NOCASE);
}
+ // Has the file been saved with Windoze directory separators rather than unix?
+ if ($contents === false) {
+ $contents = $archive->getFromName(str_replace('/', '\\', $fileName), 0, ZipArchive::FL_NOCASE);
+ }
+
return ($contents === false) ? '' : $contents;
}
@@ -447,6 +462,7 @@ class Xlsx extends BaseReader
$colourScheme = self::getAttributes($xmlTheme->themeElements->clrScheme);
$colourSchemeName = (string) $colourScheme['name'];
+ $excel->getTheme()->setThemeColorName($colourSchemeName);
$colourScheme = $xmlTheme->themeElements->clrScheme->children($drawingNS);
$themeColours = [];
@@ -458,14 +474,46 @@ class Xlsx extends BaseReader
if (isset($xmlColour->sysClr)) {
$xmlColourData = self::getAttributes($xmlColour->sysClr);
$themeColours[$themePos] = (string) $xmlColourData['lastClr'];
+ $excel->getTheme()->setThemeColor($k, (string) $xmlColourData['lastClr']);
} elseif (isset($xmlColour->srgbClr)) {
$xmlColourData = self::getAttributes($xmlColour->srgbClr);
$themeColours[$themePos] = (string) $xmlColourData['val'];
+ $excel->getTheme()->setThemeColor($k, (string) $xmlColourData['val']);
}
}
$theme = new Theme($themeName, $colourSchemeName, $themeColours);
$this->styleReader->setTheme($theme);
+ $fontScheme = self::getAttributes($xmlTheme->themeElements->fontScheme);
+ $fontSchemeName = (string) $fontScheme['name'];
+ $excel->getTheme()->setThemeFontName($fontSchemeName);
+ $majorFonts = [];
+ $minorFonts = [];
+ $fontScheme = $xmlTheme->themeElements->fontScheme->children($drawingNS);
+ $majorLatin = self::getAttributes($fontScheme->majorFont->latin)['typeface'] ?? '';
+ $majorEastAsian = self::getAttributes($fontScheme->majorFont->ea)['typeface'] ?? '';
+ $majorComplexScript = self::getAttributes($fontScheme->majorFont->cs)['typeface'] ?? '';
+ $minorLatin = self::getAttributes($fontScheme->minorFont->latin)['typeface'] ?? '';
+ $minorEastAsian = self::getAttributes($fontScheme->minorFont->ea)['typeface'] ?? '';
+ $minorComplexScript = self::getAttributes($fontScheme->minorFont->cs)['typeface'] ?? '';
+
+ foreach ($fontScheme->majorFont->font as $xmlFont) {
+ $fontAttributes = self::getAttributes($xmlFont);
+ $script = (string) ($fontAttributes['script'] ?? '');
+ if (!empty($script)) {
+ $majorFonts[$script] = (string) ($fontAttributes['typeface'] ?? '');
+ }
+ }
+ foreach ($fontScheme->minorFont->font as $xmlFont) {
+ $fontAttributes = self::getAttributes($xmlFont);
+ $script = (string) ($fontAttributes['script'] ?? '');
+ if (!empty($script)) {
+ $minorFonts[$script] = (string) ($fontAttributes['typeface'] ?? '');
+ }
+ }
+ $excel->getTheme()->setMajorFontValues($majorLatin, $majorEastAsian, $majorComplexScript, $majorFonts);
+ $excel->getTheme()->setMinorFontValues($minorLatin, $minorEastAsian, $minorComplexScript, $minorFonts);
+
break;
}
}
@@ -477,6 +525,10 @@ class Xlsx extends BaseReader
foreach ($rels->Relationship as $relx) {
$rel = self::getAttributes($relx);
$relTarget = (string) $rel['Target'];
+ // issue 3553
+ if ($relTarget[0] === '/') {
+ $relTarget = substr($relTarget, 1);
+ }
$relType = (string) $rel['Type'];
$mainNS = self::REL_TO_MAIN[$relType] ?? Namespaces::MAIN;
switch ($relType) {
@@ -507,26 +559,6 @@ class Xlsx extends BaseReader
$relsWorkbook = $this->loadZip("$dir/_rels/" . basename($relTarget) . '.rels', '');
$relsWorkbook->registerXPathNamespace('rel', Namespaces::RELATIONSHIPS);
- $sharedStrings = [];
- $relType = "rel:Relationship[@Type='"
- //. Namespaces::SHARED_STRINGS
- . "$xmlNamespaceBase/sharedStrings"
- . "']";
- $xpath = self::getArrayItem($relsWorkbook->xpath($relType));
-
- if ($xpath) {
- $xmlStrings = $this->loadZip("$dir/$xpath[Target]", $mainNS);
- if (isset($xmlStrings->si)) {
- foreach ($xmlStrings->si as $val) {
- if (isset($val->t)) {
- $sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t);
- } elseif (isset($val->r)) {
- $sharedStrings[] = $this->parseRichText($val);
- }
- }
- }
- }
-
$worksheets = [];
$macros = $customUI = null;
foreach ($relsWorkbook->Relationship as $elex) {
@@ -618,7 +650,7 @@ class Xlsx extends BaseReader
$numFmt = NumberFormat::builtInFormatCode((int) $xf['numFmtId']);
}
}
- $quotePrefix = (bool) ($xf['quotePrefix'] ?? false);
+ $quotePrefix = (bool) (string) ($xf['quotePrefix'] ?? '');
$style = (object) [
'numFmt' => $numFmt ?? NumberFormat::FORMAT_GENERAL,
@@ -653,7 +685,7 @@ class Xlsx extends BaseReader
}
}
- $quotePrefix = (bool) ($xf['quotePrefix'] ?? false);
+ $quotePrefix = (bool) (string) ($xf['quotePrefix'] ?? '');
$cellStyle = (object) [
'numFmt' => $numFmt,
@@ -682,6 +714,27 @@ class Xlsx extends BaseReader
$dxfs = $this->styleReader->dxfs($this->readDataOnly);
$styles = $this->styleReader->styles();
+ // Read content after setting the styles
+ $sharedStrings = [];
+ $relType = "rel:Relationship[@Type='"
+ //. Namespaces::SHARED_STRINGS
+ . "$xmlNamespaceBase/sharedStrings"
+ . "']";
+ $xpath = self::getArrayItem($relsWorkbook->xpath($relType));
+
+ if ($xpath) {
+ $xmlStrings = $this->loadZip("$dir/$xpath[Target]", $mainNS);
+ if (isset($xmlStrings->si)) {
+ foreach ($xmlStrings->si as $val) {
+ if (isset($val->t)) {
+ $sharedStrings[] = StringHelper::controlCharacterOOXML2PHP((string) $val->t);
+ } elseif (isset($val->r)) {
+ $sharedStrings[] = $this->parseRichText($val);
+ }
+ }
+ }
+ }
+
$xmlWorkbook = $this->loadZipNoNamespace($relTarget, $mainNS);
$xmlWorkbookNS = $this->loadZip($relTarget, $mainNS);
@@ -743,7 +796,8 @@ class Xlsx extends BaseReader
$xmlSheet = $this->loadZipNoNamespace("$dir/$fileWorksheet", $mainNS);
$xmlSheetNS = $this->loadZip("$dir/$fileWorksheet", $mainNS);
- $sharedFormulas = [];
+ // Shared Formula table is unique to each Worksheet, so we need to reset it here
+ $this->sharedFormulae = [];
if (isset($eleSheetAttr['state']) && (string) $eleSheetAttr['state'] != '') {
$docSheet->setSheetState((string) $eleSheetAttr['state']);
@@ -789,8 +843,12 @@ class Xlsx extends BaseReader
$coordinates = Coordinate::coordinateFromString($r);
if (!$this->getReadFilter()->readCell($coordinates[0], (int) $coordinates[1], $docSheet->getTitle())) {
- if (isset($cAttr->f)) {
- $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
+ // Normally, just testing for the f attribute should identify this cell as containing a formula
+ // that we need to read, even though it is outside of the filter range, in case it is a shared formula.
+ // But in some cases, this attribute isn't set; so we need to delve a level deeper and look at
+ // whether or not the cell has a child formula element that is shared.
+ if (isset($cAttr->f) || (isset($c->f, $c->f->attributes()['t']) && strtolower((string) $c->f->attributes()['t']) === 'shared')) {
+ $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError', false);
}
++$rowIndex;
@@ -822,7 +880,7 @@ class Xlsx extends BaseReader
}
} else {
// Formula
- $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToBoolean');
+ $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToBoolean');
if (isset($c->f['t'])) {
$att = $c->f;
$docSheet->getCell($r)->setFormulaAttributes($att);
@@ -832,7 +890,7 @@ class Xlsx extends BaseReader
break;
case 'inlineStr':
if (isset($c->f)) {
- $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
+ $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError');
} else {
$value = $this->parseRichText($c->is);
}
@@ -843,7 +901,7 @@ class Xlsx extends BaseReader
$value = self::castToError($c);
} else {
// Formula
- $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToError');
+ $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToError');
}
break;
@@ -852,7 +910,7 @@ class Xlsx extends BaseReader
$value = self::castToString($c);
} else {
// Formula
- $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, $sharedFormulas, 'castToString');
+ $this->castToFormula($c, $r, $cellDataType, $value, $calculatedValue, 'castToString');
if (isset($c->f['t'])) {
$attributes = $c->f['t'];
$docSheet->getCell($r)->setFormulaAttributes(['t' => (string) $attributes]);
@@ -891,6 +949,10 @@ class Xlsx extends BaseReader
// no style index means 0, it seems
$cell->setXfIndex(isset($styles[(int) ($cAttr['s'])]) ?
(int) ($cAttr['s']) : 0);
+ // issue 3495
+ if ($cell->getDataType() === DataType::TYPE_FORMULA) {
+ $cell->getStyle()->setQuotePrefix(false);
+ }
}
}
++$rowIndex;
@@ -898,6 +960,12 @@ class Xlsx extends BaseReader
++$cIndex;
}
}
+ if ($xmlSheetNS && $xmlSheetNS->ignoredErrors) {
+ foreach ($xmlSheetNS->ignoredErrors->ignoredError as $ignoredErrorx) {
+ $ignoredError = self::testSimpleXml($ignoredErrorx);
+ $this->processIgnoredErrors($ignoredError, $docSheet);
+ }
+ }
if (!$this->readDataOnly && $xmlSheetNS && $xmlSheetNS->sheetProtection) {
$protAttr = $xmlSheetNS->sheetProtection->attributes() ?? [];
@@ -2205,4 +2273,48 @@ class Xlsx extends BaseReader
return $array;
}
+
+ private function processIgnoredErrors(SimpleXMLElement $xml, Worksheet $sheet): void
+ {
+ $attributes = self::getAttributes($xml);
+ $sqref = (string) ($attributes['sqref'] ?? '');
+ $numberStoredAsText = (string) ($attributes['numberStoredAsText'] ?? '');
+ $formula = (string) ($attributes['formula'] ?? '');
+ $twoDigitTextYear = (string) ($attributes['twoDigitTextYear'] ?? '');
+ $evalError = (string) ($attributes['evalError'] ?? '');
+ if (!empty($sqref)) {
+ $explodedSqref = explode(' ', $sqref);
+ $pattern1 = '/^([A-Z]{1,3})([0-9]{1,7})(:([A-Z]{1,3})([0-9]{1,7}))?$/';
+ foreach ($explodedSqref as $sqref1) {
+ if (preg_match($pattern1, $sqref1, $matches) === 1) {
+ $firstRow = $matches[2];
+ $firstCol = $matches[1];
+ if (array_key_exists(3, $matches)) {
+ $lastCol = $matches[4];
+ $lastRow = $matches[5];
+ } else {
+ $lastCol = $firstCol;
+ $lastRow = $firstRow;
+ }
+ ++$lastCol;
+ for ($row = $firstRow; $row <= $lastRow; ++$row) {
+ for ($col = $firstCol; $col !== $lastCol; ++$col) {
+ if ($numberStoredAsText === '1') {
+ $sheet->getCell("$col$row")->getIgnoredErrors()->setNumberStoredAsText(true);
+ }
+ if ($formula === '1') {
+ $sheet->getCell("$col$row")->getIgnoredErrors()->setFormula(true);
+ }
+ if ($twoDigitTextYear === '1') {
+ $sheet->getCell("$col$row")->getIgnoredErrors()->setTwoDigitTextYear(true);
+ }
+ if ($evalError === '1') {
+ $sheet->getCell("$col$row")->getIgnoredErrors()->setEvalError(true);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php
index 5230cf0d7..c5a59f558 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Chart.php
@@ -4,6 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Reader\Xlsx;
use PhpOffice\PhpSpreadsheet\Calculation\Information\ExcelError;
use PhpOffice\PhpSpreadsheet\Chart\Axis;
+use PhpOffice\PhpSpreadsheet\Chart\AxisText;
use PhpOffice\PhpSpreadsheet\Chart\ChartColor;
use PhpOffice\PhpSpreadsheet\Chart\DataSeries;
use PhpOffice\PhpSpreadsheet\Chart\DataSeriesValues;
@@ -76,16 +77,28 @@ class Chart
$yAxis = new Axis();
$autoTitleDeleted = null;
$chartNoFill = false;
+ $chartBorderLines = null;
+ $chartFillColor = null;
$gradientArray = [];
$gradientLin = null;
$roundedCorners = false;
+ $gapWidth = null;
+ $useUpBars = null;
+ $useDownBars = null;
foreach ($chartElementsC as $chartElementKey => $chartElement) {
switch ($chartElementKey) {
case 'spPr':
- $possibleNoFill = $chartElementsC->spPr->children($this->aNamespace);
- if (isset($possibleNoFill->noFill)) {
+ $children = $chartElementsC->spPr->children($this->aNamespace);
+ if (isset($children->noFill)) {
$chartNoFill = true;
}
+ if (isset($children->solidFill)) {
+ $chartFillColor = $this->readColor($children->solidFill);
+ }
+ if (isset($children->ln)) {
+ $chartBorderLines = new GridLines();
+ $this->readLineStyle($chartElementsC, $chartBorderLines);
+ }
break;
case 'roundedCorners':
@@ -157,6 +170,9 @@ class Chart
$axisColorArray = $this->readColor($sppr->solidFill);
$xAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']);
}
+ if (isset($chartDetail->spPr->ln->noFill)) {
+ $xAxis->setNoFill(true);
+ }
}
if (isset($chartDetail->majorGridlines)) {
$majorGridlines = new GridLines();
@@ -227,6 +243,9 @@ class Chart
$axisColorArray = $this->readColor($sppr->solidFill);
$whichAxis->setFillParameters($axisColorArray['value'], $axisColorArray['alpha'], $axisColorArray['type']);
}
+ if (isset($sppr->ln->noFill)) {
+ $whichAxis->setNoFill(true);
+ }
}
if ($whichAxis !== null && isset($chartDetail->majorGridlines)) {
$majorGridlines = new GridLines();
@@ -316,6 +335,15 @@ class Chart
break;
case 'stockChart':
$plotSeries[] = $this->chartDataSeries($chartDetail, $chartDetailKey);
+ if (isset($chartDetail->upDownBars->gapWidth)) {
+ $gapWidth = self::getAttribute($chartDetail->upDownBars->gapWidth, 'val', 'integer');
+ }
+ if (isset($chartDetail->upDownBars->upBars)) {
+ $useUpBars = true;
+ }
+ if (isset($chartDetail->upDownBars->downBars)) {
+ $useDownBars = true;
+ }
$plotAttributes = $this->readChartAttributes($chartDetail);
break;
@@ -332,6 +360,15 @@ class Chart
if (!empty($gradientArray)) {
$plotArea->setGradientFillProperties($gradientArray, $gradientLin);
}
+ if (is_int($gapWidth)) {
+ $plotArea->setGapWidth($gapWidth);
+ }
+ if ($useUpBars === true) {
+ $plotArea->setUseUpBars(true);
+ }
+ if ($useDownBars === true) {
+ $plotArea->setUseDownBars(true);
+ }
break;
case 'plotVisOnly':
@@ -350,6 +387,10 @@ class Chart
$legendPos = 'r';
$legendLayout = null;
$legendOverlay = false;
+ $legendBorderLines = null;
+ $legendFillColor = null;
+ $legendText = null;
+ $addLegendText = false;
foreach ($chartDetails as $chartDetailKey => $chartDetail) {
$chartDetail = Xlsx::testSimpleXml($chartDetail);
switch ($chartDetailKey) {
@@ -364,10 +405,45 @@ class Chart
case 'layout':
$legendLayout = $this->chartLayoutDetails($chartDetail);
+ break;
+ case 'spPr':
+ $children = $chartDetails->spPr->children($this->aNamespace);
+ if (isset($children->solidFill)) {
+ $legendFillColor = $this->readColor($children->solidFill);
+ }
+ if (isset($children->ln)) {
+ $legendBorderLines = new GridLines();
+ $this->readLineStyle($chartDetails, $legendBorderLines);
+ }
+
+ break;
+ case 'txPr':
+ $children = $chartDetails->txPr->children($this->aNamespace);
+ $addLegendText = false;
+ $legendText = new AxisText();
+ if (isset($children->p->pPr->defRPr->solidFill)) {
+ $colorArray = $this->readColor($children->p->pPr->defRPr->solidFill);
+ $legendText->getFillColorObject()->setColorPropertiesArray($colorArray);
+ $addLegendText = true;
+ }
+ if (isset($children->p->pPr->defRPr->effectLst)) {
+ $this->readEffects($children->p->pPr->defRPr, $legendText, false);
+ $addLegendText = true;
+ }
+
break;
}
}
$legend = new Legend("$legendPos", $legendLayout, (bool) $legendOverlay);
+ if ($legendFillColor !== null) {
+ $legend->getFillColor()->setColorPropertiesArray($legendFillColor);
+ }
+ if ($legendBorderLines !== null) {
+ $legend->setBorderLines($legendBorderLines);
+ }
+ if ($addLegendText) {
+ $legend->setLegendText($legendText);
+ }
break;
}
@@ -378,6 +454,12 @@ class Chart
if ($chartNoFill) {
$chart->setNoFill(true);
}
+ if ($chartFillColor !== null) {
+ $chart->getFillColor()->setColorPropertiesArray($chartFillColor);
+ }
+ if ($chartBorderLines !== null) {
+ $chart->setBorderLines($chartBorderLines);
+ }
$chart->setRoundedCorners($roundedCorners);
if (is_bool($autoTitleDeleted)) {
$chart->setAutoTitleDeleted($autoTitleDeleted);
@@ -1082,6 +1164,37 @@ class Chart
return $value;
}
+ private function parseFont(SimpleXMLElement $titleDetailPart): ?Font
+ {
+ if (!isset($titleDetailPart->pPr->defRPr)) {
+ return null;
+ }
+ $fontArray = [];
+ $fontArray['size'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'sz', 'integer');
+ $fontArray['bold'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'b', 'boolean');
+ $fontArray['italic'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'i', 'boolean');
+ $fontArray['underscore'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'u', 'string');
+ $fontArray['strikethrough'] = self::getAttribute($titleDetailPart->pPr->defRPr, 'strike', 'string');
+
+ if (isset($titleDetailPart->pPr->defRPr->latin)) {
+ $fontArray['latin'] = self::getAttribute($titleDetailPart->pPr->defRPr->latin, 'typeface', 'string');
+ }
+ if (isset($titleDetailPart->pPr->defRPr->ea)) {
+ $fontArray['eastAsian'] = self::getAttribute($titleDetailPart->pPr->defRPr->ea, 'typeface', 'string');
+ }
+ if (isset($titleDetailPart->pPr->defRPr->cs)) {
+ $fontArray['complexScript'] = self::getAttribute($titleDetailPart->pPr->defRPr->cs, 'typeface', 'string');
+ }
+ if (isset($titleDetailPart->pPr->defRPr->solidFill)) {
+ $fontArray['chartColor'] = new ChartColor($this->readColor($titleDetailPart->pPr->defRPr->solidFill));
+ }
+ $font = new Font();
+ $font->setSize(null, true);
+ $font->applyFromArray($fontArray);
+
+ return $font;
+ }
+
/**
* @param ?SimpleXMLElement $chartDetail
*/
@@ -1128,8 +1241,13 @@ class Chart
}
if (isset($chartDetail->dLbls->txPr)) {
$txpr = $chartDetail->dLbls->txPr->children($this->aNamespace);
- if (isset($txpr->p->pPr->defRPr->solidFill)) {
- $plotAttributes['labelFontColor'] = new ChartColor($this->readColor($txpr->p->pPr->defRPr->solidFill));
+ if (isset($txpr->p)) {
+ $plotAttributes['labelFont'] = $this->parseFont($txpr->p);
+ if (isset($txpr->p->pPr->defRPr->effectLst)) {
+ $labelEffects = new GridLines();
+ $this->readEffects($txpr->p->pPr->defRPr, $labelEffects, false);
+ $plotAttributes['labelEffects'] = $labelEffects;
+ }
}
}
}
@@ -1176,13 +1294,19 @@ class Chart
}
}
- private function readEffects(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject): void
+ private function readEffects(SimpleXMLElement $chartDetail, ?ChartProperties $chartObject, bool $getSppr = true): void
{
- if (!isset($chartObject, $chartDetail->spPr)) {
+ if (!isset($chartObject)) {
return;
}
- $sppr = $chartDetail->spPr->children($this->aNamespace);
-
+ if ($getSppr) {
+ if (!isset($chartDetail->spPr)) {
+ return;
+ }
+ $sppr = $chartDetail->spPr->children($this->aNamespace);
+ } else {
+ $sppr = $chartDetail;
+ }
if (isset($sppr->effectLst->glow)) {
$axisGlowSize = (float) self::getAttribute($sppr->effectLst->glow, 'rad', 'integer') / ChartProperties::POINTS_WIDTH_MULTIPLIER;
if ($axisGlowSize != 0.0) {
@@ -1412,13 +1536,30 @@ class Chart
}
if (isset($chartDetail->txPr)) {
$children = $chartDetail->txPr->children($this->aNamespace);
+ $addAxisText = false;
+ $axisText = new AxisText();
if (isset($children->bodyPr)) {
/** @var string */
$textRotation = self::getAttribute($children->bodyPr, 'rot', 'string');
if (is_numeric($textRotation)) {
- $whichAxis->setAxisOption('textRotation', (string) ChartProperties::xmlToAngle($textRotation));
+ $axisText->setRotation((int) ChartProperties::xmlToAngle($textRotation));
+ $addAxisText = true;
}
}
+ if (isset($children->p->pPr->defRPr)) {
+ $font = $this->parseFont($children->p);
+ if ($font !== null) {
+ $axisText->setFont($font);
+ $addAxisText = true;
+ }
+ }
+ if (isset($children->p->pPr->defRPr->effectLst)) {
+ $this->readEffects($children->p->pPr->defRPr, $axisText, false);
+ $addAxisText = true;
+ }
+ if ($addAxisText) {
+ $whichAxis->setAxisText($axisText);
+ }
}
}
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php
index dac76230c..210c322f9 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/DataValidations.php
@@ -22,6 +22,18 @@ class DataValidations
public function load(): void
{
+ foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) {
+ // Uppercase coordinate
+ $range = strtoupper((string) $dataValidation['sqref']);
+ $rangeSet = explode(' ', $range);
+ foreach ($rangeSet as $range) {
+ if (preg_match('/^[A-Z]{1,3}\\d{1,7}/', $range, $matches) === 1) {
+ // Ensure left/top row of range exists, thereby
+ // adjusting high row/column.
+ $this->worksheet->getCell($matches[0]);
+ }
+ }
+ }
foreach ($this->worksheetXml->dataValidations->dataValidation as $dataValidation) {
// Uppercase coordinate
$range = strtoupper((string) $dataValidation['sqref']);
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php
index 72addffd5..0d4701afa 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Properties.php
@@ -73,6 +73,9 @@ class Properties
if (isset($xmlCore->Manager)) {
$this->docProps->setManager((string) $xmlCore->Manager);
}
+ if (isset($xmlCore->HyperlinkBase)) {
+ $this->docProps->setHyperlinkBase((string) $xmlCore->HyperlinkBase);
+ }
}
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php
new file mode 100644
index 000000000..fb7a39320
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/SharedFormula.php
@@ -0,0 +1,26 @@
+master = $master;
+ $this->formula = $formula;
+ }
+
+ public function master(): string
+ {
+ return $this->master;
+ }
+
+ public function formula(): string
+ {
+ return $this->formula;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php
index 705b319e7..5a360fda1 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Styles.php
@@ -136,6 +136,10 @@ class Styles extends BaseParserClass
}
}
}
+ if (isset($fontStyleXml->scheme)) {
+ $attr = $this->getStyleAttributes($fontStyleXml->scheme);
+ $fontStyle->setScheme((string) $attr['val']);
+ }
}
private function readNumberFormat(NumberFormat $numfmtStyle, SimpleXMLElement $numfmtStyleXml): void
@@ -253,10 +257,14 @@ class Styles extends BaseParserClass
public function readAlignmentStyle(Alignment $alignment, SimpleXMLElement $alignmentXml): void
{
- $horizontal = $this->getAttribute($alignmentXml, 'horizontal');
- $alignment->setHorizontal($horizontal);
- $vertical = $this->getAttribute($alignmentXml, 'vertical');
- $alignment->setVertical((string) $vertical);
+ $horizontal = (string) $this->getAttribute($alignmentXml, 'horizontal');
+ if ($horizontal !== '') {
+ $alignment->setHorizontal($horizontal);
+ }
+ $vertical = (string) $this->getAttribute($alignmentXml, 'vertical');
+ if ($vertical !== '') {
+ $alignment->setVertical($vertical);
+ }
$textRotation = (int) $this->getAttribute($alignmentXml, 'textRotation');
if ($textRotation > 90) {
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php
index 9cce7003f..6be26fc2c 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml.php
@@ -9,6 +9,7 @@ use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Cell\DataType;
use PhpOffice\PhpSpreadsheet\DefinedName;
use PhpOffice\PhpSpreadsheet\Reader\Security\XmlScanner;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
use PhpOffice\PhpSpreadsheet\Reader\Xml\PageSettings;
use PhpOffice\PhpSpreadsheet\Reader\Xml\Properties;
use PhpOffice\PhpSpreadsheet\Reader\Xml\Style;
@@ -26,6 +27,8 @@ use SimpleXMLElement;
*/
class Xml extends BaseReader
{
+ public const NAMESPACES_SS = 'urn:schemas-microsoft-com:office:spreadsheet';
+
/**
* Formats.
*
@@ -146,11 +149,9 @@ class Xml extends BaseReader
throw new Exception("Problem reading {$filename}");
}
- $namespaces = $xml->getNamespaces(true);
-
- $xml_ss = $xml->children($namespaces['ss']);
+ $xml_ss = $xml->children(self::NAMESPACES_SS);
foreach ($xml_ss->Worksheet as $worksheet) {
- $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']);
+ $worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS);
$worksheetNames[] = (string) $worksheet_ss['Name'];
}
@@ -178,12 +179,10 @@ class Xml extends BaseReader
throw new Exception("Problem reading {$filename}");
}
- $namespaces = $xml->getNamespaces(true);
-
$worksheetID = 1;
- $xml_ss = $xml->children($namespaces['ss']);
+ $xml_ss = $xml->children(self::NAMESPACES_SS);
foreach ($xml_ss->Worksheet as $worksheet) {
- $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']);
+ $worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS);
$tmpInfo = [];
$tmpInfo['worksheetName'] = '';
@@ -231,6 +230,19 @@ class Xml extends BaseReader
return $worksheetInfo;
}
+ /**
+ * Loads Spreadsheet from string.
+ */
+ public function loadSpreadsheetFromString(string $contents): Spreadsheet
+ {
+ // Create new Spreadsheet
+ $spreadsheet = new Spreadsheet();
+ $spreadsheet->removeSheetByIndex(0);
+
+ // Load into this instance
+ return $this->loadIntoExisting($contents, $spreadsheet, true);
+ }
+
/**
* Loads Spreadsheet from file.
*/
@@ -245,17 +257,19 @@ class Xml extends BaseReader
}
/**
- * Loads from file into Spreadsheet instance.
+ * Loads from file or contents into Spreadsheet instance.
*
- * @param string $filename
- *
- * @return Spreadsheet
+ * @param string $filename file name if useContents is false else file contents
*/
- public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
+ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet, bool $useContents = false): Spreadsheet
{
- File::assertFile($filename);
- if (!$this->canRead($filename)) {
- throw new Exception($filename . ' is an Invalid Spreadsheet file.');
+ if ($useContents) {
+ $this->fileContents = $filename;
+ } else {
+ File::assertFile($filename);
+ if (!$this->canRead($filename)) {
+ throw new Exception($filename . ' is an Invalid Spreadsheet file.');
+ }
}
$xml = $this->trySimpleXMLLoadString($filename);
@@ -268,14 +282,17 @@ class Xml extends BaseReader
(new Properties($spreadsheet))->readProperties($xml, $namespaces);
$this->styles = (new Style())->parseStyles($xml, $namespaces);
+ if (isset($this->styles['Default'])) {
+ $spreadsheet->getCellXfCollection()[0]->applyFromArray($this->styles['Default']);
+ }
$worksheetID = 0;
- $xml_ss = $xml->children($namespaces['ss']);
+ $xml_ss = $xml->children(self::NAMESPACES_SS);
/** @var null|SimpleXMLElement $worksheetx */
foreach ($xml_ss->Worksheet as $worksheetx) {
$worksheet = $worksheetx ?? new SimpleXMLElement('');
- $worksheet_ss = self::getAttributes($worksheet, $namespaces['ss']);
+ $worksheet_ss = self::getAttributes($worksheet, self::NAMESPACES_SS);
if (
isset($this->loadSheetsOnly, $worksheet_ss['Name']) &&
@@ -295,11 +312,15 @@ class Xml extends BaseReader
// the worksheet name in line with the formula, not the reverse
$spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
}
+ if (isset($worksheet_ss['Protected'])) {
+ $protection = (string) $worksheet_ss['Protected'] === '1';
+ $spreadsheet->getActiveSheet()->getProtection()->setSheet($protection);
+ }
// locally scoped defined names
if (isset($worksheet->Names[0])) {
foreach ($worksheet->Names[0] as $definedName) {
- $definedName_ss = self::getAttributes($definedName, $namespaces['ss']);
+ $definedName_ss = self::getAttributes($definedName, self::NAMESPACES_SS);
$name = (string) $definedName_ss['Name'];
$definedValue = (string) $definedName_ss['RefersTo'];
$convertedValue = AddressHelper::convertFormulaToA1($definedValue);
@@ -313,15 +334,35 @@ class Xml extends BaseReader
$columnID = 'A';
if (isset($worksheet->Table->Column)) {
foreach ($worksheet->Table->Column as $columnData) {
- $columnData_ss = self::getAttributes($columnData, $namespaces['ss']);
+ $columnData_ss = self::getAttributes($columnData, self::NAMESPACES_SS);
+ $colspan = 0;
+ if (isset($columnData_ss['Span'])) {
+ $spanAttr = (string) $columnData_ss['Span'];
+ if (is_numeric($spanAttr)) {
+ $colspan = max(0, (int) $spanAttr);
+ }
+ }
if (isset($columnData_ss['Index'])) {
$columnID = Coordinate::stringFromColumnIndex((int) $columnData_ss['Index']);
}
+ $columnWidth = null;
if (isset($columnData_ss['Width'])) {
$columnWidth = $columnData_ss['Width'];
- $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
}
- ++$columnID;
+ $columnVisible = null;
+ if (isset($columnData_ss['Hidden'])) {
+ $columnVisible = ((string) $columnData_ss['Hidden']) !== '1';
+ }
+ while ($colspan >= 0) {
+ if (isset($columnWidth)) {
+ $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setWidth($columnWidth / 5.4);
+ }
+ if (isset($columnVisible)) {
+ $spreadsheet->getActiveSheet()->getColumnDimension($columnID)->setVisible($columnVisible);
+ }
+ ++$columnID;
+ --$colspan;
+ }
}
}
@@ -330,14 +371,18 @@ class Xml extends BaseReader
$additionalMergedCells = 0;
foreach ($worksheet->Table->Row as $rowData) {
$rowHasData = false;
- $row_ss = self::getAttributes($rowData, $namespaces['ss']);
+ $row_ss = self::getAttributes($rowData, self::NAMESPACES_SS);
if (isset($row_ss['Index'])) {
$rowID = (int) $row_ss['Index'];
}
+ if (isset($row_ss['Hidden'])) {
+ $rowVisible = ((string) $row_ss['Hidden']) !== '1';
+ $spreadsheet->getActiveSheet()->getRowDimension($rowID)->setVisible($rowVisible);
+ }
$columnID = 'A';
foreach ($rowData->Cell as $cell) {
- $cell_ss = self::getAttributes($cell, $namespaces['ss']);
+ $cell_ss = self::getAttributes($cell, self::NAMESPACES_SS);
if (isset($cell_ss['Index'])) {
$columnID = Coordinate::stringFromColumnIndex((int) $cell_ss['Index']);
}
@@ -379,7 +424,7 @@ class Xml extends BaseReader
$cellData = $cell->Data;
$cellValue = (string) $cellData;
$type = DataType::TYPE_NULL;
- $cellData_ss = self::getAttributes($cellData, $namespaces['ss']);
+ $cellData_ss = self::getAttributes($cellData, self::NAMESPACES_SS);
if (isset($cellData_ss['Type'])) {
$cellDataType = $cellData_ss['Type'];
switch ($cellDataType) {
@@ -437,7 +482,7 @@ class Xml extends BaseReader
}
if (isset($cell->Comment)) {
- $this->parseCellComment($cell->Comment, $namespaces, $spreadsheet, $columnID, $rowID);
+ $this->parseCellComment($cell->Comment, $spreadsheet, $columnID, $rowID);
}
if (isset($cell_ss['StyleID'])) {
@@ -466,11 +511,57 @@ class Xml extends BaseReader
++$rowID;
}
+ }
- if (isset($namespaces['x'])) {
- $xmlX = $worksheet->children($namespaces['x']);
- if (isset($xmlX->WorksheetOptions)) {
- (new PageSettings($xmlX, $namespaces))->loadPageSettings($spreadsheet);
+ $dataValidations = new Xml\DataValidations();
+ $dataValidations->loadDataValidations($worksheet, $spreadsheet);
+ $xmlX = $worksheet->children(Namespaces::URN_EXCEL);
+ if (isset($xmlX->WorksheetOptions)) {
+ if (isset($xmlX->WorksheetOptions->FreezePanes)) {
+ $freezeRow = $freezeColumn = 1;
+ if (isset($xmlX->WorksheetOptions->SplitHorizontal)) {
+ $freezeRow = (int) $xmlX->WorksheetOptions->SplitHorizontal + 1;
+ }
+ if (isset($xmlX->WorksheetOptions->SplitVertical)) {
+ $freezeColumn = (int) $xmlX->WorksheetOptions->SplitVertical + 1;
+ }
+ $spreadsheet->getActiveSheet()->freezePane(Coordinate::stringFromColumnIndex($freezeColumn) . (string) $freezeRow);
+ }
+ (new PageSettings($xmlX))->loadPageSettings($spreadsheet);
+ if (isset($xmlX->WorksheetOptions->TopRowVisible, $xmlX->WorksheetOptions->LeftColumnVisible)) {
+ $leftTopRow = (string) $xmlX->WorksheetOptions->TopRowVisible;
+ $leftTopColumn = (string) $xmlX->WorksheetOptions->LeftColumnVisible;
+ if (is_numeric($leftTopRow) && is_numeric($leftTopColumn)) {
+ $leftTopCoordinate = Coordinate::stringFromColumnIndex((int) $leftTopColumn + 1) . (string) ($leftTopRow + 1);
+ $spreadsheet->getActiveSheet()->setTopLeftCell($leftTopCoordinate);
+ }
+ }
+ $rangeCalculated = false;
+ if (isset($xmlX->WorksheetOptions->Panes->Pane->RangeSelection)) {
+ if (1 === preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $xmlX->WorksheetOptions->Panes->Pane->RangeSelection, $selectionMatches)) {
+ $selectedCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2])
+ . $selectionMatches[1]
+ . ':'
+ . Coordinate::stringFromColumnIndex((int) $selectionMatches[4])
+ . $selectionMatches[3];
+ $spreadsheet->getActiveSheet()->setSelectedCells($selectedCell);
+ $rangeCalculated = true;
+ }
+ }
+ if (!$rangeCalculated) {
+ if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveRow)) {
+ $activeRow = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveRow;
+ } else {
+ $activeRow = 0;
+ }
+ if (isset($xmlX->WorksheetOptions->Panes->Pane->ActiveCol)) {
+ $activeColumn = (string) $xmlX->WorksheetOptions->Panes->Pane->ActiveCol;
+ } else {
+ $activeColumn = 0;
+ }
+ if (is_numeric($activeRow) && is_numeric($activeColumn)) {
+ $selectedCell = Coordinate::stringFromColumnIndex((int) $activeColumn + 1) . (string) ($activeRow + 1);
+ $spreadsheet->getActiveSheet()->setSelectedCells($selectedCell);
}
}
}
@@ -478,10 +569,14 @@ class Xml extends BaseReader
}
// Globally scoped defined names
- $activeWorksheet = $spreadsheet->setActiveSheetIndex(0);
+ $activeSheetIndex = 0;
+ if (isset($xml->ExcelWorkbook->ActiveSheet)) {
+ $activeSheetIndex = (int) (string) $xml->ExcelWorkbook->ActiveSheet;
+ }
+ $activeWorksheet = $spreadsheet->setActiveSheetIndex($activeSheetIndex);
if (isset($xml->Names[0])) {
foreach ($xml->Names[0] as $definedName) {
- $definedName_ss = self::getAttributes($definedName, $namespaces['ss']);
+ $definedName_ss = self::getAttributes($definedName, self::NAMESPACES_SS);
$name = (string) $definedName_ss['Name'];
$definedValue = (string) $definedName_ss['RefersTo'];
$convertedValue = AddressHelper::convertFormulaToA1($definedValue);
@@ -498,12 +593,11 @@ class Xml extends BaseReader
protected function parseCellComment(
SimpleXMLElement $comment,
- array $namespaces,
Spreadsheet $spreadsheet,
string $columnID,
int $rowID
): void {
- $commentAttributes = $comment->attributes($namespaces['ss']);
+ $commentAttributes = $comment->attributes(self::NAMESPACES_SS);
$author = 'unknown';
if (isset($commentAttributes->Author)) {
$author = (string) $commentAttributes->Author;
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/DataValidations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/DataValidations.php
new file mode 100644
index 000000000..31748cb9c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/DataValidations.php
@@ -0,0 +1,177 @@
+ DataValidation::OPERATOR_BETWEEN,
+ 'equal' => DataValidation::OPERATOR_EQUAL,
+ 'greater' => DataValidation::OPERATOR_GREATERTHAN,
+ 'greaterorequal' => DataValidation::OPERATOR_GREATERTHANOREQUAL,
+ 'less' => DataValidation::OPERATOR_LESSTHAN,
+ 'lessorequal' => DataValidation::OPERATOR_LESSTHANOREQUAL,
+ 'notbetween' => DataValidation::OPERATOR_NOTBETWEEN,
+ 'notequal' => DataValidation::OPERATOR_NOTEQUAL,
+ ];
+
+ private const TYPE_MAPPINGS = [
+ 'textlength' => DataValidation::TYPE_TEXTLENGTH,
+ ];
+
+ private int $thisRow = 0;
+
+ private int $thisColumn = 0;
+
+ private function replaceR1C1(array $matches): string
+ {
+ return AddressHelper::convertToA1($matches[0], $this->thisRow, $this->thisColumn, false);
+ }
+
+ public function loadDataValidations(SimpleXMLElement $worksheet, Spreadsheet $spreadsheet): void
+ {
+ $xmlX = $worksheet->children(Namespaces::URN_EXCEL);
+ $sheet = $spreadsheet->getActiveSheet();
+ /** @var callable */
+ $pregCallback = [$this, 'replaceR1C1'];
+ foreach ($xmlX->DataValidation as $dataValidation) {
+ $cells = [];
+ $validation = new DataValidation();
+
+ // set defaults
+ $validation->setShowDropDown(true);
+ $validation->setShowInputMessage(true);
+ $validation->setShowErrorMessage(true);
+ $validation->setShowDropDown(true);
+ $this->thisRow = 1;
+ $this->thisColumn = 1;
+
+ foreach ($dataValidation as $tagName => $tagValue) {
+ $tagValue = (string) $tagValue;
+ $tagValueLower = strtolower($tagValue);
+ switch ($tagName) {
+ case 'Range':
+ foreach (explode(',', $tagValue) as $range) {
+ $cell = '';
+ if (preg_match('/^R(\d+)C(\d+):R(\d+)C(\d+)$/', (string) $range, $selectionMatches) === 1) {
+ // range
+ $firstCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2])
+ . $selectionMatches[1];
+ $cell = $firstCell
+ . ':'
+ . Coordinate::stringFromColumnIndex((int) $selectionMatches[4])
+ . $selectionMatches[3];
+ $this->thisRow = (int) $selectionMatches[1];
+ $this->thisColumn = (int) $selectionMatches[2];
+ $sheet->getCell($firstCell);
+ } elseif (preg_match('/^R(\d+)C(\d+)$/', (string) $range, $selectionMatches) === 1) {
+ // cell
+ $cell = Coordinate::stringFromColumnIndex((int) $selectionMatches[2])
+ . $selectionMatches[1];
+ $sheet->getCell($cell);
+ $this->thisRow = (int) $selectionMatches[1];
+ $this->thisColumn = (int) $selectionMatches[2];
+ } elseif (preg_match('/^C(\d+)$/', (string) $range, $selectionMatches) === 1) {
+ // column
+ $firstCell = Coordinate::stringFromColumnIndex((int) $selectionMatches[1])
+ . '1';
+ $cell = $firstCell
+ . ':'
+ . Coordinate::stringFromColumnIndex((int) $selectionMatches[1])
+ . ((string) AddressRange::MAX_ROW);
+ $this->thisColumn = (int) $selectionMatches[1];
+ $sheet->getCell($firstCell);
+ } elseif (preg_match('/^R(\d+)$/', (string) $range, $selectionMatches)) {
+ // row
+ $firstCell = 'A'
+ . $selectionMatches[1];
+ $cell = $firstCell
+ . ':'
+ . AddressRange::MAX_COLUMN
+ . $selectionMatches[1];
+ $this->thisRow = (int) $selectionMatches[1];
+ $sheet->getCell($firstCell);
+ }
+
+ $validation->setSqref($cell);
+ $stRange = $sheet->shrinkRangeToFit($cell);
+ $cells = array_merge($cells, Coordinate::extractAllCellReferencesInRange($stRange));
+ }
+
+ break;
+ case 'Type':
+ $validation->setType(self::TYPE_MAPPINGS[$tagValueLower] ?? $tagValueLower);
+
+ break;
+ case 'Qualifier':
+ $validation->setOperator(self::OPERATOR_MAPPINGS[$tagValueLower] ?? $tagValueLower);
+
+ break;
+ case 'InputTitle':
+ $validation->setPromptTitle($tagValue);
+
+ break;
+ case 'InputMessage':
+ $validation->setPrompt($tagValue);
+
+ break;
+ case 'InputHide':
+ $validation->setShowInputMessage(false);
+
+ break;
+ case 'ErrorStyle':
+ $validation->setErrorStyle($tagValueLower);
+
+ break;
+ case 'ErrorTitle':
+ $validation->setErrorTitle($tagValue);
+
+ break;
+ case 'ErrorMessage':
+ $validation->setError($tagValue);
+
+ break;
+ case 'ErrorHide':
+ $validation->setShowErrorMessage(false);
+
+ break;
+ case 'ComboHide':
+ $validation->setShowDropDown(false);
+
+ break;
+ case 'UseBlank':
+ $validation->setAllowBlank(true);
+
+ break;
+ case 'CellRangeList':
+ // FIXME missing FIXME
+
+ break;
+ case 'Min':
+ case 'Value':
+ $tagValue = (string) preg_replace_callback(AddressHelper::R1C1_COORDINATE_REGEX, $pregCallback, $tagValue);
+ $validation->setFormula1($tagValue);
+
+ break;
+ case 'Max':
+ $tagValue = (string) preg_replace_callback(AddressHelper::R1C1_COORDINATE_REGEX, $pregCallback, $tagValue);
+ $validation->setFormula2($tagValue);
+
+ break;
+ }
+ }
+
+ foreach ($cells as $cell) {
+ $sheet->getCell($cell)->setDataValidation(clone $validation);
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php
index 39535c3e7..137cabaf3 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php
@@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
+use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\PageSetup;
use SimpleXMLElement;
@@ -14,9 +15,9 @@ class PageSettings
*/
private $printSettings;
- public function __construct(SimpleXMLElement $xmlX, array $namespaces)
+ public function __construct(SimpleXMLElement $xmlX)
{
- $printSettings = $this->pageSetup($xmlX, $namespaces, $this->getPrintDefaults());
+ $printSettings = $this->pageSetup($xmlX, $this->getPrintDefaults());
$this->printSettings = $this->printSetup($xmlX, $printSettings);
}
@@ -56,13 +57,13 @@ class PageSettings
];
}
- private function pageSetup(SimpleXMLElement $xmlX, array $namespaces, stdClass $printDefaults): stdClass
+ private function pageSetup(SimpleXMLElement $xmlX, stdClass $printDefaults): stdClass
{
if (isset($xmlX->WorksheetOptions->PageSetup)) {
foreach ($xmlX->WorksheetOptions->PageSetup as $pageSetupData) {
foreach ($pageSetupData as $pageSetupKey => $pageSetupValue) {
/** @scrutinizer ignore-call */
- $pageSetupAttributes = $pageSetupValue->attributes($namespaces['x']);
+ $pageSetupAttributes = $pageSetupValue->attributes(Namespaces::URN_EXCEL);
if ($pageSetupAttributes !== null) {
switch ($pageSetupKey) {
case 'Layout':
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php
index f0346ed02..e216c254d 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Properties.php
@@ -92,6 +92,10 @@ class Properties
case 'Manager':
$docProps->setManager($stringValue);
+ break;
+ case 'HyperlinkBase':
+ $docProps->setHyperlinkBase($stringValue);
+
break;
case 'Keywords':
$docProps->setKeywords($stringValue);
@@ -110,17 +114,10 @@ class Properties
?SimpleXMLElement $propertyValue,
SimpleXMLElement $propertyAttributes
): void {
- $propertyType = DocumentProperties::PROPERTY_TYPE_UNKNOWN;
-
switch ((string) $propertyAttributes) {
- case 'string':
- $propertyType = DocumentProperties::PROPERTY_TYPE_STRING;
- $propertyValue = trim((string) $propertyValue);
-
- break;
case 'boolean':
$propertyType = DocumentProperties::PROPERTY_TYPE_BOOLEAN;
- $propertyValue = (bool) $propertyValue;
+ $propertyValue = (bool) (string) $propertyValue;
break;
case 'integer':
@@ -134,9 +131,15 @@ class Properties
break;
case 'dateTime.tz':
+ case 'dateTime.iso8601tz':
$propertyType = DocumentProperties::PROPERTY_TYPE_DATE;
$propertyValue = trim((string) $propertyValue);
+ break;
+ default:
+ $propertyType = DocumentProperties::PROPERTY_TYPE_STRING;
+ $propertyValue = trim((string) $propertyValue);
+
break;
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php
index 774fffe8b..698acf6ac 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style.php
@@ -2,6 +2,7 @@
namespace PhpOffice\PhpSpreadsheet\Reader\Xml;
+use PhpOffice\PhpSpreadsheet\Style\Protection;
use SimpleXMLElement;
class Style
@@ -30,7 +31,7 @@ class Style
$styleID = (string) $style_ss['ID'];
$this->styles[$styleID] = $this->styles['Default'] ?? [];
- $alignment = $border = $font = $fill = $numberFormat = [];
+ $alignment = $border = $font = $fill = $numberFormat = $protection = [];
foreach ($style as $styleType => $styleDatax) {
$styleData = self::getSxml($styleDatax);
@@ -64,11 +65,31 @@ class Style
$numberFormat = $numberFormatStyleParser->parseStyle($styleAttributes);
}
+ break;
+ case 'Protection':
+ $locked = $hidden = null;
+ $styleAttributesP = $styleData->attributes($namespaces['x']);
+ if (isset($styleAttributes['Protected'])) {
+ $locked = ((bool) (string) $styleAttributes['Protected']) ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED;
+ }
+ if (isset($styleAttributesP['HideFormula'])) {
+ $hidden = ((bool) (string) $styleAttributesP['HideFormula']) ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED;
+ }
+ if ($locked !== null || $hidden !== null) {
+ $protection['protection'] = [];
+ if ($locked !== null) {
+ $protection['protection']['locked'] = $locked;
+ }
+ if ($hidden !== null) {
+ $protection['protection']['hidden'] = $hidden;
+ }
+ }
+
break;
}
}
- $this->styles[$styleID] = array_merge($alignment, $border, $font, $fill, $numberFormat);
+ $this->styles[$styleID] = array_merge($alignment, $border, $font, $fill, $numberFormat, $protection);
}
return $this->styles;
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php
index 16ab44d80..5f824889a 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php
@@ -56,11 +56,11 @@ class Font extends StyleBase
break;
case 'Bold':
- $style['font']['bold'] = true;
+ $style['font']['bold'] = $styleAttributeValue === '1';
break;
case 'Italic':
- $style['font']['italic'] = true;
+ $style['font']['italic'] = $styleAttributeValue === '1';
break;
case 'Underline':
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php
index b4d4d52af..90eee534d 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/ReferenceHelper.php
@@ -75,14 +75,11 @@ class ReferenceHelper
*
* @return int
*/
- public static function columnReverseSort($a, $b)
+ public static function columnReverseSort(string $a, string $b)
{
return -strcasecmp(strlen($a) . $a, strlen($b) . $b);
}
- /** @var int */
- private static $scrutinizer0 = 0;
-
/**
* Compare two cell addresses
* Intended for use as a Callback function for sorting cell addresses by column and row.
@@ -92,16 +89,16 @@ class ReferenceHelper
*
* @return int
*/
- public static function cellSort($a, $b)
+ public static function cellSort(string $a, string $b)
{
- $ac = $bc = '';
- $ar = self::$scrutinizer0;
- $br = 0;
+ /** @scrutinizer be-damned */
sscanf($a, '%[A-Z]%d', $ac, $ar);
+ /** @var int $ar */
+ /** @var string $ac */
+ /** @scrutinizer be-damned */
sscanf($b, '%[A-Z]%d', $bc, $br);
-
- $ac = (string) $ac;
- $bc = (string) $bc;
+ /** @var int $br */
+ /** @var string $bc */
if ($ar === $br) {
return strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
}
@@ -118,16 +115,16 @@ class ReferenceHelper
*
* @return int
*/
- public static function cellReverseSort($a, $b)
+ public static function cellReverseSort(string $a, string $b)
{
- $ac = $bc = '';
- $ar = self::$scrutinizer0;
- $br = 0;
+ /** @scrutinizer be-damned */
sscanf($a, '%[A-Z]%d', $ac, $ar);
+ /** @var int $ar */
+ /** @var string $ac */
+ /** @scrutinizer be-damned */
sscanf($b, '%[A-Z]%d', $bc, $br);
-
- $ac = (string) $ac;
- $bc = (string) $bc;
+ /** @var int $br */
+ /** @var string $bc */
if ($ar === $br) {
return -strcasecmp(strlen($ac) . $ac, strlen($bc) . $bc);
}
@@ -142,7 +139,7 @@ class ReferenceHelper
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/
- protected function adjustPageBreaks(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void
+ protected function adjustPageBreaks(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
{
$aBreaks = $worksheet->getBreaks();
($numberOfColumns > 0 || $numberOfRows > 0)
@@ -171,7 +168,7 @@ class ReferenceHelper
*
* @param Worksheet $worksheet The worksheet that we're editing
*/
- protected function adjustComments($worksheet): void
+ protected function adjustComments(Worksheet $worksheet): void
{
$aComments = $worksheet->getComments();
$aNewComments = []; // the new array of all comments
@@ -195,7 +192,7 @@ class ReferenceHelper
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/
- protected function adjustHyperlinks($worksheet, $numberOfColumns, $numberOfRows): void
+ protected function adjustHyperlinks(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
{
$aHyperlinkCollection = $worksheet->getHyperlinkCollection();
($numberOfColumns > 0 || $numberOfRows > 0)
@@ -220,7 +217,7 @@ class ReferenceHelper
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/
- protected function adjustConditionalFormatting($worksheet, $numberOfColumns, $numberOfRows): void
+ protected function adjustConditionalFormatting(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
{
$aStyles = $worksheet->getConditionalStylesCollection();
($numberOfColumns > 0 || $numberOfRows > 0)
@@ -259,7 +256,7 @@ class ReferenceHelper
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/
- protected function adjustDataValidations(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void
+ protected function adjustDataValidations(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
{
$aDataValidationCollection = $worksheet->getDataValidationCollection();
($numberOfColumns > 0 || $numberOfRows > 0)
@@ -299,7 +296,7 @@ class ReferenceHelper
* @param int $numberOfColumns Number of columns to insert/delete (negative values indicate deletion)
* @param int $numberOfRows Number of rows to insert/delete (negative values indicate deletion)
*/
- protected function adjustProtectedCells(Worksheet $worksheet, $numberOfColumns, $numberOfRows): void
+ protected function adjustProtectedCells(Worksheet $worksheet, int $numberOfColumns, int $numberOfRows): void
{
$aProtectedCells = $worksheet->getProtectedCells();
($numberOfColumns > 0 || $numberOfRows > 0)
@@ -412,7 +409,7 @@ class ReferenceHelper
$cellCollection = $worksheet->getCellCollection();
$missingCoordinates = array_filter(
array_map(function ($row) use ($highestColumn) {
- return $highestColumn . $row;
+ return "{$highestColumn}{$row}";
}, range(1, $highestRow)),
function ($coordinate) use ($cellCollection) {
return $cellCollection->has($coordinate) === false;
@@ -453,9 +450,9 @@ class ReferenceHelper
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
// Formula should be adjusted
$worksheet->getCell($newCoordinate)
- ->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle()));
+ ->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true));
} else {
- // Formula should not be adjusted
+ // Cell value should not be adjusted
$worksheet->getCell($newCoordinate)->setValueExplicit($cell->getValue(), $cell->getDataType());
}
@@ -463,10 +460,10 @@ class ReferenceHelper
$worksheet->getCellCollection()->delete($coordinate);
} else {
/* We don't need to update styles for rows/columns before our insertion position,
- but we do still need to adjust any formulae in those cells */
+ but we do still need to adjust any formulae in those cells */
if ($cell->getDataType() === DataType::TYPE_FORMULA) {
// Formula should be adjusted
- $cell->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle()));
+ $cell->setValue($this->updateFormulaReferences($cell->getValue(), $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true));
}
}
}
@@ -609,7 +606,7 @@ class ReferenceHelper
// Max worksheet size is 1,048,576 rows by 16,384 columns in Excel 2007, so our adjustments need to be at least one digit more
$column = 100000;
$row = 10000000 + (int) trim($match[3], '$');
- $cellIndex = $column . $row;
+ $cellIndex = "{$column}{$row}";
$newCellTokens[$cellIndex] = preg_quote($toString, '/');
$cellTokens[$cellIndex] = '/(?getValue();
$asFormula = ($cellAddress[0] === '=');
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
+ /**
+ * If we delete the entire range that is referenced by a Named Range, MS Excel sets the value to #REF!
+ * PhpSpreadsheet still only does a basic adjustment, so the Named Range will still reference Cells.
+ * Note that this applies only when deleting columns/rows; subsequent insertion won't fix the #REF!
+ * TODO Can we work out a method to identify Named Ranges that cease to be valid, so that we can replace
+ * them with a #REF!
+ */
if ($asFormula === true) {
- $formula = $this->updateFormulaReferences($cellAddress, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle());
+ $formula = $this->updateFormulaReferences($cellAddress, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true);
$definedName->setValue($formula);
} else {
- $definedName->setValue($this->updateCellReference(ltrim($cellAddress, '=')));
+ $definedName->setValue($this->updateCellReference(ltrim($cellAddress, '='), true));
}
}
}
@@ -929,8 +933,15 @@ class ReferenceHelper
private function updateNamedFormula(DefinedName $definedName, Worksheet $worksheet, string $beforeCellAddress, int $numberOfColumns, int $numberOfRows): void
{
if ($definedName->getWorksheet() !== null && $definedName->getWorksheet()->getHashCode() === $worksheet->getHashCode()) {
+ /**
+ * If we delete the entire range that is referenced by a Named Formula, MS Excel sets the value to #REF!
+ * PhpSpreadsheet still only does a basic adjustment, so the Named Formula will still reference Cells.
+ * Note that this applies only when deleting columns/rows; subsequent insertion won't fix the #REF!
+ * TODO Can we work out a method to identify Named Ranges that cease to be valid, so that we can replace
+ * them with a #REF!
+ */
$formula = $definedName->getValue();
- $formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle());
+ $formula = $this->updateFormulaReferences($formula, $beforeCellAddress, $numberOfColumns, $numberOfRows, $worksheet->getTitle(), true);
$definedName->setValue($formula);
}
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php
index 23733436b..ee693a2b3 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/TextElement.php
@@ -47,7 +47,7 @@ class TextElement implements ITextElement
}
/**
- * Get font.
+ * Get font. For this class, the return value is always null.
*
* @return null|\PhpOffice\PhpSpreadsheet\Style\Font
*/
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Settings.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Settings.php
index 0baf44637..d8007fd00 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Settings.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Settings.php
@@ -155,7 +155,7 @@ class Settings
/**
* Sets the implementation of cache that should be used for cell collection.
*/
- public static function setCache(CacheInterface $cache): void
+ public static function setCache(?CacheInterface $cache): void
{
self::$cache = $cache;
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php
index 26dde93f3..4f1967311 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Date.php
@@ -184,7 +184,7 @@ class Date
throw new Exception("Invalid string $value supplied for datatype Date");
}
- if (preg_match('/^\\d\\d:\\d\\d:\\d\\d/', $value) == 1) {
+ if (preg_match('/^\\s*\\d?\\d:\\d\\d(:\\d\\d([.]\\d+)?)?\\s*(am|pm)?\\s*$/i', $value) == 1) {
$newValue = fmod($newValue, 1.0);
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php
index f2fe8caa8..737a6eb59 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php
@@ -156,7 +156,11 @@ class File
if ($zipMember !== '') {
$zipfile = "zip://$filename#$zipMember";
if (!self::fileExists($zipfile)) {
- throw new ReaderException("Could not find zip member $zipfile");
+ // Has the file been saved with Windoze directory separators rather than unix?
+ $zipfile = "zip://$filename#" . str_replace('/', '\\', $zipMember);
+ if (!self::fileExists($zipfile)) {
+ throw new ReaderException("Could not find zip member $zipfile");
+ }
}
}
}
@@ -180,6 +184,14 @@ class File
return self::validateZipFirst4($filename);
}
- return self::fileExists("zip://$filename#$zipMember");
+ $zipfile = "zip://$filename#$zipMember";
+ if (self::fileExists($zipfile)) {
+ return true;
+ }
+
+ // Has the file been saved with Windoze directory separators rather than unix?
+ $zipfile = "zip://$filename#" . str_replace('/', '\\', $zipMember);
+
+ return self::fileExists($zipfile);
}
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php
index aa3512ae3..90c1992a3 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php
@@ -380,15 +380,15 @@ class Font
$approximate = self::$autoSizeMethod === self::AUTOSIZE_METHOD_APPROX;
$columnWidth = 0;
if (!$approximate) {
- $columnWidthAdjust = ceil(
- self::getTextWidthPixelsExact(
- str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
- $font,
- 0
- ) * 1.07
- );
-
try {
+ $columnWidthAdjust = ceil(
+ self::getTextWidthPixelsExact(
+ str_repeat('n', 1 * (($filterAdjustment ? 3 : 1) + ($indentAdjustment * 2))),
+ $font,
+ 0
+ ) * 1.07
+ );
+
// Width of text in pixels excl. padding
// and addition because Excel adds some padding, just use approx width of 'n' glyph
$columnWidth = self::getTextWidthPixelsExact($cellText, $font, $rotation) + $columnWidthAdjust;
@@ -453,29 +453,26 @@ class Font
$fontName = $font->getName();
$fontSize = $font->getSize();
- // Calculate column width in pixels. We assume fixed glyph width. Result varies with font name and size.
+ // Calculate column width in pixels.
+ // We assume fixed glyph width, but count double for "fullwidth" characters.
+ // Result varies with font name and size.
switch ($fontName) {
- case 'Calibri':
- // value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
- $columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText));
- $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
-
- break;
case 'Arial':
// value 8 was set because of experience in different exports at Arial 10 font.
- $columnWidth = (int) (8 * StringHelper::countCharacters($columnText));
+ $columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
$columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
break;
case 'Verdana':
// value 8 was found via interpolation by inspecting real Excel files with Verdana 10 font.
- $columnWidth = (int) (8 * StringHelper::countCharacters($columnText));
+ $columnWidth = (int) (8 * StringHelper::countCharactersDbcs($columnText));
$columnWidth = $columnWidth * $fontSize / 10; // extrapolate from font size
break;
default:
// just assume Calibri
- $columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText));
+ // value 8.26 was found via interpolation by inspecting real Excel files with Calibri 11 font.
+ $columnWidth = (int) (8.26 * StringHelper::countCharactersDbcs($columnText));
$columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
break;
@@ -564,10 +561,13 @@ class Font
if (mb_strlen(self::$trueTypeFontPath) > 1 && mb_substr(self::$trueTypeFontPath, -1) !== '/' && mb_substr(self::$trueTypeFontPath, -1) !== '\\') {
$separator = DIRECTORY_SEPARATOR;
}
- $fontFile = self::$trueTypeFontPath . $separator . $fontFile;
+ $fontFileAbsolute = preg_match('~^([A-Za-z]:)?[/\\\\]~', $fontFile) === 1;
+ if (!$fontFileAbsolute) {
+ $fontFile = self::$trueTypeFontPath . $separator . $fontFile;
+ }
// Check if file actually exists
- if ($checkPath && !file_exists($fontFile)) {
+ if ($checkPath && !file_exists($fontFile) && !$fontFileAbsolute) {
$alternateName = $name;
if ($index !== 'x' && $fontArray[$name][$index] !== $fontArray[$name]['x']) {
// Bold but no italic:
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php
index c61ee2594..5d5babc3f 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php
@@ -6,6 +6,9 @@ use PhpOffice\PhpSpreadsheet\Shared\OLE;
class ChainedBlockStream
{
+ /** @var mixed */
+ public $context;
+
/**
* The OLE container of the file that is being read.
*
@@ -160,7 +163,7 @@ class ChainedBlockStream
$this->pos = $offset;
} elseif ($whence == SEEK_CUR && -$offset <= $this->pos) {
$this->pos += $offset;
- // @phpstan-ignore-next-line
+ // @phpstan-ignore-next-line
} elseif ($whence == SEEK_END && -$offset <= count(/** @scrutinizer ignore-type */ $this->data)) {
$this->pos = strlen($this->data) + $offset;
} else {
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php
index 3d952a9c8..fcc963953 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php
@@ -134,7 +134,7 @@ class OLERead
$bbdBlocks = $this->numBigBlockDepotBlocks;
- if ($this->numExtensionBlocks != 0) {
+ if ($this->numExtensionBlocks !== 0) {
$bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS) / 4;
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php
index 30bd8c5fd..c6c198e20 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php
@@ -451,6 +451,18 @@ class StringHelper
return mb_strlen($textValue, $encoding);
}
+ /**
+ * Get character count using mb_strwidth rather than mb_strlen.
+ *
+ * @param string $encoding Encoding
+ *
+ * @return int Character count
+ */
+ public static function countCharactersDbcs(string $textValue, string $encoding = 'UTF-8'): int
+ {
+ return mb_strwidth($textValue, $encoding);
+ }
+
/**
* Get a substring of a UTF-8 encoded string.
*
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php
index b94609af7..117848c77 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php
@@ -105,7 +105,6 @@ class Trend
$className = '\PhpOffice\PhpSpreadsheet\Shared\Trend\\' . $trendType . 'BestFit';
//* @phpstan-ignore-next-line
$bestFit[$trendMethod] = new $className($yValues, $xValues, $const);
- //* @phpstan-ignore-next-line
$bestFitValue[$trendMethod] = $bestFit[$trendMethod]->getGoodnessOfFit();
}
if ($trendType != self::TREND_BEST_FIT_NO_POLY) {
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php
index f0744cd27..110908550 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php
@@ -203,6 +203,14 @@ class Spreadsheet implements JsonSerializable
*/
private $tabRatio = 600;
+ /** @var Theme */
+ private $theme;
+
+ public function getTheme(): Theme
+ {
+ return $this->theme;
+ }
+
/**
* The workbook has macros ?
*
@@ -476,6 +484,7 @@ class Spreadsheet implements JsonSerializable
{
$this->uniqueID = uniqid('', true);
$this->calculationEngine = new Calculation($this);
+ $this->theme = new Theme();
// Initialise worksheet collection and add one worksheet
$this->workSheetCollection = [];
@@ -1654,4 +1663,26 @@ class Spreadsheet implements JsonSerializable
{
throw new Exception('Spreadsheet objects cannot be json encoded');
}
+
+ public function resetThemeFonts(): void
+ {
+ $majorFontLatin = $this->theme->getMajorFontLatin();
+ $minorFontLatin = $this->theme->getMinorFontLatin();
+ foreach ($this->cellXfCollection as $cellStyleXf) {
+ $scheme = $cellStyleXf->getFont()->getScheme();
+ if ($scheme === 'major') {
+ $cellStyleXf->getFont()->setName($majorFontLatin)->setScheme($scheme);
+ } elseif ($scheme === 'minor') {
+ $cellStyleXf->getFont()->setName($minorFontLatin)->setScheme($scheme);
+ }
+ }
+ foreach ($this->cellStyleXfCollection as $cellStyleXf) {
+ $scheme = $cellStyleXf->getFont()->getScheme();
+ if ($scheme === 'major') {
+ $cellStyleXf->getFont()->setName($majorFontLatin)->setScheme($scheme);
+ } elseif ($scheme === 'minor') {
+ $cellStyleXf->getFont()->setName($minorFontLatin)->setScheme($scheme);
+ }
+ }
+ }
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php
index 3c002b270..282defc0c 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php
@@ -362,23 +362,8 @@ class Color extends Supervisor
$green = self::getGreen($hexColourValue, false);
/** @var int $blue */
$blue = self::getBlue($hexColourValue, false);
- if ($adjustPercentage > 0) {
- $red += (255 - $red) * $adjustPercentage;
- $green += (255 - $green) * $adjustPercentage;
- $blue += (255 - $blue) * $adjustPercentage;
- } else {
- $red += $red * $adjustPercentage;
- $green += $green * $adjustPercentage;
- $blue += $blue * $adjustPercentage;
- }
- $rgb = strtoupper(
- str_pad(dechex((int) $red), 2, '0', 0) .
- str_pad(dechex((int) $green), 2, '0', 0) .
- str_pad(dechex((int) $blue), 2, '0', 0)
- );
-
- return (($rgba) ? 'FF' : '') . $rgb;
+ return (($rgba) ? 'FF' : '') . RgbTint::rgbAndTintToRgb($red, $green, $blue, $adjustPercentage);
}
/**
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php
index de565d345..36069b00c 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php
@@ -248,7 +248,7 @@ class Conditional implements IComparable
/**
* Set Conditions.
*
- * @param bool|float|int|string|(bool|float|int|string)[] $conditions Condition
+ * @param (bool|float|int|string)[]|bool|float|int|string $conditions Condition
*
* @return $this
*/
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php
index 3d7bc1bce..a8eeaa986 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php
@@ -107,6 +107,9 @@ class Font extends Supervisor
*/
public $colorIndex;
+ /** @var string */
+ protected $scheme = '';
+
/**
* Create a new Font.
*
@@ -231,6 +234,12 @@ class Font extends Supervisor
if (isset($styleArray['size'])) {
$this->setSize($styleArray['size']);
}
+ if (isset($styleArray['chartColor'])) {
+ $this->chartColor = $styleArray['chartColor'];
+ }
+ if (isset($styleArray['scheme'])) {
+ $this->setScheme($styleArray['scheme']);
+ }
}
return $this;
@@ -278,13 +287,11 @@ class Font extends Supervisor
}
/**
- * Set Name.
+ * Set Name and turn off Scheme.
*
* @param string $fontname
- *
- * @return $this
*/
- public function setName($fontname)
+ public function setName($fontname): self
{
if ($fontname == '') {
$fontname = 'Calibri';
@@ -296,7 +303,7 @@ class Font extends Supervisor
$this->name = $fontname;
}
- return $this;
+ return $this->setScheme('');
}
public function setLatin(string $fontname): self
@@ -634,6 +641,13 @@ class Font extends Supervisor
return $this;
}
+ public function setChartColorFromObject(?ChartColor $chartColor): self
+ {
+ $this->chartColor = $chartColor;
+
+ return $this;
+ }
+
/**
* Get Underline.
*
@@ -774,6 +788,7 @@ class Font extends Supervisor
$this->underline .
($this->strikethrough ? 't' : 'f') .
$this->color->getHashCode() .
+ $this->scheme .
implode(
'*',
[
@@ -802,6 +817,7 @@ class Font extends Supervisor
$this->exportArray2($exportedArray, 'italic', $this->getItalic());
$this->exportArray2($exportedArray, 'latin', $this->getLatin());
$this->exportArray2($exportedArray, 'name', $this->getName());
+ $this->exportArray2($exportedArray, 'scheme', $this->getScheme());
$this->exportArray2($exportedArray, 'size', $this->getSize());
$this->exportArray2($exportedArray, 'strikethrough', $this->getStrikethrough());
$this->exportArray2($exportedArray, 'strikeType', $this->getStrikeType());
@@ -812,4 +828,27 @@ class Font extends Supervisor
return $exportedArray;
}
+
+ public function getScheme(): string
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getScheme();
+ }
+
+ return $this->scheme;
+ }
+
+ public function setScheme(string $scheme): self
+ {
+ if ($scheme === '' || $scheme === 'major' || $scheme === 'minor') {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['scheme' => $scheme]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->scheme = $scheme;
+ }
+ }
+
+ return $this;
+ }
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
index de3666ae1..41a17151d 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
@@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Style\NumberFormat;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
+use PhpOffice\PhpSpreadsheet\Reader\Xls\Color\BIFF8;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\Style\Color;
use PhpOffice\PhpSpreadsheet\Style\NumberFormat;
@@ -67,14 +68,19 @@ class Formatter
// 3 sections: [POSITIVE/TEXT] [NEGATIVE] [ZERO]
// 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT]
$sectionCount = count($sections);
- $color_regex = '/\\[(' . implode('|', Color::NAMED_COLORS) . ')\\]/mui';
+ // Colour could be a named colour, or a numeric index entry in the colour-palette
+ $color_regex = '/\\[(' . implode('|', Color::NAMED_COLORS) . '|color\\s*(\\d+))\\]/mui';
$cond_regex = '/\\[(>|>=|<|<=|=|<>)([+-]?\\d+([.]\\d+)?)\\]/';
$colors = ['', '', '', '', ''];
$conditionOperations = ['', '', '', '', ''];
$conditionComparisonValues = [0, 0, 0, 0, 0];
for ($idx = 0; $idx < $sectionCount; ++$idx) {
if (preg_match($color_regex, $sections[$idx], $matches)) {
- $colors[$idx] = $matches[0];
+ if (isset($matches[2])) {
+ $colors[$idx] = '#' . BIFF8::lookup((int) $matches[2] + 7)['rgb'];
+ } else {
+ $colors[$idx] = $matches[0];
+ }
$sections[$idx] = (string) preg_replace($color_regex, '', $sections[$idx]);
}
if (preg_match($cond_regex, $sections[$idx], $matches)) {
@@ -170,10 +176,11 @@ class Formatter
$format = (string) preg_replace('/_.?/ui', ' ', $format);
// Let's begin inspecting the format and converting the value to a formatted string
- // Check for date/time characters (not inside quotes)
if (
- (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format)) &&
- (preg_match('/0(?![^\[]*\])/miu', $format) === 0)
+ // Check for date/time characters (not inside quotes)
+ (preg_match('/(\[\$[A-Z]*-[0-9A-F]*\])*[hmsdy](?=(?:[^"]|"[^"]*")*$)/miu', $format))
+ // A date/time with a decimal time shouldn't have a digit placeholder before the decimal point
+ && (preg_match('/[0\?#]\.(?![^\[]*\])/miu', $format) === 0)
) {
// datetime format
$value = DateFormatter::format($value, $format);
@@ -194,8 +201,6 @@ class Formatter
$value = $writerInstance->$function($value, $colors);
}
- $value = str_replace(chr(0x00), '.', $value);
-
- return $value;
+ return str_replace(chr(0x00), '.', $value);
}
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Accounting.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Accounting.php
index a138ab7f1..c30028601 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Accounting.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Accounting.php
@@ -89,13 +89,13 @@ class Accounting extends Currency
(
$this->currencySymbolPosition === self::LEADING_SYMBOL &&
$this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING
- ) ? ' ' : '',
+ ) ? "\u{a0}" : '',
$this->thousandsSeparator ? '#,##' : null,
$this->decimals > 0 ? '.' . str_repeat('0', $this->decimals) : null,
(
$this->currencySymbolPosition === self::TRAILING_SYMBOL &&
$this->currencySymbolSpacing === self::SYMBOL_WITH_SPACING
- ) ? ' ' : '',
+ ) ? "\u{a0}" : '',
$this->currencySymbolPosition === self::TRAILING_SYMBOL ? $this->formatCurrencyCode() : null
);
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Date.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Date.php
new file mode 100644
index 000000000..61ac117b7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Date.php
@@ -0,0 +1,125 @@
+separators = $this->padSeparatorArray(
+ is_array($separators) ? $separators : [$separators],
+ count($formatBlocks) - 1
+ );
+ $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
+ }
+
+ private function mapFormatBlocks(string $value): string
+ {
+ // Any date masking codes are returned as lower case values
+ if (in_array(mb_strtolower($value), self::DATE_BLOCKS, true)) {
+ return mb_strtolower($value);
+ }
+
+ // Wrap any string literals in quotes, so that they're clearly defined as string literals
+ return $this->wrapLiteral($value);
+ }
+
+ public function format(): string
+ {
+ return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTime.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTime.php
new file mode 100644
index 000000000..292c1efc9
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTime.php
@@ -0,0 +1,50 @@
+
+ */
+ protected array $formatBlocks;
+
+ /**
+ * @param null|string|string[] $separators
+ * If you want to use only a single format block, then pass a null as the separator argument
+ * @param DateTimeWizard|string ...$formatBlocks
+ */
+ public function __construct($separators, ...$formatBlocks)
+ {
+ $this->separators = $this->padSeparatorArray(
+ is_array($separators) ? $separators : [$separators],
+ count($formatBlocks) - 1
+ );
+ $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
+ }
+
+ /**
+ * @param DateTimeWizard|string $value
+ */
+ private function mapFormatBlocks($value): string
+ {
+ // Any date masking codes are returned as lower case values
+ if (is_object($value)) {
+ // We can't explicitly test for Stringable until PHP >= 8.0
+ return $value;
+ }
+
+ // Wrap any string literals in quotes, so that they're clearly defined as string literals
+ return $this->wrapLiteral($value);
+ }
+
+ public function format(): string
+ {
+ return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTimeWizard.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTimeWizard.php
new file mode 100644
index 000000000..b14a6190f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/DateTimeWizard.php
@@ -0,0 +1,44 @@
+= ";
+
+ protected function padSeparatorArray(array $separators, int $count): array
+ {
+ $lastSeparator = array_pop($separators);
+
+ return $separators + array_fill(0, $count, $lastSeparator);
+ }
+
+ protected function escapeSingleCharacter(string $value): string
+ {
+ if (strpos(self::NO_ESCAPING_NEEDED, $value) !== false) {
+ return $value;
+ }
+
+ return "\\{$value}";
+ }
+
+ protected function wrapLiteral(string $value): string
+ {
+ if (mb_strlen($value, 'UTF-8') === 1) {
+ return $this->escapeSingleCharacter($value);
+ }
+
+ // Wrap any other string literals in quotes, so that they're clearly defined as string literals
+ return '"' . str_replace('"', '""', $value) . '"';
+ }
+
+ protected function intersperse(string $formatBlock, ?string $separator): string
+ {
+ return "{$formatBlock}{$separator}";
+ }
+
+ public function __toString(): string
+ {
+ return $this->format();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Duration.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Duration.php
new file mode 100644
index 000000000..b81f77acb
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Duration.php
@@ -0,0 +1,153 @@
+ self::DAYS_DURATION,
+ self::HOURS_DURATION => self::HOURS_SHORT,
+ self::MINUTES_DURATION => self::MINUTES_LONG,
+ self::SECONDS_DURATION => self::SECONDS_LONG,
+ ];
+
+ protected const DURATION_DEFAULTS = [
+ self::HOURS_LONG => self::HOURS_DURATION,
+ self::HOURS_SHORT => self::HOURS_DURATION,
+ self::MINUTES_LONG => self::MINUTES_DURATION,
+ self::MINUTES_SHORT => self::MINUTES_DURATION,
+ self::SECONDS_LONG => self::SECONDS_DURATION,
+ self::SECONDS_SHORT => self::SECONDS_DURATION,
+ ];
+
+ public const SEPARATOR_COLON = ':';
+ public const SEPARATOR_SPACE_NONBREAKING = "\u{a0}";
+ public const SEPARATOR_SPACE = ' ';
+
+ public const DURATION_DEFAULT = [
+ self::HOURS_DURATION,
+ self::MINUTES_LONG,
+ self::SECONDS_LONG,
+ ];
+
+ /**
+ * @var string[]
+ */
+ protected array $separators;
+
+ /**
+ * @var string[]
+ */
+ protected array $formatBlocks;
+
+ protected bool $durationIsSet = false;
+
+ /**
+ * @param null|string|string[] $separators
+ * If you want to use the same separator for all format blocks, then it can be passed as a string literal;
+ * if you wish to use different separators, then they should be passed as an array.
+ * If you want to use only a single format block, then pass a null as the separator argument
+ */
+ public function __construct($separators = self::SEPARATOR_COLON, string ...$formatBlocks)
+ {
+ $separators ??= self::SEPARATOR_COLON;
+ $formatBlocks = (count($formatBlocks) === 0) ? self::DURATION_DEFAULT : $formatBlocks;
+
+ $this->separators = $this->padSeparatorArray(
+ is_array($separators) ? $separators : [$separators],
+ count($formatBlocks) - 1
+ );
+ $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
+
+ if ($this->durationIsSet === false) {
+ // We need at least one duration mask, so if none has been set we change the first mask element
+ // to a duration.
+ $this->formatBlocks[0] = self::DURATION_DEFAULTS[mb_strtolower($this->formatBlocks[0])];
+ }
+ }
+
+ private function mapFormatBlocks(string $value): string
+ {
+ // Any duration masking codes are returned as lower case values
+ if (in_array(mb_strtolower($value), self::DURATION_BLOCKS, true)) {
+ if (array_key_exists(mb_strtolower($value), self::DURATION_MASKS)) {
+ if ($this->durationIsSet) {
+ // We should only have a single duration mask, the first defined in the mask set,
+ // so convert any additional duration masks to standard time masks.
+ $value = self::DURATION_MASKS[mb_strtolower($value)];
+ }
+ $this->durationIsSet = true;
+ }
+
+ return mb_strtolower($value);
+ }
+
+ // Wrap any string literals in quotes, so that they're clearly defined as string literals
+ return $this->wrapLiteral($value);
+ }
+
+ public function format(): string
+ {
+ return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Time.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Time.php
new file mode 100644
index 000000000..64b9104a6
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Wizard/Time.php
@@ -0,0 +1,105 @@
+separators = $this->padSeparatorArray(
+ is_array($separators) ? $separators : [$separators],
+ count($formatBlocks) - 1
+ );
+ $this->formatBlocks = array_map([$this, 'mapFormatBlocks'], $formatBlocks);
+ }
+
+ private function mapFormatBlocks(string $value): string
+ {
+ // Any date masking codes are returned as lower case values
+ // except for AM/PM, which is set to uppercase
+ if (in_array(mb_strtolower($value), self::TIME_BLOCKS, true)) {
+ return mb_strtolower($value);
+ } elseif (mb_strtoupper($value) === self::MORNING_AFTERNOON) {
+ return mb_strtoupper($value);
+ }
+
+ // Wrap any string literals in quotes, so that they're clearly defined as string literals
+ return $this->wrapLiteral($value);
+ }
+
+ public function format(): string
+ {
+ return implode('', array_map([$this, 'intersperse'], $this->formatBlocks, $this->separators));
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/RgbTint.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/RgbTint.php
new file mode 100644
index 000000000..582ae4839
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/RgbTint.php
@@ -0,0 +1,175 @@
+= 0.0) ? $hue : (1.0 + $hue);
+ }
+
+ /**
+ * Convert red/green/blue to HLSMAX-based hue/luminance/saturation.
+ *
+ * @return int[]
+ */
+ private static function rgbToMsHls(int $red, int $green, int $blue): array
+ {
+ $red01 = $red / self::RGBMAX;
+ $green01 = $green / self::RGBMAX;
+ $blue01 = $blue / self::RGBMAX;
+ [$hue, $luminance, $saturation] = self::rgbToHls($red01, $green01, $blue01);
+
+ return [
+ (int) round($hue * self::HLSMAX),
+ (int) round($luminance * self::HLSMAX),
+ (int) round($saturation * self::HLSMAX),
+ ];
+ }
+
+ /**
+ * Converts HLSMAX based HLS values to rgb values in the range (0,1).
+ *
+ * @return float[]
+ */
+ private static function msHlsToRgb(int $hue, int $lightness, int $saturation): array
+ {
+ return self::hlsToRgb($hue / self::HLSMAX, $lightness / self::HLSMAX, $saturation / self::HLSMAX);
+ }
+
+ /**
+ * Tints HLSMAX based luminance.
+ *
+ * @see http://ciintelligence.blogspot.co.uk/2012/02/converting-excel-theme-color-and-tint.html
+ */
+ private static function tintLuminance(float $tint, float $luminance): int
+ {
+ if ($tint < 0) {
+ return (int) round($luminance * (1.0 + $tint));
+ }
+
+ return (int) round($luminance * (1.0 - $tint) + (self::HLSMAX - self::HLSMAX * (1.0 - $tint)));
+ }
+
+ /**
+ * Return result of tinting supplied rgb as 6 hex digits.
+ */
+ public static function rgbAndTintToRgb(int $red, int $green, int $blue, float $tint): string
+ {
+ [$hue, $luminance, $saturation] = self::rgbToMsHls($red, $green, $blue);
+ [$red, $green, $blue] = self::msHlsToRgb($hue, self::tintLuminance($tint, $luminance), $saturation);
+
+ return sprintf(
+ '%02X%02X%02X',
+ (int) round($red * self::RGBMAX),
+ (int) round($green * self::RGBMAX),
+ (int) round($blue * self::RGBMAX)
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php
index be70639eb..9309830bf 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php
@@ -2,7 +2,10 @@
namespace PhpOffice\PhpSpreadsheet\Style;
+use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
class Style extends Supervisor
@@ -122,7 +125,7 @@ class Style extends Supervisor
public function getSharedComponent(): self
{
$activeSheet = $this->getActiveSheet();
- $selectedCell = $this->getActiveCell(); // e.g. 'A1'
+ $selectedCell = Functions::trimSheetFromCellReference($this->getActiveCell()); // e.g. 'A1'
if ($activeSheet->cellExists($selectedCell)) {
$xfIndex = $activeSheet->getCell($selectedCell)->getXfIndex();
@@ -203,8 +206,15 @@ class Style extends Supervisor
if ($this->isSupervisor) {
$pRange = $this->getSelectedCells();
- // Uppercase coordinate
+ // Uppercase coordinate and strip any Worksheet reference from the selected range
$pRange = strtoupper($pRange);
+ if (strpos($pRange, '!') !== false) {
+ $pRangeWorksheet = StringHelper::strToUpper(trim(substr($pRange, 0, (int) strrpos($pRange, '!')), "'"));
+ if ($pRangeWorksheet !== '' && StringHelper::strToUpper($this->getActiveSheet()->getTitle()) !== $pRangeWorksheet) {
+ throw new Exception('Invalid Worksheet for specified Range');
+ }
+ $pRange = strtoupper(Functions::trimSheetFromCellReference($pRange));
+ }
// Is it a cell range or a single cell?
if (strpos($pRange, ':') === false) {
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Theme.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Theme.php
new file mode 100644
index 000000000..ab101f01e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Theme.php
@@ -0,0 +1,269 @@
+ '000000',
+ 'lt1' => 'FFFFFF',
+ 'dk2' => '44546A',
+ 'lt2' => 'E7E6E6',
+ 'accent1' => '4472C4',
+ 'accent2' => 'ED7D31',
+ 'accent3' => 'A5A5A5',
+ 'accent4' => 'FFC000',
+ 'accent5' => '5B9BD5',
+ 'accent6' => '70AD47',
+ 'hlink' => '0563C1',
+ 'folHlink' => '954F72',
+ ];
+
+ public const COLOR_SCHEME_2007_2010_NAME = 'Office 2007-2010';
+ public const COLOR_SCHEME_2007_2010 = [
+ 'dk1' => '000000',
+ 'lt1' => 'FFFFFF',
+ 'dk2' => '1F497D',
+ 'lt2' => 'EEECE1',
+ 'accent1' => '4F81BD',
+ 'accent2' => 'C0504D',
+ 'accent3' => '9BBB59',
+ 'accent4' => '8064A2',
+ 'accent5' => '4BACC6',
+ 'accent6' => 'F79646',
+ 'hlink' => '0000FF',
+ 'folHlink' => '800080',
+ ];
+
+ /** @var string[] */
+ private $themeColors = self::COLOR_SCHEME_2007_2010;
+
+ /** @var string */
+ private $majorFontLatin = 'Cambria';
+
+ /** @var string */
+ private $majorFontEastAsian = '';
+
+ /** @var string */
+ private $majorFontComplexScript = '';
+
+ /** @var string */
+ private $minorFontLatin = 'Calibri';
+
+ /** @var string */
+ private $minorFontEastAsian = '';
+
+ /** @var string */
+ private $minorFontComplexScript = '';
+
+ /**
+ * Map of Major (header) fonts to write.
+ *
+ * @var string[]
+ */
+ private $majorFontSubstitutions = self::FONTS_TIMES_SUBSTITUTIONS;
+
+ /**
+ * Map of Minor (body) fonts to write.
+ *
+ * @var string[]
+ */
+ private $minorFontSubstitutions = self::FONTS_ARIAL_SUBSTITUTIONS;
+
+ public const FONTS_TIMES_SUBSTITUTIONS = [
+ 'Jpan' => 'MS Pゴシック',
+ 'Hang' => '맑은 고딕',
+ 'Hans' => '宋体',
+ 'Hant' => '新細明體',
+ 'Arab' => 'Times New Roman',
+ 'Hebr' => 'Times New Roman',
+ 'Thai' => 'Tahoma',
+ 'Ethi' => 'Nyala',
+ 'Beng' => 'Vrinda',
+ 'Gujr' => 'Shruti',
+ 'Khmr' => 'MoolBoran',
+ 'Knda' => 'Tunga',
+ 'Guru' => 'Raavi',
+ 'Cans' => 'Euphemia',
+ 'Cher' => 'Plantagenet Cherokee',
+ 'Yiii' => 'Microsoft Yi Baiti',
+ 'Tibt' => 'Microsoft Himalaya',
+ 'Thaa' => 'MV Boli',
+ 'Deva' => 'Mangal',
+ 'Telu' => 'Gautami',
+ 'Taml' => 'Latha',
+ 'Syrc' => 'Estrangelo Edessa',
+ 'Orya' => 'Kalinga',
+ 'Mlym' => 'Kartika',
+ 'Laoo' => 'DokChampa',
+ 'Sinh' => 'Iskoola Pota',
+ 'Mong' => 'Mongolian Baiti',
+ 'Viet' => 'Times New Roman',
+ 'Uigh' => 'Microsoft Uighur',
+ 'Geor' => 'Sylfaen',
+ ];
+
+ public const FONTS_ARIAL_SUBSTITUTIONS = [
+ 'Jpan' => 'MS Pゴシック',
+ 'Hang' => '맑은 고딕',
+ 'Hans' => '宋体',
+ 'Hant' => '新細明體',
+ 'Arab' => 'Arial',
+ 'Hebr' => 'Arial',
+ 'Thai' => 'Tahoma',
+ 'Ethi' => 'Nyala',
+ 'Beng' => 'Vrinda',
+ 'Gujr' => 'Shruti',
+ 'Khmr' => 'DaunPenh',
+ 'Knda' => 'Tunga',
+ 'Guru' => 'Raavi',
+ 'Cans' => 'Euphemia',
+ 'Cher' => 'Plantagenet Cherokee',
+ 'Yiii' => 'Microsoft Yi Baiti',
+ 'Tibt' => 'Microsoft Himalaya',
+ 'Thaa' => 'MV Boli',
+ 'Deva' => 'Mangal',
+ 'Telu' => 'Gautami',
+ 'Taml' => 'Latha',
+ 'Syrc' => 'Estrangelo Edessa',
+ 'Orya' => 'Kalinga',
+ 'Mlym' => 'Kartika',
+ 'Laoo' => 'DokChampa',
+ 'Sinh' => 'Iskoola Pota',
+ 'Mong' => 'Mongolian Baiti',
+ 'Viet' => 'Arial',
+ 'Uigh' => 'Microsoft Uighur',
+ 'Geor' => 'Sylfaen',
+ ];
+
+ public function getThemeColors(): array
+ {
+ return $this->themeColors;
+ }
+
+ public function setThemeColor(string $key, string $value): self
+ {
+ $this->themeColors[$key] = $value;
+
+ return $this;
+ }
+
+ public function getThemeColorName(): string
+ {
+ return $this->themeColorName;
+ }
+
+ public function setThemeColorName(string $name, ?array $themeColors = null): self
+ {
+ $this->themeColorName = $name;
+ if ($name === self::COLOR_SCHEME_2007_2010_NAME) {
+ $themeColors = $themeColors ?? self::COLOR_SCHEME_2007_2010;
+ } elseif ($name === self::COLOR_SCHEME_2013_PLUS_NAME) {
+ $themeColors = $themeColors ?? self::COLOR_SCHEME_2013_PLUS;
+ }
+ if ($themeColors !== null) {
+ $this->themeColors = $themeColors;
+ }
+
+ return $this;
+ }
+
+ public function getMajorFontLatin(): string
+ {
+ return $this->majorFontLatin;
+ }
+
+ public function getMajorFontEastAsian(): string
+ {
+ return $this->majorFontEastAsian;
+ }
+
+ public function getMajorFontComplexScript(): string
+ {
+ return $this->majorFontComplexScript;
+ }
+
+ public function getMajorFontSubstitutions(): array
+ {
+ return $this->majorFontSubstitutions;
+ }
+
+ /** @param null|array $substitutions */
+ public function setMajorFontValues(?string $latin, ?string $eastAsian, ?string $complexScript, $substitutions): self
+ {
+ if (!empty($latin)) {
+ $this->majorFontLatin = $latin;
+ }
+ if ($eastAsian !== null) {
+ $this->majorFontEastAsian = $eastAsian;
+ }
+ if ($complexScript !== null) {
+ $this->majorFontComplexScript = $complexScript;
+ }
+ if ($substitutions !== null) {
+ $this->majorFontSubstitutions = $substitutions;
+ }
+
+ return $this;
+ }
+
+ public function getMinorFontLatin(): string
+ {
+ return $this->minorFontLatin;
+ }
+
+ public function getMinorFontEastAsian(): string
+ {
+ return $this->minorFontEastAsian;
+ }
+
+ public function getMinorFontComplexScript(): string
+ {
+ return $this->minorFontComplexScript;
+ }
+
+ public function getMinorFontSubstitutions(): array
+ {
+ return $this->minorFontSubstitutions;
+ }
+
+ /** @param null|array $substitutions */
+ public function setMinorFontValues(?string $latin, ?string $eastAsian, ?string $complexScript, $substitutions): self
+ {
+ if (!empty($latin)) {
+ $this->minorFontLatin = $latin;
+ }
+ if ($eastAsian !== null) {
+ $this->minorFontEastAsian = $eastAsian;
+ }
+ if ($complexScript !== null) {
+ $this->minorFontComplexScript = $complexScript;
+ }
+ if ($substitutions !== null) {
+ $this->minorFontSubstitutions = $substitutions;
+ }
+
+ return $this;
+ }
+
+ public function getThemeFontName(): string
+ {
+ return $this->themeFontName;
+ }
+
+ public function setThemeFontName(?string $name): self
+ {
+ if (!empty($name)) {
+ $this->themeFontName = $name;
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php
index d6041985d..cbc4ff65f 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php
@@ -321,7 +321,7 @@ class AutoFilter
*
* @return bool
*/
- private static function filterTestInSimpleDataSet($cellValue, $dataSet)
+ protected static function filterTestInSimpleDataSet($cellValue, $dataSet)
{
$dataSetValues = $dataSet['filterValues'];
$blanks = $dataSet['blanks'];
@@ -340,7 +340,7 @@ class AutoFilter
*
* @return bool
*/
- private static function filterTestInDateGroupSet($cellValue, $dataSet)
+ protected static function filterTestInDateGroupSet($cellValue, $dataSet)
{
$dateSet = $dataSet['filterValues'];
$blanks = $dataSet['blanks'];
@@ -384,7 +384,7 @@ class AutoFilter
*
* @return bool
*/
- private static function filterTestInCustomDataSet($cellValue, $ruleSet)
+ protected static function filterTestInCustomDataSet($cellValue, $ruleSet)
{
/** @var array[] */
$dataSet = $ruleSet['filterRules'];
@@ -509,7 +509,7 @@ class AutoFilter
*
* @return bool
*/
- private static function filterTestInPeriodDateSet($cellValue, $monthSet)
+ protected static function filterTestInPeriodDateSet($cellValue, $monthSet)
{
// Blank cells are always ignored, so return a FALSE
if (($cellValue == '') || ($cellValue === null)) {
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php
index 34e1145e0..d51023fcc 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php
@@ -197,21 +197,6 @@ class PageMargins
return $this;
}
- /**
- * Implement PHP __clone to create a deep clone, not just a shallow copy.
- */
- public function __clone()
- {
- $vars = get_object_vars($this);
- foreach ($vars as $key => $value) {
- if (is_object($value)) {
- $this->$key = clone $value;
- } else {
- $this->$key = $value;
- }
- }
- }
-
public static function fromCentimeters(float $value): float
{
return $value / 2.54;
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php
index 22c99ff3b..72c8958c8 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php
@@ -885,19 +885,4 @@ class PageSetup
return $this;
}
-
- /**
- * Implement PHP __clone to create a deep clone, not just a shallow copy.
- */
- public function __clone()
- {
- $vars = get_object_vars($this);
- foreach ($vars as $key => $value) {
- if (is_object($value)) {
- $this->$key = clone $value;
- } else {
- $this->$key = $value;
- }
- }
- }
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php
index 13464c997..697f11c2a 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php
@@ -175,19 +175,4 @@ class SheetView
return $this;
}
-
- /**
- * Implement PHP __clone to create a deep clone, not just a shallow copy.
- */
- public function __clone()
- {
- $vars = get_object_vars($this);
- foreach ($vars as $key => $value) {
- if (is_object($value)) {
- $this->$key = clone $value;
- } else {
- $this->$key = $value;
- }
- }
- }
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table.php
index dc2a4f8a5..1bc8dff45 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table.php
@@ -180,7 +180,7 @@ class Table
private function updateStructuredReferencesInCells(Worksheet $worksheet, string $newName): void
{
- $pattern = '/' . preg_quote($this->name) . '\[/mui';
+ $pattern = '/' . preg_quote($this->name, '/') . '\[/mui';
foreach ($worksheet->getCoordinates(false) as $coordinate) {
$cell = $worksheet->getCell($coordinate);
@@ -196,7 +196,7 @@ class Table
private function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $newName): void
{
- $pattern = '/' . preg_quote($this->name) . '\[/mui';
+ $pattern = '/' . preg_quote($this->name, '/') . '\[/mui';
foreach ($spreadsheet->getNamedFormulae() as $namedFormula) {
$formula = $namedFormula->getValue();
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/Column.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/Column.php
index 30630c0d4..32dd4c4f8 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/Column.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Table/Column.php
@@ -225,7 +225,7 @@ class Column
private static function updateStructuredReferencesInCells(Worksheet $worksheet, string $oldTitle, string $newTitle): void
{
- $pattern = '/\[(@?)' . preg_quote($oldTitle) . '\]/mui';
+ $pattern = '/\[(@?)' . preg_quote($oldTitle, '/') . '\]/mui';
foreach ($worksheet->getCoordinates(false) as $coordinate) {
$cell = $worksheet->getCell($coordinate);
@@ -241,7 +241,7 @@ class Column
private static function updateStructuredReferencesInNamedFormulae(Spreadsheet $spreadsheet, string $oldTitle, string $newTitle): void
{
- $pattern = '/\[(@?)' . preg_quote($oldTitle) . '\]/mui';
+ $pattern = '/\[(@?)' . preg_quote($oldTitle, '/') . '\]/mui';
foreach ($spreadsheet->getNamedFormulae() as $namedFormula) {
$formula = $namedFormula->getValue();
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Validations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Validations.php
index aab3aae44..42ba566c6 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Validations.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Validations.php
@@ -53,6 +53,9 @@ class Validations
return self::validateCellRange($cellRange);
}
+ private const SETMAXROW = '${1}1:${2}' . AddressRange::MAX_ROW;
+ private const SETMAXCOL = 'A${1}:' . AddressRange::MAX_COLUMN . '${2}';
+
/**
* Validate a cell range.
*
@@ -69,7 +72,7 @@ class Validations
// or Row ranges like '1:3' to 'A1:XFD3'
$addressRange = (string) preg_replace(
['/^([A-Z]+):([A-Z]+)$/i', '/^(\\d+):(\\d+)$/'],
- ['${1}1:${2}1048576', 'A${1}:XFD${2}'],
+ [self::SETMAXROW, self::SETMAXCOL],
$addressRange
);
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php
index cc0e05b69..29221e991 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php
@@ -543,9 +543,18 @@ class Worksheet implements IComparable
*/
public function getColumnDimensions()
{
+ /** @var callable */
+ $callable = [self::class, 'columnDimensionCompare'];
+ uasort($this->columnDimensions, $callable);
+
return $this->columnDimensions;
}
+ private static function columnDimensionCompare(ColumnDimension $a, ColumnDimension $b): int
+ {
+ return $a->getColumnNumeric() - $b->getColumnNumeric();
+ }
+
/**
* Get default column dimension.
*
@@ -1806,9 +1815,15 @@ class Worksheet implements IComparable
public function getBreaks()
{
$breaks = [];
+ /** @var callable */
+ $compareFunction = [self::class, 'compareRowBreaks'];
+ uksort($this->rowBreaks, $compareFunction);
foreach ($this->rowBreaks as $break) {
$breaks[$break->getCoordinate()] = self::BREAK_ROW;
}
+ /** @var callable */
+ $compareFunction = [self::class, 'compareColumnBreaks'];
+ uksort($this->columnBreaks, $compareFunction);
foreach ($this->columnBreaks as $break) {
$breaks[$break->getCoordinate()] = self::BREAK_COLUMN;
}
@@ -1823,16 +1838,40 @@ class Worksheet implements IComparable
*/
public function getRowBreaks()
{
+ /** @var callable */
+ $compareFunction = [self::class, 'compareRowBreaks'];
+ uksort($this->rowBreaks, $compareFunction);
+
return $this->rowBreaks;
}
+ protected static function compareRowBreaks(string $coordinate1, string $coordinate2): int
+ {
+ $row1 = Coordinate::indexesFromString($coordinate1)[1];
+ $row2 = Coordinate::indexesFromString($coordinate2)[1];
+
+ return $row1 - $row2;
+ }
+
+ protected static function compareColumnBreaks(string $coordinate1, string $coordinate2): int
+ {
+ $column1 = Coordinate::indexesFromString($coordinate1)[0];
+ $column2 = Coordinate::indexesFromString($coordinate2)[0];
+
+ return $column1 - $column2;
+ }
+
/**
- * Get row breaks.
+ * Get column breaks.
*
* @return PageBreak[]
*/
public function getColumnBreaks()
{
+ /** @var callable */
+ $compareFunction = [self::class, 'compareColumnBreaks'];
+ uksort($this->columnBreaks, $compareFunction);
+
return $this->columnBreaks;
}
@@ -2448,12 +2487,12 @@ class Worksheet implements IComparable
/**
* Insert a new row, updating all possible related data.
*
- * @param int $before Insert before this one
- * @param int $numberOfRows Number of rows to insert
+ * @param int $before Insert before this row number
+ * @param int $numberOfRows Number of new rows to insert
*
* @return $this
*/
- public function insertNewRowBefore($before, $numberOfRows = 1)
+ public function insertNewRowBefore(int $before, int $numberOfRows = 1)
{
if ($before >= 1) {
$objReferenceHelper = ReferenceHelper::getInstance();
@@ -2468,12 +2507,12 @@ class Worksheet implements IComparable
/**
* Insert a new column, updating all possible related data.
*
- * @param string $before Insert before this one, eg: 'A'
- * @param int $numberOfColumns Number of columns to insert
+ * @param string $before Insert before this column Name, eg: 'A'
+ * @param int $numberOfColumns Number of new columns to insert
*
* @return $this
*/
- public function insertNewColumnBefore($before, $numberOfColumns = 1)
+ public function insertNewColumnBefore(string $before, int $numberOfColumns = 1)
{
if (!is_numeric($before)) {
$objReferenceHelper = ReferenceHelper::getInstance();
@@ -2488,12 +2527,12 @@ class Worksheet implements IComparable
/**
* Insert a new column, updating all possible related data.
*
- * @param int $beforeColumnIndex Insert before this one (numeric column coordinate of the cell)
- * @param int $numberOfColumns Number of columns to insert
+ * @param int $beforeColumnIndex Insert before this column ID (numeric column coordinate of the cell)
+ * @param int $numberOfColumns Number of new columns to insert
*
* @return $this
*/
- public function insertNewColumnBeforeByIndex($beforeColumnIndex, $numberOfColumns = 1)
+ public function insertNewColumnBeforeByIndex(int $beforeColumnIndex, int $numberOfColumns = 1)
{
if ($beforeColumnIndex >= 1) {
return $this->insertNewColumnBefore(Coordinate::stringFromColumnIndex($beforeColumnIndex), $numberOfColumns);
@@ -2505,12 +2544,12 @@ class Worksheet implements IComparable
/**
* Delete a row, updating all possible related data.
*
- * @param int $row Remove starting with this one
+ * @param int $row Remove rows, starting with this row number
* @param int $numberOfRows Number of rows to remove
*
* @return $this
*/
- public function removeRow($row, $numberOfRows = 1)
+ public function removeRow(int $row, int $numberOfRows = 1)
{
if ($row < 1) {
throw new Exception('Rows to be deleted should at least start from row 1.');
@@ -2561,12 +2600,12 @@ class Worksheet implements IComparable
/**
* Remove a column, updating all possible related data.
*
- * @param string $column Remove starting with this one, eg: 'A'
+ * @param string $column Remove columns starting with this column name, eg: 'A'
* @param int $numberOfColumns Number of columns to remove
*
* @return $this
*/
- public function removeColumn($column, $numberOfColumns = 1)
+ public function removeColumn(string $column, int $numberOfColumns = 1)
{
if (is_numeric($column)) {
throw new Exception('Column references should not be numeric.');
@@ -2623,12 +2662,12 @@ class Worksheet implements IComparable
/**
* Remove a column, updating all possible related data.
*
- * @param int $columnIndex Remove starting with this one (numeric column coordinate of the cell)
+ * @param int $columnIndex Remove starting with this column Index (numeric column coordinate)
* @param int $numColumns Number of columns to remove
*
* @return $this
*/
- public function removeColumnByIndex($columnIndex, $numColumns = 1)
+ public function removeColumnByIndex(int $columnIndex, int $numColumns = 1)
{
if ($columnIndex >= 1) {
return $this->removeColumn(Coordinate::stringFromColumnIndex($columnIndex), $numColumns);
@@ -2988,21 +3027,58 @@ class Worksheet implements IComparable
return $this;
}
+ /**
+ * @param mixed $nullValue
+ *
+ * @throws Exception
+ * @throws \PhpOffice\PhpSpreadsheet\Calculation\Exception
+ *
+ * @return mixed
+ */
+ protected function cellToArray(Cell $cell, bool $calculateFormulas, bool $formatData, $nullValue)
+ {
+ $returnValue = $nullValue;
+
+ if ($cell->getValue() !== null) {
+ if ($cell->getValue() instanceof RichText) {
+ $returnValue = $cell->getValue()->getPlainText();
+ } else {
+ $returnValue = ($calculateFormulas) ? $cell->getCalculatedValue() : $cell->getValue();
+ }
+
+ if ($formatData) {
+ $style = $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex());
+ $returnValue = NumberFormat::toFormattedString(
+ $returnValue,
+ $style->getNumberFormat()->getFormatCode() ?? NumberFormat::FORMAT_GENERAL
+ );
+ }
+ }
+
+ return $returnValue;
+ }
+
/**
* Create array from a range of cells.
*
- * @param string $range Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
* @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
* @param bool $calculateFormulas Should formulas be calculated?
* @param bool $formatData Should formatting be applied to cell values?
* @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
- * True - Return rows and columns indexed by their actual row and column IDs
- *
- * @return array
+ * True - Return rows and columns indexed by their actual row and column IDs
+ * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
+ * True - Don't return values for rows/columns that are defined as hidden.
*/
- public function rangeToArray($range, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
- {
- // Returnvalue
+ public function rangeToArray(
+ string $range,
+ $nullValue = null,
+ bool $calculateFormulas = true,
+ bool $formatData = true,
+ bool $returnCellRef = false,
+ bool $ignoreHidden = false
+ ): array {
+ $range = Validations::validateCellOrCellRange($range);
+
$returnValue = [];
// Identify the range that we need to extract from the worksheet
[$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($range);
@@ -3015,42 +3091,23 @@ class Worksheet implements IComparable
// Loop through rows
$r = -1;
for ($row = $minRow; $row <= $maxRow; ++$row) {
- $rRef = $returnCellRef ? $row : ++$r;
+ if (($ignoreHidden === true) && ($this->getRowDimension($row)->getVisible() === false)) {
+ continue;
+ }
+ $rowRef = $returnCellRef ? $row : ++$r;
$c = -1;
// Loop through columns in the current row
- for ($col = $minCol; $col != $maxCol; ++$col) {
- $cRef = $returnCellRef ? $col : ++$c;
+ for ($col = $minCol; $col !== $maxCol; ++$col) {
+ if (($ignoreHidden === true) && ($this->getColumnDimension($col)->getVisible() === false)) {
+ continue;
+ }
+ $columnRef = $returnCellRef ? $col : ++$c;
// Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen
// so we test and retrieve directly against cellCollection
- $cell = $this->cellCollection->get($col . $row);
- //if ($this->cellCollection->has($col . $row)) {
+ $cell = $this->cellCollection->get("{$col}{$row}");
+ $returnValue[$rowRef][$columnRef] = $nullValue;
if ($cell !== null) {
- // Cell exists
- if ($cell->getValue() !== null) {
- if ($cell->getValue() instanceof RichText) {
- $returnValue[$rRef][$cRef] = $cell->getValue()->getPlainText();
- } else {
- if ($calculateFormulas) {
- $returnValue[$rRef][$cRef] = $cell->getCalculatedValue();
- } else {
- $returnValue[$rRef][$cRef] = $cell->getValue();
- }
- }
-
- if ($formatData) {
- $style = $this->getParentOrThrow()->getCellXfByIndex($cell->getXfIndex());
- $returnValue[$rRef][$cRef] = NumberFormat::toFormattedString(
- $returnValue[$rRef][$cRef],
- $style->getNumberFormat()->getFormatCode() ?? NumberFormat::FORMAT_GENERAL
- );
- }
- } else {
- // Cell holds a NULL
- $returnValue[$rRef][$cRef] = $nullValue;
- }
- } else {
- // Cell doesn't exist
- $returnValue[$rRef][$cRef] = $nullValue;
+ $returnValue[$rowRef][$columnRef] = $this->cellToArray($cell, $calculateFormulas, $formatData, $nullValue);
}
}
}
@@ -3102,12 +3159,18 @@ class Worksheet implements IComparable
* @param bool $calculateFormulas Should formulas be calculated?
* @param bool $formatData Should formatting be applied to cell values?
* @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
- * True - Return rows and columns indexed by their actual row and column IDs
- *
- * @return array
+ * True - Return rows and columns indexed by their actual row and column IDs
+ * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
+ * True - Don't return values for rows/columns that are defined as hidden.
*/
- public function namedRangeToArray(string $definedName, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
- {
+ public function namedRangeToArray(
+ string $definedName,
+ $nullValue = null,
+ bool $calculateFormulas = true,
+ bool $formatData = true,
+ bool $returnCellRef = false,
+ bool $ignoreHidden = false
+ ): array {
$retVal = [];
$namedRange = $this->validateNamedRange($definedName);
if ($namedRange !== null) {
@@ -3115,7 +3178,7 @@ class Worksheet implements IComparable
$cellRange = str_replace('$', '', $cellRange);
$workSheet = $namedRange->getWorksheet();
if ($workSheet !== null) {
- $retVal = $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
+ $retVal = $workSheet->rangeToArray($cellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden);
}
}
@@ -3129,12 +3192,17 @@ class Worksheet implements IComparable
* @param bool $calculateFormulas Should formulas be calculated?
* @param bool $formatData Should formatting be applied to cell values?
* @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
- * True - Return rows and columns indexed by their actual row and column IDs
- *
- * @return array
+ * True - Return rows and columns indexed by their actual row and column IDs
+ * @param bool $ignoreHidden False - Return values for rows/columns even if they are defined as hidden.
+ * True - Don't return values for rows/columns that are defined as hidden.
*/
- public function toArray($nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
- {
+ public function toArray(
+ $nullValue = null,
+ bool $calculateFormulas = true,
+ bool $formatData = true,
+ bool $returnCellRef = false,
+ bool $ignoreHidden = false
+ ): array {
// Garbage collect...
$this->garbageCollect();
@@ -3143,7 +3211,7 @@ class Worksheet implements IComparable
$maxRow = $this->getHighestRow();
// Return
- return $this->rangeToArray('A1:' . $maxCol . $maxRow, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
+ return $this->rangeToArray("A1:{$maxCol}{$maxRow}", $nullValue, $calculateFormulas, $formatData, $returnCellRef, $ignoreHidden);
}
/**
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php
index c30bb30ac..842998f9e 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Html.php
@@ -7,9 +7,11 @@ use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Cell\Cell;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Chart\Chart;
+use PhpOffice\PhpSpreadsheet\Document\Properties;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\RichText\Run;
use PhpOffice\PhpSpreadsheet\Settings;
+use PhpOffice\PhpSpreadsheet\Shared\Date;
use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing;
use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Shared\Font as SharedFont;
@@ -342,13 +344,21 @@ class Html extends BaseWriter
private static function generateMeta(?string $val, string $desc): string
{
- return $val
+ return ($val || $val === '0')
? (' ' . PHP_EOL)
: '';
}
public const BODY_LINE = ' ' . PHP_EOL;
+ private const CUSTOM_TO_META = [
+ Properties::PROPERTY_TYPE_BOOLEAN => 'bool',
+ Properties::PROPERTY_TYPE_DATE => 'date',
+ Properties::PROPERTY_TYPE_FLOAT => 'float',
+ Properties::PROPERTY_TYPE_INTEGER => 'int',
+ Properties::PROPERTY_TYPE_STRING => 'string',
+ ];
+
/**
* Generate HTML header.
*
@@ -374,6 +384,36 @@ class Html extends BaseWriter
$html .= self::generateMeta($properties->getCategory(), 'category');
$html .= self::generateMeta($properties->getCompany(), 'company');
$html .= self::generateMeta($properties->getManager(), 'manager');
+ $html .= self::generateMeta($properties->getLastModifiedBy(), 'lastModifiedBy');
+ $date = Date::dateTimeFromTimestamp((string) $properties->getCreated());
+ $date->setTimeZone(Date::getDefaultOrLocalTimeZone());
+ $html .= self::generateMeta($date->format(DATE_W3C), 'created');
+ $date = Date::dateTimeFromTimestamp((string) $properties->getModified());
+ $date->setTimeZone(Date::getDefaultOrLocalTimeZone());
+ $html .= self::generateMeta($date->format(DATE_W3C), 'modified');
+
+ $customProperties = $properties->getCustomProperties();
+ foreach ($customProperties as $customProperty) {
+ $propertyValue = $properties->getCustomPropertyValue($customProperty);
+ $propertyType = $properties->getCustomPropertyType($customProperty);
+ $propertyQualifier = self::CUSTOM_TO_META[$propertyType] ?? null;
+ if ($propertyQualifier !== null) {
+ if ($propertyType === Properties::PROPERTY_TYPE_BOOLEAN) {
+ $propertyValue = $propertyValue ? '1' : '0';
+ } elseif ($propertyType === Properties::PROPERTY_TYPE_DATE) {
+ $date = Date::dateTimeFromTimestamp((string) $propertyValue);
+ $date->setTimeZone(Date::getDefaultOrLocalTimeZone());
+ $propertyValue = $date->format(DATE_W3C);
+ } else {
+ $propertyValue = (string) $propertyValue;
+ }
+ $html .= self::generateMeta($propertyValue, "custom.$propertyQualifier.$customProperty");
+ }
+ }
+
+ if (!empty($properties->getHyperlinkBase())) {
+ $html .= ' ' . PHP_EOL;
+ }
$html .= $includeStyles ? $this->generateStyles(true) : $this->generatePageDeclarations(true);
@@ -693,7 +733,8 @@ class Html extends BaseWriter
// max-width: 100% ensures that image doesnt overflow containing cell
// width: X sets width of supplied image.
// As a result, images bigger than cell will be contained and images smaller will not get stretched
- $html .= '
';
+ $html .= '
';
}
}
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php
index 872be52de..c9e0ba839 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods.php
@@ -12,7 +12,6 @@ use PhpOffice\PhpSpreadsheet\Writer\Ods\Settings;
use PhpOffice\PhpSpreadsheet\Writer\Ods\Styles;
use PhpOffice\PhpSpreadsheet\Writer\Ods\Thumbnails;
use ZipStream\Exception\OverflowException;
-use ZipStream\Option\Archive;
use ZipStream\ZipStream;
class Ods extends BaseWriter
@@ -158,11 +157,7 @@ class Ods extends BaseWriter
}
// Create new ZIP stream
- $options = new Archive();
- $options->setEnableZip64(false);
- $options->setOutputStream($this->fileHandle);
-
- return new ZipStream(null, $options);
+ return ZipStream0::newZipStream($this->fileHandle);
}
/**
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php
index e931421af..e0a729ab8 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php
@@ -126,7 +126,16 @@ class Content extends WriterPart
$objWriter->writeAttribute('table:name', $spreadsheet->getSheet($sheetIndex)->getTitle());
$objWriter->writeAttribute('table:style-name', Style::TABLE_STYLE_PREFIX . (string) ($sheetIndex + 1));
$objWriter->writeElement('office:forms');
+ $lastColumn = 0;
foreach ($spreadsheet->getSheet($sheetIndex)->getColumnDimensions() as $columnDimension) {
+ $thisColumn = $columnDimension->getColumnNumeric();
+ $emptyColumns = $thisColumn - $lastColumn - 1;
+ if ($emptyColumns > 0) {
+ $objWriter->startElement('table:table-column');
+ $objWriter->writeAttribute('table:number-columns-repeated', (string) $emptyColumns);
+ $objWriter->endElement();
+ }
+ $lastColumn = $thisColumn;
$objWriter->startElement('table:table-column');
$objWriter->writeAttribute(
'table:style-name',
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php
index 33a404d42..983414fcc 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php
@@ -751,11 +751,12 @@ class Xls extends BaseWriter
$dataSection_Content .= $dataProp['data']['data'];
$dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']);
- // Condition below can never be true
- //} elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
- // $dataSection_Content .= $dataProp['data']['data'];
+ /* Condition below can never be true
+ } elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
+ $dataSection_Content .= $dataProp['data']['data'];
- // $dataSection_Content_Offset += 4 + 8;
+ $dataSection_Content_Offset += 4 + 8;
+ */
} else {
$dataSection_Content .= $dataProp['data']['data'];
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php
index 6b98395f5..f195ac782 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php
@@ -643,7 +643,6 @@ class Parser
// TODO: use real error codes
throw new WriterException('Unknown range separator');
}
-
// Convert the cell references
[$row1, $col1] = $this->cellToPackedRowcol($cell1);
[$row2, $col2] = $this->cellToPackedRowcol($cell2);
@@ -1109,8 +1108,8 @@ class Parser
if (is_numeric($token) && (!is_numeric($token . $this->lookAhead) || ($this->lookAhead == '')) && ($this->lookAhead !== '!') && ($this->lookAhead !== ':')) {
return $token;
}
- // If it's a string (of maximum 255 characters)
if (preg_match('/"([^"]|""){0,255}"/', $token) && $this->lookAhead !== '"' && (substr_count($token, '"') % 2 == 0)) {
+ // If it's a string (of maximum 255 characters)
return $token;
}
// If it's an error code
@@ -1219,21 +1218,18 @@ class Parser
$this->advance();
return $result;
- // If it's an error code
- } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $this->currentToken) || $this->currentToken == '#N/A') {
+ } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $this->currentToken) || $this->currentToken == '#N/A') { // error code
$result = $this->createTree($this->currentToken, 'ptgErr', '');
$this->advance();
return $result;
- // If it's a negative value
- } elseif ($this->currentToken == '-') {
+ } elseif ($this->currentToken == '-') { // negative value
// catch "-" Term
$this->advance();
$result2 = $this->expression();
return $this->createTree('ptgUminus', $result2, '');
- // If it's a positive value
- } elseif ($this->currentToken == '+') {
+ } elseif ($this->currentToken == '+') { // positive value
// catch "+" Term
$this->advance();
$result2 = $this->expression();
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php
index 6e9b265dc..3c68847aa 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php
@@ -643,9 +643,8 @@ class Workbook extends BIFFwriter
// store the DEFINEDNAME record
$chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
-
- // (exclusive) either repeatColumns or repeatRows
} elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) {
+ // (exclusive) either repeatColumns or repeatRows.
// Columns to repeat
if ($sheetSetup->isColumnsToRepeatAtLeftSet()) {
$repeat = $sheetSetup->getColumnsToRepeatAtLeft();
@@ -1102,16 +1101,15 @@ class Workbook extends BIFFwriter
// 2. space remaining is greater than or equal to minimum space needed
// here we write as much as we can in the current block, then move to next record data block
- // 1. space remaining is less than minimum space needed
if ($space_remaining < $min_space_needed) {
+ // 1. space remaining is less than minimum space needed.
// we close the block, store the block data
$recordDatas[] = $recordData;
// and start new record data block where we start writing the string
$recordData = '';
-
- // 2. space remaining is greater than or equal to minimum space needed
} else {
+ // 2. space remaining is greater than or equal to minimum space needed.
// initialize effective remaining space, for Unicode strings this may need to be reduced by 1, see below
$effective_space_remaining = $space_remaining;
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php
index 9f23bd365..aeedd08e7 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php
@@ -503,6 +503,8 @@ class Worksheet extends BIFFwriter
$this->writeMergedCells();
// Hyperlinks
+ $phpParent = $phpSheet->getParent();
+ $hyperlinkbase = ($phpParent === null) ? '' : $phpParent->getProperties()->getHyperlinkBase();
foreach ($phpSheet->getHyperLinkCollection() as $coordinate => $hyperlink) {
[$column, $row] = Coordinate::indexesFromString($coordinate);
@@ -513,6 +515,11 @@ class Worksheet extends BIFFwriter
$url = str_replace('sheet://', 'internal:', $url);
} elseif (preg_match('/^(http:|https:|ftp:|mailto:)/', $url)) {
// URL
+ } elseif (!empty($hyperlinkbase) && preg_match('~^([A-Za-z]:)?[/\\\\]~', $url) !== 1) {
+ $url = "$hyperlinkbase$url";
+ if (preg_match('/^(http:|https:|ftp:|mailto:)/', $url) !== 1) {
+ $url = 'external:' . $url;
+ }
} else {
// external (local file)
$url = 'external:' . $url;
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php
index 07b790440..6ed12d4aa 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php
@@ -31,7 +31,6 @@ use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Workbook;
use PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet;
use ZipArchive;
use ZipStream\Exception\OverflowException;
-use ZipStream\Option\Archive;
use ZipStream\ZipStream;
class Xlsx extends BaseWriter
@@ -377,7 +376,7 @@ class Xlsx extends BaseWriter
}
// Add theme to ZIP file
- $zipContent['xl/theme/theme1.xml'] = $this->getWriterPartTheme()->writeTheme();
+ $zipContent['xl/theme/theme1.xml'] = $this->getWriterPartTheme()->writeTheme($this->spreadSheet);
// Add string table to ZIP file
$zipContent['xl/sharedStrings.xml'] = $this->getWriterPartStringTable()->writeStringTable($this->stringTable);
@@ -546,11 +545,7 @@ class Xlsx extends BaseWriter
$this->openFileHandle($filename);
- $options = new Archive();
- $options->setEnableZip64(false);
- $options->setOutputStream($this->fileHandle);
-
- $this->zip = new ZipStream(null, $options);
+ $this->zip = ZipStream0::newZipStream($this->fileHandle);
$this->addZipFiles($zipContent);
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
index 6465904ee..6d302aac4 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
@@ -14,6 +14,7 @@ use PhpOffice\PhpSpreadsheet\Chart\Title;
use PhpOffice\PhpSpreadsheet\Chart\TrendLine;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Style\Font;
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
class Chart extends WriterPart
@@ -109,12 +110,20 @@ class Chart extends WriterPart
$objWriter->endElement();
$objWriter->endElement(); // c:chart
+
+ $objWriter->startElement('c:spPr');
if ($chart->getNoFill()) {
- $objWriter->startElement('c:spPr');
$objWriter->startElement('a:noFill');
$objWriter->endElement(); // a:noFill
- $objWriter->endElement(); // c:spPr
}
+ $fillColor = $chart->getFillColor();
+ if ($fillColor->isUsable()) {
+ $this->writeColor($objWriter, $fillColor);
+ }
+ $borderLines = $chart->getBorderLines();
+ $this->writeLineStyles($objWriter, $borderLines);
+ $this->writeEffects($objWriter, $borderLines);
+ $objWriter->endElement(); // c:spPr
$this->writePrintSettings($objWriter);
@@ -201,6 +210,17 @@ class Chart extends WriterPart
$objWriter->writeAttribute('val', ($legend->getOverlay()) ? '1' : '0');
$objWriter->endElement();
+ $objWriter->startElement('c:spPr');
+ $fillColor = $legend->getFillColor();
+ if ($fillColor->isUsable()) {
+ $this->writeColor($objWriter, $fillColor);
+ }
+ $borderLines = $legend->getBorderLines();
+ $this->writeLineStyles($objWriter, $borderLines);
+ $this->writeEffects($objWriter, $borderLines);
+ $objWriter->endElement(); // c:spPr
+
+ $legendText = $legend->getLegendText();
$objWriter->startElement('c:txPr');
$objWriter->startElement('a:bodyPr');
$objWriter->endElement();
@@ -213,17 +233,21 @@ class Chart extends WriterPart
$objWriter->writeAttribute('rtl', '0');
$objWriter->startElement('a:defRPr');
- $objWriter->endElement();
- $objWriter->endElement();
+ if ($legendText !== null) {
+ $this->writeColor($objWriter, $legendText->getFillColorObject());
+ $this->writeEffects($objWriter, $legendText);
+ }
+ $objWriter->endElement(); // a:defRpr
+ $objWriter->endElement(); // a:pPr
$objWriter->startElement('a:endParaRPr');
$objWriter->writeAttribute('lang', 'en-US');
- $objWriter->endElement();
+ $objWriter->endElement(); // a:endParaRPr
- $objWriter->endElement();
- $objWriter->endElement();
+ $objWriter->endElement(); // a:p
+ $objWriter->endElement(); // c:txPr
- $objWriter->endElement();
+ $objWriter->endElement(); // c:legend
}
/**
@@ -307,19 +331,26 @@ class Chart extends WriterPart
$objWriter->startElement('c:hiLowLines');
$objWriter->endElement();
- $objWriter->startElement('c:upDownBars');
-
- $objWriter->startElement('c:gapWidth');
- $objWriter->writeAttribute('val', '300');
- $objWriter->endElement();
-
- $objWriter->startElement('c:upBars');
- $objWriter->endElement();
-
- $objWriter->startElement('c:downBars');
- $objWriter->endElement();
-
- $objWriter->endElement();
+ $gapWidth = $plotArea->getGapWidth();
+ $upBars = $plotArea->getUseUpBars();
+ $downBars = $plotArea->getUseDownBars();
+ if ($gapWidth !== null || $upBars || $downBars) {
+ $objWriter->startElement('c:upDownBars');
+ if ($gapWidth !== null) {
+ $objWriter->startElement('c:gapWidth');
+ $objWriter->writeAttribute('val', "$gapWidth");
+ $objWriter->endElement();
+ }
+ if ($upBars) {
+ $objWriter->startElement('c:upBars');
+ $objWriter->endElement();
+ }
+ if ($downBars) {
+ $objWriter->startElement('c:downBars');
+ $objWriter->endElement();
+ }
+ $objWriter->endElement(); // c:upDownBars
+ }
}
// Generate 3 unique numbers to use for axId values
@@ -428,8 +459,8 @@ class Chart extends WriterPart
}
$objWriter->endElement(); // c:spPr
}
- $fontColor = $chartLayout->getLabelFontColor();
- if ($fontColor && $fontColor->isUsable()) {
+ $labelFont = $chartLayout->getLabelFont();
+ if ($labelFont !== null) {
$objWriter->startElement('c:txPr');
$objWriter->startElement('a:bodyPr');
@@ -445,14 +476,7 @@ class Chart extends WriterPart
$objWriter->startElement('a:lstStyle');
$objWriter->endElement(); // a:lstStyle
-
- $objWriter->startElement('a:p');
- $objWriter->startElement('a:pPr');
- $objWriter->startElement('a:defRPr');
- $this->writeColor($objWriter, $fontColor);
- $objWriter->endElement(); // a:defRPr
- $objWriter->endElement(); // a:pPr
- $objWriter->endElement(); // a:p
+ $this->writeLabelFont($objWriter, $labelFont, $chartLayout->getLabelEffects());
$objWriter->endElement(); // c:txPr
}
@@ -608,25 +632,24 @@ class Chart extends WriterPart
}
$textRotation = $yAxis->getAxisOptionsProperty('textRotation');
- if (is_numeric($textRotation)) {
+ $axisText = $yAxis->getAxisText();
+
+ if ($axisText !== null || is_numeric($textRotation)) {
$objWriter->startElement('c:txPr');
$objWriter->startElement('a:bodyPr');
- $objWriter->writeAttribute('rot', Properties::angleToXml((float) $textRotation));
+ if (is_numeric($textRotation)) {
+ $objWriter->writeAttribute('rot', Properties::angleToXml((float) $textRotation));
+ }
$objWriter->endElement(); // a:bodyPr
$objWriter->startElement('a:lstStyle');
$objWriter->endElement(); // a:lstStyle
- $objWriter->startElement('a:p');
- $objWriter->startElement('a:pPr');
- $objWriter->startElement('a:defRPr');
- $objWriter->endElement(); // a:defRPr
- $objWriter->endElement(); // a:pPr
- $objWriter->endElement(); // a:p
+ $this->writeLabelFont($objWriter, ($axisText === null) ? null : $axisText->getFont(), $axisText);
$objWriter->endElement(); // c:txPr
}
$objWriter->startElement('c:spPr');
$this->writeColor($objWriter, $yAxis->getFillColorObject());
- $this->writeLineStyles($objWriter, $yAxis);
+ $this->writeLineStyles($objWriter, $yAxis, $yAxis->getNoFill());
$this->writeEffects($objWriter, $yAxis);
$objWriter->endElement(); // spPr
@@ -826,25 +849,26 @@ class Chart extends WriterPart
}
$textRotation = $xAxis->getAxisOptionsProperty('textRotation');
- if (is_numeric($textRotation)) {
+ $axisText = $xAxis->getAxisText();
+
+ if ($axisText !== null || is_numeric($textRotation)) {
$objWriter->startElement('c:txPr');
$objWriter->startElement('a:bodyPr');
- $objWriter->writeAttribute('rot', Properties::angleToXml((float) $textRotation));
+ if (is_numeric($textRotation)) {
+ $objWriter->writeAttribute('rot', Properties::angleToXml((float) $textRotation));
+ }
$objWriter->endElement(); // a:bodyPr
$objWriter->startElement('a:lstStyle');
$objWriter->endElement(); // a:lstStyle
- $objWriter->startElement('a:p');
- $objWriter->startElement('a:pPr');
- $objWriter->startElement('a:defRPr');
- $objWriter->endElement(); // a:defRPr
- $objWriter->endElement(); // a:pPr
- $objWriter->endElement(); // a:p
+
+ $this->writeLabelFont($objWriter, ($axisText === null) ? null : $axisText->getFont(), $axisText);
+
$objWriter->endElement(); // c:txPr
}
$objWriter->startElement('c:spPr');
$this->writeColor($objWriter, $xAxis->getFillColorObject());
- $this->writeLineStyles($objWriter, $xAxis);
+ $this->writeLineStyles($objWriter, $xAxis, $xAxis->getNoFill());
$this->writeEffects($objWriter, $xAxis);
$objWriter->endElement(); //end spPr
@@ -1055,14 +1079,6 @@ class Chart extends WriterPart
$labelFill = $plotLabel->getFillColorObject();
$labelFill = ($labelFill instanceof ChartColor) ? $labelFill : null;
}
- if ($plotLabel && $groupType !== DataSeries::TYPE_LINECHART) {
- $fillColor = $plotLabel->getFillColorObject();
- if ($fillColor !== null && !is_array($fillColor) && $fillColor->isUsable()) {
- $objWriter->startElement('c:spPr');
- $this->writeColor($objWriter, $fillColor);
- $objWriter->endElement(); // c:spPr
- }
- }
// Values
$plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesIdx);
@@ -1094,6 +1110,12 @@ class Chart extends WriterPart
$plotSeriesValues !== false
) {
$objWriter->startElement('c:spPr');
+ if ($plotLabel && $groupType !== DataSeries::TYPE_LINECHART) {
+ $fillColor = $plotLabel->getFillColorObject();
+ if ($fillColor !== null && !is_array($fillColor) && $fillColor->isUsable()) {
+ $this->writeColor($objWriter, $fillColor);
+ }
+ }
$fillObject = $labelFill ?? $plotSeriesValues->getFillColorObject();
$callLineStyles = true;
if ($fillObject instanceof ChartColor && $fillObject->isUsable()) {
@@ -1398,7 +1420,7 @@ class Chart extends WriterPart
$count = $plotSeriesValues->getPointCount();
$source = $plotSeriesValues->getDataSource();
$values = $plotSeriesValues->getDataValues();
- if ($count > 1 || ($count === 1 && "=$source" !== (string) $values[0])) {
+ if ($count > 1 || ($count === 1 && array_key_exists(0, $values) && "=$source" !== (string) $values[0])) {
$objWriter->startElement('c:' . $dataType . 'Cache');
if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) {
@@ -1770,4 +1792,51 @@ class Chart extends WriterPart
}
}
}
+
+ private function writeLabelFont(XMLWriter $objWriter, ?Font $labelFont, ?Properties $axisText): void
+ {
+ $objWriter->startElement('a:p');
+ $objWriter->startElement('a:pPr');
+ $objWriter->startElement('a:defRPr');
+ if ($labelFont !== null) {
+ $fontSize = $labelFont->getSize();
+ if (is_numeric($fontSize)) {
+ $fontSize *= (($fontSize < 100) ? 100 : 1);
+ $objWriter->writeAttribute('sz', (string) $fontSize);
+ }
+ if ($labelFont->getBold() === true) {
+ $objWriter->writeAttribute('b', '1');
+ }
+ if ($labelFont->getItalic() === true) {
+ $objWriter->writeAttribute('i', '1');
+ }
+ $fontColor = $labelFont->getChartColor();
+ if ($fontColor !== null) {
+ $this->writeColor($objWriter, $fontColor);
+ }
+ }
+ if ($axisText !== null) {
+ $this->writeEffects($objWriter, $axisText);
+ }
+ if ($labelFont !== null) {
+ if (!empty($labelFont->getLatin())) {
+ $objWriter->startElement('a:latin');
+ $objWriter->writeAttribute('typeface', $labelFont->getLatin());
+ $objWriter->endElement();
+ }
+ if (!empty($labelFont->getEastAsian())) {
+ $objWriter->startElement('a:eastAsian');
+ $objWriter->writeAttribute('typeface', $labelFont->getEastAsian());
+ $objWriter->endElement();
+ }
+ if (!empty($labelFont->getComplexScript())) {
+ $objWriter->startElement('a:complexScript');
+ $objWriter->writeAttribute('typeface', $labelFont->getComplexScript());
+ $objWriter->endElement();
+ }
+ }
+ $objWriter->endElement(); // a:defRPr
+ $objWriter->endElement(); // a:pPr
+ $objWriter->endElement(); // a:p
+ }
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php
index 8902826a1..8c33f5932 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php
@@ -93,6 +93,9 @@ class DocProps extends WriterPart
// SharedDoc
$objWriter->writeElement('SharedDoc', 'false');
+ // HyperlinkBase
+ $objWriter->writeElement('HyperlinkBase', $spreadsheet->getProperties()->getHyperlinkBase());
+
// HyperlinksChanged
$objWriter->writeElement('HyperlinksChanged', 'false');
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php
index ecc247d86..cf4f90693 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/FunctionPrefix.php
@@ -4,7 +4,7 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
class FunctionPrefix
{
- const XLFNREGEXP = '/(?:_xlfn\.)?((?:_xlws\.)?('
+ const XLFNREGEXP = '/(?:_xlfn\.)?((?:_xlws\.)?\b('
// functions added with Excel 2010
. 'beta[.]dist'
. '|beta[.]inv'
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
index 7f623933c..29e95eb2f 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
@@ -64,7 +64,7 @@ class StringTable extends WriterPart
/**
* Write string table to XML format.
*
- * @param (string|RichText)[] $stringTable
+ * @param (RichText|string)[] $stringTable
*
* @return string XML Output
*/
@@ -226,9 +226,10 @@ class StringTable extends WriterPart
if ($element->getFont() !== null) {
// rPr
$objWriter->startElement($prefix . 'rPr');
- $size = $element->getFont()->getSize();
- if (is_numeric($size)) {
- $objWriter->writeAttribute('sz', (string) (int) ($size * 100));
+ $fontSize = $element->getFont()->getSize();
+ if (is_numeric($fontSize)) {
+ $fontSize *= (($fontSize < 100) ? 100 : 1);
+ $objWriter->writeAttribute('sz', (string) $fontSize);
}
// Bold
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php
index 0261f22e5..baafdc334 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php
@@ -112,8 +112,13 @@ class Style extends WriterPart
$objWriter->writeAttribute('count', (string) count($spreadsheet->getCellXfCollection()));
// xf
+ $alignment = new Alignment();
+ $defaultAlignHash = $alignment->getHashCode();
+ if ($defaultAlignHash !== $spreadsheet->getDefaultStyle()->getAlignment()->getHashCode()) {
+ $defaultAlignHash = '';
+ }
foreach ($spreadsheet->getCellXfCollection() as $cellXf) {
- $this->writeCellStyleXf($objWriter, $cellXf, $spreadsheet);
+ $this->writeCellStyleXf($objWriter, $cellXf, $spreadsheet, $defaultAlignHash);
}
$objWriter->endElement();
@@ -354,6 +359,13 @@ class Style extends WriterPart
$objWriter->endElement();
}
+ if (!empty($font->getScheme())) {
+ $this->startFont($objWriter, $fontStarted);
+ $objWriter->startElement('scheme');
+ $objWriter->writeAttribute('val', $font->getScheme());
+ $objWriter->endElement();
+ }
+
if ($fontStarted) {
$objWriter->endElement();
}
@@ -400,7 +412,7 @@ class Style extends WriterPart
/**
* Write Cell Style Xf.
*/
- private function writeCellStyleXf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $style, Spreadsheet $spreadsheet): void
+ private function writeCellStyleXf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $style, Spreadsheet $spreadsheet, string $defaultAlignHash): void
{
// xf
$objWriter->startElement('xf');
@@ -424,7 +436,11 @@ class Style extends WriterPart
$objWriter->writeAttribute('applyNumberFormat', ($spreadsheet->getDefaultStyle()->getNumberFormat()->getHashCode() != $style->getNumberFormat()->getHashCode()) ? '1' : '0');
$objWriter->writeAttribute('applyFill', ($spreadsheet->getDefaultStyle()->getFill()->getHashCode() != $style->getFill()->getHashCode()) ? '1' : '0');
$objWriter->writeAttribute('applyBorder', ($spreadsheet->getDefaultStyle()->getBorders()->getHashCode() != $style->getBorders()->getHashCode()) ? '1' : '0');
- $applyAlignment = ($spreadsheet->getDefaultStyle()->getAlignment()->getHashCode() != $style->getAlignment()->getHashCode()) ? '1' : '0';
+ if ($defaultAlignHash !== '' && $defaultAlignHash === $style->getAlignment()->getHashCode()) {
+ $applyAlignment = '0';
+ } else {
+ $applyAlignment = '1';
+ }
$objWriter->writeAttribute('applyAlignment', $applyAlignment);
if ($style->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $style->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) {
$objWriter->writeAttribute('applyProtection', 'true');
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php
index 9ff29d45d..1e8ef5b43 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php
@@ -4,109 +4,17 @@ namespace PhpOffice\PhpSpreadsheet\Writer\Xlsx;
use PhpOffice\PhpSpreadsheet\Reader\Xlsx\Namespaces;
use PhpOffice\PhpSpreadsheet\Shared\XMLWriter;
+use PhpOffice\PhpSpreadsheet\Spreadsheet;
+use PhpOffice\PhpSpreadsheet\Theme as SpreadsheetTheme;
class Theme extends WriterPart
{
- /**
- * Map of Major fonts to write.
- *
- * @var string[]
- */
- private static $majorFonts = [
- 'Jpan' => 'MS Pゴシック',
- 'Hang' => '맑은 고딕',
- 'Hans' => '宋体',
- 'Hant' => '新細明體',
- 'Arab' => 'Times New Roman',
- 'Hebr' => 'Times New Roman',
- 'Thai' => 'Tahoma',
- 'Ethi' => 'Nyala',
- 'Beng' => 'Vrinda',
- 'Gujr' => 'Shruti',
- 'Khmr' => 'MoolBoran',
- 'Knda' => 'Tunga',
- 'Guru' => 'Raavi',
- 'Cans' => 'Euphemia',
- 'Cher' => 'Plantagenet Cherokee',
- 'Yiii' => 'Microsoft Yi Baiti',
- 'Tibt' => 'Microsoft Himalaya',
- 'Thaa' => 'MV Boli',
- 'Deva' => 'Mangal',
- 'Telu' => 'Gautami',
- 'Taml' => 'Latha',
- 'Syrc' => 'Estrangelo Edessa',
- 'Orya' => 'Kalinga',
- 'Mlym' => 'Kartika',
- 'Laoo' => 'DokChampa',
- 'Sinh' => 'Iskoola Pota',
- 'Mong' => 'Mongolian Baiti',
- 'Viet' => 'Times New Roman',
- 'Uigh' => 'Microsoft Uighur',
- 'Geor' => 'Sylfaen',
- ];
-
- /**
- * Map of Minor fonts to write.
- *
- * @var string[]
- */
- private static $minorFonts = [
- 'Jpan' => 'MS Pゴシック',
- 'Hang' => '맑은 고딕',
- 'Hans' => '宋体',
- 'Hant' => '新細明體',
- 'Arab' => 'Arial',
- 'Hebr' => 'Arial',
- 'Thai' => 'Tahoma',
- 'Ethi' => 'Nyala',
- 'Beng' => 'Vrinda',
- 'Gujr' => 'Shruti',
- 'Khmr' => 'DaunPenh',
- 'Knda' => 'Tunga',
- 'Guru' => 'Raavi',
- 'Cans' => 'Euphemia',
- 'Cher' => 'Plantagenet Cherokee',
- 'Yiii' => 'Microsoft Yi Baiti',
- 'Tibt' => 'Microsoft Himalaya',
- 'Thaa' => 'MV Boli',
- 'Deva' => 'Mangal',
- 'Telu' => 'Gautami',
- 'Taml' => 'Latha',
- 'Syrc' => 'Estrangelo Edessa',
- 'Orya' => 'Kalinga',
- 'Mlym' => 'Kartika',
- 'Laoo' => 'DokChampa',
- 'Sinh' => 'Iskoola Pota',
- 'Mong' => 'Mongolian Baiti',
- 'Viet' => 'Arial',
- 'Uigh' => 'Microsoft Uighur',
- 'Geor' => 'Sylfaen',
- ];
-
- /**
- * Map of core colours.
- *
- * @var string[]
- */
- private static $colourScheme = [
- 'dk2' => '1F497D',
- 'lt2' => 'EEECE1',
- 'accent1' => '4F81BD',
- 'accent2' => 'C0504D',
- 'accent3' => '9BBB59',
- 'accent4' => '8064A2',
- 'accent5' => '4BACC6',
- 'accent6' => 'F79646',
- 'hlink' => '0000FF',
- 'folHlink' => '800080',
- ];
-
/**
* Write theme to XML format.
*
* @return string XML Output
*/
- public function writeTheme()
+ public function writeTheme(Spreadsheet $spreadsheet)
{
// Create XML writer
$objWriter = null;
@@ -115,6 +23,7 @@ class Theme extends WriterPart
} else {
$objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
}
+ $theme = $spreadsheet->getTheme();
// XML header
$objWriter->startDocument('1.0', 'UTF-8', 'yes');
@@ -129,50 +38,39 @@ class Theme extends WriterPart
// a:clrScheme
$objWriter->startElement('a:clrScheme');
- $objWriter->writeAttribute('name', 'Office');
+ $objWriter->writeAttribute('name', $theme->getThemeColorName());
- // a:dk1
- $objWriter->startElement('a:dk1');
-
- // a:sysClr
- $objWriter->startElement('a:sysClr');
- $objWriter->writeAttribute('val', 'windowText');
- $objWriter->writeAttribute('lastClr', '000000');
- $objWriter->endElement();
-
- $objWriter->endElement();
-
- // a:lt1
- $objWriter->startElement('a:lt1');
-
- // a:sysClr
- $objWriter->startElement('a:sysClr');
- $objWriter->writeAttribute('val', 'window');
- $objWriter->writeAttribute('lastClr', 'FFFFFF');
- $objWriter->endElement();
-
- $objWriter->endElement();
-
- // a:dk2
- $this->writeColourScheme($objWriter);
+ $this->writeColourScheme($objWriter, $theme);
$objWriter->endElement();
// a:fontScheme
$objWriter->startElement('a:fontScheme');
- $objWriter->writeAttribute('name', 'Office');
+ $objWriter->writeAttribute('name', $theme->getThemeFontName());
// a:majorFont
$objWriter->startElement('a:majorFont');
- $this->writeFonts($objWriter, 'Cambria', self::$majorFonts);
- $objWriter->endElement();
+ $this->writeFonts(
+ $objWriter,
+ $theme->getMajorFontLatin(),
+ $theme->getMajorFontEastAsian(),
+ $theme->getMajorFontComplexScript(),
+ $theme->getMajorFontSubstitutions()
+ );
+ $objWriter->endElement(); // a:majorFont
// a:minorFont
$objWriter->startElement('a:minorFont');
- $this->writeFonts($objWriter, 'Calibri', self::$minorFonts);
- $objWriter->endElement();
+ $this->writeFonts(
+ $objWriter,
+ $theme->getMinorFontLatin(),
+ $theme->getMinorFontEastAsian(),
+ $theme->getMinorFontComplexScript(),
+ $theme->getMinorFontSubstitutions()
+ );
+ $objWriter->endElement(); // a:minorFont
- $objWriter->endElement();
+ $objWriter->endElement(); // a:fontScheme
// a:fmtScheme
$objWriter->startElement('a:fmtScheme');
@@ -786,7 +684,7 @@ class Theme extends WriterPart
*
* @param string[] $fontSet
*/
- private function writeFonts(XMLWriter $objWriter, string $latinFont, array $fontSet): void
+ private function writeFonts(XMLWriter $objWriter, string $latinFont, string $eastAsianFont, string $complexScriptFont, array $fontSet): void
{
// a:latin
$objWriter->startElement('a:latin');
@@ -795,12 +693,12 @@ class Theme extends WriterPart
// a:ea
$objWriter->startElement('a:ea');
- $objWriter->writeAttribute('typeface', '');
+ $objWriter->writeAttribute('typeface', $eastAsianFont);
$objWriter->endElement();
// a:cs
$objWriter->startElement('a:cs');
- $objWriter->writeAttribute('typeface', '');
+ $objWriter->writeAttribute('typeface', $complexScriptFont);
$objWriter->endElement();
foreach ($fontSet as $fontScript => $typeface) {
@@ -814,16 +712,33 @@ class Theme extends WriterPart
/**
* Write colour scheme to XML format.
*/
- private function writeColourScheme(XMLWriter $objWriter): void
+ private function writeColourScheme(XMLWriter $objWriter, SpreadsheetTheme $theme): void
{
- foreach (self::$colourScheme as $colourName => $colourValue) {
- $objWriter->startElement('a:' . $colourName);
+ $themeArray = $theme->getThemeColors();
+ // a:dk1
+ $objWriter->startElement('a:dk1');
+ $objWriter->startElement('a:sysClr');
+ $objWriter->writeAttribute('val', 'windowText');
+ $objWriter->writeAttribute('lastClr', $themeArray['dk1'] ?? '000000');
+ $objWriter->endElement(); // a:sysClr
+ $objWriter->endElement(); // a:dk1
- $objWriter->startElement('a:srgbClr');
- $objWriter->writeAttribute('val', $colourValue);
- $objWriter->endElement();
+ // a:lt1
+ $objWriter->startElement('a:lt1');
+ $objWriter->startElement('a:sysClr');
+ $objWriter->writeAttribute('val', 'window');
+ $objWriter->writeAttribute('lastClr', $themeArray['lt1'] ?? 'FFFFFF');
+ $objWriter->endElement(); // a:sysClr
+ $objWriter->endElement(); // a:lt1
- $objWriter->endElement();
+ foreach ($themeArray as $colourName => $colourValue) {
+ if ($colourName !== 'dk1' && $colourName !== 'lt1') {
+ $objWriter->startElement('a:' . $colourName);
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', $colourValue);
+ $objWriter->endElement(); // a:srgbClr
+ $objWriter->endElement(); // a:$colourName
+ }
}
}
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
index 53c451245..5e453b3d8 100644
--- a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
@@ -18,6 +18,18 @@ use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet as PhpspreadsheetWorksheet;
class Worksheet extends WriterPart
{
+ /** @var string */
+ private $numberStoredAsText = '';
+
+ /** @var string */
+ private $formula = '';
+
+ /** @var string */
+ private $twoDigitTextYear = '';
+
+ /** @var string */
+ private $evalError = '';
+
/**
* Write worksheet to XML format.
*
@@ -28,6 +40,10 @@ class Worksheet extends WriterPart
*/
public function writeWorksheet(PhpspreadsheetWorksheet $worksheet, $stringTable = [], $includeCharts = false)
{
+ $this->numberStoredAsText = '';
+ $this->formula = '';
+ $this->twoDigitTextYear = '';
+ $this->evalError = '';
// Create XML writer
$objWriter = null;
if ($this->getParentWriter()->getUseDiskCaching()) {
@@ -118,6 +134,9 @@ class Worksheet extends WriterPart
// AlternateContent
$this->writeAlternateContent($objWriter, $worksheet);
+ // IgnoredErrors
+ $this->writeIgnoredErrors($objWriter);
+
// Table
$this->writeTable($objWriter, $worksheet);
@@ -131,6 +150,32 @@ class Worksheet extends WriterPart
return $objWriter->getData();
}
+ private function writeIgnoredError(XMLWriter $objWriter, bool &$started, string $attr, string $cells): void
+ {
+ if ($cells !== '') {
+ if (!$started) {
+ $objWriter->startElement('ignoredErrors');
+ $started = true;
+ }
+ $objWriter->startElement('ignoredError');
+ $objWriter->writeAttribute('sqref', substr($cells, 1));
+ $objWriter->writeAttribute($attr, '1');
+ $objWriter->endElement();
+ }
+ }
+
+ private function writeIgnoredErrors(XMLWriter $objWriter): void
+ {
+ $started = false;
+ $this->writeIgnoredError($objWriter, $started, 'numberStoredAsText', $this->numberStoredAsText);
+ $this->writeIgnoredError($objWriter, $started, 'formula', $this->formula);
+ $this->writeIgnoredError($objWriter, $started, 'twoDigitTextYear', $this->twoDigitTextYear);
+ $this->writeIgnoredError($objWriter, $started, 'evalError', $this->evalError);
+ if ($started) {
+ $objWriter->endElement();
+ }
+ }
+
/**
* Write SheetPr.
*/
@@ -1134,7 +1179,20 @@ class Worksheet extends WriterPart
array_pop($columnsInRow);
foreach ($columnsInRow as $column) {
// Write cell
- $this->writeCell($objWriter, $worksheet, "{$column}{$currentRow}", $aFlippedStringTable);
+ $coord = "$column$currentRow";
+ if ($worksheet->getCell($coord)->getIgnoredErrors()->getNumberStoredAsText()) {
+ $this->numberStoredAsText .= " $coord";
+ }
+ if ($worksheet->getCell($coord)->getIgnoredErrors()->getFormula()) {
+ $this->formula .= " $coord";
+ }
+ if ($worksheet->getCell($coord)->getIgnoredErrors()->getTwoDigitTextYear()) {
+ $this->twoDigitTextYear .= " $coord";
+ }
+ if ($worksheet->getCell($coord)->getIgnoredErrors()->getEvalError()) {
+ $this->evalError .= " $coord";
+ }
+ $this->writeCell($objWriter, $worksheet, $coord, $aFlippedStringTable);
}
}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream0.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream0.php
new file mode 100644
index 000000000..886731ca9
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream0.php
@@ -0,0 +1,17 @@
+setEnableZip64(false);
+ $options->setOutputStream($fileHandle);
+
+ return new ZipStream(null, $options);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream3.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream3.php
new file mode 100644
index 000000000..d9c8d0b16
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/ZipStream3.php
@@ -0,0 +1,22 @@
+
+ * @var string[]
*/
const ALL = [
self::SSL,
diff --git a/vendor/rmccue/requests/src/Cookie.php b/vendor/rmccue/requests/src/Cookie.php
index ccbbc73db..6f971d6db 100644
--- a/vendor/rmccue/requests/src/Cookie.php
+++ b/vendor/rmccue/requests/src/Cookie.php
@@ -36,8 +36,8 @@ class Cookie {
/**
* Cookie attributes
*
- * Valid keys are (currently) path, domain, expires, max-age, secure and
- * httponly.
+ * Valid keys are `'path'`, `'domain'`, `'expires'`, `'max-age'`, `'secure'` and
+ * `'httponly'`.
*
* @var \WpOrg\Requests\Utility\CaseInsensitiveDictionary|array Array-like object
*/
@@ -46,8 +46,7 @@ class Cookie {
/**
* Cookie flags
*
- * Valid keys are (currently) creation, last-access, persistent and
- * host-only.
+ * Valid keys are `'creation'`, `'last-access'`, `'persistent'` and `'host-only'`.
*
* @var array
*/
@@ -66,11 +65,13 @@ class Cookie {
/**
* Create a new cookie object
*
- * @param string $name
- * @param string $value
+ * @param string $name The name of the cookie.
+ * @param string $value The value for the cookie.
* @param array|\WpOrg\Requests\Utility\CaseInsensitiveDictionary $attributes Associative array of attribute data
- * @param array $flags
- * @param int|null $reference_time
+ * @param array $flags The flags for the cookie.
+ * Valid keys are `'creation'`, `'last-access'`,
+ * `'persistent'` and `'host-only'`.
+ * @param int|null $reference_time Reference time for relative calculations.
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $name argument is not a string.
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $value argument is not a string.
@@ -279,7 +280,11 @@ class Cookie {
public function normalize() {
foreach ($this->attributes as $key => $value) {
$orig_value = $value;
- $value = $this->normalize_attribute($key, $value);
+
+ if (is_string($key)) {
+ $value = $this->normalize_attribute($key, $value);
+ }
+
if ($value === null) {
unset($this->attributes[$key]);
continue;
@@ -299,7 +304,7 @@ class Cookie {
* Handles parsing individual attributes from the cookie values.
*
* @param string $name Attribute name
- * @param string|boolean $value Attribute value (string value, or true if empty/flag)
+ * @param string|int|bool $value Attribute value (string/integer value, or true if empty/flag)
* @return mixed Value if available, or null if the attribute value is invalid (and should be skipped)
*/
protected function normalize_attribute($name, $value) {
diff --git a/vendor/rmccue/requests/src/Cookie/Jar.php b/vendor/rmccue/requests/src/Cookie/Jar.php
index dfbb8b739..7633786b9 100644
--- a/vendor/rmccue/requests/src/Cookie/Jar.php
+++ b/vendor/rmccue/requests/src/Cookie/Jar.php
@@ -49,7 +49,8 @@ class Jar implements ArrayAccess, IteratorAggregate {
/**
* Normalise cookie data into a \WpOrg\Requests\Cookie
*
- * @param string|\WpOrg\Requests\Cookie $cookie
+ * @param string|\WpOrg\Requests\Cookie $cookie Cookie header value, possibly pre-parsed (object).
+ * @param string $key Optional. The name for this cookie.
* @return \WpOrg\Requests\Cookie
*/
public function normalize_cookie($cookie, $key = '') {
@@ -106,7 +107,7 @@ class Jar implements ArrayAccess, IteratorAggregate {
/**
* Unset the given header
*
- * @param string $offset
+ * @param string $offset The key for the item to unset.
*/
#[ReturnTypeWillChange]
public function offsetUnset($offset) {
@@ -171,7 +172,7 @@ class Jar implements ArrayAccess, IteratorAggregate {
/**
* Parse all cookies from a response and attach them to the response
*
- * @param \WpOrg\Requests\Response $response
+ * @param \WpOrg\Requests\Response $response Response as received.
*/
public function before_redirect_check(Response $response) {
$url = $response->url;
diff --git a/vendor/rmccue/requests/src/IdnaEncoder.php b/vendor/rmccue/requests/src/IdnaEncoder.php
index 094fff3d5..4257a1acb 100644
--- a/vendor/rmccue/requests/src/IdnaEncoder.php
+++ b/vendor/rmccue/requests/src/IdnaEncoder.php
@@ -137,7 +137,7 @@ class IdnaEncoder {
*
* @internal (Testing found regex was the fastest implementation)
*
- * @param string $text
+ * @param string $text Text to examine.
* @return bool Is the text string ASCII-only?
*/
protected static function is_ascii($text) {
@@ -148,7 +148,7 @@ class IdnaEncoder {
* Prepare a text string for use as an IDNA name
*
* @todo Implement this based on RFC 3491 and the newer 5891
- * @param string $text
+ * @param string $text Text to prepare.
* @return string Prepared string
*/
protected static function nameprep($text) {
@@ -160,7 +160,7 @@ class IdnaEncoder {
*
* Based on \WpOrg\Requests\Iri::replace_invalid_with_pct_encoding()
*
- * @param string $input
+ * @param string $input Text to convert.
* @return array Unicode code points
*
* @throws \WpOrg\Requests\Exception Invalid UTF-8 codepoint (`idna.invalidcodepoint`)
@@ -329,10 +329,10 @@ class IdnaEncoder {
}
// output the code point for digit t + ((q - t) mod (base - t))
- $digit = $t + (($q - $t) % (self::BOOTSTRAP_BASE - $t));
+ $digit = (int) ($t + (($q - $t) % (self::BOOTSTRAP_BASE - $t)));
$output .= self::digit_to_char($digit);
// let q = (q - t) div (base - t)
- $q = floor(($q - $t) / (self::BOOTSTRAP_BASE - $t));
+ $q = (int) floor(($q - $t) / (self::BOOTSTRAP_BASE - $t));
} // end
// output the code point for digit q
$output .= self::digit_to_char($q);
@@ -381,7 +381,7 @@ class IdnaEncoder {
* @param int $delta
* @param int $numpoints
* @param bool $firsttime
- * @return int New bias
+ * @return int|float New bias
*
* function adapt(delta,numpoints,firsttime):
*/
diff --git a/vendor/rmccue/requests/src/Iri.php b/vendor/rmccue/requests/src/Iri.php
index 244578d34..c452c7365 100644
--- a/vendor/rmccue/requests/src/Iri.php
+++ b/vendor/rmccue/requests/src/Iri.php
@@ -395,11 +395,11 @@ class Iri {
// preceding "/" (if any) from the output buffer; otherwise,
elseif (strpos($input, '/../') === 0) {
$input = substr($input, 3);
- $output = substr_replace($output, '', strrpos($output, '/'));
+ $output = substr_replace($output, '', (strrpos($output, '/') ?: 0));
}
elseif ($input === '/..') {
$input = '/';
- $output = substr_replace($output, '', strrpos($output, '/'));
+ $output = substr_replace($output, '', (strrpos($output, '/') ?: 0));
}
// D: if the input buffer consists only of "." or "..", then remove
// that from the input buffer; otherwise,
@@ -824,7 +824,8 @@ class Iri {
else {
$iuserinfo = null;
}
- if (($port_start = strpos($remaining, ':', strpos($remaining, ']'))) !== false) {
+
+ if (($port_start = strpos($remaining, ':', (strpos($remaining, ']') ?: 0))) !== false) {
$port = substr($remaining, $port_start + 1);
if ($port === false || $port === '') {
$port = null;
diff --git a/vendor/rmccue/requests/src/Requests.php b/vendor/rmccue/requests/src/Requests.php
index a8d9d7e53..ac6ff55f9 100644
--- a/vendor/rmccue/requests/src/Requests.php
+++ b/vendor/rmccue/requests/src/Requests.php
@@ -148,7 +148,7 @@ class Requests {
*
* @var string
*/
- const VERSION = '2.0.5';
+ const VERSION = '2.0.7';
/**
* Selected transport name
@@ -642,12 +642,14 @@ class Requests {
/**
* Set the default values
*
+ * The $options parameter is updated with the results.
+ *
* @param string $url URL to request
* @param array $headers Extra headers to send with the request
* @param array|null $data Data to send either as a query string for GET/HEAD requests, or in the body for POST requests
* @param string $type HTTP request type
* @param array $options Options for the request
- * @return void $options is updated with the results
+ * @return void
*
* @throws \WpOrg\Requests\Exception When the $url is not an http(s) URL.
*/
@@ -824,9 +826,11 @@ class Requests {
* Internal use only. Converts a raw HTTP response to a \WpOrg\Requests\Response
* while still executing a multiple request.
*
+ * `$response` is either set to a \WpOrg\Requests\Response instance, or a \WpOrg\Requests\Exception object
+ *
* @param string $response Full response text including headers and body (will be overwritten with Response instance)
* @param array $request Request data as passed into {@see \WpOrg\Requests\Requests::request_multiple()}
- * @return void `$response` is either set to a \WpOrg\Requests\Response instance, or a \WpOrg\Requests\Exception object
+ * @return void
*/
public static function parse_multiple(&$response, $request) {
try {
diff --git a/vendor/rmccue/requests/src/Response.php b/vendor/rmccue/requests/src/Response.php
index 8964521a8..86a0438ba 100644
--- a/vendor/rmccue/requests/src/Response.php
+++ b/vendor/rmccue/requests/src/Response.php
@@ -137,16 +137,16 @@ class Response {
*
* @link https://php.net/json-decode
*
- * @param ?bool $associative Optional. When `true`, JSON objects will be returned as associative arrays;
- * When `false`, JSON objects will be returned as objects.
- * When `null`, JSON objects will be returned as associative arrays
- * or objects depending on whether `JSON_OBJECT_AS_ARRAY` is set in the flags.
- * Defaults to `true` (in contrast to the PHP native default of `null`).
- * @param int $depth Optional. Maximum nesting depth of the structure being decoded.
- * Defaults to `512`.
- * @param int $options Optional. Bitmask of JSON_BIGINT_AS_STRING, JSON_INVALID_UTF8_IGNORE,
- * JSON_INVALID_UTF8_SUBSTITUTE, JSON_OBJECT_AS_ARRAY, JSON_THROW_ON_ERROR.
- * Defaults to `0` (no options set).
+ * @param bool|null $associative Optional. When `true`, JSON objects will be returned as associative arrays;
+ * When `false`, JSON objects will be returned as objects.
+ * When `null`, JSON objects will be returned as associative arrays
+ * or objects depending on whether `JSON_OBJECT_AS_ARRAY` is set in the flags.
+ * Defaults to `true` (in contrast to the PHP native default of `null`).
+ * @param int $depth Optional. Maximum nesting depth of the structure being decoded.
+ * Defaults to `512`.
+ * @param int $options Optional. Bitmask of JSON_BIGINT_AS_STRING, JSON_INVALID_UTF8_IGNORE,
+ * JSON_INVALID_UTF8_SUBSTITUTE, JSON_OBJECT_AS_ARRAY, JSON_THROW_ON_ERROR.
+ * Defaults to `0` (no options set).
*
* @return array
*
diff --git a/vendor/rmccue/requests/src/Response/Headers.php b/vendor/rmccue/requests/src/Response/Headers.php
index eb4f68736..b4d0fcf91 100644
--- a/vendor/rmccue/requests/src/Response/Headers.php
+++ b/vendor/rmccue/requests/src/Response/Headers.php
@@ -27,7 +27,7 @@ class Headers extends CaseInsensitiveDictionary {
* Avoid using this where commas may be used unquoted in values, such as
* Set-Cookie headers.
*
- * @param string $offset
+ * @param string $offset Name of the header to retrieve.
* @return string|null Header value
*/
public function offsetGet($offset) {
@@ -69,7 +69,7 @@ class Headers extends CaseInsensitiveDictionary {
/**
* Get all values for a given header
*
- * @param string $offset
+ * @param string $offset Name of the header to retrieve.
* @return array|null Header values
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed argument is not valid as an array key.
@@ -79,7 +79,10 @@ class Headers extends CaseInsensitiveDictionary {
throw InvalidArgument::create(1, '$offset', 'string|int', gettype($offset));
}
- $offset = strtolower($offset);
+ if (is_string($offset)) {
+ $offset = strtolower($offset);
+ }
+
if (!isset($this->data[$offset])) {
return null;
}
diff --git a/vendor/rmccue/requests/src/Transport/Curl.php b/vendor/rmccue/requests/src/Transport/Curl.php
index 8b0a13080..7316987b5 100644
--- a/vendor/rmccue/requests/src/Transport/Curl.php
+++ b/vendor/rmccue/requests/src/Transport/Curl.php
@@ -465,7 +465,7 @@ final class Curl implements Transport {
* @param string $response Response data from the body
* @param array $options Request options
* @return string|false HTTP response data including headers. False if non-blocking.
- * @throws \WpOrg\Requests\Exception
+ * @throws \WpOrg\Requests\Exception If the request resulted in a cURL error.
*/
public function process_response($response, $options) {
if ($options['blocking'] === false) {
@@ -561,7 +561,7 @@ final class Curl implements Transport {
/**
* Format a URL given GET data
*
- * @param string $url
+ * @param string $url Original URL.
* @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query}
* @return string URL with data
*/
diff --git a/vendor/rmccue/requests/src/Transport/Fsockopen.php b/vendor/rmccue/requests/src/Transport/Fsockopen.php
index c3bd4a63d..2b53d0c10 100644
--- a/vendor/rmccue/requests/src/Transport/Fsockopen.php
+++ b/vendor/rmccue/requests/src/Transport/Fsockopen.php
@@ -51,6 +51,11 @@ final class Fsockopen implements Transport {
*/
private $max_bytes = false;
+ /**
+ * Cache for received connection errors.
+ *
+ * @var string
+ */
private $connect_error = '';
/**
@@ -405,7 +410,7 @@ final class Fsockopen implements Transport {
/**
* Format a URL given GET data
*
- * @param array $url_parts
+ * @param array $url_parts Array of URL parts as received from {@link https://www.php.net/parse_url}
* @param array|object $data Data to build query using, see {@link https://www.php.net/http_build_query}
* @return string URL with data
*/
diff --git a/vendor/rmccue/requests/src/Utility/CaseInsensitiveDictionary.php b/vendor/rmccue/requests/src/Utility/CaseInsensitiveDictionary.php
index 3c24cebd4..0e1a914cd 100644
--- a/vendor/rmccue/requests/src/Utility/CaseInsensitiveDictionary.php
+++ b/vendor/rmccue/requests/src/Utility/CaseInsensitiveDictionary.php
@@ -95,7 +95,7 @@ class CaseInsensitiveDictionary implements ArrayAccess, IteratorAggregate {
/**
* Unset the given header
*
- * @param string $offset
+ * @param string $offset The key for the item to unset.
*/
#[ReturnTypeWillChange]
public function offsetUnset($offset) {
diff --git a/vendor/rmccue/requests/src/Utility/FilteredIterator.php b/vendor/rmccue/requests/src/Utility/FilteredIterator.php
index 973a5d25a..4865966c4 100644
--- a/vendor/rmccue/requests/src/Utility/FilteredIterator.php
+++ b/vendor/rmccue/requests/src/Utility/FilteredIterator.php
@@ -28,7 +28,7 @@ final class FilteredIterator extends ArrayIterator {
/**
* Create a new iterator
*
- * @param array $data
+ * @param array $data The array or object to be iterated on.
* @param callable $callback Callback to be called on each value
*
* @throws \WpOrg\Requests\Exception\InvalidArgument When the passed $data argument is not iterable.
@@ -46,14 +46,25 @@ final class FilteredIterator extends ArrayIterator {
}
/**
- * @inheritdoc
+ * Prevent unserialization of the object for security reasons.
*
* @phpcs:disable PHPCompatibility.FunctionNameRestrictions.NewMagicMethods.__unserializeFound
+ *
+ * @param array $data Restored array of data originally serialized.
+ *
+ * @return void
*/
#[ReturnTypeWillChange]
public function __unserialize($data) {}
// phpcs:enable
+ /**
+ * Perform reinitialization tasks.
+ *
+ * Prevents a callback from being injected during unserialization of an object.
+ *
+ * @return void
+ */
public function __wakeup() {
unset($this->callback);
}
@@ -75,7 +86,11 @@ final class FilteredIterator extends ArrayIterator {
}
/**
- * @inheritdoc
+ * Prevent creating a PHP value from a stored representation of the object for security reasons.
+ *
+ * @param string $data The serialized string.
+ *
+ * @return void
*/
#[ReturnTypeWillChange]
public function unserialize($data) {}
diff --git a/vendor/services.php b/vendor/services.php
index 832d9b17b..4181a3b33 100755
--- a/vendor/services.php
+++ b/vendor/services.php
@@ -1,7 +1,9 @@
'think\\app\\Service',
- 1 => 'think\\trace\\Service',
+ 1 => 'think\\queue\\Service',
+ 2 => 'think\\trace\\Service',
+ 3 => 'yunwuxin\\cron\\Service',
);
\ No newline at end of file
diff --git a/vendor/symfony/deprecation-contracts/.gitignore b/vendor/symfony/deprecation-contracts/.gitignore
deleted file mode 100644
index c49a5d8df..000000000
--- a/vendor/symfony/deprecation-contracts/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-vendor/
-composer.lock
-phpunit.xml
diff --git a/vendor/symfony/deprecation-contracts/LICENSE b/vendor/symfony/deprecation-contracts/LICENSE
index 406242ff2..0ed3a2465 100644
--- a/vendor/symfony/deprecation-contracts/LICENSE
+++ b/vendor/symfony/deprecation-contracts/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2020-2022 Fabien Potencier
+Copyright (c) 2020-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/symfony/deprecation-contracts/README.md b/vendor/symfony/deprecation-contracts/README.md
index 4957933a6..9814864c0 100644
--- a/vendor/symfony/deprecation-contracts/README.md
+++ b/vendor/symfony/deprecation-contracts/README.md
@@ -22,5 +22,5 @@ trigger_deprecation('symfony/blockchain', '8.9', 'Using "%s" is deprecated, use
This will generate the following message:
`Since symfony/blockchain 8.9: Using "bitcoin" is deprecated, use "fabcoin" instead.`
-While not necessarily recommended, the deprecation notices can be completely ignored by declaring an empty
+While not recommended, the deprecation notices can be completely ignored by declaring an empty
`function trigger_deprecation() {}` in your application.
diff --git a/vendor/symfony/deprecation-contracts/composer.json b/vendor/symfony/deprecation-contracts/composer.json
index 1c1b4ba0e..c6d02d874 100644
--- a/vendor/symfony/deprecation-contracts/composer.json
+++ b/vendor/symfony/deprecation-contracts/composer.json
@@ -15,7 +15,7 @@
}
],
"require": {
- "php": ">=8.0.2"
+ "php": ">=8.1"
},
"autoload": {
"files": [
@@ -25,7 +25,7 @@
"minimum-stability": "dev",
"extra": {
"branch-alias": {
- "dev-main": "3.0-dev"
+ "dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
diff --git a/vendor/symfony/http-client-contracts/.gitignore b/vendor/symfony/http-client-contracts/.gitignore
deleted file mode 100644
index c49a5d8df..000000000
--- a/vendor/symfony/http-client-contracts/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-vendor/
-composer.lock
-phpunit.xml
diff --git a/vendor/symfony/http-client-contracts/HttpClientInterface.php b/vendor/symfony/http-client-contracts/HttpClientInterface.php
index bc8d196f4..59636258f 100644
--- a/vendor/symfony/http-client-contracts/HttpClientInterface.php
+++ b/vendor/symfony/http-client-contracts/HttpClientInterface.php
@@ -52,8 +52,8 @@ interface HttpClientInterface
'resolve' => [], // string[] - a map of host to IP address that SHOULD replace DNS resolution
'proxy' => null, // string - by default, the proxy-related env vars handled by curl SHOULD be honored
'no_proxy' => null, // string - a comma separated list of hosts that do not require a proxy to be reached
- 'timeout' => null, // float - the idle timeout - defaults to ini_get('default_socket_timeout')
- 'max_duration' => 0, // float - the maximum execution time for the request+response as a whole;
+ 'timeout' => null, // float - the idle timeout (in seconds) - defaults to ini_get('default_socket_timeout')
+ 'max_duration' => 0, // float - the maximum execution time (in seconds) for the request+response as a whole;
// a value lower than or equal to 0 means it is unlimited
'bindto' => '0', // string - the interface or the local socket to bind to
'verify_peer' => true, // see https://php.net/context.ssl for the following options
@@ -66,6 +66,7 @@ interface HttpClientInterface
'ciphers' => null,
'peer_fingerprint' => null,
'capture_peer_cert_chain' => false,
+ 'crypto_method' => \STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT, // STREAM_CRYPTO_METHOD_TLSv*_CLIENT - minimum TLS version
'extra' => [], // array - additional options that can be ignored if unsupported, unlike regular options
];
diff --git a/vendor/symfony/http-client-contracts/LICENSE b/vendor/symfony/http-client-contracts/LICENSE
index 74cdc2dbf..7536caeae 100644
--- a/vendor/symfony/http-client-contracts/LICENSE
+++ b/vendor/symfony/http-client-contracts/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2018-2022 Fabien Potencier
+Copyright (c) 2018-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/symfony/http-client-contracts/README.md b/vendor/symfony/http-client-contracts/README.md
index 03b3a69b7..24d72f566 100644
--- a/vendor/symfony/http-client-contracts/README.md
+++ b/vendor/symfony/http-client-contracts/README.md
@@ -3,7 +3,7 @@ Symfony HttpClient Contracts
A set of abstractions extracted out of the Symfony components.
-Can be used to build on semantics that the Symfony components proved useful - and
+Can be used to build on semantics that the Symfony components proved useful and
that already have battle tested implementations.
See https://github.com/symfony/contracts/blob/main/README.md for more information.
diff --git a/vendor/symfony/http-client-contracts/Test/Fixtures/web/index.php b/vendor/symfony/http-client-contracts/Test/Fixtures/web/index.php
index 30a704975..8e28bf532 100644
--- a/vendor/symfony/http-client-contracts/Test/Fixtures/web/index.php
+++ b/vendor/symfony/http-client-contracts/Test/Fixtures/web/index.php
@@ -15,7 +15,7 @@ if (!$_POST) {
foreach ($_SERVER as $k => $v) {
switch ($k) {
default:
- if (0 !== strpos($k, 'HTTP_')) {
+ if (!str_starts_with($k, 'HTTP_')) {
continue 2;
}
// no break
@@ -86,6 +86,12 @@ switch ($vars['REQUEST_URI']) {
header('Location: //?foo=bar', true, 301);
break;
+ case '/301/proxy':
+ case 'http://localhost:8057/301/proxy':
+ case 'http://127.0.0.1:8057/301/proxy':
+ header('Location: http://localhost:8057/', true, 301);
+ break;
+
case '/302':
if (!isset($vars['HTTP_AUTHORIZATION'])) {
header('Location: http://localhost:8057/', true, 302);
diff --git a/vendor/symfony/http-client-contracts/Test/HttpClientTestCase.php b/vendor/symfony/http-client-contracts/Test/HttpClientTestCase.php
index 8cffeac71..9cfd33fc1 100644
--- a/vendor/symfony/http-client-contracts/Test/HttpClientTestCase.php
+++ b/vendor/symfony/http-client-contracts/Test/HttpClientTestCase.php
@@ -226,13 +226,13 @@ abstract class HttpClientTestCase extends TestCase
try {
$response->getHeaders();
$this->fail(ClientExceptionInterface::class.' expected');
- } catch (ClientExceptionInterface $e) {
+ } catch (ClientExceptionInterface) {
}
try {
$response->getContent();
$this->fail(ClientExceptionInterface::class.' expected');
- } catch (ClientExceptionInterface $e) {
+ } catch (ClientExceptionInterface) {
}
$this->assertSame(404, $response->getStatusCode());
@@ -246,7 +246,7 @@ abstract class HttpClientTestCase extends TestCase
$this->assertTrue($chunk->isFirst());
}
$this->fail(ClientExceptionInterface::class.' expected');
- } catch (ClientExceptionInterface $e) {
+ } catch (ClientExceptionInterface) {
}
}
@@ -266,14 +266,14 @@ abstract class HttpClientTestCase extends TestCase
try {
$response->getStatusCode();
$this->fail(TransportExceptionInterface::class.' expected');
- } catch (TransportExceptionInterface $e) {
+ } catch (TransportExceptionInterface) {
$this->addToAssertionCount(1);
}
try {
$response->getStatusCode();
$this->fail(TransportExceptionInterface::class.' still expected');
- } catch (TransportExceptionInterface $e) {
+ } catch (TransportExceptionInterface) {
$this->addToAssertionCount(1);
}
@@ -283,7 +283,7 @@ abstract class HttpClientTestCase extends TestCase
foreach ($client->stream($response) as $r => $chunk) {
}
$this->fail(TransportExceptionInterface::class.' expected');
- } catch (TransportExceptionInterface $e) {
+ } catch (TransportExceptionInterface) {
$this->addToAssertionCount(1);
}
@@ -437,7 +437,7 @@ abstract class HttpClientTestCase extends TestCase
try {
$response->getHeaders();
$this->fail(RedirectionExceptionInterface::class.' expected');
- } catch (RedirectionExceptionInterface $e) {
+ } catch (RedirectionExceptionInterface) {
}
$this->assertSame(302, $response->getStatusCode());
@@ -859,7 +859,7 @@ abstract class HttpClientTestCase extends TestCase
try {
$response->getContent();
$this->fail(TransportExceptionInterface::class.' expected');
- } catch (TransportExceptionInterface $e) {
+ } catch (TransportExceptionInterface) {
}
}
$responses = [];
@@ -892,7 +892,7 @@ abstract class HttpClientTestCase extends TestCase
try {
unset($response);
$this->fail(TransportExceptionInterface::class.' expected');
- } catch (TransportExceptionInterface $e) {
+ } catch (TransportExceptionInterface) {
}
}
@@ -969,6 +969,14 @@ abstract class HttpClientTestCase extends TestCase
} finally {
unset($_SERVER['http_proxy']);
}
+
+ $response = $client->request('GET', 'http://localhost:8057/301/proxy', [
+ 'proxy' => 'http://localhost:8057',
+ ]);
+
+ $body = $response->toArray();
+ $this->assertSame('localhost:8057', $body['HTTP_HOST']);
+ $this->assertMatchesRegularExpression('#^http://(localhost|127\.0\.0\.1):8057/$#', $body['REQUEST_URI']);
}
public function testNoProxy()
@@ -1110,7 +1118,7 @@ abstract class HttpClientTestCase extends TestCase
try {
$response->getContent();
- } catch (TransportExceptionInterface $e) {
+ } catch (TransportExceptionInterface) {
$this->addToAssertionCount(1);
}
@@ -1125,7 +1133,7 @@ abstract class HttpClientTestCase extends TestCase
$client2 = $client->withOptions(['base_uri' => 'http://localhost:8057/']);
$this->assertNotSame($client, $client2);
- $this->assertSame(\get_class($client), \get_class($client2));
+ $this->assertSame($client::class, $client2::class);
$response = $client2->request('GET', '/');
$this->assertSame(200, $response->getStatusCode());
diff --git a/vendor/symfony/http-client-contracts/composer.json b/vendor/symfony/http-client-contracts/composer.json
index a26300f4a..084d49072 100644
--- a/vendor/symfony/http-client-contracts/composer.json
+++ b/vendor/symfony/http-client-contracts/composer.json
@@ -16,18 +16,18 @@
}
],
"require": {
- "php": ">=8.0.2"
- },
- "suggest": {
- "symfony/http-client-implementation": ""
+ "php": ">=8.1"
},
"autoload": {
- "psr-4": { "Symfony\\Contracts\\HttpClient\\": "" }
+ "psr-4": { "Symfony\\Contracts\\HttpClient\\": "" },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
- "dev-main": "3.0-dev"
+ "dev-main": "3.4-dev"
},
"thanks": {
"name": "symfony/contracts",
diff --git a/vendor/symfony/http-client/AmpHttpClient.php b/vendor/symfony/http-client/AmpHttpClient.php
index cddd3cd3a..a8d731150 100644
--- a/vendor/symfony/http-client/AmpHttpClient.php
+++ b/vendor/symfony/http-client/AmpHttpClient.php
@@ -49,21 +49,20 @@ final class AmpHttpClient implements HttpClientInterface, LoggerAwareInterface,
private array $defaultOptions = self::OPTIONS_DEFAULTS;
private static array $emptyDefaults = self::OPTIONS_DEFAULTS;
-
- private $multi;
+ private AmpClientState $multi;
/**
- * @param array $defaultOptions Default requests' options
- * @param callable $clientConfigurator A callable that builds a {@see DelegateHttpClient} from a {@see PooledHttpClient};
- * passing null builds an {@see InterceptedHttpClient} with 2 retries on failures
- * @param int $maxHostConnections The maximum number of connections to a single host
- * @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue
+ * @param array $defaultOptions Default requests' options
+ * @param callable|null $clientConfigurator A callable that builds a {@see DelegateHttpClient} from a {@see PooledHttpClient};
+ * passing null builds an {@see InterceptedHttpClient} with 2 retries on failures
+ * @param int $maxHostConnections The maximum number of connections to a single host
+ * @param int $maxPendingPushes The maximum number of pushed responses to accept in the queue
*
* @see HttpClientInterface::OPTIONS_DEFAULTS for available options
*/
public function __construct(array $defaultOptions = [], callable $clientConfigurator = null, int $maxHostConnections = 6, int $maxPendingPushes = 50)
{
- $this->defaultOptions['buffer'] = $this->defaultOptions['buffer'] ?? \Closure::fromCallable([__CLASS__, 'shouldBuffer']);
+ $this->defaultOptions['buffer'] ??= self::shouldBuffer(...);
if ($defaultOptions) {
[, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions);
@@ -74,8 +73,6 @@ final class AmpHttpClient implements HttpClientInterface, LoggerAwareInterface,
/**
* @see HttpClientInterface::OPTIONS_DEFAULTS for available options
- *
- * {@inheritdoc}
*/
public function request(string $method, string $url, array $options = []): ResponseInterface
{
@@ -88,10 +85,10 @@ final class AmpHttpClient implements HttpClientInterface, LoggerAwareInterface,
}
if ($options['bindto']) {
- if (0 === strpos($options['bindto'], 'if!')) {
+ if (str_starts_with($options['bindto'], 'if!')) {
throw new TransportException(__CLASS__.' cannot bind to network interfaces, use e.g. CurlHttpClient instead.');
}
- if (0 === strpos($options['bindto'], 'host!')) {
+ if (str_starts_with($options['bindto'], 'host!')) {
$options['bindto'] = substr($options['bindto'], 5);
}
}
@@ -119,11 +116,11 @@ final class AmpHttpClient implements HttpClientInterface, LoggerAwareInterface,
$request = new Request(implode('', $url), $method);
if ($options['http_version']) {
- switch ((float) $options['http_version']) {
- case 1.0: $request->setProtocolVersions(['1.0']); break;
- case 1.1: $request->setProtocolVersions(['1.1', '1.0']); break;
- default: $request->setProtocolVersions(['2', '1.1', '1.0']); break;
- }
+ $request->setProtocolVersions(match ((float) $options['http_version']) {
+ 1.0 => ['1.0'],
+ 1.1 => $request->setProtocolVersions(['1.1', '1.0']),
+ default => ['2', '1.1', '1.0'],
+ });
}
foreach ($options['headers'] as $v) {
@@ -147,9 +144,6 @@ final class AmpHttpClient implements HttpClientInterface, LoggerAwareInterface,
return new AmpResponse($this->multi, $request, $options, $this->logger);
}
- /**
- * {@inheritdoc}
- */
public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface
{
if ($responses instanceof AmpResponse) {
@@ -167,9 +161,7 @@ final class AmpHttpClient implements HttpClientInterface, LoggerAwareInterface,
foreach ($pushedResponses as [$pushedUrl, $pushDeferred]) {
$pushDeferred->fail(new CancelledException());
- if ($this->logger) {
- $this->logger->debug(sprintf('Unused pushed response: "%s"', $pushedUrl));
- }
+ $this->logger?->debug(sprintf('Unused pushed response: "%s"', $pushedUrl));
}
}
diff --git a/vendor/symfony/http-client/AsyncDecoratorTrait.php b/vendor/symfony/http-client/AsyncDecoratorTrait.php
index 1a2aa4fb0..912b8250e 100644
--- a/vendor/symfony/http-client/AsyncDecoratorTrait.php
+++ b/vendor/symfony/http-client/AsyncDecoratorTrait.php
@@ -26,15 +26,10 @@ trait AsyncDecoratorTrait
use DecoratorTrait;
/**
- * {@inheritdoc}
- *
* @return AsyncResponse
*/
abstract public function request(string $method, string $url, array $options = []): ResponseInterface;
- /**
- * {@inheritdoc}
- */
public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface
{
if ($responses instanceof AsyncResponse) {
diff --git a/vendor/symfony/http-client/CHANGELOG.md b/vendor/symfony/http-client/CHANGELOG.md
index 7c2fc2273..d6d50d2d5 100644
--- a/vendor/symfony/http-client/CHANGELOG.md
+++ b/vendor/symfony/http-client/CHANGELOG.md
@@ -1,6 +1,19 @@
CHANGELOG
=========
+6.2
+---
+
+ * Make `HttplugClient` implement `Psr\Http\Message\RequestFactoryInterface`, `StreamFactoryInterface` and `UriFactoryInterface`
+ * Deprecate implementing `Http\Message\RequestFactory`, `StreamFactory` and `UriFactory` on `HttplugClient`
+ * Add `withOptions()` to `HttplugClient` and `Psr18Client`
+
+6.1
+---
+
+ * Allow yielding `Exception` from MockResponse's `$body` to mock transport errors
+ * Remove credentials from requests redirected to same host but different port
+
5.4
---
diff --git a/vendor/symfony/http-client/CachingHttpClient.php b/vendor/symfony/http-client/CachingHttpClient.php
index 0271fda64..5e3fbf507 100644
--- a/vendor/symfony/http-client/CachingHttpClient.php
+++ b/vendor/symfony/http-client/CachingHttpClient.php
@@ -35,8 +35,8 @@ class CachingHttpClient implements HttpClientInterface, ResetInterface
{
use HttpClientTrait;
- private $client;
- private $cache;
+ private HttpClientInterface $client;
+ private HttpCache $cache;
private array $defaultOptions = self::OPTIONS_DEFAULTS;
public function __construct(HttpClientInterface $client, StoreInterface $store, array $defaultOptions = [])
@@ -64,9 +64,6 @@ class CachingHttpClient implements HttpClientInterface, ResetInterface
}
}
- /**
- * {@inheritdoc}
- */
public function request(string $method, string $url, array $options = []): ResponseInterface
{
[$url, $options] = $this->prepareRequest($method, $url, $options, $this->defaultOptions, true);
@@ -107,9 +104,6 @@ class CachingHttpClient implements HttpClientInterface, ResetInterface
return MockResponse::fromRequest($method, $url, $options, $response);
}
- /**
- * {@inheritdoc}
- */
public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface
{
if ($responses instanceof ResponseInterface) {
diff --git a/vendor/symfony/http-client/Chunk/DataChunk.php b/vendor/symfony/http-client/Chunk/DataChunk.php
index e2365cd0b..3507a0cd0 100644
--- a/vendor/symfony/http-client/Chunk/DataChunk.php
+++ b/vendor/symfony/http-client/Chunk/DataChunk.php
@@ -29,57 +29,36 @@ class DataChunk implements ChunkInterface
$this->content = $content;
}
- /**
- * {@inheritdoc}
- */
public function isTimeout(): bool
{
return false;
}
- /**
- * {@inheritdoc}
- */
public function isFirst(): bool
{
return false;
}
- /**
- * {@inheritdoc}
- */
public function isLast(): bool
{
return false;
}
- /**
- * {@inheritdoc}
- */
public function getInformationalStatus(): ?array
{
return null;
}
- /**
- * {@inheritdoc}
- */
public function getContent(): string
{
return $this->content;
}
- /**
- * {@inheritdoc}
- */
public function getOffset(): int
{
return $this->offset;
}
- /**
- * {@inheritdoc}
- */
public function getError(): ?string
{
return null;
diff --git a/vendor/symfony/http-client/Chunk/ErrorChunk.php b/vendor/symfony/http-client/Chunk/ErrorChunk.php
index a989c39a7..c797fc343 100644
--- a/vendor/symfony/http-client/Chunk/ErrorChunk.php
+++ b/vendor/symfony/http-client/Chunk/ErrorChunk.php
@@ -39,9 +39,6 @@ class ErrorChunk implements ChunkInterface
}
}
- /**
- * {@inheritdoc}
- */
public function isTimeout(): bool
{
$this->didThrow = true;
@@ -53,53 +50,35 @@ class ErrorChunk implements ChunkInterface
return true;
}
- /**
- * {@inheritdoc}
- */
public function isFirst(): bool
{
$this->didThrow = true;
throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage);
}
- /**
- * {@inheritdoc}
- */
public function isLast(): bool
{
$this->didThrow = true;
throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage);
}
- /**
- * {@inheritdoc}
- */
public function getInformationalStatus(): ?array
{
$this->didThrow = true;
throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage);
}
- /**
- * {@inheritdoc}
- */
public function getContent(): string
{
$this->didThrow = true;
throw null !== $this->error ? new TransportException($this->errorMessage, 0, $this->error) : new TimeoutException($this->errorMessage);
}
- /**
- * {@inheritdoc}
- */
public function getOffset(): int
{
return $this->offset;
}
- /**
- * {@inheritdoc}
- */
public function getError(): ?string
{
return $this->errorMessage;
diff --git a/vendor/symfony/http-client/Chunk/FirstChunk.php b/vendor/symfony/http-client/Chunk/FirstChunk.php
index d891ca856..f6ba8b8ac 100644
--- a/vendor/symfony/http-client/Chunk/FirstChunk.php
+++ b/vendor/symfony/http-client/Chunk/FirstChunk.php
@@ -18,9 +18,6 @@ namespace Symfony\Component\HttpClient\Chunk;
*/
class FirstChunk extends DataChunk
{
- /**
- * {@inheritdoc}
- */
public function isFirst(): bool
{
return true;
diff --git a/vendor/symfony/http-client/Chunk/InformationalChunk.php b/vendor/symfony/http-client/Chunk/InformationalChunk.php
index fbc3ccbd1..31ed1aa24 100644
--- a/vendor/symfony/http-client/Chunk/InformationalChunk.php
+++ b/vendor/symfony/http-client/Chunk/InformationalChunk.php
@@ -25,9 +25,6 @@ class InformationalChunk extends DataChunk
$this->status = [$statusCode, $headers];
}
- /**
- * {@inheritdoc}
- */
public function getInformationalStatus(): ?array
{
return $this->status;
diff --git a/vendor/symfony/http-client/Chunk/LastChunk.php b/vendor/symfony/http-client/Chunk/LastChunk.php
index 84095d392..a64d12379 100644
--- a/vendor/symfony/http-client/Chunk/LastChunk.php
+++ b/vendor/symfony/http-client/Chunk/LastChunk.php
@@ -18,9 +18,6 @@ namespace Symfony\Component\HttpClient\Chunk;
*/
class LastChunk extends DataChunk
{
- /**
- * {@inheritdoc}
- */
public function isLast(): bool
{
return true;
diff --git a/vendor/symfony/http-client/Chunk/ServerSentEvent.php b/vendor/symfony/http-client/Chunk/ServerSentEvent.php
index 1f55ba4d7..296918e6a 100644
--- a/vendor/symfony/http-client/Chunk/ServerSentEvent.php
+++ b/vendor/symfony/http-client/Chunk/ServerSentEvent.php
@@ -29,7 +29,7 @@ final class ServerSentEvent extends DataChunk implements ChunkInterface
parent::__construct(-1, $content);
// remove BOM
- if (0 === strpos($content, "\xEF\xBB\xBF")) {
+ if (str_starts_with($content, "\xEF\xBB\xBF")) {
$content = substr($content, 3);
}
diff --git a/vendor/symfony/http-client/CurlHttpClient.php b/vendor/symfony/http-client/CurlHttpClient.php
index 4bc8f9151..3143599e9 100644
--- a/vendor/symfony/http-client/CurlHttpClient.php
+++ b/vendor/symfony/http-client/CurlHttpClient.php
@@ -50,7 +50,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
/**
* An internal object to share state between the client and its responses.
*/
- private $multi;
+ private CurlClientState $multi;
/**
* @param array $defaultOptions Default request's options
@@ -65,7 +65,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\CurlHttpClient" as the "curl" extension is not installed.');
}
- $this->defaultOptions['buffer'] = $this->defaultOptions['buffer'] ?? \Closure::fromCallable([__CLASS__, 'shouldBuffer']);
+ $this->defaultOptions['buffer'] ??= self::shouldBuffer(...);
if ($defaultOptions) {
[, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions);
@@ -81,8 +81,6 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
/**
* @see HttpClientInterface::OPTIONS_DEFAULTS for available options
- *
- * {@inheritdoc}
*/
public function request(string $method, string $url, array $options = []): ResponseInterface
{
@@ -90,10 +88,8 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
$scheme = $url['scheme'];
$authority = $url['authority'];
$host = parse_url($authority, \PHP_URL_HOST);
- $proxy = $options['proxy']
- ?? ('https:' === $url['scheme'] ? $_SERVER['https_proxy'] ?? $_SERVER['HTTPS_PROXY'] ?? null : null)
- // Ignore HTTP_PROXY except on the CLI to work around httpoxy set of vulnerabilities
- ?? $_SERVER['http_proxy'] ?? (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? $_SERVER['HTTP_PROXY'] ?? null : null) ?? $_SERVER['all_proxy'] ?? $_SERVER['ALL_PROXY'] ?? null;
+ $port = parse_url($authority, \PHP_URL_PORT) ?: ('http:' === $scheme ? 80 : 443);
+ $proxy = self::getProxyUrl($options['proxy'], $url);
$url = implode('', $url);
if (!isset($options['normalized_headers']['user-agent'])) {
@@ -167,7 +163,6 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
// First reset any old DNS cache entries then add the new ones
$resolve = $this->multi->dnsCache->evictions;
$this->multi->dnsCache->evictions = [];
- $port = parse_url($authority, \PHP_URL_PORT) ?: ('http:' === $scheme ? 80 : 443);
if ($resolve && 0x072A00 > CurlClientState::$curlVersion['version_number']) {
// DNS cache removals require curl 7.42 or higher
@@ -283,21 +278,21 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
unset($this->multi->pushedResponses[$url]);
if (self::acceptPushForRequest($method, $options, $pushedResponse)) {
- $this->logger && $this->logger->debug(sprintf('Accepting pushed response: "%s %s"', $method, $url));
+ $this->logger?->debug(sprintf('Accepting pushed response: "%s %s"', $method, $url));
// Reinitialize the pushed response with request's options
$ch = $pushedResponse->handle;
$pushedResponse = $pushedResponse->response;
$pushedResponse->__construct($this->multi, $url, $options, $this->logger);
} else {
- $this->logger && $this->logger->debug(sprintf('Rejecting pushed response: "%s"', $url));
+ $this->logger?->debug(sprintf('Rejecting pushed response: "%s"', $url));
$pushedResponse = null;
}
}
if (!$pushedResponse) {
$ch = curl_init();
- $this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, $url));
+ $this->logger?->info(sprintf('Request: "%s %s"', $method, $url));
$curlopts += [\CURLOPT_SHARE => $this->multi->share];
}
@@ -308,12 +303,9 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
}
}
- return $pushedResponse ?? new CurlResponse($this->multi, $ch, $options, $this->logger, $method, self::createRedirectResolver($options, $host), CurlClientState::$curlVersion['version_number']);
+ return $pushedResponse ?? new CurlResponse($this->multi, $ch, $options, $this->logger, $method, self::createRedirectResolver($options, $host, $port), CurlClientState::$curlVersion['version_number'], $url);
}
- /**
- * {@inheritdoc}
- */
public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface
{
if ($responses instanceof CurlResponse) {
@@ -388,11 +380,12 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
*
* Work around CVE-2018-1000007: Authorization and Cookie headers should not follow redirects - fixed in Curl 7.64
*/
- private static function createRedirectResolver(array $options, string $host): \Closure
+ private static function createRedirectResolver(array $options, string $host, int $port): \Closure
{
$redirectHeaders = [];
if (0 < $options['max_redirects']) {
$redirectHeaders['host'] = $host;
+ $redirectHeaders['port'] = $port;
$redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) {
return 0 !== stripos($h, 'Host:');
});
@@ -404,10 +397,10 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
}
}
- return static function ($ch, string $location, bool $noContent) use (&$redirectHeaders) {
+ return static function ($ch, string $location, bool $noContent) use (&$redirectHeaders, $options) {
try {
$location = self::parseUrl($location);
- } catch (InvalidArgumentException $e) {
+ } catch (InvalidArgumentException) {
return null;
}
@@ -420,7 +413,8 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
}
if ($redirectHeaders && $host = parse_url('http:'.$location['authority'], \PHP_URL_HOST)) {
- $requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
+ $port = parse_url('http:'.$location['authority'], \PHP_URL_PORT) ?: ('http:' === $location['scheme'] ? 80 : 443);
+ $requestHeaders = $redirectHeaders['host'] === $host && $redirectHeaders['port'] === $port ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
curl_setopt($ch, \CURLOPT_HTTPHEADER, $requestHeaders);
} elseif ($noContent && $redirectHeaders) {
curl_setopt($ch, \CURLOPT_HTTPHEADER, $redirectHeaders['with_auth']);
@@ -429,11 +423,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
$url = self::parseUrl(curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL));
$url = self::resolveUrl($location, $url);
- curl_setopt($ch, \CURLOPT_PROXY, $options['proxy']
- ?? ('https:' === $url['scheme'] ? $_SERVER['https_proxy'] ?? $_SERVER['HTTPS_PROXY'] ?? null : null)
- // Ignore HTTP_PROXY except on the CLI to work around httpoxy set of vulnerabilities
- ?? $_SERVER['http_proxy'] ?? (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? $_SERVER['HTTP_PROXY'] ?? null : null) ?? $_SERVER['all_proxy'] ?? $_SERVER['ALL_PROXY'] ?? null
- );
+ curl_setopt($ch, \CURLOPT_PROXY, self::getProxyUrl($options['proxy'], $url));
return implode('', $url);
};
@@ -469,6 +459,7 @@ final class CurlHttpClient implements HttpClientInterface, LoggerAwareInterface,
\CURLOPT_TIMEOUT_MS => 'max_duration',
\CURLOPT_TIMEOUT => 'max_duration',
\CURLOPT_MAXREDIRS => 'max_redirects',
+ \CURLOPT_POSTREDIR => 'max_redirects',
\CURLOPT_PROXY => 'proxy',
\CURLOPT_NOPROXY => 'no_proxy',
\CURLOPT_SSL_VERIFYPEER => 'verify_peer',
diff --git a/vendor/symfony/http-client/DataCollector/HttpClientDataCollector.php b/vendor/symfony/http-client/DataCollector/HttpClientDataCollector.php
index dda03d5a3..524251729 100644
--- a/vendor/symfony/http-client/DataCollector/HttpClientDataCollector.php
+++ b/vendor/symfony/http-client/DataCollector/HttpClientDataCollector.php
@@ -11,6 +11,7 @@
namespace Symfony\Component\HttpClient\DataCollector;
+use Symfony\Component\HttpClient\HttpClientTrait;
use Symfony\Component\HttpClient\TraceableHttpClient;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
@@ -23,6 +24,8 @@ use Symfony\Component\VarDumper\Caster\ImgStub;
*/
final class HttpClientDataCollector extends DataCollector implements LateDataCollectorInterface
{
+ use HttpClientTrait;
+
/**
* @var TraceableHttpClient[]
*/
@@ -33,9 +36,6 @@ final class HttpClientDataCollector extends DataCollector implements LateDataCol
$this->clients[$name] = $client;
}
- /**
- * {@inheritdoc}
- */
public function collect(Request $request, Response $response, \Throwable $exception = null)
{
$this->lateCollect();
@@ -43,8 +43,8 @@ final class HttpClientDataCollector extends DataCollector implements LateDataCol
public function lateCollect()
{
- $this->data['request_count'] = 0;
- $this->data['error_count'] = 0;
+ $this->data['request_count'] = $this->data['request_count'] ?? 0;
+ $this->data['error_count'] = $this->data['error_count'] ?? 0;
$this->data += ['clients' => []];
foreach ($this->clients as $name => $client) {
@@ -59,7 +59,8 @@ final class HttpClientDataCollector extends DataCollector implements LateDataCol
$this->data['clients'][$name]['traces'] = array_merge($this->data['clients'][$name]['traces'], $traces);
$this->data['request_count'] += \count($traces);
- $this->data['error_count'] += $this->data['clients'][$name]['error_count'] += $errorCount;
+ $this->data['error_count'] += $errorCount;
+ $this->data['clients'][$name]['error_count'] += $errorCount;
$client->reset();
}
@@ -80,9 +81,6 @@ final class HttpClientDataCollector extends DataCollector implements LateDataCol
return $this->data['error_count'] ?? 0;
}
- /**
- * {@inheritdoc}
- */
public function getName(): string
{
return 'http_client';
@@ -145,7 +143,7 @@ final class HttpClientDataCollector extends DataCollector implements LateDataCol
}
}
- if (0 === strpos($contentType, 'image/') && class_exists(ImgStub::class)) {
+ if (str_starts_with($contentType, 'image/') && class_exists(ImgStub::class)) {
$content = new ImgStub($content, $contentType, '');
} else {
$content = [$content];
@@ -168,8 +166,97 @@ final class HttpClientDataCollector extends DataCollector implements LateDataCol
unset($traces[$i]['info']); // break PHP reference used by TraceableHttpClient
$traces[$i]['info'] = $this->cloneVar($info);
$traces[$i]['options'] = $this->cloneVar($trace['options']);
+ $traces[$i]['curlCommand'] = $this->getCurlCommand($trace);
}
return [$errorCount, $traces];
}
+
+ private function getCurlCommand(array $trace): ?string
+ {
+ if (!isset($trace['info']['debug'])) {
+ return null;
+ }
+
+ $url = $trace['info']['original_url'] ?? $trace['info']['url'] ?? $trace['url'];
+ $command = ['curl', '--compressed'];
+
+ if (isset($trace['options']['resolve'])) {
+ $port = parse_url($url, \PHP_URL_PORT) ?: (str_starts_with('http:', $url) ? 80 : 443);
+ foreach ($trace['options']['resolve'] as $host => $ip) {
+ if (null !== $ip) {
+ $command[] = '--resolve '.escapeshellarg("$host:$port:$ip");
+ }
+ }
+ }
+
+ $dataArg = [];
+
+ if ($json = $trace['options']['json'] ?? null) {
+ if (!$this->argMaxLengthIsSafe($payload = self::jsonEncode($json))) {
+ return null;
+ }
+ $dataArg[] = '--data '.escapeshellarg($payload);
+ } elseif ($body = $trace['options']['body'] ?? null) {
+ if (\is_string($body)) {
+ if (!$this->argMaxLengthIsSafe($body)) {
+ return null;
+ }
+ try {
+ $dataArg[] = '--data '.escapeshellarg($body);
+ } catch (\ValueError) {
+ return null;
+ }
+ } elseif (\is_array($body)) {
+ $body = explode('&', self::normalizeBody($body));
+ foreach ($body as $value) {
+ if (!$this->argMaxLengthIsSafe($payload = urldecode($value))) {
+ return null;
+ }
+ $dataArg[] = '--data '.escapeshellarg($payload);
+ }
+ } else {
+ return null;
+ }
+ }
+
+ $dataArg = empty($dataArg) ? null : implode(' ', $dataArg);
+
+ foreach (explode("\n", $trace['info']['debug']) as $line) {
+ $line = substr($line, 0, -1);
+
+ if (str_starts_with('< ', $line)) {
+ // End of the request, beginning of the response. Stop parsing.
+ break;
+ }
+
+ if ('' === $line || preg_match('/^[*<]|(Host: )/', $line)) {
+ continue;
+ }
+
+ if (preg_match('/^> ([A-Z]+)/', $line, $match)) {
+ $command[] = sprintf('--request %s', $match[1]);
+ $command[] = sprintf('--url %s', escapeshellarg($url));
+ continue;
+ }
+
+ $command[] = '--header '.escapeshellarg($line);
+ }
+
+ if (null !== $dataArg) {
+ $command[] = $dataArg;
+ }
+
+ return implode(" \\\n ", $command);
+ }
+
+ /**
+ * Let's be defensive : we authorize only size of 8kio on Windows for escapeshellarg() argument to avoid a fatal error.
+ *
+ * @see https://github.com/php/php-src/blob/9458f5f2c8a8e3d6c65cc181747a5a75654b7c6e/ext/standard/exec.c#L397
+ */
+ private function argMaxLengthIsSafe(string $payload): bool
+ {
+ return \strlen($payload) < ('\\' === \DIRECTORY_SEPARATOR ? 8100 : 256000);
+ }
}
diff --git a/vendor/symfony/http-client/DecoratorTrait.php b/vendor/symfony/http-client/DecoratorTrait.php
index a08b725c9..f38664b43 100644
--- a/vendor/symfony/http-client/DecoratorTrait.php
+++ b/vendor/symfony/http-client/DecoratorTrait.php
@@ -23,32 +23,23 @@ use Symfony\Contracts\Service\ResetInterface;
*/
trait DecoratorTrait
{
- private $client;
+ private HttpClientInterface $client;
public function __construct(HttpClientInterface $client = null)
{
$this->client = $client ?? HttpClient::create();
}
- /**
- * {@inheritdoc}
- */
public function request(string $method, string $url, array $options = []): ResponseInterface
{
return $this->client->request($method, $url, $options);
}
- /**
- * {@inheritdoc}
- */
public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface
{
return $this->client->stream($responses, $timeout);
}
- /**
- * {@inheritdoc}
- */
public function withOptions(array $options): static
{
$clone = clone $this;
diff --git a/vendor/symfony/http-client/DependencyInjection/HttpClientPass.php b/vendor/symfony/http-client/DependencyInjection/HttpClientPass.php
index 53645a12f..c42d873fc 100644
--- a/vendor/symfony/http-client/DependencyInjection/HttpClientPass.php
+++ b/vendor/symfony/http-client/DependencyInjection/HttpClientPass.php
@@ -19,9 +19,6 @@ use Symfony\Component\HttpClient\TraceableHttpClient;
final class HttpClientPass implements CompilerPassInterface
{
- /**
- * {@inheritdoc}
- */
public function process(ContainerBuilder $container)
{
if (!$container->hasDefinition('data_collector.http_client')) {
diff --git a/vendor/symfony/http-client/EventSourceHttpClient.php b/vendor/symfony/http-client/EventSourceHttpClient.php
index 11c54848e..377c29d5f 100644
--- a/vendor/symfony/http-client/EventSourceHttpClient.php
+++ b/vendor/symfony/http-client/EventSourceHttpClient.php
@@ -84,7 +84,7 @@ final class EventSourceHttpClient implements HttpClientInterface, ResetInterface
return;
}
- } catch (TransportExceptionInterface $e) {
+ } catch (TransportExceptionInterface) {
$state->lastError = $lastError ?? microtime(true);
if (null === $state->buffer || ($isTimeout && microtime(true) - $state->lastError < $state->reconnectionTime)) {
diff --git a/vendor/symfony/http-client/Exception/HttpExceptionTrait.php b/vendor/symfony/http-client/Exception/HttpExceptionTrait.php
index 8cbaa1cd1..264ef24b2 100644
--- a/vendor/symfony/http-client/Exception/HttpExceptionTrait.php
+++ b/vendor/symfony/http-client/Exception/HttpExceptionTrait.php
@@ -20,7 +20,7 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
*/
trait HttpExceptionTrait
{
- private $response;
+ private ResponseInterface $response;
public function __construct(ResponseInterface $response)
{
diff --git a/vendor/symfony/http-client/HttpClient.php b/vendor/symfony/http-client/HttpClient.php
index 352410d33..0e7d9b440 100644
--- a/vendor/symfony/http-client/HttpClient.php
+++ b/vendor/symfony/http-client/HttpClient.php
@@ -42,7 +42,7 @@ final class HttpClient
}
static $curlVersion = null;
- $curlVersion = $curlVersion ?? curl_version();
+ $curlVersion ??= curl_version();
// HTTP/2 push crashes before curl 7.61
if (0x073D00 > $curlVersion['version_number'] || !(\CURL_VERSION_HTTP2 & $curlVersion['features'])) {
diff --git a/vendor/symfony/http-client/HttpClientTrait.php b/vendor/symfony/http-client/HttpClientTrait.php
index f09b4e8ea..36118113f 100644
--- a/vendor/symfony/http-client/HttpClientTrait.php
+++ b/vendor/symfony/http-client/HttpClientTrait.php
@@ -25,9 +25,6 @@ trait HttpClientTrait
{
private static int $CHUNK_SIZE = 16372;
- /**
- * {@inheritdoc}
- */
public function withOptions(array $options): static
{
$clone = clone $this;
@@ -97,6 +94,10 @@ trait HttpClientTrait
}
if (isset($options['body'])) {
+ if (\is_array($options['body']) && (!isset($options['normalized_headers']['content-type'][0]) || !str_contains($options['normalized_headers']['content-type'][0], 'application/x-www-form-urlencoded'))) {
+ $options['normalized_headers']['content-type'] = ['Content-Type: application/x-www-form-urlencoded'];
+ }
+
$options['body'] = self::normalizeBody($options['body']);
if (\is_string($options['body'])
@@ -202,7 +203,7 @@ trait HttpClientTrait
}
// Option "query" is never inherited from defaults
- $options['query'] = $options['query'] ?? [];
+ $options['query'] ??= [];
$options += $defaultOptions;
@@ -394,13 +395,13 @@ trait HttpClientTrait
private static function normalizePeerFingerprint(mixed $fingerprint): array
{
if (\is_string($fingerprint)) {
- switch (\strlen($fingerprint = str_replace(':', '', $fingerprint))) {
- case 32: $fingerprint = ['md5' => $fingerprint]; break;
- case 40: $fingerprint = ['sha1' => $fingerprint]; break;
- case 44: $fingerprint = ['pin-sha256' => [$fingerprint]]; break;
- case 64: $fingerprint = ['sha256' => $fingerprint]; break;
- default: throw new InvalidArgumentException(sprintf('Cannot auto-detect fingerprint algorithm for "%s".', $fingerprint));
- }
+ $fingerprint = match (\strlen($fingerprint = str_replace(':', '', $fingerprint))) {
+ 32 => ['md5' => $fingerprint],
+ 40 => ['sha1' => $fingerprint],
+ 44 => ['pin-sha256' => [$fingerprint]],
+ 64 => ['sha256' => $fingerprint],
+ default => throw new InvalidArgumentException(sprintf('Cannot auto-detect fingerprint algorithm for "%s".', $fingerprint)),
+ };
} elseif (\is_array($fingerprint)) {
foreach ($fingerprint as $algo => $hash) {
$fingerprint[$algo] = 'pin-sha256' === $algo ? (array) $hash : str_replace(':', '', $hash);
@@ -417,7 +418,7 @@ trait HttpClientTrait
*/
private static function jsonEncode(mixed $value, int $flags = null, int $maxDepth = 512): string
{
- $flags = $flags ?? (\JSON_HEX_TAG | \JSON_HEX_APOS | \JSON_HEX_AMP | \JSON_HEX_QUOT | \JSON_PRESERVE_ZERO_FRACTION);
+ $flags ??= \JSON_HEX_TAG | \JSON_HEX_APOS | \JSON_HEX_AMP | \JSON_HEX_QUOT | \JSON_PRESERVE_ZERO_FRACTION;
try {
$value = json_encode($value, $flags | \JSON_THROW_ON_ERROR, $maxDepth);
@@ -457,7 +458,7 @@ trait HttpClientTrait
} else {
if (null === $url['path']) {
$url['path'] = $base['path'];
- $url['query'] = $url['query'] ?? $base['query'];
+ $url['query'] ??= $base['query'];
} else {
if ('/' !== $url['path'][0]) {
if (null === $base['path']) {
@@ -539,7 +540,7 @@ trait HttpClientTrait
}
// https://tools.ietf.org/html/rfc3986#section-3.3
- $parts[$part] = preg_replace_callback("#[^-A-Za-z0-9._~!$&/'()*+,;=:@%]++#", function ($m) { return rawurlencode($m[0]); }, $parts[$part]);
+ $parts[$part] = preg_replace_callback("#[^-A-Za-z0-9._~!$&/'()[\]*+,;=:@{}%]++#", function ($m) { return rawurlencode($m[0]); }, $parts[$part]);
}
return [
@@ -613,6 +614,23 @@ trait HttpClientTrait
$queryArray = [];
if ($queryString) {
+ if (str_contains($queryString, '%')) {
+ // https://tools.ietf.org/html/rfc3986#section-2.3 + some chars not encoded by browsers
+ $queryString = strtr($queryString, [
+ '%21' => '!',
+ '%24' => '$',
+ '%28' => '(',
+ '%29' => ')',
+ '%2A' => '*',
+ '%2F' => '/',
+ '%3A' => ':',
+ '%3B' => ';',
+ '%40' => '@',
+ '%5B' => '[',
+ '%5D' => ']',
+ ]);
+ }
+
foreach (explode('&', $queryString) as $v) {
$queryArray[rawurldecode(explode('=', $v, 2)[0])] = $v;
}
@@ -626,16 +644,7 @@ trait HttpClientTrait
*/
private static function getProxy(?string $proxy, array $url, ?string $noProxy): ?array
{
- if (null === $proxy) {
- // Ignore HTTP_PROXY except on the CLI to work around httpoxy set of vulnerabilities
- $proxy = $_SERVER['http_proxy'] ?? (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? $_SERVER['HTTP_PROXY'] ?? null : null) ?? $_SERVER['all_proxy'] ?? $_SERVER['ALL_PROXY'] ?? null;
-
- if ('https:' === $url['scheme']) {
- $proxy = $_SERVER['https_proxy'] ?? $_SERVER['HTTPS_PROXY'] ?? $proxy;
- }
- }
-
- if (null === $proxy) {
+ if (null === $proxy = self::getProxyUrl($proxy, $url)) {
return null;
}
@@ -653,7 +662,7 @@ trait HttpClientTrait
throw new TransportException(sprintf('Unsupported proxy scheme "%s": "http" or "https" expected.', $proxy['scheme']));
}
- $noProxy = $noProxy ?? $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? '';
+ $noProxy ??= $_SERVER['no_proxy'] ?? $_SERVER['NO_PROXY'] ?? '';
$noProxy = $noProxy ? preg_split('/[\s,]+/', $noProxy) : [];
return [
@@ -663,6 +672,22 @@ trait HttpClientTrait
];
}
+ private static function getProxyUrl(?string $proxy, array $url): ?string
+ {
+ if (null !== $proxy) {
+ return $proxy;
+ }
+
+ // Ignore HTTP_PROXY except on the CLI to work around httpoxy set of vulnerabilities
+ $proxy = $_SERVER['http_proxy'] ?? (\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) ? $_SERVER['HTTP_PROXY'] ?? null : null) ?? $_SERVER['all_proxy'] ?? $_SERVER['ALL_PROXY'] ?? null;
+
+ if ('https:' === $url['scheme']) {
+ $proxy = $_SERVER['https_proxy'] ?? $_SERVER['HTTPS_PROXY'] ?? $proxy;
+ }
+
+ return $proxy;
+ }
+
private static function shouldBuffer(array $headers): bool
{
if (null === $contentType = $headers['content-type'][0] ?? null) {
diff --git a/vendor/symfony/http-client/HttpOptions.php b/vendor/symfony/http-client/HttpOptions.php
index d71b11926..a07fac7ed 100644
--- a/vendor/symfony/http-client/HttpOptions.php
+++ b/vendor/symfony/http-client/HttpOptions.php
@@ -32,7 +32,7 @@ class HttpOptions
/**
* @return $this
*/
- public function setAuthBasic(string $user, string $password = ''): static
+ public function setAuthBasic(string $user, #[\SensitiveParameter] string $password = ''): static
{
$this->options['auth_basic'] = $user;
@@ -46,7 +46,7 @@ class HttpOptions
/**
* @return $this
*/
- public function setAuthBearer(string $token): static
+ public function setAuthBearer(#[\SensitiveParameter] string $token): static
{
$this->options['auth_bearer'] = $token;
diff --git a/vendor/symfony/http-client/HttplugClient.php b/vendor/symfony/http-client/HttplugClient.php
index 676cfe6ae..661f9fec0 100644
--- a/vendor/symfony/http-client/HttplugClient.php
+++ b/vendor/symfony/http-client/HttplugClient.php
@@ -46,7 +46,11 @@ if (!interface_exists(HttplugInterface::class)) {
}
if (!interface_exists(RequestFactory::class)) {
- throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/message-factory" package is not installed. Try running "composer require nyholm/psr7".');
+ throw new \LogicException('You cannot use "Symfony\Component\HttpClient\HttplugClient" as the "php-http/message-factory" package is not installed. Try running "composer require php-http/message-factory".');
+}
+
+if (!interface_exists(RequestFactoryInterface::class)) {
+ throw new \LogicException('You cannot use the "Symfony\Component\HttpClient\HttplugClient" as the "psr/http-factory" package is not installed. Try running "composer require nyholm/psr7".');
}
/**
@@ -57,18 +61,18 @@ if (!interface_exists(RequestFactory::class)) {
*
* @author Nicolas Grekas
*/
-final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestFactory, StreamFactory, UriFactory, ResetInterface
+final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestFactoryInterface, StreamFactoryInterface, UriFactoryInterface, RequestFactory, StreamFactory, UriFactory, ResetInterface
{
- private $client;
- private $responseFactory;
- private $streamFactory;
+ private HttpClientInterface $client;
+ private ResponseFactoryInterface $responseFactory;
+ private StreamFactoryInterface $streamFactory;
/**
* @var \SplObjectStorage|null
*/
private ?\SplObjectStorage $promisePool;
- private $waitLoop;
+ private HttplugWaitLoop $waitLoop;
public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null)
{
@@ -95,9 +99,14 @@ final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestF
$this->waitLoop = new HttplugWaitLoop($this->client, $this->promisePool, $this->responseFactory, $this->streamFactory);
}
- /**
- * {@inheritdoc}
- */
+ public function withOptions(array $options): static
+ {
+ $clone = clone $this;
+ $clone->client = $clone->client->withOptions($options);
+
+ return $clone;
+ }
+
public function sendRequest(RequestInterface $request): Psr7ResponseInterface
{
try {
@@ -107,9 +116,6 @@ final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestF
}
}
- /**
- * {@inheritdoc}
- */
public function sendAsyncRequest(RequestInterface $request): HttplugPromise
{
if (!$promisePool = $this->promisePool) {
@@ -149,10 +155,14 @@ final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestF
}
/**
- * {@inheritdoc}
+ * @param string $method
+ * @param UriInterface|string $uri
*/
public function createRequest($method, $uri, array $headers = [], $body = null, $protocolVersion = '1.1'): RequestInterface
{
+ if (2 < \func_num_args()) {
+ trigger_deprecation('symfony/http-client', '6.2', 'Passing more than 2 arguments to "%s()" is deprecated.', __METHOD__);
+ }
if ($this->responseFactory instanceof RequestFactoryInterface) {
$request = $this->responseFactory->createRequest($method, $uri);
} elseif (class_exists(Request::class)) {
@@ -165,7 +175,7 @@ final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestF
$request = $request
->withProtocolVersion($protocolVersion)
- ->withBody($this->createStream($body))
+ ->withBody($this->createStream($body ?? ''))
;
foreach ($headers as $name => $value) {
@@ -176,20 +186,24 @@ final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestF
}
/**
- * {@inheritdoc}
+ * @param string $content
*/
- public function createStream($body = null): StreamInterface
+ public function createStream($content = ''): StreamInterface
{
- if ($body instanceof StreamInterface) {
- return $body;
+ if (!\is_string($content)) {
+ trigger_deprecation('symfony/http-client', '6.2', 'Passing a "%s" to "%s()" is deprecated, use "createStreamFrom*()" instead.', get_debug_type($content), __METHOD__);
}
- if (\is_string($body ?? '')) {
- $stream = $this->streamFactory->createStream($body ?? '');
- } elseif (\is_resource($body)) {
- $stream = $this->streamFactory->createStreamFromResource($body);
+ if ($content instanceof StreamInterface) {
+ return $content;
+ }
+
+ if (\is_string($content ?? '')) {
+ $stream = $this->streamFactory->createStream($content ?? '');
+ } elseif (\is_resource($content)) {
+ $stream = $this->streamFactory->createStreamFromResource($content);
} else {
- throw new \InvalidArgumentException(sprintf('"%s()" expects string, resource or StreamInterface, "%s" given.', __METHOD__, get_debug_type($body)));
+ throw new \InvalidArgumentException(sprintf('"%s()" expects string, resource or StreamInterface, "%s" given.', __METHOD__, get_debug_type($content)));
}
if ($stream->isSeekable()) {
@@ -199,11 +213,25 @@ final class HttplugClient implements HttplugInterface, HttpAsyncClient, RequestF
return $stream;
}
- /**
- * {@inheritdoc}
- */
- public function createUri($uri): UriInterface
+ public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
{
+ return $this->streamFactory->createStreamFromFile($filename, $mode);
+ }
+
+ public function createStreamFromResource($resource): StreamInterface
+ {
+ return $this->streamFactory->createStreamFromResource($resource);
+ }
+
+ /**
+ * @param string $uri
+ */
+ public function createUri($uri = ''): UriInterface
+ {
+ if (!\is_string($uri)) {
+ trigger_deprecation('symfony/http-client', '6.2', 'Passing a "%s" to "%s()" is deprecated, pass a string instead.', get_debug_type($uri), __METHOD__);
+ }
+
if ($uri instanceof UriInterface) {
return $uri;
}
diff --git a/vendor/symfony/http-client/Internal/AmpBody.php b/vendor/symfony/http-client/Internal/AmpBody.php
index a6ff7635b..bd995e17d 100644
--- a/vendor/symfony/http-client/Internal/AmpBody.php
+++ b/vendor/symfony/http-client/Internal/AmpBody.php
@@ -25,7 +25,7 @@ use Symfony\Component\HttpClient\Exception\TransportException;
*/
class AmpBody implements RequestBody, InputStream
{
- private $body;
+ private ResourceInputStream|\Closure|string $body;
private array $info;
private \Closure $onProgress;
private ?int $offset = 0;
diff --git a/vendor/symfony/http-client/Internal/AmpClientState.php b/vendor/symfony/http-client/Internal/AmpClientState.php
index 28906e629..18a1722c3 100644
--- a/vendor/symfony/http-client/Internal/AmpClientState.php
+++ b/vendor/symfony/http-client/Internal/AmpClientState.php
@@ -49,12 +49,12 @@ final class AmpClientState extends ClientState
private \Closure $clientConfigurator;
private int $maxHostConnections;
private int $maxPendingPushes;
- private $logger;
+ private ?LoggerInterface $logger;
public function __construct(?callable $clientConfigurator, int $maxHostConnections, int $maxPendingPushes, ?LoggerInterface &$logger)
{
$clientConfigurator ??= static fn (PooledHttpClient $client) => new InterceptedHttpClient($client, new RetryRequests(2));
- $this->clientConfigurator = $clientConfigurator instanceof \Closure ? $clientConfigurator : \Closure::fromCallable($clientConfigurator);
+ $this->clientConfigurator = $clientConfigurator(...);
$this->maxHostConnections = $maxHostConnections;
$this->maxPendingPushes = $maxPendingPushes;
@@ -76,7 +76,7 @@ final class AmpClientState extends ClientState
foreach ($options['proxy']['no_proxy'] as $rule) {
$dotRule = '.'.ltrim($rule, '.');
- if ('*' === $rule || $host === $rule || substr($host, -\strlen($dotRule)) === $dotRule) {
+ if ('*' === $rule || $host === $rule || str_ends_with($host, $dotRule)) {
$options['proxy'] = null;
break;
}
@@ -200,11 +200,11 @@ final class AmpClientState extends ClientState
if ($this->maxPendingPushes <= \count($this->pushedResponses[$authority] ?? [])) {
$fifoUrl = key($this->pushedResponses[$authority]);
unset($this->pushedResponses[$authority][$fifoUrl]);
- $this->logger && $this->logger->debug(sprintf('Evicting oldest pushed response: "%s"', $fifoUrl));
+ $this->logger?->debug(sprintf('Evicting oldest pushed response: "%s"', $fifoUrl));
}
$url = (string) $request->getUri();
- $this->logger && $this->logger->debug(sprintf('Queueing pushed response: "%s"', $url));
+ $this->logger?->debug(sprintf('Queueing pushed response: "%s"', $url));
$this->pushedResponses[$authority][] = [$url, $deferred, $request, $response, [
'proxy' => $options['proxy'],
'bindto' => $options['bindto'],
diff --git a/vendor/symfony/http-client/Internal/AmpListener.php b/vendor/symfony/http-client/Internal/AmpListener.php
index 37c43d148..206c44982 100644
--- a/vendor/symfony/http-client/Internal/AmpListener.php
+++ b/vendor/symfony/http-client/Internal/AmpListener.php
@@ -50,7 +50,7 @@ class AmpListener implements EventListener
public function startRequest(Request $request): Promise
{
- $this->info['start_time'] = $this->info['start_time'] ?? microtime(true);
+ $this->info['start_time'] ??= microtime(true);
($this->onProgress)();
return new Success();
@@ -81,7 +81,7 @@ class AmpListener implements EventListener
{
$host = $stream->getRemoteAddress()->getHost();
- if (false !== strpos($host, ':')) {
+ if (str_contains($host, ':')) {
$host = '['.$host.']';
}
diff --git a/vendor/symfony/http-client/Internal/CurlClientState.php b/vendor/symfony/http-client/Internal/CurlClientState.php
index 2934fc863..8e2a42ed0 100644
--- a/vendor/symfony/http-client/Internal/CurlClientState.php
+++ b/vendor/symfony/http-client/Internal/CurlClientState.php
@@ -25,6 +25,7 @@ final class CurlClientState extends ClientState
{
public ?\CurlMultiHandle $handle;
public ?\CurlShareHandle $share;
+ public bool $performing = false;
/** @var PushedResponse[] */
public array $pushedResponses = [];
@@ -38,7 +39,7 @@ final class CurlClientState extends ClientState
public function __construct(int $maxHostConnections, int $maxPendingPushes)
{
- self::$curlVersion = self::$curlVersion ?? curl_version();
+ self::$curlVersion ??= curl_version();
$this->handle = curl_multi_init();
$this->dnsCache = new DnsCache();
@@ -82,7 +83,7 @@ final class CurlClientState extends ClientState
public function reset()
{
foreach ($this->pushedResponses as $url => $response) {
- $this->logger && $this->logger->debug(sprintf('Unused pushed response: "%s"', $url));
+ $this->logger?->debug(sprintf('Unused pushed response: "%s"', $url));
curl_multi_remove_handle($this->handle, $response->handle);
curl_close($response->handle);
}
@@ -113,7 +114,7 @@ final class CurlClientState extends ClientState
}
if (!isset($headers[':method']) || !isset($headers[':scheme']) || !isset($headers[':authority']) || !isset($headers[':path'])) {
- $this->logger && $this->logger->debug(sprintf('Rejecting pushed response from "%s": pushed headers are invalid', $origin));
+ $this->logger?->debug(sprintf('Rejecting pushed response from "%s": pushed headers are invalid', $origin));
return \CURL_PUSH_DENY;
}
@@ -124,7 +125,7 @@ final class CurlClientState extends ClientState
// but this is a MUST in the HTTP/2 RFC; let's restrict pushes to the original host,
// ignoring domains mentioned as alt-name in the certificate for now (same as curl).
if (!str_starts_with($origin, $url.'/')) {
- $this->logger && $this->logger->debug(sprintf('Rejecting pushed response from "%s": server is not authoritative for "%s"', $origin, $url));
+ $this->logger?->debug(sprintf('Rejecting pushed response from "%s": server is not authoritative for "%s"', $origin, $url));
return \CURL_PUSH_DENY;
}
@@ -132,11 +133,11 @@ final class CurlClientState extends ClientState
if ($maxPendingPushes <= \count($this->pushedResponses)) {
$fifoUrl = key($this->pushedResponses);
unset($this->pushedResponses[$fifoUrl]);
- $this->logger && $this->logger->debug(sprintf('Evicting oldest pushed response: "%s"', $fifoUrl));
+ $this->logger?->debug(sprintf('Evicting oldest pushed response: "%s"', $fifoUrl));
}
$url .= $headers[':path'][0];
- $this->logger && $this->logger->debug(sprintf('Queueing pushed response: "%s"', $url));
+ $this->logger?->debug(sprintf('Queueing pushed response: "%s"', $url));
$this->pushedResponses[$url] = new PushedResponse(new CurlResponse($this, $pushed), $headers, $this->openHandles[(int) $parent][1] ?? [], $pushed);
diff --git a/vendor/symfony/http-client/Internal/HttplugWaitLoop.php b/vendor/symfony/http-client/Internal/HttplugWaitLoop.php
index e3685c4d4..85d7e01d6 100644
--- a/vendor/symfony/http-client/Internal/HttplugWaitLoop.php
+++ b/vendor/symfony/http-client/Internal/HttplugWaitLoop.php
@@ -30,10 +30,10 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
*/
final class HttplugWaitLoop
{
- private $client;
+ private HttpClientInterface $client;
private ?\SplObjectStorage $promisePool;
- private $responseFactory;
- private $streamFactory;
+ private ResponseFactoryInterface $responseFactory;
+ private StreamFactoryInterface $streamFactory;
/**
* @param \SplObjectStorage|null $promisePool
@@ -120,7 +120,11 @@ final class HttplugWaitLoop
foreach ($response->getHeaders(false) as $name => $values) {
foreach ($values as $value) {
- $psrResponse = $psrResponse->withAddedHeader($name, $value);
+ try {
+ $psrResponse = $psrResponse->withAddedHeader($name, $value);
+ } catch (\InvalidArgumentException $e) {
+ // ignore invalid header
+ }
}
}
diff --git a/vendor/symfony/http-client/Internal/PushedResponse.php b/vendor/symfony/http-client/Internal/PushedResponse.php
index 51c94e9ba..f1e0ad687 100644
--- a/vendor/symfony/http-client/Internal/PushedResponse.php
+++ b/vendor/symfony/http-client/Internal/PushedResponse.php
@@ -22,7 +22,7 @@ use Symfony\Component\HttpClient\Response\CurlResponse;
*/
final class PushedResponse
{
- public $response;
+ public CurlResponse $response;
/** @var string[] */
public array $requestHeaders;
diff --git a/vendor/symfony/http-client/LICENSE b/vendor/symfony/http-client/LICENSE
index 99757d517..7536caeae 100644
--- a/vendor/symfony/http-client/LICENSE
+++ b/vendor/symfony/http-client/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2018-2023 Fabien Potencier
+Copyright (c) 2018-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/symfony/http-client/MockHttpClient.php b/vendor/symfony/http-client/MockHttpClient.php
index ecb2423d5..7906b9a96 100644
--- a/vendor/symfony/http-client/MockHttpClient.php
+++ b/vendor/symfony/http-client/MockHttpClient.php
@@ -28,7 +28,7 @@ class MockHttpClient implements HttpClientInterface, ResetInterface
{
use HttpClientTrait;
- private $responseFactory;
+ private ResponseInterface|\Closure|iterable|null $responseFactory;
private int $requestsCount = 0;
private array $defaultOptions = [];
@@ -56,12 +56,9 @@ class MockHttpClient implements HttpClientInterface, ResetInterface
})();
}
- $this->responseFactory = !\is_callable($responseFactory) || $responseFactory instanceof \Closure ? $responseFactory : \Closure::fromCallable($responseFactory);
+ $this->responseFactory = !\is_callable($responseFactory) ? $responseFactory : $responseFactory(...);
}
- /**
- * {@inheritdoc}
- */
public function request(string $method, string $url, array $options = []): ResponseInterface
{
[$url, $options] = $this->prepareRequest($method, $url, $options, $this->defaultOptions, true);
@@ -81,15 +78,12 @@ class MockHttpClient implements HttpClientInterface, ResetInterface
++$this->requestsCount;
if (!$response instanceof ResponseInterface) {
- throw new TransportException(sprintf('The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "%s" given.', \is_object($response) ? \get_class($response) : \gettype($response)));
+ throw new TransportException(sprintf('The response factory passed to MockHttpClient must return/yield an instance of ResponseInterface, "%s" given.', get_debug_type($response)));
}
return MockResponse::fromRequest($method, $url, $options, $response);
}
- /**
- * {@inheritdoc}
- */
public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface
{
if ($responses instanceof ResponseInterface) {
@@ -104,9 +98,6 @@ class MockHttpClient implements HttpClientInterface, ResetInterface
return $this->requestsCount;
}
- /**
- * {@inheritdoc}
- */
public function withOptions(array $options): static
{
$clone = clone $this;
diff --git a/vendor/symfony/http-client/NativeHttpClient.php b/vendor/symfony/http-client/NativeHttpClient.php
index 83625ae51..734effbb4 100644
--- a/vendor/symfony/http-client/NativeHttpClient.php
+++ b/vendor/symfony/http-client/NativeHttpClient.php
@@ -39,7 +39,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
private array $defaultOptions = self::OPTIONS_DEFAULTS;
private static array $emptyDefaults = self::OPTIONS_DEFAULTS;
- private $multi;
+ private NativeClientState $multi;
/**
* @param array $defaultOptions Default request's options
@@ -49,7 +49,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
*/
public function __construct(array $defaultOptions = [], int $maxHostConnections = 6)
{
- $this->defaultOptions['buffer'] = $this->defaultOptions['buffer'] ?? \Closure::fromCallable([__CLASS__, 'shouldBuffer']);
+ $this->defaultOptions['buffer'] ??= self::shouldBuffer(...);
if ($defaultOptions) {
[, $this->defaultOptions] = self::prepareRequest(null, null, $defaultOptions, $this->defaultOptions);
@@ -61,8 +61,6 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
/**
* @see HttpClientInterface::OPTIONS_DEFAULTS for available options
- *
- * {@inheritdoc}
*/
public function request(string $method, string $url, array $options = []): ResponseInterface
{
@@ -189,7 +187,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
$this->multi->dnsCache = $options['resolve'] + $this->multi->dnsCache;
}
- $this->logger && $this->logger->info(sprintf('Request: "%s %s"', $method, implode('', $url)));
+ $this->logger?->info(sprintf('Request: "%s %s"', $method, implode('', $url)));
if (!isset($options['normalized_headers']['user-agent'])) {
$options['headers'][] = 'User-Agent: Symfony HttpClient/Native';
@@ -248,15 +246,12 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
$url['authority'] = substr_replace($url['authority'], $ip, -\strlen($host) - \strlen($port), \strlen($host));
}
- return [self::createRedirectResolver($options, $host, $proxy, $info, $onProgress), implode('', $url)];
+ return [self::createRedirectResolver($options, $host, $port, $proxy, $info, $onProgress), implode('', $url)];
};
return new NativeResponse($this->multi, $context, implode('', $url), $options, $info, $resolver, $onProgress, $this->logger);
}
- /**
- * {@inheritdoc}
- */
public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface
{
if ($responses instanceof NativeResponse) {
@@ -342,11 +337,11 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
/**
* Handles redirects - the native logic is too buggy to be used.
*/
- private static function createRedirectResolver(array $options, string $host, ?array $proxy, array &$info, ?\Closure $onProgress): \Closure
+ private static function createRedirectResolver(array $options, string $host, string $port, ?array $proxy, array &$info, ?\Closure $onProgress): \Closure
{
$redirectHeaders = [];
if (0 < $maxRedirects = $options['max_redirects']) {
- $redirectHeaders = ['host' => $host];
+ $redirectHeaders = ['host' => $host, 'port' => $port];
$redirectHeaders['with_auth'] = $redirectHeaders['no_auth'] = array_filter($options['headers'], static function ($h) {
return 0 !== stripos($h, 'Host:');
});
@@ -367,7 +362,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
try {
$url = self::parseUrl($location);
- } catch (InvalidArgumentException $e) {
+ } catch (InvalidArgumentException) {
$info['redirect_url'] = null;
return null;
@@ -406,7 +401,7 @@ final class NativeHttpClient implements HttpClientInterface, LoggerAwareInterfac
if (false !== (parse_url($location, \PHP_URL_HOST) ?? false)) {
// Authorization and Cookie headers MUST NOT follow except for the initial host name
- $requestHeaders = $redirectHeaders['host'] === $host ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
+ $requestHeaders = $redirectHeaders['host'] === $host && $redirectHeaders['port'] === $port ? $redirectHeaders['with_auth'] : $redirectHeaders['no_auth'];
$requestHeaders[] = 'Host: '.$host.$port;
$dnsResolve = !self::configureHeadersAndProxy($context, $host, $requestHeaders, $proxy, 'https:' === $url['scheme']);
} else {
diff --git a/vendor/symfony/http-client/NoPrivateNetworkHttpClient.php b/vendor/symfony/http-client/NoPrivateNetworkHttpClient.php
index d3ea5ad5e..89885abe1 100644
--- a/vendor/symfony/http-client/NoPrivateNetworkHttpClient.php
+++ b/vendor/symfony/http-client/NoPrivateNetworkHttpClient.php
@@ -45,7 +45,7 @@ final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwa
'::/128',
];
- private $client;
+ private HttpClientInterface $client;
private string|array|null $subnets;
/**
@@ -62,9 +62,6 @@ final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwa
$this->subnets = $subnets;
}
- /**
- * {@inheritdoc}
- */
public function request(string $method, string $url, array $options = []): ResponseInterface
{
$onProgress = $options['on_progress'] ?? null;
@@ -90,17 +87,11 @@ final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwa
return $this->client->request($method, $url, $options);
}
- /**
- * {@inheritdoc}
- */
public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface
{
return $this->client->stream($responses, $timeout);
}
- /**
- * {@inheritdoc}
- */
public function setLogger(LoggerInterface $logger): void
{
if ($this->client instanceof LoggerAwareInterface) {
@@ -108,9 +99,6 @@ final class NoPrivateNetworkHttpClient implements HttpClientInterface, LoggerAwa
}
}
- /**
- * {@inheritdoc}
- */
public function withOptions(array $options): static
{
$clone = clone $this;
diff --git a/vendor/symfony/http-client/Psr18Client.php b/vendor/symfony/http-client/Psr18Client.php
index 650c28570..699acf449 100644
--- a/vendor/symfony/http-client/Psr18Client.php
+++ b/vendor/symfony/http-client/Psr18Client.php
@@ -52,9 +52,9 @@ if (!interface_exists(ClientInterface::class)) {
*/
final class Psr18Client implements ClientInterface, RequestFactoryInterface, StreamFactoryInterface, UriFactoryInterface, ResetInterface
{
- private $client;
- private $responseFactory;
- private $streamFactory;
+ private HttpClientInterface $client;
+ private ResponseFactoryInterface $responseFactory;
+ private StreamFactoryInterface $streamFactory;
public function __construct(HttpClientInterface $client = null, ResponseFactoryInterface $responseFactory = null, StreamFactoryInterface $streamFactory = null)
{
@@ -79,9 +79,14 @@ final class Psr18Client implements ClientInterface, RequestFactoryInterface, Str
$this->streamFactory = $streamFactory;
}
- /**
- * {@inheritdoc}
- */
+ public function withOptions(array $options): static
+ {
+ $clone = clone $this;
+ $clone->client = $clone->client->withOptions($options);
+
+ return $clone;
+ }
+
public function sendRequest(RequestInterface $request): ResponseInterface
{
try {
@@ -131,9 +136,6 @@ final class Psr18Client implements ClientInterface, RequestFactoryInterface, Str
}
}
- /**
- * {@inheritdoc}
- */
public function createRequest(string $method, $uri): RequestInterface
{
if ($this->responseFactory instanceof RequestFactoryInterface) {
@@ -151,9 +153,6 @@ final class Psr18Client implements ClientInterface, RequestFactoryInterface, Str
throw new \LogicException(sprintf('You cannot use "%s()" as the "nyholm/psr7" package is not installed. Try running "composer require nyholm/psr7".', __METHOD__));
}
- /**
- * {@inheritdoc}
- */
public function createStream(string $content = ''): StreamInterface
{
$stream = $this->streamFactory->createStream($content);
@@ -165,25 +164,16 @@ final class Psr18Client implements ClientInterface, RequestFactoryInterface, Str
return $stream;
}
- /**
- * {@inheritdoc}
- */
public function createStreamFromFile(string $filename, string $mode = 'r'): StreamInterface
{
return $this->streamFactory->createStreamFromFile($filename, $mode);
}
- /**
- * {@inheritdoc}
- */
public function createStreamFromResource($resource): StreamInterface
{
return $this->streamFactory->createStreamFromResource($resource);
}
- /**
- * {@inheritdoc}
- */
public function createUri(string $uri = ''): UriInterface
{
if ($this->responseFactory instanceof UriFactoryInterface) {
@@ -214,7 +204,7 @@ final class Psr18Client implements ClientInterface, RequestFactoryInterface, Str
*/
class Psr18NetworkException extends \RuntimeException implements NetworkExceptionInterface
{
- private $request;
+ private RequestInterface $request;
public function __construct(TransportExceptionInterface $e, RequestInterface $request)
{
@@ -233,7 +223,7 @@ class Psr18NetworkException extends \RuntimeException implements NetworkExceptio
*/
class Psr18RequestException extends \InvalidArgumentException implements RequestExceptionInterface
{
- private $request;
+ private RequestInterface $request;
public function __construct(TransportExceptionInterface $e, RequestInterface $request)
{
diff --git a/vendor/symfony/http-client/README.md b/vendor/symfony/http-client/README.md
index 0c55ccc11..214489b7e 100644
--- a/vendor/symfony/http-client/README.md
+++ b/vendor/symfony/http-client/README.md
@@ -3,16 +3,6 @@ HttpClient component
The HttpClient component provides powerful methods to fetch HTTP resources synchronously or asynchronously.
-Sponsor
--------
-
-The Httpclient component for Symfony 5.4/6.0 is [backed][1] by [Klaxoon][2].
-
-Klaxoon is a platform that empowers organizations to run effective and
-productive workshops easily in a hybrid environment. Anytime, Anywhere.
-
-Help Symfony by [sponsoring][3] its development!
-
Resources
---------
@@ -21,7 +11,3 @@ Resources
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
-
-[1]: https://symfony.com/backers
-[2]: https://klaxoon.com
-[3]: https://symfony.com/sponsor
diff --git a/vendor/symfony/http-client/Response/AmpResponse.php b/vendor/symfony/http-client/Response/AmpResponse.php
index cea7102ea..9aad82641 100644
--- a/vendor/symfony/http-client/Response/AmpResponse.php
+++ b/vendor/symfony/http-client/Response/AmpResponse.php
@@ -45,9 +45,8 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
private static string $nextId = 'a';
- private $multi;
+ private AmpClientState $multi;
private ?array $options;
- private $canceller;
private \Closure $onProgress;
private static ?string $delay = null;
@@ -73,13 +72,14 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
$info = &$this->info;
$headers = &$this->headers;
- $canceller = $this->canceller = new CancellationTokenSource();
+ $canceller = new CancellationTokenSource();
$handle = &$this->handle;
$info['url'] = (string) $request->getUri();
$info['http_method'] = $request->getMethod();
$info['start_time'] = null;
$info['redirect_url'] = null;
+ $info['original_url'] = $info['url'];
$info['redirect_time'] = 0.0;
$info['redirect_count'] = 0;
$info['size_upload'] = 0.0;
@@ -136,9 +136,6 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
});
}
- /**
- * {@inheritdoc}
- */
public function getInfo(string $type = null): mixed
{
return null !== $type ? $this->info[$type] ?? null : $this->info;
@@ -167,9 +164,6 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
}
}
- /**
- * {@inheritdoc}
- */
private static function schedule(self $response, array &$runningResponses): void
{
if (isset($runningResponses[0])) {
@@ -185,8 +179,6 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
}
/**
- * {@inheritdoc}
- *
* @param AmpClientState $multi
*/
private static function perform(ClientState $multi, array &$responses = null): void
@@ -207,8 +199,6 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
}
/**
- * {@inheritdoc}
- *
* @param AmpClientState $multi
*/
private static function select(ClientState $multi, float $timeout): int
@@ -216,7 +206,7 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
$timeout += microtime(true);
self::$delay = Loop::defer(static function () use ($timeout) {
if (0 < $timeout -= microtime(true)) {
- self::$delay = Loop::delay(ceil(1000 * $timeout), [Loop::class, 'stop']);
+ self::$delay = Loop::delay(ceil(1000 * $timeout), Loop::stop(...));
} else {
Loop::stop();
}
@@ -227,7 +217,7 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
return null === self::$delay ? 1 : 0;
}
- private static function generateResponse(Request $request, AmpClientState $multi, string $id, array &$info, array &$headers, CancellationTokenSource $canceller, array &$options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause)
+ private static function generateResponse(Request $request, AmpClientState $multi, string $id, array &$info, array &$headers, CancellationTokenSource $canceller, array &$options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause): \Generator
{
$request->setInformationalResponseHandler(static function (Response $response) use ($multi, $id, &$info, &$headers) {
self::addResponseHeaders($response, $info, $headers);
@@ -238,7 +228,7 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
try {
/* @var Response $response */
if (null === $response = yield from self::getPushedResponse($request, $multi, $info, $headers, $options, $logger)) {
- $logger && $logger->info(sprintf('Request: "%s %s"', $info['http_method'], $info['url']));
+ $logger?->info(sprintf('Request: "%s %s"', $info['http_method'], $info['url']));
$response = yield from self::followRedirects($request, $multi, $info, $headers, $canceller, $options, $onProgress, $handle, $logger, $pause);
}
@@ -286,7 +276,7 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
self::stopLoop();
}
- private static function followRedirects(Request $originRequest, AmpClientState $multi, array &$info, array &$headers, CancellationTokenSource $canceller, array $options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause)
+ private static function followRedirects(Request $originRequest, AmpClientState $multi, array &$info, array &$headers, CancellationTokenSource $canceller, array $options, \Closure $onProgress, &$handle, ?LoggerInterface $logger, Promise &$pause): \Generator
{
yield $pause;
@@ -310,11 +300,11 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
};
try {
- $previousUrl = $previousUrl ?? $urlResolver::parseUrl($info['url']);
+ $previousUrl ??= $urlResolver::parseUrl($info['url']);
$location = $urlResolver::parseUrl($location);
$location = $urlResolver::resolveUrl($location, $previousUrl);
$info['redirect_url'] = implode('', $location);
- } catch (InvalidArgumentException $e) {
+ } catch (InvalidArgumentException) {
return $response;
}
@@ -322,13 +312,13 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
return $response;
}
- $logger && $logger->info(sprintf('Redirecting: "%s %s"', $status, $info['url']));
+ $logger?->info(sprintf('Redirecting: "%s %s"', $status, $info['url']));
try {
// Discard body of redirects
while (null !== yield $response->getBody()->read()) {
}
- } catch (HttpException|StreamException $e) {
+ } catch (HttpException|StreamException) {
// Ignore streaming errors on previous responses
}
@@ -358,7 +348,7 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
}
foreach ($originRequest->getRawHeaders() as [$name, $value]) {
- $request->setHeader($name, $value);
+ $request->addHeader($name, $value);
}
if ($request->getUri()->getAuthority() !== $originRequest->getUri()->getAuthority()) {
@@ -400,7 +390,7 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
/**
* Accepts pushed responses only if their headers related to authentication match the request.
*/
- private static function getPushedResponse(Request $request, AmpClientState $multi, array &$info, array &$headers, array $options, ?LoggerInterface $logger)
+ private static function getPushedResponse(Request $request, AmpClientState $multi, array &$info, array &$headers, array $options, ?LoggerInterface $logger): \Generator
{
if ('' !== $options['body']) {
return null;
@@ -430,14 +420,14 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
foreach ($response->getHeaderArray('vary') as $vary) {
foreach (preg_split('/\s*+,\s*+/', $vary) as $v) {
if ('*' === $v || ($pushedRequest->getHeaderArray($v) !== $request->getHeaderArray($v) && 'accept-encoding' !== strtolower($v))) {
- $logger && $logger->debug(sprintf('Skipping pushed response: "%s"', $info['url']));
+ $logger?->debug(sprintf('Skipping pushed response: "%s"', $info['url']));
continue 3;
}
}
}
$pushDeferred->resolve();
- $logger && $logger->debug(sprintf('Accepting pushed response: "%s %s"', $info['http_method'], $info['url']));
+ $logger?->debug(sprintf('Accepting pushed response: "%s %s"', $info['http_method'], $info['url']));
self::addResponseHeaders($response, $info, $headers);
unset($multi->pushedResponses[$authority][$i]);
@@ -456,6 +446,6 @@ final class AmpResponse implements ResponseInterface, StreamableInterface
self::$delay = null;
}
- Loop::defer([Loop::class, 'stop']);
+ Loop::defer(Loop::stop(...));
}
}
diff --git a/vendor/symfony/http-client/Response/AsyncContext.php b/vendor/symfony/http-client/Response/AsyncContext.php
index 7fd815d52..55903463a 100644
--- a/vendor/symfony/http-client/Response/AsyncContext.php
+++ b/vendor/symfony/http-client/Response/AsyncContext.php
@@ -26,8 +26,8 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
final class AsyncContext
{
private $passthru;
- private $client;
- private $response;
+ private HttpClientInterface $client;
+ private ResponseInterface $response;
private array $info = [];
private $content;
private int $offset;
diff --git a/vendor/symfony/http-client/Response/AsyncResponse.php b/vendor/symfony/http-client/Response/AsyncResponse.php
index 14c080e9f..8236623b2 100644
--- a/vendor/symfony/http-client/Response/AsyncResponse.php
+++ b/vendor/symfony/http-client/Response/AsyncResponse.php
@@ -34,8 +34,8 @@ final class AsyncResponse implements ResponseInterface, StreamableInterface
private const FIRST_CHUNK_YIELDED = 1;
private const LAST_CHUNK_YIELDED = 2;
- private $client;
- private $response;
+ private ?HttpClientInterface $client;
+ private ResponseInterface $response;
private array $info = ['canceled' => false];
private $passthru;
private $stream;
@@ -123,9 +123,6 @@ final class AsyncResponse implements ResponseInterface, StreamableInterface
return $this->info + $this->response->getInfo();
}
- /**
- * {@inheritdoc}
- */
public function toStream(bool $throw = true)
{
if ($throw) {
@@ -146,9 +143,6 @@ final class AsyncResponse implements ResponseInterface, StreamableInterface
return $stream;
}
- /**
- * {@inheritdoc}
- */
public function cancel(): void
{
if ($this->info['canceled']) {
@@ -171,7 +165,7 @@ final class AsyncResponse implements ResponseInterface, StreamableInterface
}
$this->passthru = null;
- } catch (ExceptionInterface $e) {
+ } catch (ExceptionInterface) {
// ignore any errors when canceling
}
}
@@ -196,7 +190,7 @@ final class AsyncResponse implements ResponseInterface, StreamableInterface
foreach (self::passthru($this->client, $this, new LastChunk()) as $chunk) {
// no-op
}
- } catch (ExceptionInterface $e) {
+ } catch (ExceptionInterface) {
// ignore any errors when destructing
}
}
diff --git a/vendor/symfony/http-client/Response/CommonResponseTrait.php b/vendor/symfony/http-client/Response/CommonResponseTrait.php
index 448b85424..06f6b7d88 100644
--- a/vendor/symfony/http-client/Response/CommonResponseTrait.php
+++ b/vendor/symfony/http-client/Response/CommonResponseTrait.php
@@ -35,9 +35,6 @@ trait CommonResponseTrait
private int $offset = 0;
private ?array $jsonData = null;
- /**
- * {@inheritdoc}
- */
public function getContent(bool $throw = true): string
{
if ($this->initializer) {
@@ -75,9 +72,6 @@ trait CommonResponseTrait
return stream_get_contents($this->content);
}
- /**
- * {@inheritdoc}
- */
public function toArray(bool $throw = true): array
{
if ('' === $content = $this->getContent($throw)) {
@@ -106,9 +100,6 @@ trait CommonResponseTrait
return $content;
}
- /**
- * {@inheritdoc}
- */
public function toStream(bool $throw = true)
{
if ($throw) {
diff --git a/vendor/symfony/http-client/Response/CurlResponse.php b/vendor/symfony/http-client/Response/CurlResponse.php
index ae18d5840..9c151546a 100644
--- a/vendor/symfony/http-client/Response/CurlResponse.php
+++ b/vendor/symfony/http-client/Response/CurlResponse.php
@@ -32,8 +32,7 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
}
use TransportResponseTrait;
- private static bool $performing = false;
- private $multi;
+ private CurlClientState $multi;
/**
* @var resource
@@ -43,7 +42,7 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
/**
* @internal
*/
- public function __construct(CurlClientState $multi, \CurlHandle|string $ch, array $options = null, LoggerInterface $logger = null, string $method = 'GET', callable $resolveRedirect = null, int $curlVersion = null)
+ public function __construct(CurlClientState $multi, \CurlHandle|string $ch, array $options = null, LoggerInterface $logger = null, string $method = 'GET', callable $resolveRedirect = null, int $curlVersion = null, string $originalUrl = null)
{
$this->multi = $multi;
@@ -68,7 +67,8 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
$this->info['http_method'] = $method;
$this->info['user_data'] = $options['user_data'] ?? null;
$this->info['max_duration'] = $options['max_duration'] ?? null;
- $this->info['start_time'] = $this->info['start_time'] ?? microtime(true);
+ $this->info['start_time'] ??= microtime(true);
+ $this->info['original_url'] = $originalUrl ?? $this->info['url'] ?? curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL);
$info = &$this->info;
$headers = &$this->headers;
$debugBuffer = $this->debugBuffer;
@@ -79,17 +79,7 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
}
curl_setopt($ch, \CURLOPT_HEADERFUNCTION, static function ($ch, string $data) use (&$info, &$headers, $options, $multi, $id, &$location, $resolveRedirect, $logger): int {
- if (0 !== substr_compare($data, "\r\n", -2)) {
- return 0;
- }
-
- $len = 0;
-
- foreach (explode("\r\n", substr($data, 0, -2)) as $data) {
- $len += 2 + self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger);
- }
-
- return $len;
+ return self::parseHeaderLine($ch, $data, $info, $headers, $options, $multi, $id, $location, $resolveRedirect, $logger);
});
if (null === $options) {
@@ -181,7 +171,7 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
unset($multi->pauseExpiries[$id], $multi->openHandles[$id], $multi->handlesActivity[$id]);
curl_setopt($ch, \CURLOPT_PRIVATE, '_0');
- if (self::$performing) {
+ if ($multi->performing) {
return;
}
@@ -203,9 +193,6 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
});
}
- /**
- * {@inheritdoc}
- */
public function getInfo(string $type = null): mixed
{
if (!$info = $this->finalInfo) {
@@ -234,18 +221,15 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
return null !== $type ? $info[$type] ?? null : $info;
}
- /**
- * {@inheritdoc}
- */
public function getContent(bool $throw = true): string
{
- $performing = self::$performing;
- self::$performing = $performing || '_0' === curl_getinfo($this->handle, \CURLINFO_PRIVATE);
+ $performing = $this->multi->performing;
+ $this->multi->performing = $performing || '_0' === curl_getinfo($this->handle, \CURLINFO_PRIVATE);
try {
return $this->doGetContent($throw);
} finally {
- self::$performing = $performing;
+ $this->multi->performing = $performing;
}
}
@@ -264,9 +248,6 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
}
}
- /**
- * {@inheritdoc}
- */
private static function schedule(self $response, array &$runningResponses): void
{
if (isset($runningResponses[$i = (int) $response->multi->handle])) {
@@ -283,13 +264,11 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
}
/**
- * {@inheritdoc}
- *
* @param CurlClientState $multi
*/
private static function perform(ClientState $multi, array &$responses = null): void
{
- if (self::$performing) {
+ if ($multi->performing) {
if ($responses) {
$response = current($responses);
$multi->handlesActivity[(int) $response->handle][] = null;
@@ -300,7 +279,7 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
}
try {
- self::$performing = true;
+ $multi->performing = true;
++$multi->execCounter;
$active = 0;
while (\CURLM_CALL_MULTI_PERFORM === ($err = curl_multi_exec($multi->handle, $active))) {
@@ -337,13 +316,11 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
$multi->handlesActivity[$id][] = \in_array($result, [\CURLE_OK, \CURLE_TOO_MANY_REDIRECTS], true) || '_0' === $waitFor || curl_getinfo($ch, \CURLINFO_SIZE_DOWNLOAD) === curl_getinfo($ch, \CURLINFO_CONTENT_LENGTH_DOWNLOAD) ? null : new TransportException(ucfirst(curl_error($ch) ?: curl_strerror($result)).sprintf(' for "%s".', curl_getinfo($ch, \CURLINFO_EFFECTIVE_URL)));
}
} finally {
- self::$performing = false;
+ $multi->performing = false;
}
}
/**
- * {@inheritdoc}
- *
* @param CurlClientState $multi
*/
private static function select(ClientState $multi, float $timeout): int
@@ -379,19 +356,29 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
*/
private static function parseHeaderLine($ch, string $data, array &$info, array &$headers, ?array $options, CurlClientState $multi, int $id, ?string &$location, ?callable $resolveRedirect, ?LoggerInterface $logger): int
{
+ if (!str_ends_with($data, "\r\n")) {
+ return 0;
+ }
+
$waitFor = @curl_getinfo($ch, \CURLINFO_PRIVATE) ?: '_0';
if ('H' !== $waitFor[0]) {
return \strlen($data); // Ignore HTTP trailers
}
- if ('' !== $data) {
+ $statusCode = curl_getinfo($ch, \CURLINFO_RESPONSE_CODE);
+
+ if ($statusCode !== $info['http_code'] && !preg_match("#^HTTP/\d+(?:\.\d+)? {$statusCode}(?: |\r\n$)#", $data)) {
+ return \strlen($data); // Ignore headers from responses to CONNECT requests
+ }
+
+ if ("\r\n" !== $data) {
// Regular header line: add it to the list
- self::addResponseHeaders([$data], $info, $headers);
+ self::addResponseHeaders([substr($data, 0, -2)], $info, $headers);
if (!str_starts_with($data, 'HTTP/')) {
if (0 === stripos($data, 'Location:')) {
- $location = trim(substr($data, 9));
+ $location = trim(substr($data, 9, -2));
}
return \strlen($data);
@@ -414,7 +401,7 @@ final class CurlResponse implements ResponseInterface, StreamableInterface
// End of headers: handle informational responses, redirects, etc.
- if (200 > $statusCode = curl_getinfo($ch, \CURLINFO_RESPONSE_CODE)) {
+ if (200 > $statusCode) {
$multi->handlesActivity[$id][] = new InformationalChunk($statusCode, $headers);
$location = null;
diff --git a/vendor/symfony/http-client/Response/HttplugPromise.php b/vendor/symfony/http-client/Response/HttplugPromise.php
index 09ed10df3..deaea720d 100644
--- a/vendor/symfony/http-client/Response/HttplugPromise.php
+++ b/vendor/symfony/http-client/Response/HttplugPromise.php
@@ -23,7 +23,7 @@ use Psr\Http\Message\ResponseInterface as Psr7ResponseInterface;
*/
final class HttplugPromise implements HttplugPromiseInterface
{
- private $promise;
+ private GuzzlePromiseInterface $promise;
public function __construct(GuzzlePromiseInterface $promise)
{
@@ -43,17 +43,12 @@ final class HttplugPromise implements HttplugPromiseInterface
$this->promise->cancel();
}
- /**
- * {@inheritdoc}
- */
public function getState(): string
{
return $this->promise->getState();
}
/**
- * {@inheritdoc}
- *
* @return Psr7ResponseInterface|mixed
*/
public function wait($unwrap = true): mixed
diff --git a/vendor/symfony/http-client/Response/MockResponse.php b/vendor/symfony/http-client/Response/MockResponse.php
index 496b7bee5..24089013f 100644
--- a/vendor/symfony/http-client/Response/MockResponse.php
+++ b/vendor/symfony/http-client/Response/MockResponse.php
@@ -39,9 +39,9 @@ class MockResponse implements ResponseInterface, StreamableInterface
private static int $idSequence = 0;
/**
- * @param string|string[]|iterable $body The response body as a string or an iterable of strings,
- * yielding an empty string simulates an idle timeout,
- * throwing an exception yields an ErrorChunk
+ * @param string|iterable $body The response body as a string or an iterable of strings,
+ * yielding an empty string simulates an idle timeout,
+ * throwing or yielding an exception yields an ErrorChunk
*
* @see ResponseInterface::getInfo() for possible info, e.g. "response_headers"
*/
@@ -90,17 +90,11 @@ class MockResponse implements ResponseInterface, StreamableInterface
return $this->requestMethod;
}
- /**
- * {@inheritdoc}
- */
public function getInfo(string $type = null): mixed
{
return null !== $type ? $this->info[$type] ?? null : $this->info;
}
- /**
- * {@inheritdoc}
- */
public function cancel(): void
{
$this->info['canceled'] = true;
@@ -110,11 +104,12 @@ class MockResponse implements ResponseInterface, StreamableInterface
} catch (TransportException $e) {
// ignore errors when canceling
}
+
+ $onProgress = $this->requestOptions['on_progress'] ?? static function () {};
+ $dlSize = isset($this->headers['content-encoding']) || 'HEAD' === ($this->info['http_method'] ?? null) || \in_array($this->info['http_code'], [204, 304], true) ? 0 : (int) ($this->headers['content-length'][0] ?? 0);
+ $onProgress($this->offset, $dlSize, $this->info);
}
- /**
- * {@inheritdoc}
- */
protected function close(): void
{
$this->inflate = null;
@@ -142,6 +137,7 @@ class MockResponse implements ResponseInterface, StreamableInterface
$response->info['user_data'] = $options['user_data'] ?? null;
$response->info['max_duration'] = $options['max_duration'] ?? null;
$response->info['url'] = $url;
+ $response->info['original_url'] = $url;
if ($mock instanceof self) {
$mock->requestOptions = $response->requestOptions;
@@ -155,16 +151,13 @@ class MockResponse implements ResponseInterface, StreamableInterface
return $response;
}
- /**
- * {@inheritdoc}
- */
protected static function schedule(self $response, array &$runningResponses): void
{
if (!isset($response->id)) {
throw new InvalidArgumentException('MockResponse instances must be issued by MockHttpClient before processing.');
}
- $multi = self::$mainMulti ?? self::$mainMulti = new ClientState();
+ $multi = self::$mainMulti ??= new ClientState();
if (!isset($runningResponses[0])) {
$runningResponses[0] = [$multi, []];
@@ -173,9 +166,6 @@ class MockResponse implements ResponseInterface, StreamableInterface
$runningResponses[0][1][$response->id] = $response;
}
- /**
- * {@inheritdoc}
- */
protected static function perform(ClientState $multi, array &$responses): void
{
foreach ($responses as $response) {
@@ -219,9 +209,6 @@ class MockResponse implements ResponseInterface, StreamableInterface
}
}
- /**
- * {@inheritdoc}
- */
protected static function select(ClientState $multi, float $timeout): int
{
return 42;
@@ -230,7 +217,7 @@ class MockResponse implements ResponseInterface, StreamableInterface
/**
* Simulates sending the request.
*/
- private static function writeRequest(self $response, array $options, ResponseInterface $mock)
+ private static function writeRequest(self $response, array $options, ResponseInterface $mock): void
{
$onProgress = $options['on_progress'] ?? static function () {};
$response->info += $mock->getInfo() ?: [];
@@ -273,7 +260,7 @@ class MockResponse implements ResponseInterface, StreamableInterface
/**
* Simulates reading the response.
*/
- private static function readResponse(self $response, array $options, ResponseInterface $mock, int &$offset)
+ private static function readResponse(self $response, array $options, ResponseInterface $mock, int &$offset): void
{
$onProgress = $options['on_progress'] ?? static function () {};
@@ -307,6 +294,10 @@ class MockResponse implements ResponseInterface, StreamableInterface
if (!\is_string($body)) {
try {
foreach ($body as $chunk) {
+ if ($chunk instanceof \Throwable) {
+ throw $chunk;
+ }
+
if ('' === $chunk = (string) $chunk) {
// simulate an idle timeout
$response->body[] = new ErrorChunk($offset, sprintf('Idle timeout reached for "%s".', $response->info['url']));
diff --git a/vendor/symfony/http-client/Response/NativeResponse.php b/vendor/symfony/http-client/Response/NativeResponse.php
index 96f1256a1..a6b9dd989 100644
--- a/vendor/symfony/http-client/Response/NativeResponse.php
+++ b/vendor/symfony/http-client/Response/NativeResponse.php
@@ -43,7 +43,7 @@ final class NativeResponse implements ResponseInterface, StreamableInterface
*/
private $buffer;
- private $multi;
+ private NativeClientState $multi;
private float $pauseExpiry = 0.0;
/**
@@ -66,6 +66,7 @@ final class NativeResponse implements ResponseInterface, StreamableInterface
// Temporary resource to dechunk the response stream
$this->buffer = fopen('php://temp', 'w+');
+ $info['original_url'] = implode('', $info['url']);
$info['user_data'] = $options['user_data'];
$info['max_duration'] = $options['max_duration'];
++$multi->responseCount;
@@ -87,9 +88,6 @@ final class NativeResponse implements ResponseInterface, StreamableInterface
});
}
- /**
- * {@inheritdoc}
- */
public function getInfo(string $type = null): mixed
{
if (!$info = $this->finalInfo) {
@@ -127,7 +125,7 @@ final class NativeResponse implements ResponseInterface, StreamableInterface
throw new TransportException($msg);
}
- $this->logger && $this->logger->info(sprintf('%s for "%s".', $msg, $url ?? $this->url));
+ $this->logger?->info(sprintf('%s for "%s".', $msg, $url ?? $this->url));
});
try {
@@ -163,7 +161,7 @@ final class NativeResponse implements ResponseInterface, StreamableInterface
break;
}
- $this->logger && $this->logger->info(sprintf('Redirecting: "%s %s"', $this->info['http_code'], $url ?? $this->url));
+ $this->logger?->info(sprintf('Redirecting: "%s %s"', $this->info['http_code'], $url ?? $this->url));
}
} catch (\Throwable $e) {
$this->close();
@@ -208,18 +206,12 @@ final class NativeResponse implements ResponseInterface, StreamableInterface
$this->multi->hosts[$host] = 1 + ($this->multi->hosts[$host] ?? 0);
}
- /**
- * {@inheritdoc}
- */
private function close(): void
{
$this->canary->cancel();
$this->handle = $this->buffer = $this->inflate = $this->onProgress = null;
}
- /**
- * {@inheritdoc}
- */
private static function schedule(self $response, array &$runningResponses): void
{
if (!isset($runningResponses[$i = $response->multi->id])) {
@@ -236,8 +228,6 @@ final class NativeResponse implements ResponseInterface, StreamableInterface
}
/**
- * {@inheritdoc}
- *
* @param NativeClientState $multi
*/
private static function perform(ClientState $multi, array &$responses = null): void
@@ -347,8 +337,6 @@ final class NativeResponse implements ResponseInterface, StreamableInterface
}
/**
- * {@inheritdoc}
- *
* @param NativeClientState $multi
*/
private static function select(ClientState $multi, float $timeout): int
@@ -365,7 +353,7 @@ final class NativeResponse implements ResponseInterface, StreamableInterface
continue;
}
- if ($pauseExpiry && ($now ?? $now = microtime(true)) < $pauseExpiry) {
+ if ($pauseExpiry && ($now ??= microtime(true)) < $pauseExpiry) {
$timeout = min($timeout, $pauseExpiry - $now);
continue;
}
diff --git a/vendor/symfony/http-client/Response/StreamWrapper.php b/vendor/symfony/http-client/Response/StreamWrapper.php
index 3768e2e06..f891313dd 100644
--- a/vendor/symfony/http-client/Response/StreamWrapper.php
+++ b/vendor/symfony/http-client/Response/StreamWrapper.php
@@ -25,9 +25,9 @@ class StreamWrapper
/** @var resource|null */
public $context;
- private $client;
+ private HttpClientInterface|ResponseInterface $client;
- private $response;
+ private ResponseInterface $response;
/** @var resource|string|null */
private $content;
@@ -116,7 +116,7 @@ class StreamWrapper
return false;
}
- public function stream_read(int $count)
+ public function stream_read(int $count): string|false
{
if (\is_resource($this->content)) {
// Empty the internal activity list
@@ -174,9 +174,7 @@ class StreamWrapper
if ('' !== $data = $chunk->getContent()) {
if (\strlen($data) > $count) {
- if (null === $this->content) {
- $this->content = substr($data, $count);
- }
+ $this->content ??= substr($data, $count);
$data = substr($data, 0, $count);
}
$this->offset += \strlen($data);
diff --git a/vendor/symfony/http-client/Response/TraceableResponse.php b/vendor/symfony/http-client/Response/TraceableResponse.php
index f8a7ceedc..507799e14 100644
--- a/vendor/symfony/http-client/Response/TraceableResponse.php
+++ b/vendor/symfony/http-client/Response/TraceableResponse.php
@@ -31,10 +31,10 @@ use Symfony\Contracts\HttpClient\ResponseInterface;
*/
class TraceableResponse implements ResponseInterface, StreamableInterface
{
- private $client;
- private $response;
+ private HttpClientInterface $client;
+ private ResponseInterface $response;
private mixed $content;
- private $event;
+ private ?StopwatchEvent $event;
public function __construct(HttpClientInterface $client, ResponseInterface $response, &$content, StopwatchEvent $event = null)
{
@@ -59,7 +59,7 @@ class TraceableResponse implements ResponseInterface, StreamableInterface
try {
$this->response->__destruct();
} finally {
- if ($this->event && $this->event->isStarted()) {
+ if ($this->event?->isStarted()) {
$this->event->stop();
}
}
@@ -70,7 +70,7 @@ class TraceableResponse implements ResponseInterface, StreamableInterface
try {
return $this->response->getStatusCode();
} finally {
- if ($this->event && $this->event->isStarted()) {
+ if ($this->event?->isStarted()) {
$this->event->lap();
}
}
@@ -81,7 +81,7 @@ class TraceableResponse implements ResponseInterface, StreamableInterface
try {
return $this->response->getHeaders($throw);
} finally {
- if ($this->event && $this->event->isStarted()) {
+ if ($this->event?->isStarted()) {
$this->event->lap();
}
}
@@ -96,7 +96,7 @@ class TraceableResponse implements ResponseInterface, StreamableInterface
return $this->content = $this->response->getContent(false);
} finally {
- if ($this->event && $this->event->isStarted()) {
+ if ($this->event?->isStarted()) {
$this->event->stop();
}
if ($throw) {
@@ -114,7 +114,7 @@ class TraceableResponse implements ResponseInterface, StreamableInterface
return $this->content = $this->response->toArray(false);
} finally {
- if ($this->event && $this->event->isStarted()) {
+ if ($this->event?->isStarted()) {
$this->event->stop();
}
if ($throw) {
@@ -127,7 +127,7 @@ class TraceableResponse implements ResponseInterface, StreamableInterface
{
$this->response->cancel();
- if ($this->event && $this->event->isStarted()) {
+ if ($this->event?->isStarted()) {
$this->event->stop();
}
}
@@ -202,7 +202,7 @@ class TraceableResponse implements ResponseInterface, StreamableInterface
}
}
- private function checkStatusCode(int $code)
+ private function checkStatusCode(int $code): void
{
if (500 <= $code) {
throw new ServerException($this);
diff --git a/vendor/symfony/http-client/Response/TransportResponseTrait.php b/vendor/symfony/http-client/Response/TransportResponseTrait.php
index 802ba83f2..ac53b99a6 100644
--- a/vendor/symfony/http-client/Response/TransportResponseTrait.php
+++ b/vendor/symfony/http-client/Response/TransportResponseTrait.php
@@ -11,11 +11,13 @@
namespace Symfony\Component\HttpClient\Response;
+use Psr\Log\LoggerInterface;
use Symfony\Component\HttpClient\Chunk\DataChunk;
use Symfony\Component\HttpClient\Chunk\ErrorChunk;
use Symfony\Component\HttpClient\Chunk\FirstChunk;
use Symfony\Component\HttpClient\Chunk\LastChunk;
use Symfony\Component\HttpClient\Exception\TransportException;
+use Symfony\Component\HttpClient\Internal\Canary;
use Symfony\Component\HttpClient\Internal\ClientState;
/**
@@ -27,7 +29,7 @@ use Symfony\Component\HttpClient\Internal\ClientState;
*/
trait TransportResponseTrait
{
- private $canary;
+ private Canary $canary;
private array $headers = [];
private array $info = [
'response_headers' => [],
@@ -40,13 +42,10 @@ trait TransportResponseTrait
private $handle;
private int|string $id;
private ?float $timeout = 0;
- private $inflate = null;
+ private \InflateContext|bool|null $inflate = null;
private ?array $finalInfo = null;
- private $logger = null;
+ private ?LoggerInterface $logger = null;
- /**
- * {@inheritdoc}
- */
public function getStatusCode(): int
{
if ($this->initializer) {
@@ -56,9 +55,6 @@ trait TransportResponseTrait
return $this->info['http_code'];
}
- /**
- * {@inheritdoc}
- */
public function getHeaders(bool $throw = true): array
{
if ($this->initializer) {
@@ -72,9 +68,6 @@ trait TransportResponseTrait
return $this->headers;
}
- /**
- * {@inheritdoc}
- */
public function cancel(): void
{
$this->info['canceled'] = true;
@@ -129,7 +122,7 @@ trait TransportResponseTrait
/**
* Ensures the request is always sent and that the response code was checked.
*/
- private function doDestruct()
+ private function doDestruct(): void
{
$this->shouldBuffer = true;
@@ -189,7 +182,7 @@ trait TransportResponseTrait
continue;
} elseif ($elapsedTimeout >= $timeoutMax) {
$multi->handlesActivity[$j] = [new ErrorChunk($response->offset, sprintf('Idle timeout reached for "%s".', $response->getInfo('url')))];
- $multi->lastTimeout ?? $multi->lastTimeout = $lastActivity;
+ $multi->lastTimeout ??= $lastActivity;
} else {
continue;
}
diff --git a/vendor/symfony/http-client/Retry/GenericRetryStrategy.php b/vendor/symfony/http-client/Retry/GenericRetryStrategy.php
index 9d1473c34..edbf2c066 100644
--- a/vendor/symfony/http-client/Retry/GenericRetryStrategy.php
+++ b/vendor/symfony/http-client/Retry/GenericRetryStrategy.php
@@ -102,7 +102,7 @@ class GenericRetryStrategy implements RetryStrategyInterface
$delay = $this->delayMs * $this->multiplier ** $context->getInfo('retry_count');
if ($this->jitter > 0) {
- $randomness = $delay * $this->jitter;
+ $randomness = (int) ($delay * $this->jitter);
$delay = $delay + random_int(-$randomness, +$randomness);
}
diff --git a/vendor/symfony/http-client/RetryableHttpClient.php b/vendor/symfony/http-client/RetryableHttpClient.php
index 71d0a8cf5..d0c13165b 100644
--- a/vendor/symfony/http-client/RetryableHttpClient.php
+++ b/vendor/symfony/http-client/RetryableHttpClient.php
@@ -32,9 +32,9 @@ class RetryableHttpClient implements HttpClientInterface, ResetInterface
{
use AsyncDecoratorTrait;
- private $strategy;
+ private RetryStrategyInterface $strategy;
private int $maxRetries;
- private $logger;
+ private LoggerInterface $logger;
/**
* @param int $maxRetries The maximum number of times to retry
diff --git a/vendor/symfony/http-client/ScopingHttpClient.php b/vendor/symfony/http-client/ScopingHttpClient.php
index 74bb82045..da8ef4c98 100644
--- a/vendor/symfony/http-client/ScopingHttpClient.php
+++ b/vendor/symfony/http-client/ScopingHttpClient.php
@@ -28,7 +28,7 @@ class ScopingHttpClient implements HttpClientInterface, ResetInterface, LoggerAw
{
use HttpClientTrait;
- private $client;
+ private HttpClientInterface $client;
private array $defaultOptionsByRegexp;
private ?string $defaultRegexp;
@@ -45,18 +45,13 @@ class ScopingHttpClient implements HttpClientInterface, ResetInterface, LoggerAw
public static function forBaseUri(HttpClientInterface $client, string $baseUri, array $defaultOptions = [], string $regexp = null): self
{
- if (null === $regexp) {
- $regexp = preg_quote(implode('', self::resolveUrl(self::parseUrl('.'), self::parseUrl($baseUri))));
- }
+ $regexp ??= preg_quote(implode('', self::resolveUrl(self::parseUrl('.'), self::parseUrl($baseUri))));
$defaultOptions['base_uri'] = $baseUri;
return new self($client, [$regexp => $defaultOptions], $regexp);
}
- /**
- * {@inheritdoc}
- */
public function request(string $method, string $url, array $options = []): ResponseInterface
{
$e = null;
@@ -93,9 +88,6 @@ class ScopingHttpClient implements HttpClientInterface, ResetInterface, LoggerAw
return $this->client->request($method, $url, $options);
}
- /**
- * {@inheritdoc}
- */
public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface
{
return $this->client->stream($responses, $timeout);
@@ -108,9 +100,6 @@ class ScopingHttpClient implements HttpClientInterface, ResetInterface, LoggerAw
}
}
- /**
- * {@inheritdoc}
- */
public function setLogger(LoggerInterface $logger): void
{
if ($this->client instanceof LoggerAwareInterface) {
@@ -118,9 +107,6 @@ class ScopingHttpClient implements HttpClientInterface, ResetInterface, LoggerAw
}
}
- /**
- * {@inheritdoc}
- */
public function withOptions(array $options): static
{
$clone = clone $this;
diff --git a/vendor/symfony/http-client/TraceableHttpClient.php b/vendor/symfony/http-client/TraceableHttpClient.php
index bca2859d4..b9df9732a 100644
--- a/vendor/symfony/http-client/TraceableHttpClient.php
+++ b/vendor/symfony/http-client/TraceableHttpClient.php
@@ -26,8 +26,8 @@ use Symfony\Contracts\Service\ResetInterface;
*/
final class TraceableHttpClient implements HttpClientInterface, ResetInterface, LoggerAwareInterface
{
- private $client;
- private $stopwatch;
+ private HttpClientInterface $client;
+ private ?Stopwatch $stopwatch;
private \ArrayObject $tracedRequests;
public function __construct(HttpClientInterface $client, Stopwatch $stopwatch = null)
@@ -37,9 +37,6 @@ final class TraceableHttpClient implements HttpClientInterface, ResetInterface,
$this->tracedRequests = new \ArrayObject();
}
- /**
- * {@inheritdoc}
- */
public function request(string $method, string $url, array $options = []): ResponseInterface
{
$content = null;
@@ -66,12 +63,9 @@ final class TraceableHttpClient implements HttpClientInterface, ResetInterface,
}
};
- return new TraceableResponse($this->client, $this->client->request($method, $url, $options), $content, null === $this->stopwatch ? null : $this->stopwatch->start("$method $url", 'http_client'));
+ return new TraceableResponse($this->client, $this->client->request($method, $url, $options), $content, $this->stopwatch?->start("$method $url", 'http_client'));
}
- /**
- * {@inheritdoc}
- */
public function stream(ResponseInterface|iterable $responses, float $timeout = null): ResponseStreamInterface
{
if ($responses instanceof TraceableResponse) {
@@ -95,9 +89,6 @@ final class TraceableHttpClient implements HttpClientInterface, ResetInterface,
$this->tracedRequests->exchangeArray([]);
}
- /**
- * {@inheritdoc}
- */
public function setLogger(LoggerInterface $logger): void
{
if ($this->client instanceof LoggerAwareInterface) {
@@ -105,9 +96,6 @@ final class TraceableHttpClient implements HttpClientInterface, ResetInterface,
}
}
- /**
- * {@inheritdoc}
- */
public function withOptions(array $options): static
{
$clone = clone $this;
diff --git a/vendor/symfony/http-client/composer.json b/vendor/symfony/http-client/composer.json
index 02a7653b5..34a1714bf 100644
--- a/vendor/symfony/http-client/composer.json
+++ b/vendor/symfony/http-client/composer.json
@@ -2,6 +2,7 @@
"name": "symfony/http-client",
"type": "library",
"description": "Provides powerful methods to fetch HTTP resources synchronously or asynchronously",
+ "keywords": ["http"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
@@ -21,8 +22,9 @@
"symfony/http-client-implementation": "3.0"
},
"require": {
- "php": ">=8.0.2",
+ "php": ">=8.1",
"psr/log": "^1|^2|^3",
+ "symfony/deprecation-contracts": "^2.1|^3",
"symfony/http-client-contracts": "^3",
"symfony/service-contracts": "^1.0|^2|^3"
},
@@ -34,6 +36,7 @@
"guzzlehttp/promises": "^1.4",
"nyholm/psr7": "^1.0",
"php-http/httplug": "^1.0|^2.0",
+ "php-http/message-factory": "^1.0",
"psr/http-client": "^1.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/http-kernel": "^5.4|^6.0",
diff --git a/vendor/symfony/http-foundation/BinaryFileResponse.php b/vendor/symfony/http-foundation/BinaryFileResponse.php
index 8990c9111..08bd03bf6 100644
--- a/vendor/symfony/http-foundation/BinaryFileResponse.php
+++ b/vendor/symfony/http-foundation/BinaryFileResponse.php
@@ -224,7 +224,7 @@ class BinaryFileResponse extends Response
$parts = HeaderUtils::split($request->headers->get('X-Accel-Mapping', ''), ',=');
foreach ($parts as $part) {
[$pathPrefix, $location] = $part;
- if (substr($path, 0, \strlen($pathPrefix)) === $pathPrefix) {
+ if (str_starts_with($path, $pathPrefix)) {
$path = $location.substr($path, \strlen($pathPrefix));
// Only set X-Accel-Redirect header if a valid URI can be produced
// as nginx does not serve arbitrary file paths.
diff --git a/vendor/symfony/http-foundation/CHANGELOG.md b/vendor/symfony/http-foundation/CHANGELOG.md
index b528419d3..fdbd39cea 100644
--- a/vendor/symfony/http-foundation/CHANGELOG.md
+++ b/vendor/symfony/http-foundation/CHANGELOG.md
@@ -1,6 +1,12 @@
CHANGELOG
=========
+6.1
+---
+
+ * Add stale while revalidate and stale if error cache header
+ * Allow dynamic session "ttl" when using a remote storage
+
6.0
---
diff --git a/vendor/symfony/http-foundation/Cookie.php b/vendor/symfony/http-foundation/Cookie.php
index fb2853046..b6166d70e 100644
--- a/vendor/symfony/http-foundation/Cookie.php
+++ b/vendor/symfony/http-foundation/Cookie.php
@@ -87,7 +87,7 @@ class Cookie
*
* @throws \InvalidArgumentException
*/
- public function __construct(string $name, string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = 'lax')
+ public function __construct(string $name, string $value = null, int|string|\DateTimeInterface $expire = 0, ?string $path = '/', string $domain = null, bool $secure = null, bool $httpOnly = true, bool $raw = false, ?string $sameSite = self::SAMESITE_LAX)
{
// from PHP source code
if ($raw && false !== strpbrk($name, self::RESERVED_CHARS_LIST)) {
@@ -244,12 +244,12 @@ class Cookie
$str .= '=';
if ('' === (string) $this->getValue()) {
- $str .= 'deleted; expires='.gmdate('D, d-M-Y H:i:s T', time() - 31536001).'; Max-Age=0';
+ $str .= 'deleted; expires='.gmdate('D, d M Y H:i:s T', time() - 31536001).'; Max-Age=0';
} else {
$str .= $this->isRaw() ? $this->getValue() : rawurlencode($this->getValue());
if (0 !== $this->getExpiresTime()) {
- $str .= '; expires='.gmdate('D, d-M-Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge();
+ $str .= '; expires='.gmdate('D, d M Y H:i:s T', $this->getExpiresTime()).'; Max-Age='.$this->getMaxAge();
}
}
diff --git a/vendor/symfony/http-foundation/ExpressionRequestMatcher.php b/vendor/symfony/http-foundation/ExpressionRequestMatcher.php
index 9dbf2f743..f2a07307d 100644
--- a/vendor/symfony/http-foundation/ExpressionRequestMatcher.php
+++ b/vendor/symfony/http-foundation/ExpressionRequestMatcher.php
@@ -21,8 +21,8 @@ use Symfony\Component\ExpressionLanguage\ExpressionLanguage;
*/
class ExpressionRequestMatcher extends RequestMatcher
{
- private $language;
- private $expression;
+ private ExpressionLanguage $language;
+ private Expression|string $expression;
public function setExpression(ExpressionLanguage $language, Expression|string $expression)
{
diff --git a/vendor/symfony/http-foundation/IpUtils.php b/vendor/symfony/http-foundation/IpUtils.php
index 51c698e4e..8f78d1b1d 100644
--- a/vendor/symfony/http-foundation/IpUtils.php
+++ b/vendor/symfony/http-foundation/IpUtils.php
@@ -168,7 +168,7 @@ class IpUtils
public static function anonymize(string $ip): string
{
$wrappedIPv6 = false;
- if ('[' === substr($ip, 0, 1) && ']' === substr($ip, -1, 1)) {
+ if (str_starts_with($ip, '[') && str_ends_with($ip, ']')) {
$wrappedIPv6 = true;
$ip = substr($ip, 1, -1);
}
diff --git a/vendor/symfony/http-foundation/README.md b/vendor/symfony/http-foundation/README.md
index 424f2c4f0..5cf900744 100644
--- a/vendor/symfony/http-foundation/README.md
+++ b/vendor/symfony/http-foundation/README.md
@@ -4,16 +4,6 @@ HttpFoundation Component
The HttpFoundation component defines an object-oriented layer for the HTTP
specification.
-Sponsor
--------
-
-The HttpFoundation component for Symfony 5.4/6.0 is [backed][1] by [Laravel][2].
-
-Laravel is a PHP web development framework that is passionate about maximum developer
-happiness. Laravel is built using a variety of bespoke and Symfony based components.
-
-Help Symfony by [sponsoring][3] its development!
-
Resources
---------
@@ -22,7 +12,3 @@ Resources
* [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)
-
-[1]: https://symfony.com/backers
-[2]: https://laravel.com/
-[3]: https://symfony.com/sponsor
diff --git a/vendor/symfony/http-foundation/Request.php b/vendor/symfony/http-foundation/Request.php
index a20e9af15..b39b9c062 100644
--- a/vendor/symfony/http-foundation/Request.php
+++ b/vendor/symfony/http-foundation/Request.php
@@ -690,6 +690,8 @@ class Request
/**
* Gets the Session.
+ *
+ * @throws SessionNotFoundException When session is not set properly
*/
public function getSession(): SessionInterface
{
@@ -1874,7 +1876,7 @@ class Request
if (class_exists(\Locale::class, false)) {
\Locale::setDefault($locale);
}
- } catch (\Exception $e) {
+ } catch (\Exception) {
}
}
diff --git a/vendor/symfony/http-foundation/Response.php b/vendor/symfony/http-foundation/Response.php
index c48efdfb7..80fedc19b 100644
--- a/vendor/symfony/http-foundation/Response.php
+++ b/vendor/symfony/http-foundation/Response.php
@@ -98,6 +98,8 @@ class Response
'proxy_revalidate' => false,
'max_age' => true,
's_maxage' => true,
+ 'stale_if_error' => true, // RFC5861
+ 'stale_while_revalidate' => true, // RFC5861
'immutable' => false,
'last_modified' => true,
'etag' => true,
@@ -699,7 +701,7 @@ class Response
{
try {
return $this->headers->getDate('Expires');
- } catch (\RuntimeException $e) {
+ } catch (\RuntimeException) {
// according to RFC 2616 invalid date formats (e.g. "0" and "-1") must be treated as in the past
return \DateTime::createFromFormat('U', time() - 172800);
}
@@ -774,6 +776,38 @@ class Response
return $this;
}
+ /**
+ * Sets the number of seconds after which the response should no longer be returned by shared caches when backend is down.
+ *
+ * This method sets the Cache-Control stale-if-error directive.
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setStaleIfError(int $value): static
+ {
+ $this->headers->addCacheControlDirective('stale-if-error', $value);
+
+ return $this;
+ }
+
+ /**
+ * Sets the number of seconds after which the response should no longer return stale content by shared caches.
+ *
+ * This method sets the Cache-Control stale-while-revalidate directive.
+ *
+ * @return $this
+ *
+ * @final
+ */
+ public function setStaleWhileRevalidate(int $value): static
+ {
+ $this->headers->addCacheControlDirective('stale-while-revalidate', $value);
+
+ return $this;
+ }
+
/**
* Sets the number of seconds after which the response should no longer be considered fresh by shared caches.
*
@@ -947,6 +981,14 @@ class Response
$this->setSharedMaxAge($options['s_maxage']);
}
+ if (isset($options['stale_while_revalidate'])) {
+ $this->setStaleWhileRevalidate($options['stale_while_revalidate']);
+ }
+
+ if (isset($options['stale_if_error'])) {
+ $this->setStaleIfError($options['stale_if_error']);
+ }
+
foreach (self::HTTP_RESPONSE_CACHE_CONTROL_DIRECTIVES as $directive => $hasValue) {
if (!$hasValue && isset($options[$directive])) {
if ($options[$directive]) {
diff --git a/vendor/symfony/http-foundation/Session/Session.php b/vendor/symfony/http-foundation/Session/Session.php
index 3254f1885..4e17e7efc 100644
--- a/vendor/symfony/http-foundation/Session/Session.php
+++ b/vendor/symfony/http-foundation/Session/Session.php
@@ -43,13 +43,13 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable
public function __construct(SessionStorageInterface $storage = null, AttributeBagInterface $attributes = null, FlashBagInterface $flashes = null, callable $usageReporter = null)
{
$this->storage = $storage ?? new NativeSessionStorage();
- $this->usageReporter = $usageReporter instanceof \Closure || !\is_callable($usageReporter) ? $usageReporter : \Closure::fromCallable($usageReporter);
+ $this->usageReporter = null === $usageReporter ? null : $usageReporter(...);
- $attributes = $attributes ?? new AttributeBag();
+ $attributes ??= new AttributeBag();
$this->attributeName = $attributes->getName();
$this->registerBag($attributes);
- $flashes = $flashes ?? new FlashBag();
+ $flashes ??= new FlashBag();
$this->flashName = $flashes->getName();
$this->registerBag($flashes);
}
diff --git a/vendor/symfony/http-foundation/Session/SessionBagProxy.php b/vendor/symfony/http-foundation/Session/SessionBagProxy.php
index 1d405e3c7..76dfed724 100644
--- a/vendor/symfony/http-foundation/Session/SessionBagProxy.php
+++ b/vendor/symfony/http-foundation/Session/SessionBagProxy.php
@@ -18,7 +18,7 @@ namespace Symfony\Component\HttpFoundation\Session;
*/
final class SessionBagProxy implements SessionBagInterface
{
- private $bag;
+ private SessionBagInterface $bag;
private array $data;
private ?int $usageIndex;
private ?\Closure $usageReporter;
@@ -28,7 +28,7 @@ final class SessionBagProxy implements SessionBagInterface
$this->bag = $bag;
$this->data = &$data;
$this->usageIndex = &$usageIndex;
- $this->usageReporter = $usageReporter instanceof \Closure || !\is_callable($usageReporter) ? $usageReporter : \Closure::fromCallable($usageReporter);
+ $this->usageReporter = null === $usageReporter ? null : $usageReporter(...);
}
public function getBag(): SessionBagInterface
diff --git a/vendor/symfony/http-foundation/Session/SessionFactory.php b/vendor/symfony/http-foundation/Session/SessionFactory.php
index ffbd82cc4..cdb6af51e 100644
--- a/vendor/symfony/http-foundation/Session/SessionFactory.php
+++ b/vendor/symfony/http-foundation/Session/SessionFactory.php
@@ -22,15 +22,15 @@ class_exists(Session::class);
*/
class SessionFactory implements SessionFactoryInterface
{
- private $requestStack;
- private $storageFactory;
+ private RequestStack $requestStack;
+ private SessionStorageFactoryInterface $storageFactory;
private ?\Closure $usageReporter;
public function __construct(RequestStack $requestStack, SessionStorageFactoryInterface $storageFactory, callable $usageReporter = null)
{
$this->requestStack = $requestStack;
$this->storageFactory = $storageFactory;
- $this->usageReporter = $usageReporter instanceof \Closure || !\is_callable($usageReporter) ? $usageReporter : \Closure::fromCallable($usageReporter);
+ $this->usageReporter = null === $usageReporter ? null : $usageReporter(...);
}
public function createSession(): SessionInterface
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php
index 377d1e090..9962fef3d 100644
--- a/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MarshallingSessionHandler.php
@@ -18,8 +18,8 @@ use Symfony\Component\Cache\Marshaller\MarshallerInterface;
*/
class MarshallingSessionHandler implements \SessionHandlerInterface, \SessionUpdateTimestampHandlerInterface
{
- private $handler;
- private $marshaller;
+ private AbstractSessionHandler $handler;
+ private MarshallerInterface $marshaller;
public function __construct(AbstractSessionHandler $handler, MarshallerInterface $marshaller)
{
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php
index b61a6d0e9..04c1afdd3 100644
--- a/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MemcachedSessionHandler.php
@@ -21,12 +21,12 @@ namespace Symfony\Component\HttpFoundation\Session\Storage\Handler;
*/
class MemcachedSessionHandler extends AbstractSessionHandler
{
- private $memcached;
+ private \Memcached $memcached;
/**
* Time to live in seconds.
*/
- private ?int $ttl;
+ private int|\Closure|null $ttl;
/**
* Key prefix for shared environments.
@@ -84,7 +84,7 @@ class MemcachedSessionHandler extends AbstractSessionHandler
private function getCompatibleTtl(): int
{
- $ttl = (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime'));
+ $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime');
// If the relative TTL that is used exceeds 30 days, memcached will treat the value as Unix time.
// We have to convert it to an absolute Unix time at this point, to make sure the TTL is correct.
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php
index f4886dd9c..c95537777 100644
--- a/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/MongoDbSessionHandler.php
@@ -26,9 +26,10 @@ use MongoDB\Collection;
*/
class MongoDbSessionHandler extends AbstractSessionHandler
{
- private $mongo;
- private $collection;
+ private Client $mongo;
+ private Collection $collection;
private array $options;
+ private int|\Closure|null $ttl;
/**
* Constructor.
@@ -39,7 +40,8 @@ class MongoDbSessionHandler extends AbstractSessionHandler
* * id_field: The field name for storing the session id [default: _id]
* * data_field: The field name for storing the session data [default: data]
* * time_field: The field name for storing the timestamp [default: time]
- * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at].
+ * * expiry_field: The field name for storing the expiry-timestamp [default: expires_at]
+ * * ttl: The time to live in seconds.
*
* It is strongly recommended to put an index on the `expiry_field` for
* garbage-collection. Alternatively it's possible to automatically expire
@@ -74,6 +76,7 @@ class MongoDbSessionHandler extends AbstractSessionHandler
'time_field' => 'time',
'expiry_field' => 'expires_at',
], $options);
+ $this->ttl = $this->options['ttl'] ?? null;
}
public function close(): bool
@@ -105,7 +108,8 @@ class MongoDbSessionHandler extends AbstractSessionHandler
*/
protected function doWrite(string $sessionId, string $data): bool
{
- $expiry = new UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000);
+ $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime');
+ $expiry = new UTCDateTime((time() + (int) $ttl) * 1000);
$fields = [
$this->options['time_field'] => new UTCDateTime(),
@@ -124,7 +128,8 @@ class MongoDbSessionHandler extends AbstractSessionHandler
public function updateTimestamp(string $sessionId, string $data): bool
{
- $expiry = new UTCDateTime((time() + (int) \ini_get('session.gc_maxlifetime')) * 1000);
+ $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime');
+ $expiry = new UTCDateTime((time() + (int) $ttl) * 1000);
$this->getCollection()->updateOne(
[$this->options['id_field'] => $sessionId],
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php
index 40ac49d91..dd80145aa 100644
--- a/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/PdoSessionHandler.php
@@ -65,7 +65,7 @@ class PdoSessionHandler extends AbstractSessionHandler
*/
public const LOCK_TRANSACTIONAL = 2;
- private $pdo;
+ private \PDO $pdo;
/**
* DSN string or null for session.save_path or false when lazy connection disabled.
@@ -79,6 +79,11 @@ class PdoSessionHandler extends AbstractSessionHandler
private string $lifetimeCol = 'sess_lifetime';
private string $timeCol = 'sess_time';
+ /**
+ * Time to live in seconds.
+ */
+ private int|\Closure|null $ttl;
+
/**
* Username when lazy-connect.
*/
@@ -137,6 +142,7 @@ class PdoSessionHandler extends AbstractSessionHandler
* * db_password: The password when lazy-connect [default: '']
* * db_connection_options: An array of driver-specific connection options [default: []]
* * lock_mode: The strategy for locking, see constants [default: LOCK_TRANSACTIONAL]
+ * * ttl: The time to live in seconds.
*
* @param \PDO|string|null $pdoOrDsn A \PDO instance or DSN string or URL string or null
*
@@ -166,6 +172,7 @@ class PdoSessionHandler extends AbstractSessionHandler
$this->password = $options['db_password'] ?? $this->password;
$this->connectionOptions = $options['db_connection_options'] ?? $this->connectionOptions;
$this->lockMode = $options['lock_mode'] ?? $this->lockMode;
+ $this->ttl = $options['ttl'] ?? null;
}
/**
@@ -184,30 +191,19 @@ class PdoSessionHandler extends AbstractSessionHandler
// connect if we are not yet
$this->getConnection();
- switch ($this->driver) {
- case 'mysql':
- // We use varbinary for the ID column because it prevents unwanted conversions:
- // - character set conversions between server and client
- // - trailing space removal
- // - case-insensitivity
- // - language processing like é == e
- $sql = "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB";
- break;
- case 'sqlite':
- $sql = "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
- break;
- case 'pgsql':
- $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
- break;
- case 'oci':
- $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
- break;
- case 'sqlsrv':
- $sql = "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)";
- break;
- default:
- throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver));
- }
+ $sql = match ($this->driver) {
+ // We use varbinary for the ID column because it prevents unwanted conversions:
+ // - character set conversions between server and client
+ // - trailing space removal
+ // - case-insensitivity
+ // - language processing like é == e
+ 'mysql' => "CREATE TABLE $this->table ($this->idCol VARBINARY(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER UNSIGNED NOT NULL, $this->timeCol INTEGER UNSIGNED NOT NULL) COLLATE utf8mb4_bin, ENGINE = InnoDB",
+ 'sqlite' => "CREATE TABLE $this->table ($this->idCol TEXT NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)",
+ 'pgsql' => "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol BYTEA NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)",
+ 'oci' => "CREATE TABLE $this->table ($this->idCol VARCHAR2(128) NOT NULL PRIMARY KEY, $this->dataCol BLOB NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)",
+ 'sqlsrv' => "CREATE TABLE $this->table ($this->idCol VARCHAR(128) NOT NULL PRIMARY KEY, $this->dataCol VARBINARY(MAX) NOT NULL, $this->lifetimeCol INTEGER NOT NULL, $this->timeCol INTEGER NOT NULL)",
+ default => throw new \DomainException(sprintf('Creating the session table is currently not implemented for PDO driver "%s".', $this->driver)),
+ };
try {
$this->pdo->exec($sql);
@@ -286,7 +282,7 @@ class PdoSessionHandler extends AbstractSessionHandler
*/
protected function doWrite(string $sessionId, string $data): bool
{
- $maxlifetime = (int) \ini_get('session.gc_maxlifetime');
+ $maxlifetime = (int) (($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'));
try {
// We use a single MERGE SQL query when supported by the database.
@@ -329,7 +325,7 @@ class PdoSessionHandler extends AbstractSessionHandler
public function updateTimestamp(string $sessionId, string $data): bool
{
- $expiry = time() + (int) \ini_get('session.gc_maxlifetime');
+ $expiry = time() + (int) (($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime'));
try {
$updateStmt = $this->pdo->prepare(
@@ -454,7 +450,7 @@ class PdoSessionHandler extends AbstractSessionHandler
// If "unix_socket" is not in the query, we continue with the same process as pgsql
// no break
case 'pgsql':
- $dsn ?? $dsn = 'pgsql:';
+ $dsn ??= 'pgsql:';
if (isset($params['host']) && '' !== $params['host']) {
$dsn .= 'host='.$params['host'].';';
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php b/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php
index f9f3ce6ed..d4dc1b91a 100644
--- a/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/RedisSessionHandler.php
@@ -23,7 +23,7 @@ use Symfony\Component\Cache\Traits\RedisProxy;
*/
class RedisSessionHandler extends AbstractSessionHandler
{
- private $redis;
+ private \Redis|\RedisArray|\RedisCluster|\Predis\ClientInterface|RedisProxy|RedisClusterProxy $redis;
/**
* Key prefix for shared environments.
@@ -33,7 +33,7 @@ class RedisSessionHandler extends AbstractSessionHandler
/**
* Time to live in seconds.
*/
- private ?int $ttl;
+ private int|\Closure|null $ttl;
/**
* List of available options:
@@ -66,7 +66,8 @@ class RedisSessionHandler extends AbstractSessionHandler
*/
protected function doWrite(string $sessionId, string $data): bool
{
- $result = $this->redis->setEx($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')), $data);
+ $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime');
+ $result = $this->redis->setEx($this->prefix.$sessionId, (int) $ttl, $data);
return $result && !$result instanceof ErrorInterface;
}
@@ -81,7 +82,7 @@ class RedisSessionHandler extends AbstractSessionHandler
if ($unlink) {
try {
$unlink = false !== $this->redis->unlink($this->prefix.$sessionId);
- } catch (\Throwable $e) {
+ } catch (\Throwable) {
$unlink = false;
}
}
@@ -109,6 +110,8 @@ class RedisSessionHandler extends AbstractSessionHandler
public function updateTimestamp(string $sessionId, string $data): bool
{
- return $this->redis->expire($this->prefix.$sessionId, (int) ($this->ttl ?? \ini_get('session.gc_maxlifetime')));
+ $ttl = ($this->ttl instanceof \Closure ? ($this->ttl)() : $this->ttl) ?? \ini_get('session.gc_maxlifetime');
+
+ return $this->redis->expire($this->prefix.$sessionId, (int) $ttl);
}
}
diff --git a/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php b/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php
index 19ff94c81..9ad2a1090 100644
--- a/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php
+++ b/vendor/symfony/http-foundation/Session/Storage/Handler/SessionHandlerFactory.php
@@ -21,11 +21,16 @@ use Symfony\Component\Cache\Traits\RedisProxy;
*/
class SessionHandlerFactory
{
- public static function createHandler(object|string $connection): AbstractSessionHandler
+ public static function createHandler(object|string $connection, array $options = []): AbstractSessionHandler
{
- if ($options = \is_string($connection) ? parse_url($connection) : false) {
- parse_str($options['query'] ?? '', $options);
+ if ($query = \is_string($connection) ? parse_url($connection) : false) {
+ parse_str($query['query'] ?? '', $query);
+
+ if (($options['ttl'] ?? null) instanceof \Closure) {
+ $query['ttl'] = $options['ttl'];
+ }
}
+ $options = ($query ?: []) + $options;
switch (true) {
case $connection instanceof \Redis:
@@ -58,7 +63,7 @@ class SessionHandlerFactory
$handlerClass = str_starts_with($connection, 'memcached:') ? MemcachedSessionHandler::class : RedisSessionHandler::class;
$connection = AbstractAdapter::createConnection($connection, ['lazy' => true]);
- return new $handlerClass($connection, array_intersect_key($options ?: [], ['prefix' => 1, 'ttl' => 1]));
+ return new $handlerClass($connection, array_intersect_key($options, ['prefix' => 1, 'ttl' => 1]));
case str_starts_with($connection, 'pdo_oci://'):
if (!class_exists(DriverManager::class)) {
@@ -76,7 +81,7 @@ class SessionHandlerFactory
case str_starts_with($connection, 'sqlsrv://'):
case str_starts_with($connection, 'sqlite://'):
case str_starts_with($connection, 'sqlite3://'):
- return new PdoSessionHandler($connection, $options ?: []);
+ return new PdoSessionHandler($connection, $options);
}
throw new \InvalidArgumentException(sprintf('Unsupported Connection: "%s".', $connection));
diff --git a/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php
index c354d12ae..8ecf943dc 100644
--- a/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php
+++ b/vendor/symfony/http-foundation/Session/Storage/MockFileSessionStorageFactory.php
@@ -23,7 +23,7 @@ class MockFileSessionStorageFactory implements SessionStorageFactoryInterface
{
private ?string $savePath;
private string $name;
- private $metaBag;
+ private ?MetadataBag $metaBag;
/**
* @see MockFileSessionStorage constructor.
diff --git a/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php
index ea6c7de1e..08901284c 100644
--- a/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php
+++ b/vendor/symfony/http-foundation/Session/Storage/NativeSessionStorageFactory.php
@@ -23,8 +23,8 @@ class_exists(NativeSessionStorage::class);
class NativeSessionStorageFactory implements SessionStorageFactoryInterface
{
private array $options;
- private $handler;
- private $metaBag;
+ private AbstractProxy|\SessionHandlerInterface|null $handler;
+ private ?MetadataBag $metaBag;
private bool $secure;
/**
@@ -41,7 +41,7 @@ class NativeSessionStorageFactory implements SessionStorageFactoryInterface
public function createStorage(?Request $request): SessionStorageInterface
{
$storage = new NativeSessionStorage($this->options, $this->handler, $this->metaBag);
- if ($this->secure && $request && $request->isSecure()) {
+ if ($this->secure && $request?->isSecure()) {
$storage->setOptions(['cookie_secure' => true]);
}
diff --git a/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php
index cce1ec6c3..5cc738024 100644
--- a/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php
+++ b/vendor/symfony/http-foundation/Session/Storage/PhpBridgeSessionStorageFactory.php
@@ -22,8 +22,8 @@ class_exists(PhpBridgeSessionStorage::class);
*/
class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface
{
- private $handler;
- private $metaBag;
+ private AbstractProxy|\SessionHandlerInterface|null $handler;
+ private ?MetadataBag $metaBag;
private bool $secure;
public function __construct(AbstractProxy|\SessionHandlerInterface $handler = null, MetadataBag $metaBag = null, bool $secure = false)
@@ -36,7 +36,7 @@ class PhpBridgeSessionStorageFactory implements SessionStorageFactoryInterface
public function createStorage(?Request $request): SessionStorageInterface
{
$storage = new PhpBridgeSessionStorage($this->handler, $this->metaBag);
- if ($this->secure && $request && $request->isSecure()) {
+ if ($this->secure && $request?->isSecure()) {
$storage->setOptions(['cookie_secure' => true]);
}
diff --git a/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php b/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php
index cb9699c32..765849960 100644
--- a/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php
+++ b/vendor/symfony/http-foundation/Test/Constraint/ResponseFormatSame.php
@@ -22,7 +22,7 @@ use Symfony\Component\HttpFoundation\Response;
*/
final class ResponseFormatSame extends Constraint
{
- private $request;
+ private Request $request;
private ?string $format;
public function __construct(Request $request, ?string $format)
diff --git a/vendor/symfony/http-foundation/UrlHelper.php b/vendor/symfony/http-foundation/UrlHelper.php
index c15f101cd..42fcf0459 100644
--- a/vendor/symfony/http-foundation/UrlHelper.php
+++ b/vendor/symfony/http-foundation/UrlHelper.php
@@ -20,8 +20,8 @@ use Symfony\Component\Routing\RequestContext;
*/
final class UrlHelper
{
- private $requestStack;
- private $requestContext;
+ private RequestStack $requestStack;
+ private ?RequestContext $requestContext;
public function __construct(RequestStack $requestStack, RequestContext $requestContext = null)
{
@@ -31,7 +31,7 @@ final class UrlHelper
public function getAbsoluteUrl(string $path): string
{
- if (str_contains($path, '://') || '//' === substr($path, 0, 2)) {
+ if (str_contains($path, '://') || str_starts_with($path, '//')) {
return $path;
}
@@ -60,7 +60,7 @@ final class UrlHelper
public function getRelativePath(string $path): string
{
- if (str_contains($path, '://') || '//' === substr($path, 0, 2)) {
+ if (str_contains($path, '://') || str_starts_with($path, '//')) {
return $path;
}
diff --git a/vendor/symfony/http-foundation/composer.json b/vendor/symfony/http-foundation/composer.json
index 42405d957..1b232eb54 100644
--- a/vendor/symfony/http-foundation/composer.json
+++ b/vendor/symfony/http-foundation/composer.json
@@ -16,7 +16,7 @@
}
],
"require": {
- "php": ">=8.0.2",
+ "php": ">=8.1",
"symfony/deprecation-contracts": "^2.1|^3",
"symfony/polyfill-mbstring": "~1.1"
},
diff --git a/vendor/symfony/mime/Address.php b/vendor/symfony/mime/Address.php
index c3b7e16ed..ce57f77ee 100644
--- a/vendor/symfony/mime/Address.php
+++ b/vendor/symfony/mime/Address.php
@@ -42,7 +42,7 @@ final class Address
public function __construct(string $address, string $name = '')
{
if (!class_exists(EmailValidator::class)) {
- throw new LogicException(sprintf('The "%s" class cannot be used as it needs "%s"; try running "composer require egulias/email-validator".', __CLASS__, EmailValidator::class));
+ throw new LogicException(sprintf('The "%s" class cannot be used as it needs "%s". Try running "composer require egulias/email-validator".', __CLASS__, EmailValidator::class));
}
self::$validator ??= new EmailValidator();
@@ -92,7 +92,7 @@ final class Address
return $address;
}
- if (false === strpos($address, '<')) {
+ if (!str_contains($address, '<')) {
return new self($address);
}
diff --git a/vendor/symfony/mime/CHANGELOG.md b/vendor/symfony/mime/CHANGELOG.md
index b076e4e86..41eb14a4e 100644
--- a/vendor/symfony/mime/CHANGELOG.md
+++ b/vendor/symfony/mime/CHANGELOG.md
@@ -1,6 +1,24 @@
CHANGELOG
=========
+6.3
+---
+
+ * Support detection of related parts if `Content-Id` is used instead of the name
+ * Add `TextPart::getDisposition()`
+
+6.2
+---
+
+ * Add `File`
+ * Deprecate `Email::attachPart()`, use `addPart()` instead
+ * Deprecate calling `Message::setBody()` without arguments
+
+6.1
+---
+
+ * Add `DataPart::getFilename()` and `DataPart::getContentType()`
+
6.0
---
diff --git a/vendor/symfony/mime/CharacterStream.php b/vendor/symfony/mime/CharacterStream.php
index 2433c4489..21d7bc5f0 100644
--- a/vendor/symfony/mime/CharacterStream.php
+++ b/vendor/symfony/mime/CharacterStream.php
@@ -72,29 +72,22 @@ final class CharacterStream
$this->fixedWidth = 0;
$this->map = ['p' => [], 'i' => []];
} else {
- switch ($charset) {
+ $this->fixedWidth = match ($charset) {
// 16 bits
- case 'ucs2':
- case 'ucs-2':
- case 'utf16':
- case 'utf-16':
- $this->fixedWidth = 2;
- break;
-
- // 32 bits
- case 'ucs4':
- case 'ucs-4':
- case 'utf32':
- case 'utf-32':
- $this->fixedWidth = 4;
- break;
-
- // 7-8 bit charsets: (us-)?ascii, (iso|iec)-?8859-?[0-9]+, windows-?125[0-9], cp-?[0-9]+, ansi, macintosh,
+ 'ucs2',
+ 'ucs-2',
+ 'utf16',
+ 'utf-16' => 2,
+ // 32 bits
+ 'ucs4',
+ 'ucs-4',
+ 'utf32',
+ 'utf-32' => 4,
+ // 7-8 bit charsets: (us-)?ascii, (iso|iec)-?8859-?[0-9]+, windows-?125[0-9], cp-?[0-9]+, ansi, macintosh,
// koi-?7, koi-?8-?.+, mik, (cork|t1), v?iscii
- // and fallback
- default:
- $this->fixedWidth = 1;
- }
+ // and fallback
+ default => 1,
+ };
}
if (\is_resource($input)) {
$blocks = 16372;
diff --git a/vendor/symfony/mime/Crypto/DkimSigner.php b/vendor/symfony/mime/Crypto/DkimSigner.php
index ce07747df..1d2005e5c 100644
--- a/vendor/symfony/mime/Crypto/DkimSigner.php
+++ b/vendor/symfony/mime/Crypto/DkimSigner.php
@@ -30,7 +30,7 @@ final class DkimSigner
public const ALGO_SHA256 = 'rsa-sha256';
public const ALGO_ED25519 = 'ed25519-sha256'; // RFC 8463
- private $key;
+ private \OpenSSLAsymmetricKey $key;
private string $domainName;
private string $selector;
private array $defaultOptions;
diff --git a/vendor/symfony/mime/Crypto/SMimeEncrypter.php b/vendor/symfony/mime/Crypto/SMimeEncrypter.php
index 0ccf2cd5e..7d5dfecd2 100644
--- a/vendor/symfony/mime/Crypto/SMimeEncrypter.php
+++ b/vendor/symfony/mime/Crypto/SMimeEncrypter.php
@@ -33,7 +33,7 @@ final class SMimeEncrypter extends SMime
}
if (\is_array($certificate)) {
- $this->certs = array_map([$this, 'normalizeFilePath'], $certificate);
+ $this->certs = array_map($this->normalizeFilePath(...), $certificate);
} else {
$this->certs = $this->normalizeFilePath($certificate);
}
diff --git a/vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php b/vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php
index 5b2e66b5c..70fa78633 100644
--- a/vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php
+++ b/vendor/symfony/mime/DependencyInjection/AddMimeTypeGuesserPass.php
@@ -23,7 +23,7 @@ use Symfony\Component\DependencyInjection\Reference;
class AddMimeTypeGuesserPass implements CompilerPassInterface
{
/**
- * {@inheritdoc}
+ * @return void
*/
public function process(ContainerBuilder $container)
{
diff --git a/vendor/symfony/mime/DraftEmail.php b/vendor/symfony/mime/DraftEmail.php
new file mode 100644
index 000000000..a60fea173
--- /dev/null
+++ b/vendor/symfony/mime/DraftEmail.php
@@ -0,0 +1,45 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mime;
+
+use Symfony\Component\Mime\Header\Headers;
+use Symfony\Component\Mime\Part\AbstractPart;
+
+/**
+ * @author Kevin Bond
+ */
+class DraftEmail extends Email
+{
+ public function __construct(Headers $headers = null, AbstractPart $body = null)
+ {
+ parent::__construct($headers, $body);
+
+ $this->getHeaders()->addTextHeader('X-Unsent', '1');
+ }
+
+ /**
+ * Override default behavior as draft emails do not require From/Sender/Date/Message-ID headers.
+ * These are added by the client that actually sends the email.
+ */
+ public function getPreparedHeaders(): Headers
+ {
+ $headers = clone $this->getHeaders();
+
+ if (!$headers->has('MIME-Version')) {
+ $headers->addTextHeader('MIME-Version', '1.0');
+ }
+
+ $headers->remove('Bcc');
+
+ return $headers;
+ }
+}
diff --git a/vendor/symfony/mime/Email.php b/vendor/symfony/mime/Email.php
index cfbfa8e9d..7f3496d1f 100644
--- a/vendor/symfony/mime/Email.php
+++ b/vendor/symfony/mime/Email.php
@@ -14,6 +14,7 @@ namespace Symfony\Component\Mime;
use Symfony\Component\Mime\Exception\LogicException;
use Symfony\Component\Mime\Part\AbstractPart;
use Symfony\Component\Mime\Part\DataPart;
+use Symfony\Component\Mime\Part\File;
use Symfony\Component\Mime\Part\Multipart\AlternativePart;
use Symfony\Component\Mime\Part\Multipart\MixedPart;
use Symfony\Component\Mime\Part\Multipart\RelatedPart;
@@ -326,14 +327,7 @@ class Email extends Message
*/
public function attach($body, string $name = null, string $contentType = null): static
{
- if (!\is_string($body) && !\is_resource($body)) {
- throw new \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body)));
- }
-
- $this->cachedBody = null;
- $this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
-
- return $this;
+ return $this->addPart(new DataPart($body, $name, $contentType));
}
/**
@@ -341,10 +335,7 @@ class Email extends Message
*/
public function attachFromPath(string $path, string $name = null, string $contentType = null): static
{
- $this->cachedBody = null;
- $this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => false];
-
- return $this;
+ return $this->addPart(new DataPart(new File($path), $name, $contentType));
}
/**
@@ -354,14 +345,7 @@ class Email extends Message
*/
public function embed($body, string $name = null, string $contentType = null): static
{
- if (!\is_string($body) && !\is_resource($body)) {
- throw new \TypeError(sprintf('The body must be a string or a resource (got "%s").', get_debug_type($body)));
- }
-
- $this->cachedBody = null;
- $this->attachments[] = ['body' => $body, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
-
- return $this;
+ return $this->addPart((new DataPart($body, $name, $contentType))->asInline());
}
/**
@@ -369,34 +353,38 @@ class Email extends Message
*/
public function embedFromPath(string $path, string $name = null, string $contentType = null): static
{
- $this->cachedBody = null;
- $this->attachments[] = ['path' => $path, 'name' => $name, 'content-type' => $contentType, 'inline' => true];
+ return $this->addPart((new DataPart(new File($path), $name, $contentType))->asInline());
+ }
- return $this;
+ /**
+ * @return $this
+ *
+ * @deprecated since Symfony 6.2, use addPart() instead
+ */
+ public function attachPart(DataPart $part): static
+ {
+ @trigger_deprecation('symfony/mime', '6.2', 'The "%s()" method is deprecated, use "addPart()" instead.', __METHOD__);
+
+ return $this->addPart($part);
}
/**
* @return $this
*/
- public function attachPart(DataPart $part): static
+ public function addPart(DataPart $part): static
{
$this->cachedBody = null;
- $this->attachments[] = ['part' => $part];
+ $this->attachments[] = $part;
return $this;
}
/**
- * @return array|DataPart[]
+ * @return DataPart[]
*/
public function getAttachments(): array
{
- $parts = [];
- foreach ($this->attachments as $attachment) {
- $parts[] = $this->createDataPart($attachment);
- }
-
- return $parts;
+ return $this->attachments;
}
public function getBody(): AbstractPart
@@ -408,13 +396,25 @@ class Email extends Message
return $this->generateBody();
}
+ /**
+ * @return void
+ */
public function ensureValidity()
+ {
+ $this->ensureBodyValid();
+
+ if ('1' === $this->getHeaders()->getHeaderBody('X-Unsent')) {
+ throw new LogicException('Cannot send messages marked as "draft".');
+ }
+
+ parent::ensureValidity();
+ }
+
+ private function ensureBodyValid(): void
{
if (null === $this->text && null === $this->html && !$this->attachments) {
throw new LogicException('A message must have a text or an HTML part or attachments.');
}
-
- parent::ensureValidity();
}
/**
@@ -443,7 +443,7 @@ class Email extends Message
return $this->cachedBody;
}
- $this->ensureValidity();
+ $this->ensureBodyValid();
[$htmlPart, $otherParts, $relatedParts] = $this->prepareParts();
@@ -479,43 +479,39 @@ class Email extends Message
if (null !== $html) {
$htmlPart = new TextPart($html, $this->htmlCharset, 'html');
$html = $htmlPart->getBody();
- preg_match_all('(
]*src\s*=\s*(?:([\'"])cid:(.+?)\\1|cid:([^>\s]+)))i', $html, $names);
- $names = array_filter(array_unique(array_merge($names[2], $names[3])));
+
+ $regexes = [
+ '
]*src\s*=\s*(?:([\'"])cid:(.+?)\\1|cid:([^>\s]+))',
+ '<\w+\s+[^>]*background\s*=\s*(?:([\'"])cid:(.+?)\\1|cid:([^>\s]+))',
+ ];
+ $tmpMatches = [];
+ foreach ($regexes as $regex) {
+ preg_match_all('/'.$regex.'/i', $html, $tmpMatches);
+ $names = array_merge($names, $tmpMatches[2], $tmpMatches[3]);
+ }
+ $names = array_filter(array_unique($names));
}
- // usage of reflection is a temporary workaround for missing getters that will be added in 6.2
- $nameRef = new \ReflectionProperty(TextPart::class, 'name');
- $nameRef->setAccessible(true);
$otherParts = $relatedParts = [];
- foreach ($this->attachments as $attachment) {
- $part = $this->createDataPart($attachment);
- if (isset($attachment['part'])) {
- $attachment['name'] = $nameRef->getValue($part);
- }
-
- $related = false;
+ foreach ($this->attachments as $part) {
foreach ($names as $name) {
- if ($name !== $attachment['name']) {
+ if ($name !== $part->getName() && (!$part->hasContentId() || $name !== $part->getContentId())) {
continue;
}
if (isset($relatedParts[$name])) {
continue 2;
}
- $part->setDisposition('inline');
- $html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html, $count);
- if ($count) {
- $related = true;
+
+ if ($name !== $part->getContentId()) {
+ $html = str_replace('cid:'.$name, 'cid:'.$part->getContentId(), $html, $count);
}
- $part->setName($part->getContentId());
+ $relatedParts[$name] = $part;
+ $part->setName($part->getContentId())->asInline();
- break;
+ continue 2;
}
- if ($related) {
- $relatedParts[$attachment['name']] = $part;
- } else {
- $otherParts[] = $part;
- }
+ $otherParts[] = $part;
}
if (null !== $htmlPart) {
$htmlPart = new TextPart($html, $this->htmlCharset, 'html');
@@ -524,24 +520,6 @@ class Email extends Message
return [$htmlPart, $otherParts, array_values($relatedParts)];
}
- private function createDataPart(array $attachment): DataPart
- {
- if (isset($attachment['part'])) {
- return $attachment['part'];
- }
-
- if (isset($attachment['body'])) {
- $part = new DataPart($attachment['body'], $attachment['name'] ?? null, $attachment['content-type'] ?? null);
- } else {
- $part = DataPart::fromPath($attachment['path'] ?? '', $attachment['name'] ?? null, $attachment['content-type'] ?? null);
- }
- if ($attachment['inline']) {
- $part->asInline();
- }
-
- return $part;
- }
-
/**
* @return $this
*/
@@ -552,7 +530,10 @@ class Email extends Message
return $this;
}
- private function addListAddressHeaderBody(string $name, array $addresses)
+ /**
+ * @return $this
+ */
+ private function addListAddressHeaderBody(string $name, array $addresses): static
{
if (!$header = $this->getHeaders()->get($name)) {
return $this->setListAddressHeaderBody($name, $addresses);
@@ -591,12 +572,6 @@ class Email extends Message
$this->html = (new TextPart($this->html))->getBody();
}
- foreach ($this->attachments as $i => $attachment) {
- if (isset($attachment['body']) && \is_resource($attachment['body'])) {
- $this->attachments[$i]['body'] = (new TextPart($attachment['body']))->getBody();
- }
- }
-
return [$this->text, $this->textCharset, $this->html, $this->htmlCharset, $this->attachments, parent::__serialize()];
}
diff --git a/vendor/symfony/mime/Encoder/QpContentEncoder.php b/vendor/symfony/mime/Encoder/QpContentEncoder.php
index 4703cc2e6..6f420fff3 100644
--- a/vendor/symfony/mime/Encoder/QpContentEncoder.php
+++ b/vendor/symfony/mime/Encoder/QpContentEncoder.php
@@ -46,15 +46,10 @@ final class QpContentEncoder implements ContentEncoderInterface
// transform =0D=0A to CRLF
$string = str_replace(["\t=0D=0A", ' =0D=0A', '=0D=0A'], ["=09\r\n", "=20\r\n", "\r\n"], $string);
- switch (\ord(substr($string, -1))) {
- case 0x09:
- $string = substr_replace($string, '=09', -1);
- break;
- case 0x20:
- $string = substr_replace($string, '=20', -1);
- break;
- }
-
- return $string;
+ return match (\ord(substr($string, -1))) {
+ 0x09 => substr_replace($string, '=09', -1),
+ 0x20 => substr_replace($string, '=20', -1),
+ default => $string,
+ };
}
}
diff --git a/vendor/symfony/mime/Encoder/QpEncoder.php b/vendor/symfony/mime/Encoder/QpEncoder.php
index 0b4bcf538..160dde329 100644
--- a/vendor/symfony/mime/Encoder/QpEncoder.php
+++ b/vendor/symfony/mime/Encoder/QpEncoder.php
@@ -106,8 +106,6 @@ class QpEncoder implements EncoderInterface
}
/**
- * {@inheritdoc}
- *
* Takes an unencoded string and produces a QP encoded string from it.
*
* QP encoded strings have a maximum line length of 76 characters.
@@ -184,12 +182,11 @@ class QpEncoder implements EncoderInterface
private function standardize(string $string): string
{
$string = str_replace(["\t=0D=0A", ' =0D=0A', '=0D=0A'], ["=09\r\n", "=20\r\n", "\r\n"], $string);
- switch ($end = \ord(substr($string, -1))) {
- case 0x09:
- case 0x20:
- $string = substr_replace($string, self::QP_MAP[$end], -1);
- }
- return $string;
+ return match ($end = \ord(substr($string, -1))) {
+ 0x09,
+ 0x20 => substr_replace($string, self::QP_MAP[$end], -1),
+ default => $string,
+ };
}
}
diff --git a/vendor/symfony/mime/FileBinaryMimeTypeGuesser.php b/vendor/symfony/mime/FileBinaryMimeTypeGuesser.php
index 5d6bee847..83e2950ce 100644
--- a/vendor/symfony/mime/FileBinaryMimeTypeGuesser.php
+++ b/vendor/symfony/mime/FileBinaryMimeTypeGuesser.php
@@ -36,9 +36,6 @@ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
$this->cmd = $cmd;
}
- /**
- * {@inheritdoc}
- */
public function isGuesserSupported(): bool
{
static $supported = null;
@@ -58,9 +55,6 @@ class FileBinaryMimeTypeGuesser implements MimeTypeGuesserInterface
return $supported = 0 === $exitStatus && '' !== $binPath;
}
- /**
- * {@inheritdoc}
- */
public function guessMimeType(string $path): ?string
{
if (!is_file($path) || !is_readable($path)) {
diff --git a/vendor/symfony/mime/FileinfoMimeTypeGuesser.php b/vendor/symfony/mime/FileinfoMimeTypeGuesser.php
index c40a8196d..aff590115 100644
--- a/vendor/symfony/mime/FileinfoMimeTypeGuesser.php
+++ b/vendor/symfony/mime/FileinfoMimeTypeGuesser.php
@@ -24,7 +24,7 @@ class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
private ?string $magicFile;
/**
- * @param string $magicFile A magic file to use with the finfo instance
+ * @param string|null $magicFile A magic file to use with the finfo instance
*
* @see http://www.php.net/manual/en/function.finfo-open.php
*/
@@ -33,17 +33,11 @@ class FileinfoMimeTypeGuesser implements MimeTypeGuesserInterface
$this->magicFile = $magicFile;
}
- /**
- * {@inheritdoc}
- */
public function isGuesserSupported(): bool
{
return \function_exists('finfo_open');
}
- /**
- * {@inheritdoc}
- */
public function guessMimeType(string $path): ?string
{
if (!is_file($path) || !is_readable($path)) {
diff --git a/vendor/symfony/mime/Header/AbstractHeader.php b/vendor/symfony/mime/Header/AbstractHeader.php
index 46e566345..122281127 100644
--- a/vendor/symfony/mime/Header/AbstractHeader.php
+++ b/vendor/symfony/mime/Header/AbstractHeader.php
@@ -34,6 +34,9 @@ abstract class AbstractHeader implements HeaderInterface
$this->name = $name;
}
+ /**
+ * @return void
+ */
public function setCharset(string $charset)
{
$this->charset = $charset;
@@ -48,6 +51,8 @@ abstract class AbstractHeader implements HeaderInterface
* Set the language used in this Header.
*
* For example, for US English, 'en-us'.
+ *
+ * @return void
*/
public function setLanguage(string $lang)
{
@@ -64,6 +69,9 @@ abstract class AbstractHeader implements HeaderInterface
return $this->name;
}
+ /**
+ * @return void
+ */
public function setMaxLineLength(int $lineLength)
{
$this->lineLength = $lineLength;
@@ -231,9 +239,7 @@ abstract class AbstractHeader implements HeaderInterface
*/
protected function toTokens(string $string = null): array
{
- if (null === $string) {
- $string = $this->getBodyAsString();
- }
+ $string ??= $this->getBodyAsString();
$tokens = [];
// Generate atoms; split at all invisible boundaries followed by WSP
@@ -263,8 +269,8 @@ abstract class AbstractHeader implements HeaderInterface
// Build all tokens back into compliant header
foreach ($tokens as $i => $token) {
// Line longer than specified maximum or token was just a new line
- if (("\r\n" === $token) ||
- ($i > 0 && \strlen($currentLine.$token) > $this->lineLength)
+ if (("\r\n" === $token)
+ || ($i > 0 && \strlen($currentLine.$token) > $this->lineLength)
&& '' !== $currentLine) {
$headerLines[] = '';
$currentLine = &$headerLines[$lineCount++];
diff --git a/vendor/symfony/mime/Header/DateHeader.php b/vendor/symfony/mime/Header/DateHeader.php
index b07dfedeb..67156cb79 100644
--- a/vendor/symfony/mime/Header/DateHeader.php
+++ b/vendor/symfony/mime/Header/DateHeader.php
@@ -30,7 +30,7 @@ final class DateHeader extends AbstractHeader
/**
* @param \DateTimeInterface $body
*/
- public function setBody(mixed $body)
+ public function setBody(mixed $body): void
{
$this->setDateTime($body);
}
@@ -50,7 +50,7 @@ final class DateHeader extends AbstractHeader
*
* If a DateTime instance is provided, it is converted to DateTimeImmutable.
*/
- public function setDateTime(\DateTimeInterface $dateTime)
+ public function setDateTime(\DateTimeInterface $dateTime): void
{
if ($dateTime instanceof \DateTime) {
$immutable = new \DateTimeImmutable('@'.$dateTime->getTimestamp());
diff --git a/vendor/symfony/mime/Header/HeaderInterface.php b/vendor/symfony/mime/Header/HeaderInterface.php
index 092fc238a..5bc4162c3 100644
--- a/vendor/symfony/mime/Header/HeaderInterface.php
+++ b/vendor/symfony/mime/Header/HeaderInterface.php
@@ -22,6 +22,8 @@ interface HeaderInterface
* Sets the body.
*
* The type depends on the Header concrete class.
+ *
+ * @return void
*/
public function setBody(mixed $body);
@@ -32,16 +34,25 @@ interface HeaderInterface
*/
public function getBody(): mixed;
+ /**
+ * @return void
+ */
public function setCharset(string $charset);
public function getCharset(): ?string;
+ /**
+ * @return void
+ */
public function setLanguage(string $lang);
public function getLanguage(): ?string;
public function getName(): string;
+ /**
+ * @return void
+ */
public function setMaxLineLength(int $lineLength);
public function getMaxLineLength(): int;
diff --git a/vendor/symfony/mime/Header/Headers.php b/vendor/symfony/mime/Header/Headers.php
index 115928155..57d730127 100644
--- a/vendor/symfony/mime/Header/Headers.php
+++ b/vendor/symfony/mime/Header/Headers.php
@@ -34,8 +34,8 @@ final class Headers
'cc' => MailboxListHeader::class,
'bcc' => MailboxListHeader::class,
'message-id' => IdentificationHeader::class,
- 'in-reply-to' => UnstructuredHeader::class, // `In-Reply-To` and `References` are less strict than RFC 2822 (3.6.4) to allow users entering the original email's ...
- 'references' => UnstructuredHeader::class, // ... `Message-ID`, even if that is no valid `msg-id`
+ 'in-reply-to' => [UnstructuredHeader::class, IdentificationHeader::class], // `In-Reply-To` and `References` are less strict than RFC 2822 (3.6.4) to allow users entering the original email's ...
+ 'references' => [UnstructuredHeader::class, IdentificationHeader::class], // ... `Message-ID`, even if that is no valid `msg-id`
'return-path' => PathHeader::class,
];
@@ -61,7 +61,7 @@ final class Headers
}
}
- public function setMaxLineLength(int $lineLength)
+ public function setMaxLineLength(int $lineLength): void
{
$this->lineLength = $lineLength;
foreach ($this->all() as $header) {
@@ -137,7 +137,11 @@ final class Headers
*/
public function addHeader(string $name, mixed $argument, array $more = []): static
{
- $parts = explode('\\', self::HEADER_CLASS_MAP[strtolower($name)] ?? UnstructuredHeader::class);
+ $headerClass = self::HEADER_CLASS_MAP[strtolower($name)] ?? UnstructuredHeader::class;
+ if (\is_array($headerClass)) {
+ $headerClass = $headerClass[0];
+ }
+ $parts = explode('\\', $headerClass);
$method = 'add'.ucfirst(array_pop($parts));
if ('addUnstructuredHeader' === $method) {
$method = 'addTextHeader';
@@ -220,10 +224,22 @@ final class Headers
public static function checkHeaderClass(HeaderInterface $header): void
{
$name = strtolower($header->getName());
-
- if (($c = self::HEADER_CLASS_MAP[$name] ?? null) && !$header instanceof $c) {
- throw new LogicException(sprintf('The "%s" header must be an instance of "%s" (got "%s").', $header->getName(), $c, get_debug_type($header)));
+ $headerClasses = self::HEADER_CLASS_MAP[$name] ?? [];
+ if (!\is_array($headerClasses)) {
+ $headerClasses = [$headerClasses];
}
+
+ if (!$headerClasses) {
+ return;
+ }
+
+ foreach ($headerClasses as $c) {
+ if ($header instanceof $c) {
+ return;
+ }
+ }
+
+ throw new LogicException(sprintf('The "%s" header must be an instance of "%s" (got "%s").', $header->getName(), implode('" or "', $headerClasses), get_debug_type($header)));
}
public function toString(): string
@@ -248,7 +264,7 @@ final class Headers
return $arr;
}
- public function getHeaderBody(string $name)
+ public function getHeaderBody(string $name): mixed
{
return $this->has($name) ? $this->get($name)->getBody() : null;
}
diff --git a/vendor/symfony/mime/Header/IdentificationHeader.php b/vendor/symfony/mime/Header/IdentificationHeader.php
index 40fd7c36d..14e18bf25 100644
--- a/vendor/symfony/mime/Header/IdentificationHeader.php
+++ b/vendor/symfony/mime/Header/IdentificationHeader.php
@@ -36,7 +36,7 @@ final class IdentificationHeader extends AbstractHeader
*
* @throws RfcComplianceException
*/
- public function setBody(mixed $body)
+ public function setBody(mixed $body): void
{
$this->setId($body);
}
@@ -53,7 +53,7 @@ final class IdentificationHeader extends AbstractHeader
*
* @throws RfcComplianceException
*/
- public function setId(string|array $id)
+ public function setId(string|array $id): void
{
$this->setIds(\is_array($id) ? $id : [$id]);
}
@@ -75,7 +75,7 @@ final class IdentificationHeader extends AbstractHeader
*
* @throws RfcComplianceException
*/
- public function setIds(array $ids)
+ public function setIds(array $ids): void
{
$this->ids = [];
$this->idsAsAddresses = [];
diff --git a/vendor/symfony/mime/Header/MailboxHeader.php b/vendor/symfony/mime/Header/MailboxHeader.php
index 877e72e35..8ba964b53 100644
--- a/vendor/symfony/mime/Header/MailboxHeader.php
+++ b/vendor/symfony/mime/Header/MailboxHeader.php
@@ -21,7 +21,7 @@ use Symfony\Component\Mime\Exception\RfcComplianceException;
*/
final class MailboxHeader extends AbstractHeader
{
- private $address;
+ private Address $address;
public function __construct(string $name, Address $address)
{
@@ -35,7 +35,7 @@ final class MailboxHeader extends AbstractHeader
*
* @throws RfcComplianceException
*/
- public function setBody(mixed $body)
+ public function setBody(mixed $body): void
{
$this->setAddress($body);
}
@@ -51,7 +51,7 @@ final class MailboxHeader extends AbstractHeader
/**
* @throws RfcComplianceException
*/
- public function setAddress(Address $address)
+ public function setAddress(Address $address): void
{
$this->address = $address;
}
diff --git a/vendor/symfony/mime/Header/MailboxListHeader.php b/vendor/symfony/mime/Header/MailboxListHeader.php
index e6e6ce195..8d902fb75 100644
--- a/vendor/symfony/mime/Header/MailboxListHeader.php
+++ b/vendor/symfony/mime/Header/MailboxListHeader.php
@@ -38,15 +38,15 @@ final class MailboxListHeader extends AbstractHeader
*
* @throws RfcComplianceException
*/
- public function setBody(mixed $body)
+ public function setBody(mixed $body): void
{
$this->setAddresses($body);
}
/**
- * @throws RfcComplianceException
- *
* @return Address[]
+ *
+ * @throws RfcComplianceException
*/
public function getBody(): array
{
@@ -60,7 +60,7 @@ final class MailboxListHeader extends AbstractHeader
*
* @throws RfcComplianceException
*/
- public function setAddresses(array $addresses)
+ public function setAddresses(array $addresses): void
{
$this->addresses = [];
$this->addAddresses($addresses);
@@ -73,7 +73,7 @@ final class MailboxListHeader extends AbstractHeader
*
* @throws RfcComplianceException
*/
- public function addAddresses(array $addresses)
+ public function addAddresses(array $addresses): void
{
foreach ($addresses as $address) {
$this->addAddress($address);
@@ -83,7 +83,7 @@ final class MailboxListHeader extends AbstractHeader
/**
* @throws RfcComplianceException
*/
- public function addAddress(Address $address)
+ public function addAddress(Address $address): void
{
$this->addresses[] = $address;
}
@@ -99,9 +99,9 @@ final class MailboxListHeader extends AbstractHeader
/**
* Gets the full mailbox list of this Header as an array of valid RFC 2822 strings.
*
- * @throws RfcComplianceException
- *
* @return string[]
+ *
+ * @throws RfcComplianceException
*/
public function getAddressStrings(): array
{
diff --git a/vendor/symfony/mime/Header/ParameterizedHeader.php b/vendor/symfony/mime/Header/ParameterizedHeader.php
index ac5ccc58e..0618e82bd 100644
--- a/vendor/symfony/mime/Header/ParameterizedHeader.php
+++ b/vendor/symfony/mime/Header/ParameterizedHeader.php
@@ -25,7 +25,7 @@ final class ParameterizedHeader extends UnstructuredHeader
*/
public const TOKEN_REGEX = '(?:[\x21\x23-\x27\x2A\x2B\x2D\x2E\x30-\x39\x41-\x5A\x5E-\x7E]+)';
- private $encoder = null;
+ private ?Rfc2231Encoder $encoder = null;
private array $parameters = [];
public function __construct(string $name, string $value, array $parameters = [])
@@ -41,7 +41,7 @@ final class ParameterizedHeader extends UnstructuredHeader
}
}
- public function setParameter(string $parameter, ?string $value)
+ public function setParameter(string $parameter, ?string $value): void
{
$this->setParameters(array_merge($this->getParameters(), [$parameter => $value]));
}
@@ -54,7 +54,7 @@ final class ParameterizedHeader extends UnstructuredHeader
/**
* @param string[] $parameters
*/
- public function setParameters(array $parameters)
+ public function setParameters(array $parameters): void
{
$this->parameters = $parameters;
}
diff --git a/vendor/symfony/mime/Header/PathHeader.php b/vendor/symfony/mime/Header/PathHeader.php
index af267bb10..63eb30af0 100644
--- a/vendor/symfony/mime/Header/PathHeader.php
+++ b/vendor/symfony/mime/Header/PathHeader.php
@@ -21,7 +21,7 @@ use Symfony\Component\Mime\Exception\RfcComplianceException;
*/
final class PathHeader extends AbstractHeader
{
- private $address;
+ private Address $address;
public function __construct(string $name, Address $address)
{
@@ -35,7 +35,7 @@ final class PathHeader extends AbstractHeader
*
* @throws RfcComplianceException
*/
- public function setBody(mixed $body)
+ public function setBody(mixed $body): void
{
$this->setAddress($body);
}
@@ -45,7 +45,7 @@ final class PathHeader extends AbstractHeader
return $this->getAddress();
}
- public function setAddress(Address $address)
+ public function setAddress(Address $address): void
{
$this->address = $address;
}
diff --git a/vendor/symfony/mime/Header/UnstructuredHeader.php b/vendor/symfony/mime/Header/UnstructuredHeader.php
index 668b0be7f..61c06d8f5 100644
--- a/vendor/symfony/mime/Header/UnstructuredHeader.php
+++ b/vendor/symfony/mime/Header/UnstructuredHeader.php
@@ -29,6 +29,8 @@ class UnstructuredHeader extends AbstractHeader
/**
* @param string $body
+ *
+ * @return void
*/
public function setBody(mixed $body)
{
@@ -50,6 +52,8 @@ class UnstructuredHeader extends AbstractHeader
/**
* Set the (unencoded) value of this header.
+ *
+ * @return void
*/
public function setValue(string $value)
{
diff --git a/vendor/symfony/mime/HtmlToTextConverter/DefaultHtmlToTextConverter.php b/vendor/symfony/mime/HtmlToTextConverter/DefaultHtmlToTextConverter.php
new file mode 100644
index 000000000..2aaf8e6c4
--- /dev/null
+++ b/vendor/symfony/mime/HtmlToTextConverter/DefaultHtmlToTextConverter.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mime\HtmlToTextConverter;
+
+/**
+ * @author Fabien Potencier
+ */
+class DefaultHtmlToTextConverter implements HtmlToTextConverterInterface
+{
+ public function convert(string $html, string $charset): string
+ {
+ return strip_tags(preg_replace('{<(head|style)\b.*?\1>}is', '', $html));
+ }
+}
diff --git a/vendor/symfony/mime/HtmlToTextConverter/HtmlToTextConverterInterface.php b/vendor/symfony/mime/HtmlToTextConverter/HtmlToTextConverterInterface.php
new file mode 100644
index 000000000..696f37ccf
--- /dev/null
+++ b/vendor/symfony/mime/HtmlToTextConverter/HtmlToTextConverterInterface.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mime\HtmlToTextConverter;
+
+/**
+ * @author Fabien Potencier
+ */
+interface HtmlToTextConverterInterface
+{
+ /**
+ * Converts an HTML representation of a Message to a text representation.
+ *
+ * The output must use the same charset as the HTML one.
+ */
+ public function convert(string $html, string $charset): string;
+}
diff --git a/vendor/symfony/mime/HtmlToTextConverter/LeagueHtmlToMarkdownConverter.php b/vendor/symfony/mime/HtmlToTextConverter/LeagueHtmlToMarkdownConverter.php
new file mode 100644
index 000000000..253a7b19f
--- /dev/null
+++ b/vendor/symfony/mime/HtmlToTextConverter/LeagueHtmlToMarkdownConverter.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mime\HtmlToTextConverter;
+
+use League\HTMLToMarkdown\HtmlConverter;
+use League\HTMLToMarkdown\HtmlConverterInterface;
+
+/**
+ * @author Fabien Potencier
+ */
+class LeagueHtmlToMarkdownConverter implements HtmlToTextConverterInterface
+{
+ public function __construct(
+ private HtmlConverterInterface $converter = new HtmlConverter([
+ 'hard_break' => true,
+ 'strip_tags' => true,
+ 'remove_nodes' => 'head style',
+ ]),
+ ) {
+ }
+
+ public function convert(string $html, string $charset): string
+ {
+ return $this->converter->convert($html);
+ }
+}
diff --git a/vendor/symfony/mime/LICENSE b/vendor/symfony/mime/LICENSE
index 58b42bc8a..4dd83ce0f 100644
--- a/vendor/symfony/mime/LICENSE
+++ b/vendor/symfony/mime/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2010-2023 Fabien Potencier
+Copyright (c) 2010-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/symfony/mime/Message.php b/vendor/symfony/mime/Message.php
index a60d0f527..e636c2e8e 100644
--- a/vendor/symfony/mime/Message.php
+++ b/vendor/symfony/mime/Message.php
@@ -21,8 +21,8 @@ use Symfony\Component\Mime\Part\TextPart;
*/
class Message extends RawMessage
{
- private $headers;
- private $body;
+ private Headers $headers;
+ private ?AbstractPart $body;
public function __construct(Headers $headers = null, AbstractPart $body = null)
{
@@ -44,6 +44,9 @@ class Message extends RawMessage
*/
public function setBody(AbstractPart $body = null): static
{
+ if (1 > \func_num_args()) {
+ trigger_deprecation('symfony/mime', '6.2', 'Calling "%s()" without any arguments is deprecated, pass null explicitly instead.', __METHOD__);
+ }
$this->body = $body;
return $this;
@@ -122,6 +125,9 @@ class Message extends RawMessage
yield from $body->toIterable();
}
+ /**
+ * @return void
+ */
public function ensureValidity()
{
if (!$this->headers->has('To') && !$this->headers->has('Cc') && !$this->headers->has('Bcc')) {
diff --git a/vendor/symfony/mime/MessageConverter.php b/vendor/symfony/mime/MessageConverter.php
index 0539eac8e..bdce921af 100644
--- a/vendor/symfony/mime/MessageConverter.php
+++ b/vendor/symfony/mime/MessageConverter.php
@@ -58,7 +58,7 @@ final class MessageConverter
throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($message)));
}
- return self::attachParts($email, \array_slice($parts, 1));
+ return self::addParts($email, \array_slice($parts, 1));
}
throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($message)));
@@ -80,9 +80,9 @@ final class MessageConverter
{
$parts = $part->getParts();
if (
- 2 === \count($parts) &&
- $parts[0] instanceof TextPart && 'text' === $parts[0]->getMediaType() && 'plain' === $parts[0]->getMediaSubtype() &&
- $parts[1] instanceof TextPart && 'text' === $parts[1]->getMediaType() && 'html' === $parts[1]->getMediaSubtype()
+ 2 === \count($parts)
+ && $parts[0] instanceof TextPart && 'text' === $parts[0]->getMediaType() && 'plain' === $parts[0]->getMediaSubtype()
+ && $parts[1] instanceof TextPart && 'text' === $parts[1]->getMediaType() && 'html' === $parts[1]->getMediaSubtype()
) {
return (new Email(clone $message->getHeaders()))
->text($parts[0]->getBody(), $parts[0]->getPreparedHeaders()->getHeaderParameter('Content-Type', 'charset') ?: 'utf-8')
@@ -104,20 +104,17 @@ final class MessageConverter
throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($message)));
}
- return self::attachParts($email, \array_slice($parts, 1));
+ return self::addParts($email, \array_slice($parts, 1));
}
- private static function attachParts(Email $email, array $parts): Email
+ private static function addParts(Email $email, array $parts): Email
{
foreach ($parts as $part) {
if (!$part instanceof DataPart) {
throw new RuntimeException(sprintf('Unable to create an Email from an instance of "%s" as the body is too complex.', get_debug_type($email)));
}
- $headers = $part->getPreparedHeaders();
- $method = 'inline' === $headers->getHeaderBody('Content-Disposition') ? 'embed' : 'attach';
- $name = $headers->getHeaderParameter('Content-Disposition', 'filename');
- $email->$method($part->getBody(), $name, $part->getMediaType().'/'.$part->getMediaSubtype());
+ $email->addPart($part);
}
return $email;
diff --git a/vendor/symfony/mime/MimeTypes.php b/vendor/symfony/mime/MimeTypes.php
index 77cdcc23c..21c8aecf2 100644
--- a/vendor/symfony/mime/MimeTypes.php
+++ b/vendor/symfony/mime/MimeTypes.php
@@ -58,14 +58,14 @@ final class MimeTypes implements MimeTypesInterface
$this->registerGuesser(new FileinfoMimeTypeGuesser());
}
- public static function setDefault(self $default)
+ public static function setDefault(self $default): void
{
self::$default = $default;
}
public static function getDefault(): self
{
- return self::$default ?? self::$default = new self();
+ return self::$default ??= new self();
}
/**
@@ -73,14 +73,11 @@ final class MimeTypes implements MimeTypesInterface
*
* The last registered guesser has precedence over the other ones.
*/
- public function registerGuesser(MimeTypeGuesserInterface $guesser)
+ public function registerGuesser(MimeTypeGuesserInterface $guesser): void
{
array_unshift($this->guessers, $guesser);
}
- /**
- * {@inheritdoc}
- */
public function getExtensions(string $mimeType): array
{
if ($this->extensions) {
@@ -90,9 +87,6 @@ final class MimeTypes implements MimeTypesInterface
return $extensions ?? self::MAP[$mimeType] ?? self::MAP[$lcMimeType ?? strtolower($mimeType)] ?? [];
}
- /**
- * {@inheritdoc}
- */
public function getMimeTypes(string $ext): array
{
if ($this->mimeTypes) {
@@ -102,9 +96,6 @@ final class MimeTypes implements MimeTypesInterface
return $mimeTypes ?? self::REVERSE_MAP[$ext] ?? self::REVERSE_MAP[$lcExt ?? strtolower($ext)] ?? [];
}
- /**
- * {@inheritdoc}
- */
public function isGuesserSupported(): bool
{
foreach ($this->guessers as $guesser) {
@@ -117,8 +108,6 @@ final class MimeTypes implements MimeTypesInterface
}
/**
- * {@inheritdoc}
- *
* The file is passed to each registered MIME type guesser in reverse order
* of their registration (last registered is queried first). Once a guesser
* returns a value that is not null, this method terminates and returns the
diff --git a/vendor/symfony/mime/Part/AbstractMultipartPart.php b/vendor/symfony/mime/Part/AbstractMultipartPart.php
index 685d25062..b7980ea0c 100644
--- a/vendor/symfony/mime/Part/AbstractMultipartPart.php
+++ b/vendor/symfony/mime/Part/AbstractMultipartPart.php
@@ -90,10 +90,6 @@ abstract class AbstractMultipartPart extends AbstractPart
private function getBoundary(): string
{
- if (null === $this->boundary) {
- $this->boundary = strtr(base64_encode(random_bytes(6)), '+/', '-_');
- }
-
- return $this->boundary;
+ return $this->boundary ??= strtr(base64_encode(random_bytes(6)), '+/', '-_');
}
}
diff --git a/vendor/symfony/mime/Part/DataPart.php b/vendor/symfony/mime/Part/DataPart.php
index 300772edf..b42ecb4da 100644
--- a/vendor/symfony/mime/Part/DataPart.php
+++ b/vendor/symfony/mime/Part/DataPart.php
@@ -13,7 +13,6 @@ namespace Symfony\Component\Mime\Part;
use Symfony\Component\Mime\Exception\InvalidArgumentException;
use Symfony\Component\Mime\Header\Headers;
-use Symfony\Component\Mime\MimeTypes;
/**
* @author Fabien Potencier
@@ -23,23 +22,22 @@ class DataPart extends TextPart
/** @internal */
protected $_parent;
- private static $mimeTypes;
-
private $filename;
private $mediaType;
private $cid;
- private $handle;
/**
- * @param resource|string $body
+ * @param resource|string|File $body Use a File instance to defer loading the file until rendering
*/
public function __construct($body, string $filename = null, string $contentType = null, string $encoding = null)
{
unset($this->_parent);
- if (null === $contentType) {
- $contentType = 'application/octet-stream';
+ if ($body instanceof File && !$filename) {
+ $filename = $body->getFilename();
}
+
+ $contentType ??= $body instanceof File ? $body->getContentType() : 'application/octet-stream';
[$this->mediaType, $subtype] = explode('/', $contentType);
parent::__construct($body, null, $subtype, $encoding);
@@ -53,32 +51,7 @@ class DataPart extends TextPart
public static function fromPath(string $path, string $name = null, string $contentType = null): self
{
- if (null === $contentType) {
- $ext = strtolower(substr($path, strrpos($path, '.') + 1));
- if (null === self::$mimeTypes) {
- self::$mimeTypes = new MimeTypes();
- }
- $contentType = self::$mimeTypes->getMimeTypes($ext)[0] ?? 'application/octet-stream';
- }
-
- if ((is_file($path) && !is_readable($path)) || is_dir($path)) {
- throw new InvalidArgumentException(sprintf('Path "%s" is not readable.', $path));
- }
-
- if (false === $handle = @fopen($path, 'r', false)) {
- throw new InvalidArgumentException(sprintf('Unable to open path "%s".', $path));
- }
-
- if (!is_file($path)) {
- $cache = fopen('php://temp', 'r+');
- stream_copy_to_stream($handle, $cache);
- $handle = $cache;
- }
-
- $p = new self($handle, $name ?: basename($path), $contentType);
- $p->handle = $handle;
-
- return $p;
+ return new self(new File($path), $name, $contentType);
}
/**
@@ -89,6 +62,20 @@ class DataPart extends TextPart
return $this->setDisposition('inline');
}
+ /**
+ * @return $this
+ */
+ public function setContentId(string $cid): static
+ {
+ if (!str_contains($cid, '@')) {
+ throw new InvalidArgumentException(sprintf('Invalid cid "%s".', $cid));
+ }
+
+ $this->cid = $cid;
+
+ return $this;
+ }
+
public function getContentId(): string
{
return $this->cid ?: $this->cid = $this->generateContentId();
@@ -129,18 +116,21 @@ class DataPart extends TextPart
return $str;
}
+ public function getFilename(): ?string
+ {
+ return $this->filename;
+ }
+
+ public function getContentType(): string
+ {
+ return implode('/', [$this->getMediaType(), $this->getMediaSubtype()]);
+ }
+
private function generateContentId(): string
{
return bin2hex(random_bytes(16)).'@symfony';
}
- public function __destruct()
- {
- if (null !== $this->handle && \is_resource($this->handle)) {
- fclose($this->handle);
- }
- }
-
public function __sleep(): array
{
// converts the body to a string
@@ -149,7 +139,6 @@ class DataPart extends TextPart
$this->_parent = [];
foreach (['body', 'charset', 'subtype', 'disposition', 'name', 'encoding'] as $name) {
$r = new \ReflectionProperty(TextPart::class, $name);
- $r->setAccessible(true);
$this->_parent[$name] = $r->getValue($this);
}
$this->_headers = $this->getHeaders();
@@ -160,7 +149,6 @@ class DataPart extends TextPart
public function __wakeup()
{
$r = new \ReflectionProperty(AbstractPart::class, 'headers');
- $r->setAccessible(true);
$r->setValue($this, $this->_headers);
unset($this->_headers);
@@ -172,7 +160,6 @@ class DataPart extends TextPart
throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
}
$r = new \ReflectionProperty(TextPart::class, $name);
- $r->setAccessible(true);
$r->setValue($this, $this->_parent[$name]);
}
unset($this->_parent);
diff --git a/vendor/symfony/mime/Part/File.php b/vendor/symfony/mime/Part/File.php
new file mode 100644
index 000000000..0d75066ea
--- /dev/null
+++ b/vendor/symfony/mime/Part/File.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Mime\Part;
+
+use Symfony\Component\Mime\MimeTypes;
+
+/**
+ * @author Fabien Potencier
+ */
+class File
+{
+ private static $mimeTypes;
+
+ public function __construct(
+ private string $path,
+ private ?string $filename = null,
+ ) {
+ }
+
+ public function getPath(): string
+ {
+ return $this->path;
+ }
+
+ public function getContentType(): string
+ {
+ $ext = strtolower(pathinfo($this->path, \PATHINFO_EXTENSION));
+ self::$mimeTypes ??= new MimeTypes();
+
+ return self::$mimeTypes->getMimeTypes($ext)[0] ?? 'application/octet-stream';
+ }
+
+ public function getSize(): int
+ {
+ return filesize($this->path);
+ }
+
+ public function getFilename(): string
+ {
+ return $this->filename ??= basename($this->getPath());
+ }
+}
diff --git a/vendor/symfony/mime/Part/Multipart/FormDataPart.php b/vendor/symfony/mime/Part/Multipart/FormDataPart.php
index 5f473ed25..e7dc3b17b 100644
--- a/vendor/symfony/mime/Part/Multipart/FormDataPart.php
+++ b/vendor/symfony/mime/Part/Multipart/FormDataPart.php
@@ -34,7 +34,7 @@ final class FormDataPart extends AbstractMultipartPart
foreach ($fields as $name => $value) {
if (!\is_string($value) && !\is_array($value) && !$value instanceof TextPart) {
- throw new InvalidArgumentException(sprintf('A form field value can only be a string, an array, or an instance of TextPart ("%s" given).', get_debug_type($value)));
+ throw new InvalidArgumentException(sprintf('The value of the form field "%s" can only be a string, an array, or an instance of TextPart ("%s" given).', $name, get_debug_type($value)));
}
$this->fields[$name] = $value;
@@ -58,7 +58,7 @@ final class FormDataPart extends AbstractMultipartPart
$values = [];
$prepare = function ($item, $key, $root = null) use (&$values, &$prepare) {
- if (\is_int($key) && \is_array($item)) {
+ if (null === $root && \is_int($key) && \is_array($item)) {
if (1 !== \count($item)) {
throw new InvalidArgumentException(sprintf('Form field values with integer keys can only have one array element, the key being the field name and the value being the field value, %d provided.', \count($item)));
}
@@ -96,10 +96,7 @@ final class FormDataPart extends AbstractMultipartPart
{
static $r;
- if (null === $r) {
- $r = new \ReflectionProperty(TextPart::class, 'encoding');
- $r->setAccessible(true);
- }
+ $r ??= new \ReflectionProperty(TextPart::class, 'encoding');
$part->setDisposition('form-data');
$part->setName($name);
diff --git a/vendor/symfony/mime/Part/SMimePart.php b/vendor/symfony/mime/Part/SMimePart.php
index 29e9dd19e..7021819f6 100644
--- a/vendor/symfony/mime/Part/SMimePart.php
+++ b/vendor/symfony/mime/Part/SMimePart.php
@@ -107,7 +107,6 @@ class SMimePart extends AbstractPart
public function __wakeup(): void
{
$r = new \ReflectionProperty(AbstractPart::class, 'headers');
- $r->setAccessible(true);
$r->setValue($this, $this->_headers);
unset($this->_headers);
}
diff --git a/vendor/symfony/mime/Part/TextPart.php b/vendor/symfony/mime/Part/TextPart.php
index bd73861d5..3b1eb8d82 100644
--- a/vendor/symfony/mime/Part/TextPart.php
+++ b/vendor/symfony/mime/Part/TextPart.php
@@ -40,7 +40,7 @@ class TextPart extends AbstractPart
private $seekable;
/**
- * @param resource|string $body
+ * @param resource|string|File $body Use a File instance to defer loading the file until rendering
*/
public function __construct($body, ?string $charset = 'utf-8', string $subtype = 'plain', string $encoding = null)
{
@@ -48,8 +48,15 @@ class TextPart extends AbstractPart
parent::__construct();
- if (!\is_string($body) && !\is_resource($body)) {
- throw new \TypeError(sprintf('The body of "%s" must be a string or a resource (got "%s").', self::class, get_debug_type($body)));
+ if (!\is_string($body) && !\is_resource($body) && !$body instanceof File) {
+ throw new \TypeError(sprintf('The body of "%s" must be a string, a resource, or an instance of "%s" (got "%s").', self::class, File::class, get_debug_type($body)));
+ }
+
+ if ($body instanceof File) {
+ $path = $body->getPath();
+ if ((is_file($path) && !is_readable($path)) || is_dir($path)) {
+ throw new InvalidArgumentException(sprintf('Path "%s" is not readable.', $path));
+ }
}
$this->body = $body;
@@ -89,6 +96,14 @@ class TextPart extends AbstractPart
return $this;
}
+ /**
+ * @return ?string null or one of attachment, inline, or form-data
+ */
+ public function getDisposition(): ?string
+ {
+ return $this->disposition;
+ }
+
/**
* Sets the name of the file (used by FormDataPart).
*
@@ -101,8 +116,20 @@ class TextPart extends AbstractPart
return $this;
}
+ /**
+ * Gets the name of the file.
+ */
+ public function getName(): ?string
+ {
+ return $this->name;
+ }
+
public function getBody(): string
{
+ if ($this->body instanceof File) {
+ return file_get_contents($this->body->getPath());
+ }
+
if (null === $this->seekable) {
return $this->body;
}
@@ -121,7 +148,14 @@ class TextPart extends AbstractPart
public function bodyToIterable(): iterable
{
- if (null !== $this->seekable) {
+ if ($this->body instanceof File) {
+ $path = $this->body->getPath();
+ if (false === $handle = @fopen($path, 'r', false)) {
+ throw new InvalidArgumentException(sprintf('Unable to open path "%s".', $path));
+ }
+
+ yield from $this->getEncoder()->encodeByteStream($handle);
+ } elseif (null !== $this->seekable) {
if ($this->seekable) {
rewind($this->body);
}
@@ -170,14 +204,14 @@ class TextPart extends AbstractPart
private function getEncoder(): ContentEncoderInterface
{
if ('8bit' === $this->encoding) {
- return self::$encoders[$this->encoding] ?? (self::$encoders[$this->encoding] = new EightBitContentEncoder());
+ return self::$encoders[$this->encoding] ??= new EightBitContentEncoder();
}
if ('quoted-printable' === $this->encoding) {
- return self::$encoders[$this->encoding] ?? (self::$encoders[$this->encoding] = new QpContentEncoder());
+ return self::$encoders[$this->encoding] ??= new QpContentEncoder();
}
- return self::$encoders[$this->encoding] ?? (self::$encoders[$this->encoding] = new Base64ContentEncoder());
+ return self::$encoders[$this->encoding] ??= new Base64ContentEncoder();
}
private function chooseEncoding(): string
@@ -192,7 +226,7 @@ class TextPart extends AbstractPart
public function __sleep(): array
{
// convert resources to strings for serialization
- if (null !== $this->seekable) {
+ if (null !== $this->seekable || $this->body instanceof File) {
$this->body = $this->getBody();
$this->seekable = null;
}
@@ -205,7 +239,6 @@ class TextPart extends AbstractPart
public function __wakeup()
{
$r = new \ReflectionProperty(AbstractPart::class, 'headers');
- $r->setAccessible(true);
$r->setValue($this, $this->_headers);
unset($this->_headers);
}
diff --git a/vendor/symfony/mime/RawMessage.php b/vendor/symfony/mime/RawMessage.php
index ed051aab6..aec398c58 100644
--- a/vendor/symfony/mime/RawMessage.php
+++ b/vendor/symfony/mime/RawMessage.php
@@ -54,6 +54,8 @@ class RawMessage
}
/**
+ * @return void
+ *
* @throws LogicException if the message is not valid
*/
public function ensureValidity()
diff --git a/vendor/symfony/mime/Test/Constraint/EmailAddressContains.php b/vendor/symfony/mime/Test/Constraint/EmailAddressContains.php
index e32e3d951..6ca9cc058 100644
--- a/vendor/symfony/mime/Test/Constraint/EmailAddressContains.php
+++ b/vendor/symfony/mime/Test/Constraint/EmailAddressContains.php
@@ -27,9 +27,6 @@ final class EmailAddressContains extends Constraint
$this->expectedValue = $expectedValue;
}
- /**
- * {@inheritdoc}
- */
public function toString(): string
{
return sprintf('contains address "%s" with value "%s"', $this->headerName, $this->expectedValue);
@@ -37,12 +34,10 @@ final class EmailAddressContains extends Constraint
/**
* @param RawMessage $message
- *
- * {@inheritdoc}
*/
protected function matches($message): bool
{
- if (RawMessage::class === \get_class($message)) {
+ if (RawMessage::class === $message::class) {
throw new \LogicException('Unable to test a message address on a RawMessage instance.');
}
@@ -64,8 +59,6 @@ final class EmailAddressContains extends Constraint
/**
* @param RawMessage $message
- *
- * {@inheritdoc}
*/
protected function failureDescription($message): string
{
diff --git a/vendor/symfony/mime/Test/Constraint/EmailAttachmentCount.php b/vendor/symfony/mime/Test/Constraint/EmailAttachmentCount.php
index a0ee81ddd..d5c8ae981 100644
--- a/vendor/symfony/mime/Test/Constraint/EmailAttachmentCount.php
+++ b/vendor/symfony/mime/Test/Constraint/EmailAttachmentCount.php
@@ -26,9 +26,6 @@ final class EmailAttachmentCount extends Constraint
$this->transport = $transport;
}
- /**
- * {@inheritdoc}
- */
public function toString(): string
{
return sprintf('has sent "%d" attachment(s)', $this->expectedValue);
@@ -36,12 +33,10 @@ final class EmailAttachmentCount extends Constraint
/**
* @param RawMessage $message
- *
- * {@inheritdoc}
*/
protected function matches($message): bool
{
- if (RawMessage::class === \get_class($message) || Message::class === \get_class($message)) {
+ if (RawMessage::class === $message::class || Message::class === $message::class) {
throw new \LogicException('Unable to test a message attachment on a RawMessage or Message instance.');
}
@@ -50,8 +45,6 @@ final class EmailAttachmentCount extends Constraint
/**
* @param RawMessage $message
- *
- * {@inheritdoc}
*/
protected function failureDescription($message): string
{
diff --git a/vendor/symfony/mime/Test/Constraint/EmailHasHeader.php b/vendor/symfony/mime/Test/Constraint/EmailHasHeader.php
index ef4a28585..b7c5625b2 100644
--- a/vendor/symfony/mime/Test/Constraint/EmailHasHeader.php
+++ b/vendor/symfony/mime/Test/Constraint/EmailHasHeader.php
@@ -23,9 +23,6 @@ final class EmailHasHeader extends Constraint
$this->headerName = $headerName;
}
- /**
- * {@inheritdoc}
- */
public function toString(): string
{
return sprintf('has header "%s"', $this->headerName);
@@ -33,12 +30,10 @@ final class EmailHasHeader extends Constraint
/**
* @param RawMessage $message
- *
- * {@inheritdoc}
*/
protected function matches($message): bool
{
- if (RawMessage::class === \get_class($message)) {
+ if (RawMessage::class === $message::class) {
throw new \LogicException('Unable to test a message header on a RawMessage instance.');
}
@@ -47,8 +42,6 @@ final class EmailHasHeader extends Constraint
/**
* @param RawMessage $message
- *
- * {@inheritdoc}
*/
protected function failureDescription($message): string
{
diff --git a/vendor/symfony/mime/Test/Constraint/EmailHeaderSame.php b/vendor/symfony/mime/Test/Constraint/EmailHeaderSame.php
index bb0cba698..edb6ab425 100644
--- a/vendor/symfony/mime/Test/Constraint/EmailHeaderSame.php
+++ b/vendor/symfony/mime/Test/Constraint/EmailHeaderSame.php
@@ -26,9 +26,6 @@ final class EmailHeaderSame extends Constraint
$this->expectedValue = $expectedValue;
}
- /**
- * {@inheritdoc}
- */
public function toString(): string
{
return sprintf('has header "%s" with value "%s"', $this->headerName, $this->expectedValue);
@@ -36,12 +33,10 @@ final class EmailHeaderSame extends Constraint
/**
* @param RawMessage $message
- *
- * {@inheritdoc}
*/
protected function matches($message): bool
{
- if (RawMessage::class === \get_class($message)) {
+ if (RawMessage::class === $message::class) {
throw new \LogicException('Unable to test a message header on a RawMessage instance.');
}
@@ -50,8 +45,6 @@ final class EmailHeaderSame extends Constraint
/**
* @param RawMessage $message
- *
- * {@inheritdoc}
*/
protected function failureDescription($message): string
{
diff --git a/vendor/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php b/vendor/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php
index 67ab85eb6..cbf303d1b 100644
--- a/vendor/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php
+++ b/vendor/symfony/mime/Test/Constraint/EmailHtmlBodyContains.php
@@ -24,31 +24,24 @@ final class EmailHtmlBodyContains extends Constraint
$this->expectedText = $expectedText;
}
- /**
- * {@inheritdoc}
- */
public function toString(): string
{
return sprintf('contains "%s"', $this->expectedText);
}
/**
- * {@inheritdoc}
- *
* @param RawMessage $message
*/
protected function matches($message): bool
{
- if (RawMessage::class === \get_class($message) || Message::class === \get_class($message)) {
+ if (RawMessage::class === $message::class || Message::class === $message::class) {
throw new \LogicException('Unable to test a message HTML body on a RawMessage or Message instance.');
}
- return false !== mb_strpos($message->getHtmlBody(), $this->expectedText);
+ return str_contains($message->getHtmlBody(), $this->expectedText);
}
/**
- * {@inheritdoc}
- *
* @param RawMessage $message
*/
protected function failureDescription($message): string
diff --git a/vendor/symfony/mime/Test/Constraint/EmailTextBodyContains.php b/vendor/symfony/mime/Test/Constraint/EmailTextBodyContains.php
index f31655fd0..592595cf4 100644
--- a/vendor/symfony/mime/Test/Constraint/EmailTextBodyContains.php
+++ b/vendor/symfony/mime/Test/Constraint/EmailTextBodyContains.php
@@ -24,31 +24,24 @@ final class EmailTextBodyContains extends Constraint
$this->expectedText = $expectedText;
}
- /**
- * {@inheritdoc}
- */
public function toString(): string
{
return sprintf('contains "%s"', $this->expectedText);
}
/**
- * {@inheritdoc}
- *
* @param RawMessage $message
*/
protected function matches($message): bool
{
- if (RawMessage::class === \get_class($message) || Message::class === \get_class($message)) {
+ if (RawMessage::class === $message::class || Message::class === $message::class) {
throw new \LogicException('Unable to test a message text body on a RawMessage or Message instance.');
}
- return false !== mb_strpos($message->getTextBody(), $this->expectedText);
+ return str_contains($message->getTextBody(), $this->expectedText);
}
/**
- * {@inheritdoc}
- *
* @param RawMessage $message
*/
protected function failureDescription($message): string
diff --git a/vendor/symfony/mime/composer.json b/vendor/symfony/mime/composer.json
index 02e3a07f0..b7bdc7824 100644
--- a/vendor/symfony/mime/composer.json
+++ b/vendor/symfony/mime/composer.json
@@ -16,24 +16,26 @@
}
],
"require": {
- "php": ">=8.0.2",
+ "php": ">=8.1",
+ "symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-intl-idn": "^1.10",
"symfony/polyfill-mbstring": "^1.0"
},
"require-dev": {
"egulias/email-validator": "^2.1.10|^3.1|^4",
+ "league/html-to-markdown": "^5.0",
"phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0",
"symfony/dependency-injection": "^5.4|^6.0",
"symfony/property-access": "^5.4|^6.0",
"symfony/property-info": "^5.4|^6.0",
- "symfony/serializer": "^5.4.14|~6.0.14|^6.1.6"
+ "symfony/serializer": "~6.2.13|^6.3.2"
},
"conflict": {
"egulias/email-validator": "~3.0.0",
"phpdocumentor/reflection-docblock": "<3.2.2",
"phpdocumentor/type-resolver": "<1.4.0",
"symfony/mailer": "<5.4",
- "symfony/serializer": "<5.4.14|>=6.0,<6.0.14|>=6.1,<6.1.6"
+ "symfony/serializer": "<6.2.13|>=6.3,<6.3.2"
},
"autoload": {
"psr-4": { "Symfony\\Component\\Mime\\": "" },
diff --git a/vendor/symfony/polyfill-ctype/Ctype.php b/vendor/symfony/polyfill-ctype/Ctype.php
deleted file mode 100755
index ba75a2c95..000000000
--- a/vendor/symfony/polyfill-ctype/Ctype.php
+++ /dev/null
@@ -1,232 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Polyfill\Ctype;
-
-/**
- * Ctype implementation through regex.
- *
- * @internal
- *
- * @author Gert de Pagter
- */
-final class Ctype
-{
- /**
- * Returns TRUE if every character in text is either a letter or a digit, FALSE otherwise.
- *
- * @see https://php.net/ctype-alnum
- *
- * @param mixed $text
- *
- * @return bool
- */
- public static function ctype_alnum($text)
- {
- $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
-
- return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z0-9]/', $text);
- }
-
- /**
- * Returns TRUE if every character in text is a letter, FALSE otherwise.
- *
- * @see https://php.net/ctype-alpha
- *
- * @param mixed $text
- *
- * @return bool
- */
- public static function ctype_alpha($text)
- {
- $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
-
- return \is_string($text) && '' !== $text && !preg_match('/[^A-Za-z]/', $text);
- }
-
- /**
- * Returns TRUE if every character in text is a control character from the current locale, FALSE otherwise.
- *
- * @see https://php.net/ctype-cntrl
- *
- * @param mixed $text
- *
- * @return bool
- */
- public static function ctype_cntrl($text)
- {
- $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
-
- return \is_string($text) && '' !== $text && !preg_match('/[^\x00-\x1f\x7f]/', $text);
- }
-
- /**
- * Returns TRUE if every character in the string text is a decimal digit, FALSE otherwise.
- *
- * @see https://php.net/ctype-digit
- *
- * @param mixed $text
- *
- * @return bool
- */
- public static function ctype_digit($text)
- {
- $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
-
- return \is_string($text) && '' !== $text && !preg_match('/[^0-9]/', $text);
- }
-
- /**
- * Returns TRUE if every character in text is printable and actually creates visible output (no white space), FALSE otherwise.
- *
- * @see https://php.net/ctype-graph
- *
- * @param mixed $text
- *
- * @return bool
- */
- public static function ctype_graph($text)
- {
- $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
-
- return \is_string($text) && '' !== $text && !preg_match('/[^!-~]/', $text);
- }
-
- /**
- * Returns TRUE if every character in text is a lowercase letter.
- *
- * @see https://php.net/ctype-lower
- *
- * @param mixed $text
- *
- * @return bool
- */
- public static function ctype_lower($text)
- {
- $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
-
- return \is_string($text) && '' !== $text && !preg_match('/[^a-z]/', $text);
- }
-
- /**
- * Returns TRUE if every character in text will actually create output (including blanks). Returns FALSE if text contains control characters or characters that do not have any output or control function at all.
- *
- * @see https://php.net/ctype-print
- *
- * @param mixed $text
- *
- * @return bool
- */
- public static function ctype_print($text)
- {
- $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
-
- return \is_string($text) && '' !== $text && !preg_match('/[^ -~]/', $text);
- }
-
- /**
- * Returns TRUE if every character in text is printable, but neither letter, digit or blank, FALSE otherwise.
- *
- * @see https://php.net/ctype-punct
- *
- * @param mixed $text
- *
- * @return bool
- */
- public static function ctype_punct($text)
- {
- $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
-
- return \is_string($text) && '' !== $text && !preg_match('/[^!-\/\:-@\[-`\{-~]/', $text);
- }
-
- /**
- * Returns TRUE if every character in text creates some sort of white space, FALSE otherwise. Besides the blank character this also includes tab, vertical tab, line feed, carriage return and form feed characters.
- *
- * @see https://php.net/ctype-space
- *
- * @param mixed $text
- *
- * @return bool
- */
- public static function ctype_space($text)
- {
- $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
-
- return \is_string($text) && '' !== $text && !preg_match('/[^\s]/', $text);
- }
-
- /**
- * Returns TRUE if every character in text is an uppercase letter.
- *
- * @see https://php.net/ctype-upper
- *
- * @param mixed $text
- *
- * @return bool
- */
- public static function ctype_upper($text)
- {
- $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
-
- return \is_string($text) && '' !== $text && !preg_match('/[^A-Z]/', $text);
- }
-
- /**
- * Returns TRUE if every character in text is a hexadecimal 'digit', that is a decimal digit or a character from [A-Fa-f] , FALSE otherwise.
- *
- * @see https://php.net/ctype-xdigit
- *
- * @param mixed $text
- *
- * @return bool
- */
- public static function ctype_xdigit($text)
- {
- $text = self::convert_int_to_char_for_ctype($text, __FUNCTION__);
-
- return \is_string($text) && '' !== $text && !preg_match('/[^A-Fa-f0-9]/', $text);
- }
-
- /**
- * Converts integers to their char versions according to normal ctype behaviour, if needed.
- *
- * If an integer between -128 and 255 inclusive is provided,
- * it is interpreted as the ASCII value of a single character
- * (negative values have 256 added in order to allow characters in the Extended ASCII range).
- * Any other integer is interpreted as a string containing the decimal digits of the integer.
- *
- * @param mixed $int
- * @param string $function
- *
- * @return mixed
- */
- private static function convert_int_to_char_for_ctype($int, $function)
- {
- if (!\is_int($int)) {
- return $int;
- }
-
- if ($int < -128 || $int > 255) {
- return (string) $int;
- }
-
- if (\PHP_VERSION_ID >= 80100) {
- @trigger_error($function.'(): Argument of type int will be interpreted as string in the future', \E_USER_DEPRECATED);
- }
-
- if ($int < 0) {
- $int += 256;
- }
-
- return \chr($int);
- }
-}
diff --git a/vendor/symfony/polyfill-ctype/README.md b/vendor/symfony/polyfill-ctype/README.md
deleted file mode 100755
index b144d03c3..000000000
--- a/vendor/symfony/polyfill-ctype/README.md
+++ /dev/null
@@ -1,12 +0,0 @@
-Symfony Polyfill / Ctype
-========================
-
-This component provides `ctype_*` functions to users who run php versions without the ctype extension.
-
-More information can be found in the
-[main Polyfill README](https://github.com/symfony/polyfill/blob/main/README.md).
-
-License
-=======
-
-This library is released under the [MIT license](LICENSE).
diff --git a/vendor/symfony/polyfill-ctype/bootstrap.php b/vendor/symfony/polyfill-ctype/bootstrap.php
deleted file mode 100755
index d54524b31..000000000
--- a/vendor/symfony/polyfill-ctype/bootstrap.php
+++ /dev/null
@@ -1,50 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-use Symfony\Polyfill\Ctype as p;
-
-if (\PHP_VERSION_ID >= 80000) {
- return require __DIR__.'/bootstrap80.php';
-}
-
-if (!function_exists('ctype_alnum')) {
- function ctype_alnum($text) { return p\Ctype::ctype_alnum($text); }
-}
-if (!function_exists('ctype_alpha')) {
- function ctype_alpha($text) { return p\Ctype::ctype_alpha($text); }
-}
-if (!function_exists('ctype_cntrl')) {
- function ctype_cntrl($text) { return p\Ctype::ctype_cntrl($text); }
-}
-if (!function_exists('ctype_digit')) {
- function ctype_digit($text) { return p\Ctype::ctype_digit($text); }
-}
-if (!function_exists('ctype_graph')) {
- function ctype_graph($text) { return p\Ctype::ctype_graph($text); }
-}
-if (!function_exists('ctype_lower')) {
- function ctype_lower($text) { return p\Ctype::ctype_lower($text); }
-}
-if (!function_exists('ctype_print')) {
- function ctype_print($text) { return p\Ctype::ctype_print($text); }
-}
-if (!function_exists('ctype_punct')) {
- function ctype_punct($text) { return p\Ctype::ctype_punct($text); }
-}
-if (!function_exists('ctype_space')) {
- function ctype_space($text) { return p\Ctype::ctype_space($text); }
-}
-if (!function_exists('ctype_upper')) {
- function ctype_upper($text) { return p\Ctype::ctype_upper($text); }
-}
-if (!function_exists('ctype_xdigit')) {
- function ctype_xdigit($text) { return p\Ctype::ctype_xdigit($text); }
-}
diff --git a/vendor/symfony/polyfill-ctype/bootstrap80.php b/vendor/symfony/polyfill-ctype/bootstrap80.php
deleted file mode 100755
index ab2f8611d..000000000
--- a/vendor/symfony/polyfill-ctype/bootstrap80.php
+++ /dev/null
@@ -1,46 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-use Symfony\Polyfill\Ctype as p;
-
-if (!function_exists('ctype_alnum')) {
- function ctype_alnum(mixed $text): bool { return p\Ctype::ctype_alnum($text); }
-}
-if (!function_exists('ctype_alpha')) {
- function ctype_alpha(mixed $text): bool { return p\Ctype::ctype_alpha($text); }
-}
-if (!function_exists('ctype_cntrl')) {
- function ctype_cntrl(mixed $text): bool { return p\Ctype::ctype_cntrl($text); }
-}
-if (!function_exists('ctype_digit')) {
- function ctype_digit(mixed $text): bool { return p\Ctype::ctype_digit($text); }
-}
-if (!function_exists('ctype_graph')) {
- function ctype_graph(mixed $text): bool { return p\Ctype::ctype_graph($text); }
-}
-if (!function_exists('ctype_lower')) {
- function ctype_lower(mixed $text): bool { return p\Ctype::ctype_lower($text); }
-}
-if (!function_exists('ctype_print')) {
- function ctype_print(mixed $text): bool { return p\Ctype::ctype_print($text); }
-}
-if (!function_exists('ctype_punct')) {
- function ctype_punct(mixed $text): bool { return p\Ctype::ctype_punct($text); }
-}
-if (!function_exists('ctype_space')) {
- function ctype_space(mixed $text): bool { return p\Ctype::ctype_space($text); }
-}
-if (!function_exists('ctype_upper')) {
- function ctype_upper(mixed $text): bool { return p\Ctype::ctype_upper($text); }
-}
-if (!function_exists('ctype_xdigit')) {
- function ctype_xdigit(mixed $text): bool { return p\Ctype::ctype_xdigit($text); }
-}
diff --git a/vendor/symfony/polyfill-ctype/composer.json b/vendor/symfony/polyfill-ctype/composer.json
deleted file mode 100755
index 1b3efff57..000000000
--- a/vendor/symfony/polyfill-ctype/composer.json
+++ /dev/null
@@ -1,41 +0,0 @@
-{
- "name": "symfony/polyfill-ctype",
- "type": "library",
- "description": "Symfony polyfill for ctype functions",
- "keywords": ["polyfill", "compatibility", "portable", "ctype"],
- "homepage": "https://symfony.com",
- "license": "MIT",
- "authors": [
- {
- "name": "Gert de Pagter",
- "email": "BackEndTea@gmail.com"
- },
- {
- "name": "Symfony Community",
- "homepage": "https://symfony.com/contributors"
- }
- ],
- "require": {
- "php": ">=7.1"
- },
- "provide": {
- "ext-ctype": "*"
- },
- "autoload": {
- "psr-4": { "Symfony\\Polyfill\\Ctype\\": "" },
- "files": [ "bootstrap.php" ]
- },
- "suggest": {
- "ext-ctype": "For best performance"
- },
- "minimum-stability": "dev",
- "extra": {
- "branch-alias": {
- "dev-main": "1.27-dev"
- },
- "thanks": {
- "name": "symfony/polyfill",
- "url": "https://github.com/symfony/polyfill"
- }
- }
-}
diff --git a/vendor/symfony/process/CHANGELOG.md b/vendor/symfony/process/CHANGELOG.md
new file mode 100644
index 000000000..31b9ee6a2
--- /dev/null
+++ b/vendor/symfony/process/CHANGELOG.md
@@ -0,0 +1,116 @@
+CHANGELOG
+=========
+
+5.2.0
+-----
+
+ * added `Process::setOptions()` to set `Process` specific options
+ * added option `create_new_console` to allow a subprocess to continue
+ to run after the main script exited, both on Linux and on Windows
+
+5.1.0
+-----
+
+ * added `Process::getStartTime()` to retrieve the start time of the process as float
+
+5.0.0
+-----
+
+ * removed `Process::inheritEnvironmentVariables()`
+ * removed `PhpProcess::setPhpBinary()`
+ * `Process` must be instantiated with a command array, use `Process::fromShellCommandline()` when the command should be parsed by the shell
+ * removed `Process::setCommandLine()`
+
+4.4.0
+-----
+
+ * deprecated `Process::inheritEnvironmentVariables()`: env variables are always inherited.
+ * added `Process::getLastOutputTime()` method
+
+4.2.0
+-----
+
+ * added the `Process::fromShellCommandline()` to run commands in a shell wrapper
+ * deprecated passing a command as string when creating a `Process` instance
+ * deprecated the `Process::setCommandline()` and the `PhpProcess::setPhpBinary()` methods
+ * added the `Process::waitUntil()` method to wait for the process only for a
+ specific output, then continue the normal execution of your application
+
+4.1.0
+-----
+
+ * added the `Process::isTtySupported()` method that allows to check for TTY support
+ * made `PhpExecutableFinder` look for the `PHP_BINARY` env var when searching the php binary
+ * added the `ProcessSignaledException` class to properly catch signaled process errors
+
+4.0.0
+-----
+
+ * environment variables will always be inherited
+ * added a second `array $env = []` argument to the `start()`, `run()`,
+ `mustRun()`, and `restart()` methods of the `Process` class
+ * added a second `array $env = []` argument to the `start()` method of the
+ `PhpProcess` class
+ * the `ProcessUtils::escapeArgument()` method has been removed
+ * the `areEnvironmentVariablesInherited()`, `getOptions()`, and `setOptions()`
+ methods of the `Process` class have been removed
+ * support for passing `proc_open()` options has been removed
+ * removed the `ProcessBuilder` class, use the `Process` class instead
+ * removed the `getEnhanceWindowsCompatibility()` and `setEnhanceWindowsCompatibility()` methods of the `Process` class
+ * passing a not existing working directory to the constructor of the `Symfony\Component\Process\Process` class is not
+ supported anymore
+
+3.4.0
+-----
+
+ * deprecated the ProcessBuilder class
+ * deprecated calling `Process::start()` without setting a valid working directory beforehand (via `setWorkingDirectory()` or constructor)
+
+3.3.0
+-----
+
+ * added command line arrays in the `Process` class
+ * added `$env` argument to `Process::start()`, `run()`, `mustRun()` and `restart()` methods
+ * deprecated the `ProcessUtils::escapeArgument()` method
+ * deprecated not inheriting environment variables
+ * deprecated configuring `proc_open()` options
+ * deprecated configuring enhanced Windows compatibility
+ * deprecated configuring enhanced sigchild compatibility
+
+2.5.0
+-----
+
+ * added support for PTY mode
+ * added the convenience method "mustRun"
+ * deprecation: Process::setStdin() is deprecated in favor of Process::setInput()
+ * deprecation: Process::getStdin() is deprecated in favor of Process::getInput()
+ * deprecation: Process::setInput() and ProcessBuilder::setInput() do not accept non-scalar types
+
+2.4.0
+-----
+
+ * added the ability to define an idle timeout
+
+2.3.0
+-----
+
+ * added ProcessUtils::escapeArgument() to fix the bug in escapeshellarg() function on Windows
+ * added Process::signal()
+ * added Process::getPid()
+ * added support for a TTY mode
+
+2.2.0
+-----
+
+ * added ProcessBuilder::setArguments() to reset the arguments on a builder
+ * added a way to retrieve the standard and error output incrementally
+ * added Process:restart()
+
+2.1.0
+-----
+
+ * added support for non-blocking processes (start(), wait(), isRunning(), stop())
+ * enhanced Windows compatibility
+ * added Process::getExitCodeText() that returns a string representation for
+ the exit code returned by the process
+ * added ProcessBuilder
diff --git a/vendor/symfony/process/Exception/ExceptionInterface.php b/vendor/symfony/process/Exception/ExceptionInterface.php
new file mode 100644
index 000000000..bd4a60403
--- /dev/null
+++ b/vendor/symfony/process/Exception/ExceptionInterface.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * Marker Interface for the Process Component.
+ *
+ * @author Johannes M. Schmitt
+ */
+interface ExceptionInterface extends \Throwable
+{
+}
diff --git a/vendor/symfony/process/Exception/InvalidArgumentException.php b/vendor/symfony/process/Exception/InvalidArgumentException.php
new file mode 100644
index 000000000..926ee2118
--- /dev/null
+++ b/vendor/symfony/process/Exception/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * InvalidArgumentException for the Process Component.
+ *
+ * @author Romain Neutron
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/process/Exception/LogicException.php b/vendor/symfony/process/Exception/LogicException.php
new file mode 100644
index 000000000..be3d490dd
--- /dev/null
+++ b/vendor/symfony/process/Exception/LogicException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+/**
+ * LogicException for the Process Component.
+ *
+ * @author Romain Neutron
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/process/Exception/ProcessFailedException.php b/vendor/symfony/process/Exception/ProcessFailedException.php
new file mode 100644
index 000000000..328acfde5
--- /dev/null
+++ b/vendor/symfony/process/Exception/ProcessFailedException.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception for failed processes.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ProcessFailedException extends RuntimeException
+{
+ private $process;
+
+ public function __construct(Process $process)
+ {
+ if ($process->isSuccessful()) {
+ throw new InvalidArgumentException('Expected a failed process, but the given process was successful.');
+ }
+
+ $error = sprintf('The command "%s" failed.'."\n\nExit Code: %s(%s)\n\nWorking directory: %s",
+ $process->getCommandLine(),
+ $process->getExitCode(),
+ $process->getExitCodeText(),
+ $process->getWorkingDirectory()
+ );
+
+ if (!$process->isOutputDisabled()) {
+ $error .= sprintf("\n\nOutput:\n================\n%s\n\nError Output:\n================\n%s",
+ $process->getOutput(),
+ $process->getErrorOutput()
+ );
+ }
+
+ parent::__construct($error);
+
+ $this->process = $process;
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+}
diff --git a/vendor/symfony/process/Exception/ProcessSignaledException.php b/vendor/symfony/process/Exception/ProcessSignaledException.php
new file mode 100644
index 000000000..d4d322756
--- /dev/null
+++ b/vendor/symfony/process/Exception/ProcessSignaledException.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process has been signaled.
+ *
+ * @author Sullivan Senechal
+ */
+final class ProcessSignaledException extends RuntimeException
+{
+ private $process;
+
+ public function __construct(Process $process)
+ {
+ $this->process = $process;
+
+ parent::__construct(sprintf('The process has been signaled with signal "%s".', $process->getTermSignal()));
+ }
+
+ public function getProcess(): Process
+ {
+ return $this->process;
+ }
+
+ public function getSignal(): int
+ {
+ return $this->getProcess()->getTermSignal();
+ }
+}
diff --git a/vendor/symfony/process/Exception/ProcessTimedOutException.php b/vendor/symfony/process/Exception/ProcessTimedOutException.php
new file mode 100644
index 000000000..94391a459
--- /dev/null
+++ b/vendor/symfony/process/Exception/ProcessTimedOutException.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Exception;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * Exception that is thrown when a process times out.
+ *
+ * @author Johannes M. Schmitt
+ */
+class ProcessTimedOutException extends RuntimeException
+{
+ public const TYPE_GENERAL = 1;
+ public const TYPE_IDLE = 2;
+
+ private $process;
+ private $timeoutType;
+
+ public function __construct(Process $process, int $timeoutType)
+ {
+ $this->process = $process;
+ $this->timeoutType = $timeoutType;
+
+ parent::__construct(sprintf(
+ 'The process "%s" exceeded the timeout of %s seconds.',
+ $process->getCommandLine(),
+ $this->getExceededTimeout()
+ ));
+ }
+
+ public function getProcess()
+ {
+ return $this->process;
+ }
+
+ public function isGeneralTimeout()
+ {
+ return self::TYPE_GENERAL === $this->timeoutType;
+ }
+
+ public function isIdleTimeout()
+ {
+ return self::TYPE_IDLE === $this->timeoutType;
+ }
+
+ public function getExceededTimeout()
+ {
+ switch ($this->timeoutType) {
+ case self::TYPE_GENERAL:
+ return $this->process->getTimeout();
+
+ case self::TYPE_IDLE:
+ return $this->process->getIdleTimeout();
+
+ default:
+ throw new \LogicException(sprintf('Unknown timeout type "%d".', $this->timeoutType));
+ }
+ }
+}
diff --git a/vendor/symfony/yaml/Exception/RuntimeException.php b/vendor/symfony/process/Exception/RuntimeException.php
old mode 100755
new mode 100644
similarity index 68%
rename from vendor/symfony/yaml/Exception/RuntimeException.php
rename to vendor/symfony/process/Exception/RuntimeException.php
index 3f36b73be..adead2536
--- a/vendor/symfony/yaml/Exception/RuntimeException.php
+++ b/vendor/symfony/process/Exception/RuntimeException.php
@@ -9,12 +9,12 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Yaml\Exception;
+namespace Symfony\Component\Process\Exception;
/**
- * Exception class thrown when an error occurs during parsing.
+ * RuntimeException for the Process Component.
*
- * @author Romain Neutron
+ * @author Johannes M. Schmitt
*/
class RuntimeException extends \RuntimeException implements ExceptionInterface
{
diff --git a/vendor/symfony/process/ExecutableFinder.php b/vendor/symfony/process/ExecutableFinder.php
new file mode 100644
index 000000000..eb8f06292
--- /dev/null
+++ b/vendor/symfony/process/ExecutableFinder.php
@@ -0,0 +1,86 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * Generic executable finder.
+ *
+ * @author Fabien Potencier
+ * @author Johannes M. Schmitt
+ */
+class ExecutableFinder
+{
+ private $suffixes = ['.exe', '.bat', '.cmd', '.com'];
+
+ /**
+ * Replaces default suffixes of executable.
+ */
+ public function setSuffixes(array $suffixes)
+ {
+ $this->suffixes = $suffixes;
+ }
+
+ /**
+ * Adds new possible suffix to check for executable.
+ */
+ public function addSuffix(string $suffix)
+ {
+ $this->suffixes[] = $suffix;
+ }
+
+ /**
+ * Finds an executable by name.
+ *
+ * @param string $name The executable name (without the extension)
+ * @param string|null $default The default to return if no executable is found
+ * @param array $extraDirs Additional dirs to check into
+ *
+ * @return string|null
+ */
+ public function find(string $name, string $default = null, array $extraDirs = [])
+ {
+ if (\ini_get('open_basedir')) {
+ $searchPath = array_merge(explode(\PATH_SEPARATOR, \ini_get('open_basedir')), $extraDirs);
+ $dirs = [];
+ foreach ($searchPath as $path) {
+ // Silencing against https://bugs.php.net/69240
+ if (@is_dir($path)) {
+ $dirs[] = $path;
+ } else {
+ if (basename($path) == $name && @is_executable($path)) {
+ return $path;
+ }
+ }
+ }
+ } else {
+ $dirs = array_merge(
+ explode(\PATH_SEPARATOR, getenv('PATH') ?: getenv('Path')),
+ $extraDirs
+ );
+ }
+
+ $suffixes = [''];
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $pathExt = getenv('PATHEXT');
+ $suffixes = array_merge($pathExt ? explode(\PATH_SEPARATOR, $pathExt) : $this->suffixes, $suffixes);
+ }
+ foreach ($suffixes as $suffix) {
+ foreach ($dirs as $dir) {
+ if (@is_file($file = $dir.\DIRECTORY_SEPARATOR.$name.$suffix) && ('\\' === \DIRECTORY_SEPARATOR || @is_executable($file))) {
+ return $file;
+ }
+ }
+ }
+
+ return $default;
+ }
+}
diff --git a/vendor/symfony/process/InputStream.php b/vendor/symfony/process/InputStream.php
new file mode 100644
index 000000000..240665f32
--- /dev/null
+++ b/vendor/symfony/process/InputStream.php
@@ -0,0 +1,96 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * Provides a way to continuously write to the input of a Process until the InputStream is closed.
+ *
+ * @author Nicolas Grekas
+ *
+ * @implements \IteratorAggregate
+ */
+class InputStream implements \IteratorAggregate
+{
+ /** @var callable|null */
+ private $onEmpty = null;
+ private $input = [];
+ private $open = true;
+
+ /**
+ * Sets a callback that is called when the write buffer becomes empty.
+ */
+ public function onEmpty(callable $onEmpty = null)
+ {
+ $this->onEmpty = $onEmpty;
+ }
+
+ /**
+ * Appends an input to the write buffer.
+ *
+ * @param resource|string|int|float|bool|\Traversable|null $input The input to append as scalar,
+ * stream resource or \Traversable
+ */
+ public function write($input)
+ {
+ if (null === $input) {
+ return;
+ }
+ if ($this->isClosed()) {
+ throw new RuntimeException(sprintf('"%s" is closed.', static::class));
+ }
+ $this->input[] = ProcessUtils::validateInput(__METHOD__, $input);
+ }
+
+ /**
+ * Closes the write buffer.
+ */
+ public function close()
+ {
+ $this->open = false;
+ }
+
+ /**
+ * Tells whether the write buffer is closed or not.
+ */
+ public function isClosed()
+ {
+ return !$this->open;
+ }
+
+ /**
+ * @return \Traversable
+ */
+ #[\ReturnTypeWillChange]
+ public function getIterator()
+ {
+ $this->open = true;
+
+ while ($this->open || $this->input) {
+ if (!$this->input) {
+ yield '';
+ continue;
+ }
+ $current = array_shift($this->input);
+
+ if ($current instanceof \Iterator) {
+ yield from $current;
+ } else {
+ yield $current;
+ }
+ if (!$this->input && $this->open && null !== $onEmpty = $this->onEmpty) {
+ $this->write($onEmpty($this));
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/yaml/LICENSE b/vendor/symfony/process/LICENSE
old mode 100755
new mode 100644
similarity index 95%
rename from vendor/symfony/yaml/LICENSE
rename to vendor/symfony/process/LICENSE
index 21d7fb9e2..0138f8f07
--- a/vendor/symfony/yaml/LICENSE
+++ b/vendor/symfony/process/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2004-2018 Fabien Potencier
+Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/symfony/process/PhpExecutableFinder.php b/vendor/symfony/process/PhpExecutableFinder.php
new file mode 100644
index 000000000..bed6c3dc8
--- /dev/null
+++ b/vendor/symfony/process/PhpExecutableFinder.php
@@ -0,0 +1,103 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+/**
+ * An executable finder specifically designed for the PHP executable.
+ *
+ * @author Fabien Potencier
+ * @author Johannes M. Schmitt
+ */
+class PhpExecutableFinder
+{
+ private $executableFinder;
+
+ public function __construct()
+ {
+ $this->executableFinder = new ExecutableFinder();
+ }
+
+ /**
+ * Finds The PHP executable.
+ *
+ * @return string|false
+ */
+ public function find(bool $includeArgs = true)
+ {
+ if ($php = getenv('PHP_BINARY')) {
+ if (!is_executable($php)) {
+ $command = '\\' === \DIRECTORY_SEPARATOR ? 'where' : 'command -v';
+ if ($php = strtok(exec($command.' '.escapeshellarg($php)), \PHP_EOL)) {
+ if (!is_executable($php)) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ }
+
+ if (@is_dir($php)) {
+ return false;
+ }
+
+ return $php;
+ }
+
+ $args = $this->findArguments();
+ $args = $includeArgs && $args ? ' '.implode(' ', $args) : '';
+
+ // PHP_BINARY return the current sapi executable
+ if (\PHP_BINARY && \in_array(\PHP_SAPI, ['cli', 'cli-server', 'phpdbg'], true)) {
+ return \PHP_BINARY.$args;
+ }
+
+ if ($php = getenv('PHP_PATH')) {
+ if (!@is_executable($php) || @is_dir($php)) {
+ return false;
+ }
+
+ return $php;
+ }
+
+ if ($php = getenv('PHP_PEAR_PHP_BIN')) {
+ if (@is_executable($php) && !@is_dir($php)) {
+ return $php;
+ }
+ }
+
+ if (@is_executable($php = \PHP_BINDIR.('\\' === \DIRECTORY_SEPARATOR ? '\\php.exe' : '/php')) && !@is_dir($php)) {
+ return $php;
+ }
+
+ $dirs = [\PHP_BINDIR];
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $dirs[] = 'C:\xampp\php\\';
+ }
+
+ return $this->executableFinder->find('php', false, $dirs);
+ }
+
+ /**
+ * Finds the PHP executable arguments.
+ *
+ * @return array
+ */
+ public function findArguments()
+ {
+ $arguments = [];
+ if ('phpdbg' === \PHP_SAPI) {
+ $arguments[] = '-qrr';
+ }
+
+ return $arguments;
+ }
+}
diff --git a/vendor/symfony/process/PhpProcess.php b/vendor/symfony/process/PhpProcess.php
new file mode 100644
index 000000000..2bc338e5e
--- /dev/null
+++ b/vendor/symfony/process/PhpProcess.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\RuntimeException;
+
+/**
+ * PhpProcess runs a PHP script in an independent process.
+ *
+ * $p = new PhpProcess('');
+ * $p->run();
+ * print $p->getOutput()."\n";
+ *
+ * @author Fabien Potencier
+ */
+class PhpProcess extends Process
+{
+ /**
+ * @param string $script The PHP script to run (as a string)
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param int $timeout The timeout in seconds
+ * @param array|null $php Path to the PHP binary to use with any additional arguments
+ */
+ public function __construct(string $script, string $cwd = null, array $env = null, int $timeout = 60, array $php = null)
+ {
+ if (null === $php) {
+ $executableFinder = new PhpExecutableFinder();
+ $php = $executableFinder->find(false);
+ $php = false === $php ? null : array_merge([$php], $executableFinder->findArguments());
+ }
+ if ('phpdbg' === \PHP_SAPI) {
+ $file = tempnam(sys_get_temp_dir(), 'dbg');
+ file_put_contents($file, $script);
+ register_shutdown_function('unlink', $file);
+ $php[] = $file;
+ $script = null;
+ }
+
+ parent::__construct($php, $cwd, $env, $script, $timeout);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+ {
+ throw new LogicException(sprintf('The "%s()" method cannot be called when using "%s".', __METHOD__, self::class));
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function start(callable $callback = null, array $env = [])
+ {
+ if (null === $this->getCommandLine()) {
+ throw new RuntimeException('Unable to find the PHP executable.');
+ }
+
+ parent::start($callback, $env);
+ }
+}
diff --git a/vendor/symfony/process/Pipes/AbstractPipes.php b/vendor/symfony/process/Pipes/AbstractPipes.php
new file mode 100644
index 000000000..656dc0328
--- /dev/null
+++ b/vendor/symfony/process/Pipes/AbstractPipes.php
@@ -0,0 +1,180 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+abstract class AbstractPipes implements PipesInterface
+{
+ public $pipes = [];
+
+ private $inputBuffer = '';
+ private $input;
+ private $blocked = true;
+ private $lastError;
+
+ /**
+ * @param resource|string|int|float|bool|\Iterator|null $input
+ */
+ public function __construct($input)
+ {
+ if (\is_resource($input) || $input instanceof \Iterator) {
+ $this->input = $input;
+ } elseif (\is_string($input)) {
+ $this->inputBuffer = $input;
+ } else {
+ $this->inputBuffer = (string) $input;
+ }
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ foreach ($this->pipes as $pipe) {
+ if (\is_resource($pipe)) {
+ fclose($pipe);
+ }
+ }
+ $this->pipes = [];
+ }
+
+ /**
+ * Returns true if a system call has been interrupted.
+ */
+ protected function hasSystemCallBeenInterrupted(): bool
+ {
+ $lastError = $this->lastError;
+ $this->lastError = null;
+
+ // stream_select returns false when the `select` system call is interrupted by an incoming signal
+ return null !== $lastError && false !== stripos($lastError, 'interrupted system call');
+ }
+
+ /**
+ * Unblocks streams.
+ */
+ protected function unblock()
+ {
+ if (!$this->blocked) {
+ return;
+ }
+
+ foreach ($this->pipes as $pipe) {
+ stream_set_blocking($pipe, 0);
+ }
+ if (\is_resource($this->input)) {
+ stream_set_blocking($this->input, 0);
+ }
+
+ $this->blocked = false;
+ }
+
+ /**
+ * Writes input to stdin.
+ *
+ * @throws InvalidArgumentException When an input iterator yields a non supported value
+ */
+ protected function write(): ?array
+ {
+ if (!isset($this->pipes[0])) {
+ return null;
+ }
+ $input = $this->input;
+
+ if ($input instanceof \Iterator) {
+ if (!$input->valid()) {
+ $input = null;
+ } elseif (\is_resource($input = $input->current())) {
+ stream_set_blocking($input, 0);
+ } elseif (!isset($this->inputBuffer[0])) {
+ if (!\is_string($input)) {
+ if (!\is_scalar($input)) {
+ throw new InvalidArgumentException(sprintf('"%s" yielded a value of type "%s", but only scalars and stream resources are supported.', get_debug_type($this->input), get_debug_type($input)));
+ }
+ $input = (string) $input;
+ }
+ $this->inputBuffer = $input;
+ $this->input->next();
+ $input = null;
+ } else {
+ $input = null;
+ }
+ }
+
+ $r = $e = [];
+ $w = [$this->pipes[0]];
+
+ // let's have a look if something changed in streams
+ if (false === @stream_select($r, $w, $e, 0, 0)) {
+ return null;
+ }
+
+ foreach ($w as $stdin) {
+ if (isset($this->inputBuffer[0])) {
+ $written = fwrite($stdin, $this->inputBuffer);
+ $this->inputBuffer = substr($this->inputBuffer, $written);
+ if (isset($this->inputBuffer[0])) {
+ return [$this->pipes[0]];
+ }
+ }
+
+ if ($input) {
+ while (true) {
+ $data = fread($input, self::CHUNK_SIZE);
+ if (!isset($data[0])) {
+ break;
+ }
+ $written = fwrite($stdin, $data);
+ $data = substr($data, $written);
+ if (isset($data[0])) {
+ $this->inputBuffer = $data;
+
+ return [$this->pipes[0]];
+ }
+ }
+ if (feof($input)) {
+ if ($this->input instanceof \Iterator) {
+ $this->input->next();
+ } else {
+ $this->input = null;
+ }
+ }
+ }
+ }
+
+ // no input to read on resource, buffer is empty
+ if (!isset($this->inputBuffer[0]) && !($this->input instanceof \Iterator ? $this->input->valid() : $this->input)) {
+ $this->input = null;
+ fclose($this->pipes[0]);
+ unset($this->pipes[0]);
+ } elseif (!$w) {
+ return [$this->pipes[0]];
+ }
+
+ return null;
+ }
+
+ /**
+ * @internal
+ */
+ public function handleError(int $type, string $msg)
+ {
+ $this->lastError = $msg;
+ }
+}
diff --git a/vendor/symfony/process/Pipes/PipesInterface.php b/vendor/symfony/process/Pipes/PipesInterface.php
new file mode 100644
index 000000000..50eb5c47e
--- /dev/null
+++ b/vendor/symfony/process/Pipes/PipesInterface.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+/**
+ * PipesInterface manages descriptors and pipes for the use of proc_open.
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+interface PipesInterface
+{
+ public const CHUNK_SIZE = 16384;
+
+ /**
+ * Returns an array of descriptors for the use of proc_open.
+ */
+ public function getDescriptors(): array;
+
+ /**
+ * Returns an array of filenames indexed by their related stream in case these pipes use temporary files.
+ *
+ * @return string[]
+ */
+ public function getFiles(): array;
+
+ /**
+ * Reads data in file handles and pipes.
+ *
+ * @param bool $blocking Whether to use blocking calls or not
+ * @param bool $close Whether to close pipes if they've reached EOF
+ *
+ * @return string[] An array of read data indexed by their fd
+ */
+ public function readAndWrite(bool $blocking, bool $close = false): array;
+
+ /**
+ * Returns if the current state has open file handles or pipes.
+ */
+ public function areOpen(): bool;
+
+ /**
+ * Returns if pipes are able to read output.
+ */
+ public function haveReadSupport(): bool;
+
+ /**
+ * Closes file handles and pipes.
+ */
+ public function close();
+}
diff --git a/vendor/symfony/process/Pipes/UnixPipes.php b/vendor/symfony/process/Pipes/UnixPipes.php
new file mode 100644
index 000000000..5a0e9d47f
--- /dev/null
+++ b/vendor/symfony/process/Pipes/UnixPipes.php
@@ -0,0 +1,163 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Process;
+
+/**
+ * UnixPipes implementation uses unix pipes as handles.
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+class UnixPipes extends AbstractPipes
+{
+ private $ttyMode;
+ private $ptyMode;
+ private $haveReadSupport;
+
+ public function __construct(?bool $ttyMode, bool $ptyMode, $input, bool $haveReadSupport)
+ {
+ $this->ttyMode = $ttyMode;
+ $this->ptyMode = $ptyMode;
+ $this->haveReadSupport = $haveReadSupport;
+
+ parent::__construct($input);
+ }
+
+ public function __sleep(): array
+ {
+ throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+ }
+
+ public function __wakeup()
+ {
+ throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors(): array
+ {
+ if (!$this->haveReadSupport) {
+ $nullstream = fopen('/dev/null', 'c');
+
+ return [
+ ['pipe', 'r'],
+ $nullstream,
+ $nullstream,
+ ];
+ }
+
+ if ($this->ttyMode) {
+ return [
+ ['file', '/dev/tty', 'r'],
+ ['file', '/dev/tty', 'w'],
+ ['file', '/dev/tty', 'w'],
+ ];
+ }
+
+ if ($this->ptyMode && Process::isPtySupported()) {
+ return [
+ ['pty'],
+ ['pty'],
+ ['pty'],
+ ];
+ }
+
+ return [
+ ['pipe', 'r'],
+ ['pipe', 'w'], // stdout
+ ['pipe', 'w'], // stderr
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles(): array
+ {
+ return [];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite(bool $blocking, bool $close = false): array
+ {
+ $this->unblock();
+ $w = $this->write();
+
+ $read = $e = [];
+ $r = $this->pipes;
+ unset($r[0]);
+
+ // let's have a look if something changed in streams
+ set_error_handler([$this, 'handleError']);
+ if (($r || $w) && false === stream_select($r, $w, $e, 0, $blocking ? Process::TIMEOUT_PRECISION * 1E6 : 0)) {
+ restore_error_handler();
+ // if a system call has been interrupted, forget about it, let's try again
+ // otherwise, an error occurred, let's reset pipes
+ if (!$this->hasSystemCallBeenInterrupted()) {
+ $this->pipes = [];
+ }
+
+ return $read;
+ }
+ restore_error_handler();
+
+ foreach ($r as $pipe) {
+ // prior PHP 5.4 the array passed to stream_select is modified and
+ // lose key association, we have to find back the key
+ $read[$type = array_search($pipe, $this->pipes, true)] = '';
+
+ do {
+ $data = @fread($pipe, self::CHUNK_SIZE);
+ $read[$type] .= $data;
+ } while (isset($data[0]) && ($close || isset($data[self::CHUNK_SIZE - 1])));
+
+ if (!isset($read[$type][0])) {
+ unset($read[$type]);
+ }
+
+ if ($close && feof($pipe)) {
+ fclose($pipe);
+ unset($this->pipes[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function haveReadSupport(): bool
+ {
+ return $this->haveReadSupport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen(): bool
+ {
+ return (bool) $this->pipes;
+ }
+}
diff --git a/vendor/symfony/process/Pipes/WindowsPipes.php b/vendor/symfony/process/Pipes/WindowsPipes.php
new file mode 100644
index 000000000..bca84f574
--- /dev/null
+++ b/vendor/symfony/process/Pipes/WindowsPipes.php
@@ -0,0 +1,204 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process\Pipes;
+
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Process;
+
+/**
+ * WindowsPipes implementation uses temporary files as handles.
+ *
+ * @see https://bugs.php.net/51800
+ * @see https://bugs.php.net/65650
+ *
+ * @author Romain Neutron
+ *
+ * @internal
+ */
+class WindowsPipes extends AbstractPipes
+{
+ private $files = [];
+ private $fileHandles = [];
+ private $lockHandles = [];
+ private $readBytes = [
+ Process::STDOUT => 0,
+ Process::STDERR => 0,
+ ];
+ private $haveReadSupport;
+
+ public function __construct($input, bool $haveReadSupport)
+ {
+ $this->haveReadSupport = $haveReadSupport;
+
+ if ($this->haveReadSupport) {
+ // Fix for PHP bug #51800: reading from STDOUT pipe hangs forever on Windows if the output is too big.
+ // Workaround for this problem is to use temporary files instead of pipes on Windows platform.
+ //
+ // @see https://bugs.php.net/51800
+ $pipes = [
+ Process::STDOUT => Process::OUT,
+ Process::STDERR => Process::ERR,
+ ];
+ $tmpDir = sys_get_temp_dir();
+ $lastError = 'unknown reason';
+ set_error_handler(function ($type, $msg) use (&$lastError) { $lastError = $msg; });
+ for ($i = 0;; ++$i) {
+ foreach ($pipes as $pipe => $name) {
+ $file = sprintf('%s\\sf_proc_%02X.%s', $tmpDir, $i, $name);
+
+ if (!$h = fopen($file.'.lock', 'w')) {
+ if (file_exists($file.'.lock')) {
+ continue 2;
+ }
+ restore_error_handler();
+ throw new RuntimeException('A temporary file could not be opened to write the process output: '.$lastError);
+ }
+ if (!flock($h, \LOCK_EX | \LOCK_NB)) {
+ continue 2;
+ }
+ if (isset($this->lockHandles[$pipe])) {
+ flock($this->lockHandles[$pipe], \LOCK_UN);
+ fclose($this->lockHandles[$pipe]);
+ }
+ $this->lockHandles[$pipe] = $h;
+
+ if (!($h = fopen($file, 'w')) || !fclose($h) || !$h = fopen($file, 'r')) {
+ flock($this->lockHandles[$pipe], \LOCK_UN);
+ fclose($this->lockHandles[$pipe]);
+ unset($this->lockHandles[$pipe]);
+ continue 2;
+ }
+ $this->fileHandles[$pipe] = $h;
+ $this->files[$pipe] = $file;
+ }
+ break;
+ }
+ restore_error_handler();
+ }
+
+ parent::__construct($input);
+ }
+
+ public function __sleep(): array
+ {
+ throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+ }
+
+ public function __wakeup()
+ {
+ throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getDescriptors(): array
+ {
+ if (!$this->haveReadSupport) {
+ $nullstream = fopen('NUL', 'c');
+
+ return [
+ ['pipe', 'r'],
+ $nullstream,
+ $nullstream,
+ ];
+ }
+
+ // We're not using pipe on Windows platform as it hangs (https://bugs.php.net/51800)
+ // We're not using file handles as it can produce corrupted output https://bugs.php.net/65650
+ // So we redirect output within the commandline and pass the nul device to the process
+ return [
+ ['pipe', 'r'],
+ ['file', 'NUL', 'w'],
+ ['file', 'NUL', 'w'],
+ ];
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getFiles(): array
+ {
+ return $this->files;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function readAndWrite(bool $blocking, bool $close = false): array
+ {
+ $this->unblock();
+ $w = $this->write();
+ $read = $r = $e = [];
+
+ if ($blocking) {
+ if ($w) {
+ @stream_select($r, $w, $e, 0, Process::TIMEOUT_PRECISION * 1E6);
+ } elseif ($this->fileHandles) {
+ usleep(Process::TIMEOUT_PRECISION * 1E6);
+ }
+ }
+ foreach ($this->fileHandles as $type => $fileHandle) {
+ $data = stream_get_contents($fileHandle, -1, $this->readBytes[$type]);
+
+ if (isset($data[0])) {
+ $this->readBytes[$type] += \strlen($data);
+ $read[$type] = $data;
+ }
+ if ($close) {
+ ftruncate($fileHandle, 0);
+ fclose($fileHandle);
+ flock($this->lockHandles[$type], \LOCK_UN);
+ fclose($this->lockHandles[$type]);
+ unset($this->fileHandles[$type], $this->lockHandles[$type]);
+ }
+ }
+
+ return $read;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function haveReadSupport(): bool
+ {
+ return $this->haveReadSupport;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function areOpen(): bool
+ {
+ return $this->pipes && $this->fileHandles;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function close()
+ {
+ parent::close();
+ foreach ($this->fileHandles as $type => $handle) {
+ ftruncate($handle, 0);
+ fclose($handle);
+ flock($this->lockHandles[$type], \LOCK_UN);
+ fclose($this->lockHandles[$type]);
+ }
+ $this->fileHandles = $this->lockHandles = [];
+ }
+}
diff --git a/vendor/symfony/process/Process.php b/vendor/symfony/process/Process.php
new file mode 100644
index 000000000..9b19475ac
--- /dev/null
+++ b/vendor/symfony/process/Process.php
@@ -0,0 +1,1652 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+use Symfony\Component\Process\Exception\LogicException;
+use Symfony\Component\Process\Exception\ProcessFailedException;
+use Symfony\Component\Process\Exception\ProcessSignaledException;
+use Symfony\Component\Process\Exception\ProcessTimedOutException;
+use Symfony\Component\Process\Exception\RuntimeException;
+use Symfony\Component\Process\Pipes\PipesInterface;
+use Symfony\Component\Process\Pipes\UnixPipes;
+use Symfony\Component\Process\Pipes\WindowsPipes;
+
+/**
+ * Process is a thin wrapper around proc_* functions to easily
+ * start independent PHP processes.
+ *
+ * @author Fabien Potencier
+ * @author Romain Neutron
+ *
+ * @implements \IteratorAggregate
+ */
+class Process implements \IteratorAggregate
+{
+ public const ERR = 'err';
+ public const OUT = 'out';
+
+ public const STATUS_READY = 'ready';
+ public const STATUS_STARTED = 'started';
+ public const STATUS_TERMINATED = 'terminated';
+
+ public const STDIN = 0;
+ public const STDOUT = 1;
+ public const STDERR = 2;
+
+ // Timeout Precision in seconds.
+ public const TIMEOUT_PRECISION = 0.2;
+
+ public const ITER_NON_BLOCKING = 1; // By default, iterating over outputs is a blocking call, use this flag to make it non-blocking
+ public const ITER_KEEP_OUTPUT = 2; // By default, outputs are cleared while iterating, use this flag to keep them in memory
+ public const ITER_SKIP_OUT = 4; // Use this flag to skip STDOUT while iterating
+ public const ITER_SKIP_ERR = 8; // Use this flag to skip STDERR while iterating
+
+ private $callback;
+ private $hasCallback = false;
+ private $commandline;
+ private $cwd;
+ private $env = [];
+ private $input;
+ private $starttime;
+ private $lastOutputTime;
+ private $timeout;
+ private $idleTimeout;
+ private $exitcode;
+ private $fallbackStatus = [];
+ private $processInformation;
+ private $outputDisabled = false;
+ private $stdout;
+ private $stderr;
+ private $process;
+ private $status = self::STATUS_READY;
+ private $incrementalOutputOffset = 0;
+ private $incrementalErrorOutputOffset = 0;
+ private $tty = false;
+ private $pty;
+ private $options = ['suppress_errors' => true, 'bypass_shell' => true];
+
+ private $useFileHandles = false;
+ /** @var PipesInterface */
+ private $processPipes;
+
+ private $latestSignal;
+
+ private static $sigchild;
+
+ /**
+ * Exit codes translation table.
+ *
+ * User-defined errors must use exit codes in the 64-113 range.
+ */
+ public static $exitCodes = [
+ 0 => 'OK',
+ 1 => 'General error',
+ 2 => 'Misuse of shell builtins',
+
+ 126 => 'Invoked command cannot execute',
+ 127 => 'Command not found',
+ 128 => 'Invalid exit argument',
+
+ // signals
+ 129 => 'Hangup',
+ 130 => 'Interrupt',
+ 131 => 'Quit and dump core',
+ 132 => 'Illegal instruction',
+ 133 => 'Trace/breakpoint trap',
+ 134 => 'Process aborted',
+ 135 => 'Bus error: "access to undefined portion of memory object"',
+ 136 => 'Floating point exception: "erroneous arithmetic operation"',
+ 137 => 'Kill (terminate immediately)',
+ 138 => 'User-defined 1',
+ 139 => 'Segmentation violation',
+ 140 => 'User-defined 2',
+ 141 => 'Write to pipe with no one reading',
+ 142 => 'Signal raised by alarm',
+ 143 => 'Termination (request to terminate)',
+ // 144 - not defined
+ 145 => 'Child process terminated, stopped (or continued*)',
+ 146 => 'Continue if stopped',
+ 147 => 'Stop executing temporarily',
+ 148 => 'Terminal stop signal',
+ 149 => 'Background process attempting to read from tty ("in")',
+ 150 => 'Background process attempting to write to tty ("out")',
+ 151 => 'Urgent data available on socket',
+ 152 => 'CPU time limit exceeded',
+ 153 => 'File size limit exceeded',
+ 154 => 'Signal raised by timer counting virtual time: "virtual timer expired"',
+ 155 => 'Profiling timer expired',
+ // 156 - not defined
+ 157 => 'Pollable event',
+ // 158 - not defined
+ 159 => 'Bad syscall',
+ ];
+
+ /**
+ * @param array $command The command to run and its arguments listed as separate entries
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input
+ * @param int|float|null $timeout The timeout in seconds or null to disable
+ *
+ * @throws LogicException When proc_open is not installed
+ */
+ public function __construct(array $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+ {
+ if (!\function_exists('proc_open')) {
+ throw new LogicException('The Process class relies on proc_open, which is not available on your PHP installation.');
+ }
+
+ $this->commandline = $command;
+ $this->cwd = $cwd;
+
+ // on Windows, if the cwd changed via chdir(), proc_open defaults to the dir where PHP was started
+ // on Gnu/Linux, PHP builds with --enable-maintainer-zts are also affected
+ // @see : https://bugs.php.net/51800
+ // @see : https://bugs.php.net/50524
+ if (null === $this->cwd && (\defined('ZEND_THREAD_SAFE') || '\\' === \DIRECTORY_SEPARATOR)) {
+ $this->cwd = getcwd();
+ }
+ if (null !== $env) {
+ $this->setEnv($env);
+ }
+
+ $this->setInput($input);
+ $this->setTimeout($timeout);
+ $this->useFileHandles = '\\' === \DIRECTORY_SEPARATOR;
+ $this->pty = false;
+ }
+
+ /**
+ * Creates a Process instance as a command-line to be run in a shell wrapper.
+ *
+ * Command-lines are parsed by the shell of your OS (/bin/sh on Unix-like, cmd.exe on Windows.)
+ * This allows using e.g. pipes or conditional execution. In this mode, signals are sent to the
+ * shell wrapper and not to your commands.
+ *
+ * In order to inject dynamic values into command-lines, we strongly recommend using placeholders.
+ * This will save escaping values, which is not portable nor secure anyway:
+ *
+ * $process = Process::fromShellCommandline('my_command "${:MY_VAR}"');
+ * $process->run(null, ['MY_VAR' => $theValue]);
+ *
+ * @param string $command The command line to pass to the shell of the OS
+ * @param string|null $cwd The working directory or null to use the working dir of the current PHP process
+ * @param array|null $env The environment variables or null to use the same environment as the current PHP process
+ * @param mixed $input The input as stream resource, scalar or \Traversable, or null for no input
+ * @param int|float|null $timeout The timeout in seconds or null to disable
+ *
+ * @return static
+ *
+ * @throws LogicException When proc_open is not installed
+ */
+ public static function fromShellCommandline(string $command, string $cwd = null, array $env = null, $input = null, ?float $timeout = 60)
+ {
+ $process = new static([], $cwd, $env, $input, $timeout);
+ $process->commandline = $command;
+
+ return $process;
+ }
+
+ /**
+ * @return array
+ */
+ public function __sleep()
+ {
+ throw new \BadMethodCallException('Cannot serialize '.__CLASS__);
+ }
+
+ public function __wakeup()
+ {
+ throw new \BadMethodCallException('Cannot unserialize '.__CLASS__);
+ }
+
+ public function __destruct()
+ {
+ if ($this->options['create_new_console'] ?? false) {
+ $this->processPipes->close();
+ } else {
+ $this->stop(0);
+ }
+ }
+
+ public function __clone()
+ {
+ $this->resetProcessData();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * The callback receives the type of output (out or err) and
+ * some bytes from the output in real-time. It allows to have feedback
+ * from the independent process during execution.
+ *
+ * The STDOUT and STDERR are also available after the process is finished
+ * via the getOutput() and getErrorOutput() methods.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @return int The exit status code
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ * @throws ProcessTimedOutException When process timed out
+ * @throws ProcessSignaledException When process stopped after receiving signal
+ * @throws LogicException In case a callback is provided and output has been disabled
+ *
+ * @final
+ */
+ public function run(callable $callback = null, array $env = []): int
+ {
+ $this->start($callback, $env);
+
+ return $this->wait();
+ }
+
+ /**
+ * Runs the process.
+ *
+ * This is identical to run() except that an exception is thrown if the process
+ * exits with a non-zero exit code.
+ *
+ * @return $this
+ *
+ * @throws ProcessFailedException if the process didn't terminate successfully
+ *
+ * @final
+ */
+ public function mustRun(callable $callback = null, array $env = []): self
+ {
+ if (0 !== $this->run($callback, $env)) {
+ throw new ProcessFailedException($this);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Starts the process and returns after writing the input to STDIN.
+ *
+ * This method blocks until all STDIN data is sent to the process then it
+ * returns while the process runs in the background.
+ *
+ * The termination of the process can be awaited with wait().
+ *
+ * The callback receives the type of output (out or err) and some bytes from
+ * the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ * @throws LogicException In case a callback is provided and output has been disabled
+ */
+ public function start(callable $callback = null, array $env = [])
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running.');
+ }
+
+ $this->resetProcessData();
+ $this->starttime = $this->lastOutputTime = microtime(true);
+ $this->callback = $this->buildCallback($callback);
+ $this->hasCallback = null !== $callback;
+ $descriptors = $this->getDescriptors();
+
+ if ($this->env) {
+ $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->env, $env, 'strcasecmp') : $this->env;
+ }
+
+ $env += '\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($this->getDefaultEnv(), $env, 'strcasecmp') : $this->getDefaultEnv();
+
+ if (\is_array($commandline = $this->commandline)) {
+ $commandline = implode(' ', array_map([$this, 'escapeArgument'], $commandline));
+
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ // exec is mandatory to deal with sending a signal to the process
+ $commandline = 'exec '.$commandline;
+ }
+ } else {
+ $commandline = $this->replacePlaceholders($commandline, $env);
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $commandline = $this->prepareWindowsCommandLine($commandline, $env);
+ } elseif (!$this->useFileHandles && $this->isSigchildEnabled()) {
+ // last exit code is output on the fourth pipe and caught to work around --enable-sigchild
+ $descriptors[3] = ['pipe', 'w'];
+
+ // See https://unix.stackexchange.com/questions/71205/background-process-pipe-input
+ $commandline = '{ ('.$commandline.') <&3 3<&- 3>/dev/null & } 3<&0;';
+ $commandline .= 'pid=$!; echo $pid >&3; wait $pid; code=$?; echo $code >&3; exit $code';
+
+ // Workaround for the bug, when PTS functionality is enabled.
+ // @see : https://bugs.php.net/69442
+ $ptsWorkaround = fopen(__FILE__, 'r');
+ }
+
+ $envPairs = [];
+ foreach ($env as $k => $v) {
+ if (false !== $v && false === \in_array($k, ['argc', 'argv', 'ARGC', 'ARGV'], true)) {
+ $envPairs[] = $k.'='.$v;
+ }
+ }
+
+ if (!is_dir($this->cwd)) {
+ throw new RuntimeException(sprintf('The provided cwd "%s" does not exist.', $this->cwd));
+ }
+
+ $this->process = @proc_open($commandline, $descriptors, $this->processPipes->pipes, $this->cwd, $envPairs, $this->options);
+
+ if (!\is_resource($this->process)) {
+ throw new RuntimeException('Unable to launch a new process.');
+ }
+ $this->status = self::STATUS_STARTED;
+
+ if (isset($descriptors[3])) {
+ $this->fallbackStatus['pid'] = (int) fgets($this->processPipes->pipes[3]);
+ }
+
+ if ($this->tty) {
+ return;
+ }
+
+ $this->updateStatus(false);
+ $this->checkTimeout();
+ }
+
+ /**
+ * Restarts the process.
+ *
+ * Be warned that the process is cloned before being started.
+ *
+ * @param callable|null $callback A PHP callback to run whenever there is some
+ * output available on STDOUT or STDERR
+ *
+ * @return static
+ *
+ * @throws RuntimeException When process can't be launched
+ * @throws RuntimeException When process is already running
+ *
+ * @see start()
+ *
+ * @final
+ */
+ public function restart(callable $callback = null, array $env = []): self
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Process is already running.');
+ }
+
+ $process = clone $this;
+ $process->start($callback, $env);
+
+ return $process;
+ }
+
+ /**
+ * Waits for the process to terminate.
+ *
+ * The callback receives the type of output (out or err) and some bytes
+ * from the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @param callable|null $callback A valid PHP callback
+ *
+ * @return int The exitcode of the process
+ *
+ * @throws ProcessTimedOutException When process timed out
+ * @throws ProcessSignaledException When process stopped after receiving signal
+ * @throws LogicException When process is not yet started
+ */
+ public function wait(callable $callback = null)
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+
+ $this->updateStatus(false);
+
+ if (null !== $callback) {
+ if (!$this->processPipes->haveReadSupport()) {
+ $this->stop(0);
+ throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::wait".');
+ }
+ $this->callback = $this->buildCallback($callback);
+ }
+
+ do {
+ $this->checkTimeout();
+ $running = $this->isRunning() && ('\\' === \DIRECTORY_SEPARATOR || $this->processPipes->areOpen());
+ $this->readPipes($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+ } while ($running);
+
+ while ($this->isRunning()) {
+ $this->checkTimeout();
+ usleep(1000);
+ }
+
+ if ($this->processInformation['signaled'] && $this->processInformation['termsig'] !== $this->latestSignal) {
+ throw new ProcessSignaledException($this);
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Waits until the callback returns true.
+ *
+ * The callback receives the type of output (out or err) and some bytes
+ * from the output in real-time while writing the standard input to the process.
+ * It allows to have feedback from the independent process during execution.
+ *
+ * @throws RuntimeException When process timed out
+ * @throws LogicException When process is not yet started
+ * @throws ProcessTimedOutException In case the timeout was reached
+ */
+ public function waitUntil(callable $callback): bool
+ {
+ $this->requireProcessIsStarted(__FUNCTION__);
+ $this->updateStatus(false);
+
+ if (!$this->processPipes->haveReadSupport()) {
+ $this->stop(0);
+ throw new LogicException('Pass the callback to the "Process::start" method or call enableOutput to use a callback with "Process::waitUntil".');
+ }
+ $callback = $this->buildCallback($callback);
+
+ $ready = false;
+ while (true) {
+ $this->checkTimeout();
+ $running = '\\' === \DIRECTORY_SEPARATOR ? $this->isRunning() : $this->processPipes->areOpen();
+ $output = $this->processPipes->readAndWrite($running, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+
+ foreach ($output as $type => $data) {
+ if (3 !== $type) {
+ $ready = $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data) || $ready;
+ } elseif (!isset($this->fallbackStatus['signaled'])) {
+ $this->fallbackStatus['exitcode'] = (int) $data;
+ }
+ }
+ if ($ready) {
+ return true;
+ }
+ if (!$running) {
+ return false;
+ }
+
+ usleep(1000);
+ }
+ }
+
+ /**
+ * Returns the Pid (process identifier), if applicable.
+ *
+ * @return int|null The process id if running, null otherwise
+ */
+ public function getPid()
+ {
+ return $this->isRunning() ? $this->processInformation['pid'] : null;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
+ *
+ * @return $this
+ *
+ * @throws LogicException In case the process is not running
+ * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
+ * @throws RuntimeException In case of failure
+ */
+ public function signal(int $signal)
+ {
+ $this->doSignal($signal, true);
+
+ return $this;
+ }
+
+ /**
+ * Disables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the process is already running
+ * @throws LogicException if an idle timeout is set
+ */
+ public function disableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Disabling output while the process is running is not possible.');
+ }
+ if (null !== $this->idleTimeout) {
+ throw new LogicException('Output cannot be disabled while an idle timeout is set.');
+ }
+
+ $this->outputDisabled = true;
+
+ return $this;
+ }
+
+ /**
+ * Enables fetching output and error output from the underlying process.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the process is already running
+ */
+ public function enableOutput()
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Enabling output while the process is running is not possible.');
+ }
+
+ $this->outputDisabled = false;
+
+ return $this;
+ }
+
+ /**
+ * Returns true in case the output is disabled, false otherwise.
+ *
+ * @return bool
+ */
+ public function isOutputDisabled()
+ {
+ return $this->outputDisabled;
+ }
+
+ /**
+ * Returns the current output of the process (STDOUT).
+ *
+ * @return string
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ if (false === $ret = stream_get_contents($this->stdout, -1, 0)) {
+ return '';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the output incrementally.
+ *
+ * In comparison with the getOutput method which always return the whole
+ * output, this one returns the new output since the last call.
+ *
+ * @return string
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getIncrementalOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ $latest = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+ $this->incrementalOutputOffset = ftell($this->stdout);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ return $latest;
+ }
+
+ /**
+ * Returns an iterator to the output of the process, with the output type as keys (Process::OUT/ERR).
+ *
+ * @param int $flags A bit field of Process::ITER_* flags
+ *
+ * @return \Generator
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ #[\ReturnTypeWillChange]
+ public function getIterator(int $flags = 0)
+ {
+ $this->readPipesForOutput(__FUNCTION__, false);
+
+ $clearOutput = !(self::ITER_KEEP_OUTPUT & $flags);
+ $blocking = !(self::ITER_NON_BLOCKING & $flags);
+ $yieldOut = !(self::ITER_SKIP_OUT & $flags);
+ $yieldErr = !(self::ITER_SKIP_ERR & $flags);
+
+ while (null !== $this->callback || ($yieldOut && !feof($this->stdout)) || ($yieldErr && !feof($this->stderr))) {
+ if ($yieldOut) {
+ $out = stream_get_contents($this->stdout, -1, $this->incrementalOutputOffset);
+
+ if (isset($out[0])) {
+ if ($clearOutput) {
+ $this->clearOutput();
+ } else {
+ $this->incrementalOutputOffset = ftell($this->stdout);
+ }
+
+ yield self::OUT => $out;
+ }
+ }
+
+ if ($yieldErr) {
+ $err = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+
+ if (isset($err[0])) {
+ if ($clearOutput) {
+ $this->clearErrorOutput();
+ } else {
+ $this->incrementalErrorOutputOffset = ftell($this->stderr);
+ }
+
+ yield self::ERR => $err;
+ }
+ }
+
+ if (!$blocking && !isset($out[0]) && !isset($err[0])) {
+ yield self::OUT => '';
+ }
+
+ $this->checkTimeout();
+ $this->readPipesForOutput(__FUNCTION__, $blocking);
+ }
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return $this
+ */
+ public function clearOutput()
+ {
+ ftruncate($this->stdout, 0);
+ fseek($this->stdout, 0);
+ $this->incrementalOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the current error output of the process (STDERR).
+ *
+ * @return string
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getErrorOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ if (false === $ret = stream_get_contents($this->stderr, -1, 0)) {
+ return '';
+ }
+
+ return $ret;
+ }
+
+ /**
+ * Returns the errorOutput incrementally.
+ *
+ * In comparison with the getErrorOutput method which always return the
+ * whole error output, this one returns the new error output since the last
+ * call.
+ *
+ * @return string
+ *
+ * @throws LogicException in case the output has been disabled
+ * @throws LogicException In case the process is not started
+ */
+ public function getIncrementalErrorOutput()
+ {
+ $this->readPipesForOutput(__FUNCTION__);
+
+ $latest = stream_get_contents($this->stderr, -1, $this->incrementalErrorOutputOffset);
+ $this->incrementalErrorOutputOffset = ftell($this->stderr);
+
+ if (false === $latest) {
+ return '';
+ }
+
+ return $latest;
+ }
+
+ /**
+ * Clears the process output.
+ *
+ * @return $this
+ */
+ public function clearErrorOutput()
+ {
+ ftruncate($this->stderr, 0);
+ fseek($this->stderr, 0);
+ $this->incrementalErrorOutputOffset = 0;
+
+ return $this;
+ }
+
+ /**
+ * Returns the exit code returned by the process.
+ *
+ * @return int|null The exit status code, null if the Process is not terminated
+ */
+ public function getExitCode()
+ {
+ $this->updateStatus(false);
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Returns a string representation for the exit code returned by the process.
+ *
+ * This method relies on the Unix exit code status standardization
+ * and might not be relevant for other operating systems.
+ *
+ * @return string|null A string representation for the exit status code, null if the Process is not terminated
+ *
+ * @see http://tldp.org/LDP/abs/html/exitcodes.html
+ * @see http://en.wikipedia.org/wiki/Unix_signal
+ */
+ public function getExitCodeText()
+ {
+ if (null === $exitcode = $this->getExitCode()) {
+ return null;
+ }
+
+ return self::$exitCodes[$exitcode] ?? 'Unknown error';
+ }
+
+ /**
+ * Checks if the process ended successfully.
+ *
+ * @return bool
+ */
+ public function isSuccessful()
+ {
+ return 0 === $this->getExitCode();
+ }
+
+ /**
+ * Returns true if the child process has been terminated by an uncaught signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenSignaled()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['signaled'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to terminate its execution.
+ *
+ * It is only meaningful if hasBeenSignaled() returns true.
+ *
+ * @return int
+ *
+ * @throws RuntimeException In case --enable-sigchild is activated
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getTermSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ if ($this->isSigchildEnabled() && -1 === $this->processInformation['termsig']) {
+ throw new RuntimeException('This PHP has been compiled with --enable-sigchild. Term signal cannot be retrieved.');
+ }
+
+ return $this->processInformation['termsig'];
+ }
+
+ /**
+ * Returns true if the child process has been stopped by a signal.
+ *
+ * It always returns false on Windows.
+ *
+ * @return bool
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function hasBeenStopped()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['stopped'];
+ }
+
+ /**
+ * Returns the number of the signal that caused the child process to stop its execution.
+ *
+ * It is only meaningful if hasBeenStopped() returns true.
+ *
+ * @return int
+ *
+ * @throws LogicException In case the process is not terminated
+ */
+ public function getStopSignal()
+ {
+ $this->requireProcessIsTerminated(__FUNCTION__);
+
+ return $this->processInformation['stopsig'];
+ }
+
+ /**
+ * Checks if the process is currently running.
+ *
+ * @return bool
+ */
+ public function isRunning()
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return false;
+ }
+
+ $this->updateStatus(false);
+
+ return $this->processInformation['running'];
+ }
+
+ /**
+ * Checks if the process has been started with no regard to the current state.
+ *
+ * @return bool
+ */
+ public function isStarted()
+ {
+ return self::STATUS_READY != $this->status;
+ }
+
+ /**
+ * Checks if the process is terminated.
+ *
+ * @return bool
+ */
+ public function isTerminated()
+ {
+ $this->updateStatus(false);
+
+ return self::STATUS_TERMINATED == $this->status;
+ }
+
+ /**
+ * Gets the process status.
+ *
+ * The status is one of: ready, started, terminated.
+ *
+ * @return string
+ */
+ public function getStatus()
+ {
+ $this->updateStatus(false);
+
+ return $this->status;
+ }
+
+ /**
+ * Stops the process.
+ *
+ * @param int|float $timeout The timeout in seconds
+ * @param int|null $signal A POSIX signal to send in case the process has not stop at timeout, default is SIGKILL (9)
+ *
+ * @return int|null The exit-code of the process or null if it's not running
+ */
+ public function stop(float $timeout = 10, int $signal = null)
+ {
+ $timeoutMicro = microtime(true) + $timeout;
+ if ($this->isRunning()) {
+ // given SIGTERM may not be defined and that "proc_terminate" uses the constant value and not the constant itself, we use the same here
+ $this->doSignal(15, false);
+ do {
+ usleep(1000);
+ } while ($this->isRunning() && microtime(true) < $timeoutMicro);
+
+ if ($this->isRunning()) {
+ // Avoid exception here: process is supposed to be running, but it might have stopped just
+ // after this line. In any case, let's silently discard the error, we cannot do anything.
+ $this->doSignal($signal ?: 9, false);
+ }
+ }
+
+ if ($this->isRunning()) {
+ if (isset($this->fallbackStatus['pid'])) {
+ unset($this->fallbackStatus['pid']);
+
+ return $this->stop(0, $signal);
+ }
+ $this->close();
+ }
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Adds a line to the STDOUT stream.
+ *
+ * @internal
+ */
+ public function addOutput(string $line)
+ {
+ $this->lastOutputTime = microtime(true);
+
+ fseek($this->stdout, 0, \SEEK_END);
+ fwrite($this->stdout, $line);
+ fseek($this->stdout, $this->incrementalOutputOffset);
+ }
+
+ /**
+ * Adds a line to the STDERR stream.
+ *
+ * @internal
+ */
+ public function addErrorOutput(string $line)
+ {
+ $this->lastOutputTime = microtime(true);
+
+ fseek($this->stderr, 0, \SEEK_END);
+ fwrite($this->stderr, $line);
+ fseek($this->stderr, $this->incrementalErrorOutputOffset);
+ }
+
+ /**
+ * Gets the last output time in seconds.
+ */
+ public function getLastOutputTime(): ?float
+ {
+ return $this->lastOutputTime;
+ }
+
+ /**
+ * Gets the command line to be executed.
+ *
+ * @return string
+ */
+ public function getCommandLine()
+ {
+ return \is_array($this->commandline) ? implode(' ', array_map([$this, 'escapeArgument'], $this->commandline)) : $this->commandline;
+ }
+
+ /**
+ * Gets the process timeout in seconds (max. runtime).
+ *
+ * @return float|null
+ */
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+
+ /**
+ * Gets the process idle timeout in seconds (max. time since last output).
+ *
+ * @return float|null
+ */
+ public function getIdleTimeout()
+ {
+ return $this->idleTimeout;
+ }
+
+ /**
+ * Sets the process timeout (max. runtime) in seconds.
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @return $this
+ *
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setTimeout(?float $timeout)
+ {
+ $this->timeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Sets the process idle timeout (max. time since last output) in seconds.
+ *
+ * To disable the timeout, set this value to null.
+ *
+ * @return $this
+ *
+ * @throws LogicException if the output is disabled
+ * @throws InvalidArgumentException if the timeout is negative
+ */
+ public function setIdleTimeout(?float $timeout)
+ {
+ if (null !== $timeout && $this->outputDisabled) {
+ throw new LogicException('Idle timeout cannot be set while the output is disabled.');
+ }
+
+ $this->idleTimeout = $this->validateTimeout($timeout);
+
+ return $this;
+ }
+
+ /**
+ * Enables or disables the TTY mode.
+ *
+ * @return $this
+ *
+ * @throws RuntimeException In case the TTY mode is not supported
+ */
+ public function setTty(bool $tty)
+ {
+ if ('\\' === \DIRECTORY_SEPARATOR && $tty) {
+ throw new RuntimeException('TTY mode is not supported on Windows platform.');
+ }
+
+ if ($tty && !self::isTtySupported()) {
+ throw new RuntimeException('TTY mode requires /dev/tty to be read/writable.');
+ }
+
+ $this->tty = $tty;
+
+ return $this;
+ }
+
+ /**
+ * Checks if the TTY mode is enabled.
+ *
+ * @return bool
+ */
+ public function isTty()
+ {
+ return $this->tty;
+ }
+
+ /**
+ * Sets PTY mode.
+ *
+ * @return $this
+ */
+ public function setPty(bool $bool)
+ {
+ $this->pty = $bool;
+
+ return $this;
+ }
+
+ /**
+ * Returns PTY state.
+ *
+ * @return bool
+ */
+ public function isPty()
+ {
+ return $this->pty;
+ }
+
+ /**
+ * Gets the working directory.
+ *
+ * @return string|null
+ */
+ public function getWorkingDirectory()
+ {
+ if (null === $this->cwd) {
+ // getcwd() will return false if any one of the parent directories does not have
+ // the readable or search mode set, even if the current directory does
+ return getcwd() ?: null;
+ }
+
+ return $this->cwd;
+ }
+
+ /**
+ * Sets the current working directory.
+ *
+ * @return $this
+ */
+ public function setWorkingDirectory(string $cwd)
+ {
+ $this->cwd = $cwd;
+
+ return $this;
+ }
+
+ /**
+ * Gets the environment variables.
+ *
+ * @return array
+ */
+ public function getEnv()
+ {
+ return $this->env;
+ }
+
+ /**
+ * Sets the environment variables.
+ *
+ * @param array $env The new environment variables
+ *
+ * @return $this
+ */
+ public function setEnv(array $env)
+ {
+ $this->env = $env;
+
+ return $this;
+ }
+
+ /**
+ * Gets the Process input.
+ *
+ * @return resource|string|\Iterator|null
+ */
+ public function getInput()
+ {
+ return $this->input;
+ }
+
+ /**
+ * Sets the input.
+ *
+ * This content will be passed to the underlying process standard input.
+ *
+ * @param string|int|float|bool|resource|\Traversable|null $input The content
+ *
+ * @return $this
+ *
+ * @throws LogicException In case the process is running
+ */
+ public function setInput($input)
+ {
+ if ($this->isRunning()) {
+ throw new LogicException('Input cannot be set while the process is running.');
+ }
+
+ $this->input = ProcessUtils::validateInput(__METHOD__, $input);
+
+ return $this;
+ }
+
+ /**
+ * Performs a check between the timeout definition and the time the process started.
+ *
+ * In case you run a background process (with the start method), you should
+ * trigger this method regularly to ensure the process timeout
+ *
+ * @throws ProcessTimedOutException In case the timeout was reached
+ */
+ public function checkTimeout()
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return;
+ }
+
+ if (null !== $this->timeout && $this->timeout < microtime(true) - $this->starttime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_GENERAL);
+ }
+
+ if (null !== $this->idleTimeout && $this->idleTimeout < microtime(true) - $this->lastOutputTime) {
+ $this->stop(0);
+
+ throw new ProcessTimedOutException($this, ProcessTimedOutException::TYPE_IDLE);
+ }
+ }
+
+ /**
+ * @throws LogicException in case process is not started
+ */
+ public function getStartTime(): float
+ {
+ if (!$this->isStarted()) {
+ throw new LogicException('Start time is only available after process start.');
+ }
+
+ return $this->starttime;
+ }
+
+ /**
+ * Defines options to pass to the underlying proc_open().
+ *
+ * @see https://php.net/proc_open for the options supported by PHP.
+ *
+ * Enabling the "create_new_console" option allows a subprocess to continue
+ * to run after the main process exited, on both Windows and *nix
+ */
+ public function setOptions(array $options)
+ {
+ if ($this->isRunning()) {
+ throw new RuntimeException('Setting options while the process is running is not possible.');
+ }
+
+ $defaultOptions = $this->options;
+ $existingOptions = ['blocking_pipes', 'create_process_group', 'create_new_console'];
+
+ foreach ($options as $key => $value) {
+ if (!\in_array($key, $existingOptions)) {
+ $this->options = $defaultOptions;
+ throw new LogicException(sprintf('Invalid option "%s" passed to "%s()". Supported options are "%s".', $key, __METHOD__, implode('", "', $existingOptions)));
+ }
+ $this->options[$key] = $value;
+ }
+ }
+
+ /**
+ * Returns whether TTY is supported on the current operating system.
+ */
+ public static function isTtySupported(): bool
+ {
+ static $isTtySupported;
+
+ if (null === $isTtySupported) {
+ $isTtySupported = (bool) @proc_open('echo 1 >/dev/null', [['file', '/dev/tty', 'r'], ['file', '/dev/tty', 'w'], ['file', '/dev/tty', 'w']], $pipes);
+ }
+
+ return $isTtySupported;
+ }
+
+ /**
+ * Returns whether PTY is supported on the current operating system.
+ *
+ * @return bool
+ */
+ public static function isPtySupported()
+ {
+ static $result;
+
+ if (null !== $result) {
+ return $result;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ return $result = false;
+ }
+
+ return $result = (bool) @proc_open('echo 1 >/dev/null', [['pty'], ['pty'], ['pty']], $pipes);
+ }
+
+ /**
+ * Creates the descriptors needed by the proc_open.
+ */
+ private function getDescriptors(): array
+ {
+ if ($this->input instanceof \Iterator) {
+ $this->input->rewind();
+ }
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ $this->processPipes = new WindowsPipes($this->input, !$this->outputDisabled || $this->hasCallback);
+ } else {
+ $this->processPipes = new UnixPipes($this->isTty(), $this->isPty(), $this->input, !$this->outputDisabled || $this->hasCallback);
+ }
+
+ return $this->processPipes->getDescriptors();
+ }
+
+ /**
+ * Builds up the callback used by wait().
+ *
+ * The callbacks adds all occurred output to the specific buffer and calls
+ * the user callback (if present) with the received output.
+ *
+ * @param callable|null $callback The user defined PHP callback
+ *
+ * @return \Closure
+ */
+ protected function buildCallback(callable $callback = null)
+ {
+ if ($this->outputDisabled) {
+ return function ($type, $data) use ($callback): bool {
+ return null !== $callback && $callback($type, $data);
+ };
+ }
+
+ $out = self::OUT;
+
+ return function ($type, $data) use ($callback, $out): bool {
+ if ($out == $type) {
+ $this->addOutput($data);
+ } else {
+ $this->addErrorOutput($data);
+ }
+
+ return null !== $callback && $callback($type, $data);
+ };
+ }
+
+ /**
+ * Updates the status of the process, reads pipes.
+ *
+ * @param bool $blocking Whether to use a blocking read call
+ */
+ protected function updateStatus(bool $blocking)
+ {
+ if (self::STATUS_STARTED !== $this->status) {
+ return;
+ }
+
+ $this->processInformation = proc_get_status($this->process);
+ $running = $this->processInformation['running'];
+
+ $this->readPipes($running && $blocking, '\\' !== \DIRECTORY_SEPARATOR || !$running);
+
+ if ($this->fallbackStatus && $this->isSigchildEnabled()) {
+ $this->processInformation = $this->fallbackStatus + $this->processInformation;
+ }
+
+ if (!$running) {
+ $this->close();
+ }
+ }
+
+ /**
+ * Returns whether PHP has been compiled with the '--enable-sigchild' option or not.
+ *
+ * @return bool
+ */
+ protected function isSigchildEnabled()
+ {
+ if (null !== self::$sigchild) {
+ return self::$sigchild;
+ }
+
+ if (!\function_exists('phpinfo')) {
+ return self::$sigchild = false;
+ }
+
+ ob_start();
+ phpinfo(\INFO_GENERAL);
+
+ return self::$sigchild = str_contains(ob_get_clean(), '--enable-sigchild');
+ }
+
+ /**
+ * Reads pipes for the freshest output.
+ *
+ * @param string $caller The name of the method that needs fresh outputs
+ * @param bool $blocking Whether to use blocking calls or not
+ *
+ * @throws LogicException in case output has been disabled or process is not started
+ */
+ private function readPipesForOutput(string $caller, bool $blocking = false)
+ {
+ if ($this->outputDisabled) {
+ throw new LogicException('Output has been disabled.');
+ }
+
+ $this->requireProcessIsStarted($caller);
+
+ $this->updateStatus($blocking);
+ }
+
+ /**
+ * Validates and returns the filtered timeout.
+ *
+ * @throws InvalidArgumentException if the given timeout is a negative number
+ */
+ private function validateTimeout(?float $timeout): ?float
+ {
+ $timeout = (float) $timeout;
+
+ if (0.0 === $timeout) {
+ $timeout = null;
+ } elseif ($timeout < 0) {
+ throw new InvalidArgumentException('The timeout value must be a valid positive integer or float number.');
+ }
+
+ return $timeout;
+ }
+
+ /**
+ * Reads pipes, executes callback.
+ *
+ * @param bool $blocking Whether to use blocking calls or not
+ * @param bool $close Whether to close file handles or not
+ */
+ private function readPipes(bool $blocking, bool $close)
+ {
+ $result = $this->processPipes->readAndWrite($blocking, $close);
+
+ $callback = $this->callback;
+ foreach ($result as $type => $data) {
+ if (3 !== $type) {
+ $callback(self::STDOUT === $type ? self::OUT : self::ERR, $data);
+ } elseif (!isset($this->fallbackStatus['signaled'])) {
+ $this->fallbackStatus['exitcode'] = (int) $data;
+ }
+ }
+ }
+
+ /**
+ * Closes process resource, closes file handles, sets the exitcode.
+ *
+ * @return int The exitcode
+ */
+ private function close(): int
+ {
+ $this->processPipes->close();
+ if (\is_resource($this->process)) {
+ proc_close($this->process);
+ }
+ $this->exitcode = $this->processInformation['exitcode'];
+ $this->status = self::STATUS_TERMINATED;
+
+ if (-1 === $this->exitcode) {
+ if ($this->processInformation['signaled'] && 0 < $this->processInformation['termsig']) {
+ // if process has been signaled, no exitcode but a valid termsig, apply Unix convention
+ $this->exitcode = 128 + $this->processInformation['termsig'];
+ } elseif ($this->isSigchildEnabled()) {
+ $this->processInformation['signaled'] = true;
+ $this->processInformation['termsig'] = -1;
+ }
+ }
+
+ // Free memory from self-reference callback created by buildCallback
+ // Doing so in other contexts like __destruct or by garbage collector is ineffective
+ // Now pipes are closed, so the callback is no longer necessary
+ $this->callback = null;
+
+ return $this->exitcode;
+ }
+
+ /**
+ * Resets data related to the latest run of the process.
+ */
+ private function resetProcessData()
+ {
+ $this->starttime = null;
+ $this->callback = null;
+ $this->exitcode = null;
+ $this->fallbackStatus = [];
+ $this->processInformation = null;
+ $this->stdout = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
+ $this->stderr = fopen('php://temp/maxmemory:'.(1024 * 1024), 'w+');
+ $this->process = null;
+ $this->latestSignal = null;
+ $this->status = self::STATUS_READY;
+ $this->incrementalOutputOffset = 0;
+ $this->incrementalErrorOutputOffset = 0;
+ }
+
+ /**
+ * Sends a POSIX signal to the process.
+ *
+ * @param int $signal A valid POSIX signal (see https://php.net/pcntl.constants)
+ * @param bool $throwException Whether to throw exception in case signal failed
+ *
+ * @throws LogicException In case the process is not running
+ * @throws RuntimeException In case --enable-sigchild is activated and the process can't be killed
+ * @throws RuntimeException In case of failure
+ */
+ private function doSignal(int $signal, bool $throwException): bool
+ {
+ if (null === $pid = $this->getPid()) {
+ if ($throwException) {
+ throw new LogicException('Cannot send signal on a non running process.');
+ }
+
+ return false;
+ }
+
+ if ('\\' === \DIRECTORY_SEPARATOR) {
+ exec(sprintf('taskkill /F /T /PID %d 2>&1', $pid), $output, $exitCode);
+ if ($exitCode && $this->isRunning()) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Unable to kill the process (%s).', implode(' ', $output)));
+ }
+
+ return false;
+ }
+ } else {
+ if (!$this->isSigchildEnabled()) {
+ $ok = @proc_terminate($this->process, $signal);
+ } elseif (\function_exists('posix_kill')) {
+ $ok = @posix_kill($pid, $signal);
+ } elseif ($ok = proc_open(sprintf('kill -%d %d', $signal, $pid), [2 => ['pipe', 'w']], $pipes)) {
+ $ok = false === fgets($pipes[2]);
+ }
+ if (!$ok) {
+ if ($throwException) {
+ throw new RuntimeException(sprintf('Error while sending signal "%s".', $signal));
+ }
+
+ return false;
+ }
+ }
+
+ $this->latestSignal = $signal;
+ $this->fallbackStatus['signaled'] = true;
+ $this->fallbackStatus['exitcode'] = -1;
+ $this->fallbackStatus['termsig'] = $this->latestSignal;
+
+ return true;
+ }
+
+ private function prepareWindowsCommandLine(string $cmd, array &$env): string
+ {
+ $uid = uniqid('', true);
+ $varCount = 0;
+ $varCache = [];
+ $cmd = preg_replace_callback(
+ '/"(?:(
+ [^"%!^]*+
+ (?:
+ (?: !LF! | "(?:\^[%!^])?+" )
+ [^"%!^]*+
+ )++
+ ) | [^"]*+ )"/x',
+ function ($m) use (&$env, &$varCache, &$varCount, $uid) {
+ if (!isset($m[1])) {
+ return $m[0];
+ }
+ if (isset($varCache[$m[0]])) {
+ return $varCache[$m[0]];
+ }
+ if (str_contains($value = $m[1], "\0")) {
+ $value = str_replace("\0", '?', $value);
+ }
+ if (false === strpbrk($value, "\"%!\n")) {
+ return '"'.$value.'"';
+ }
+
+ $value = str_replace(['!LF!', '"^!"', '"^%"', '"^^"', '""'], ["\n", '!', '%', '^', '"'], $value);
+ $value = '"'.preg_replace('/(\\\\*)"/', '$1$1\\"', $value).'"';
+ $var = $uid.++$varCount;
+
+ $env[$var] = $value;
+
+ return $varCache[$m[0]] = '!'.$var.'!';
+ },
+ $cmd
+ );
+
+ $cmd = 'cmd /V:ON /E:ON /D /C ('.str_replace("\n", ' ', $cmd).')';
+ foreach ($this->processPipes->getFiles() as $offset => $filename) {
+ $cmd .= ' '.$offset.'>"'.$filename.'"';
+ }
+
+ return $cmd;
+ }
+
+ /**
+ * Ensures the process is running or terminated, throws a LogicException if the process has a not started.
+ *
+ * @throws LogicException if the process has not run
+ */
+ private function requireProcessIsStarted(string $functionName)
+ {
+ if (!$this->isStarted()) {
+ throw new LogicException(sprintf('Process must be started before calling "%s()".', $functionName));
+ }
+ }
+
+ /**
+ * Ensures the process is terminated, throws a LogicException if the process has a status different than "terminated".
+ *
+ * @throws LogicException if the process is not yet terminated
+ */
+ private function requireProcessIsTerminated(string $functionName)
+ {
+ if (!$this->isTerminated()) {
+ throw new LogicException(sprintf('Process must be terminated before calling "%s()".', $functionName));
+ }
+ }
+
+ /**
+ * Escapes a string to be used as a shell argument.
+ */
+ private function escapeArgument(?string $argument): string
+ {
+ if ('' === $argument || null === $argument) {
+ return '""';
+ }
+ if ('\\' !== \DIRECTORY_SEPARATOR) {
+ return "'".str_replace("'", "'\\''", $argument)."'";
+ }
+ if (str_contains($argument, "\0")) {
+ $argument = str_replace("\0", '?', $argument);
+ }
+ if (!preg_match('/[\/()%!^"<>&|\s]/', $argument)) {
+ return $argument;
+ }
+ $argument = preg_replace('/(\\\\+)$/', '$1$1', $argument);
+
+ return '"'.str_replace(['"', '^', '%', '!', "\n"], ['""', '"^^"', '"^%"', '"^!"', '!LF!'], $argument).'"';
+ }
+
+ private function replacePlaceholders(string $commandline, array $env)
+ {
+ return preg_replace_callback('/"\$\{:([_a-zA-Z]++[_a-zA-Z0-9]*+)\}"/', function ($matches) use ($commandline, $env) {
+ if (!isset($env[$matches[1]]) || false === $env[$matches[1]]) {
+ throw new InvalidArgumentException(sprintf('Command line is missing a value for parameter "%s": ', $matches[1]).$commandline);
+ }
+
+ return $this->escapeArgument($env[$matches[1]]);
+ }, $commandline);
+ }
+
+ private function getDefaultEnv(): array
+ {
+ $env = getenv();
+ $env = ('\\' === \DIRECTORY_SEPARATOR ? array_intersect_ukey($env, $_SERVER, 'strcasecmp') : array_intersect_key($env, $_SERVER)) ?: $env;
+
+ return $_ENV + ('\\' === \DIRECTORY_SEPARATOR ? array_diff_ukey($env, $_ENV, 'strcasecmp') : $env);
+ }
+}
diff --git a/vendor/symfony/process/ProcessUtils.php b/vendor/symfony/process/ProcessUtils.php
new file mode 100644
index 000000000..2a7aff71b
--- /dev/null
+++ b/vendor/symfony/process/ProcessUtils.php
@@ -0,0 +1,69 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Process;
+
+use Symfony\Component\Process\Exception\InvalidArgumentException;
+
+/**
+ * ProcessUtils is a bunch of utility methods.
+ *
+ * This class contains static methods only and is not meant to be instantiated.
+ *
+ * @author Martin Hasoň
+ */
+class ProcessUtils
+{
+ /**
+ * This class should not be instantiated.
+ */
+ private function __construct()
+ {
+ }
+
+ /**
+ * Validates and normalizes a Process input.
+ *
+ * @param string $caller The name of method call that validates the input
+ * @param mixed $input The input to validate
+ *
+ * @return mixed
+ *
+ * @throws InvalidArgumentException In case the input is not valid
+ */
+ public static function validateInput(string $caller, $input)
+ {
+ if (null !== $input) {
+ if (\is_resource($input)) {
+ return $input;
+ }
+ if (\is_string($input)) {
+ return $input;
+ }
+ if (\is_scalar($input)) {
+ return (string) $input;
+ }
+ if ($input instanceof Process) {
+ return $input->getIterator($input::ITER_SKIP_ERR);
+ }
+ if ($input instanceof \Iterator) {
+ return $input;
+ }
+ if ($input instanceof \Traversable) {
+ return new \IteratorIterator($input);
+ }
+
+ throw new InvalidArgumentException(sprintf('"%s" only accepts strings, Traversable objects or stream resources.', $caller));
+ }
+
+ return $input;
+ }
+}
diff --git a/vendor/symfony/process/README.md b/vendor/symfony/process/README.md
new file mode 100644
index 000000000..8777de4a6
--- /dev/null
+++ b/vendor/symfony/process/README.md
@@ -0,0 +1,28 @@
+Process Component
+=================
+
+The Process component executes commands in sub-processes.
+
+Sponsor
+-------
+
+The Process component for Symfony 5.4/6.0 is [backed][1] by [SensioLabs][2].
+
+As the creator of Symfony, SensioLabs supports companies using Symfony, with an
+offering encompassing consultancy, expertise, services, training, and technical
+assistance to ensure the success of web application development projects.
+
+Help Symfony by [sponsoring][3] its development!
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/components/process.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
+
+[1]: https://symfony.com/backers
+[2]: https://sensiolabs.com
+[3]: https://symfony.com/sponsor
diff --git a/vendor/symfony/yaml/composer.json b/vendor/symfony/process/composer.json
old mode 100755
new mode 100644
similarity index 59%
rename from vendor/symfony/yaml/composer.json
rename to vendor/symfony/process/composer.json
index d07323608..1669eba57
--- a/vendor/symfony/yaml/composer.json
+++ b/vendor/symfony/process/composer.json
@@ -1,7 +1,7 @@
{
- "name": "symfony/yaml",
+ "name": "symfony/process",
"type": "library",
- "description": "Symfony Yaml Component",
+ "description": "Executes commands in sub-processes",
"keywords": [],
"homepage": "https://symfony.com",
"license": "MIT",
@@ -16,19 +16,14 @@
}
],
"require": {
- "php": ">=5.3.9",
- "symfony/polyfill-ctype": "~1.8"
+ "php": ">=7.2.5",
+ "symfony/polyfill-php80": "^1.16"
},
"autoload": {
- "psr-4": { "Symfony\\Component\\Yaml\\": "" },
+ "psr-4": { "Symfony\\Component\\Process\\": "" },
"exclude-from-classmap": [
"/Tests/"
]
},
- "minimum-stability": "dev",
- "extra": {
- "branch-alias": {
- "dev-master": "2.8-dev"
- }
- }
+ "minimum-stability": "dev"
}
diff --git a/vendor/symfony/psr-http-message-bridge/.php-cs-fixer.dist.php b/vendor/symfony/psr-http-message-bridge/.php-cs-fixer.dist.php
old mode 100755
new mode 100644
diff --git a/vendor/symfony/psr-http-message-bridge/ArgumentValueResolver/PsrServerRequestResolver.php b/vendor/symfony/psr-http-message-bridge/ArgumentValueResolver/PsrServerRequestResolver.php
old mode 100755
new mode 100644
index 29dc7dc98..61cd8c5c5
--- a/vendor/symfony/psr-http-message-bridge/ArgumentValueResolver/PsrServerRequestResolver.php
+++ b/vendor/symfony/psr-http-message-bridge/ArgumentValueResolver/PsrServerRequestResolver.php
@@ -17,6 +17,7 @@ use Psr\Http\Message\ServerRequestInterface;
use Symfony\Bridge\PsrHttpMessage\HttpMessageFactoryInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ArgumentValueResolverInterface;
+use Symfony\Component\HttpKernel\Controller\ValueResolverInterface as BaseValueResolverInterface;
use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
/**
@@ -25,7 +26,7 @@ use Symfony\Component\HttpKernel\ControllerMetadata\ArgumentMetadata;
* @author Iltar van der Berg
* @author Alexander M. Turek
*/
-final class PsrServerRequestResolver implements ArgumentValueResolverInterface
+final class PsrServerRequestResolver implements ArgumentValueResolverInterface, ValueResolverInterface
{
private const SUPPORTED_TYPES = [
ServerRequestInterface::class => true,
@@ -45,6 +46,10 @@ final class PsrServerRequestResolver implements ArgumentValueResolverInterface
*/
public function supports(Request $request, ArgumentMetadata $argument): bool
{
+ if ($this instanceof BaseValueResolverInterface) {
+ trigger_deprecation('symfony/psr-http-message-bridge', '2.3', 'Method "%s" is deprecated, call "resolve()" without calling "supports()" first.', __METHOD__);
+ }
+
return self::SUPPORTED_TYPES[$argument->getType()] ?? false;
}
@@ -53,6 +58,10 @@ final class PsrServerRequestResolver implements ArgumentValueResolverInterface
*/
public function resolve(Request $request, ArgumentMetadata $argument): \Traversable
{
+ if (!isset(self::SUPPORTED_TYPES[$argument->getType()])) {
+ return;
+ }
+
yield $this->httpMessageFactory->createRequest($request);
}
}
diff --git a/vendor/symfony/psr-http-message-bridge/ArgumentValueResolver/ValueResolverInterface.php b/vendor/symfony/psr-http-message-bridge/ArgumentValueResolver/ValueResolverInterface.php
new file mode 100644
index 000000000..83a321ab7
--- /dev/null
+++ b/vendor/symfony/psr-http-message-bridge/ArgumentValueResolver/ValueResolverInterface.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\PsrHttpMessage\ArgumentValueResolver;
+
+use Symfony\Component\HttpKernel\Controller\ValueResolverInterface as BaseValueResolverInterface;
+
+if (interface_exists(BaseValueResolverInterface::class)) {
+ /** @internal */
+ interface ValueResolverInterface extends BaseValueResolverInterface
+ {
+ }
+} else {
+ /** @internal */
+ interface ValueResolverInterface
+ {
+ }
+}
diff --git a/vendor/symfony/psr-http-message-bridge/CHANGELOG.md b/vendor/symfony/psr-http-message-bridge/CHANGELOG.md
old mode 100755
new mode 100644
index 1378fd6ac..f32c06f2e
--- a/vendor/symfony/psr-http-message-bridge/CHANGELOG.md
+++ b/vendor/symfony/psr-http-message-bridge/CHANGELOG.md
@@ -1,6 +1,15 @@
CHANGELOG
=========
+# 2.3.1 (2023-07-26)
+
+* Don't rely on `Request::getPayload()` to populate the parsed body
+
+# 2.3.0 (2023-07-25)
+
+* Leverage `Request::getPayload()` to populate the parsed body of PSR-7 requests
+* Implement `ValueResolverInterface` introduced with Symfony 6.2
+
# 2.2.0 (2023-04-21)
* Drop support for Symfony 4
diff --git a/vendor/symfony/psr-http-message-bridge/EventListener/PsrResponseListener.php b/vendor/symfony/psr-http-message-bridge/EventListener/PsrResponseListener.php
old mode 100755
new mode 100644
diff --git a/vendor/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php b/vendor/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php
old mode 100755
new mode 100644
index 457e3468d..a69e8ffee
--- a/vendor/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php
+++ b/vendor/symfony/psr-http-message-bridge/Factory/HttpFoundationFactory.php
@@ -41,6 +41,8 @@ class HttpFoundationFactory implements HttpFoundationFactoryInterface
/**
* {@inheritdoc}
+ *
+ * @return Request
*/
public function createRequest(ServerRequestInterface $psrRequest, bool $streamed = false)
{
@@ -121,6 +123,8 @@ class HttpFoundationFactory implements HttpFoundationFactoryInterface
/**
* {@inheritdoc}
+ *
+ * @return Response
*/
public function createResponse(ResponseInterface $psrResponse, bool $streamed = false)
{
@@ -217,13 +221,13 @@ class HttpFoundationFactory implements HttpFoundationFactoryInterface
return new Cookie(
$cookieName,
$cookieValue,
- isset($cookieExpire) ? $cookieExpire : 0,
- isset($cookiePath) ? $cookiePath : '/',
- isset($cookieDomain) ? $cookieDomain : null,
+ $cookieExpire ?? 0,
+ $cookiePath ?? '/',
+ $cookieDomain ?? null,
isset($cookieSecure),
isset($cookieHttpOnly),
true,
- isset($samesite) ? $samesite : null
+ $samesite ?? null
);
}
diff --git a/vendor/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php b/vendor/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php
old mode 100755
new mode 100644
index b1b6f9ae2..09c4360dd
--- a/vendor/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php
+++ b/vendor/symfony/psr-http-message-bridge/Factory/PsrHttpFactory.php
@@ -12,7 +12,9 @@
namespace Symfony\Bridge\PsrHttpMessage\Factory;
use Psr\Http\Message\ResponseFactoryInterface;
+use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestFactoryInterface;
+use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\StreamFactoryInterface;
use Psr\Http\Message\UploadedFileFactoryInterface;
use Psr\Http\Message\UploadedFileInterface;
@@ -27,6 +29,7 @@ use Symfony\Component\HttpFoundation\StreamedResponse;
* Builds Psr\HttpMessage instances using a PSR-17 implementation.
*
* @author Antonio J. García Lagar
+ * @author Aurélien Pillevesse
*/
class PsrHttpFactory implements HttpMessageFactoryInterface
{
@@ -45,6 +48,8 @@ class PsrHttpFactory implements HttpMessageFactoryInterface
/**
* {@inheritdoc}
+ *
+ * @return ServerRequestInterface
*/
public function createRequest(Request $symfonyRequest)
{
@@ -67,12 +72,28 @@ class PsrHttpFactory implements HttpMessageFactoryInterface
$body = $this->streamFactory->createStreamFromResource($symfonyRequest->getContent(true));
+ if (method_exists(Request::class, 'getContentTypeFormat')) {
+ $format = $symfonyRequest->getContentTypeFormat();
+ } else {
+ $format = $symfonyRequest->getContentType();
+ }
+
+ if ('json' === $format) {
+ $parsedBody = json_decode($symfonyRequest->getContent(), true, 512, \JSON_BIGINT_AS_STRING);
+
+ if (!\is_array($parsedBody)) {
+ $parsedBody = null;
+ }
+ } else {
+ $parsedBody = $symfonyRequest->request->all();
+ }
+
$request = $request
->withBody($body)
->withUploadedFiles($this->getFiles($symfonyRequest->files->all()))
->withCookieParams($symfonyRequest->cookies->all())
->withQueryParams($symfonyRequest->query->all())
- ->withParsedBody($symfonyRequest->request->all())
+ ->withParsedBody($parsedBody)
;
foreach ($symfonyRequest->attributes->all() as $key => $value) {
@@ -84,10 +105,8 @@ class PsrHttpFactory implements HttpMessageFactoryInterface
/**
* Converts Symfony uploaded files array to the PSR one.
- *
- * @return array
*/
- private function getFiles(array $uploadedFiles)
+ private function getFiles(array $uploadedFiles): array
{
$files = [];
@@ -108,10 +127,8 @@ class PsrHttpFactory implements HttpMessageFactoryInterface
/**
* Creates a PSR-7 UploadedFile instance from a Symfony one.
- *
- * @return UploadedFileInterface
*/
- private function createUploadedFile(UploadedFile $symfonyUploadedFile)
+ private function createUploadedFile(UploadedFile $symfonyUploadedFile): UploadedFileInterface
{
return $this->uploadedFileFactory->createUploadedFile(
$this->streamFactory->createStreamFromFile(
@@ -126,6 +143,8 @@ class PsrHttpFactory implements HttpMessageFactoryInterface
/**
* {@inheritdoc}
+ *
+ * @return ResponseInterface
*/
public function createResponse(Response $symfonyResponse)
{
diff --git a/vendor/symfony/psr-http-message-bridge/Factory/UploadedFile.php b/vendor/symfony/psr-http-message-bridge/Factory/UploadedFile.php
old mode 100755
new mode 100644
index b9510455c..4d38a1f7c
--- a/vendor/symfony/psr-http-message-bridge/Factory/UploadedFile.php
+++ b/vendor/symfony/psr-http-message-bridge/Factory/UploadedFile.php
@@ -52,7 +52,7 @@ class UploadedFile extends BaseUploadedFile
/**
* {@inheritdoc}
*/
- public function move($directory, $name = null): File
+ public function move(string $directory, string $name = null): File
{
if (!$this->isValid() || $this->test) {
return parent::move($directory, $name);
diff --git a/vendor/symfony/psr-http-message-bridge/HttpFoundationFactoryInterface.php b/vendor/symfony/psr-http-message-bridge/HttpFoundationFactoryInterface.php
old mode 100755
new mode 100644
diff --git a/vendor/symfony/psr-http-message-bridge/HttpMessageFactoryInterface.php b/vendor/symfony/psr-http-message-bridge/HttpMessageFactoryInterface.php
old mode 100755
new mode 100644
diff --git a/vendor/symfony/psr-http-message-bridge/LICENSE b/vendor/symfony/psr-http-message-bridge/LICENSE
old mode 100755
new mode 100644
diff --git a/vendor/symfony/psr-http-message-bridge/README.md b/vendor/symfony/psr-http-message-bridge/README.md
old mode 100755
new mode 100644
diff --git a/vendor/symfony/psr-http-message-bridge/composer.json b/vendor/symfony/psr-http-message-bridge/composer.json
old mode 100755
new mode 100644
index e08b15c7a..b705eb2e2
--- a/vendor/symfony/psr-http-message-bridge/composer.json
+++ b/vendor/symfony/psr-http-message-bridge/composer.json
@@ -18,6 +18,7 @@
"require": {
"php": ">=7.2.5",
"psr/http-message": "^1.0 || ^2.0",
+ "symfony/deprecation-contracts": "^2.5 || ^3.0",
"symfony/http-foundation": "^5.4 || ^6.0"
},
"require-dev": {
@@ -41,7 +42,7 @@
},
"extra": {
"branch-alias": {
- "dev-main": "2.2-dev"
+ "dev-main": "2.3-dev"
}
}
}
diff --git a/vendor/symfony/translation-contracts/CHANGELOG.md b/vendor/symfony/translation-contracts/CHANGELOG.md
new file mode 100644
index 000000000..7932e2613
--- /dev/null
+++ b/vendor/symfony/translation-contracts/CHANGELOG.md
@@ -0,0 +1,5 @@
+CHANGELOG
+=========
+
+The changelog is maintained for all Symfony contracts at the following URL:
+https://github.com/symfony/contracts/blob/main/CHANGELOG.md
diff --git a/vendor/symfony/translation-contracts/LICENSE b/vendor/symfony/translation-contracts/LICENSE
new file mode 100644
index 000000000..7536caeae
--- /dev/null
+++ b/vendor/symfony/translation-contracts/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2018-present Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/translation-contracts/LocaleAwareInterface.php b/vendor/symfony/translation-contracts/LocaleAwareInterface.php
new file mode 100644
index 000000000..db40ba13e
--- /dev/null
+++ b/vendor/symfony/translation-contracts/LocaleAwareInterface.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Translation;
+
+interface LocaleAwareInterface
+{
+ /**
+ * Sets the current locale.
+ *
+ * @return void
+ *
+ * @throws \InvalidArgumentException If the locale contains invalid characters
+ */
+ public function setLocale(string $locale);
+
+ /**
+ * Returns the current locale.
+ */
+ public function getLocale(): string;
+}
diff --git a/vendor/symfony/translation-contracts/README.md b/vendor/symfony/translation-contracts/README.md
new file mode 100644
index 000000000..b211d5849
--- /dev/null
+++ b/vendor/symfony/translation-contracts/README.md
@@ -0,0 +1,9 @@
+Symfony Translation Contracts
+=============================
+
+A set of abstractions extracted out of the Symfony components.
+
+Can be used to build on semantics that the Symfony components proved useful and
+that already have battle tested implementations.
+
+See https://github.com/symfony/contracts/blob/main/README.md for more information.
diff --git a/vendor/symfony/translation-contracts/Test/TranslatorTest.php b/vendor/symfony/translation-contracts/Test/TranslatorTest.php
new file mode 100644
index 000000000..674b78b3e
--- /dev/null
+++ b/vendor/symfony/translation-contracts/Test/TranslatorTest.php
@@ -0,0 +1,384 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Translation\Test;
+
+use PHPUnit\Framework\TestCase;
+use Symfony\Contracts\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorTrait;
+
+/**
+ * Test should cover all languages mentioned on http://translate.sourceforge.net/wiki/l10n/pluralforms
+ * and Plural forms mentioned on http://www.gnu.org/software/gettext/manual/gettext.html#Plural-forms.
+ *
+ * See also https://developer.mozilla.org/en/Localization_and_Plurals which mentions 15 rules having a maximum of 6 forms.
+ * The mozilla code is also interesting to check for.
+ *
+ * As mentioned by chx http://drupal.org/node/1273968 we can cover all by testing number from 0 to 199
+ *
+ * The goal to cover all languages is to far fetched so this test case is smaller.
+ *
+ * @author Clemens Tolboom clemens@build2be.nl
+ */
+class TranslatorTest extends TestCase
+{
+ private $defaultLocale;
+
+ protected function setUp(): void
+ {
+ $this->defaultLocale = \Locale::getDefault();
+ \Locale::setDefault('en');
+ }
+
+ protected function tearDown(): void
+ {
+ \Locale::setDefault($this->defaultLocale);
+ }
+
+ public function getTranslator(): TranslatorInterface
+ {
+ return new class() implements TranslatorInterface {
+ use TranslatorTrait;
+ };
+ }
+
+ /**
+ * @dataProvider getTransTests
+ */
+ public function testTrans($expected, $id, $parameters)
+ {
+ $translator = $this->getTranslator();
+
+ $this->assertEquals($expected, $translator->trans($id, $parameters));
+ }
+
+ /**
+ * @dataProvider getTransChoiceTests
+ */
+ public function testTransChoiceWithExplicitLocale($expected, $id, $number)
+ {
+ $translator = $this->getTranslator();
+
+ $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number]));
+ }
+
+ /**
+ * @requires extension intl
+ *
+ * @dataProvider getTransChoiceTests
+ */
+ public function testTransChoiceWithDefaultLocale($expected, $id, $number)
+ {
+ $translator = $this->getTranslator();
+
+ $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number]));
+ }
+
+ /**
+ * @dataProvider getTransChoiceTests
+ */
+ public function testTransChoiceWithEnUsPosix($expected, $id, $number)
+ {
+ $translator = $this->getTranslator();
+ $translator->setLocale('en_US_POSIX');
+
+ $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number]));
+ }
+
+ public function testGetSetLocale()
+ {
+ $translator = $this->getTranslator();
+
+ $this->assertEquals('en', $translator->getLocale());
+ }
+
+ /**
+ * @requires extension intl
+ */
+ public function testGetLocaleReturnsDefaultLocaleIfNotSet()
+ {
+ $translator = $this->getTranslator();
+
+ \Locale::setDefault('pt_BR');
+ $this->assertEquals('pt_BR', $translator->getLocale());
+
+ \Locale::setDefault('en');
+ $this->assertEquals('en', $translator->getLocale());
+ }
+
+ public static function getTransTests()
+ {
+ return [
+ ['Symfony is great!', 'Symfony is great!', []],
+ ['Symfony is awesome!', 'Symfony is %what%!', ['%what%' => 'awesome']],
+ ];
+ }
+
+ public static function getTransChoiceTests()
+ {
+ return [
+ ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
+ ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1],
+ ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10],
+ ['There are 0 apples', 'There is 1 apple|There are %count% apples', 0],
+ ['There is 1 apple', 'There is 1 apple|There are %count% apples', 1],
+ ['There are 10 apples', 'There is 1 apple|There are %count% apples', 10],
+ // custom validation messages may be coded with a fixed value
+ ['There are 2 apples', 'There are 2 apples', 2],
+ ];
+ }
+
+ /**
+ * @dataProvider getInterval
+ */
+ public function testInterval($expected, $number, $interval)
+ {
+ $translator = $this->getTranslator();
+
+ $this->assertEquals($expected, $translator->trans($interval.' foo|[1,Inf[ bar', ['%count%' => $number]));
+ }
+
+ public static function getInterval()
+ {
+ return [
+ ['foo', 3, '{1,2, 3 ,4}'],
+ ['bar', 10, '{1,2, 3 ,4}'],
+ ['bar', 3, '[1,2]'],
+ ['foo', 1, '[1,2]'],
+ ['foo', 2, '[1,2]'],
+ ['bar', 1, ']1,2['],
+ ['bar', 2, ']1,2['],
+ ['foo', log(0), '[-Inf,2['],
+ ['foo', -log(0), '[-2,+Inf]'],
+ ];
+ }
+
+ /**
+ * @dataProvider getChooseTests
+ */
+ public function testChoose($expected, $id, $number, $locale = null)
+ {
+ $translator = $this->getTranslator();
+
+ $this->assertEquals($expected, $translator->trans($id, ['%count%' => $number], null, $locale));
+ }
+
+ public function testReturnMessageIfExactlyOneStandardRuleIsGiven()
+ {
+ $translator = $this->getTranslator();
+
+ $this->assertEquals('There are two apples', $translator->trans('There are two apples', ['%count%' => 2]));
+ }
+
+ /**
+ * @dataProvider getNonMatchingMessages
+ */
+ public function testThrowExceptionIfMatchingMessageCannotBeFound($id, $number)
+ {
+ $this->expectException(\InvalidArgumentException::class);
+ $translator = $this->getTranslator();
+
+ $translator->trans($id, ['%count%' => $number]);
+ }
+
+ public static function getNonMatchingMessages()
+ {
+ return [
+ ['{0} There are no apples|{1} There is one apple', 2],
+ ['{1} There is one apple|]1,Inf] There are %count% apples', 0],
+ ['{1} There is one apple|]2,Inf] There are %count% apples', 2],
+ ['{0} There are no apples|There is one apple', 2],
+ ];
+ }
+
+ public static function getChooseTests()
+ {
+ return [
+ ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
+ ['There are no apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
+ ['There are no apples', '{0}There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 0],
+
+ ['There is one apple', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 1],
+
+ ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10],
+ ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf]There are %count% apples', 10],
+ ['There are 10 apples', '{0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples', 10],
+
+ ['There are 0 apples', 'There is one apple|There are %count% apples', 0],
+ ['There is one apple', 'There is one apple|There are %count% apples', 1],
+ ['There are 10 apples', 'There is one apple|There are %count% apples', 10],
+
+ ['There are 0 apples', 'one: There is one apple|more: There are %count% apples', 0],
+ ['There is one apple', 'one: There is one apple|more: There are %count% apples', 1],
+ ['There are 10 apples', 'one: There is one apple|more: There are %count% apples', 10],
+
+ ['There are no apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 0],
+ ['There is one apple', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 1],
+ ['There are 10 apples', '{0} There are no apples|one: There is one apple|more: There are %count% apples', 10],
+
+ ['', '{0}|{1} There is one apple|]1,Inf] There are %count% apples', 0],
+ ['', '{0} There are no apples|{1}|]1,Inf] There are %count% apples', 1],
+
+ // Indexed only tests which are Gettext PoFile* compatible strings.
+ ['There are 0 apples', 'There is one apple|There are %count% apples', 0],
+ ['There is one apple', 'There is one apple|There are %count% apples', 1],
+ ['There are 2 apples', 'There is one apple|There are %count% apples', 2],
+
+ // Tests for float numbers
+ ['There is almost one apple', '{0} There are no apples|]0,1[ There is almost one apple|{1} There is one apple|[1,Inf] There is more than one apple', 0.7],
+ ['There is one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1],
+ ['There is more than one apple', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 1.7],
+ ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0],
+ ['There are no apples', '{0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0.0],
+ ['There are no apples', '{0.0} There are no apples|]0,1[There are %count% apples|{1} There is one apple|[1,Inf] There is more than one apple', 0],
+
+ // Test texts with new-lines
+ // with double-quotes and \n in id & double-quotes and actual newlines in text
+ ["This is a text with a\n new-line in it. Selector = 0.", '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 0],
+ // with double-quotes and \n in id and single-quotes and actual newlines in text
+ ["This is a text with a\n new-line in it. Selector = 1.", '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 1],
+ ["This is a text with a\n new-line in it. Selector > 1.", '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 5],
+ // with double-quotes and id split across lines
+ ['This is a text with a
+ new-line in it. Selector = 1.', '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 1],
+ // with single-quotes and id split across lines
+ ['This is a text with a
+ new-line in it. Selector > 1.', '{0}This is a text with a
+ new-line in it. Selector = 0.|{1}This is a text with a
+ new-line in it. Selector = 1.|[1,Inf]This is a text with a
+ new-line in it. Selector > 1.', 5],
+ // with single-quotes and \n in text
+ ['This is a text with a\nnew-line in it. Selector = 0.', '{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.', 0],
+ // with double-quotes and id split across lines
+ ["This is a text with a\nnew-line in it. Selector = 1.", "{0}This is a text with a\nnew-line in it. Selector = 0.|{1}This is a text with a\nnew-line in it. Selector = 1.|[1,Inf]This is a text with a\nnew-line in it. Selector > 1.", 1],
+ // escape pipe
+ ['This is a text with | in it. Selector = 0.', '{0}This is a text with || in it. Selector = 0.|{1}This is a text with || in it. Selector = 1.', 0],
+ // Empty plural set (2 plural forms) from a .PO file
+ ['', '|', 1],
+ // Empty plural set (3 plural forms) from a .PO file
+ ['', '||', 1],
+
+ // Floating values
+ ['1.5 liters', '%count% liter|%count% liters', 1.5],
+ ['1.5 litre', '%count% litre|%count% litres', 1.5, 'fr'],
+
+ // Negative values
+ ['-1 degree', '%count% degree|%count% degrees', -1],
+ ['-1 degré', '%count% degré|%count% degrés', -1],
+ ['-1.5 degrees', '%count% degree|%count% degrees', -1.5],
+ ['-1.5 degré', '%count% degré|%count% degrés', -1.5, 'fr'],
+ ['-2 degrees', '%count% degree|%count% degrees', -2],
+ ['-2 degrés', '%count% degré|%count% degrés', -2],
+ ];
+ }
+
+ /**
+ * @dataProvider failingLangcodes
+ */
+ public function testFailedLangcodes($nplural, $langCodes)
+ {
+ $matrix = $this->generateTestData($langCodes);
+ $this->validateMatrix($nplural, $matrix, false);
+ }
+
+ /**
+ * @dataProvider successLangcodes
+ */
+ public function testLangcodes($nplural, $langCodes)
+ {
+ $matrix = $this->generateTestData($langCodes);
+ $this->validateMatrix($nplural, $matrix);
+ }
+
+ /**
+ * This array should contain all currently known langcodes.
+ *
+ * As it is impossible to have this ever complete we should try as hard as possible to have it almost complete.
+ */
+ public static function successLangcodes(): array
+ {
+ return [
+ ['1', ['ay', 'bo', 'cgg', 'dz', 'id', 'ja', 'jbo', 'ka', 'kk', 'km', 'ko', 'ky']],
+ ['2', ['nl', 'fr', 'en', 'de', 'de_GE', 'hy', 'hy_AM', 'en_US_POSIX']],
+ ['3', ['be', 'bs', 'cs', 'hr']],
+ ['4', ['cy', 'mt', 'sl']],
+ ['6', ['ar']],
+ ];
+ }
+
+ /**
+ * This array should be at least empty within the near future.
+ *
+ * This both depends on a complete list trying to add above as understanding
+ * the plural rules of the current failing languages.
+ *
+ * @return array with nplural together with langcodes
+ */
+ public static function failingLangcodes(): array
+ {
+ return [
+ ['1', ['fa']],
+ ['2', ['jbo']],
+ ['3', ['cbs']],
+ ['4', ['gd', 'kw']],
+ ['5', ['ga']],
+ ];
+ }
+
+ /**
+ * We validate only on the plural coverage. Thus the real rules is not tested.
+ *
+ * @param string $nplural Plural expected
+ * @param array $matrix Containing langcodes and their plural index values
+ */
+ protected function validateMatrix(string $nplural, array $matrix, bool $expectSuccess = true)
+ {
+ foreach ($matrix as $langCode => $data) {
+ $indexes = array_flip($data);
+ if ($expectSuccess) {
+ $this->assertCount($nplural, $indexes, "Langcode '$langCode' has '$nplural' plural forms.");
+ } else {
+ $this->assertNotEquals((int) $nplural, \count($indexes), "Langcode '$langCode' has '$nplural' plural forms.");
+ }
+ }
+ }
+
+ protected function generateTestData($langCodes)
+ {
+ $translator = new class() {
+ use TranslatorTrait {
+ getPluralizationRule as public;
+ }
+ };
+
+ $matrix = [];
+ foreach ($langCodes as $langCode) {
+ for ($count = 0; $count < 200; ++$count) {
+ $plural = $translator->getPluralizationRule($count, $langCode);
+ $matrix[$langCode][$count] = $plural;
+ }
+ }
+
+ return $matrix;
+ }
+}
diff --git a/vendor/symfony/translation-contracts/TranslatableInterface.php b/vendor/symfony/translation-contracts/TranslatableInterface.php
new file mode 100644
index 000000000..47fd6fa02
--- /dev/null
+++ b/vendor/symfony/translation-contracts/TranslatableInterface.php
@@ -0,0 +1,20 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Translation;
+
+/**
+ * @author Nicolas Grekas
+ */
+interface TranslatableInterface
+{
+ public function trans(TranslatorInterface $translator, string $locale = null): string;
+}
diff --git a/vendor/symfony/translation-contracts/TranslatorInterface.php b/vendor/symfony/translation-contracts/TranslatorInterface.php
new file mode 100644
index 000000000..018db07eb
--- /dev/null
+++ b/vendor/symfony/translation-contracts/TranslatorInterface.php
@@ -0,0 +1,68 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Translation;
+
+/**
+ * @author Fabien Potencier
+ */
+interface TranslatorInterface
+{
+ /**
+ * Translates the given message.
+ *
+ * When a number is provided as a parameter named "%count%", the message is parsed for plural
+ * forms and a translation is chosen according to this number using the following rules:
+ *
+ * Given a message with different plural translations separated by a
+ * pipe (|), this method returns the correct portion of the message based
+ * on the given number, locale and the pluralization rules in the message
+ * itself.
+ *
+ * The message supports two different types of pluralization rules:
+ *
+ * interval: {0} There are no apples|{1} There is one apple|]1,Inf] There are %count% apples
+ * indexed: There is one apple|There are %count% apples
+ *
+ * The indexed solution can also contain labels (e.g. one: There is one apple).
+ * This is purely for making the translations more clear - it does not
+ * affect the functionality.
+ *
+ * The two methods can also be mixed:
+ * {0} There are no apples|one: There is one apple|more: There are %count% apples
+ *
+ * An interval can represent a finite set of numbers:
+ * {1,2,3,4}
+ *
+ * An interval can represent numbers between two numbers:
+ * [1, +Inf]
+ * ]-1,2[
+ *
+ * The left delimiter can be [ (inclusive) or ] (exclusive).
+ * The right delimiter can be [ (exclusive) or ] (inclusive).
+ * Beside numbers, you can use -Inf and +Inf for the infinite.
+ *
+ * @see https://en.wikipedia.org/wiki/ISO_31-11
+ *
+ * @param string $id The message id (may also be an object that can be cast to string)
+ * @param array $parameters An array of parameters for the message
+ * @param string|null $domain The domain for the message or null to use the default
+ * @param string|null $locale The locale or null to use the default
+ *
+ * @throws \InvalidArgumentException If the locale contains invalid characters
+ */
+ public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string;
+
+ /**
+ * Returns the default locale.
+ */
+ public function getLocale(): string;
+}
diff --git a/vendor/symfony/translation-contracts/TranslatorTrait.php b/vendor/symfony/translation-contracts/TranslatorTrait.php
new file mode 100644
index 000000000..e3b0adff0
--- /dev/null
+++ b/vendor/symfony/translation-contracts/TranslatorTrait.php
@@ -0,0 +1,225 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Contracts\Translation;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+
+/**
+ * A trait to help implement TranslatorInterface and LocaleAwareInterface.
+ *
+ * @author Fabien Potencier
+ */
+trait TranslatorTrait
+{
+ private ?string $locale = null;
+
+ /**
+ * @return void
+ */
+ public function setLocale(string $locale)
+ {
+ $this->locale = $locale;
+ }
+
+ public function getLocale(): string
+ {
+ return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en');
+ }
+
+ public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string
+ {
+ if (null === $id || '' === $id) {
+ return '';
+ }
+
+ if (!isset($parameters['%count%']) || !is_numeric($parameters['%count%'])) {
+ return strtr($id, $parameters);
+ }
+
+ $number = (float) $parameters['%count%'];
+ $locale = $locale ?: $this->getLocale();
+
+ $parts = [];
+ if (preg_match('/^\|++$/', $id)) {
+ $parts = explode('|', $id);
+ } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) {
+ $parts = $matches[0];
+ }
+
+ $intervalRegexp = <<<'EOF'
+/^(?P
+ ({\s*
+ (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)
+ \s*})
+
+ |
+
+ (?P[\[\]])
+ \s*
+ (?P-Inf|\-?\d+(\.\d+)?)
+ \s*,\s*
+ (?P\+?Inf|\-?\d+(\.\d+)?)
+ \s*
+ (?P[\[\]])
+)\s*(?P.*?)$/xs
+EOF;
+
+ $standardRules = [];
+ foreach ($parts as $part) {
+ $part = trim(str_replace('||', '|', $part));
+
+ // try to match an explicit rule, then fallback to the standard ones
+ if (preg_match($intervalRegexp, $part, $matches)) {
+ if ($matches[2]) {
+ foreach (explode(',', $matches[3]) as $n) {
+ if ($number == $n) {
+ return strtr($matches['message'], $parameters);
+ }
+ }
+ } else {
+ $leftNumber = '-Inf' === $matches['left'] ? -\INF : (float) $matches['left'];
+ $rightNumber = is_numeric($matches['right']) ? (float) $matches['right'] : \INF;
+
+ if (('[' === $matches['left_delimiter'] ? $number >= $leftNumber : $number > $leftNumber)
+ && (']' === $matches['right_delimiter'] ? $number <= $rightNumber : $number < $rightNumber)
+ ) {
+ return strtr($matches['message'], $parameters);
+ }
+ }
+ } elseif (preg_match('/^\w+\:\s*(.*?)$/', $part, $matches)) {
+ $standardRules[] = $matches[1];
+ } else {
+ $standardRules[] = $part;
+ }
+ }
+
+ $position = $this->getPluralizationRule($number, $locale);
+
+ if (!isset($standardRules[$position])) {
+ // when there's exactly one rule given, and that rule is a standard
+ // rule, use this rule
+ if (1 === \count($parts) && isset($standardRules[0])) {
+ return strtr($standardRules[0], $parameters);
+ }
+
+ $message = sprintf('Unable to choose a translation for "%s" with locale "%s" for value "%d". Double check that this translation has the correct plural options (e.g. "There is one apple|There are %%count%% apples").', $id, $locale, $number);
+
+ if (class_exists(InvalidArgumentException::class)) {
+ throw new InvalidArgumentException($message);
+ }
+
+ throw new \InvalidArgumentException($message);
+ }
+
+ return strtr($standardRules[$position], $parameters);
+ }
+
+ /**
+ * Returns the plural position to use for the given locale and number.
+ *
+ * The plural rules are derived from code of the Zend Framework (2010-09-25),
+ * which is subject to the new BSD license (http://framework.zend.com/license/new-bsd).
+ * Copyright (c) 2005-2010 Zend Technologies USA Inc. (http://www.zend.com)
+ */
+ private function getPluralizationRule(float $number, string $locale): int
+ {
+ $number = abs($number);
+
+ return match ('pt_BR' !== $locale && 'en_US_POSIX' !== $locale && \strlen($locale) > 3 ? substr($locale, 0, strrpos($locale, '_')) : $locale) {
+ 'af',
+ 'bn',
+ 'bg',
+ 'ca',
+ 'da',
+ 'de',
+ 'el',
+ 'en',
+ 'en_US_POSIX',
+ 'eo',
+ 'es',
+ 'et',
+ 'eu',
+ 'fa',
+ 'fi',
+ 'fo',
+ 'fur',
+ 'fy',
+ 'gl',
+ 'gu',
+ 'ha',
+ 'he',
+ 'hu',
+ 'is',
+ 'it',
+ 'ku',
+ 'lb',
+ 'ml',
+ 'mn',
+ 'mr',
+ 'nah',
+ 'nb',
+ 'ne',
+ 'nl',
+ 'nn',
+ 'no',
+ 'oc',
+ 'om',
+ 'or',
+ 'pa',
+ 'pap',
+ 'ps',
+ 'pt',
+ 'so',
+ 'sq',
+ 'sv',
+ 'sw',
+ 'ta',
+ 'te',
+ 'tk',
+ 'ur',
+ 'zu' => (1 == $number) ? 0 : 1,
+ 'am',
+ 'bh',
+ 'fil',
+ 'fr',
+ 'gun',
+ 'hi',
+ 'hy',
+ 'ln',
+ 'mg',
+ 'nso',
+ 'pt_BR',
+ 'ti',
+ 'wa' => ($number < 2) ? 0 : 1,
+ 'be',
+ 'bs',
+ 'hr',
+ 'ru',
+ 'sh',
+ 'sr',
+ 'uk' => ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2),
+ 'cs',
+ 'sk' => (1 == $number) ? 0 : ((($number >= 2) && ($number <= 4)) ? 1 : 2),
+ 'ga' => (1 == $number) ? 0 : ((2 == $number) ? 1 : 2),
+ 'lt' => ((1 == $number % 10) && (11 != $number % 100)) ? 0 : ((($number % 10 >= 2) && (($number % 100 < 10) || ($number % 100 >= 20))) ? 1 : 2),
+ 'sl' => (1 == $number % 100) ? 0 : ((2 == $number % 100) ? 1 : (((3 == $number % 100) || (4 == $number % 100)) ? 2 : 3)),
+ 'mk' => (1 == $number % 10) ? 0 : 1,
+ 'mt' => (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 1) && ($number % 100 < 11))) ? 1 : ((($number % 100 > 10) && ($number % 100 < 20)) ? 2 : 3)),
+ 'lv' => (0 == $number) ? 0 : (((1 == $number % 10) && (11 != $number % 100)) ? 1 : 2),
+ 'pl' => (1 == $number) ? 0 : ((($number % 10 >= 2) && ($number % 10 <= 4) && (($number % 100 < 12) || ($number % 100 > 14))) ? 1 : 2),
+ 'cy' => (1 == $number) ? 0 : ((2 == $number) ? 1 : (((8 == $number) || (11 == $number)) ? 2 : 3)),
+ 'ro' => (1 == $number) ? 0 : (((0 == $number) || (($number % 100 > 0) && ($number % 100 < 20))) ? 1 : 2),
+ 'ar' => (0 == $number) ? 0 : ((1 == $number) ? 1 : ((2 == $number) ? 2 : ((($number % 100 >= 3) && ($number % 100 <= 10)) ? 3 : ((($number % 100 >= 11) && ($number % 100 <= 99)) ? 4 : 5)))),
+ default => 0,
+ };
+ }
+}
diff --git a/vendor/symfony/translation-contracts/composer.json b/vendor/symfony/translation-contracts/composer.json
new file mode 100644
index 000000000..213b5cda8
--- /dev/null
+++ b/vendor/symfony/translation-contracts/composer.json
@@ -0,0 +1,37 @@
+{
+ "name": "symfony/translation-contracts",
+ "type": "library",
+ "description": "Generic abstractions related to translation",
+ "keywords": ["abstractions", "contracts", "decoupling", "interfaces", "interoperability", "standards"],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=8.1"
+ },
+ "autoload": {
+ "psr-4": { "Symfony\\Contracts\\Translation\\": "" },
+ "exclude-from-classmap": [
+ "/Test/"
+ ]
+ },
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-main": "3.4-dev"
+ },
+ "thanks": {
+ "name": "symfony/contracts",
+ "url": "https://github.com/symfony/contracts"
+ }
+ }
+}
diff --git a/vendor/symfony/translation/CHANGELOG.md b/vendor/symfony/translation/CHANGELOG.md
new file mode 100644
index 000000000..07ba0d031
--- /dev/null
+++ b/vendor/symfony/translation/CHANGELOG.md
@@ -0,0 +1,196 @@
+CHANGELOG
+=========
+
+6.2.7
+-----
+
+ * [BC BREAK] The following data providers for `ProviderFactoryTestCase` are now static:
+ `supportsProvider()`, `createProvider()`, `unsupportedSchemeProvider()`and `incompleteDsnProvider()`
+ * [BC BREAK] `ProviderTestCase::toStringProvider()` is now static
+
+6.2
+---
+
+ * Deprecate `PhpStringTokenParser`
+ * Deprecate `PhpExtractor` in favor of `PhpAstExtractor`
+ * Add `PhpAstExtractor` (requires [nikic/php-parser](https://github.com/nikic/php-parser) to be installed)
+
+6.1
+---
+
+ * Parameters implementing `TranslatableInterface` are processed
+ * Add the file extension to the `XliffFileDumper` constructor
+
+5.4
+---
+
+ * Add `github` format & autodetection to render errors as annotations when
+ running the XLIFF linter command in a Github Actions environment.
+ * Translation providers are not experimental anymore
+
+5.3
+---
+
+ * Add `translation:pull` and `translation:push` commands to manage translations with third-party providers
+ * Add `TranslatorBagInterface::getCatalogues` method
+ * Add support to load XLIFF string in `XliffFileLoader`
+
+5.2.0
+-----
+
+ * added support for calling `trans` with ICU formatted messages
+ * added `PseudoLocalizationTranslator`
+ * added `TranslatableMessage` objects that represent a message that can be translated
+ * added the `t()` function to easily create `TranslatableMessage` objects
+ * Added support for extracting messages from `TranslatableMessage` objects
+
+5.1.0
+-----
+
+ * added support for `name` attribute on `unit` element from xliff2 to be used as a translation key instead of always the `source` element
+
+5.0.0
+-----
+
+ * removed support for using `null` as the locale in `Translator`
+ * removed `TranslatorInterface`
+ * removed `MessageSelector`
+ * removed `ChoiceMessageFormatterInterface`
+ * removed `PluralizationRule`
+ * removed `Interval`
+ * removed `transChoice()` methods, use the trans() method instead with a %count% parameter
+ * removed `FileDumper::setBackup()` and `TranslationWriter::disableBackup()`
+ * removed `MessageFormatter::choiceFormat()`
+ * added argument `$filename` to `PhpExtractor::parseTokens()`
+ * removed support for implicit STDIN usage in the `lint:xliff` command, use `lint:xliff -` (append a dash) instead to make it explicit.
+
+4.4.0
+-----
+
+ * deprecated support for using `null` as the locale in `Translator`
+ * deprecated accepting STDIN implicitly when using the `lint:xliff` command, use `lint:xliff -` (append a dash) instead to make it explicit.
+ * Marked the `TranslationDataCollector` class as `@final`.
+
+4.3.0
+-----
+
+ * Improved Xliff 1.2 loader to load the original file's metadata
+ * Added `TranslatorPathsPass`
+
+4.2.0
+-----
+
+ * Started using ICU parent locales as fallback locales.
+ * allow using the ICU message format using domains with the "+intl-icu" suffix
+ * deprecated `Translator::transChoice()` in favor of using `Translator::trans()` with a `%count%` parameter
+ * deprecated `TranslatorInterface` in favor of `Symfony\Contracts\Translation\TranslatorInterface`
+ * deprecated `MessageSelector`, `Interval` and `PluralizationRules`; use `IdentityTranslator` instead
+ * Added `IntlFormatter` and `IntlFormatterInterface`
+ * added support for multiple files and directories in `XliffLintCommand`
+ * Marked `Translator::getFallbackLocales()` and `TranslationDataCollector::getFallbackLocales()` as internal
+
+4.1.0
+-----
+
+ * The `FileDumper::setBackup()` method is deprecated.
+ * The `TranslationWriter::disableBackup()` method is deprecated.
+ * The `XliffFileDumper` will write "name" on the "unit" node when dumping XLIFF 2.0.
+
+4.0.0
+-----
+
+ * removed the backup feature of the `FileDumper` class
+ * removed `TranslationWriter::writeTranslations()` method
+ * removed support for passing `MessageSelector` instances to the constructor of the `Translator` class
+
+3.4.0
+-----
+
+ * Added `TranslationDumperPass`
+ * Added `TranslationExtractorPass`
+ * Added `TranslatorPass`
+ * Added `TranslationReader` and `TranslationReaderInterface`
+ * Added `` section to the Xliff 2.0 dumper.
+ * Improved Xliff 2.0 loader to load `` section.
+ * Added `TranslationWriterInterface`
+ * Deprecated `TranslationWriter::writeTranslations` in favor of `TranslationWriter::write`
+ * added support for adding custom message formatter and decoupling the default one.
+ * Added `PhpExtractor`
+ * Added `PhpStringTokenParser`
+
+3.2.0
+-----
+
+ * Added support for escaping `|` in plural translations with double pipe.
+
+3.1.0
+-----
+
+ * Deprecated the backup feature of the file dumper classes.
+
+3.0.0
+-----
+
+ * removed `FileDumper::format()` method.
+ * Changed the visibility of the locale property in `Translator` from protected to private.
+
+2.8.0
+-----
+
+ * deprecated FileDumper::format(), overwrite FileDumper::formatCatalogue() instead.
+ * deprecated Translator::getMessages(), rely on TranslatorBagInterface::getCatalogue() instead.
+ * added `FileDumper::formatCatalogue` which allows format the catalogue without dumping it into file.
+ * added option `json_encoding` to JsonFileDumper
+ * added options `as_tree`, `inline` to YamlFileDumper
+ * added support for XLIFF 2.0.
+ * added support for XLIFF target and tool attributes.
+ * added message parameters to DataCollectorTranslator.
+ * [DEPRECATION] The `DiffOperation` class has been deprecated and
+ will be removed in Symfony 3.0, since its operation has nothing to do with 'diff',
+ so the class name is misleading. The `TargetOperation` class should be used for
+ this use-case instead.
+
+2.7.0
+-----
+
+ * added DataCollectorTranslator for collecting the translated messages.
+
+2.6.0
+-----
+
+ * added possibility to cache catalogues
+ * added TranslatorBagInterface
+ * added LoggingTranslator
+ * added Translator::getMessages() for retrieving the message catalogue as an array
+
+2.5.0
+-----
+
+ * added relative file path template to the file dumpers
+ * added optional backup to the file dumpers
+ * changed IcuResFileDumper to extend FileDumper
+
+2.3.0
+-----
+
+ * added classes to make operations on catalogues (like making a diff or a merge on 2 catalogues)
+ * added Translator::getFallbackLocales()
+ * deprecated Translator::setFallbackLocale() in favor of the new Translator::setFallbackLocales() method
+
+2.2.0
+-----
+
+ * QtTranslationsLoader class renamed to QtFileLoader. QtTranslationsLoader is deprecated and will be removed in 2.3.
+ * [BC BREAK] uniformized the exception thrown by the load() method when an error occurs. The load() method now
+ throws Symfony\Component\Translation\Exception\NotFoundResourceException when a resource cannot be found
+ and Symfony\Component\Translation\Exception\InvalidResourceException when a resource is invalid.
+ * changed the exception class thrown by some load() methods from \RuntimeException to \InvalidArgumentException
+ (IcuDatFileLoader, IcuResFileLoader and QtFileLoader)
+
+2.1.0
+-----
+
+ * added support for more than one fallback locale
+ * added support for extracting translation messages from templates (Twig and PHP)
+ * added dumpers for translation catalogs
+ * added support for QT, gettext, and ResourceBundles
diff --git a/vendor/symfony/translation/Catalogue/AbstractOperation.php b/vendor/symfony/translation/Catalogue/AbstractOperation.php
new file mode 100644
index 000000000..65eea0bb9
--- /dev/null
+++ b/vendor/symfony/translation/Catalogue/AbstractOperation.php
@@ -0,0 +1,190 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Catalogue;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Exception\LogicException;
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\MessageCatalogueInterface;
+
+/**
+ * Base catalogues binary operation class.
+ *
+ * A catalogue binary operation performs operation on
+ * source (the left argument) and target (the right argument) catalogues.
+ *
+ * @author Jean-François Simon
+ */
+abstract class AbstractOperation implements OperationInterface
+{
+ public const OBSOLETE_BATCH = 'obsolete';
+ public const NEW_BATCH = 'new';
+ public const ALL_BATCH = 'all';
+
+ protected $source;
+ protected $target;
+ protected $result;
+
+ /**
+ * @var array|null The domains affected by this operation
+ */
+ private $domains;
+
+ /**
+ * This array stores 'all', 'new' and 'obsolete' messages for all valid domains.
+ *
+ * The data structure of this array is as follows:
+ *
+ * [
+ * 'domain 1' => [
+ * 'all' => [...],
+ * 'new' => [...],
+ * 'obsolete' => [...]
+ * ],
+ * 'domain 2' => [
+ * 'all' => [...],
+ * 'new' => [...],
+ * 'obsolete' => [...]
+ * ],
+ * ...
+ * ]
+ *
+ * @var array The array that stores 'all', 'new' and 'obsolete' messages
+ */
+ protected $messages;
+
+ /**
+ * @throws LogicException
+ */
+ public function __construct(MessageCatalogueInterface $source, MessageCatalogueInterface $target)
+ {
+ if ($source->getLocale() !== $target->getLocale()) {
+ throw new LogicException('Operated catalogues must belong to the same locale.');
+ }
+
+ $this->source = $source;
+ $this->target = $target;
+ $this->result = new MessageCatalogue($source->getLocale());
+ $this->messages = [];
+ }
+
+ public function getDomains(): array
+ {
+ if (null === $this->domains) {
+ $domains = [];
+ foreach ([$this->source, $this->target] as $catalogue) {
+ foreach ($catalogue->getDomains() as $domain) {
+ $domains[$domain] = $domain;
+
+ if ($catalogue->all($domainIcu = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX)) {
+ $domains[$domainIcu] = $domainIcu;
+ }
+ }
+ }
+
+ $this->domains = array_values($domains);
+ }
+
+ return $this->domains;
+ }
+
+ public function getMessages(string $domain): array
+ {
+ if (!\in_array($domain, $this->getDomains())) {
+ throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
+ }
+
+ if (!isset($this->messages[$domain][self::ALL_BATCH])) {
+ $this->processDomain($domain);
+ }
+
+ return $this->messages[$domain][self::ALL_BATCH];
+ }
+
+ public function getNewMessages(string $domain): array
+ {
+ if (!\in_array($domain, $this->getDomains())) {
+ throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
+ }
+
+ if (!isset($this->messages[$domain][self::NEW_BATCH])) {
+ $this->processDomain($domain);
+ }
+
+ return $this->messages[$domain][self::NEW_BATCH];
+ }
+
+ public function getObsoleteMessages(string $domain): array
+ {
+ if (!\in_array($domain, $this->getDomains())) {
+ throw new InvalidArgumentException(sprintf('Invalid domain: "%s".', $domain));
+ }
+
+ if (!isset($this->messages[$domain][self::OBSOLETE_BATCH])) {
+ $this->processDomain($domain);
+ }
+
+ return $this->messages[$domain][self::OBSOLETE_BATCH];
+ }
+
+ public function getResult(): MessageCatalogueInterface
+ {
+ foreach ($this->getDomains() as $domain) {
+ if (!isset($this->messages[$domain])) {
+ $this->processDomain($domain);
+ }
+ }
+
+ return $this->result;
+ }
+
+ /**
+ * @param self::*_BATCH $batch
+ */
+ public function moveMessagesToIntlDomainsIfPossible(string $batch = self::ALL_BATCH): void
+ {
+ // If MessageFormatter class does not exists, intl domains are not supported.
+ if (!class_exists(\MessageFormatter::class)) {
+ return;
+ }
+
+ foreach ($this->getDomains() as $domain) {
+ $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
+ $messages = match ($batch) {
+ self::OBSOLETE_BATCH => $this->getObsoleteMessages($domain),
+ self::NEW_BATCH => $this->getNewMessages($domain),
+ self::ALL_BATCH => $this->getMessages($domain),
+ default => throw new \InvalidArgumentException(sprintf('$batch argument must be one of ["%s", "%s", "%s"].', self::ALL_BATCH, self::NEW_BATCH, self::OBSOLETE_BATCH)),
+ };
+
+ if (!$messages || (!$this->source->all($intlDomain) && $this->source->all($domain))) {
+ continue;
+ }
+
+ $result = $this->getResult();
+ $allIntlMessages = $result->all($intlDomain);
+ $currentMessages = array_diff_key($messages, $result->all($domain));
+ $result->replace($currentMessages, $domain);
+ $result->replace($allIntlMessages + $messages, $intlDomain);
+ }
+ }
+
+ /**
+ * Performs operation on source and target catalogues for the given domain and
+ * stores the results.
+ *
+ * @param string $domain The domain which the operation will be performed for
+ *
+ * @return void
+ */
+ abstract protected function processDomain(string $domain);
+}
diff --git a/vendor/symfony/translation/Catalogue/MergeOperation.php b/vendor/symfony/translation/Catalogue/MergeOperation.php
new file mode 100644
index 000000000..1b777a843
--- /dev/null
+++ b/vendor/symfony/translation/Catalogue/MergeOperation.php
@@ -0,0 +1,72 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Catalogue;
+
+use Symfony\Component\Translation\MessageCatalogueInterface;
+
+/**
+ * Merge operation between two catalogues as follows:
+ * all = source ∪ target = {x: x ∈ source ∨ x ∈ target}
+ * new = all ∖ source = {x: x ∈ target ∧ x ∉ source}
+ * obsolete = source ∖ all = {x: x ∈ source ∧ x ∉ source ∧ x ∉ target} = ∅
+ * Basically, the result contains messages from both catalogues.
+ *
+ * @author Jean-François Simon
+ */
+class MergeOperation extends AbstractOperation
+{
+ /**
+ * @return void
+ */
+ protected function processDomain(string $domain)
+ {
+ $this->messages[$domain] = [
+ 'all' => [],
+ 'new' => [],
+ 'obsolete' => [],
+ ];
+ $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
+
+ foreach ($this->target->getCatalogueMetadata('', $domain) ?? [] as $key => $value) {
+ if (null === $this->result->getCatalogueMetadata($key, $domain)) {
+ $this->result->setCatalogueMetadata($key, $value, $domain);
+ }
+ }
+
+ foreach ($this->target->getCatalogueMetadata('', $intlDomain) ?? [] as $key => $value) {
+ if (null === $this->result->getCatalogueMetadata($key, $intlDomain)) {
+ $this->result->setCatalogueMetadata($key, $value, $intlDomain);
+ }
+ }
+
+ foreach ($this->source->all($domain) as $id => $message) {
+ $this->messages[$domain]['all'][$id] = $message;
+ $d = $this->source->defines($id, $intlDomain) ? $intlDomain : $domain;
+ $this->result->add([$id => $message], $d);
+ if (null !== $keyMetadata = $this->source->getMetadata($id, $d)) {
+ $this->result->setMetadata($id, $keyMetadata, $d);
+ }
+ }
+
+ foreach ($this->target->all($domain) as $id => $message) {
+ if (!$this->source->has($id, $domain)) {
+ $this->messages[$domain]['all'][$id] = $message;
+ $this->messages[$domain]['new'][$id] = $message;
+ $d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain;
+ $this->result->add([$id => $message], $d);
+ if (null !== $keyMetadata = $this->target->getMetadata($id, $d)) {
+ $this->result->setMetadata($id, $keyMetadata, $d);
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/translation/Catalogue/OperationInterface.php b/vendor/symfony/translation/Catalogue/OperationInterface.php
new file mode 100644
index 000000000..1fe9534e8
--- /dev/null
+++ b/vendor/symfony/translation/Catalogue/OperationInterface.php
@@ -0,0 +1,61 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Catalogue;
+
+use Symfony\Component\Translation\MessageCatalogueInterface;
+
+/**
+ * Represents an operation on catalogue(s).
+ *
+ * An instance of this interface performs an operation on one or more catalogues and
+ * stores intermediate and final results of the operation.
+ *
+ * The first catalogue in its argument(s) is called the 'source catalogue' or 'source' and
+ * the following results are stored:
+ *
+ * Messages: also called 'all', are valid messages for the given domain after the operation is performed.
+ *
+ * New Messages: also called 'new' (new = all ∖ source = {x: x ∈ all ∧ x ∉ source}).
+ *
+ * Obsolete Messages: also called 'obsolete' (obsolete = source ∖ all = {x: x ∈ source ∧ x ∉ all}).
+ *
+ * Result: also called 'result', is the resulting catalogue for the given domain that holds the same messages as 'all'.
+ *
+ * @author Jean-François Simon
+ */
+interface OperationInterface
+{
+ /**
+ * Returns domains affected by operation.
+ */
+ public function getDomains(): array;
+
+ /**
+ * Returns all valid messages ('all') after operation.
+ */
+ public function getMessages(string $domain): array;
+
+ /**
+ * Returns new messages ('new') after operation.
+ */
+ public function getNewMessages(string $domain): array;
+
+ /**
+ * Returns obsolete messages ('obsolete') after operation.
+ */
+ public function getObsoleteMessages(string $domain): array;
+
+ /**
+ * Returns resulting catalogue ('result').
+ */
+ public function getResult(): MessageCatalogueInterface;
+}
diff --git a/vendor/symfony/translation/Catalogue/TargetOperation.php b/vendor/symfony/translation/Catalogue/TargetOperation.php
new file mode 100644
index 000000000..2c0ec722e
--- /dev/null
+++ b/vendor/symfony/translation/Catalogue/TargetOperation.php
@@ -0,0 +1,86 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Catalogue;
+
+use Symfony\Component\Translation\MessageCatalogueInterface;
+
+/**
+ * Target operation between two catalogues:
+ * intersection = source ∩ target = {x: x ∈ source ∧ x ∈ target}
+ * all = intersection ∪ (target ∖ intersection) = target
+ * new = all ∖ source = {x: x ∈ target ∧ x ∉ source}
+ * obsolete = source ∖ all = source ∖ target = {x: x ∈ source ∧ x ∉ target}
+ * Basically, the result contains messages from the target catalogue.
+ *
+ * @author Michael Lee
+ */
+class TargetOperation extends AbstractOperation
+{
+ /**
+ * @return void
+ */
+ protected function processDomain(string $domain)
+ {
+ $this->messages[$domain] = [
+ 'all' => [],
+ 'new' => [],
+ 'obsolete' => [],
+ ];
+ $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
+
+ foreach ($this->target->getCatalogueMetadata('', $domain) ?? [] as $key => $value) {
+ if (null === $this->result->getCatalogueMetadata($key, $domain)) {
+ $this->result->setCatalogueMetadata($key, $value, $domain);
+ }
+ }
+
+ foreach ($this->target->getCatalogueMetadata('', $intlDomain) ?? [] as $key => $value) {
+ if (null === $this->result->getCatalogueMetadata($key, $intlDomain)) {
+ $this->result->setCatalogueMetadata($key, $value, $intlDomain);
+ }
+ }
+
+ // For 'all' messages, the code can't be simplified as ``$this->messages[$domain]['all'] = $target->all($domain);``,
+ // because doing so will drop messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback}
+ //
+ // For 'new' messages, the code can't be simplified as ``array_diff_assoc($this->target->all($domain), $this->source->all($domain));``
+ // because doing so will not exclude messages like {x: x ∈ target ∧ x ∉ source.all ∧ x ∈ source.fallback}
+ //
+ // For 'obsolete' messages, the code can't be simplified as ``array_diff_assoc($this->source->all($domain), $this->target->all($domain))``
+ // because doing so will not exclude messages like {x: x ∈ source ∧ x ∉ target.all ∧ x ∈ target.fallback}
+
+ foreach ($this->source->all($domain) as $id => $message) {
+ if ($this->target->has($id, $domain)) {
+ $this->messages[$domain]['all'][$id] = $message;
+ $d = $this->source->defines($id, $intlDomain) ? $intlDomain : $domain;
+ $this->result->add([$id => $message], $d);
+ if (null !== $keyMetadata = $this->source->getMetadata($id, $d)) {
+ $this->result->setMetadata($id, $keyMetadata, $d);
+ }
+ } else {
+ $this->messages[$domain]['obsolete'][$id] = $message;
+ }
+ }
+
+ foreach ($this->target->all($domain) as $id => $message) {
+ if (!$this->source->has($id, $domain)) {
+ $this->messages[$domain]['all'][$id] = $message;
+ $this->messages[$domain]['new'][$id] = $message;
+ $d = $this->target->defines($id, $intlDomain) ? $intlDomain : $domain;
+ $this->result->add([$id => $message], $d);
+ if (null !== $keyMetadata = $this->target->getMetadata($id, $d)) {
+ $this->result->setMetadata($id, $keyMetadata, $d);
+ }
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/translation/CatalogueMetadataAwareInterface.php b/vendor/symfony/translation/CatalogueMetadataAwareInterface.php
new file mode 100644
index 000000000..c845959f1
--- /dev/null
+++ b/vendor/symfony/translation/CatalogueMetadataAwareInterface.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+/**
+ * This interface is used to get, set, and delete metadata about the Catalogue.
+ *
+ * @author Hugo Alliaume
+ */
+interface CatalogueMetadataAwareInterface
+{
+ /**
+ * Gets catalogue metadata for the given domain and key.
+ *
+ * Passing an empty domain will return an array with all catalogue metadata indexed by
+ * domain and then by key. Passing an empty key will return an array with all
+ * catalogue metadata for the given domain.
+ *
+ * @return mixed The value that was set or an array with the domains/keys or null
+ */
+ public function getCatalogueMetadata(string $key = '', string $domain = 'messages'): mixed;
+
+ /**
+ * Adds catalogue metadata to a message domain.
+ *
+ * @return void
+ */
+ public function setCatalogueMetadata(string $key, mixed $value, string $domain = 'messages');
+
+ /**
+ * Deletes catalogue metadata for the given key and domain.
+ *
+ * Passing an empty domain will delete all catalogue metadata. Passing an empty key will
+ * delete all metadata for the given domain.
+ *
+ * @return void
+ */
+ public function deleteCatalogueMetadata(string $key = '', string $domain = 'messages');
+}
diff --git a/vendor/symfony/translation/Command/TranslationPullCommand.php b/vendor/symfony/translation/Command/TranslationPullCommand.php
new file mode 100644
index 000000000..646b92c83
--- /dev/null
+++ b/vendor/symfony/translation/Command/TranslationPullCommand.php
@@ -0,0 +1,180 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Command;
+
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\Translation\Catalogue\TargetOperation;
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\Provider\TranslationProviderCollection;
+use Symfony\Component\Translation\Reader\TranslationReaderInterface;
+use Symfony\Component\Translation\Writer\TranslationWriterInterface;
+
+/**
+ * @author Mathieu Santostefano
+ */
+#[AsCommand(name: 'translation:pull', description: 'Pull translations from a given provider.')]
+final class TranslationPullCommand extends Command
+{
+ use TranslationTrait;
+
+ private TranslationProviderCollection $providerCollection;
+ private TranslationWriterInterface $writer;
+ private TranslationReaderInterface $reader;
+ private string $defaultLocale;
+ private array $transPaths;
+ private array $enabledLocales;
+
+ public function __construct(TranslationProviderCollection $providerCollection, TranslationWriterInterface $writer, TranslationReaderInterface $reader, string $defaultLocale, array $transPaths = [], array $enabledLocales = [])
+ {
+ $this->providerCollection = $providerCollection;
+ $this->writer = $writer;
+ $this->reader = $reader;
+ $this->defaultLocale = $defaultLocale;
+ $this->transPaths = $transPaths;
+ $this->enabledLocales = $enabledLocales;
+
+ parent::__construct();
+ }
+
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ if ($input->mustSuggestArgumentValuesFor('provider')) {
+ $suggestions->suggestValues($this->providerCollection->keys());
+
+ return;
+ }
+
+ if ($input->mustSuggestOptionValuesFor('domains')) {
+ $provider = $this->providerCollection->get($input->getArgument('provider'));
+
+ if (method_exists($provider, 'getDomains')) {
+ $suggestions->suggestValues($provider->getDomains());
+ }
+
+ return;
+ }
+
+ if ($input->mustSuggestOptionValuesFor('locales')) {
+ $suggestions->suggestValues($this->enabledLocales);
+
+ return;
+ }
+
+ if ($input->mustSuggestOptionValuesFor('format')) {
+ $suggestions->suggestValues(['php', 'xlf', 'xlf12', 'xlf20', 'po', 'mo', 'yml', 'yaml', 'ts', 'csv', 'json', 'ini', 'res']);
+ }
+ }
+
+ protected function configure(): void
+ {
+ $keys = $this->providerCollection->keys();
+ $defaultProvider = 1 === \count($keys) ? $keys[0] : null;
+
+ $this
+ ->setDefinition([
+ new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to pull translations from.', $defaultProvider),
+ new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with provider ones (it will delete not synchronized messages).'),
+ new InputOption('intl-icu', null, InputOption::VALUE_NONE, 'Associated to --force option, it will write messages in "%domain%+intl-icu.%locale%.xlf" files.'),
+ new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to pull.'),
+ new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to pull.'),
+ new InputOption('format', null, InputOption::VALUE_OPTIONAL, 'Override the default output format.', 'xlf12'),
+ ])
+ ->setHelp(<<<'EOF'
+The %command.name%> command pulls translations from the given provider. Only
+new translations are pulled, existing ones are not overwritten.
+
+You can overwrite existing translations (and remove the missing ones on local side) by using the --force> flag:
+
+ php %command.full_name% --force provider>
+
+Full example:
+
+ php %command.full_name% provider --force --domains=messages --domains=validators --locales=en>
+
+This command pulls all translations associated with the messages> and validators> domains for the en> locale.
+Local translations for the specified domains and locale are deleted if they're not present on the provider and overwritten if it's the case.
+Local translations for others domains and locales are ignored.
+EOF
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $io = new SymfonyStyle($input, $output);
+
+ $provider = $this->providerCollection->get($input->getArgument('provider'));
+ $force = $input->getOption('force');
+ $intlIcu = $input->getOption('intl-icu');
+ $locales = $input->getOption('locales') ?: $this->enabledLocales;
+ $domains = $input->getOption('domains');
+ $format = $input->getOption('format');
+ $xliffVersion = '1.2';
+
+ if ($intlIcu && !$force) {
+ $io->note('--intl-icu option only has an effect when used with --force. Here, it will be ignored.');
+ }
+
+ switch ($format) {
+ case 'xlf20': $xliffVersion = '2.0';
+ // no break
+ case 'xlf12': $format = 'xlf';
+ }
+
+ $writeOptions = [
+ 'path' => end($this->transPaths),
+ 'xliff_version' => $xliffVersion,
+ 'default_locale' => $this->defaultLocale,
+ ];
+
+ if (!$domains) {
+ $domains = $provider->getDomains();
+ }
+
+ $providerTranslations = $provider->read($domains, $locales);
+
+ if ($force) {
+ foreach ($providerTranslations->getCatalogues() as $catalogue) {
+ $operation = new TargetOperation(new MessageCatalogue($catalogue->getLocale()), $catalogue);
+ if ($intlIcu) {
+ $operation->moveMessagesToIntlDomainsIfPossible();
+ }
+ $this->writer->write($operation->getResult(), $format, $writeOptions);
+ }
+
+ $io->success(sprintf('Local translations has been updated from "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
+
+ return 0;
+ }
+
+ $localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths);
+
+ // Append pulled translations to local ones.
+ $localTranslations->addBag($providerTranslations->diff($localTranslations));
+
+ foreach ($localTranslations->getCatalogues() as $catalogue) {
+ $this->writer->write($catalogue, $format, $writeOptions);
+ }
+
+ $io->success(sprintf('New translations from "%s" has been written locally (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
+
+ return 0;
+ }
+}
diff --git a/vendor/symfony/translation/Command/TranslationPushCommand.php b/vendor/symfony/translation/Command/TranslationPushCommand.php
new file mode 100644
index 000000000..1d04adbc9
--- /dev/null
+++ b/vendor/symfony/translation/Command/TranslationPushCommand.php
@@ -0,0 +1,182 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Command;
+
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Exception\InvalidArgumentException;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\Translation\Provider\FilteringProvider;
+use Symfony\Component\Translation\Provider\TranslationProviderCollection;
+use Symfony\Component\Translation\Reader\TranslationReaderInterface;
+use Symfony\Component\Translation\TranslatorBag;
+
+/**
+ * @author Mathieu Santostefano
+ */
+#[AsCommand(name: 'translation:push', description: 'Push translations to a given provider.')]
+final class TranslationPushCommand extends Command
+{
+ use TranslationTrait;
+
+ private TranslationProviderCollection $providers;
+ private TranslationReaderInterface $reader;
+ private array $transPaths;
+ private array $enabledLocales;
+
+ public function __construct(TranslationProviderCollection $providers, TranslationReaderInterface $reader, array $transPaths = [], array $enabledLocales = [])
+ {
+ $this->providers = $providers;
+ $this->reader = $reader;
+ $this->transPaths = $transPaths;
+ $this->enabledLocales = $enabledLocales;
+
+ parent::__construct();
+ }
+
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ if ($input->mustSuggestArgumentValuesFor('provider')) {
+ $suggestions->suggestValues($this->providers->keys());
+
+ return;
+ }
+
+ if ($input->mustSuggestOptionValuesFor('domains')) {
+ $provider = $this->providers->get($input->getArgument('provider'));
+
+ if ($provider && method_exists($provider, 'getDomains')) {
+ $domains = $provider->getDomains();
+ $suggestions->suggestValues($domains);
+ }
+
+ return;
+ }
+
+ if ($input->mustSuggestOptionValuesFor('locales')) {
+ $suggestions->suggestValues($this->enabledLocales);
+ }
+ }
+
+ protected function configure(): void
+ {
+ $keys = $this->providers->keys();
+ $defaultProvider = 1 === \count($keys) ? $keys[0] : null;
+
+ $this
+ ->setDefinition([
+ new InputArgument('provider', null !== $defaultProvider ? InputArgument::OPTIONAL : InputArgument::REQUIRED, 'The provider to push translations to.', $defaultProvider),
+ new InputOption('force', null, InputOption::VALUE_NONE, 'Override existing translations with local ones (it will delete not synchronized messages).'),
+ new InputOption('delete-missing', null, InputOption::VALUE_NONE, 'Delete translations available on provider but not locally.'),
+ new InputOption('domains', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the domains to push.'),
+ new InputOption('locales', null, InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, 'Specify the locales to push.', $this->enabledLocales),
+ ])
+ ->setHelp(<<<'EOF'
+The %command.name%> command pushes translations to the given provider. Only new
+translations are pushed, existing ones are not overwritten.
+
+You can overwrite existing translations by using the --force> flag:
+
+ php %command.full_name% --force provider>
+
+You can delete provider translations which are not present locally by using the --delete-missing> flag:
+
+ php %command.full_name% --delete-missing provider>
+
+Full example:
+
+ php %command.full_name% provider --force --delete-missing --domains=messages --domains=validators --locales=en>
+
+This command pushes all translations associated with the messages> and validators> domains for the en> locale.
+Provider translations for the specified domains and locale are deleted if they're not present locally and overwritten if it's the case.
+Provider translations for others domains and locales are ignored.
+EOF
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $provider = $this->providers->get($input->getArgument('provider'));
+
+ if (!$this->enabledLocales) {
+ throw new InvalidArgumentException(sprintf('You must define "framework.enabled_locales" or "framework.translator.providers.%s.locales" config key in order to work with translation providers.', parse_url($provider, \PHP_URL_SCHEME)));
+ }
+
+ $io = new SymfonyStyle($input, $output);
+ $domains = $input->getOption('domains');
+ $locales = $input->getOption('locales');
+ $force = $input->getOption('force');
+ $deleteMissing = $input->getOption('delete-missing');
+
+ if (!$domains && $provider instanceof FilteringProvider) {
+ $domains = $provider->getDomains();
+ }
+
+ // Reading local translations must be done after retrieving the domains from the provider
+ // in order to manage only translations from configured domains
+ $localTranslations = $this->readLocalTranslations($locales, $domains, $this->transPaths);
+
+ if (!$domains) {
+ $domains = $this->getDomainsFromTranslatorBag($localTranslations);
+ }
+
+ if (!$deleteMissing && $force) {
+ $provider->write($localTranslations);
+
+ $io->success(sprintf('All local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
+
+ return 0;
+ }
+
+ $providerTranslations = $provider->read($domains, $locales);
+
+ if ($deleteMissing) {
+ $provider->delete($providerTranslations->diff($localTranslations));
+
+ $io->success(sprintf('Missing translations on "%s" has been deleted (for "%s" locale(s), and "%s" domain(s)).', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
+
+ // Read provider translations again, after missing translations deletion,
+ // to avoid push freshly deleted translations.
+ $providerTranslations = $provider->read($domains, $locales);
+ }
+
+ $translationsToWrite = $localTranslations->diff($providerTranslations);
+
+ if ($force) {
+ $translationsToWrite->addBag($localTranslations->intersect($providerTranslations));
+ }
+
+ $provider->write($translationsToWrite);
+
+ $io->success(sprintf('%s local translations has been sent to "%s" (for "%s" locale(s), and "%s" domain(s)).', $force ? 'All' : 'New', parse_url($provider, \PHP_URL_SCHEME), implode(', ', $locales), implode(', ', $domains)));
+
+ return 0;
+ }
+
+ private function getDomainsFromTranslatorBag(TranslatorBag $translatorBag): array
+ {
+ $domains = [];
+
+ foreach ($translatorBag->getCatalogues() as $catalogue) {
+ $domains += $catalogue->getDomains();
+ }
+
+ return array_unique($domains);
+ }
+}
diff --git a/vendor/symfony/translation/Command/TranslationTrait.php b/vendor/symfony/translation/Command/TranslationTrait.php
new file mode 100644
index 000000000..eafaffd3f
--- /dev/null
+++ b/vendor/symfony/translation/Command/TranslationTrait.php
@@ -0,0 +1,77 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Command;
+
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\MessageCatalogueInterface;
+use Symfony\Component\Translation\TranslatorBag;
+
+/**
+ * @internal
+ */
+trait TranslationTrait
+{
+ private function readLocalTranslations(array $locales, array $domains, array $transPaths): TranslatorBag
+ {
+ $bag = new TranslatorBag();
+
+ foreach ($locales as $locale) {
+ $catalogue = new MessageCatalogue($locale);
+ foreach ($transPaths as $path) {
+ $this->reader->read($path, $catalogue);
+ }
+
+ if ($domains) {
+ foreach ($domains as $domain) {
+ $bag->addCatalogue($this->filterCatalogue($catalogue, $domain));
+ }
+ } else {
+ $bag->addCatalogue($catalogue);
+ }
+ }
+
+ return $bag;
+ }
+
+ private function filterCatalogue(MessageCatalogue $catalogue, string $domain): MessageCatalogue
+ {
+ $filteredCatalogue = new MessageCatalogue($catalogue->getLocale());
+
+ // extract intl-icu messages only
+ $intlDomain = $domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX;
+ if ($intlMessages = $catalogue->all($intlDomain)) {
+ $filteredCatalogue->add($intlMessages, $intlDomain);
+ }
+
+ // extract all messages and subtract intl-icu messages
+ if ($messages = array_diff($catalogue->all($domain), $intlMessages)) {
+ $filteredCatalogue->add($messages, $domain);
+ }
+ foreach ($catalogue->getResources() as $resource) {
+ $filteredCatalogue->addResource($resource);
+ }
+
+ if ($metadata = $catalogue->getMetadata('', $intlDomain)) {
+ foreach ($metadata as $k => $v) {
+ $filteredCatalogue->setMetadata($k, $v, $intlDomain);
+ }
+ }
+
+ if ($metadata = $catalogue->getMetadata('', $domain)) {
+ foreach ($metadata as $k => $v) {
+ $filteredCatalogue->setMetadata($k, $v, $domain);
+ }
+ }
+
+ return $filteredCatalogue;
+ }
+}
diff --git a/vendor/symfony/translation/Command/XliffLintCommand.php b/vendor/symfony/translation/Command/XliffLintCommand.php
new file mode 100644
index 000000000..ba68635db
--- /dev/null
+++ b/vendor/symfony/translation/Command/XliffLintCommand.php
@@ -0,0 +1,285 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Command;
+
+use Symfony\Component\Console\Attribute\AsCommand;
+use Symfony\Component\Console\CI\GithubActionReporter;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Completion\CompletionInput;
+use Symfony\Component\Console\Completion\CompletionSuggestions;
+use Symfony\Component\Console\Exception\RuntimeException;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\Console\Style\SymfonyStyle;
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Util\XliffUtils;
+
+/**
+ * Validates XLIFF files syntax and outputs encountered errors.
+ *
+ * @author Grégoire Pineau
+ * @author Robin Chalas
+ * @author Javier Eguiluz
+ */
+#[AsCommand(name: 'lint:xliff', description: 'Lint an XLIFF file and outputs encountered errors')]
+class XliffLintCommand extends Command
+{
+ private string $format;
+ private bool $displayCorrectFiles;
+ private ?\Closure $directoryIteratorProvider;
+ private ?\Closure $isReadableProvider;
+ private bool $requireStrictFileNames;
+
+ public function __construct(string $name = null, callable $directoryIteratorProvider = null, callable $isReadableProvider = null, bool $requireStrictFileNames = true)
+ {
+ parent::__construct($name);
+
+ $this->directoryIteratorProvider = null === $directoryIteratorProvider ? null : $directoryIteratorProvider(...);
+ $this->isReadableProvider = null === $isReadableProvider ? null : $isReadableProvider(...);
+ $this->requireStrictFileNames = $requireStrictFileNames;
+ }
+
+ /**
+ * @return void
+ */
+ protected function configure()
+ {
+ $this
+ ->addArgument('filename', InputArgument::IS_ARRAY, 'A file, a directory or "-" for reading from STDIN')
+ ->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format ("%s")', implode('", "', $this->getAvailableFormatOptions())))
+ ->setHelp(<<%command.name% command lints an XLIFF file and outputs to STDOUT
+the first encountered syntax error.
+
+You can validates XLIFF contents passed from STDIN:
+
+ cat filename | php %command.full_name% -
+
+You can also validate the syntax of a file:
+
+ php %command.full_name% filename
+
+Or of a whole directory:
+
+ php %command.full_name% dirname
+ php %command.full_name% dirname --format=json
+
+EOF
+ )
+ ;
+ }
+
+ protected function execute(InputInterface $input, OutputInterface $output): int
+ {
+ $io = new SymfonyStyle($input, $output);
+ $filenames = (array) $input->getArgument('filename');
+ $this->format = $input->getOption('format') ?? (GithubActionReporter::isGithubActionEnvironment() ? 'github' : 'txt');
+ $this->displayCorrectFiles = $output->isVerbose();
+
+ if (['-'] === $filenames) {
+ return $this->display($io, [$this->validate(file_get_contents('php://stdin'))]);
+ }
+
+ if (!$filenames) {
+ throw new RuntimeException('Please provide a filename or pipe file content to STDIN.');
+ }
+
+ $filesInfo = [];
+ foreach ($filenames as $filename) {
+ if (!$this->isReadable($filename)) {
+ throw new RuntimeException(sprintf('File or directory "%s" is not readable.', $filename));
+ }
+
+ foreach ($this->getFiles($filename) as $file) {
+ $filesInfo[] = $this->validate(file_get_contents($file), $file);
+ }
+ }
+
+ return $this->display($io, $filesInfo);
+ }
+
+ private function validate(string $content, string $file = null): array
+ {
+ $errors = [];
+
+ // Avoid: Warning DOMDocument::loadXML(): Empty string supplied as input
+ if ('' === trim($content)) {
+ return ['file' => $file, 'valid' => true];
+ }
+
+ $internal = libxml_use_internal_errors(true);
+
+ $document = new \DOMDocument();
+ $document->loadXML($content);
+
+ if (null !== $targetLanguage = $this->getTargetLanguageFromFile($document)) {
+ $normalizedLocalePattern = sprintf('(%s|%s)', preg_quote($targetLanguage, '/'), preg_quote(str_replace('-', '_', $targetLanguage), '/'));
+ // strict file names require translation files to be named '____.locale.xlf'
+ // otherwise, both '____.locale.xlf' and 'locale.____.xlf' are allowed
+ // also, the regexp matching must be case-insensitive, as defined for 'target-language' values
+ // http://docs.oasis-open.org/xliff/v1.2/os/xliff-core.html#target-language
+ $expectedFilenamePattern = $this->requireStrictFileNames ? sprintf('/^.*\.(?i:%s)\.(?:xlf|xliff)/', $normalizedLocalePattern) : sprintf('/^(?:.*\.(?i:%s)|(?i:%s)\..*)\.(?:xlf|xliff)/', $normalizedLocalePattern, $normalizedLocalePattern);
+
+ if (0 === preg_match($expectedFilenamePattern, basename($file))) {
+ $errors[] = [
+ 'line' => -1,
+ 'column' => -1,
+ 'message' => sprintf('There is a mismatch between the language included in the file name ("%s") and the "%s" value used in the "target-language" attribute of the file.', basename($file), $targetLanguage),
+ ];
+ }
+ }
+
+ foreach (XliffUtils::validateSchema($document) as $xmlError) {
+ $errors[] = [
+ 'line' => $xmlError['line'],
+ 'column' => $xmlError['column'],
+ 'message' => $xmlError['message'],
+ ];
+ }
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($internal);
+
+ return ['file' => $file, 'valid' => 0 === \count($errors), 'messages' => $errors];
+ }
+
+ private function display(SymfonyStyle $io, array $files): int
+ {
+ return match ($this->format) {
+ 'txt' => $this->displayTxt($io, $files),
+ 'json' => $this->displayJson($io, $files),
+ 'github' => $this->displayTxt($io, $files, true),
+ default => throw new InvalidArgumentException(sprintf('Supported formats are "%s".', implode('", "', $this->getAvailableFormatOptions()))),
+ };
+ }
+
+ private function displayTxt(SymfonyStyle $io, array $filesInfo, bool $errorAsGithubAnnotations = false): int
+ {
+ $countFiles = \count($filesInfo);
+ $erroredFiles = 0;
+ $githubReporter = $errorAsGithubAnnotations ? new GithubActionReporter($io) : null;
+
+ foreach ($filesInfo as $info) {
+ if ($info['valid'] && $this->displayCorrectFiles) {
+ $io->comment('OK'.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
+ } elseif (!$info['valid']) {
+ ++$erroredFiles;
+ $io->text(' ERROR '.($info['file'] ? sprintf(' in %s', $info['file']) : ''));
+ $io->listing(array_map(function ($error) use ($info, $githubReporter) {
+ // general document errors have a '-1' line number
+ $line = -1 === $error['line'] ? null : $error['line'];
+
+ $githubReporter?->error($error['message'], $info['file'], $line, null !== $line ? $error['column'] : null);
+
+ return null === $line ? $error['message'] : sprintf('Line %d, Column %d: %s', $line, $error['column'], $error['message']);
+ }, $info['messages']));
+ }
+ }
+
+ if (0 === $erroredFiles) {
+ $io->success(sprintf('All %d XLIFF files contain valid syntax.', $countFiles));
+ } else {
+ $io->warning(sprintf('%d XLIFF files have valid syntax and %d contain errors.', $countFiles - $erroredFiles, $erroredFiles));
+ }
+
+ return min($erroredFiles, 1);
+ }
+
+ private function displayJson(SymfonyStyle $io, array $filesInfo): int
+ {
+ $errors = 0;
+
+ array_walk($filesInfo, function (&$v) use (&$errors) {
+ $v['file'] = (string) $v['file'];
+ if (!$v['valid']) {
+ ++$errors;
+ }
+ });
+
+ $io->writeln(json_encode($filesInfo, \JSON_PRETTY_PRINT | \JSON_UNESCAPED_SLASHES));
+
+ return min($errors, 1);
+ }
+
+ /**
+ * @return iterable<\SplFileInfo>
+ */
+ private function getFiles(string $fileOrDirectory): iterable
+ {
+ if (is_file($fileOrDirectory)) {
+ yield new \SplFileInfo($fileOrDirectory);
+
+ return;
+ }
+
+ foreach ($this->getDirectoryIterator($fileOrDirectory) as $file) {
+ if (!\in_array($file->getExtension(), ['xlf', 'xliff'])) {
+ continue;
+ }
+
+ yield $file;
+ }
+ }
+
+ /**
+ * @return iterable<\SplFileInfo>
+ */
+ private function getDirectoryIterator(string $directory): iterable
+ {
+ $default = fn ($directory) => new \RecursiveIteratorIterator(
+ new \RecursiveDirectoryIterator($directory, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::FOLLOW_SYMLINKS),
+ \RecursiveIteratorIterator::LEAVES_ONLY
+ );
+
+ if (null !== $this->directoryIteratorProvider) {
+ return ($this->directoryIteratorProvider)($directory, $default);
+ }
+
+ return $default($directory);
+ }
+
+ private function isReadable(string $fileOrDirectory): bool
+ {
+ $default = fn ($fileOrDirectory) => is_readable($fileOrDirectory);
+
+ if (null !== $this->isReadableProvider) {
+ return ($this->isReadableProvider)($fileOrDirectory, $default);
+ }
+
+ return $default($fileOrDirectory);
+ }
+
+ private function getTargetLanguageFromFile(\DOMDocument $xliffContents): ?string
+ {
+ foreach ($xliffContents->getElementsByTagName('file')[0]->attributes ?? [] as $attribute) {
+ if ('target-language' === $attribute->nodeName) {
+ return $attribute->nodeValue;
+ }
+ }
+
+ return null;
+ }
+
+ public function complete(CompletionInput $input, CompletionSuggestions $suggestions): void
+ {
+ if ($input->mustSuggestOptionValuesFor('format')) {
+ $suggestions->suggestValues($this->getAvailableFormatOptions());
+ }
+ }
+
+ private function getAvailableFormatOptions(): array
+ {
+ return ['txt', 'json', 'github'];
+ }
+}
diff --git a/vendor/symfony/translation/DataCollector/TranslationDataCollector.php b/vendor/symfony/translation/DataCollector/TranslationDataCollector.php
new file mode 100644
index 000000000..cdbba8cf3
--- /dev/null
+++ b/vendor/symfony/translation/DataCollector/TranslationDataCollector.php
@@ -0,0 +1,148 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\DataCollector;
+
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\DataCollector\DataCollector;
+use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
+use Symfony\Component\Translation\DataCollectorTranslator;
+use Symfony\Component\VarDumper\Cloner\Data;
+
+/**
+ * @author Abdellatif Ait boudad
+ *
+ * @final
+ */
+class TranslationDataCollector extends DataCollector implements LateDataCollectorInterface
+{
+ private DataCollectorTranslator $translator;
+
+ public function __construct(DataCollectorTranslator $translator)
+ {
+ $this->translator = $translator;
+ }
+
+ public function lateCollect(): void
+ {
+ $messages = $this->sanitizeCollectedMessages($this->translator->getCollectedMessages());
+
+ $this->data += $this->computeCount($messages);
+ $this->data['messages'] = $messages;
+
+ $this->data = $this->cloneVar($this->data);
+ }
+
+ public function collect(Request $request, Response $response, \Throwable $exception = null): void
+ {
+ $this->data['locale'] = $this->translator->getLocale();
+ $this->data['fallback_locales'] = $this->translator->getFallbackLocales();
+ }
+
+ public function reset(): void
+ {
+ $this->data = [];
+ }
+
+ public function getMessages(): array|Data
+ {
+ return $this->data['messages'] ?? [];
+ }
+
+ public function getCountMissings(): int
+ {
+ return $this->data[DataCollectorTranslator::MESSAGE_MISSING] ?? 0;
+ }
+
+ public function getCountFallbacks(): int
+ {
+ return $this->data[DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK] ?? 0;
+ }
+
+ public function getCountDefines(): int
+ {
+ return $this->data[DataCollectorTranslator::MESSAGE_DEFINED] ?? 0;
+ }
+
+ public function getLocale()
+ {
+ return !empty($this->data['locale']) ? $this->data['locale'] : null;
+ }
+
+ /**
+ * @internal
+ */
+ public function getFallbackLocales()
+ {
+ return (isset($this->data['fallback_locales']) && \count($this->data['fallback_locales']) > 0) ? $this->data['fallback_locales'] : [];
+ }
+
+ public function getName(): string
+ {
+ return 'translation';
+ }
+
+ private function sanitizeCollectedMessages(array $messages): array
+ {
+ $result = [];
+ foreach ($messages as $key => $message) {
+ $messageId = $message['locale'].$message['domain'].$message['id'];
+
+ if (!isset($result[$messageId])) {
+ $message['count'] = 1;
+ $message['parameters'] = !empty($message['parameters']) ? [$message['parameters']] : [];
+ $messages[$key]['translation'] = $this->sanitizeString($message['translation']);
+ $result[$messageId] = $message;
+ } else {
+ if (!empty($message['parameters'])) {
+ $result[$messageId]['parameters'][] = $message['parameters'];
+ }
+
+ ++$result[$messageId]['count'];
+ }
+
+ unset($messages[$key]);
+ }
+
+ return $result;
+ }
+
+ private function computeCount(array $messages): array
+ {
+ $count = [
+ DataCollectorTranslator::MESSAGE_DEFINED => 0,
+ DataCollectorTranslator::MESSAGE_MISSING => 0,
+ DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK => 0,
+ ];
+
+ foreach ($messages as $message) {
+ ++$count[$message['state']];
+ }
+
+ return $count;
+ }
+
+ private function sanitizeString(string $string, int $length = 80): string
+ {
+ $string = trim(preg_replace('/\s+/', ' ', $string));
+
+ if (false !== $encoding = mb_detect_encoding($string, null, true)) {
+ if (mb_strlen($string, $encoding) > $length) {
+ return mb_substr($string, 0, $length - 3, $encoding).'...';
+ }
+ } elseif (\strlen($string) > $length) {
+ return substr($string, 0, $length - 3).'...';
+ }
+
+ return $string;
+ }
+}
diff --git a/vendor/symfony/translation/DataCollectorTranslator.php b/vendor/symfony/translation/DataCollectorTranslator.php
new file mode 100644
index 000000000..0e584daad
--- /dev/null
+++ b/vendor/symfony/translation/DataCollectorTranslator.php
@@ -0,0 +1,146 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Contracts\Translation\LocaleAwareInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+/**
+ * @author Abdellatif Ait boudad
+ */
+class DataCollectorTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface, WarmableInterface
+{
+ public const MESSAGE_DEFINED = 0;
+ public const MESSAGE_MISSING = 1;
+ public const MESSAGE_EQUALS_FALLBACK = 2;
+
+ private TranslatorInterface $translator;
+ private array $messages = [];
+
+ /**
+ * @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator
+ */
+ public function __construct(TranslatorInterface $translator)
+ {
+ if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) {
+ throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', get_debug_type($translator)));
+ }
+
+ $this->translator = $translator;
+ }
+
+ public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string
+ {
+ $trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale);
+ $this->collectMessage($locale, $domain, $id, $trans, $parameters);
+
+ return $trans;
+ }
+
+ /**
+ * @return void
+ */
+ public function setLocale(string $locale)
+ {
+ $this->translator->setLocale($locale);
+ }
+
+ public function getLocale(): string
+ {
+ return $this->translator->getLocale();
+ }
+
+ public function getCatalogue(string $locale = null): MessageCatalogueInterface
+ {
+ return $this->translator->getCatalogue($locale);
+ }
+
+ public function getCatalogues(): array
+ {
+ return $this->translator->getCatalogues();
+ }
+
+ /**
+ * @return string[]
+ */
+ public function warmUp(string $cacheDir): array
+ {
+ if ($this->translator instanceof WarmableInterface) {
+ return (array) $this->translator->warmUp($cacheDir);
+ }
+
+ return [];
+ }
+
+ /**
+ * Gets the fallback locales.
+ */
+ public function getFallbackLocales(): array
+ {
+ if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) {
+ return $this->translator->getFallbackLocales();
+ }
+
+ return [];
+ }
+
+ /**
+ * Passes through all unknown calls onto the translator object.
+ */
+ public function __call(string $method, array $args)
+ {
+ return $this->translator->{$method}(...$args);
+ }
+
+ public function getCollectedMessages(): array
+ {
+ return $this->messages;
+ }
+
+ private function collectMessage(?string $locale, ?string $domain, string $id, string $translation, ?array $parameters = []): void
+ {
+ $domain ??= 'messages';
+
+ $catalogue = $this->translator->getCatalogue($locale);
+ $locale = $catalogue->getLocale();
+ $fallbackLocale = null;
+ if ($catalogue->defines($id, $domain)) {
+ $state = self::MESSAGE_DEFINED;
+ } elseif ($catalogue->has($id, $domain)) {
+ $state = self::MESSAGE_EQUALS_FALLBACK;
+
+ $fallbackCatalogue = $catalogue->getFallbackCatalogue();
+ while ($fallbackCatalogue) {
+ if ($fallbackCatalogue->defines($id, $domain)) {
+ $fallbackLocale = $fallbackCatalogue->getLocale();
+ break;
+ }
+ $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue();
+ }
+ } else {
+ $state = self::MESSAGE_MISSING;
+ }
+
+ $this->messages[] = [
+ 'locale' => $locale,
+ 'fallbackLocale' => $fallbackLocale,
+ 'domain' => $domain,
+ 'id' => $id,
+ 'translation' => $translation,
+ 'parameters' => $parameters,
+ 'state' => $state,
+ 'transChoiceNumber' => isset($parameters['%count%']) && is_numeric($parameters['%count%']) ? $parameters['%count%'] : null,
+ ];
+ }
+}
diff --git a/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php b/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php
new file mode 100644
index 000000000..2ece6ac7b
--- /dev/null
+++ b/vendor/symfony/translation/DependencyInjection/TranslationDumperPass.php
@@ -0,0 +1,38 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Adds tagged translation.formatter services to translation writer.
+ */
+class TranslationDumperPass implements CompilerPassInterface
+{
+ /**
+ * @return void
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition('translation.writer')) {
+ return;
+ }
+
+ $definition = $container->getDefinition('translation.writer');
+
+ foreach ($container->findTaggedServiceIds('translation.dumper', true) as $id => $attributes) {
+ $definition->addMethodCall('addDumper', [$attributes[0]['alias'], new Reference($id)]);
+ }
+ }
+}
diff --git a/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php b/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php
new file mode 100644
index 000000000..1baf9341e
--- /dev/null
+++ b/vendor/symfony/translation/DependencyInjection/TranslationExtractorPass.php
@@ -0,0 +1,43 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Exception\RuntimeException;
+use Symfony\Component\DependencyInjection\Reference;
+
+/**
+ * Adds tagged translation.extractor services to translation extractor.
+ */
+class TranslationExtractorPass implements CompilerPassInterface
+{
+ /**
+ * @return void
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition('translation.extractor')) {
+ return;
+ }
+
+ $definition = $container->getDefinition('translation.extractor');
+
+ foreach ($container->findTaggedServiceIds('translation.extractor', true) as $id => $attributes) {
+ if (!isset($attributes[0]['alias'])) {
+ throw new RuntimeException(sprintf('The alias for the tag "translation.extractor" of service "%s" must be set.', $id));
+ }
+
+ $definition->addMethodCall('addExtractor', [$attributes[0]['alias'], new Reference($id)]);
+ }
+ }
+}
diff --git a/vendor/symfony/translation/DependencyInjection/TranslatorPass.php b/vendor/symfony/translation/DependencyInjection/TranslatorPass.php
new file mode 100644
index 000000000..dd6ea3c83
--- /dev/null
+++ b/vendor/symfony/translation/DependencyInjection/TranslatorPass.php
@@ -0,0 +1,94 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+use Symfony\Component\DependencyInjection\Compiler\ServiceLocatorTagPass;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Reference;
+
+class TranslatorPass implements CompilerPassInterface
+{
+ /**
+ * @return void
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition('translator.default')) {
+ return;
+ }
+
+ $loaders = [];
+ $loaderRefs = [];
+ foreach ($container->findTaggedServiceIds('translation.loader', true) as $id => $attributes) {
+ $loaderRefs[$id] = new Reference($id);
+ $loaders[$id][] = $attributes[0]['alias'];
+ if (isset($attributes[0]['legacy-alias'])) {
+ $loaders[$id][] = $attributes[0]['legacy-alias'];
+ }
+ }
+
+ if ($container->hasDefinition('translation.reader')) {
+ $definition = $container->getDefinition('translation.reader');
+ foreach ($loaders as $id => $formats) {
+ foreach ($formats as $format) {
+ $definition->addMethodCall('addLoader', [$format, $loaderRefs[$id]]);
+ }
+ }
+ }
+
+ $container
+ ->findDefinition('translator.default')
+ ->replaceArgument(0, ServiceLocatorTagPass::register($container, $loaderRefs))
+ ->replaceArgument(3, $loaders)
+ ;
+
+ if ($container->hasDefinition('validator') && $container->hasDefinition('translation.extractor.visitor.constraint')) {
+ $constraintVisitorDefinition = $container->getDefinition('translation.extractor.visitor.constraint');
+ $constraintClassNames = [];
+
+ foreach ($container->getDefinitions() as $definition) {
+ if (!$definition->hasTag('validator.constraint_validator')) {
+ continue;
+ }
+ // Resolve constraint validator FQCN even if defined as %foo.validator.class% parameter
+ $className = $container->getParameterBag()->resolveValue($definition->getClass());
+ // Extraction of the constraint class name from the Constraint Validator FQCN
+ $constraintClassNames[] = str_replace('Validator', '', substr(strrchr($className, '\\'), 1));
+ }
+
+ $constraintVisitorDefinition->setArgument(0, $constraintClassNames);
+ }
+
+ if (!$container->hasParameter('twig.default_path')) {
+ return;
+ }
+
+ $paths = array_keys($container->getDefinition('twig.template_iterator')->getArgument(1));
+ if ($container->hasDefinition('console.command.translation_debug')) {
+ $definition = $container->getDefinition('console.command.translation_debug');
+ $definition->replaceArgument(4, $container->getParameter('twig.default_path'));
+
+ if (\count($definition->getArguments()) > 6) {
+ $definition->replaceArgument(6, $paths);
+ }
+ }
+ if ($container->hasDefinition('console.command.translation_extract')) {
+ $definition = $container->getDefinition('console.command.translation_extract');
+ $definition->replaceArgument(5, $container->getParameter('twig.default_path'));
+
+ if (\count($definition->getArguments()) > 7) {
+ $definition->replaceArgument(7, $paths);
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php b/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php
new file mode 100644
index 000000000..f7f954eea
--- /dev/null
+++ b/vendor/symfony/translation/DependencyInjection/TranslatorPathsPass.php
@@ -0,0 +1,143 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\DependencyInjection;
+
+use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ServiceLocator;
+use Symfony\Component\HttpKernel\Controller\ArgumentResolver\TraceableValueResolver;
+
+/**
+ * @author Yonel Ceruto
+ */
+class TranslatorPathsPass extends AbstractRecursivePass
+{
+ private int $level = 0;
+
+ /**
+ * @var array
+ */
+ private array $paths = [];
+
+ /**
+ * @var array
+ */
+ private array $definitions = [];
+
+ /**
+ * @var array>
+ */
+ private array $controllers = [];
+
+ /**
+ * @return void
+ */
+ public function process(ContainerBuilder $container)
+ {
+ if (!$container->hasDefinition('translator')) {
+ return;
+ }
+
+ foreach ($this->findControllerArguments($container) as $controller => $argument) {
+ $id = substr($controller, 0, strpos($controller, ':') ?: \strlen($controller));
+ if ($container->hasDefinition($id)) {
+ [$locatorRef] = $argument->getValues();
+ $this->controllers[(string) $locatorRef][$container->getDefinition($id)->getClass()] = true;
+ }
+ }
+
+ try {
+ parent::process($container);
+
+ $paths = [];
+ foreach ($this->paths as $class => $_) {
+ if (($r = $container->getReflectionClass($class)) && !$r->isInterface()) {
+ $paths[] = $r->getFileName();
+ foreach ($r->getTraits() as $trait) {
+ $paths[] = $trait->getFileName();
+ }
+ }
+ }
+ if ($paths) {
+ if ($container->hasDefinition('console.command.translation_debug')) {
+ $definition = $container->getDefinition('console.command.translation_debug');
+ $definition->replaceArgument(6, array_merge($definition->getArgument(6), $paths));
+ }
+ if ($container->hasDefinition('console.command.translation_extract')) {
+ $definition = $container->getDefinition('console.command.translation_extract');
+ $definition->replaceArgument(7, array_merge($definition->getArgument(7), $paths));
+ }
+ }
+ } finally {
+ $this->level = 0;
+ $this->paths = [];
+ $this->definitions = [];
+ }
+ }
+
+ protected function processValue(mixed $value, bool $isRoot = false): mixed
+ {
+ if ($value instanceof Reference) {
+ if ('translator' === (string) $value) {
+ for ($i = $this->level - 1; $i >= 0; --$i) {
+ $class = $this->definitions[$i]->getClass();
+
+ if (ServiceLocator::class === $class) {
+ if (!isset($this->controllers[$this->currentId])) {
+ continue;
+ }
+ foreach ($this->controllers[$this->currentId] as $class => $_) {
+ $this->paths[$class] = true;
+ }
+ } else {
+ $this->paths[$class] = true;
+ }
+
+ break;
+ }
+ }
+
+ return $value;
+ }
+
+ if ($value instanceof Definition) {
+ $this->definitions[$this->level++] = $value;
+ $value = parent::processValue($value, $isRoot);
+ unset($this->definitions[--$this->level]);
+
+ return $value;
+ }
+
+ return parent::processValue($value, $isRoot);
+ }
+
+ private function findControllerArguments(ContainerBuilder $container): array
+ {
+ if (!$container->has('argument_resolver.service')) {
+ return [];
+ }
+ $resolverDef = $container->findDefinition('argument_resolver.service');
+
+ if (TraceableValueResolver::class === $resolverDef->getClass()) {
+ $resolverDef = $container->getDefinition($resolverDef->getArgument(0));
+ }
+
+ $argument = $resolverDef->getArgument(0);
+ if ($argument instanceof Reference) {
+ $argument = $container->getDefinition($argument);
+ }
+
+ return $argument->getArgument(0);
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/CsvFileDumper.php b/vendor/symfony/translation/Dumper/CsvFileDumper.php
new file mode 100644
index 000000000..8f5475259
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/CsvFileDumper.php
@@ -0,0 +1,56 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * CsvFileDumper generates a csv formatted string representation of a message catalogue.
+ *
+ * @author Stealth35
+ */
+class CsvFileDumper extends FileDumper
+{
+ private string $delimiter = ';';
+ private string $enclosure = '"';
+
+ public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
+ {
+ $handle = fopen('php://memory', 'r+');
+
+ foreach ($messages->all($domain) as $source => $target) {
+ fputcsv($handle, [$source, $target], $this->delimiter, $this->enclosure);
+ }
+
+ rewind($handle);
+ $output = stream_get_contents($handle);
+ fclose($handle);
+
+ return $output;
+ }
+
+ /**
+ * Sets the delimiter and escape character for CSV.
+ *
+ * @return void
+ */
+ public function setCsvControl(string $delimiter = ';', string $enclosure = '"')
+ {
+ $this->delimiter = $delimiter;
+ $this->enclosure = $enclosure;
+ }
+
+ protected function getExtension(): string
+ {
+ return 'csv';
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/DumperInterface.php b/vendor/symfony/translation/Dumper/DumperInterface.php
new file mode 100644
index 000000000..6bf42931e
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/DumperInterface.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * DumperInterface is the interface implemented by all translation dumpers.
+ * There is no common option.
+ *
+ * @author Michel Salib
+ */
+interface DumperInterface
+{
+ /**
+ * Dumps the message catalogue.
+ *
+ * @param array $options Options that are used by the dumper
+ *
+ * @return void
+ */
+ public function dump(MessageCatalogue $messages, array $options = []);
+}
diff --git a/vendor/symfony/translation/Dumper/FileDumper.php b/vendor/symfony/translation/Dumper/FileDumper.php
new file mode 100644
index 000000000..ed2c278aa
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/FileDumper.php
@@ -0,0 +1,110 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Exception\RuntimeException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * FileDumper is an implementation of DumperInterface that dump a message catalogue to file(s).
+ *
+ * Options:
+ * - path (mandatory): the directory where the files should be saved
+ *
+ * @author Michel Salib
+ */
+abstract class FileDumper implements DumperInterface
+{
+ /**
+ * A template for the relative paths to files.
+ *
+ * @var string
+ */
+ protected $relativePathTemplate = '%domain%.%locale%.%extension%';
+
+ /**
+ * Sets the template for the relative paths to files.
+ *
+ * @param string $relativePathTemplate A template for the relative paths to files
+ *
+ * @return void
+ */
+ public function setRelativePathTemplate(string $relativePathTemplate)
+ {
+ $this->relativePathTemplate = $relativePathTemplate;
+ }
+
+ /**
+ * @return void
+ */
+ public function dump(MessageCatalogue $messages, array $options = [])
+ {
+ if (!\array_key_exists('path', $options)) {
+ throw new InvalidArgumentException('The file dumper needs a path option.');
+ }
+
+ // save a file for each domain
+ foreach ($messages->getDomains() as $domain) {
+ $fullpath = $options['path'].'/'.$this->getRelativePath($domain, $messages->getLocale());
+ if (!file_exists($fullpath)) {
+ $directory = \dirname($fullpath);
+ if (!file_exists($directory) && !@mkdir($directory, 0777, true)) {
+ throw new RuntimeException(sprintf('Unable to create directory "%s".', $directory));
+ }
+ }
+
+ $intlDomain = $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX;
+ $intlMessages = $messages->all($intlDomain);
+
+ if ($intlMessages) {
+ $intlPath = $options['path'].'/'.$this->getRelativePath($intlDomain, $messages->getLocale());
+ file_put_contents($intlPath, $this->formatCatalogue($messages, $intlDomain, $options));
+
+ $messages->replace([], $intlDomain);
+
+ try {
+ if ($messages->all($domain)) {
+ file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options));
+ }
+ continue;
+ } finally {
+ $messages->replace($intlMessages, $intlDomain);
+ }
+ }
+
+ file_put_contents($fullpath, $this->formatCatalogue($messages, $domain, $options));
+ }
+ }
+
+ /**
+ * Transforms a domain of a message catalogue to its string representation.
+ */
+ abstract public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string;
+
+ /**
+ * Gets the file extension of the dumper.
+ */
+ abstract protected function getExtension(): string;
+
+ /**
+ * Gets the relative file path using the template.
+ */
+ private function getRelativePath(string $domain, string $locale): string
+ {
+ return strtr($this->relativePathTemplate, [
+ '%domain%' => $domain,
+ '%locale%' => $locale,
+ '%extension%' => $this->getExtension(),
+ ]);
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/IcuResFileDumper.php b/vendor/symfony/translation/Dumper/IcuResFileDumper.php
new file mode 100644
index 000000000..72c1ec089
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/IcuResFileDumper.php
@@ -0,0 +1,95 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * IcuResDumper generates an ICU ResourceBundle formatted string representation of a message catalogue.
+ *
+ * @author Stealth35
+ */
+class IcuResFileDumper extends FileDumper
+{
+ protected $relativePathTemplate = '%domain%/%locale%.%extension%';
+
+ public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
+ {
+ $data = $indexes = $resources = '';
+
+ foreach ($messages->all($domain) as $source => $target) {
+ $indexes .= pack('v', \strlen($data) + 28);
+ $data .= $source."\0";
+ }
+
+ $data .= $this->writePadding($data);
+
+ $keyTop = $this->getPosition($data);
+
+ foreach ($messages->all($domain) as $source => $target) {
+ $resources .= pack('V', $this->getPosition($data));
+
+ $data .= pack('V', \strlen($target))
+ .mb_convert_encoding($target."\0", 'UTF-16LE', 'UTF-8')
+ .$this->writePadding($data)
+ ;
+ }
+
+ $resOffset = $this->getPosition($data);
+
+ $data .= pack('v', \count($messages->all($domain)))
+ .$indexes
+ .$this->writePadding($data)
+ .$resources
+ ;
+
+ $bundleTop = $this->getPosition($data);
+
+ $root = pack('V7',
+ $resOffset + (2 << 28), // Resource Offset + Resource Type
+ 6, // Index length
+ $keyTop, // Index keys top
+ $bundleTop, // Index resources top
+ $bundleTop, // Index bundle top
+ \count($messages->all($domain)), // Index max table length
+ 0 // Index attributes
+ );
+
+ $header = pack('vC2v4C12@32',
+ 32, // Header size
+ 0xDA, 0x27, // Magic number 1 and 2
+ 20, 0, 0, 2, // Rest of the header, ..., Size of a char
+ 0x52, 0x65, 0x73, 0x42, // Data format identifier
+ 1, 2, 0, 0, // Data version
+ 1, 4, 0, 0 // Unicode version
+ );
+
+ return $header.$root.$data;
+ }
+
+ private function writePadding(string $data): ?string
+ {
+ $padding = \strlen($data) % 4;
+
+ return $padding ? str_repeat("\xAA", 4 - $padding) : null;
+ }
+
+ private function getPosition(string $data): float|int
+ {
+ return (\strlen($data) + 28) / 4;
+ }
+
+ protected function getExtension(): string
+ {
+ return 'res';
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/IniFileDumper.php b/vendor/symfony/translation/Dumper/IniFileDumper.php
new file mode 100644
index 000000000..6cbdef606
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/IniFileDumper.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * IniFileDumper generates an ini formatted string representation of a message catalogue.
+ *
+ * @author Stealth35
+ */
+class IniFileDumper extends FileDumper
+{
+ public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
+ {
+ $output = '';
+
+ foreach ($messages->all($domain) as $source => $target) {
+ $escapeTarget = str_replace('"', '\"', $target);
+ $output .= $source.'="'.$escapeTarget."\"\n";
+ }
+
+ return $output;
+ }
+
+ protected function getExtension(): string
+ {
+ return 'ini';
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/JsonFileDumper.php b/vendor/symfony/translation/Dumper/JsonFileDumper.php
new file mode 100644
index 000000000..e5035397f
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/JsonFileDumper.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * JsonFileDumper generates an json formatted string representation of a message catalogue.
+ *
+ * @author singles
+ */
+class JsonFileDumper extends FileDumper
+{
+ public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
+ {
+ $flags = $options['json_encoding'] ?? \JSON_PRETTY_PRINT;
+
+ return json_encode($messages->all($domain), $flags);
+ }
+
+ protected function getExtension(): string
+ {
+ return 'json';
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/MoFileDumper.php b/vendor/symfony/translation/Dumper/MoFileDumper.php
new file mode 100644
index 000000000..9ded5f4ef
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/MoFileDumper.php
@@ -0,0 +1,76 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\Loader\MoFileLoader;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * MoFileDumper generates a gettext formatted string representation of a message catalogue.
+ *
+ * @author Stealth35
+ */
+class MoFileDumper extends FileDumper
+{
+ public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
+ {
+ $sources = $targets = $sourceOffsets = $targetOffsets = '';
+ $offsets = [];
+ $size = 0;
+
+ foreach ($messages->all($domain) as $source => $target) {
+ $offsets[] = array_map('strlen', [$sources, $source, $targets, $target]);
+ $sources .= "\0".$source;
+ $targets .= "\0".$target;
+ ++$size;
+ }
+
+ $header = [
+ 'magicNumber' => MoFileLoader::MO_LITTLE_ENDIAN_MAGIC,
+ 'formatRevision' => 0,
+ 'count' => $size,
+ 'offsetId' => MoFileLoader::MO_HEADER_SIZE,
+ 'offsetTranslated' => MoFileLoader::MO_HEADER_SIZE + (8 * $size),
+ 'sizeHashes' => 0,
+ 'offsetHashes' => MoFileLoader::MO_HEADER_SIZE + (16 * $size),
+ ];
+
+ $sourcesSize = \strlen($sources);
+ $sourcesStart = $header['offsetHashes'] + 1;
+
+ foreach ($offsets as $offset) {
+ $sourceOffsets .= $this->writeLong($offset[1])
+ .$this->writeLong($offset[0] + $sourcesStart);
+ $targetOffsets .= $this->writeLong($offset[3])
+ .$this->writeLong($offset[2] + $sourcesStart + $sourcesSize);
+ }
+
+ $output = implode('', array_map($this->writeLong(...), $header))
+ .$sourceOffsets
+ .$targetOffsets
+ .$sources
+ .$targets
+ ;
+
+ return $output;
+ }
+
+ protected function getExtension(): string
+ {
+ return 'mo';
+ }
+
+ private function writeLong(mixed $str): string
+ {
+ return pack('V*', $str);
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/PhpFileDumper.php b/vendor/symfony/translation/Dumper/PhpFileDumper.php
new file mode 100644
index 000000000..51e90665d
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/PhpFileDumper.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * PhpFileDumper generates PHP files from a message catalogue.
+ *
+ * @author Michel Salib
+ */
+class PhpFileDumper extends FileDumper
+{
+ public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
+ {
+ return "all($domain), true).";\n";
+ }
+
+ protected function getExtension(): string
+ {
+ return 'php';
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/PoFileDumper.php b/vendor/symfony/translation/Dumper/PoFileDumper.php
new file mode 100644
index 000000000..a2d0deb78
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/PoFileDumper.php
@@ -0,0 +1,131 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * PoFileDumper generates a gettext formatted string representation of a message catalogue.
+ *
+ * @author Stealth35
+ */
+class PoFileDumper extends FileDumper
+{
+ public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
+ {
+ $output = 'msgid ""'."\n";
+ $output .= 'msgstr ""'."\n";
+ $output .= '"Content-Type: text/plain; charset=UTF-8\n"'."\n";
+ $output .= '"Content-Transfer-Encoding: 8bit\n"'."\n";
+ $output .= '"Language: '.$messages->getLocale().'\n"'."\n";
+ $output .= "\n";
+
+ $newLine = false;
+ foreach ($messages->all($domain) as $source => $target) {
+ if ($newLine) {
+ $output .= "\n";
+ } else {
+ $newLine = true;
+ }
+ $metadata = $messages->getMetadata($source, $domain);
+
+ if (isset($metadata['comments'])) {
+ $output .= $this->formatComments($metadata['comments']);
+ }
+ if (isset($metadata['flags'])) {
+ $output .= $this->formatComments(implode(',', (array) $metadata['flags']), ',');
+ }
+ if (isset($metadata['sources'])) {
+ $output .= $this->formatComments(implode(' ', (array) $metadata['sources']), ':');
+ }
+
+ $sourceRules = $this->getStandardRules($source);
+ $targetRules = $this->getStandardRules($target);
+ if (2 == \count($sourceRules) && [] !== $targetRules) {
+ $output .= sprintf('msgid "%s"'."\n", $this->escape($sourceRules[0]));
+ $output .= sprintf('msgid_plural "%s"'."\n", $this->escape($sourceRules[1]));
+ foreach ($targetRules as $i => $targetRule) {
+ $output .= sprintf('msgstr[%d] "%s"'."\n", $i, $this->escape($targetRule));
+ }
+ } else {
+ $output .= sprintf('msgid "%s"'."\n", $this->escape($source));
+ $output .= sprintf('msgstr "%s"'."\n", $this->escape($target));
+ }
+ }
+
+ return $output;
+ }
+
+ private function getStandardRules(string $id): array
+ {
+ // Partly copied from TranslatorTrait::trans.
+ $parts = [];
+ if (preg_match('/^\|++$/', $id)) {
+ $parts = explode('|', $id);
+ } elseif (preg_match_all('/(?:\|\||[^\|])++/', $id, $matches)) {
+ $parts = $matches[0];
+ }
+
+ $intervalRegexp = <<<'EOF'
+/^(?P
+ ({\s*
+ (\-?\d+(\.\d+)?[\s*,\s*\-?\d+(\.\d+)?]*)
+ \s*})
+
+ |
+
+ (?P[\[\]])
+ \s*
+ (?P-Inf|\-?\d+(\.\d+)?)
+ \s*,\s*
+ (?P\+?Inf|\-?\d+(\.\d+)?)
+ \s*
+ (?P[\[\]])
+)\s*(?P.*?)$/xs
+EOF;
+
+ $standardRules = [];
+ foreach ($parts as $part) {
+ $part = trim(str_replace('||', '|', $part));
+
+ if (preg_match($intervalRegexp, $part)) {
+ // Explicit rule is not a standard rule.
+ return [];
+ } else {
+ $standardRules[] = $part;
+ }
+ }
+
+ return $standardRules;
+ }
+
+ protected function getExtension(): string
+ {
+ return 'po';
+ }
+
+ private function escape(string $str): string
+ {
+ return addcslashes($str, "\0..\37\42\134");
+ }
+
+ private function formatComments(string|array $comments, string $prefix = ''): ?string
+ {
+ $output = null;
+
+ foreach ((array) $comments as $comment) {
+ $output .= sprintf('#%s %s'."\n", $prefix, $comment);
+ }
+
+ return $output;
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/QtFileDumper.php b/vendor/symfony/translation/Dumper/QtFileDumper.php
new file mode 100644
index 000000000..0373e9c10
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/QtFileDumper.php
@@ -0,0 +1,55 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * QtFileDumper generates ts files from a message catalogue.
+ *
+ * @author Benjamin Eberlei
+ */
+class QtFileDumper extends FileDumper
+{
+ public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
+ {
+ $dom = new \DOMDocument('1.0', 'utf-8');
+ $dom->formatOutput = true;
+ $ts = $dom->appendChild($dom->createElement('TS'));
+ $context = $ts->appendChild($dom->createElement('context'));
+ $context->appendChild($dom->createElement('name', $domain));
+
+ foreach ($messages->all($domain) as $source => $target) {
+ $message = $context->appendChild($dom->createElement('message'));
+ $metadata = $messages->getMetadata($source, $domain);
+ if (isset($metadata['sources'])) {
+ foreach ((array) $metadata['sources'] as $location) {
+ $loc = explode(':', $location, 2);
+ $location = $message->appendChild($dom->createElement('location'));
+ $location->setAttribute('filename', $loc[0]);
+ if (isset($loc[1])) {
+ $location->setAttribute('line', $loc[1]);
+ }
+ }
+ }
+ $message->appendChild($dom->createElement('source', $source));
+ $message->appendChild($dom->createElement('translation', $target));
+ }
+
+ return $dom->saveXML();
+ }
+
+ protected function getExtension(): string
+ {
+ return 'ts';
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/XliffFileDumper.php b/vendor/symfony/translation/Dumper/XliffFileDumper.php
new file mode 100644
index 000000000..22f0227b9
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/XliffFileDumper.php
@@ -0,0 +1,221 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * XliffFileDumper generates xliff files from a message catalogue.
+ *
+ * @author Michel Salib
+ */
+class XliffFileDumper extends FileDumper
+{
+ public function __construct(
+ private string $extension = 'xlf',
+ ) {
+ }
+
+ public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
+ {
+ $xliffVersion = '1.2';
+ if (\array_key_exists('xliff_version', $options)) {
+ $xliffVersion = $options['xliff_version'];
+ }
+
+ if (\array_key_exists('default_locale', $options)) {
+ $defaultLocale = $options['default_locale'];
+ } else {
+ $defaultLocale = \Locale::getDefault();
+ }
+
+ if ('1.2' === $xliffVersion) {
+ return $this->dumpXliff1($defaultLocale, $messages, $domain, $options);
+ }
+ if ('2.0' === $xliffVersion) {
+ return $this->dumpXliff2($defaultLocale, $messages, $domain);
+ }
+
+ throw new InvalidArgumentException(sprintf('No support implemented for dumping XLIFF version "%s".', $xliffVersion));
+ }
+
+ protected function getExtension(): string
+ {
+ return $this->extension;
+ }
+
+ private function dumpXliff1(string $defaultLocale, MessageCatalogue $messages, ?string $domain, array $options = []): string
+ {
+ $toolInfo = ['tool-id' => 'symfony', 'tool-name' => 'Symfony'];
+ if (\array_key_exists('tool_info', $options)) {
+ $toolInfo = array_merge($toolInfo, $options['tool_info']);
+ }
+
+ $dom = new \DOMDocument('1.0', 'utf-8');
+ $dom->formatOutput = true;
+
+ $xliff = $dom->appendChild($dom->createElement('xliff'));
+ $xliff->setAttribute('version', '1.2');
+ $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:1.2');
+
+ $xliffFile = $xliff->appendChild($dom->createElement('file'));
+ $xliffFile->setAttribute('source-language', str_replace('_', '-', $defaultLocale));
+ $xliffFile->setAttribute('target-language', str_replace('_', '-', $messages->getLocale()));
+ $xliffFile->setAttribute('datatype', 'plaintext');
+ $xliffFile->setAttribute('original', 'file.ext');
+
+ $xliffHead = $xliffFile->appendChild($dom->createElement('header'));
+ $xliffTool = $xliffHead->appendChild($dom->createElement('tool'));
+ foreach ($toolInfo as $id => $value) {
+ $xliffTool->setAttribute($id, $value);
+ }
+
+ if ($catalogueMetadata = $messages->getCatalogueMetadata('', $domain) ?? []) {
+ $xliffPropGroup = $xliffHead->appendChild($dom->createElement('prop-group'));
+ foreach ($catalogueMetadata as $key => $value) {
+ $xliffProp = $xliffPropGroup->appendChild($dom->createElement('prop'));
+ $xliffProp->setAttribute('prop-type', $key);
+ $xliffProp->appendChild($dom->createTextNode($value));
+ }
+ }
+
+ $xliffBody = $xliffFile->appendChild($dom->createElement('body'));
+ foreach ($messages->all($domain) as $source => $target) {
+ $translation = $dom->createElement('trans-unit');
+
+ $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._'));
+ $translation->setAttribute('resname', $source);
+
+ $s = $translation->appendChild($dom->createElement('source'));
+ $s->appendChild($dom->createTextNode($source));
+
+ // Does the target contain characters requiring a CDATA section?
+ $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target);
+
+ $targetElement = $dom->createElement('target');
+ $metadata = $messages->getMetadata($source, $domain);
+ if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) {
+ foreach ($metadata['target-attributes'] as $name => $value) {
+ $targetElement->setAttribute($name, $value);
+ }
+ }
+ $t = $translation->appendChild($targetElement);
+ $t->appendChild($text);
+
+ if ($this->hasMetadataArrayInfo('notes', $metadata)) {
+ foreach ($metadata['notes'] as $note) {
+ if (!isset($note['content'])) {
+ continue;
+ }
+
+ $n = $translation->appendChild($dom->createElement('note'));
+ $n->appendChild($dom->createTextNode($note['content']));
+
+ if (isset($note['priority'])) {
+ $n->setAttribute('priority', $note['priority']);
+ }
+
+ if (isset($note['from'])) {
+ $n->setAttribute('from', $note['from']);
+ }
+ }
+ }
+
+ $xliffBody->appendChild($translation);
+ }
+
+ return $dom->saveXML();
+ }
+
+ private function dumpXliff2(string $defaultLocale, MessageCatalogue $messages, ?string $domain): string
+ {
+ $dom = new \DOMDocument('1.0', 'utf-8');
+ $dom->formatOutput = true;
+
+ $xliff = $dom->appendChild($dom->createElement('xliff'));
+ $xliff->setAttribute('xmlns', 'urn:oasis:names:tc:xliff:document:2.0');
+ $xliff->setAttribute('version', '2.0');
+ $xliff->setAttribute('srcLang', str_replace('_', '-', $defaultLocale));
+ $xliff->setAttribute('trgLang', str_replace('_', '-', $messages->getLocale()));
+
+ $xliffFile = $xliff->appendChild($dom->createElement('file'));
+ if (str_ends_with($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX)) {
+ $xliffFile->setAttribute('id', substr($domain, 0, -\strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX)).'.'.$messages->getLocale());
+ } else {
+ $xliffFile->setAttribute('id', $domain.'.'.$messages->getLocale());
+ }
+
+ if ($catalogueMetadata = $messages->getCatalogueMetadata('', $domain) ?? []) {
+ $xliff->setAttribute('xmlns:m', 'urn:oasis:names:tc:xliff:metadata:2.0');
+ $xliffMetadata = $xliffFile->appendChild($dom->createElement('m:metadata'));
+ foreach ($catalogueMetadata as $key => $value) {
+ $xliffMeta = $xliffMetadata->appendChild($dom->createElement('prop'));
+ $xliffMeta->setAttribute('type', $key);
+ $xliffMeta->appendChild($dom->createTextNode($value));
+ }
+ }
+
+ foreach ($messages->all($domain) as $source => $target) {
+ $translation = $dom->createElement('unit');
+ $translation->setAttribute('id', strtr(substr(base64_encode(hash('sha256', $source, true)), 0, 7), '/+', '._'));
+
+ if (\strlen($source) <= 80) {
+ $translation->setAttribute('name', $source);
+ }
+
+ $metadata = $messages->getMetadata($source, $domain);
+
+ // Add notes section
+ if ($this->hasMetadataArrayInfo('notes', $metadata)) {
+ $notesElement = $dom->createElement('notes');
+ foreach ($metadata['notes'] as $note) {
+ $n = $dom->createElement('note');
+ $n->appendChild($dom->createTextNode($note['content'] ?? ''));
+ unset($note['content']);
+
+ foreach ($note as $name => $value) {
+ $n->setAttribute($name, $value);
+ }
+ $notesElement->appendChild($n);
+ }
+ $translation->appendChild($notesElement);
+ }
+
+ $segment = $translation->appendChild($dom->createElement('segment'));
+
+ $s = $segment->appendChild($dom->createElement('source'));
+ $s->appendChild($dom->createTextNode($source));
+
+ // Does the target contain characters requiring a CDATA section?
+ $text = 1 === preg_match('/[&<>]/', $target) ? $dom->createCDATASection($target) : $dom->createTextNode($target);
+
+ $targetElement = $dom->createElement('target');
+ if ($this->hasMetadataArrayInfo('target-attributes', $metadata)) {
+ foreach ($metadata['target-attributes'] as $name => $value) {
+ $targetElement->setAttribute($name, $value);
+ }
+ }
+ $t = $segment->appendChild($targetElement);
+ $t->appendChild($text);
+
+ $xliffFile->appendChild($translation);
+ }
+
+ return $dom->saveXML();
+ }
+
+ private function hasMetadataArrayInfo(string $key, array $metadata = null): bool
+ {
+ return is_iterable($metadata[$key] ?? null);
+ }
+}
diff --git a/vendor/symfony/translation/Dumper/YamlFileDumper.php b/vendor/symfony/translation/Dumper/YamlFileDumper.php
new file mode 100644
index 000000000..d2670331e
--- /dev/null
+++ b/vendor/symfony/translation/Dumper/YamlFileDumper.php
@@ -0,0 +1,56 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Dumper;
+
+use Symfony\Component\Translation\Exception\LogicException;
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\Util\ArrayConverter;
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * YamlFileDumper generates yaml files from a message catalogue.
+ *
+ * @author Michel Salib
+ */
+class YamlFileDumper extends FileDumper
+{
+ private string $extension;
+
+ public function __construct(string $extension = 'yml')
+ {
+ $this->extension = $extension;
+ }
+
+ public function formatCatalogue(MessageCatalogue $messages, string $domain, array $options = []): string
+ {
+ if (!class_exists(Yaml::class)) {
+ throw new LogicException('Dumping translations in the YAML format requires the Symfony Yaml component.');
+ }
+
+ $data = $messages->all($domain);
+
+ if (isset($options['as_tree']) && $options['as_tree']) {
+ $data = ArrayConverter::expandToTree($data);
+ }
+
+ if (isset($options['inline']) && ($inline = (int) $options['inline']) > 0) {
+ return Yaml::dump($data, $inline);
+ }
+
+ return Yaml::dump($data);
+ }
+
+ protected function getExtension(): string
+ {
+ return $this->extension;
+ }
+}
diff --git a/vendor/symfony/yaml/Exception/ExceptionInterface.php b/vendor/symfony/translation/Exception/ExceptionInterface.php
old mode 100755
new mode 100644
similarity index 78%
rename from vendor/symfony/yaml/Exception/ExceptionInterface.php
rename to vendor/symfony/translation/Exception/ExceptionInterface.php
index ad850eea1..8f9c54ef7
--- a/vendor/symfony/yaml/Exception/ExceptionInterface.php
+++ b/vendor/symfony/translation/Exception/ExceptionInterface.php
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Yaml\Exception;
+namespace Symfony\Component\Translation\Exception;
/**
* Exception interface for all exceptions thrown by the component.
*
* @author Fabien Potencier
*/
-interface ExceptionInterface
+interface ExceptionInterface extends \Throwable
{
}
diff --git a/vendor/symfony/translation/Exception/IncompleteDsnException.php b/vendor/symfony/translation/Exception/IncompleteDsnException.php
new file mode 100644
index 000000000..cb0ce027e
--- /dev/null
+++ b/vendor/symfony/translation/Exception/IncompleteDsnException.php
@@ -0,0 +1,24 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+class IncompleteDsnException extends InvalidArgumentException
+{
+ public function __construct(string $message, string $dsn = null, \Throwable $previous = null)
+ {
+ if ($dsn) {
+ $message = sprintf('Invalid "%s" provider DSN: ', $dsn).$message;
+ }
+
+ parent::__construct($message, 0, $previous);
+ }
+}
diff --git a/vendor/symfony/translation/Exception/InvalidArgumentException.php b/vendor/symfony/translation/Exception/InvalidArgumentException.php
new file mode 100644
index 000000000..90d06690f
--- /dev/null
+++ b/vendor/symfony/translation/Exception/InvalidArgumentException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * Base InvalidArgumentException for the Translation component.
+ *
+ * @author Abdellatif Ait boudad
+ */
+class InvalidArgumentException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/translation/Exception/InvalidResourceException.php b/vendor/symfony/translation/Exception/InvalidResourceException.php
new file mode 100644
index 000000000..cf079432c
--- /dev/null
+++ b/vendor/symfony/translation/Exception/InvalidResourceException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * Thrown when a resource cannot be loaded.
+ *
+ * @author Fabien Potencier
+ */
+class InvalidResourceException extends \InvalidArgumentException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/translation/Exception/LogicException.php b/vendor/symfony/translation/Exception/LogicException.php
new file mode 100644
index 000000000..9019c7e7b
--- /dev/null
+++ b/vendor/symfony/translation/Exception/LogicException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * Base LogicException for Translation component.
+ *
+ * @author Abdellatif Ait boudad
+ */
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/translation/Exception/MissingRequiredOptionException.php b/vendor/symfony/translation/Exception/MissingRequiredOptionException.php
new file mode 100644
index 000000000..2b5f80806
--- /dev/null
+++ b/vendor/symfony/translation/Exception/MissingRequiredOptionException.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * @author Oskar Stark
+ */
+class MissingRequiredOptionException extends IncompleteDsnException
+{
+ public function __construct(string $option, string $dsn = null, \Throwable $previous = null)
+ {
+ $message = sprintf('The option "%s" is required but missing.', $option);
+
+ parent::__construct($message, $dsn, $previous);
+ }
+}
diff --git a/vendor/symfony/yaml/Exception/DumpException.php b/vendor/symfony/translation/Exception/NotFoundResourceException.php
old mode 100755
new mode 100644
similarity index 61%
rename from vendor/symfony/yaml/Exception/DumpException.php
rename to vendor/symfony/translation/Exception/NotFoundResourceException.php
index cce972f24..cff73ae30
--- a/vendor/symfony/yaml/Exception/DumpException.php
+++ b/vendor/symfony/translation/Exception/NotFoundResourceException.php
@@ -9,13 +9,13 @@
* file that was distributed with this source code.
*/
-namespace Symfony\Component\Yaml\Exception;
+namespace Symfony\Component\Translation\Exception;
/**
- * Exception class thrown when an error occurs during dumping.
+ * Thrown when a resource does not exist.
*
* @author Fabien Potencier
*/
-class DumpException extends RuntimeException
+class NotFoundResourceException extends \InvalidArgumentException implements ExceptionInterface
{
}
diff --git a/vendor/symfony/translation/Exception/ProviderException.php b/vendor/symfony/translation/Exception/ProviderException.php
new file mode 100644
index 000000000..65883f852
--- /dev/null
+++ b/vendor/symfony/translation/Exception/ProviderException.php
@@ -0,0 +1,41 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+use Symfony\Contracts\HttpClient\ResponseInterface;
+
+/**
+ * @author Fabien Potencier
+ */
+class ProviderException extends RuntimeException implements ProviderExceptionInterface
+{
+ private ResponseInterface $response;
+ private string $debug;
+
+ public function __construct(string $message, ResponseInterface $response, int $code = 0, \Exception $previous = null)
+ {
+ $this->response = $response;
+ $this->debug = $response->getInfo('debug') ?? '';
+
+ parent::__construct($message, $code, $previous);
+ }
+
+ public function getResponse(): ResponseInterface
+ {
+ return $this->response;
+ }
+
+ public function getDebug(): string
+ {
+ return $this->debug;
+ }
+}
diff --git a/vendor/symfony/translation/Exception/ProviderExceptionInterface.php b/vendor/symfony/translation/Exception/ProviderExceptionInterface.php
new file mode 100644
index 000000000..922e82726
--- /dev/null
+++ b/vendor/symfony/translation/Exception/ProviderExceptionInterface.php
@@ -0,0 +1,23 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * @author Fabien Potencier
+ */
+interface ProviderExceptionInterface extends ExceptionInterface
+{
+ /*
+ * Returns debug info coming from the Symfony\Contracts\HttpClient\ResponseInterface
+ */
+ public function getDebug(): string;
+}
diff --git a/vendor/symfony/translation/Exception/RuntimeException.php b/vendor/symfony/translation/Exception/RuntimeException.php
new file mode 100644
index 000000000..dcd794082
--- /dev/null
+++ b/vendor/symfony/translation/Exception/RuntimeException.php
@@ -0,0 +1,21 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+/**
+ * Base RuntimeException for the Translation component.
+ *
+ * @author Abdellatif Ait boudad
+ */
+class RuntimeException extends \RuntimeException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/translation/Exception/UnsupportedSchemeException.php b/vendor/symfony/translation/Exception/UnsupportedSchemeException.php
new file mode 100644
index 000000000..f09488e14
--- /dev/null
+++ b/vendor/symfony/translation/Exception/UnsupportedSchemeException.php
@@ -0,0 +1,54 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Exception;
+
+use Symfony\Component\Translation\Bridge;
+use Symfony\Component\Translation\Provider\Dsn;
+
+class UnsupportedSchemeException extends LogicException
+{
+ private const SCHEME_TO_PACKAGE_MAP = [
+ 'crowdin' => [
+ 'class' => Bridge\Crowdin\CrowdinProviderFactory::class,
+ 'package' => 'symfony/crowdin-translation-provider',
+ ],
+ 'loco' => [
+ 'class' => Bridge\Loco\LocoProviderFactory::class,
+ 'package' => 'symfony/loco-translation-provider',
+ ],
+ 'lokalise' => [
+ 'class' => Bridge\Lokalise\LokaliseProviderFactory::class,
+ 'package' => 'symfony/lokalise-translation-provider',
+ ],
+ ];
+
+ public function __construct(Dsn $dsn, string $name = null, array $supported = [])
+ {
+ $provider = $dsn->getScheme();
+ if (false !== $pos = strpos($provider, '+')) {
+ $provider = substr($provider, 0, $pos);
+ }
+ $package = self::SCHEME_TO_PACKAGE_MAP[$provider] ?? null;
+ if ($package && !class_exists($package['class'])) {
+ parent::__construct(sprintf('Unable to synchronize translations via "%s" as the provider is not installed. Try running "composer require %s".', $provider, $package['package']));
+
+ return;
+ }
+
+ $message = sprintf('The "%s" scheme is not supported', $dsn->getScheme());
+ if ($name && $supported) {
+ $message .= sprintf('; supported schemes for translation provider "%s" are: "%s"', $name, implode('", "', $supported));
+ }
+
+ parent::__construct($message.'.');
+ }
+}
diff --git a/vendor/symfony/translation/Extractor/AbstractFileExtractor.php b/vendor/symfony/translation/Extractor/AbstractFileExtractor.php
new file mode 100644
index 000000000..4c088b94f
--- /dev/null
+++ b/vendor/symfony/translation/Extractor/AbstractFileExtractor.php
@@ -0,0 +1,67 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+
+/**
+ * Base class used by classes that extract translation messages from files.
+ *
+ * @author Marcos D. Sánchez
+ */
+abstract class AbstractFileExtractor
+{
+ protected function extractFiles(string|iterable $resource): iterable
+ {
+ if (is_iterable($resource)) {
+ $files = [];
+ foreach ($resource as $file) {
+ if ($this->canBeExtracted($file)) {
+ $files[] = $this->toSplFileInfo($file);
+ }
+ }
+ } elseif (is_file($resource)) {
+ $files = $this->canBeExtracted($resource) ? [$this->toSplFileInfo($resource)] : [];
+ } else {
+ $files = $this->extractFromDirectory($resource);
+ }
+
+ return $files;
+ }
+
+ private function toSplFileInfo(string $file): \SplFileInfo
+ {
+ return new \SplFileInfo($file);
+ }
+
+ /**
+ * @throws InvalidArgumentException
+ */
+ protected function isFile(string $file): bool
+ {
+ if (!is_file($file)) {
+ throw new InvalidArgumentException(sprintf('The "%s" file does not exist.', $file));
+ }
+
+ return true;
+ }
+
+ /**
+ * @return bool
+ */
+ abstract protected function canBeExtracted(string $file);
+
+ /**
+ * @return iterable
+ */
+ abstract protected function extractFromDirectory(string|array $resource);
+}
diff --git a/vendor/symfony/translation/Extractor/ChainExtractor.php b/vendor/symfony/translation/Extractor/ChainExtractor.php
new file mode 100644
index 000000000..d36f7f385
--- /dev/null
+++ b/vendor/symfony/translation/Extractor/ChainExtractor.php
@@ -0,0 +1,59 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * ChainExtractor extracts translation messages from template files.
+ *
+ * @author Michel Salib
+ */
+class ChainExtractor implements ExtractorInterface
+{
+ /**
+ * The extractors.
+ *
+ * @var ExtractorInterface[]
+ */
+ private array $extractors = [];
+
+ /**
+ * Adds a loader to the translation extractor.
+ *
+ * @return void
+ */
+ public function addExtractor(string $format, ExtractorInterface $extractor)
+ {
+ $this->extractors[$format] = $extractor;
+ }
+
+ /**
+ * @return void
+ */
+ public function setPrefix(string $prefix)
+ {
+ foreach ($this->extractors as $extractor) {
+ $extractor->setPrefix($prefix);
+ }
+ }
+
+ /**
+ * @return void
+ */
+ public function extract(string|iterable $directory, MessageCatalogue $catalogue)
+ {
+ foreach ($this->extractors as $extractor) {
+ $extractor->extract($directory, $catalogue);
+ }
+ }
+}
diff --git a/vendor/symfony/translation/Extractor/ExtractorInterface.php b/vendor/symfony/translation/Extractor/ExtractorInterface.php
new file mode 100644
index 000000000..642130af7
--- /dev/null
+++ b/vendor/symfony/translation/Extractor/ExtractorInterface.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * Extracts translation messages from a directory or files to the catalogue.
+ * New found messages are injected to the catalogue using the prefix.
+ *
+ * @author Michel Salib
+ */
+interface ExtractorInterface
+{
+ /**
+ * Extracts translation messages from files, a file or a directory to the catalogue.
+ *
+ * @param string|iterable $resource Files, a file or a directory
+ *
+ * @return void
+ */
+ public function extract(string|iterable $resource, MessageCatalogue $catalogue);
+
+ /**
+ * Sets the prefix that should be used for new found messages.
+ *
+ * @return void
+ */
+ public function setPrefix(string $prefix);
+}
diff --git a/vendor/symfony/translation/Extractor/PhpAstExtractor.php b/vendor/symfony/translation/Extractor/PhpAstExtractor.php
new file mode 100644
index 000000000..4dd7f41b2
--- /dev/null
+++ b/vendor/symfony/translation/Extractor/PhpAstExtractor.php
@@ -0,0 +1,80 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor;
+
+use PhpParser\NodeTraverser;
+use PhpParser\NodeVisitor;
+use PhpParser\Parser;
+use PhpParser\ParserFactory;
+use Symfony\Component\Finder\Finder;
+use Symfony\Component\Translation\Extractor\Visitor\AbstractVisitor;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * PhpAstExtractor extracts translation messages from a PHP AST.
+ *
+ * @author Mathieu Santostefano
+ */
+final class PhpAstExtractor extends AbstractFileExtractor implements ExtractorInterface
+{
+ private Parser $parser;
+
+ public function __construct(
+ /**
+ * @param iterable $visitors
+ */
+ private readonly iterable $visitors,
+ private string $prefix = '',
+ ) {
+ if (!class_exists(ParserFactory::class)) {
+ throw new \LogicException(sprintf('You cannot use "%s" as the "nikic/php-parser" package is not installed. Try running "composer require nikic/php-parser".', static::class));
+ }
+
+ $this->parser = (new ParserFactory())->create(ParserFactory::PREFER_PHP7);
+ }
+
+ public function extract(iterable|string $resource, MessageCatalogue $catalogue): void
+ {
+ foreach ($this->extractFiles($resource) as $file) {
+ $traverser = new NodeTraverser();
+ /** @var AbstractVisitor&NodeVisitor $visitor */
+ foreach ($this->visitors as $visitor) {
+ $visitor->initialize($catalogue, $file, $this->prefix);
+ $traverser->addVisitor($visitor);
+ }
+
+ $nodes = $this->parser->parse(file_get_contents($file));
+ $traverser->traverse($nodes);
+ }
+ }
+
+ public function setPrefix(string $prefix): void
+ {
+ $this->prefix = $prefix;
+ }
+
+ protected function canBeExtracted(string $file): bool
+ {
+ return 'php' === pathinfo($file, \PATHINFO_EXTENSION)
+ && $this->isFile($file)
+ && preg_match('/\bt\(|->trans\(|TranslatableMessage|Symfony\\\\Component\\\\Validator\\\\Constraints/i', file_get_contents($file));
+ }
+
+ protected function extractFromDirectory(array|string $resource): iterable|Finder
+ {
+ if (!class_exists(Finder::class)) {
+ throw new \LogicException(sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class));
+ }
+
+ return (new Finder())->files()->name('*.php')->in($resource);
+ }
+}
diff --git a/vendor/symfony/translation/Extractor/PhpExtractor.php b/vendor/symfony/translation/Extractor/PhpExtractor.php
new file mode 100644
index 000000000..7ff27f7c8
--- /dev/null
+++ b/vendor/symfony/translation/Extractor/PhpExtractor.php
@@ -0,0 +1,333 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor;
+
+trigger_deprecation('symfony/translation', '6.2', '"%s" is deprecated, use "%s" instead.', PhpExtractor::class, PhpAstExtractor::class);
+
+use Symfony\Component\Finder\Finder;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * PhpExtractor extracts translation messages from a PHP template.
+ *
+ * @author Michel Salib
+ *
+ * @deprecated since Symfony 6.2, use the PhpAstExtractor instead
+ */
+class PhpExtractor extends AbstractFileExtractor implements ExtractorInterface
+{
+ public const MESSAGE_TOKEN = 300;
+ public const METHOD_ARGUMENTS_TOKEN = 1000;
+ public const DOMAIN_TOKEN = 1001;
+
+ /**
+ * Prefix for new found message.
+ */
+ private string $prefix = '';
+
+ /**
+ * The sequence that captures translation messages.
+ */
+ protected $sequences = [
+ [
+ '->',
+ 'trans',
+ '(',
+ self::MESSAGE_TOKEN,
+ ',',
+ self::METHOD_ARGUMENTS_TOKEN,
+ ',',
+ self::DOMAIN_TOKEN,
+ ],
+ [
+ '->',
+ 'trans',
+ '(',
+ self::MESSAGE_TOKEN,
+ ],
+ [
+ 'new',
+ 'TranslatableMessage',
+ '(',
+ self::MESSAGE_TOKEN,
+ ',',
+ self::METHOD_ARGUMENTS_TOKEN,
+ ',',
+ self::DOMAIN_TOKEN,
+ ],
+ [
+ 'new',
+ 'TranslatableMessage',
+ '(',
+ self::MESSAGE_TOKEN,
+ ],
+ [
+ 'new',
+ '\\',
+ 'Symfony',
+ '\\',
+ 'Component',
+ '\\',
+ 'Translation',
+ '\\',
+ 'TranslatableMessage',
+ '(',
+ self::MESSAGE_TOKEN,
+ ',',
+ self::METHOD_ARGUMENTS_TOKEN,
+ ',',
+ self::DOMAIN_TOKEN,
+ ],
+ [
+ 'new',
+ '\Symfony\Component\Translation\TranslatableMessage',
+ '(',
+ self::MESSAGE_TOKEN,
+ ',',
+ self::METHOD_ARGUMENTS_TOKEN,
+ ',',
+ self::DOMAIN_TOKEN,
+ ],
+ [
+ 'new',
+ '\\',
+ 'Symfony',
+ '\\',
+ 'Component',
+ '\\',
+ 'Translation',
+ '\\',
+ 'TranslatableMessage',
+ '(',
+ self::MESSAGE_TOKEN,
+ ],
+ [
+ 'new',
+ '\Symfony\Component\Translation\TranslatableMessage',
+ '(',
+ self::MESSAGE_TOKEN,
+ ],
+ [
+ 't',
+ '(',
+ self::MESSAGE_TOKEN,
+ ',',
+ self::METHOD_ARGUMENTS_TOKEN,
+ ',',
+ self::DOMAIN_TOKEN,
+ ],
+ [
+ 't',
+ '(',
+ self::MESSAGE_TOKEN,
+ ],
+ ];
+
+ /**
+ * @return void
+ */
+ public function extract(string|iterable $resource, MessageCatalogue $catalog)
+ {
+ $files = $this->extractFiles($resource);
+ foreach ($files as $file) {
+ $this->parseTokens(token_get_all(file_get_contents($file)), $catalog, $file);
+
+ gc_mem_caches();
+ }
+ }
+
+ /**
+ * @return void
+ */
+ public function setPrefix(string $prefix)
+ {
+ $this->prefix = $prefix;
+ }
+
+ /**
+ * Normalizes a token.
+ */
+ protected function normalizeToken(mixed $token): ?string
+ {
+ if (isset($token[1]) && 'b"' !== $token) {
+ return $token[1];
+ }
+
+ return $token;
+ }
+
+ /**
+ * Seeks to a non-whitespace token.
+ */
+ private function seekToNextRelevantToken(\Iterator $tokenIterator): void
+ {
+ for (; $tokenIterator->valid(); $tokenIterator->next()) {
+ $t = $tokenIterator->current();
+ if (\T_WHITESPACE !== $t[0]) {
+ break;
+ }
+ }
+ }
+
+ private function skipMethodArgument(\Iterator $tokenIterator): void
+ {
+ $openBraces = 0;
+
+ for (; $tokenIterator->valid(); $tokenIterator->next()) {
+ $t = $tokenIterator->current();
+
+ if ('[' === $t[0] || '(' === $t[0]) {
+ ++$openBraces;
+ }
+
+ if (']' === $t[0] || ')' === $t[0]) {
+ --$openBraces;
+ }
+
+ if ((0 === $openBraces && ',' === $t[0]) || (-1 === $openBraces && ')' === $t[0])) {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Extracts the message from the iterator while the tokens
+ * match allowed message tokens.
+ */
+ private function getValue(\Iterator $tokenIterator): string
+ {
+ $message = '';
+ $docToken = '';
+ $docPart = '';
+
+ for (; $tokenIterator->valid(); $tokenIterator->next()) {
+ $t = $tokenIterator->current();
+ if ('.' === $t) {
+ // Concatenate with next token
+ continue;
+ }
+ if (!isset($t[1])) {
+ break;
+ }
+
+ switch ($t[0]) {
+ case \T_START_HEREDOC:
+ $docToken = $t[1];
+ break;
+ case \T_ENCAPSED_AND_WHITESPACE:
+ case \T_CONSTANT_ENCAPSED_STRING:
+ if ('' === $docToken) {
+ $message .= PhpStringTokenParser::parse($t[1]);
+ } else {
+ $docPart = $t[1];
+ }
+ break;
+ case \T_END_HEREDOC:
+ if ($indentation = strspn($t[1], ' ')) {
+ $docPartWithLineBreaks = $docPart;
+ $docPart = '';
+
+ foreach (preg_split('~(\r\n|\n|\r)~', $docPartWithLineBreaks, -1, \PREG_SPLIT_DELIM_CAPTURE) as $str) {
+ if (\in_array($str, ["\r\n", "\n", "\r"], true)) {
+ $docPart .= $str;
+ } else {
+ $docPart .= substr($str, $indentation);
+ }
+ }
+ }
+
+ $message .= PhpStringTokenParser::parseDocString($docToken, $docPart);
+ $docToken = '';
+ $docPart = '';
+ break;
+ case \T_WHITESPACE:
+ break;
+ default:
+ break 2;
+ }
+ }
+
+ return $message;
+ }
+
+ /**
+ * Extracts trans message from PHP tokens.
+ *
+ * @return void
+ */
+ protected function parseTokens(array $tokens, MessageCatalogue $catalog, string $filename)
+ {
+ $tokenIterator = new \ArrayIterator($tokens);
+
+ for ($key = 0; $key < $tokenIterator->count(); ++$key) {
+ foreach ($this->sequences as $sequence) {
+ $message = '';
+ $domain = 'messages';
+ $tokenIterator->seek($key);
+
+ foreach ($sequence as $sequenceKey => $item) {
+ $this->seekToNextRelevantToken($tokenIterator);
+
+ if ($this->normalizeToken($tokenIterator->current()) === $item) {
+ $tokenIterator->next();
+ continue;
+ } elseif (self::MESSAGE_TOKEN === $item) {
+ $message = $this->getValue($tokenIterator);
+
+ if (\count($sequence) === ($sequenceKey + 1)) {
+ break;
+ }
+ } elseif (self::METHOD_ARGUMENTS_TOKEN === $item) {
+ $this->skipMethodArgument($tokenIterator);
+ } elseif (self::DOMAIN_TOKEN === $item) {
+ $domainToken = $this->getValue($tokenIterator);
+ if ('' !== $domainToken) {
+ $domain = $domainToken;
+ }
+
+ break;
+ } else {
+ break;
+ }
+ }
+
+ if ($message) {
+ $catalog->set($message, $this->prefix.$message, $domain);
+ $metadata = $catalog->getMetadata($message, $domain) ?? [];
+ $normalizedFilename = preg_replace('{[\\\\/]+}', '/', $filename);
+ $metadata['sources'][] = $normalizedFilename.':'.$tokens[$key][2];
+ $catalog->setMetadata($message, $metadata, $domain);
+ break;
+ }
+ }
+ }
+ }
+
+ /**
+ * @throws \InvalidArgumentException
+ */
+ protected function canBeExtracted(string $file): bool
+ {
+ return $this->isFile($file) && 'php' === pathinfo($file, \PATHINFO_EXTENSION);
+ }
+
+ protected function extractFromDirectory(string|array $directory): iterable
+ {
+ if (!class_exists(Finder::class)) {
+ throw new \LogicException(sprintf('You cannot use "%s" as the "symfony/finder" package is not installed. Try running "composer require symfony/finder".', static::class));
+ }
+
+ $finder = new Finder();
+
+ return $finder->files()->name('*.php')->in($directory);
+ }
+}
diff --git a/vendor/symfony/translation/Extractor/PhpStringTokenParser.php b/vendor/symfony/translation/Extractor/PhpStringTokenParser.php
new file mode 100644
index 000000000..3b854ce73
--- /dev/null
+++ b/vendor/symfony/translation/Extractor/PhpStringTokenParser.php
@@ -0,0 +1,141 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor;
+
+trigger_deprecation('symfony/translation', '6.2', '"%s" is deprecated.', PhpStringTokenParser::class);
+
+/*
+ * The following is derived from code at http://github.com/nikic/PHP-Parser
+ *
+ * Copyright (c) 2011 by Nikita Popov
+ *
+ * Some rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * * The names of the contributors may not be used to endorse or
+ * promote products derived from this software without specific
+ * prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @deprecated since Symfony 6.2
+ */
+class PhpStringTokenParser
+{
+ protected static $replacements = [
+ '\\' => '\\',
+ '$' => '$',
+ 'n' => "\n",
+ 'r' => "\r",
+ 't' => "\t",
+ 'f' => "\f",
+ 'v' => "\v",
+ 'e' => "\x1B",
+ ];
+
+ /**
+ * Parses a string token.
+ *
+ * @param string $str String token content
+ */
+ public static function parse(string $str): string
+ {
+ $bLength = 0;
+ if ('b' === $str[0]) {
+ $bLength = 1;
+ }
+
+ if ('\'' === $str[$bLength]) {
+ return str_replace(
+ ['\\\\', '\\\''],
+ ['\\', '\''],
+ substr($str, $bLength + 1, -1)
+ );
+ } else {
+ return self::parseEscapeSequences(substr($str, $bLength + 1, -1), '"');
+ }
+ }
+
+ /**
+ * Parses escape sequences in strings (all string types apart from single quoted).
+ *
+ * @param string $str String without quotes
+ * @param string|null $quote Quote type
+ */
+ public static function parseEscapeSequences(string $str, string $quote = null): string
+ {
+ if (null !== $quote) {
+ $str = str_replace('\\'.$quote, $quote, $str);
+ }
+
+ return preg_replace_callback(
+ '~\\\\([\\\\$nrtfve]|[xX][0-9a-fA-F]{1,2}|[0-7]{1,3})~',
+ [__CLASS__, 'parseCallback'],
+ $str
+ );
+ }
+
+ private static function parseCallback(array $matches): string
+ {
+ $str = $matches[1];
+
+ if (isset(self::$replacements[$str])) {
+ return self::$replacements[$str];
+ } elseif ('x' === $str[0] || 'X' === $str[0]) {
+ return \chr(hexdec($str));
+ } else {
+ return \chr(octdec($str));
+ }
+ }
+
+ /**
+ * Parses a constant doc string.
+ *
+ * @param string $startToken Doc string start token content (<<
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor\Visitor;
+
+use PhpParser\Node;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * @author Mathieu Santostefano
+ */
+abstract class AbstractVisitor
+{
+ private MessageCatalogue $catalogue;
+ private \SplFileInfo $file;
+ private string $messagePrefix;
+
+ public function initialize(MessageCatalogue $catalogue, \SplFileInfo $file, string $messagePrefix): void
+ {
+ $this->catalogue = $catalogue;
+ $this->file = $file;
+ $this->messagePrefix = $messagePrefix;
+ }
+
+ protected function addMessageToCatalogue(string $message, ?string $domain, int $line): void
+ {
+ $domain ??= 'messages';
+ $this->catalogue->set($message, $this->messagePrefix.$message, $domain);
+ $metadata = $this->catalogue->getMetadata($message, $domain) ?? [];
+ $normalizedFilename = preg_replace('{[\\\\/]+}', '/', $this->file);
+ $metadata['sources'][] = $normalizedFilename.':'.$line;
+ $this->catalogue->setMetadata($message, $metadata, $domain);
+ }
+
+ protected function getStringArguments(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node, int|string $index, bool $indexIsRegex = false): array
+ {
+ if (\is_string($index)) {
+ return $this->getStringNamedArguments($node, $index, $indexIsRegex);
+ }
+
+ $args = $node instanceof Node\Expr\CallLike ? $node->getRawArgs() : $node->args;
+
+ if (!($arg = $args[$index] ?? null) instanceof Node\Arg) {
+ return [];
+ }
+
+ return (array) $this->getStringValue($arg->value);
+ }
+
+ protected function hasNodeNamedArguments(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node): bool
+ {
+ $args = $node instanceof Node\Expr\CallLike ? $node->getRawArgs() : $node->args;
+
+ foreach ($args as $arg) {
+ if ($arg instanceof Node\Arg && null !== $arg->name) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ protected function nodeFirstNamedArgumentIndex(Node\Expr\CallLike|Node\Attribute|Node\Expr\New_ $node): int
+ {
+ $args = $node instanceof Node\Expr\CallLike ? $node->getRawArgs() : $node->args;
+
+ foreach ($args as $i => $arg) {
+ if ($arg instanceof Node\Arg && null !== $arg->name) {
+ return $i;
+ }
+ }
+
+ return \PHP_INT_MAX;
+ }
+
+ private function getStringNamedArguments(Node\Expr\CallLike|Node\Attribute $node, string $argumentName = null, bool $isArgumentNamePattern = false): array
+ {
+ $args = $node instanceof Node\Expr\CallLike ? $node->getArgs() : $node->args;
+ $argumentValues = [];
+
+ foreach ($args as $arg) {
+ if (!$isArgumentNamePattern && $arg->name?->toString() === $argumentName) {
+ $argumentValues[] = $this->getStringValue($arg->value);
+ } elseif ($isArgumentNamePattern && preg_match($argumentName, $arg->name?->toString() ?? '') > 0) {
+ $argumentValues[] = $this->getStringValue($arg->value);
+ }
+ }
+
+ return array_filter($argumentValues);
+ }
+
+ private function getStringValue(Node $node): ?string
+ {
+ if ($node instanceof Node\Scalar\String_) {
+ return $node->value;
+ }
+
+ if ($node instanceof Node\Expr\BinaryOp\Concat) {
+ if (null === $left = $this->getStringValue($node->left)) {
+ return null;
+ }
+
+ if (null === $right = $this->getStringValue($node->right)) {
+ return null;
+ }
+
+ return $left.$right;
+ }
+
+ if ($node instanceof Node\Expr\Assign && $node->expr instanceof Node\Scalar\String_) {
+ return $node->expr->value;
+ }
+
+ return null;
+ }
+}
diff --git a/vendor/symfony/translation/Extractor/Visitor/ConstraintVisitor.php b/vendor/symfony/translation/Extractor/Visitor/ConstraintVisitor.php
new file mode 100644
index 000000000..33dc8437a
--- /dev/null
+++ b/vendor/symfony/translation/Extractor/Visitor/ConstraintVisitor.php
@@ -0,0 +1,112 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor\Visitor;
+
+use PhpParser\Node;
+use PhpParser\NodeVisitor;
+
+/**
+ * @author Mathieu Santostefano
+ *
+ * Code mostly comes from https://github.com/php-translation/extractor/blob/master/src/Visitor/Php/Symfony/Constraint.php
+ */
+final class ConstraintVisitor extends AbstractVisitor implements NodeVisitor
+{
+ public function __construct(
+ private readonly array $constraintClassNames = []
+ ) {
+ }
+
+ public function beforeTraverse(array $nodes): ?Node
+ {
+ return null;
+ }
+
+ public function enterNode(Node $node): ?Node
+ {
+ if (!$node instanceof Node\Expr\New_ && !$node instanceof Node\Attribute) {
+ return null;
+ }
+
+ $className = $node instanceof Node\Attribute ? $node->name : $node->class;
+ if (!$className instanceof Node\Name) {
+ return null;
+ }
+
+ $parts = $className->parts;
+ $isConstraintClass = false;
+
+ foreach ($parts as $part) {
+ if (\in_array($part, $this->constraintClassNames, true)) {
+ $isConstraintClass = true;
+
+ break;
+ }
+ }
+
+ if (!$isConstraintClass) {
+ return null;
+ }
+
+ $arg = $node->args[0] ?? null;
+ if (!$arg instanceof Node\Arg) {
+ return null;
+ }
+
+ if ($this->hasNodeNamedArguments($node)) {
+ $messages = $this->getStringArguments($node, '/message/i', true);
+ } else {
+ if (!$arg->value instanceof Node\Expr\Array_) {
+ // There is no way to guess which argument is a message to be translated.
+ return null;
+ }
+
+ $messages = [];
+ $options = $arg->value;
+
+ /** @var Node\Expr\ArrayItem $item */
+ foreach ($options->items as $item) {
+ if (!$item->key instanceof Node\Scalar\String_) {
+ continue;
+ }
+
+ if (false === stripos($item->key->value ?? '', 'message')) {
+ continue;
+ }
+
+ if (!$item->value instanceof Node\Scalar\String_) {
+ continue;
+ }
+
+ $messages[] = $item->value->value;
+
+ break;
+ }
+ }
+
+ foreach ($messages as $message) {
+ $this->addMessageToCatalogue($message, 'validators', $node->getStartLine());
+ }
+
+ return null;
+ }
+
+ public function leaveNode(Node $node): ?Node
+ {
+ return null;
+ }
+
+ public function afterTraverse(array $nodes): ?Node
+ {
+ return null;
+ }
+}
diff --git a/vendor/symfony/translation/Extractor/Visitor/TransMethodVisitor.php b/vendor/symfony/translation/Extractor/Visitor/TransMethodVisitor.php
new file mode 100644
index 000000000..0b537baa2
--- /dev/null
+++ b/vendor/symfony/translation/Extractor/Visitor/TransMethodVisitor.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor\Visitor;
+
+use PhpParser\Node;
+use PhpParser\NodeVisitor;
+
+/**
+ * @author Mathieu Santostefano
+ */
+final class TransMethodVisitor extends AbstractVisitor implements NodeVisitor
+{
+ public function beforeTraverse(array $nodes): ?Node
+ {
+ return null;
+ }
+
+ public function enterNode(Node $node): ?Node
+ {
+ if (!$node instanceof Node\Expr\MethodCall && !$node instanceof Node\Expr\FuncCall) {
+ return null;
+ }
+
+ if (!\is_string($node->name) && !$node->name instanceof Node\Identifier && !$node->name instanceof Node\Name) {
+ return null;
+ }
+
+ $name = (string) $node->name;
+
+ if ('trans' === $name || 't' === $name) {
+ $firstNamedArgumentIndex = $this->nodeFirstNamedArgumentIndex($node);
+
+ if (!$messages = $this->getStringArguments($node, 0 < $firstNamedArgumentIndex ? 0 : 'message')) {
+ return null;
+ }
+
+ $domain = $this->getStringArguments($node, 2 < $firstNamedArgumentIndex ? 2 : 'domain')[0] ?? null;
+
+ foreach ($messages as $message) {
+ $this->addMessageToCatalogue($message, $domain, $node->getStartLine());
+ }
+ }
+
+ return null;
+ }
+
+ public function leaveNode(Node $node): ?Node
+ {
+ return null;
+ }
+
+ public function afterTraverse(array $nodes): ?Node
+ {
+ return null;
+ }
+}
diff --git a/vendor/symfony/translation/Extractor/Visitor/TranslatableMessageVisitor.php b/vendor/symfony/translation/Extractor/Visitor/TranslatableMessageVisitor.php
new file mode 100644
index 000000000..c1505a135
--- /dev/null
+++ b/vendor/symfony/translation/Extractor/Visitor/TranslatableMessageVisitor.php
@@ -0,0 +1,65 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Extractor\Visitor;
+
+use PhpParser\Node;
+use PhpParser\NodeVisitor;
+
+/**
+ * @author Mathieu Santostefano
+ */
+final class TranslatableMessageVisitor extends AbstractVisitor implements NodeVisitor
+{
+ public function beforeTraverse(array $nodes): ?Node
+ {
+ return null;
+ }
+
+ public function enterNode(Node $node): ?Node
+ {
+ if (!$node instanceof Node\Expr\New_) {
+ return null;
+ }
+
+ if (!($className = $node->class) instanceof Node\Name) {
+ return null;
+ }
+
+ if (!\in_array('TranslatableMessage', $className->parts, true)) {
+ return null;
+ }
+
+ $firstNamedArgumentIndex = $this->nodeFirstNamedArgumentIndex($node);
+
+ if (!$messages = $this->getStringArguments($node, 0 < $firstNamedArgumentIndex ? 0 : 'message')) {
+ return null;
+ }
+
+ $domain = $this->getStringArguments($node, 2 < $firstNamedArgumentIndex ? 2 : 'domain')[0] ?? null;
+
+ foreach ($messages as $message) {
+ $this->addMessageToCatalogue($message, $domain, $node->getStartLine());
+ }
+
+ return null;
+ }
+
+ public function leaveNode(Node $node): ?Node
+ {
+ return null;
+ }
+
+ public function afterTraverse(array $nodes): ?Node
+ {
+ return null;
+ }
+}
diff --git a/vendor/symfony/translation/Formatter/IntlFormatter.php b/vendor/symfony/translation/Formatter/IntlFormatter.php
new file mode 100644
index 000000000..30873cc58
--- /dev/null
+++ b/vendor/symfony/translation/Formatter/IntlFormatter.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Formatter;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Exception\LogicException;
+
+/**
+ * @author Guilherme Blanco
+ * @author Abdellatif Ait boudad
+ */
+class IntlFormatter implements IntlFormatterInterface
+{
+ private $hasMessageFormatter;
+ private $cache = [];
+
+ public function formatIntl(string $message, string $locale, array $parameters = []): string
+ {
+ // MessageFormatter constructor throws an exception if the message is empty
+ if ('' === $message) {
+ return '';
+ }
+
+ if (!$formatter = $this->cache[$locale][$message] ?? null) {
+ if (!$this->hasMessageFormatter ??= class_exists(\MessageFormatter::class)) {
+ throw new LogicException('Cannot parse message translation: please install the "intl" PHP extension or the "symfony/polyfill-intl-messageformatter" package.');
+ }
+ try {
+ $this->cache[$locale][$message] = $formatter = new \MessageFormatter($locale, $message);
+ } catch (\IntlException $e) {
+ throw new InvalidArgumentException(sprintf('Invalid message format (error #%d): ', intl_get_error_code()).intl_get_error_message(), 0, $e);
+ }
+ }
+
+ foreach ($parameters as $key => $value) {
+ if (\in_array($key[0] ?? null, ['%', '{'], true)) {
+ unset($parameters[$key]);
+ $parameters[trim($key, '%{ }')] = $value;
+ }
+ }
+
+ if (false === $message = $formatter->format($parameters)) {
+ throw new InvalidArgumentException(sprintf('Unable to format message (error #%s): ', $formatter->getErrorCode()).$formatter->getErrorMessage());
+ }
+
+ return $message;
+ }
+}
diff --git a/vendor/symfony/translation/Formatter/IntlFormatterInterface.php b/vendor/symfony/translation/Formatter/IntlFormatterInterface.php
new file mode 100644
index 000000000..02fc6acbd
--- /dev/null
+++ b/vendor/symfony/translation/Formatter/IntlFormatterInterface.php
@@ -0,0 +1,27 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Formatter;
+
+/**
+ * Formats ICU message patterns.
+ *
+ * @author Nicolas Grekas
+ */
+interface IntlFormatterInterface
+{
+ /**
+ * Formats a localized message using rules defined by ICU MessageFormat.
+ *
+ * @see http://icu-project.org/apiref/icu4c/classMessageFormat.html#details
+ */
+ public function formatIntl(string $message, string $locale, array $parameters = []): string;
+}
diff --git a/vendor/symfony/translation/Formatter/MessageFormatter.php b/vendor/symfony/translation/Formatter/MessageFormatter.php
new file mode 100644
index 000000000..29ad574ee
--- /dev/null
+++ b/vendor/symfony/translation/Formatter/MessageFormatter.php
@@ -0,0 +1,50 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Formatter;
+
+use Symfony\Component\Translation\IdentityTranslator;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+// Help opcache.preload discover always-needed symbols
+class_exists(IntlFormatter::class);
+
+/**
+ * @author Abdellatif Ait boudad
+ */
+class MessageFormatter implements MessageFormatterInterface, IntlFormatterInterface
+{
+ private TranslatorInterface $translator;
+ private IntlFormatterInterface $intlFormatter;
+
+ /**
+ * @param TranslatorInterface|null $translator An identity translator to use as selector for pluralization
+ */
+ public function __construct(TranslatorInterface $translator = null, IntlFormatterInterface $intlFormatter = null)
+ {
+ $this->translator = $translator ?? new IdentityTranslator();
+ $this->intlFormatter = $intlFormatter ?? new IntlFormatter();
+ }
+
+ public function format(string $message, string $locale, array $parameters = []): string
+ {
+ if ($this->translator instanceof TranslatorInterface) {
+ return $this->translator->trans($message, $parameters, null, $locale);
+ }
+
+ return strtr($message, $parameters);
+ }
+
+ public function formatIntl(string $message, string $locale, array $parameters = []): string
+ {
+ return $this->intlFormatter->formatIntl($message, $locale, $parameters);
+ }
+}
diff --git a/vendor/symfony/translation/Formatter/MessageFormatterInterface.php b/vendor/symfony/translation/Formatter/MessageFormatterInterface.php
new file mode 100644
index 000000000..d5c41c190
--- /dev/null
+++ b/vendor/symfony/translation/Formatter/MessageFormatterInterface.php
@@ -0,0 +1,28 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Formatter;
+
+/**
+ * @author Guilherme Blanco
+ * @author Abdellatif Ait boudad
+ */
+interface MessageFormatterInterface
+{
+ /**
+ * Formats a localized message pattern with given arguments.
+ *
+ * @param string $message The message (may also be an object that can be cast to string)
+ * @param string $locale The message locale
+ * @param array $parameters An array of parameters for the message
+ */
+ public function format(string $message, string $locale, array $parameters = []): string;
+}
diff --git a/vendor/symfony/translation/IdentityTranslator.php b/vendor/symfony/translation/IdentityTranslator.php
new file mode 100644
index 000000000..46875edf2
--- /dev/null
+++ b/vendor/symfony/translation/IdentityTranslator.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Contracts\Translation\LocaleAwareInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+use Symfony\Contracts\Translation\TranslatorTrait;
+
+/**
+ * IdentityTranslator does not translate anything.
+ *
+ * @author Fabien Potencier
+ */
+class IdentityTranslator implements TranslatorInterface, LocaleAwareInterface
+{
+ use TranslatorTrait;
+}
diff --git a/vendor/symfony/translation/LICENSE b/vendor/symfony/translation/LICENSE
new file mode 100644
index 000000000..0138f8f07
--- /dev/null
+++ b/vendor/symfony/translation/LICENSE
@@ -0,0 +1,19 @@
+Copyright (c) 2004-present Fabien Potencier
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/vendor/symfony/translation/Loader/ArrayLoader.php b/vendor/symfony/translation/Loader/ArrayLoader.php
new file mode 100644
index 000000000..e63a7d05b
--- /dev/null
+++ b/vendor/symfony/translation/Loader/ArrayLoader.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * ArrayLoader loads translations from a PHP array.
+ *
+ * @author Fabien Potencier
+ */
+class ArrayLoader implements LoaderInterface
+{
+ public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
+ {
+ $resource = $this->flatten($resource);
+ $catalogue = new MessageCatalogue($locale);
+ $catalogue->add($resource, $domain);
+
+ return $catalogue;
+ }
+
+ /**
+ * Flattens an nested array of translations.
+ *
+ * The scheme used is:
+ * 'key' => ['key2' => ['key3' => 'value']]
+ * Becomes:
+ * 'key.key2.key3' => 'value'
+ */
+ private function flatten(array $messages): array
+ {
+ $result = [];
+ foreach ($messages as $key => $value) {
+ if (\is_array($value)) {
+ foreach ($this->flatten($value) as $k => $v) {
+ if (null !== $v) {
+ $result[$key.'.'.$k] = $v;
+ }
+ }
+ } elseif (null !== $value) {
+ $result[$key] = $value;
+ }
+ }
+
+ return $result;
+ }
+}
diff --git a/vendor/symfony/translation/Loader/CsvFileLoader.php b/vendor/symfony/translation/Loader/CsvFileLoader.php
new file mode 100644
index 000000000..7f2f96be6
--- /dev/null
+++ b/vendor/symfony/translation/Loader/CsvFileLoader.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+
+/**
+ * CsvFileLoader loads translations from CSV files.
+ *
+ * @author Saša Stamenković
+ */
+class CsvFileLoader extends FileLoader
+{
+ private string $delimiter = ';';
+ private string $enclosure = '"';
+ private string $escape = '\\';
+
+ protected function loadResource(string $resource): array
+ {
+ $messages = [];
+
+ try {
+ $file = new \SplFileObject($resource, 'rb');
+ } catch (\RuntimeException $e) {
+ throw new NotFoundResourceException(sprintf('Error opening file "%s".', $resource), 0, $e);
+ }
+
+ $file->setFlags(\SplFileObject::READ_CSV | \SplFileObject::SKIP_EMPTY);
+ $file->setCsvControl($this->delimiter, $this->enclosure, $this->escape);
+
+ foreach ($file as $data) {
+ if (false === $data) {
+ continue;
+ }
+
+ if (!str_starts_with($data[0], '#') && isset($data[1]) && 2 === \count($data)) {
+ $messages[$data[0]] = $data[1];
+ }
+ }
+
+ return $messages;
+ }
+
+ /**
+ * Sets the delimiter, enclosure, and escape character for CSV.
+ *
+ * @return void
+ */
+ public function setCsvControl(string $delimiter = ';', string $enclosure = '"', string $escape = '\\')
+ {
+ $this->delimiter = $delimiter;
+ $this->enclosure = $enclosure;
+ $this->escape = $escape;
+ }
+}
diff --git a/vendor/symfony/translation/Loader/FileLoader.php b/vendor/symfony/translation/Loader/FileLoader.php
new file mode 100644
index 000000000..877c3bbc7
--- /dev/null
+++ b/vendor/symfony/translation/Loader/FileLoader.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * @author Abdellatif Ait boudad
+ */
+abstract class FileLoader extends ArrayLoader
+{
+ public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
+ {
+ if (!stream_is_local($resource)) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
+ $messages = $this->loadResource($resource);
+
+ // empty resource
+ $messages ??= [];
+
+ // not an array
+ if (!\is_array($messages)) {
+ throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource));
+ }
+
+ $catalogue = parent::load($messages, $locale, $domain);
+
+ if (class_exists(FileResource::class)) {
+ $catalogue->addResource(new FileResource($resource));
+ }
+
+ return $catalogue;
+ }
+
+ /**
+ * @throws InvalidResourceException if stream content has an invalid format
+ */
+ abstract protected function loadResource(string $resource): array;
+}
diff --git a/vendor/symfony/translation/Loader/IcuDatFileLoader.php b/vendor/symfony/translation/Loader/IcuDatFileLoader.php
new file mode 100644
index 000000000..76e4e7f02
--- /dev/null
+++ b/vendor/symfony/translation/Loader/IcuDatFileLoader.php
@@ -0,0 +1,58 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * IcuResFileLoader loads translations from a resource bundle.
+ *
+ * @author stealth35
+ */
+class IcuDatFileLoader extends IcuResFileLoader
+{
+ public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
+ {
+ if (!stream_is_local($resource.'.dat')) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource.'.dat')) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
+ try {
+ $rb = new \ResourceBundle($locale, $resource);
+ } catch (\Exception) {
+ $rb = null;
+ }
+
+ if (!$rb) {
+ throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource));
+ } elseif (intl_is_failure($rb->getErrorCode())) {
+ throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode());
+ }
+
+ $messages = $this->flatten($rb);
+ $catalogue = new MessageCatalogue($locale);
+ $catalogue->add($messages, $domain);
+
+ if (class_exists(FileResource::class)) {
+ $catalogue->addResource(new FileResource($resource.'.dat'));
+ }
+
+ return $catalogue;
+ }
+}
diff --git a/vendor/symfony/translation/Loader/IcuResFileLoader.php b/vendor/symfony/translation/Loader/IcuResFileLoader.php
new file mode 100644
index 000000000..94d55b861
--- /dev/null
+++ b/vendor/symfony/translation/Loader/IcuResFileLoader.php
@@ -0,0 +1,86 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Config\Resource\DirectoryResource;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * IcuResFileLoader loads translations from a resource bundle.
+ *
+ * @author stealth35
+ */
+class IcuResFileLoader implements LoaderInterface
+{
+ public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
+ {
+ if (!stream_is_local($resource)) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!is_dir($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
+ try {
+ $rb = new \ResourceBundle($locale, $resource);
+ } catch (\Exception) {
+ $rb = null;
+ }
+
+ if (!$rb) {
+ throw new InvalidResourceException(sprintf('Cannot load resource "%s".', $resource));
+ } elseif (intl_is_failure($rb->getErrorCode())) {
+ throw new InvalidResourceException($rb->getErrorMessage(), $rb->getErrorCode());
+ }
+
+ $messages = $this->flatten($rb);
+ $catalogue = new MessageCatalogue($locale);
+ $catalogue->add($messages, $domain);
+
+ if (class_exists(DirectoryResource::class)) {
+ $catalogue->addResource(new DirectoryResource($resource));
+ }
+
+ return $catalogue;
+ }
+
+ /**
+ * Flattens an ResourceBundle.
+ *
+ * The scheme used is:
+ * key { key2 { key3 { "value" } } }
+ * Becomes:
+ * 'key.key2.key3' => 'value'
+ *
+ * This function takes an array by reference and will modify it
+ *
+ * @param \ResourceBundle $rb The ResourceBundle that will be flattened
+ * @param array $messages Used internally for recursive calls
+ * @param string|null $path Current path being parsed, used internally for recursive calls
+ */
+ protected function flatten(\ResourceBundle $rb, array &$messages = [], string $path = null): array
+ {
+ foreach ($rb as $key => $value) {
+ $nodePath = $path ? $path.'.'.$key : $key;
+ if ($value instanceof \ResourceBundle) {
+ $this->flatten($value, $messages, $nodePath);
+ } else {
+ $messages[$nodePath] = $value;
+ }
+ }
+
+ return $messages;
+ }
+}
diff --git a/vendor/symfony/translation/Loader/IniFileLoader.php b/vendor/symfony/translation/Loader/IniFileLoader.php
new file mode 100644
index 000000000..3126896c8
--- /dev/null
+++ b/vendor/symfony/translation/Loader/IniFileLoader.php
@@ -0,0 +1,25 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+/**
+ * IniFileLoader loads translations from an ini file.
+ *
+ * @author stealth35
+ */
+class IniFileLoader extends FileLoader
+{
+ protected function loadResource(string $resource): array
+ {
+ return parse_ini_file($resource, true);
+ }
+}
diff --git a/vendor/symfony/translation/Loader/JsonFileLoader.php b/vendor/symfony/translation/Loader/JsonFileLoader.php
new file mode 100644
index 000000000..385553ef6
--- /dev/null
+++ b/vendor/symfony/translation/Loader/JsonFileLoader.php
@@ -0,0 +1,51 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+
+/**
+ * JsonFileLoader loads translations from an json file.
+ *
+ * @author singles
+ */
+class JsonFileLoader extends FileLoader
+{
+ protected function loadResource(string $resource): array
+ {
+ $messages = [];
+ if ($data = file_get_contents($resource)) {
+ $messages = json_decode($data, true);
+
+ if (0 < $errorCode = json_last_error()) {
+ throw new InvalidResourceException('Error parsing JSON: '.$this->getJSONErrorMessage($errorCode));
+ }
+ }
+
+ return $messages;
+ }
+
+ /**
+ * Translates JSON_ERROR_* constant into meaningful message.
+ */
+ private function getJSONErrorMessage(int $errorCode): string
+ {
+ return match ($errorCode) {
+ \JSON_ERROR_DEPTH => 'Maximum stack depth exceeded',
+ \JSON_ERROR_STATE_MISMATCH => 'Underflow or the modes mismatch',
+ \JSON_ERROR_CTRL_CHAR => 'Unexpected control character found',
+ \JSON_ERROR_SYNTAX => 'Syntax error, malformed JSON',
+ \JSON_ERROR_UTF8 => 'Malformed UTF-8 characters, possibly incorrectly encoded',
+ default => 'Unknown error',
+ };
+ }
+}
diff --git a/vendor/symfony/translation/Loader/LoaderInterface.php b/vendor/symfony/translation/Loader/LoaderInterface.php
new file mode 100644
index 000000000..29d5560d1
--- /dev/null
+++ b/vendor/symfony/translation/Loader/LoaderInterface.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * LoaderInterface is the interface implemented by all translation loaders.
+ *
+ * @author Fabien Potencier
+ */
+interface LoaderInterface
+{
+ /**
+ * Loads a locale.
+ *
+ * @throws NotFoundResourceException when the resource cannot be found
+ * @throws InvalidResourceException when the resource cannot be loaded
+ */
+ public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue;
+}
diff --git a/vendor/symfony/translation/Loader/MoFileLoader.php b/vendor/symfony/translation/Loader/MoFileLoader.php
new file mode 100644
index 000000000..8427c393e
--- /dev/null
+++ b/vendor/symfony/translation/Loader/MoFileLoader.php
@@ -0,0 +1,138 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+
+/**
+ * @copyright Copyright (c) 2010, Union of RAD http://union-of-rad.org (http://lithify.me/)
+ */
+class MoFileLoader extends FileLoader
+{
+ /**
+ * Magic used for validating the format of an MO file as well as
+ * detecting if the machine used to create that file was little endian.
+ */
+ public const MO_LITTLE_ENDIAN_MAGIC = 0x950412DE;
+
+ /**
+ * Magic used for validating the format of an MO file as well as
+ * detecting if the machine used to create that file was big endian.
+ */
+ public const MO_BIG_ENDIAN_MAGIC = 0xDE120495;
+
+ /**
+ * The size of the header of an MO file in bytes.
+ */
+ public const MO_HEADER_SIZE = 28;
+
+ /**
+ * Parses machine object (MO) format, independent of the machine's endian it
+ * was created on. Both 32bit and 64bit systems are supported.
+ */
+ protected function loadResource(string $resource): array
+ {
+ $stream = fopen($resource, 'r');
+
+ $stat = fstat($stream);
+
+ if ($stat['size'] < self::MO_HEADER_SIZE) {
+ throw new InvalidResourceException('MO stream content has an invalid format.');
+ }
+ $magic = unpack('V1', fread($stream, 4));
+ $magic = hexdec(substr(dechex(current($magic)), -8));
+
+ if (self::MO_LITTLE_ENDIAN_MAGIC == $magic) {
+ $isBigEndian = false;
+ } elseif (self::MO_BIG_ENDIAN_MAGIC == $magic) {
+ $isBigEndian = true;
+ } else {
+ throw new InvalidResourceException('MO stream content has an invalid format.');
+ }
+
+ // formatRevision
+ $this->readLong($stream, $isBigEndian);
+ $count = $this->readLong($stream, $isBigEndian);
+ $offsetId = $this->readLong($stream, $isBigEndian);
+ $offsetTranslated = $this->readLong($stream, $isBigEndian);
+ // sizeHashes
+ $this->readLong($stream, $isBigEndian);
+ // offsetHashes
+ $this->readLong($stream, $isBigEndian);
+
+ $messages = [];
+
+ for ($i = 0; $i < $count; ++$i) {
+ $pluralId = null;
+ $translated = null;
+
+ fseek($stream, $offsetId + $i * 8);
+
+ $length = $this->readLong($stream, $isBigEndian);
+ $offset = $this->readLong($stream, $isBigEndian);
+
+ if ($length < 1) {
+ continue;
+ }
+
+ fseek($stream, $offset);
+ $singularId = fread($stream, $length);
+
+ if (str_contains($singularId, "\000")) {
+ [$singularId, $pluralId] = explode("\000", $singularId);
+ }
+
+ fseek($stream, $offsetTranslated + $i * 8);
+ $length = $this->readLong($stream, $isBigEndian);
+ $offset = $this->readLong($stream, $isBigEndian);
+
+ if ($length < 1) {
+ continue;
+ }
+
+ fseek($stream, $offset);
+ $translated = fread($stream, $length);
+
+ if (str_contains($translated, "\000")) {
+ $translated = explode("\000", $translated);
+ }
+
+ $ids = ['singular' => $singularId, 'plural' => $pluralId];
+ $item = compact('ids', 'translated');
+
+ if (!empty($item['ids']['singular'])) {
+ $id = $item['ids']['singular'];
+ if (isset($item['ids']['plural'])) {
+ $id .= '|'.$item['ids']['plural'];
+ }
+ $messages[$id] = stripcslashes(implode('|', (array) $item['translated']));
+ }
+ }
+
+ fclose($stream);
+
+ return array_filter($messages);
+ }
+
+ /**
+ * Reads an unsigned long from stream respecting endianness.
+ *
+ * @param resource $stream
+ */
+ private function readLong($stream, bool $isBigEndian): int
+ {
+ $result = unpack($isBigEndian ? 'N1' : 'V1', fread($stream, 4));
+ $result = current($result);
+
+ return (int) substr($result, -8);
+ }
+}
diff --git a/vendor/symfony/translation/Loader/PhpFileLoader.php b/vendor/symfony/translation/Loader/PhpFileLoader.php
new file mode 100644
index 000000000..93f23cd95
--- /dev/null
+++ b/vendor/symfony/translation/Loader/PhpFileLoader.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+/**
+ * PhpFileLoader loads translations from PHP files returning an array of translations.
+ *
+ * @author Fabien Potencier
+ */
+class PhpFileLoader extends FileLoader
+{
+ private static ?array $cache = [];
+
+ protected function loadResource(string $resource): array
+ {
+ if ([] === self::$cache && \function_exists('opcache_invalidate') && filter_var(\ini_get('opcache.enable'), \FILTER_VALIDATE_BOOL) && (!\in_array(\PHP_SAPI, ['cli', 'phpdbg'], true) || filter_var(\ini_get('opcache.enable_cli'), \FILTER_VALIDATE_BOOL))) {
+ self::$cache = null;
+ }
+
+ if (null === self::$cache) {
+ return require $resource;
+ }
+
+ return self::$cache[$resource] ??= require $resource;
+ }
+}
diff --git a/vendor/symfony/translation/Loader/PoFileLoader.php b/vendor/symfony/translation/Loader/PoFileLoader.php
new file mode 100644
index 000000000..620d97339
--- /dev/null
+++ b/vendor/symfony/translation/Loader/PoFileLoader.php
@@ -0,0 +1,147 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+/**
+ * @copyright Copyright (c) 2010, Union of RAD https://github.com/UnionOfRAD/lithium
+ * @copyright Copyright (c) 2012, Clemens Tolboom
+ */
+class PoFileLoader extends FileLoader
+{
+ /**
+ * Parses portable object (PO) format.
+ *
+ * From https://www.gnu.org/software/gettext/manual/gettext.html#PO-Files
+ * we should be able to parse files having:
+ *
+ * white-space
+ * # translator-comments
+ * #. extracted-comments
+ * #: reference...
+ * #, flag...
+ * #| msgid previous-untranslated-string
+ * msgid untranslated-string
+ * msgstr translated-string
+ *
+ * extra or different lines are:
+ *
+ * #| msgctxt previous-context
+ * #| msgid previous-untranslated-string
+ * msgctxt context
+ *
+ * #| msgid previous-untranslated-string-singular
+ * #| msgid_plural previous-untranslated-string-plural
+ * msgid untranslated-string-singular
+ * msgid_plural untranslated-string-plural
+ * msgstr[0] translated-string-case-0
+ * ...
+ * msgstr[N] translated-string-case-n
+ *
+ * The definition states:
+ * - white-space and comments are optional.
+ * - msgid "" that an empty singleline defines a header.
+ *
+ * This parser sacrifices some features of the reference implementation the
+ * differences to that implementation are as follows.
+ * - No support for comments spanning multiple lines.
+ * - Translator and extracted comments are treated as being the same type.
+ * - Message IDs are allowed to have other encodings as just US-ASCII.
+ *
+ * Items with an empty id are ignored.
+ */
+ protected function loadResource(string $resource): array
+ {
+ $stream = fopen($resource, 'r');
+
+ $defaults = [
+ 'ids' => [],
+ 'translated' => null,
+ ];
+
+ $messages = [];
+ $item = $defaults;
+ $flags = [];
+
+ while ($line = fgets($stream)) {
+ $line = trim($line);
+
+ if ('' === $line) {
+ // Whitespace indicated current item is done
+ if (!\in_array('fuzzy', $flags)) {
+ $this->addMessage($messages, $item);
+ }
+ $item = $defaults;
+ $flags = [];
+ } elseif (str_starts_with($line, '#,')) {
+ $flags = array_map('trim', explode(',', substr($line, 2)));
+ } elseif (str_starts_with($line, 'msgid "')) {
+ // We start a new msg so save previous
+ // TODO: this fails when comments or contexts are added
+ $this->addMessage($messages, $item);
+ $item = $defaults;
+ $item['ids']['singular'] = substr($line, 7, -1);
+ } elseif (str_starts_with($line, 'msgstr "')) {
+ $item['translated'] = substr($line, 8, -1);
+ } elseif ('"' === $line[0]) {
+ $continues = isset($item['translated']) ? 'translated' : 'ids';
+
+ if (\is_array($item[$continues])) {
+ end($item[$continues]);
+ $item[$continues][key($item[$continues])] .= substr($line, 1, -1);
+ } else {
+ $item[$continues] .= substr($line, 1, -1);
+ }
+ } elseif (str_starts_with($line, 'msgid_plural "')) {
+ $item['ids']['plural'] = substr($line, 14, -1);
+ } elseif (str_starts_with($line, 'msgstr[')) {
+ $size = strpos($line, ']');
+ $item['translated'][(int) substr($line, 7, 1)] = substr($line, $size + 3, -1);
+ }
+ }
+ // save last item
+ if (!\in_array('fuzzy', $flags)) {
+ $this->addMessage($messages, $item);
+ }
+ fclose($stream);
+
+ return $messages;
+ }
+
+ /**
+ * Save a translation item to the messages.
+ *
+ * A .po file could contain by error missing plural indexes. We need to
+ * fix these before saving them.
+ */
+ private function addMessage(array &$messages, array $item): void
+ {
+ if (!empty($item['ids']['singular'])) {
+ $id = stripcslashes($item['ids']['singular']);
+ if (isset($item['ids']['plural'])) {
+ $id .= '|'.stripcslashes($item['ids']['plural']);
+ }
+
+ $translated = (array) $item['translated'];
+ // PO are by definition indexed so sort by index.
+ ksort($translated);
+ // Make sure every index is filled.
+ end($translated);
+ $count = key($translated);
+ // Fill missing spots with '-'.
+ $empties = array_fill(0, $count + 1, '-');
+ $translated += $empties;
+ ksort($translated);
+
+ $messages[$id] = stripcslashes(implode('|', $translated));
+ }
+ }
+}
diff --git a/vendor/symfony/translation/Loader/QtFileLoader.php b/vendor/symfony/translation/Loader/QtFileLoader.php
new file mode 100644
index 000000000..235f85ee9
--- /dev/null
+++ b/vendor/symfony/translation/Loader/QtFileLoader.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Config\Util\XmlUtils;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+use Symfony\Component\Translation\Exception\RuntimeException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * QtFileLoader loads translations from QT Translations XML files.
+ *
+ * @author Benjamin Eberlei
+ */
+class QtFileLoader implements LoaderInterface
+{
+ public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
+ {
+ if (!class_exists(XmlUtils::class)) {
+ throw new RuntimeException('Loading translations from the QT format requires the Symfony Config component.');
+ }
+
+ if (!stream_is_local($resource)) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
+ try {
+ $dom = XmlUtils::loadFile($resource);
+ } catch (\InvalidArgumentException $e) {
+ throw new InvalidResourceException(sprintf('Unable to load "%s".', $resource), $e->getCode(), $e);
+ }
+
+ $internalErrors = libxml_use_internal_errors(true);
+ libxml_clear_errors();
+
+ $xpath = new \DOMXPath($dom);
+ $nodes = $xpath->evaluate('//TS/context/name[text()="'.$domain.'"]');
+
+ $catalogue = new MessageCatalogue($locale);
+ if (1 == $nodes->length) {
+ $translations = $nodes->item(0)->nextSibling->parentNode->parentNode->getElementsByTagName('message');
+ foreach ($translations as $translation) {
+ $translationValue = (string) $translation->getElementsByTagName('translation')->item(0)->nodeValue;
+
+ if (!empty($translationValue)) {
+ $catalogue->set(
+ (string) $translation->getElementsByTagName('source')->item(0)->nodeValue,
+ $translationValue,
+ $domain
+ );
+ }
+ }
+
+ if (class_exists(FileResource::class)) {
+ $catalogue->addResource(new FileResource($resource));
+ }
+ }
+
+ libxml_use_internal_errors($internalErrors);
+
+ return $catalogue;
+ }
+}
diff --git a/vendor/symfony/translation/Loader/XliffFileLoader.php b/vendor/symfony/translation/Loader/XliffFileLoader.php
new file mode 100644
index 000000000..34cad36c4
--- /dev/null
+++ b/vendor/symfony/translation/Loader/XliffFileLoader.php
@@ -0,0 +1,233 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Config\Resource\FileResource;
+use Symfony\Component\Config\Util\Exception\InvalidXmlException;
+use Symfony\Component\Config\Util\Exception\XmlParsingException;
+use Symfony\Component\Config\Util\XmlUtils;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+use Symfony\Component\Translation\Exception\RuntimeException;
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\Util\XliffUtils;
+
+/**
+ * XliffFileLoader loads translations from XLIFF files.
+ *
+ * @author Fabien Potencier
+ */
+class XliffFileLoader implements LoaderInterface
+{
+ public function load(mixed $resource, string $locale, string $domain = 'messages'): MessageCatalogue
+ {
+ if (!class_exists(XmlUtils::class)) {
+ throw new RuntimeException('Loading translations from the Xliff format requires the Symfony Config component.');
+ }
+
+ if (!$this->isXmlString($resource)) {
+ if (!stream_is_local($resource)) {
+ throw new InvalidResourceException(sprintf('This is not a local file "%s".', $resource));
+ }
+
+ if (!file_exists($resource)) {
+ throw new NotFoundResourceException(sprintf('File "%s" not found.', $resource));
+ }
+
+ if (!is_file($resource)) {
+ throw new InvalidResourceException(sprintf('This is neither a file nor an XLIFF string "%s".', $resource));
+ }
+ }
+
+ try {
+ if ($this->isXmlString($resource)) {
+ $dom = XmlUtils::parse($resource);
+ } else {
+ $dom = XmlUtils::loadFile($resource);
+ }
+ } catch (\InvalidArgumentException|XmlParsingException|InvalidXmlException $e) {
+ throw new InvalidResourceException(sprintf('Unable to load "%s": ', $resource).$e->getMessage(), $e->getCode(), $e);
+ }
+
+ if ($errors = XliffUtils::validateSchema($dom)) {
+ throw new InvalidResourceException(sprintf('Invalid resource provided: "%s"; Errors: ', $resource).XliffUtils::getErrorsAsString($errors));
+ }
+
+ $catalogue = new MessageCatalogue($locale);
+ $this->extract($dom, $catalogue, $domain);
+
+ if (is_file($resource) && class_exists(FileResource::class)) {
+ $catalogue->addResource(new FileResource($resource));
+ }
+
+ return $catalogue;
+ }
+
+ private function extract(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void
+ {
+ $xliffVersion = XliffUtils::getVersionNumber($dom);
+
+ if ('1.2' === $xliffVersion) {
+ $this->extractXliff1($dom, $catalogue, $domain);
+ }
+
+ if ('2.0' === $xliffVersion) {
+ $this->extractXliff2($dom, $catalogue, $domain);
+ }
+ }
+
+ /**
+ * Extract messages and metadata from DOMDocument into a MessageCatalogue.
+ */
+ private function extractXliff1(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void
+ {
+ $xml = simplexml_import_dom($dom);
+ $encoding = $dom->encoding ? strtoupper($dom->encoding) : null;
+
+ $namespace = 'urn:oasis:names:tc:xliff:document:1.2';
+ $xml->registerXPathNamespace('xliff', $namespace);
+
+ foreach ($xml->xpath('//xliff:file') as $file) {
+ $fileAttributes = $file->attributes();
+
+ $file->registerXPathNamespace('xliff', $namespace);
+
+ foreach ($file->xpath('.//xliff:prop') as $prop) {
+ $catalogue->setCatalogueMetadata($prop->attributes()['prop-type'], (string) $prop, $domain);
+ }
+
+ foreach ($file->xpath('.//xliff:trans-unit') as $translation) {
+ $attributes = $translation->attributes();
+
+ if (!(isset($attributes['resname']) || isset($translation->source))) {
+ continue;
+ }
+
+ $source = isset($attributes['resname']) && $attributes['resname'] ? $attributes['resname'] : $translation->source;
+ // If the xlf file has another encoding specified, try to convert it because
+ // simple_xml will always return utf-8 encoded values
+ $target = $this->utf8ToCharset((string) ($translation->target ?? $translation->source), $encoding);
+
+ $catalogue->set((string) $source, $target, $domain);
+
+ $metadata = [
+ 'source' => (string) $translation->source,
+ 'file' => [
+ 'original' => (string) $fileAttributes['original'],
+ ],
+ ];
+ if ($notes = $this->parseNotesMetadata($translation->note, $encoding)) {
+ $metadata['notes'] = $notes;
+ }
+
+ if (isset($translation->target) && $translation->target->attributes()) {
+ $metadata['target-attributes'] = [];
+ foreach ($translation->target->attributes() as $key => $value) {
+ $metadata['target-attributes'][$key] = (string) $value;
+ }
+ }
+
+ if (isset($attributes['id'])) {
+ $metadata['id'] = (string) $attributes['id'];
+ }
+
+ $catalogue->setMetadata((string) $source, $metadata, $domain);
+ }
+ }
+ }
+
+ private function extractXliff2(\DOMDocument $dom, MessageCatalogue $catalogue, string $domain): void
+ {
+ $xml = simplexml_import_dom($dom);
+ $encoding = $dom->encoding ? strtoupper($dom->encoding) : null;
+
+ $xml->registerXPathNamespace('xliff', 'urn:oasis:names:tc:xliff:document:2.0');
+
+ foreach ($xml->xpath('//xliff:unit') as $unit) {
+ foreach ($unit->segment as $segment) {
+ $attributes = $unit->attributes();
+ $source = $attributes['name'] ?? $segment->source;
+
+ // If the xlf file has another encoding specified, try to convert it because
+ // simple_xml will always return utf-8 encoded values
+ $target = $this->utf8ToCharset((string) ($segment->target ?? $segment->source), $encoding);
+
+ $catalogue->set((string) $source, $target, $domain);
+
+ $metadata = [];
+ if (isset($segment->target) && $segment->target->attributes()) {
+ $metadata['target-attributes'] = [];
+ foreach ($segment->target->attributes() as $key => $value) {
+ $metadata['target-attributes'][$key] = (string) $value;
+ }
+ }
+
+ if (isset($unit->notes)) {
+ $metadata['notes'] = [];
+ foreach ($unit->notes->note as $noteNode) {
+ $note = [];
+ foreach ($noteNode->attributes() as $key => $value) {
+ $note[$key] = (string) $value;
+ }
+ $note['content'] = (string) $noteNode;
+ $metadata['notes'][] = $note;
+ }
+ }
+
+ $catalogue->setMetadata((string) $source, $metadata, $domain);
+ }
+ }
+ }
+
+ /**
+ * Convert a UTF8 string to the specified encoding.
+ */
+ private function utf8ToCharset(string $content, string $encoding = null): string
+ {
+ if ('UTF-8' !== $encoding && !empty($encoding)) {
+ return mb_convert_encoding($content, $encoding, 'UTF-8');
+ }
+
+ return $content;
+ }
+
+ private function parseNotesMetadata(\SimpleXMLElement $noteElement = null, string $encoding = null): array
+ {
+ $notes = [];
+
+ if (null === $noteElement) {
+ return $notes;
+ }
+
+ /** @var \SimpleXMLElement $xmlNote */
+ foreach ($noteElement as $xmlNote) {
+ $noteAttributes = $xmlNote->attributes();
+ $note = ['content' => $this->utf8ToCharset((string) $xmlNote, $encoding)];
+ if (isset($noteAttributes['priority'])) {
+ $note['priority'] = (int) $noteAttributes['priority'];
+ }
+
+ if (isset($noteAttributes['from'])) {
+ $note['from'] = (string) $noteAttributes['from'];
+ }
+
+ $notes[] = $note;
+ }
+
+ return $notes;
+ }
+
+ private function isXmlString(string $resource): bool
+ {
+ return str_starts_with($resource, '
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Loader;
+
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+use Symfony\Component\Translation\Exception\LogicException;
+use Symfony\Component\Yaml\Exception\ParseException;
+use Symfony\Component\Yaml\Parser as YamlParser;
+use Symfony\Component\Yaml\Yaml;
+
+/**
+ * YamlFileLoader loads translations from Yaml files.
+ *
+ * @author Fabien Potencier
+ */
+class YamlFileLoader extends FileLoader
+{
+ private $yamlParser;
+
+ protected function loadResource(string $resource): array
+ {
+ if (null === $this->yamlParser) {
+ if (!class_exists(\Symfony\Component\Yaml\Parser::class)) {
+ throw new LogicException('Loading translations from the YAML format requires the Symfony Yaml component.');
+ }
+
+ $this->yamlParser = new YamlParser();
+ }
+
+ try {
+ $messages = $this->yamlParser->parseFile($resource, Yaml::PARSE_CONSTANT);
+ } catch (ParseException $e) {
+ throw new InvalidResourceException(sprintf('The file "%s" does not contain valid YAML: ', $resource).$e->getMessage(), 0, $e);
+ }
+
+ if (null !== $messages && !\is_array($messages)) {
+ throw new InvalidResourceException(sprintf('Unable to load file "%s".', $resource));
+ }
+
+ return $messages ?: [];
+ }
+}
diff --git a/vendor/symfony/translation/LocaleSwitcher.php b/vendor/symfony/translation/LocaleSwitcher.php
new file mode 100644
index 000000000..48ef4396f
--- /dev/null
+++ b/vendor/symfony/translation/LocaleSwitcher.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Component\Routing\RequestContext;
+use Symfony\Contracts\Translation\LocaleAwareInterface;
+
+/**
+ * @author Kevin Bond
+ */
+class LocaleSwitcher implements LocaleAwareInterface
+{
+ private string $defaultLocale;
+
+ /**
+ * @param LocaleAwareInterface[] $localeAwareServices
+ */
+ public function __construct(
+ private string $locale,
+ private iterable $localeAwareServices,
+ private ?RequestContext $requestContext = null,
+ ) {
+ $this->defaultLocale = $locale;
+ }
+
+ public function setLocale(string $locale): void
+ {
+ if (class_exists(\Locale::class)) {
+ \Locale::setDefault($locale);
+ }
+ $this->locale = $locale;
+ $this->requestContext?->setParameter('_locale', $locale);
+
+ foreach ($this->localeAwareServices as $service) {
+ $service->setLocale($locale);
+ }
+ }
+
+ public function getLocale(): string
+ {
+ return $this->locale;
+ }
+
+ /**
+ * Switch to a new locale, execute a callback, then switch back to the original.
+ *
+ * @template T
+ *
+ * @param callable():T $callback
+ *
+ * @return T
+ */
+ public function runWithLocale(string $locale, callable $callback): mixed
+ {
+ $original = $this->getLocale();
+ $this->setLocale($locale);
+
+ try {
+ return $callback();
+ } finally {
+ $this->setLocale($original);
+ }
+ }
+
+ public function reset(): void
+ {
+ $this->setLocale($this->defaultLocale);
+ }
+}
diff --git a/vendor/symfony/translation/LoggingTranslator.php b/vendor/symfony/translation/LoggingTranslator.php
new file mode 100644
index 000000000..e107bf223
--- /dev/null
+++ b/vendor/symfony/translation/LoggingTranslator.php
@@ -0,0 +1,115 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Psr\Log\LoggerInterface;
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Contracts\Translation\LocaleAwareInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+/**
+ * @author Abdellatif Ait boudad
+ */
+class LoggingTranslator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface
+{
+ private TranslatorInterface $translator;
+ private LoggerInterface $logger;
+
+ /**
+ * @param TranslatorInterface&TranslatorBagInterface&LocaleAwareInterface $translator The translator must implement TranslatorBagInterface
+ */
+ public function __construct(TranslatorInterface $translator, LoggerInterface $logger)
+ {
+ if (!$translator instanceof TranslatorBagInterface || !$translator instanceof LocaleAwareInterface) {
+ throw new InvalidArgumentException(sprintf('The Translator "%s" must implement TranslatorInterface, TranslatorBagInterface and LocaleAwareInterface.', get_debug_type($translator)));
+ }
+
+ $this->translator = $translator;
+ $this->logger = $logger;
+ }
+
+ public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string
+ {
+ $trans = $this->translator->trans($id = (string) $id, $parameters, $domain, $locale);
+ $this->log($id, $domain, $locale);
+
+ return $trans;
+ }
+
+ /**
+ * @return void
+ */
+ public function setLocale(string $locale)
+ {
+ $prev = $this->translator->getLocale();
+ $this->translator->setLocale($locale);
+ if ($prev === $locale) {
+ return;
+ }
+
+ $this->logger->debug(sprintf('The locale of the translator has changed from "%s" to "%s".', $prev, $locale));
+ }
+
+ public function getLocale(): string
+ {
+ return $this->translator->getLocale();
+ }
+
+ public function getCatalogue(string $locale = null): MessageCatalogueInterface
+ {
+ return $this->translator->getCatalogue($locale);
+ }
+
+ public function getCatalogues(): array
+ {
+ return $this->translator->getCatalogues();
+ }
+
+ /**
+ * Gets the fallback locales.
+ */
+ public function getFallbackLocales(): array
+ {
+ if ($this->translator instanceof Translator || method_exists($this->translator, 'getFallbackLocales')) {
+ return $this->translator->getFallbackLocales();
+ }
+
+ return [];
+ }
+
+ /**
+ * Passes through all unknown calls onto the translator object.
+ */
+ public function __call(string $method, array $args)
+ {
+ return $this->translator->{$method}(...$args);
+ }
+
+ /**
+ * Logs for missing translations.
+ */
+ private function log(string $id, ?string $domain, ?string $locale): void
+ {
+ $domain ??= 'messages';
+
+ $catalogue = $this->translator->getCatalogue($locale);
+ if ($catalogue->defines($id, $domain)) {
+ return;
+ }
+
+ if ($catalogue->has($id, $domain)) {
+ $this->logger->debug('Translation use fallback catalogue.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]);
+ } else {
+ $this->logger->warning('Translation not found.', ['id' => $id, 'domain' => $domain, 'locale' => $catalogue->getLocale()]);
+ }
+ }
+}
diff --git a/vendor/symfony/translation/MessageCatalogue.php b/vendor/symfony/translation/MessageCatalogue.php
new file mode 100644
index 000000000..379d947ce
--- /dev/null
+++ b/vendor/symfony/translation/MessageCatalogue.php
@@ -0,0 +1,338 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Component\Config\Resource\ResourceInterface;
+use Symfony\Component\Translation\Exception\LogicException;
+
+/**
+ * @author Fabien Potencier
+ */
+class MessageCatalogue implements MessageCatalogueInterface, MetadataAwareInterface, CatalogueMetadataAwareInterface
+{
+ private array $messages = [];
+ private array $metadata = [];
+ private array $catalogueMetadata = [];
+ private array $resources = [];
+ private string $locale;
+ private ?MessageCatalogueInterface $fallbackCatalogue = null;
+ private ?self $parent = null;
+
+ /**
+ * @param array $messages An array of messages classified by domain
+ */
+ public function __construct(string $locale, array $messages = [])
+ {
+ $this->locale = $locale;
+ $this->messages = $messages;
+ }
+
+ public function getLocale(): string
+ {
+ return $this->locale;
+ }
+
+ public function getDomains(): array
+ {
+ $domains = [];
+
+ foreach ($this->messages as $domain => $messages) {
+ if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) {
+ $domain = substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX));
+ }
+ $domains[$domain] = $domain;
+ }
+
+ return array_values($domains);
+ }
+
+ public function all(string $domain = null): array
+ {
+ if (null !== $domain) {
+ // skip messages merge if intl-icu requested explicitly
+ if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) {
+ return $this->messages[$domain] ?? [];
+ }
+
+ return ($this->messages[$domain.self::INTL_DOMAIN_SUFFIX] ?? []) + ($this->messages[$domain] ?? []);
+ }
+
+ $allMessages = [];
+
+ foreach ($this->messages as $domain => $messages) {
+ if (str_ends_with($domain, self::INTL_DOMAIN_SUFFIX)) {
+ $domain = substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX));
+ $allMessages[$domain] = $messages + ($allMessages[$domain] ?? []);
+ } else {
+ $allMessages[$domain] = ($allMessages[$domain] ?? []) + $messages;
+ }
+ }
+
+ return $allMessages;
+ }
+
+ /**
+ * @return void
+ */
+ public function set(string $id, string $translation, string $domain = 'messages')
+ {
+ $this->add([$id => $translation], $domain);
+ }
+
+ public function has(string $id, string $domain = 'messages'): bool
+ {
+ if (isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) {
+ return true;
+ }
+
+ if (null !== $this->fallbackCatalogue) {
+ return $this->fallbackCatalogue->has($id, $domain);
+ }
+
+ return false;
+ }
+
+ public function defines(string $id, string $domain = 'messages'): bool
+ {
+ return isset($this->messages[$domain][$id]) || isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id]);
+ }
+
+ public function get(string $id, string $domain = 'messages'): string
+ {
+ if (isset($this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id])) {
+ return $this->messages[$domain.self::INTL_DOMAIN_SUFFIX][$id];
+ }
+
+ if (isset($this->messages[$domain][$id])) {
+ return $this->messages[$domain][$id];
+ }
+
+ if (null !== $this->fallbackCatalogue) {
+ return $this->fallbackCatalogue->get($id, $domain);
+ }
+
+ return $id;
+ }
+
+ /**
+ * @return void
+ */
+ public function replace(array $messages, string $domain = 'messages')
+ {
+ unset($this->messages[$domain], $this->messages[$domain.self::INTL_DOMAIN_SUFFIX]);
+
+ $this->add($messages, $domain);
+ }
+
+ /**
+ * @return void
+ */
+ public function add(array $messages, string $domain = 'messages')
+ {
+ $altDomain = str_ends_with($domain, self::INTL_DOMAIN_SUFFIX) ? substr($domain, 0, -\strlen(self::INTL_DOMAIN_SUFFIX)) : $domain.self::INTL_DOMAIN_SUFFIX;
+ foreach ($messages as $id => $message) {
+ unset($this->messages[$altDomain][$id]);
+ $this->messages[$domain][$id] = $message;
+ }
+
+ if ([] === ($this->messages[$altDomain] ?? null)) {
+ unset($this->messages[$altDomain]);
+ }
+ }
+
+ /**
+ * @return void
+ */
+ public function addCatalogue(MessageCatalogueInterface $catalogue)
+ {
+ if ($catalogue->getLocale() !== $this->locale) {
+ throw new LogicException(sprintf('Cannot add a catalogue for locale "%s" as the current locale for this catalogue is "%s".', $catalogue->getLocale(), $this->locale));
+ }
+
+ foreach ($catalogue->all() as $domain => $messages) {
+ if ($intlMessages = $catalogue->all($domain.self::INTL_DOMAIN_SUFFIX)) {
+ $this->add($intlMessages, $domain.self::INTL_DOMAIN_SUFFIX);
+ $messages = array_diff_key($messages, $intlMessages);
+ }
+ $this->add($messages, $domain);
+ }
+
+ foreach ($catalogue->getResources() as $resource) {
+ $this->addResource($resource);
+ }
+
+ if ($catalogue instanceof MetadataAwareInterface) {
+ $metadata = $catalogue->getMetadata('', '');
+ $this->addMetadata($metadata);
+ }
+
+ if ($catalogue instanceof CatalogueMetadataAwareInterface) {
+ $catalogueMetadata = $catalogue->getCatalogueMetadata('', '');
+ $this->addCatalogueMetadata($catalogueMetadata);
+ }
+ }
+
+ /**
+ * @return void
+ */
+ public function addFallbackCatalogue(MessageCatalogueInterface $catalogue)
+ {
+ // detect circular references
+ $c = $catalogue;
+ while ($c = $c->getFallbackCatalogue()) {
+ if ($c->getLocale() === $this->getLocale()) {
+ throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale()));
+ }
+ }
+
+ $c = $this;
+ do {
+ if ($c->getLocale() === $catalogue->getLocale()) {
+ throw new LogicException(sprintf('Circular reference detected when adding a fallback catalogue for locale "%s".', $catalogue->getLocale()));
+ }
+
+ foreach ($catalogue->getResources() as $resource) {
+ $c->addResource($resource);
+ }
+ } while ($c = $c->parent);
+
+ $catalogue->parent = $this;
+ $this->fallbackCatalogue = $catalogue;
+
+ foreach ($catalogue->getResources() as $resource) {
+ $this->addResource($resource);
+ }
+ }
+
+ public function getFallbackCatalogue(): ?MessageCatalogueInterface
+ {
+ return $this->fallbackCatalogue;
+ }
+
+ public function getResources(): array
+ {
+ return array_values($this->resources);
+ }
+
+ /**
+ * @return void
+ */
+ public function addResource(ResourceInterface $resource)
+ {
+ $this->resources[$resource->__toString()] = $resource;
+ }
+
+ public function getMetadata(string $key = '', string $domain = 'messages'): mixed
+ {
+ if ('' == $domain) {
+ return $this->metadata;
+ }
+
+ if (isset($this->metadata[$domain])) {
+ if ('' == $key) {
+ return $this->metadata[$domain];
+ }
+
+ if (isset($this->metadata[$domain][$key])) {
+ return $this->metadata[$domain][$key];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return void
+ */
+ public function setMetadata(string $key, mixed $value, string $domain = 'messages')
+ {
+ $this->metadata[$domain][$key] = $value;
+ }
+
+ /**
+ * @return void
+ */
+ public function deleteMetadata(string $key = '', string $domain = 'messages')
+ {
+ if ('' == $domain) {
+ $this->metadata = [];
+ } elseif ('' == $key) {
+ unset($this->metadata[$domain]);
+ } else {
+ unset($this->metadata[$domain][$key]);
+ }
+ }
+
+ public function getCatalogueMetadata(string $key = '', string $domain = 'messages'): mixed
+ {
+ if (!$domain) {
+ return $this->catalogueMetadata;
+ }
+
+ if (isset($this->catalogueMetadata[$domain])) {
+ if (!$key) {
+ return $this->catalogueMetadata[$domain];
+ }
+
+ if (isset($this->catalogueMetadata[$domain][$key])) {
+ return $this->catalogueMetadata[$domain][$key];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * @return void
+ */
+ public function setCatalogueMetadata(string $key, mixed $value, string $domain = 'messages')
+ {
+ $this->catalogueMetadata[$domain][$key] = $value;
+ }
+
+ /**
+ * @return void
+ */
+ public function deleteCatalogueMetadata(string $key = '', string $domain = 'messages')
+ {
+ if (!$domain) {
+ $this->catalogueMetadata = [];
+ } elseif (!$key) {
+ unset($this->catalogueMetadata[$domain]);
+ } else {
+ unset($this->catalogueMetadata[$domain][$key]);
+ }
+ }
+
+ /**
+ * Adds current values with the new values.
+ *
+ * @param array $values Values to add
+ */
+ private function addMetadata(array $values): void
+ {
+ foreach ($values as $domain => $keys) {
+ foreach ($keys as $key => $value) {
+ $this->setMetadata($key, $value, $domain);
+ }
+ }
+ }
+
+ private function addCatalogueMetadata(array $values): void
+ {
+ foreach ($values as $domain => $keys) {
+ foreach ($keys as $key => $value) {
+ $this->setCatalogueMetadata($key, $value, $domain);
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/translation/MessageCatalogueInterface.php b/vendor/symfony/translation/MessageCatalogueInterface.php
new file mode 100644
index 000000000..ed819f155
--- /dev/null
+++ b/vendor/symfony/translation/MessageCatalogueInterface.php
@@ -0,0 +1,134 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Component\Config\Resource\ResourceInterface;
+
+/**
+ * MessageCatalogueInterface.
+ *
+ * @author Fabien Potencier
+ */
+interface MessageCatalogueInterface
+{
+ public const INTL_DOMAIN_SUFFIX = '+intl-icu';
+
+ /**
+ * Gets the catalogue locale.
+ */
+ public function getLocale(): string;
+
+ /**
+ * Gets the domains.
+ */
+ public function getDomains(): array;
+
+ /**
+ * Gets the messages within a given domain.
+ *
+ * If $domain is null, it returns all messages.
+ */
+ public function all(string $domain = null): array;
+
+ /**
+ * Sets a message translation.
+ *
+ * @param string $id The message id
+ * @param string $translation The messages translation
+ * @param string $domain The domain name
+ *
+ * @return void
+ */
+ public function set(string $id, string $translation, string $domain = 'messages');
+
+ /**
+ * Checks if a message has a translation.
+ *
+ * @param string $id The message id
+ * @param string $domain The domain name
+ */
+ public function has(string $id, string $domain = 'messages'): bool;
+
+ /**
+ * Checks if a message has a translation (it does not take into account the fallback mechanism).
+ *
+ * @param string $id The message id
+ * @param string $domain The domain name
+ */
+ public function defines(string $id, string $domain = 'messages'): bool;
+
+ /**
+ * Gets a message translation.
+ *
+ * @param string $id The message id
+ * @param string $domain The domain name
+ */
+ public function get(string $id, string $domain = 'messages'): string;
+
+ /**
+ * Sets translations for a given domain.
+ *
+ * @param array $messages An array of translations
+ * @param string $domain The domain name
+ *
+ * @return void
+ */
+ public function replace(array $messages, string $domain = 'messages');
+
+ /**
+ * Adds translations for a given domain.
+ *
+ * @param array $messages An array of translations
+ * @param string $domain The domain name
+ *
+ * @return void
+ */
+ public function add(array $messages, string $domain = 'messages');
+
+ /**
+ * Merges translations from the given Catalogue into the current one.
+ *
+ * The two catalogues must have the same locale.
+ *
+ * @return void
+ */
+ public function addCatalogue(self $catalogue);
+
+ /**
+ * Merges translations from the given Catalogue into the current one
+ * only when the translation does not exist.
+ *
+ * This is used to provide default translations when they do not exist for the current locale.
+ *
+ * @return void
+ */
+ public function addFallbackCatalogue(self $catalogue);
+
+ /**
+ * Gets the fallback catalogue.
+ */
+ public function getFallbackCatalogue(): ?self;
+
+ /**
+ * Returns an array of resources loaded to build this collection.
+ *
+ * @return ResourceInterface[]
+ */
+ public function getResources(): array;
+
+ /**
+ * Adds a resource for this collection.
+ *
+ * @return void
+ */
+ public function addResource(ResourceInterface $resource);
+}
diff --git a/vendor/symfony/translation/MetadataAwareInterface.php b/vendor/symfony/translation/MetadataAwareInterface.php
new file mode 100644
index 000000000..39e5326c3
--- /dev/null
+++ b/vendor/symfony/translation/MetadataAwareInterface.php
@@ -0,0 +1,48 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+/**
+ * This interface is used to get, set, and delete metadata about the translation messages.
+ *
+ * @author Fabien Potencier
+ */
+interface MetadataAwareInterface
+{
+ /**
+ * Gets metadata for the given domain and key.
+ *
+ * Passing an empty domain will return an array with all metadata indexed by
+ * domain and then by key. Passing an empty key will return an array with all
+ * metadata for the given domain.
+ *
+ * @return mixed The value that was set or an array with the domains/keys or null
+ */
+ public function getMetadata(string $key = '', string $domain = 'messages'): mixed;
+
+ /**
+ * Adds metadata to a message domain.
+ *
+ * @return void
+ */
+ public function setMetadata(string $key, mixed $value, string $domain = 'messages');
+
+ /**
+ * Deletes metadata for the given key and domain.
+ *
+ * Passing an empty domain will delete all metadata. Passing an empty key will
+ * delete all metadata for the given domain.
+ *
+ * @return void
+ */
+ public function deleteMetadata(string $key = '', string $domain = 'messages');
+}
diff --git a/vendor/symfony/translation/Provider/AbstractProviderFactory.php b/vendor/symfony/translation/Provider/AbstractProviderFactory.php
new file mode 100644
index 000000000..f0c11d85d
--- /dev/null
+++ b/vendor/symfony/translation/Provider/AbstractProviderFactory.php
@@ -0,0 +1,37 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Provider;
+
+use Symfony\Component\Translation\Exception\IncompleteDsnException;
+
+abstract class AbstractProviderFactory implements ProviderFactoryInterface
+{
+ public function supports(Dsn $dsn): bool
+ {
+ return \in_array($dsn->getScheme(), $this->getSupportedSchemes(), true);
+ }
+
+ /**
+ * @return string[]
+ */
+ abstract protected function getSupportedSchemes(): array;
+
+ protected function getUser(Dsn $dsn): string
+ {
+ return $dsn->getUser() ?? throw new IncompleteDsnException('User is not set.', $dsn->getScheme().'://'.$dsn->getHost());
+ }
+
+ protected function getPassword(Dsn $dsn): string
+ {
+ return $dsn->getPassword() ?? throw new IncompleteDsnException('Password is not set.', $dsn->getOriginalDsn());
+ }
+}
diff --git a/vendor/symfony/translation/Provider/Dsn.php b/vendor/symfony/translation/Provider/Dsn.php
new file mode 100644
index 000000000..af75cb3ae
--- /dev/null
+++ b/vendor/symfony/translation/Provider/Dsn.php
@@ -0,0 +1,110 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Provider;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Exception\MissingRequiredOptionException;
+
+/**
+ * @author Fabien Potencier
+ * @author Oskar Stark
+ */
+final class Dsn
+{
+ private ?string $scheme;
+ private ?string $host;
+ private ?string $user;
+ private ?string $password;
+ private ?int $port;
+ private ?string $path;
+ private array $options = [];
+ private string $originalDsn;
+
+ public function __construct(#[\SensitiveParameter] string $dsn)
+ {
+ $this->originalDsn = $dsn;
+
+ if (false === $parsedDsn = parse_url($dsn)) {
+ throw new InvalidArgumentException('The translation provider DSN is invalid.');
+ }
+
+ if (!isset($parsedDsn['scheme'])) {
+ throw new InvalidArgumentException('The translation provider DSN must contain a scheme.');
+ }
+ $this->scheme = $parsedDsn['scheme'];
+
+ if (!isset($parsedDsn['host'])) {
+ throw new InvalidArgumentException('The translation provider DSN must contain a host (use "default" by default).');
+ }
+ $this->host = $parsedDsn['host'];
+
+ $this->user = '' !== ($parsedDsn['user'] ?? '') ? urldecode($parsedDsn['user']) : null;
+ $this->password = '' !== ($parsedDsn['pass'] ?? '') ? urldecode($parsedDsn['pass']) : null;
+ $this->port = $parsedDsn['port'] ?? null;
+ $this->path = $parsedDsn['path'] ?? null;
+ parse_str($parsedDsn['query'] ?? '', $this->options);
+ }
+
+ public function getScheme(): string
+ {
+ return $this->scheme;
+ }
+
+ public function getHost(): string
+ {
+ return $this->host;
+ }
+
+ public function getUser(): ?string
+ {
+ return $this->user;
+ }
+
+ public function getPassword(): ?string
+ {
+ return $this->password;
+ }
+
+ public function getPort(int $default = null): ?int
+ {
+ return $this->port ?? $default;
+ }
+
+ public function getOption(string $key, mixed $default = null): mixed
+ {
+ return $this->options[$key] ?? $default;
+ }
+
+ public function getRequiredOption(string $key): mixed
+ {
+ if (!\array_key_exists($key, $this->options) || '' === trim($this->options[$key])) {
+ throw new MissingRequiredOptionException($key);
+ }
+
+ return $this->options[$key];
+ }
+
+ public function getOptions(): array
+ {
+ return $this->options;
+ }
+
+ public function getPath(): ?string
+ {
+ return $this->path;
+ }
+
+ public function getOriginalDsn(): string
+ {
+ return $this->originalDsn;
+ }
+}
diff --git a/vendor/symfony/translation/Provider/FilteringProvider.php b/vendor/symfony/translation/Provider/FilteringProvider.php
new file mode 100644
index 000000000..d4465b9fd
--- /dev/null
+++ b/vendor/symfony/translation/Provider/FilteringProvider.php
@@ -0,0 +1,62 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Provider;
+
+use Symfony\Component\Translation\TranslatorBag;
+use Symfony\Component\Translation\TranslatorBagInterface;
+
+/**
+ * Filters domains and locales between the Translator config values and those specific to each provider.
+ *
+ * @author Mathieu Santostefano
+ */
+class FilteringProvider implements ProviderInterface
+{
+ private ProviderInterface $provider;
+ private array $locales;
+ private array $domains;
+
+ public function __construct(ProviderInterface $provider, array $locales, array $domains = [])
+ {
+ $this->provider = $provider;
+ $this->locales = $locales;
+ $this->domains = $domains;
+ }
+
+ public function __toString(): string
+ {
+ return (string) $this->provider;
+ }
+
+ public function write(TranslatorBagInterface $translatorBag): void
+ {
+ $this->provider->write($translatorBag);
+ }
+
+ public function read(array $domains, array $locales): TranslatorBag
+ {
+ $domains = !$this->domains ? $domains : array_intersect($this->domains, $domains);
+ $locales = array_intersect($this->locales, $locales);
+
+ return $this->provider->read($domains, $locales);
+ }
+
+ public function delete(TranslatorBagInterface $translatorBag): void
+ {
+ $this->provider->delete($translatorBag);
+ }
+
+ public function getDomains(): array
+ {
+ return $this->domains;
+ }
+}
diff --git a/vendor/symfony/translation/Provider/NullProvider.php b/vendor/symfony/translation/Provider/NullProvider.php
new file mode 100644
index 000000000..f00392ea0
--- /dev/null
+++ b/vendor/symfony/translation/Provider/NullProvider.php
@@ -0,0 +1,39 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Provider;
+
+use Symfony\Component\Translation\TranslatorBag;
+use Symfony\Component\Translation\TranslatorBagInterface;
+
+/**
+ * @author Mathieu Santostefano
+ */
+class NullProvider implements ProviderInterface
+{
+ public function __toString(): string
+ {
+ return 'null';
+ }
+
+ public function write(TranslatorBagInterface $translatorBag, bool $override = false): void
+ {
+ }
+
+ public function read(array $domains, array $locales): TranslatorBag
+ {
+ return new TranslatorBag();
+ }
+
+ public function delete(TranslatorBagInterface $translatorBag): void
+ {
+ }
+}
diff --git a/vendor/symfony/translation/Provider/NullProviderFactory.php b/vendor/symfony/translation/Provider/NullProviderFactory.php
new file mode 100644
index 000000000..f350f1602
--- /dev/null
+++ b/vendor/symfony/translation/Provider/NullProviderFactory.php
@@ -0,0 +1,34 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Provider;
+
+use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
+
+/**
+ * @author Mathieu Santostefano
+ */
+final class NullProviderFactory extends AbstractProviderFactory
+{
+ public function create(Dsn $dsn): ProviderInterface
+ {
+ if ('null' === $dsn->getScheme()) {
+ return new NullProvider();
+ }
+
+ throw new UnsupportedSchemeException($dsn, 'null', $this->getSupportedSchemes());
+ }
+
+ protected function getSupportedSchemes(): array
+ {
+ return ['null'];
+ }
+}
diff --git a/vendor/symfony/translation/Provider/ProviderFactoryInterface.php b/vendor/symfony/translation/Provider/ProviderFactoryInterface.php
new file mode 100644
index 000000000..3fd4494b4
--- /dev/null
+++ b/vendor/symfony/translation/Provider/ProviderFactoryInterface.php
@@ -0,0 +1,26 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Provider;
+
+use Symfony\Component\Translation\Exception\IncompleteDsnException;
+use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
+
+interface ProviderFactoryInterface
+{
+ /**
+ * @throws UnsupportedSchemeException
+ * @throws IncompleteDsnException
+ */
+ public function create(Dsn $dsn): ProviderInterface;
+
+ public function supports(Dsn $dsn): bool;
+}
diff --git a/vendor/symfony/translation/Provider/ProviderInterface.php b/vendor/symfony/translation/Provider/ProviderInterface.php
new file mode 100644
index 000000000..a32193f29
--- /dev/null
+++ b/vendor/symfony/translation/Provider/ProviderInterface.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Provider;
+
+use Symfony\Component\Translation\TranslatorBag;
+use Symfony\Component\Translation\TranslatorBagInterface;
+
+interface ProviderInterface
+{
+ public function __toString(): string;
+
+ /**
+ * Translations available in the TranslatorBag only must be created.
+ * Translations available in both the TranslatorBag and on the provider
+ * must be overwritten.
+ * Translations available on the provider only must be kept.
+ */
+ public function write(TranslatorBagInterface $translatorBag): void;
+
+ public function read(array $domains, array $locales): TranslatorBag;
+
+ public function delete(TranslatorBagInterface $translatorBag): void;
+}
diff --git a/vendor/symfony/translation/Provider/TranslationProviderCollection.php b/vendor/symfony/translation/Provider/TranslationProviderCollection.php
new file mode 100644
index 000000000..61ac641cd
--- /dev/null
+++ b/vendor/symfony/translation/Provider/TranslationProviderCollection.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Provider;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+
+/**
+ * @author Mathieu Santostefano
+ */
+final class TranslationProviderCollection
+{
+ /**
+ * @var array
+ */
+ private $providers;
+
+ /**
+ * @param array $providers
+ */
+ public function __construct(iterable $providers)
+ {
+ $this->providers = \is_array($providers) ? $providers : iterator_to_array($providers);
+ }
+
+ public function __toString(): string
+ {
+ return '['.implode(',', array_keys($this->providers)).']';
+ }
+
+ public function has(string $name): bool
+ {
+ return isset($this->providers[$name]);
+ }
+
+ public function get(string $name): ProviderInterface
+ {
+ if (!$this->has($name)) {
+ throw new InvalidArgumentException(sprintf('Provider "%s" not found. Available: "%s".', $name, (string) $this));
+ }
+
+ return $this->providers[$name];
+ }
+
+ public function keys(): array
+ {
+ return array_keys($this->providers);
+ }
+}
diff --git a/vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php b/vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php
new file mode 100644
index 000000000..6300c8750
--- /dev/null
+++ b/vendor/symfony/translation/Provider/TranslationProviderCollectionFactory.php
@@ -0,0 +1,57 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Provider;
+
+use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
+
+/**
+ * @author Mathieu Santostefano
+ */
+class TranslationProviderCollectionFactory
+{
+ private iterable $factories;
+ private array $enabledLocales;
+
+ /**
+ * @param iterable $factories
+ */
+ public function __construct(iterable $factories, array $enabledLocales)
+ {
+ $this->factories = $factories;
+ $this->enabledLocales = $enabledLocales;
+ }
+
+ public function fromConfig(array $config): TranslationProviderCollection
+ {
+ $providers = [];
+ foreach ($config as $name => $currentConfig) {
+ $providers[$name] = $this->fromDsnObject(
+ new Dsn($currentConfig['dsn']),
+ !$currentConfig['locales'] ? $this->enabledLocales : $currentConfig['locales'],
+ !$currentConfig['domains'] ? [] : $currentConfig['domains']
+ );
+ }
+
+ return new TranslationProviderCollection($providers);
+ }
+
+ public function fromDsnObject(Dsn $dsn, array $locales, array $domains = []): ProviderInterface
+ {
+ foreach ($this->factories as $factory) {
+ if ($factory->supports($dsn)) {
+ return new FilteringProvider($factory->create($dsn), $locales, $domains);
+ }
+ }
+
+ throw new UnsupportedSchemeException($dsn);
+ }
+}
diff --git a/vendor/symfony/translation/PseudoLocalizationTranslator.php b/vendor/symfony/translation/PseudoLocalizationTranslator.php
new file mode 100644
index 000000000..0207e9997
--- /dev/null
+++ b/vendor/symfony/translation/PseudoLocalizationTranslator.php
@@ -0,0 +1,365 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+/**
+ * This translator should only be used in a development environment.
+ */
+final class PseudoLocalizationTranslator implements TranslatorInterface
+{
+ private const EXPANSION_CHARACTER = '~';
+
+ private TranslatorInterface $translator;
+ private bool $accents;
+ private float $expansionFactor;
+ private bool $brackets;
+ private bool $parseHTML;
+
+ /**
+ * @var string[]
+ */
+ private array $localizableHTMLAttributes;
+
+ /**
+ * Available options:
+ * * accents:
+ * type: boolean
+ * default: true
+ * description: replace ASCII characters of the translated string with accented versions or similar characters
+ * example: if true, "foo" => "ƒöö".
+ *
+ * * expansion_factor:
+ * type: float
+ * default: 1
+ * validation: it must be greater than or equal to 1
+ * description: expand the translated string by the given factor with spaces and tildes
+ * example: if 2, "foo" => "~foo ~"
+ *
+ * * brackets:
+ * type: boolean
+ * default: true
+ * description: wrap the translated string with brackets
+ * example: if true, "foo" => "[foo]"
+ *
+ * * parse_html:
+ * type: boolean
+ * default: false
+ * description: parse the translated string as HTML - looking for HTML tags has a performance impact but allows to preserve them from alterations - it also allows to compute the visible translated string length which is useful to correctly expand ot when it contains HTML
+ * warning: unclosed tags are unsupported, they will be fixed (closed) by the parser - eg, "foo bar" => "foo
bar
"
+ *
+ * * localizable_html_attributes:
+ * type: string[]
+ * default: []
+ * description: the list of HTML attributes whose values can be altered - it is only useful when the "parse_html" option is set to true
+ * example: if ["title"], and with the "accents" option set to true, "
Profile" => "
Þŕöƒîļé" - if "title" was not in the "localizable_html_attributes" list, the title attribute data would be left unchanged.
+ */
+ public function __construct(TranslatorInterface $translator, array $options = [])
+ {
+ $this->translator = $translator;
+ $this->accents = $options['accents'] ?? true;
+
+ if (1.0 > ($this->expansionFactor = $options['expansion_factor'] ?? 1.0)) {
+ throw new \InvalidArgumentException('The expansion factor must be greater than or equal to 1.');
+ }
+
+ $this->brackets = $options['brackets'] ?? true;
+
+ $this->parseHTML = $options['parse_html'] ?? false;
+ if ($this->parseHTML && !$this->accents && 1.0 === $this->expansionFactor) {
+ $this->parseHTML = false;
+ }
+
+ $this->localizableHTMLAttributes = $options['localizable_html_attributes'] ?? [];
+ }
+
+ public function trans(string $id, array $parameters = [], string $domain = null, string $locale = null): string
+ {
+ $trans = '';
+ $visibleText = '';
+
+ foreach ($this->getParts($this->translator->trans($id, $parameters, $domain, $locale)) as [$visible, $localizable, $text]) {
+ if ($visible) {
+ $visibleText .= $text;
+ }
+
+ if (!$localizable) {
+ $trans .= $text;
+
+ continue;
+ }
+
+ $this->addAccents($trans, $text);
+ }
+
+ $this->expand($trans, $visibleText);
+
+ $this->addBrackets($trans);
+
+ return $trans;
+ }
+
+ public function getLocale(): string
+ {
+ return $this->translator->getLocale();
+ }
+
+ private function getParts(string $originalTrans): array
+ {
+ if (!$this->parseHTML) {
+ return [[true, true, $originalTrans]];
+ }
+
+ $html = mb_encode_numericentity($originalTrans, [0x80, 0xFFFF, 0, 0xFFFF], mb_detect_encoding($originalTrans, null, true) ?: 'UTF-8');
+
+ $useInternalErrors = libxml_use_internal_errors(true);
+
+ $dom = new \DOMDocument();
+ $dom->loadHTML('
'.$html.'');
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($useInternalErrors);
+
+ return $this->parseNode($dom->childNodes->item(1)->childNodes->item(0)->childNodes->item(0));
+ }
+
+ private function parseNode(\DOMNode $node): array
+ {
+ $parts = [];
+
+ foreach ($node->childNodes as $childNode) {
+ if (!$childNode instanceof \DOMElement) {
+ $parts[] = [true, true, $childNode->nodeValue];
+
+ continue;
+ }
+
+ $parts[] = [false, false, '<'.$childNode->tagName];
+
+ /** @var \DOMAttr $attribute */
+ foreach ($childNode->attributes as $attribute) {
+ $parts[] = [false, false, ' '.$attribute->nodeName.'="'];
+
+ $localizableAttribute = \in_array($attribute->nodeName, $this->localizableHTMLAttributes, true);
+ foreach (preg_split('/(&(?:amp|quot|#039|lt|gt);+)/', htmlspecialchars($attribute->nodeValue, \ENT_QUOTES, 'UTF-8'), -1, \PREG_SPLIT_DELIM_CAPTURE) as $i => $match) {
+ if ('' === $match) {
+ continue;
+ }
+
+ $parts[] = [false, $localizableAttribute && 0 === $i % 2, $match];
+ }
+
+ $parts[] = [false, false, '"'];
+ }
+
+ $parts[] = [false, false, '>'];
+
+ $parts = array_merge($parts, $this->parseNode($childNode, $parts));
+
+ $parts[] = [false, false, ''.$childNode->tagName.'>'];
+ }
+
+ return $parts;
+ }
+
+ private function addAccents(string &$trans, string $text): void
+ {
+ $trans .= $this->accents ? strtr($text, [
+ ' ' => ' ',
+ '!' => '¡',
+ '"' => '″',
+ '#' => '♯',
+ '$' => '€',
+ '%' => '‰',
+ '&' => '⅋',
+ '\'' => '´',
+ '(' => '{',
+ ')' => '}',
+ '*' => '⁎',
+ '+' => '⁺',
+ ',' => '،',
+ '-' => '‐',
+ '.' => '·',
+ '/' => '⁄',
+ '0' => '⓪',
+ '1' => '①',
+ '2' => '②',
+ '3' => '③',
+ '4' => '④',
+ '5' => '⑤',
+ '6' => '⑥',
+ '7' => '⑦',
+ '8' => '⑧',
+ '9' => '⑨',
+ ':' => '∶',
+ ';' => '⁏',
+ '<' => '≤',
+ '=' => '≂',
+ '>' => '≥',
+ '?' => '¿',
+ '@' => '՞',
+ 'A' => 'Å',
+ 'B' => 'Ɓ',
+ 'C' => 'Ç',
+ 'D' => 'Ð',
+ 'E' => 'É',
+ 'F' => 'Ƒ',
+ 'G' => 'Ĝ',
+ 'H' => 'Ĥ',
+ 'I' => 'Î',
+ 'J' => 'Ĵ',
+ 'K' => 'Ķ',
+ 'L' => 'Ļ',
+ 'M' => 'Ṁ',
+ 'N' => 'Ñ',
+ 'O' => 'Ö',
+ 'P' => 'Þ',
+ 'Q' => 'Ǫ',
+ 'R' => 'Ŕ',
+ 'S' => 'Š',
+ 'T' => 'Ţ',
+ 'U' => 'Û',
+ 'V' => 'Ṽ',
+ 'W' => 'Ŵ',
+ 'X' => 'Ẋ',
+ 'Y' => 'Ý',
+ 'Z' => 'Ž',
+ '[' => '⁅',
+ '\\' => '∖',
+ ']' => '⁆',
+ '^' => '˄',
+ '_' => '‿',
+ '`' => '‵',
+ 'a' => 'å',
+ 'b' => 'ƀ',
+ 'c' => 'ç',
+ 'd' => 'ð',
+ 'e' => 'é',
+ 'f' => 'ƒ',
+ 'g' => 'ĝ',
+ 'h' => 'ĥ',
+ 'i' => 'î',
+ 'j' => 'ĵ',
+ 'k' => 'ķ',
+ 'l' => 'ļ',
+ 'm' => 'ɱ',
+ 'n' => 'ñ',
+ 'o' => 'ö',
+ 'p' => 'þ',
+ 'q' => 'ǫ',
+ 'r' => 'ŕ',
+ 's' => 'š',
+ 't' => 'ţ',
+ 'u' => 'û',
+ 'v' => 'ṽ',
+ 'w' => 'ŵ',
+ 'x' => 'ẋ',
+ 'y' => 'ý',
+ 'z' => 'ž',
+ '{' => '(',
+ '|' => '¦',
+ '}' => ')',
+ '~' => '˞',
+ ]) : $text;
+ }
+
+ private function expand(string &$trans, string $visibleText): void
+ {
+ if (1.0 >= $this->expansionFactor) {
+ return;
+ }
+
+ $visibleLength = $this->strlen($visibleText);
+ $missingLength = (int) ceil($visibleLength * $this->expansionFactor) - $visibleLength;
+ if ($this->brackets) {
+ $missingLength -= 2;
+ }
+
+ if (0 >= $missingLength) {
+ return;
+ }
+
+ $words = [];
+ $wordsCount = 0;
+ foreach (preg_split('/ +/', $visibleText, -1, \PREG_SPLIT_NO_EMPTY) as $word) {
+ $wordLength = $this->strlen($word);
+
+ if ($wordLength >= $missingLength) {
+ continue;
+ }
+
+ if (!isset($words[$wordLength])) {
+ $words[$wordLength] = 0;
+ }
+
+ ++$words[$wordLength];
+ ++$wordsCount;
+ }
+
+ if (!$words) {
+ $trans .= 1 === $missingLength ? self::EXPANSION_CHARACTER : ' '.str_repeat(self::EXPANSION_CHARACTER, $missingLength - 1);
+
+ return;
+ }
+
+ arsort($words, \SORT_NUMERIC);
+
+ $longestWordLength = max(array_keys($words));
+
+ while (true) {
+ $r = mt_rand(1, $wordsCount);
+
+ foreach ($words as $length => $count) {
+ $r -= $count;
+ if ($r <= 0) {
+ break;
+ }
+ }
+
+ $trans .= ' '.str_repeat(self::EXPANSION_CHARACTER, $length);
+
+ $missingLength -= $length + 1;
+
+ if (0 === $missingLength) {
+ return;
+ }
+
+ while ($longestWordLength >= $missingLength) {
+ $wordsCount -= $words[$longestWordLength];
+ unset($words[$longestWordLength]);
+
+ if (!$words) {
+ $trans .= 1 === $missingLength ? self::EXPANSION_CHARACTER : ' '.str_repeat(self::EXPANSION_CHARACTER, $missingLength - 1);
+
+ return;
+ }
+
+ $longestWordLength = max(array_keys($words));
+ }
+ }
+ }
+
+ private function addBrackets(string &$trans): void
+ {
+ if (!$this->brackets) {
+ return;
+ }
+
+ $trans = '['.$trans.']';
+ }
+
+ private function strlen(string $s): int
+ {
+ return false === ($encoding = mb_detect_encoding($s, null, true)) ? \strlen($s) : mb_strlen($s, $encoding);
+ }
+}
diff --git a/vendor/symfony/translation/README.md b/vendor/symfony/translation/README.md
new file mode 100644
index 000000000..8c981a60e
--- /dev/null
+++ b/vendor/symfony/translation/README.md
@@ -0,0 +1,46 @@
+Translation Component
+=====================
+
+The Translation component provides tools to internationalize your application.
+
+Getting Started
+---------------
+
+```
+$ composer require symfony/translation
+```
+
+```php
+use Symfony\Component\Translation\Translator;
+use Symfony\Component\Translation\Loader\ArrayLoader;
+
+$translator = new Translator('fr_FR');
+$translator->addLoader('array', new ArrayLoader());
+$translator->addResource('array', [
+ 'Hello World!' => 'Bonjour !',
+], 'fr_FR');
+
+echo $translator->trans('Hello World!'); // outputs « Bonjour ! »
+```
+
+Sponsor
+-------
+
+The Translation component for Symfony 6.3 is [backed][1] by:
+
+ * [Crowdin][2], a cloud-based localization management software helping teams to go global and stay agile.
+
+Help Symfony by [sponsoring][3] its development!
+
+Resources
+---------
+
+ * [Documentation](https://symfony.com/doc/current/translation.html)
+ * [Contributing](https://symfony.com/doc/current/contributing/index.html)
+ * [Report issues](https://github.com/symfony/symfony/issues) and
+ [send Pull Requests](https://github.com/symfony/symfony/pulls)
+ in the [main Symfony repository](https://github.com/symfony/symfony)
+
+[1]: https://symfony.com/backers
+[2]: https://crowdin.com
+[3]: https://symfony.com/sponsor
diff --git a/vendor/symfony/translation/Reader/TranslationReader.php b/vendor/symfony/translation/Reader/TranslationReader.php
new file mode 100644
index 000000000..01408d4dc
--- /dev/null
+++ b/vendor/symfony/translation/Reader/TranslationReader.php
@@ -0,0 +1,64 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Reader;
+
+use Symfony\Component\Finder\Finder;
+use Symfony\Component\Translation\Loader\LoaderInterface;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * TranslationReader reads translation messages from translation files.
+ *
+ * @author Michel Salib
+ */
+class TranslationReader implements TranslationReaderInterface
+{
+ /**
+ * Loaders used for import.
+ *
+ * @var array
+ */
+ private array $loaders = [];
+
+ /**
+ * Adds a loader to the translation extractor.
+ *
+ * @param string $format The format of the loader
+ *
+ * @return void
+ */
+ public function addLoader(string $format, LoaderInterface $loader)
+ {
+ $this->loaders[$format] = $loader;
+ }
+
+ /**
+ * @return void
+ */
+ public function read(string $directory, MessageCatalogue $catalogue)
+ {
+ if (!is_dir($directory)) {
+ return;
+ }
+
+ foreach ($this->loaders as $format => $loader) {
+ // load any existing translation files
+ $finder = new Finder();
+ $extension = $catalogue->getLocale().'.'.$format;
+ $files = $finder->files()->name('*.'.$extension)->in($directory);
+ foreach ($files as $file) {
+ $domain = substr($file->getFilename(), 0, -1 * \strlen($extension) - 1);
+ $catalogue->addCatalogue($loader->load($file->getPathname(), $catalogue->getLocale(), $domain));
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/translation/Reader/TranslationReaderInterface.php b/vendor/symfony/translation/Reader/TranslationReaderInterface.php
new file mode 100644
index 000000000..ea74dc23f
--- /dev/null
+++ b/vendor/symfony/translation/Reader/TranslationReaderInterface.php
@@ -0,0 +1,29 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Reader;
+
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * TranslationReader reads translation messages from translation files.
+ *
+ * @author Tobias Nyholm
+ */
+interface TranslationReaderInterface
+{
+ /**
+ * Reads translation messages from a directory to the catalogue.
+ *
+ * @return void
+ */
+ public function read(string $directory, MessageCatalogue $catalogue);
+}
diff --git a/vendor/symfony/translation/Resources/bin/translation-status.php b/vendor/symfony/translation/Resources/bin/translation-status.php
new file mode 100644
index 000000000..8064190d8
--- /dev/null
+++ b/vendor/symfony/translation/Resources/bin/translation-status.php
@@ -0,0 +1,274 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+if ('cli' !== \PHP_SAPI) {
+ throw new Exception('This script must be run from the command line.');
+}
+
+$usageInstructions = << false,
+ // NULL = analyze all locales
+ 'locale_to_analyze' => null,
+ // append --incomplete to only show incomplete languages
+ 'include_completed_languages' => true,
+ // the reference files all the other translations are compared to
+ 'original_files' => [
+ 'src/Symfony/Component/Form/Resources/translations/validators.en.xlf',
+ 'src/Symfony/Component/Security/Core/Resources/translations/security.en.xlf',
+ 'src/Symfony/Component/Validator/Resources/translations/validators.en.xlf',
+ ],
+];
+
+$argc = $_SERVER['argc'];
+$argv = $_SERVER['argv'];
+
+if ($argc > 4) {
+ echo str_replace('translation-status.php', $argv[0], $usageInstructions);
+ exit(1);
+}
+
+foreach (array_slice($argv, 1) as $argumentOrOption) {
+ if ('--incomplete' === $argumentOrOption) {
+ $config['include_completed_languages'] = false;
+ continue;
+ }
+
+ if (str_starts_with($argumentOrOption, '-')) {
+ $config['verbose_output'] = true;
+ } else {
+ $config['locale_to_analyze'] = $argumentOrOption;
+ }
+}
+
+foreach ($config['original_files'] as $originalFilePath) {
+ if (!file_exists($originalFilePath)) {
+ echo sprintf('The following file does not exist. Make sure that you execute this command at the root dir of the Symfony code repository.%s %s', \PHP_EOL, $originalFilePath);
+ exit(1);
+ }
+}
+
+$totalMissingTranslations = 0;
+$totalTranslationMismatches = 0;
+
+foreach ($config['original_files'] as $originalFilePath) {
+ $translationFilePaths = findTranslationFiles($originalFilePath, $config['locale_to_analyze']);
+ $translationStatus = calculateTranslationStatus($originalFilePath, $translationFilePaths);
+
+ $totalMissingTranslations += array_sum(array_map(fn ($translation) => count($translation['missingKeys']), array_values($translationStatus)));
+ $totalTranslationMismatches += array_sum(array_map(fn ($translation) => count($translation['mismatches']), array_values($translationStatus)));
+
+ printTranslationStatus($originalFilePath, $translationStatus, $config['verbose_output'], $config['include_completed_languages']);
+}
+
+exit($totalTranslationMismatches > 0 ? 1 : 0);
+
+function findTranslationFiles($originalFilePath, $localeToAnalyze): array
+{
+ $translations = [];
+
+ $translationsDir = dirname($originalFilePath);
+ $originalFileName = basename($originalFilePath);
+ $translationFileNamePattern = str_replace('.en.', '.*.', $originalFileName);
+
+ $translationFiles = glob($translationsDir.'/'.$translationFileNamePattern, \GLOB_NOSORT);
+ sort($translationFiles);
+ foreach ($translationFiles as $filePath) {
+ $locale = extractLocaleFromFilePath($filePath);
+
+ if (null !== $localeToAnalyze && $locale !== $localeToAnalyze) {
+ continue;
+ }
+
+ $translations[$locale] = $filePath;
+ }
+
+ return $translations;
+}
+
+function calculateTranslationStatus($originalFilePath, $translationFilePaths): array
+{
+ $translationStatus = [];
+ $allTranslationKeys = extractTranslationKeys($originalFilePath);
+
+ foreach ($translationFilePaths as $locale => $translationPath) {
+ $translatedKeys = extractTranslationKeys($translationPath);
+ $missingKeys = array_diff_key($allTranslationKeys, $translatedKeys);
+ $mismatches = findTransUnitMismatches($allTranslationKeys, $translatedKeys);
+
+ $translationStatus[$locale] = [
+ 'total' => count($allTranslationKeys),
+ 'translated' => count($translatedKeys),
+ 'missingKeys' => $missingKeys,
+ 'mismatches' => $mismatches,
+ ];
+ $translationStatus[$locale]['is_completed'] = isTranslationCompleted($translationStatus[$locale]);
+ }
+
+ return $translationStatus;
+}
+
+function isTranslationCompleted(array $translationStatus): bool
+{
+ return $translationStatus['total'] === $translationStatus['translated'] && 0 === count($translationStatus['mismatches']);
+}
+
+function printTranslationStatus($originalFilePath, $translationStatus, $verboseOutput, $includeCompletedLanguages)
+{
+ printTitle($originalFilePath);
+ printTable($translationStatus, $verboseOutput, $includeCompletedLanguages);
+ echo \PHP_EOL.\PHP_EOL;
+}
+
+function extractLocaleFromFilePath($filePath)
+{
+ $parts = explode('.', $filePath);
+
+ return $parts[count($parts) - 2];
+}
+
+function extractTranslationKeys($filePath): array
+{
+ $translationKeys = [];
+ $contents = new \SimpleXMLElement(file_get_contents($filePath));
+
+ foreach ($contents->file->body->{'trans-unit'} as $translationKey) {
+ $translationId = (string) $translationKey['id'];
+ $translationKey = (string) $translationKey->source;
+
+ $translationKeys[$translationId] = $translationKey;
+ }
+
+ return $translationKeys;
+}
+
+/**
+ * Check whether the trans-unit id and source match with the base translation.
+ */
+function findTransUnitMismatches(array $baseTranslationKeys, array $translatedKeys): array
+{
+ $mismatches = [];
+
+ foreach ($baseTranslationKeys as $translationId => $translationKey) {
+ if (!isset($translatedKeys[$translationId])) {
+ continue;
+ }
+ if ($translatedKeys[$translationId] !== $translationKey) {
+ $mismatches[$translationId] = [
+ 'found' => $translatedKeys[$translationId],
+ 'expected' => $translationKey,
+ ];
+ }
+ }
+
+ return $mismatches;
+}
+
+function printTitle($title)
+{
+ echo $title.\PHP_EOL;
+ echo str_repeat('=', strlen($title)).\PHP_EOL.\PHP_EOL;
+}
+
+function printTable($translations, $verboseOutput, bool $includeCompletedLanguages)
+{
+ if (0 === count($translations)) {
+ echo 'No translations found';
+
+ return;
+ }
+ $longestLocaleNameLength = max(array_map('strlen', array_keys($translations)));
+
+ foreach ($translations as $locale => $translation) {
+ if (!$includeCompletedLanguages && $translation['is_completed']) {
+ continue;
+ }
+
+ if ($translation['translated'] > $translation['total']) {
+ textColorRed();
+ } elseif (count($translation['mismatches']) > 0) {
+ textColorRed();
+ } elseif ($translation['is_completed']) {
+ textColorGreen();
+ }
+
+ echo sprintf(
+ '| Locale: %-'.$longestLocaleNameLength.'s | Translated: %2d/%2d | Mismatches: %d |',
+ $locale,
+ $translation['translated'],
+ $translation['total'],
+ count($translation['mismatches'])
+ ).\PHP_EOL;
+
+ textColorNormal();
+
+ $shouldBeClosed = false;
+ if (true === $verboseOutput && count($translation['missingKeys']) > 0) {
+ echo '| Missing Translations:'.\PHP_EOL;
+
+ foreach ($translation['missingKeys'] as $id => $content) {
+ echo sprintf('| (id=%s) %s', $id, $content).\PHP_EOL;
+ }
+ $shouldBeClosed = true;
+ }
+ if (true === $verboseOutput && count($translation['mismatches']) > 0) {
+ echo '| Mismatches between trans-unit id and source:'.\PHP_EOL;
+
+ foreach ($translation['mismatches'] as $id => $content) {
+ echo sprintf('| (id=%s) Expected: %s', $id, $content['expected']).\PHP_EOL;
+ echo sprintf('| Found: %s', $content['found']).\PHP_EOL;
+ }
+ $shouldBeClosed = true;
+ }
+ if ($shouldBeClosed) {
+ echo str_repeat('-', 80).\PHP_EOL;
+ }
+ }
+}
+
+function textColorGreen()
+{
+ echo "\033[32m";
+}
+
+function textColorRed()
+{
+ echo "\033[31m";
+}
+
+function textColorNormal()
+{
+ echo "\033[0m";
+}
diff --git a/vendor/symfony/translation/Resources/data/parents.json b/vendor/symfony/translation/Resources/data/parents.json
new file mode 100644
index 000000000..32a33cdaf
--- /dev/null
+++ b/vendor/symfony/translation/Resources/data/parents.json
@@ -0,0 +1,141 @@
+{
+ "az_Cyrl": "root",
+ "bs_Cyrl": "root",
+ "en_150": "en_001",
+ "en_AG": "en_001",
+ "en_AI": "en_001",
+ "en_AT": "en_150",
+ "en_AU": "en_001",
+ "en_BB": "en_001",
+ "en_BE": "en_150",
+ "en_BM": "en_001",
+ "en_BS": "en_001",
+ "en_BW": "en_001",
+ "en_BZ": "en_001",
+ "en_CC": "en_001",
+ "en_CH": "en_150",
+ "en_CK": "en_001",
+ "en_CM": "en_001",
+ "en_CX": "en_001",
+ "en_CY": "en_001",
+ "en_DE": "en_150",
+ "en_DG": "en_001",
+ "en_DK": "en_150",
+ "en_DM": "en_001",
+ "en_ER": "en_001",
+ "en_FI": "en_150",
+ "en_FJ": "en_001",
+ "en_FK": "en_001",
+ "en_FM": "en_001",
+ "en_GB": "en_001",
+ "en_GD": "en_001",
+ "en_GG": "en_001",
+ "en_GH": "en_001",
+ "en_GI": "en_001",
+ "en_GM": "en_001",
+ "en_GY": "en_001",
+ "en_HK": "en_001",
+ "en_IE": "en_001",
+ "en_IL": "en_001",
+ "en_IM": "en_001",
+ "en_IN": "en_001",
+ "en_IO": "en_001",
+ "en_JE": "en_001",
+ "en_JM": "en_001",
+ "en_KE": "en_001",
+ "en_KI": "en_001",
+ "en_KN": "en_001",
+ "en_KY": "en_001",
+ "en_LC": "en_001",
+ "en_LR": "en_001",
+ "en_LS": "en_001",
+ "en_MG": "en_001",
+ "en_MO": "en_001",
+ "en_MS": "en_001",
+ "en_MT": "en_001",
+ "en_MU": "en_001",
+ "en_MV": "en_001",
+ "en_MW": "en_001",
+ "en_MY": "en_001",
+ "en_NA": "en_001",
+ "en_NF": "en_001",
+ "en_NG": "en_001",
+ "en_NL": "en_150",
+ "en_NR": "en_001",
+ "en_NU": "en_001",
+ "en_NZ": "en_001",
+ "en_PG": "en_001",
+ "en_PK": "en_001",
+ "en_PN": "en_001",
+ "en_PW": "en_001",
+ "en_RW": "en_001",
+ "en_SB": "en_001",
+ "en_SC": "en_001",
+ "en_SD": "en_001",
+ "en_SE": "en_150",
+ "en_SG": "en_001",
+ "en_SH": "en_001",
+ "en_SI": "en_150",
+ "en_SL": "en_001",
+ "en_SS": "en_001",
+ "en_SX": "en_001",
+ "en_SZ": "en_001",
+ "en_TC": "en_001",
+ "en_TK": "en_001",
+ "en_TO": "en_001",
+ "en_TT": "en_001",
+ "en_TV": "en_001",
+ "en_TZ": "en_001",
+ "en_UG": "en_001",
+ "en_VC": "en_001",
+ "en_VG": "en_001",
+ "en_VU": "en_001",
+ "en_WS": "en_001",
+ "en_ZA": "en_001",
+ "en_ZM": "en_001",
+ "en_ZW": "en_001",
+ "es_AR": "es_419",
+ "es_BO": "es_419",
+ "es_BR": "es_419",
+ "es_BZ": "es_419",
+ "es_CL": "es_419",
+ "es_CO": "es_419",
+ "es_CR": "es_419",
+ "es_CU": "es_419",
+ "es_DO": "es_419",
+ "es_EC": "es_419",
+ "es_GT": "es_419",
+ "es_HN": "es_419",
+ "es_MX": "es_419",
+ "es_NI": "es_419",
+ "es_PA": "es_419",
+ "es_PE": "es_419",
+ "es_PR": "es_419",
+ "es_PY": "es_419",
+ "es_SV": "es_419",
+ "es_US": "es_419",
+ "es_UY": "es_419",
+ "es_VE": "es_419",
+ "ff_Adlm": "root",
+ "hi_Latn": "en_IN",
+ "ks_Deva": "root",
+ "nb": "no",
+ "nn": "no",
+ "pa_Arab": "root",
+ "pt_AO": "pt_PT",
+ "pt_CH": "pt_PT",
+ "pt_CV": "pt_PT",
+ "pt_GQ": "pt_PT",
+ "pt_GW": "pt_PT",
+ "pt_LU": "pt_PT",
+ "pt_MO": "pt_PT",
+ "pt_MZ": "pt_PT",
+ "pt_ST": "pt_PT",
+ "pt_TL": "pt_PT",
+ "sd_Deva": "root",
+ "sr_Latn": "root",
+ "uz_Arab": "root",
+ "uz_Cyrl": "root",
+ "zh_Hant": "root",
+ "zh_Hant_MO": "zh_Hant_HK"
+}
diff --git a/vendor/symfony/translation/Resources/functions.php b/vendor/symfony/translation/Resources/functions.php
new file mode 100644
index 000000000..901d2f87e
--- /dev/null
+++ b/vendor/symfony/translation/Resources/functions.php
@@ -0,0 +1,22 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+if (!\function_exists(t::class)) {
+ /**
+ * @author Nate Wiebe
+ */
+ function t(string $message, array $parameters = [], string $domain = null): TranslatableMessage
+ {
+ return new TranslatableMessage($message, $parameters, $domain);
+ }
+}
diff --git a/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-transitional.xsd b/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-transitional.xsd
new file mode 100644
index 000000000..1f38de72f
--- /dev/null
+++ b/vendor/symfony/translation/Resources/schemas/xliff-core-1.2-transitional.xsd
@@ -0,0 +1,2261 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Values for the attribute 'context-type'.
+
+
+
+
+ Indicates a database content.
+
+
+
+
+ Indicates the content of an element within an XML document.
+
+
+
+
+ Indicates the name of an element within an XML document.
+
+
+
+
+ Indicates the line number from the sourcefile (see context-type="sourcefile") where the <source> is found.
+
+
+
+
+ Indicates a the number of parameters contained within the <source>.
+
+
+
+
+ Indicates notes pertaining to the parameters in the <source>.
+
+
+
+
+ Indicates the content of a record within a database.
+
+
+
+
+ Indicates the name of a record within a database.
+
+
+
+
+ Indicates the original source file in the case that multiple files are merged to form the original file from which the XLIFF file is created. This differs from the original <file> attribute in that this sourcefile is one of many that make up that file.
+
+
+
+
+
+
+ Values for the attribute 'count-type'.
+
+
+
+
+ Indicates the count units are items that are used X times in a certain context; example: this is a reusable text unit which is used 42 times in other texts.
+
+
+
+
+ Indicates the count units are translation units existing already in the same document.
+
+
+
+
+ Indicates a total count.
+
+
+
+
+
+
+ Values for the attribute 'ctype' when used other elements than <ph> or <x>.
+
+
+
+
+ Indicates a run of bolded text.
+
+
+
+
+ Indicates a run of text in italics.
+
+
+
+
+ Indicates a run of underlined text.
+
+
+
+
+ Indicates a run of hyper-text.
+
+
+
+
+
+
+ Values for the attribute 'ctype' when used with <ph> or <x>.
+
+
+
+
+ Indicates a inline image.
+
+
+
+
+ Indicates a page break.
+
+
+
+
+ Indicates a line break.
+
+
+
+
+
+
+
+
+
+
+
+ Values for the attribute 'datatype'.
+
+
+
+
+ Indicates Active Server Page data.
+
+
+
+
+ Indicates C source file data.
+
+
+
+
+ Indicates Channel Definition Format (CDF) data.
+
+
+
+
+ Indicates ColdFusion data.
+
+
+
+
+ Indicates C++ source file data.
+
+
+
+
+ Indicates C-Sharp data.
+
+
+
+
+ Indicates strings from C, ASM, and driver files data.
+
+
+
+
+ Indicates comma-separated values data.
+
+
+
+
+ Indicates database data.
+
+
+
+
+ Indicates portions of document that follows data and contains metadata.
+
+
+
+
+ Indicates portions of document that precedes data and contains metadata.
+
+
+
+
+ Indicates data from standard UI file operations dialogs (e.g., Open, Save, Save As, Export, Import).
+
+
+
+
+ Indicates standard user input screen data.
+
+
+
+
+ Indicates HyperText Markup Language (HTML) data - document instance.
+
+
+
+
+ Indicates content within an HTML document’s <body> element.
+
+
+
+
+ Indicates Windows INI file data.
+
+
+
+
+ Indicates Interleaf data.
+
+
+
+
+ Indicates Java source file data (extension '.java').
+
+
+
+
+ Indicates Java property resource bundle data.
+
+
+
+
+ Indicates Java list resource bundle data.
+
+
+
+
+ Indicates JavaScript source file data.
+
+
+
+
+ Indicates JScript source file data.
+
+
+
+
+ Indicates information relating to formatting.
+
+
+
+
+ Indicates LISP source file data.
+
+
+
+
+ Indicates information relating to margin formats.
+
+
+
+
+ Indicates a file containing menu.
+
+
+
+
+ Indicates numerically identified string table.
+
+
+
+
+ Indicates Maker Interchange Format (MIF) data.
+
+
+
+
+ Indicates that the datatype attribute value is a MIME Type value and is defined in the mime-type attribute.
+
+
+
+
+ Indicates GNU Machine Object data.
+
+
+
+
+ Indicates Message Librarian strings created by Novell's Message Librarian Tool.
+
+
+
+
+ Indicates information to be displayed at the bottom of each page of a document.
+
+
+
+
+ Indicates information to be displayed at the top of each page of a document.
+
+
+
+
+ Indicates a list of property values (e.g., settings within INI files or preferences dialog).
+
+
+
+
+ Indicates Pascal source file data.
+
+
+
+
+ Indicates Hypertext Preprocessor data.
+
+
+
+
+ Indicates plain text file (no formatting other than, possibly, wrapping).
+
+
+
+
+ Indicates GNU Portable Object file.
+
+
+
+
+ Indicates dynamically generated user defined document. e.g. Oracle Report, Crystal Report, etc.
+
+
+
+
+ Indicates Windows .NET binary resources.
+
+
+
+
+ Indicates Windows .NET Resources.
+
+
+
+
+ Indicates Rich Text Format (RTF) data.
+
+
+
+
+ Indicates Standard Generalized Markup Language (SGML) data - document instance.
+
+
+
+
+ Indicates Standard Generalized Markup Language (SGML) data - Document Type Definition (DTD).
+
+
+
+
+ Indicates Scalable Vector Graphic (SVG) data.
+
+
+
+
+ Indicates VisualBasic Script source file.
+
+
+
+
+ Indicates warning message.
+
+
+
+
+ Indicates Windows (Win32) resources (i.e. resources extracted from an RC script, a message file, or a compiled file).
+
+
+
+
+ Indicates Extensible HyperText Markup Language (XHTML) data - document instance.
+
+
+
+
+ Indicates Extensible Markup Language (XML) data - document instance.
+
+
+
+
+ Indicates Extensible Markup Language (XML) data - Document Type Definition (DTD).
+
+
+
+
+ Indicates Extensible Stylesheet Language (XSL) data.
+
+
+
+
+ Indicates XUL elements.
+
+
+
+
+
+
+ Values for the attribute 'mtype'.
+
+
+
+
+ Indicates the marked text is an abbreviation.
+
+
+
+
+ ISO-12620 2.1.8: A term resulting from the omission of any part of the full term while designating the same concept.
+
+
+
+
+ ISO-12620 2.1.8.1: An abbreviated form of a simple term resulting from the omission of some of its letters (e.g. 'adj.' for 'adjective').
+
+
+
+
+ ISO-12620 2.1.8.4: An abbreviated form of a term made up of letters from the full form of a multiword term strung together into a sequence pronounced only syllabically (e.g. 'radar' for 'radio detecting and ranging').
+
+
+
+
+ ISO-12620: A proper-name term, such as the name of an agency or other proper entity.
+
+
+
+
+ ISO-12620 2.1.18.1: A recurrent word combination characterized by cohesion in that the components of the collocation must co-occur within an utterance or series of utterances, even though they do not necessarily have to maintain immediate proximity to one another.
+
+
+
+
+ ISO-12620 2.1.5: A synonym for an international scientific term that is used in general discourse in a given language.
+
+
+
+
+ Indicates the marked text is a date and/or time.
+
+
+
+
+ ISO-12620 2.1.15: An expression used to represent a concept based on a statement that two mathematical expressions are, for instance, equal as identified by the equal sign (=), or assigned to one another by a similar sign.
+
+
+
+
+ ISO-12620 2.1.7: The complete representation of a term for which there is an abbreviated form.
+
+
+
+
+ ISO-12620 2.1.14: Figures, symbols or the like used to express a concept briefly, such as a mathematical or chemical formula.
+
+
+
+
+ ISO-12620 2.1.1: The concept designation that has been chosen to head a terminological record.
+
+
+
+
+ ISO-12620 2.1.8.3: An abbreviated form of a term consisting of some of the initial letters of the words making up a multiword term or the term elements making up a compound term when these letters are pronounced individually (e.g. 'BSE' for 'bovine spongiform encephalopathy').
+
+
+
+
+ ISO-12620 2.1.4: A term that is part of an international scientific nomenclature as adopted by an appropriate scientific body.
+
+
+
+
+ ISO-12620 2.1.6: A term that has the same or nearly identical orthographic or phonemic form in many languages.
+
+
+
+
+ ISO-12620 2.1.16: An expression used to represent a concept based on mathematical or logical relations, such as statements of inequality, set relationships, Boolean operations, and the like.
+
+
+
+
+ ISO-12620 2.1.17: A unit to track object.
+
+
+
+
+ Indicates the marked text is a name.
+
+
+
+
+ ISO-12620 2.1.3: A term that represents the same or a very similar concept as another term in the same language, but for which interchangeability is limited to some contexts and inapplicable in others.
+
+
+
+
+ ISO-12620 2.1.17.2: A unique alphanumeric designation assigned to an object in a manufacturing system.
+
+
+
+
+ Indicates the marked text is a phrase.
+
+
+
+
+ ISO-12620 2.1.18: Any group of two or more words that form a unit, the meaning of which frequently cannot be deduced based on the combined sense of the words making up the phrase.
+
+
+
+
+ Indicates the marked text should not be translated.
+
+
+
+
+ ISO-12620 2.1.12: A form of a term resulting from an operation whereby non-Latin writing systems are converted to the Latin alphabet.
+
+
+
+
+ Indicates that the marked text represents a segment.
+
+
+
+
+ ISO-12620 2.1.18.2: A fixed, lexicalized phrase.
+
+
+
+
+ ISO-12620 2.1.8.2: A variant of a multiword term that includes fewer words than the full form of the term (e.g. 'Group of Twenty-four' for 'Intergovernmental Group of Twenty-four on International Monetary Affairs').
+
+
+
+
+ ISO-12620 2.1.17.1: Stock keeping unit, an inventory item identified by a unique alphanumeric designation assigned to an object in an inventory control system.
+
+
+
+
+ ISO-12620 2.1.19: A fixed chunk of recurring text.
+
+
+
+
+ ISO-12620 2.1.13: A designation of a concept by letters, numerals, pictograms or any combination thereof.
+
+
+
+
+ ISO-12620 2.1.2: Any term that represents the same or a very similar concept as the main entry term in a term entry.
+
+
+
+
+ ISO-12620 2.1.18.3: Phraseological unit in a language that expresses the same semantic content as another phrase in that same language.
+
+
+
+
+ Indicates the marked text is a term.
+
+
+
+
+ ISO-12620 2.1.11: A form of a term resulting from an operation whereby the characters of one writing system are represented by characters from another writing system, taking into account the pronunciation of the characters converted.
+
+
+
+
+ ISO-12620 2.1.10: A form of a term resulting from an operation whereby the characters of an alphabetic writing system are represented by characters from another alphabetic writing system.
+
+
+
+
+ ISO-12620 2.1.8.5: An abbreviated form of a term resulting from the omission of one or more term elements or syllables (e.g. 'flu' for 'influenza').
+
+
+
+
+ ISO-12620 2.1.9: One of the alternate forms of a term.
+
+
+
+
+
+
+ Values for the attribute 'restype'.
+
+
+
+
+ Indicates a Windows RC AUTO3STATE control.
+
+
+
+
+ Indicates a Windows RC AUTOCHECKBOX control.
+
+
+
+
+ Indicates a Windows RC AUTORADIOBUTTON control.
+
+
+
+
+ Indicates a Windows RC BEDIT control.
+
+
+
+
+ Indicates a bitmap, for example a BITMAP resource in Windows.
+
+
+
+
+ Indicates a button object, for example a BUTTON control Windows.
+
+
+
+
+ Indicates a caption, such as the caption of a dialog box.
+
+
+
+
+ Indicates the cell in a table, for example the content of the <td> element in HTML.
+
+
+
+
+ Indicates check box object, for example a CHECKBOX control in Windows.
+
+
+
+
+ Indicates a menu item with an associated checkbox.
+
+
+
+
+ Indicates a list box, but with a check-box for each item.
+
+
+
+
+ Indicates a color selection dialog.
+
+
+
+
+ Indicates a combination of edit box and listbox object, for example a COMBOBOX control in Windows.
+
+
+
+
+ Indicates an initialization entry of an extended combobox DLGINIT resource block. (code 0x1234).
+
+
+
+
+ Indicates an initialization entry of a combobox DLGINIT resource block (code 0x0403).
+
+
+
+
+ Indicates a UI base class element that cannot be represented by any other element.
+
+
+
+
+ Indicates a context menu.
+
+
+
+
+ Indicates a Windows RC CTEXT control.
+
+
+
+
+ Indicates a cursor, for example a CURSOR resource in Windows.
+
+
+
+
+ Indicates a date/time picker.
+
+
+
+
+ Indicates a Windows RC DEFPUSHBUTTON control.
+
+
+
+
+ Indicates a dialog box.
+
+
+
+
+ Indicates a Windows RC DLGINIT resource block.
+
+
+
+
+ Indicates an edit box object, for example an EDIT control in Windows.
+
+
+
+
+ Indicates a filename.
+
+
+
+
+ Indicates a file dialog.
+
+
+
+
+ Indicates a footnote.
+
+
+
+
+ Indicates a font name.
+
+
+
+
+ Indicates a footer.
+
+
+
+
+ Indicates a frame object.
+
+
+
+
+ Indicates a XUL grid element.
+
+
+
+
+ Indicates a groupbox object, for example a GROUPBOX control in Windows.
+
+
+
+
+ Indicates a header item.
+
+
+
+
+ Indicates a heading, such has the content of <h1>, <h2>, etc. in HTML.
+
+
+
+
+ Indicates a Windows RC HEDIT control.
+
+
+
+
+ Indicates a horizontal scrollbar.
+
+
+
+
+ Indicates an icon, for example an ICON resource in Windows.
+
+
+
+
+ Indicates a Windows RC IEDIT control.
+
+
+
+
+ Indicates keyword list, such as the content of the Keywords meta-data in HTML, or a K footnote in WinHelp RTF.
+
+
+
+
+ Indicates a label object.
+
+
+
+
+ Indicates a label that is also a HTML link (not necessarily a URL).
+
+
+
+
+ Indicates a list (a group of list-items, for example an <ol> or <ul> element in HTML).
+
+
+
+
+ Indicates a listbox object, for example an LISTBOX control in Windows.
+
+
+
+
+ Indicates an list item (an entry in a list).
+
+
+
+
+ Indicates a Windows RC LTEXT control.
+
+
+
+
+ Indicates a menu (a group of menu-items).
+
+
+
+
+ Indicates a toolbar containing one or more tope level menus.
+
+
+
+
+ Indicates a menu item (an entry in a menu).
+
+
+
+
+ Indicates a XUL menuseparator element.
+
+
+
+
+ Indicates a message, for example an entry in a MESSAGETABLE resource in Windows.
+
+
+
+
+ Indicates a calendar control.
+
+
+
+
+ Indicates an edit box beside a spin control.
+
+
+
+
+ Indicates a catch all for rectangular areas.
+
+
+
+
+ Indicates a standalone menu not necessarily associated with a menubar.
+
+
+
+
+ Indicates a pushbox object, for example a PUSHBOX control in Windows.
+
+
+
+
+ Indicates a Windows RC PUSHBUTTON control.
+
+
+
+
+ Indicates a radio button object.
+
+
+
+
+ Indicates a menuitem with associated radio button.
+
+
+
+
+ Indicates raw data resources for an application.
+
+
+
+
+ Indicates a row in a table.
+
+
+
+
+ Indicates a Windows RC RTEXT control.
+
+
+
+
+ Indicates a user navigable container used to show a portion of a document.
+
+
+
+
+ Indicates a generic divider object (e.g. menu group separator).
+
+
+
+
+ Windows accelerators, shortcuts in resource or property files.
+
+
+
+
+ Indicates a UI control to indicate process activity but not progress.
+
+
+
+
+ Indicates a splitter bar.
+
+
+
+
+ Indicates a Windows RC STATE3 control.
+
+
+
+
+ Indicates a window for providing feedback to the users, like 'read-only', etc.
+
+
+
+
+ Indicates a string, for example an entry in a STRINGTABLE resource in Windows.
+
+
+
+
+ Indicates a layers of controls with a tab to select layers.
+
+
+
+
+ Indicates a display and edits regular two-dimensional tables of cells.
+
+
+
+
+ Indicates a XUL textbox element.
+
+
+
+
+ Indicates a UI button that can be toggled to on or off state.
+
+
+
+
+ Indicates an array of controls, usually buttons.
+
+
+
+
+ Indicates a pop up tool tip text.
+
+
+
+
+ Indicates a bar with a pointer indicating a position within a certain range.
+
+
+
+
+ Indicates a control that displays a set of hierarchical data.
+
+
+
+
+ Indicates a URI (URN or URL).
+
+
+
+
+ Indicates a Windows RC USERBUTTON control.
+
+
+
+
+ Indicates a user-defined control like CONTROL control in Windows.
+
+
+
+
+ Indicates the text of a variable.
+
+
+
+
+ Indicates version information about a resource like VERSIONINFO in Windows.
+
+
+
+
+ Indicates a vertical scrollbar.
+
+
+
+
+ Indicates a graphical window.
+
+
+
+
+
+
+ Values for the attribute 'size-unit'.
+
+
+
+
+ Indicates a size in 8-bit bytes.
+
+
+
+
+ Indicates a size in Unicode characters.
+
+
+
+
+ Indicates a size in columns. Used for HTML text area.
+
+
+
+
+ Indicates a size in centimeters.
+
+
+
+
+ Indicates a size in dialog units, as defined in Windows resources.
+
+
+
+
+ Indicates a size in 'font-size' units (as defined in CSS).
+
+
+
+
+ Indicates a size in 'x-height' units (as defined in CSS).
+
+
+
+
+ Indicates a size in glyphs. A glyph is considered to be one or more combined Unicode characters that represent a single displayable text character. Sometimes referred to as a 'grapheme cluster'
+
+
+
+
+ Indicates a size in inches.
+
+
+
+
+ Indicates a size in millimeters.
+
+
+
+
+ Indicates a size in percentage.
+
+
+
+
+ Indicates a size in pixels.
+
+
+
+
+ Indicates a size in point.
+
+
+
+
+ Indicates a size in rows. Used for HTML text area.
+
+
+
+
+
+
+ Values for the attribute 'state'.
+
+
+
+
+ Indicates the terminating state.
+
+
+
+
+ Indicates only non-textual information needs adaptation.
+
+
+
+
+ Indicates both text and non-textual information needs adaptation.
+
+
+
+
+ Indicates only non-textual information needs review.
+
+
+
+
+ Indicates both text and non-textual information needs review.
+
+
+
+
+ Indicates that only the text of the item needs to be reviewed.
+
+
+
+
+ Indicates that the item needs to be translated.
+
+
+
+
+ Indicates that the item is new. For example, translation units that were not in a previous version of the document.
+
+
+
+
+ Indicates that changes are reviewed and approved.
+
+
+
+
+ Indicates that the item has been translated.
+
+
+
+
+
+
+ Values for the attribute 'state-qualifier'.
+
+
+
+
+ Indicates an exact match. An exact match occurs when a source text of a segment is exactly the same as the source text of a segment that was translated previously.
+
+
+
+
+ Indicates a fuzzy match. A fuzzy match occurs when a source text of a segment is very similar to the source text of a segment that was translated previously (e.g. when the difference is casing, a few changed words, white-space discripancy, etc.).
+
+
+
+
+ Indicates a match based on matching IDs (in addition to matching text).
+
+
+
+
+ Indicates a translation derived from a glossary.
+
+
+
+
+ Indicates a translation derived from existing translation.
+
+
+
+
+ Indicates a translation derived from machine translation.
+
+
+
+
+ Indicates a translation derived from a translation repository.
+
+
+
+
+ Indicates a translation derived from a translation memory.
+
+
+
+
+ Indicates the translation is suggested by machine translation.
+
+
+
+
+ Indicates that the item has been rejected because of incorrect grammar.
+
+
+
+
+ Indicates that the item has been rejected because it is incorrect.
+
+
+
+
+ Indicates that the item has been rejected because it is too long or too short.
+
+
+
+
+ Indicates that the item has been rejected because of incorrect spelling.
+
+
+
+
+ Indicates the translation is suggested by translation memory.
+
+
+
+
+
+
+ Values for the attribute 'unit'.
+
+
+
+
+ Refers to words.
+
+
+
+
+ Refers to pages.
+
+
+
+
+ Refers to <trans-unit> elements.
+
+
+
+
+ Refers to <bin-unit> elements.
+
+
+
+
+ Refers to glyphs.
+
+
+
+
+ Refers to <trans-unit> and/or <bin-unit> elements.
+
+
+
+
+ Refers to the occurrences of instances defined by the count-type value.
+
+
+
+
+ Refers to characters.
+
+
+
+
+ Refers to lines.
+
+
+
+
+ Refers to sentences.
+
+
+
+
+ Refers to paragraphs.
+
+
+
+
+ Refers to segments.
+
+
+
+
+ Refers to placeables (inline elements).
+
+
+
+
+
+
+ Values for the attribute 'priority'.
+
+
+
+
+ Highest priority.
+
+
+
+
+ High priority.
+
+
+
+
+ High priority, but not as important as 2.
+
+
+
+
+ High priority, but not as important as 3.
+
+
+
+
+ Medium priority, but more important than 6.
+
+
+
+
+ Medium priority, but less important than 5.
+
+
+
+
+ Low priority, but more important than 8.
+
+
+
+
+ Low priority, but more important than 9.
+
+
+
+
+ Low priority.
+
+
+
+
+ Lowest priority.
+
+
+
+
+
+
+
+
+ This value indicates that all properties can be reformatted. This value must be used alone.
+
+
+
+
+ This value indicates that no properties should be reformatted. This value must be used alone.
+
+
+
+
+
+
+
+
+
+
+
+
+ This value indicates that all information in the coord attribute can be modified.
+
+
+
+
+ This value indicates that the x information in the coord attribute can be modified.
+
+
+
+
+ This value indicates that the y information in the coord attribute can be modified.
+
+
+
+
+ This value indicates that the cx information in the coord attribute can be modified.
+
+
+
+
+ This value indicates that the cy information in the coord attribute can be modified.
+
+
+
+
+ This value indicates that all the information in the font attribute can be modified.
+
+
+
+
+ This value indicates that the name information in the font attribute can be modified.
+
+
+
+
+ This value indicates that the size information in the font attribute can be modified.
+
+
+
+
+ This value indicates that the weight information in the font attribute can be modified.
+
+
+
+
+ This value indicates that the information in the css-style attribute can be modified.
+
+
+
+
+ This value indicates that the information in the style attribute can be modified.
+
+
+
+
+ This value indicates that the information in the exstyle attribute can be modified.
+
+
+
+
+
+
+
+
+
+
+
+
+ Indicates that the context is informational in nature, specifying for example, how a term should be translated. Thus, should be displayed to anyone editing the XLIFF document.
+
+
+
+
+ Indicates that the context-group is used to specify where the term was found in the translatable source. Thus, it is not displayed.
+
+
+
+
+ Indicates that the context information should be used during translation memory lookups. Thus, it is not displayed.
+
+
+
+
+
+
+
+
+ Represents a translation proposal from a translation memory or other resource.
+
+
+
+
+ Represents a previous version of the target element.
+
+
+
+
+ Represents a rejected version of the target element.
+
+
+
+
+ Represents a translation to be used for reference purposes only, for example from a related product or a different language.
+
+
+
+
+ Represents a proposed translation that was used for the translation of the trans-unit, possibly modified.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Values for the attribute 'coord'.
+
+
+
+
+
+
+
+ Version values: 1.0 and 1.1 are allowed for backward compatibility.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vendor/symfony/translation/Resources/schemas/xliff-core-2.0.xsd b/vendor/symfony/translation/Resources/schemas/xliff-core-2.0.xsd
new file mode 100644
index 000000000..963232f97
--- /dev/null
+++ b/vendor/symfony/translation/Resources/schemas/xliff-core-2.0.xsd
@@ -0,0 +1,411 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/vendor/symfony/translation/Resources/schemas/xml.xsd b/vendor/symfony/translation/Resources/schemas/xml.xsd
new file mode 100644
index 000000000..a46162a7a
--- /dev/null
+++ b/vendor/symfony/translation/Resources/schemas/xml.xsd
@@ -0,0 +1,309 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
lang (as an attribute name)
+
+
+ denotes an attribute whose value
+ is a language code for the natural language of the content of
+ any element; its value is inherited. This name is reserved
+ by virtue of its definition in the XML specification.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
space (as an attribute name)
+
+ denotes an attribute whose
+ value is a keyword indicating what whitespace processing
+ discipline is intended for the content of the element; its
+ value is inherited. This name is reserved by virtue of its
+ definition in the XML specification.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
base (as an attribute name)
+
+ denotes an attribute whose value
+ provides a URI to be used as the base for interpreting any
+ relative URIs in the scope of the element on which it
+ appears; its value is inherited. This name is reserved
+ by virtue of its definition in the XML Base specification.
+
+
+ See http://www.w3.org/TR/xmlbase/
+ for information about this attribute.
+
+
+
+
+
+
+
+
+
+
+
+
+
id (as an attribute name)
+
+
+ denotes an attribute whose value
+ should be interpreted as if declared to be of type ID.
+ This name is reserved by virtue of its definition in the
+ xml:id specification.
+
+
+ See http://www.w3.org/TR/xml-id/
+ for information about this attribute.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Father (in any context at all)
+
+
+
+ denotes Jon Bosak, the chair of
+ the original XML Working Group. This name is reserved by
+ the following decision of the W3C XML Plenary and
+ XML Coordination groups:
+
+
+
+
+ In appreciation for his vision, leadership and
+ dedication the W3C XML Plenary on this 10th day of
+ February, 2000, reserves for Jon Bosak in perpetuity
+ the XML name "xml:Father".
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ This schema defines attributes and an attribute group suitable
+ for use by schemas wishing to allow xml:base
,
+ xml:lang
, xml:space
or
+ xml:id
attributes on elements they define.
+
+
+
+ To enable this, such a schema must import this schema for
+ the XML namespace, e.g. as follows:
+
+
+ <schema.. .>
+ .. .
+ <import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2001/xml.xsd"/>
+
+
+ or
+
+
+
+ <import namespace="http://www.w3.org/XML/1998/namespace"
+ schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
+
+
+ Subsequently, qualified reference to any of the attributes or the
+ group defined below will have the desired effect, e.g.
+
+
+ <type.. .>
+ .. .
+ <attributeGroup ref="xml:specialAttrs"/>
+
+
+ will define a type which will schema-validate an instance element
+ with any of those attributes.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ In keeping with the XML Schema WG's standard versioning
+ policy, this schema document will persist at
+
+ http://www.w3.org/2009/01/xml.xsd.
+
+
+ At the date of issue it can also be found at
+
+ http://www.w3.org/2001/xml.xsd.
+
+
+
+ The schema document at that URI may however change in the future,
+ in order to remain compatible with the latest version of XML
+ Schema itself, or with the XML namespace itself. In other words,
+ if the XML Schema or XML namespaces change, the version of this
+ document at
+ http://www.w3.org/2001/xml.xsd
+
+ will change accordingly; the version at
+
+ http://www.w3.org/2009/01/xml.xsd
+
+ will not change.
+
+
+
+ Previous dated (and unchanging) versions of this schema
+ document are at:
+
+
+
+
+
+
+
+
diff --git a/vendor/symfony/translation/Test/ProviderFactoryTestCase.php b/vendor/symfony/translation/Test/ProviderFactoryTestCase.php
new file mode 100644
index 000000000..a9c7c0ebf
--- /dev/null
+++ b/vendor/symfony/translation/Test/ProviderFactoryTestCase.php
@@ -0,0 +1,155 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Test;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\HttpClient\MockHttpClient;
+use Symfony\Component\Translation\Dumper\XliffFileDumper;
+use Symfony\Component\Translation\Exception\IncompleteDsnException;
+use Symfony\Component\Translation\Exception\UnsupportedSchemeException;
+use Symfony\Component\Translation\Loader\LoaderInterface;
+use Symfony\Component\Translation\Provider\Dsn;
+use Symfony\Component\Translation\Provider\ProviderFactoryInterface;
+use Symfony\Component\Translation\TranslatorBagInterface;
+use Symfony\Contracts\HttpClient\HttpClientInterface;
+
+/**
+ * A test case to ease testing a translation provider factory.
+ *
+ * @author Mathieu Santostefano
+ *
+ * @internal
+ */
+abstract class ProviderFactoryTestCase extends TestCase
+{
+ protected HttpClientInterface $client;
+ protected LoggerInterface|MockObject $logger;
+ protected string $defaultLocale;
+ protected LoaderInterface|MockObject $loader;
+ protected XliffFileDumper|MockObject $xliffFileDumper;
+ protected TranslatorBagInterface|MockObject $translatorBag;
+
+ abstract public function createFactory(): ProviderFactoryInterface;
+
+ /**
+ * @return iterable
+ */
+ abstract public static function supportsProvider(): iterable;
+
+ /**
+ * @return iterable
+ */
+ abstract public static function createProvider(): iterable;
+
+ /**
+ * @return iterable
+ */
+ public static function unsupportedSchemeProvider(): iterable
+ {
+ return [];
+ }
+
+ /**
+ * @return iterable
+ */
+ public static function incompleteDsnProvider(): iterable
+ {
+ return [];
+ }
+
+ /**
+ * @dataProvider supportsProvider
+ */
+ public function testSupports(bool $expected, string $dsn)
+ {
+ $factory = $this->createFactory();
+
+ $this->assertSame($expected, $factory->supports(new Dsn($dsn)));
+ }
+
+ /**
+ * @dataProvider createProvider
+ */
+ public function testCreate(string $expected, string $dsn)
+ {
+ $factory = $this->createFactory();
+ $provider = $factory->create(new Dsn($dsn));
+
+ $this->assertSame($expected, (string) $provider);
+ }
+
+ /**
+ * @dataProvider unsupportedSchemeProvider
+ */
+ public function testUnsupportedSchemeException(string $dsn, string $message = null)
+ {
+ $factory = $this->createFactory();
+
+ $dsn = new Dsn($dsn);
+
+ $this->expectException(UnsupportedSchemeException::class);
+ if (null !== $message) {
+ $this->expectExceptionMessage($message);
+ }
+
+ $factory->create($dsn);
+ }
+
+ /**
+ * @dataProvider incompleteDsnProvider
+ */
+ public function testIncompleteDsnException(string $dsn, string $message = null)
+ {
+ $factory = $this->createFactory();
+
+ $dsn = new Dsn($dsn);
+
+ $this->expectException(IncompleteDsnException::class);
+ if (null !== $message) {
+ $this->expectExceptionMessage($message);
+ }
+
+ $factory->create($dsn);
+ }
+
+ protected function getClient(): HttpClientInterface
+ {
+ return $this->client ??= new MockHttpClient();
+ }
+
+ protected function getLogger(): LoggerInterface
+ {
+ return $this->logger ??= $this->createMock(LoggerInterface::class);
+ }
+
+ protected function getDefaultLocale(): string
+ {
+ return $this->defaultLocale ??= 'en';
+ }
+
+ protected function getLoader(): LoaderInterface
+ {
+ return $this->loader ??= $this->createMock(LoaderInterface::class);
+ }
+
+ protected function getXliffFileDumper(): XliffFileDumper
+ {
+ return $this->xliffFileDumper ??= $this->createMock(XliffFileDumper::class);
+ }
+
+ protected function getTranslatorBag(): TranslatorBagInterface
+ {
+ return $this->translatorBag ??= $this->createMock(TranslatorBagInterface::class);
+ }
+}
diff --git a/vendor/symfony/translation/Test/ProviderTestCase.php b/vendor/symfony/translation/Test/ProviderTestCase.php
new file mode 100644
index 000000000..2d16f2bfc
--- /dev/null
+++ b/vendor/symfony/translation/Test/ProviderTestCase.php
@@ -0,0 +1,84 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Test;
+
+use PHPUnit\Framework\MockObject\MockObject;
+use PHPUnit\Framework\TestCase;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\HttpClient\MockHttpClient;
+use Symfony\Component\Translation\Dumper\XliffFileDumper;
+use Symfony\Component\Translation\Loader\LoaderInterface;
+use Symfony\Component\Translation\Provider\ProviderInterface;
+use Symfony\Component\Translation\TranslatorBagInterface;
+use Symfony\Contracts\HttpClient\HttpClientInterface;
+
+/**
+ * A test case to ease testing a translation provider.
+ *
+ * @author Mathieu Santostefano
+ *
+ * @internal
+ */
+abstract class ProviderTestCase extends TestCase
+{
+ protected HttpClientInterface $client;
+ protected LoggerInterface|MockObject $logger;
+ protected string $defaultLocale;
+ protected LoaderInterface|MockObject $loader;
+ protected XliffFileDumper|MockObject $xliffFileDumper;
+ protected TranslatorBagInterface|MockObject $translatorBag;
+
+ abstract public static function createProvider(HttpClientInterface $client, LoaderInterface $loader, LoggerInterface $logger, string $defaultLocale, string $endpoint): ProviderInterface;
+
+ /**
+ * @return iterable
+ */
+ abstract public static function toStringProvider(): iterable;
+
+ /**
+ * @dataProvider toStringProvider
+ */
+ public function testToString(ProviderInterface $provider, string $expected)
+ {
+ $this->assertSame($expected, (string) $provider);
+ }
+
+ protected function getClient(): MockHttpClient
+ {
+ return $this->client ??= new MockHttpClient();
+ }
+
+ protected function getLoader(): LoaderInterface
+ {
+ return $this->loader ??= $this->createMock(LoaderInterface::class);
+ }
+
+ protected function getLogger(): LoggerInterface
+ {
+ return $this->logger ??= $this->createMock(LoggerInterface::class);
+ }
+
+ protected function getDefaultLocale(): string
+ {
+ return $this->defaultLocale ??= 'en';
+ }
+
+ protected function getXliffFileDumper(): XliffFileDumper
+ {
+ return $this->xliffFileDumper ??= $this->createMock(XliffFileDumper::class);
+ }
+
+ protected function getTranslatorBag(): TranslatorBagInterface
+ {
+ return $this->translatorBag ??= $this->createMock(TranslatorBagInterface::class);
+ }
+}
diff --git a/vendor/symfony/translation/TranslatableMessage.php b/vendor/symfony/translation/TranslatableMessage.php
new file mode 100644
index 000000000..91d4c1941
--- /dev/null
+++ b/vendor/symfony/translation/TranslatableMessage.php
@@ -0,0 +1,60 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Contracts\Translation\TranslatableInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+/**
+ * @author Nate Wiebe
+ */
+class TranslatableMessage implements TranslatableInterface
+{
+ private string $message;
+ private array $parameters;
+ private ?string $domain;
+
+ public function __construct(string $message, array $parameters = [], string $domain = null)
+ {
+ $this->message = $message;
+ $this->parameters = $parameters;
+ $this->domain = $domain;
+ }
+
+ public function __toString(): string
+ {
+ return $this->getMessage();
+ }
+
+ public function getMessage(): string
+ {
+ return $this->message;
+ }
+
+ public function getParameters(): array
+ {
+ return $this->parameters;
+ }
+
+ public function getDomain(): ?string
+ {
+ return $this->domain;
+ }
+
+ public function trans(TranslatorInterface $translator, string $locale = null): string
+ {
+ return $translator->trans($this->getMessage(), array_map(
+ static fn ($parameter) => $parameter instanceof TranslatableInterface ? $parameter->trans($translator, $locale) : $parameter,
+ $this->getParameters()
+ ), $this->getDomain(), $locale);
+ }
+}
diff --git a/vendor/symfony/translation/Translator.php b/vendor/symfony/translation/Translator.php
new file mode 100644
index 000000000..132138e92
--- /dev/null
+++ b/vendor/symfony/translation/Translator.php
@@ -0,0 +1,472 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Component\Config\ConfigCacheFactory;
+use Symfony\Component\Config\ConfigCacheFactoryInterface;
+use Symfony\Component\Config\ConfigCacheInterface;
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Exception\NotFoundResourceException;
+use Symfony\Component\Translation\Exception\RuntimeException;
+use Symfony\Component\Translation\Formatter\IntlFormatterInterface;
+use Symfony\Component\Translation\Formatter\MessageFormatter;
+use Symfony\Component\Translation\Formatter\MessageFormatterInterface;
+use Symfony\Component\Translation\Loader\LoaderInterface;
+use Symfony\Contracts\Translation\LocaleAwareInterface;
+use Symfony\Contracts\Translation\TranslatableInterface;
+use Symfony\Contracts\Translation\TranslatorInterface;
+
+// Help opcache.preload discover always-needed symbols
+class_exists(MessageCatalogue::class);
+
+/**
+ * @author Fabien Potencier
+ */
+class Translator implements TranslatorInterface, TranslatorBagInterface, LocaleAwareInterface
+{
+ /**
+ * @var MessageCatalogueInterface[]
+ */
+ protected $catalogues = [];
+
+ private string $locale;
+
+ /**
+ * @var string[]
+ */
+ private array $fallbackLocales = [];
+
+ /**
+ * @var LoaderInterface[]
+ */
+ private array $loaders = [];
+
+ private array $resources = [];
+
+ private MessageFormatterInterface $formatter;
+
+ private ?string $cacheDir;
+
+ private bool $debug;
+
+ private array $cacheVary;
+
+ private ?ConfigCacheFactoryInterface $configCacheFactory;
+
+ private array $parentLocales;
+
+ private bool $hasIntlFormatter;
+
+ /**
+ * @throws InvalidArgumentException If a locale contains invalid characters
+ */
+ public function __construct(string $locale, MessageFormatterInterface $formatter = null, string $cacheDir = null, bool $debug = false, array $cacheVary = [])
+ {
+ $this->setLocale($locale);
+
+ $this->formatter = $formatter ??= new MessageFormatter();
+ $this->cacheDir = $cacheDir;
+ $this->debug = $debug;
+ $this->cacheVary = $cacheVary;
+ $this->hasIntlFormatter = $formatter instanceof IntlFormatterInterface;
+ }
+
+ /**
+ * @return void
+ */
+ public function setConfigCacheFactory(ConfigCacheFactoryInterface $configCacheFactory)
+ {
+ $this->configCacheFactory = $configCacheFactory;
+ }
+
+ /**
+ * Adds a Loader.
+ *
+ * @param string $format The name of the loader (@see addResource())
+ *
+ * @return void
+ */
+ public function addLoader(string $format, LoaderInterface $loader)
+ {
+ $this->loaders[$format] = $loader;
+ }
+
+ /**
+ * Adds a Resource.
+ *
+ * @param string $format The name of the loader (@see addLoader())
+ * @param mixed $resource The resource name
+ *
+ * @return void
+ *
+ * @throws InvalidArgumentException If the locale contains invalid characters
+ */
+ public function addResource(string $format, mixed $resource, string $locale, string $domain = null)
+ {
+ $domain ??= 'messages';
+
+ $this->assertValidLocale($locale);
+ $locale ?: $locale = class_exists(\Locale::class) ? \Locale::getDefault() : 'en';
+
+ $this->resources[$locale][] = [$format, $resource, $domain];
+
+ if (\in_array($locale, $this->fallbackLocales)) {
+ $this->catalogues = [];
+ } else {
+ unset($this->catalogues[$locale]);
+ }
+ }
+
+ /**
+ * @return void
+ */
+ public function setLocale(string $locale)
+ {
+ $this->assertValidLocale($locale);
+ $this->locale = $locale;
+ }
+
+ public function getLocale(): string
+ {
+ return $this->locale ?: (class_exists(\Locale::class) ? \Locale::getDefault() : 'en');
+ }
+
+ /**
+ * Sets the fallback locales.
+ *
+ * @param string[] $locales
+ *
+ * @return void
+ *
+ * @throws InvalidArgumentException If a locale contains invalid characters
+ */
+ public function setFallbackLocales(array $locales)
+ {
+ // needed as the fallback locales are linked to the already loaded catalogues
+ $this->catalogues = [];
+
+ foreach ($locales as $locale) {
+ $this->assertValidLocale($locale);
+ }
+
+ $this->fallbackLocales = $this->cacheVary['fallback_locales'] = $locales;
+ }
+
+ /**
+ * Gets the fallback locales.
+ *
+ * @internal
+ */
+ public function getFallbackLocales(): array
+ {
+ return $this->fallbackLocales;
+ }
+
+ public function trans(?string $id, array $parameters = [], string $domain = null, string $locale = null): string
+ {
+ if (null === $id || '' === $id) {
+ return '';
+ }
+
+ $domain ??= 'messages';
+
+ $catalogue = $this->getCatalogue($locale);
+ $locale = $catalogue->getLocale();
+ while (!$catalogue->defines($id, $domain)) {
+ if ($cat = $catalogue->getFallbackCatalogue()) {
+ $catalogue = $cat;
+ $locale = $catalogue->getLocale();
+ } else {
+ break;
+ }
+ }
+
+ $parameters = array_map(fn ($parameter) => $parameter instanceof TranslatableInterface ? $parameter->trans($this, $locale) : $parameter, $parameters);
+
+ $len = \strlen(MessageCatalogue::INTL_DOMAIN_SUFFIX);
+ if ($this->hasIntlFormatter
+ && ($catalogue->defines($id, $domain.MessageCatalogue::INTL_DOMAIN_SUFFIX)
+ || (\strlen($domain) > $len && 0 === substr_compare($domain, MessageCatalogue::INTL_DOMAIN_SUFFIX, -$len, $len)))
+ ) {
+ return $this->formatter->formatIntl($catalogue->get($id, $domain), $locale, $parameters);
+ }
+
+ return $this->formatter->format($catalogue->get($id, $domain), $locale, $parameters);
+ }
+
+ public function getCatalogue(string $locale = null): MessageCatalogueInterface
+ {
+ if (!$locale) {
+ $locale = $this->getLocale();
+ } else {
+ $this->assertValidLocale($locale);
+ }
+
+ if (!isset($this->catalogues[$locale])) {
+ $this->loadCatalogue($locale);
+ }
+
+ return $this->catalogues[$locale];
+ }
+
+ public function getCatalogues(): array
+ {
+ return array_values($this->catalogues);
+ }
+
+ /**
+ * Gets the loaders.
+ *
+ * @return LoaderInterface[]
+ */
+ protected function getLoaders(): array
+ {
+ return $this->loaders;
+ }
+
+ /**
+ * @return void
+ */
+ protected function loadCatalogue(string $locale)
+ {
+ if (null === $this->cacheDir) {
+ $this->initializeCatalogue($locale);
+ } else {
+ $this->initializeCacheCatalogue($locale);
+ }
+ }
+
+ /**
+ * @return void
+ */
+ protected function initializeCatalogue(string $locale)
+ {
+ $this->assertValidLocale($locale);
+
+ try {
+ $this->doLoadCatalogue($locale);
+ } catch (NotFoundResourceException $e) {
+ if (!$this->computeFallbackLocales($locale)) {
+ throw $e;
+ }
+ }
+ $this->loadFallbackCatalogues($locale);
+ }
+
+ private function initializeCacheCatalogue(string $locale): void
+ {
+ if (isset($this->catalogues[$locale])) {
+ /* Catalogue already initialized. */
+ return;
+ }
+
+ $this->assertValidLocale($locale);
+ $cache = $this->getConfigCacheFactory()->cache($this->getCatalogueCachePath($locale),
+ function (ConfigCacheInterface $cache) use ($locale) {
+ $this->dumpCatalogue($locale, $cache);
+ }
+ );
+
+ if (isset($this->catalogues[$locale])) {
+ /* Catalogue has been initialized as it was written out to cache. */
+ return;
+ }
+
+ /* Read catalogue from cache. */
+ $this->catalogues[$locale] = include $cache->getPath();
+ }
+
+ private function dumpCatalogue(string $locale, ConfigCacheInterface $cache): void
+ {
+ $this->initializeCatalogue($locale);
+ $fallbackContent = $this->getFallbackContent($this->catalogues[$locale]);
+
+ $content = sprintf(<<getAllMessages($this->catalogues[$locale]), true),
+ $fallbackContent
+ );
+
+ $cache->write($content, $this->catalogues[$locale]->getResources());
+ }
+
+ private function getFallbackContent(MessageCatalogue $catalogue): string
+ {
+ $fallbackContent = '';
+ $current = '';
+ $replacementPattern = '/[^a-z0-9_]/i';
+ $fallbackCatalogue = $catalogue->getFallbackCatalogue();
+ while ($fallbackCatalogue) {
+ $fallback = $fallbackCatalogue->getLocale();
+ $fallbackSuffix = ucfirst(preg_replace($replacementPattern, '_', $fallback));
+ $currentSuffix = ucfirst(preg_replace($replacementPattern, '_', $current));
+
+ $fallbackContent .= sprintf(<<<'EOF'
+$catalogue%s = new MessageCatalogue('%s', %s);
+$catalogue%s->addFallbackCatalogue($catalogue%s);
+
+EOF
+ ,
+ $fallbackSuffix,
+ $fallback,
+ var_export($this->getAllMessages($fallbackCatalogue), true),
+ $currentSuffix,
+ $fallbackSuffix
+ );
+ $current = $fallbackCatalogue->getLocale();
+ $fallbackCatalogue = $fallbackCatalogue->getFallbackCatalogue();
+ }
+
+ return $fallbackContent;
+ }
+
+ private function getCatalogueCachePath(string $locale): string
+ {
+ return $this->cacheDir.'/catalogue.'.$locale.'.'.strtr(substr(base64_encode(hash('sha256', serialize($this->cacheVary), true)), 0, 7), '/', '_').'.php';
+ }
+
+ /**
+ * @internal
+ */
+ protected function doLoadCatalogue(string $locale): void
+ {
+ $this->catalogues[$locale] = new MessageCatalogue($locale);
+
+ if (isset($this->resources[$locale])) {
+ foreach ($this->resources[$locale] as $resource) {
+ if (!isset($this->loaders[$resource[0]])) {
+ if (\is_string($resource[1])) {
+ throw new RuntimeException(sprintf('No loader is registered for the "%s" format when loading the "%s" resource.', $resource[0], $resource[1]));
+ }
+
+ throw new RuntimeException(sprintf('No loader is registered for the "%s" format.', $resource[0]));
+ }
+ $this->catalogues[$locale]->addCatalogue($this->loaders[$resource[0]]->load($resource[1], $locale, $resource[2]));
+ }
+ }
+ }
+
+ private function loadFallbackCatalogues(string $locale): void
+ {
+ $current = $this->catalogues[$locale];
+
+ foreach ($this->computeFallbackLocales($locale) as $fallback) {
+ if (!isset($this->catalogues[$fallback])) {
+ $this->initializeCatalogue($fallback);
+ }
+
+ $fallbackCatalogue = new MessageCatalogue($fallback, $this->getAllMessages($this->catalogues[$fallback]));
+ foreach ($this->catalogues[$fallback]->getResources() as $resource) {
+ $fallbackCatalogue->addResource($resource);
+ }
+ $current->addFallbackCatalogue($fallbackCatalogue);
+ $current = $fallbackCatalogue;
+ }
+ }
+
+ /**
+ * @return array
+ */
+ protected function computeFallbackLocales(string $locale)
+ {
+ $this->parentLocales ??= json_decode(file_get_contents(__DIR__.'/Resources/data/parents.json'), true);
+
+ $originLocale = $locale;
+ $locales = [];
+
+ while ($locale) {
+ $parent = $this->parentLocales[$locale] ?? null;
+
+ if ($parent) {
+ $locale = 'root' !== $parent ? $parent : null;
+ } elseif (\function_exists('locale_parse')) {
+ $localeSubTags = locale_parse($locale);
+ $locale = null;
+ if (1 < \count($localeSubTags)) {
+ array_pop($localeSubTags);
+ $locale = locale_compose($localeSubTags) ?: null;
+ }
+ } elseif ($i = strrpos($locale, '_') ?: strrpos($locale, '-')) {
+ $locale = substr($locale, 0, $i);
+ } else {
+ $locale = null;
+ }
+
+ if (null !== $locale) {
+ $locales[] = $locale;
+ }
+ }
+
+ foreach ($this->fallbackLocales as $fallback) {
+ if ($fallback === $originLocale) {
+ continue;
+ }
+
+ $locales[] = $fallback;
+ }
+
+ return array_unique($locales);
+ }
+
+ /**
+ * Asserts that the locale is valid, throws an Exception if not.
+ *
+ * @return void
+ *
+ * @throws InvalidArgumentException If the locale contains invalid characters
+ */
+ protected function assertValidLocale(string $locale)
+ {
+ if (!preg_match('/^[a-z0-9@_\\.\\-]*$/i', $locale)) {
+ throw new InvalidArgumentException(sprintf('Invalid "%s" locale.', $locale));
+ }
+ }
+
+ /**
+ * Provides the ConfigCache factory implementation, falling back to a
+ * default implementation if necessary.
+ */
+ private function getConfigCacheFactory(): ConfigCacheFactoryInterface
+ {
+ $this->configCacheFactory ??= new ConfigCacheFactory($this->debug);
+
+ return $this->configCacheFactory;
+ }
+
+ private function getAllMessages(MessageCatalogueInterface $catalogue): array
+ {
+ $allMessages = [];
+
+ foreach ($catalogue->all() as $domain => $messages) {
+ if ($intlMessages = $catalogue->all($domain.MessageCatalogue::INTL_DOMAIN_SUFFIX)) {
+ $allMessages[$domain.MessageCatalogue::INTL_DOMAIN_SUFFIX] = $intlMessages;
+ $messages = array_diff_key($messages, $intlMessages);
+ }
+ if ($messages) {
+ $allMessages[$domain] = $messages;
+ }
+ }
+
+ return $allMessages;
+ }
+}
diff --git a/vendor/symfony/translation/TranslatorBag.php b/vendor/symfony/translation/TranslatorBag.php
new file mode 100644
index 000000000..84275ee14
--- /dev/null
+++ b/vendor/symfony/translation/TranslatorBag.php
@@ -0,0 +1,102 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Component\Translation\Catalogue\AbstractOperation;
+use Symfony\Component\Translation\Catalogue\TargetOperation;
+
+final class TranslatorBag implements TranslatorBagInterface
+{
+ /** @var MessageCatalogue[] */
+ private array $catalogues = [];
+
+ public function addCatalogue(MessageCatalogue $catalogue): void
+ {
+ if (null !== $existingCatalogue = $this->getCatalogue($catalogue->getLocale())) {
+ $catalogue->addCatalogue($existingCatalogue);
+ }
+
+ $this->catalogues[$catalogue->getLocale()] = $catalogue;
+ }
+
+ public function addBag(TranslatorBagInterface $bag): void
+ {
+ foreach ($bag->getCatalogues() as $catalogue) {
+ $this->addCatalogue($catalogue);
+ }
+ }
+
+ public function getCatalogue(string $locale = null): MessageCatalogueInterface
+ {
+ if (null === $locale || !isset($this->catalogues[$locale])) {
+ $this->catalogues[$locale] = new MessageCatalogue($locale);
+ }
+
+ return $this->catalogues[$locale];
+ }
+
+ public function getCatalogues(): array
+ {
+ return array_values($this->catalogues);
+ }
+
+ public function diff(TranslatorBagInterface $diffBag): self
+ {
+ $diff = new self();
+
+ foreach ($this->catalogues as $locale => $catalogue) {
+ if (null === $diffCatalogue = $diffBag->getCatalogue($locale)) {
+ $diff->addCatalogue($catalogue);
+
+ continue;
+ }
+
+ $operation = new TargetOperation($diffCatalogue, $catalogue);
+ $operation->moveMessagesToIntlDomainsIfPossible(AbstractOperation::NEW_BATCH);
+ $newCatalogue = new MessageCatalogue($locale);
+
+ foreach ($catalogue->getDomains() as $domain) {
+ $newCatalogue->add($operation->getNewMessages($domain), $domain);
+ }
+
+ $diff->addCatalogue($newCatalogue);
+ }
+
+ return $diff;
+ }
+
+ public function intersect(TranslatorBagInterface $intersectBag): self
+ {
+ $diff = new self();
+
+ foreach ($this->catalogues as $locale => $catalogue) {
+ if (null === $intersectCatalogue = $intersectBag->getCatalogue($locale)) {
+ continue;
+ }
+
+ $operation = new TargetOperation($catalogue, $intersectCatalogue);
+ $operation->moveMessagesToIntlDomainsIfPossible(AbstractOperation::OBSOLETE_BATCH);
+ $obsoleteCatalogue = new MessageCatalogue($locale);
+
+ foreach ($operation->getDomains() as $domain) {
+ $obsoleteCatalogue->add(
+ array_diff($operation->getMessages($domain), $operation->getNewMessages($domain)),
+ $domain
+ );
+ }
+
+ $diff->addCatalogue($obsoleteCatalogue);
+ }
+
+ return $diff;
+ }
+}
diff --git a/vendor/symfony/translation/TranslatorBagInterface.php b/vendor/symfony/translation/TranslatorBagInterface.php
new file mode 100644
index 000000000..a787acf12
--- /dev/null
+++ b/vendor/symfony/translation/TranslatorBagInterface.php
@@ -0,0 +1,36 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+
+/**
+ * @author Abdellatif Ait boudad
+ */
+interface TranslatorBagInterface
+{
+ /**
+ * Gets the catalogue by locale.
+ *
+ * @param string|null $locale The locale or null to use the default
+ *
+ * @throws InvalidArgumentException If the locale contains invalid characters
+ */
+ public function getCatalogue(string $locale = null): MessageCatalogueInterface;
+
+ /**
+ * Returns all catalogues of the instance.
+ *
+ * @return MessageCatalogueInterface[]
+ */
+ public function getCatalogues(): array;
+}
diff --git a/vendor/symfony/translation/Util/ArrayConverter.php b/vendor/symfony/translation/Util/ArrayConverter.php
new file mode 100644
index 000000000..8938e54f8
--- /dev/null
+++ b/vendor/symfony/translation/Util/ArrayConverter.php
@@ -0,0 +1,97 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Util;
+
+/**
+ * ArrayConverter generates tree like structure from a message catalogue.
+ * e.g. this
+ * 'foo.bar1' => 'test1',
+ * 'foo.bar2' => 'test2'
+ * converts to follows:
+ * foo:
+ * bar1: test1
+ * bar2: test2.
+ *
+ * @author Gennady Telegin
+ */
+class ArrayConverter
+{
+ /**
+ * Converts linear messages array to tree-like array.
+ * For example this array('foo.bar' => 'value') will be converted to ['foo' => ['bar' => 'value']].
+ *
+ * @param array $messages Linear messages array
+ */
+ public static function expandToTree(array $messages): array
+ {
+ $tree = [];
+
+ foreach ($messages as $id => $value) {
+ $referenceToElement = &self::getElementByPath($tree, explode('.', $id));
+
+ $referenceToElement = $value;
+
+ unset($referenceToElement);
+ }
+
+ return $tree;
+ }
+
+ private static function &getElementByPath(array &$tree, array $parts): mixed
+ {
+ $elem = &$tree;
+ $parentOfElem = null;
+
+ foreach ($parts as $i => $part) {
+ if (isset($elem[$part]) && \is_string($elem[$part])) {
+ /* Process next case:
+ * 'foo': 'test1',
+ * 'foo.bar': 'test2'
+ *
+ * $tree['foo'] was string before we found array {bar: test2}.
+ * Treat new element as string too, e.g. add $tree['foo.bar'] = 'test2';
+ */
+ $elem = &$elem[implode('.', \array_slice($parts, $i))];
+ break;
+ }
+ $parentOfElem = &$elem;
+ $elem = &$elem[$part];
+ }
+
+ if ($elem && \is_array($elem) && $parentOfElem) {
+ /* Process next case:
+ * 'foo.bar': 'test1'
+ * 'foo': 'test2'
+ *
+ * $tree['foo'] was array = {bar: 'test1'} before we found string constant `foo`.
+ * Cancel treating $tree['foo'] as array and cancel back it expansion,
+ * e.g. make it $tree['foo.bar'] = 'test1' again.
+ */
+ self::cancelExpand($parentOfElem, $part, $elem);
+ }
+
+ return $elem;
+ }
+
+ private static function cancelExpand(array &$tree, string $prefix, array $node): void
+ {
+ $prefix .= '.';
+
+ foreach ($node as $id => $value) {
+ if (\is_string($value)) {
+ $tree[$prefix.$id] = $value;
+ } else {
+ self::cancelExpand($tree, $prefix.$id, $value);
+ }
+ }
+ }
+}
diff --git a/vendor/symfony/translation/Util/XliffUtils.php b/vendor/symfony/translation/Util/XliffUtils.php
new file mode 100644
index 000000000..335c34beb
--- /dev/null
+++ b/vendor/symfony/translation/Util/XliffUtils.php
@@ -0,0 +1,191 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Util;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Exception\InvalidResourceException;
+
+/**
+ * Provides some utility methods for XLIFF translation files, such as validating
+ * their contents according to the XSD schema.
+ *
+ * @author Fabien Potencier
+ */
+class XliffUtils
+{
+ /**
+ * Gets xliff file version based on the root "version" attribute.
+ *
+ * Defaults to 1.2 for backwards compatibility.
+ *
+ * @throws InvalidArgumentException
+ */
+ public static function getVersionNumber(\DOMDocument $dom): string
+ {
+ /** @var \DOMNode $xliff */
+ foreach ($dom->getElementsByTagName('xliff') as $xliff) {
+ $version = $xliff->attributes->getNamedItem('version');
+ if ($version) {
+ return $version->nodeValue;
+ }
+
+ $namespace = $xliff->attributes->getNamedItem('xmlns');
+ if ($namespace) {
+ if (0 !== substr_compare('urn:oasis:names:tc:xliff:document:', $namespace->nodeValue, 0, 34)) {
+ throw new InvalidArgumentException(sprintf('Not a valid XLIFF namespace "%s".', $namespace));
+ }
+
+ return substr($namespace, 34);
+ }
+ }
+
+ // Falls back to v1.2
+ return '1.2';
+ }
+
+ /**
+ * Validates and parses the given file into a DOMDocument.
+ *
+ * @throws InvalidResourceException
+ */
+ public static function validateSchema(\DOMDocument $dom): array
+ {
+ $xliffVersion = static::getVersionNumber($dom);
+ $internalErrors = libxml_use_internal_errors(true);
+ if ($shouldEnable = self::shouldEnableEntityLoader()) {
+ $disableEntities = libxml_disable_entity_loader(false);
+ }
+ try {
+ $isValid = @$dom->schemaValidateSource(self::getSchema($xliffVersion));
+ if (!$isValid) {
+ return self::getXmlErrors($internalErrors);
+ }
+ } finally {
+ if ($shouldEnable) {
+ libxml_disable_entity_loader($disableEntities);
+ }
+ }
+
+ $dom->normalizeDocument();
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($internalErrors);
+
+ return [];
+ }
+
+ private static function shouldEnableEntityLoader(): bool
+ {
+ static $dom, $schema;
+ if (null === $dom) {
+ $dom = new \DOMDocument();
+ $dom->loadXML('');
+
+ $tmpfile = tempnam(sys_get_temp_dir(), 'symfony');
+ register_shutdown_function(static function () use ($tmpfile) {
+ @unlink($tmpfile);
+ });
+ $schema = '
+
+
+';
+ file_put_contents($tmpfile, '
+
+
+
+');
+ }
+
+ return !@$dom->schemaValidateSource($schema);
+ }
+
+ public static function getErrorsAsString(array $xmlErrors): string
+ {
+ $errorsAsString = '';
+
+ foreach ($xmlErrors as $error) {
+ $errorsAsString .= sprintf("[%s %s] %s (in %s - line %d, column %d)\n",
+ \LIBXML_ERR_WARNING === $error['level'] ? 'WARNING' : 'ERROR',
+ $error['code'],
+ $error['message'],
+ $error['file'],
+ $error['line'],
+ $error['column']
+ );
+ }
+
+ return $errorsAsString;
+ }
+
+ private static function getSchema(string $xliffVersion): string
+ {
+ if ('1.2' === $xliffVersion) {
+ $schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-1.2-transitional.xsd');
+ $xmlUri = 'http://www.w3.org/2001/xml.xsd';
+ } elseif ('2.0' === $xliffVersion) {
+ $schemaSource = file_get_contents(__DIR__.'/../Resources/schemas/xliff-core-2.0.xsd');
+ $xmlUri = 'informativeCopiesOf3rdPartySchemas/w3c/xml.xsd';
+ } else {
+ throw new InvalidArgumentException(sprintf('No support implemented for loading XLIFF version "%s".', $xliffVersion));
+ }
+
+ return self::fixXmlLocation($schemaSource, $xmlUri);
+ }
+
+ /**
+ * Internally changes the URI of a dependent xsd to be loaded locally.
+ */
+ private static function fixXmlLocation(string $schemaSource, string $xmlUri): string
+ {
+ $newPath = str_replace('\\', '/', __DIR__).'/../Resources/schemas/xml.xsd';
+ $parts = explode('/', $newPath);
+ $locationstart = 'file:///';
+ if (0 === stripos($newPath, 'phar://')) {
+ $tmpfile = tempnam(sys_get_temp_dir(), 'symfony');
+ if ($tmpfile) {
+ copy($newPath, $tmpfile);
+ $parts = explode('/', str_replace('\\', '/', $tmpfile));
+ } else {
+ array_shift($parts);
+ $locationstart = 'phar:///';
+ }
+ }
+
+ $drive = '\\' === \DIRECTORY_SEPARATOR ? array_shift($parts).'/' : '';
+ $newPath = $locationstart.$drive.implode('/', array_map('rawurlencode', $parts));
+
+ return str_replace($xmlUri, $newPath, $schemaSource);
+ }
+
+ /**
+ * Returns the XML errors of the internal XML parser.
+ */
+ private static function getXmlErrors(bool $internalErrors): array
+ {
+ $errors = [];
+ foreach (libxml_get_errors() as $error) {
+ $errors[] = [
+ 'level' => \LIBXML_ERR_WARNING == $error->level ? 'WARNING' : 'ERROR',
+ 'code' => $error->code,
+ 'message' => trim($error->message),
+ 'file' => $error->file ?: 'n/a',
+ 'line' => $error->line,
+ 'column' => $error->column,
+ ];
+ }
+
+ libxml_clear_errors();
+ libxml_use_internal_errors($internalErrors);
+
+ return $errors;
+ }
+}
diff --git a/vendor/symfony/translation/Writer/TranslationWriter.php b/vendor/symfony/translation/Writer/TranslationWriter.php
new file mode 100644
index 000000000..61e03cb0e
--- /dev/null
+++ b/vendor/symfony/translation/Writer/TranslationWriter.php
@@ -0,0 +1,75 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Writer;
+
+use Symfony\Component\Translation\Dumper\DumperInterface;
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Exception\RuntimeException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * TranslationWriter writes translation messages.
+ *
+ * @author Michel Salib
+ */
+class TranslationWriter implements TranslationWriterInterface
+{
+ /**
+ * @var array
+ */
+ private array $dumpers = [];
+
+ /**
+ * Adds a dumper to the writer.
+ *
+ * @return void
+ */
+ public function addDumper(string $format, DumperInterface $dumper)
+ {
+ $this->dumpers[$format] = $dumper;
+ }
+
+ /**
+ * Obtains the list of supported formats.
+ */
+ public function getFormats(): array
+ {
+ return array_keys($this->dumpers);
+ }
+
+ /**
+ * Writes translation from the catalogue according to the selected format.
+ *
+ * @param string $format The format to use to dump the messages
+ * @param array $options Options that are passed to the dumper
+ *
+ * @return void
+ *
+ * @throws InvalidArgumentException
+ */
+ public function write(MessageCatalogue $catalogue, string $format, array $options = [])
+ {
+ if (!isset($this->dumpers[$format])) {
+ throw new InvalidArgumentException(sprintf('There is no dumper associated with format "%s".', $format));
+ }
+
+ // get the right dumper
+ $dumper = $this->dumpers[$format];
+
+ if (isset($options['path']) && !is_dir($options['path']) && !@mkdir($options['path'], 0777, true) && !is_dir($options['path'])) {
+ throw new RuntimeException(sprintf('Translation Writer was not able to create directory "%s".', $options['path']));
+ }
+
+ // save
+ $dumper->dump($catalogue, $options);
+ }
+}
diff --git a/vendor/symfony/translation/Writer/TranslationWriterInterface.php b/vendor/symfony/translation/Writer/TranslationWriterInterface.php
new file mode 100644
index 000000000..5ebb9794a
--- /dev/null
+++ b/vendor/symfony/translation/Writer/TranslationWriterInterface.php
@@ -0,0 +1,35 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\Translation\Writer;
+
+use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\MessageCatalogue;
+
+/**
+ * TranslationWriter writes translation messages.
+ *
+ * @author Michel Salib
+ */
+interface TranslationWriterInterface
+{
+ /**
+ * Writes translation from the catalogue according to the selected format.
+ *
+ * @param string $format The format to use to dump the messages
+ * @param array $options Options that are passed to the dumper
+ *
+ * @return void
+ *
+ * @throws InvalidArgumentException
+ */
+ public function write(MessageCatalogue $catalogue, string $format, array $options = []);
+}
diff --git a/vendor/symfony/translation/composer.json b/vendor/symfony/translation/composer.json
new file mode 100644
index 000000000..ee8e415dd
--- /dev/null
+++ b/vendor/symfony/translation/composer.json
@@ -0,0 +1,60 @@
+{
+ "name": "symfony/translation",
+ "type": "library",
+ "description": "Provides tools to internationalize your application",
+ "keywords": [],
+ "homepage": "https://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Fabien Potencier",
+ "email": "fabien@symfony.com"
+ },
+ {
+ "name": "Symfony Community",
+ "homepage": "https://symfony.com/contributors"
+ }
+ ],
+ "require": {
+ "php": ">=8.1",
+ "symfony/deprecation-contracts": "^2.5|^3",
+ "symfony/polyfill-mbstring": "~1.0",
+ "symfony/translation-contracts": "^2.5|^3.0"
+ },
+ "require-dev": {
+ "nikic/php-parser": "^4.13",
+ "symfony/config": "^5.4|^6.0",
+ "symfony/console": "^5.4|^6.0",
+ "symfony/dependency-injection": "^5.4|^6.0",
+ "symfony/http-client-contracts": "^2.5|^3.0",
+ "symfony/http-kernel": "^5.4|^6.0",
+ "symfony/intl": "^5.4|^6.0",
+ "symfony/polyfill-intl-icu": "^1.21",
+ "symfony/routing": "^5.4|^6.0",
+ "symfony/service-contracts": "^2.5|^3",
+ "symfony/yaml": "^5.4|^6.0",
+ "symfony/finder": "^5.4|^6.0",
+ "psr/log": "^1|^2|^3"
+ },
+ "conflict": {
+ "symfony/config": "<5.4",
+ "symfony/dependency-injection": "<5.4",
+ "symfony/http-client-contracts": "<2.5",
+ "symfony/http-kernel": "<5.4",
+ "symfony/service-contracts": "<2.5",
+ "symfony/twig-bundle": "<5.4",
+ "symfony/yaml": "<5.4",
+ "symfony/console": "<5.4"
+ },
+ "provide": {
+ "symfony/translation-implementation": "2.3|3.0"
+ },
+ "autoload": {
+ "files": [ "Resources/functions.php" ],
+ "psr-4": { "Symfony\\Component\\Translation\\": "" },
+ "exclude-from-classmap": [
+ "/Tests/"
+ ]
+ },
+ "minimum-stability": "dev"
+}
diff --git a/vendor/symfony/var-exporter/CHANGELOG.md b/vendor/symfony/var-exporter/CHANGELOG.md
index 3406c30ef..1b21a0bbd 100644
--- a/vendor/symfony/var-exporter/CHANGELOG.md
+++ b/vendor/symfony/var-exporter/CHANGELOG.md
@@ -1,6 +1,14 @@
CHANGELOG
=========
+6.2
+---
+
+ * Add support for lazy ghost objects and virtual proxies
+ * Add `Hydrator::hydrate()`
+ * Preserve PHP references also when using `Hydrator::hydrate()` or `Instantiator::instantiate()`
+ * Add support for hydrating from native (array) casts
+
5.1.0
-----
diff --git a/vendor/symfony/var-exporter/Exception/LogicException.php b/vendor/symfony/var-exporter/Exception/LogicException.php
new file mode 100644
index 000000000..619d0559a
--- /dev/null
+++ b/vendor/symfony/var-exporter/Exception/LogicException.php
@@ -0,0 +1,16 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarExporter\Exception;
+
+class LogicException extends \LogicException implements ExceptionInterface
+{
+}
diff --git a/vendor/symfony/var-exporter/Hydrator.php b/vendor/symfony/var-exporter/Hydrator.php
new file mode 100644
index 000000000..5f456fb3c
--- /dev/null
+++ b/vendor/symfony/var-exporter/Hydrator.php
@@ -0,0 +1,78 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarExporter;
+
+use Symfony\Component\VarExporter\Internal\Hydrator as InternalHydrator;
+
+/**
+ * Utility class to hydrate the properties of an object.
+ *
+ * @author Nicolas Grekas
+ */
+final class Hydrator
+{
+ /**
+ * Sets the properties of an object, including private and protected ones.
+ *
+ * For example:
+ *
+ * // Sets the public or protected $object->propertyName property
+ * Hydrator::hydrate($object, ['propertyName' => $propertyValue]);
+ *
+ * // Sets a private property defined on its parent Bar class:
+ * Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]);
+ *
+ * // Alternative way to set the private $object->privateBarProperty property
+ * Hydrator::hydrate($object, [], [
+ * Bar::class => ['privateBarProperty' => $propertyValue],
+ * ]);
+ *
+ * Instances of ArrayObject, ArrayIterator and SplObjectStorage can be hydrated
+ * by using the special "\0" property name to define their internal value:
+ *
+ * // Hydrates an SplObjectStorage where $info1 is attached to $obj1, etc.
+ * Hydrator::hydrate($object, ["\0" => [$obj1, $info1, $obj2, $info2...]]);
+ *
+ * // Hydrates an ArrayObject populated with $inputArray
+ * Hydrator::hydrate($object, ["\0" => [$inputArray]]);
+ *
+ * @template T of object
+ *
+ * @param T $instance The object to hydrate
+ * @param array $properties The properties to set on the instance
+ * @param array> $scopedProperties The properties to set on the instance,
+ * keyed by their declaring class
+ *
+ * @return T
+ */
+ public static function hydrate(object $instance, array $properties = [], array $scopedProperties = []): object
+ {
+ if ($properties) {
+ $class = $instance::class;
+ $propertyScopes = InternalHydrator::$propertyScopes[$class] ??= InternalHydrator::getPropertyScopes($class);
+
+ foreach ($properties as $name => &$value) {
+ [$scope, $name, $readonlyScope] = $propertyScopes[$name] ?? [$class, $name, $class];
+ $scopedProperties[$readonlyScope ?? $scope][$name] = &$value;
+ }
+ unset($value);
+ }
+
+ foreach ($scopedProperties as $scope => $properties) {
+ if ($properties) {
+ (InternalHydrator::$simpleHydrators[$scope] ??= InternalHydrator::getSimpleHydrator($scope))($properties, $instance);
+ }
+ }
+
+ return $instance;
+ }
+}
diff --git a/vendor/symfony/var-exporter/Instantiator.php b/vendor/symfony/var-exporter/Instantiator.php
index 38fce27b6..10200c00b 100644
--- a/vendor/symfony/var-exporter/Instantiator.php
+++ b/vendor/symfony/var-exporter/Instantiator.php
@@ -13,7 +13,6 @@ namespace Symfony\Component\VarExporter;
use Symfony\Component\VarExporter\Exception\ExceptionInterface;
use Symfony\Component\VarExporter\Exception\NotInstantiableTypeException;
-use Symfony\Component\VarExporter\Internal\Hydrator;
use Symfony\Component\VarExporter\Internal\Registry;
/**
@@ -26,67 +25,35 @@ final class Instantiator
/**
* Creates an object and sets its properties without calling its constructor nor any other methods.
*
- * For example:
+ * @see Hydrator::hydrate() for examples
*
- * // creates an empty instance of Foo
- * Instantiator::instantiate(Foo::class);
+ * @template T of object
*
- * // creates a Foo instance and sets one of its properties
- * Instantiator::instantiate(Foo::class, ['propertyName' => $propertyValue]);
+ * @param class-string $class The class of the instance to create
+ * @param array $properties The properties to set on the instance
+ * @param array> $scopedProperties The properties to set on the instance,
+ * keyed by their declaring class
*
- * // creates a Foo instance and sets a private property defined on its parent Bar class
- * Instantiator::instantiate(Foo::class, [], [
- * Bar::class => ['privateBarProperty' => $propertyValue],
- * ]);
- *
- * Instances of ArrayObject, ArrayIterator and SplObjectStorage can be created
- * by using the special "\0" property name to define their internal value:
- *
- * // creates an SplObjectStorage where $info1 is attached to $obj1, etc.
- * Instantiator::instantiate(SplObjectStorage::class, ["\0" => [$obj1, $info1, $obj2, $info2...]]);
- *
- * // creates an ArrayObject populated with $inputArray
- * Instantiator::instantiate(ArrayObject::class, ["\0" => [$inputArray]]);
- *
- * @param string $class The class of the instance to create
- * @param array $properties The properties to set on the instance
- * @param array $privateProperties The private properties to set on the instance,
- * keyed by their declaring class
+ * @return T
*
* @throws ExceptionInterface When the instance cannot be created
*/
- public static function instantiate(string $class, array $properties = [], array $privateProperties = []): object
+ public static function instantiate(string $class, array $properties = [], array $scopedProperties = []): object
{
- $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);
+ $reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class);
if (Registry::$cloneable[$class]) {
- $wrappedInstance = [clone Registry::$prototypes[$class]];
+ $instance = clone Registry::$prototypes[$class];
} elseif (Registry::$instantiableWithoutConstructor[$class]) {
- $wrappedInstance = [$reflector->newInstanceWithoutConstructor()];
+ $instance = $reflector->newInstanceWithoutConstructor();
} elseif (null === Registry::$prototypes[$class]) {
throw new NotInstantiableTypeException($class);
} elseif ($reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize')) {
- $wrappedInstance = [unserialize('C:'.\strlen($class).':"'.$class.'":0:{}')];
+ $instance = unserialize('C:'.\strlen($class).':"'.$class.'":0:{}');
} else {
- $wrappedInstance = [unserialize('O:'.\strlen($class).':"'.$class.'":0:{}')];
+ $instance = unserialize('O:'.\strlen($class).':"'.$class.'":0:{}');
}
- if ($properties) {
- $privateProperties[$class] = isset($privateProperties[$class]) ? $properties + $privateProperties[$class] : $properties;
- }
-
- foreach ($privateProperties as $class => $properties) {
- if (!$properties) {
- continue;
- }
- foreach ($properties as $name => $value) {
- // because they're also used for "unserialization", hydrators
- // deal with array of instances, so we need to wrap values
- $properties[$name] = [$value];
- }
- (Hydrator::$hydrators[$class] ?? Hydrator::getHydrator($class))($properties, $wrappedInstance);
- }
-
- return $wrappedInstance[0];
+ return $properties || $scopedProperties ? Hydrator::hydrate($instance, $properties, $scopedProperties) : $instance;
}
}
diff --git a/vendor/symfony/var-exporter/Internal/Exporter.php b/vendor/symfony/var-exporter/Internal/Exporter.php
index 6ee3ee7f4..e7ee44b09 100644
--- a/vendor/symfony/var-exporter/Internal/Exporter.php
+++ b/vendor/symfony/var-exporter/Internal/Exporter.php
@@ -31,9 +31,11 @@ class Exporter
* @param int &$objectsCount
* @param bool &$valuesAreStatic
*
+ * @return array
+ *
* @throws NotInstantiableTypeException When a value cannot be serialized
*/
- public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic): array
+ public static function prepare($values, $objectsPool, &$refsPool, &$objectsCount, &$valuesAreStatic)
{
$refs = $values;
foreach ($values as $k => $value) {
@@ -71,22 +73,31 @@ class Exporter
goto handle_value;
}
- $class = \get_class($value);
- $reflector = Registry::$reflectors[$class] ?? Registry::getClassReflector($class);
+ $class = $value::class;
+ $reflector = Registry::$reflectors[$class] ??= Registry::getClassReflector($class);
+ $properties = [];
if ($reflector->hasMethod('__serialize')) {
if (!$reflector->getMethod('__serialize')->isPublic()) {
throw new \Error(sprintf('Call to %s method "%s::__serialize()".', $reflector->getMethod('__serialize')->isProtected() ? 'protected' : 'private', $class));
}
- if (!\is_array($properties = $value->__serialize())) {
+ if (!\is_array($serializeProperties = $value->__serialize())) {
throw new \TypeError($class.'::__serialize() must return an array');
}
+ if ($reflector->hasMethod('__unserialize')) {
+ $properties = $serializeProperties;
+ } else {
+ foreach ($serializeProperties as $n => $v) {
+ $c = \PHP_VERSION_ID >= 80100 && $reflector->hasProperty($n) && ($p = $reflector->getProperty($n))->isReadOnly() ? $p->class : 'stdClass';
+ $properties[$c][$n] = $v;
+ }
+ }
+
goto prepare_value;
}
- $properties = [];
$sleep = null;
$proto = Registry::$prototypes[$class];
@@ -133,7 +144,7 @@ class Exporter
$i = 0;
$n = (string) $name;
if ('' === $n || "\0" !== $n[0]) {
- $c = \PHP_VERSION_ID >= 80100 && $reflector->hasProperty($n) && ($p = $reflector->getProperty($n))->isReadOnly() ? $p->class : 'stdClass';
+ $c = $reflector->hasProperty($n) && ($p = $reflector->getProperty($n))->isReadOnly() ? $p->class : 'stdClass';
} elseif ('*' === $n[1]) {
$n = substr($n, 3);
$c = $reflector->getProperty($n)->class;
@@ -189,7 +200,7 @@ class Exporter
return $values;
}
- public static function export($value, string $indent = '')
+ public static function export($value, $indent = '')
{
switch (true) {
case \is_int($value) || \is_float($value): return var_export($value, true);
@@ -228,7 +239,7 @@ class Exporter
return substr($m[1], 0, -2);
}
- if ('n".\'' === substr($m[1], -4)) {
+ if (str_ends_with($m[1], 'n".\'')) {
return substr_replace($m[1], "\n".$subIndent.".'".$m[2], -2);
}
@@ -366,7 +377,7 @@ class Exporter
self::export($value->wakeups, $subIndent),
];
- return '\\'.\get_class($value)."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')';
+ return '\\'.$value::class."::hydrate(\n".$subIndent.implode(",\n".$subIndent, $code)."\n".$indent.')';
}
/**
@@ -376,7 +387,7 @@ class Exporter
private static function getArrayObjectProperties($value, $proto): array
{
$reflector = $value instanceof \ArrayIterator ? 'ArrayIterator' : 'ArrayObject';
- $reflector = Registry::$reflectors[$reflector] ?? Registry::getClassReflector($reflector);
+ $reflector = Registry::$reflectors[$reflector] ??= Registry::getClassReflector($reflector);
$properties = [
$arrayValue = (array) $value,
diff --git a/vendor/symfony/var-exporter/Internal/Hydrator.php b/vendor/symfony/var-exporter/Internal/Hydrator.php
index 5ed6bdc94..f665f6ee1 100644
--- a/vendor/symfony/var-exporter/Internal/Hydrator.php
+++ b/vendor/symfony/var-exporter/Internal/Hydrator.php
@@ -21,6 +21,8 @@ use Symfony\Component\VarExporter\Exception\ClassNotFoundException;
class Hydrator
{
public static $hydrators = [];
+ public static $simpleHydrators = [];
+ public static $propertyScopes = [];
public $registry;
public $values;
@@ -40,7 +42,7 @@ class Hydrator
public static function hydrate($objects, $values, $properties, $value, $wakeups)
{
foreach ($properties as $class => $vars) {
- (self::$hydrators[$class] ?? self::getHydrator($class))($vars, $objects);
+ (self::$hydrators[$class] ??= self::getHydrator($class))($vars, $objects);
}
foreach ($wakeups as $k => $v) {
if (\is_array($v)) {
@@ -55,26 +57,28 @@ class Hydrator
public static function getHydrator($class)
{
+ $baseHydrator = self::$hydrators['stdClass'] ??= static function ($properties, $objects) {
+ foreach ($properties as $name => $values) {
+ foreach ($values as $i => $v) {
+ $objects[$i]->$name = $v;
+ }
+ }
+ };
+
switch ($class) {
case 'stdClass':
- return self::$hydrators[$class] = static function ($properties, $objects) {
- foreach ($properties as $name => $values) {
- foreach ($values as $i => $v) {
- $objects[$i]->$name = $v;
- }
- }
- };
+ return $baseHydrator;
case 'ErrorException':
- return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \ErrorException {
+ return $baseHydrator->bindTo(null, new class() extends \ErrorException {
});
case 'TypeError':
- return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, new class() extends \Error {
+ return $baseHydrator->bindTo(null, new class() extends \Error {
});
case 'SplObjectStorage':
- return self::$hydrators[$class] = static function ($properties, $objects) {
+ return static function ($properties, $objects) {
foreach ($properties as $name => $values) {
if ("\0" === $name) {
foreach ($values as $i => $v) {
@@ -99,9 +103,9 @@ class Hydrator
switch ($class) {
case 'ArrayIterator':
case 'ArrayObject':
- $constructor = \Closure::fromCallable([$classReflector->getConstructor(), 'invokeArgs']);
+ $constructor = $classReflector->getConstructor()->invokeArgs(...);
- return self::$hydrators[$class] = static function ($properties, $objects) use ($constructor) {
+ return static function ($properties, $objects) use ($constructor) {
foreach ($properties as $name => $values) {
if ("\0" !== $name) {
foreach ($values as $i => $v) {
@@ -116,26 +120,25 @@ class Hydrator
}
if (!$classReflector->isInternal()) {
- return self::$hydrators[$class] = (self::$hydrators['stdClass'] ?? self::getHydrator('stdClass'))->bindTo(null, $class);
+ return $baseHydrator->bindTo(null, $class);
}
if ($classReflector->name !== $class) {
- return self::$hydrators[$classReflector->name] ?? self::getHydrator($classReflector->name);
+ return self::$hydrators[$classReflector->name] ??= self::getHydrator($classReflector->name);
}
$propertySetters = [];
foreach ($classReflector->getProperties() as $propertyReflector) {
if (!$propertyReflector->isStatic()) {
- $propertyReflector->setAccessible(true);
- $propertySetters[$propertyReflector->name] = \Closure::fromCallable([$propertyReflector, 'setValue']);
+ $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...);
}
}
if (!$propertySetters) {
- return self::$hydrators[$class] = self::$hydrators['stdClass'] ?? self::getHydrator('stdClass');
+ return $baseHydrator;
}
- return self::$hydrators[$class] = static function ($properties, $objects) use ($propertySetters) {
+ return static function ($properties, $objects) use ($propertySetters) {
foreach ($properties as $name => $values) {
if ($setValue = $propertySetters[$name] ?? null) {
foreach ($values as $i => $v) {
@@ -149,4 +152,148 @@ class Hydrator
}
};
}
+
+ public static function getSimpleHydrator($class)
+ {
+ $baseHydrator = self::$simpleHydrators['stdClass'] ??= (function ($properties, $object) {
+ $readonly = (array) $this;
+
+ foreach ($properties as $name => &$value) {
+ $object->$name = $value;
+
+ if (!($readonly[$name] ?? false)) {
+ $object->$name = &$value;
+ }
+ }
+ })->bindTo(new \stdClass());
+
+ switch ($class) {
+ case 'stdClass':
+ return $baseHydrator;
+
+ case 'ErrorException':
+ return $baseHydrator->bindTo(new \stdClass(), new class() extends \ErrorException {
+ });
+
+ case 'TypeError':
+ return $baseHydrator->bindTo(new \stdClass(), new class() extends \Error {
+ });
+
+ case 'SplObjectStorage':
+ return static function ($properties, $object) {
+ foreach ($properties as $name => &$value) {
+ if ("\0" !== $name) {
+ $object->$name = $value;
+ $object->$name = &$value;
+ continue;
+ }
+ for ($i = 0; $i < \count($value); ++$i) {
+ $object->attach($value[$i], $value[++$i]);
+ }
+ }
+ };
+ }
+
+ if (!class_exists($class) && !interface_exists($class, false) && !trait_exists($class, false)) {
+ throw new ClassNotFoundException($class);
+ }
+ $classReflector = new \ReflectionClass($class);
+
+ switch ($class) {
+ case 'ArrayIterator':
+ case 'ArrayObject':
+ $constructor = $classReflector->getConstructor()->invokeArgs(...);
+
+ return static function ($properties, $object) use ($constructor) {
+ foreach ($properties as $name => &$value) {
+ if ("\0" === $name) {
+ $constructor($object, $value);
+ } else {
+ $object->$name = $value;
+ $object->$name = &$value;
+ }
+ }
+ };
+ }
+
+ if (!$classReflector->isInternal()) {
+ $readonly = new \stdClass();
+ foreach ($classReflector->getProperties(\ReflectionProperty::IS_READONLY) as $propertyReflector) {
+ if ($class === $propertyReflector->class) {
+ $readonly->{$propertyReflector->name} = true;
+ }
+ }
+
+ return $baseHydrator->bindTo($readonly, $class);
+ }
+
+ if ($classReflector->name !== $class) {
+ return self::$simpleHydrators[$classReflector->name] ??= self::getSimpleHydrator($classReflector->name);
+ }
+
+ $propertySetters = [];
+ foreach ($classReflector->getProperties() as $propertyReflector) {
+ if (!$propertyReflector->isStatic()) {
+ $propertySetters[$propertyReflector->name] = $propertyReflector->setValue(...);
+ }
+ }
+
+ if (!$propertySetters) {
+ return $baseHydrator;
+ }
+
+ return static function ($properties, $object) use ($propertySetters) {
+ foreach ($properties as $name => &$value) {
+ if ($setValue = $propertySetters[$name] ?? null) {
+ $setValue($object, $value);
+ } else {
+ $object->$name = $value;
+ $object->$name = &$value;
+ }
+ }
+ };
+ }
+
+ /**
+ * @return array
+ */
+ public static function getPropertyScopes($class)
+ {
+ $propertyScopes = [];
+ $r = new \ReflectionClass($class);
+
+ foreach ($r->getProperties() as $property) {
+ $flags = $property->getModifiers();
+
+ if (\ReflectionProperty::IS_STATIC & $flags) {
+ continue;
+ }
+ $name = $property->name;
+
+ if (\ReflectionProperty::IS_PRIVATE & $flags) {
+ $propertyScopes["\0$class\0$name"] = $propertyScopes[$name] = [$class, $name, $flags & \ReflectionProperty::IS_READONLY ? $class : null];
+ continue;
+ }
+ $propertyScopes[$name] = [$class, $name, $flags & \ReflectionProperty::IS_READONLY ? $property->class : null];
+
+ if (\ReflectionProperty::IS_PROTECTED & $flags) {
+ $propertyScopes["\0*\0$name"] = $propertyScopes[$name];
+ }
+ }
+
+ while ($r = $r->getParentClass()) {
+ $class = $r->name;
+
+ foreach ($r->getProperties(\ReflectionProperty::IS_PRIVATE) as $property) {
+ if (!$property->isStatic()) {
+ $name = $property->name;
+ $readonlyScope = $property->isReadOnly() ? $class : null;
+ $propertyScopes["\0$class\0$name"] = [$class, $name, $readonlyScope];
+ $propertyScopes[$name] ??= [$class, $name, $readonlyScope];
+ }
+ }
+ }
+
+ return $propertyScopes;
+ }
}
diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php b/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php
new file mode 100644
index 000000000..d9d992557
--- /dev/null
+++ b/vendor/symfony/var-exporter/Internal/LazyObjectRegistry.php
@@ -0,0 +1,146 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarExporter\Internal;
+
+/**
+ * Stores the state of lazy objects and caches related reflection information.
+ *
+ * As a micro-optimization, this class uses no type declarations.
+ *
+ * @internal
+ */
+class LazyObjectRegistry
+{
+ /**
+ * @var array
+ */
+ public static $classReflectors = [];
+
+ /**
+ * @var array>
+ */
+ public static $defaultProperties = [];
+
+ /**
+ * @var array>
+ */
+ public static $classResetters = [];
+
+ /**
+ * @var array
+ */
+ public static $classAccessors = [];
+
+ /**
+ * @var array
+ */
+ public static $parentMethods = [];
+
+ public static ?\Closure $noInitializerState = null;
+
+ public static function getClassResetters($class)
+ {
+ $classProperties = [];
+
+ if ((self::$classReflectors[$class] ??= new \ReflectionClass($class))->isInternal()) {
+ $propertyScopes = [];
+ } else {
+ $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
+ }
+
+ foreach ($propertyScopes as $key => [$scope, $name, $readonlyScope]) {
+ $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;
+
+ if ($k === $key && "\0$class\0lazyObjectState" !== $k) {
+ $classProperties[$readonlyScope ?? $scope][$name] = $key;
+ }
+ }
+
+ $resetters = [];
+ foreach ($classProperties as $scope => $properties) {
+ $resetters[] = \Closure::bind(static function ($instance, $skippedProperties, $onlyProperties = null) use ($properties) {
+ foreach ($properties as $name => $key) {
+ if (!\array_key_exists($key, $skippedProperties) && (null === $onlyProperties || \array_key_exists($key, $onlyProperties))) {
+ unset($instance->$name);
+ }
+ }
+ }, null, $scope);
+ }
+
+ $resetters[] = static function ($instance, $skippedProperties, $onlyProperties = null) {
+ foreach ((array) $instance as $name => $value) {
+ if ("\0" !== ($name[0] ?? '') && !\array_key_exists($name, $skippedProperties) && (null === $onlyProperties || \array_key_exists($name, $onlyProperties))) {
+ unset($instance->$name);
+ }
+ }
+ };
+
+ return $resetters;
+ }
+
+ public static function getClassAccessors($class)
+ {
+ return \Closure::bind(static fn () => [
+ 'get' => static function &($instance, $name, $readonly) {
+ if (!$readonly) {
+ return $instance->$name;
+ }
+ $value = $instance->$name;
+
+ return $value;
+ },
+ 'set' => static function ($instance, $name, $value) {
+ $instance->$name = $value;
+ },
+ 'isset' => static fn ($instance, $name) => isset($instance->$name),
+ 'unset' => static function ($instance, $name) {
+ unset($instance->$name);
+ },
+ ], null, \Closure::class === $class ? null : $class)();
+ }
+
+ public static function getParentMethods($class)
+ {
+ $parent = get_parent_class($class);
+ $methods = [];
+
+ foreach (['set', 'isset', 'unset', 'clone', 'serialize', 'unserialize', 'sleep', 'wakeup', 'destruct', 'get'] as $method) {
+ if (!$parent || !method_exists($parent, '__'.$method)) {
+ $methods[$method] = false;
+ } else {
+ $m = new \ReflectionMethod($parent, '__'.$method);
+ $methods[$method] = !$m->isAbstract() && !$m->isPrivate();
+ }
+ }
+
+ $methods['get'] = $methods['get'] ? ($m->returnsReference() ? 2 : 1) : 0;
+
+ return $methods;
+ }
+
+ public static function getScope($propertyScopes, $class, $property, $readonlyScope = null)
+ {
+ if (null === $readonlyScope && !isset($propertyScopes[$k = "\0$class\0$property"]) && !isset($propertyScopes[$k = "\0*\0$property"])) {
+ return null;
+ }
+ $frame = debug_backtrace(\DEBUG_BACKTRACE_PROVIDE_OBJECT | \DEBUG_BACKTRACE_IGNORE_ARGS, 3)[2];
+
+ if (\ReflectionProperty::class === $scope = $frame['class'] ?? \Closure::class) {
+ $scope = $frame['object']->class;
+ }
+ if (null === $readonlyScope && '*' === $k[1] && ($class === $scope || (is_subclass_of($class, $scope) && !isset($propertyScopes["\0$scope\0$property"])))) {
+ return null;
+ }
+
+ return $scope;
+ }
+}
diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectState.php b/vendor/symfony/var-exporter/Internal/LazyObjectState.php
new file mode 100644
index 000000000..2f649dd1c
--- /dev/null
+++ b/vendor/symfony/var-exporter/Internal/LazyObjectState.php
@@ -0,0 +1,130 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarExporter\Internal;
+
+use Symfony\Component\VarExporter\Hydrator as PublicHydrator;
+
+/**
+ * Keeps the state of lazy objects.
+ *
+ * As a micro-optimization, this class uses no type declarations.
+ *
+ * @internal
+ */
+class LazyObjectState
+{
+ public const STATUS_UNINITIALIZED_FULL = 1;
+ public const STATUS_UNINITIALIZED_PARTIAL = 2;
+ public const STATUS_INITIALIZED_FULL = 3;
+ public const STATUS_INITIALIZED_PARTIAL = 4;
+
+ /**
+ * @var array
+ */
+ public readonly array $skippedProperties;
+
+ /**
+ * @var self::STATUS_*
+ */
+ public int $status = 0;
+
+ public object $realInstance;
+
+ public function __construct(public readonly \Closure|array $initializer, $skippedProperties = [])
+ {
+ $this->skippedProperties = $skippedProperties;
+ $this->status = \is_array($initializer) ? self::STATUS_UNINITIALIZED_PARTIAL : self::STATUS_UNINITIALIZED_FULL;
+ }
+
+ public function initialize($instance, $propertyName, $propertyScope)
+ {
+ if (self::STATUS_INITIALIZED_FULL === $this->status) {
+ return self::STATUS_INITIALIZED_FULL;
+ }
+
+ if (\is_array($this->initializer)) {
+ $class = $instance::class;
+ $propertyScope ??= $class;
+ $propertyScopes = Hydrator::$propertyScopes[$class];
+ $propertyScopes[$k = "\0$propertyScope\0$propertyName"] ?? $propertyScopes[$k = "\0*\0$propertyName"] ?? $k = $propertyName;
+
+ if ($initializer = $this->initializer[$k] ?? null) {
+ $value = $initializer(...[$instance, $propertyName, $propertyScope, LazyObjectRegistry::$defaultProperties[$class][$k] ?? null]);
+ $accessor = LazyObjectRegistry::$classAccessors[$propertyScope] ??= LazyObjectRegistry::getClassAccessors($propertyScope);
+ $accessor['set']($instance, $propertyName, $value);
+
+ return $this->status = self::STATUS_INITIALIZED_PARTIAL;
+ }
+
+ $status = self::STATUS_UNINITIALIZED_PARTIAL;
+
+ if ($initializer = $this->initializer["\0"] ?? null) {
+ if (!\is_array($values = $initializer($instance, LazyObjectRegistry::$defaultProperties[$class]))) {
+ throw new \TypeError(sprintf('The lazy-initializer defined for instance of "%s" must return an array, got "%s".', $class, get_debug_type($values)));
+ }
+ $properties = (array) $instance;
+ foreach ($values as $key => $value) {
+ if ($k === $key) {
+ $status = self::STATUS_INITIALIZED_PARTIAL;
+ }
+ if (!\array_key_exists($key, $properties) && [$scope, $name, $readonlyScope] = $propertyScopes[$key] ?? null) {
+ $scope = $readonlyScope ?? ('*' !== $scope ? $scope : $class);
+ $accessor = LazyObjectRegistry::$classAccessors[$scope] ??= LazyObjectRegistry::getClassAccessors($scope);
+ $accessor['set']($instance, $name, $value);
+ }
+ }
+ }
+
+ return $status;
+ }
+
+ $this->status = self::STATUS_INITIALIZED_FULL;
+
+ try {
+ if ($defaultProperties = array_diff_key(LazyObjectRegistry::$defaultProperties[$instance::class], $this->skippedProperties)) {
+ PublicHydrator::hydrate($instance, $defaultProperties);
+ }
+
+ ($this->initializer)($instance);
+ } catch (\Throwable $e) {
+ $this->status = self::STATUS_UNINITIALIZED_FULL;
+ $this->reset($instance);
+
+ throw $e;
+ }
+
+ return self::STATUS_INITIALIZED_FULL;
+ }
+
+ public function reset($instance): void
+ {
+ $class = $instance::class;
+ $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
+ $skippedProperties = $this->skippedProperties;
+ $properties = (array) $instance;
+ $onlyProperties = \is_array($this->initializer) ? $this->initializer : null;
+
+ foreach ($propertyScopes as $key => [$scope, $name, $readonlyScope]) {
+ $propertyScopes[$k = "\0$scope\0$name"] ?? $propertyScopes[$k = "\0*\0$name"] ?? $k = $name;
+
+ if ($k === $key && (null !== $readonlyScope || !\array_key_exists($k, $properties))) {
+ $skippedProperties[$k] = true;
+ }
+ }
+
+ foreach (LazyObjectRegistry::$classResetters[$class] as $reset) {
+ $reset($instance, $skippedProperties, $onlyProperties);
+ }
+
+ $this->status = self::STATUS_INITIALIZED_FULL === $this->status ? self::STATUS_UNINITIALIZED_FULL : self::STATUS_UNINITIALIZED_PARTIAL;
+ }
+}
diff --git a/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php b/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php
new file mode 100644
index 000000000..cccdf6cff
--- /dev/null
+++ b/vendor/symfony/var-exporter/Internal/LazyObjectTrait.php
@@ -0,0 +1,30 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarExporter\Internal;
+
+if (\PHP_VERSION_ID >= 80300) {
+ /**
+ * @internal
+ */
+ trait LazyObjectTrait
+ {
+ private readonly LazyObjectState $lazyObjectState;
+ }
+} else {
+ /**
+ * @internal
+ */
+ trait LazyObjectTrait
+ {
+ private LazyObjectState $lazyObjectState;
+ }
+}
diff --git a/vendor/symfony/var-exporter/Internal/Registry.php b/vendor/symfony/var-exporter/Internal/Registry.php
index a9fb06146..09d2de2a0 100644
--- a/vendor/symfony/var-exporter/Internal/Registry.php
+++ b/vendor/symfony/var-exporter/Internal/Registry.php
@@ -58,9 +58,9 @@ class Registry
public static function f($class)
{
- $reflector = self::$reflectors[$class] ?? self::getClassReflector($class, true, false);
+ $reflector = self::$reflectors[$class] ??= self::getClassReflector($class, true, false);
- return self::$factories[$class] = \Closure::fromCallable([$reflector, 'newInstanceWithoutConstructor']);
+ return self::$factories[$class] = [$reflector, 'newInstanceWithoutConstructor'](...);
}
public static function getClassReflector($class, $instantiableWithoutConstructor = false, $cloneable = null)
@@ -75,17 +75,17 @@ class Registry
} elseif (!$isClass || $reflector->isAbstract()) {
throw new NotInstantiableTypeException($class);
} elseif ($reflector->name !== $class) {
- $reflector = self::$reflectors[$name = $reflector->name] ?? self::getClassReflector($name, false, $cloneable);
+ $reflector = self::$reflectors[$name = $reflector->name] ??= self::getClassReflector($name, false, $cloneable);
self::$cloneable[$class] = self::$cloneable[$name];
self::$instantiableWithoutConstructor[$class] = self::$instantiableWithoutConstructor[$name];
self::$prototypes[$class] = self::$prototypes[$name];
- return self::$reflectors[$class] = $reflector;
+ return $reflector;
} else {
try {
$proto = $reflector->newInstanceWithoutConstructor();
$instantiableWithoutConstructor = true;
- } catch (\ReflectionException $e) {
+ } catch (\ReflectionException) {
$proto = $reflector->implementsInterface('Serializable') && !method_exists($class, '__unserialize') ? 'C:' : 'O:';
if ('C:' === $proto && !$reflector->getMethod('unserialize')->isInternal()) {
$proto = null;
@@ -132,15 +132,13 @@ class Registry
new \ReflectionProperty(\Error::class, 'trace'),
new \ReflectionProperty(\Exception::class, 'trace'),
];
- $setTrace[0]->setAccessible(true);
- $setTrace[1]->setAccessible(true);
- $setTrace[0] = \Closure::fromCallable([$setTrace[0], 'setValue']);
- $setTrace[1] = \Closure::fromCallable([$setTrace[1], 'setValue']);
+ $setTrace[0] = $setTrace[0]->setValue(...);
+ $setTrace[1] = $setTrace[1]->setValue(...);
}
$setTrace[$proto instanceof \Exception]($proto, []);
}
- return self::$reflectors[$class] = $reflector;
+ return $reflector;
}
}
diff --git a/vendor/symfony/var-exporter/LICENSE b/vendor/symfony/var-exporter/LICENSE
index 99757d517..7536caeae 100644
--- a/vendor/symfony/var-exporter/LICENSE
+++ b/vendor/symfony/var-exporter/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2018-2023 Fabien Potencier
+Copyright (c) 2018-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/vendor/symfony/var-exporter/LazyGhostTrait.php b/vendor/symfony/var-exporter/LazyGhostTrait.php
new file mode 100644
index 000000000..13e33f59c
--- /dev/null
+++ b/vendor/symfony/var-exporter/LazyGhostTrait.php
@@ -0,0 +1,391 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarExporter;
+
+use Symfony\Component\VarExporter\Internal\Hydrator;
+use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry;
+use Symfony\Component\VarExporter\Internal\LazyObjectState;
+use Symfony\Component\VarExporter\Internal\LazyObjectTrait;
+
+trait LazyGhostTrait
+{
+ use LazyObjectTrait;
+
+ /**
+ * Creates a lazy-loading ghost instance.
+ *
+ * When the initializer is a closure, it should initialize all properties at
+ * once and is given the instance to initialize as argument.
+ *
+ * When the initializer is an array of closures, it should be indexed by
+ * properties and closures should accept 4 arguments: the instance to
+ * initialize, the property to initialize, its write-scope, and its default
+ * value. Each closure should return the value of the corresponding property.
+ * The special "\0" key can be used to define a closure that returns all
+ * properties at once when full-initialization is needed; it takes the
+ * instance and its default properties as arguments.
+ *
+ * Properties should be indexed by their array-cast name, see
+ * https://php.net/manual/language.types.array#language.types.array.casting
+ *
+ * @param (\Closure(static):void
+ * |array
+ * |array{"\0": \Closure(static, array):array}) $initializer
+ * @param array|null $skippedProperties An array indexed by the properties to skip, aka the ones
+ * that the initializer doesn't set when its a closure
+ * @param static|null $instance
+ */
+ public static function createLazyGhost(\Closure|array $initializer, array $skippedProperties = null, object $instance = null): static
+ {
+ $onlyProperties = null === $skippedProperties && \is_array($initializer) ? $initializer : null;
+
+ if (self::class !== $class = $instance ? $instance::class : static::class) {
+ $skippedProperties["\0".self::class."\0lazyObjectState"] = true;
+ } elseif (\defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) {
+ Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES;
+ }
+
+ $instance ??= (Registry::$classReflectors[$class] ??= new \ReflectionClass($class))->newInstanceWithoutConstructor();
+ Registry::$defaultProperties[$class] ??= (array) $instance;
+ $instance->lazyObjectState = new LazyObjectState($initializer, $skippedProperties ??= []);
+
+ foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) {
+ $reset($instance, $skippedProperties, $onlyProperties);
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Returns whether the object is initialized.
+ *
+ * @param $partial Whether partially initialized objects should be considered as initialized
+ */
+ public function isLazyObjectInitialized(bool $partial = false): bool
+ {
+ if (!$state = $this->lazyObjectState ?? null) {
+ return true;
+ }
+
+ if (!\is_array($state->initializer)) {
+ return LazyObjectState::STATUS_INITIALIZED_FULL === $state->status;
+ }
+
+ $class = $this::class;
+ $properties = (array) $this;
+
+ if ($partial) {
+ return (bool) array_intersect_key($state->initializer, $properties);
+ }
+
+ $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
+ foreach ($state->initializer as $key => $initializer) {
+ if (!\array_key_exists($key, $properties) && isset($propertyScopes[$key])) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * Forces initialization of a lazy object and returns it.
+ */
+ public function initializeLazyObject(): static
+ {
+ if (!$state = $this->lazyObjectState ?? null) {
+ return $this;
+ }
+
+ if (!\is_array($state->initializer)) {
+ if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) {
+ $state->initialize($this, '', null);
+ }
+
+ return $this;
+ }
+
+ $values = isset($state->initializer["\0"]) ? null : [];
+
+ $class = $this::class;
+ $properties = (array) $this;
+ $propertyScopes = Hydrator::$propertyScopes[$class] ??= Hydrator::getPropertyScopes($class);
+ foreach ($state->initializer as $key => $initializer) {
+ if (\array_key_exists($key, $properties) || ![$scope, $name, $readonlyScope] = $propertyScopes[$key] ?? null) {
+ continue;
+ }
+ $scope = $readonlyScope ?? ('*' !== $scope ? $scope : $class);
+
+ if (null === $values) {
+ if (!\is_array($values = ($state->initializer["\0"])($this, Registry::$defaultProperties[$class]))) {
+ throw new \TypeError(sprintf('The lazy-initializer defined for instance of "%s" must return an array, got "%s".', $class, get_debug_type($values)));
+ }
+
+ if (\array_key_exists($key, $properties = (array) $this)) {
+ continue;
+ }
+ }
+
+ if (\array_key_exists($key, $values)) {
+ $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
+ $accessor['set']($this, $name, $properties[$key] = $values[$key]);
+ } else {
+ $state->initialize($this, $name, $scope);
+ $properties = (array) $this;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object
+ */
+ public function resetLazyObject(): bool
+ {
+ if (!$state = $this->lazyObjectState ?? null) {
+ return false;
+ }
+
+ if (LazyObjectState::STATUS_UNINITIALIZED_FULL !== $state->status) {
+ $state->reset($this);
+ }
+
+ return true;
+ }
+
+ public function &__get($name): mixed
+ {
+ $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
+ $scope = null;
+
+ if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) {
+ $scope = Registry::getScope($propertyScopes, $class, $name);
+ $state = $this->lazyObjectState ?? null;
+
+ if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))
+ && LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $readonlyScope ?? $scope)
+ ) {
+ goto get_in_scope;
+ }
+ }
+
+ if ($parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get']) {
+ if (2 === $parent) {
+ return parent::__get($name);
+ }
+ $value = parent::__get($name);
+
+ return $value;
+ }
+
+ if (null === $class) {
+ $frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
+ trigger_error(sprintf('Undefined property: %s::$%s in %s on line %s', $this::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE);
+ }
+
+ get_in_scope:
+
+ try {
+ if (null === $scope) {
+ if (null === $readonlyScope) {
+ return $this->$name;
+ }
+ $value = $this->$name;
+
+ return $value;
+ }
+ $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
+
+ return $accessor['get']($this, $name, null !== $readonlyScope);
+ } catch (\Error $e) {
+ if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) {
+ throw $e;
+ }
+
+ try {
+ if (null === $scope) {
+ $this->$name = [];
+
+ return $this->$name;
+ }
+
+ $accessor['set']($this, $name, []);
+
+ return $accessor['get']($this, $name, null !== $readonlyScope);
+ } catch (\Error) {
+ throw $e;
+ }
+ }
+ }
+
+ public function __set($name, $value): void
+ {
+ $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
+ $scope = null;
+
+ if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) {
+ $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope);
+ $state = $this->lazyObjectState ?? null;
+
+ if ($state && ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"]))) {
+ if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) {
+ $state->initialize($this, $name, $readonlyScope ?? $scope);
+ }
+ goto set_in_scope;
+ }
+ }
+
+ if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) {
+ parent::__set($name, $value);
+
+ return;
+ }
+
+ set_in_scope:
+
+ if (null === $scope) {
+ $this->$name = $value;
+ } else {
+ $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
+ $accessor['set']($this, $name, $value);
+ }
+ }
+
+ public function __isset($name): bool
+ {
+ $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
+ $scope = null;
+
+ if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) {
+ $scope = Registry::getScope($propertyScopes, $class, $name);
+ $state = $this->lazyObjectState ?? null;
+
+ if ($state && (null === $scope || isset($propertyScopes["\0$scope\0$name"]))
+ && LazyObjectState::STATUS_UNINITIALIZED_PARTIAL !== $state->initialize($this, $name, $readonlyScope ?? $scope)
+ ) {
+ goto isset_in_scope;
+ }
+ }
+
+ if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) {
+ return parent::__isset($name);
+ }
+
+ isset_in_scope:
+
+ if (null === $scope) {
+ return isset($this->$name);
+ }
+ $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
+
+ return $accessor['isset']($this, $name);
+ }
+
+ public function __unset($name): void
+ {
+ $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
+ $scope = null;
+
+ if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) {
+ $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope);
+ $state = $this->lazyObjectState ?? null;
+
+ if ($state && ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"]))) {
+ if (LazyObjectState::STATUS_UNINITIALIZED_FULL === $state->status) {
+ $state->initialize($this, $name, $readonlyScope ?? $scope);
+ }
+ goto unset_in_scope;
+ }
+ }
+
+ if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) {
+ parent::__unset($name);
+
+ return;
+ }
+
+ unset_in_scope:
+
+ if (null === $scope) {
+ unset($this->$name);
+ } else {
+ $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
+ $accessor['unset']($this, $name);
+ }
+ }
+
+ public function __clone(): void
+ {
+ if ($state = $this->lazyObjectState ?? null) {
+ $this->lazyObjectState = clone $state;
+ }
+
+ if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) {
+ parent::__clone();
+ }
+ }
+
+ public function __serialize(): array
+ {
+ $class = self::class;
+
+ if ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) {
+ $properties = parent::__serialize();
+ } else {
+ $this->initializeLazyObject();
+ $properties = (array) $this;
+ }
+ unset($properties["\0$class\0lazyObjectState"]);
+
+ if (Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) {
+ return $properties;
+ }
+
+ $scope = get_parent_class($class);
+ $data = [];
+
+ foreach (parent::__sleep() as $name) {
+ $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null;
+
+ if (null === $k) {
+ trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE);
+ } else {
+ $data[$k] = $value;
+ }
+ }
+
+ return $data;
+ }
+
+ public function __destruct()
+ {
+ $state = $this->lazyObjectState ?? null;
+
+ if ($state && \in_array($state->status, [LazyObjectState::STATUS_UNINITIALIZED_FULL, LazyObjectState::STATUS_UNINITIALIZED_PARTIAL], true)) {
+ return;
+ }
+
+ if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) {
+ parent::__destruct();
+ }
+ }
+
+ private function setLazyObjectAsInitialized(bool $initialized): void
+ {
+ $state = $this->lazyObjectState ?? null;
+
+ if ($state && !\is_array($state->initializer)) {
+ $state->status = $initialized ? LazyObjectState::STATUS_INITIALIZED_FULL : LazyObjectState::STATUS_UNINITIALIZED_FULL;
+ }
+ }
+}
diff --git a/vendor/symfony/var-exporter/LazyObjectInterface.php b/vendor/symfony/var-exporter/LazyObjectInterface.php
new file mode 100644
index 000000000..367088459
--- /dev/null
+++ b/vendor/symfony/var-exporter/LazyObjectInterface.php
@@ -0,0 +1,32 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarExporter;
+
+interface LazyObjectInterface
+{
+ /**
+ * Returns whether the object is initialized.
+ *
+ * @param $partial Whether partially initialized objects should be considered as initialized
+ */
+ public function isLazyObjectInitialized(bool $partial = false): bool;
+
+ /**
+ * Forces initialization of a lazy object and returns it.
+ */
+ public function initializeLazyObject(): object;
+
+ /**
+ * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object
+ */
+ public function resetLazyObject(): bool;
+}
diff --git a/vendor/symfony/var-exporter/LazyProxyTrait.php b/vendor/symfony/var-exporter/LazyProxyTrait.php
new file mode 100644
index 000000000..153c38208
--- /dev/null
+++ b/vendor/symfony/var-exporter/LazyProxyTrait.php
@@ -0,0 +1,347 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarExporter;
+
+use Symfony\Component\VarExporter\Hydrator as PublicHydrator;
+use Symfony\Component\VarExporter\Internal\Hydrator;
+use Symfony\Component\VarExporter\Internal\LazyObjectRegistry as Registry;
+use Symfony\Component\VarExporter\Internal\LazyObjectState;
+use Symfony\Component\VarExporter\Internal\LazyObjectTrait;
+
+trait LazyProxyTrait
+{
+ use LazyObjectTrait;
+
+ /**
+ * Creates a lazy-loading virtual proxy.
+ *
+ * @param \Closure():object $initializer Returns the proxied object
+ * @param static|null $instance
+ */
+ public static function createLazyProxy(\Closure $initializer, object $instance = null): static
+ {
+ if (self::class !== $class = $instance ? $instance::class : static::class) {
+ $skippedProperties = ["\0".self::class."\0lazyObjectState" => true];
+ } elseif (\defined($class.'::LAZY_OBJECT_PROPERTY_SCOPES')) {
+ Hydrator::$propertyScopes[$class] ??= $class::LAZY_OBJECT_PROPERTY_SCOPES;
+ }
+
+ $instance ??= (Registry::$classReflectors[$class] ??= new \ReflectionClass($class))->newInstanceWithoutConstructor();
+ $instance->lazyObjectState = new LazyObjectState($initializer);
+
+ foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) {
+ $reset($instance, $skippedProperties ??= []);
+ }
+
+ return $instance;
+ }
+
+ /**
+ * Returns whether the object is initialized.
+ *
+ * @param $partial Whether partially initialized objects should be considered as initialized
+ */
+ public function isLazyObjectInitialized(bool $partial = false): bool
+ {
+ return !isset($this->lazyObjectState) || isset($this->lazyObjectState->realInstance) || Registry::$noInitializerState === $this->lazyObjectState->initializer;
+ }
+
+ /**
+ * Forces initialization of a lazy object and returns it.
+ */
+ public function initializeLazyObject(): parent
+ {
+ if ($state = $this->lazyObjectState ?? null) {
+ return $state->realInstance ??= ($state->initializer)();
+ }
+
+ return $this;
+ }
+
+ /**
+ * @return bool Returns false when the object cannot be reset, ie when it's not a lazy object
+ */
+ public function resetLazyObject(): bool
+ {
+ if (!isset($this->lazyObjectState) || Registry::$noInitializerState === $this->lazyObjectState->initializer) {
+ return false;
+ }
+
+ unset($this->lazyObjectState->realInstance);
+
+ return true;
+ }
+
+ public function &__get($name): mixed
+ {
+ $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
+ $scope = null;
+ $instance = $this;
+
+ if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) {
+ $scope = Registry::getScope($propertyScopes, $class, $name);
+
+ if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) {
+ if ($state = $this->lazyObjectState ?? null) {
+ $instance = $state->realInstance ??= ($state->initializer)();
+ }
+ $parent = 2;
+ goto get_in_scope;
+ }
+ }
+ $parent = (Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['get'];
+
+ if ($state = $this->lazyObjectState ?? null) {
+ $instance = $state->realInstance ??= ($state->initializer)();
+ } else {
+ if (2 === $parent) {
+ return parent::__get($name);
+ }
+ $value = parent::__get($name);
+
+ return $value;
+ }
+
+ if (!$parent && null === $class && !\array_key_exists($name, (array) $instance)) {
+ $frame = debug_backtrace(\DEBUG_BACKTRACE_IGNORE_ARGS, 1)[0];
+ trigger_error(sprintf('Undefined property: %s::$%s in %s on line %s', $instance::class, $name, $frame['file'], $frame['line']), \E_USER_NOTICE);
+ }
+
+ get_in_scope:
+
+ try {
+ if (null === $scope) {
+ if (null === $readonlyScope && 1 !== $parent) {
+ return $instance->$name;
+ }
+ $value = $instance->$name;
+
+ return $value;
+ }
+ $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
+
+ return $accessor['get']($instance, $name, null !== $readonlyScope || 1 === $parent);
+ } catch (\Error $e) {
+ if (\Error::class !== $e::class || !str_starts_with($e->getMessage(), 'Cannot access uninitialized non-nullable property')) {
+ throw $e;
+ }
+
+ try {
+ if (null === $scope) {
+ $instance->$name = [];
+
+ return $instance->$name;
+ }
+
+ $accessor['set']($instance, $name, []);
+
+ return $accessor['get']($instance, $name, null !== $readonlyScope || 1 === $parent);
+ } catch (\Error) {
+ throw $e;
+ }
+ }
+ }
+
+ public function __set($name, $value): void
+ {
+ $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
+ $scope = null;
+ $instance = $this;
+
+ if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) {
+ $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope);
+
+ if ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) {
+ if ($state = $this->lazyObjectState ?? null) {
+ $instance = $state->realInstance ??= ($state->initializer)();
+ }
+ goto set_in_scope;
+ }
+ }
+
+ if ($state = $this->lazyObjectState ?? null) {
+ $instance = $state->realInstance ??= ($state->initializer)();
+ } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['set']) {
+ parent::__set($name, $value);
+
+ return;
+ }
+
+ set_in_scope:
+
+ if (null === $scope) {
+ $instance->$name = $value;
+ } else {
+ $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
+ $accessor['set']($instance, $name, $value);
+ }
+ }
+
+ public function __isset($name): bool
+ {
+ $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
+ $scope = null;
+ $instance = $this;
+
+ if ([$class] = $propertyScopes[$name] ?? null) {
+ $scope = Registry::getScope($propertyScopes, $class, $name);
+
+ if (null === $scope || isset($propertyScopes["\0$scope\0$name"])) {
+ if ($state = $this->lazyObjectState ?? null) {
+ $instance = $state->realInstance ??= ($state->initializer)();
+ }
+ goto isset_in_scope;
+ }
+ }
+
+ if ($state = $this->lazyObjectState ?? null) {
+ $instance = $state->realInstance ??= ($state->initializer)();
+ } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['isset']) {
+ return parent::__isset($name);
+ }
+
+ isset_in_scope:
+
+ if (null === $scope) {
+ return isset($instance->$name);
+ }
+ $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
+
+ return $accessor['isset']($instance, $name);
+ }
+
+ public function __unset($name): void
+ {
+ $propertyScopes = Hydrator::$propertyScopes[$this::class] ??= Hydrator::getPropertyScopes($this::class);
+ $scope = null;
+ $instance = $this;
+
+ if ([$class, , $readonlyScope] = $propertyScopes[$name] ?? null) {
+ $scope = Registry::getScope($propertyScopes, $class, $name, $readonlyScope);
+
+ if ($readonlyScope === $scope || isset($propertyScopes["\0$scope\0$name"])) {
+ if ($state = $this->lazyObjectState ?? null) {
+ $instance = $state->realInstance ??= ($state->initializer)();
+ }
+ goto unset_in_scope;
+ }
+ }
+
+ if ($state = $this->lazyObjectState ?? null) {
+ $instance = $state->realInstance ??= ($state->initializer)();
+ } elseif ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['unset']) {
+ parent::__unset($name);
+
+ return;
+ }
+
+ unset_in_scope:
+
+ if (null === $scope) {
+ unset($instance->$name);
+ } else {
+ $accessor = Registry::$classAccessors[$scope] ??= Registry::getClassAccessors($scope);
+ $accessor['unset']($instance, $name);
+ }
+ }
+
+ public function __clone(): void
+ {
+ if (!isset($this->lazyObjectState)) {
+ if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['clone']) {
+ parent::__clone();
+ }
+
+ return;
+ }
+
+ $this->lazyObjectState = clone $this->lazyObjectState;
+
+ if (isset($this->lazyObjectState->realInstance)) {
+ $this->lazyObjectState->realInstance = clone $this->lazyObjectState->realInstance;
+ }
+ }
+
+ public function __serialize(): array
+ {
+ $class = self::class;
+ $state = $this->lazyObjectState ?? null;
+
+ if (!$state && (Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['serialize']) {
+ $properties = parent::__serialize();
+ } else {
+ $properties = (array) $this;
+
+ if ($state) {
+ unset($properties["\0$class\0lazyObjectState"]);
+ $properties["\0$class\0lazyObjectReal"] = $state->realInstance ??= ($state->initializer)();
+ }
+ }
+
+ if ($state || Registry::$parentMethods[$class]['serialize'] || !Registry::$parentMethods[$class]['sleep']) {
+ return $properties;
+ }
+
+ $scope = get_parent_class($class);
+ $data = [];
+
+ foreach (parent::__sleep() as $name) {
+ $value = $properties[$k = $name] ?? $properties[$k = "\0*\0$name"] ?? $properties[$k = "\0$scope\0$name"] ?? $k = null;
+
+ if (null === $k) {
+ trigger_error(sprintf('serialize(): "%s" returned as member variable from __sleep() but does not exist', $name), \E_USER_NOTICE);
+ } else {
+ $data[$k] = $value;
+ }
+ }
+
+ return $data;
+ }
+
+ public function __unserialize(array $data): void
+ {
+ $class = self::class;
+
+ if ($instance = $data["\0$class\0lazyObjectReal"] ?? null) {
+ unset($data["\0$class\0lazyObjectReal"]);
+
+ foreach (Registry::$classResetters[$class] ??= Registry::getClassResetters($class) as $reset) {
+ $reset($this, $data);
+ }
+
+ if ($data) {
+ PublicHydrator::hydrate($this, $data);
+ }
+ $this->lazyObjectState = new LazyObjectState(Registry::$noInitializerState ??= static fn () => throw new \LogicException('Lazy proxy has no initializer.'));
+ $this->lazyObjectState->realInstance = $instance;
+ } elseif ((Registry::$parentMethods[$class] ??= Registry::getParentMethods($class))['unserialize']) {
+ parent::__unserialize($data);
+ } else {
+ PublicHydrator::hydrate($this, $data);
+
+ if (Registry::$parentMethods[$class]['wakeup']) {
+ parent::__wakeup();
+ }
+ }
+ }
+
+ public function __destruct()
+ {
+ if (isset($this->lazyObjectState)) {
+ return;
+ }
+
+ if ((Registry::$parentMethods[self::class] ??= Registry::getParentMethods(self::class))['destruct']) {
+ parent::__destruct();
+ }
+ }
+}
diff --git a/vendor/symfony/var-exporter/ProxyHelper.php b/vendor/symfony/var-exporter/ProxyHelper.php
new file mode 100644
index 000000000..2e150cb5c
--- /dev/null
+++ b/vendor/symfony/var-exporter/ProxyHelper.php
@@ -0,0 +1,365 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarExporter;
+
+use Symfony\Component\VarExporter\Exception\LogicException;
+use Symfony\Component\VarExporter\Internal\Hydrator;
+use Symfony\Component\VarExporter\Internal\LazyObjectRegistry;
+
+/**
+ * @author Nicolas Grekas
+ */
+final class ProxyHelper
+{
+ /**
+ * Helps generate lazy-loading ghost objects.
+ *
+ * @throws LogicException When the class is incompatible with ghost objects
+ */
+ public static function generateLazyGhost(\ReflectionClass $class): string
+ {
+ if (\PHP_VERSION_ID >= 80200 && \PHP_VERSION_ID < 80300 && $class->isReadOnly()) {
+ throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" is readonly.', $class->name));
+ }
+ if ($class->isFinal()) {
+ throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" is final.', $class->name));
+ }
+ if ($class->isInterface() || $class->isAbstract()) {
+ throw new LogicException(sprintf('Cannot generate lazy ghost: "%s" is not a concrete class.', $class->name));
+ }
+ if (\stdClass::class !== $class->name && $class->isInternal()) {
+ throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" is internal.', $class->name));
+ }
+ if ($class->hasMethod('__get') && 'mixed' !== (self::exportType($class->getMethod('__get')) ?? 'mixed')) {
+ throw new LogicException(sprintf('Cannot generate lazy ghost: return type of method "%s::__get()" should be "mixed".', $class->name));
+ }
+
+ static $traitMethods;
+ $traitMethods ??= (new \ReflectionClass(LazyGhostTrait::class))->getMethods();
+
+ foreach ($traitMethods as $method) {
+ if ($class->hasMethod($method->name) && $class->getMethod($method->name)->isFinal()) {
+ throw new LogicException(sprintf('Cannot generate lazy ghost: method "%s::%s()" is final.', $class->name, $method->name));
+ }
+ }
+
+ $parent = $class;
+ while ($parent = $parent->getParentClass()) {
+ if (\stdClass::class !== $parent->name && $parent->isInternal()) {
+ throw new LogicException(sprintf('Cannot generate lazy ghost: class "%s" extends "%s" which is internal.', $class->name, $parent->name));
+ }
+ }
+ $propertyScopes = self::exportPropertyScopes($class->name);
+
+ return <<name} implements \Symfony\Component\VarExporter\LazyObjectInterface
+ {
+ use \Symfony\Component\VarExporter\LazyGhostTrait;
+
+ private const LAZY_OBJECT_PROPERTY_SCOPES = {$propertyScopes};
+ }
+
+ // Help opcache.preload discover always-needed symbols
+ class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class);
+ class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class);
+ class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class);
+
+ EOPHP;
+ }
+
+ /**
+ * Helps generate lazy-loading virtual proxies.
+ *
+ * @param \ReflectionClass[] $interfaces
+ *
+ * @throws LogicException When the class is incompatible with virtual proxies
+ */
+ public static function generateLazyProxy(?\ReflectionClass $class, array $interfaces = []): string
+ {
+ if (!class_exists($class?->name ?? \stdClass::class, false)) {
+ throw new LogicException(sprintf('Cannot generate lazy proxy: "%s" is not a class.', $class->name));
+ }
+ if ($class?->isFinal()) {
+ throw new LogicException(sprintf('Cannot generate lazy proxy: class "%s" is final.', $class->name));
+ }
+ if (\PHP_VERSION_ID >= 80200 && \PHP_VERSION_ID < 80300 && $class?->isReadOnly()) {
+ throw new LogicException(sprintf('Cannot generate lazy proxy: class "%s" is readonly.', $class->name));
+ }
+
+ $methodReflectors = [$class?->getMethods(\ReflectionMethod::IS_PUBLIC | \ReflectionMethod::IS_PROTECTED) ?? []];
+ foreach ($interfaces as $interface) {
+ if (!$interface->isInterface()) {
+ throw new LogicException(sprintf('Cannot generate lazy proxy: "%s" is not an interface.', $interface->name));
+ }
+ $methodReflectors[] = $interface->getMethods();
+ }
+ $methodReflectors = array_merge(...$methodReflectors);
+
+ $extendsInternalClass = false;
+ if ($parent = $class) {
+ do {
+ $extendsInternalClass = \stdClass::class !== $parent->name && $parent->isInternal();
+ } while (!$extendsInternalClass && $parent = $parent->getParentClass());
+ }
+ $methodsHaveToBeProxied = $extendsInternalClass;
+ $methods = [];
+
+ foreach ($methodReflectors as $method) {
+ if ('__get' !== strtolower($method->name) || 'mixed' === ($type = self::exportType($method) ?? 'mixed')) {
+ continue;
+ }
+ $methodsHaveToBeProxied = true;
+ $trait = new \ReflectionMethod(LazyProxyTrait::class, '__get');
+ $body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine());
+ $body[0] = str_replace('): mixed', '): '.$type, $body[0]);
+ $methods['__get'] = strtr(implode('', $body).' }', [
+ 'Hydrator' => '\\'.Hydrator::class,
+ 'Registry' => '\\'.LazyObjectRegistry::class,
+ ]);
+ break;
+ }
+
+ foreach ($methodReflectors as $method) {
+ if (($method->isStatic() && !$method->isAbstract()) || isset($methods[$lcName = strtolower($method->name)])) {
+ continue;
+ }
+ if ($method->isFinal()) {
+ if ($extendsInternalClass || $methodsHaveToBeProxied || method_exists(LazyProxyTrait::class, $method->name)) {
+ throw new LogicException(sprintf('Cannot generate lazy proxy: method "%s::%s()" is final.', $class->name, $method->name));
+ }
+ continue;
+ }
+ if (method_exists(LazyProxyTrait::class, $method->name) || ($method->isProtected() && !$method->isAbstract())) {
+ continue;
+ }
+
+ $signature = self::exportSignature($method, true, $args);
+ $parentCall = $method->isAbstract() ? "throw new \BadMethodCallException('Cannot forward abstract method \"{$method->class}::{$method->name}()\".')" : "parent::{$method->name}({$args})";
+
+ if ($method->isStatic()) {
+ $body = " $parentCall;";
+ } elseif (str_ends_with($signature, '): never') || str_ends_with($signature, '): void')) {
+ $body = <<lazyObjectState)) {
+ (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args});
+ } else {
+ {$parentCall};
+ }
+ EOPHP;
+ } else {
+ if (!$methodsHaveToBeProxied && !$method->isAbstract()) {
+ // Skip proxying methods that might return $this
+ foreach (preg_split('/[()|&]++/', self::exportType($method) ?? 'static') as $type) {
+ if (\in_array($type = ltrim($type, '?'), ['static', 'object'], true)) {
+ continue 2;
+ }
+ foreach ([$class, ...$interfaces] as $r) {
+ if ($r && is_a($r->name, $type, true)) {
+ continue 3;
+ }
+ }
+ }
+ }
+
+ $body = <<lazyObjectState)) {
+ return (\$this->lazyObjectState->realInstance ??= (\$this->lazyObjectState->initializer)())->{$method->name}({$args});
+ }
+
+ return {$parentCall};
+ EOPHP;
+ }
+ $methods[$lcName] = " {$signature}\n {\n{$body}\n }";
+ }
+
+ $types = $interfaces = array_unique(array_column($interfaces, 'name'));
+ $interfaces[] = LazyObjectInterface::class;
+ $interfaces = implode(', \\', $interfaces);
+ $parent = $class ? ' extends \\'.$class->name : '';
+ array_unshift($types, $class ? 'parent' : '');
+ $type = ltrim(implode('&\\', $types), '&');
+
+ if (!$class) {
+ $trait = new \ReflectionMethod(LazyProxyTrait::class, 'initializeLazyObject');
+ $body = \array_slice(file($trait->getFileName()), $trait->getStartLine() - 1, $trait->getEndLine() - $trait->getStartLine());
+ $body[0] = str_replace('): parent', '): '.$type, $body[0]);
+ $methods = ['initializeLazyObject' => implode('', $body).' }'] + $methods;
+ }
+ $body = $methods ? "\n".implode("\n\n", $methods)."\n" : '';
+ $propertyScopes = $class ? self::exportPropertyScopes($class->name) : '[]';
+
+ return <<getParameters() as $param) {
+ $parameters[] = ($param->getAttributes(\SensitiveParameter::class) ? '#[\SensitiveParameter] ' : '')
+ .($withParameterTypes && $param->hasType() ? self::exportType($param).' ' : '')
+ .($param->isPassedByReference() ? '&' : '')
+ .($param->isVariadic() ? '...' : '').'$'.$param->name
+ .($param->isOptional() && !$param->isVariadic() ? ' = '.self::exportDefault($param) : '');
+ $hasByRef = $hasByRef || $param->isPassedByReference();
+ $args .= ($param->isVariadic() ? '...$' : '$').$param->name.', ';
+ }
+
+ if (!$param || !$hasByRef) {
+ $args = '...\func_get_args()';
+ } elseif ($param->isVariadic()) {
+ $args = substr($args, 0, -2);
+ } else {
+ $args .= sprintf('...\array_slice(\func_get_args(), %d)', \count($parameters));
+ }
+
+ $signature = 'function '.($function->returnsReference() ? '&' : '')
+ .($function->isClosure() ? '' : $function->name).'('.implode(', ', $parameters).')';
+
+ if ($function instanceof \ReflectionMethod) {
+ $signature = ($function->isPublic() ? 'public ' : ($function->isProtected() ? 'protected ' : 'private '))
+ .($function->isStatic() ? 'static ' : '').$signature;
+ }
+ if ($function->hasReturnType()) {
+ $signature .= ': '.self::exportType($function);
+ }
+
+ static $getPrototype;
+ $getPrototype ??= (new \ReflectionMethod(\ReflectionMethod::class, 'getPrototype'))->invoke(...);
+
+ while ($function) {
+ if ($function->hasTentativeReturnType()) {
+ return '#[\ReturnTypeWillChange] '.$signature;
+ }
+
+ try {
+ $function = $function instanceof \ReflectionMethod && $function->isAbstract() ? false : $getPrototype($function);
+ } catch (\ReflectionException) {
+ break;
+ }
+ }
+
+ return $signature;
+ }
+
+ public static function exportType(\ReflectionFunctionAbstract|\ReflectionProperty|\ReflectionParameter $owner, bool $noBuiltin = false, \ReflectionType $type = null): ?string
+ {
+ if (!$type ??= $owner instanceof \ReflectionFunctionAbstract ? $owner->getReturnType() : $owner->getType()) {
+ return null;
+ }
+ $class = null;
+ $types = [];
+ if ($type instanceof \ReflectionUnionType) {
+ $reflectionTypes = $type->getTypes();
+ $glue = '|';
+ } elseif ($type instanceof \ReflectionIntersectionType) {
+ $reflectionTypes = $type->getTypes();
+ $glue = '&';
+ } else {
+ $reflectionTypes = [$type];
+ $glue = null;
+ }
+
+ foreach ($reflectionTypes as $type) {
+ if ($type instanceof \ReflectionIntersectionType) {
+ if ('' !== $name = '('.self::exportType($owner, $noBuiltin, $type).')') {
+ $types[] = $name;
+ }
+ continue;
+ }
+ $name = $type->getName();
+
+ if ($noBuiltin && $type->isBuiltin()) {
+ continue;
+ }
+ if (\in_array($name, ['parent', 'self'], true) && $class ??= $owner->getDeclaringClass()) {
+ $name = 'parent' === $name ? ($class->getParentClass() ?: null)?->name ?? 'parent' : $class->name;
+ }
+
+ $types[] = ($noBuiltin || $type->isBuiltin() || 'static' === $name ? '' : '\\').$name;
+ }
+
+ if (!$types) {
+ return '';
+ }
+ if (null === $glue) {
+ return (!$noBuiltin && $type->allowsNull() && 'mixed' !== $name ? '?' : '').$types[0];
+ }
+ sort($types);
+
+ return implode($glue, $types);
+ }
+
+ private static function exportPropertyScopes(string $parent): string
+ {
+ $propertyScopes = Hydrator::$propertyScopes[$parent] ??= Hydrator::getPropertyScopes($parent);
+ uksort($propertyScopes, 'strnatcmp');
+ $propertyScopes = VarExporter::export($propertyScopes);
+ $propertyScopes = str_replace(VarExporter::export($parent), 'parent::class', $propertyScopes);
+ $propertyScopes = preg_replace("/(?|(,)\n( ) |\n |,\n (\]))/", '$1$2', $propertyScopes);
+ $propertyScopes = str_replace("\n", "\n ", $propertyScopes);
+
+ return $propertyScopes;
+ }
+
+ private static function exportDefault(\ReflectionParameter $param): string
+ {
+ $default = rtrim(substr(explode('$'.$param->name.' = ', (string) $param, 2)[1] ?? '', 0, -2));
+
+ if (\in_array($default, ['', 'NULL'], true)) {
+ return 'null';
+ }
+ if (str_ends_with($default, "...'") && preg_match("/^'(?:[^'\\\\]*+(?:\\\\.)*+)*+'$/", $default)) {
+ return VarExporter::export($param->getDefaultValue());
+ }
+
+ $regexp = "/(\"(?:[^\"\\\\]*+(?:\\\\.)*+)*+\"|'(?:[^'\\\\]*+(?:\\\\.)*+)*+')/";
+ $parts = preg_split($regexp, $default, -1, \PREG_SPLIT_DELIM_CAPTURE | \PREG_SPLIT_NO_EMPTY);
+
+ $regexp = '/([\[\( ]|^)([a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z0-9_\x7f-\xff]++)*+)(?!: )/';
+ $callback = (false !== strpbrk($default, "\\:('") && $class = $param->getDeclaringClass())
+ ? fn ($m) => $m[1].match ($m[2]) {
+ 'new', 'false', 'true', 'null' => $m[2],
+ 'NULL' => 'null',
+ 'self' => '\\'.$class->name,
+ 'namespace\\parent',
+ 'parent' => ($parent = $class->getParentClass()) ? '\\'.$parent->name : 'parent',
+ default => '\\'.$m[2],
+ }
+ : fn ($m) => $m[1].match ($m[2]) {
+ 'new', 'false', 'true', 'null', 'self', 'parent' => $m[2],
+ 'NULL' => 'null',
+ default => '\\'.$m[2],
+ };
+
+ return implode('', array_map(fn ($part) => match ($part[0]) {
+ '"' => $part, // for internal classes only
+ "'" => false !== strpbrk($part, "\\\0\r\n") ? '"'.substr(str_replace(['$', "\0", "\r", "\n"], ['\$', '\0', '\r', '\n'], $part), 1, -1).'"' : $part,
+ default => preg_replace_callback($regexp, $callback, $part),
+ }, $parts));
+ }
+}
diff --git a/vendor/symfony/var-exporter/README.md b/vendor/symfony/var-exporter/README.md
index a34e4c23d..7c7a58e29 100644
--- a/vendor/symfony/var-exporter/README.md
+++ b/vendor/symfony/var-exporter/README.md
@@ -1,15 +1,22 @@
VarExporter Component
=====================
-The VarExporter component allows exporting any serializable PHP data structure to
-plain PHP code. While doing so, it preserves all the semantics associated with
-the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`,
-`__serialize`, `__unserialize`).
+The VarExporter component provides various tools to deal with the internal state
+of objects:
-It also provides an instantiator that allows creating and populating objects
-without calling their constructor nor any other methods.
+- `VarExporter::export()` allows exporting any serializable PHP data structure to
+ plain PHP code. While doing so, it preserves all the semantics associated with
+ the serialization mechanism of PHP (`__wakeup`, `__sleep`, `Serializable`,
+ `__serialize`, `__unserialize`.)
+- `Instantiator::instantiate()` creates an object and sets its properties without
+ calling its constructor nor any other methods.
+- `Hydrator::hydrate()` can set the properties of an existing object.
+- `Lazy*Trait` can make a class behave as a lazy-loading ghost or virtual proxy.
-The reason to use this component *vs* `serialize()` or
+VarExporter::export()
+---------------------
+
+The reason to use `VarExporter::export()` *vs* `serialize()` or
[igbinary](https://github.com/igbinary/igbinary) is performance: thanks to
OPcache, the resulting code is significantly faster and more memory efficient
than using `unserialize()` or `igbinary_unserialize()`.
@@ -20,14 +27,118 @@ It also provides a few improvements over `var_export()`/`serialize()`:
* the output is PSR-2 compatible;
* the output can be re-indented without messing up with `\r` or `\n` in the data
- * missing classes throw a `ClassNotFoundException` instead of being unserialized to
- `PHP_Incomplete_Class` objects;
+ * missing classes throw a `ClassNotFoundException` instead of being unserialized
+ to `PHP_Incomplete_Class` objects;
* references involving `SplObjectStorage`, `ArrayObject` or `ArrayIterator`
instances are preserved;
* `Reflection*`, `IteratorIterator` and `RecursiveIteratorIterator` classes
throw an exception when being serialized (their unserialized version is broken
anyway, see https://bugs.php.net/76737).
+Instantiator and Hydrator
+-------------------------
+
+`Instantiator::instantiate($class)` creates an object of the given class without
+calling its constructor nor any other methods.
+
+`Hydrator::hydrate()` sets the properties of an existing object, including
+private and protected ones. For example:
+
+```php
+// Sets the public or protected $object->propertyName property
+Hydrator::hydrate($object, ['propertyName' => $propertyValue]);
+
+// Sets a private property defined on its parent Bar class:
+Hydrator::hydrate($object, ["\0Bar\0privateBarProperty" => $propertyValue]);
+
+// Alternative way to set the private $object->privateBarProperty property
+Hydrator::hydrate($object, [], [
+ Bar::class => ['privateBarProperty' => $propertyValue],
+]);
+```
+
+`Lazy*Trait`
+------------
+
+The component provides two lazy-loading patterns: ghost objects and virtual
+proxies (see https://martinfowler.com/eaaCatalog/lazyLoad.html for reference.)
+
+Ghost objects work only with concrete and non-internal classes. In the generic
+case, they are not compatible with using factories in their initializer.
+
+Virtual proxies work with concrete, abstract or internal classes. They provide an
+API that looks like the actual objects and forward calls to them. They can cause
+identity problems because proxies might not be seen as equivalents to the actual
+objects they proxy.
+
+Because of this identity problem, ghost objects should be preferred when
+possible. Exceptions thrown by the `ProxyHelper` class can help decide when it
+can be used or not.
+
+Ghost objects and virtual proxies both provide implementations for the
+`LazyObjectInterface` which allows resetting them to their initial state or to
+forcibly initialize them when needed. Note that resetting a ghost object skips
+its read-only properties. You should use a virtual proxy to reset read-only
+properties.
+
+### `LazyGhostTrait`
+
+By using `LazyGhostTrait` either directly in your classes or by using
+`ProxyHelper::generateLazyGhost()`, you can make their instances lazy-loadable.
+This works by creating these instances empty and by computing their state only
+when accessing a property.
+
+```php
+class FooLazyGhost extends Foo
+{
+ use LazyGhostTrait;
+}
+
+$foo = FooLazyGhost::createLazyGhost(initializer: function (Foo $instance): void {
+ // [...] Use whatever heavy logic you need here
+ // to compute the $dependencies of the $instance
+ $instance->__construct(...$dependencies);
+ // [...] Call setters, etc. if needed
+});
+
+// $foo is now a lazy-loading ghost object. The initializer will
+// be called only when and if a *property* is accessed.
+```
+
+You can also partially initialize the objects on a property-by-property basis by
+adding two arguments to the initializer:
+
+```php
+$initializer = function (Foo $instance, string $propertyName, ?string $propertyScope): mixed {
+ if (Foo::class === $propertyScope && 'bar' === $propertyName) {
+ return 123;
+ }
+ // [...] Add more logic for the other properties
+};
+```
+
+### `LazyProxyTrait`
+
+Alternatively, `LazyProxyTrait` can be used to create virtual proxies:
+
+```php
+$proxyCode = ProxyHelper::generateLazyProxy(new ReflectionClass(Foo::class));
+// $proxyCode contains the reference to LazyProxyTrait
+// and should be dumped into a file in production envs
+eval('class FooLazyProxy'.$proxyCode);
+
+$foo = FooLazyProxy::createLazyProxy(initializer: function (): Foo {
+ // [...] Use whatever heavy logic you need here
+ // to compute the $dependencies of the $instance
+ $instance = new Foo(...$dependencies);
+ // [...] Call setters, etc. if needed
+
+ return $instance;
+});
+// $foo is now a lazy-loading virtual proxy object. The initializer will
+// be called only when and if a *method* is called.
+```
+
Resources
---------
diff --git a/vendor/symfony/var-exporter/VarExporter.php b/vendor/symfony/var-exporter/VarExporter.php
index 3e2a4cc03..c12eb4f95 100644
--- a/vendor/symfony/var-exporter/VarExporter.php
+++ b/vendor/symfony/var-exporter/VarExporter.php
@@ -32,8 +32,8 @@ final class VarExporter
/**
* Exports a serializable PHP value to PHP code.
*
- * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise
- * @param bool &$classes Classes found in the value are added to this list as both keys and values
+ * @param bool &$isStaticValue Set to true after execution if the provided value is static, false otherwise
+ * @param array &$foundClasses Classes found in the value are added to this list as both keys and values
*
* @throws ExceptionInterface When the provided value cannot be serialized
*/
diff --git a/vendor/symfony/var-exporter/composer.json b/vendor/symfony/var-exporter/composer.json
index 3bf21a64e..83140ee7d 100644
--- a/vendor/symfony/var-exporter/composer.json
+++ b/vendor/symfony/var-exporter/composer.json
@@ -2,7 +2,7 @@
"name": "symfony/var-exporter",
"type": "library",
"description": "Allows exporting any serializable PHP data structure to plain PHP code",
- "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone"],
+ "keywords": ["export", "serialize", "instantiate", "hydrate", "construct", "clone", "lazy-loading", "proxy"],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
@@ -16,7 +16,7 @@
}
],
"require": {
- "php": ">=8.0.2"
+ "php": ">=8.1"
},
"require-dev": {
"symfony/var-dumper": "^5.4|^6.0"
diff --git a/vendor/symfony/yaml/.gitignore b/vendor/symfony/yaml/.gitignore
deleted file mode 100755
index c49a5d8df..000000000
--- a/vendor/symfony/yaml/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-vendor/
-composer.lock
-phpunit.xml
diff --git a/vendor/symfony/yaml/CHANGELOG.md b/vendor/symfony/yaml/CHANGELOG.md
deleted file mode 100755
index f55b57047..000000000
--- a/vendor/symfony/yaml/CHANGELOG.md
+++ /dev/null
@@ -1,28 +0,0 @@
-CHANGELOG
-=========
-
-2.8.0
------
-
- * Deprecated usage of a colon in an unquoted mapping value
- * Deprecated usage of @, \`, | and > at the beginning of an unquoted string
- * When surrounding strings with double-quotes, you must now escape `\` characters. Not
- escaping those characters (when surrounded by double-quotes) is deprecated.
-
- Before:
-
- ```yml
- class: "Foo\Var"
- ```
-
- After:
-
- ```yml
- class: "Foo\\Var"
- ```
-
-2.1.0
------
-
- * Yaml::parse() does not evaluate loaded files as PHP files by default
- anymore (call Yaml::enablePhpParsing() to get back the old behavior)
diff --git a/vendor/symfony/yaml/Dumper.php b/vendor/symfony/yaml/Dumper.php
deleted file mode 100755
index 8b523d16e..000000000
--- a/vendor/symfony/yaml/Dumper.php
+++ /dev/null
@@ -1,77 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Yaml;
-
-/**
- * Dumper dumps PHP variables to YAML strings.
- *
- * @author Fabien Potencier
- */
-class Dumper
-{
- /**
- * The amount of spaces to use for indentation of nested nodes.
- *
- * @var int
- */
- protected $indentation = 4;
-
- /**
- * Sets the indentation.
- *
- * @param int $num The amount of spaces to use for indentation of nested nodes
- */
- public function setIndentation($num)
- {
- if ($num < 1) {
- throw new \InvalidArgumentException('The indentation must be greater than zero.');
- }
-
- $this->indentation = (int) $num;
- }
-
- /**
- * Dumps a PHP value to YAML.
- *
- * @param mixed $input The PHP value
- * @param int $inline The level where you switch to inline YAML
- * @param int $indent The level of indentation (used internally)
- * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
- * @param bool $objectSupport True if object support is enabled, false otherwise
- *
- * @return string The YAML representation of the PHP value
- */
- public function dump($input, $inline = 0, $indent = 0, $exceptionOnInvalidType = false, $objectSupport = false)
- {
- $output = '';
- $prefix = $indent ? str_repeat(' ', $indent) : '';
-
- if ($inline <= 0 || !\is_array($input) || empty($input)) {
- $output .= $prefix.Inline::dump($input, $exceptionOnInvalidType, $objectSupport);
- } else {
- $isAHash = Inline::isHash($input);
-
- foreach ($input as $key => $value) {
- $willBeInlined = $inline - 1 <= 0 || !\is_array($value) || empty($value);
-
- $output .= sprintf('%s%s%s%s',
- $prefix,
- $isAHash ? Inline::dump($key, $exceptionOnInvalidType, $objectSupport).':' : '-',
- $willBeInlined ? ' ' : "\n",
- $this->dump($value, $inline - 1, $willBeInlined ? 0 : $indent + $this->indentation, $exceptionOnInvalidType, $objectSupport)
- ).($willBeInlined ? "\n" : '');
- }
- }
-
- return $output;
- }
-}
diff --git a/vendor/symfony/yaml/Escaper.php b/vendor/symfony/yaml/Escaper.php
deleted file mode 100755
index 2b1321f64..000000000
--- a/vendor/symfony/yaml/Escaper.php
+++ /dev/null
@@ -1,101 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Yaml;
-
-/**
- * Escaper encapsulates escaping rules for single and double-quoted
- * YAML strings.
- *
- * @author Matthew Lewinski
- *
- * @internal
- */
-class Escaper
-{
- // Characters that would cause a dumped string to require double quoting.
- const REGEX_CHARACTER_TO_ESCAPE = "[\\x00-\\x1f]|\xc2\x85|\xc2\xa0|\xe2\x80\xa8|\xe2\x80\xa9";
-
- // Mapping arrays for escaping a double quoted string. The backslash is
- // first to ensure proper escaping because str_replace operates iteratively
- // on the input arrays. This ordering of the characters avoids the use of strtr,
- // which performs more slowly.
- private static $escapees = array('\\', '\\\\', '\\"', '"',
- "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07",
- "\x08", "\x09", "\x0a", "\x0b", "\x0c", "\x0d", "\x0e", "\x0f",
- "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17",
- "\x18", "\x19", "\x1a", "\x1b", "\x1c", "\x1d", "\x1e", "\x1f",
- "\xc2\x85", "\xc2\xa0", "\xe2\x80\xa8", "\xe2\x80\xa9",
- );
- private static $escaped = array('\\\\', '\\"', '\\\\', '\\"',
- '\\0', '\\x01', '\\x02', '\\x03', '\\x04', '\\x05', '\\x06', '\\a',
- '\\b', '\\t', '\\n', '\\v', '\\f', '\\r', '\\x0e', '\\x0f',
- '\\x10', '\\x11', '\\x12', '\\x13', '\\x14', '\\x15', '\\x16', '\\x17',
- '\\x18', '\\x19', '\\x1a', '\\e', '\\x1c', '\\x1d', '\\x1e', '\\x1f',
- '\\N', '\\_', '\\L', '\\P',
- );
-
- /**
- * Determines if a PHP value would require double quoting in YAML.
- *
- * @param string $value A PHP value
- *
- * @return bool True if the value would require double quotes
- */
- public static function requiresDoubleQuoting($value)
- {
- return 0 < preg_match('/'.self::REGEX_CHARACTER_TO_ESCAPE.'/u', $value);
- }
-
- /**
- * Escapes and surrounds a PHP value with double quotes.
- *
- * @param string $value A PHP value
- *
- * @return string The quoted, escaped string
- */
- public static function escapeWithDoubleQuotes($value)
- {
- return sprintf('"%s"', str_replace(self::$escapees, self::$escaped, $value));
- }
-
- /**
- * Determines if a PHP value would require single quoting in YAML.
- *
- * @param string $value A PHP value
- *
- * @return bool True if the value would require single quotes
- */
- public static function requiresSingleQuoting($value)
- {
- // Determines if a PHP value is entirely composed of a value that would
- // require single quoting in YAML.
- if (\in_array(strtolower($value), array('null', '~', 'true', 'false', 'y', 'n', 'yes', 'no', 'on', 'off'))) {
- return true;
- }
-
- // Determines if the PHP value contains any single characters that would
- // cause it to require single quoting in YAML.
- return 0 < preg_match('/[ \s \' " \: \{ \} \[ \] , & \* \# \?] | \A[ \- ? | < > = ! % @ ` ]/x', $value);
- }
-
- /**
- * Escapes and surrounds a PHP value with single quotes.
- *
- * @param string $value A PHP value
- *
- * @return string The quoted, escaped string
- */
- public static function escapeWithSingleQuotes($value)
- {
- return sprintf("'%s'", str_replace('\'', '\'\'', $value));
- }
-}
diff --git a/vendor/symfony/yaml/Exception/ParseException.php b/vendor/symfony/yaml/Exception/ParseException.php
deleted file mode 100755
index 60802b6d7..000000000
--- a/vendor/symfony/yaml/Exception/ParseException.php
+++ /dev/null
@@ -1,144 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Yaml\Exception;
-
-/**
- * Exception class thrown when an error occurs during parsing.
- *
- * @author Fabien Potencier
- */
-class ParseException extends RuntimeException
-{
- private $parsedFile;
- private $parsedLine;
- private $snippet;
- private $rawMessage;
-
- /**
- * @param string $message The error message
- * @param int $parsedLine The line where the error occurred
- * @param string|null $snippet The snippet of code near the problem
- * @param string|null $parsedFile The file name where the error occurred
- * @param \Exception|null $previous The previous exception
- */
- public function __construct($message, $parsedLine = -1, $snippet = null, $parsedFile = null, \Exception $previous = null)
- {
- $this->parsedFile = $parsedFile;
- $this->parsedLine = $parsedLine;
- $this->snippet = $snippet;
- $this->rawMessage = $message;
-
- $this->updateRepr();
-
- parent::__construct($this->message, 0, $previous);
- }
-
- /**
- * Gets the snippet of code near the error.
- *
- * @return string The snippet of code
- */
- public function getSnippet()
- {
- return $this->snippet;
- }
-
- /**
- * Sets the snippet of code near the error.
- *
- * @param string $snippet The code snippet
- */
- public function setSnippet($snippet)
- {
- $this->snippet = $snippet;
-
- $this->updateRepr();
- }
-
- /**
- * Gets the filename where the error occurred.
- *
- * This method returns null if a string is parsed.
- *
- * @return string The filename
- */
- public function getParsedFile()
- {
- return $this->parsedFile;
- }
-
- /**
- * Sets the filename where the error occurred.
- *
- * @param string $parsedFile The filename
- */
- public function setParsedFile($parsedFile)
- {
- $this->parsedFile = $parsedFile;
-
- $this->updateRepr();
- }
-
- /**
- * Gets the line where the error occurred.
- *
- * @return int The file line
- */
- public function getParsedLine()
- {
- return $this->parsedLine;
- }
-
- /**
- * Sets the line where the error occurred.
- *
- * @param int $parsedLine The file line
- */
- public function setParsedLine($parsedLine)
- {
- $this->parsedLine = $parsedLine;
-
- $this->updateRepr();
- }
-
- private function updateRepr()
- {
- $this->message = $this->rawMessage;
-
- $dot = false;
- if ('.' === substr($this->message, -1)) {
- $this->message = substr($this->message, 0, -1);
- $dot = true;
- }
-
- if (null !== $this->parsedFile) {
- if (\PHP_VERSION_ID >= 50400) {
- $jsonOptions = JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE;
- } else {
- $jsonOptions = 0;
- }
- $this->message .= sprintf(' in %s', json_encode($this->parsedFile, $jsonOptions));
- }
-
- if ($this->parsedLine >= 0) {
- $this->message .= sprintf(' at line %d', $this->parsedLine);
- }
-
- if ($this->snippet) {
- $this->message .= sprintf(' (near "%s")', $this->snippet);
- }
-
- if ($dot) {
- $this->message .= '.';
- }
- }
-}
diff --git a/vendor/symfony/yaml/Inline.php b/vendor/symfony/yaml/Inline.php
deleted file mode 100755
index 639ff4a79..000000000
--- a/vendor/symfony/yaml/Inline.php
+++ /dev/null
@@ -1,609 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Yaml;
-
-use Symfony\Component\Yaml\Exception\DumpException;
-use Symfony\Component\Yaml\Exception\ParseException;
-
-/**
- * Inline implements a YAML parser/dumper for the YAML inline syntax.
- *
- * @author Fabien Potencier
- */
-class Inline
-{
- const REGEX_QUOTED_STRING = '(?:"([^"\\\\]*+(?:\\\\.[^"\\\\]*+)*+)"|\'([^\']*+(?:\'\'[^\']*+)*+)\')';
-
- private static $exceptionOnInvalidType = false;
- private static $objectSupport = false;
- private static $objectForMap = false;
-
- /**
- * Converts a YAML string to a PHP value.
- *
- * @param string $value A YAML string
- * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
- * @param bool $objectSupport True if object support is enabled, false otherwise
- * @param bool $objectForMap True if maps should return a stdClass instead of array()
- * @param array $references Mapping of variable names to values
- *
- * @return mixed A PHP value
- *
- * @throws ParseException
- */
- public static function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false, $references = array())
- {
- self::$exceptionOnInvalidType = $exceptionOnInvalidType;
- self::$objectSupport = $objectSupport;
- self::$objectForMap = $objectForMap;
-
- $value = trim($value);
-
- if ('' === $value) {
- return '';
- }
-
- if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
- $mbEncoding = mb_internal_encoding();
- mb_internal_encoding('ASCII');
- }
-
- $i = 0;
- switch ($value[0]) {
- case '[':
- $result = self::parseSequence($value, $i, $references);
- ++$i;
- break;
- case '{':
- $result = self::parseMapping($value, $i, $references);
- ++$i;
- break;
- default:
- $result = self::parseScalar($value, null, array('"', "'"), $i, true, $references);
- }
-
- // some comments are allowed at the end
- if (preg_replace('/\s+#.*$/A', '', substr($value, $i))) {
- throw new ParseException(sprintf('Unexpected characters near "%s".', substr($value, $i)));
- }
-
- if (isset($mbEncoding)) {
- mb_internal_encoding($mbEncoding);
- }
-
- return $result;
- }
-
- /**
- * Dumps a given PHP variable to a YAML string.
- *
- * @param mixed $value The PHP variable to convert
- * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
- * @param bool $objectSupport True if object support is enabled, false otherwise
- *
- * @return string The YAML string representing the PHP value
- *
- * @throws DumpException When trying to dump PHP resource
- */
- public static function dump($value, $exceptionOnInvalidType = false, $objectSupport = false)
- {
- switch (true) {
- case \is_resource($value):
- if ($exceptionOnInvalidType) {
- throw new DumpException(sprintf('Unable to dump PHP resources in a YAML file ("%s").', get_resource_type($value)));
- }
-
- return 'null';
- case \is_object($value):
- if ($objectSupport) {
- return '!php/object:'.serialize($value);
- }
-
- if ($exceptionOnInvalidType) {
- throw new DumpException('Object support when dumping a YAML file has been disabled.');
- }
-
- return 'null';
- case \is_array($value):
- return self::dumpArray($value, $exceptionOnInvalidType, $objectSupport);
- case null === $value:
- return 'null';
- case true === $value:
- return 'true';
- case false === $value:
- return 'false';
- case ctype_digit($value):
- return \is_string($value) ? "'$value'" : (int) $value;
- case is_numeric($value):
- $locale = setlocale(LC_NUMERIC, 0);
- if (false !== $locale) {
- setlocale(LC_NUMERIC, 'C');
- }
- if (\is_float($value)) {
- $repr = (string) $value;
- if (is_infinite($value)) {
- $repr = str_ireplace('INF', '.Inf', $repr);
- } elseif (floor($value) == $value && $repr == $value) {
- // Preserve float data type since storing a whole number will result in integer value.
- $repr = '!!float '.$repr;
- }
- } else {
- $repr = \is_string($value) ? "'$value'" : (string) $value;
- }
- if (false !== $locale) {
- setlocale(LC_NUMERIC, $locale);
- }
-
- return $repr;
- case '' == $value:
- return "''";
- case Escaper::requiresDoubleQuoting($value):
- return Escaper::escapeWithDoubleQuotes($value);
- case Escaper::requiresSingleQuoting($value):
- case Parser::preg_match(self::getHexRegex(), $value):
- case Parser::preg_match(self::getTimestampRegex(), $value):
- return Escaper::escapeWithSingleQuotes($value);
- default:
- return $value;
- }
- }
-
- /**
- * Check if given array is hash or just normal indexed array.
- *
- * @internal
- *
- * @param array $value The PHP array to check
- *
- * @return bool true if value is hash array, false otherwise
- */
- public static function isHash(array $value)
- {
- $expectedKey = 0;
-
- foreach ($value as $key => $val) {
- if ($key !== $expectedKey++) {
- return true;
- }
- }
-
- return false;
- }
-
- /**
- * Dumps a PHP array to a YAML string.
- *
- * @param array $value The PHP array to dump
- * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
- * @param bool $objectSupport True if object support is enabled, false otherwise
- *
- * @return string The YAML string representing the PHP array
- */
- private static function dumpArray($value, $exceptionOnInvalidType, $objectSupport)
- {
- // array
- if ($value && !self::isHash($value)) {
- $output = array();
- foreach ($value as $val) {
- $output[] = self::dump($val, $exceptionOnInvalidType, $objectSupport);
- }
-
- return sprintf('[%s]', implode(', ', $output));
- }
-
- // hash
- $output = array();
- foreach ($value as $key => $val) {
- $output[] = sprintf('%s: %s', self::dump($key, $exceptionOnInvalidType, $objectSupport), self::dump($val, $exceptionOnInvalidType, $objectSupport));
- }
-
- return sprintf('{ %s }', implode(', ', $output));
- }
-
- /**
- * Parses a YAML scalar.
- *
- * @param string $scalar
- * @param string[] $delimiters
- * @param string[] $stringDelimiters
- * @param int &$i
- * @param bool $evaluate
- * @param array $references
- *
- * @return string
- *
- * @throws ParseException When malformed inline YAML string is parsed
- *
- * @internal
- */
- public static function parseScalar($scalar, $delimiters = null, $stringDelimiters = array('"', "'"), &$i = 0, $evaluate = true, $references = array())
- {
- if (\in_array($scalar[$i], $stringDelimiters)) {
- // quoted scalar
- $output = self::parseQuotedScalar($scalar, $i);
-
- if (null !== $delimiters) {
- $tmp = ltrim(substr($scalar, $i), ' ');
- if ('' === $tmp) {
- throw new ParseException(sprintf('Unexpected end of line, expected one of "%s".', implode('', $delimiters)));
- }
- if (!\in_array($tmp[0], $delimiters)) {
- throw new ParseException(sprintf('Unexpected characters (%s).', substr($scalar, $i)));
- }
- }
- } else {
- // "normal" string
- if (!$delimiters) {
- $output = substr($scalar, $i);
- $i += \strlen($output);
-
- // remove comments
- if (Parser::preg_match('/[ \t]+#/', $output, $match, PREG_OFFSET_CAPTURE)) {
- $output = substr($output, 0, $match[0][1]);
- }
- } elseif (Parser::preg_match('/^(.+?)('.implode('|', $delimiters).')/', substr($scalar, $i), $match)) {
- $output = $match[1];
- $i += \strlen($output);
- } else {
- throw new ParseException(sprintf('Malformed inline YAML string: %s.', $scalar));
- }
-
- // a non-quoted string cannot start with @ or ` (reserved) nor with a scalar indicator (| or >)
- if ($output && ('@' === $output[0] || '`' === $output[0] || '|' === $output[0] || '>' === $output[0])) {
- @trigger_error(sprintf('Not quoting the scalar "%s" starting with "%s" is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $output, $output[0]), E_USER_DEPRECATED);
-
- // to be thrown in 3.0
- // throw new ParseException(sprintf('The reserved indicator "%s" cannot start a plain scalar; you need to quote the scalar.', $output[0]));
- }
-
- if ($evaluate) {
- $output = self::evaluateScalar($output, $references);
- }
- }
-
- return $output;
- }
-
- /**
- * Parses a YAML quoted scalar.
- *
- * @param string $scalar
- * @param int &$i
- *
- * @return string
- *
- * @throws ParseException When malformed inline YAML string is parsed
- */
- private static function parseQuotedScalar($scalar, &$i)
- {
- if (!Parser::preg_match('/'.self::REGEX_QUOTED_STRING.'/Au', substr($scalar, $i), $match)) {
- throw new ParseException(sprintf('Malformed inline YAML string: %s.', substr($scalar, $i)));
- }
-
- $output = substr($match[0], 1, \strlen($match[0]) - 2);
-
- $unescaper = new Unescaper();
- if ('"' == $scalar[$i]) {
- $output = $unescaper->unescapeDoubleQuotedString($output);
- } else {
- $output = $unescaper->unescapeSingleQuotedString($output);
- }
-
- $i += \strlen($match[0]);
-
- return $output;
- }
-
- /**
- * Parses a YAML sequence.
- *
- * @param string $sequence
- * @param int &$i
- * @param array $references
- *
- * @return array
- *
- * @throws ParseException When malformed inline YAML string is parsed
- */
- private static function parseSequence($sequence, &$i = 0, $references = array())
- {
- $output = array();
- $len = \strlen($sequence);
- ++$i;
-
- // [foo, bar, ...]
- while ($i < $len) {
- switch ($sequence[$i]) {
- case '[':
- // nested sequence
- $output[] = self::parseSequence($sequence, $i, $references);
- break;
- case '{':
- // nested mapping
- $output[] = self::parseMapping($sequence, $i, $references);
- break;
- case ']':
- return $output;
- case ',':
- case ' ':
- break;
- default:
- $isQuoted = \in_array($sequence[$i], array('"', "'"));
- $value = self::parseScalar($sequence, array(',', ']'), array('"', "'"), $i, true, $references);
-
- // the value can be an array if a reference has been resolved to an array var
- if (!\is_array($value) && !$isQuoted && false !== strpos($value, ': ')) {
- // embedded mapping?
- try {
- $pos = 0;
- $value = self::parseMapping('{'.$value.'}', $pos, $references);
- } catch (\InvalidArgumentException $e) {
- // no, it's not
- }
- }
-
- $output[] = $value;
-
- --$i;
- }
-
- ++$i;
- }
-
- throw new ParseException(sprintf('Malformed inline YAML string: %s.', $sequence));
- }
-
- /**
- * Parses a YAML mapping.
- *
- * @param string $mapping
- * @param int &$i
- * @param array $references
- *
- * @return array|\stdClass
- *
- * @throws ParseException When malformed inline YAML string is parsed
- */
- private static function parseMapping($mapping, &$i = 0, $references = array())
- {
- $output = array();
- $len = \strlen($mapping);
- ++$i;
- $allowOverwrite = false;
-
- // {foo: bar, bar:foo, ...}
- while ($i < $len) {
- switch ($mapping[$i]) {
- case ' ':
- case ',':
- ++$i;
- continue 2;
- case '}':
- if (self::$objectForMap) {
- return (object) $output;
- }
-
- return $output;
- }
-
- // key
- $key = self::parseScalar($mapping, array(':', ' '), array('"', "'"), $i, false);
-
- if ('<<' === $key) {
- $allowOverwrite = true;
- }
-
- // value
- $done = false;
-
- while ($i < $len) {
- switch ($mapping[$i]) {
- case '[':
- // nested sequence
- $value = self::parseSequence($mapping, $i, $references);
- // Spec: Keys MUST be unique; first one wins.
- // Parser cannot abort this mapping earlier, since lines
- // are processed sequentially.
- // But overwriting is allowed when a merge node is used in current block.
- if ('<<' === $key) {
- foreach ($value as $parsedValue) {
- $output += $parsedValue;
- }
- } elseif ($allowOverwrite || !isset($output[$key])) {
- $output[$key] = $value;
- }
- $done = true;
- break;
- case '{':
- // nested mapping
- $value = self::parseMapping($mapping, $i, $references);
- // Spec: Keys MUST be unique; first one wins.
- // Parser cannot abort this mapping earlier, since lines
- // are processed sequentially.
- // But overwriting is allowed when a merge node is used in current block.
- if ('<<' === $key) {
- $output += $value;
- } elseif ($allowOverwrite || !isset($output[$key])) {
- $output[$key] = $value;
- }
- $done = true;
- break;
- case ':':
- case ' ':
- break;
- default:
- $value = self::parseScalar($mapping, array(',', '}'), array('"', "'"), $i, true, $references);
- // Spec: Keys MUST be unique; first one wins.
- // Parser cannot abort this mapping earlier, since lines
- // are processed sequentially.
- // But overwriting is allowed when a merge node is used in current block.
- if ('<<' === $key) {
- $output += $value;
- } elseif ($allowOverwrite || !isset($output[$key])) {
- $output[$key] = $value;
- }
- $done = true;
- --$i;
- }
-
- ++$i;
-
- if ($done) {
- continue 2;
- }
- }
- }
-
- throw new ParseException(sprintf('Malformed inline YAML string: %s.', $mapping));
- }
-
- /**
- * Evaluates scalars and replaces magic values.
- *
- * @param string $scalar
- * @param array $references
- *
- * @return mixed The evaluated YAML string
- *
- * @throws ParseException when object parsing support was disabled and the parser detected a PHP object or when a reference could not be resolved
- */
- private static function evaluateScalar($scalar, $references = array())
- {
- $scalar = trim($scalar);
- $scalarLower = strtolower($scalar);
-
- if (0 === strpos($scalar, '*')) {
- if (false !== $pos = strpos($scalar, '#')) {
- $value = substr($scalar, 1, $pos - 2);
- } else {
- $value = substr($scalar, 1);
- }
-
- // an unquoted *
- if (false === $value || '' === $value) {
- throw new ParseException('A reference must contain at least one character.');
- }
-
- if (!array_key_exists($value, $references)) {
- throw new ParseException(sprintf('Reference "%s" does not exist.', $value));
- }
-
- return $references[$value];
- }
-
- switch (true) {
- case 'null' === $scalarLower:
- case '' === $scalar:
- case '~' === $scalar:
- return;
- case 'true' === $scalarLower:
- return true;
- case 'false' === $scalarLower:
- return false;
- // Optimise for returning strings.
- case '+' === $scalar[0] || '-' === $scalar[0] || '.' === $scalar[0] || '!' === $scalar[0] || is_numeric($scalar[0]):
- switch (true) {
- case 0 === strpos($scalar, '!str'):
- return (string) substr($scalar, 5);
- case 0 === strpos($scalar, '! '):
- return (int) self::parseScalar(substr($scalar, 2));
- case 0 === strpos($scalar, '!php/object:'):
- if (self::$objectSupport) {
- return unserialize(substr($scalar, 12));
- }
-
- if (self::$exceptionOnInvalidType) {
- throw new ParseException('Object support when parsing a YAML file has been disabled.');
- }
-
- return;
- case 0 === strpos($scalar, '!!php/object:'):
- if (self::$objectSupport) {
- return unserialize(substr($scalar, 13));
- }
-
- if (self::$exceptionOnInvalidType) {
- throw new ParseException('Object support when parsing a YAML file has been disabled.');
- }
-
- return;
- case 0 === strpos($scalar, '!!float '):
- return (float) substr($scalar, 8);
- case ctype_digit($scalar):
- $raw = $scalar;
- $cast = (int) $scalar;
-
- return '0' == $scalar[0] ? octdec($scalar) : (((string) $raw == (string) $cast) ? $cast : $raw);
- case '-' === $scalar[0] && ctype_digit(substr($scalar, 1)):
- $raw = $scalar;
- $cast = (int) $scalar;
-
- return '0' == $scalar[1] ? octdec($scalar) : (((string) $raw === (string) $cast) ? $cast : $raw);
- case is_numeric($scalar):
- case Parser::preg_match(self::getHexRegex(), $scalar):
- return '0x' === $scalar[0].$scalar[1] ? hexdec($scalar) : (float) $scalar;
- case '.inf' === $scalarLower:
- case '.nan' === $scalarLower:
- return -log(0);
- case '-.inf' === $scalarLower:
- return log(0);
- case Parser::preg_match('/^(-|\+)?[0-9,]+(\.[0-9]+)?$/', $scalar):
- return (float) str_replace(',', '', $scalar);
- case Parser::preg_match(self::getTimestampRegex(), $scalar):
- $timeZone = date_default_timezone_get();
- date_default_timezone_set('UTC');
- $time = strtotime($scalar);
- date_default_timezone_set($timeZone);
-
- return $time;
- }
- // no break
- default:
- return (string) $scalar;
- }
- }
-
- /**
- * Gets a regex that matches a YAML date.
- *
- * @return string The regular expression
- *
- * @see http://www.yaml.org/spec/1.2/spec.html#id2761573
- */
- private static function getTimestampRegex()
- {
- return <<[0-9][0-9][0-9][0-9])
- -(?P[0-9][0-9]?)
- -(?P[0-9][0-9]?)
- (?:(?:[Tt]|[ \t]+)
- (?P[0-9][0-9]?)
- :(?P[0-9][0-9])
- :(?P[0-9][0-9])
- (?:\.(?P[0-9]*))?
- (?:[ \t]*(?PZ|(?P[-+])(?P[0-9][0-9]?)
- (?::(?P[0-9][0-9]))?))?)?
- $~x
-EOF;
- }
-
- /**
- * Gets a regex that matches a YAML number in hexadecimal notation.
- *
- * @return string
- */
- private static function getHexRegex()
- {
- return '~^0x[0-9a-f]++$~i';
- }
-}
diff --git a/vendor/symfony/yaml/Parser.php b/vendor/symfony/yaml/Parser.php
deleted file mode 100755
index cb0d8f16b..000000000
--- a/vendor/symfony/yaml/Parser.php
+++ /dev/null
@@ -1,852 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Yaml;
-
-use Symfony\Component\Yaml\Exception\ParseException;
-
-/**
- * Parser parses YAML strings to convert them to PHP arrays.
- *
- * @author Fabien Potencier
- */
-class Parser
-{
- const BLOCK_SCALAR_HEADER_PATTERN = '(?P\||>)(?P\+|\-|\d+|\+\d+|\-\d+|\d+\+|\d+\-)?(?P +#.*)?';
- // BC - wrongly named
- const FOLDED_SCALAR_PATTERN = self::BLOCK_SCALAR_HEADER_PATTERN;
-
- private $offset = 0;
- private $totalNumberOfLines;
- private $lines = array();
- private $currentLineNb = -1;
- private $currentLine = '';
- private $refs = array();
- private $skippedLineNumbers = array();
- private $locallySkippedLineNumbers = array();
-
- /**
- * @param int $offset The offset of YAML document (used for line numbers in error messages)
- * @param int|null $totalNumberOfLines The overall number of lines being parsed
- * @param int[] $skippedLineNumbers Number of comment lines that have been skipped by the parser
- */
- public function __construct($offset = 0, $totalNumberOfLines = null, array $skippedLineNumbers = array())
- {
- $this->offset = $offset;
- $this->totalNumberOfLines = $totalNumberOfLines;
- $this->skippedLineNumbers = $skippedLineNumbers;
- }
-
- /**
- * Parses a YAML string to a PHP value.
- *
- * @param string $value A YAML string
- * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types (a PHP resource or object), false otherwise
- * @param bool $objectSupport True if object support is enabled, false otherwise
- * @param bool $objectForMap True if maps should return a stdClass instead of array()
- *
- * @return mixed A PHP value
- *
- * @throws ParseException If the YAML is not valid
- */
- public function parse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
- {
- if (false === preg_match('//u', $value)) {
- throw new ParseException('The YAML value does not appear to be valid UTF-8.');
- }
-
- $this->refs = array();
-
- $mbEncoding = null;
- $e = null;
- $data = null;
-
- if (2 /* MB_OVERLOAD_STRING */ & (int) ini_get('mbstring.func_overload')) {
- $mbEncoding = mb_internal_encoding();
- mb_internal_encoding('UTF-8');
- }
-
- try {
- $data = $this->doParse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
- } catch (\Exception $e) {
- } catch (\Throwable $e) {
- }
-
- if (null !== $mbEncoding) {
- mb_internal_encoding($mbEncoding);
- }
-
- $this->lines = array();
- $this->currentLine = '';
- $this->refs = array();
- $this->skippedLineNumbers = array();
- $this->locallySkippedLineNumbers = array();
-
- if (null !== $e) {
- throw $e;
- }
-
- return $data;
- }
-
- private function doParse($value, $exceptionOnInvalidType = false, $objectSupport = false, $objectForMap = false)
- {
- $this->currentLineNb = -1;
- $this->currentLine = '';
- $value = $this->cleanup($value);
- $this->lines = explode("\n", $value);
- $this->locallySkippedLineNumbers = array();
-
- if (null === $this->totalNumberOfLines) {
- $this->totalNumberOfLines = \count($this->lines);
- }
-
- $data = array();
- $context = null;
- $allowOverwrite = false;
-
- while ($this->moveToNextLine()) {
- if ($this->isCurrentLineEmpty()) {
- continue;
- }
-
- // tab?
- if ("\t" === $this->currentLine[0]) {
- throw new ParseException('A YAML file cannot contain tabs as indentation.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
- }
-
- $isRef = $mergeNode = false;
- if (self::preg_match('#^\-((?P\s+)(?P.+))?$#u', rtrim($this->currentLine), $values)) {
- if ($context && 'mapping' == $context) {
- throw new ParseException('You cannot define a sequence item when in a mapping', $this->getRealCurrentLineNb() + 1, $this->currentLine);
- }
- $context = 'sequence';
-
- if (isset($values['value']) && self::preg_match('#^&(?P[[^ ]+) *(?P.*)#u', $values['value'], $matches)) {
- $isRef = $matches['ref'];
- $values['value'] = $matches['value'];
- }
-
- // array
- if (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#')) {
- $data[] = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(null, true), $exceptionOnInvalidType, $objectSupport, $objectForMap);
- } else {
- if (isset($values['leadspaces'])
- && self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\{\[].*?) *\:(\s+(?P.+))?$#u', rtrim($values['value']), $matches)
- ) {
- // this is a compact notation element, add to next block and parse
- $block = $values['value'];
- if ($this->isNextLineIndented()) {
- $block .= "\n".$this->getNextEmbedBlock($this->getCurrentLineIndentation() + \strlen($values['leadspaces']) + 1);
- }
-
- $data[] = $this->parseBlock($this->getRealCurrentLineNb(), $block, $exceptionOnInvalidType, $objectSupport, $objectForMap);
- } else {
- $data[] = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context);
- }
- }
- if ($isRef) {
- $this->refs[$isRef] = end($data);
- }
- } elseif (
- self::preg_match('#^(?P'.Inline::REGEX_QUOTED_STRING.'|[^ \'"\[\{].*?) *\:(\s+(?P.+))?$#u', rtrim($this->currentLine), $values)
- && (false === strpos($values['key'], ' #') || \in_array($values['key'][0], array('"', "'")))
- ) {
- if ($context && 'sequence' == $context) {
- throw new ParseException('You cannot define a mapping item when in a sequence', $this->currentLineNb + 1, $this->currentLine);
- }
- $context = 'mapping';
-
- // force correct settings
- Inline::parse(null, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
- try {
- $key = Inline::parseScalar($values['key']);
- } catch (ParseException $e) {
- $e->setParsedLine($this->getRealCurrentLineNb() + 1);
- $e->setSnippet($this->currentLine);
-
- throw $e;
- }
-
- // Convert float keys to strings, to avoid being converted to integers by PHP
- if (\is_float($key)) {
- $key = (string) $key;
- }
-
- if ('<<' === $key && (!isset($values['value']) || !self::preg_match('#^&(?P][[^ ]+)#u', $values['value'], $refMatches))) {
- $mergeNode = true;
- $allowOverwrite = true;
- if (isset($values['value']) && 0 === strpos($values['value'], '*')) {
- $refName = substr($values['value'], 1);
- if (!array_key_exists($refName, $this->refs)) {
- throw new ParseException(sprintf('Reference "%s" does not exist.', $refName), $this->getRealCurrentLineNb() + 1, $this->currentLine);
- }
-
- $refValue = $this->refs[$refName];
-
- if (!\is_array($refValue)) {
- throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
- }
-
- $data += $refValue; // array union
- } else {
- if (isset($values['value']) && '' !== $values['value']) {
- $value = $values['value'];
- } else {
- $value = $this->getNextEmbedBlock();
- }
- $parsed = $this->parseBlock($this->getRealCurrentLineNb() + 1, $value, $exceptionOnInvalidType, $objectSupport, $objectForMap);
-
- if (!\is_array($parsed)) {
- throw new ParseException('YAML merge keys used with a scalar value instead of an array.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
- }
-
- if (isset($parsed[0])) {
- // If the value associated with the merge key is a sequence, then this sequence is expected to contain mapping nodes
- // and each of these nodes is merged in turn according to its order in the sequence. Keys in mapping nodes earlier
- // in the sequence override keys specified in later mapping nodes.
- foreach ($parsed as $parsedItem) {
- if (!\is_array($parsedItem)) {
- throw new ParseException('Merge items must be arrays.', $this->getRealCurrentLineNb() + 1, $parsedItem);
- }
-
- $data += $parsedItem; // array union
- }
- } else {
- // If the value associated with the key is a single mapping node, each of its key/value pairs is inserted into the
- // current mapping, unless the key already exists in it.
- $data += $parsed; // array union
- }
- }
- } elseif ('<<' !== $key && isset($values['value']) && self::preg_match('#^&(?P][[^ ]+) *(?P.*)#u', $values['value'], $matches)) {
- $isRef = $matches['ref'];
- $values['value'] = $matches['value'];
- }
-
- if ($mergeNode) {
- // Merge keys
- } elseif (!isset($values['value']) || '' == trim($values['value'], ' ') || 0 === strpos(ltrim($values['value'], ' '), '#') || '<<' === $key) {
- // hash
- // if next line is less indented or equal, then it means that the current value is null
- if (!$this->isNextLineIndented() && !$this->isNextLineUnIndentedCollection()) {
- // Spec: Keys MUST be unique; first one wins.
- // But overwriting is allowed when a merge node is used in current block.
- if ($allowOverwrite || !isset($data[$key])) {
- $data[$key] = null;
- }
- } else {
- $value = $this->parseBlock($this->getRealCurrentLineNb() + 1, $this->getNextEmbedBlock(), $exceptionOnInvalidType, $objectSupport, $objectForMap);
-
- if ('<<' === $key) {
- $this->refs[$refMatches['ref']] = $value;
- $data += $value;
- } elseif ($allowOverwrite || !isset($data[$key])) {
- // Spec: Keys MUST be unique; first one wins.
- // But overwriting is allowed when a merge node is used in current block.
- $data[$key] = $value;
- }
- }
- } else {
- $value = $this->parseValue($values['value'], $exceptionOnInvalidType, $objectSupport, $objectForMap, $context);
- // Spec: Keys MUST be unique; first one wins.
- // But overwriting is allowed when a merge node is used in current block.
- if ($allowOverwrite || !isset($data[$key])) {
- $data[$key] = $value;
- }
- }
- if ($isRef) {
- $this->refs[$isRef] = $data[$key];
- }
- } else {
- // multiple documents are not supported
- if ('---' === $this->currentLine) {
- throw new ParseException('Multiple documents are not supported.', $this->currentLineNb + 1, $this->currentLine);
- }
-
- // 1-liner optionally followed by newline(s)
- if (\is_string($value) && $this->lines[0] === trim($value)) {
- try {
- $value = Inline::parse($this->lines[0], $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
- } catch (ParseException $e) {
- $e->setParsedLine($this->getRealCurrentLineNb() + 1);
- $e->setSnippet($this->currentLine);
-
- throw $e;
- }
-
- return $value;
- }
-
- throw new ParseException('Unable to parse.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
- }
- }
-
- if ($objectForMap && !\is_object($data) && 'mapping' === $context) {
- $object = new \stdClass();
-
- foreach ($data as $key => $value) {
- $object->$key = $value;
- }
-
- $data = $object;
- }
-
- return empty($data) ? null : $data;
- }
-
- private function parseBlock($offset, $yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap)
- {
- $skippedLineNumbers = $this->skippedLineNumbers;
-
- foreach ($this->locallySkippedLineNumbers as $lineNumber) {
- if ($lineNumber < $offset) {
- continue;
- }
-
- $skippedLineNumbers[] = $lineNumber;
- }
-
- $parser = new self($offset, $this->totalNumberOfLines, $skippedLineNumbers);
- $parser->refs = &$this->refs;
-
- return $parser->doParse($yaml, $exceptionOnInvalidType, $objectSupport, $objectForMap);
- }
-
- /**
- * Returns the current line number (takes the offset into account).
- *
- * @return int The current line number
- */
- private function getRealCurrentLineNb()
- {
- $realCurrentLineNumber = $this->currentLineNb + $this->offset;
-
- foreach ($this->skippedLineNumbers as $skippedLineNumber) {
- if ($skippedLineNumber > $realCurrentLineNumber) {
- break;
- }
-
- ++$realCurrentLineNumber;
- }
-
- return $realCurrentLineNumber;
- }
-
- /**
- * Returns the current line indentation.
- *
- * @return int The current line indentation
- */
- private function getCurrentLineIndentation()
- {
- return \strlen($this->currentLine) - \strlen(ltrim($this->currentLine, ' '));
- }
-
- /**
- * Returns the next embed block of YAML.
- *
- * @param int $indentation The indent level at which the block is to be read, or null for default
- * @param bool $inSequence True if the enclosing data structure is a sequence
- *
- * @return string A YAML string
- *
- * @throws ParseException When indentation problem are detected
- */
- private function getNextEmbedBlock($indentation = null, $inSequence = false)
- {
- $oldLineIndentation = $this->getCurrentLineIndentation();
- $blockScalarIndentations = array();
-
- if ($this->isBlockScalarHeader()) {
- $blockScalarIndentations[] = $this->getCurrentLineIndentation();
- }
-
- if (!$this->moveToNextLine()) {
- return;
- }
-
- if (null === $indentation) {
- $newIndent = $this->getCurrentLineIndentation();
-
- $unindentedEmbedBlock = $this->isStringUnIndentedCollectionItem();
-
- if (!$this->isCurrentLineEmpty() && 0 === $newIndent && !$unindentedEmbedBlock) {
- throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
- }
- } else {
- $newIndent = $indentation;
- }
-
- $data = array();
- if ($this->getCurrentLineIndentation() >= $newIndent) {
- $data[] = substr($this->currentLine, $newIndent);
- } else {
- $this->moveToPreviousLine();
-
- return;
- }
-
- if ($inSequence && $oldLineIndentation === $newIndent && isset($data[0][0]) && '-' === $data[0][0]) {
- // the previous line contained a dash but no item content, this line is a sequence item with the same indentation
- // and therefore no nested list or mapping
- $this->moveToPreviousLine();
-
- return;
- }
-
- $isItUnindentedCollection = $this->isStringUnIndentedCollectionItem();
-
- if (empty($blockScalarIndentations) && $this->isBlockScalarHeader()) {
- $blockScalarIndentations[] = $this->getCurrentLineIndentation();
- }
-
- $previousLineIndentation = $this->getCurrentLineIndentation();
-
- while ($this->moveToNextLine()) {
- $indent = $this->getCurrentLineIndentation();
-
- // terminate all block scalars that are more indented than the current line
- if (!empty($blockScalarIndentations) && $indent < $previousLineIndentation && '' !== trim($this->currentLine)) {
- foreach ($blockScalarIndentations as $key => $blockScalarIndentation) {
- if ($blockScalarIndentation >= $this->getCurrentLineIndentation()) {
- unset($blockScalarIndentations[$key]);
- }
- }
- }
-
- if (empty($blockScalarIndentations) && !$this->isCurrentLineComment() && $this->isBlockScalarHeader()) {
- $blockScalarIndentations[] = $this->getCurrentLineIndentation();
- }
-
- $previousLineIndentation = $indent;
-
- if ($isItUnindentedCollection && !$this->isCurrentLineEmpty() && !$this->isStringUnIndentedCollectionItem() && $newIndent === $indent) {
- $this->moveToPreviousLine();
- break;
- }
-
- if ($this->isCurrentLineBlank()) {
- $data[] = substr($this->currentLine, $newIndent);
- continue;
- }
-
- // we ignore "comment" lines only when we are not inside a scalar block
- if (empty($blockScalarIndentations) && $this->isCurrentLineComment()) {
- // remember ignored comment lines (they are used later in nested
- // parser calls to determine real line numbers)
- //
- // CAUTION: beware to not populate the global property here as it
- // will otherwise influence the getRealCurrentLineNb() call here
- // for consecutive comment lines and subsequent embedded blocks
- $this->locallySkippedLineNumbers[] = $this->getRealCurrentLineNb();
-
- continue;
- }
-
- if ($indent >= $newIndent) {
- $data[] = substr($this->currentLine, $newIndent);
- } elseif (0 == $indent) {
- $this->moveToPreviousLine();
-
- break;
- } else {
- throw new ParseException('Indentation problem.', $this->getRealCurrentLineNb() + 1, $this->currentLine);
- }
- }
-
- return implode("\n", $data);
- }
-
- /**
- * Moves the parser to the next line.
- *
- * @return bool
- */
- private function moveToNextLine()
- {
- if ($this->currentLineNb >= \count($this->lines) - 1) {
- return false;
- }
-
- $this->currentLine = $this->lines[++$this->currentLineNb];
-
- return true;
- }
-
- /**
- * Moves the parser to the previous line.
- *
- * @return bool
- */
- private function moveToPreviousLine()
- {
- if ($this->currentLineNb < 1) {
- return false;
- }
-
- $this->currentLine = $this->lines[--$this->currentLineNb];
-
- return true;
- }
-
- /**
- * Parses a YAML value.
- *
- * @param string $value A YAML value
- * @param bool $exceptionOnInvalidType True if an exception must be thrown on invalid types false otherwise
- * @param bool $objectSupport True if object support is enabled, false otherwise
- * @param bool $objectForMap True if maps should return a stdClass instead of array()
- * @param string $context The parser context (either sequence or mapping)
- *
- * @return mixed A PHP value
- *
- * @throws ParseException When reference does not exist
- */
- private function parseValue($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $context)
- {
- if (0 === strpos($value, '*')) {
- if (false !== $pos = strpos($value, '#')) {
- $value = substr($value, 1, $pos - 2);
- } else {
- $value = substr($value, 1);
- }
-
- if (!array_key_exists($value, $this->refs)) {
- throw new ParseException(sprintf('Reference "%s" does not exist.', $value), $this->currentLineNb + 1, $this->currentLine);
- }
-
- return $this->refs[$value];
- }
-
- if (self::preg_match('/^'.self::BLOCK_SCALAR_HEADER_PATTERN.'$/', $value, $matches)) {
- $modifiers = isset($matches['modifiers']) ? $matches['modifiers'] : '';
-
- return $this->parseBlockScalar($matches['separator'], preg_replace('#\d+#', '', $modifiers), (int) abs($modifiers));
- }
-
- try {
- $parsedValue = Inline::parse($value, $exceptionOnInvalidType, $objectSupport, $objectForMap, $this->refs);
-
- if ('mapping' === $context && '"' !== $value[0] && "'" !== $value[0] && '[' !== $value[0] && '{' !== $value[0] && '!' !== $value[0] && false !== strpos($parsedValue, ': ')) {
- @trigger_error(sprintf('Using a colon in the unquoted mapping value "%s" in line %d is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.', $value, $this->getRealCurrentLineNb() + 1), E_USER_DEPRECATED);
-
- // to be thrown in 3.0
- // throw new ParseException('A colon cannot be used in an unquoted mapping value.');
- }
-
- return $parsedValue;
- } catch (ParseException $e) {
- $e->setParsedLine($this->getRealCurrentLineNb() + 1);
- $e->setSnippet($this->currentLine);
-
- throw $e;
- }
- }
-
- /**
- * Parses a block scalar.
- *
- * @param string $style The style indicator that was used to begin this block scalar (| or >)
- * @param string $chomping The chomping indicator that was used to begin this block scalar (+ or -)
- * @param int $indentation The indentation indicator that was used to begin this block scalar
- *
- * @return string The text value
- */
- private function parseBlockScalar($style, $chomping = '', $indentation = 0)
- {
- $notEOF = $this->moveToNextLine();
- if (!$notEOF) {
- return '';
- }
-
- $isCurrentLineBlank = $this->isCurrentLineBlank();
- $blockLines = array();
-
- // leading blank lines are consumed before determining indentation
- while ($notEOF && $isCurrentLineBlank) {
- // newline only if not EOF
- if ($notEOF = $this->moveToNextLine()) {
- $blockLines[] = '';
- $isCurrentLineBlank = $this->isCurrentLineBlank();
- }
- }
-
- // determine indentation if not specified
- if (0 === $indentation) {
- if (self::preg_match('/^ +/', $this->currentLine, $matches)) {
- $indentation = \strlen($matches[0]);
- }
- }
-
- if ($indentation > 0) {
- $pattern = sprintf('/^ {%d}(.*)$/', $indentation);
-
- while (
- $notEOF && (
- $isCurrentLineBlank ||
- self::preg_match($pattern, $this->currentLine, $matches)
- )
- ) {
- if ($isCurrentLineBlank && \strlen($this->currentLine) > $indentation) {
- $blockLines[] = substr($this->currentLine, $indentation);
- } elseif ($isCurrentLineBlank) {
- $blockLines[] = '';
- } else {
- $blockLines[] = $matches[1];
- }
-
- // newline only if not EOF
- if ($notEOF = $this->moveToNextLine()) {
- $isCurrentLineBlank = $this->isCurrentLineBlank();
- }
- }
- } elseif ($notEOF) {
- $blockLines[] = '';
- }
-
- if ($notEOF) {
- $blockLines[] = '';
- $this->moveToPreviousLine();
- } elseif (!$notEOF && !$this->isCurrentLineLastLineInDocument()) {
- $blockLines[] = '';
- }
-
- // folded style
- if ('>' === $style) {
- $text = '';
- $previousLineIndented = false;
- $previousLineBlank = false;
-
- for ($i = 0, $blockLinesCount = \count($blockLines); $i < $blockLinesCount; ++$i) {
- if ('' === $blockLines[$i]) {
- $text .= "\n";
- $previousLineIndented = false;
- $previousLineBlank = true;
- } elseif (' ' === $blockLines[$i][0]) {
- $text .= "\n".$blockLines[$i];
- $previousLineIndented = true;
- $previousLineBlank = false;
- } elseif ($previousLineIndented) {
- $text .= "\n".$blockLines[$i];
- $previousLineIndented = false;
- $previousLineBlank = false;
- } elseif ($previousLineBlank || 0 === $i) {
- $text .= $blockLines[$i];
- $previousLineIndented = false;
- $previousLineBlank = false;
- } else {
- $text .= ' '.$blockLines[$i];
- $previousLineIndented = false;
- $previousLineBlank = false;
- }
- }
- } else {
- $text = implode("\n", $blockLines);
- }
-
- // deal with trailing newlines
- if ('' === $chomping) {
- $text = preg_replace('/\n+$/', "\n", $text);
- } elseif ('-' === $chomping) {
- $text = preg_replace('/\n+$/', '', $text);
- }
-
- return $text;
- }
-
- /**
- * Returns true if the next line is indented.
- *
- * @return bool Returns true if the next line is indented, false otherwise
- */
- private function isNextLineIndented()
- {
- $currentIndentation = $this->getCurrentLineIndentation();
- $EOF = !$this->moveToNextLine();
-
- while (!$EOF && $this->isCurrentLineEmpty()) {
- $EOF = !$this->moveToNextLine();
- }
-
- if ($EOF) {
- return false;
- }
-
- $ret = $this->getCurrentLineIndentation() > $currentIndentation;
-
- $this->moveToPreviousLine();
-
- return $ret;
- }
-
- /**
- * Returns true if the current line is blank or if it is a comment line.
- *
- * @return bool Returns true if the current line is empty or if it is a comment line, false otherwise
- */
- private function isCurrentLineEmpty()
- {
- return $this->isCurrentLineBlank() || $this->isCurrentLineComment();
- }
-
- /**
- * Returns true if the current line is blank.
- *
- * @return bool Returns true if the current line is blank, false otherwise
- */
- private function isCurrentLineBlank()
- {
- return '' == trim($this->currentLine, ' ');
- }
-
- /**
- * Returns true if the current line is a comment line.
- *
- * @return bool Returns true if the current line is a comment line, false otherwise
- */
- private function isCurrentLineComment()
- {
- //checking explicitly the first char of the trim is faster than loops or strpos
- $ltrimmedLine = ltrim($this->currentLine, ' ');
-
- return '' !== $ltrimmedLine && '#' === $ltrimmedLine[0];
- }
-
- private function isCurrentLineLastLineInDocument()
- {
- return ($this->offset + $this->currentLineNb) >= ($this->totalNumberOfLines - 1);
- }
-
- /**
- * Cleanups a YAML string to be parsed.
- *
- * @param string $value The input YAML string
- *
- * @return string A cleaned up YAML string
- */
- private function cleanup($value)
- {
- $value = str_replace(array("\r\n", "\r"), "\n", $value);
-
- // strip YAML header
- $count = 0;
- $value = preg_replace('#^\%YAML[: ][\d\.]+.*\n#u', '', $value, -1, $count);
- $this->offset += $count;
-
- // remove leading comments
- $trimmedValue = preg_replace('#^(\#.*?\n)+#s', '', $value, -1, $count);
- if (1 == $count) {
- // items have been removed, update the offset
- $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
- $value = $trimmedValue;
- }
-
- // remove start of the document marker (---)
- $trimmedValue = preg_replace('#^\-\-\-.*?\n#s', '', $value, -1, $count);
- if (1 == $count) {
- // items have been removed, update the offset
- $this->offset += substr_count($value, "\n") - substr_count($trimmedValue, "\n");
- $value = $trimmedValue;
-
- // remove end of the document marker (...)
- $value = preg_replace('#\.\.\.\s*$#', '', $value);
- }
-
- return $value;
- }
-
- /**
- * Returns true if the next line starts unindented collection.
- *
- * @return bool Returns true if the next line starts unindented collection, false otherwise
- */
- private function isNextLineUnIndentedCollection()
- {
- $currentIndentation = $this->getCurrentLineIndentation();
- $notEOF = $this->moveToNextLine();
-
- while ($notEOF && $this->isCurrentLineEmpty()) {
- $notEOF = $this->moveToNextLine();
- }
-
- if (false === $notEOF) {
- return false;
- }
-
- $ret = $this->getCurrentLineIndentation() === $currentIndentation && $this->isStringUnIndentedCollectionItem();
-
- $this->moveToPreviousLine();
-
- return $ret;
- }
-
- /**
- * Returns true if the string is un-indented collection item.
- *
- * @return bool Returns true if the string is un-indented collection item, false otherwise
- */
- private function isStringUnIndentedCollectionItem()
- {
- return '-' === rtrim($this->currentLine) || 0 === strpos($this->currentLine, '- ');
- }
-
- /**
- * Tests whether or not the current line is the header of a block scalar.
- *
- * @return bool
- */
- private function isBlockScalarHeader()
- {
- return (bool) self::preg_match('~'.self::BLOCK_SCALAR_HEADER_PATTERN.'$~', $this->currentLine);
- }
-
- /**
- * A local wrapper for `preg_match` which will throw a ParseException if there
- * is an internal error in the PCRE engine.
- *
- * This avoids us needing to check for "false" every time PCRE is used
- * in the YAML engine
- *
- * @throws ParseException on a PCRE internal error
- *
- * @see preg_last_error()
- *
- * @internal
- */
- public static function preg_match($pattern, $subject, &$matches = null, $flags = 0, $offset = 0)
- {
- if (false === $ret = preg_match($pattern, $subject, $matches, $flags, $offset)) {
- switch (preg_last_error()) {
- case PREG_INTERNAL_ERROR:
- $error = 'Internal PCRE error.';
- break;
- case PREG_BACKTRACK_LIMIT_ERROR:
- $error = 'pcre.backtrack_limit reached.';
- break;
- case PREG_RECURSION_LIMIT_ERROR:
- $error = 'pcre.recursion_limit reached.';
- break;
- case PREG_BAD_UTF8_ERROR:
- $error = 'Malformed UTF-8 data.';
- break;
- case PREG_BAD_UTF8_OFFSET_ERROR:
- $error = 'Offset doesn\'t correspond to the begin of a valid UTF-8 code point.';
- break;
- default:
- $error = 'Error.';
- }
-
- throw new ParseException($error);
- }
-
- return $ret;
- }
-}
diff --git a/vendor/symfony/yaml/README.md b/vendor/symfony/yaml/README.md
deleted file mode 100755
index 0d324881c..000000000
--- a/vendor/symfony/yaml/README.md
+++ /dev/null
@@ -1,13 +0,0 @@
-Yaml Component
-==============
-
-The Yaml component loads and dumps YAML files.
-
-Resources
----------
-
- * [Documentation](https://symfony.com/doc/current/components/yaml/index.html)
- * [Contributing](https://symfony.com/doc/current/contributing/index.html)
- * [Report issues](https://github.com/symfony/symfony/issues) and
- [send Pull Requests](https://github.com/symfony/symfony/pulls)
- in the [main Symfony repository](https://github.com/symfony/symfony)
diff --git a/vendor/symfony/yaml/Tests/DumperTest.php b/vendor/symfony/yaml/Tests/DumperTest.php
deleted file mode 100755
index 6bac6dac1..000000000
--- a/vendor/symfony/yaml/Tests/DumperTest.php
+++ /dev/null
@@ -1,257 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Yaml\Tests;
-
-use PHPUnit\Framework\TestCase;
-use Symfony\Component\Yaml\Dumper;
-use Symfony\Component\Yaml\Parser;
-
-class DumperTest extends TestCase
-{
- protected $parser;
- protected $dumper;
- protected $path;
-
- protected $array = array(
- '' => 'bar',
- 'foo' => '#bar',
- 'foo\'bar' => array(),
- 'bar' => array(1, 'foo'),
- 'foobar' => array(
- 'foo' => 'bar',
- 'bar' => array(1, 'foo'),
- 'foobar' => array(
- 'foo' => 'bar',
- 'bar' => array(1, 'foo'),
- ),
- ),
- );
-
- protected function setUp()
- {
- $this->parser = new Parser();
- $this->dumper = new Dumper();
- $this->path = __DIR__.'/Fixtures';
- }
-
- protected function tearDown()
- {
- $this->parser = null;
- $this->dumper = null;
- $this->path = null;
- $this->array = null;
- }
-
- public function testSetIndentation()
- {
- $this->dumper->setIndentation(7);
-
- $expected = <<<'EOF'
-'': bar
-foo: '#bar'
-'foo''bar': { }
-bar:
- - 1
- - foo
-foobar:
- foo: bar
- bar:
- - 1
- - foo
- foobar:
- foo: bar
- bar:
- - 1
- - foo
-
-EOF;
- $this->assertEquals($expected, $this->dumper->dump($this->array, 4, 0));
- }
-
- public function testSpecifications()
- {
- $files = $this->parser->parse(file_get_contents($this->path.'/index.yml'));
- foreach ($files as $file) {
- $yamls = file_get_contents($this->path.'/'.$file.'.yml');
-
- // split YAMLs documents
- foreach (preg_split('/^---( %YAML\:1\.0)?/m', $yamls) as $yaml) {
- if (!$yaml) {
- continue;
- }
-
- $test = $this->parser->parse($yaml);
- if (isset($test['dump_skip']) && $test['dump_skip']) {
- continue;
- } elseif (isset($test['todo']) && $test['todo']) {
- // TODO
- } else {
- eval('$expected = '.trim($test['php']).';');
- $this->assertSame($expected, $this->parser->parse($this->dumper->dump($expected, 10)), $test['test']);
- }
- }
- }
- }
-
- public function testInlineLevel()
- {
- $expected = <<<'EOF'
-{ '': bar, foo: '#bar', 'foo''bar': { }, bar: [1, foo], foobar: { foo: bar, bar: [1, foo], foobar: { foo: bar, bar: [1, foo] } } }
-EOF;
- $this->assertEquals($expected, $this->dumper->dump($this->array, -10), '->dump() takes an inline level argument');
- $this->assertEquals($expected, $this->dumper->dump($this->array, 0), '->dump() takes an inline level argument');
-
- $expected = <<<'EOF'
-'': bar
-foo: '#bar'
-'foo''bar': { }
-bar: [1, foo]
-foobar: { foo: bar, bar: [1, foo], foobar: { foo: bar, bar: [1, foo] } }
-
-EOF;
- $this->assertEquals($expected, $this->dumper->dump($this->array, 1), '->dump() takes an inline level argument');
-
- $expected = <<<'EOF'
-'': bar
-foo: '#bar'
-'foo''bar': { }
-bar:
- - 1
- - foo
-foobar:
- foo: bar
- bar: [1, foo]
- foobar: { foo: bar, bar: [1, foo] }
-
-EOF;
- $this->assertEquals($expected, $this->dumper->dump($this->array, 2), '->dump() takes an inline level argument');
-
- $expected = <<<'EOF'
-'': bar
-foo: '#bar'
-'foo''bar': { }
-bar:
- - 1
- - foo
-foobar:
- foo: bar
- bar:
- - 1
- - foo
- foobar:
- foo: bar
- bar: [1, foo]
-
-EOF;
- $this->assertEquals($expected, $this->dumper->dump($this->array, 3), '->dump() takes an inline level argument');
-
- $expected = <<<'EOF'
-'': bar
-foo: '#bar'
-'foo''bar': { }
-bar:
- - 1
- - foo
-foobar:
- foo: bar
- bar:
- - 1
- - foo
- foobar:
- foo: bar
- bar:
- - 1
- - foo
-
-EOF;
- $this->assertEquals($expected, $this->dumper->dump($this->array, 4), '->dump() takes an inline level argument');
- $this->assertEquals($expected, $this->dumper->dump($this->array, 10), '->dump() takes an inline level argument');
- }
-
- public function testObjectSupportEnabled()
- {
- $dump = $this->dumper->dump(array('foo' => new A(), 'bar' => 1), 0, 0, false, true);
-
- $this->assertEquals('{ foo: !php/object:O:30:"Symfony\Component\Yaml\Tests\A":1:{s:1:"a";s:3:"foo";}, bar: 1 }', $dump, '->dump() is able to dump objects');
- }
-
- public function testObjectSupportDisabledButNoExceptions()
- {
- $dump = $this->dumper->dump(array('foo' => new A(), 'bar' => 1));
-
- $this->assertEquals('{ foo: null, bar: 1 }', $dump, '->dump() does not dump objects when disabled');
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\DumpException
- */
- public function testObjectSupportDisabledWithExceptions()
- {
- $this->dumper->dump(array('foo' => new A(), 'bar' => 1), 0, 0, true, false);
- }
-
- /**
- * @dataProvider getEscapeSequences
- */
- public function testEscapedEscapeSequencesInQuotedScalar($input, $expected)
- {
- $this->assertEquals($expected, $this->dumper->dump($input));
- }
-
- public function getEscapeSequences()
- {
- return array(
- 'empty string' => array('', "''"),
- 'null' => array("\x0", '"\\0"'),
- 'bell' => array("\x7", '"\\a"'),
- 'backspace' => array("\x8", '"\\b"'),
- 'horizontal-tab' => array("\t", '"\\t"'),
- 'line-feed' => array("\n", '"\\n"'),
- 'vertical-tab' => array("\v", '"\\v"'),
- 'form-feed' => array("\xC", '"\\f"'),
- 'carriage-return' => array("\r", '"\\r"'),
- 'escape' => array("\x1B", '"\\e"'),
- 'space' => array(' ', "' '"),
- 'double-quote' => array('"', "'\"'"),
- 'slash' => array('/', '/'),
- 'backslash' => array('\\', '\\'),
- 'next-line' => array("\xC2\x85", '"\\N"'),
- 'non-breaking-space' => array("\xc2\xa0", '"\\_"'),
- 'line-separator' => array("\xE2\x80\xA8", '"\\L"'),
- 'paragraph-separator' => array("\xE2\x80\xA9", '"\\P"'),
- 'colon' => array(':', "':'"),
- );
- }
-
- /**
- * @expectedException \InvalidArgumentException
- * @expectedExceptionMessage The indentation must be greater than zero
- */
- public function testZeroIndentationThrowsException()
- {
- $this->dumper->setIndentation(0);
- }
-
- /**
- * @expectedException \InvalidArgumentException
- * @expectedExceptionMessage The indentation must be greater than zero
- */
- public function testNegativeIndentationThrowsException()
- {
- $this->dumper->setIndentation(-4);
- }
-}
-
-class A
-{
- public $a = 'foo';
-}
diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsAnchorAlias.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsAnchorAlias.yml
deleted file mode 100755
index 5f9c94275..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/YtsAnchorAlias.yml
+++ /dev/null
@@ -1,31 +0,0 @@
---- %YAML:1.0
-test: Simple Alias Example
-brief: >
- If you need to refer to the same item of data twice,
- you can give that item an alias. The alias is a plain
- string, starting with an ampersand. The item may then
- be referred to by the alias throughout your document
- by using an asterisk before the name of the alias.
- This is called an anchor.
-yaml: |
- - &showell Steve
- - Clark
- - Brian
- - Oren
- - *showell
-php: |
- array('Steve', 'Clark', 'Brian', 'Oren', 'Steve')
-
----
-test: Alias of a Mapping
-brief: >
- An alias can be used on any item of data, including
- sequences, mappings, and other complex data types.
-yaml: |
- - &hello
- Meat: pork
- Starch: potato
- - banana
- - *hello
-php: |
- array(array('Meat'=>'pork', 'Starch'=>'potato'), 'banana', array('Meat'=>'pork', 'Starch'=>'potato'))
diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsBasicTests.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsBasicTests.yml
deleted file mode 100755
index dfd93021d..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/YtsBasicTests.yml
+++ /dev/null
@@ -1,202 +0,0 @@
---- %YAML:1.0
-test: Simple Sequence
-brief: |
- You can specify a list in YAML by placing each
- member of the list on a new line with an opening
- dash. These lists are called sequences.
-yaml: |
- - apple
- - banana
- - carrot
-php: |
- array('apple', 'banana', 'carrot')
----
-test: Sequence With Item Being Null In The Middle
-brief: |
- You can specify a list in YAML by placing each
- member of the list on a new line with an opening
- dash. These lists are called sequences.
-yaml: |
- - apple
- -
- - carrot
-php: |
- array('apple', null, 'carrot')
----
-test: Sequence With Last Item Being Null
-brief: |
- You can specify a list in YAML by placing each
- member of the list on a new line with an opening
- dash. These lists are called sequences.
-yaml: |
- - apple
- - banana
- -
-php: |
- array('apple', 'banana', null)
----
-test: Nested Sequences
-brief: |
- You can include a sequence within another
- sequence by giving the sequence an empty
- dash, followed by an indented list.
-yaml: |
- -
- - foo
- - bar
- - baz
-php: |
- array(array('foo', 'bar', 'baz'))
----
-test: Mixed Sequences
-brief: |
- Sequences can contain any YAML data,
- including strings and other sequences.
-yaml: |
- - apple
- -
- - foo
- - bar
- - x123
- - banana
- - carrot
-php: |
- array('apple', array('foo', 'bar', 'x123'), 'banana', 'carrot')
----
-test: Deeply Nested Sequences
-brief: |
- Sequences can be nested even deeper, with each
- level of indentation representing a level of
- depth.
-yaml: |
- -
- -
- - uno
- - dos
-php: |
- array(array(array('uno', 'dos')))
----
-test: Simple Mapping
-brief: |
- You can add a keyed list (also known as a dictionary or
- hash) to your document by placing each member of the
- list on a new line, with a colon separating the key
- from its value. In YAML, this type of list is called
- a mapping.
-yaml: |
- foo: whatever
- bar: stuff
-php: |
- array('foo' => 'whatever', 'bar' => 'stuff')
----
-test: Sequence in a Mapping
-brief: |
- A value in a mapping can be a sequence.
-yaml: |
- foo: whatever
- bar:
- - uno
- - dos
-php: |
- array('foo' => 'whatever', 'bar' => array('uno', 'dos'))
----
-test: Nested Mappings
-brief: |
- A value in a mapping can be another mapping.
-yaml: |
- foo: whatever
- bar:
- fruit: apple
- name: steve
- sport: baseball
-php: |
- array(
- 'foo' => 'whatever',
- 'bar' => array(
- 'fruit' => 'apple',
- 'name' => 'steve',
- 'sport' => 'baseball'
- )
- )
----
-test: Mixed Mapping
-brief: |
- A mapping can contain any assortment
- of mappings and sequences as values.
-yaml: |
- foo: whatever
- bar:
- -
- fruit: apple
- name: steve
- sport: baseball
- - more
- -
- python: rocks
- perl: papers
- ruby: scissorses
-php: |
- array(
- 'foo' => 'whatever',
- 'bar' => array(
- array(
- 'fruit' => 'apple',
- 'name' => 'steve',
- 'sport' => 'baseball'
- ),
- 'more',
- array(
- 'python' => 'rocks',
- 'perl' => 'papers',
- 'ruby' => 'scissorses'
- )
- )
- )
----
-test: Mapping-in-Sequence Shortcut
-todo: true
-brief: |
- If you are adding a mapping to a sequence, you
- can place the mapping on the same line as the
- dash as a shortcut.
-yaml: |
- - work on YAML.py:
- - work on Store
-php: |
- array(array('work on YAML.py' => array('work on Store')))
----
-test: Sequence-in-Mapping Shortcut
-todo: true
-brief: |
- The dash in a sequence counts as indentation, so
- you can add a sequence inside of a mapping without
- needing spaces as indentation.
-yaml: |
- allow:
- - 'localhost'
- - '%.sourceforge.net'
- - '%.freepan.org'
-php: |
- array('allow' => array('localhost', '%.sourceforge.net', '%.freepan.org'))
----
-todo: true
-test: Merge key
-brief: |
- A merge key ('<<') can be used in a mapping to insert other mappings. If
- the value associated with the merge key is a mapping, each of its key/value
- pairs is inserted into the current mapping.
-yaml: |
- mapping:
- name: Joe
- job: Accountant
- <<:
- age: 38
-php: |
- array(
- 'mapping' =>
- array(
- 'name' => 'Joe',
- 'job' => 'Accountant',
- 'age' => 38
- )
- )
diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsBlockMapping.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsBlockMapping.yml
deleted file mode 100755
index f7ca469b4..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/YtsBlockMapping.yml
+++ /dev/null
@@ -1,51 +0,0 @@
----
-test: One Element Mapping
-brief: |
- A mapping with one key/value pair
-yaml: |
- foo: bar
-php: |
- array('foo' => 'bar')
----
-test: Multi Element Mapping
-brief: |
- More than one key/value pair
-yaml: |
- red: baron
- white: walls
- blue: berries
-php: |
- array(
- 'red' => 'baron',
- 'white' => 'walls',
- 'blue' => 'berries',
- )
----
-test: Values aligned
-brief: |
- Often times human editors of documents will align the values even
- though YAML emitters generally don't.
-yaml: |
- red: baron
- white: walls
- blue: berries
-php: |
- array(
- 'red' => 'baron',
- 'white' => 'walls',
- 'blue' => 'berries',
- )
----
-test: Colons aligned
-brief: |
- Spaces can come before the ': ' key/value separator.
-yaml: |
- red : baron
- white : walls
- blue : berries
-php: |
- array(
- 'red' => 'baron',
- 'white' => 'walls',
- 'blue' => 'berries',
- )
diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsDocumentSeparator.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsDocumentSeparator.yml
deleted file mode 100755
index d98810256..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/YtsDocumentSeparator.yml
+++ /dev/null
@@ -1,85 +0,0 @@
---- %YAML:1.0
-test: Trailing Document Separator
-todo: true
-brief: >
- You can separate YAML documents
- with a string of three dashes.
-yaml: |
- - foo: 1
- bar: 2
- ---
- more: stuff
-python: |
- [
- [ { 'foo': 1, 'bar': 2 } ],
- { 'more': 'stuff' }
- ]
-ruby: |
- [ { 'foo' => 1, 'bar' => 2 } ]
-
----
-test: Leading Document Separator
-todo: true
-brief: >
- You can explicitly give an opening
- document separator to your YAML stream.
-yaml: |
- ---
- - foo: 1
- bar: 2
- ---
- more: stuff
-python: |
- [
- [ {'foo': 1, 'bar': 2}],
- {'more': 'stuff'}
- ]
-ruby: |
- [ { 'foo' => 1, 'bar' => 2 } ]
-
----
-test: YAML Header
-todo: true
-brief: >
- The opening separator can contain directives
- to the YAML parser, such as the version
- number.
-yaml: |
- --- %YAML:1.0
- foo: 1
- bar: 2
-php: |
- array('foo' => 1, 'bar' => 2)
-documents: 1
-
----
-test: Red Herring Document Separator
-brief: >
- Separators included in blocks or strings
- are treated as blocks or strings, as the
- document separator should have no indentation
- preceding it.
-yaml: |
- foo: |
- ---
-php: |
- array('foo' => "---\n")
-
----
-test: Multiple Document Separators in Block
-brief: >
- This technique allows you to embed other YAML
- documents within literal blocks.
-yaml: |
- foo: |
- ---
- foo: bar
- ---
- yo: baz
- bar: |
- fooness
-php: |
- array(
- 'foo' => "---\nfoo: bar\n---\nyo: baz\n",
- 'bar' => "fooness\n"
- )
diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsErrorTests.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsErrorTests.yml
deleted file mode 100755
index e8506fcb6..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/YtsErrorTests.yml
+++ /dev/null
@@ -1,25 +0,0 @@
----
-test: Missing value for hash item
-todo: true
-brief: |
- Third item in this hash doesn't have a value
-yaml: |
- okay: value
- also okay: ~
- causes error because no value specified
- last key: value okay here too
-python-error: causes error because no value specified
-
----
-test: Not indenting enough
-brief: |
- There was a bug in PyYaml where it was off by one
- in the indentation check. It was allowing the YAML
- below.
-# This is actually valid YAML now. Someone should tell showell.
-yaml: |
- foo:
- firstline: 1
- secondline: 2
-php: |
- array('foo' => null, 'firstline' => 1, 'secondline' => 2)
diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsFlowCollections.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsFlowCollections.yml
deleted file mode 100755
index 03090e4ab..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/YtsFlowCollections.yml
+++ /dev/null
@@ -1,60 +0,0 @@
----
-test: Simple Inline Array
-brief: >
- Sequences can be contained on a
- single line, using the inline syntax.
- Separate each entry with commas and
- enclose in square brackets.
-yaml: |
- seq: [ a, b, c ]
-php: |
- array('seq' => array('a', 'b', 'c'))
----
-test: Simple Inline Hash
-brief: >
- Mapping can also be contained on
- a single line, using the inline
- syntax. Each key-value pair is
- separated by a colon, with a comma
- between each entry in the mapping.
- Enclose with curly braces.
-yaml: |
- hash: { name: Steve, foo: bar }
-php: |
- array('hash' => array('name' => 'Steve', 'foo' => 'bar'))
----
-test: Multi-line Inline Collections
-todo: true
-brief: >
- Both inline sequences and inline mappings
- can span multiple lines, provided that you
- indent the additional lines.
-yaml: |
- languages: [ Ruby,
- Perl,
- Python ]
- websites: { YAML: yaml.org,
- Ruby: ruby-lang.org,
- Python: python.org,
- Perl: use.perl.org }
-php: |
- array(
- 'languages' => array('Ruby', 'Perl', 'Python'),
- 'websites' => array(
- 'YAML' => 'yaml.org',
- 'Ruby' => 'ruby-lang.org',
- 'Python' => 'python.org',
- 'Perl' => 'use.perl.org'
- )
- )
----
-test: Commas in Values (not in the spec!)
-todo: true
-brief: >
- List items in collections are delimited by commas, but
- there must be a space after each comma. This allows you
- to add numbers without quoting.
-yaml: |
- attendances: [ 45,123, 70,000, 17,222 ]
-php: |
- array('attendances' => array(45123, 70000, 17222))
diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsFoldedScalars.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsFoldedScalars.yml
deleted file mode 100755
index a14735a55..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/YtsFoldedScalars.yml
+++ /dev/null
@@ -1,176 +0,0 @@
---- %YAML:1.0
-test: Single ending newline
-brief: >
- A pipe character, followed by an indented
- block of text is treated as a literal
- block, in which newlines are preserved
- throughout the block, including the final
- newline.
-yaml: |
- ---
- this: |
- Foo
- Bar
-php: |
- array('this' => "Foo\nBar\n")
----
-test: The '+' indicator
-brief: >
- The '+' indicator says to keep newlines at the end of text
- blocks.
-yaml: |
- normal: |
- extra new lines not kept
-
- preserving: |+
- extra new lines are kept
-
-
- dummy: value
-php: |
- array(
- 'normal' => "extra new lines not kept\n",
- 'preserving' => "extra new lines are kept\n\n\n",
- 'dummy' => 'value'
- )
----
-test: Three trailing newlines in literals
-brief: >
- To give you more control over how space
- is preserved in text blocks, YAML has
- the keep '+' and chomp '-' indicators.
- The keep indicator will preserve all
- ending newlines, while the chomp indicator
- will strip all ending newlines.
-yaml: |
- clipped: |
- This has one newline.
-
-
-
- same as "clipped" above: "This has one newline.\n"
-
- stripped: |-
- This has no newline.
-
-
-
- same as "stripped" above: "This has no newline."
-
- kept: |+
- This has four newlines.
-
-
-
- same as "kept" above: "This has four newlines.\n\n\n\n"
-php: |
- array(
- 'clipped' => "This has one newline.\n",
- 'same as "clipped" above' => "This has one newline.\n",
- 'stripped' => 'This has no newline.',
- 'same as "stripped" above' => 'This has no newline.',
- 'kept' => "This has four newlines.\n\n\n\n",
- 'same as "kept" above' => "This has four newlines.\n\n\n\n"
- )
----
-test: Extra trailing newlines with spaces
-todo: true
-brief: >
- Normally, only a single newline is kept
- from the end of a literal block, unless the
- keep '+' character is used in combination
- with the pipe. The following example
- will preserve all ending whitespace
- since the last line of both literal blocks
- contains spaces which extend past the indentation
- level.
-yaml: |
- ---
- this: |
- Foo
-
-
- kept: |+
- Foo
-
-
-php: |
- array('this' => "Foo\n\n \n",
- 'kept' => "Foo\n\n \n" )
-
----
-test: Folded Block in a Sequence
-brief: >
- A greater-then character, followed by an indented
- block of text is treated as a folded block, in
- which lines of text separated by a single newline
- are concatenated as a single line.
-yaml: |
- ---
- - apple
- - banana
- - >
- can't you see
- the beauty of yaml?
- hmm
- - dog
-php: |
- array(
- 'apple',
- 'banana',
- "can't you see the beauty of yaml? hmm\n",
- 'dog'
- )
----
-test: Folded Block as a Mapping Value
-brief: >
- Both literal and folded blocks can be
- used in collections, as values in a
- sequence or a mapping.
-yaml: |
- ---
- quote: >
- Mark McGwire's
- year was crippled
- by a knee injury.
- source: espn
-php: |
- array(
- 'quote' => "Mark McGwire's year was crippled by a knee injury.\n",
- 'source' => 'espn'
- )
----
-test: Three trailing newlines in folded blocks
-brief: >
- The keep and chomp indicators can also
- be applied to folded blocks.
-yaml: |
- clipped: >
- This has one newline.
-
-
-
- same as "clipped" above: "This has one newline.\n"
-
- stripped: >-
- This has no newline.
-
-
-
- same as "stripped" above: "This has no newline."
-
- kept: >+
- This has four newlines.
-
-
-
- same as "kept" above: "This has four newlines.\n\n\n\n"
-php: |
- array(
- 'clipped' => "This has one newline.\n",
- 'same as "clipped" above' => "This has one newline.\n",
- 'stripped' => 'This has no newline.',
- 'same as "stripped" above' => 'This has no newline.',
- 'kept' => "This has four newlines.\n\n\n\n",
- 'same as "kept" above' => "This has four newlines.\n\n\n\n"
- )
diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsNullsAndEmpties.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsNullsAndEmpties.yml
deleted file mode 100755
index 9a5300f2e..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/YtsNullsAndEmpties.yml
+++ /dev/null
@@ -1,45 +0,0 @@
---- %YAML:1.0
-test: Empty Sequence
-brief: >
- You can represent the empty sequence
- with an empty inline sequence.
-yaml: |
- empty: []
-php: |
- array('empty' => array())
----
-test: Empty Mapping
-brief: >
- You can represent the empty mapping
- with an empty inline mapping.
-yaml: |
- empty: {}
-php: |
- array('empty' => array())
----
-test: Empty Sequence as Entire Document
-yaml: |
- []
-php: |
- array()
----
-test: Empty Mapping as Entire Document
-yaml: |
- {}
-php: |
- array()
----
-test: Null as Document
-yaml: |
- ~
-php: |
- null
----
-test: Empty String
-brief: >
- You can represent an empty string
- with a pair of quotes.
-yaml: |
- ''
-php: |
- ''
diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsSpecificationExamples.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsSpecificationExamples.yml
deleted file mode 100755
index ec1c4c3a1..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/YtsSpecificationExamples.yml
+++ /dev/null
@@ -1,1697 +0,0 @@
---- %YAML:1.0
-test: Sequence of scalars
-spec: 2.1
-yaml: |
- - Mark McGwire
- - Sammy Sosa
- - Ken Griffey
-php: |
- array('Mark McGwire', 'Sammy Sosa', 'Ken Griffey')
----
-test: Mapping of scalars to scalars
-spec: 2.2
-yaml: |
- hr: 65
- avg: 0.278
- rbi: 147
-php: |
- array('hr' => 65, 'avg' => 0.278, 'rbi' => 147)
----
-test: Mapping of scalars to sequences
-spec: 2.3
-yaml: |
- american:
- - Boston Red Sox
- - Detroit Tigers
- - New York Yankees
- national:
- - New York Mets
- - Chicago Cubs
- - Atlanta Braves
-php: |
- array('american' =>
- array( 'Boston Red Sox', 'Detroit Tigers',
- 'New York Yankees' ),
- 'national' =>
- array( 'New York Mets', 'Chicago Cubs',
- 'Atlanta Braves' )
- )
----
-test: Sequence of mappings
-spec: 2.4
-yaml: |
- -
- name: Mark McGwire
- hr: 65
- avg: 0.278
- -
- name: Sammy Sosa
- hr: 63
- avg: 0.288
-php: |
- array(
- array('name' => 'Mark McGwire', 'hr' => 65, 'avg' => 0.278),
- array('name' => 'Sammy Sosa', 'hr' => 63, 'avg' => 0.288)
- )
----
-test: Legacy A5
-todo: true
-spec: legacy_A5
-yaml: |
- ?
- - New York Yankees
- - Atlanta Braves
- :
- - 2001-07-02
- - 2001-08-12
- - 2001-08-14
- ?
- - Detroit Tigers
- - Chicago Cubs
- :
- - 2001-07-23
-perl-busted: >
- YAML.pm will be able to emulate this behavior soon. In this regard
- it may be somewhat more correct than Python's native behaviour which
- can only use tuples as mapping keys. PyYAML will also need to figure
- out some clever way to roundtrip structured keys.
-python: |
- [
- {
- ('New York Yankees', 'Atlanta Braves'):
- [yaml.timestamp('2001-07-02'),
- yaml.timestamp('2001-08-12'),
- yaml.timestamp('2001-08-14')],
- ('Detroit Tigers', 'Chicago Cubs'):
- [yaml.timestamp('2001-07-23')]
- }
- ]
-ruby: |
- {
- [ 'New York Yankees', 'Atlanta Braves' ] =>
- [ Date.new( 2001, 7, 2 ), Date.new( 2001, 8, 12 ), Date.new( 2001, 8, 14 ) ],
- [ 'Detroit Tigers', 'Chicago Cubs' ] =>
- [ Date.new( 2001, 7, 23 ) ]
- }
-syck: |
- struct test_node seq1[] = {
- { T_STR, 0, "New York Yankees" },
- { T_STR, 0, "Atlanta Braves" },
- end_node
- };
- struct test_node seq2[] = {
- { T_STR, 0, "2001-07-02" },
- { T_STR, 0, "2001-08-12" },
- { T_STR, 0, "2001-08-14" },
- end_node
- };
- struct test_node seq3[] = {
- { T_STR, 0, "Detroit Tigers" },
- { T_STR, 0, "Chicago Cubs" },
- end_node
- };
- struct test_node seq4[] = {
- { T_STR, 0, "2001-07-23" },
- end_node
- };
- struct test_node map[] = {
- { T_SEQ, 0, 0, seq1 },
- { T_SEQ, 0, 0, seq2 },
- { T_SEQ, 0, 0, seq3 },
- { T_SEQ, 0, 0, seq4 },
- end_node
- };
- struct test_node stream[] = {
- { T_MAP, 0, 0, map },
- end_node
- };
-
----
-test: Sequence of sequences
-spec: 2.5
-yaml: |
- - [ name , hr , avg ]
- - [ Mark McGwire , 65 , 0.278 ]
- - [ Sammy Sosa , 63 , 0.288 ]
-php: |
- array(
- array( 'name', 'hr', 'avg' ),
- array( 'Mark McGwire', 65, 0.278 ),
- array( 'Sammy Sosa', 63, 0.288 )
- )
----
-test: Mapping of mappings
-todo: true
-spec: 2.6
-yaml: |
- Mark McGwire: {hr: 65, avg: 0.278}
- Sammy Sosa: {
- hr: 63,
- avg: 0.288
- }
-php: |
- array(
- 'Mark McGwire' =>
- array( 'hr' => 65, 'avg' => 0.278 ),
- 'Sammy Sosa' =>
- array( 'hr' => 63, 'avg' => 0.288 )
- )
----
-test: Two documents in a stream each with a leading comment
-todo: true
-spec: 2.7
-yaml: |
- # Ranking of 1998 home runs
- ---
- - Mark McGwire
- - Sammy Sosa
- - Ken Griffey
-
- # Team ranking
- ---
- - Chicago Cubs
- - St Louis Cardinals
-ruby: |
- y = YAML::Stream.new
- y.add( [ 'Mark McGwire', 'Sammy Sosa', 'Ken Griffey' ] )
- y.add( [ 'Chicago Cubs', 'St Louis Cardinals' ] )
-documents: 2
-
----
-test: Play by play feed from a game
-todo: true
-spec: 2.8
-yaml: |
- ---
- time: 20:03:20
- player: Sammy Sosa
- action: strike (miss)
- ...
- ---
- time: 20:03:47
- player: Sammy Sosa
- action: grand slam
- ...
-perl: |
- [ 'Mark McGwire', 'Sammy Sosa', 'Ken Griffey' ]
-documents: 2
-
----
-test: Single document with two comments
-spec: 2.9
-yaml: |
- hr: # 1998 hr ranking
- - Mark McGwire
- - Sammy Sosa
- rbi:
- # 1998 rbi ranking
- - Sammy Sosa
- - Ken Griffey
-php: |
- array(
- 'hr' => array( 'Mark McGwire', 'Sammy Sosa' ),
- 'rbi' => array( 'Sammy Sosa', 'Ken Griffey' )
- )
----
-test: Node for Sammy Sosa appears twice in this document
-spec: 2.10
-yaml: |
- ---
- hr:
- - Mark McGwire
- # Following node labeled SS
- - &SS Sammy Sosa
- rbi:
- - *SS # Subsequent occurrence
- - Ken Griffey
-php: |
- array(
- 'hr' =>
- array('Mark McGwire', 'Sammy Sosa'),
- 'rbi' =>
- array('Sammy Sosa', 'Ken Griffey')
- )
----
-test: Mapping between sequences
-todo: true
-spec: 2.11
-yaml: |
- ? # PLAY SCHEDULE
- - Detroit Tigers
- - Chicago Cubs
- :
- - 2001-07-23
-
- ? [ New York Yankees,
- Atlanta Braves ]
- : [ 2001-07-02, 2001-08-12,
- 2001-08-14 ]
-ruby: |
- {
- [ 'Detroit Tigers', 'Chicago Cubs' ] => [ Date.new( 2001, 7, 23 ) ],
- [ 'New York Yankees', 'Atlanta Braves' ] => [ Date.new( 2001, 7, 2 ), Date.new( 2001, 8, 12 ), Date.new( 2001, 8, 14 ) ]
- }
-syck: |
- struct test_node seq1[] = {
- { T_STR, 0, "New York Yankees" },
- { T_STR, 0, "Atlanta Braves" },
- end_node
- };
- struct test_node seq2[] = {
- { T_STR, 0, "2001-07-02" },
- { T_STR, 0, "2001-08-12" },
- { T_STR, 0, "2001-08-14" },
- end_node
- };
- struct test_node seq3[] = {
- { T_STR, 0, "Detroit Tigers" },
- { T_STR, 0, "Chicago Cubs" },
- end_node
- };
- struct test_node seq4[] = {
- { T_STR, 0, "2001-07-23" },
- end_node
- };
- struct test_node map[] = {
- { T_SEQ, 0, 0, seq3 },
- { T_SEQ, 0, 0, seq4 },
- { T_SEQ, 0, 0, seq1 },
- { T_SEQ, 0, 0, seq2 },
- end_node
- };
- struct test_node stream[] = {
- { T_MAP, 0, 0, map },
- end_node
- };
-
----
-test: Sequence key shortcut
-spec: 2.12
-yaml: |
- ---
- # products purchased
- - item : Super Hoop
- quantity: 1
- - item : Basketball
- quantity: 4
- - item : Big Shoes
- quantity: 1
-php: |
- array (
- array (
- 'item' => 'Super Hoop',
- 'quantity' => 1,
- ),
- array (
- 'item' => 'Basketball',
- 'quantity' => 4,
- ),
- array (
- 'item' => 'Big Shoes',
- 'quantity' => 1,
- )
- )
-perl: |
- [
- { item => 'Super Hoop', quantity => 1 },
- { item => 'Basketball', quantity => 4 },
- { item => 'Big Shoes', quantity => 1 }
- ]
-
-ruby: |
- [
- { 'item' => 'Super Hoop', 'quantity' => 1 },
- { 'item' => 'Basketball', 'quantity' => 4 },
- { 'item' => 'Big Shoes', 'quantity' => 1 }
- ]
-python: |
- [
- { 'item': 'Super Hoop', 'quantity': 1 },
- { 'item': 'Basketball', 'quantity': 4 },
- { 'item': 'Big Shoes', 'quantity': 1 }
- ]
-syck: |
- struct test_node map1[] = {
- { T_STR, 0, "item" },
- { T_STR, 0, "Super Hoop" },
- { T_STR, 0, "quantity" },
- { T_STR, 0, "1" },
- end_node
- };
- struct test_node map2[] = {
- { T_STR, 0, "item" },
- { T_STR, 0, "Basketball" },
- { T_STR, 0, "quantity" },
- { T_STR, 0, "4" },
- end_node
- };
- struct test_node map3[] = {
- { T_STR, 0, "item" },
- { T_STR, 0, "Big Shoes" },
- { T_STR, 0, "quantity" },
- { T_STR, 0, "1" },
- end_node
- };
- struct test_node seq[] = {
- { T_MAP, 0, 0, map1 },
- { T_MAP, 0, 0, map2 },
- { T_MAP, 0, 0, map3 },
- end_node
- };
- struct test_node stream[] = {
- { T_SEQ, 0, 0, seq },
- end_node
- };
-
-
----
-test: Literal perserves newlines
-todo: true
-spec: 2.13
-yaml: |
- # ASCII Art
- --- |
- \//||\/||
- // || ||_
-perl: |
- "\\//||\\/||\n// || ||_\n"
-ruby: |
- "\\//||\\/||\n// || ||_\n"
-python: |
- [
- flushLeft(
- """
- \//||\/||
- // || ||_
- """
- )
- ]
-syck: |
- struct test_node stream[] = {
- { T_STR, 0, "\\//||\\/||\n// || ||_\n" },
- end_node
- };
-
----
-test: Folded treats newlines as a space
-todo: true
-spec: 2.14
-yaml: |
- ---
- Mark McGwire's
- year was crippled
- by a knee injury.
-perl: |
- "Mark McGwire's year was crippled by a knee injury."
-ruby: |
- "Mark McGwire's year was crippled by a knee injury."
-python: |
- [ "Mark McGwire's year was crippled by a knee injury." ]
-syck: |
- struct test_node stream[] = {
- { T_STR, 0, "Mark McGwire's year was crippled by a knee injury." },
- end_node
- };
-
----
-test: Newlines preserved for indented and blank lines
-todo: true
-spec: 2.15
-yaml: |
- --- >
- Sammy Sosa completed another
- fine season with great stats.
-
- 63 Home Runs
- 0.288 Batting Average
-
- What a year!
-perl: |
- "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n"
-ruby: |
- "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n"
-python: |
- [
- flushLeft(
- """
- Sammy Sosa completed another fine season with great stats.
-
- 63 Home Runs
- 0.288 Batting Average
-
- What a year!
- """
- )
- ]
-syck: |
- struct test_node stream[] = {
- { T_STR, 0, "Sammy Sosa completed another fine season with great stats.\n\n 63 Home Runs\n 0.288 Batting Average\n\nWhat a year!\n" },
- end_node
- };
-
-
----
-test: Indentation determines scope
-spec: 2.16
-yaml: |
- name: Mark McGwire
- accomplishment: >
- Mark set a major league
- home run record in 1998.
- stats: |
- 65 Home Runs
- 0.278 Batting Average
-php: |
- array(
- 'name' => 'Mark McGwire',
- 'accomplishment' => "Mark set a major league home run record in 1998.\n",
- 'stats' => "65 Home Runs\n0.278 Batting Average\n"
- )
----
-test: Quoted scalars
-todo: true
-spec: 2.17
-yaml: |
- unicode: "Sosa did fine.\u263A"
- control: "\b1998\t1999\t2000\n"
- hexesc: "\x0D\x0A is \r\n"
-
- single: '"Howdy!" he cried.'
- quoted: ' # not a ''comment''.'
- tie-fighter: '|\-*-/|'
-ruby: |
- {
- "tie-fighter" => "|\\-*-/|",
- "control"=>"\0101998\t1999\t2000\n",
- "unicode"=>"Sosa did fine." + ["263A".hex ].pack('U*'),
- "quoted"=>" # not a 'comment'.",
- "single"=>"\"Howdy!\" he cried.",
- "hexesc"=>"\r\n is \r\n"
- }
----
-test: Multiline flow scalars
-todo: true
-spec: 2.18
-yaml: |
- plain:
- This unquoted scalar
- spans many lines.
-
- quoted: "So does this
- quoted scalar.\n"
-ruby: |
- {
- 'plain' => 'This unquoted scalar spans many lines.',
- 'quoted' => "So does this quoted scalar.\n"
- }
----
-test: Integers
-spec: 2.19
-yaml: |
- canonical: 12345
- decimal: +12,345
- octal: 014
- hexadecimal: 0xC
-php: |
- array(
- 'canonical' => 12345,
- 'decimal' => 12345.0,
- 'octal' => 014,
- 'hexadecimal' => 0xC
- )
----
-# FIX: spec shows parens around -inf and NaN
-test: Floating point
-spec: 2.20
-yaml: |
- canonical: 1.23015e+3
- exponential: 12.3015e+02
- fixed: 1,230.15
- negative infinity: -.inf
- not a number: .NaN
- float as whole number: !!float 1
-php: |
- array(
- 'canonical' => 1230.15,
- 'exponential' => 1230.15,
- 'fixed' => 1230.15,
- 'negative infinity' => log(0),
- 'not a number' => -log(0),
- 'float as whole number' => (float) 1
- )
----
-test: Miscellaneous
-spec: 2.21
-yaml: |
- null: ~
- true: true
- false: false
- string: '12345'
-php: |
- array(
- '' => null,
- 1 => true,
- 0 => false,
- 'string' => '12345'
- )
----
-test: Timestamps
-todo: true
-spec: 2.22
-yaml: |
- canonical: 2001-12-15T02:59:43.1Z
- iso8601: 2001-12-14t21:59:43.10-05:00
- spaced: 2001-12-14 21:59:43.10 -05:00
- date: 2002-12-14 # Time is noon UTC
-php: |
- array(
- 'canonical' => YAML::mktime( 2001, 12, 15, 2, 59, 43, 0.10 ),
- 'iso8601' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ),
- 'spaced' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ),
- 'date' => Date.new( 2002, 12, 14 )
- )
----
-test: legacy Timestamps test
-todo: true
-spec: legacy D4
-yaml: |
- canonical: 2001-12-15T02:59:43.00Z
- iso8601: 2001-02-28t21:59:43.00-05:00
- spaced: 2001-12-14 21:59:43.00 -05:00
- date: 2002-12-14
-php: |
- array(
- 'canonical' => Time::utc( 2001, 12, 15, 2, 59, 43, 0 ),
- 'iso8601' => YAML::mktime( 2001, 2, 28, 21, 59, 43, 0, "-05:00" ),
- 'spaced' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0, "-05:00" ),
- 'date' => Date.new( 2002, 12, 14 )
- )
----
-test: Various explicit families
-todo: true
-spec: 2.23
-yaml: |
- not-date: !str 2002-04-28
- picture: !binary |
- R0lGODlhDAAMAIQAAP//9/X
- 17unp5WZmZgAAAOfn515eXv
- Pz7Y6OjuDg4J+fn5OTk6enp
- 56enmleECcgggoBADs=
-
- application specific tag: !!something |
- The semantics of the tag
- above may be different for
- different documents.
-
-ruby-setup: |
- YAML.add_private_type( "something" ) do |type, val|
- "SOMETHING: #{val}"
- end
-ruby: |
- {
- 'not-date' => '2002-04-28',
- 'picture' => "GIF89a\f\000\f\000\204\000\000\377\377\367\365\365\356\351\351\345fff\000\000\000\347\347\347^^^\363\363\355\216\216\216\340\340\340\237\237\237\223\223\223\247\247\247\236\236\236i^\020' \202\n\001\000;",
- 'application specific tag' => "SOMETHING: The semantics of the tag\nabove may be different for\ndifferent documents.\n"
- }
----
-test: Application specific family
-todo: true
-spec: 2.24
-yaml: |
- # Establish a tag prefix
- --- !clarkevans.com,2002/graph/^shape
- # Use the prefix: shorthand for
- # !clarkevans.com,2002/graph/circle
- - !^circle
- center: &ORIGIN {x: 73, 'y': 129}
- radius: 7
- - !^line # !clarkevans.com,2002/graph/line
- start: *ORIGIN
- finish: { x: 89, 'y': 102 }
- - !^label
- start: *ORIGIN
- color: 0xFFEEBB
- value: Pretty vector drawing.
-ruby-setup: |
- YAML.add_domain_type( "clarkevans.com,2002", 'graph/shape' ) { |type, val|
- if Array === val
- val << "Shape Container"
- val
- else
- raise YAML::Error, "Invalid graph of class #{ val.class }: " + val.inspect
- end
- }
- one_shape_proc = Proc.new { |type, val|
- scheme, domain, type = type.split( /:/, 3 )
- if val.is_a? ::Hash
- val['TYPE'] = "Shape: #{type}"
- val
- else
- raise YAML::Error, "Invalid graph of class #{ val.class }: " + val.inspect
- end
- }
- YAML.add_domain_type( "clarkevans.com,2002", 'graph/circle', &one_shape_proc )
- YAML.add_domain_type( "clarkevans.com,2002", 'graph/line', &one_shape_proc )
- YAML.add_domain_type( "clarkevans.com,2002", 'graph/label', &one_shape_proc )
-ruby: |
- [
- {
- "radius" => 7,
- "center"=>
- {
- "x" => 73,
- "y" => 129
- },
- "TYPE" => "Shape: graph/circle"
- }, {
- "finish" =>
- {
- "x" => 89,
- "y" => 102
- },
- "TYPE" => "Shape: graph/line",
- "start" =>
- {
- "x" => 73,
- "y" => 129
- }
- }, {
- "TYPE" => "Shape: graph/label",
- "value" => "Pretty vector drawing.",
- "start" =>
- {
- "x" => 73,
- "y" => 129
- },
- "color" => 16772795
- },
- "Shape Container"
- ]
-# ---
-# test: Unordered set
-# spec: 2.25
-# yaml: |
-# # sets are represented as a
-# # mapping where each key is
-# # associated with the empty string
-# --- !set
-# ? Mark McGwire
-# ? Sammy Sosa
-# ? Ken Griff
----
-test: Ordered mappings
-todo: true
-spec: 2.26
-yaml: |
- # ordered maps are represented as
- # a sequence of mappings, with
- # each mapping having one key
- --- !omap
- - Mark McGwire: 65
- - Sammy Sosa: 63
- - Ken Griffy: 58
-ruby: |
- YAML::Omap[
- 'Mark McGwire', 65,
- 'Sammy Sosa', 63,
- 'Ken Griffy', 58
- ]
----
-test: Invoice
-dump_skip: true
-spec: 2.27
-yaml: |
- --- !clarkevans.com,2002/^invoice
- invoice: 34843
- date : 2001-01-23
- bill-to: &id001
- given : Chris
- family : Dumars
- address:
- lines: |
- 458 Walkman Dr.
- Suite #292
- city : Royal Oak
- state : MI
- postal : 48046
- ship-to: *id001
- product:
- -
- sku : BL394D
- quantity : 4
- description : Basketball
- price : 450.00
- -
- sku : BL4438H
- quantity : 1
- description : Super Hoop
- price : 2392.00
- tax : 251.42
- total: 4443.52
- comments: >
- Late afternoon is best.
- Backup contact is Nancy
- Billsmer @ 338-4338.
-php: |
- array(
- 'invoice' => 34843, 'date' => gmmktime(0, 0, 0, 1, 23, 2001),
- 'bill-to' =>
- array( 'given' => 'Chris', 'family' => 'Dumars', 'address' => array( 'lines' => "458 Walkman Dr.\nSuite #292\n", 'city' => 'Royal Oak', 'state' => 'MI', 'postal' => 48046 ) )
- , 'ship-to' =>
- array( 'given' => 'Chris', 'family' => 'Dumars', 'address' => array( 'lines' => "458 Walkman Dr.\nSuite #292\n", 'city' => 'Royal Oak', 'state' => 'MI', 'postal' => 48046 ) )
- , 'product' =>
- array(
- array( 'sku' => 'BL394D', 'quantity' => 4, 'description' => 'Basketball', 'price' => 450.00 ),
- array( 'sku' => 'BL4438H', 'quantity' => 1, 'description' => 'Super Hoop', 'price' => 2392.00 )
- ),
- 'tax' => 251.42, 'total' => 4443.52,
- 'comments' => "Late afternoon is best. Backup contact is Nancy Billsmer @ 338-4338.\n"
- )
----
-test: Log file
-todo: true
-spec: 2.28
-yaml: |
- ---
- Time: 2001-11-23 15:01:42 -05:00
- User: ed
- Warning: >
- This is an error message
- for the log file
- ---
- Time: 2001-11-23 15:02:31 -05:00
- User: ed
- Warning: >
- A slightly different error
- message.
- ---
- Date: 2001-11-23 15:03:17 -05:00
- User: ed
- Fatal: >
- Unknown variable "bar"
- Stack:
- - file: TopClass.py
- line: 23
- code: |
- x = MoreObject("345\n")
- - file: MoreClass.py
- line: 58
- code: |-
- foo = bar
-ruby: |
- y = YAML::Stream.new
- y.add( { 'Time' => YAML::mktime( 2001, 11, 23, 15, 01, 42, 00, "-05:00" ),
- 'User' => 'ed', 'Warning' => "This is an error message for the log file\n" } )
- y.add( { 'Time' => YAML::mktime( 2001, 11, 23, 15, 02, 31, 00, "-05:00" ),
- 'User' => 'ed', 'Warning' => "A slightly different error message.\n" } )
- y.add( { 'Date' => YAML::mktime( 2001, 11, 23, 15, 03, 17, 00, "-05:00" ),
- 'User' => 'ed', 'Fatal' => "Unknown variable \"bar\"\n",
- 'Stack' => [
- { 'file' => 'TopClass.py', 'line' => 23, 'code' => "x = MoreObject(\"345\\n\")\n" },
- { 'file' => 'MoreClass.py', 'line' => 58, 'code' => "foo = bar" } ] } )
-documents: 3
-
----
-test: Throwaway comments
-yaml: |
- ### These are four throwaway comment ###
-
- ### lines (the second line is empty). ###
- this: | # Comments may trail lines.
- contains three lines of text.
- The third one starts with a
- # character. This isn't a comment.
-
- # These are three throwaway comment
- # lines (the first line is empty).
-php: |
- array(
- 'this' => "contains three lines of text.\nThe third one starts with a\n# character. This isn't a comment.\n"
- )
----
-test: Document with a single value
-todo: true
-yaml: |
- --- >
- This YAML stream contains a single text value.
- The next stream is a log file - a sequence of
- log entries. Adding an entry to the log is a
- simple matter of appending it at the end.
-ruby: |
- "This YAML stream contains a single text value. The next stream is a log file - a sequence of log entries. Adding an entry to the log is a simple matter of appending it at the end.\n"
----
-test: Document stream
-todo: true
-yaml: |
- ---
- at: 2001-08-12 09:25:00.00 Z
- type: GET
- HTTP: '1.0'
- url: '/index.html'
- ---
- at: 2001-08-12 09:25:10.00 Z
- type: GET
- HTTP: '1.0'
- url: '/toc.html'
-ruby: |
- y = YAML::Stream.new
- y.add( {
- 'at' => Time::utc( 2001, 8, 12, 9, 25, 00 ),
- 'type' => 'GET',
- 'HTTP' => '1.0',
- 'url' => '/index.html'
- } )
- y.add( {
- 'at' => Time::utc( 2001, 8, 12, 9, 25, 10 ),
- 'type' => 'GET',
- 'HTTP' => '1.0',
- 'url' => '/toc.html'
- } )
-documents: 2
-
----
-test: Top level mapping
-yaml: |
- # This stream is an example of a top-level mapping.
- invoice : 34843
- date : 2001-01-23
- total : 4443.52
-php: |
- array(
- 'invoice' => 34843,
- 'date' => gmmktime(0, 0, 0, 1, 23, 2001),
- 'total' => 4443.52
- )
----
-test: Single-line documents
-todo: true
-yaml: |
- # The following is a sequence of three documents.
- # The first contains an empty mapping, the second
- # an empty sequence, and the last an empty string.
- --- {}
- --- [ ]
- --- ''
-ruby: |
- y = YAML::Stream.new
- y.add( {} )
- y.add( [] )
- y.add( '' )
-documents: 3
-
----
-test: Document with pause
-todo: true
-yaml: |
- # A communication channel based on a YAML stream.
- ---
- sent at: 2002-06-06 11:46:25.10 Z
- payload: Whatever
- # Receiver can process this as soon as the following is sent:
- ...
- # Even if the next message is sent long after:
- ---
- sent at: 2002-06-06 12:05:53.47 Z
- payload: Whatever
- ...
-ruby: |
- y = YAML::Stream.new
- y.add(
- { 'sent at' => YAML::mktime( 2002, 6, 6, 11, 46, 25, 0.10 ),
- 'payload' => 'Whatever' }
- )
- y.add(
- { "payload" => "Whatever", "sent at" => YAML::mktime( 2002, 6, 6, 12, 5, 53, 0.47 ) }
- )
-documents: 2
-
----
-test: Explicit typing
-yaml: |
- integer: 12
- also int: ! "12"
- string: !str 12
-php: |
- array( 'integer' => 12, 'also int' => 12, 'string' => '12' )
----
-test: Private types
-todo: true
-yaml: |
- # Both examples below make use of the 'x-private:ball'
- # type family URI, but with different semantics.
- ---
- pool: !!ball
- number: 8
- color: black
- ---
- bearing: !!ball
- material: steel
-ruby: |
- y = YAML::Stream.new
- y.add( { 'pool' =>
- YAML::PrivateType.new( 'ball',
- { 'number' => 8, 'color' => 'black' } ) }
- )
- y.add( { 'bearing' =>
- YAML::PrivateType.new( 'ball',
- { 'material' => 'steel' } ) }
- )
-documents: 2
-
----
-test: Type family under yaml.org
-yaml: |
- # The URI is 'tag:yaml.org,2002:str'
- - !str a Unicode string
-php: |
- array( 'a Unicode string' )
----
-test: Type family under perl.yaml.org
-todo: true
-yaml: |
- # The URI is 'tag:perl.yaml.org,2002:Text::Tabs'
- - !perl/Text::Tabs {}
-ruby: |
- [ YAML::DomainType.new( 'perl.yaml.org,2002', 'Text::Tabs', {} ) ]
----
-test: Type family under clarkevans.com
-todo: true
-yaml: |
- # The URI is 'tag:clarkevans.com,2003-02:timesheet'
- - !clarkevans.com,2003-02/timesheet {}
-ruby: |
- [ YAML::DomainType.new( 'clarkevans.com,2003-02', 'timesheet', {} ) ]
----
-test: URI Escaping
-todo: true
-yaml: |
- same:
- - !domain.tld,2002/type\x30 value
- - !domain.tld,2002/type0 value
- different: # As far as the YAML parser is concerned
- - !domain.tld,2002/type%30 value
- - !domain.tld,2002/type0 value
-ruby-setup: |
- YAML.add_domain_type( "domain.tld,2002", "type0" ) { |type, val|
- "ONE: #{val}"
- }
- YAML.add_domain_type( "domain.tld,2002", "type%30" ) { |type, val|
- "TWO: #{val}"
- }
-ruby: |
- { 'same' => [ 'ONE: value', 'ONE: value' ], 'different' => [ 'TWO: value', 'ONE: value' ] }
----
-test: URI Prefixing
-todo: true
-yaml: |
- # 'tag:domain.tld,2002:invoice' is some type family.
- invoice: !domain.tld,2002/^invoice
- # 'seq' is shorthand for 'tag:yaml.org,2002:seq'.
- # This does not effect '^customer' below
- # because it is does not specify a prefix.
- customers: !seq
- # '^customer' is shorthand for the full
- # notation 'tag:domain.tld,2002:customer'.
- - !^customer
- given : Chris
- family : Dumars
-ruby-setup: |
- YAML.add_domain_type( "domain.tld,2002", /(invoice|customer)/ ) { |type, val|
- if val.is_a? ::Hash
- scheme, domain, type = type.split( /:/, 3 )
- val['type'] = "domain #{type}"
- val
- else
- raise YAML::Error, "Not a Hash in domain.tld/invoice: " + val.inspect
- end
- }
-ruby: |
- { "invoice"=> { "customers"=> [ { "given"=>"Chris", "type"=>"domain customer", "family"=>"Dumars" } ], "type"=>"domain invoice" } }
-
----
-test: Overriding anchors
-yaml: |
- anchor : &A001 This scalar has an anchor.
- override : &A001 >
- The alias node below is a
- repeated use of this value.
- alias : *A001
-php: |
- array( 'anchor' => 'This scalar has an anchor.',
- 'override' => "The alias node below is a repeated use of this value.\n",
- 'alias' => "The alias node below is a repeated use of this value.\n" )
----
-test: Flow and block formatting
-todo: true
-yaml: |
- empty: []
- flow: [ one, two, three # May span lines,
- , four, # indentation is
- five ] # mostly ignored.
- block:
- - First item in top sequence
- -
- - Subordinate sequence entry
- - >
- A folded sequence entry
- - Sixth item in top sequence
-ruby: |
- { 'empty' => [], 'flow' => [ 'one', 'two', 'three', 'four', 'five' ],
- 'block' => [ 'First item in top sequence', [ 'Subordinate sequence entry' ],
- "A folded sequence entry\n", 'Sixth item in top sequence' ] }
----
-test: Complete mapping test
-todo: true
-yaml: |
- empty: {}
- flow: { one: 1, two: 2 }
- spanning: { one: 1,
- two: 2 }
- block:
- first : First entry
- second:
- key: Subordinate mapping
- third:
- - Subordinate sequence
- - { }
- - Previous mapping is empty.
- - A key: value pair in a sequence.
- A second: key:value pair.
- - The previous entry is equal to the following one.
- -
- A key: value pair in a sequence.
- A second: key:value pair.
- !float 12 : This key is a float.
- ? >
- ?
- : This key had to be protected.
- "\a" : This key had to be escaped.
- ? >
- This is a
- multi-line
- folded key
- : Whose value is
- also multi-line.
- ? this also works as a key
- : with a value at the next line.
- ?
- - This key
- - is a sequence
- :
- - With a sequence value.
- ?
- This: key
- is a: mapping
- :
- with a: mapping value.
-ruby: |
- { 'empty' => {}, 'flow' => { 'one' => 1, 'two' => 2 },
- 'spanning' => { 'one' => 1, 'two' => 2 },
- 'block' => { 'first' => 'First entry', 'second' =>
- { 'key' => 'Subordinate mapping' }, 'third' =>
- [ 'Subordinate sequence', {}, 'Previous mapping is empty.',
- { 'A key' => 'value pair in a sequence.', 'A second' => 'key:value pair.' },
- 'The previous entry is equal to the following one.',
- { 'A key' => 'value pair in a sequence.', 'A second' => 'key:value pair.' } ],
- 12.0 => 'This key is a float.', "?\n" => 'This key had to be protected.',
- "\a" => 'This key had to be escaped.',
- "This is a multi-line folded key\n" => "Whose value is also multi-line.",
- 'this also works as a key' => 'with a value at the next line.',
- [ 'This key', 'is a sequence' ] => [ 'With a sequence value.' ] } }
- # Couldn't recreate map exactly, so we'll do a detailed check to be sure it's entact
- obj_y['block'].keys.each { |k|
- if Hash === k
- v = obj_y['block'][k]
- if k['This'] == 'key' and k['is a'] == 'mapping' and v['with a'] == 'mapping value.'
- obj_r['block'][k] = v
- end
- end
- }
----
-test: Literal explicit indentation
-yaml: |
- # Explicit indentation must
- # be given in all the three
- # following cases.
- leading spaces: |2
- This value starts with four spaces.
-
- leading line break: |2
-
- This value starts with a line break.
-
- leading comment indicator: |2
- # first line starts with a
- # character.
-
- # Explicit indentation may
- # also be given when it is
- # not required.
- redundant: |2
- This value is indented 2 spaces.
-php: |
- array(
- 'leading spaces' => " This value starts with four spaces.\n",
- 'leading line break' => "\nThis value starts with a line break.\n",
- 'leading comment indicator' => "# first line starts with a\n# character.\n",
- 'redundant' => "This value is indented 2 spaces.\n"
- )
----
-test: Chomping and keep modifiers
-yaml: |
- clipped: |
- This has one newline.
-
- same as "clipped" above: "This has one newline.\n"
-
- stripped: |-
- This has no newline.
-
- same as "stripped" above: "This has no newline."
-
- kept: |+
- This has two newlines.
-
- same as "kept" above: "This has two newlines.\n\n"
-php: |
- array(
- 'clipped' => "This has one newline.\n",
- 'same as "clipped" above' => "This has one newline.\n",
- 'stripped' => 'This has no newline.',
- 'same as "stripped" above' => 'This has no newline.',
- 'kept' => "This has two newlines.\n\n",
- 'same as "kept" above' => "This has two newlines.\n\n"
- )
----
-test: Literal combinations
-todo: true
-yaml: |
- empty: |
-
- literal: |
- The \ ' " characters may be
- freely used. Leading white
- space is significant.
-
- Line breaks are significant.
- Thus this value contains one
- empty line and ends with a
- single line break, but does
- not start with one.
-
- is equal to: "The \\ ' \" characters may \
- be\nfreely used. Leading white\n space \
- is significant.\n\nLine breaks are \
- significant.\nThus this value contains \
- one\nempty line and ends with a\nsingle \
- line break, but does\nnot start with one.\n"
-
- # Comments may follow a block
- # scalar value. They must be
- # less indented.
-
- # Modifiers may be combined in any order.
- indented and chomped: |2-
- This has no newline.
-
- also written as: |-2
- This has no newline.
-
- both are equal to: " This has no newline."
-php: |
- array(
- 'empty' => '',
- 'literal' => "The \\ ' \" characters may be\nfreely used. Leading white\n space " +
- "is significant.\n\nLine breaks are significant.\nThus this value contains one\n" +
- "empty line and ends with a\nsingle line break, but does\nnot start with one.\n",
- 'is equal to' => "The \\ ' \" characters may be\nfreely used. Leading white\n space " +
- "is significant.\n\nLine breaks are significant.\nThus this value contains one\n" +
- "empty line and ends with a\nsingle line break, but does\nnot start with one.\n",
- 'indented and chomped' => ' This has no newline.',
- 'also written as' => ' This has no newline.',
- 'both are equal to' => ' This has no newline.'
- )
----
-test: Folded combinations
-todo: true
-yaml: |
- empty: >
-
- one paragraph: >
- Line feeds are converted
- to spaces, so this value
- contains no line breaks
- except for the final one.
-
- multiple paragraphs: >2
-
- An empty line, either
- at the start or in
- the value:
-
- Is interpreted as a
- line break. Thus this
- value contains three
- line breaks.
-
- indented text: >
- This is a folded
- paragraph followed
- by a list:
- * first entry
- * second entry
- Followed by another
- folded paragraph,
- another list:
-
- * first entry
-
- * second entry
-
- And a final folded
- paragraph.
-
- above is equal to: |
- This is a folded paragraph followed by a list:
- * first entry
- * second entry
- Followed by another folded paragraph, another list:
-
- * first entry
-
- * second entry
-
- And a final folded paragraph.
-
- # Explicit comments may follow
- # but must be less indented.
-php: |
- array(
- 'empty' => '',
- 'one paragraph' => 'Line feeds are converted to spaces, so this value'.
- " contains no line breaks except for the final one.\n",
- 'multiple paragraphs' => "\nAn empty line, either at the start or in the value:\n".
- "Is interpreted as a line break. Thus this value contains three line breaks.\n",
- 'indented text' => "This is a folded paragraph followed by a list:\n".
- " * first entry\n * second entry\nFollowed by another folded paragraph, ".
- "another list:\n\n * first entry\n\n * second entry\n\nAnd a final folded paragraph.\n",
- 'above is equal to' => "This is a folded paragraph followed by a list:\n".
- " * first entry\n * second entry\nFollowed by another folded paragraph, ".
- "another list:\n\n * first entry\n\n * second entry\n\nAnd a final folded paragraph.\n"
- )
----
-test: Single quotes
-todo: true
-yaml: |
- empty: ''
- second: '! : \ etc. can be used freely.'
- third: 'a single quote '' must be escaped.'
- span: 'this contains
- six spaces
-
- and one
- line break'
- is same as: "this contains six spaces\nand one line break"
-php: |
- array(
- 'empty' => '',
- 'second' => '! : \\ etc. can be used freely.',
- 'third' => "a single quote ' must be escaped.",
- 'span' => "this contains six spaces\nand one line break",
- 'is same as' => "this contains six spaces\nand one line break"
- )
----
-test: Double quotes
-todo: true
-yaml: |
- empty: ""
- second: "! : etc. can be used freely."
- third: "a \" or a \\ must be escaped."
- fourth: "this value ends with an LF.\n"
- span: "this contains
- four \
- spaces"
- is equal to: "this contains four spaces"
-php: |
- array(
- 'empty' => '',
- 'second' => '! : etc. can be used freely.',
- 'third' => 'a " or a \\ must be escaped.',
- 'fourth' => "this value ends with an LF.\n",
- 'span' => "this contains four spaces",
- 'is equal to' => "this contains four spaces"
- )
----
-test: Unquoted strings
-todo: true
-yaml: |
- first: There is no unquoted empty string.
-
- second: 12 ## This is an integer.
-
- third: !str 12 ## This is a string.
-
- span: this contains
- six spaces
-
- and one
- line break
-
- indicators: this has no comments.
- #:foo and bar# are
- both text.
-
- flow: [ can span
- lines, # comment
- like
- this ]
-
- note: { one-line keys: but multi-line values }
-
-php: |
- array(
- 'first' => 'There is no unquoted empty string.',
- 'second' => 12,
- 'third' => '12',
- 'span' => "this contains six spaces\nand one line break",
- 'indicators' => "this has no comments. #:foo and bar# are both text.",
- 'flow' => [ 'can span lines', 'like this' ],
- 'note' => { 'one-line keys' => 'but multi-line values' }
- )
----
-test: Spanning sequences
-todo: true
-yaml: |
- # The following are equal seqs
- # with different identities.
- flow: [ one, two ]
- spanning: [ one,
- two ]
- block:
- - one
- - two
-php: |
- array(
- 'flow' => [ 'one', 'two' ],
- 'spanning' => [ 'one', 'two' ],
- 'block' => [ 'one', 'two' ]
- )
----
-test: Flow mappings
-yaml: |
- # The following are equal maps
- # with different identities.
- flow: { one: 1, two: 2 }
- block:
- one: 1
- two: 2
-php: |
- array(
- 'flow' => array( 'one' => 1, 'two' => 2 ),
- 'block' => array( 'one' => 1, 'two' => 2 )
- )
----
-test: Representations of 12
-todo: true
-yaml: |
- - 12 # An integer
- # The following scalars
- # are loaded to the
- # string value '1' '2'.
- - !str 12
- - '12'
- - "12"
- - "\
- 1\
- 2\
- "
- # Strings containing paths and regexps can be unquoted:
- - /foo/bar
- - d:/foo/bar
- - foo/bar
- - /a.*b/
-php: |
- array( 12, '12', '12', '12', '12', '/foo/bar', 'd:/foo/bar', 'foo/bar', '/a.*b/' )
----
-test: "Null"
-todo: true
-yaml: |
- canonical: ~
-
- english: null
-
- # This sequence has five
- # entries, two with values.
- sparse:
- - ~
- - 2nd entry
- - Null
- - 4th entry
- -
-
- four: This mapping has five keys,
- only two with values.
-
-php: |
- array (
- 'canonical' => null,
- 'english' => null,
- 'sparse' => array( null, '2nd entry', null, '4th entry', null ]),
- 'four' => 'This mapping has five keys, only two with values.'
- )
----
-test: Omap
-todo: true
-yaml: |
- # Explicitly typed dictionary.
- Bestiary: !omap
- - aardvark: African pig-like ant eater. Ugly.
- - anteater: South-American ant eater. Two species.
- - anaconda: South-American constrictor snake. Scary.
- # Etc.
-ruby: |
- {
- 'Bestiary' => YAML::Omap[
- 'aardvark', 'African pig-like ant eater. Ugly.',
- 'anteater', 'South-American ant eater. Two species.',
- 'anaconda', 'South-American constrictor snake. Scary.'
- ]
- }
-
----
-test: Pairs
-todo: true
-yaml: |
- # Explicitly typed pairs.
- tasks: !pairs
- - meeting: with team.
- - meeting: with boss.
- - break: lunch.
- - meeting: with client.
-ruby: |
- {
- 'tasks' => YAML::Pairs[
- 'meeting', 'with team.',
- 'meeting', 'with boss.',
- 'break', 'lunch.',
- 'meeting', 'with client.'
- ]
- }
-
----
-test: Set
-todo: true
-yaml: |
- # Explicitly typed set.
- baseball players: !set
- Mark McGwire:
- Sammy Sosa:
- Ken Griffey:
-ruby: |
- {
- 'baseball players' => YAML::Set[
- 'Mark McGwire', nil,
- 'Sammy Sosa', nil,
- 'Ken Griffey', nil
- ]
- }
-
----
-test: Boolean
-yaml: |
- false: used as key
- logical: true
- answer: false
-php: |
- array(
- false => 'used as key',
- 'logical' => true,
- 'answer' => false
- )
----
-test: Integer
-yaml: |
- canonical: 12345
- decimal: +12,345
- octal: 014
- hexadecimal: 0xC
-php: |
- array(
- 'canonical' => 12345,
- 'decimal' => 12345.0,
- 'octal' => 12,
- 'hexadecimal' => 12
- )
----
-test: Float
-yaml: |
- canonical: 1.23015e+3
- exponential: 12.3015e+02
- fixed: 1,230.15
- negative infinity: -.inf
- not a number: .NaN
-php: |
- array(
- 'canonical' => 1230.15,
- 'exponential' => 1230.15,
- 'fixed' => 1230.15,
- 'negative infinity' => log(0),
- 'not a number' => -log(0)
- )
----
-test: Timestamp
-todo: true
-yaml: |
- canonical: 2001-12-15T02:59:43.1Z
- valid iso8601: 2001-12-14t21:59:43.10-05:00
- space separated: 2001-12-14 21:59:43.10 -05:00
- date (noon UTC): 2002-12-14
-ruby: |
- array(
- 'canonical' => YAML::mktime( 2001, 12, 15, 2, 59, 43, 0.10 ),
- 'valid iso8601' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ),
- 'space separated' => YAML::mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ),
- 'date (noon UTC)' => Date.new( 2002, 12, 14 )
- )
----
-test: Binary
-todo: true
-yaml: |
- canonical: !binary "\
- R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5\
- OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+\
- +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC\
- AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs="
- base64: !binary |
- R0lGODlhDAAMAIQAAP//9/X17unp5WZmZgAAAOfn515eXvPz7Y6OjuDg4J+fn5
- OTk6enp56enmlpaWNjY6Ojo4SEhP/++f/++f/++f/++f/++f/++f/++f/++f/+
- +f/++f/++f/++f/++f/++SH+Dk1hZGUgd2l0aCBHSU1QACwAAAAADAAMAAAFLC
- AgjoEwnuNAFOhpEMTRiggcz4BNJHrv/zCFcLiwMWYNG84BwwEeECcgggoBADs=
- description: >
- The binary value above is a tiny arrow
- encoded as a gif image.
-ruby-setup: |
- arrow_gif = "GIF89a\f\000\f\000\204\000\000\377\377\367\365\365\356\351\351\345fff\000\000\000\347\347\347^^^\363\363\355\216\216\216\340\340\340\237\237\237\223\223\223\247\247\247\236\236\236iiiccc\243\243\243\204\204\204\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371\377\376\371!\376\016Made with GIMP\000,\000\000\000\000\f\000\f\000\000\005, \216\2010\236\343@\024\350i\020\304\321\212\010\034\317\200M$z\357\3770\205p\270\2601f\r\e\316\001\303\001\036\020' \202\n\001\000;"
-ruby: |
- {
- 'canonical' => arrow_gif,
- 'base64' => arrow_gif,
- 'description' => "The binary value above is a tiny arrow encoded as a gif image.\n"
- }
-
----
-test: Merge key
-todo: true
-yaml: |
- ---
- - &CENTER { x: 1, y: 2 }
- - &LEFT { x: 0, y: 2 }
- - &BIG { r: 10 }
- - &SMALL { r: 1 }
-
- # All the following maps are equal:
-
- - # Explicit keys
- x: 1
- y: 2
- r: 10
- label: center/big
-
- - # Merge one map
- << : *CENTER
- r: 10
- label: center/big
-
- - # Merge multiple maps
- << : [ *CENTER, *BIG ]
- label: center/big
-
- - # Override
- << : [ *BIG, *LEFT, *SMALL ]
- x: 1
- label: center/big
-
-ruby-setup: |
- center = { 'x' => 1, 'y' => 2 }
- left = { 'x' => 0, 'y' => 2 }
- big = { 'r' => 10 }
- small = { 'r' => 1 }
- node1 = { 'x' => 1, 'y' => 2, 'r' => 10, 'label' => 'center/big' }
- node2 = center.dup
- node2.update( { 'r' => 10, 'label' => 'center/big' } )
- node3 = big.dup
- node3.update( center )
- node3.update( { 'label' => 'center/big' } )
- node4 = small.dup
- node4.update( left )
- node4.update( big )
- node4.update( { 'x' => 1, 'label' => 'center/big' } )
-
-ruby: |
- [
- center, left, big, small, node1, node2, node3, node4
- ]
-
----
-test: Default key
-todo: true
-yaml: |
- --- # Old schema
- link with:
- - library1.dll
- - library2.dll
- --- # New schema
- link with:
- - = : library1.dll
- version: 1.2
- - = : library2.dll
- version: 2.3
-ruby: |
- y = YAML::Stream.new
- y.add( { 'link with' => [ 'library1.dll', 'library2.dll' ] } )
- obj_h = Hash[ 'version' => 1.2 ]
- obj_h.default = 'library1.dll'
- obj_h2 = Hash[ 'version' => 2.3 ]
- obj_h2.default = 'library2.dll'
- y.add( { 'link with' => [ obj_h, obj_h2 ] } )
-documents: 2
-
----
-test: Special keys
-todo: true
-yaml: |
- "!": These three keys
- "&": had to be quoted
- "=": and are normal strings.
- # NOTE: the following node should NOT be serialized this way.
- encoded node :
- !special '!' : '!type'
- !special|canonical '&' : 12
- = : value
- # The proper way to serialize the above node is as follows:
- node : !!type &12 value
-ruby: |
- { '!' => 'These three keys', '&' => 'had to be quoted',
- '=' => 'and are normal strings.',
- 'encoded node' => YAML::PrivateType.new( 'type', 'value' ),
- 'node' => YAML::PrivateType.new( 'type', 'value' ) }
diff --git a/vendor/symfony/yaml/Tests/Fixtures/YtsTypeTransfers.yml b/vendor/symfony/yaml/Tests/Fixtures/YtsTypeTransfers.yml
deleted file mode 100755
index 46c8d4a2c..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/YtsTypeTransfers.yml
+++ /dev/null
@@ -1,244 +0,0 @@
---- %YAML:1.0
-test: Strings
-brief: >
- Any group of characters beginning with an
- alphabetic or numeric character is a string,
- unless it belongs to one of the groups below
- (such as an Integer or Time).
-yaml: |
- String
-php: |
- 'String'
----
-test: String characters
-brief: >
- A string can contain any alphabetic or
- numeric character, along with many
- punctuation characters, including the
- period, dash, space, quotes, exclamation, and
- question mark.
-yaml: |
- - What's Yaml?
- - It's for writing data structures in plain text.
- - And?
- - And what? That's not good enough for you?
- - No, I mean, "And what about Yaml?"
- - Oh, oh yeah. Uh.. Yaml for Ruby.
-php: |
- array(
- "What's Yaml?",
- "It's for writing data structures in plain text.",
- "And?",
- "And what? That's not good enough for you?",
- "No, I mean, \"And what about Yaml?\"",
- "Oh, oh yeah. Uh.. Yaml for Ruby."
- )
----
-test: Indicators in Strings
-brief: >
- Be careful using indicators in strings. In particular,
- the comma, colon, and pound sign must be used carefully.
-yaml: |
- the colon followed by space is an indicator: but is a string:right here
- same for the pound sign: here we have it#in a string
- the comma can, honestly, be used in most cases: [ but not in, inline collections ]
-php: |
- array(
- 'the colon followed by space is an indicator' => 'but is a string:right here',
- 'same for the pound sign' => 'here we have it#in a string',
- 'the comma can, honestly, be used in most cases' => array('but not in', 'inline collections')
- )
----
-test: Forcing Strings
-brief: >
- Any YAML type can be forced into a string using the
- explicit !str method.
-yaml: |
- date string: !str 2001-08-01
- number string: !str 192
-php: |
- array(
- 'date string' => '2001-08-01',
- 'number string' => '192'
- )
----
-test: Single-quoted Strings
-brief: >
- You can also enclose your strings within single quotes,
- which allows use of slashes, colons, and other indicators
- freely. Inside single quotes, you can represent a single
- quote in your string by using two single quotes next to
- each other.
-yaml: |
- all my favorite symbols: '#:!/%.)'
- a few i hate: '&(*'
- why do i hate them?: 'it''s very hard to explain'
- entities: '£ me'
-php: |
- array(
- 'all my favorite symbols' => '#:!/%.)',
- 'a few i hate' => '&(*',
- 'why do i hate them?' => 'it\'s very hard to explain',
- 'entities' => '£ me'
- )
----
-test: Double-quoted Strings
-brief: >
- Enclosing strings in double quotes allows you
- to use escapings to represent ASCII and
- Unicode characters.
-yaml: |
- i know where i want my line breaks: "one here\nand another here\n"
-php: |
- array(
- 'i know where i want my line breaks' => "one here\nand another here\n"
- )
----
-test: Multi-line Quoted Strings
-todo: true
-brief: >
- Both single- and double-quoted strings may be
- carried on to new lines in your YAML document.
- They must be indented a step and indentation
- is interpreted as a single space.
-yaml: |
- i want a long string: "so i'm going to
- let it go on and on to other lines
- until i end it with a quote."
-php: |
- array('i want a long string' => "so i'm going to ".
- "let it go on and on to other lines ".
- "until i end it with a quote."
- )
-
----
-test: Plain scalars
-todo: true
-brief: >
- Unquoted strings may also span multiple lines, if they
- are free of YAML space indicators and indented.
-yaml: |
- - My little toe is broken in two places;
- - I'm crazy to have skied this way;
- - I'm not the craziest he's seen, since there was always the German guy
- who skied for 3 hours on a broken shin bone (just below the kneecap);
- - Nevertheless, second place is respectable, and he doesn't
- recommend going for the record;
- - He's going to put my foot in plaster for a month;
- - This would impair my skiing ability somewhat for the
- duration, as can be imagined.
-php: |
- array(
- "My little toe is broken in two places;",
- "I'm crazy to have skied this way;",
- "I'm not the craziest he's seen, since there was always ".
- "the German guy who skied for 3 hours on a broken shin ".
- "bone (just below the kneecap);",
- "Nevertheless, second place is respectable, and he doesn't ".
- "recommend going for the record;",
- "He's going to put my foot in plaster for a month;",
- "This would impair my skiing ability somewhat for the duration, ".
- "as can be imagined."
- )
----
-test: 'Null'
-brief: >
- You can use the tilde '~' character for a null value.
-yaml: |
- name: Mr. Show
- hosted by: Bob and David
- date of next season: ~
-php: |
- array(
- 'name' => 'Mr. Show',
- 'hosted by' => 'Bob and David',
- 'date of next season' => null
- )
----
-test: Boolean
-brief: >
- You can use 'true' and 'false' for Boolean values.
-yaml: |
- Is Gus a Liar?: true
- Do I rely on Gus for Sustenance?: false
-php: |
- array(
- 'Is Gus a Liar?' => true,
- 'Do I rely on Gus for Sustenance?' => false
- )
----
-test: Integers
-dump_skip: true
-brief: >
- An integer is a series of numbers, optionally
- starting with a positive or negative sign. Integers
- may also contain commas for readability.
-yaml: |
- zero: 0
- simple: 12
- one-thousand: 1,000
- negative one-thousand: -1,000
-php: |
- array(
- 'zero' => 0,
- 'simple' => 12,
- 'one-thousand' => 1000.0,
- 'negative one-thousand' => -1000.0
- )
----
-test: Integers as Map Keys
-brief: >
- An integer can be used a dictionary key.
-yaml: |
- 1: one
- 2: two
- 3: three
-php: |
- array(
- 1 => 'one',
- 2 => 'two',
- 3 => 'three'
- )
----
-test: Floats
-dump_skip: true
-brief: >
- Floats are represented by numbers with decimals,
- allowing for scientific notation, as well as
- positive and negative infinity and "not a number."
-yaml: |
- a simple float: 2.00
- larger float: 1,000.09
- scientific notation: 1.00009e+3
-php: |
- array(
- 'a simple float' => 2.0,
- 'larger float' => 1000.09,
- 'scientific notation' => 1000.09
- )
----
-test: Time
-todo: true
-brief: >
- You can represent timestamps by using
- ISO8601 format, or a variation which
- allows spaces between the date, time and
- time zone.
-yaml: |
- iso8601: 2001-12-14t21:59:43.10-05:00
- space separated: 2001-12-14 21:59:43.10 -05:00
-php: |
- array(
- 'iso8601' => mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" ),
- 'space separated' => mktime( 2001, 12, 14, 21, 59, 43, 0.10, "-05:00" )
- )
----
-test: Date
-todo: true
-brief: >
- A date can be represented by its year,
- month and day in ISO8601 order.
-yaml: |
- 1976-07-31
-php: |
- date( 1976, 7, 31 )
diff --git a/vendor/symfony/yaml/Tests/Fixtures/embededPhp.yml b/vendor/symfony/yaml/Tests/Fixtures/embededPhp.yml
deleted file mode 100755
index ec456ed09..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/embededPhp.yml
+++ /dev/null
@@ -1 +0,0 @@
-value:
diff --git a/vendor/symfony/yaml/Tests/Fixtures/escapedCharacters.yml b/vendor/symfony/yaml/Tests/Fixtures/escapedCharacters.yml
deleted file mode 100755
index 6ca044c8d..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/escapedCharacters.yml
+++ /dev/null
@@ -1,155 +0,0 @@
-test: outside double quotes
-yaml: |
- \0 \ \a \b \n
-php: |
- "\\0 \\ \\a \\b \\n"
----
-test: null
-yaml: |
- "\0"
-php: |
- "\x00"
----
-test: bell
-yaml: |
- "\a"
-php: |
- "\x07"
----
-test: backspace
-yaml: |
- "\b"
-php: |
- "\x08"
----
-test: horizontal tab (1)
-yaml: |
- "\t"
-php: |
- "\x09"
----
-test: horizontal tab (2)
-yaml: |
- "\ "
-php: |
- "\x09"
----
-test: line feed
-yaml: |
- "\n"
-php: |
- "\x0a"
----
-test: vertical tab
-yaml: |
- "\v"
-php: |
- "\x0b"
----
-test: form feed
-yaml: |
- "\f"
-php: |
- "\x0c"
----
-test: carriage return
-yaml: |
- "\r"
-php: |
- "\x0d"
----
-test: escape
-yaml: |
- "\e"
-php: |
- "\x1b"
----
-test: space
-yaml: |
- "\ "
-php: |
- "\x20"
----
-test: slash
-yaml: |
- "\/"
-php: |
- "\x2f"
----
-test: backslash
-yaml: |
- "\\"
-php: |
- "\\"
----
-test: Unicode next line
-yaml: |
- "\N"
-php: |
- "\xc2\x85"
----
-test: Unicode non-breaking space
-yaml: |
- "\_"
-php: |
- "\xc2\xa0"
----
-test: Unicode line separator
-yaml: |
- "\L"
-php: |
- "\xe2\x80\xa8"
----
-test: Unicode paragraph separator
-yaml: |
- "\P"
-php: |
- "\xe2\x80\xa9"
----
-test: Escaped 8-bit Unicode
-yaml: |
- "\x42"
-php: |
- "B"
----
-test: Escaped 16-bit Unicode
-yaml: |
- "\u20ac"
-php: |
- "\xe2\x82\xac"
----
-test: Escaped 32-bit Unicode
-yaml: |
- "\U00000043"
-php: |
- "C"
----
-test: Example 5.13 Escaped Characters
-note: |
- Currently throws an error parsing first line. Maybe Symfony Yaml doesn't support
- continuation of string across multiple lines? Keeping test here but disabled.
-todo: true
-yaml: |
- "Fun with \\
- \" \a \b \e \f \
- \n \r \t \v \0 \
- \ \_ \N \L \P \
- \x41 \u0041 \U00000041"
-php: |
- "Fun with \x5C\n\x22 \x07 \x08 \x1B \x0C\n\x0A \x0D \x09 \x0B \x00\n\x20 \xA0 \x85 \xe2\x80\xa8 \xe2\x80\xa9\nA A A"
----
-test: Double quotes with a line feed
-yaml: |
- { double: "some value\n \"some quoted string\" and 'some single quotes one'" }
-php: |
- array(
- 'double' => "some value\n \"some quoted string\" and 'some single quotes one'"
- )
----
-test: Backslashes
-yaml: |
- { single: 'foo\Var', no-quotes: foo\Var, double: "foo\\Var" }
-php: |
- array(
- 'single' => 'foo\Var', 'no-quotes' => 'foo\Var', 'double' => 'foo\Var'
- )
diff --git a/vendor/symfony/yaml/Tests/Fixtures/index.yml b/vendor/symfony/yaml/Tests/Fixtures/index.yml
deleted file mode 100755
index 3216a89eb..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/index.yml
+++ /dev/null
@@ -1,18 +0,0 @@
-- escapedCharacters
-- sfComments
-- sfCompact
-- sfTests
-- sfObjects
-- sfMergeKey
-- sfQuotes
-- YtsAnchorAlias
-- YtsBasicTests
-- YtsBlockMapping
-- YtsDocumentSeparator
-- YtsErrorTests
-- YtsFlowCollections
-- YtsFoldedScalars
-- YtsNullsAndEmpties
-- YtsSpecificationExamples
-- YtsTypeTransfers
-- unindentedCollections
diff --git a/vendor/symfony/yaml/Tests/Fixtures/sfComments.yml b/vendor/symfony/yaml/Tests/Fixtures/sfComments.yml
deleted file mode 100755
index b72a9b699..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/sfComments.yml
+++ /dev/null
@@ -1,76 +0,0 @@
---- %YAML:1.0
-test: Comments at the end of a line
-brief: >
- Comments at the end of a line
-yaml: |
- ex1: "foo # bar"
- ex2: "foo # bar" # comment
- ex3: 'foo # bar' # comment
- ex4: foo # comment
- ex5: foo # comment with tab before
- ex6: foo#foo # comment here
- ex7: foo # ignore me # and me
-php: |
- array('ex1' => 'foo # bar', 'ex2' => 'foo # bar', 'ex3' => 'foo # bar', 'ex4' => 'foo', 'ex5' => 'foo', 'ex6' => 'foo#foo', 'ex7' => 'foo')
----
-test: Comments in the middle
-brief: >
- Comments in the middle
-yaml: |
- foo:
- # some comment
- # some comment
- bar: foo
- # some comment
- # some comment
-php: |
- array('foo' => array('bar' => 'foo'))
----
-test: Comments on a hash line
-brief: >
- Comments on a hash line
-yaml: |
- foo: # a comment
- foo: bar # a comment
-php: |
- array('foo' => array('foo' => 'bar'))
----
-test: 'Value starting with a #'
-brief: >
- 'Value starting with a #'
-yaml: |
- foo: '#bar'
-php: |
- array('foo' => '#bar')
----
-test: Document starting with a comment and a separator
-brief: >
- Commenting before document start is allowed
-yaml: |
- # document comment
- ---
- foo: bar # a comment
-php: |
- array('foo' => 'bar')
----
-test: Comment containing a colon on a hash line
-brief: >
- Comment containing a colon on a scalar line
-yaml: 'foo # comment: this is also part of the comment'
-php: |
- 'foo'
----
-test: 'Hash key containing a #'
-brief: >
- 'Hash key containing a #'
-yaml: 'foo#bar: baz'
-php: |
- array('foo#bar' => 'baz')
----
-test: 'Hash key ending with a space and a #'
-brief: >
- 'Hash key ending with a space and a #'
-yaml: |
- 'foo #': baz
-php: |
- array('foo #' => 'baz')
diff --git a/vendor/symfony/yaml/Tests/Fixtures/sfCompact.yml b/vendor/symfony/yaml/Tests/Fixtures/sfCompact.yml
deleted file mode 100755
index 1339d23a6..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/sfCompact.yml
+++ /dev/null
@@ -1,159 +0,0 @@
---- %YAML:1.0
-test: Compact notation
-brief: |
- Compact notation for sets of mappings with single element
-yaml: |
- ---
- # products purchased
- - item : Super Hoop
- - item : Basketball
- quantity: 1
- - item:
- name: Big Shoes
- nick: Biggies
- quantity: 1
-php: |
- array (
- array (
- 'item' => 'Super Hoop',
- ),
- array (
- 'item' => 'Basketball',
- 'quantity' => 1,
- ),
- array (
- 'item' => array(
- 'name' => 'Big Shoes',
- 'nick' => 'Biggies'
- ),
- 'quantity' => 1
- )
- )
----
-test: Compact notation combined with inline notation
-brief: |
- Combinations of compact and inline notation are allowed
-yaml: |
- ---
- items:
- - { item: Super Hoop, quantity: 1 }
- - [ Basketball, Big Shoes ]
-php: |
- array (
- 'items' => array (
- array (
- 'item' => 'Super Hoop',
- 'quantity' => 1,
- ),
- array (
- 'Basketball',
- 'Big Shoes'
- )
- )
- )
---- %YAML:1.0
-test: Compact notation
-brief: |
- Compact notation for sets of mappings with single element
-yaml: |
- ---
- # products purchased
- - item : Super Hoop
- - item : Basketball
- quantity: 1
- - item:
- name: Big Shoes
- nick: Biggies
- quantity: 1
-php: |
- array (
- array (
- 'item' => 'Super Hoop',
- ),
- array (
- 'item' => 'Basketball',
- 'quantity' => 1,
- ),
- array (
- 'item' => array(
- 'name' => 'Big Shoes',
- 'nick' => 'Biggies'
- ),
- 'quantity' => 1
- )
- )
----
-test: Compact notation combined with inline notation
-brief: |
- Combinations of compact and inline notation are allowed
-yaml: |
- ---
- items:
- - { item: Super Hoop, quantity: 1 }
- - [ Basketball, Big Shoes ]
-php: |
- array (
- 'items' => array (
- array (
- 'item' => 'Super Hoop',
- 'quantity' => 1,
- ),
- array (
- 'Basketball',
- 'Big Shoes'
- )
- )
- )
---- %YAML:1.0
-test: Compact notation
-brief: |
- Compact notation for sets of mappings with single element
-yaml: |
- ---
- # products purchased
- - item : Super Hoop
- - item : Basketball
- quantity: 1
- - item:
- name: Big Shoes
- nick: Biggies
- quantity: 1
-php: |
- array (
- array (
- 'item' => 'Super Hoop',
- ),
- array (
- 'item' => 'Basketball',
- 'quantity' => 1,
- ),
- array (
- 'item' => array(
- 'name' => 'Big Shoes',
- 'nick' => 'Biggies'
- ),
- 'quantity' => 1
- )
- )
----
-test: Compact notation combined with inline notation
-brief: |
- Combinations of compact and inline notation are allowed
-yaml: |
- ---
- items:
- - { item: Super Hoop, quantity: 1 }
- - [ Basketball, Big Shoes ]
-php: |
- array (
- 'items' => array (
- array (
- 'item' => 'Super Hoop',
- 'quantity' => 1,
- ),
- array (
- 'Basketball',
- 'Big Shoes'
- )
- )
- )
diff --git a/vendor/symfony/yaml/Tests/Fixtures/sfMergeKey.yml b/vendor/symfony/yaml/Tests/Fixtures/sfMergeKey.yml
deleted file mode 100755
index 499446c10..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/sfMergeKey.yml
+++ /dev/null
@@ -1,66 +0,0 @@
---- %YAML:1.0
-test: Simple In Place Substitution
-brief: >
- If you want to reuse an entire alias, only overwriting what is different
- you can use a << in place substitution. This is not part of the official
- YAML spec, but a widely implemented extension. See the following URL for
- details: http://yaml.org/type/merge.html
-yaml: |
- foo: &foo
- a: Steve
- b: Clark
- c: Brian
- e: notnull
- bar:
- a: before
- d: other
- e: ~
- <<: *foo
- b: new
- x: Oren
- c:
- foo: bar
- foo: ignore
- bar: foo
- bar_inline: {a: before, d: other, <<: *foo, b: new, x: Oren, c: { foo: bar, foo: ignore, bar: foo}}
- duplicate:
- foo: bar
- foo: ignore
- foo2: &foo2
- a: Ballmer
- ding: &dong [ fi, fei, fo, fam]
- check:
- <<:
- - *foo
- - *dong
- isit: tested
- head:
- <<: [ *foo , *dong , *foo2 ]
- taz: &taz
- a: Steve
- w:
- p: 1234
- nested:
- <<: *taz
- d: Doug
- w: &nestedref
- p: 12345
- z:
- <<: *nestedref
- head_inline: &head_inline { <<: [ *foo , *dong , *foo2 ] }
- recursive_inline: { <<: *head_inline, c: { <<: *foo2 } }
-php: |
- array(
- 'foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull'),
- 'bar' => array('a' => 'before', 'd' => 'other', 'e' => null, 'b' => 'new', 'c' => array('foo' => 'bar', 'bar' => 'foo'), 'x' => 'Oren'),
- 'bar_inline' => array('a' => 'before', 'd' => 'other', 'b' => 'new', 'c' => array('foo' => 'bar', 'bar' => 'foo'), 'e' => 'notnull', 'x' => 'Oren'),
- 'duplicate' => array('foo' => 'bar'),
- 'foo2' => array('a' => 'Ballmer'),
- 'ding' => array('fi', 'fei', 'fo', 'fam'),
- 'check' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam', 'isit' => 'tested'),
- 'head' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam'),
- 'taz' => array('a' => 'Steve', 'w' => array('p' => 1234)),
- 'nested' => array('a' => 'Steve', 'w' => array('p' => 12345), 'd' => 'Doug', 'z' => array('p' => 12345)),
- 'head_inline' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam'),
- 'recursive_inline' => array('a' => 'Steve', 'b' => 'Clark', 'c' => array('a' => 'Ballmer'), 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam'),
- )
diff --git a/vendor/symfony/yaml/Tests/Fixtures/sfObjects.yml b/vendor/symfony/yaml/Tests/Fixtures/sfObjects.yml
deleted file mode 100755
index ee124b244..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/sfObjects.yml
+++ /dev/null
@@ -1,11 +0,0 @@
---- %YAML:1.0
-test: Objects
-brief: >
- Comments at the end of a line
-yaml: |
- ex1: "foo # bar"
- ex2: "foo # bar" # comment
- ex3: 'foo # bar' # comment
- ex4: foo # comment
-php: |
- array('ex1' => 'foo # bar', 'ex2' => 'foo # bar', 'ex3' => 'foo # bar', 'ex4' => 'foo')
diff --git a/vendor/symfony/yaml/Tests/Fixtures/sfQuotes.yml b/vendor/symfony/yaml/Tests/Fixtures/sfQuotes.yml
deleted file mode 100755
index 7c60baec9..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/sfQuotes.yml
+++ /dev/null
@@ -1,33 +0,0 @@
---- %YAML:1.0
-test: Some characters at the beginning of a string must be escaped
-brief: >
- Some characters at the beginning of a string must be escaped
-yaml: |
- foo: '| bar'
-php: |
- array('foo' => '| bar')
----
-test: A key can be a quoted string
-brief: >
- A key can be a quoted string
-yaml: |
- "foo1": bar
- 'foo2': bar
- "foo \" bar": bar
- 'foo '' bar': bar
- 'foo3: ': bar
- "foo4: ": bar
- foo5: { "foo \" bar: ": bar, 'foo '' bar: ': bar }
-php: |
- array(
- 'foo1' => 'bar',
- 'foo2' => 'bar',
- 'foo " bar' => 'bar',
- 'foo \' bar' => 'bar',
- 'foo3: ' => 'bar',
- 'foo4: ' => 'bar',
- 'foo5' => array(
- 'foo " bar: ' => 'bar',
- 'foo \' bar: ' => 'bar',
- ),
- )
diff --git a/vendor/symfony/yaml/Tests/Fixtures/sfTests.yml b/vendor/symfony/yaml/Tests/Fixtures/sfTests.yml
deleted file mode 100755
index a427be1c8..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/sfTests.yml
+++ /dev/null
@@ -1,149 +0,0 @@
---- %YAML:1.0
-test: Multiple quoted string on one line
-brief: >
- Multiple quoted string on one line
-yaml: |
- stripped_title: { name: "foo bar", help: "bar foo" }
-php: |
- array('stripped_title' => array('name' => 'foo bar', 'help' => 'bar foo'))
----
-test: Empty sequence
-yaml: |
- foo: [ ]
-php: |
- array('foo' => array())
----
-test: Empty value
-yaml: |
- foo:
-php: |
- array('foo' => null)
----
-test: Inline string parsing
-brief: >
- Inline string parsing
-yaml: |
- test: ['complex: string', 'another [string]']
-php: |
- array('test' => array('complex: string', 'another [string]'))
----
-test: Boolean
-brief: >
- Boolean
-yaml: |
- - false
- - true
- - null
- - ~
- - 'false'
- - 'true'
- - 'null'
- - '~'
-php: |
- array(
- false,
- true,
- null,
- null,
- 'false',
- 'true',
- 'null',
- '~',
- )
----
-test: Empty lines in literal blocks
-brief: >
- Empty lines in literal blocks
-yaml: |
- foo:
- bar: |
- foo
-
-
-
- bar
-php: |
- array('foo' => array('bar' => "foo\n\n\n \nbar\n"))
----
-test: Empty lines in folded blocks
-brief: >
- Empty lines in folded blocks
-yaml: |
- foo:
- bar: >
-
- foo
-
-
- bar
-php: |
- array('foo' => array('bar' => "\nfoo\n\nbar\n"))
----
-test: IP addresses
-brief: >
- IP addresses
-yaml: |
- foo: 10.0.0.2
-php: |
- array('foo' => '10.0.0.2')
----
-test: A sequence with an embedded mapping
-brief: >
- A sequence with an embedded mapping
-yaml: |
- - foo
- - bar: { bar: foo }
-php: |
- array('foo', array('bar' => array('bar' => 'foo')))
----
-test: A sequence with an unordered array
-brief: >
- A sequence with an unordered array
-yaml: |
- 1: foo
- 0: bar
-php: |
- array(1 => 'foo', 0 => 'bar')
----
-test: Octal
-brief: as in spec example 2.19, octal value is converted
-yaml: |
- foo: 0123
-php: |
- array('foo' => 83)
----
-test: Octal strings
-brief: Octal notation in a string must remain a string
-yaml: |
- foo: "0123"
-php: |
- array('foo' => '0123')
----
-test: Octal strings
-brief: Octal notation in a string must remain a string
-yaml: |
- foo: '0123'
-php: |
- array('foo' => '0123')
----
-test: Octal strings
-brief: Octal notation in a string must remain a string
-yaml: |
- foo: |
- 0123
-php: |
- array('foo' => "0123\n")
----
-test: Document as a simple hash
-brief: Document as a simple hash
-yaml: |
- { foo: bar }
-php: |
- array('foo' => 'bar')
----
-test: Document as a simple array
-brief: Document as a simple array
-yaml: |
- [ foo, bar ]
-php: |
- array('foo', 'bar')
diff --git a/vendor/symfony/yaml/Tests/Fixtures/unindentedCollections.yml b/vendor/symfony/yaml/Tests/Fixtures/unindentedCollections.yml
deleted file mode 100755
index 0c96108e9..000000000
--- a/vendor/symfony/yaml/Tests/Fixtures/unindentedCollections.yml
+++ /dev/null
@@ -1,82 +0,0 @@
---- %YAML:1.0
-test: Unindented collection
-brief: >
- Unindented collection
-yaml: |
- collection:
- - item1
- - item2
- - item3
-php: |
- array('collection' => array('item1', 'item2', 'item3'))
----
-test: Nested unindented collection (two levels)
-brief: >
- Nested unindented collection
-yaml: |
- collection:
- key:
- - a
- - b
- - c
-php: |
- array('collection' => array('key' => array('a', 'b', 'c')))
----
-test: Nested unindented collection (three levels)
-brief: >
- Nested unindented collection
-yaml: |
- collection:
- key:
- subkey:
- - one
- - two
- - three
-php: |
- array('collection' => array('key' => array('subkey' => array('one', 'two', 'three'))))
----
-test: Key/value after unindented collection (1)
-brief: >
- Key/value after unindented collection (1)
-yaml: |
- collection:
- key:
- - a
- - b
- - c
- foo: bar
-php: |
- array('collection' => array('key' => array('a', 'b', 'c')), 'foo' => 'bar')
----
-test: Key/value after unindented collection (at the same level)
-brief: >
- Key/value after unindented collection
-yaml: |
- collection:
- key:
- - a
- - b
- - c
- foo: bar
-php: |
- array('collection' => array('key' => array('a', 'b', 'c'), 'foo' => 'bar'))
----
-test: Shortcut Key after unindented collection
-brief: >
- Key/value after unindented collection
-yaml: |
- collection:
- - key: foo
- foo: bar
-php: |
- array('collection' => array(array('key' => 'foo', 'foo' => 'bar')))
----
-test: Shortcut Key after unindented collection with custom spaces
-brief: >
- Key/value after unindented collection
-yaml: |
- collection:
- - key: foo
- foo: bar
-php: |
- array('collection' => array(array('key' => 'foo', 'foo' => 'bar')))
diff --git a/vendor/symfony/yaml/Tests/InlineTest.php b/vendor/symfony/yaml/Tests/InlineTest.php
deleted file mode 100755
index 66ef70192..000000000
--- a/vendor/symfony/yaml/Tests/InlineTest.php
+++ /dev/null
@@ -1,506 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Yaml\Tests;
-
-use PHPUnit\Framework\TestCase;
-use Symfony\Component\Yaml\Inline;
-
-class InlineTest extends TestCase
-{
- /**
- * @dataProvider getTestsForParse
- */
- public function testParse($yaml, $value)
- {
- $this->assertSame($value, Inline::parse($yaml), sprintf('::parse() converts an inline YAML to a PHP structure (%s)', $yaml));
- }
-
- /**
- * @dataProvider getTestsForParseWithMapObjects
- */
- public function testParseWithMapObjects($yaml, $value)
- {
- $actual = Inline::parse($yaml, false, false, true);
-
- $this->assertSame(serialize($value), serialize($actual));
- }
-
- /**
- * @dataProvider getTestsForDump
- */
- public function testDump($yaml, $value)
- {
- $this->assertEquals($yaml, Inline::dump($value), sprintf('::dump() converts a PHP structure to an inline YAML (%s)', $yaml));
-
- $this->assertSame($value, Inline::parse(Inline::dump($value)), 'check consistency');
- }
-
- public function testDumpNumericValueWithLocale()
- {
- $locale = setlocale(LC_NUMERIC, 0);
- if (false === $locale) {
- $this->markTestSkipped('Your platform does not support locales.');
- }
-
- try {
- $requiredLocales = array('fr_FR.UTF-8', 'fr_FR.UTF8', 'fr_FR.utf-8', 'fr_FR.utf8', 'French_France.1252');
- if (false === setlocale(LC_NUMERIC, $requiredLocales)) {
- $this->markTestSkipped('Could not set any of required locales: '.implode(', ', $requiredLocales));
- }
-
- $this->assertEquals('1.2', Inline::dump(1.2));
- $this->assertContains('fr', strtolower(setlocale(LC_NUMERIC, 0)));
- setlocale(LC_NUMERIC, $locale);
- } catch (\Exception $e) {
- setlocale(LC_NUMERIC, $locale);
- throw $e;
- }
- }
-
- public function testHashStringsResemblingExponentialNumericsShouldNotBeChangedToINF()
- {
- $value = '686e444';
-
- $this->assertSame($value, Inline::parse(Inline::dump($value)));
- }
-
- /**
- * @group legacy
- * throws \Symfony\Component\Yaml\Exception\ParseException in 3.0
- */
- public function testParseScalarWithNonEscapedBlackslashShouldThrowException()
- {
- $this->assertSame('Foo\Var', Inline::parse('"Foo\Var"'));
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- */
- public function testParseScalarWithNonEscapedBlackslashAtTheEndShouldThrowException()
- {
- Inline::parse('"Foo\\"');
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- */
- public function testParseScalarWithIncorrectlyQuotedStringShouldThrowException()
- {
- $value = "'don't do somthin' like that'";
- Inline::parse($value);
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- */
- public function testParseScalarWithIncorrectlyDoubleQuotedStringShouldThrowException()
- {
- $value = '"don"t do somthin" like that"';
- Inline::parse($value);
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- */
- public function testParseInvalidMappingKeyShouldThrowException()
- {
- $value = '{ "foo " bar": "bar" }';
- Inline::parse($value);
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- */
- public function testParseInvalidMappingShouldThrowException()
- {
- Inline::parse('[foo] bar');
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- */
- public function testParseInvalidSequenceShouldThrowException()
- {
- Inline::parse('{ foo: bar } bar');
- }
-
- public function testParseScalarWithCorrectlyQuotedStringShouldReturnString()
- {
- $value = "'don''t do somthin'' like that'";
- $expect = "don't do somthin' like that";
-
- $this->assertSame($expect, Inline::parseScalar($value));
- }
-
- /**
- * @dataProvider getDataForParseReferences
- */
- public function testParseReferences($yaml, $expected)
- {
- $this->assertSame($expected, Inline::parse($yaml, false, false, false, array('var' => 'var-value')));
- }
-
- public function getDataForParseReferences()
- {
- return array(
- 'scalar' => array('*var', 'var-value'),
- 'list' => array('[ *var ]', array('var-value')),
- 'list-in-list' => array('[[ *var ]]', array(array('var-value'))),
- 'map-in-list' => array('[ { key: *var } ]', array(array('key' => 'var-value'))),
- 'embedded-mapping-in-list' => array('[ key: *var ]', array(array('key' => 'var-value'))),
- 'map' => array('{ key: *var }', array('key' => 'var-value')),
- 'list-in-map' => array('{ key: [*var] }', array('key' => array('var-value'))),
- 'map-in-map' => array('{ foo: { bar: *var } }', array('foo' => array('bar' => 'var-value'))),
- );
- }
-
- public function testParseMapReferenceInSequence()
- {
- $foo = array(
- 'a' => 'Steve',
- 'b' => 'Clark',
- 'c' => 'Brian',
- );
- $this->assertSame(array($foo), Inline::parse('[*foo]', false, false, false, array('foo' => $foo)));
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- * @expectedExceptionMessage A reference must contain at least one character.
- */
- public function testParseUnquotedAsterisk()
- {
- Inline::parse('{ foo: * }');
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- * @expectedExceptionMessage A reference must contain at least one character.
- */
- public function testParseUnquotedAsteriskFollowedByAComment()
- {
- Inline::parse('{ foo: * #foo }');
- }
-
- /**
- * @group legacy
- * @expectedDeprecation Not quoting the scalar "@foo " starting with "@" is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.
- * throws \Symfony\Component\Yaml\Exception\ParseException in 3.0
- */
- public function testParseUnquotedScalarStartingWithReservedAtIndicator()
- {
- Inline::parse('{ foo: @foo }');
- }
-
- /**
- * @group legacy
- * @expectedDeprecation Not quoting the scalar "`foo " starting with "`" is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.
- * throws \Symfony\Component\Yaml\Exception\ParseException in 3.0
- */
- public function testParseUnquotedScalarStartingWithReservedBacktickIndicator()
- {
- Inline::parse('{ foo: `foo }');
- }
-
- /**
- * @group legacy
- * @expectedDeprecation Not quoting the scalar "|foo " starting with "|" is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.
- * throws \Symfony\Component\Yaml\Exception\ParseException in 3.0
- */
- public function testParseUnquotedScalarStartingWithLiteralStyleIndicator()
- {
- Inline::parse('{ foo: |foo }');
- }
-
- /**
- * @group legacy
- * @expectedDeprecation Not quoting the scalar ">foo " starting with ">" is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.
- * throws \Symfony\Component\Yaml\Exception\ParseException in 3.0
- */
- public function testParseUnquotedScalarStartingWithFoldedStyleIndicator()
- {
- Inline::parse('{ foo: >foo }');
- }
-
- public function getScalarIndicators()
- {
- return array(array('|'), array('>'));
- }
-
- /**
- * @dataProvider getDataForIsHash
- */
- public function testIsHash($array, $expected)
- {
- $this->assertSame($expected, Inline::isHash($array));
- }
-
- public function getDataForIsHash()
- {
- return array(
- array(array(), false),
- array(array(1, 2, 3), false),
- array(array(2 => 1, 1 => 2, 0 => 3), true),
- array(array('foo' => 1, 'bar' => 2), true),
- );
- }
-
- public function getTestsForParse()
- {
- return array(
- array('', ''),
- array('null', null),
- array('false', false),
- array('true', true),
- array('12', 12),
- array('-12', -12),
- array('"quoted string"', 'quoted string'),
- array("'quoted string'", 'quoted string'),
- array('12.30e+02', 12.30e+02),
- array('0x4D2', 0x4D2),
- array('02333', 02333),
- array('.Inf', -log(0)),
- array('-.Inf', log(0)),
- array("'686e444'", '686e444'),
- array('686e444', 646e444),
- array('123456789123456789123456789123456789', '123456789123456789123456789123456789'),
- array('"foo\r\nbar"', "foo\r\nbar"),
- array("'foo#bar'", 'foo#bar'),
- array("'foo # bar'", 'foo # bar'),
- array("'#cfcfcf'", '#cfcfcf'),
- array('::form_base.html.twig', '::form_base.html.twig'),
-
- // Pre-YAML-1.2 booleans
- array("'y'", 'y'),
- array("'n'", 'n'),
- array("'yes'", 'yes'),
- array("'no'", 'no'),
- array("'on'", 'on'),
- array("'off'", 'off'),
-
- array('2007-10-30', gmmktime(0, 0, 0, 10, 30, 2007)),
- array('2007-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 2007)),
- array('2007-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 2007)),
- array('1960-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 1960)),
- array('1730-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 1730)),
-
- array('"a \\"string\\" with \'quoted strings inside\'"', 'a "string" with \'quoted strings inside\''),
- array("'a \"string\" with ''quoted strings inside'''", 'a "string" with \'quoted strings inside\''),
-
- // sequences
- // urls are no key value mapping. see #3609. Valid yaml "key: value" mappings require a space after the colon
- array('[foo, http://urls.are/no/mappings, false, null, 12]', array('foo', 'http://urls.are/no/mappings', false, null, 12)),
- array('[ foo , bar , false , null , 12 ]', array('foo', 'bar', false, null, 12)),
- array('[\'foo,bar\', \'foo bar\']', array('foo,bar', 'foo bar')),
-
- // mappings
- array('{foo:bar,bar:foo,false:false,null:null,integer:12}', array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12)),
- array('{ foo : bar, bar : foo, false : false, null : null, integer : 12 }', array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12)),
- array('{foo: \'bar\', bar: \'foo: bar\'}', array('foo' => 'bar', 'bar' => 'foo: bar')),
- array('{\'foo\': \'bar\', "bar": \'foo: bar\'}', array('foo' => 'bar', 'bar' => 'foo: bar')),
- array('{\'foo\'\'\': \'bar\', "bar\"": \'foo: bar\'}', array('foo\'' => 'bar', 'bar"' => 'foo: bar')),
- array('{\'foo: \': \'bar\', "bar: ": \'foo: bar\'}', array('foo: ' => 'bar', 'bar: ' => 'foo: bar')),
-
- // nested sequences and mappings
- array('[foo, [bar, foo]]', array('foo', array('bar', 'foo'))),
- array('[foo, {bar: foo}]', array('foo', array('bar' => 'foo'))),
- array('{ foo: {bar: foo} }', array('foo' => array('bar' => 'foo'))),
- array('{ foo: [bar, foo] }', array('foo' => array('bar', 'foo'))),
-
- array('[ foo, [ bar, foo ] ]', array('foo', array('bar', 'foo'))),
-
- array('[{ foo: {bar: foo} }]', array(array('foo' => array('bar' => 'foo')))),
-
- array('[foo, [bar, [foo, [bar, foo]], foo]]', array('foo', array('bar', array('foo', array('bar', 'foo')), 'foo'))),
-
- array('[foo, {bar: foo, foo: [foo, {bar: foo}]}, [foo, {bar: foo}]]', array('foo', array('bar' => 'foo', 'foo' => array('foo', array('bar' => 'foo'))), array('foo', array('bar' => 'foo')))),
-
- array('[foo, bar: { foo: bar }]', array('foo', '1' => array('bar' => array('foo' => 'bar')))),
- array('[foo, \'@foo.baz\', { \'%foo%\': \'foo is %foo%\', bar: \'%foo%\' }, true, \'@service_container\']', array('foo', '@foo.baz', array('%foo%' => 'foo is %foo%', 'bar' => '%foo%'), true, '@service_container')),
- );
- }
-
- public function getTestsForParseWithMapObjects()
- {
- return array(
- array('', ''),
- array('null', null),
- array('false', false),
- array('true', true),
- array('12', 12),
- array('-12', -12),
- array('"quoted string"', 'quoted string'),
- array("'quoted string'", 'quoted string'),
- array('12.30e+02', 12.30e+02),
- array('0x4D2', 0x4D2),
- array('02333', 02333),
- array('.Inf', -log(0)),
- array('-.Inf', log(0)),
- array("'686e444'", '686e444'),
- array('686e444', 646e444),
- array('123456789123456789123456789123456789', '123456789123456789123456789123456789'),
- array('"foo\r\nbar"', "foo\r\nbar"),
- array("'foo#bar'", 'foo#bar'),
- array("'foo # bar'", 'foo # bar'),
- array("'#cfcfcf'", '#cfcfcf'),
- array('::form_base.html.twig', '::form_base.html.twig'),
-
- array('2007-10-30', gmmktime(0, 0, 0, 10, 30, 2007)),
- array('2007-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 2007)),
- array('2007-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 2007)),
- array('1960-10-30 02:59:43 Z', gmmktime(2, 59, 43, 10, 30, 1960)),
- array('1730-10-30T02:59:43Z', gmmktime(2, 59, 43, 10, 30, 1730)),
-
- array('"a \\"string\\" with \'quoted strings inside\'"', 'a "string" with \'quoted strings inside\''),
- array("'a \"string\" with ''quoted strings inside'''", 'a "string" with \'quoted strings inside\''),
-
- // sequences
- // urls are no key value mapping. see #3609. Valid yaml "key: value" mappings require a space after the colon
- array('[foo, http://urls.are/no/mappings, false, null, 12]', array('foo', 'http://urls.are/no/mappings', false, null, 12)),
- array('[ foo , bar , false , null , 12 ]', array('foo', 'bar', false, null, 12)),
- array('[\'foo,bar\', \'foo bar\']', array('foo,bar', 'foo bar')),
-
- // mappings
- array('{foo:bar,bar:foo,false:false,null:null,integer:12}', (object) array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12)),
- array('{ foo : bar, bar : foo, false : false, null : null, integer : 12 }', (object) array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12)),
- array('{foo: \'bar\', bar: \'foo: bar\'}', (object) array('foo' => 'bar', 'bar' => 'foo: bar')),
- array('{\'foo\': \'bar\', "bar": \'foo: bar\'}', (object) array('foo' => 'bar', 'bar' => 'foo: bar')),
- array('{\'foo\'\'\': \'bar\', "bar\"": \'foo: bar\'}', (object) array('foo\'' => 'bar', 'bar"' => 'foo: bar')),
- array('{\'foo: \': \'bar\', "bar: ": \'foo: bar\'}', (object) array('foo: ' => 'bar', 'bar: ' => 'foo: bar')),
-
- // nested sequences and mappings
- array('[foo, [bar, foo]]', array('foo', array('bar', 'foo'))),
- array('[foo, {bar: foo}]', array('foo', (object) array('bar' => 'foo'))),
- array('{ foo: {bar: foo} }', (object) array('foo' => (object) array('bar' => 'foo'))),
- array('{ foo: [bar, foo] }', (object) array('foo' => array('bar', 'foo'))),
-
- array('[ foo, [ bar, foo ] ]', array('foo', array('bar', 'foo'))),
-
- array('[{ foo: {bar: foo} }]', array((object) array('foo' => (object) array('bar' => 'foo')))),
-
- array('[foo, [bar, [foo, [bar, foo]], foo]]', array('foo', array('bar', array('foo', array('bar', 'foo')), 'foo'))),
-
- array('[foo, {bar: foo, foo: [foo, {bar: foo}]}, [foo, {bar: foo}]]', array('foo', (object) array('bar' => 'foo', 'foo' => array('foo', (object) array('bar' => 'foo'))), array('foo', (object) array('bar' => 'foo')))),
-
- array('[foo, bar: { foo: bar }]', array('foo', '1' => (object) array('bar' => (object) array('foo' => 'bar')))),
- array('[foo, \'@foo.baz\', { \'%foo%\': \'foo is %foo%\', bar: \'%foo%\' }, true, \'@service_container\']', array('foo', '@foo.baz', (object) array('%foo%' => 'foo is %foo%', 'bar' => '%foo%'), true, '@service_container')),
-
- array('{}', new \stdClass()),
- array('{ foo : bar, bar : {} }', (object) array('foo' => 'bar', 'bar' => new \stdClass())),
- array('{ foo : [], bar : {} }', (object) array('foo' => array(), 'bar' => new \stdClass())),
- array('{foo: \'bar\', bar: {} }', (object) array('foo' => 'bar', 'bar' => new \stdClass())),
- array('{\'foo\': \'bar\', "bar": {}}', (object) array('foo' => 'bar', 'bar' => new \stdClass())),
- array('{\'foo\': \'bar\', "bar": \'{}\'}', (object) array('foo' => 'bar', 'bar' => '{}')),
-
- array('[foo, [{}, {}]]', array('foo', array(new \stdClass(), new \stdClass()))),
- array('[foo, [[], {}]]', array('foo', array(array(), new \stdClass()))),
- array('[foo, [[{}, {}], {}]]', array('foo', array(array(new \stdClass(), new \stdClass()), new \stdClass()))),
- array('[foo, {bar: {}}]', array('foo', '1' => (object) array('bar' => new \stdClass()))),
- );
- }
-
- public function getTestsForDump()
- {
- return array(
- array('null', null),
- array('false', false),
- array('true', true),
- array('12', 12),
- array("'quoted string'", 'quoted string'),
- array('!!float 1230', 12.30e+02),
- array('1234', 0x4D2),
- array('1243', 02333),
- array('.Inf', -log(0)),
- array('-.Inf', log(0)),
- array("'686e444'", '686e444'),
- array('"foo\r\nbar"', "foo\r\nbar"),
- array("'foo#bar'", 'foo#bar'),
- array("'foo # bar'", 'foo # bar'),
- array("'#cfcfcf'", '#cfcfcf'),
-
- array("'a \"string\" with ''quoted strings inside'''", 'a "string" with \'quoted strings inside\''),
-
- array("'-dash'", '-dash'),
- array("'-'", '-'),
-
- // Pre-YAML-1.2 booleans
- array("'y'", 'y'),
- array("'n'", 'n'),
- array("'yes'", 'yes'),
- array("'no'", 'no'),
- array("'on'", 'on'),
- array("'off'", 'off'),
-
- // sequences
- array('[foo, bar, false, null, 12]', array('foo', 'bar', false, null, 12)),
- array('[\'foo,bar\', \'foo bar\']', array('foo,bar', 'foo bar')),
-
- // mappings
- array('{ foo: bar, bar: foo, \'false\': false, \'null\': null, integer: 12 }', array('foo' => 'bar', 'bar' => 'foo', 'false' => false, 'null' => null, 'integer' => 12)),
- array('{ foo: bar, bar: \'foo: bar\' }', array('foo' => 'bar', 'bar' => 'foo: bar')),
-
- // nested sequences and mappings
- array('[foo, [bar, foo]]', array('foo', array('bar', 'foo'))),
-
- array('[foo, [bar, [foo, [bar, foo]], foo]]', array('foo', array('bar', array('foo', array('bar', 'foo')), 'foo'))),
-
- array('{ foo: { bar: foo } }', array('foo' => array('bar' => 'foo'))),
-
- array('[foo, { bar: foo }]', array('foo', array('bar' => 'foo'))),
-
- array('[foo, { bar: foo, foo: [foo, { bar: foo }] }, [foo, { bar: foo }]]', array('foo', array('bar' => 'foo', 'foo' => array('foo', array('bar' => 'foo'))), array('foo', array('bar' => 'foo')))),
-
- array('[foo, \'@foo.baz\', { \'%foo%\': \'foo is %foo%\', bar: \'%foo%\' }, true, \'@service_container\']', array('foo', '@foo.baz', array('%foo%' => 'foo is %foo%', 'bar' => '%foo%'), true, '@service_container')),
-
- array('{ foo: { bar: { 1: 2, baz: 3 } } }', array('foo' => array('bar' => array(1 => 2, 'baz' => 3)))),
- );
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- * @expectedExceptionMessage Malformed inline YAML string: {this, is not, supported}.
- */
- public function testNotSupportedMissingValue()
- {
- Inline::parse('{this, is not, supported}');
- }
-
- public function testVeryLongQuotedStrings()
- {
- $longStringWithQuotes = str_repeat("x\r\n\\\"x\"x", 1000);
-
- $yamlString = Inline::dump(array('longStringWithQuotes' => $longStringWithQuotes));
- $arrayFromYaml = Inline::parse($yamlString);
-
- $this->assertEquals($longStringWithQuotes, $arrayFromYaml['longStringWithQuotes']);
- }
-
- public function testBooleanMappingKeysAreConvertedToStrings()
- {
- $this->assertSame(array('false' => 'foo'), Inline::parse('{false: foo}'));
- $this->assertSame(array('true' => 'foo'), Inline::parse('{true: foo}'));
- }
-
- public function testTheEmptyStringIsAValidMappingKey()
- {
- $this->assertSame(array('' => 'foo'), Inline::parse('{ "": foo }'));
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- * @expectedExceptionMessage Unexpected end of line, expected one of ",}".
- */
- public function testUnfinishedInlineMap()
- {
- Inline::parse("{abc: 'def'");
- }
-}
diff --git a/vendor/symfony/yaml/Tests/ParseExceptionTest.php b/vendor/symfony/yaml/Tests/ParseExceptionTest.php
deleted file mode 100755
index b7797fb7f..000000000
--- a/vendor/symfony/yaml/Tests/ParseExceptionTest.php
+++ /dev/null
@@ -1,42 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Yaml\Tests;
-
-use PHPUnit\Framework\TestCase;
-use Symfony\Component\Yaml\Exception\ParseException;
-
-class ParseExceptionTest extends TestCase
-{
- public function testGetMessage()
- {
- $exception = new ParseException('Error message', 42, 'foo: bar', '/var/www/app/config.yml');
- if (\PHP_VERSION_ID >= 50400) {
- $message = 'Error message in "/var/www/app/config.yml" at line 42 (near "foo: bar")';
- } else {
- $message = 'Error message in "\\/var\\/www\\/app\\/config.yml" at line 42 (near "foo: bar")';
- }
-
- $this->assertEquals($message, $exception->getMessage());
- }
-
- public function testGetMessageWithUnicodeInFilename()
- {
- $exception = new ParseException('Error message', 42, 'foo: bar', 'äöü.yml');
- if (\PHP_VERSION_ID >= 50400) {
- $message = 'Error message in "äöü.yml" at line 42 (near "foo: bar")';
- } else {
- $message = 'Error message in "\u00e4\u00f6\u00fc.yml" at line 42 (near "foo: bar")';
- }
-
- $this->assertEquals($message, $exception->getMessage());
- }
-}
diff --git a/vendor/symfony/yaml/Tests/ParserTest.php b/vendor/symfony/yaml/Tests/ParserTest.php
deleted file mode 100755
index 0cf9bd4df..000000000
--- a/vendor/symfony/yaml/Tests/ParserTest.php
+++ /dev/null
@@ -1,1300 +0,0 @@
-
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
-
-namespace Symfony\Component\Yaml\Tests;
-
-use PHPUnit\Framework\TestCase;
-use Symfony\Component\Yaml\Parser;
-use Symfony\Component\Yaml\Yaml;
-
-class ParserTest extends TestCase
-{
- /** @var Parser */
- protected $parser;
-
- protected function setUp()
- {
- $this->parser = new Parser();
- }
-
- protected function tearDown()
- {
- $this->parser = null;
- }
-
- /**
- * @dataProvider getDataFormSpecifications
- */
- public function testSpecifications($file, $expected, $yaml, $comment)
- {
- $this->assertEquals($expected, var_export($this->parser->parse($yaml), true), $comment);
- }
-
- public function getDataFormSpecifications()
- {
- $parser = new Parser();
- $path = __DIR__.'/Fixtures';
-
- $tests = array();
- $files = $parser->parse(file_get_contents($path.'/index.yml'));
- foreach ($files as $file) {
- $yamls = file_get_contents($path.'/'.$file.'.yml');
-
- // split YAMLs documents
- foreach (preg_split('/^---( %YAML\:1\.0)?/m', $yamls) as $yaml) {
- if (!$yaml) {
- continue;
- }
-
- $test = $parser->parse($yaml);
- if (isset($test['todo']) && $test['todo']) {
- // TODO
- } else {
- eval('$expected = '.trim($test['php']).';');
-
- $tests[] = array($file, var_export($expected, true), $test['yaml'], $test['test']);
- }
- }
- }
-
- return $tests;
- }
-
- public function testTabsInYaml()
- {
- // test tabs in YAML
- $yamls = array(
- "foo:\n bar",
- "foo:\n bar",
- "foo:\n bar",
- "foo:\n bar",
- );
-
- foreach ($yamls as $yaml) {
- try {
- $content = $this->parser->parse($yaml);
-
- $this->fail('YAML files must not contain tabs');
- } catch (\Exception $e) {
- $this->assertInstanceOf('\Exception', $e, 'YAML files must not contain tabs');
- $this->assertEquals('A YAML file cannot contain tabs as indentation at line 2 (near "'.strpbrk($yaml, "\t").'").', $e->getMessage(), 'YAML files must not contain tabs');
- }
- }
- }
-
- public function testEndOfTheDocumentMarker()
- {
- $yaml = <<<'EOF'
---- %YAML:1.0
-foo
-...
-EOF;
-
- $this->assertEquals('foo', $this->parser->parse($yaml));
- }
-
- public function getBlockChompingTests()
- {
- $tests = array();
-
- $yaml = <<<'EOF'
-foo: |-
- one
- two
-bar: |-
- one
- two
-
-EOF;
- $expected = array(
- 'foo' => "one\ntwo",
- 'bar' => "one\ntwo",
- );
- $tests['Literal block chomping strip with single trailing newline'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: |-
- one
- two
-
-bar: |-
- one
- two
-
-
-EOF;
- $expected = array(
- 'foo' => "one\ntwo",
- 'bar' => "one\ntwo",
- );
- $tests['Literal block chomping strip with multiple trailing newlines'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-{}
-
-
-EOF;
- $expected = array();
- $tests['Literal block chomping strip with multiple trailing newlines after a 1-liner'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: |-
- one
- two
-bar: |-
- one
- two
-EOF;
- $expected = array(
- 'foo' => "one\ntwo",
- 'bar' => "one\ntwo",
- );
- $tests['Literal block chomping strip without trailing newline'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: |
- one
- two
-bar: |
- one
- two
-
-EOF;
- $expected = array(
- 'foo' => "one\ntwo\n",
- 'bar' => "one\ntwo\n",
- );
- $tests['Literal block chomping clip with single trailing newline'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: |
- one
- two
-
-bar: |
- one
- two
-
-
-EOF;
- $expected = array(
- 'foo' => "one\ntwo\n",
- 'bar' => "one\ntwo\n",
- );
- $tests['Literal block chomping clip with multiple trailing newlines'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo:
-- bar: |
- one
-
- two
-EOF;
- $expected = array(
- 'foo' => array(
- array(
- 'bar' => "one\n\ntwo",
- ),
- ),
- );
- $tests['Literal block chomping clip with embedded blank line inside unindented collection'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: |
- one
- two
-bar: |
- one
- two
-EOF;
- $expected = array(
- 'foo' => "one\ntwo\n",
- 'bar' => "one\ntwo",
- );
- $tests['Literal block chomping clip without trailing newline'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: |+
- one
- two
-bar: |+
- one
- two
-
-EOF;
- $expected = array(
- 'foo' => "one\ntwo\n",
- 'bar' => "one\ntwo\n",
- );
- $tests['Literal block chomping keep with single trailing newline'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: |+
- one
- two
-
-bar: |+
- one
- two
-
-
-EOF;
- $expected = array(
- 'foo' => "one\ntwo\n\n",
- 'bar' => "one\ntwo\n\n",
- );
- $tests['Literal block chomping keep with multiple trailing newlines'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: |+
- one
- two
-bar: |+
- one
- two
-EOF;
- $expected = array(
- 'foo' => "one\ntwo\n",
- 'bar' => "one\ntwo",
- );
- $tests['Literal block chomping keep without trailing newline'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: >-
- one
- two
-bar: >-
- one
- two
-
-EOF;
- $expected = array(
- 'foo' => 'one two',
- 'bar' => 'one two',
- );
- $tests['Folded block chomping strip with single trailing newline'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: >-
- one
- two
-
-bar: >-
- one
- two
-
-
-EOF;
- $expected = array(
- 'foo' => 'one two',
- 'bar' => 'one two',
- );
- $tests['Folded block chomping strip with multiple trailing newlines'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: >-
- one
- two
-bar: >-
- one
- two
-EOF;
- $expected = array(
- 'foo' => 'one two',
- 'bar' => 'one two',
- );
- $tests['Folded block chomping strip without trailing newline'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: >
- one
- two
-bar: >
- one
- two
-
-EOF;
- $expected = array(
- 'foo' => "one two\n",
- 'bar' => "one two\n",
- );
- $tests['Folded block chomping clip with single trailing newline'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: >
- one
- two
-
-bar: >
- one
- two
-
-
-EOF;
- $expected = array(
- 'foo' => "one two\n",
- 'bar' => "one two\n",
- );
- $tests['Folded block chomping clip with multiple trailing newlines'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: >
- one
- two
-bar: >
- one
- two
-EOF;
- $expected = array(
- 'foo' => "one two\n",
- 'bar' => 'one two',
- );
- $tests['Folded block chomping clip without trailing newline'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: >+
- one
- two
-bar: >+
- one
- two
-
-EOF;
- $expected = array(
- 'foo' => "one two\n",
- 'bar' => "one two\n",
- );
- $tests['Folded block chomping keep with single trailing newline'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: >+
- one
- two
-
-bar: >+
- one
- two
-
-
-EOF;
- $expected = array(
- 'foo' => "one two\n\n",
- 'bar' => "one two\n\n",
- );
- $tests['Folded block chomping keep with multiple trailing newlines'] = array($expected, $yaml);
-
- $yaml = <<<'EOF'
-foo: >+
- one
- two
-bar: >+
- one
- two
-EOF;
- $expected = array(
- 'foo' => "one two\n",
- 'bar' => 'one two',
- );
- $tests['Folded block chomping keep without trailing newline'] = array($expected, $yaml);
-
- return $tests;
- }
-
- /**
- * @dataProvider getBlockChompingTests
- */
- public function testBlockChomping($expected, $yaml)
- {
- $this->assertSame($expected, $this->parser->parse($yaml));
- }
-
- /**
- * Regression test for issue #7989.
- *
- * @see https://github.com/symfony/symfony/issues/7989
- */
- public function testBlockLiteralWithLeadingNewlines()
- {
- $yaml = <<<'EOF'
-foo: |-
-
-
- bar
-
-EOF;
- $expected = array(
- 'foo' => "\n\nbar",
- );
-
- $this->assertSame($expected, $this->parser->parse($yaml));
- }
-
- public function testObjectSupportEnabled()
- {
- $input = <<<'EOF'
-foo: !!php/object:O:30:"Symfony\Component\Yaml\Tests\B":1:{s:1:"b";s:3:"foo";}
-bar: 1
-EOF;
- $this->assertEquals(array('foo' => new B(), 'bar' => 1), $this->parser->parse($input, false, true), '->parse() is able to parse objects');
-
- $input = <<<'EOF'
-foo: !php/object:O:30:"Symfony\Component\Yaml\Tests\B":1:{s:1:"b";s:3:"foo";}
-bar: 1
-EOF;
- $this->assertEquals(array('foo' => new B(), 'bar' => 1), $this->parser->parse($input, false, true), '->parse() is able to parse objects');
- }
-
- /**
- * @dataProvider invalidDumpedObjectProvider
- */
- public function testObjectSupportDisabledButNoExceptions($input)
- {
- $this->assertEquals(array('foo' => null, 'bar' => 1), $this->parser->parse($input), '->parse() does not parse objects');
- }
-
- /**
- * @dataProvider getObjectForMapTests
- */
- public function testObjectForMap($yaml, $expected)
- {
- $this->assertEquals($expected, $this->parser->parse($yaml, false, false, true));
- }
-
- public function getObjectForMapTests()
- {
- $tests = array();
-
- $yaml = <<<'EOF'
-foo:
- fiz: [cat]
-EOF;
- $expected = new \stdClass();
- $expected->foo = new \stdClass();
- $expected->foo->fiz = array('cat');
- $tests['mapping'] = array($yaml, $expected);
-
- $yaml = '{ "foo": "bar", "fiz": "cat" }';
- $expected = new \stdClass();
- $expected->foo = 'bar';
- $expected->fiz = 'cat';
- $tests['inline-mapping'] = array($yaml, $expected);
-
- $yaml = "foo: bar\nbaz: foobar";
- $expected = new \stdClass();
- $expected->foo = 'bar';
- $expected->baz = 'foobar';
- $tests['object-for-map-is-applied-after-parsing'] = array($yaml, $expected);
-
- $yaml = <<<'EOT'
-array:
- - key: one
- - key: two
-EOT;
- $expected = new \stdClass();
- $expected->array = array();
- $expected->array[0] = new \stdClass();
- $expected->array[0]->key = 'one';
- $expected->array[1] = new \stdClass();
- $expected->array[1]->key = 'two';
- $tests['nest-map-and-sequence'] = array($yaml, $expected);
-
- $yaml = <<<'YAML'
-map:
- 1: one
- 2: two
-YAML;
- $expected = new \stdClass();
- $expected->map = new \stdClass();
- $expected->map->{1} = 'one';
- $expected->map->{2} = 'two';
- $tests['numeric-keys'] = array($yaml, $expected);
-
- $yaml = <<<'YAML'
-map:
- 0: one
- 1: two
-YAML;
- $expected = new \stdClass();
- $expected->map = new \stdClass();
- $expected->map->{0} = 'one';
- $expected->map->{1} = 'two';
- $tests['zero-indexed-numeric-keys'] = array($yaml, $expected);
-
- return $tests;
- }
-
- /**
- * @dataProvider invalidDumpedObjectProvider
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- */
- public function testObjectsSupportDisabledWithExceptions($yaml)
- {
- $this->parser->parse($yaml, true, false);
- }
-
- public function invalidDumpedObjectProvider()
- {
- $yamlTag = <<<'EOF'
-foo: !!php/object:O:30:"Symfony\Tests\Component\Yaml\B":1:{s:1:"b";s:3:"foo";}
-bar: 1
-EOF;
- $localTag = <<<'EOF'
-foo: !php/object:O:30:"Symfony\Tests\Component\Yaml\B":1:{s:1:"b";s:3:"foo";}
-bar: 1
-EOF;
-
- return array(
- 'yaml-tag' => array($yamlTag),
- 'local-tag' => array($localTag),
- );
- }
-
- /**
- * @requires extension iconv
- */
- public function testNonUtf8Exception()
- {
- $yamls = array(
- iconv('UTF-8', 'ISO-8859-1', "foo: 'äöüß'"),
- iconv('UTF-8', 'ISO-8859-15', "euro: '€'"),
- iconv('UTF-8', 'CP1252', "cp1252: '©ÉÇáñ'"),
- );
-
- foreach ($yamls as $yaml) {
- try {
- $this->parser->parse($yaml);
-
- $this->fail('charsets other than UTF-8 are rejected.');
- } catch (\Exception $e) {
- $this->assertInstanceOf('Symfony\Component\Yaml\Exception\ParseException', $e, 'charsets other than UTF-8 are rejected.');
- }
- }
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- */
- public function testUnindentedCollectionException()
- {
- $yaml = <<<'EOF'
-
-collection:
--item1
--item2
--item3
-
-EOF;
-
- $this->parser->parse($yaml);
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- */
- public function testShortcutKeyUnindentedCollectionException()
- {
- $yaml = <<<'EOF'
-
-collection:
-- key: foo
- foo: bar
-
-EOF;
-
- $this->parser->parse($yaml);
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- * @expectedExceptionMessageRegExp /^Multiple documents are not supported.+/
- */
- public function testMultipleDocumentsNotSupportedException()
- {
- Yaml::parse(<<<'EOL'
-# Ranking of 1998 home runs
----
-- Mark McGwire
-- Sammy Sosa
-- Ken Griffey
-
-# Team ranking
----
-- Chicago Cubs
-- St Louis Cardinals
-EOL
- );
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- */
- public function testSequenceInAMapping()
- {
- Yaml::parse(<<<'EOF'
-yaml:
- hash: me
- - array stuff
-EOF
- );
- }
-
- public function testSequenceInMappingStartedBySingleDashLine()
- {
- $yaml = <<<'EOT'
-a:
--
- b:
- -
- bar: baz
-- foo
-d: e
-EOT;
- $expected = array(
- 'a' => array(
- array(
- 'b' => array(
- array(
- 'bar' => 'baz',
- ),
- ),
- ),
- 'foo',
- ),
- 'd' => 'e',
- );
-
- $this->assertSame($expected, $this->parser->parse($yaml));
- }
-
- public function testSequenceFollowedByCommentEmbeddedInMapping()
- {
- $yaml = <<<'EOT'
-a:
- b:
- - c
-# comment
- d: e
-EOT;
- $expected = array(
- 'a' => array(
- 'b' => array('c'),
- 'd' => 'e',
- ),
- );
-
- $this->assertSame($expected, $this->parser->parse($yaml));
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- */
- public function testMappingInASequence()
- {
- Yaml::parse(<<<'EOF'
-yaml:
- - array stuff
- hash: me
-EOF
- );
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- * @expectedExceptionMessage missing colon
- */
- public function testScalarInSequence()
- {
- Yaml::parse(<<<'EOF'
-foo:
- - bar
-"missing colon"
- foo: bar
-EOF
- );
- }
-
- /**
- * > It is an error for two equal keys to appear in the same mapping node.
- * > In such a case the YAML processor may continue, ignoring the second
- * > `key: value` pair and issuing an appropriate warning. This strategy
- * > preserves a consistent information model for one-pass and random access
- * > applications.
- *
- * @see http://yaml.org/spec/1.2/spec.html#id2759572
- * @see http://yaml.org/spec/1.1/#id932806
- */
- public function testMappingDuplicateKeyBlock()
- {
- $input = <<<'EOD'
-parent:
- child: first
- child: duplicate
-parent:
- child: duplicate
- child: duplicate
-EOD;
- $expected = array(
- 'parent' => array(
- 'child' => 'first',
- ),
- );
- $this->assertSame($expected, Yaml::parse($input));
- }
-
- public function testMappingDuplicateKeyFlow()
- {
- $input = <<<'EOD'
-parent: { child: first, child: duplicate }
-parent: { child: duplicate, child: duplicate }
-EOD;
- $expected = array(
- 'parent' => array(
- 'child' => 'first',
- ),
- );
- $this->assertSame($expected, Yaml::parse($input));
- }
-
- public function testEmptyValue()
- {
- $input = <<<'EOF'
-hash:
-EOF;
-
- $this->assertEquals(array('hash' => null), Yaml::parse($input));
- }
-
- public function testCommentAtTheRootIndent()
- {
- $this->assertEquals(array(
- 'services' => array(
- 'app.foo_service' => array(
- 'class' => 'Foo',
- ),
- 'app/bar_service' => array(
- 'class' => 'Bar',
- ),
- ),
- ), Yaml::parse(<<<'EOF'
-# comment 1
-services:
-# comment 2
- # comment 3
- app.foo_service:
- class: Foo
-# comment 4
- # comment 5
- app/bar_service:
- class: Bar
-EOF
- ));
- }
-
- public function testStringBlockWithComments()
- {
- $this->assertEquals(array('content' => <<<'EOT'
-# comment 1
-header
-
- # comment 2
-
- ]title
-
-
-footer # comment3
-EOT
- ), Yaml::parse(<<<'EOF'
-content: |
- # comment 1
- header
-
- # comment 2
-
- title
-
-
- footer # comment3
-EOF
- ));
- }
-
- public function testFoldedStringBlockWithComments()
- {
- $this->assertEquals(array(array('content' => <<<'EOT'
-# comment 1
-header
-
- # comment 2
-
- title
-
-
-footer # comment3
-EOT
- )), Yaml::parse(<<<'EOF'
--
- content: |
- # comment 1
- header
-
- # comment 2
-
- title
-
-
- footer # comment3
-EOF
- ));
- }
-
- public function testNestedFoldedStringBlockWithComments()
- {
- $this->assertEquals(array(array(
- 'title' => 'some title',
- 'content' => <<<'EOT'
-# comment 1
-header
-
- # comment 2
-
- title
-
-
-footer # comment3
-EOT
- )), Yaml::parse(<<<'EOF'
--
- title: some title
- content: |
- # comment 1
- header
-
- # comment 2
-
- title
-
-
- footer # comment3
-EOF
- ));
- }
-
- public function testReferenceResolvingInInlineStrings()
- {
- $this->assertEquals(array(
- 'var' => 'var-value',
- 'scalar' => 'var-value',
- 'list' => array('var-value'),
- 'list_in_list' => array(array('var-value')),
- 'map_in_list' => array(array('key' => 'var-value')),
- 'embedded_mapping' => array(array('key' => 'var-value')),
- 'map' => array('key' => 'var-value'),
- 'list_in_map' => array('key' => array('var-value')),
- 'map_in_map' => array('foo' => array('bar' => 'var-value')),
- ), Yaml::parse(<<<'EOF'
-var: &var var-value
-scalar: *var
-list: [ *var ]
-list_in_list: [[ *var ]]
-map_in_list: [ { key: *var } ]
-embedded_mapping: [ key: *var ]
-map: { key: *var }
-list_in_map: { key: [*var] }
-map_in_map: { foo: { bar: *var } }
-EOF
- ));
- }
-
- public function testYamlDirective()
- {
- $yaml = <<<'EOF'
-%YAML 1.2
----
-foo: 1
-bar: 2
-EOF;
- $this->assertEquals(array('foo' => 1, 'bar' => 2), $this->parser->parse($yaml));
- }
-
- public function testFloatKeys()
- {
- $yaml = <<<'EOF'
-foo:
- 1.2: "bar"
- 1.3: "baz"
-EOF;
-
- $expected = array(
- 'foo' => array(
- '1.2' => 'bar',
- '1.3' => 'baz',
- ),
- );
-
- $this->assertEquals($expected, $this->parser->parse($yaml));
- }
-
- /**
- * @group legacy
- * @expectedDeprecation Using a colon in the unquoted mapping value "bar: baz" in line 1 is deprecated since Symfony 2.8 and will throw a ParseException in 3.0.
- * throw ParseException in Symfony 3.0
- */
- public function testColonInMappingValueException()
- {
- $yaml = <<<'EOF'
-foo: bar: baz
-EOF;
-
- $this->parser->parse($yaml);
- }
-
- public function testColonInMappingValueExceptionNotTriggeredByColonInComment()
- {
- $yaml = <<<'EOT'
-foo:
- bar: foobar # Note: a comment after a colon
-EOT;
-
- $this->assertSame(array('foo' => array('bar' => 'foobar')), $this->parser->parse($yaml));
- }
-
- /**
- * @dataProvider getCommentLikeStringInScalarBlockData
- */
- public function testCommentLikeStringsAreNotStrippedInBlockScalars($yaml, $expectedParserResult)
- {
- $this->assertSame($expectedParserResult, $this->parser->parse($yaml));
- }
-
- public function getCommentLikeStringInScalarBlockData()
- {
- $tests = array();
-
- $yaml = <<<'EOT'
-pages:
- -
- title: some title
- content: |
- # comment 1
- header
-
- # comment 2
-
- title
-
-
- footer # comment3
-EOT;
- $expected = array(
- 'pages' => array(
- array(
- 'title' => 'some title',
- 'content' => <<<'EOT'
-# comment 1
-header
-
- # comment 2
-
- title
-
-
-footer # comment3
-EOT
- ,
- ),
- ),
- );
- $tests[] = array($yaml, $expected);
-
- $yaml = <<<'EOT'
-test: |
- foo
- # bar
- baz
-collection:
- - one: |
- foo
- # bar
- baz
- - two: |
- foo
- # bar
- baz
-EOT;
- $expected = array(
- 'test' => <<<'EOT'
-foo
-# bar
-baz
-
-EOT
- ,
- 'collection' => array(
- array(
- 'one' => <<<'EOT'
-foo
-# bar
-baz
-
-EOT
- ,
- ),
- array(
- 'two' => <<<'EOT'
-foo
-# bar
-baz
-EOT
- ,
- ),
- ),
- );
- $tests[] = array($yaml, $expected);
-
- $yaml = <<<'EOT'
-foo:
- bar:
- scalar-block: >
- line1
- line2>
- baz:
-# comment
- foobar: ~
-EOT;
- $expected = array(
- 'foo' => array(
- 'bar' => array(
- 'scalar-block' => "line1 line2>\n",
- ),
- 'baz' => array(
- 'foobar' => null,
- ),
- ),
- );
- $tests[] = array($yaml, $expected);
-
- $yaml = <<<'EOT'
-a:
- b: hello
-# c: |
-# first row
-# second row
- d: hello
-EOT;
- $expected = array(
- 'a' => array(
- 'b' => 'hello',
- 'd' => 'hello',
- ),
- );
- $tests[] = array($yaml, $expected);
-
- return $tests;
- }
-
- public function testBlankLinesAreParsedAsNewLinesInFoldedBlocks()
- {
- $yaml = <<<'EOT'
-test: >
- A heading
-
-
- - a list
- - may be a good example
-
-EOT;
-
- $this->assertSame(
- array(
- 'test' => <<<'EOT'
-A heading
- - a list
- may be a good example
-EOT
- ,
- ),
- $this->parser->parse($yaml)
- );
- }
-
- public function testAdditionallyIndentedLinesAreParsedAsNewLinesInFoldedBlocks()
- {
- $yaml = <<<'EOT'
-test: >
- A heading
-
-
- - a list
- - may be a good example
-
-EOT;
-
- $this->assertSame(
- array(
- 'test' => <<<'EOT'
-A heading
-
- - a list
- - may be a good example
-
-EOT
- ,
- ),
- $this->parser->parse($yaml)
- );
- }
-
- /**
- * @param $lineNumber
- * @param $yaml
- * @dataProvider parserThrowsExceptionWithCorrectLineNumberProvider
- */
- public function testParserThrowsExceptionWithCorrectLineNumber($lineNumber, $yaml)
- {
- if (method_exists($this, 'expectException')) {
- $this->expectException('\Symfony\Component\Yaml\Exception\ParseException');
- $this->expectExceptionMessage(sprintf('Unexpected characters near "," at line %d (near "bar: "123",").', $lineNumber));
- } else {
- $this->setExpectedException('\Symfony\Component\Yaml\Exception\ParseException', sprintf('Unexpected characters near "," at line %d (near "bar: "123",").', $lineNumber));
- }
-
- $this->parser->parse($yaml);
- }
-
- public function parserThrowsExceptionWithCorrectLineNumberProvider()
- {
- return array(
- array(
- 4,
- <<<'YAML'
-foo:
- -
- # bar
- bar: "123",
-YAML
- ),
- array(
- 5,
- <<<'YAML'
-foo:
- -
- # bar
- # bar
- bar: "123",
-YAML
- ),
- array(
- 8,
- <<<'YAML'
-foo:
- -
- # foobar
- baz: 123
-bar:
- -
- # bar
- bar: "123",
-YAML
- ),
- array(
- 10,
- <<<'YAML'
-foo:
- -
- # foobar
- # foobar
- baz: 123
-bar:
- -
- # bar
- # bar
- bar: "123",
-YAML
- ),
- );
- }
-
- public function testCanParseVeryLongValue()
- {
- $longStringWithSpaces = str_repeat('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ', 20000);
- $trickyVal = array('x' => $longStringWithSpaces);
-
- $yamlString = Yaml::dump($trickyVal);
- $arrayFromYaml = $this->parser->parse($yamlString);
-
- $this->assertEquals($trickyVal, $arrayFromYaml);
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- * @expectedExceptionMessage Reference "foo" does not exist at line 2
- */
- public function testParserCleansUpReferencesBetweenRuns()
- {
- $yaml = <<parser->parse($yaml);
-
- $yaml = <<parser->parse($yaml);
- }
-
- public function testParseReferencesOnMergeKeys()
- {
- $yaml = << array(
- 'a' => 'foo',
- 'b' => 'bar',
- 'c' => 'baz',
- ),
- 'mergekeyderef' => array(
- 'd' => 'quux',
- 'b' => 'bar',
- 'c' => 'baz',
- ),
- );
-
- $this->assertSame($expected, $this->parser->parse($yaml));
- }
-
- /**
- * @expectedException \Symfony\Component\Yaml\Exception\ParseException
- * @expectedExceptionMessage Reference "foo" does not exist
- */
- public function testEvalRefException()
- {
- $yaml = <<