diff --git a/vendor/phpoffice/phpspreadsheet/LICENSE b/vendor/phpoffice/phpspreadsheet/LICENSE
new file mode 100644
index 0000000..3ec5723
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2019 PhpSpreadsheet Authors
+
+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/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMax.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMax.php
new file mode 100644
index 0000000..9c5c730
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Database/DMax.php
@@ -0,0 +1,46 @@
+format('m');
+ $oYear = (int) $PHPDateObject->format('Y');
+
+ $adjustmentMonthsString = (string) $adjustmentMonths;
+ if ($adjustmentMonths > 0) {
+ $adjustmentMonthsString = '+' . $adjustmentMonths;
+ }
+ if ($adjustmentMonths != 0) {
+ $PHPDateObject->modify($adjustmentMonthsString . ' months');
+ }
+ $nMonth = (int) $PHPDateObject->format('m');
+ $nYear = (int) $PHPDateObject->format('Y');
+
+ $monthDiff = ($nMonth - $oMonth) + (($nYear - $oYear) * 12);
+ if ($monthDiff != $adjustmentMonths) {
+ $adjustDays = (int) $PHPDateObject->format('d');
+ $adjustDaysString = '-' . $adjustDays . ' days';
+ $PHPDateObject->modify($adjustDaysString);
+ }
+
+ return $PHPDateObject;
+ }
+
+ /**
+ * Help reduce perceived complexity of some tests.
+ *
+ * @param mixed $value
+ * @param mixed $altValue
+ */
+ public static function replaceIfEmpty(&$value, $altValue): void
+ {
+ $value = $value ?: $altValue;
+ }
+
+ /**
+ * Adjust year in ambiguous situations.
+ */
+ public static function adjustYear(string $testVal1, string $testVal2, string &$testVal3): void
+ {
+ if (!is_numeric($testVal1) || $testVal1 < 31) {
+ if (!is_numeric($testVal2) || $testVal2 < 12) {
+ if (is_numeric($testVal3) && $testVal3 < 12) {
+ $testVal3 += 2000;
+ }
+ }
+ }
+ }
+
+ /**
+ * Return result in one of three formats.
+ *
+ * @return mixed
+ */
+ public static function returnIn3FormatsArray(array $dateArray, bool $noFrac = false)
+ {
+ $retType = Functions::getReturnDateType();
+ if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) {
+ return new DateTime(
+ $dateArray['year']
+ . '-' . $dateArray['month']
+ . '-' . $dateArray['day']
+ . ' ' . $dateArray['hour']
+ . ':' . $dateArray['minute']
+ . ':' . $dateArray['second']
+ );
+ }
+ $excelDateValue =
+ SharedDateHelper::formattedPHPToExcel(
+ $dateArray['year'],
+ $dateArray['month'],
+ $dateArray['day'],
+ $dateArray['hour'],
+ $dateArray['minute'],
+ $dateArray['second']
+ );
+ if ($retType === Functions::RETURNDATE_EXCEL) {
+ return $noFrac ? floor($excelDateValue) : (float) $excelDateValue;
+ }
+ // RETURNDATE_UNIX_TIMESTAMP)
+
+ return (int) SharedDateHelper::excelToTimestamp($excelDateValue);
+ }
+
+ /**
+ * Return result in one of three formats.
+ *
+ * @return mixed
+ */
+ public static function returnIn3FormatsFloat(float $excelDateValue)
+ {
+ $retType = Functions::getReturnDateType();
+ if ($retType === Functions::RETURNDATE_EXCEL) {
+ return $excelDateValue;
+ }
+ if ($retType === Functions::RETURNDATE_UNIX_TIMESTAMP) {
+ return (int) SharedDateHelper::excelToTimestamp($excelDateValue);
+ }
+ // RETURNDATE_PHP_DATETIME_OBJECT
+
+ return SharedDateHelper::excelToDateTimeObject($excelDateValue);
+ }
+
+ /**
+ * Return result in one of three formats.
+ *
+ * @return mixed
+ */
+ public static function returnIn3FormatsObject(DateTime $PHPDateObject)
+ {
+ $retType = Functions::getReturnDateType();
+ if ($retType === Functions::RETURNDATE_PHP_DATETIME_OBJECT) {
+ return $PHPDateObject;
+ }
+ if ($retType === Functions::RETURNDATE_EXCEL) {
+ return (float) SharedDateHelper::PHPToExcel($PHPDateObject);
+ }
+ // RETURNDATE_UNIX_TIMESTAMP
+ $stamp = SharedDateHelper::PHPToExcel($PHPDateObject);
+ $stamp = is_bool($stamp) ? ((int) $stamp) : $stamp;
+
+ return (int) SharedDateHelper::excelToTimestamp($stamp);
+ }
+
+ private static function baseDate(): int
+ {
+ if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
+ return 0;
+ }
+ if (SharedDateHelper::getExcelCalendar() === SharedDateHelper::CALENDAR_MAC_1904) {
+ return 0;
+ }
+
+ return 1;
+ }
+
+ /**
+ * Many functions accept null/false/true argument treated as 0/0/1.
+ *
+ * @param mixed $number
+ */
+ public static function nullFalseTrueToNumber(&$number, bool $allowBool = true): void
+ {
+ $number = Functions::flattenSingleValue($number);
+ $nullVal = self::baseDate();
+ if ($number === null) {
+ $number = $nullVal;
+ } elseif ($allowBool && is_bool($number)) {
+ $number = $nullVal + (int) $number;
+ }
+ }
+
+ /**
+ * Many functions accept null argument treated as 0.
+ *
+ * @param mixed $number
+ *
+ * @return float|int
+ */
+ public static function validateNumericNull($number)
+ {
+ $number = Functions::flattenSingleValue($number);
+ if ($number === null) {
+ return 0;
+ }
+ if (is_int($number)) {
+ return $number;
+ }
+ if (is_numeric($number)) {
+ return (float) $number;
+ }
+
+ throw new Exception(Functions::VALUE());
+ }
+
+ /**
+ * Many functions accept null/false/true argument treated as 0/0/1.
+ *
+ * @param mixed $number
+ *
+ * @return float
+ */
+ public static function validateNotNegative($number)
+ {
+ if (!is_numeric($number)) {
+ throw new Exception(Functions::VALUE());
+ }
+ if ($number >= 0) {
+ return (float) $number;
+ }
+
+ throw new Exception(Functions::NAN());
+ }
+
+ public static function silly1900(DateTime $PHPDateObject, string $mod = '-1 day'): void
+ {
+ $isoDate = $PHPDateObject->format('c');
+ if ($isoDate < '1900-03-01') {
+ $PHPDateObject->modify($mod);
+ }
+ }
+
+ public static function dateParse(string $string): array
+ {
+ return self::forceArray(date_parse($string));
+ }
+
+ public static function dateParseSucceeded(array $dateArray): bool
+ {
+ return $dateArray['error_count'] === 0;
+ }
+
+ /**
+ * Despite documentation, date_parse probably never returns false.
+ * Just in case, this routine helps guarantee it.
+ *
+ * @param array|false $dateArray
+ */
+ private static function forceArray($dateArray): array
+ {
+ return is_array($dateArray) ? $dateArray : ['error_count' => 1];
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php
new file mode 100644
index 0000000..c72d006
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/Month.php
@@ -0,0 +1,101 @@
+getMessage();
+ }
+ $adjustmentMonths = floor($adjustmentMonths);
+
+ // Execute function
+ $PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths);
+
+ return Helpers::returnIn3FormatsObject($PHPDateObject);
+ }
+
+ /**
+ * EOMONTH.
+ *
+ * Returns the date value for the last day of the month that is the indicated number of months
+ * before or after start_date.
+ * Use EOMONTH to calculate maturity dates or due dates that fall on the last day of the month.
+ *
+ * Excel Function:
+ * EOMONTH(dateValue,adjustmentMonths)
+ *
+ * @param mixed $dateValue Excel date serial value (float), PHP date timestamp (integer),
+ * PHP DateTime object, or a standard date string
+ * Or can be an array of date values
+ * @param array|int $adjustmentMonths The number of months before or after start_date.
+ * A positive value for months yields a future date;
+ * a negative value yields a past date.
+ * Or can be an array of adjustment values
+ *
+ * @return array|mixed Excel date/time serial value, PHP date/time serial value or PHP date/time object,
+ * depending on the value of the ReturnDateType flag
+ * If an array of values is passed as the argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function lastDay($dateValue, $adjustmentMonths)
+ {
+ if (is_array($dateValue) || is_array($adjustmentMonths)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $dateValue, $adjustmentMonths);
+ }
+
+ try {
+ $dateValue = Helpers::getDateValue($dateValue, false);
+ $adjustmentMonths = Helpers::validateNumericNull($adjustmentMonths);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+ $adjustmentMonths = floor($adjustmentMonths);
+
+ // Execute function
+ $PHPDateObject = Helpers::adjustDateByMonths($dateValue, $adjustmentMonths + 1);
+ $adjustDays = (int) $PHPDateObject->format('d');
+ $adjustDaysString = '-' . $adjustDays . ' days';
+ $PHPDateObject->modify($adjustDaysString);
+
+ return Helpers::returnIn3FormatsObject($PHPDateObject);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php
new file mode 100644
index 0000000..3b8942b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/DateTimeExcel/NetworkDays.php
@@ -0,0 +1,119 @@
+getMessage();
+ }
+
+ // Execute function
+ $startDow = self::calcStartDow($startDate);
+ $endDow = self::calcEndDow($endDate);
+ $wholeWeekDays = (int) floor(($endDate - $startDate) / 7) * 5;
+ $partWeekDays = self::calcPartWeekDays($startDow, $endDow);
+
+ // Test any extra holiday parameters
+ $holidayCountedArray = [];
+ foreach ($holidayArray as $holidayDate) {
+ if (($holidayDate >= $startDate) && ($holidayDate <= $endDate)) {
+ if ((Week::day($holidayDate, 2) < 6) && (!in_array($holidayDate, $holidayCountedArray))) {
+ --$partWeekDays;
+ $holidayCountedArray[] = $holidayDate;
+ }
+ }
+ }
+
+ return self::applySign($wholeWeekDays + $partWeekDays, $sDate, $eDate);
+ }
+
+ private static function calcStartDow(float $startDate): int
+ {
+ $startDow = 6 - (int) Week::day($startDate, 2);
+ if ($startDow < 0) {
+ $startDow = 5;
+ }
+
+ return $startDow;
+ }
+
+ private static function calcEndDow(float $endDate): int
+ {
+ $endDow = (int) Week::day($endDate, 2);
+ if ($endDow >= 6) {
+ $endDow = 0;
+ }
+
+ return $endDow;
+ }
+
+ private static function calcPartWeekDays(int $startDow, int $endDow): int
+ {
+ $partWeekDays = $endDow + $startDow;
+ if ($partWeekDays > 5) {
+ $partWeekDays -= 5;
+ }
+
+ return $partWeekDays;
+ }
+
+ private static function applySign(int $result, float $sDate, float $eDate): int
+ {
+ return ($sDate > $eDate) ? -$result : $result;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php
new file mode 100644
index 0000000..7f4657c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engine/Logger.php
@@ -0,0 +1,140 @@
+cellStack = $stack;
+ }
+
+ /**
+ * Enable/Disable Calculation engine logging.
+ *
+ * @param bool $writeDebugLog
+ */
+ public function setWriteDebugLog($writeDebugLog): void
+ {
+ $this->writeDebugLog = $writeDebugLog;
+ }
+
+ /**
+ * Return whether calculation engine logging is enabled or disabled.
+ *
+ * @return bool
+ */
+ public function getWriteDebugLog()
+ {
+ return $this->writeDebugLog;
+ }
+
+ /**
+ * Enable/Disable echoing of debug log information.
+ *
+ * @param bool $echoDebugLog
+ */
+ public function setEchoDebugLog($echoDebugLog): void
+ {
+ $this->echoDebugLog = $echoDebugLog;
+ }
+
+ /**
+ * Return whether echoing of debug log information is enabled or disabled.
+ *
+ * @return bool
+ */
+ public function getEchoDebugLog()
+ {
+ return $this->echoDebugLog;
+ }
+
+ /**
+ * Write an entry to the calculation engine debug log.
+ */
+ public function writeDebugLog(...$args): void
+ {
+ // Only write the debug log if logging is enabled
+ if ($this->writeDebugLog) {
+ $message = implode('', $args);
+ $cellReference = implode(' -> ', $this->cellStack->showStack());
+ if ($this->echoDebugLog) {
+ echo $cellReference,
+ ($this->cellStack->count() > 0 ? ' => ' : ''),
+ $message,
+ PHP_EOL;
+ }
+ $this->debugLog[] = $cellReference .
+ ($this->cellStack->count() > 0 ? ' => ' : '') .
+ $message;
+ }
+ }
+
+ /**
+ * Write a series of entries to the calculation engine debug log.
+ *
+ * @param string[] $args
+ */
+ public function mergeDebugLog(array $args): void
+ {
+ if ($this->writeDebugLog) {
+ foreach ($args as $entry) {
+ $this->writeDebugLog($entry);
+ }
+ }
+ }
+
+ /**
+ * Clear the calculation engine debug log.
+ */
+ public function clearLog(): void
+ {
+ $this->debugLog = [];
+ }
+
+ /**
+ * Return the calculation engine debug log.
+ *
+ * @return string[]
+ */
+ public function getLog()
+ {
+ return $this->debugLog;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php
new file mode 100644
index 0000000..d70b32d
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering.php
@@ -0,0 +1,1446 @@
+ $complex->getReal(),
+ 'imaginary' => $complex->getImaginary(),
+ 'suffix' => $complex->getSuffix(),
+ ];
+ }
+
+ /**
+ * BESSELI.
+ *
+ * Returns the modified Bessel function In(x), which is equivalent to the Bessel function evaluated
+ * for purely imaginary arguments
+ *
+ * Excel Function:
+ * BESSELI(x,ord)
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the BESSELI() method in the Engineering\BesselI class instead
+ *
+ * @param float $x The value at which to evaluate the function.
+ * If x is nonnumeric, BESSELI returns the #VALUE! error value.
+ * @param int $ord The order of the Bessel function.
+ * If ord is not an integer, it is truncated.
+ * If $ord is nonnumeric, BESSELI returns the #VALUE! error value.
+ * If $ord < 0, BESSELI returns the #NUM! error value.
+ *
+ * @return array|float|string Result, or a string containing an error
+ */
+ public static function BESSELI($x, $ord)
+ {
+ return Engineering\BesselI::BESSELI($x, $ord);
+ }
+
+ /**
+ * BESSELJ.
+ *
+ * Returns the Bessel function
+ *
+ * Excel Function:
+ * BESSELJ(x,ord)
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the BESSELJ() method in the Engineering\BesselJ class instead
+ *
+ * @param float $x The value at which to evaluate the function.
+ * If x is nonnumeric, BESSELJ returns the #VALUE! error value.
+ * @param int $ord The order of the Bessel function. If n is not an integer, it is truncated.
+ * If $ord is nonnumeric, BESSELJ returns the #VALUE! error value.
+ * If $ord < 0, BESSELJ returns the #NUM! error value.
+ *
+ * @return array|float|string Result, or a string containing an error
+ */
+ public static function BESSELJ($x, $ord)
+ {
+ return Engineering\BesselJ::BESSELJ($x, $ord);
+ }
+
+ /**
+ * BESSELK.
+ *
+ * Returns the modified Bessel function Kn(x), which is equivalent to the Bessel functions evaluated
+ * for purely imaginary arguments.
+ *
+ * Excel Function:
+ * BESSELK(x,ord)
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the BESSELK() method in the Engineering\BesselK class instead
+ *
+ * @param float $x The value at which to evaluate the function.
+ * If x is nonnumeric, BESSELK returns the #VALUE! error value.
+ * @param int $ord The order of the Bessel function. If n is not an integer, it is truncated.
+ * If $ord is nonnumeric, BESSELK returns the #VALUE! error value.
+ * If $ord < 0, BESSELK returns the #NUM! error value.
+ *
+ * @return array|float|string Result, or a string containing an error
+ */
+ public static function BESSELK($x, $ord)
+ {
+ return Engineering\BesselK::BESSELK($x, $ord);
+ }
+
+ /**
+ * BESSELY.
+ *
+ * Returns the Bessel function, which is also called the Weber function or the Neumann function.
+ *
+ * Excel Function:
+ * BESSELY(x,ord)
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the BESSELY() method in the Engineering\BesselY class instead
+ *
+ * @param float $x The value at which to evaluate the function.
+ * If x is nonnumeric, BESSELY returns the #VALUE! error value.
+ * @param int $ord The order of the Bessel function. If n is not an integer, it is truncated.
+ * If $ord is nonnumeric, BESSELY returns the #VALUE! error value.
+ * If $ord < 0, BESSELY returns the #NUM! error value.
+ *
+ * @return array|float|string Result, or a string containing an error
+ */
+ public static function BESSELY($x, $ord)
+ {
+ return Engineering\BesselY::BESSELY($x, $ord);
+ }
+
+ /**
+ * BINTODEC.
+ *
+ * Return a binary value as decimal.
+ *
+ * Excel Function:
+ * BIN2DEC(x)
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the toDecimal() method in the Engineering\ConvertBinary class instead
+ *
+ * @param mixed $x The binary number (as a string) that you want to convert. The number
+ * cannot contain more than 10 characters (10 bits). The most significant
+ * bit of number is the sign bit. The remaining 9 bits are magnitude bits.
+ * Negative numbers are represented using two's-complement notation.
+ * If number is not a valid binary number, or if number contains more than
+ * 10 characters (10 bits), BIN2DEC returns the #NUM! error value.
+ *
+ * @return array|string
+ */
+ public static function BINTODEC($x)
+ {
+ return Engineering\ConvertBinary::toDecimal($x);
+ }
+
+ /**
+ * BINTOHEX.
+ *
+ * Return a binary value as hex.
+ *
+ * Excel Function:
+ * BIN2HEX(x[,places])
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the toHex() method in the Engineering\ConvertBinary class instead
+ *
+ * @param mixed $x The binary number (as a string) that you want to convert. The number
+ * cannot contain more than 10 characters (10 bits). The most significant
+ * bit of number is the sign bit. The remaining 9 bits are magnitude bits.
+ * Negative numbers are represented using two's-complement notation.
+ * If number is not a valid binary number, or if number contains more than
+ * 10 characters (10 bits), BIN2HEX returns the #NUM! error value.
+ * @param mixed $places The number of characters to use. If places is omitted, BIN2HEX uses the
+ * minimum number of characters necessary. Places is useful for padding the
+ * return value with leading 0s (zeros).
+ * If places is not an integer, it is truncated.
+ * If places is nonnumeric, BIN2HEX returns the #VALUE! error value.
+ * If places is negative, BIN2HEX returns the #NUM! error value.
+ *
+ * @return array|string
+ */
+ public static function BINTOHEX($x, $places = null)
+ {
+ return Engineering\ConvertBinary::toHex($x, $places);
+ }
+
+ /**
+ * BINTOOCT.
+ *
+ * Return a binary value as octal.
+ *
+ * Excel Function:
+ * BIN2OCT(x[,places])
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the toOctal() method in the Engineering\ConvertBinary class instead
+ *
+ * @param mixed $x The binary number (as a string) that you want to convert. The number
+ * cannot contain more than 10 characters (10 bits). The most significant
+ * bit of number is the sign bit. The remaining 9 bits are magnitude bits.
+ * Negative numbers are represented using two's-complement notation.
+ * If number is not a valid binary number, or if number contains more than
+ * 10 characters (10 bits), BIN2OCT returns the #NUM! error value.
+ * @param mixed $places The number of characters to use. If places is omitted, BIN2OCT uses the
+ * minimum number of characters necessary. Places is useful for padding the
+ * return value with leading 0s (zeros).
+ * If places is not an integer, it is truncated.
+ * If places is nonnumeric, BIN2OCT returns the #VALUE! error value.
+ * If places is negative, BIN2OCT returns the #NUM! error value.
+ *
+ * @return array|string
+ */
+ public static function BINTOOCT($x, $places = null)
+ {
+ return Engineering\ConvertBinary::toOctal($x, $places);
+ }
+
+ /**
+ * DECTOBIN.
+ *
+ * Return a decimal value as binary.
+ *
+ * Excel Function:
+ * DEC2BIN(x[,places])
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the toBinary() method in the Engineering\ConvertDecimal class instead
+ *
+ * @param mixed $x The decimal integer you want to convert. If number is negative,
+ * valid place values are ignored and DEC2BIN returns a 10-character
+ * (10-bit) binary number in which the most significant bit is the sign
+ * bit. The remaining 9 bits are magnitude bits. Negative numbers are
+ * represented using two's-complement notation.
+ * If number < -512 or if number > 511, DEC2BIN returns the #NUM! error
+ * value.
+ * If number is nonnumeric, DEC2BIN returns the #VALUE! error value.
+ * If DEC2BIN requires more than places characters, it returns the #NUM!
+ * error value.
+ * @param mixed $places The number of characters to use. If places is omitted, DEC2BIN uses
+ * the minimum number of characters necessary. Places is useful for
+ * padding the return value with leading 0s (zeros).
+ * If places is not an integer, it is truncated.
+ * If places is nonnumeric, DEC2BIN returns the #VALUE! error value.
+ * If places is zero or negative, DEC2BIN returns the #NUM! error value.
+ *
+ * @return array|string
+ */
+ public static function DECTOBIN($x, $places = null)
+ {
+ return Engineering\ConvertDecimal::toBinary($x, $places);
+ }
+
+ /**
+ * DECTOHEX.
+ *
+ * Return a decimal value as hex.
+ *
+ * Excel Function:
+ * DEC2HEX(x[,places])
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the toHex() method in the Engineering\ConvertDecimal class instead
+ *
+ * @param mixed $x The decimal integer you want to convert. If number is negative,
+ * places is ignored and DEC2HEX returns a 10-character (40-bit)
+ * hexadecimal number in which the most significant bit is the sign
+ * bit. The remaining 39 bits are magnitude bits. Negative numbers
+ * are represented using two's-complement notation.
+ * If number < -549,755,813,888 or if number > 549,755,813,887,
+ * DEC2HEX returns the #NUM! error value.
+ * If number is nonnumeric, DEC2HEX returns the #VALUE! error value.
+ * If DEC2HEX requires more than places characters, it returns the
+ * #NUM! error value.
+ * @param mixed $places The number of characters to use. If places is omitted, DEC2HEX uses
+ * the minimum number of characters necessary. Places is useful for
+ * padding the return value with leading 0s (zeros).
+ * If places is not an integer, it is truncated.
+ * If places is nonnumeric, DEC2HEX returns the #VALUE! error value.
+ * If places is zero or negative, DEC2HEX returns the #NUM! error value.
+ *
+ * @return array|string
+ */
+ public static function DECTOHEX($x, $places = null)
+ {
+ return Engineering\ConvertDecimal::toHex($x, $places);
+ }
+
+ /**
+ * DECTOOCT.
+ *
+ * Return an decimal value as octal.
+ *
+ * Excel Function:
+ * DEC2OCT(x[,places])
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the toOctal() method in the Engineering\ConvertDecimal class instead
+ *
+ * @param mixed $x The decimal integer you want to convert. If number is negative,
+ * places is ignored and DEC2OCT returns a 10-character (30-bit)
+ * octal number in which the most significant bit is the sign bit.
+ * The remaining 29 bits are magnitude bits. Negative numbers are
+ * represented using two's-complement notation.
+ * If number < -536,870,912 or if number > 536,870,911, DEC2OCT
+ * returns the #NUM! error value.
+ * If number is nonnumeric, DEC2OCT returns the #VALUE! error value.
+ * If DEC2OCT requires more than places characters, it returns the
+ * #NUM! error value.
+ * @param mixed $places The number of characters to use. If places is omitted, DEC2OCT uses
+ * the minimum number of characters necessary. Places is useful for
+ * padding the return value with leading 0s (zeros).
+ * If places is not an integer, it is truncated.
+ * If places is nonnumeric, DEC2OCT returns the #VALUE! error value.
+ * If places is zero or negative, DEC2OCT returns the #NUM! error value.
+ *
+ * @return array|string
+ */
+ public static function DECTOOCT($x, $places = null)
+ {
+ return Engineering\ConvertDecimal::toOctal($x, $places);
+ }
+
+ /**
+ * HEXTOBIN.
+ *
+ * Return a hex value as binary.
+ *
+ * Excel Function:
+ * HEX2BIN(x[,places])
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the toBinary() method in the Engineering\ConvertHex class instead
+ *
+ * @param mixed $x the hexadecimal number (as a string) that you want to convert.
+ * Number cannot contain more than 10 characters.
+ * The most significant bit of number is the sign bit (40th bit from the right).
+ * The remaining 9 bits are magnitude bits.
+ * Negative numbers are represented using two's-complement notation.
+ * If number is negative, HEX2BIN ignores places and returns a 10-character binary number.
+ * If number is negative, it cannot be less than FFFFFFFE00,
+ * and if number is positive, it cannot be greater than 1FF.
+ * If number is not a valid hexadecimal number, HEX2BIN returns the #NUM! error value.
+ * If HEX2BIN requires more than places characters, it returns the #NUM! error value.
+ * @param mixed $places The number of characters to use. If places is omitted,
+ * HEX2BIN uses the minimum number of characters necessary. Places
+ * is useful for padding the return value with leading 0s (zeros).
+ * If places is not an integer, it is truncated.
+ * If places is nonnumeric, HEX2BIN returns the #VALUE! error value.
+ * If places is negative, HEX2BIN returns the #NUM! error value.
+ *
+ * @return array|string
+ */
+ public static function HEXTOBIN($x, $places = null)
+ {
+ return Engineering\ConvertHex::toBinary($x, $places);
+ }
+
+ /**
+ * HEXTODEC.
+ *
+ * Return a hex value as decimal.
+ *
+ * Excel Function:
+ * HEX2DEC(x)
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the toDecimal() method in the Engineering\ConvertHex class instead
+ *
+ * @param mixed $x The hexadecimal number (as a string) that you want to convert. This number cannot
+ * contain more than 10 characters (40 bits). The most significant
+ * bit of number is the sign bit. The remaining 39 bits are magnitude
+ * bits. Negative numbers are represented using two's-complement
+ * notation.
+ * If number is not a valid hexadecimal number, HEX2DEC returns the
+ * #NUM! error value.
+ *
+ * @return array|string
+ */
+ public static function HEXTODEC($x)
+ {
+ return Engineering\ConvertHex::toDecimal($x);
+ }
+
+ /**
+ * HEXTOOCT.
+ *
+ * Return a hex value as octal.
+ *
+ * Excel Function:
+ * HEX2OCT(x[,places])
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the toOctal() method in the Engineering\ConvertHex class instead
+ *
+ * @param mixed $x The hexadecimal number (as a string) that you want to convert. Number cannot
+ * contain more than 10 characters. The most significant bit of
+ * number is the sign bit. The remaining 39 bits are magnitude
+ * bits. Negative numbers are represented using two's-complement
+ * notation.
+ * If number is negative, HEX2OCT ignores places and returns a
+ * 10-character octal number.
+ * If number is negative, it cannot be less than FFE0000000, and
+ * if number is positive, it cannot be greater than 1FFFFFFF.
+ * If number is not a valid hexadecimal number, HEX2OCT returns
+ * the #NUM! error value.
+ * If HEX2OCT requires more than places characters, it returns
+ * the #NUM! error value.
+ * @param mixed $places The number of characters to use. If places is omitted, HEX2OCT
+ * uses the minimum number of characters necessary. Places is
+ * useful for padding the return value with leading 0s (zeros).
+ * If places is not an integer, it is truncated.
+ * If places is nonnumeric, HEX2OCT returns the #VALUE! error
+ * value.
+ * If places is negative, HEX2OCT returns the #NUM! error value.
+ *
+ * @return array|string
+ */
+ public static function HEXTOOCT($x, $places = null)
+ {
+ return Engineering\ConvertHex::toOctal($x, $places);
+ }
+
+ /**
+ * OCTTOBIN.
+ *
+ * Return an octal value as binary.
+ *
+ * Excel Function:
+ * OCT2BIN(x[,places])
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the toBinary() method in the Engineering\ConvertOctal class instead
+ *
+ * @param mixed $x The octal number you want to convert. Number may not
+ * contain more than 10 characters. The most significant
+ * bit of number is the sign bit. The remaining 29 bits
+ * are magnitude bits. Negative numbers are represented
+ * using two's-complement notation.
+ * If number is negative, OCT2BIN ignores places and returns
+ * a 10-character binary number.
+ * If number is negative, it cannot be less than 7777777000,
+ * and if number is positive, it cannot be greater than 777.
+ * If number is not a valid octal number, OCT2BIN returns
+ * the #NUM! error value.
+ * If OCT2BIN requires more than places characters, it
+ * returns the #NUM! error value.
+ * @param mixed $places The number of characters to use. If places is omitted,
+ * OCT2BIN uses the minimum number of characters necessary.
+ * Places is useful for padding the return value with
+ * leading 0s (zeros).
+ * If places is not an integer, it is truncated.
+ * If places is nonnumeric, OCT2BIN returns the #VALUE!
+ * error value.
+ * If places is negative, OCT2BIN returns the #NUM! error
+ * value.
+ *
+ * @return array|string
+ */
+ public static function OCTTOBIN($x, $places = null)
+ {
+ return Engineering\ConvertOctal::toBinary($x, $places);
+ }
+
+ /**
+ * OCTTODEC.
+ *
+ * Return an octal value as decimal.
+ *
+ * Excel Function:
+ * OCT2DEC(x)
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the toDecimal() method in the Engineering\ConvertOctal class instead
+ *
+ * @param mixed $x The octal number you want to convert. Number may not contain
+ * more than 10 octal characters (30 bits). The most significant
+ * bit of number is the sign bit. The remaining 29 bits are
+ * magnitude bits. Negative numbers are represented using
+ * two's-complement notation.
+ * If number is not a valid octal number, OCT2DEC returns the
+ * #NUM! error value.
+ *
+ * @return array|string
+ */
+ public static function OCTTODEC($x)
+ {
+ return Engineering\ConvertOctal::toDecimal($x);
+ }
+
+ /**
+ * OCTTOHEX.
+ *
+ * Return an octal value as hex.
+ *
+ * Excel Function:
+ * OCT2HEX(x[,places])
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the toHex() method in the Engineering\ConvertOctal class instead
+ *
+ * @param mixed $x The octal number you want to convert. Number may not contain
+ * more than 10 octal characters (30 bits). The most significant
+ * bit of number is the sign bit. The remaining 29 bits are
+ * magnitude bits. Negative numbers are represented using
+ * two's-complement notation.
+ * If number is negative, OCT2HEX ignores places and returns a
+ * 10-character hexadecimal number.
+ * If number is not a valid octal number, OCT2HEX returns the
+ * #NUM! error value.
+ * If OCT2HEX requires more than places characters, it returns
+ * the #NUM! error value.
+ * @param mixed $places The number of characters to use. If places is omitted, OCT2HEX
+ * uses the minimum number of characters necessary. Places is useful
+ * for padding the return value with leading 0s (zeros).
+ * If places is not an integer, it is truncated.
+ * If places is nonnumeric, OCT2HEX returns the #VALUE! error value.
+ * If places is negative, OCT2HEX returns the #NUM! error value.
+ *
+ * @return array|string
+ */
+ public static function OCTTOHEX($x, $places = null)
+ {
+ return Engineering\ConvertOctal::toHex($x, $places);
+ }
+
+ /**
+ * COMPLEX.
+ *
+ * Converts real and imaginary coefficients into a complex number of the form x +/- yi or x +/- yj.
+ *
+ * Excel Function:
+ * COMPLEX(realNumber,imaginary[,suffix])
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the COMPLEX() method in the Engineering\Complex class instead
+ *
+ * @param array|float $realNumber the real coefficient of the complex number
+ * @param array|float $imaginary the imaginary coefficient of the complex number
+ * @param array|string $suffix The suffix for the imaginary component of the complex number.
+ * If omitted, the suffix is assumed to be "i".
+ *
+ * @return array|string
+ */
+ public static function COMPLEX($realNumber = 0.0, $imaginary = 0.0, $suffix = 'i')
+ {
+ return Engineering\Complex::COMPLEX($realNumber, $imaginary, $suffix);
+ }
+
+ /**
+ * IMAGINARY.
+ *
+ * Returns the imaginary coefficient of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMAGINARY(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMAGINARY() method in the Engineering\Complex class instead
+ *
+ * @param string $complexNumber the complex number for which you want the imaginary
+ * coefficient
+ *
+ * @return array|float|string
+ */
+ public static function IMAGINARY($complexNumber)
+ {
+ return Engineering\Complex::IMAGINARY($complexNumber);
+ }
+
+ /**
+ * IMREAL.
+ *
+ * Returns the real coefficient of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMREAL(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMREAL() method in the Engineering\Complex class instead
+ *
+ * @param string $complexNumber the complex number for which you want the real coefficient
+ *
+ * @return array|float|string
+ */
+ public static function IMREAL($complexNumber)
+ {
+ return Engineering\Complex::IMREAL($complexNumber);
+ }
+
+ /**
+ * IMABS.
+ *
+ * Returns the absolute value (modulus) of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMABS(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMABS() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param string $complexNumber the complex number for which you want the absolute value
+ *
+ * @return array|float|string
+ */
+ public static function IMABS($complexNumber)
+ {
+ return ComplexFunctions::IMABS($complexNumber);
+ }
+
+ /**
+ * IMARGUMENT.
+ *
+ * Returns the argument theta of a complex number, i.e. the angle in radians from the real
+ * axis to the representation of the number in polar coordinates.
+ *
+ * Excel Function:
+ * IMARGUMENT(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMARGUMENT() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param array|string $complexNumber the complex number for which you want the argument theta
+ *
+ * @return array|float|string
+ */
+ public static function IMARGUMENT($complexNumber)
+ {
+ return ComplexFunctions::IMARGUMENT($complexNumber);
+ }
+
+ /**
+ * IMCONJUGATE.
+ *
+ * Returns the complex conjugate of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMCONJUGATE(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMARGUMENT() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param array|string $complexNumber the complex number for which you want the conjugate
+ *
+ * @return array|string
+ */
+ public static function IMCONJUGATE($complexNumber)
+ {
+ return ComplexFunctions::IMCONJUGATE($complexNumber);
+ }
+
+ /**
+ * IMCOS.
+ *
+ * Returns the cosine of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMCOS(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMCOS() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param array|string $complexNumber the complex number for which you want the cosine
+ *
+ * @return array|float|string
+ */
+ public static function IMCOS($complexNumber)
+ {
+ return ComplexFunctions::IMCOS($complexNumber);
+ }
+
+ /**
+ * IMCOSH.
+ *
+ * Returns the hyperbolic cosine of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMCOSH(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMCOSH() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param array|string $complexNumber the complex number for which you want the hyperbolic cosine
+ *
+ * @return array|float|string
+ */
+ public static function IMCOSH($complexNumber)
+ {
+ return ComplexFunctions::IMCOSH($complexNumber);
+ }
+
+ /**
+ * IMCOT.
+ *
+ * Returns the cotangent of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMCOT(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMCOT() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param array|string $complexNumber the complex number for which you want the cotangent
+ *
+ * @return array|float|string
+ */
+ public static function IMCOT($complexNumber)
+ {
+ return ComplexFunctions::IMCOT($complexNumber);
+ }
+
+ /**
+ * IMCSC.
+ *
+ * Returns the cosecant of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMCSC(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMCSC() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param array|string $complexNumber the complex number for which you want the cosecant
+ *
+ * @return array|float|string
+ */
+ public static function IMCSC($complexNumber)
+ {
+ return ComplexFunctions::IMCSC($complexNumber);
+ }
+
+ /**
+ * IMCSCH.
+ *
+ * Returns the hyperbolic cosecant of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMCSCH(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMCSCH() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param array|string $complexNumber the complex number for which you want the hyperbolic cosecant
+ *
+ * @return array|float|string
+ */
+ public static function IMCSCH($complexNumber)
+ {
+ return ComplexFunctions::IMCSCH($complexNumber);
+ }
+
+ /**
+ * IMSIN.
+ *
+ * Returns the sine of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMSIN(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMSIN() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param string $complexNumber the complex number for which you want the sine
+ *
+ * @return array|float|string
+ */
+ public static function IMSIN($complexNumber)
+ {
+ return ComplexFunctions::IMSIN($complexNumber);
+ }
+
+ /**
+ * IMSINH.
+ *
+ * Returns the hyperbolic sine of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMSINH(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMSINH() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param string $complexNumber the complex number for which you want the hyperbolic sine
+ *
+ * @return array|float|string
+ */
+ public static function IMSINH($complexNumber)
+ {
+ return ComplexFunctions::IMSINH($complexNumber);
+ }
+
+ /**
+ * IMSEC.
+ *
+ * Returns the secant of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMSEC(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMSEC() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param string $complexNumber the complex number for which you want the secant
+ *
+ * @return array|float|string
+ */
+ public static function IMSEC($complexNumber)
+ {
+ return ComplexFunctions::IMSEC($complexNumber);
+ }
+
+ /**
+ * IMSECH.
+ *
+ * Returns the hyperbolic secant of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMSECH(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMSECH() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param string $complexNumber the complex number for which you want the hyperbolic secant
+ *
+ * @return array|float|string
+ */
+ public static function IMSECH($complexNumber)
+ {
+ return ComplexFunctions::IMSECH($complexNumber);
+ }
+
+ /**
+ * IMTAN.
+ *
+ * Returns the tangent of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMTAN(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMTAN() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param string $complexNumber the complex number for which you want the tangent
+ *
+ * @return array|float|string
+ */
+ public static function IMTAN($complexNumber)
+ {
+ return ComplexFunctions::IMTAN($complexNumber);
+ }
+
+ /**
+ * IMSQRT.
+ *
+ * Returns the square root of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMSQRT(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMSQRT() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param string $complexNumber the complex number for which you want the square root
+ *
+ * @return array|string
+ */
+ public static function IMSQRT($complexNumber)
+ {
+ return ComplexFunctions::IMSQRT($complexNumber);
+ }
+
+ /**
+ * IMLN.
+ *
+ * Returns the natural logarithm of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMLN(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMLN() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param string $complexNumber the complex number for which you want the natural logarithm
+ *
+ * @return array|string
+ */
+ public static function IMLN($complexNumber)
+ {
+ return ComplexFunctions::IMLN($complexNumber);
+ }
+
+ /**
+ * IMLOG10.
+ *
+ * Returns the common logarithm (base 10) of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMLOG10(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMLOG10() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param string $complexNumber the complex number for which you want the common logarithm
+ *
+ * @return array|string
+ */
+ public static function IMLOG10($complexNumber)
+ {
+ return ComplexFunctions::IMLOG10($complexNumber);
+ }
+
+ /**
+ * IMLOG2.
+ *
+ * Returns the base-2 logarithm of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMLOG2(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMLOG2() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param string $complexNumber the complex number for which you want the base-2 logarithm
+ *
+ * @return array|string
+ */
+ public static function IMLOG2($complexNumber)
+ {
+ return ComplexFunctions::IMLOG2($complexNumber);
+ }
+
+ /**
+ * IMEXP.
+ *
+ * Returns the exponential of a complex number in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMEXP(complexNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMEXP() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param string $complexNumber the complex number for which you want the exponential
+ *
+ * @return array|string
+ */
+ public static function IMEXP($complexNumber)
+ {
+ return ComplexFunctions::IMEXP($complexNumber);
+ }
+
+ /**
+ * IMPOWER.
+ *
+ * Returns a complex number in x + yi or x + yj text format raised to a power.
+ *
+ * Excel Function:
+ * IMPOWER(complexNumber,realNumber)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMPOWER() method in the Engineering\ComplexFunctions class instead
+ *
+ * @param string $complexNumber the complex number you want to raise to a power
+ * @param float $realNumber the power to which you want to raise the complex number
+ *
+ * @return array|string
+ */
+ public static function IMPOWER($complexNumber, $realNumber)
+ {
+ return ComplexFunctions::IMPOWER($complexNumber, $realNumber);
+ }
+
+ /**
+ * IMDIV.
+ *
+ * Returns the quotient of two complex numbers in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMDIV(complexDividend,complexDivisor)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMDIV() method in the Engineering\ComplexOperations class instead
+ *
+ * @param string $complexDividend the complex numerator or dividend
+ * @param string $complexDivisor the complex denominator or divisor
+ *
+ * @return array|string
+ */
+ public static function IMDIV($complexDividend, $complexDivisor)
+ {
+ return ComplexOperations::IMDIV($complexDividend, $complexDivisor);
+ }
+
+ /**
+ * IMSUB.
+ *
+ * Returns the difference of two complex numbers in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMSUB(complexNumber1,complexNumber2)
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMSUB() method in the Engineering\ComplexOperations class instead
+ *
+ * @param string $complexNumber1 the complex number from which to subtract complexNumber2
+ * @param string $complexNumber2 the complex number to subtract from complexNumber1
+ *
+ * @return array|string
+ */
+ public static function IMSUB($complexNumber1, $complexNumber2)
+ {
+ return ComplexOperations::IMSUB($complexNumber1, $complexNumber2);
+ }
+
+ /**
+ * IMSUM.
+ *
+ * Returns the sum of two or more complex numbers in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMSUM(complexNumber[,complexNumber[,...]])
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMSUM() method in the Engineering\ComplexOperations class instead
+ *
+ * @param string ...$complexNumbers Series of complex numbers to add
+ *
+ * @return string
+ */
+ public static function IMSUM(...$complexNumbers)
+ {
+ return ComplexOperations::IMSUM(...$complexNumbers);
+ }
+
+ /**
+ * IMPRODUCT.
+ *
+ * Returns the product of two or more complex numbers in x + yi or x + yj text format.
+ *
+ * Excel Function:
+ * IMPRODUCT(complexNumber[,complexNumber[,...]])
+ *
+ * @Deprecated 1.18.0
+ *
+ * @see Use the IMPRODUCT() method in the Engineering\ComplexOperations class instead
+ *
+ * @param string ...$complexNumbers Series of complex numbers to multiply
+ *
+ * @return string
+ */
+ public static function IMPRODUCT(...$complexNumbers)
+ {
+ return ComplexOperations::IMPRODUCT(...$complexNumbers);
+ }
+
+ /**
+ * DELTA.
+ *
+ * Tests whether two values are equal. Returns 1 if number1 = number2; returns 0 otherwise.
+ * Use this function to filter a set of values. For example, by summing several DELTA
+ * functions you calculate the count of equal pairs. This function is also known as the
+ * Kronecker Delta function.
+ *
+ * Excel Function:
+ * DELTA(a[,b])
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the DELTA() method in the Engineering\Compare class instead
+ *
+ * @param float $a the first number
+ * @param float $b The second number. If omitted, b is assumed to be zero.
+ *
+ * @return array|int|string (string in the event of an error)
+ */
+ public static function DELTA($a, $b = 0)
+ {
+ return Engineering\Compare::DELTA($a, $b);
+ }
+
+ /**
+ * GESTEP.
+ *
+ * Excel Function:
+ * GESTEP(number[,step])
+ *
+ * Returns 1 if number >= step; returns 0 (zero) otherwise
+ * Use this function to filter a set of values. For example, by summing several GESTEP
+ * functions you calculate the count of values that exceed a threshold.
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the GESTEP() method in the Engineering\Compare class instead
+ *
+ * @param float $number the value to test against step
+ * @param float $step The threshold value. If you omit a value for step, GESTEP uses zero.
+ *
+ * @return array|int|string (string in the event of an error)
+ */
+ public static function GESTEP($number, $step = 0)
+ {
+ return Engineering\Compare::GESTEP($number, $step);
+ }
+
+ /**
+ * BITAND.
+ *
+ * Returns the bitwise AND of two integer values.
+ *
+ * Excel Function:
+ * BITAND(number1, number2)
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the BITAND() method in the Engineering\BitWise class instead
+ *
+ * @param int $number1
+ * @param int $number2
+ *
+ * @return array|int|string
+ */
+ public static function BITAND($number1, $number2)
+ {
+ return Engineering\BitWise::BITAND($number1, $number2);
+ }
+
+ /**
+ * BITOR.
+ *
+ * Returns the bitwise OR of two integer values.
+ *
+ * Excel Function:
+ * BITOR(number1, number2)
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the BITOR() method in the Engineering\BitWise class instead
+ *
+ * @param int $number1
+ * @param int $number2
+ *
+ * @return array|int|string
+ */
+ public static function BITOR($number1, $number2)
+ {
+ return Engineering\BitWise::BITOR($number1, $number2);
+ }
+
+ /**
+ * BITXOR.
+ *
+ * Returns the bitwise XOR of two integer values.
+ *
+ * Excel Function:
+ * BITXOR(number1, number2)
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the BITXOR() method in the Engineering\BitWise class instead
+ *
+ * @param int $number1
+ * @param int $number2
+ *
+ * @return array|int|string
+ */
+ public static function BITXOR($number1, $number2)
+ {
+ return Engineering\BitWise::BITXOR($number1, $number2);
+ }
+
+ /**
+ * BITLSHIFT.
+ *
+ * Returns the number value shifted left by shift_amount bits.
+ *
+ * Excel Function:
+ * BITLSHIFT(number, shift_amount)
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the BITLSHIFT() method in the Engineering\BitWise class instead
+ *
+ * @param int $number
+ * @param int $shiftAmount
+ *
+ * @return array|float|int|string
+ */
+ public static function BITLSHIFT($number, $shiftAmount)
+ {
+ return Engineering\BitWise::BITLSHIFT($number, $shiftAmount);
+ }
+
+ /**
+ * BITRSHIFT.
+ *
+ * Returns the number value shifted right by shift_amount bits.
+ *
+ * Excel Function:
+ * BITRSHIFT(number, shift_amount)
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the BITRSHIFT() method in the Engineering\BitWise class instead
+ *
+ * @param int $number
+ * @param int $shiftAmount
+ *
+ * @return array|float|int|string
+ */
+ public static function BITRSHIFT($number, $shiftAmount)
+ {
+ return Engineering\BitWise::BITRSHIFT($number, $shiftAmount);
+ }
+
+ /**
+ * ERF.
+ *
+ * Returns the error function integrated between the lower and upper bound arguments.
+ *
+ * Note: In Excel 2007 or earlier, if you input a negative value for the upper or lower bound arguments,
+ * the function would return a #NUM! error. However, in Excel 2010, the function algorithm was
+ * improved, so that it can now calculate the function for both positive and negative ranges.
+ * PhpSpreadsheet follows Excel 2010 behaviour, and accepts negative arguments.
+ *
+ * Excel Function:
+ * ERF(lower[,upper])
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the ERF() method in the Engineering\Erf class instead
+ *
+ * @param float $lower lower bound for integrating ERF
+ * @param float $upper upper bound for integrating ERF.
+ * If omitted, ERF integrates between zero and lower_limit
+ *
+ * @return array|float|string
+ */
+ public static function ERF($lower, $upper = null)
+ {
+ return Engineering\Erf::ERF($lower, $upper);
+ }
+
+ /**
+ * ERFPRECISE.
+ *
+ * Returns the error function integrated between the lower and upper bound arguments.
+ *
+ * Excel Function:
+ * ERF.PRECISE(limit)
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the ERFPRECISE() method in the Engineering\Erf class instead
+ *
+ * @param float $limit bound for integrating ERF
+ *
+ * @return array|float|string
+ */
+ public static function ERFPRECISE($limit)
+ {
+ return Engineering\Erf::ERFPRECISE($limit);
+ }
+
+ /**
+ * ERFC.
+ *
+ * Returns the complementary ERF function integrated between x and infinity
+ *
+ * Note: In Excel 2007 or earlier, if you input a negative value for the lower bound argument,
+ * the function would return a #NUM! error. However, in Excel 2010, the function algorithm was
+ * improved, so that it can now calculate the function for both positive and negative x values.
+ * PhpSpreadsheet follows Excel 2010 behaviour, and accepts nagative arguments.
+ *
+ * Excel Function:
+ * ERFC(x)
+ *
+ * @Deprecated 1.17.0
+ *
+ * @see Use the ERFC() method in the Engineering\ErfC class instead
+ *
+ * @param float $x The lower bound for integrating ERFC
+ *
+ * @return array|float|string
+ */
+ public static function ERFC($x)
+ {
+ return Engineering\ErfC::ERFC($x);
+ }
+
+ /**
+ * getConversionGroups
+ * Returns a list of the different conversion groups for UOM conversions.
+ *
+ * @Deprecated 1.16.0
+ *
+ * @see Use the getConversionCategories() method in the Engineering\ConvertUOM class instead
+ *
+ * @return array
+ */
+ public static function getConversionGroups()
+ {
+ return Engineering\ConvertUOM::getConversionCategories();
+ }
+
+ /**
+ * getConversionGroupUnits
+ * Returns an array of units of measure, for a specified conversion group, or for all groups.
+ *
+ * @Deprecated 1.16.0
+ *
+ * @see Use the getConversionCategoryUnits() method in the ConvertUOM class instead
+ *
+ * @param null|mixed $category
+ *
+ * @return array
+ */
+ public static function getConversionGroupUnits($category = null)
+ {
+ return Engineering\ConvertUOM::getConversionCategoryUnits($category);
+ }
+
+ /**
+ * getConversionGroupUnitDetails.
+ *
+ * @Deprecated 1.16.0
+ *
+ * @see Use the getConversionCategoryUnitDetails() method in the ConvertUOM class instead
+ *
+ * @param null|mixed $category
+ *
+ * @return array
+ */
+ public static function getConversionGroupUnitDetails($category = null)
+ {
+ return Engineering\ConvertUOM::getConversionCategoryUnitDetails($category);
+ }
+
+ /**
+ * getConversionMultipliers
+ * Returns an array of the Multiplier prefixes that can be used with Units of Measure in CONVERTUOM().
+ *
+ * @Deprecated 1.16.0
+ *
+ * @see Use the getConversionMultipliers() method in the ConvertUOM class instead
+ *
+ * @return mixed[]
+ */
+ public static function getConversionMultipliers()
+ {
+ return Engineering\ConvertUOM::getConversionMultipliers();
+ }
+
+ /**
+ * getBinaryConversionMultipliers.
+ *
+ * Returns an array of the additional Multiplier prefixes that can be used with Information Units of Measure
+ * in CONVERTUOM().
+ *
+ * @Deprecated 1.16.0
+ *
+ * @see Use the getBinaryConversionMultipliers() method in the ConvertUOM class instead
+ *
+ * @return mixed[]
+ */
+ public static function getBinaryConversionMultipliers()
+ {
+ return Engineering\ConvertUOM::getBinaryConversionMultipliers();
+ }
+
+ /**
+ * CONVERTUOM.
+ *
+ * Converts a number from one measurement system to another.
+ * For example, CONVERT can translate a table of distances in miles to a table of distances
+ * in kilometers.
+ *
+ * Excel Function:
+ * CONVERT(value,fromUOM,toUOM)
+ *
+ * @Deprecated 1.16.0
+ *
+ * @see Use the CONVERT() method in the ConvertUOM class instead
+ *
+ * @param float|int $value the value in fromUOM to convert
+ * @param string $fromUOM the units for value
+ * @param string $toUOM the units for the result
+ *
+ * @return array|float|string
+ */
+ public static function CONVERTUOM($value, $fromUOM, $toUOM)
+ {
+ return Engineering\ConvertUOM::CONVERT($value, $fromUOM, $toUOM);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php
new file mode 100644
index 0000000..01630af
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/EngineeringValidations.php
@@ -0,0 +1,33 @@
+ 2.2) {
+ return 1 - ErfC::ERFC($value);
+ }
+ $sum = $term = $value;
+ $xsqr = ($value * $value);
+ $j = 1;
+ do {
+ $term *= $xsqr / $j;
+ $sum -= $term / (2 * $j + 1);
+ ++$j;
+ $term *= $xsqr / $j;
+ $sum += $term / (2 * $j + 1);
+ ++$j;
+ if ($sum == 0.0) {
+ break;
+ }
+ } while (abs($term / $sum) > Functions::PRECISION);
+
+ return self::$twoSqrtPi * $sum;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php
new file mode 100644
index 0000000..da1a932
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Engineering/ErfC.php
@@ -0,0 +1,76 @@
+ Functions::PRECISION);
+
+ return self::$oneSqrtPi * exp(-$value * $value) * $q2;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php
new file mode 100644
index 0000000..87c7d22
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Exception.php
@@ -0,0 +1,26 @@
+line = $line;
+ $e->file = $file;
+
+ throw $e;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php
new file mode 100644
index 0000000..41e51d4
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/ExceptionHandler.php
@@ -0,0 +1,22 @@
+getMessage();
+ }
+
+ return self::calculateFutureValue($rate, $numberOfPeriods, $payment, $presentValue, $type);
+ }
+
+ /**
+ * PV.
+ *
+ * Returns the Present Value of a cash flow with constant payments and interest rate (annuities).
+ *
+ * @param mixed $rate Interest rate per period
+ * @param mixed $numberOfPeriods Number of periods as an integer
+ * @param mixed $payment Periodic payment (annuity)
+ * @param mixed $futureValue Future Value
+ * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
+ *
+ * @return float|string Result, or a string containing an error
+ */
+ public static function presentValue(
+ $rate,
+ $numberOfPeriods,
+ $payment = 0.0,
+ $futureValue = 0.0,
+ $type = FinancialConstants::PAYMENT_END_OF_PERIOD
+ ) {
+ $rate = Functions::flattenSingleValue($rate);
+ $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
+ $payment = ($payment === null) ? 0.0 : Functions::flattenSingleValue($payment);
+ $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
+ $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
+
+ try {
+ $rate = CashFlowValidations::validateRate($rate);
+ $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
+ $payment = CashFlowValidations::validateFloat($payment);
+ $futureValue = CashFlowValidations::validateFutureValue($futureValue);
+ $type = CashFlowValidations::validatePeriodType($type);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ // Validate parameters
+ if ($numberOfPeriods < 0) {
+ return Functions::NAN();
+ }
+
+ return self::calculatePresentValue($rate, $numberOfPeriods, $payment, $futureValue, $type);
+ }
+
+ /**
+ * NPER.
+ *
+ * Returns the number of periods for a cash flow with constant periodic payments (annuities), and interest rate.
+ *
+ * @param mixed $rate Interest rate per period
+ * @param mixed $payment Periodic payment (annuity)
+ * @param mixed $presentValue Present Value
+ * @param mixed $futureValue Future Value
+ * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
+ *
+ * @return float|string Result, or a string containing an error
+ */
+ public static function periods(
+ $rate,
+ $payment,
+ $presentValue,
+ $futureValue = 0.0,
+ $type = FinancialConstants::PAYMENT_END_OF_PERIOD
+ ) {
+ $rate = Functions::flattenSingleValue($rate);
+ $payment = Functions::flattenSingleValue($payment);
+ $presentValue = Functions::flattenSingleValue($presentValue);
+ $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
+ $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
+
+ try {
+ $rate = CashFlowValidations::validateRate($rate);
+ $payment = CashFlowValidations::validateFloat($payment);
+ $presentValue = CashFlowValidations::validatePresentValue($presentValue);
+ $futureValue = CashFlowValidations::validateFutureValue($futureValue);
+ $type = CashFlowValidations::validatePeriodType($type);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ // Validate parameters
+ if ($payment == 0.0) {
+ return Functions::NAN();
+ }
+
+ return self::calculatePeriods($rate, $payment, $presentValue, $futureValue, $type);
+ }
+
+ private static function calculateFutureValue(
+ float $rate,
+ int $numberOfPeriods,
+ float $payment,
+ float $presentValue,
+ int $type
+ ): float {
+ if ($rate !== null && $rate != 0) {
+ return -$presentValue *
+ (1 + $rate) ** $numberOfPeriods - $payment * (1 + $rate * $type) * ((1 + $rate) ** $numberOfPeriods - 1)
+ / $rate;
+ }
+
+ return -$presentValue - $payment * $numberOfPeriods;
+ }
+
+ private static function calculatePresentValue(
+ float $rate,
+ int $numberOfPeriods,
+ float $payment,
+ float $futureValue,
+ int $type
+ ): float {
+ if ($rate != 0.0) {
+ return (-$payment * (1 + $rate * $type)
+ * (((1 + $rate) ** $numberOfPeriods - 1) / $rate) - $futureValue) / (1 + $rate) ** $numberOfPeriods;
+ }
+
+ return -$futureValue - $payment * $numberOfPeriods;
+ }
+
+ /**
+ * @return float|string
+ */
+ private static function calculatePeriods(
+ float $rate,
+ float $payment,
+ float $presentValue,
+ float $futureValue,
+ int $type
+ ) {
+ if ($rate != 0.0) {
+ if ($presentValue == 0.0) {
+ return Functions::NAN();
+ }
+
+ return log(($payment * (1 + $rate * $type) / $rate - $futureValue) /
+ ($presentValue + $payment * (1 + $rate * $type) / $rate)) / log(1 + $rate);
+ }
+
+ return (-$presentValue - $futureValue) / $payment;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php
new file mode 100644
index 0000000..56d2e37
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Interest.php
@@ -0,0 +1,216 @@
+getMessage();
+ }
+
+ // Validate parameters
+ if ($period <= 0 || $period > $numberOfPeriods) {
+ return Functions::NAN();
+ }
+
+ // Calculate
+ $interestAndPrincipal = new InterestAndPrincipal(
+ $interestRate,
+ $period,
+ $numberOfPeriods,
+ $presentValue,
+ $futureValue,
+ $type
+ );
+
+ return $interestAndPrincipal->interest();
+ }
+
+ /**
+ * ISPMT.
+ *
+ * Returns the interest payment for an investment based on an interest rate and a constant payment schedule.
+ *
+ * Excel Function:
+ * =ISPMT(interest_rate, period, number_payments, pv)
+ *
+ * @param mixed $interestRate is the interest rate for the investment
+ * @param mixed $period is the period to calculate the interest rate. It must be betweeen 1 and number_payments.
+ * @param mixed $numberOfPeriods is the number of payments for the annuity
+ * @param mixed $principleRemaining is the loan amount or present value of the payments
+ */
+ public static function schedulePayment($interestRate, $period, $numberOfPeriods, $principleRemaining)
+ {
+ $interestRate = Functions::flattenSingleValue($interestRate);
+ $period = Functions::flattenSingleValue($period);
+ $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
+ $principleRemaining = Functions::flattenSingleValue($principleRemaining);
+
+ try {
+ $interestRate = CashFlowValidations::validateRate($interestRate);
+ $period = CashFlowValidations::validateInt($period);
+ $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
+ $principleRemaining = CashFlowValidations::validateFloat($principleRemaining);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ // Validate parameters
+ if ($period <= 0 || $period > $numberOfPeriods) {
+ return Functions::NAN();
+ }
+
+ // Return value
+ $returnValue = 0;
+
+ // Calculate
+ $principlePayment = ($principleRemaining * 1.0) / ($numberOfPeriods * 1.0);
+ for ($i = 0; $i <= $period; ++$i) {
+ $returnValue = $interestRate * $principleRemaining * -1;
+ $principleRemaining -= $principlePayment;
+ // principle needs to be 0 after the last payment, don't let floating point screw it up
+ if ($i == $numberOfPeriods) {
+ $returnValue = 0.0;
+ }
+ }
+
+ return $returnValue;
+ }
+
+ /**
+ * RATE.
+ *
+ * Returns the interest rate per period of an annuity.
+ * RATE is calculated by iteration and can have zero or more solutions.
+ * If the successive results of RATE do not converge to within 0.0000001 after 20 iterations,
+ * RATE returns the #NUM! error value.
+ *
+ * Excel Function:
+ * RATE(nper,pmt,pv[,fv[,type[,guess]]])
+ *
+ * @param mixed $numberOfPeriods The total number of payment periods in an annuity
+ * @param mixed $payment The payment made each period and cannot change over the life of the annuity.
+ * Typically, pmt includes principal and interest but no other fees or taxes.
+ * @param mixed $presentValue The present value - the total amount that a series of future payments is worth now
+ * @param mixed $futureValue The future value, or a cash balance you want to attain after the last payment is made.
+ * If fv is omitted, it is assumed to be 0 (the future value of a loan,
+ * for example, is 0).
+ * @param mixed $type A number 0 or 1 and indicates when payments are due:
+ * 0 or omitted At the end of the period.
+ * 1 At the beginning of the period.
+ * @param mixed $guess Your guess for what the rate will be.
+ * If you omit guess, it is assumed to be 10 percent.
+ *
+ * @return float|string
+ */
+ public static function rate(
+ $numberOfPeriods,
+ $payment,
+ $presentValue,
+ $futureValue = 0.0,
+ $type = FinancialConstants::PAYMENT_END_OF_PERIOD,
+ $guess = 0.1
+ ) {
+ $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
+ $payment = Functions::flattenSingleValue($payment);
+ $presentValue = Functions::flattenSingleValue($presentValue);
+ $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
+ $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
+ $guess = ($guess === null) ? 0.1 : Functions::flattenSingleValue($guess);
+
+ try {
+ $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
+ $payment = CashFlowValidations::validateFloat($payment);
+ $presentValue = CashFlowValidations::validatePresentValue($presentValue);
+ $futureValue = CashFlowValidations::validateFutureValue($futureValue);
+ $type = CashFlowValidations::validatePeriodType($type);
+ $guess = CashFlowValidations::validateFloat($guess);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ $rate = $guess;
+ // rest of code adapted from python/numpy
+ $close = false;
+ $iter = 0;
+ while (!$close && $iter < self::FINANCIAL_MAX_ITERATIONS) {
+ $nextdiff = self::rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type);
+ if (!is_numeric($nextdiff)) {
+ break;
+ }
+ $rate1 = $rate - $nextdiff;
+ $close = abs($rate1 - $rate) < self::FINANCIAL_PRECISION;
+ ++$iter;
+ $rate = $rate1;
+ }
+
+ return $close ? $rate : Functions::NAN();
+ }
+
+ private static function rateNextGuess($rate, $numberOfPeriods, $payment, $presentValue, $futureValue, $type)
+ {
+ if ($rate == 0.0) {
+ return Functions::NAN();
+ }
+ $tt1 = ($rate + 1) ** $numberOfPeriods;
+ $tt2 = ($rate + 1) ** ($numberOfPeriods - 1);
+ $numerator = $futureValue + $tt1 * $presentValue + $payment * ($tt1 - 1) * ($rate * $type + 1) / $rate;
+ $denominator = $numberOfPeriods * $tt2 * $presentValue - $payment * ($tt1 - 1)
+ * ($rate * $type + 1) / ($rate * $rate) + $numberOfPeriods
+ * $payment * $tt2 * ($rate * $type + 1) / $rate + $payment * ($tt1 - 1) * $type / $rate;
+ if ($denominator == 0) {
+ return Functions::NAN();
+ }
+
+ return $numerator / $denominator;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php
new file mode 100644
index 0000000..ca989e0
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/InterestAndPrincipal.php
@@ -0,0 +1,44 @@
+interest = $interest;
+ $this->principal = $principal;
+ }
+
+ public function interest(): float
+ {
+ return $this->interest;
+ }
+
+ public function principal(): float
+ {
+ return $this->principal;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php
new file mode 100644
index 0000000..e103f92
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Constant/Periodic/Payments.php
@@ -0,0 +1,115 @@
+getMessage();
+ }
+
+ // Calculate
+ if ($interestRate != 0.0) {
+ return (-$futureValue - $presentValue * (1 + $interestRate) ** $numberOfPeriods) /
+ (1 + $interestRate * $type) / (((1 + $interestRate) ** $numberOfPeriods - 1) / $interestRate);
+ }
+
+ return (-$presentValue - $futureValue) / $numberOfPeriods;
+ }
+
+ /**
+ * PPMT.
+ *
+ * Returns the interest payment for a given period for an investment based on periodic, constant payments
+ * and a constant interest rate.
+ *
+ * @param mixed $interestRate Interest rate per period
+ * @param mixed $period Period for which we want to find the interest
+ * @param mixed $numberOfPeriods Number of periods
+ * @param mixed $presentValue Present Value
+ * @param mixed $futureValue Future Value
+ * @param mixed $type Payment type: 0 = at the end of each period, 1 = at the beginning of each period
+ *
+ * @return float|string Result, or a string containing an error
+ */
+ public static function interestPayment(
+ $interestRate,
+ $period,
+ $numberOfPeriods,
+ $presentValue,
+ $futureValue = 0,
+ $type = FinancialConstants::PAYMENT_END_OF_PERIOD
+ ) {
+ $interestRate = Functions::flattenSingleValue($interestRate);
+ $period = Functions::flattenSingleValue($period);
+ $numberOfPeriods = Functions::flattenSingleValue($numberOfPeriods);
+ $presentValue = Functions::flattenSingleValue($presentValue);
+ $futureValue = ($futureValue === null) ? 0.0 : Functions::flattenSingleValue($futureValue);
+ $type = ($type === null) ? FinancialConstants::PAYMENT_END_OF_PERIOD : Functions::flattenSingleValue($type);
+
+ try {
+ $interestRate = CashFlowValidations::validateRate($interestRate);
+ $period = CashFlowValidations::validateInt($period);
+ $numberOfPeriods = CashFlowValidations::validateInt($numberOfPeriods);
+ $presentValue = CashFlowValidations::validatePresentValue($presentValue);
+ $futureValue = CashFlowValidations::validateFutureValue($futureValue);
+ $type = CashFlowValidations::validatePeriodType($type);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ // Validate parameters
+ if ($period <= 0 || $period > $numberOfPeriods) {
+ return Functions::NAN();
+ }
+
+ // Calculate
+ $interestAndPrincipal = new InterestAndPrincipal(
+ $interestRate,
+ $period,
+ $numberOfPeriods,
+ $presentValue,
+ $futureValue,
+ $type
+ );
+
+ return $interestAndPrincipal->principal();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php
new file mode 100644
index 0000000..d590e1a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/CashFlow/Variable/NonPeriodic.php
@@ -0,0 +1,258 @@
+ 1;
+ $datesIsArray = count($dates) > 1;
+ if (!$valuesIsArray && !$datesIsArray) {
+ return Functions::NA();
+ }
+ if (count($values) != count($dates)) {
+ return Functions::NAN();
+ }
+
+ $datesCount = count($dates);
+ for ($i = 0; $i < $datesCount; ++$i) {
+ try {
+ $dates[$i] = DateTimeExcel\Helpers::getDateValue($dates[$i]);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+ }
+
+ return self::xirrPart2($values);
+ }
+
+ private static function xirrPart2(array &$values): string
+ {
+ $valCount = count($values);
+ $foundpos = false;
+ $foundneg = false;
+ for ($i = 0; $i < $valCount; ++$i) {
+ $fld = $values[$i];
+ if (!is_numeric($fld)) {
+ return Functions::VALUE();
+ } elseif ($fld > 0) {
+ $foundpos = true;
+ } elseif ($fld < 0) {
+ $foundneg = true;
+ }
+ }
+ if (!self::bothNegAndPos($foundneg, $foundpos)) {
+ return Functions::NAN();
+ }
+
+ return '';
+ }
+
+ /**
+ * @return float|string
+ */
+ private static function xirrPart3(array $values, array $dates, float $x1, float $x2)
+ {
+ $f = self::xnpvOrdered($x1, $values, $dates, false);
+ if ($f < 0.0) {
+ $rtb = $x1;
+ $dx = $x2 - $x1;
+ } else {
+ $rtb = $x2;
+ $dx = $x1 - $x2;
+ }
+
+ $rslt = Functions::VALUE();
+ for ($i = 0; $i < self::FINANCIAL_MAX_ITERATIONS; ++$i) {
+ $dx *= 0.5;
+ $x_mid = $rtb + $dx;
+ $f_mid = (float) self::xnpvOrdered($x_mid, $values, $dates, false);
+ if ($f_mid <= 0.0) {
+ $rtb = $x_mid;
+ }
+ if ((abs($f_mid) < self::FINANCIAL_PRECISION) || (abs($dx) < self::FINANCIAL_PRECISION)) {
+ $rslt = $x_mid;
+
+ break;
+ }
+ }
+
+ return $rslt;
+ }
+
+ /**
+ * @param mixed $rate
+ * @param mixed $values
+ * @param mixed $dates
+ *
+ * @return float|string
+ */
+ private static function xnpvOrdered($rate, $values, $dates, bool $ordered = true)
+ {
+ $rate = Functions::flattenSingleValue($rate);
+ $values = Functions::flattenArray($values);
+ $dates = Functions::flattenArray($dates);
+ $valCount = count($values);
+
+ try {
+ self::validateXnpv($rate, $values, $dates);
+ $date0 = DateTimeExcel\Helpers::getDateValue($dates[0]);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ $xnpv = 0.0;
+ for ($i = 0; $i < $valCount; ++$i) {
+ if (!is_numeric($values[$i])) {
+ return Functions::VALUE();
+ }
+
+ try {
+ $datei = DateTimeExcel\Helpers::getDateValue($dates[$i]);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+ if ($date0 > $datei) {
+ $dif = $ordered ? Functions::NAN() : -((int) DateTimeExcel\Difference::interval($datei, $date0, 'd'));
+ } else {
+ $dif = DateTimeExcel\Difference::interval($date0, $datei, 'd');
+ }
+ if (!is_numeric($dif)) {
+ return $dif;
+ }
+ if ($rate <= -1.0) {
+ $xnpv += -abs($values[$i]) / (-1 - $rate) ** ($dif / 365);
+ } else {
+ $xnpv += $values[$i] / (1 + $rate) ** ($dif / 365);
+ }
+ }
+
+ return is_finite($xnpv) ? $xnpv : Functions::VALUE();
+ }
+
+ /**
+ * @param mixed $rate
+ */
+ private static function validateXnpv($rate, array $values, array $dates): void
+ {
+ if (!is_numeric($rate)) {
+ throw new Exception(Functions::VALUE());
+ }
+ $valCount = count($values);
+ if ($valCount != count($dates)) {
+ throw new Exception(Functions::NAN());
+ }
+ if ($valCount > 1 && ((min($values) > 0) || (max($values) < 0))) {
+ throw new Exception(Functions::NAN());
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Dollar.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Dollar.php
new file mode 100644
index 0000000..0dde0c1
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Dollar.php
@@ -0,0 +1,131 @@
+getMessage();
+ }
+
+ // Additional parameter validations
+ if ($fraction < 0) {
+ return Functions::NAN();
+ }
+ if ($fraction == 0) {
+ return Functions::DIV0();
+ }
+
+ $dollars = ($fractionalDollar < 0) ? ceil($fractionalDollar) : floor($fractionalDollar);
+ $cents = fmod($fractionalDollar, 1.0);
+ $cents /= $fraction;
+ $cents *= 10 ** ceil(log10($fraction));
+
+ return $dollars + $cents;
+ }
+
+ /**
+ * DOLLARFR.
+ *
+ * Converts a dollar price expressed as a decimal number into a dollar price
+ * expressed as a fraction.
+ * Fractional dollar numbers are sometimes used for security prices.
+ *
+ * Excel Function:
+ * DOLLARFR(decimal_dollar,fraction)
+ *
+ * @param mixed $decimalDollar Decimal Dollar
+ * Or can be an array of values
+ * @param mixed $fraction Fraction
+ * Or can be an array of values
+ *
+ * @return array|float|string
+ */
+ public static function fractional($decimalDollar = null, $fraction = 0)
+ {
+ if (is_array($decimalDollar) || is_array($fraction)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $decimalDollar, $fraction);
+ }
+
+ try {
+ $decimalDollar = FinancialValidations::validateFloat(
+ Functions::flattenSingleValue($decimalDollar) ?? 0.0
+ );
+ $fraction = FinancialValidations::validateInt(Functions::flattenSingleValue($fraction));
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ // Additional parameter validations
+ if ($fraction < 0) {
+ return Functions::NAN();
+ }
+ if ($fraction == 0) {
+ return Functions::DIV0();
+ }
+
+ $dollars = ($decimalDollar < 0.0) ? ceil($decimalDollar) : floor($decimalDollar);
+ $cents = fmod($decimalDollar, 1);
+ $cents *= $fraction;
+ $cents *= 10 ** (-ceil(log10($fraction)));
+
+ return $dollars + $cents;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/FinancialValidations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/FinancialValidations.php
new file mode 100644
index 0000000..310e005
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/FinancialValidations.php
@@ -0,0 +1,158 @@
+ 4)) {
+ throw new Exception(Functions::NAN());
+ }
+
+ return $basis;
+ }
+
+ /**
+ * @param mixed $price
+ */
+ public static function validatePrice($price): float
+ {
+ $price = self::validateFloat($price);
+ if ($price < 0.0) {
+ throw new Exception(Functions::NAN());
+ }
+
+ return $price;
+ }
+
+ /**
+ * @param mixed $parValue
+ */
+ public static function validateParValue($parValue): float
+ {
+ $parValue = self::validateFloat($parValue);
+ if ($parValue < 0.0) {
+ throw new Exception(Functions::NAN());
+ }
+
+ return $parValue;
+ }
+
+ /**
+ * @param mixed $yield
+ */
+ public static function validateYield($yield): float
+ {
+ $yield = self::validateFloat($yield);
+ if ($yield < 0.0) {
+ throw new Exception(Functions::NAN());
+ }
+
+ return $yield;
+ }
+
+ /**
+ * @param mixed $discount
+ */
+ public static function validateDiscount($discount): float
+ {
+ $discount = self::validateFloat($discount);
+ if ($discount <= 0.0) {
+ throw new Exception(Functions::NAN());
+ }
+
+ return $discount;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Helpers.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Helpers.php
new file mode 100644
index 0000000..d339b13
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/Helpers.php
@@ -0,0 +1,58 @@
+format('d') === $date->format('t');
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php
new file mode 100644
index 0000000..72df31e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Financial/InterestRate.php
@@ -0,0 +1,72 @@
+getMessage();
+ }
+
+ if ($nominalRate <= 0 || $periodsPerYear < 1) {
+ return Functions::NAN();
+ }
+
+ return ((1 + $nominalRate / $periodsPerYear) ** $periodsPerYear) - 1;
+ }
+
+ /**
+ * NOMINAL.
+ *
+ * Returns the nominal interest rate given the effective rate and the number of compounding payments per year.
+ *
+ * @param mixed $effectiveRate Effective interest rate as a float
+ * @param mixed $periodsPerYear Integer number of compounding payments per year
+ *
+ * @return float|string Result, or a string containing an error
+ */
+ public static function nominal($effectiveRate = 0, $periodsPerYear = 0)
+ {
+ $effectiveRate = Functions::flattenSingleValue($effectiveRate);
+ $periodsPerYear = Functions::flattenSingleValue($periodsPerYear);
+
+ try {
+ $effectiveRate = FinancialValidations::validateFloat($effectiveRate);
+ $periodsPerYear = FinancialValidations::validateInt($periodsPerYear);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ if ($effectiveRate <= 0 || $periodsPerYear < 1) {
+ return Functions::NAN();
+ }
+
+ // Calculate
+ return $periodsPerYear * (($effectiveRate + 1) ** (1 / $periodsPerYear) - 1);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaParser.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaParser.php
new file mode 100644
index 0000000..ddf45b2
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaParser.php
@@ -0,0 +1,629 @@
+<';
+ const OPERATORS_POSTFIX = '%';
+
+ /**
+ * Formula.
+ *
+ * @var string
+ */
+ private $formula;
+
+ /**
+ * Tokens.
+ *
+ * @var FormulaToken[]
+ */
+ private $tokens = [];
+
+ /**
+ * Create a new FormulaParser.
+ *
+ * @param string $formula Formula to parse
+ */
+ public function __construct($formula = '')
+ {
+ // Check parameters
+ if ($formula === null) {
+ throw new Exception('Invalid parameter passed: formula');
+ }
+
+ // Initialise values
+ $this->formula = trim($formula);
+ // Parse!
+ $this->parseToTokens();
+ }
+
+ /**
+ * Get Formula.
+ *
+ * @return string
+ */
+ public function getFormula()
+ {
+ return $this->formula;
+ }
+
+ /**
+ * Get Token.
+ *
+ * @param int $id Token id
+ */
+ public function getToken(int $id = 0): FormulaToken
+ {
+ if (isset($this->tokens[$id])) {
+ return $this->tokens[$id];
+ }
+
+ throw new Exception("Token with id $id does not exist.");
+ }
+
+ /**
+ * Get Token count.
+ *
+ * @return int
+ */
+ public function getTokenCount()
+ {
+ return count($this->tokens);
+ }
+
+ /**
+ * Get Tokens.
+ *
+ * @return FormulaToken[]
+ */
+ public function getTokens()
+ {
+ return $this->tokens;
+ }
+
+ /**
+ * Parse to tokens.
+ */
+ private function parseToTokens(): void
+ {
+ // No attempt is made to verify formulas; assumes formulas are derived from Excel, where
+ // they can only exist if valid; stack overflows/underflows sunk as nulls without exceptions.
+
+ // Check if the formula has a valid starting =
+ $formulaLength = strlen($this->formula);
+ if ($formulaLength < 2 || $this->formula[0] != '=') {
+ return;
+ }
+
+ // Helper variables
+ $tokens1 = $tokens2 = $stack = [];
+ $inString = $inPath = $inRange = $inError = false;
+ $token = $previousToken = $nextToken = null;
+
+ $index = 1;
+ $value = '';
+
+ $ERRORS = ['#NULL!', '#DIV/0!', '#VALUE!', '#REF!', '#NAME?', '#NUM!', '#N/A'];
+ $COMPARATORS_MULTI = ['>=', '<=', '<>'];
+
+ while ($index < $formulaLength) {
+ // state-dependent character evaluation (order is important)
+
+ // double-quoted strings
+ // embeds are doubled
+ // end marks token
+ if ($inString) {
+ if ($this->formula[$index] == self::QUOTE_DOUBLE) {
+ if ((($index + 2) <= $formulaLength) && ($this->formula[$index + 1] == self::QUOTE_DOUBLE)) {
+ $value .= self::QUOTE_DOUBLE;
+ ++$index;
+ } else {
+ $inString = false;
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND, FormulaToken::TOKEN_SUBTYPE_TEXT);
+ $value = '';
+ }
+ } else {
+ $value .= $this->formula[$index];
+ }
+ ++$index;
+
+ continue;
+ }
+
+ // single-quoted strings (links)
+ // embeds are double
+ // end does not mark a token
+ if ($inPath) {
+ if ($this->formula[$index] == self::QUOTE_SINGLE) {
+ if ((($index + 2) <= $formulaLength) && ($this->formula[$index + 1] == self::QUOTE_SINGLE)) {
+ $value .= self::QUOTE_SINGLE;
+ ++$index;
+ } else {
+ $inPath = false;
+ }
+ } else {
+ $value .= $this->formula[$index];
+ }
+ ++$index;
+
+ continue;
+ }
+
+ // bracked strings (R1C1 range index or linked workbook name)
+ // no embeds (changed to "()" by Excel)
+ // end does not mark a token
+ if ($inRange) {
+ if ($this->formula[$index] == self::BRACKET_CLOSE) {
+ $inRange = false;
+ }
+ $value .= $this->formula[$index];
+ ++$index;
+
+ continue;
+ }
+
+ // error values
+ // end marks a token, determined from absolute list of values
+ if ($inError) {
+ $value .= $this->formula[$index];
+ ++$index;
+ if (in_array($value, $ERRORS)) {
+ $inError = false;
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND, FormulaToken::TOKEN_SUBTYPE_ERROR);
+ $value = '';
+ }
+
+ continue;
+ }
+
+ // scientific notation check
+ if (strpos(self::OPERATORS_SN, $this->formula[$index]) !== false) {
+ if (strlen($value) > 1) {
+ if (preg_match('/^[1-9]{1}(\\.\\d+)?E{1}$/', $this->formula[$index]) != 0) {
+ $value .= $this->formula[$index];
+ ++$index;
+
+ continue;
+ }
+ }
+ }
+
+ // independent character evaluation (order not important)
+
+ // establish state-dependent character evaluations
+ if ($this->formula[$index] == self::QUOTE_DOUBLE) {
+ if (strlen($value) > 0) {
+ // unexpected
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
+ $value = '';
+ }
+ $inString = true;
+ ++$index;
+
+ continue;
+ }
+
+ if ($this->formula[$index] == self::QUOTE_SINGLE) {
+ if (strlen($value) > 0) {
+ // unexpected
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
+ $value = '';
+ }
+ $inPath = true;
+ ++$index;
+
+ continue;
+ }
+
+ if ($this->formula[$index] == self::BRACKET_OPEN) {
+ $inRange = true;
+ $value .= self::BRACKET_OPEN;
+ ++$index;
+
+ continue;
+ }
+
+ if ($this->formula[$index] == self::ERROR_START) {
+ if (strlen($value) > 0) {
+ // unexpected
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
+ $value = '';
+ }
+ $inError = true;
+ $value .= self::ERROR_START;
+ ++$index;
+
+ continue;
+ }
+
+ // mark start and end of arrays and array rows
+ if ($this->formula[$index] == self::BRACE_OPEN) {
+ if (strlen($value) > 0) {
+ // unexpected
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_UNKNOWN);
+ $value = '';
+ }
+
+ $tmp = new FormulaToken('ARRAY', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
+ $tokens1[] = $tmp;
+ $stack[] = clone $tmp;
+
+ $tmp = new FormulaToken('ARRAYROW', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
+ $tokens1[] = $tmp;
+ $stack[] = clone $tmp;
+
+ ++$index;
+
+ continue;
+ }
+
+ if ($this->formula[$index] == self::SEMICOLON) {
+ if (strlen($value) > 0) {
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
+ $value = '';
+ }
+
+ $tmp = array_pop($stack);
+ $tmp->setValue('');
+ $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
+ $tokens1[] = $tmp;
+
+ $tmp = new FormulaToken(',', FormulaToken::TOKEN_TYPE_ARGUMENT);
+ $tokens1[] = $tmp;
+
+ $tmp = new FormulaToken('ARRAYROW', FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
+ $tokens1[] = $tmp;
+ $stack[] = clone $tmp;
+
+ ++$index;
+
+ continue;
+ }
+
+ if ($this->formula[$index] == self::BRACE_CLOSE) {
+ if (strlen($value) > 0) {
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
+ $value = '';
+ }
+
+ $tmp = array_pop($stack);
+ $tmp->setValue('');
+ $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
+ $tokens1[] = $tmp;
+
+ $tmp = array_pop($stack);
+ $tmp->setValue('');
+ $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
+ $tokens1[] = $tmp;
+
+ ++$index;
+
+ continue;
+ }
+
+ // trim white-space
+ if ($this->formula[$index] == self::WHITESPACE) {
+ if (strlen($value) > 0) {
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
+ $value = '';
+ }
+ $tokens1[] = new FormulaToken('', FormulaToken::TOKEN_TYPE_WHITESPACE);
+ ++$index;
+ while (($this->formula[$index] == self::WHITESPACE) && ($index < $formulaLength)) {
+ ++$index;
+ }
+
+ continue;
+ }
+
+ // multi-character comparators
+ if (($index + 2) <= $formulaLength) {
+ if (in_array(substr($this->formula, $index, 2), $COMPARATORS_MULTI)) {
+ if (strlen($value) > 0) {
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
+ $value = '';
+ }
+ $tokens1[] = new FormulaToken(substr($this->formula, $index, 2), FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_LOGICAL);
+ $index += 2;
+
+ continue;
+ }
+ }
+
+ // standard infix operators
+ if (strpos(self::OPERATORS_INFIX, $this->formula[$index]) !== false) {
+ if (strlen($value) > 0) {
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
+ $value = '';
+ }
+ $tokens1[] = new FormulaToken($this->formula[$index], FormulaToken::TOKEN_TYPE_OPERATORINFIX);
+ ++$index;
+
+ continue;
+ }
+
+ // standard postfix operators (only one)
+ if (strpos(self::OPERATORS_POSTFIX, $this->formula[$index]) !== false) {
+ if (strlen($value) > 0) {
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
+ $value = '';
+ }
+ $tokens1[] = new FormulaToken($this->formula[$index], FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX);
+ ++$index;
+
+ continue;
+ }
+
+ // start subexpression or function
+ if ($this->formula[$index] == self::PAREN_OPEN) {
+ if (strlen($value) > 0) {
+ $tmp = new FormulaToken($value, FormulaToken::TOKEN_TYPE_FUNCTION, FormulaToken::TOKEN_SUBTYPE_START);
+ $tokens1[] = $tmp;
+ $stack[] = clone $tmp;
+ $value = '';
+ } else {
+ $tmp = new FormulaToken('', FormulaToken::TOKEN_TYPE_SUBEXPRESSION, FormulaToken::TOKEN_SUBTYPE_START);
+ $tokens1[] = $tmp;
+ $stack[] = clone $tmp;
+ }
+ ++$index;
+
+ continue;
+ }
+
+ // function, subexpression, or array parameters, or operand unions
+ if ($this->formula[$index] == self::COMMA) {
+ if (strlen($value) > 0) {
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
+ $value = '';
+ }
+
+ $tmp = array_pop($stack);
+ $tmp->setValue('');
+ $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
+ $stack[] = $tmp;
+
+ if ($tmp->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) {
+ $tokens1[] = new FormulaToken(',', FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_UNION);
+ } else {
+ $tokens1[] = new FormulaToken(',', FormulaToken::TOKEN_TYPE_ARGUMENT);
+ }
+ ++$index;
+
+ continue;
+ }
+
+ // stop subexpression
+ if ($this->formula[$index] == self::PAREN_CLOSE) {
+ if (strlen($value) > 0) {
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
+ $value = '';
+ }
+
+ $tmp = array_pop($stack);
+ $tmp->setValue('');
+ $tmp->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_STOP);
+ $tokens1[] = $tmp;
+
+ ++$index;
+
+ continue;
+ }
+
+ // token accumulation
+ $value .= $this->formula[$index];
+ ++$index;
+ }
+
+ // dump remaining accumulation
+ if (strlen($value) > 0) {
+ $tokens1[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERAND);
+ }
+
+ // move tokenList to new set, excluding unnecessary white-space tokens and converting necessary ones to intersections
+ $tokenCount = count($tokens1);
+ for ($i = 0; $i < $tokenCount; ++$i) {
+ $token = $tokens1[$i];
+ if (isset($tokens1[$i - 1])) {
+ $previousToken = $tokens1[$i - 1];
+ } else {
+ $previousToken = null;
+ }
+ if (isset($tokens1[$i + 1])) {
+ $nextToken = $tokens1[$i + 1];
+ } else {
+ $nextToken = null;
+ }
+
+ if ($token === null) {
+ continue;
+ }
+
+ if ($token->getTokenType() != FormulaToken::TOKEN_TYPE_WHITESPACE) {
+ $tokens2[] = $token;
+
+ continue;
+ }
+
+ if ($previousToken === null) {
+ continue;
+ }
+
+ if (
+ !(
+ (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
+ (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
+ ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
+ )
+ ) {
+ continue;
+ }
+
+ if ($nextToken === null) {
+ continue;
+ }
+
+ if (
+ !(
+ (($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START)) ||
+ (($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) && ($nextToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_START)) ||
+ ($nextToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
+ )
+ ) {
+ continue;
+ }
+
+ $tokens2[] = new FormulaToken($value, FormulaToken::TOKEN_TYPE_OPERATORINFIX, FormulaToken::TOKEN_SUBTYPE_INTERSECTION);
+ }
+
+ // move tokens to final list, switching infix "-" operators to prefix when appropriate, switching infix "+" operators
+ // to noop when appropriate, identifying operand and infix-operator subtypes, and pulling "@" from function names
+ $this->tokens = [];
+
+ $tokenCount = count($tokens2);
+ for ($i = 0; $i < $tokenCount; ++$i) {
+ $token = $tokens2[$i];
+ if (isset($tokens2[$i - 1])) {
+ $previousToken = $tokens2[$i - 1];
+ } else {
+ $previousToken = null;
+ }
+ if (isset($tokens2[$i + 1])) {
+ $nextToken = $tokens2[$i + 1];
+ } else {
+ $nextToken = null;
+ }
+
+ if ($token === null) {
+ continue;
+ }
+
+ if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == '-') {
+ if ($i == 0) {
+ $token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX);
+ } elseif (
+ (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) &&
+ ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
+ (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) &&
+ ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
+ ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX) ||
+ ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
+ ) {
+ $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH);
+ } else {
+ $token->setTokenType(FormulaToken::TOKEN_TYPE_OPERATORPREFIX);
+ }
+
+ $this->tokens[] = $token;
+
+ continue;
+ }
+
+ if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX && $token->getValue() == '+') {
+ if ($i == 0) {
+ continue;
+ } elseif (
+ (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) &&
+ ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
+ (($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_SUBEXPRESSION) &&
+ ($previousToken->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_STOP)) ||
+ ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORPOSTFIX) ||
+ ($previousToken->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND)
+ ) {
+ $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH);
+ } else {
+ continue;
+ }
+
+ $this->tokens[] = $token;
+
+ continue;
+ }
+
+ if (
+ $token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERATORINFIX &&
+ $token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING
+ ) {
+ if (strpos('<>=', substr($token->getValue(), 0, 1)) !== false) {
+ $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_LOGICAL);
+ } elseif ($token->getValue() == '&') {
+ $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_CONCATENATION);
+ } else {
+ $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_MATH);
+ }
+
+ $this->tokens[] = $token;
+
+ continue;
+ }
+
+ if (
+ $token->getTokenType() == FormulaToken::TOKEN_TYPE_OPERAND &&
+ $token->getTokenSubType() == FormulaToken::TOKEN_SUBTYPE_NOTHING
+ ) {
+ if (!is_numeric($token->getValue())) {
+ if (strtoupper($token->getValue()) == 'TRUE' || strtoupper($token->getValue()) == 'FALSE') {
+ $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_LOGICAL);
+ } else {
+ $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_RANGE);
+ }
+ } else {
+ $token->setTokenSubType(FormulaToken::TOKEN_SUBTYPE_NUMBER);
+ }
+
+ $this->tokens[] = $token;
+
+ continue;
+ }
+
+ if ($token->getTokenType() == FormulaToken::TOKEN_TYPE_FUNCTION) {
+ if (strlen($token->getValue()) > 0) {
+ if (substr($token->getValue(), 0, 1) == '@') {
+ $token->setValue(substr($token->getValue(), 1));
+ }
+ }
+ }
+
+ $this->tokens[] = $token;
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaToken.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaToken.php
new file mode 100644
index 0000000..68e5eea
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/FormulaToken.php
@@ -0,0 +1,150 @@
+value = $value;
+ $this->tokenType = $tokenType;
+ $this->tokenSubType = $tokenSubType;
+ }
+
+ /**
+ * Get Value.
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Set Value.
+ *
+ * @param string $value
+ */
+ public function setValue($value): void
+ {
+ $this->value = $value;
+ }
+
+ /**
+ * Get Token Type (represented by TOKEN_TYPE_*).
+ *
+ * @return string
+ */
+ public function getTokenType()
+ {
+ return $this->tokenType;
+ }
+
+ /**
+ * Set Token Type (represented by TOKEN_TYPE_*).
+ *
+ * @param string $value
+ */
+ public function setTokenType($value): void
+ {
+ $this->tokenType = $value;
+ }
+
+ /**
+ * Get Token SubType (represented by TOKEN_SUBTYPE_*).
+ *
+ * @return string
+ */
+ public function getTokenSubType()
+ {
+ return $this->tokenSubType;
+ }
+
+ /**
+ * Set Token SubType (represented by TOKEN_SUBTYPE_*).
+ *
+ * @param string $value
+ */
+ public function setTokenSubType($value): void
+ {
+ $this->tokenSubType = $value;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Functions.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Functions.php
new file mode 100644
index 0000000..0ccf72e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Functions.php
@@ -0,0 +1,729 @@
+ '#NULL!',
+ 'divisionbyzero' => '#DIV/0!',
+ 'value' => '#VALUE!',
+ 'reference' => '#REF!',
+ 'name' => '#NAME?',
+ 'num' => '#NUM!',
+ 'na' => '#N/A',
+ 'gettingdata' => '#GETTING_DATA',
+ ];
+
+ /**
+ * Set the Compatibility Mode.
+ *
+ * @param string $compatibilityMode Compatibility Mode
+ * Permitted values are:
+ * Functions::COMPATIBILITY_EXCEL 'Excel'
+ * Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
+ * Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
+ *
+ * @return bool (Success or Failure)
+ */
+ public static function setCompatibilityMode($compatibilityMode)
+ {
+ if (
+ ($compatibilityMode == self::COMPATIBILITY_EXCEL) ||
+ ($compatibilityMode == self::COMPATIBILITY_GNUMERIC) ||
+ ($compatibilityMode == self::COMPATIBILITY_OPENOFFICE)
+ ) {
+ self::$compatibilityMode = $compatibilityMode;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return the current Compatibility Mode.
+ *
+ * @return string Compatibility Mode
+ * Possible Return values are:
+ * Functions::COMPATIBILITY_EXCEL 'Excel'
+ * Functions::COMPATIBILITY_GNUMERIC 'Gnumeric'
+ * Functions::COMPATIBILITY_OPENOFFICE 'OpenOfficeCalc'
+ */
+ public static function getCompatibilityMode()
+ {
+ return self::$compatibilityMode;
+ }
+
+ /**
+ * Set the Return Date Format used by functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object).
+ *
+ * @param string $returnDateType Return Date Format
+ * Permitted values are:
+ * Functions::RETURNDATE_UNIX_TIMESTAMP 'P'
+ * Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O'
+ * Functions::RETURNDATE_EXCEL 'E'
+ *
+ * @return bool Success or failure
+ */
+ public static function setReturnDateType($returnDateType)
+ {
+ if (
+ ($returnDateType == self::RETURNDATE_UNIX_TIMESTAMP) ||
+ ($returnDateType == self::RETURNDATE_PHP_DATETIME_OBJECT) ||
+ ($returnDateType == self::RETURNDATE_EXCEL)
+ ) {
+ self::$returnDateType = $returnDateType;
+
+ return true;
+ }
+
+ return false;
+ }
+
+ /**
+ * Return the current Return Date Format for functions that return a date/time (Excel, PHP Serialized Numeric or PHP Object).
+ *
+ * @return string Return Date Format
+ * Possible Return values are:
+ * Functions::RETURNDATE_UNIX_TIMESTAMP 'P'
+ * Functions::RETURNDATE_PHP_DATETIME_OBJECT 'O'
+ * Functions::RETURNDATE_EXCEL 'E'
+ */
+ public static function getReturnDateType()
+ {
+ return self::$returnDateType;
+ }
+
+ /**
+ * DUMMY.
+ *
+ * @return string #Not Yet Implemented
+ */
+ public static function DUMMY()
+ {
+ return '#Not Yet Implemented';
+ }
+
+ /**
+ * DIV0.
+ *
+ * @return string #Not Yet Implemented
+ */
+ public static function DIV0()
+ {
+ return self::$errorCodes['divisionbyzero'];
+ }
+
+ /**
+ * NA.
+ *
+ * Excel Function:
+ * =NA()
+ *
+ * Returns the error value #N/A
+ * #N/A is the error value that means "no value is available."
+ *
+ * @return string #N/A!
+ */
+ public static function NA()
+ {
+ return self::$errorCodes['na'];
+ }
+
+ /**
+ * NaN.
+ *
+ * Returns the error value #NUM!
+ *
+ * @return string #NUM!
+ */
+ public static function NAN()
+ {
+ return self::$errorCodes['num'];
+ }
+
+ /**
+ * NAME.
+ *
+ * Returns the error value #NAME?
+ *
+ * @return string #NAME?
+ */
+ public static function NAME()
+ {
+ return self::$errorCodes['name'];
+ }
+
+ /**
+ * REF.
+ *
+ * Returns the error value #REF!
+ *
+ * @return string #REF!
+ */
+ public static function REF()
+ {
+ return self::$errorCodes['reference'];
+ }
+
+ /**
+ * NULL.
+ *
+ * Returns the error value #NULL!
+ *
+ * @return string #NULL!
+ */
+ public static function null()
+ {
+ return self::$errorCodes['null'];
+ }
+
+ /**
+ * VALUE.
+ *
+ * Returns the error value #VALUE!
+ *
+ * @return string #VALUE!
+ */
+ public static function VALUE()
+ {
+ return self::$errorCodes['value'];
+ }
+
+ public static function isMatrixValue($idx)
+ {
+ return (substr_count($idx, '.') <= 1) || (preg_match('/\.[A-Z]/', $idx) > 0);
+ }
+
+ public static function isValue($idx)
+ {
+ return substr_count($idx, '.') == 0;
+ }
+
+ public static function isCellValue($idx)
+ {
+ return substr_count($idx, '.') > 1;
+ }
+
+ public static function ifCondition($condition)
+ {
+ $condition = self::flattenSingleValue($condition);
+
+ if ($condition === '') {
+ return '=""';
+ }
+ if (!is_string($condition) || !in_array($condition[0], ['>', '<', '='])) {
+ $condition = self::operandSpecialHandling($condition);
+ if (is_bool($condition)) {
+ return '=' . ($condition ? 'TRUE' : 'FALSE');
+ } elseif (!is_numeric($condition)) {
+ $condition = Calculation::wrapResult(strtoupper($condition));
+ }
+
+ return str_replace('""""', '""', '=' . $condition);
+ }
+ preg_match('/(=|<[>=]?|>=?)(.*)/', $condition, $matches);
+ [, $operator, $operand] = $matches;
+
+ $operand = self::operandSpecialHandling($operand);
+ if (is_numeric(trim($operand, '"'))) {
+ $operand = trim($operand, '"');
+ } elseif (!is_numeric($operand) && $operand !== 'FALSE' && $operand !== 'TRUE') {
+ $operand = str_replace('"', '""', $operand);
+ $operand = Calculation::wrapResult(strtoupper($operand));
+ }
+
+ return str_replace('""""', '""', $operator . $operand);
+ }
+
+ private static function operandSpecialHandling($operand)
+ {
+ if (is_numeric($operand) || is_bool($operand)) {
+ return $operand;
+ } elseif (strtoupper($operand) === Calculation::getTRUE() || strtoupper($operand) === Calculation::getFALSE()) {
+ return strtoupper($operand);
+ }
+
+ // Check for percentage
+ if (preg_match('/^\-?\d*\.?\d*\s?\%$/', $operand)) {
+ return ((float) rtrim($operand, '%')) / 100;
+ }
+
+ // Check for dates
+ if (($dateValueOperand = Date::stringToExcel($operand)) !== false) {
+ return $dateValueOperand;
+ }
+
+ return $operand;
+ }
+
+ /**
+ * ERROR_TYPE.
+ *
+ * @param mixed $value Value to check
+ *
+ * @return int|string
+ */
+ public static function errorType($value = '')
+ {
+ $value = self::flattenSingleValue($value);
+
+ $i = 1;
+ foreach (self::$errorCodes as $errorCode) {
+ if ($value === $errorCode) {
+ return $i;
+ }
+ ++$i;
+ }
+
+ return self::NA();
+ }
+
+ /**
+ * IS_BLANK.
+ *
+ * @param mixed $value Value to check
+ *
+ * @return bool
+ */
+ public static function isBlank($value = null)
+ {
+ if ($value !== null) {
+ $value = self::flattenSingleValue($value);
+ }
+
+ return $value === null;
+ }
+
+ /**
+ * IS_ERR.
+ *
+ * @param mixed $value Value to check
+ *
+ * @return bool
+ */
+ public static function isErr($value = '')
+ {
+ $value = self::flattenSingleValue($value);
+
+ return self::isError($value) && (!self::isNa(($value)));
+ }
+
+ /**
+ * IS_ERROR.
+ *
+ * @param mixed $value Value to check
+ *
+ * @return bool
+ */
+ public static function isError($value = '')
+ {
+ $value = self::flattenSingleValue($value);
+
+ if (!is_string($value)) {
+ return false;
+ }
+
+ return in_array($value, self::$errorCodes);
+ }
+
+ /**
+ * IS_NA.
+ *
+ * @param mixed $value Value to check
+ *
+ * @return bool
+ */
+ public static function isNa($value = '')
+ {
+ $value = self::flattenSingleValue($value);
+
+ return $value === self::NA();
+ }
+
+ /**
+ * IS_EVEN.
+ *
+ * @param mixed $value Value to check
+ *
+ * @return bool|string
+ */
+ public static function isEven($value = null)
+ {
+ $value = self::flattenSingleValue($value);
+
+ if ($value === null) {
+ return self::NAME();
+ } elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
+ return self::VALUE();
+ }
+
+ return $value % 2 == 0;
+ }
+
+ /**
+ * IS_ODD.
+ *
+ * @param mixed $value Value to check
+ *
+ * @return bool|string
+ */
+ public static function isOdd($value = null)
+ {
+ $value = self::flattenSingleValue($value);
+
+ if ($value === null) {
+ return self::NAME();
+ } elseif ((is_bool($value)) || ((is_string($value)) && (!is_numeric($value)))) {
+ return self::VALUE();
+ }
+
+ return abs($value) % 2 == 1;
+ }
+
+ /**
+ * IS_NUMBER.
+ *
+ * @param mixed $value Value to check
+ *
+ * @return bool
+ */
+ public static function isNumber($value = null)
+ {
+ $value = self::flattenSingleValue($value);
+
+ if (is_string($value)) {
+ return false;
+ }
+
+ return is_numeric($value);
+ }
+
+ /**
+ * IS_LOGICAL.
+ *
+ * @param mixed $value Value to check
+ *
+ * @return bool
+ */
+ public static function isLogical($value = null)
+ {
+ $value = self::flattenSingleValue($value);
+
+ return is_bool($value);
+ }
+
+ /**
+ * IS_TEXT.
+ *
+ * @param mixed $value Value to check
+ *
+ * @return bool
+ */
+ public static function isText($value = null)
+ {
+ $value = self::flattenSingleValue($value);
+
+ return is_string($value) && !self::isError($value);
+ }
+
+ /**
+ * IS_NONTEXT.
+ *
+ * @param mixed $value Value to check
+ *
+ * @return bool
+ */
+ public static function isNonText($value = null)
+ {
+ return !self::isText($value);
+ }
+
+ /**
+ * N.
+ *
+ * Returns a value converted to a number
+ *
+ * @param null|mixed $value The value you want converted
+ *
+ * @return number N converts values listed in the following table
+ * If value is or refers to N returns
+ * A number That number
+ * A date The serial number of that date
+ * TRUE 1
+ * FALSE 0
+ * An error value The error value
+ * Anything else 0
+ */
+ public static function n($value = null)
+ {
+ while (is_array($value)) {
+ $value = array_shift($value);
+ }
+
+ switch (gettype($value)) {
+ case 'double':
+ case 'float':
+ case 'integer':
+ return $value;
+ case 'boolean':
+ return (int) $value;
+ case 'string':
+ // Errors
+ if ((strlen($value) > 0) && ($value[0] == '#')) {
+ return $value;
+ }
+
+ break;
+ }
+
+ return 0;
+ }
+
+ /**
+ * TYPE.
+ *
+ * Returns a number that identifies the type of a value
+ *
+ * @param null|mixed $value The value you want tested
+ *
+ * @return number N converts values listed in the following table
+ * If value is or refers to N returns
+ * A number 1
+ * Text 2
+ * Logical Value 4
+ * An error value 16
+ * Array or Matrix 64
+ */
+ public static function TYPE($value = null)
+ {
+ $value = self::flattenArrayIndexed($value);
+ if (is_array($value) && (count($value) > 1)) {
+ end($value);
+ $a = key($value);
+ // Range of cells is an error
+ if (self::isCellValue($a)) {
+ return 16;
+ // Test for Matrix
+ } elseif (self::isMatrixValue($a)) {
+ return 64;
+ }
+ } elseif (empty($value)) {
+ // Empty Cell
+ return 1;
+ }
+ $value = self::flattenSingleValue($value);
+
+ if (($value === null) || (is_float($value)) || (is_int($value))) {
+ return 1;
+ } elseif (is_bool($value)) {
+ return 4;
+ } elseif (is_array($value)) {
+ return 64;
+ } elseif (is_string($value)) {
+ // Errors
+ if ((strlen($value) > 0) && ($value[0] == '#')) {
+ return 16;
+ }
+
+ return 2;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Convert a multi-dimensional array to a simple 1-dimensional array.
+ *
+ * @param array|mixed $array Array to be flattened
+ *
+ * @return array Flattened array
+ */
+ public static function flattenArray($array)
+ {
+ if (!is_array($array)) {
+ return (array) $array;
+ }
+
+ $arrayValues = [];
+ foreach ($array as $value) {
+ if (is_array($value)) {
+ foreach ($value as $val) {
+ if (is_array($val)) {
+ foreach ($val as $v) {
+ $arrayValues[] = $v;
+ }
+ } else {
+ $arrayValues[] = $val;
+ }
+ }
+ } else {
+ $arrayValues[] = $value;
+ }
+ }
+
+ return $arrayValues;
+ }
+
+ /**
+ * @param mixed $value
+ *
+ * @return null|mixed
+ */
+ public static function scalar($value)
+ {
+ if (!is_array($value)) {
+ return $value;
+ }
+
+ do {
+ $value = array_pop($value);
+ } while (is_array($value));
+
+ return $value;
+ }
+
+ /**
+ * Convert a multi-dimensional array to a simple 1-dimensional array, but retain an element of indexing.
+ *
+ * @param array|mixed $array Array to be flattened
+ *
+ * @return array Flattened array
+ */
+ public static function flattenArrayIndexed($array)
+ {
+ if (!is_array($array)) {
+ return (array) $array;
+ }
+
+ $arrayValues = [];
+ foreach ($array as $k1 => $value) {
+ if (is_array($value)) {
+ foreach ($value as $k2 => $val) {
+ if (is_array($val)) {
+ foreach ($val as $k3 => $v) {
+ $arrayValues[$k1 . '.' . $k2 . '.' . $k3] = $v;
+ }
+ } else {
+ $arrayValues[$k1 . '.' . $k2] = $val;
+ }
+ }
+ } else {
+ $arrayValues[$k1] = $value;
+ }
+ }
+
+ return $arrayValues;
+ }
+
+ /**
+ * Convert an array to a single scalar value by extracting the first element.
+ *
+ * @param mixed $value Array or scalar value
+ *
+ * @return mixed
+ */
+ public static function flattenSingleValue($value = '')
+ {
+ while (is_array($value)) {
+ $value = array_shift($value);
+ }
+
+ return $value;
+ }
+
+ /**
+ * ISFORMULA.
+ *
+ * @param mixed $cellReference The cell to check
+ * @param ?Cell $cell The current cell (containing this formula)
+ *
+ * @return bool|string
+ */
+ public static function isFormula($cellReference = '', ?Cell $cell = null)
+ {
+ if ($cell === null) {
+ return self::REF();
+ }
+ $cellReference = self::expandDefinedName((string) $cellReference, $cell);
+ $cellReference = self::trimTrailingRange($cellReference);
+
+ preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellReference, $matches);
+
+ $cellReference = $matches[6] . $matches[7];
+ $worksheetName = str_replace("''", "'", trim($matches[2], "'"));
+
+ $worksheet = (!empty($worksheetName))
+ ? $cell->getWorksheet()->getParent()->getSheetByName($worksheetName)
+ : $cell->getWorksheet();
+
+ return $worksheet->getCell($cellReference)->isFormula();
+ }
+
+ public static function expandDefinedName(string $coordinate, Cell $cell): string
+ {
+ $worksheet = $cell->getWorksheet();
+ $spreadsheet = $worksheet->getParent();
+ // Uppercase coordinate
+ $pCoordinatex = strtoupper($coordinate);
+ // Eliminate leading equal sign
+ $pCoordinatex = Worksheet::pregReplace('/^=/', '', $pCoordinatex);
+ $defined = $spreadsheet->getDefinedName($pCoordinatex, $worksheet);
+ if ($defined !== null) {
+ $worksheet2 = $defined->getWorkSheet();
+ if (!$defined->isFormula() && $worksheet2 !== null) {
+ $coordinate = "'" . $worksheet2->getTitle() . "'!" . Worksheet::pregReplace('/^=/', '', $defined->getValue());
+ }
+ }
+
+ return $coordinate;
+ }
+
+ public static function trimTrailingRange(string $coordinate): string
+ {
+ return Worksheet::pregReplace('/:[\\w\$]+$/', '', $coordinate);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php
new file mode 100644
index 0000000..8b53464
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Internal/MakeMatrix.php
@@ -0,0 +1,11 @@
+ 0) && ($returnValue == $argCount);
+ }
+
+ /**
+ * LOGICAL_OR.
+ *
+ * Returns boolean TRUE if any argument is TRUE; returns FALSE if all arguments are FALSE.
+ *
+ * Excel Function:
+ * =OR(logical1[,logical2[, ...]])
+ *
+ * The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays
+ * or references that contain logical values.
+ *
+ * Boolean arguments are treated as True or False as appropriate
+ * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
+ * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string
+ * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
+ *
+ * @param mixed $args Data values
+ *
+ * @return bool|string the logical OR of the arguments
+ */
+ public static function logicalOr(...$args)
+ {
+ $args = Functions::flattenArray($args);
+
+ if (count($args) == 0) {
+ return Functions::VALUE();
+ }
+
+ $args = array_filter($args, function ($value) {
+ return $value !== null || (is_string($value) && trim($value) == '');
+ });
+
+ $returnValue = self::countTrueValues($args);
+ if (is_string($returnValue)) {
+ return $returnValue;
+ }
+
+ return $returnValue > 0;
+ }
+
+ /**
+ * LOGICAL_XOR.
+ *
+ * Returns the Exclusive Or logical operation for one or more supplied conditions.
+ * i.e. the Xor function returns TRUE if an odd number of the supplied conditions evaluate to TRUE,
+ * and FALSE otherwise.
+ *
+ * Excel Function:
+ * =XOR(logical1[,logical2[, ...]])
+ *
+ * The arguments must evaluate to logical values such as TRUE or FALSE, or the arguments must be arrays
+ * or references that contain logical values.
+ *
+ * Boolean arguments are treated as True or False as appropriate
+ * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
+ * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string
+ * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
+ *
+ * @param mixed $args Data values
+ *
+ * @return bool|string the logical XOR of the arguments
+ */
+ public static function logicalXor(...$args)
+ {
+ $args = Functions::flattenArray($args);
+
+ if (count($args) == 0) {
+ return Functions::VALUE();
+ }
+
+ $args = array_filter($args, function ($value) {
+ return $value !== null || (is_string($value) && trim($value) == '');
+ });
+
+ $returnValue = self::countTrueValues($args);
+ if (is_string($returnValue)) {
+ return $returnValue;
+ }
+
+ return $returnValue % 2 == 1;
+ }
+
+ /**
+ * NOT.
+ *
+ * Returns the boolean inverse of the argument.
+ *
+ * Excel Function:
+ * =NOT(logical)
+ *
+ * The argument must evaluate to a logical value such as TRUE or FALSE
+ *
+ * Boolean arguments are treated as True or False as appropriate
+ * Integer or floating point arguments are treated as True, except for 0 or 0.0 which are False
+ * If any argument value is a string, or a Null, the function returns a #VALUE! error, unless the string
+ * holds the value TRUE or FALSE, in which case it is evaluated as the corresponding boolean value
+ *
+ * @param mixed $logical A value or expression that can be evaluated to TRUE or FALSE
+ * Or can be an array of values
+ *
+ * @return array|bool|string the boolean inverse of the argument
+ * If an array of values is passed as an argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function NOT($logical = false)
+ {
+ if (is_array($logical)) {
+ return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $logical);
+ }
+
+ if (is_string($logical)) {
+ $logical = mb_strtoupper($logical, 'UTF-8');
+ if (($logical == 'TRUE') || ($logical == Calculation::getTRUE())) {
+ return false;
+ } elseif (($logical == 'FALSE') || ($logical == Calculation::getFALSE())) {
+ return true;
+ }
+
+ return Functions::VALUE();
+ }
+
+ return !$logical;
+ }
+
+ /**
+ * @return int|string
+ */
+ private static function countTrueValues(array $args)
+ {
+ $trueValueCount = 0;
+
+ foreach ($args as $arg) {
+ // Is it a boolean value?
+ if (is_bool($arg)) {
+ $trueValueCount += $arg;
+ } elseif ((is_numeric($arg)) && (!is_string($arg))) {
+ $trueValueCount += ((int) $arg != 0);
+ } elseif (is_string($arg)) {
+ $arg = mb_strtoupper($arg, 'UTF-8');
+ if (($arg == 'TRUE') || ($arg == Calculation::getTRUE())) {
+ $arg = true;
+ } elseif (($arg == 'FALSE') || ($arg == Calculation::getFALSE())) {
+ $arg = false;
+ } else {
+ return Functions::VALUE();
+ }
+ $trueValueCount += ($arg != 0);
+ }
+ }
+
+ return $trueValueCount;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef.php
new file mode 100644
index 0000000..6765048
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef.php
@@ -0,0 +1,416 @@
+getMessage();
+ }
+
+ // MATCH() is not case sensitive, so we convert lookup value to be lower cased if it's a string type.
+ if (is_string($lookupValue)) {
+ $lookupValue = StringHelper::strToLower($lookupValue);
+ }
+
+ $valueKey = null;
+ switch ($matchType) {
+ case self::MATCHTYPE_LARGEST_VALUE:
+ $valueKey = self::matchLargestValue($lookupArray, $lookupValue, $keySet);
+
+ break;
+ case self::MATCHTYPE_FIRST_VALUE:
+ $valueKey = self::matchFirstValue($lookupArray, $lookupValue);
+
+ break;
+ case self::MATCHTYPE_SMALLEST_VALUE:
+ default:
+ $valueKey = self::matchSmallestValue($lookupArray, $lookupValue);
+ }
+
+ if ($valueKey !== null) {
+ return ++$valueKey;
+ }
+
+ // Unsuccessful in finding a match, return #N/A error value
+ return Functions::NA();
+ }
+
+ private static function matchFirstValue($lookupArray, $lookupValue)
+ {
+ $wildcardLookup = ((bool) preg_match('/([\?\*])/', $lookupValue));
+ $wildcard = WildcardMatch::wildcard($lookupValue);
+
+ foreach ($lookupArray as $i => $lookupArrayValue) {
+ $typeMatch = ((gettype($lookupValue) === gettype($lookupArrayValue)) ||
+ (is_numeric($lookupValue) && is_numeric($lookupArrayValue)));
+
+ if (
+ $typeMatch && is_string($lookupValue) &&
+ $wildcardLookup && WildcardMatch::compare($lookupArrayValue, $wildcard)
+ ) {
+ // wildcard match
+ return $i;
+ } elseif ($lookupArrayValue === $lookupValue) {
+ // exact match
+ return $i;
+ }
+ }
+
+ return null;
+ }
+
+ private static function matchLargestValue($lookupArray, $lookupValue, $keySet)
+ {
+ foreach ($lookupArray as $i => $lookupArrayValue) {
+ $typeMatch = ((gettype($lookupValue) === gettype($lookupArrayValue)) ||
+ (is_numeric($lookupValue) && is_numeric($lookupArrayValue)));
+
+ if ($typeMatch && ($lookupArrayValue <= $lookupValue)) {
+ return array_search($i, $keySet);
+ }
+ }
+
+ return null;
+ }
+
+ private static function matchSmallestValue($lookupArray, $lookupValue)
+ {
+ $valueKey = null;
+
+ // The basic algorithm is:
+ // Iterate and keep the highest match until the next element is smaller than the searched value.
+ // Return immediately if perfect match is found
+ foreach ($lookupArray as $i => $lookupArrayValue) {
+ $typeMatch = gettype($lookupValue) === gettype($lookupArrayValue);
+
+ if ($lookupArrayValue === $lookupValue) {
+ // Another "special" case. If a perfect match is found,
+ // the algorithm gives up immediately
+ return $i;
+ } elseif ($typeMatch && $lookupArrayValue >= $lookupValue) {
+ $valueKey = $i;
+ } elseif ($typeMatch && $lookupArrayValue < $lookupValue) {
+ //Excel algorithm gives up immediately if the first element is smaller than the searched value
+ break;
+ }
+ }
+
+ return $valueKey;
+ }
+
+ private static function validateLookupValue($lookupValue): void
+ {
+ // Lookup_value type has to be number, text, or logical values
+ if ((!is_numeric($lookupValue)) && (!is_string($lookupValue)) && (!is_bool($lookupValue))) {
+ throw new Exception(Functions::NA());
+ }
+ }
+
+ private static function validateMatchType($matchType): void
+ {
+ // Match_type is 0, 1 or -1
+ if (
+ ($matchType !== self::MATCHTYPE_FIRST_VALUE) &&
+ ($matchType !== self::MATCHTYPE_LARGEST_VALUE) && ($matchType !== self::MATCHTYPE_SMALLEST_VALUE)
+ ) {
+ throw new Exception(Functions::NA());
+ }
+ }
+
+ private static function validateLookupArray($lookupArray): void
+ {
+ // Lookup_array should not be empty
+ $lookupArraySize = count($lookupArray);
+ if ($lookupArraySize <= 0) {
+ throw new Exception(Functions::NA());
+ }
+ }
+
+ private static function prepareLookupArray($lookupArray, $matchType)
+ {
+ // Lookup_array should contain only number, text, or logical values, or empty (null) cells
+ foreach ($lookupArray as $i => $value) {
+ // check the type of the value
+ if ((!is_numeric($value)) && (!is_string($value)) && (!is_bool($value)) && ($value !== null)) {
+ throw new Exception(Functions::NA());
+ }
+ // Convert strings to lowercase for case-insensitive testing
+ if (is_string($value)) {
+ $lookupArray[$i] = StringHelper::strToLower($value);
+ }
+ if (
+ ($value === null) &&
+ (($matchType == self::MATCHTYPE_LARGEST_VALUE) || ($matchType == self::MATCHTYPE_SMALLEST_VALUE))
+ ) {
+ unset($lookupArray[$i]);
+ }
+ }
+
+ return $lookupArray;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Formula.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Formula.php
new file mode 100644
index 0000000..f3e6c41
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Formula.php
@@ -0,0 +1,43 @@
+getWorksheet()->getParent()->getSheetByName($worksheetName)
+ : $cell->getWorksheet();
+
+ if (
+ $worksheet === null ||
+ !$worksheet->cellExists($cellReference) ||
+ !$worksheet->getCell($cellReference)->isFormula()
+ ) {
+ return Functions::NA();
+ }
+
+ return $worksheet->getCell($cellReference)->getValue();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php
new file mode 100644
index 0000000..7db2780
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/HLookup.php
@@ -0,0 +1,116 @@
+getMessage();
+ }
+
+ $f = array_keys($lookupArray);
+ $firstRow = reset($f);
+ if ((!is_array($lookupArray[$firstRow])) || ($indexNumber > count($lookupArray))) {
+ return Functions::REF();
+ }
+
+ $firstkey = $f[0] - 1;
+ $returnColumn = $firstkey + $indexNumber;
+ $firstColumn = array_shift($f);
+ $rowNumber = self::hLookupSearch($lookupValue, $lookupArray, $firstColumn, $notExactMatch);
+
+ if ($rowNumber !== null) {
+ // otherwise return the appropriate value
+ return $lookupArray[$returnColumn][Coordinate::stringFromColumnIndex($rowNumber)];
+ }
+
+ return Functions::NA();
+ }
+
+ /**
+ * @param mixed $lookupValue The value that you want to match in lookup_array
+ * @param mixed $column The column to look up
+ * @param mixed $notExactMatch determines if you are looking for an exact match based on lookup_value
+ */
+ private static function hLookupSearch($lookupValue, array $lookupArray, $column, $notExactMatch): ?int
+ {
+ $lookupLower = StringHelper::strToLower($lookupValue);
+
+ $rowNumber = null;
+ foreach ($lookupArray[$column] as $rowKey => $rowData) {
+ // break if we have passed possible keys
+ $bothNumeric = is_numeric($lookupValue) && is_numeric($rowData);
+ $bothNotNumeric = !is_numeric($lookupValue) && !is_numeric($rowData);
+ $cellDataLower = StringHelper::strToLower($rowData);
+
+ if (
+ $notExactMatch &&
+ (($bothNumeric && $rowData > $lookupValue) || ($bothNotNumeric && $cellDataLower > $lookupLower))
+ ) {
+ break;
+ }
+
+ $rowNumber = self::checkMatch(
+ $bothNumeric,
+ $bothNotNumeric,
+ $notExactMatch,
+ Coordinate::columnIndexFromString($rowKey),
+ $cellDataLower,
+ $lookupLower,
+ $rowNumber
+ );
+ }
+
+ return $rowNumber;
+ }
+
+ private static function convertLiteralArray(array $lookupArray): array
+ {
+ if (array_key_exists(0, $lookupArray)) {
+ $lookupArray2 = [];
+ $row = 0;
+ foreach ($lookupArray as $arrayVal) {
+ ++$row;
+ if (!is_array($arrayVal)) {
+ $arrayVal = [$arrayVal];
+ }
+ $arrayVal2 = [];
+ foreach ($arrayVal as $key2 => $val2) {
+ $index = Coordinate::stringFromColumnIndex($key2 + 1);
+ $arrayVal2[$index] = $val2;
+ }
+ $lookupArray2[$row] = $arrayVal2;
+ }
+ $lookupArray = $lookupArray2;
+ }
+
+ return $lookupArray;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php
new file mode 100644
index 0000000..28e8df8
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Helpers.php
@@ -0,0 +1,74 @@
+getWorkSheet();
+ $sheetTitle = ($workSheet === null) ? '' : $workSheet->getTitle();
+ $value = preg_replace('/^=/', '', $namedRange->getValue());
+ self::adjustSheetTitle($sheetTitle, $value);
+ $cellAddress1 = $sheetTitle . $value;
+ $cellAddress = $cellAddress1;
+ $a1 = self::CELLADDRESS_USE_A1;
+ }
+ if (strpos($cellAddress, ':') !== false) {
+ [$cellAddress1, $cellAddress2] = explode(':', $cellAddress);
+ }
+ $cellAddress = self::convertR1C1($cellAddress1, $cellAddress2, $a1);
+
+ return [$cellAddress1, $cellAddress2, $cellAddress];
+ }
+
+ public static function extractWorksheet(string $cellAddress, Cell $cell): array
+ {
+ $sheetName = '';
+ if (strpos($cellAddress, '!') !== false) {
+ [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
+ $sheetName = trim($sheetName, "'");
+ }
+
+ $worksheet = ($sheetName !== '')
+ ? $cell->getWorksheet()->getParent()->getSheetByName($sheetName)
+ : $cell->getWorksheet();
+
+ return [$cellAddress, $worksheet, $sheetName];
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php
new file mode 100644
index 0000000..1448c08
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Hyperlink.php
@@ -0,0 +1,40 @@
+getHyperlink()->setUrl($linkURL);
+ $cell->getHyperlink()->setTooltip($displayName);
+
+ return $displayName;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php
new file mode 100644
index 0000000..b5a3541
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Indirect.php
@@ -0,0 +1,97 @@
+getMessage();
+ }
+
+ [$cellAddress, $worksheet, $sheetName] = Helpers::extractWorksheet($cellAddress, $cell);
+
+ [$cellAddress1, $cellAddress2, $cellAddress] = Helpers::extractCellAddresses($cellAddress, $a1, $cell->getWorkSheet(), $sheetName);
+
+ if (
+ (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress1, $matches)) ||
+ (($cellAddress2 !== null) && (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $cellAddress2, $matches)))
+ ) {
+ return Functions::REF();
+ }
+
+ return self::extractRequiredCells($worksheet, $cellAddress);
+ }
+
+ /**
+ * Extract range values.
+ *
+ * @return mixed Array of values in range if range contains more than one element.
+ * Otherwise, a single value is returned.
+ */
+ private static function extractRequiredCells(?Worksheet $worksheet, string $cellAddress)
+ {
+ return Calculation::getInstance($worksheet !== null ? $worksheet->getParent() : null)
+ ->extractCellRange($cellAddress, $worksheet, false);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php
new file mode 100644
index 0000000..e21d35d
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Lookup.php
@@ -0,0 +1,105 @@
+ 1) || (!$hasResultVector && $lookupRows === 2 && $lookupColumns !== 2)) {
+ $lookupVector = LookupRef\Matrix::transpose($lookupVector);
+ $lookupRows = self::rowCount($lookupVector);
+ $lookupColumns = self::columnCount($lookupVector);
+ }
+
+ $resultVector = self::verifyResultVector($lookupVector, $resultVector);
+
+ if ($lookupRows === 2 && !$hasResultVector) {
+ $resultVector = array_pop($lookupVector);
+ $lookupVector = array_shift($lookupVector);
+ }
+
+ if ($lookupColumns !== 2) {
+ $lookupVector = self::verifyLookupValues($lookupVector, $resultVector);
+ }
+
+ return VLookup::lookup($lookupValue, $lookupVector, 2);
+ }
+
+ private static function verifyLookupValues(array $lookupVector, array $resultVector): array
+ {
+ foreach ($lookupVector as &$value) {
+ if (is_array($value)) {
+ $k = array_keys($value);
+ $key1 = $key2 = array_shift($k);
+ ++$key2;
+ $dataValue1 = $value[$key1];
+ } else {
+ $key1 = 0;
+ $key2 = 1;
+ $dataValue1 = $value;
+ }
+
+ $dataValue2 = array_shift($resultVector);
+ if (is_array($dataValue2)) {
+ $dataValue2 = array_shift($dataValue2);
+ }
+ $value = [$key1 => $dataValue1, $key2 => $dataValue2];
+ }
+ unset($value);
+
+ return $lookupVector;
+ }
+
+ private static function verifyResultVector(array $lookupVector, $resultVector)
+ {
+ if ($resultVector === null) {
+ $resultVector = $lookupVector;
+ }
+
+ $resultRows = self::rowCount($resultVector);
+ $resultColumns = self::columnCount($resultVector);
+
+ // we correctly orient our results
+ if ($resultRows === 1 && $resultColumns > 1) {
+ $resultVector = LookupRef\Matrix::transpose($resultVector);
+ }
+
+ return $resultVector;
+ }
+
+ private static function rowCount(array $dataArray): int
+ {
+ return count($dataArray);
+ }
+
+ private static function columnCount(array $dataArray): int
+ {
+ $rowKeys = array_keys($dataArray);
+ $row = array_shift($rowKeys);
+
+ return count($dataArray[$row]);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php
new file mode 100644
index 0000000..80fc99a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/LookupBase.php
@@ -0,0 +1,48 @@
+getMessage();
+ }
+
+ if (!is_array($matrix) || ($rowNum > count($matrix))) {
+ return Functions::REF();
+ }
+
+ $rowKeys = array_keys($matrix);
+ $columnKeys = @array_keys($matrix[$rowKeys[0]]);
+
+ if ($columnNum > count($columnKeys)) {
+ return Functions::REF();
+ }
+
+ if ($columnNum === 0) {
+ return self::extractRowValue($matrix, $rowKeys, $rowNum);
+ }
+
+ $columnNum = $columnKeys[--$columnNum];
+ if ($rowNum === 0) {
+ return array_map(
+ function ($value) {
+ return [$value];
+ },
+ array_column($matrix, $columnNum)
+ );
+ }
+ $rowNum = $rowKeys[--$rowNum];
+
+ return $matrix[$rowNum][$columnNum];
+ }
+
+ private static function extractRowValue(array $matrix, array $rowKeys, int $rowNum)
+ {
+ if ($rowNum === 0) {
+ return $matrix;
+ }
+
+ $rowNum = $rowKeys[--$rowNum];
+ $row = $matrix[$rowNum];
+ if (is_array($row)) {
+ return [$rowNum => $row];
+ }
+
+ return $row;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php
new file mode 100644
index 0000000..7e33f55
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/LookupRef/Offset.php
@@ -0,0 +1,136 @@
+getParent() : null)
+ ->extractCellRange($cellAddress, $worksheet, false);
+ }
+
+ private static function extractWorksheet($cellAddress, Cell $cell): array
+ {
+ $sheetName = '';
+ if (strpos($cellAddress, '!') !== false) {
+ [$sheetName, $cellAddress] = Worksheet::extractSheetTitle($cellAddress, true);
+ $sheetName = trim($sheetName, "'");
+ }
+
+ $worksheet = ($sheetName !== '')
+ ? $cell->getWorksheet()->getParent()->getSheetByName($sheetName)
+ : $cell->getWorksheet();
+
+ return [$cellAddress, $worksheet];
+ }
+
+ private static function adjustEndCellColumnForWidth(string $endCellColumn, $width, int $startCellColumn, $columns)
+ {
+ $endCellColumn = Coordinate::columnIndexFromString($endCellColumn) - 1;
+ if (($width !== null) && (!is_object($width))) {
+ $endCellColumn = $startCellColumn + (int) $width - 1;
+ } else {
+ $endCellColumn += (int) $columns;
+ }
+
+ return $endCellColumn;
+ }
+
+ private static function adustEndCellRowForHeight($height, int $startCellRow, $rows, $endCellRow): int
+ {
+ if (($height !== null) && (!is_object($height))) {
+ $endCellRow = $startCellRow + (int) $height - 1;
+ } else {
+ $endCellRow += (int) $rows;
+ }
+
+ return $endCellRow;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig.php
new file mode 100644
index 0000000..993154c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig.php
@@ -0,0 +1,1520 @@
+getMessage();
+ }
+
+ return exp($number);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php
new file mode 100644
index 0000000..b6883e2
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Factorial.php
@@ -0,0 +1,125 @@
+getMessage();
+ }
+
+ $factLoop = floor($factVal);
+ if ($factVal > $factLoop) {
+ if (Functions::getCompatibilityMode() == Functions::COMPATIBILITY_GNUMERIC) {
+ return Statistical\Distributions\Gamma::gammaValue($factVal + 1);
+ }
+ }
+
+ $factorial = 1;
+ while ($factLoop > 1) {
+ $factorial *= $factLoop--;
+ }
+
+ return $factorial;
+ }
+
+ /**
+ * FACTDOUBLE.
+ *
+ * Returns the double factorial of a number.
+ *
+ * Excel Function:
+ * FACTDOUBLE(factVal)
+ *
+ * @param array|float $factVal Factorial Value, or can be an array of numbers
+ *
+ * @return array|float|int|string Double Factorial, or a string containing an error
+ * If an array of numbers is passed as the argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function factDouble($factVal)
+ {
+ if (is_array($factVal)) {
+ return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $factVal);
+ }
+
+ try {
+ $factVal = Helpers::validateNumericNullSubstitution($factVal, 0);
+ Helpers::validateNotNegative($factVal);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ $factLoop = floor($factVal);
+ $factorial = 1;
+ while ($factLoop > 1) {
+ $factorial *= $factLoop;
+ $factLoop -= 2;
+ }
+
+ return $factorial;
+ }
+
+ /**
+ * MULTINOMIAL.
+ *
+ * Returns the ratio of the factorial of a sum of values to the product of factorials.
+ *
+ * @param mixed[] $args An array of mixed values for the Data Series
+ *
+ * @return float|string The result, or a string containing an error
+ */
+ public static function multinomial(...$args)
+ {
+ $summer = 0;
+ $divisor = 1;
+
+ try {
+ // Loop through arguments
+ foreach (Functions::flattenArray($args) as $argx) {
+ $arg = Helpers::validateNumericNullSubstitution($argx, null);
+ Helpers::validateNotNegative($arg);
+ $arg = (int) $arg;
+ $summer += $arg;
+ $divisor *= self::fact($arg);
+ }
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ $summer = self::fact($summer);
+
+ return $summer / $divisor;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php
new file mode 100644
index 0000000..16cde98
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Floor.php
@@ -0,0 +1,194 @@
+getMessage();
+ }
+
+ return self::argumentsOk((float) $number, (float) $significance);
+ }
+
+ /**
+ * FLOOR.MATH.
+ *
+ * Round a number down to the nearest integer or to the nearest multiple of significance.
+ *
+ * Excel Function:
+ * FLOOR.MATH(number[,significance[,mode]])
+ *
+ * @param mixed $number Number to round
+ * Or can be an array of values
+ * @param mixed $significance Significance
+ * Or can be an array of values
+ * @param mixed $mode direction to round negative numbers
+ * Or can be an array of values
+ *
+ * @return array|float|string Rounded Number, or a string containing an error
+ * If an array of numbers is passed as an argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function math($number, $significance = null, $mode = 0)
+ {
+ if (is_array($number) || is_array($significance) || is_array($mode)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance, $mode);
+ }
+
+ try {
+ $number = Helpers::validateNumericNullBool($number);
+ $significance = Helpers::validateNumericNullSubstitution($significance, ($number < 0) ? -1 : 1);
+ $mode = Helpers::validateNumericNullSubstitution($mode, null);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ return self::argsOk((float) $number, (float) $significance, (int) $mode);
+ }
+
+ /**
+ * FLOOR.PRECISE.
+ *
+ * Rounds number down, toward zero, to the nearest multiple of significance.
+ *
+ * Excel Function:
+ * FLOOR.PRECISE(number[,significance])
+ *
+ * @param array|float $number Number to round
+ * Or can be an array of values
+ * @param array|float $significance Significance
+ * Or can be an array of values
+ *
+ * @return array|float|string Rounded Number, or a string containing an error
+ * If an array of numbers is passed as an argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function precise($number, $significance = 1)
+ {
+ if (is_array($number) || is_array($significance)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $number, $significance);
+ }
+
+ try {
+ $number = Helpers::validateNumericNullBool($number);
+ $significance = Helpers::validateNumericNullSubstitution($significance, null);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ return self::argumentsOkPrecise((float) $number, (float) $significance);
+ }
+
+ /**
+ * Avoid Scrutinizer problems concerning complexity.
+ *
+ * @return float|string
+ */
+ private static function argumentsOkPrecise(float $number, float $significance)
+ {
+ if ($significance == 0.0) {
+ return Functions::DIV0();
+ }
+ if ($number == 0.0) {
+ return 0.0;
+ }
+
+ return floor($number / abs($significance)) * abs($significance);
+ }
+
+ /**
+ * Avoid Scrutinizer complexity problems.
+ *
+ * @return float|string Rounded Number, or a string containing an error
+ */
+ private static function argsOk(float $number, float $significance, int $mode)
+ {
+ if (!$significance) {
+ return Functions::DIV0();
+ }
+ if (!$number) {
+ return 0.0;
+ }
+ if (self::floorMathTest($number, $significance, $mode)) {
+ return ceil($number / $significance) * $significance;
+ }
+
+ return floor($number / $significance) * $significance;
+ }
+
+ /**
+ * Let FLOORMATH complexity pass Scrutinizer.
+ */
+ private static function floorMathTest(float $number, float $significance, int $mode): bool
+ {
+ return Helpers::returnSign($significance) == -1 || (Helpers::returnSign($number) == -1 && !empty($mode));
+ }
+
+ /**
+ * Avoid Scrutinizer problems concerning complexity.
+ *
+ * @return float|string
+ */
+ private static function argumentsOk(float $number, float $significance)
+ {
+ if ($significance == 0.0) {
+ return Functions::DIV0();
+ }
+ if ($number == 0.0) {
+ return 0.0;
+ }
+ if (Helpers::returnSign($significance) == 1) {
+ return floor($number / $significance) * $significance;
+ }
+ if (Helpers::returnSign($number) == -1 && Helpers::returnSign($significance) == -1) {
+ return floor($number / $significance) * $significance;
+ }
+
+ return Functions::NAN();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php
new file mode 100644
index 0000000..1dd52fa
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Gcd.php
@@ -0,0 +1,69 @@
+getMessage();
+ }
+
+ if (count($arrayArgs) <= 0) {
+ return Functions::VALUE();
+ }
+ $gcd = (int) array_pop($arrayArgs);
+ do {
+ $gcd = self::evaluateGCD($gcd, (int) array_pop($arrayArgs));
+ } while (!empty($arrayArgs));
+
+ return $gcd;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php
new file mode 100644
index 0000000..b89644a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Helpers.php
@@ -0,0 +1,129 @@
+= 0.
+ *
+ * @param float|int $number
+ */
+ public static function validateNotNegative($number, ?string $except = null): void
+ {
+ if ($number >= 0) {
+ return;
+ }
+
+ throw new Exception($except ?? Functions::NAN());
+ }
+
+ /**
+ * Confirm number > 0.
+ *
+ * @param float|int $number
+ */
+ public static function validatePositive($number, ?string $except = null): void
+ {
+ if ($number > 0) {
+ return;
+ }
+
+ throw new Exception($except ?? Functions::NAN());
+ }
+
+ /**
+ * Confirm number != 0.
+ *
+ * @param float|int $number
+ */
+ public static function validateNotZero($number): void
+ {
+ if ($number) {
+ return;
+ }
+
+ throw new Exception(Functions::DIV0());
+ }
+
+ public static function returnSign(float $number): int
+ {
+ return $number ? (($number > 0) ? 1 : -1) : 0;
+ }
+
+ public static function getEven(float $number): float
+ {
+ $significance = 2 * self::returnSign($number);
+
+ return $significance ? (ceil($number / $significance) * $significance) : 0;
+ }
+
+ /**
+ * Return NAN or value depending on argument.
+ *
+ * @param float $result Number
+ *
+ * @return float|string
+ */
+ public static function numberOrNan($result)
+ {
+ return is_nan($result) ? Functions::NAN() : $result;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php
new file mode 100644
index 0000000..f7f7764
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/IntClass.php
@@ -0,0 +1,40 @@
+getMessage();
+ }
+
+ return (int) floor($number);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php
new file mode 100644
index 0000000..46e9816
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Lcm.php
@@ -0,0 +1,110 @@
+ 1; --$i) {
+ if (($value % $i) == 0) {
+ $factorArray = array_merge($factorArray, self::factors($value / $i));
+ $factorArray = array_merge($factorArray, self::factors($i));
+ if ($i <= sqrt($value)) {
+ break;
+ }
+ }
+ }
+ if (!empty($factorArray)) {
+ rsort($factorArray);
+
+ return $factorArray;
+ }
+
+ return [(int) $value];
+ }
+
+ /**
+ * LCM.
+ *
+ * Returns the lowest common multiplier of a series of numbers
+ * The least common multiple is the smallest positive integer that is a multiple
+ * of all integer arguments number1, number2, and so on. Use LCM to add fractions
+ * with different denominators.
+ *
+ * Excel Function:
+ * LCM(number1[,number2[, ...]])
+ *
+ * @param mixed ...$args Data values
+ *
+ * @return int|string Lowest Common Multiplier, or a string containing an error
+ */
+ public static function evaluate(...$args)
+ {
+ try {
+ $arrayArgs = [];
+ $anyZeros = 0;
+ $anyNonNulls = 0;
+ foreach (Functions::flattenArray($args) as $value1) {
+ $anyNonNulls += (int) ($value1 !== null);
+ $value = Helpers::validateNumericNullSubstitution($value1, 1);
+ Helpers::validateNotNegative($value);
+ $arrayArgs[] = (int) $value;
+ $anyZeros += (int) !((bool) $value);
+ }
+ self::testNonNulls($anyNonNulls);
+ if ($anyZeros) {
+ return 0;
+ }
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ $returnValue = 1;
+ $allPoweredFactors = [];
+ // Loop through arguments
+ foreach ($arrayArgs as $value) {
+ $myFactors = self::factors(floor($value));
+ $myCountedFactors = array_count_values($myFactors);
+ $myPoweredFactors = [];
+ foreach ($myCountedFactors as $myCountedFactor => $myCountedPower) {
+ $myPoweredFactors[$myCountedFactor] = $myCountedFactor ** $myCountedPower;
+ }
+ self::processPoweredFactors($allPoweredFactors, $myPoweredFactors);
+ }
+ foreach ($allPoweredFactors as $allPoweredFactor) {
+ $returnValue *= (int) $allPoweredFactor;
+ }
+
+ return $returnValue;
+ }
+
+ private static function processPoweredFactors(array &$allPoweredFactors, array &$myPoweredFactors): void
+ {
+ foreach ($myPoweredFactors as $myPoweredValue => $myPoweredFactor) {
+ if (isset($allPoweredFactors[$myPoweredValue])) {
+ if ($allPoweredFactors[$myPoweredValue] < $myPoweredFactor) {
+ $allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
+ }
+ } else {
+ $allPoweredFactors[$myPoweredValue] = $myPoweredFactor;
+ }
+ }
+ }
+
+ private static function testNonNulls(int $anyNonNulls): void
+ {
+ if (!$anyNonNulls) {
+ throw new Exception(Functions::VALUE());
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php
new file mode 100644
index 0000000..7b07f09
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Logarithms.php
@@ -0,0 +1,102 @@
+getMessage();
+ }
+
+ return log($number, $base);
+ }
+
+ /**
+ * LOG10.
+ *
+ * Returns the result of builtin function log after validating args.
+ *
+ * @param mixed $number Should be numeric
+ * Or can be an array of values
+ *
+ * @return array|float|string Rounded number
+ * If an array of numbers is passed as an argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function base10($number)
+ {
+ if (is_array($number)) {
+ return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
+ }
+
+ try {
+ $number = Helpers::validateNumericNullBool($number);
+ Helpers::validatePositive($number);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ return log10($number);
+ }
+
+ /**
+ * LN.
+ *
+ * Returns the result of builtin function log after validating args.
+ *
+ * @param mixed $number Should be numeric
+ * Or can be an array of values
+ *
+ * @return array|float|string Rounded number
+ * If an array of numbers is passed as an argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function natural($number)
+ {
+ if (is_array($number)) {
+ return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $number);
+ }
+
+ try {
+ $number = Helpers::validateNumericNullBool($number);
+ Helpers::validatePositive($number);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ return log($number);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php
new file mode 100644
index 0000000..474f862
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/MatrixFunctions.php
@@ -0,0 +1,179 @@
+getMessage();
+ }
+
+ if ($step === 0) {
+ return array_chunk(
+ array_fill(0, $rows * $columns, $start),
+ max($columns, 1)
+ );
+ }
+
+ return array_chunk(
+ range($start, $start + (($rows * $columns - 1) * $step), $step),
+ max($columns, 1)
+ );
+ }
+
+ /**
+ * MDETERM.
+ *
+ * Returns the matrix determinant of an array.
+ *
+ * Excel Function:
+ * MDETERM(array)
+ *
+ * @param mixed $matrixValues A matrix of values
+ *
+ * @return float|string The result, or a string containing an error
+ */
+ public static function determinant($matrixValues)
+ {
+ try {
+ $matrix = self::getMatrix($matrixValues);
+
+ return $matrix->determinant();
+ } catch (MatrixException $ex) {
+ return Functions::VALUE();
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+ }
+
+ /**
+ * MINVERSE.
+ *
+ * Returns the inverse matrix for the matrix stored in an array.
+ *
+ * Excel Function:
+ * MINVERSE(array)
+ *
+ * @param mixed $matrixValues A matrix of values
+ *
+ * @return array|string The result, or a string containing an error
+ */
+ public static function inverse($matrixValues)
+ {
+ try {
+ $matrix = self::getMatrix($matrixValues);
+
+ return $matrix->inverse()->toArray();
+ } catch (MatrixDiv0Exception $e) {
+ return Functions::NAN();
+ } catch (MatrixException $e) {
+ return Functions::VALUE();
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+ }
+
+ /**
+ * MMULT.
+ *
+ * @param mixed $matrixData1 A matrix of values
+ * @param mixed $matrixData2 A matrix of values
+ *
+ * @return array|string The result, or a string containing an error
+ */
+ public static function multiply($matrixData1, $matrixData2)
+ {
+ try {
+ $matrixA = self::getMatrix($matrixData1);
+ $matrixB = self::getMatrix($matrixData2);
+
+ return $matrixA->multiply($matrixB)->toArray();
+ } catch (MatrixException $ex) {
+ return Functions::VALUE();
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+ }
+
+ /**
+ * MUnit.
+ *
+ * @param mixed $dimension Number of rows and columns
+ *
+ * @return array|string The result, or a string containing an error
+ */
+ public static function identity($dimension)
+ {
+ try {
+ $dimension = (int) Helpers::validateNumericNullBool($dimension);
+ Helpers::validatePositive($dimension, Functions::VALUE());
+ $matrix = Builder::createIdentityMatrix($dimension, 0)->toArray();
+
+ return $matrix;
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php
new file mode 100644
index 0000000..4e48151
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/MathTrig/Operations.php
@@ -0,0 +1,163 @@
+getMessage();
+ }
+
+ if (($dividend < 0.0) && ($divisor > 0.0)) {
+ return $divisor - fmod(abs($dividend), $divisor);
+ }
+ if (($dividend > 0.0) && ($divisor < 0.0)) {
+ return $divisor + fmod($dividend, abs($divisor));
+ }
+
+ return fmod($dividend, $divisor);
+ }
+
+ /**
+ * POWER.
+ *
+ * Computes x raised to the power y.
+ *
+ * @param array|float|int $x
+ * Or can be an array of values
+ * @param array|float|int $y
+ * Or can be an array of values
+ *
+ * @return array|float|int|string The result, or a string containing an error
+ * If an array of numbers is passed as an argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function power($x, $y)
+ {
+ if (is_array($x) || is_array($y)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $x, $y);
+ }
+
+ try {
+ $x = Helpers::validateNumericNullBool($x);
+ $y = Helpers::validateNumericNullBool($y);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ // Validate parameters
+ if (!$x && !$y) {
+ return Functions::NAN();
+ }
+ if (!$x && $y < 0.0) {
+ return Functions::DIV0();
+ }
+
+ // Return
+ $result = $x ** $y;
+
+ return Helpers::numberOrNan($result);
+ }
+
+ /**
+ * PRODUCT.
+ *
+ * PRODUCT returns the product of all the values and cells referenced in the argument list.
+ *
+ * Excel Function:
+ * PRODUCT(value1[,value2[, ...]])
+ *
+ * @param mixed ...$args Data values
+ *
+ * @return float|string
+ */
+ public static function product(...$args)
+ {
+ // Return value
+ $returnValue = null;
+
+ // Loop through arguments
+ foreach (Functions::flattenArray($args) as $arg) {
+ // Is it a numeric value?
+ if (is_numeric($arg)) {
+ if ($returnValue === null) {
+ $returnValue = $arg;
+ } else {
+ $returnValue *= $arg;
+ }
+ } else {
+ return Functions::VALUE();
+ }
+ }
+
+ // Return
+ if ($returnValue === null) {
+ return 0;
+ }
+
+ return $returnValue;
+ }
+
+ /**
+ * QUOTIENT.
+ *
+ * QUOTIENT function returns the integer portion of a division. Numerator is the divided number
+ * and denominator is the divisor.
+ *
+ * Excel Function:
+ * QUOTIENT(value1,value2)
+ *
+ * @param mixed $numerator Expect float|int
+ * Or can be an array of values
+ * @param mixed $denominator Expect float|int
+ * Or can be an array of values
+ *
+ * @return array|int|string
+ * If an array of numbers is passed as an argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function quotient($numerator, $denominator)
+ {
+ if (is_array($numerator) || is_array($denominator)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $numerator, $denominator);
+ }
+
+ try {
+ $numerator = Helpers::validateNumericNullSubstitution($numerator, 0);
+ $denominator = Helpers::validateNumericNullSubstitution($denominator, 0);
+ Helpers::validateNotZero($denominator);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ return (int) ($numerator / $denominator);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php
new file mode 100644
index 0000000..d4d0c7e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Averages/Mean.php
@@ -0,0 +1,131 @@
+ 0)) {
+ $aCount = Counts::COUNT($aArgs);
+ if (Minimum::min($aArgs) > 0) {
+ return $aMean ** (1 / $aCount);
+ }
+ }
+
+ return Functions::NAN();
+ }
+
+ /**
+ * HARMEAN.
+ *
+ * Returns the harmonic mean of a data set. The harmonic mean is the reciprocal of the
+ * arithmetic mean of reciprocals.
+ *
+ * Excel Function:
+ * HARMEAN(value1[,value2[, ...]])
+ *
+ * @param mixed ...$args Data values
+ *
+ * @return float|string
+ */
+ public static function harmonic(...$args)
+ {
+ // Loop through arguments
+ $aArgs = Functions::flattenArray($args);
+ if (Minimum::min($aArgs) < 0) {
+ return Functions::NAN();
+ }
+
+ $returnValue = 0;
+ $aCount = 0;
+ foreach ($aArgs as $arg) {
+ // Is it a numeric value?
+ if ((is_numeric($arg)) && (!is_string($arg))) {
+ if ($arg <= 0) {
+ return Functions::NAN();
+ }
+ $returnValue += (1 / $arg);
+ ++$aCount;
+ }
+ }
+
+ // Return
+ if ($aCount > 0) {
+ return 1 / ($returnValue / $aCount);
+ }
+
+ return Functions::NA();
+ }
+
+ /**
+ * TRIMMEAN.
+ *
+ * Returns the mean of the interior of a data set. TRIMMEAN calculates the mean
+ * taken by excluding a percentage of data points from the top and bottom tails
+ * of a data set.
+ *
+ * Excel Function:
+ * TRIMEAN(value1[,value2[, ...]], $discard)
+ *
+ * @param mixed $args Data values
+ *
+ * @return float|string
+ */
+ public static function trim(...$args)
+ {
+ $aArgs = Functions::flattenArray($args);
+
+ // Calculate
+ $percent = array_pop($aArgs);
+
+ if ((is_numeric($percent)) && (!is_string($percent))) {
+ if (($percent < 0) || ($percent > 1)) {
+ return Functions::NAN();
+ }
+
+ $mArgs = [];
+ foreach ($aArgs as $arg) {
+ // Is it a numeric value?
+ if ((is_numeric($arg)) && (!is_string($arg))) {
+ $mArgs[] = $arg;
+ }
+ }
+
+ $discard = floor(Counts::COUNT($mArgs) * $percent / 2);
+ sort($mArgs);
+
+ for ($i = 0; $i < $discard; ++$i) {
+ array_pop($mArgs);
+ array_shift($mArgs);
+ }
+
+ return Averages::average($mArgs);
+ }
+
+ return Functions::VALUE();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php
new file mode 100644
index 0000000..57ef00a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/DistributionValidations.php
@@ -0,0 +1,24 @@
+ 1.0) {
+ throw new Exception(Functions::NAN());
+ }
+
+ return $probability;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php
new file mode 100644
index 0000000..ca55857
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Exponential.php
@@ -0,0 +1,55 @@
+getMessage();
+ }
+
+ if (($value < 0) || ($lambda < 0)) {
+ return Functions::NAN();
+ }
+
+ if ($cumulative === true) {
+ return 1 - exp(0 - $value * $lambda);
+ }
+
+ return $lambda * exp(0 - $value * $lambda);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php
new file mode 100644
index 0000000..0fd0aab
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/F.php
@@ -0,0 +1,64 @@
+getMessage();
+ }
+
+ if ($value < 0 || $u < 1 || $v < 1) {
+ return Functions::NAN();
+ }
+
+ if ($cumulative) {
+ $adjustedValue = ($u * $value) / ($u * $value + $v);
+
+ return Beta::incompleteBeta($adjustedValue, $u / 2, $v / 2);
+ }
+
+ return (Gamma::gammaValue(($v + $u) / 2) /
+ (Gamma::gammaValue($u / 2) * Gamma::gammaValue($v / 2))) *
+ (($u / $v) ** ($u / 2)) *
+ (($value ** (($u - 2) / 2)) / ((1 + ($u / $v) * $value) ** (($u + $v) / 2)));
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php
new file mode 100644
index 0000000..8d1b5ce
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Fisher.php
@@ -0,0 +1,74 @@
+getMessage();
+ }
+
+ if (($value <= -1) || ($value >= 1)) {
+ return Functions::NAN();
+ }
+
+ return 0.5 * log((1 + $value) / (1 - $value));
+ }
+
+ /**
+ * FISHERINV.
+ *
+ * Returns the inverse of the Fisher transformation. Use this transformation when
+ * analyzing correlations between ranges or arrays of data. If y = FISHER(x), then
+ * FISHERINV(y) = x.
+ *
+ * @param mixed $probability Float probability at which you want to evaluate the distribution
+ * Or can be an array of values
+ *
+ * @return array|float|string
+ * If an array of numbers is passed as an argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function inverse($probability)
+ {
+ if (is_array($probability)) {
+ return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $probability);
+ }
+
+ try {
+ DistributionValidations::validateFloat($probability);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ return (exp(2 * $probability) - 1) / (exp(2 * $probability) + 1);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php
new file mode 100644
index 0000000..adf5342
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Gamma.php
@@ -0,0 +1,151 @@
+getMessage();
+ }
+
+ if ((((int) $value) == ((float) $value)) && $value <= 0.0) {
+ return Functions::NAN();
+ }
+
+ return self::gammaValue($value);
+ }
+
+ /**
+ * GAMMADIST.
+ *
+ * Returns the gamma distribution.
+ *
+ * @param mixed $value Float Value at which you want to evaluate the distribution
+ * Or can be an array of values
+ * @param mixed $a Parameter to the distribution as a float
+ * Or can be an array of values
+ * @param mixed $b Parameter to the distribution as a float
+ * Or can be an array of values
+ * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
+ * Or can be an array of values
+ *
+ * @return array|float|string
+ * If an array of numbers is passed as an argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function distribution($value, $a, $b, $cumulative)
+ {
+ if (is_array($value) || is_array($a) || is_array($b) || is_array($cumulative)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $a, $b, $cumulative);
+ }
+
+ try {
+ $value = DistributionValidations::validateFloat($value);
+ $a = DistributionValidations::validateFloat($a);
+ $b = DistributionValidations::validateFloat($b);
+ $cumulative = DistributionValidations::validateBool($cumulative);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ if (($value < 0) || ($a <= 0) || ($b <= 0)) {
+ return Functions::NAN();
+ }
+
+ return self::calculateDistribution($value, $a, $b, $cumulative);
+ }
+
+ /**
+ * GAMMAINV.
+ *
+ * Returns the inverse of the Gamma distribution.
+ *
+ * @param mixed $probability Float probability at which you want to evaluate the distribution
+ * Or can be an array of values
+ * @param mixed $alpha Parameter to the distribution as a float
+ * Or can be an array of values
+ * @param mixed $beta Parameter to the distribution as a float
+ * Or can be an array of values
+ *
+ * @return array|float|string
+ * If an array of numbers is passed as an argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function inverse($probability, $alpha, $beta)
+ {
+ if (is_array($probability) || is_array($alpha) || is_array($beta)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $alpha, $beta);
+ }
+
+ try {
+ $probability = DistributionValidations::validateProbability($probability);
+ $alpha = DistributionValidations::validateFloat($alpha);
+ $beta = DistributionValidations::validateFloat($beta);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ if (($alpha <= 0.0) || ($beta <= 0.0)) {
+ return Functions::NAN();
+ }
+
+ return self::calculateInverse($probability, $alpha, $beta);
+ }
+
+ /**
+ * GAMMALN.
+ *
+ * Returns the natural logarithm of the gamma function.
+ *
+ * @param mixed $value Float Value at which you want to evaluate the distribution
+ * Or can be an array of values
+ *
+ * @return array|float|string
+ * If an array of numbers is passed as an argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function ln($value)
+ {
+ if (is_array($value)) {
+ return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+ }
+
+ try {
+ $value = DistributionValidations::validateFloat($value);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ if ($value <= 0) {
+ return Functions::NAN();
+ }
+
+ return log(self::gammaValue($value));
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php
new file mode 100644
index 0000000..89170f7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/GammaBase.php
@@ -0,0 +1,381 @@
+ Functions::PRECISION) && (++$i <= self::MAX_ITERATIONS)) {
+ // Apply Newton-Raphson step
+ $result = self::calculateDistribution($x, $alpha, $beta, true);
+ $error = $result - $probability;
+
+ if ($error == 0.0) {
+ $dx = 0;
+ } elseif ($error < 0.0) {
+ $xLo = $x;
+ } else {
+ $xHi = $x;
+ }
+
+ $pdf = self::calculateDistribution($x, $alpha, $beta, false);
+ // Avoid division by zero
+ if ($pdf !== 0.0) {
+ $dx = $error / $pdf;
+ $xNew = $x - $dx;
+ }
+
+ // If the NR fails to converge (which for example may be the
+ // case if the initial guess is too rough) we apply a bisection
+ // step to determine a more narrow interval around the root.
+ if (($xNew < $xLo) || ($xNew > $xHi) || ($pdf == 0.0)) {
+ $xNew = ($xLo + $xHi) / 2;
+ $dx = $xNew - $x;
+ }
+ $x = $xNew;
+ }
+
+ if ($i === self::MAX_ITERATIONS) {
+ return Functions::NA();
+ }
+
+ return $x;
+ }
+
+ //
+ // Implementation of the incomplete Gamma function
+ //
+ public static function incompleteGamma(float $a, float $x): float
+ {
+ static $max = 32;
+ $summer = 0;
+ for ($n = 0; $n <= $max; ++$n) {
+ $divisor = $a;
+ for ($i = 1; $i <= $n; ++$i) {
+ $divisor *= ($a + $i);
+ }
+ $summer += ($x ** $n / $divisor);
+ }
+
+ return $x ** $a * exp(0 - $x) * $summer;
+ }
+
+ //
+ // Implementation of the Gamma function
+ //
+ public static function gammaValue(float $value): float
+ {
+ if ($value == 0.0) {
+ return 0;
+ }
+
+ static $p0 = 1.000000000190015;
+ static $p = [
+ 1 => 76.18009172947146,
+ 2 => -86.50532032941677,
+ 3 => 24.01409824083091,
+ 4 => -1.231739572450155,
+ 5 => 1.208650973866179e-3,
+ 6 => -5.395239384953e-6,
+ ];
+
+ $y = $x = $value;
+ $tmp = $x + 5.5;
+ $tmp -= ($x + 0.5) * log($tmp);
+
+ $summer = $p0;
+ for ($j = 1; $j <= 6; ++$j) {
+ $summer += ($p[$j] / ++$y);
+ }
+
+ return exp(0 - $tmp + log(self::SQRT2PI * $summer / $x));
+ }
+
+ /**
+ * logGamma function.
+ *
+ * @version 1.1
+ *
+ * @author Jaco van Kooten
+ *
+ * Original author was Jaco van Kooten. Ported to PHP by Paul Meagher.
+ *
+ * The natural logarithm of the gamma function.
+ * Based on public domain NETLIB (Fortran) code by W. J. Cody and L. Stoltz
+ * Applied Mathematics Division
+ * Argonne National Laboratory
+ * Argonne, IL 60439
+ *
+ * References:
+ *
+ *
W. J. Cody and K. E. Hillstrom, 'Chebyshev Approximations for the Natural
+ * Logarithm of the Gamma Function,' Math. Comp. 21, 1967, pp. 198-203.
+ *
K. E. Hillstrom, ANL/AMD Program ANLC366S, DGAMMA/DLGAMA, May, 1969.
+ *
Hart, Et. Al., Computer Approximations, Wiley and sons, New York, 1968.
+ *
+ *
+ *
+ * From the original documentation:
+ *
+ *
+ * This routine calculates the LOG(GAMMA) function for a positive real argument X.
+ * Computation is based on an algorithm outlined in references 1 and 2.
+ * The program uses rational functions that theoretically approximate LOG(GAMMA)
+ * to at least 18 significant decimal digits. The approximation for X > 12 is from
+ * reference 3, while approximations for X < 12.0 are similar to those in reference
+ * 1, but are unpublished. The accuracy achieved depends on the arithmetic system,
+ * the compiler, the intrinsic functions, and proper selection of the
+ * machine-dependent constants.
+ *
+ *
+ * Error returns:
+ * The program returns the value XINF for X .LE. 0.0 or when overflow would occur.
+ * The computation is believed to be free of underflow and overflow.
+ *
+ *
+ * @return float MAX_VALUE for x < 0.0 or when overflow would occur, i.e. x > 2.55E305
+ */
+
+ // Log Gamma related constants
+ private const LG_D1 = -0.5772156649015328605195174;
+
+ private const LG_D2 = 0.4227843350984671393993777;
+
+ private const LG_D4 = 1.791759469228055000094023;
+
+ private const LG_P1 = [
+ 4.945235359296727046734888,
+ 201.8112620856775083915565,
+ 2290.838373831346393026739,
+ 11319.67205903380828685045,
+ 28557.24635671635335736389,
+ 38484.96228443793359990269,
+ 26377.48787624195437963534,
+ 7225.813979700288197698961,
+ ];
+
+ private const LG_P2 = [
+ 4.974607845568932035012064,
+ 542.4138599891070494101986,
+ 15506.93864978364947665077,
+ 184793.2904445632425417223,
+ 1088204.76946882876749847,
+ 3338152.967987029735917223,
+ 5106661.678927352456275255,
+ 3074109.054850539556250927,
+ ];
+
+ private const LG_P4 = [
+ 14745.02166059939948905062,
+ 2426813.369486704502836312,
+ 121475557.4045093227939592,
+ 2663432449.630976949898078,
+ 29403789566.34553899906876,
+ 170266573776.5398868392998,
+ 492612579337.743088758812,
+ 560625185622.3951465078242,
+ ];
+
+ private const LG_Q1 = [
+ 67.48212550303777196073036,
+ 1113.332393857199323513008,
+ 7738.757056935398733233834,
+ 27639.87074403340708898585,
+ 54993.10206226157329794414,
+ 61611.22180066002127833352,
+ 36351.27591501940507276287,
+ 8785.536302431013170870835,
+ ];
+
+ private const LG_Q2 = [
+ 183.0328399370592604055942,
+ 7765.049321445005871323047,
+ 133190.3827966074194402448,
+ 1136705.821321969608938755,
+ 5267964.117437946917577538,
+ 13467014.54311101692290052,
+ 17827365.30353274213975932,
+ 9533095.591844353613395747,
+ ];
+
+ private const LG_Q4 = [
+ 2690.530175870899333379843,
+ 639388.5654300092398984238,
+ 41355999.30241388052042842,
+ 1120872109.61614794137657,
+ 14886137286.78813811542398,
+ 101680358627.2438228077304,
+ 341747634550.7377132798597,
+ 446315818741.9713286462081,
+ ];
+
+ private const LG_C = [
+ -0.001910444077728,
+ 8.4171387781295e-4,
+ -5.952379913043012e-4,
+ 7.93650793500350248e-4,
+ -0.002777777777777681622553,
+ 0.08333333333333333331554247,
+ 0.0057083835261,
+ ];
+
+ // Rough estimate of the fourth root of logGamma_xBig
+ private const LG_FRTBIG = 2.25e76;
+
+ private const PNT68 = 0.6796875;
+
+ // Function cache for logGamma
+ private static $logGammaCacheResult = 0.0;
+
+ private static $logGammaCacheX = 0.0;
+
+ public static function logGamma(float $x): float
+ {
+ if ($x == self::$logGammaCacheX) {
+ return self::$logGammaCacheResult;
+ }
+
+ $y = $x;
+ if ($y > 0.0 && $y <= self::LOG_GAMMA_X_MAX_VALUE) {
+ if ($y <= self::EPS) {
+ $res = -log($y);
+ } elseif ($y <= 1.5) {
+ $res = self::logGamma1($y);
+ } elseif ($y <= 4.0) {
+ $res = self::logGamma2($y);
+ } elseif ($y <= 12.0) {
+ $res = self::logGamma3($y);
+ } else {
+ $res = self::logGamma4($y);
+ }
+ } else {
+ // --------------------------
+ // Return for bad arguments
+ // --------------------------
+ $res = self::MAX_VALUE;
+ }
+
+ // ------------------------------
+ // Final adjustments and return
+ // ------------------------------
+ self::$logGammaCacheX = $x;
+ self::$logGammaCacheResult = $res;
+
+ return $res;
+ }
+
+ private static function logGamma1(float $y)
+ {
+ // ---------------------
+ // EPS .LT. X .LE. 1.5
+ // ---------------------
+ if ($y < self::PNT68) {
+ $corr = -log($y);
+ $xm1 = $y;
+ } else {
+ $corr = 0.0;
+ $xm1 = $y - 1.0;
+ }
+
+ $xden = 1.0;
+ $xnum = 0.0;
+ if ($y <= 0.5 || $y >= self::PNT68) {
+ for ($i = 0; $i < 8; ++$i) {
+ $xnum = $xnum * $xm1 + self::LG_P1[$i];
+ $xden = $xden * $xm1 + self::LG_Q1[$i];
+ }
+
+ return $corr + $xm1 * (self::LG_D1 + $xm1 * ($xnum / $xden));
+ }
+
+ $xm2 = $y - 1.0;
+ for ($i = 0; $i < 8; ++$i) {
+ $xnum = $xnum * $xm2 + self::LG_P2[$i];
+ $xden = $xden * $xm2 + self::LG_Q2[$i];
+ }
+
+ return $corr + $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden));
+ }
+
+ private static function logGamma2(float $y)
+ {
+ // ---------------------
+ // 1.5 .LT. X .LE. 4.0
+ // ---------------------
+ $xm2 = $y - 2.0;
+ $xden = 1.0;
+ $xnum = 0.0;
+ for ($i = 0; $i < 8; ++$i) {
+ $xnum = $xnum * $xm2 + self::LG_P2[$i];
+ $xden = $xden * $xm2 + self::LG_Q2[$i];
+ }
+
+ return $xm2 * (self::LG_D2 + $xm2 * ($xnum / $xden));
+ }
+
+ protected static function logGamma3(float $y)
+ {
+ // ----------------------
+ // 4.0 .LT. X .LE. 12.0
+ // ----------------------
+ $xm4 = $y - 4.0;
+ $xden = -1.0;
+ $xnum = 0.0;
+ for ($i = 0; $i < 8; ++$i) {
+ $xnum = $xnum * $xm4 + self::LG_P4[$i];
+ $xden = $xden * $xm4 + self::LG_Q4[$i];
+ }
+
+ return self::LG_D4 + $xm4 * ($xnum / $xden);
+ }
+
+ protected static function logGamma4(float $y)
+ {
+ // ---------------------------------
+ // Evaluate for argument .GE. 12.0
+ // ---------------------------------
+ $res = 0.0;
+ if ($y <= self::LG_FRTBIG) {
+ $res = self::LG_C[6];
+ $ysq = $y * $y;
+ for ($i = 0; $i < 6; ++$i) {
+ $res = $res / $ysq + self::LG_C[$i];
+ }
+ $res /= $y;
+ $corr = log($y);
+ $res = $res + log(self::SQRT2PI) - 0.5 * $corr;
+ $res += $y * ($corr - 1.0);
+ }
+
+ return $res;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php
new file mode 100644
index 0000000..bb7adf7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/HyperGeometric.php
@@ -0,0 +1,76 @@
+getMessage();
+ }
+
+ if (($sampleSuccesses < 0) || ($sampleSuccesses > $sampleNumber) || ($sampleSuccesses > $populationSuccesses)) {
+ return Functions::NAN();
+ }
+ if (($sampleNumber <= 0) || ($sampleNumber > $populationNumber)) {
+ return Functions::NAN();
+ }
+ if (($populationSuccesses <= 0) || ($populationSuccesses > $populationNumber)) {
+ return Functions::NAN();
+ }
+
+ $successesPopulationAndSample = (float) Combinations::withoutRepetition($populationSuccesses, $sampleSuccesses);
+ $numbersPopulationAndSample = (float) Combinations::withoutRepetition($populationNumber, $sampleNumber);
+ $adjustedPopulationAndSample = (float) Combinations::withoutRepetition(
+ $populationNumber - $populationSuccesses,
+ $sampleNumber - $sampleSuccesses
+ );
+
+ return $successesPopulationAndSample * $adjustedPopulationAndSample / $numbersPopulationAndSample;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php
new file mode 100644
index 0000000..4bf2e0c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/LogNormal.php
@@ -0,0 +1,137 @@
+getMessage();
+ }
+
+ if (($value <= 0) || ($stdDev <= 0)) {
+ return Functions::NAN();
+ }
+
+ return StandardNormal::cumulative((log($value) - $mean) / $stdDev);
+ }
+
+ /**
+ * LOGNORM.DIST.
+ *
+ * Returns the lognormal distribution of x, where ln(x) is normally distributed
+ * with parameters mean and standard_dev.
+ *
+ * @param mixed $value Float value for which we want the probability
+ * Or can be an array of values
+ * @param mixed $mean Mean value as a float
+ * Or can be an array of values
+ * @param mixed $stdDev Standard Deviation as a float
+ * Or can be an array of values
+ * @param mixed $cumulative Boolean value indicating if we want the cdf (true) or the pdf (false)
+ * Or can be an array of values
+ *
+ * @return array|float|string The result, or a string containing an error
+ * If an array of numbers is passed as an argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function distribution($value, $mean, $stdDev, $cumulative = false)
+ {
+ if (is_array($value) || is_array($mean) || is_array($stdDev) || is_array($cumulative)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $mean, $stdDev, $cumulative);
+ }
+
+ try {
+ $value = DistributionValidations::validateFloat($value);
+ $mean = DistributionValidations::validateFloat($mean);
+ $stdDev = DistributionValidations::validateFloat($stdDev);
+ $cumulative = DistributionValidations::validateBool($cumulative);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ if (($value <= 0) || ($stdDev <= 0)) {
+ return Functions::NAN();
+ }
+
+ if ($cumulative === true) {
+ return StandardNormal::distribution((log($value) - $mean) / $stdDev, true);
+ }
+
+ return (1 / (sqrt(2 * M_PI) * $stdDev * $value)) *
+ exp(0 - ((log($value) - $mean) ** 2 / (2 * $stdDev ** 2)));
+ }
+
+ /**
+ * LOGINV.
+ *
+ * Returns the inverse of the lognormal cumulative distribution
+ *
+ * @param mixed $probability Float probability for which we want the value
+ * Or can be an array of values
+ * @param mixed $mean Mean Value as a float
+ * Or can be an array of values
+ * @param mixed $stdDev Standard Deviation as a float
+ * Or can be an array of values
+ *
+ * @return array|float|string The result, or a string containing an error
+ * If an array of numbers is passed as an argument, then the returned result will also be an array
+ * with the same dimensions
+ *
+ * @TODO Try implementing P J Acklam's refinement algorithm for greater
+ * accuracy if I can get my head round the mathematics
+ * (as described at) http://home.online.no/~pjacklam/notes/invnorm/
+ */
+ public static function inverse($probability, $mean, $stdDev)
+ {
+ if (is_array($probability) || is_array($mean) || is_array($stdDev)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $mean, $stdDev);
+ }
+
+ try {
+ $probability = DistributionValidations::validateProbability($probability);
+ $mean = DistributionValidations::validateFloat($mean);
+ $stdDev = DistributionValidations::validateFloat($stdDev);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ if ($stdDev <= 0) {
+ return Functions::NAN();
+ }
+
+ return exp($mean + $stdDev * StandardNormal::inverse($probability));
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php
new file mode 100644
index 0000000..2621167
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/NewtonRaphson.php
@@ -0,0 +1,62 @@
+callback = $callback;
+ }
+
+ public function execute(float $probability)
+ {
+ $xLo = 100;
+ $xHi = 0;
+
+ $dx = 1;
+ $x = $xNew = 1;
+ $i = 0;
+
+ while ((abs($dx) > Functions::PRECISION) && ($i++ < self::MAX_ITERATIONS)) {
+ // Apply Newton-Raphson step
+ $result = call_user_func($this->callback, $x);
+ $error = $result - $probability;
+
+ if ($error == 0.0) {
+ $dx = 0;
+ } elseif ($error < 0.0) {
+ $xLo = $x;
+ } else {
+ $xHi = $x;
+ }
+
+ // Avoid division by zero
+ if ($result != 0.0) {
+ $dx = $error / $result;
+ $xNew = $x - $dx;
+ }
+
+ // If the NR fails to converge (which for example may be the
+ // case if the initial guess is too rough) we apply a bisection
+ // step to determine a more narrow interval around the root.
+ if (($xNew < $xLo) || ($xNew > $xHi) || ($result == 0.0)) {
+ $xNew = ($xLo + $xHi) / 2;
+ $dx = $xNew - $x;
+ }
+ $x = $xNew;
+ }
+
+ if ($i == self::MAX_ITERATIONS) {
+ return Functions::NA();
+ }
+
+ return $x;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php
new file mode 100644
index 0000000..f876ed6
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Distributions/Normal.php
@@ -0,0 +1,180 @@
+getMessage();
+ }
+
+ if ($stdDev < 0) {
+ return Functions::NAN();
+ }
+
+ if ($cumulative) {
+ return 0.5 * (1 + Engineering\Erf::erfValue(($value - $mean) / ($stdDev * sqrt(2))));
+ }
+
+ return (1 / (self::SQRT2PI * $stdDev)) * exp(0 - (($value - $mean) ** 2 / (2 * ($stdDev * $stdDev))));
+ }
+
+ /**
+ * NORMINV.
+ *
+ * Returns the inverse of the normal cumulative distribution for the specified mean and standard deviation.
+ *
+ * @param mixed $probability Float probability for which we want the value
+ * Or can be an array of values
+ * @param mixed $mean Mean Value as a float
+ * Or can be an array of values
+ * @param mixed $stdDev Standard Deviation as a float
+ * Or can be an array of values
+ *
+ * @return array|float|string The result, or a string containing an error
+ * If an array of numbers is passed as an argument, then the returned result will also be an array
+ * with the same dimensions
+ */
+ public static function inverse($probability, $mean, $stdDev)
+ {
+ if (is_array($probability) || is_array($mean) || is_array($stdDev)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $probability, $mean, $stdDev);
+ }
+
+ try {
+ $probability = DistributionValidations::validateProbability($probability);
+ $mean = DistributionValidations::validateFloat($mean);
+ $stdDev = DistributionValidations::validateFloat($stdDev);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ if ($stdDev < 0) {
+ return Functions::NAN();
+ }
+
+ return (self::inverseNcdf($probability) * $stdDev) + $mean;
+ }
+
+ /*
+ * inverse_ncdf.php
+ * -------------------
+ * begin : Friday, January 16, 2004
+ * copyright : (C) 2004 Michael Nickerson
+ * email : nickersonm@yahoo.com
+ *
+ */
+ private static function inverseNcdf($p)
+ {
+ // Inverse ncdf approximation by Peter J. Acklam, implementation adapted to
+ // PHP by Michael Nickerson, using Dr. Thomas Ziegler's C implementation as
+ // a guide. http://home.online.no/~pjacklam/notes/invnorm/index.html
+ // I have not checked the accuracy of this implementation. Be aware that PHP
+ // will truncate the coeficcients to 14 digits.
+
+ // You have permission to use and distribute this function freely for
+ // whatever purpose you want, but please show common courtesy and give credit
+ // where credit is due.
+
+ // Input paramater is $p - probability - where 0 < p < 1.
+
+ // Coefficients in rational approximations
+ static $a = [
+ 1 => -3.969683028665376e+01,
+ 2 => 2.209460984245205e+02,
+ 3 => -2.759285104469687e+02,
+ 4 => 1.383577518672690e+02,
+ 5 => -3.066479806614716e+01,
+ 6 => 2.506628277459239e+00,
+ ];
+
+ static $b = [
+ 1 => -5.447609879822406e+01,
+ 2 => 1.615858368580409e+02,
+ 3 => -1.556989798598866e+02,
+ 4 => 6.680131188771972e+01,
+ 5 => -1.328068155288572e+01,
+ ];
+
+ static $c = [
+ 1 => -7.784894002430293e-03,
+ 2 => -3.223964580411365e-01,
+ 3 => -2.400758277161838e+00,
+ 4 => -2.549732539343734e+00,
+ 5 => 4.374664141464968e+00,
+ 6 => 2.938163982698783e+00,
+ ];
+
+ static $d = [
+ 1 => 7.784695709041462e-03,
+ 2 => 3.224671290700398e-01,
+ 3 => 2.445134137142996e+00,
+ 4 => 3.754408661907416e+00,
+ ];
+
+ // Define lower and upper region break-points.
+ $p_low = 0.02425; //Use lower region approx. below this
+ $p_high = 1 - $p_low; //Use upper region approx. above this
+
+ if (0 < $p && $p < $p_low) {
+ // Rational approximation for lower region.
+ $q = sqrt(-2 * log($p));
+
+ return ((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) /
+ (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
+ } elseif ($p_high < $p && $p < 1) {
+ // Rational approximation for upper region.
+ $q = sqrt(-2 * log(1 - $p));
+
+ return -((((($c[1] * $q + $c[2]) * $q + $c[3]) * $q + $c[4]) * $q + $c[5]) * $q + $c[6]) /
+ (((($d[1] * $q + $d[2]) * $q + $d[3]) * $q + $d[4]) * $q + 1);
+ }
+
+ // Rational approximation for central region.
+ $q = $p - 0.5;
+ $r = $q * $q;
+
+ return ((((($a[1] * $r + $a[2]) * $r + $a[3]) * $r + $a[4]) * $r + $a[5]) * $r + $a[6]) * $q /
+ ((((($b[1] * $r + $b[2]) * $r + $b[3]) * $r + $b[4]) * $r + $b[5]) * $r + 1);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php
new file mode 100644
index 0000000..bd17b06
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/MaxMinBase.php
@@ -0,0 +1,17 @@
+ $returnValue)) {
+ $returnValue = $arg;
+ }
+ }
+ }
+
+ if ($returnValue === null) {
+ return 0;
+ }
+
+ return $returnValue;
+ }
+
+ /**
+ * MAXA.
+ *
+ * Returns the greatest value in a list of arguments, including numbers, text, and logical values
+ *
+ * Excel Function:
+ * MAXA(value1[,value2[, ...]])
+ *
+ * @param mixed ...$args Data values
+ *
+ * @return float
+ */
+ public static function maxA(...$args)
+ {
+ $returnValue = null;
+
+ // Loop through arguments
+ $aArgs = Functions::flattenArray($args);
+ foreach ($aArgs as $arg) {
+ // Is it a numeric value?
+ if ((is_numeric($arg)) || (is_bool($arg)) || ((is_string($arg) && ($arg != '')))) {
+ $arg = self::datatypeAdjustmentAllowStrings($arg);
+ if (($returnValue === null) || ($arg > $returnValue)) {
+ $returnValue = $arg;
+ }
+ }
+ }
+
+ if ($returnValue === null) {
+ return 0;
+ }
+
+ return $returnValue;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php
new file mode 100644
index 0000000..596aad7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/Statistical/Minimum.php
@@ -0,0 +1,78 @@
+getMessage();
+ }
+
+ if (($entry < 0) || ($entry > 1)) {
+ return Functions::NAN();
+ }
+
+ $mArgs = self::percentileFilterValues($aArgs);
+ $mValueCount = count($mArgs);
+ if ($mValueCount > 0) {
+ sort($mArgs);
+ $count = Counts::COUNT($mArgs);
+ $index = $entry * ($count - 1);
+ $iBase = floor($index);
+ if ($index == $iBase) {
+ return $mArgs[$index];
+ }
+ $iNext = $iBase + 1;
+ $iProportion = $index - $iBase;
+
+ return $mArgs[$iBase] + (($mArgs[$iNext] - $mArgs[$iBase]) * $iProportion);
+ }
+
+ return Functions::NAN();
+ }
+
+ /**
+ * PERCENTRANK.
+ *
+ * Returns the rank of a value in a data set as a percentage of the data set.
+ * Note that the returned rank is simply rounded to the appropriate significant digits,
+ * rather than floored (as MS Excel), so value 3 for a value set of 1, 2, 3, 4 will return
+ * 0.667 rather than 0.666
+ *
+ * @param mixed $valueSet An array of (float) values, or a reference to, a list of numbers
+ * @param mixed $value The number whose rank you want to find
+ * @param mixed $significance The (integer) number of significant digits for the returned percentage value
+ *
+ * @return float|string (string if result is an error)
+ */
+ public static function PERCENTRANK($valueSet, $value, $significance = 3)
+ {
+ $valueSet = Functions::flattenArray($valueSet);
+ $value = Functions::flattenSingleValue($value);
+ $significance = ($significance === null) ? 3 : Functions::flattenSingleValue($significance);
+
+ try {
+ $value = StatisticalValidations::validateFloat($value);
+ $significance = StatisticalValidations::validateInt($significance);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ $valueSet = self::rankFilterValues($valueSet);
+ $valueCount = count($valueSet);
+ if ($valueCount == 0) {
+ return Functions::NA();
+ }
+ sort($valueSet, SORT_NUMERIC);
+
+ $valueAdjustor = $valueCount - 1;
+ if (($value < $valueSet[0]) || ($value > $valueSet[$valueAdjustor])) {
+ return Functions::NA();
+ }
+
+ $pos = array_search($value, $valueSet);
+ if ($pos === false) {
+ $pos = 0;
+ $testValue = $valueSet[0];
+ while ($testValue < $value) {
+ $testValue = $valueSet[++$pos];
+ }
+ --$pos;
+ $pos += (($value - $valueSet[$pos]) / ($testValue - $valueSet[$pos]));
+ }
+
+ return round($pos / $valueAdjustor, $significance);
+ }
+
+ /**
+ * QUARTILE.
+ *
+ * Returns the quartile of a data set.
+ *
+ * Excel Function:
+ * QUARTILE(value1[,value2[, ...]],entry)
+ *
+ * @param mixed $args Data values
+ *
+ * @return float|string The result, or a string containing an error
+ */
+ public static function QUARTILE(...$args)
+ {
+ $aArgs = Functions::flattenArray($args);
+ $entry = array_pop($aArgs);
+
+ try {
+ $entry = StatisticalValidations::validateFloat($entry);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ $entry = floor($entry);
+ $entry /= 4;
+ if (($entry < 0) || ($entry > 1)) {
+ return Functions::NAN();
+ }
+
+ return self::PERCENTILE($aArgs, $entry);
+ }
+
+ /**
+ * RANK.
+ *
+ * Returns the rank of a number in a list of numbers.
+ *
+ * @param mixed $value The number whose rank you want to find
+ * @param mixed $valueSet An array of float values, or a reference to, a list of numbers
+ * @param mixed $order Order to sort the values in the value set
+ *
+ * @return float|string The result, or a string containing an error (0 = Descending, 1 = Ascending)
+ */
+ public static function RANK($value, $valueSet, $order = self::RANK_SORT_DESCENDING)
+ {
+ $value = Functions::flattenSingleValue($value);
+ $valueSet = Functions::flattenArray($valueSet);
+ $order = ($order === null) ? self::RANK_SORT_DESCENDING : Functions::flattenSingleValue($order);
+
+ try {
+ $value = StatisticalValidations::validateFloat($value);
+ $order = StatisticalValidations::validateInt($order);
+ } catch (Exception $e) {
+ return $e->getMessage();
+ }
+
+ $valueSet = self::rankFilterValues($valueSet);
+ if ($order === self::RANK_SORT_DESCENDING) {
+ rsort($valueSet, SORT_NUMERIC);
+ } else {
+ sort($valueSet, SORT_NUMERIC);
+ }
+
+ $pos = array_search($value, $valueSet);
+ if ($pos === false) {
+ return Functions::NA();
+ }
+
+ return ++$pos;
+ }
+
+ protected static function percentileFilterValues(array $dataSet)
+ {
+ return array_filter(
+ $dataSet,
+ function ($value): bool {
+ return is_numeric($value) && !is_string($value);
+ }
+ );
+ }
+
+ protected static function rankFilterValues(array $dataSet)
+ {
+ return array_filter(
+ $dataSet,
+ function ($value): bool {
+ return is_numeric($value);
+ }
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php
new file mode 100644
index 0000000..d29f80c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Extract.php
@@ -0,0 +1,98 @@
+getMessage();
+ }
+
+ return mb_substr($value ?? '', 0, $chars, 'UTF-8');
+ }
+
+ /**
+ * MID.
+ *
+ * @param mixed $value String value from which to extract characters
+ * Or can be an array of values
+ * @param mixed $start Integer offset of the first character that we want to extract
+ * Or can be an array of values
+ * @param mixed $chars The number of characters to extract (as an integer)
+ * Or can be an array of values
+ *
+ * @return array|string The joined string
+ * If an array of values is passed for the $value, $start or $chars arguments, then the returned result
+ * will also be an array with matching dimensions
+ */
+ public static function mid($value, $start, $chars)
+ {
+ if (is_array($value) || is_array($start) || is_array($chars)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $start, $chars);
+ }
+
+ try {
+ $value = Helpers::extractString($value);
+ $start = Helpers::extractInt($start, 1);
+ $chars = Helpers::extractInt($chars, 0);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
+
+ return mb_substr($value ?? '', --$start, $chars, 'UTF-8');
+ }
+
+ /**
+ * RIGHT.
+ *
+ * @param mixed $value String value from which to extract characters
+ * Or can be an array of values
+ * @param mixed $chars The number of characters to extract (as an integer)
+ * Or can be an array of values
+ *
+ * @return array|string The joined string
+ * If an array of values is passed for the $value or $chars arguments, then the returned result
+ * will also be an array with matching dimensions
+ */
+ public static function right($value, $chars = 1)
+ {
+ if (is_array($value) || is_array($chars)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $chars);
+ }
+
+ try {
+ $value = Helpers::extractString($value);
+ $chars = Helpers::extractInt($chars, 0, 1);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
+
+ return mb_substr($value ?? '', mb_strlen($value ?? '', 'UTF-8') - $chars, $chars, 'UTF-8');
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php
new file mode 100644
index 0000000..b4b634f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Format.php
@@ -0,0 +1,279 @@
+getMessage();
+ }
+
+ $mask = '$#,##0';
+ if ($decimals > 0) {
+ $mask .= '.' . str_repeat('0', $decimals);
+ } else {
+ $round = 10 ** abs($decimals);
+ if ($value < 0) {
+ $round = 0 - $round;
+ }
+ $value = MathTrig\Round::multiple($value, $round);
+ }
+ $mask = "{$mask};-{$mask}";
+
+ return NumberFormat::toFormattedString($value, $mask);
+ }
+
+ /**
+ * FIXED.
+ *
+ * @param mixed $value The value to format
+ * Or can be an array of values
+ * @param mixed $decimals Integer value for the number of decimal places that should be formatted
+ * Or can be an array of values
+ * @param mixed $noCommas Boolean value indicating whether the value should have thousands separators or not
+ * Or can be an array of values
+ *
+ * @return array|string
+ * If an array of values is passed for either of the arguments, then the returned result
+ * will also be an array with matching dimensions
+ */
+ public static function FIXEDFORMAT($value, $decimals = 2, $noCommas = false)
+ {
+ if (is_array($value) || is_array($decimals) || is_array($noCommas)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $decimals, $noCommas);
+ }
+
+ try {
+ $value = Helpers::extractFloat($value);
+ $decimals = Helpers::extractInt($decimals, -100, 0, true);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
+
+ $valueResult = round($value, $decimals);
+ if ($decimals < 0) {
+ $decimals = 0;
+ }
+ if ($noCommas === false) {
+ $valueResult = number_format(
+ $valueResult,
+ $decimals,
+ StringHelper::getDecimalSeparator(),
+ StringHelper::getThousandsSeparator()
+ );
+ }
+
+ return (string) $valueResult;
+ }
+
+ /**
+ * TEXT.
+ *
+ * @param mixed $value The value to format
+ * Or can be an array of values
+ * @param mixed $format A string with the Format mask that should be used
+ * Or can be an array of values
+ *
+ * @return array|string
+ * If an array of values is passed for either of the arguments, then the returned result
+ * will also be an array with matching dimensions
+ */
+ public static function TEXTFORMAT($value, $format)
+ {
+ if (is_array($value) || is_array($format)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $format);
+ }
+
+ $value = Helpers::extractString($value);
+ $format = Helpers::extractString($format);
+
+ if (!is_numeric($value) && Date::isDateTimeFormatCode($format)) {
+ $value = DateTimeExcel\DateValue::fromString($value);
+ }
+
+ return (string) NumberFormat::toFormattedString($value, $format);
+ }
+
+ /**
+ * @param mixed $value Value to check
+ *
+ * @return mixed
+ */
+ private static function convertValue($value)
+ {
+ $value = $value ?? 0;
+ if (is_bool($value)) {
+ if (Functions::getCompatibilityMode() === Functions::COMPATIBILITY_OPENOFFICE) {
+ $value = (int) $value;
+ } else {
+ throw new CalcExp(Functions::VALUE());
+ }
+ }
+
+ return $value;
+ }
+
+ /**
+ * VALUE.
+ *
+ * @param mixed $value Value to check
+ * Or can be an array of values
+ *
+ * @return array|DateTimeInterface|float|int|string A string if arguments are invalid
+ * If an array of values is passed for the argument, then the returned result
+ * will also be an array with matching dimensions
+ */
+ public static function VALUE($value = '')
+ {
+ if (is_array($value)) {
+ return self::evaluateSingleArgumentArray([self::class, __FUNCTION__], $value);
+ }
+
+ try {
+ $value = self::convertValue($value);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
+ if (!is_numeric($value)) {
+ $numberValue = str_replace(
+ StringHelper::getThousandsSeparator(),
+ '',
+ trim($value, " \t\n\r\0\x0B" . StringHelper::getCurrencyCode())
+ );
+ if (is_numeric($numberValue)) {
+ return (float) $numberValue;
+ }
+
+ $dateSetting = Functions::getReturnDateType();
+ Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
+
+ if (strpos($value, ':') !== false) {
+ $timeValue = Functions::scalar(DateTimeExcel\TimeValue::fromString($value));
+ if ($timeValue !== Functions::VALUE()) {
+ Functions::setReturnDateType($dateSetting);
+
+ return $timeValue;
+ }
+ }
+ $dateValue = Functions::scalar(DateTimeExcel\DateValue::fromString($value));
+ if ($dateValue !== Functions::VALUE()) {
+ Functions::setReturnDateType($dateSetting);
+
+ return $dateValue;
+ }
+ Functions::setReturnDateType($dateSetting);
+
+ return Functions::VALUE();
+ }
+
+ return (float) $value;
+ }
+
+ /**
+ * @param mixed $decimalSeparator
+ */
+ private static function getDecimalSeparator($decimalSeparator): string
+ {
+ return empty($decimalSeparator) ? StringHelper::getDecimalSeparator() : (string) $decimalSeparator;
+ }
+
+ /**
+ * @param mixed $groupSeparator
+ */
+ private static function getGroupSeparator($groupSeparator): string
+ {
+ return empty($groupSeparator) ? StringHelper::getThousandsSeparator() : (string) $groupSeparator;
+ }
+
+ /**
+ * NUMBERVALUE.
+ *
+ * @param mixed $value The value to format
+ * Or can be an array of values
+ * @param mixed $decimalSeparator A string with the decimal separator to use, defaults to locale defined value
+ * Or can be an array of values
+ * @param mixed $groupSeparator A string with the group/thousands separator to use, defaults to locale defined value
+ * Or can be an array of values
+ *
+ * @return array|float|string
+ */
+ public static function NUMBERVALUE($value = '', $decimalSeparator = null, $groupSeparator = null)
+ {
+ if (is_array($value) || is_array($decimalSeparator) || is_array($groupSeparator)) {
+ return self::evaluateArrayArguments([self::class, __FUNCTION__], $value, $decimalSeparator, $groupSeparator);
+ }
+
+ try {
+ $value = self::convertValue($value);
+ $decimalSeparator = self::getDecimalSeparator($decimalSeparator);
+ $groupSeparator = self::getGroupSeparator($groupSeparator);
+ } catch (CalcExp $e) {
+ return $e->getMessage();
+ }
+
+ if (!is_numeric($value)) {
+ $decimalPositions = preg_match_all('/' . preg_quote($decimalSeparator) . '/', $value, $matches, PREG_OFFSET_CAPTURE);
+ if ($decimalPositions > 1) {
+ return Functions::VALUE();
+ }
+ $decimalOffset = array_pop($matches[0])[1];
+ if (strpos($value, $groupSeparator, $decimalOffset) !== false) {
+ return Functions::VALUE();
+ }
+
+ $value = str_replace([$groupSeparator, $decimalSeparator], ['', '.'], $value);
+
+ // Handle the special case of trailing % signs
+ $percentageString = rtrim($value, '%');
+ if (!is_numeric($percentageString)) {
+ return Functions::VALUE();
+ }
+
+ $percentageAdjustment = strlen($value) - strlen($percentageString);
+ if ($percentageAdjustment) {
+ $value = (float) $percentageString;
+ $value /= 10 ** ($percentageAdjustment * 2);
+ }
+ }
+
+ return is_array($value) ? Functions::VALUE() : (float) $value;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Helpers.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Helpers.php
new file mode 100644
index 0000000..86c405a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Calculation/TextData/Helpers.php
@@ -0,0 +1,86 @@
+url = $url;
+ $this->tooltip = $tooltip;
+ }
+
+ /**
+ * Get URL.
+ *
+ * @return string
+ */
+ public function getUrl()
+ {
+ return $this->url;
+ }
+
+ /**
+ * Set URL.
+ *
+ * @param string $url
+ *
+ * @return $this
+ */
+ public function setUrl($url)
+ {
+ $this->url = $url;
+
+ return $this;
+ }
+
+ /**
+ * Get tooltip.
+ *
+ * @return string
+ */
+ public function getTooltip()
+ {
+ return $this->tooltip;
+ }
+
+ /**
+ * Set tooltip.
+ *
+ * @param string $tooltip
+ *
+ * @return $this
+ */
+ public function setTooltip($tooltip)
+ {
+ $this->tooltip = $tooltip;
+
+ return $this;
+ }
+
+ /**
+ * Is this hyperlink internal? (to another worksheet).
+ *
+ * @return bool
+ */
+ public function isInternal()
+ {
+ return strpos($this->url, 'sheet://') !== false;
+ }
+
+ /**
+ * @return string
+ */
+ public function getTypeHyperlink()
+ {
+ return $this->isInternal() ? '' : 'External';
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->url .
+ $this->tooltip .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php
new file mode 100644
index 0000000..5af9f5f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Cell/IValueBinder.php
@@ -0,0 +1,16 @@
+ [
+ 'type' => self::EXCEL_COLOR_TYPE_STANDARD,
+ 'value' => null,
+ 'alpha' => 0,
+ ],
+ 'style' => [
+ 'width' => '9525',
+ 'compound' => self::LINE_STYLE_COMPOUND_SIMPLE,
+ 'dash' => self::LINE_STYLE_DASH_SOLID,
+ 'cap' => self::LINE_STYLE_CAP_FLAT,
+ 'join' => self::LINE_STYLE_JOIN_BEVEL,
+ 'arrow' => [
+ 'head' => [
+ 'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW,
+ 'size' => self::LINE_STYLE_ARROW_SIZE_5,
+ ],
+ 'end' => [
+ 'type' => self::LINE_STYLE_ARROW_TYPE_NOARROW,
+ 'size' => self::LINE_STYLE_ARROW_SIZE_8,
+ ],
+ ],
+ ],
+ ];
+
+ private $shadowProperties = [
+ 'presets' => self::SHADOW_PRESETS_NOSHADOW,
+ 'effect' => null,
+ 'color' => [
+ 'type' => self::EXCEL_COLOR_TYPE_STANDARD,
+ 'value' => 'black',
+ 'alpha' => 85,
+ ],
+ 'size' => [
+ 'sx' => null,
+ 'sy' => null,
+ 'kx' => null,
+ ],
+ 'blur' => null,
+ 'direction' => null,
+ 'distance' => null,
+ 'algn' => null,
+ 'rotWithShape' => null,
+ ];
+
+ private $glowProperties = [
+ 'size' => null,
+ 'color' => [
+ 'type' => self::EXCEL_COLOR_TYPE_STANDARD,
+ 'value' => 'black',
+ 'alpha' => 40,
+ ],
+ ];
+
+ private $softEdges = [
+ 'size' => null,
+ ];
+
+ /**
+ * Get Object State.
+ *
+ * @return bool
+ */
+ public function getObjectState()
+ {
+ return $this->objectState;
+ }
+
+ /**
+ * Change Object State to True.
+ *
+ * @return $this
+ */
+ private function activateObject()
+ {
+ $this->objectState = true;
+
+ return $this;
+ }
+
+ /**
+ * Set Line Color Properties.
+ *
+ * @param string $value
+ * @param int $alpha
+ * @param string $colorType
+ */
+ public function setLineColorProperties($value, $alpha = 0, $colorType = self::EXCEL_COLOR_TYPE_STANDARD): void
+ {
+ $this->activateObject()
+ ->lineProperties['color'] = $this->setColorProperties(
+ $value,
+ $alpha,
+ $colorType
+ );
+ }
+
+ /**
+ * Set Line Color Properties.
+ *
+ * @param float $lineWidth
+ * @param string $compoundType
+ * @param string $dashType
+ * @param string $capType
+ * @param string $joinType
+ * @param string $headArrowType
+ * @param string $headArrowSize
+ * @param string $endArrowType
+ * @param string $endArrowSize
+ */
+ public function setLineStyleProperties($lineWidth = null, $compoundType = null, $dashType = null, $capType = null, $joinType = null, $headArrowType = null, $headArrowSize = null, $endArrowType = null, $endArrowSize = null): void
+ {
+ $this->activateObject();
+ ($lineWidth !== null)
+ ? $this->lineProperties['style']['width'] = $this->getExcelPointsWidth((float) $lineWidth)
+ : null;
+ ($compoundType !== null)
+ ? $this->lineProperties['style']['compound'] = (string) $compoundType
+ : null;
+ ($dashType !== null)
+ ? $this->lineProperties['style']['dash'] = (string) $dashType
+ : null;
+ ($capType !== null)
+ ? $this->lineProperties['style']['cap'] = (string) $capType
+ : null;
+ ($joinType !== null)
+ ? $this->lineProperties['style']['join'] = (string) $joinType
+ : null;
+ ($headArrowType !== null)
+ ? $this->lineProperties['style']['arrow']['head']['type'] = (string) $headArrowType
+ : null;
+ ($headArrowSize !== null)
+ ? $this->lineProperties['style']['arrow']['head']['size'] = (string) $headArrowSize
+ : null;
+ ($endArrowType !== null)
+ ? $this->lineProperties['style']['arrow']['end']['type'] = (string) $endArrowType
+ : null;
+ ($endArrowSize !== null)
+ ? $this->lineProperties['style']['arrow']['end']['size'] = (string) $endArrowSize
+ : null;
+ }
+
+ /**
+ * Get Line Color Property.
+ *
+ * @param string $propertyName
+ *
+ * @return string
+ */
+ public function getLineColorProperty($propertyName)
+ {
+ return $this->lineProperties['color'][$propertyName];
+ }
+
+ /**
+ * Get Line Style Property.
+ *
+ * @param array|string $elements
+ *
+ * @return string
+ */
+ public function getLineStyleProperty($elements)
+ {
+ return $this->getArrayElementsValue($this->lineProperties['style'], $elements);
+ }
+
+ /**
+ * Set Glow Properties.
+ *
+ * @param float $size
+ * @param string $colorValue
+ * @param int $colorAlpha
+ * @param string $colorType
+ */
+ public function setGlowProperties($size, $colorValue = null, $colorAlpha = null, $colorType = null): void
+ {
+ $this
+ ->activateObject()
+ ->setGlowSize($size)
+ ->setGlowColor($colorValue, $colorAlpha, $colorType);
+ }
+
+ /**
+ * Get Glow Color Property.
+ *
+ * @param string $propertyName
+ *
+ * @return string
+ */
+ public function getGlowColor($propertyName)
+ {
+ return $this->glowProperties['color'][$propertyName];
+ }
+
+ /**
+ * Get Glow Size.
+ *
+ * @return string
+ */
+ public function getGlowSize()
+ {
+ return $this->glowProperties['size'];
+ }
+
+ /**
+ * Set Glow Size.
+ *
+ * @param float $size
+ *
+ * @return $this
+ */
+ private function setGlowSize($size)
+ {
+ $this->glowProperties['size'] = $this->getExcelPointsWidth((float) $size);
+
+ return $this;
+ }
+
+ /**
+ * Set Glow Color.
+ *
+ * @param string $color
+ * @param int $alpha
+ * @param string $colorType
+ *
+ * @return $this
+ */
+ private function setGlowColor($color, $alpha, $colorType)
+ {
+ if ($color !== null) {
+ $this->glowProperties['color']['value'] = (string) $color;
+ }
+ if ($alpha !== null) {
+ $this->glowProperties['color']['alpha'] = $this->getTrueAlpha((int) $alpha);
+ }
+ if ($colorType !== null) {
+ $this->glowProperties['color']['type'] = (string) $colorType;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Line Style Arrow Parameters.
+ *
+ * @param string $arrowSelector
+ * @param string $propertySelector
+ *
+ * @return string
+ */
+ public function getLineStyleArrowParameters($arrowSelector, $propertySelector)
+ {
+ return $this->getLineStyleArrowSize($this->lineProperties['style']['arrow'][$arrowSelector]['size'], $propertySelector);
+ }
+
+ /**
+ * Set Shadow Properties.
+ *
+ * @param int $presets
+ * @param string $colorValue
+ * @param string $colorType
+ * @param string $colorAlpha
+ * @param string $blur
+ * @param int $angle
+ * @param float $distance
+ */
+ public function setShadowProperties($presets, $colorValue = null, $colorType = null, $colorAlpha = null, $blur = null, $angle = null, $distance = null): void
+ {
+ $this->activateObject()
+ ->setShadowPresetsProperties((int) $presets)
+ ->setShadowColor(
+ $colorValue ?? $this->shadowProperties['color']['value'],
+ $colorAlpha === null ? (int) $this->shadowProperties['color']['alpha'] : $this->getTrueAlpha($colorAlpha),
+ $colorType ?? $this->shadowProperties['color']['type']
+ )
+ ->setShadowBlur((float) $blur)
+ ->setShadowAngle($angle)
+ ->setShadowDistance($distance);
+ }
+
+ /**
+ * Set Shadow Presets Properties.
+ *
+ * @param int $presets
+ *
+ * @return $this
+ */
+ private function setShadowPresetsProperties($presets)
+ {
+ $this->shadowProperties['presets'] = $presets;
+ $this->setShadowPropertiesMapValues($this->getShadowPresetsMap($presets));
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Properties Values.
+ *
+ * @param mixed $reference
+ *
+ * @return $this
+ */
+ private function setShadowPropertiesMapValues(array $propertiesMap, &$reference = null)
+ {
+ $base_reference = $reference;
+ foreach ($propertiesMap as $property_key => $property_val) {
+ if (is_array($property_val)) {
+ if ($reference === null) {
+ $reference = &$this->shadowProperties[$property_key];
+ } else {
+ $reference = &$reference[$property_key];
+ }
+ $this->setShadowPropertiesMapValues($property_val, $reference);
+ } else {
+ if ($base_reference === null) {
+ $this->shadowProperties[$property_key] = $property_val;
+ } else {
+ $reference[$property_key] = $property_val;
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Color.
+ *
+ * @param string $color
+ * @param int $alpha
+ * @param string $colorType
+ *
+ * @return $this
+ */
+ private function setShadowColor($color, $alpha, $colorType)
+ {
+ if ($color !== null) {
+ $this->shadowProperties['color']['value'] = (string) $color;
+ }
+ if ($alpha !== null) {
+ $this->shadowProperties['color']['alpha'] = $this->getTrueAlpha((int) $alpha);
+ }
+ if ($colorType !== null) {
+ $this->shadowProperties['color']['type'] = (string) $colorType;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Blur.
+ *
+ * @param float $blur
+ *
+ * @return $this
+ */
+ private function setShadowBlur($blur)
+ {
+ if ($blur !== null) {
+ $this->shadowProperties['blur'] = (string) $this->getExcelPointsWidth($blur);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Angle.
+ *
+ * @param int $angle
+ *
+ * @return $this
+ */
+ private function setShadowAngle($angle)
+ {
+ if ($angle !== null) {
+ $this->shadowProperties['direction'] = (string) $this->getExcelPointsAngle($angle);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Shadow Distance.
+ *
+ * @param float $distance
+ *
+ * @return $this
+ */
+ private function setShadowDistance($distance)
+ {
+ if ($distance !== null) {
+ $this->shadowProperties['distance'] = (string) $this->getExcelPointsWidth($distance);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Shadow Property.
+ *
+ * @param string|string[] $elements
+ *
+ * @return string
+ */
+ public function getShadowProperty($elements)
+ {
+ return $this->getArrayElementsValue($this->shadowProperties, $elements);
+ }
+
+ /**
+ * Set Soft Edges Size.
+ *
+ * @param float $size
+ */
+ public function setSoftEdgesSize($size): void
+ {
+ if ($size !== null) {
+ $this->activateObject();
+ $this->softEdges['size'] = (string) $this->getExcelPointsWidth($size);
+ }
+ }
+
+ /**
+ * Get Soft Edges Size.
+ *
+ * @return string
+ */
+ public function getSoftEdgesSize()
+ {
+ return $this->softEdges['size'];
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php
new file mode 100644
index 0000000..cea9655
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Layout.php
@@ -0,0 +1,481 @@
+layoutTarget = $layout['layoutTarget'];
+ }
+ if (isset($layout['xMode'])) {
+ $this->xMode = $layout['xMode'];
+ }
+ if (isset($layout['yMode'])) {
+ $this->yMode = $layout['yMode'];
+ }
+ if (isset($layout['x'])) {
+ $this->xPos = (float) $layout['x'];
+ }
+ if (isset($layout['y'])) {
+ $this->yPos = (float) $layout['y'];
+ }
+ if (isset($layout['w'])) {
+ $this->width = (float) $layout['w'];
+ }
+ if (isset($layout['h'])) {
+ $this->height = (float) $layout['h'];
+ }
+ }
+
+ /**
+ * Get Layout Target.
+ *
+ * @return string
+ */
+ public function getLayoutTarget()
+ {
+ return $this->layoutTarget;
+ }
+
+ /**
+ * Set Layout Target.
+ *
+ * @param string $target
+ *
+ * @return $this
+ */
+ public function setLayoutTarget($target)
+ {
+ $this->layoutTarget = $target;
+
+ return $this;
+ }
+
+ /**
+ * Get X-Mode.
+ *
+ * @return string
+ */
+ public function getXMode()
+ {
+ return $this->xMode;
+ }
+
+ /**
+ * Set X-Mode.
+ *
+ * @param string $mode
+ *
+ * @return $this
+ */
+ public function setXMode($mode)
+ {
+ $this->xMode = (string) $mode;
+
+ return $this;
+ }
+
+ /**
+ * Get Y-Mode.
+ *
+ * @return string
+ */
+ public function getYMode()
+ {
+ return $this->yMode;
+ }
+
+ /**
+ * Set Y-Mode.
+ *
+ * @param string $mode
+ *
+ * @return $this
+ */
+ public function setYMode($mode)
+ {
+ $this->yMode = (string) $mode;
+
+ return $this;
+ }
+
+ /**
+ * Get X-Position.
+ *
+ * @return number
+ */
+ public function getXPosition()
+ {
+ return $this->xPos;
+ }
+
+ /**
+ * Set X-Position.
+ *
+ * @param float $position
+ *
+ * @return $this
+ */
+ public function setXPosition($position)
+ {
+ $this->xPos = (float) $position;
+
+ return $this;
+ }
+
+ /**
+ * Get Y-Position.
+ *
+ * @return number
+ */
+ public function getYPosition()
+ {
+ return $this->yPos;
+ }
+
+ /**
+ * Set Y-Position.
+ *
+ * @param float $position
+ *
+ * @return $this
+ */
+ public function setYPosition($position)
+ {
+ $this->yPos = (float) $position;
+
+ return $this;
+ }
+
+ /**
+ * Get Width.
+ *
+ * @return number
+ */
+ public function getWidth()
+ {
+ return $this->width;
+ }
+
+ /**
+ * Set Width.
+ *
+ * @param float $width
+ *
+ * @return $this
+ */
+ public function setWidth($width)
+ {
+ $this->width = $width;
+
+ return $this;
+ }
+
+ /**
+ * Get Height.
+ *
+ * @return number
+ */
+ public function getHeight()
+ {
+ return $this->height;
+ }
+
+ /**
+ * Set Height.
+ *
+ * @param float $height
+ *
+ * @return $this
+ */
+ public function setHeight($height)
+ {
+ $this->height = $height;
+
+ return $this;
+ }
+
+ /**
+ * Get show legend key.
+ *
+ * @return bool
+ */
+ public function getShowLegendKey()
+ {
+ return $this->showLegendKey;
+ }
+
+ /**
+ * Set show legend key
+ * Specifies that legend keys should be shown in data labels.
+ *
+ * @param bool $showLegendKey Show legend key
+ *
+ * @return $this
+ */
+ public function setShowLegendKey($showLegendKey)
+ {
+ $this->showLegendKey = $showLegendKey;
+
+ return $this;
+ }
+
+ /**
+ * Get show value.
+ *
+ * @return bool
+ */
+ public function getShowVal()
+ {
+ return $this->showVal;
+ }
+
+ /**
+ * Set show val
+ * Specifies that the value should be shown in data labels.
+ *
+ * @param bool $showDataLabelValues Show val
+ *
+ * @return $this
+ */
+ public function setShowVal($showDataLabelValues)
+ {
+ $this->showVal = $showDataLabelValues;
+
+ return $this;
+ }
+
+ /**
+ * Get show category name.
+ *
+ * @return bool
+ */
+ public function getShowCatName()
+ {
+ return $this->showCatName;
+ }
+
+ /**
+ * Set show cat name
+ * Specifies that the category name should be shown in data labels.
+ *
+ * @param bool $showCategoryName Show cat name
+ *
+ * @return $this
+ */
+ public function setShowCatName($showCategoryName)
+ {
+ $this->showCatName = $showCategoryName;
+
+ return $this;
+ }
+
+ /**
+ * Get show data series name.
+ *
+ * @return bool
+ */
+ public function getShowSerName()
+ {
+ return $this->showSerName;
+ }
+
+ /**
+ * Set show ser name
+ * Specifies that the series name should be shown in data labels.
+ *
+ * @param bool $showSeriesName Show series name
+ *
+ * @return $this
+ */
+ public function setShowSerName($showSeriesName)
+ {
+ $this->showSerName = $showSeriesName;
+
+ return $this;
+ }
+
+ /**
+ * Get show percentage.
+ *
+ * @return bool
+ */
+ public function getShowPercent()
+ {
+ return $this->showPercent;
+ }
+
+ /**
+ * Set show percentage
+ * Specifies that the percentage should be shown in data labels.
+ *
+ * @param bool $showPercentage Show percentage
+ *
+ * @return $this
+ */
+ public function setShowPercent($showPercentage)
+ {
+ $this->showPercent = $showPercentage;
+
+ return $this;
+ }
+
+ /**
+ * Get show bubble size.
+ *
+ * @return bool
+ */
+ public function getShowBubbleSize()
+ {
+ return $this->showBubbleSize;
+ }
+
+ /**
+ * Set show bubble size
+ * Specifies that the bubble size should be shown in data labels.
+ *
+ * @param bool $showBubbleSize Show bubble size
+ *
+ * @return $this
+ */
+ public function setShowBubbleSize($showBubbleSize)
+ {
+ $this->showBubbleSize = $showBubbleSize;
+
+ return $this;
+ }
+
+ /**
+ * Get show leader lines.
+ *
+ * @return bool
+ */
+ public function getShowLeaderLines()
+ {
+ return $this->showLeaderLines;
+ }
+
+ /**
+ * Set show leader lines
+ * Specifies that leader lines should be shown in data labels.
+ *
+ * @param bool $showLeaderLines Show leader lines
+ *
+ * @return $this
+ */
+ public function setShowLeaderLines($showLeaderLines)
+ {
+ $this->showLeaderLines = $showLeaderLines;
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php
new file mode 100644
index 0000000..2f003cd
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Legend.php
@@ -0,0 +1,149 @@
+ self::POSITION_BOTTOM,
+ self::XL_LEGEND_POSITION_CORNER => self::POSITION_TOPRIGHT,
+ self::XL_LEGEND_POSITION_CUSTOM => '??',
+ self::XL_LEGEND_POSITION_LEFT => self::POSITION_LEFT,
+ self::XL_LEGEND_POSITION_RIGHT => self::POSITION_RIGHT,
+ self::XL_LEGEND_POSITION_TOP => self::POSITION_TOP,
+ ];
+
+ /**
+ * Legend position.
+ *
+ * @var string
+ */
+ private $position = self::POSITION_RIGHT;
+
+ /**
+ * Allow overlay of other elements?
+ *
+ * @var bool
+ */
+ private $overlay = true;
+
+ /**
+ * Legend Layout.
+ *
+ * @var Layout
+ */
+ private $layout;
+
+ /**
+ * Create a new Legend.
+ *
+ * @param string $position
+ * @param bool $overlay
+ */
+ public function __construct($position = self::POSITION_RIGHT, ?Layout $layout = null, $overlay = false)
+ {
+ $this->setPosition($position);
+ $this->layout = $layout;
+ $this->setOverlay($overlay);
+ }
+
+ /**
+ * Get legend position as an excel string value.
+ *
+ * @return string
+ */
+ public function getPosition()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Get legend position using an excel string value.
+ *
+ * @param string $position see self::POSITION_*
+ *
+ * @return bool
+ */
+ public function setPosition($position)
+ {
+ if (!in_array($position, self::$positionXLref)) {
+ return false;
+ }
+
+ $this->position = $position;
+
+ return true;
+ }
+
+ /**
+ * Get legend position as an Excel internal numeric value.
+ *
+ * @return int
+ */
+ public function getPositionXL()
+ {
+ return array_search($this->position, self::$positionXLref);
+ }
+
+ /**
+ * Set legend position using an Excel internal numeric value.
+ *
+ * @param int $positionXL see self::XL_LEGEND_POSITION_*
+ *
+ * @return bool
+ */
+ public function setPositionXL($positionXL)
+ {
+ if (!isset(self::$positionXLref[$positionXL])) {
+ return false;
+ }
+
+ $this->position = self::$positionXLref[$positionXL];
+
+ return true;
+ }
+
+ /**
+ * Get allow overlay of other elements?
+ *
+ * @return bool
+ */
+ public function getOverlay()
+ {
+ return $this->overlay;
+ }
+
+ /**
+ * Set allow overlay of other elements?
+ *
+ * @param bool $overlay
+ */
+ public function setOverlay($overlay): void
+ {
+ $this->overlay = $overlay;
+ }
+
+ /**
+ * Get Layout.
+ *
+ * @return Layout
+ */
+ public function getLayout()
+ {
+ return $this->layout;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php
new file mode 100644
index 0000000..3032f6b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Chart/Renderer/IRenderer.php
@@ -0,0 +1,22 @@
+graph = null;
+ $this->chart = $chart;
+ }
+
+ private static function init(): void
+ {
+ static $loaded = false;
+ if ($loaded) {
+ return;
+ }
+
+ \JpGraph\JpGraph::load();
+ \JpGraph\JpGraph::module('bar');
+ \JpGraph\JpGraph::module('contour');
+ \JpGraph\JpGraph::module('line');
+ \JpGraph\JpGraph::module('pie');
+ \JpGraph\JpGraph::module('pie3d');
+ \JpGraph\JpGraph::module('radar');
+ \JpGraph\JpGraph::module('regstat');
+ \JpGraph\JpGraph::module('scatter');
+ \JpGraph\JpGraph::module('stock');
+
+ self::$markSet = [
+ 'diamond' => MARK_DIAMOND,
+ 'square' => MARK_SQUARE,
+ 'triangle' => MARK_UTRIANGLE,
+ 'x' => MARK_X,
+ 'star' => MARK_STAR,
+ 'dot' => MARK_FILLEDCIRCLE,
+ 'dash' => MARK_DTRIANGLE,
+ 'circle' => MARK_CIRCLE,
+ 'plus' => MARK_CROSS,
+ ];
+
+ $loaded = true;
+ }
+
+ private function formatPointMarker($seriesPlot, $markerID)
+ {
+ $plotMarkKeys = array_keys(self::$markSet);
+ if ($markerID === null) {
+ // Use default plot marker (next marker in the series)
+ self::$plotMark %= count(self::$markSet);
+ $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]);
+ } elseif ($markerID !== 'none') {
+ // Use specified plot marker (if it exists)
+ if (isset(self::$markSet[$markerID])) {
+ $seriesPlot->mark->SetType(self::$markSet[$markerID]);
+ } else {
+ // If the specified plot marker doesn't exist, use default plot marker (next marker in the series)
+ self::$plotMark %= count(self::$markSet);
+ $seriesPlot->mark->SetType(self::$markSet[$plotMarkKeys[self::$plotMark++]]);
+ }
+ } else {
+ // Hide plot marker
+ $seriesPlot->mark->Hide();
+ }
+ $seriesPlot->mark->SetColor(self::$colourSet[self::$plotColour]);
+ $seriesPlot->mark->SetFillColor(self::$colourSet[self::$plotColour]);
+ $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
+
+ return $seriesPlot;
+ }
+
+ private function formatDataSetLabels($groupID, $datasetLabels, $labelCount, $rotation = '')
+ {
+ $datasetLabelFormatCode = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getFormatCode();
+ if ($datasetLabelFormatCode !== null) {
+ // Retrieve any label formatting code
+ $datasetLabelFormatCode = stripslashes($datasetLabelFormatCode);
+ }
+
+ $testCurrentIndex = 0;
+ foreach ($datasetLabels as $i => $datasetLabel) {
+ if (is_array($datasetLabel)) {
+ if ($rotation == 'bar') {
+ $datasetLabels[$i] = implode(' ', $datasetLabel);
+ } else {
+ $datasetLabel = array_reverse($datasetLabel);
+ $datasetLabels[$i] = implode("\n", $datasetLabel);
+ }
+ } else {
+ // Format labels according to any formatting code
+ if ($datasetLabelFormatCode !== null) {
+ $datasetLabels[$i] = NumberFormat::toFormattedString($datasetLabel, $datasetLabelFormatCode);
+ }
+ }
+ ++$testCurrentIndex;
+ }
+
+ return $datasetLabels;
+ }
+
+ private function percentageSumCalculation($groupID, $seriesCount)
+ {
+ $sumValues = [];
+ // Adjust our values to a percentage value across all series in the group
+ for ($i = 0; $i < $seriesCount; ++$i) {
+ if ($i == 0) {
+ $sumValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
+ } else {
+ $nextValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
+ foreach ($nextValues as $k => $value) {
+ if (isset($sumValues[$k])) {
+ $sumValues[$k] += $value;
+ } else {
+ $sumValues[$k] = $value;
+ }
+ }
+ }
+ }
+
+ return $sumValues;
+ }
+
+ private function percentageAdjustValues($dataValues, $sumValues)
+ {
+ foreach ($dataValues as $k => $dataValue) {
+ $dataValues[$k] = $dataValue / $sumValues[$k] * 100;
+ }
+
+ return $dataValues;
+ }
+
+ private function getCaption($captionElement)
+ {
+ // Read any caption
+ $caption = ($captionElement !== null) ? $captionElement->getCaption() : null;
+ // Test if we have a title caption to display
+ if ($caption !== null) {
+ // If we do, it could be a plain string or an array
+ if (is_array($caption)) {
+ // Implode an array to a plain string
+ $caption = implode('', $caption);
+ }
+ }
+
+ return $caption;
+ }
+
+ private function renderTitle(): void
+ {
+ $title = $this->getCaption($this->chart->getTitle());
+ if ($title !== null) {
+ $this->graph->title->Set($title);
+ }
+ }
+
+ private function renderLegend(): void
+ {
+ $legend = $this->chart->getLegend();
+ if ($legend !== null) {
+ $legendPosition = $legend->getPosition();
+ switch ($legendPosition) {
+ case 'r':
+ $this->graph->legend->SetPos(0.01, 0.5, 'right', 'center'); // right
+ $this->graph->legend->SetColumns(1);
+
+ break;
+ case 'l':
+ $this->graph->legend->SetPos(0.01, 0.5, 'left', 'center'); // left
+ $this->graph->legend->SetColumns(1);
+
+ break;
+ case 't':
+ $this->graph->legend->SetPos(0.5, 0.01, 'center', 'top'); // top
+
+ break;
+ case 'b':
+ $this->graph->legend->SetPos(0.5, 0.99, 'center', 'bottom'); // bottom
+
+ break;
+ default:
+ $this->graph->legend->SetPos(0.01, 0.01, 'right', 'top'); // top-right
+ $this->graph->legend->SetColumns(1);
+
+ break;
+ }
+ } else {
+ $this->graph->legend->Hide();
+ }
+ }
+
+ private function renderCartesianPlotArea($type = 'textlin'): void
+ {
+ $this->graph = new Graph(self::$width, self::$height);
+ $this->graph->SetScale($type);
+
+ $this->renderTitle();
+
+ // Rotate for bar rather than column chart
+ $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotDirection();
+ $reverse = $rotation == 'bar';
+
+ $xAxisLabel = $this->chart->getXAxisLabel();
+ if ($xAxisLabel !== null) {
+ $title = $this->getCaption($xAxisLabel);
+ if ($title !== null) {
+ $this->graph->xaxis->SetTitle($title, 'center');
+ $this->graph->xaxis->title->SetMargin(35);
+ if ($reverse) {
+ $this->graph->xaxis->title->SetAngle(90);
+ $this->graph->xaxis->title->SetMargin(90);
+ }
+ }
+ }
+
+ $yAxisLabel = $this->chart->getYAxisLabel();
+ if ($yAxisLabel !== null) {
+ $title = $this->getCaption($yAxisLabel);
+ if ($title !== null) {
+ $this->graph->yaxis->SetTitle($title, 'center');
+ if ($reverse) {
+ $this->graph->yaxis->title->SetAngle(0);
+ $this->graph->yaxis->title->SetMargin(-55);
+ }
+ }
+ }
+ }
+
+ private function renderPiePlotArea(): void
+ {
+ $this->graph = new PieGraph(self::$width, self::$height);
+
+ $this->renderTitle();
+ }
+
+ private function renderRadarPlotArea(): void
+ {
+ $this->graph = new RadarGraph(self::$width, self::$height);
+ $this->graph->SetScale('lin');
+
+ $this->renderTitle();
+ }
+
+ private function renderPlotLine($groupID, $filled = false, $combination = false, $dimensions = '2d'): void
+ {
+ $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
+
+ $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
+ if ($labelCount > 0) {
+ $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
+ $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount);
+ $this->graph->xaxis->SetTickLabels($datasetLabels);
+ }
+
+ $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+ $seriesPlots = [];
+ if ($grouping == 'percentStacked') {
+ $sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
+ } else {
+ $sumValues = [];
+ }
+
+ // Loop through each data series in turn
+ for ($i = 0; $i < $seriesCount; ++$i) {
+ $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
+ $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
+
+ if ($grouping == 'percentStacked') {
+ $dataValues = $this->percentageAdjustValues($dataValues, $sumValues);
+ }
+
+ // Fill in any missing values in the $dataValues array
+ $testCurrentIndex = 0;
+ foreach ($dataValues as $k => $dataValue) {
+ while ($k != $testCurrentIndex) {
+ $dataValues[$testCurrentIndex] = null;
+ ++$testCurrentIndex;
+ }
+ ++$testCurrentIndex;
+ }
+
+ $seriesPlot = new LinePlot($dataValues);
+ if ($combination) {
+ $seriesPlot->SetBarCenter();
+ }
+
+ if ($filled) {
+ $seriesPlot->SetFilled(true);
+ $seriesPlot->SetColor('black');
+ $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]);
+ } else {
+ // Set the appropriate plot marker
+ $this->formatPointMarker($seriesPlot, $marker);
+ }
+ $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
+ $seriesPlot->SetLegend($dataLabel);
+
+ $seriesPlots[] = $seriesPlot;
+ }
+
+ if ($grouping == 'standard') {
+ $groupPlot = $seriesPlots;
+ } else {
+ $groupPlot = new AccLinePlot($seriesPlots);
+ }
+ $this->graph->Add($groupPlot);
+ }
+
+ private function renderPlotBar($groupID, $dimensions = '2d'): void
+ {
+ $rotation = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotDirection();
+ // Rotate for bar rather than column chart
+ if (($groupID == 0) && ($rotation == 'bar')) {
+ $this->graph->Set90AndMargin();
+ }
+ $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
+
+ $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
+ if ($labelCount > 0) {
+ $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
+ $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount, $rotation);
+ // Rotate for bar rather than column chart
+ if ($rotation == 'bar') {
+ $datasetLabels = array_reverse($datasetLabels);
+ $this->graph->yaxis->SetPos('max');
+ $this->graph->yaxis->SetLabelAlign('center', 'top');
+ $this->graph->yaxis->SetLabelSide(SIDE_RIGHT);
+ }
+ $this->graph->xaxis->SetTickLabels($datasetLabels);
+ }
+
+ $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+ $seriesPlots = [];
+ if ($grouping == 'percentStacked') {
+ $sumValues = $this->percentageSumCalculation($groupID, $seriesCount);
+ } else {
+ $sumValues = [];
+ }
+
+ // Loop through each data series in turn
+ for ($j = 0; $j < $seriesCount; ++$j) {
+ $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues();
+ if ($grouping == 'percentStacked') {
+ $dataValues = $this->percentageAdjustValues($dataValues, $sumValues);
+ }
+
+ // Fill in any missing values in the $dataValues array
+ $testCurrentIndex = 0;
+ foreach ($dataValues as $k => $dataValue) {
+ while ($k != $testCurrentIndex) {
+ $dataValues[$testCurrentIndex] = null;
+ ++$testCurrentIndex;
+ }
+ ++$testCurrentIndex;
+ }
+
+ // Reverse the $dataValues order for bar rather than column chart
+ if ($rotation == 'bar') {
+ $dataValues = array_reverse($dataValues);
+ }
+ $seriesPlot = new BarPlot($dataValues);
+ $seriesPlot->SetColor('black');
+ $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour++]);
+ if ($dimensions == '3d') {
+ $seriesPlot->SetShadow();
+ }
+ if (!$this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)) {
+ $dataLabel = '';
+ } else {
+ $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($j)->getDataValue();
+ }
+ $seriesPlot->SetLegend($dataLabel);
+
+ $seriesPlots[] = $seriesPlot;
+ }
+ // Reverse the plot order for bar rather than column chart
+ if (($rotation == 'bar') && ($grouping != 'percentStacked')) {
+ $seriesPlots = array_reverse($seriesPlots);
+ }
+
+ if ($grouping == 'clustered') {
+ $groupPlot = new GroupBarPlot($seriesPlots);
+ } elseif ($grouping == 'standard') {
+ $groupPlot = new GroupBarPlot($seriesPlots);
+ } else {
+ $groupPlot = new AccBarPlot($seriesPlots);
+ if ($dimensions == '3d') {
+ $groupPlot->SetShadow();
+ }
+ }
+
+ $this->graph->Add($groupPlot);
+ }
+
+ private function renderPlotScatter($groupID, $bubble): void
+ {
+ $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
+ $scatterStyle = $bubbleSize = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
+
+ $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+ $seriesPlots = [];
+
+ // Loop through each data series in turn
+ for ($i = 0; $i < $seriesCount; ++$i) {
+ $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
+ $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
+
+ foreach ($dataValuesY as $k => $dataValueY) {
+ $dataValuesY[$k] = $k;
+ }
+
+ $seriesPlot = new ScatterPlot($dataValuesX, $dataValuesY);
+ if ($scatterStyle == 'lineMarker') {
+ $seriesPlot->SetLinkPoints();
+ $seriesPlot->link->SetColor(self::$colourSet[self::$plotColour]);
+ } elseif ($scatterStyle == 'smoothMarker') {
+ $spline = new Spline($dataValuesY, $dataValuesX);
+ [$splineDataY, $splineDataX] = $spline->Get(count($dataValuesX) * self::$width / 20);
+ $lplot = new LinePlot($splineDataX, $splineDataY);
+ $lplot->SetColor(self::$colourSet[self::$plotColour]);
+
+ $this->graph->Add($lplot);
+ }
+
+ if ($bubble) {
+ $this->formatPointMarker($seriesPlot, 'dot');
+ $seriesPlot->mark->SetColor('black');
+ $seriesPlot->mark->SetSize($bubbleSize);
+ } else {
+ $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
+ $this->formatPointMarker($seriesPlot, $marker);
+ }
+ $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
+ $seriesPlot->SetLegend($dataLabel);
+
+ $this->graph->Add($seriesPlot);
+ }
+ }
+
+ private function renderPlotRadar($groupID): void
+ {
+ $radarStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
+
+ $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+ $seriesPlots = [];
+
+ // Loop through each data series in turn
+ for ($i = 0; $i < $seriesCount; ++$i) {
+ $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
+ $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
+ $marker = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getPointMarker();
+
+ $dataValues = [];
+ foreach ($dataValuesY as $k => $dataValueY) {
+ $dataValues[$k] = implode(' ', array_reverse($dataValueY));
+ }
+ $tmp = array_shift($dataValues);
+ $dataValues[] = $tmp;
+ $tmp = array_shift($dataValuesX);
+ $dataValuesX[] = $tmp;
+
+ $this->graph->SetTitles(array_reverse($dataValues));
+
+ $seriesPlot = new RadarPlot(array_reverse($dataValuesX));
+
+ $dataLabel = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotLabelByIndex($i)->getDataValue();
+ $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
+ if ($radarStyle == 'filled') {
+ $seriesPlot->SetFillColor(self::$colourSet[self::$plotColour]);
+ }
+ $this->formatPointMarker($seriesPlot, $marker);
+ $seriesPlot->SetLegend($dataLabel);
+
+ $this->graph->Add($seriesPlot);
+ }
+ }
+
+ private function renderPlotContour($groupID): void
+ {
+ $contourStyle = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
+
+ $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+ $seriesPlots = [];
+
+ $dataValues = [];
+ // Loop through each data series in turn
+ for ($i = 0; $i < $seriesCount; ++$i) {
+ $dataValuesY = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex($i)->getDataValues();
+ $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($i)->getDataValues();
+
+ $dataValues[$i] = $dataValuesX;
+ }
+ $seriesPlot = new ContourPlot($dataValues);
+
+ $this->graph->Add($seriesPlot);
+ }
+
+ private function renderPlotStock($groupID): void
+ {
+ $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+ $plotOrder = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotOrder();
+
+ $dataValues = [];
+ // Loop through each data series in turn and build the plot arrays
+ foreach ($plotOrder as $i => $v) {
+ $dataValuesX = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($v)->getDataValues();
+ foreach ($dataValuesX as $j => $dataValueX) {
+ $dataValues[$plotOrder[$i]][$j] = $dataValueX;
+ }
+ }
+ if (empty($dataValues)) {
+ return;
+ }
+
+ $dataValuesPlot = [];
+ // Flatten the plot arrays to a single dimensional array to work with jpgraph
+ $jMax = count($dataValues[0]);
+ for ($j = 0; $j < $jMax; ++$j) {
+ for ($i = 0; $i < $seriesCount; ++$i) {
+ $dataValuesPlot[] = $dataValues[$i][$j];
+ }
+ }
+
+ // Set the x-axis labels
+ $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
+ if ($labelCount > 0) {
+ $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
+ $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount);
+ $this->graph->xaxis->SetTickLabels($datasetLabels);
+ }
+
+ $seriesPlot = new StockPlot($dataValuesPlot);
+ $seriesPlot->SetWidth(20);
+
+ $this->graph->Add($seriesPlot);
+ }
+
+ private function renderAreaChart($groupCount, $dimensions = '2d'): void
+ {
+ $this->renderCartesianPlotArea();
+
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $this->renderPlotLine($i, true, false, $dimensions);
+ }
+ }
+
+ private function renderLineChart($groupCount, $dimensions = '2d'): void
+ {
+ $this->renderCartesianPlotArea();
+
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $this->renderPlotLine($i, false, false, $dimensions);
+ }
+ }
+
+ private function renderBarChart($groupCount, $dimensions = '2d'): void
+ {
+ $this->renderCartesianPlotArea();
+
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $this->renderPlotBar($i, $dimensions);
+ }
+ }
+
+ private function renderScatterChart($groupCount): void
+ {
+ $this->renderCartesianPlotArea('linlin');
+
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $this->renderPlotScatter($i, false);
+ }
+ }
+
+ private function renderBubbleChart($groupCount): void
+ {
+ $this->renderCartesianPlotArea('linlin');
+
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $this->renderPlotScatter($i, true);
+ }
+ }
+
+ private function renderPieChart($groupCount, $dimensions = '2d', $doughnut = false, $multiplePlots = false): void
+ {
+ $this->renderPiePlotArea();
+
+ $iLimit = ($multiplePlots) ? $groupCount : 1;
+ for ($groupID = 0; $groupID < $iLimit; ++$groupID) {
+ $grouping = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotGrouping();
+ $exploded = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotStyle();
+ $datasetLabels = [];
+ if ($groupID == 0) {
+ $labelCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex(0)->getPointCount();
+ if ($labelCount > 0) {
+ $datasetLabels = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotCategoryByIndex(0)->getDataValues();
+ $datasetLabels = $this->formatDataSetLabels($groupID, $datasetLabels, $labelCount);
+ }
+ }
+
+ $seriesCount = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotSeriesCount();
+ $seriesPlots = [];
+ // For pie charts, we only display the first series: doughnut charts generally display all series
+ $jLimit = ($multiplePlots) ? $seriesCount : 1;
+ // Loop through each data series in turn
+ for ($j = 0; $j < $jLimit; ++$j) {
+ $dataValues = $this->chart->getPlotArea()->getPlotGroupByIndex($groupID)->getPlotValuesByIndex($j)->getDataValues();
+
+ // Fill in any missing values in the $dataValues array
+ $testCurrentIndex = 0;
+ foreach ($dataValues as $k => $dataValue) {
+ while ($k != $testCurrentIndex) {
+ $dataValues[$testCurrentIndex] = null;
+ ++$testCurrentIndex;
+ }
+ ++$testCurrentIndex;
+ }
+
+ if ($dimensions == '3d') {
+ $seriesPlot = new PiePlot3D($dataValues);
+ } else {
+ if ($doughnut) {
+ $seriesPlot = new PiePlotC($dataValues);
+ } else {
+ $seriesPlot = new PiePlot($dataValues);
+ }
+ }
+
+ if ($multiplePlots) {
+ $seriesPlot->SetSize(($jLimit - $j) / ($jLimit * 4));
+ }
+
+ if ($doughnut) {
+ $seriesPlot->SetMidColor('white');
+ }
+
+ $seriesPlot->SetColor(self::$colourSet[self::$plotColour++]);
+ if (count($datasetLabels) > 0) {
+ $seriesPlot->SetLabels(array_fill(0, count($datasetLabels), ''));
+ }
+ if ($dimensions != '3d') {
+ $seriesPlot->SetGuideLines(false);
+ }
+ if ($j == 0) {
+ if ($exploded) {
+ $seriesPlot->ExplodeAll();
+ }
+ $seriesPlot->SetLegends($datasetLabels);
+ }
+
+ $this->graph->Add($seriesPlot);
+ }
+ }
+ }
+
+ private function renderRadarChart($groupCount): void
+ {
+ $this->renderRadarPlotArea();
+
+ for ($groupID = 0; $groupID < $groupCount; ++$groupID) {
+ $this->renderPlotRadar($groupID);
+ }
+ }
+
+ private function renderStockChart($groupCount): void
+ {
+ $this->renderCartesianPlotArea('intint');
+
+ for ($groupID = 0; $groupID < $groupCount; ++$groupID) {
+ $this->renderPlotStock($groupID);
+ }
+ }
+
+ private function renderContourChart($groupCount, $dimensions): void
+ {
+ $this->renderCartesianPlotArea('intint');
+
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $this->renderPlotContour($i);
+ }
+ }
+
+ private function renderCombinationChart($groupCount, $dimensions, $outputDestination)
+ {
+ $this->renderCartesianPlotArea();
+
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $dimensions = null;
+ $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
+ switch ($chartType) {
+ case 'area3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'areaChart':
+ $this->renderPlotLine($i, true, true, $dimensions);
+
+ break;
+ case 'bar3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'barChart':
+ $this->renderPlotBar($i, $dimensions);
+
+ break;
+ case 'line3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'lineChart':
+ $this->renderPlotLine($i, false, true, $dimensions);
+
+ break;
+ case 'scatterChart':
+ $this->renderPlotScatter($i, false);
+
+ break;
+ case 'bubbleChart':
+ $this->renderPlotScatter($i, true);
+
+ break;
+ default:
+ $this->graph = null;
+
+ return false;
+ }
+ }
+
+ $this->renderLegend();
+
+ $this->graph->Stroke($outputDestination);
+
+ return true;
+ }
+
+ public function render($outputDestination)
+ {
+ self::$plotColour = 0;
+
+ $groupCount = $this->chart->getPlotArea()->getPlotGroupCount();
+
+ $dimensions = null;
+ if ($groupCount == 1) {
+ $chartType = $this->chart->getPlotArea()->getPlotGroupByIndex(0)->getPlotType();
+ } else {
+ $chartTypes = [];
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $chartTypes[] = $this->chart->getPlotArea()->getPlotGroupByIndex($i)->getPlotType();
+ }
+ $chartTypes = array_unique($chartTypes);
+ if (count($chartTypes) == 1) {
+ $chartType = array_pop($chartTypes);
+ } elseif (count($chartTypes) == 0) {
+ echo 'Chart is not yet implemented ';
+
+ return false;
+ } else {
+ return $this->renderCombinationChart($groupCount, $dimensions, $outputDestination);
+ }
+ }
+
+ switch ($chartType) {
+ case 'area3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'areaChart':
+ $this->renderAreaChart($groupCount, $dimensions);
+
+ break;
+ case 'bar3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'barChart':
+ $this->renderBarChart($groupCount, $dimensions);
+
+ break;
+ case 'line3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'lineChart':
+ $this->renderLineChart($groupCount, $dimensions);
+
+ break;
+ case 'pie3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'pieChart':
+ $this->renderPieChart($groupCount, $dimensions, false, false);
+
+ break;
+ case 'doughnut3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'doughnutChart':
+ $this->renderPieChart($groupCount, $dimensions, true, true);
+
+ break;
+ case 'scatterChart':
+ $this->renderScatterChart($groupCount);
+
+ break;
+ case 'bubbleChart':
+ $this->renderBubbleChart($groupCount);
+
+ break;
+ case 'radarChart':
+ $this->renderRadarChart($groupCount);
+
+ break;
+ case 'surface3DChart':
+ $dimensions = '3d';
+ // no break
+ case 'surfaceChart':
+ $this->renderContourChart($groupCount, $dimensions);
+
+ break;
+ case 'stockChart':
+ $this->renderStockChart($groupCount);
+
+ break;
+ default:
+ echo $chartType . ' is not yet implemented ';
+
+ return false;
+ }
+ $this->renderLegend();
+
+ $this->graph->Stroke($outputDestination);
+
+ return true;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory.php
new file mode 100644
index 0000000..2690ab7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Collection/Memory.php
@@ -0,0 +1,123 @@
+cache = [];
+
+ return true;
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function delete($key)
+ {
+ unset($this->cache[$key]);
+
+ return true;
+ }
+
+ /**
+ * @param iterable $keys
+ *
+ * @return bool
+ */
+ public function deleteMultiple($keys)
+ {
+ foreach ($keys as $key) {
+ $this->delete($key);
+ }
+
+ return true;
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $default
+ *
+ * @return mixed
+ */
+ public function get($key, $default = null)
+ {
+ if ($this->has($key)) {
+ return $this->cache[$key];
+ }
+
+ return $default;
+ }
+
+ /**
+ * @param iterable $keys
+ * @param mixed $default
+ *
+ * @return iterable
+ */
+ public function getMultiple($keys, $default = null)
+ {
+ $results = [];
+ foreach ($keys as $key) {
+ $results[$key] = $this->get($key, $default);
+ }
+
+ return $results;
+ }
+
+ /**
+ * @param string $key
+ *
+ * @return bool
+ */
+ public function has($key)
+ {
+ return array_key_exists($key, $this->cache);
+ }
+
+ /**
+ * @param string $key
+ * @param mixed $value
+ * @param null|DateInterval|int $ttl
+ *
+ * @return bool
+ */
+ public function set($key, $value, $ttl = null)
+ {
+ $this->cache[$key] = $value;
+
+ return true;
+ }
+
+ /**
+ * @param iterable $values
+ * @param null|DateInterval|int $ttl
+ *
+ * @return bool
+ */
+ public function setMultiple($values, $ttl = null)
+ {
+ foreach ($values as $key => $value) {
+ $this->set($key, $value);
+ }
+
+ return true;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php
new file mode 100644
index 0000000..9c5ab30
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Exception.php
@@ -0,0 +1,7 @@
+
+ */
+ protected $items = [];
+
+ /**
+ * HashTable key map.
+ *
+ * @var array
+ */
+ protected $keyMap = [];
+
+ /**
+ * Create a new HashTable.
+ *
+ * @param T[] $source Optional source array to create HashTable from
+ */
+ public function __construct($source = null)
+ {
+ if ($source !== null) {
+ // Create HashTable
+ $this->addFromSource($source);
+ }
+ }
+
+ /**
+ * Add HashTable items from source.
+ *
+ * @param T[] $source Source array to create HashTable from
+ */
+ public function addFromSource(?array $source = null): void
+ {
+ // Check if an array was passed
+ if ($source === null) {
+ return;
+ }
+
+ foreach ($source as $item) {
+ $this->add($item);
+ }
+ }
+
+ /**
+ * Add HashTable item.
+ *
+ * @param T $source Item to add
+ */
+ public function add(IComparable $source): void
+ {
+ $hash = $source->getHashCode();
+ if (!isset($this->items[$hash])) {
+ $this->items[$hash] = $source;
+ $this->keyMap[count($this->items) - 1] = $hash;
+ }
+ }
+
+ /**
+ * Remove HashTable item.
+ *
+ * @param T $source Item to remove
+ */
+ public function remove(IComparable $source): void
+ {
+ $hash = $source->getHashCode();
+ if (isset($this->items[$hash])) {
+ unset($this->items[$hash]);
+
+ $deleteKey = -1;
+ foreach ($this->keyMap as $key => $value) {
+ if ($deleteKey >= 0) {
+ $this->keyMap[$key - 1] = $value;
+ }
+
+ if ($value == $hash) {
+ $deleteKey = $key;
+ }
+ }
+ unset($this->keyMap[count($this->keyMap) - 1]);
+ }
+ }
+
+ /**
+ * Clear HashTable.
+ */
+ public function clear(): void
+ {
+ $this->items = [];
+ $this->keyMap = [];
+ }
+
+ /**
+ * Count.
+ *
+ * @return int
+ */
+ public function count()
+ {
+ return count($this->items);
+ }
+
+ /**
+ * Get index for hash code.
+ *
+ * @return false|int Index
+ */
+ public function getIndexForHashCode(string $hashCode)
+ {
+ return array_search($hashCode, $this->keyMap, true);
+ }
+
+ /**
+ * Get by index.
+ *
+ * @return null|T
+ */
+ public function getByIndex(int $index)
+ {
+ if (isset($this->keyMap[$index])) {
+ return $this->getByHashCode($this->keyMap[$index]);
+ }
+
+ return null;
+ }
+
+ /**
+ * Get by hashcode.
+ *
+ * @return null|T
+ */
+ public function getByHashCode(string $hashCode)
+ {
+ if (isset($this->items[$hashCode])) {
+ return $this->items[$hashCode];
+ }
+
+ return null;
+ }
+
+ /**
+ * HashTable to array.
+ *
+ * @return T[]
+ */
+ public function toArray()
+ {
+ return $this->items;
+ }
+
+ /**
+ * 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) {
+ // each member of this class is an array
+ if (is_array($value)) {
+ $array1 = $value;
+ foreach ($array1 as $key1 => $value1) {
+ if (is_object($value1)) {
+ $array1[$key1] = clone $value1;
+ }
+ }
+ $this->$key = $array1;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php
new file mode 100644
index 0000000..73a3308
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Helper/Html.php
@@ -0,0 +1,839 @@
+ 'f0f8ff',
+ 'antiquewhite' => 'faebd7',
+ 'antiquewhite1' => 'ffefdb',
+ 'antiquewhite2' => 'eedfcc',
+ 'antiquewhite3' => 'cdc0b0',
+ 'antiquewhite4' => '8b8378',
+ 'aqua' => '00ffff',
+ 'aquamarine1' => '7fffd4',
+ 'aquamarine2' => '76eec6',
+ 'aquamarine4' => '458b74',
+ 'azure1' => 'f0ffff',
+ 'azure2' => 'e0eeee',
+ 'azure3' => 'c1cdcd',
+ 'azure4' => '838b8b',
+ 'beige' => 'f5f5dc',
+ 'bisque1' => 'ffe4c4',
+ 'bisque2' => 'eed5b7',
+ 'bisque3' => 'cdb79e',
+ 'bisque4' => '8b7d6b',
+ 'black' => '000000',
+ 'blanchedalmond' => 'ffebcd',
+ 'blue' => '0000ff',
+ 'blue1' => '0000ff',
+ 'blue2' => '0000ee',
+ 'blue4' => '00008b',
+ 'blueviolet' => '8a2be2',
+ 'brown' => 'a52a2a',
+ 'brown1' => 'ff4040',
+ 'brown2' => 'ee3b3b',
+ 'brown3' => 'cd3333',
+ 'brown4' => '8b2323',
+ 'burlywood' => 'deb887',
+ 'burlywood1' => 'ffd39b',
+ 'burlywood2' => 'eec591',
+ 'burlywood3' => 'cdaa7d',
+ 'burlywood4' => '8b7355',
+ 'cadetblue' => '5f9ea0',
+ 'cadetblue1' => '98f5ff',
+ 'cadetblue2' => '8ee5ee',
+ 'cadetblue3' => '7ac5cd',
+ 'cadetblue4' => '53868b',
+ 'chartreuse1' => '7fff00',
+ 'chartreuse2' => '76ee00',
+ 'chartreuse3' => '66cd00',
+ 'chartreuse4' => '458b00',
+ 'chocolate' => 'd2691e',
+ 'chocolate1' => 'ff7f24',
+ 'chocolate2' => 'ee7621',
+ 'chocolate3' => 'cd661d',
+ 'coral' => 'ff7f50',
+ 'coral1' => 'ff7256',
+ 'coral2' => 'ee6a50',
+ 'coral3' => 'cd5b45',
+ 'coral4' => '8b3e2f',
+ 'cornflowerblue' => '6495ed',
+ 'cornsilk1' => 'fff8dc',
+ 'cornsilk2' => 'eee8cd',
+ 'cornsilk3' => 'cdc8b1',
+ 'cornsilk4' => '8b8878',
+ 'cyan1' => '00ffff',
+ 'cyan2' => '00eeee',
+ 'cyan3' => '00cdcd',
+ 'cyan4' => '008b8b',
+ 'darkgoldenrod' => 'b8860b',
+ 'darkgoldenrod1' => 'ffb90f',
+ 'darkgoldenrod2' => 'eead0e',
+ 'darkgoldenrod3' => 'cd950c',
+ 'darkgoldenrod4' => '8b6508',
+ 'darkgreen' => '006400',
+ 'darkkhaki' => 'bdb76b',
+ 'darkolivegreen' => '556b2f',
+ 'darkolivegreen1' => 'caff70',
+ 'darkolivegreen2' => 'bcee68',
+ 'darkolivegreen3' => 'a2cd5a',
+ 'darkolivegreen4' => '6e8b3d',
+ 'darkorange' => 'ff8c00',
+ 'darkorange1' => 'ff7f00',
+ 'darkorange2' => 'ee7600',
+ 'darkorange3' => 'cd6600',
+ 'darkorange4' => '8b4500',
+ 'darkorchid' => '9932cc',
+ 'darkorchid1' => 'bf3eff',
+ 'darkorchid2' => 'b23aee',
+ 'darkorchid3' => '9a32cd',
+ 'darkorchid4' => '68228b',
+ 'darksalmon' => 'e9967a',
+ 'darkseagreen' => '8fbc8f',
+ 'darkseagreen1' => 'c1ffc1',
+ 'darkseagreen2' => 'b4eeb4',
+ 'darkseagreen3' => '9bcd9b',
+ 'darkseagreen4' => '698b69',
+ 'darkslateblue' => '483d8b',
+ 'darkslategray' => '2f4f4f',
+ 'darkslategray1' => '97ffff',
+ 'darkslategray2' => '8deeee',
+ 'darkslategray3' => '79cdcd',
+ 'darkslategray4' => '528b8b',
+ 'darkturquoise' => '00ced1',
+ 'darkviolet' => '9400d3',
+ 'deeppink1' => 'ff1493',
+ 'deeppink2' => 'ee1289',
+ 'deeppink3' => 'cd1076',
+ 'deeppink4' => '8b0a50',
+ 'deepskyblue1' => '00bfff',
+ 'deepskyblue2' => '00b2ee',
+ 'deepskyblue3' => '009acd',
+ 'deepskyblue4' => '00688b',
+ 'dimgray' => '696969',
+ 'dodgerblue1' => '1e90ff',
+ 'dodgerblue2' => '1c86ee',
+ 'dodgerblue3' => '1874cd',
+ 'dodgerblue4' => '104e8b',
+ 'firebrick' => 'b22222',
+ 'firebrick1' => 'ff3030',
+ 'firebrick2' => 'ee2c2c',
+ 'firebrick3' => 'cd2626',
+ 'firebrick4' => '8b1a1a',
+ 'floralwhite' => 'fffaf0',
+ 'forestgreen' => '228b22',
+ 'fuchsia' => 'ff00ff',
+ 'gainsboro' => 'dcdcdc',
+ 'ghostwhite' => 'f8f8ff',
+ 'gold1' => 'ffd700',
+ 'gold2' => 'eec900',
+ 'gold3' => 'cdad00',
+ 'gold4' => '8b7500',
+ 'goldenrod' => 'daa520',
+ 'goldenrod1' => 'ffc125',
+ 'goldenrod2' => 'eeb422',
+ 'goldenrod3' => 'cd9b1d',
+ 'goldenrod4' => '8b6914',
+ 'gray' => 'bebebe',
+ 'gray1' => '030303',
+ 'gray10' => '1a1a1a',
+ 'gray11' => '1c1c1c',
+ 'gray12' => '1f1f1f',
+ 'gray13' => '212121',
+ 'gray14' => '242424',
+ 'gray15' => '262626',
+ 'gray16' => '292929',
+ 'gray17' => '2b2b2b',
+ 'gray18' => '2e2e2e',
+ 'gray19' => '303030',
+ 'gray2' => '050505',
+ 'gray20' => '333333',
+ 'gray21' => '363636',
+ 'gray22' => '383838',
+ 'gray23' => '3b3b3b',
+ 'gray24' => '3d3d3d',
+ 'gray25' => '404040',
+ 'gray26' => '424242',
+ 'gray27' => '454545',
+ 'gray28' => '474747',
+ 'gray29' => '4a4a4a',
+ 'gray3' => '080808',
+ 'gray30' => '4d4d4d',
+ 'gray31' => '4f4f4f',
+ 'gray32' => '525252',
+ 'gray33' => '545454',
+ 'gray34' => '575757',
+ 'gray35' => '595959',
+ 'gray36' => '5c5c5c',
+ 'gray37' => '5e5e5e',
+ 'gray38' => '616161',
+ 'gray39' => '636363',
+ 'gray4' => '0a0a0a',
+ 'gray40' => '666666',
+ 'gray41' => '696969',
+ 'gray42' => '6b6b6b',
+ 'gray43' => '6e6e6e',
+ 'gray44' => '707070',
+ 'gray45' => '737373',
+ 'gray46' => '757575',
+ 'gray47' => '787878',
+ 'gray48' => '7a7a7a',
+ 'gray49' => '7d7d7d',
+ 'gray5' => '0d0d0d',
+ 'gray50' => '7f7f7f',
+ 'gray51' => '828282',
+ 'gray52' => '858585',
+ 'gray53' => '878787',
+ 'gray54' => '8a8a8a',
+ 'gray55' => '8c8c8c',
+ 'gray56' => '8f8f8f',
+ 'gray57' => '919191',
+ 'gray58' => '949494',
+ 'gray59' => '969696',
+ 'gray6' => '0f0f0f',
+ 'gray60' => '999999',
+ 'gray61' => '9c9c9c',
+ 'gray62' => '9e9e9e',
+ 'gray63' => 'a1a1a1',
+ 'gray64' => 'a3a3a3',
+ 'gray65' => 'a6a6a6',
+ 'gray66' => 'a8a8a8',
+ 'gray67' => 'ababab',
+ 'gray68' => 'adadad',
+ 'gray69' => 'b0b0b0',
+ 'gray7' => '121212',
+ 'gray70' => 'b3b3b3',
+ 'gray71' => 'b5b5b5',
+ 'gray72' => 'b8b8b8',
+ 'gray73' => 'bababa',
+ 'gray74' => 'bdbdbd',
+ 'gray75' => 'bfbfbf',
+ 'gray76' => 'c2c2c2',
+ 'gray77' => 'c4c4c4',
+ 'gray78' => 'c7c7c7',
+ 'gray79' => 'c9c9c9',
+ 'gray8' => '141414',
+ 'gray80' => 'cccccc',
+ 'gray81' => 'cfcfcf',
+ 'gray82' => 'd1d1d1',
+ 'gray83' => 'd4d4d4',
+ 'gray84' => 'd6d6d6',
+ 'gray85' => 'd9d9d9',
+ 'gray86' => 'dbdbdb',
+ 'gray87' => 'dedede',
+ 'gray88' => 'e0e0e0',
+ 'gray89' => 'e3e3e3',
+ 'gray9' => '171717',
+ 'gray90' => 'e5e5e5',
+ 'gray91' => 'e8e8e8',
+ 'gray92' => 'ebebeb',
+ 'gray93' => 'ededed',
+ 'gray94' => 'f0f0f0',
+ 'gray95' => 'f2f2f2',
+ 'gray97' => 'f7f7f7',
+ 'gray98' => 'fafafa',
+ 'gray99' => 'fcfcfc',
+ 'green' => '00ff00',
+ 'green1' => '00ff00',
+ 'green2' => '00ee00',
+ 'green3' => '00cd00',
+ 'green4' => '008b00',
+ 'greenyellow' => 'adff2f',
+ 'honeydew1' => 'f0fff0',
+ 'honeydew2' => 'e0eee0',
+ 'honeydew3' => 'c1cdc1',
+ 'honeydew4' => '838b83',
+ 'hotpink' => 'ff69b4',
+ 'hotpink1' => 'ff6eb4',
+ 'hotpink2' => 'ee6aa7',
+ 'hotpink3' => 'cd6090',
+ 'hotpink4' => '8b3a62',
+ 'indianred' => 'cd5c5c',
+ 'indianred1' => 'ff6a6a',
+ 'indianred2' => 'ee6363',
+ 'indianred3' => 'cd5555',
+ 'indianred4' => '8b3a3a',
+ 'ivory1' => 'fffff0',
+ 'ivory2' => 'eeeee0',
+ 'ivory3' => 'cdcdc1',
+ 'ivory4' => '8b8b83',
+ 'khaki' => 'f0e68c',
+ 'khaki1' => 'fff68f',
+ 'khaki2' => 'eee685',
+ 'khaki3' => 'cdc673',
+ 'khaki4' => '8b864e',
+ 'lavender' => 'e6e6fa',
+ 'lavenderblush1' => 'fff0f5',
+ 'lavenderblush2' => 'eee0e5',
+ 'lavenderblush3' => 'cdc1c5',
+ 'lavenderblush4' => '8b8386',
+ 'lawngreen' => '7cfc00',
+ 'lemonchiffon1' => 'fffacd',
+ 'lemonchiffon2' => 'eee9bf',
+ 'lemonchiffon3' => 'cdc9a5',
+ 'lemonchiffon4' => '8b8970',
+ 'light' => 'eedd82',
+ 'lightblue' => 'add8e6',
+ 'lightblue1' => 'bfefff',
+ 'lightblue2' => 'b2dfee',
+ 'lightblue3' => '9ac0cd',
+ 'lightblue4' => '68838b',
+ 'lightcoral' => 'f08080',
+ 'lightcyan1' => 'e0ffff',
+ 'lightcyan2' => 'd1eeee',
+ 'lightcyan3' => 'b4cdcd',
+ 'lightcyan4' => '7a8b8b',
+ 'lightgoldenrod1' => 'ffec8b',
+ 'lightgoldenrod2' => 'eedc82',
+ 'lightgoldenrod3' => 'cdbe70',
+ 'lightgoldenrod4' => '8b814c',
+ 'lightgoldenrodyellow' => 'fafad2',
+ 'lightgray' => 'd3d3d3',
+ 'lightpink' => 'ffb6c1',
+ 'lightpink1' => 'ffaeb9',
+ 'lightpink2' => 'eea2ad',
+ 'lightpink3' => 'cd8c95',
+ 'lightpink4' => '8b5f65',
+ 'lightsalmon1' => 'ffa07a',
+ 'lightsalmon2' => 'ee9572',
+ 'lightsalmon3' => 'cd8162',
+ 'lightsalmon4' => '8b5742',
+ 'lightseagreen' => '20b2aa',
+ 'lightskyblue' => '87cefa',
+ 'lightskyblue1' => 'b0e2ff',
+ 'lightskyblue2' => 'a4d3ee',
+ 'lightskyblue3' => '8db6cd',
+ 'lightskyblue4' => '607b8b',
+ 'lightslateblue' => '8470ff',
+ 'lightslategray' => '778899',
+ 'lightsteelblue' => 'b0c4de',
+ 'lightsteelblue1' => 'cae1ff',
+ 'lightsteelblue2' => 'bcd2ee',
+ 'lightsteelblue3' => 'a2b5cd',
+ 'lightsteelblue4' => '6e7b8b',
+ 'lightyellow1' => 'ffffe0',
+ 'lightyellow2' => 'eeeed1',
+ 'lightyellow3' => 'cdcdb4',
+ 'lightyellow4' => '8b8b7a',
+ 'lime' => '00ff00',
+ 'limegreen' => '32cd32',
+ 'linen' => 'faf0e6',
+ 'magenta' => 'ff00ff',
+ 'magenta2' => 'ee00ee',
+ 'magenta3' => 'cd00cd',
+ 'magenta4' => '8b008b',
+ 'maroon' => 'b03060',
+ 'maroon1' => 'ff34b3',
+ 'maroon2' => 'ee30a7',
+ 'maroon3' => 'cd2990',
+ 'maroon4' => '8b1c62',
+ 'medium' => '66cdaa',
+ 'mediumaquamarine' => '66cdaa',
+ 'mediumblue' => '0000cd',
+ 'mediumorchid' => 'ba55d3',
+ 'mediumorchid1' => 'e066ff',
+ 'mediumorchid2' => 'd15fee',
+ 'mediumorchid3' => 'b452cd',
+ 'mediumorchid4' => '7a378b',
+ 'mediumpurple' => '9370db',
+ 'mediumpurple1' => 'ab82ff',
+ 'mediumpurple2' => '9f79ee',
+ 'mediumpurple3' => '8968cd',
+ 'mediumpurple4' => '5d478b',
+ 'mediumseagreen' => '3cb371',
+ 'mediumslateblue' => '7b68ee',
+ 'mediumspringgreen' => '00fa9a',
+ 'mediumturquoise' => '48d1cc',
+ 'mediumvioletred' => 'c71585',
+ 'midnightblue' => '191970',
+ 'mintcream' => 'f5fffa',
+ 'mistyrose1' => 'ffe4e1',
+ 'mistyrose2' => 'eed5d2',
+ 'mistyrose3' => 'cdb7b5',
+ 'mistyrose4' => '8b7d7b',
+ 'moccasin' => 'ffe4b5',
+ 'navajowhite1' => 'ffdead',
+ 'navajowhite2' => 'eecfa1',
+ 'navajowhite3' => 'cdb38b',
+ 'navajowhite4' => '8b795e',
+ 'navy' => '000080',
+ 'navyblue' => '000080',
+ 'oldlace' => 'fdf5e6',
+ 'olive' => '808000',
+ 'olivedrab' => '6b8e23',
+ 'olivedrab1' => 'c0ff3e',
+ 'olivedrab2' => 'b3ee3a',
+ 'olivedrab4' => '698b22',
+ 'orange' => 'ffa500',
+ 'orange1' => 'ffa500',
+ 'orange2' => 'ee9a00',
+ 'orange3' => 'cd8500',
+ 'orange4' => '8b5a00',
+ 'orangered1' => 'ff4500',
+ 'orangered2' => 'ee4000',
+ 'orangered3' => 'cd3700',
+ 'orangered4' => '8b2500',
+ 'orchid' => 'da70d6',
+ 'orchid1' => 'ff83fa',
+ 'orchid2' => 'ee7ae9',
+ 'orchid3' => 'cd69c9',
+ 'orchid4' => '8b4789',
+ 'pale' => 'db7093',
+ 'palegoldenrod' => 'eee8aa',
+ 'palegreen' => '98fb98',
+ 'palegreen1' => '9aff9a',
+ 'palegreen2' => '90ee90',
+ 'palegreen3' => '7ccd7c',
+ 'palegreen4' => '548b54',
+ 'paleturquoise' => 'afeeee',
+ 'paleturquoise1' => 'bbffff',
+ 'paleturquoise2' => 'aeeeee',
+ 'paleturquoise3' => '96cdcd',
+ 'paleturquoise4' => '668b8b',
+ 'palevioletred' => 'db7093',
+ 'palevioletred1' => 'ff82ab',
+ 'palevioletred2' => 'ee799f',
+ 'palevioletred3' => 'cd6889',
+ 'palevioletred4' => '8b475d',
+ 'papayawhip' => 'ffefd5',
+ 'peachpuff1' => 'ffdab9',
+ 'peachpuff2' => 'eecbad',
+ 'peachpuff3' => 'cdaf95',
+ 'peachpuff4' => '8b7765',
+ 'pink' => 'ffc0cb',
+ 'pink1' => 'ffb5c5',
+ 'pink2' => 'eea9b8',
+ 'pink3' => 'cd919e',
+ 'pink4' => '8b636c',
+ 'plum' => 'dda0dd',
+ 'plum1' => 'ffbbff',
+ 'plum2' => 'eeaeee',
+ 'plum3' => 'cd96cd',
+ 'plum4' => '8b668b',
+ 'powderblue' => 'b0e0e6',
+ 'purple' => 'a020f0',
+ 'rebeccapurple' => '663399',
+ 'purple1' => '9b30ff',
+ 'purple2' => '912cee',
+ 'purple3' => '7d26cd',
+ 'purple4' => '551a8b',
+ 'red' => 'ff0000',
+ 'red1' => 'ff0000',
+ 'red2' => 'ee0000',
+ 'red3' => 'cd0000',
+ 'red4' => '8b0000',
+ 'rosybrown' => 'bc8f8f',
+ 'rosybrown1' => 'ffc1c1',
+ 'rosybrown2' => 'eeb4b4',
+ 'rosybrown3' => 'cd9b9b',
+ 'rosybrown4' => '8b6969',
+ 'royalblue' => '4169e1',
+ 'royalblue1' => '4876ff',
+ 'royalblue2' => '436eee',
+ 'royalblue3' => '3a5fcd',
+ 'royalblue4' => '27408b',
+ 'saddlebrown' => '8b4513',
+ 'salmon' => 'fa8072',
+ 'salmon1' => 'ff8c69',
+ 'salmon2' => 'ee8262',
+ 'salmon3' => 'cd7054',
+ 'salmon4' => '8b4c39',
+ 'sandybrown' => 'f4a460',
+ 'seagreen1' => '54ff9f',
+ 'seagreen2' => '4eee94',
+ 'seagreen3' => '43cd80',
+ 'seagreen4' => '2e8b57',
+ 'seashell1' => 'fff5ee',
+ 'seashell2' => 'eee5de',
+ 'seashell3' => 'cdc5bf',
+ 'seashell4' => '8b8682',
+ 'sienna' => 'a0522d',
+ 'sienna1' => 'ff8247',
+ 'sienna2' => 'ee7942',
+ 'sienna3' => 'cd6839',
+ 'sienna4' => '8b4726',
+ 'silver' => 'c0c0c0',
+ 'skyblue' => '87ceeb',
+ 'skyblue1' => '87ceff',
+ 'skyblue2' => '7ec0ee',
+ 'skyblue3' => '6ca6cd',
+ 'skyblue4' => '4a708b',
+ 'slateblue' => '6a5acd',
+ 'slateblue1' => '836fff',
+ 'slateblue2' => '7a67ee',
+ 'slateblue3' => '6959cd',
+ 'slateblue4' => '473c8b',
+ 'slategray' => '708090',
+ 'slategray1' => 'c6e2ff',
+ 'slategray2' => 'b9d3ee',
+ 'slategray3' => '9fb6cd',
+ 'slategray4' => '6c7b8b',
+ 'snow1' => 'fffafa',
+ 'snow2' => 'eee9e9',
+ 'snow3' => 'cdc9c9',
+ 'snow4' => '8b8989',
+ 'springgreen1' => '00ff7f',
+ 'springgreen2' => '00ee76',
+ 'springgreen3' => '00cd66',
+ 'springgreen4' => '008b45',
+ 'steelblue' => '4682b4',
+ 'steelblue1' => '63b8ff',
+ 'steelblue2' => '5cacee',
+ 'steelblue3' => '4f94cd',
+ 'steelblue4' => '36648b',
+ 'tan' => 'd2b48c',
+ 'tan1' => 'ffa54f',
+ 'tan2' => 'ee9a49',
+ 'tan3' => 'cd853f',
+ 'tan4' => '8b5a2b',
+ 'teal' => '008080',
+ 'thistle' => 'd8bfd8',
+ 'thistle1' => 'ffe1ff',
+ 'thistle2' => 'eed2ee',
+ 'thistle3' => 'cdb5cd',
+ 'thistle4' => '8b7b8b',
+ 'tomato1' => 'ff6347',
+ 'tomato2' => 'ee5c42',
+ 'tomato3' => 'cd4f39',
+ 'tomato4' => '8b3626',
+ 'turquoise' => '40e0d0',
+ 'turquoise1' => '00f5ff',
+ 'turquoise2' => '00e5ee',
+ 'turquoise3' => '00c5cd',
+ 'turquoise4' => '00868b',
+ 'violet' => 'ee82ee',
+ 'violetred' => 'd02090',
+ 'violetred1' => 'ff3e96',
+ 'violetred2' => 'ee3a8c',
+ 'violetred3' => 'cd3278',
+ 'violetred4' => '8b2252',
+ 'wheat' => 'f5deb3',
+ 'wheat1' => 'ffe7ba',
+ 'wheat2' => 'eed8ae',
+ 'wheat3' => 'cdba96',
+ 'wheat4' => '8b7e66',
+ 'white' => 'ffffff',
+ 'whitesmoke' => 'f5f5f5',
+ 'yellow' => 'ffff00',
+ 'yellow1' => 'ffff00',
+ 'yellow2' => 'eeee00',
+ 'yellow3' => 'cdcd00',
+ 'yellow4' => '8b8b00',
+ 'yellowgreen' => '9acd32',
+ ];
+
+ protected $face;
+
+ protected $size;
+
+ protected $color;
+
+ protected $bold = false;
+
+ protected $italic = false;
+
+ protected $underline = false;
+
+ protected $superscript = false;
+
+ protected $subscript = false;
+
+ protected $strikethrough = false;
+
+ protected $startTagCallbacks = [
+ 'font' => 'startFontTag',
+ 'b' => 'startBoldTag',
+ 'strong' => 'startBoldTag',
+ 'i' => 'startItalicTag',
+ 'em' => 'startItalicTag',
+ 'u' => 'startUnderlineTag',
+ 'ins' => 'startUnderlineTag',
+ 'del' => 'startStrikethruTag',
+ 'sup' => 'startSuperscriptTag',
+ 'sub' => 'startSubscriptTag',
+ ];
+
+ protected $endTagCallbacks = [
+ 'font' => 'endFontTag',
+ 'b' => 'endBoldTag',
+ 'strong' => 'endBoldTag',
+ 'i' => 'endItalicTag',
+ 'em' => 'endItalicTag',
+ 'u' => 'endUnderlineTag',
+ 'ins' => 'endUnderlineTag',
+ 'del' => 'endStrikethruTag',
+ 'sup' => 'endSuperscriptTag',
+ 'sub' => 'endSubscriptTag',
+ 'br' => 'breakTag',
+ 'p' => 'breakTag',
+ 'h1' => 'breakTag',
+ 'h2' => 'breakTag',
+ 'h3' => 'breakTag',
+ 'h4' => 'breakTag',
+ 'h5' => 'breakTag',
+ 'h6' => 'breakTag',
+ ];
+
+ protected $stack = [];
+
+ protected $stringData = '';
+
+ /**
+ * @var RichText
+ */
+ protected $richTextObject;
+
+ protected function initialise(): void
+ {
+ $this->face = $this->size = $this->color = null;
+ $this->bold = $this->italic = $this->underline = $this->superscript = $this->subscript = $this->strikethrough = false;
+
+ $this->stack = [];
+
+ $this->stringData = '';
+ }
+
+ /**
+ * Parse HTML formatting and return the resulting RichText.
+ *
+ * @param string $html
+ *
+ * @return RichText
+ */
+ public function toRichTextObject($html)
+ {
+ $this->initialise();
+
+ // Create a new DOM object
+ $dom = new DOMDocument();
+ // Load the HTML file into the DOM object
+ // Note the use of error suppression, because typically this will be an html fragment, so not fully valid markup
+ $prefix = '';
+ @$dom->loadHTML($prefix . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
+ // Discard excess white space
+ $dom->preserveWhiteSpace = false;
+
+ $this->richTextObject = new RichText();
+ $this->parseElements($dom);
+
+ // Clean any further spurious whitespace
+ $this->cleanWhitespace();
+
+ return $this->richTextObject;
+ }
+
+ protected function cleanWhitespace(): void
+ {
+ foreach ($this->richTextObject->getRichTextElements() as $key => $element) {
+ $text = $element->getText();
+ // Trim any leading spaces on the first run
+ if ($key == 0) {
+ $text = ltrim($text);
+ }
+ // Trim any spaces immediately after a line break
+ $text = preg_replace('/\n */mu', "\n", $text);
+ $element->setText($text);
+ }
+ }
+
+ protected function buildTextRun(): void
+ {
+ $text = $this->stringData;
+ if (trim($text) === '') {
+ return;
+ }
+
+ $richtextRun = $this->richTextObject->createTextRun($this->stringData);
+ if ($this->face) {
+ $richtextRun->getFont()->setName($this->face);
+ }
+ if ($this->size) {
+ $richtextRun->getFont()->setSize($this->size);
+ }
+ if ($this->color) {
+ $richtextRun->getFont()->setColor(new Color('ff' . $this->color));
+ }
+ if ($this->bold) {
+ $richtextRun->getFont()->setBold(true);
+ }
+ if ($this->italic) {
+ $richtextRun->getFont()->setItalic(true);
+ }
+ if ($this->underline) {
+ $richtextRun->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
+ }
+ if ($this->superscript) {
+ $richtextRun->getFont()->setSuperscript(true);
+ }
+ if ($this->subscript) {
+ $richtextRun->getFont()->setSubscript(true);
+ }
+ if ($this->strikethrough) {
+ $richtextRun->getFont()->setStrikethrough(true);
+ }
+ $this->stringData = '';
+ }
+
+ protected function rgbToColour(string $rgbValue): string
+ {
+ preg_match_all('/\d+/', $rgbValue, $values);
+ foreach ($values[0] as &$value) {
+ $value = str_pad(dechex($value), 2, '0', STR_PAD_LEFT);
+ }
+
+ return implode('', $values[0]);
+ }
+
+ public static function colourNameLookup(string $colorName): string
+ {
+ return self::$colourMap[$colorName] ?? '';
+ }
+
+ protected function startFontTag($tag): void
+ {
+ foreach ($tag->attributes as $attribute) {
+ $attributeName = strtolower($attribute->name);
+ $attributeValue = $attribute->value;
+
+ if ($attributeName == 'color') {
+ if (preg_match('/rgb\s*\(/', $attributeValue)) {
+ $this->$attributeName = $this->rgbToColour($attributeValue);
+ } elseif (strpos(trim($attributeValue), '#') === 0) {
+ $this->$attributeName = ltrim($attributeValue, '#');
+ } else {
+ $this->$attributeName = static::colourNameLookup($attributeValue);
+ }
+ } else {
+ $this->$attributeName = $attributeValue;
+ }
+ }
+ }
+
+ protected function endFontTag(): void
+ {
+ $this->face = $this->size = $this->color = null;
+ }
+
+ protected function startBoldTag(): void
+ {
+ $this->bold = true;
+ }
+
+ protected function endBoldTag(): void
+ {
+ $this->bold = false;
+ }
+
+ protected function startItalicTag(): void
+ {
+ $this->italic = true;
+ }
+
+ protected function endItalicTag(): void
+ {
+ $this->italic = false;
+ }
+
+ protected function startUnderlineTag(): void
+ {
+ $this->underline = true;
+ }
+
+ protected function endUnderlineTag(): void
+ {
+ $this->underline = false;
+ }
+
+ protected function startSubscriptTag(): void
+ {
+ $this->subscript = true;
+ }
+
+ protected function endSubscriptTag(): void
+ {
+ $this->subscript = false;
+ }
+
+ protected function startSuperscriptTag(): void
+ {
+ $this->superscript = true;
+ }
+
+ protected function endSuperscriptTag(): void
+ {
+ $this->superscript = false;
+ }
+
+ protected function startStrikethruTag(): void
+ {
+ $this->strikethrough = true;
+ }
+
+ protected function endStrikethruTag(): void
+ {
+ $this->strikethrough = false;
+ }
+
+ protected function breakTag(): void
+ {
+ $this->stringData .= "\n";
+ }
+
+ protected function parseTextNode(DOMText $textNode): void
+ {
+ $domText = preg_replace(
+ '/\s+/u',
+ ' ',
+ str_replace(["\r", "\n"], ' ', $textNode->nodeValue)
+ );
+ $this->stringData .= $domText;
+ $this->buildTextRun();
+ }
+
+ /**
+ * @param string $callbackTag
+ */
+ protected function handleCallback(DOMElement $element, $callbackTag, array $callbacks): void
+ {
+ if (isset($callbacks[$callbackTag])) {
+ $elementHandler = $callbacks[$callbackTag];
+ if (method_exists($this, $elementHandler)) {
+ call_user_func([$this, $elementHandler], $element);
+ }
+ }
+ }
+
+ protected function parseElementNode(DOMElement $element): void
+ {
+ $callbackTag = strtolower($element->nodeName);
+ $this->stack[] = $callbackTag;
+
+ $this->handleCallback($element, $callbackTag, $this->startTagCallbacks);
+
+ $this->parseElements($element);
+ array_pop($this->stack);
+
+ $this->handleCallback($element, $callbackTag, $this->endTagCallbacks);
+ }
+
+ protected function parseElements(DOMNode $element): void
+ {
+ foreach ($element->childNodes as $child) {
+ if ($child instanceof DOMText) {
+ $this->parseTextNode($child);
+ } elseif ($child instanceof DOMElement) {
+ $this->parseElementNode($child);
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php
new file mode 100644
index 0000000..c215847
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/IComparable.php
@@ -0,0 +1,13 @@
+ Reader\Xlsx::class,
+ 'Xls' => Reader\Xls::class,
+ 'Xml' => Reader\Xml::class,
+ 'Ods' => Reader\Ods::class,
+ 'Slk' => Reader\Slk::class,
+ 'Gnumeric' => Reader\Gnumeric::class,
+ 'Html' => Reader\Html::class,
+ 'Csv' => Reader\Csv::class,
+ ];
+
+ private static $writers = [
+ 'Xls' => Writer\Xls::class,
+ 'Xlsx' => Writer\Xlsx::class,
+ 'Ods' => Writer\Ods::class,
+ 'Csv' => Writer\Csv::class,
+ 'Html' => Writer\Html::class,
+ 'Tcpdf' => Writer\Pdf\Tcpdf::class,
+ 'Dompdf' => Writer\Pdf\Dompdf::class,
+ 'Mpdf' => Writer\Pdf\Mpdf::class,
+ ];
+
+ /**
+ * Create Writer\IWriter.
+ */
+ public static function createWriter(Spreadsheet $spreadsheet, string $writerType): IWriter
+ {
+ if (!isset(self::$writers[$writerType])) {
+ throw new Writer\Exception("No writer found for type $writerType");
+ }
+
+ // Instantiate writer
+ $className = self::$writers[$writerType];
+
+ return new $className($spreadsheet);
+ }
+
+ /**
+ * Create IReader.
+ */
+ public static function createReader(string $readerType): IReader
+ {
+ if (!isset(self::$readers[$readerType])) {
+ throw new Reader\Exception("No reader found for type $readerType");
+ }
+
+ // Instantiate reader
+ $className = self::$readers[$readerType];
+
+ return new $className();
+ }
+
+ /**
+ * Loads Spreadsheet from file using automatic Reader\IReader resolution.
+ *
+ * @param string $filename The name of the spreadsheet file
+ */
+ public static function load(string $filename, int $flags = 0): Spreadsheet
+ {
+ $reader = self::createReaderForFile($filename);
+
+ return $reader->load($filename, $flags);
+ }
+
+ /**
+ * Identify file type using automatic IReader resolution.
+ */
+ public static function identify(string $filename): string
+ {
+ $reader = self::createReaderForFile($filename);
+ $className = get_class($reader);
+ $classType = explode('\\', $className);
+ unset($reader);
+
+ return array_pop($classType);
+ }
+
+ /**
+ * Create Reader\IReader for file using automatic IReader resolution.
+ */
+ public static function createReaderForFile(string $filename): IReader
+ {
+ File::assertFile($filename);
+
+ // First, lucky guess by inspecting file extension
+ $guessedReader = self::getReaderTypeFromExtension($filename);
+ if ($guessedReader !== null) {
+ $reader = self::createReader($guessedReader);
+
+ // Let's see if we are lucky
+ if ($reader->canRead($filename)) {
+ return $reader;
+ }
+ }
+
+ // If we reach here then "lucky guess" didn't give any result
+ // Try walking through all the options in self::$autoResolveClasses
+ foreach (self::$readers as $type => $class) {
+ // Ignore our original guess, we know that won't work
+ if ($type !== $guessedReader) {
+ $reader = self::createReader($type);
+ if ($reader->canRead($filename)) {
+ return $reader;
+ }
+ }
+ }
+
+ throw new Reader\Exception('Unable to identify a reader for this file');
+ }
+
+ /**
+ * Guess a reader type from the file extension, if any.
+ */
+ private static function getReaderTypeFromExtension(string $filename): ?string
+ {
+ $pathinfo = pathinfo($filename);
+ if (!isset($pathinfo['extension'])) {
+ return null;
+ }
+
+ switch (strtolower($pathinfo['extension'])) {
+ case 'xlsx': // Excel (OfficeOpenXML) Spreadsheet
+ case 'xlsm': // Excel (OfficeOpenXML) Macro Spreadsheet (macros will be discarded)
+ case 'xltx': // Excel (OfficeOpenXML) Template
+ case 'xltm': // Excel (OfficeOpenXML) Macro Template (macros will be discarded)
+ return 'Xlsx';
+ case 'xls': // Excel (BIFF) Spreadsheet
+ case 'xlt': // Excel (BIFF) Template
+ return 'Xls';
+ case 'ods': // Open/Libre Offic Calc
+ case 'ots': // Open/Libre Offic Calc Template
+ return 'Ods';
+ case 'slk':
+ return 'Slk';
+ case 'xml': // Excel 2003 SpreadSheetML
+ return 'Xml';
+ case 'gnumeric':
+ return 'Gnumeric';
+ case 'htm':
+ case 'html':
+ return 'Html';
+ case 'csv':
+ // Do nothing
+ // We must not try to use CSV reader since it loads
+ // all files including Excel files etc.
+ return null;
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Register a writer with its type and class name.
+ */
+ public static function registerWriter(string $writerType, string $writerClass): void
+ {
+ if (!is_a($writerClass, IWriter::class, true)) {
+ throw new Writer\Exception('Registered writers must implement ' . IWriter::class);
+ }
+
+ self::$writers[$writerType] = $writerClass;
+ }
+
+ /**
+ * Register a reader with its type and class name.
+ */
+ public static function registerReader(string $readerType, string $readerClass): void
+ {
+ if (!is_a($readerClass, IReader::class, true)) {
+ throw new Reader\Exception('Registered readers must implement ' . IReader::class);
+ }
+
+ self::$readers[$readerType] = $readerClass;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php
new file mode 100644
index 0000000..500151f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedFormula.php
@@ -0,0 +1,45 @@
+value;
+ }
+
+ /**
+ * Set the formula value.
+ */
+ public function setFormula(string $formula): self
+ {
+ if (!empty($formula)) {
+ $this->value = $formula;
+ }
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php
new file mode 100644
index 0000000..db9c5f1
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/NamedRange.php
@@ -0,0 +1,55 @@
+value;
+ }
+
+ /**
+ * Set the range value.
+ */
+ public function setRange(string $range): self
+ {
+ if (!empty($range)) {
+ $this->value = $range;
+ }
+
+ return $this;
+ }
+
+ public function getCellsInRange(): array
+ {
+ $range = $this->value;
+ if (substr($range, 0, 1) === '=') {
+ $range = substr($range, 1);
+ }
+
+ return Coordinate::extractAllCellReferencesInRange($range);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Exception.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Exception.php
new file mode 100644
index 0000000..ddbc9b5
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Exception.php
@@ -0,0 +1,9 @@
+ [
+ '10' => DataType::TYPE_NULL,
+ '20' => DataType::TYPE_BOOL,
+ '30' => DataType::TYPE_NUMERIC, // Integer doesn't exist in Excel
+ '40' => DataType::TYPE_NUMERIC, // Float
+ '50' => DataType::TYPE_ERROR,
+ '60' => DataType::TYPE_STRING,
+ //'70': // Cell Range
+ //'80': // Array
+ ],
+ ];
+
+ /**
+ * Create a new Gnumeric.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->referenceHelper = ReferenceHelper::getInstance();
+ $this->securityScanner = XmlScanner::getInstance($this);
+ }
+
+ /**
+ * Can the current IReader read the file?
+ */
+ 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);
+ }
+ }
+
+ return isset($data) && $data === chr(0x1F) . chr(0x8B);
+ }
+
+ private static function matchXml(XMLReader $xml, string $expectedLocalName): bool
+ {
+ return $xml->namespaceURI === self::NAMESPACE_GNM
+ && $xml->localName === $expectedLocalName
+ && $xml->nodeType === XMLReader::ELEMENT;
+ }
+
+ /**
+ * Reads names of the worksheets from a file, without parsing the whole file to a Spreadsheet object.
+ *
+ * @param string $filename
+ *
+ * @return array
+ */
+ public function listWorksheetNames($filename)
+ {
+ File::assertFile($filename);
+
+ $xml = new XMLReader();
+ $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($filename)), null, Settings::getLibXmlLoaderOptions());
+ $xml->setParserProperty(2, true);
+
+ $worksheetNames = [];
+ while ($xml->read()) {
+ if (self::matchXml($xml, 'SheetName')) {
+ $xml->read(); // Move onto the value node
+ $worksheetNames[] = (string) $xml->value;
+ } elseif (self::matchXml($xml, 'Sheets')) {
+ // break out of the loop once we've got our sheet names rather than parse the entire file
+ break;
+ }
+ }
+
+ return $worksheetNames;
+ }
+
+ /**
+ * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
+ *
+ * @param string $filename
+ *
+ * @return array
+ */
+ public function listWorksheetInfo($filename)
+ {
+ File::assertFile($filename);
+
+ $xml = new XMLReader();
+ $xml->xml($this->securityScanner->scanFile('compress.zlib://' . realpath($filename)), null, Settings::getLibXmlLoaderOptions());
+ $xml->setParserProperty(2, true);
+
+ $worksheetInfo = [];
+ while ($xml->read()) {
+ if (self::matchXml($xml, 'Sheet')) {
+ $tmpInfo = [
+ 'worksheetName' => '',
+ 'lastColumnLetter' => 'A',
+ 'lastColumnIndex' => 0,
+ 'totalRows' => 0,
+ 'totalColumns' => 0,
+ ];
+
+ while ($xml->read()) {
+ if (self::matchXml($xml, 'Name')) {
+ $xml->read(); // Move onto the value node
+ $tmpInfo['worksheetName'] = (string) $xml->value;
+ } elseif (self::matchXml($xml, 'MaxCol')) {
+ $xml->read(); // Move onto the value node
+ $tmpInfo['lastColumnIndex'] = (int) $xml->value;
+ $tmpInfo['totalColumns'] = (int) $xml->value + 1;
+ } elseif (self::matchXml($xml, 'MaxRow')) {
+ $xml->read(); // Move onto the value node
+ $tmpInfo['totalRows'] = (int) $xml->value + 1;
+
+ break;
+ }
+ }
+ $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
+ $worksheetInfo[] = $tmpInfo;
+ }
+ }
+
+ return $worksheetInfo;
+ }
+
+ /**
+ * @param string $filename
+ *
+ * @return string
+ */
+ private function gzfileGetContents($filename)
+ {
+ $file = @gzopen($filename, 'rb');
+ $data = '';
+ if ($file !== false) {
+ while (!gzeof($file)) {
+ $data .= gzread($file, 1024);
+ }
+ gzclose($file);
+ }
+
+ return $data;
+ }
+
+ public static function gnumericMappings(): array
+ {
+ return array_merge(self::$mappings, Styles::$mappings);
+ }
+
+ private function processComments(SimpleXMLElement $sheet): void
+ {
+ if ((!$this->readDataOnly) && (isset($sheet->Objects))) {
+ foreach ($sheet->Objects->children(self::NAMESPACE_GNM) as $key => $comment) {
+ $commentAttributes = $comment->attributes();
+ // Only comment objects are handled at the moment
+ if ($commentAttributes && $commentAttributes->Text) {
+ $this->spreadsheet->getActiveSheet()->getComment((string) $commentAttributes->ObjectBound)
+ ->setAuthor((string) $commentAttributes->Author)
+ ->setText($this->parseRichText((string) $commentAttributes->Text));
+ }
+ }
+ }
+ }
+
+ /**
+ * @param mixed $value
+ */
+ private static function testSimpleXml($value): SimpleXMLElement
+ {
+ return ($value instanceof SimpleXMLElement) ? $value : new SimpleXMLElement('');
+ }
+
+ /**
+ * Loads Spreadsheet from file.
+ *
+ * @return Spreadsheet
+ */
+ public function load(string $filename, int $flags = 0)
+ {
+ $this->processFlags($flags);
+
+ // Create new Spreadsheet
+ $spreadsheet = new Spreadsheet();
+ $spreadsheet->removeSheetByIndex(0);
+
+ // Load into this instance
+ return $this->loadIntoExisting($filename, $spreadsheet);
+ }
+
+ /**
+ * Loads from file into Spreadsheet instance.
+ */
+ public function loadIntoExisting(string $filename, Spreadsheet $spreadsheet): Spreadsheet
+ {
+ $this->spreadsheet = $spreadsheet;
+ File::assertFile($filename);
+
+ $gFileData = $this->gzfileGetContents($filename);
+
+ $xml2 = simplexml_load_string($this->securityScanner->scan($gFileData), 'SimpleXMLElement', Settings::getLibXmlLoaderOptions());
+ $xml = self::testSimpleXml($xml2);
+
+ $gnmXML = $xml->children(self::NAMESPACE_GNM);
+ (new Properties($this->spreadsheet))->readProperties($xml, $gnmXML);
+
+ $worksheetID = 0;
+ foreach ($gnmXML->Sheets->Sheet as $sheetOrNull) {
+ $sheet = self::testSimpleXml($sheetOrNull);
+ $worksheetName = (string) $sheet->Name;
+ if (is_array($this->loadSheetsOnly) && !in_array($worksheetName, $this->loadSheetsOnly, true)) {
+ continue;
+ }
+
+ $maxRow = $maxCol = 0;
+
+ // Create new Worksheet
+ $this->spreadsheet->createSheet();
+ $this->spreadsheet->setActiveSheetIndex($worksheetID);
+ // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula
+ // cells... during the load, all formulae should be correct, and we're simply bringing the worksheet
+ // name in line with the formula, not the reverse
+ $this->spreadsheet->getActiveSheet()->setTitle($worksheetName, false, false);
+
+ if (!$this->readDataOnly) {
+ (new PageSetup($this->spreadsheet))
+ ->printInformation($sheet)
+ ->sheetMargins($sheet);
+ }
+
+ foreach ($sheet->Cells->Cell as $cellOrNull) {
+ $cell = self::testSimpleXml($cellOrNull);
+ $cellAttributes = self::testSimpleXml($cell->attributes());
+ $row = (int) $cellAttributes->Row + 1;
+ $column = (int) $cellAttributes->Col;
+
+ if ($row > $maxRow) {
+ $maxRow = $row;
+ }
+ if ($column > $maxCol) {
+ $maxCol = $column;
+ }
+
+ $column = Coordinate::stringFromColumnIndex($column + 1);
+
+ // Read cell?
+ if ($this->getReadFilter() !== null) {
+ if (!$this->getReadFilter()->readCell($column, $row, $worksheetName)) {
+ continue;
+ }
+ }
+
+ $ValueType = $cellAttributes->ValueType;
+ $ExprID = (string) $cellAttributes->ExprID;
+ $type = DataType::TYPE_FORMULA;
+ if ($ExprID > '') {
+ if (((string) $cell) > '') {
+ $this->expressions[$ExprID] = [
+ 'column' => $cellAttributes->Col,
+ 'row' => $cellAttributes->Row,
+ 'formula' => (string) $cell,
+ ];
+ } else {
+ $expression = $this->expressions[$ExprID];
+
+ $cell = $this->referenceHelper->updateFormulaReferences(
+ $expression['formula'],
+ 'A1',
+ $cellAttributes->Col - $expression['column'],
+ $cellAttributes->Row - $expression['row'],
+ $worksheetName
+ );
+ }
+ $type = DataType::TYPE_FORMULA;
+ } else {
+ $vtype = (string) $ValueType;
+ if (array_key_exists($vtype, self::$mappings['dataType'])) {
+ $type = self::$mappings['dataType'][$vtype];
+ }
+ if ($vtype === '20') { // Boolean
+ $cell = $cell == 'TRUE';
+ }
+ }
+ $this->spreadsheet->getActiveSheet()->getCell($column . $row)->setValueExplicit((string) $cell, $type);
+ }
+
+ if ($sheet->Styles !== null) {
+ (new Styles($this->spreadsheet, $this->readDataOnly))->read($sheet, $maxRow, $maxCol);
+ }
+
+ $this->processComments($sheet);
+ $this->processColumnWidths($sheet, $maxCol);
+ $this->processRowHeights($sheet, $maxRow);
+ $this->processMergedCells($sheet);
+ $this->processAutofilter($sheet);
+
+ ++$worksheetID;
+ }
+
+ $this->processDefinedNames($gnmXML);
+
+ // Return
+ return $this->spreadsheet;
+ }
+
+ private function processMergedCells(?SimpleXMLElement $sheet): void
+ {
+ // Handle Merged Cells in this worksheet
+ if ($sheet !== null && isset($sheet->MergedRegions)) {
+ foreach ($sheet->MergedRegions->Merge as $mergeCells) {
+ if (strpos((string) $mergeCells, ':') !== false) {
+ $this->spreadsheet->getActiveSheet()->mergeCells($mergeCells);
+ }
+ }
+ }
+ }
+
+ private function processAutofilter(?SimpleXMLElement $sheet): void
+ {
+ if ($sheet !== null && isset($sheet->Filters)) {
+ foreach ($sheet->Filters->Filter as $autofilter) {
+ if ($autofilter !== null) {
+ $attributes = $autofilter->attributes();
+ if (isset($attributes['Area'])) {
+ $this->spreadsheet->getActiveSheet()->setAutoFilter((string) $attributes['Area']);
+ }
+ }
+ }
+ }
+ }
+
+ private function setColumnWidth(int $whichColumn, float $defaultWidth): void
+ {
+ $columnDimension = $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($whichColumn + 1));
+ if ($columnDimension !== null) {
+ $columnDimension->setWidth($defaultWidth);
+ }
+ }
+
+ private function setColumnInvisible(int $whichColumn): void
+ {
+ $columnDimension = $this->spreadsheet->getActiveSheet()->getColumnDimension(Coordinate::stringFromColumnIndex($whichColumn + 1));
+ if ($columnDimension !== null) {
+ $columnDimension->setVisible(false);
+ }
+ }
+
+ private function processColumnLoop(int $whichColumn, int $maxCol, ?SimpleXMLElement $columnOverride, float $defaultWidth): int
+ {
+ $columnOverride = self::testSimpleXml($columnOverride);
+ $columnAttributes = self::testSimpleXml($columnOverride->attributes());
+ $column = $columnAttributes['No'];
+ $columnWidth = ((float) $columnAttributes['Unit']) / 5.4;
+ $hidden = (isset($columnAttributes['Hidden'])) && ((string) $columnAttributes['Hidden'] == '1');
+ $columnCount = (int) ($columnAttributes['Count'] ?? 1);
+ while ($whichColumn < $column) {
+ $this->setColumnWidth($whichColumn, $defaultWidth);
+ ++$whichColumn;
+ }
+ while (($whichColumn < ($column + $columnCount)) && ($whichColumn <= $maxCol)) {
+ $this->setColumnWidth($whichColumn, $columnWidth);
+ if ($hidden) {
+ $this->setColumnInvisible($whichColumn);
+ }
+ ++$whichColumn;
+ }
+
+ return $whichColumn;
+ }
+
+ private function processColumnWidths(?SimpleXMLElement $sheet, int $maxCol): void
+ {
+ if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Cols))) {
+ // Column Widths
+ $defaultWidth = 0;
+ $columnAttributes = $sheet->Cols->attributes();
+ if ($columnAttributes !== null) {
+ $defaultWidth = $columnAttributes['DefaultSizePts'] / 5.4;
+ }
+ $whichColumn = 0;
+ foreach ($sheet->Cols->ColInfo as $columnOverride) {
+ $whichColumn = $this->processColumnLoop($whichColumn, $maxCol, $columnOverride, $defaultWidth);
+ }
+ while ($whichColumn <= $maxCol) {
+ $this->setColumnWidth($whichColumn, $defaultWidth);
+ ++$whichColumn;
+ }
+ }
+ }
+
+ private function setRowHeight(int $whichRow, float $defaultHeight): void
+ {
+ $rowDimension = $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow);
+ if ($rowDimension !== null) {
+ $rowDimension->setRowHeight($defaultHeight);
+ }
+ }
+
+ private function setRowInvisible(int $whichRow): void
+ {
+ $rowDimension = $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow);
+ if ($rowDimension !== null) {
+ $rowDimension->setVisible(false);
+ }
+ }
+
+ private function processRowLoop(int $whichRow, int $maxRow, ?SimpleXMLElement $rowOverride, float $defaultHeight): int
+ {
+ $rowOverride = self::testSimpleXml($rowOverride);
+ $rowAttributes = self::testSimpleXml($rowOverride->attributes());
+ $row = $rowAttributes['No'];
+ $rowHeight = (float) $rowAttributes['Unit'];
+ $hidden = (isset($rowAttributes['Hidden'])) && ((string) $rowAttributes['Hidden'] == '1');
+ $rowCount = (int) ($rowAttributes['Count'] ?? 1);
+ while ($whichRow < $row) {
+ ++$whichRow;
+ $this->setRowHeight($whichRow, $defaultHeight);
+ }
+ while (($whichRow < ($row + $rowCount)) && ($whichRow < $maxRow)) {
+ ++$whichRow;
+ $this->setRowHeight($whichRow, $rowHeight);
+ if ($hidden) {
+ $this->setRowInvisible($whichRow);
+ }
+ }
+
+ return $whichRow;
+ }
+
+ private function processRowHeights(?SimpleXMLElement $sheet, int $maxRow): void
+ {
+ if ((!$this->readDataOnly) && $sheet !== null && (isset($sheet->Rows))) {
+ // Row Heights
+ $defaultHeight = 0;
+ $rowAttributes = $sheet->Rows->attributes();
+ if ($rowAttributes !== null) {
+ $defaultHeight = (float) $rowAttributes['DefaultSizePts'];
+ }
+ $whichRow = 0;
+
+ foreach ($sheet->Rows->RowInfo as $rowOverride) {
+ $whichRow = $this->processRowLoop($whichRow, $maxRow, $rowOverride, $defaultHeight);
+ }
+ // never executed, I can't figure out any circumstances
+ // under which it would be executed, and, even if
+ // such exist, I'm not convinced this is needed.
+ //while ($whichRow < $maxRow) {
+ // ++$whichRow;
+ // $this->spreadsheet->getActiveSheet()->getRowDimension($whichRow)->setRowHeight($defaultHeight);
+ //}
+ }
+ }
+
+ private function processDefinedNames(?SimpleXMLElement $gnmXML): void
+ {
+ // Loop through definedNames (global named ranges)
+ if ($gnmXML !== null && isset($gnmXML->Names)) {
+ foreach ($gnmXML->Names->Name as $definedName) {
+ $name = (string) $definedName->name;
+ $value = (string) $definedName->value;
+ if (stripos($value, '#REF!') !== false) {
+ continue;
+ }
+
+ [$worksheetName] = Worksheet::extractSheetTitle($value, true);
+ $worksheetName = trim($worksheetName, "'");
+ $worksheet = $this->spreadsheet->getSheetByName($worksheetName);
+ // Worksheet might still be null if we're only loading selected sheets rather than the full spreadsheet
+ if ($worksheet !== null) {
+ $this->spreadsheet->addDefinedName(DefinedName::createInstance($name, $worksheet, $value));
+ }
+ }
+ }
+ }
+
+ private function parseRichText(string $is): RichText
+ {
+ $value = new RichText();
+ $value->createText($is);
+
+ return $value;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php
new file mode 100644
index 0000000..5b501e0
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Gnumeric/PageSetup.php
@@ -0,0 +1,142 @@
+spreadsheet = $spreadsheet;
+ }
+
+ public function printInformation(SimpleXMLElement $sheet): self
+ {
+ if (isset($sheet->PrintInformation)) {
+ $printInformation = $sheet->PrintInformation[0];
+ if (!$printInformation) {
+ return $this;
+ }
+
+ $scale = (string) $printInformation->Scale->attributes()['percentage'];
+ $pageOrder = (string) $printInformation->order;
+ $orientation = (string) $printInformation->orientation;
+ $horizontalCentered = (string) $printInformation->hcenter->attributes()['value'];
+ $verticalCentered = (string) $printInformation->vcenter->attributes()['value'];
+
+ $this->spreadsheet->getActiveSheet()->getPageSetup()
+ ->setPageOrder($pageOrder === 'r_then_d' ? WorksheetPageSetup::PAGEORDER_OVER_THEN_DOWN : WorksheetPageSetup::PAGEORDER_DOWN_THEN_OVER)
+ ->setScale((int) $scale)
+ ->setOrientation($orientation ?? WorksheetPageSetup::ORIENTATION_DEFAULT)
+ ->setHorizontalCentered((bool) $horizontalCentered)
+ ->setVerticalCentered((bool) $verticalCentered);
+ }
+
+ return $this;
+ }
+
+ public function sheetMargins(SimpleXMLElement $sheet): self
+ {
+ if (isset($sheet->PrintInformation, $sheet->PrintInformation->Margins)) {
+ $marginSet = [
+ // Default Settings
+ 'top' => 0.75,
+ 'header' => 0.3,
+ 'left' => 0.7,
+ 'right' => 0.7,
+ 'bottom' => 0.75,
+ 'footer' => 0.3,
+ ];
+
+ $marginSet = $this->buildMarginSet($sheet, $marginSet);
+ $this->adjustMargins($marginSet);
+ }
+
+ return $this;
+ }
+
+ private function buildMarginSet(SimpleXMLElement $sheet, array $marginSet): array
+ {
+ foreach ($sheet->PrintInformation->Margins->children(Gnumeric::NAMESPACE_GNM) as $key => $margin) {
+ $marginAttributes = $margin->attributes();
+ $marginSize = ($marginAttributes['Points']) ?? 72; // Default is 72pt
+ // Convert value in points to inches
+ $marginSize = PageMargins::fromPoints((float) $marginSize);
+ $marginSet[$key] = $marginSize;
+ }
+
+ return $marginSet;
+ }
+
+ private function adjustMargins(array $marginSet): void
+ {
+ foreach ($marginSet as $key => $marginSize) {
+ // Gnumeric is quirky in the way it displays the header/footer values:
+ // header is actually the sum of top and header; footer is actually the sum of bottom and footer
+ // then top is actually the header value, and bottom is actually the footer value
+ switch ($key) {
+ case 'left':
+ case 'right':
+ $this->sheetMargin($key, $marginSize);
+
+ break;
+ case 'top':
+ $this->sheetMargin($key, $marginSet['header'] ?? 0);
+
+ break;
+ case 'bottom':
+ $this->sheetMargin($key, $marginSet['footer'] ?? 0);
+
+ break;
+ case 'header':
+ $this->sheetMargin($key, ($marginSet['top'] ?? 0) - $marginSize);
+
+ break;
+ case 'footer':
+ $this->sheetMargin($key, ($marginSet['bottom'] ?? 0) - $marginSize);
+
+ break;
+ }
+ }
+ }
+
+ private function sheetMargin(string $key, float $marginSize): void
+ {
+ switch ($key) {
+ case 'top':
+ $this->spreadsheet->getActiveSheet()->getPageMargins()->setTop($marginSize);
+
+ break;
+ case 'bottom':
+ $this->spreadsheet->getActiveSheet()->getPageMargins()->setBottom($marginSize);
+
+ break;
+ case 'left':
+ $this->spreadsheet->getActiveSheet()->getPageMargins()->setLeft($marginSize);
+
+ break;
+ case 'right':
+ $this->spreadsheet->getActiveSheet()->getPageMargins()->setRight($marginSize);
+
+ break;
+ case 'header':
+ $this->spreadsheet->getActiveSheet()->getPageMargins()->setHeader($marginSize);
+
+ break;
+ case 'footer':
+ $this->spreadsheet->getActiveSheet()->getPageMargins()->setFooter($marginSize);
+
+ break;
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php
new file mode 100644
index 0000000..c37f1c1
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Html.php
@@ -0,0 +1,1057 @@
+ [
+ 'font' => [
+ 'bold' => true,
+ 'size' => 24,
+ ],
+ ], // Bold, 24pt
+ 'h2' => [
+ 'font' => [
+ 'bold' => true,
+ 'size' => 18,
+ ],
+ ], // Bold, 18pt
+ 'h3' => [
+ 'font' => [
+ 'bold' => true,
+ 'size' => 13.5,
+ ],
+ ], // Bold, 13.5pt
+ 'h4' => [
+ 'font' => [
+ 'bold' => true,
+ 'size' => 12,
+ ],
+ ], // Bold, 12pt
+ 'h5' => [
+ 'font' => [
+ 'bold' => true,
+ 'size' => 10,
+ ],
+ ], // Bold, 10pt
+ 'h6' => [
+ 'font' => [
+ 'bold' => true,
+ 'size' => 7.5,
+ ],
+ ], // Bold, 7.5pt
+ 'a' => [
+ 'font' => [
+ 'underline' => true,
+ 'color' => [
+ 'argb' => Color::COLOR_BLUE,
+ ],
+ ],
+ ], // Blue underlined
+ 'hr' => [
+ 'borders' => [
+ 'bottom' => [
+ 'borderStyle' => Border::BORDER_THIN,
+ 'color' => [
+ Color::COLOR_BLACK,
+ ],
+ ],
+ ],
+ ], // Bottom border
+ 'strong' => [
+ 'font' => [
+ 'bold' => true,
+ ],
+ ], // Bold
+ 'b' => [
+ 'font' => [
+ 'bold' => true,
+ ],
+ ], // Bold
+ 'i' => [
+ 'font' => [
+ 'italic' => true,
+ ],
+ ], // Italic
+ 'em' => [
+ 'font' => [
+ 'italic' => true,
+ ],
+ ], // Italic
+ ];
+
+ /** @var array */
+ protected $rowspan = [];
+
+ /**
+ * Create a new HTML Reader instance.
+ */
+ public function __construct()
+ {
+ parent::__construct();
+ $this->securityScanner = XmlScanner::getInstance($this);
+ }
+
+ /**
+ * Validate that the current file is an HTML file.
+ */
+ public function canRead(string $filename): bool
+ {
+ // Check if file exists
+ try {
+ $this->openFile($filename);
+ } catch (Exception $e) {
+ return false;
+ }
+
+ $beginning = $this->readBeginning();
+ $startWithTag = self::startsWithTag($beginning);
+ $containsTags = self::containsTags($beginning);
+ $endsWithTag = self::endsWithTag($this->readEnding());
+
+ fclose($this->fileHandle);
+
+ return $startWithTag && $containsTags && $endsWithTag;
+ }
+
+ private function readBeginning(): string
+ {
+ fseek($this->fileHandle, 0);
+
+ return (string) fread($this->fileHandle, self::TEST_SAMPLE_SIZE);
+ }
+
+ private function readEnding(): string
+ {
+ $meta = stream_get_meta_data($this->fileHandle);
+ $filename = $meta['uri'];
+
+ $size = (int) filesize($filename);
+ if ($size === 0) {
+ return '';
+ }
+
+ $blockSize = self::TEST_SAMPLE_SIZE;
+ if ($size < $blockSize) {
+ $blockSize = $size;
+ }
+
+ fseek($this->fileHandle, $size - $blockSize);
+
+ return (string) fread($this->fileHandle, $blockSize);
+ }
+
+ private static function startsWithTag(string $data): bool
+ {
+ return '<' === substr(trim($data), 0, 1);
+ }
+
+ private static function endsWithTag(string $data): bool
+ {
+ return '>' === substr(trim($data), -1, 1);
+ }
+
+ private static function containsTags(string $data): bool
+ {
+ return strlen($data) !== strlen(strip_tags($data));
+ }
+
+ /**
+ * Loads Spreadsheet from file.
+ *
+ * @return Spreadsheet
+ */
+ public function load(string $filename, int $flags = 0)
+ {
+ $this->processFlags($flags);
+
+ // Create new Spreadsheet
+ $spreadsheet = new Spreadsheet();
+
+ // Load into this instance
+ return $this->loadIntoExisting($filename, $spreadsheet);
+ }
+
+ /**
+ * Set input encoding.
+ *
+ * @param string $inputEncoding Input encoding, eg: 'ANSI'
+ *
+ * @return $this
+ *
+ * @codeCoverageIgnore
+ *
+ * @deprecated no use is made of this property
+ */
+ public function setInputEncoding($inputEncoding)
+ {
+ $this->inputEncoding = $inputEncoding;
+
+ return $this;
+ }
+
+ /**
+ * Get input encoding.
+ *
+ * @return string
+ *
+ * @codeCoverageIgnore
+ *
+ * @deprecated no use is made of this property
+ */
+ public function getInputEncoding()
+ {
+ return $this->inputEncoding;
+ }
+
+ // Data Array used for testing only, should write to Spreadsheet object on completion of tests
+
+ /** @var array */
+ protected $dataArray = [];
+
+ /** @var int */
+ protected $tableLevel = 0;
+
+ /** @var array */
+ protected $nestedColumn = ['A'];
+
+ protected function setTableStartColumn(string $column): string
+ {
+ if ($this->tableLevel == 0) {
+ $column = 'A';
+ }
+ ++$this->tableLevel;
+ $this->nestedColumn[$this->tableLevel] = $column;
+
+ return $this->nestedColumn[$this->tableLevel];
+ }
+
+ protected function getTableStartColumn(): string
+ {
+ return $this->nestedColumn[$this->tableLevel];
+ }
+
+ protected function releaseTableStartColumn(): string
+ {
+ --$this->tableLevel;
+
+ return array_pop($this->nestedColumn);
+ }
+
+ /**
+ * Flush cell.
+ *
+ * @param string $column
+ * @param int|string $row
+ * @param mixed $cellContent
+ */
+ protected function flushCell(Worksheet $sheet, $column, $row, &$cellContent): 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);
+ $this->dataArray[$row][$column] = $cellContent;
+ }
+ } else {
+ // We have a Rich Text run
+ // TODO
+ $this->dataArray[$row][$column] = 'RICH TEXT: ' . $cellContent;
+ }
+ $cellContent = (string) '';
+ }
+
+ private function processDomElementBody(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child): void
+ {
+ $attributeArray = [];
+ foreach (($child->attributes ?? []) as $attribute) {
+ $attributeArray[$attribute->name] = $attribute->value;
+ }
+
+ if ($child->nodeName === 'body') {
+ $row = 1;
+ $column = 'A';
+ $cellContent = '';
+ $this->tableLevel = 0;
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ } else {
+ $this->processDomElementTitle($sheet, $row, $column, $cellContent, $child, $attributeArray);
+ }
+ }
+
+ private function processDomElementTitle(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
+ {
+ if ($child->nodeName === 'title') {
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ $sheet->setTitle($cellContent, true, true);
+ $cellContent = '';
+ } else {
+ $this->processDomElementSpanEtc($sheet, $row, $column, $cellContent, $child, $attributeArray);
+ }
+ }
+
+ private const SPAN_ETC = ['span', 'div', 'font', 'i', 'em', 'strong', 'b'];
+
+ private function processDomElementSpanEtc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
+ {
+ if (in_array((string) $child->nodeName, self::SPAN_ETC, true)) {
+ if (isset($attributeArray['class']) && $attributeArray['class'] === 'comment') {
+ $sheet->getComment($column . $row)
+ ->getText()
+ ->createTextRun($child->textContent);
+ } else {
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ }
+
+ if (isset($this->formats[$child->nodeName])) {
+ $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
+ }
+ } else {
+ $this->processDomElementHr($sheet, $row, $column, $cellContent, $child, $attributeArray);
+ }
+ }
+
+ 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);
+ ++$row;
+ if (isset($this->formats[$child->nodeName])) {
+ $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
+ }
+ ++$row;
+ }
+ // fall through to br
+ $this->processDomElementBr($sheet, $row, $column, $cellContent, $child, $attributeArray);
+ }
+
+ private function processDomElementBr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
+ {
+ if ($child->nodeName === 'br' || $child->nodeName === 'hr') {
+ if ($this->tableLevel > 0) {
+ // If we're inside a table, replace with a \n and set the cell to wrap
+ $cellContent .= "\n";
+ $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);
+ ++$row;
+ }
+ } else {
+ $this->processDomElementA($sheet, $row, $column, $cellContent, $child, $attributeArray);
+ }
+ }
+
+ private function processDomElementA(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
+ {
+ if ($child->nodeName === 'a') {
+ foreach ($attributeArray as $attributeName => $attributeValue) {
+ switch ($attributeName) {
+ case 'href':
+ $sheet->getCell($column . $row)->getHyperlink()->setUrl($attributeValue);
+ if (isset($this->formats[$child->nodeName])) {
+ $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
+ }
+
+ break;
+ case 'class':
+ if ($attributeValue === 'comment-indicator') {
+ break; // Ignore - it's just a red square.
+ }
+ }
+ }
+ // no idea why this should be needed
+ //$cellContent .= ' ';
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ } else {
+ $this->processDomElementH1Etc($sheet, $row, $column, $cellContent, $child, $attributeArray);
+ }
+ }
+
+ private const H1_ETC = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'ul', 'p'];
+
+ private function processDomElementH1Etc(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
+ {
+ if (in_array((string) $child->nodeName, self::H1_ETC, true)) {
+ if ($this->tableLevel > 0) {
+ // If we're inside a table, replace with a \n
+ $cellContent .= $cellContent ? "\n" : '';
+ $sheet->getStyle($column . $row)->getAlignment()->setWrapText(true);
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ } else {
+ if ($cellContent > '') {
+ $this->flushCell($sheet, $column, $row, $cellContent);
+ ++$row;
+ }
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ $this->flushCell($sheet, $column, $row, $cellContent);
+
+ if (isset($this->formats[$child->nodeName])) {
+ $sheet->getStyle($column . $row)->applyFromArray($this->formats[$child->nodeName]);
+ }
+
+ ++$row;
+ $column = 'A';
+ }
+ } else {
+ $this->processDomElementLi($sheet, $row, $column, $cellContent, $child, $attributeArray);
+ }
+ }
+
+ private function processDomElementLi(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
+ {
+ if ($child->nodeName === 'li') {
+ if ($this->tableLevel > 0) {
+ // If we're inside a table, replace with a \n
+ $cellContent .= $cellContent ? "\n" : '';
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ } else {
+ if ($cellContent > '') {
+ $this->flushCell($sheet, $column, $row, $cellContent);
+ }
+ ++$row;
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ $this->flushCell($sheet, $column, $row, $cellContent);
+ $column = 'A';
+ }
+ } else {
+ $this->processDomElementImg($sheet, $row, $column, $cellContent, $child, $attributeArray);
+ }
+ }
+
+ private function processDomElementImg(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
+ {
+ if ($child->nodeName === 'img') {
+ $this->insertImage($sheet, $column, $row, $attributeArray);
+ } else {
+ $this->processDomElementTable($sheet, $row, $column, $cellContent, $child, $attributeArray);
+ }
+ }
+
+ 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);
+ $column = $this->setTableStartColumn($column);
+ if ($this->tableLevel > 1 && $row > 1) {
+ --$row;
+ }
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ $column = $this->releaseTableStartColumn();
+ if ($this->tableLevel > 1) {
+ ++$column;
+ } else {
+ ++$row;
+ }
+ } else {
+ $this->processDomElementTr($sheet, $row, $column, $cellContent, $child, $attributeArray);
+ }
+ }
+
+ private function processDomElementTr(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
+ {
+ if ($child->nodeName === 'tr') {
+ $column = $this->getTableStartColumn();
+ $cellContent = '';
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+
+ if (isset($attributeArray['height'])) {
+ $sheet->getRowDimension($row)->setRowHeight($attributeArray['height']);
+ }
+
+ ++$row;
+ } else {
+ $this->processDomElementThTdOther($sheet, $row, $column, $cellContent, $child, $attributeArray);
+ }
+ }
+
+ private function processDomElementThTdOther(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
+ {
+ if ($child->nodeName !== 'td' && $child->nodeName !== 'th') {
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+ } else {
+ $this->processDomElementThTd($sheet, $row, $column, $cellContent, $child, $attributeArray);
+ }
+ }
+
+ private function processDomElementBgcolor(Worksheet $sheet, int $row, string $column, array $attributeArray): void
+ {
+ if (isset($attributeArray['bgcolor'])) {
+ $sheet->getStyle("$column$row")->applyFromArray(
+ [
+ 'fill' => [
+ 'fillType' => Fill::FILL_SOLID,
+ 'color' => ['rgb' => $this->getStyleColor($attributeArray['bgcolor'])],
+ ],
+ ]
+ );
+ }
+ }
+
+ private function processDomElementWidth(Worksheet $sheet, string $column, array $attributeArray): void
+ {
+ if (isset($attributeArray['width'])) {
+ $sheet->getColumnDimension($column)->setWidth((new CssDimension($attributeArray['width']))->width());
+ }
+ }
+
+ private function processDomElementHeight(Worksheet $sheet, int $row, array $attributeArray): void
+ {
+ if (isset($attributeArray['height'])) {
+ $sheet->getRowDimension($row)->setRowHeight((new CssDimension($attributeArray['height']))->height());
+ }
+ }
+
+ private function processDomElementAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void
+ {
+ if (isset($attributeArray['align'])) {
+ $sheet->getStyle($column . $row)->getAlignment()->setHorizontal($attributeArray['align']);
+ }
+ }
+
+ private function processDomElementVAlign(Worksheet $sheet, int $row, string $column, array $attributeArray): void
+ {
+ if (isset($attributeArray['valign'])) {
+ $sheet->getStyle($column . $row)->getAlignment()->setVertical($attributeArray['valign']);
+ }
+ }
+
+ private function processDomElementDataFormat(Worksheet $sheet, int $row, string $column, array $attributeArray): void
+ {
+ if (isset($attributeArray['data-format'])) {
+ $sheet->getStyle($column . $row)->getNumberFormat()->setFormatCode($attributeArray['data-format']);
+ }
+ }
+
+ private function processDomElementThTd(Worksheet $sheet, int &$row, string &$column, string &$cellContent, DOMElement $child, array &$attributeArray): void
+ {
+ while (isset($this->rowspan[$column . $row])) {
+ ++$column;
+ }
+ $this->processDomElement($child, $sheet, $row, $column, $cellContent);
+
+ // apply inline style
+ $this->applyInlineStyle($sheet, $row, $column, $attributeArray);
+
+ $this->flushCell($sheet, $column, $row, $cellContent);
+
+ $this->processDomElementBgcolor($sheet, $row, $column, $attributeArray);
+ $this->processDomElementWidth($sheet, $column, $attributeArray);
+ $this->processDomElementHeight($sheet, $row, $attributeArray);
+ $this->processDomElementAlign($sheet, $row, $column, $attributeArray);
+ $this->processDomElementVAlign($sheet, $row, $column, $attributeArray);
+ $this->processDomElementDataFormat($sheet, $row, $column, $attributeArray);
+
+ if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
+ //create merging rowspan and colspan
+ $columnTo = $column;
+ for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
+ ++$columnTo;
+ }
+ $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1);
+ foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) {
+ $this->rowspan[$value] = true;
+ }
+ $sheet->mergeCells($range);
+ $column = $columnTo;
+ } elseif (isset($attributeArray['rowspan'])) {
+ //create merging rowspan
+ $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1);
+ foreach (Coordinate::extractAllCellReferencesInRange($range) as $value) {
+ $this->rowspan[$value] = true;
+ }
+ $sheet->mergeCells($range);
+ } elseif (isset($attributeArray['colspan'])) {
+ //create merging colspan
+ $columnTo = $column;
+ for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
+ ++$columnTo;
+ }
+ $sheet->mergeCells($column . $row . ':' . $columnTo . $row);
+ $column = $columnTo;
+ }
+
+ ++$column;
+ }
+
+ protected function processDomElement(DOMNode $element, Worksheet $sheet, int &$row, string &$column, string &$cellContent): void
+ {
+ foreach ($element->childNodes as $child) {
+ if ($child instanceof DOMText) {
+ $domText = preg_replace('/\s+/u', ' ', trim($child->nodeValue));
+ if (is_string($cellContent)) {
+ // simply append the text if the cell content is a plain text string
+ $cellContent .= $domText;
+ }
+ // but if we have a rich text run instead, we need to append it correctly
+ // TODO
+ } elseif ($child instanceof DOMElement) {
+ $this->processDomElementBody($sheet, $row, $column, $cellContent, $child);
+ }
+ }
+ }
+
+ /**
+ * Make sure mb_convert_encoding returns string.
+ *
+ * @param mixed $result
+ */
+ private static function ensureString($result): string
+ {
+ return is_string($result) ? $result : '';
+ }
+
+ /**
+ * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
+ *
+ * @param string $filename
+ *
+ * @return Spreadsheet
+ */
+ public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
+ {
+ // Validate
+ if (!$this->canRead($filename)) {
+ throw new Exception($filename . ' is an Invalid HTML file.');
+ }
+
+ // Create a new DOM object
+ $dom = new DOMDocument();
+ // Reload the HTML file into the DOM object
+ try {
+ $convert = mb_convert_encoding($this->securityScanner->scanFile($filename), 'HTML-ENTITIES', 'UTF-8');
+ $loaded = $dom->loadHTML(self::ensureString($convert));
+ } catch (Throwable $e) {
+ $loaded = false;
+ }
+ if ($loaded === false) {
+ throw new Exception('Failed to load ' . $filename . ' as a DOM Document', 0, $e ?? null);
+ }
+
+ return $this->loadDocument($dom, $spreadsheet);
+ }
+
+ /**
+ * Spreadsheet from content.
+ *
+ * @param string $content
+ */
+ public function loadFromString($content, ?Spreadsheet $spreadsheet = null): Spreadsheet
+ {
+ // Create a new DOM object
+ $dom = new DOMDocument();
+ // Reload the HTML file into the DOM object
+ try {
+ $convert = mb_convert_encoding($this->securityScanner->scan($content), 'HTML-ENTITIES', 'UTF-8');
+ $loaded = $dom->loadHTML(self::ensureString($convert));
+ } catch (Throwable $e) {
+ $loaded = false;
+ }
+ if ($loaded === false) {
+ throw new Exception('Failed to load content as a DOM Document', 0, $e ?? null);
+ }
+
+ return $this->loadDocument($dom, $spreadsheet ?? new Spreadsheet());
+ }
+
+ /**
+ * Loads PhpSpreadsheet from DOMDocument into PhpSpreadsheet instance.
+ */
+ private function loadDocument(DOMDocument $document, Spreadsheet $spreadsheet): Spreadsheet
+ {
+ while ($spreadsheet->getSheetCount() <= $this->sheetIndex) {
+ $spreadsheet->createSheet();
+ }
+ $spreadsheet->setActiveSheetIndex($this->sheetIndex);
+
+ // Discard white space
+ $document->preserveWhiteSpace = false;
+
+ $row = 0;
+ $column = 'A';
+ $content = '';
+ $this->rowspan = [];
+ $this->processDomElement($document, $spreadsheet->getActiveSheet(), $row, $column, $content);
+
+ // Return
+ return $spreadsheet;
+ }
+
+ /**
+ * Get sheet index.
+ *
+ * @return int
+ */
+ public function getSheetIndex()
+ {
+ return $this->sheetIndex;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param int $sheetIndex Sheet index
+ *
+ * @return $this
+ */
+ public function setSheetIndex($sheetIndex)
+ {
+ $this->sheetIndex = $sheetIndex;
+
+ return $this;
+ }
+
+ /**
+ * Apply inline css inline style.
+ *
+ * NOTES :
+ * Currently only intended for td & th element,
+ * and only takes 'background-color' and 'color'; property with HEX color
+ *
+ * TODO :
+ * - Implement to other propertie, such as border
+ *
+ * @param int $row
+ * @param string $column
+ * @param array $attributeArray
+ */
+ private function applyInlineStyle(Worksheet &$sheet, $row, $column, $attributeArray): void
+ {
+ if (!isset($attributeArray['style'])) {
+ return;
+ }
+
+ if (isset($attributeArray['rowspan'], $attributeArray['colspan'])) {
+ $columnTo = $column;
+ for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
+ ++$columnTo;
+ }
+ $range = $column . $row . ':' . $columnTo . ($row + (int) $attributeArray['rowspan'] - 1);
+ $cellStyle = $sheet->getStyle($range);
+ } elseif (isset($attributeArray['rowspan'])) {
+ $range = $column . $row . ':' . $column . ($row + (int) $attributeArray['rowspan'] - 1);
+ $cellStyle = $sheet->getStyle($range);
+ } elseif (isset($attributeArray['colspan'])) {
+ $columnTo = $column;
+ for ($i = 0; $i < (int) $attributeArray['colspan'] - 1; ++$i) {
+ ++$columnTo;
+ }
+ $range = $column . $row . ':' . $columnTo . $row;
+ $cellStyle = $sheet->getStyle($range);
+ } else {
+ $cellStyle = $sheet->getStyle($column . $row);
+ }
+
+ // add color styles (background & text) from dom element,currently support : td & th, using ONLY inline css style with RGB color
+ $styles = explode(';', $attributeArray['style']);
+ foreach ($styles as $st) {
+ $value = explode(':', $st);
+ $styleName = isset($value[0]) ? trim($value[0]) : null;
+ $styleValue = isset($value[1]) ? trim($value[1]) : null;
+ $styleValueString = (string) $styleValue;
+
+ if (!$styleName) {
+ continue;
+ }
+
+ switch ($styleName) {
+ case 'background':
+ case 'background-color':
+ $styleColor = $this->getStyleColor($styleValueString);
+
+ if (!$styleColor) {
+ continue 2;
+ }
+
+ $cellStyle->applyFromArray(['fill' => ['fillType' => Fill::FILL_SOLID, 'color' => ['rgb' => $styleColor]]]);
+
+ break;
+ case 'color':
+ $styleColor = $this->getStyleColor($styleValueString);
+
+ if (!$styleColor) {
+ continue 2;
+ }
+
+ $cellStyle->applyFromArray(['font' => ['color' => ['rgb' => $styleColor]]]);
+
+ break;
+
+ case 'border':
+ $this->setBorderStyle($cellStyle, $styleValueString, 'allBorders');
+
+ break;
+
+ case 'border-top':
+ $this->setBorderStyle($cellStyle, $styleValueString, 'top');
+
+ break;
+
+ case 'border-bottom':
+ $this->setBorderStyle($cellStyle, $styleValueString, 'bottom');
+
+ break;
+
+ case 'border-left':
+ $this->setBorderStyle($cellStyle, $styleValueString, 'left');
+
+ break;
+
+ case 'border-right':
+ $this->setBorderStyle($cellStyle, $styleValueString, 'right');
+
+ break;
+
+ case 'font-size':
+ $cellStyle->getFont()->setSize(
+ (float) $styleValue
+ );
+
+ break;
+
+ case 'font-weight':
+ if ($styleValue === 'bold' || $styleValue >= 500) {
+ $cellStyle->getFont()->setBold(true);
+ }
+
+ break;
+
+ case 'font-style':
+ if ($styleValue === 'italic') {
+ $cellStyle->getFont()->setItalic(true);
+ }
+
+ break;
+
+ case 'font-family':
+ $cellStyle->getFont()->setName(str_replace('\'', '', $styleValueString));
+
+ break;
+
+ case 'text-decoration':
+ switch ($styleValue) {
+ case 'underline':
+ $cellStyle->getFont()->setUnderline(Font::UNDERLINE_SINGLE);
+
+ break;
+ case 'line-through':
+ $cellStyle->getFont()->setStrikethrough(true);
+
+ break;
+ }
+
+ break;
+
+ case 'text-align':
+ $cellStyle->getAlignment()->setHorizontal($styleValueString);
+
+ break;
+
+ case 'vertical-align':
+ $cellStyle->getAlignment()->setVertical($styleValueString);
+
+ break;
+
+ case 'width':
+ $sheet->getColumnDimension($column)->setWidth(
+ (new CssDimension($styleValue ?? ''))->width()
+ );
+
+ break;
+
+ case 'height':
+ $sheet->getRowDimension($row)->setRowHeight(
+ (new CssDimension($styleValue ?? ''))->height()
+ );
+
+ break;
+
+ case 'word-wrap':
+ $cellStyle->getAlignment()->setWrapText(
+ $styleValue === 'break-word'
+ );
+
+ break;
+
+ case 'text-indent':
+ $cellStyle->getAlignment()->setIndent(
+ (int) str_replace(['px'], '', $styleValueString)
+ );
+
+ break;
+ }
+ }
+ }
+
+ /**
+ * Check if has #, so we can get clean hex.
+ *
+ * @param mixed $value
+ *
+ * @return null|string
+ */
+ public function getStyleColor($value)
+ {
+ $value = (string) $value;
+ if (strpos($value ?? '', '#') === 0) {
+ return substr($value, 1);
+ }
+
+ return \PhpOffice\PhpSpreadsheet\Helper\Html::colourNameLookup($value);
+ }
+
+ /**
+ * @param string $column
+ * @param int $row
+ */
+ private function insertImage(Worksheet $sheet, $column, $row, array $attributes): void
+ {
+ if (!isset($attributes['src'])) {
+ return;
+ }
+
+ $src = urldecode($attributes['src']);
+ $width = isset($attributes['width']) ? (float) $attributes['width'] : null;
+ $height = isset($attributes['height']) ? (float) $attributes['height'] : null;
+ $name = $attributes['alt'] ?? null;
+
+ $drawing = new Drawing();
+ $drawing->setPath($src);
+ $drawing->setWorksheet($sheet);
+ $drawing->setCoordinates($column . $row);
+ $drawing->setOffsetX(0);
+ $drawing->setOffsetY(10);
+ $drawing->setResizeProportional(true);
+
+ if ($name) {
+ $drawing->setName($name);
+ }
+
+ if ($width) {
+ $drawing->setWidth((int) $width);
+ }
+
+ if ($height) {
+ $drawing->setHeight((int) $height);
+ }
+
+ $sheet->getColumnDimension($column)->setWidth(
+ $drawing->getWidth() / 6
+ );
+
+ $sheet->getRowDimension($row)->setRowHeight(
+ $drawing->getHeight() * 0.9
+ );
+ }
+
+ private const BORDER_MAPPINGS = [
+ 'dash-dot' => Border::BORDER_DASHDOT,
+ 'dash-dot-dot' => Border::BORDER_DASHDOTDOT,
+ 'dashed' => Border::BORDER_DASHED,
+ 'dotted' => Border::BORDER_DOTTED,
+ 'double' => Border::BORDER_DOUBLE,
+ 'hair' => Border::BORDER_HAIR,
+ 'medium' => Border::BORDER_MEDIUM,
+ 'medium-dashed' => Border::BORDER_MEDIUMDASHED,
+ 'medium-dash-dot' => Border::BORDER_MEDIUMDASHDOT,
+ 'medium-dash-dot-dot' => Border::BORDER_MEDIUMDASHDOTDOT,
+ 'none' => Border::BORDER_NONE,
+ 'slant-dash-dot' => Border::BORDER_SLANTDASHDOT,
+ 'solid' => Border::BORDER_THIN,
+ 'thick' => Border::BORDER_THICK,
+ ];
+
+ public static function getBorderMappings(): array
+ {
+ return self::BORDER_MAPPINGS;
+ }
+
+ /**
+ * Map html border style to PhpSpreadsheet border style.
+ *
+ * @param string $style
+ *
+ * @return null|string
+ */
+ public function getBorderStyle($style)
+ {
+ return self::BORDER_MAPPINGS[$style] ?? null;
+ }
+
+ /**
+ * @param string $styleValue
+ * @param string $type
+ */
+ private function setBorderStyle(Style $cellStyle, $styleValue, $type): void
+ {
+ if (trim($styleValue) === Border::BORDER_NONE) {
+ $borderStyle = Border::BORDER_NONE;
+ $color = null;
+ } else {
+ $borderArray = explode(' ', $styleValue);
+ $borderCount = count($borderArray);
+ if ($borderCount >= 3) {
+ $borderStyle = $borderArray[1];
+ $color = $borderArray[2];
+ } else {
+ $borderStyle = $borderArray[0];
+ $color = $borderArray[1] ?? null;
+ }
+ }
+
+ $cellStyle->applyFromArray([
+ 'borders' => [
+ $type => [
+ 'borderStyle' => $this->getBorderStyle($borderStyle),
+ 'color' => ['rgb' => $this->getStyleColor($color)],
+ ],
+ ],
+ ]);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php
new file mode 100644
index 0000000..9f68a7f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/IReadFilter.php
@@ -0,0 +1,17 @@
+securityScanner = XmlScanner::getInstance($this);
+ }
+
+ /**
+ * Can the current IReader read the file?
+ */
+ public function canRead(string $filename): bool
+ {
+ $mimeType = 'UNKNOWN';
+
+ // Load file
+
+ if (File::testFileNoThrow($filename, '')) {
+ $zip = new ZipArchive();
+ if ($zip->open($filename) === true) {
+ // check if it is an OOXML archive
+ $stat = $zip->statName('mimetype');
+ if ($stat && ($stat['size'] <= 255)) {
+ $mimeType = $zip->getFromName($stat['name']);
+ } elseif ($zip->statName('META-INF/manifest.xml')) {
+ $xml = simplexml_load_string(
+ $this->securityScanner->scan($zip->getFromName('META-INF/manifest.xml')),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ $namespacesContent = $xml->getNamespaces(true);
+ if (isset($namespacesContent['manifest'])) {
+ $manifest = $xml->children($namespacesContent['manifest']);
+ foreach ($manifest as $manifestDataSet) {
+ $manifestAttributes = $manifestDataSet->attributes($namespacesContent['manifest']);
+ if ($manifestAttributes && $manifestAttributes->{'full-path'} == '/') {
+ $mimeType = (string) $manifestAttributes->{'media-type'};
+
+ break;
+ }
+ }
+ }
+ }
+
+ $zip->close();
+ }
+ }
+
+ return $mimeType === 'application/vnd.oasis.opendocument.spreadsheet';
+ }
+
+ /**
+ * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object.
+ *
+ * @param string $filename
+ *
+ * @return string[]
+ */
+ public function listWorksheetNames($filename)
+ {
+ File::assertFile($filename, self::INITIAL_FILE);
+
+ $worksheetNames = [];
+
+ $xml = new XMLReader();
+ $xml->xml(
+ $this->securityScanner->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE),
+ null,
+ Settings::getLibXmlLoaderOptions()
+ );
+ $xml->setParserProperty(2, true);
+
+ // Step into the first level of content of the XML
+ $xml->read();
+ while ($xml->read()) {
+ // Quickly jump through to the office:body node
+ while ($xml->name !== 'office:body') {
+ if ($xml->isEmptyElement) {
+ $xml->read();
+ } else {
+ $xml->next();
+ }
+ }
+ // Now read each node until we find our first table:table node
+ while ($xml->read()) {
+ if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
+ // Loop through each table:table node reading the table:name attribute for each worksheet name
+ do {
+ $worksheetNames[] = $xml->getAttribute('table:name');
+ $xml->next();
+ } while ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT);
+ }
+ }
+ }
+
+ return $worksheetNames;
+ }
+
+ /**
+ * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns).
+ *
+ * @param string $filename
+ *
+ * @return array
+ */
+ public function listWorksheetInfo($filename)
+ {
+ File::assertFile($filename, self::INITIAL_FILE);
+
+ $worksheetInfo = [];
+
+ $xml = new XMLReader();
+ $xml->xml(
+ $this->securityScanner->scanFile('zip://' . realpath($filename) . '#' . self::INITIAL_FILE),
+ null,
+ Settings::getLibXmlLoaderOptions()
+ );
+ $xml->setParserProperty(2, true);
+
+ // Step into the first level of content of the XML
+ $xml->read();
+ while ($xml->read()) {
+ // Quickly jump through to the office:body node
+ while ($xml->name !== 'office:body') {
+ if ($xml->isEmptyElement) {
+ $xml->read();
+ } else {
+ $xml->next();
+ }
+ }
+ // Now read each node until we find our first table:table node
+ while ($xml->read()) {
+ if ($xml->name == 'table:table' && $xml->nodeType == XMLReader::ELEMENT) {
+ $worksheetNames[] = $xml->getAttribute('table:name');
+
+ $tmpInfo = [
+ 'worksheetName' => $xml->getAttribute('table:name'),
+ 'lastColumnLetter' => 'A',
+ 'lastColumnIndex' => 0,
+ 'totalRows' => 0,
+ 'totalColumns' => 0,
+ ];
+
+ // Loop through each child node of the table:table element reading
+ $currCells = 0;
+ do {
+ $xml->read();
+ if ($xml->name == 'table:table-row' && $xml->nodeType == XMLReader::ELEMENT) {
+ $rowspan = $xml->getAttribute('table:number-rows-repeated');
+ $rowspan = empty($rowspan) ? 1 : $rowspan;
+ $tmpInfo['totalRows'] += $rowspan;
+ $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
+ $currCells = 0;
+ // Step into the row
+ $xml->read();
+ do {
+ $doread = true;
+ if ($xml->name == 'table:table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
+ if (!$xml->isEmptyElement) {
+ ++$currCells;
+ $xml->next();
+ $doread = false;
+ }
+ } elseif ($xml->name == 'table:covered-table-cell' && $xml->nodeType == XMLReader::ELEMENT) {
+ $mergeSize = $xml->getAttribute('table:number-columns-repeated');
+ $currCells += (int) $mergeSize;
+ }
+ if ($doread) {
+ $xml->read();
+ }
+ } while ($xml->name != 'table:table-row');
+ }
+ } while ($xml->name != 'table:table');
+
+ $tmpInfo['totalColumns'] = max($tmpInfo['totalColumns'], $currCells);
+ $tmpInfo['lastColumnIndex'] = $tmpInfo['totalColumns'] - 1;
+ $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1);
+ $worksheetInfo[] = $tmpInfo;
+ }
+ }
+ }
+
+ return $worksheetInfo;
+ }
+
+ /**
+ * Loads PhpSpreadsheet from file.
+ *
+ * @return Spreadsheet
+ */
+ public function load(string $filename, int $flags = 0)
+ {
+ $this->processFlags($flags);
+
+ // Create new Spreadsheet
+ $spreadsheet = new Spreadsheet();
+
+ // Load into this instance
+ return $this->loadIntoExisting($filename, $spreadsheet);
+ }
+
+ /**
+ * Loads PhpSpreadsheet from file into PhpSpreadsheet instance.
+ *
+ * @param string $filename
+ *
+ * @return Spreadsheet
+ */
+ public function loadIntoExisting($filename, Spreadsheet $spreadsheet)
+ {
+ File::assertFile($filename, self::INITIAL_FILE);
+
+ $zip = new ZipArchive();
+ $zip->open($filename);
+
+ // Meta
+
+ $xml = @simplexml_load_string(
+ $this->securityScanner->scan($zip->getFromName('meta.xml')),
+ 'SimpleXMLElement',
+ Settings::getLibXmlLoaderOptions()
+ );
+ if ($xml === false) {
+ throw new Exception('Unable to read data from {$pFilename}');
+ }
+
+ $namespacesMeta = $xml->getNamespaces(true);
+
+ (new DocumentProperties($spreadsheet))->load($xml, $namespacesMeta);
+
+ // Styles
+
+ $dom = new DOMDocument('1.01', 'UTF-8');
+ $dom->loadXML(
+ $this->securityScanner->scan($zip->getFromName('styles.xml')),
+ Settings::getLibXmlLoaderOptions()
+ );
+
+ $pageSettings = new PageSettings($dom);
+
+ // Main Content
+
+ $dom = new DOMDocument('1.01', 'UTF-8');
+ $dom->loadXML(
+ $this->securityScanner->scan($zip->getFromName(self::INITIAL_FILE)),
+ Settings::getLibXmlLoaderOptions()
+ );
+
+ $officeNs = $dom->lookupNamespaceUri('office');
+ $tableNs = $dom->lookupNamespaceUri('table');
+ $textNs = $dom->lookupNamespaceUri('text');
+ $xlinkNs = $dom->lookupNamespaceUri('xlink');
+
+ $pageSettings->readStyleCrossReferences($dom);
+
+ $autoFilterReader = new AutoFilter($spreadsheet, $tableNs);
+ $definedNameReader = new DefinedNames($spreadsheet, $tableNs);
+
+ // Content
+ $spreadsheets = $dom->getElementsByTagNameNS($officeNs, 'body')
+ ->item(0)
+ ->getElementsByTagNameNS($officeNs, 'spreadsheet');
+
+ foreach ($spreadsheets as $workbookData) {
+ /** @var DOMElement $workbookData */
+ $tables = $workbookData->getElementsByTagNameNS($tableNs, 'table');
+
+ $worksheetID = 0;
+ foreach ($tables as $worksheetDataSet) {
+ /** @var DOMElement $worksheetDataSet */
+ $worksheetName = $worksheetDataSet->getAttributeNS($tableNs, 'name');
+
+ // Check loadSheetsOnly
+ if (
+ isset($this->loadSheetsOnly)
+ && $worksheetName
+ && !in_array($worksheetName, $this->loadSheetsOnly)
+ ) {
+ continue;
+ }
+
+ $worksheetStyleName = $worksheetDataSet->getAttributeNS($tableNs, 'style-name');
+
+ // Create sheet
+ if ($worksheetID > 0) {
+ $spreadsheet->createSheet(); // First sheet is added by default
+ }
+ $spreadsheet->setActiveSheetIndex($worksheetID);
+
+ if ($worksheetName) {
+ // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in
+ // formula cells... during the load, all formulae should be correct, and we're simply
+ // bringing the worksheet name in line with the formula, not the reverse
+ $spreadsheet->getActiveSheet()->setTitle((string) $worksheetName, false, false);
+ }
+
+ // Go through every child of table element
+ $rowID = 1;
+ foreach ($worksheetDataSet->childNodes as $childNode) {
+ /** @var DOMElement $childNode */
+
+ // Filter elements which are not under the "table" ns
+ if ($childNode->namespaceURI != $tableNs) {
+ continue;
+ }
+
+ $key = $childNode->nodeName;
+
+ // Remove ns from node name
+ if (strpos($key, ':') !== false) {
+ $keyChunks = explode(':', $key);
+ $key = array_pop($keyChunks);
+ }
+
+ switch ($key) {
+ case 'table-header-rows':
+ /// TODO :: Figure this out. This is only a partial implementation I guess.
+ // ($rowData it's not used at all and I'm not sure that PHPExcel
+ // has an API for this)
+
+// foreach ($rowData as $keyRowData => $cellData) {
+// $rowData = $cellData;
+// break;
+// }
+ break;
+ case 'table-row':
+ if ($childNode->hasAttributeNS($tableNs, 'number-rows-repeated')) {
+ $rowRepeats = (int) $childNode->getAttributeNS($tableNs, 'number-rows-repeated');
+ } else {
+ $rowRepeats = 1;
+ }
+
+ $columnID = 'A';
+ /** @var DOMElement $cellData */
+ foreach ($childNode->childNodes as $cellData) {
+ if ($this->getReadFilter() !== null) {
+ if (!$this->getReadFilter()->readCell($columnID, $rowID, $worksheetName)) {
+ ++$columnID;
+
+ continue;
+ }
+ }
+
+ // Initialize variables
+ $formatting = $hyperlink = null;
+ $hasCalculatedValue = false;
+ $cellDataFormula = '';
+
+ if ($cellData->hasAttributeNS($tableNs, 'formula')) {
+ $cellDataFormula = $cellData->getAttributeNS($tableNs, 'formula');
+ $hasCalculatedValue = true;
+ }
+
+ // Annotations
+ $annotation = $cellData->getElementsByTagNameNS($officeNs, 'annotation');
+
+ if ($annotation->length > 0) {
+ $textNode = $annotation->item(0)->getElementsByTagNameNS($textNs, 'p');
+
+ if ($textNode->length > 0) {
+ $text = $this->scanElementForText($textNode->item(0));
+
+ $spreadsheet->getActiveSheet()
+ ->getComment($columnID . $rowID)
+ ->setText($this->parseRichText($text));
+// ->setAuthor( $author )
+ }
+ }
+
+ // Content
+
+ /** @var DOMElement[] $paragraphs */
+ $paragraphs = [];
+
+ foreach ($cellData->childNodes as $item) {
+ /** @var DOMElement $item */
+
+ // Filter text:p elements
+ if ($item->nodeName == 'text:p') {
+ $paragraphs[] = $item;
+ }
+ }
+
+ if (count($paragraphs) > 0) {
+ // Consolidate if there are multiple p records (maybe with spans as well)
+ $dataArray = [];
+
+ // Text can have multiple text:p and within those, multiple text:span.
+ // text:p newlines, but text:span does not.
+ // Also, here we assume there is no text data is span fields are specified, since
+ // we have no way of knowing proper positioning anyway.
+
+ foreach ($paragraphs as $pData) {
+ $dataArray[] = $this->scanElementForText($pData);
+ }
+ $allCellDataText = implode("\n", $dataArray);
+
+ $type = $cellData->getAttributeNS($officeNs, 'value-type');
+
+ switch ($type) {
+ case 'string':
+ $type = DataType::TYPE_STRING;
+ $dataValue = $allCellDataText;
+
+ foreach ($paragraphs as $paragraph) {
+ $link = $paragraph->getElementsByTagNameNS($textNs, 'a');
+ if ($link->length > 0) {
+ $hyperlink = $link->item(0)->getAttributeNS($xlinkNs, 'href');
+ }
+ }
+
+ break;
+ case 'boolean':
+ $type = DataType::TYPE_BOOL;
+ $dataValue = ($allCellDataText == 'TRUE') ? true : false;
+
+ break;
+ case 'percentage':
+ $type = DataType::TYPE_NUMERIC;
+ $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
+
+ // percentage should always be float
+ //if (floor($dataValue) == $dataValue) {
+ // $dataValue = (int) $dataValue;
+ //}
+ $formatting = NumberFormat::FORMAT_PERCENTAGE_00;
+
+ break;
+ case 'currency':
+ $type = DataType::TYPE_NUMERIC;
+ $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
+
+ if (floor($dataValue) == $dataValue) {
+ $dataValue = (int) $dataValue;
+ }
+ $formatting = NumberFormat::FORMAT_CURRENCY_USD_SIMPLE;
+
+ break;
+ case 'float':
+ $type = DataType::TYPE_NUMERIC;
+ $dataValue = (float) $cellData->getAttributeNS($officeNs, 'value');
+
+ if (floor($dataValue) == $dataValue) {
+ if ($dataValue == (int) $dataValue) {
+ $dataValue = (int) $dataValue;
+ }
+ }
+
+ break;
+ case 'date':
+ $type = DataType::TYPE_NUMERIC;
+ $value = $cellData->getAttributeNS($officeNs, 'date-value');
+
+ $dateObj = new DateTime($value);
+ [$year, $month, $day, $hour, $minute, $second] = explode(
+ ' ',
+ $dateObj->format('Y m d H i s')
+ );
+
+ $dataValue = Date::formattedPHPToExcel(
+ (int) $year,
+ (int) $month,
+ (int) $day,
+ (int) $hour,
+ (int) $minute,
+ (int) $second
+ );
+
+ if ($dataValue != floor($dataValue)) {
+ $formatting = NumberFormat::FORMAT_DATE_XLSX15
+ . ' '
+ . NumberFormat::FORMAT_DATE_TIME4;
+ } else {
+ $formatting = NumberFormat::FORMAT_DATE_XLSX15;
+ }
+
+ break;
+ case 'time':
+ $type = DataType::TYPE_NUMERIC;
+
+ $timeValue = $cellData->getAttributeNS($officeNs, 'time-value');
+
+ $dataValue = Date::PHPToExcel(
+ strtotime(
+ '01-01-1970 ' . implode(':', sscanf($timeValue, 'PT%dH%dM%dS') ?? [])
+ )
+ );
+ $formatting = NumberFormat::FORMAT_DATE_TIME4;
+
+ break;
+ default:
+ $dataValue = null;
+ }
+ } else {
+ $type = DataType::TYPE_NULL;
+ $dataValue = null;
+ }
+
+ if ($hasCalculatedValue) {
+ $type = DataType::TYPE_FORMULA;
+ $cellDataFormula = substr($cellDataFormula, strpos($cellDataFormula, ':=') + 1);
+ $cellDataFormula = $this->convertToExcelFormulaValue($cellDataFormula);
+ }
+
+ if ($cellData->hasAttributeNS($tableNs, 'number-columns-repeated')) {
+ $colRepeats = (int) $cellData->getAttributeNS($tableNs, 'number-columns-repeated');
+ } else {
+ $colRepeats = 1;
+ }
+
+ if ($type !== null) {
+ for ($i = 0; $i < $colRepeats; ++$i) {
+ if ($i > 0) {
+ ++$columnID;
+ }
+
+ if ($type !== DataType::TYPE_NULL) {
+ for ($rowAdjust = 0; $rowAdjust < $rowRepeats; ++$rowAdjust) {
+ $rID = $rowID + $rowAdjust;
+
+ $cell = $spreadsheet->getActiveSheet()
+ ->getCell($columnID . $rID);
+
+ // Set value
+ if ($hasCalculatedValue) {
+ $cell->setValueExplicit($cellDataFormula, $type);
+ } else {
+ $cell->setValueExplicit($dataValue, $type);
+ }
+
+ if ($hasCalculatedValue) {
+ $cell->setCalculatedValue($dataValue);
+ }
+
+ // Set other properties
+ if ($formatting !== null) {
+ $spreadsheet->getActiveSheet()
+ ->getStyle($columnID . $rID)
+ ->getNumberFormat()
+ ->setFormatCode($formatting);
+ } else {
+ $spreadsheet->getActiveSheet()
+ ->getStyle($columnID . $rID)
+ ->getNumberFormat()
+ ->setFormatCode(NumberFormat::FORMAT_GENERAL);
+ }
+
+ if ($hyperlink !== null) {
+ $cell->getHyperlink()
+ ->setUrl($hyperlink);
+ }
+ }
+ }
+ }
+ }
+
+ // Merged cells
+ if (
+ $cellData->hasAttributeNS($tableNs, 'number-columns-spanned')
+ || $cellData->hasAttributeNS($tableNs, 'number-rows-spanned')
+ ) {
+ if (($type !== DataType::TYPE_NULL) || (!$this->readDataOnly)) {
+ $columnTo = $columnID;
+
+ if ($cellData->hasAttributeNS($tableNs, 'number-columns-spanned')) {
+ $columnIndex = Coordinate::columnIndexFromString($columnID);
+ $columnIndex += (int) $cellData->getAttributeNS($tableNs, 'number-columns-spanned');
+ $columnIndex -= 2;
+
+ $columnTo = Coordinate::stringFromColumnIndex($columnIndex + 1);
+ }
+
+ $rowTo = $rowID;
+
+ if ($cellData->hasAttributeNS($tableNs, 'number-rows-spanned')) {
+ $rowTo = $rowTo + (int) $cellData->getAttributeNS($tableNs, 'number-rows-spanned') - 1;
+ }
+
+ $cellRange = $columnID . $rowID . ':' . $columnTo . $rowTo;
+ $spreadsheet->getActiveSheet()->mergeCells($cellRange);
+ }
+ }
+
+ ++$columnID;
+ }
+ $rowID += $rowRepeats;
+
+ break;
+ }
+ }
+ $pageSettings->setPrintSettingsForWorksheet($spreadsheet->getActiveSheet(), $worksheetStyleName);
+ ++$worksheetID;
+ }
+
+ $autoFilterReader->read($workbookData);
+ $definedNameReader->read($workbookData);
+ }
+ $spreadsheet->setActiveSheetIndex(0);
+
+ if ($zip->locateName('settings.xml') !== false) {
+ $this->processSettings($zip, $spreadsheet);
+ }
+
+ // Return
+ return $spreadsheet;
+ }
+
+ private function processSettings(ZipArchive $zip, Spreadsheet $spreadsheet): void
+ {
+ $dom = new DOMDocument('1.01', 'UTF-8');
+ $dom->loadXML(
+ $this->securityScanner->scan($zip->getFromName('settings.xml')),
+ Settings::getLibXmlLoaderOptions()
+ );
+ //$xlinkNs = $dom->lookupNamespaceUri('xlink');
+ $configNs = $dom->lookupNamespaceUri('config');
+ //$oooNs = $dom->lookupNamespaceUri('ooo');
+ $officeNs = $dom->lookupNamespaceUri('office');
+ $settings = $dom->getElementsByTagNameNS($officeNs, 'settings')
+ ->item(0);
+ $this->lookForActiveSheet($settings, $spreadsheet, $configNs);
+ $this->lookForSelectedCells($settings, $spreadsheet, $configNs);
+ }
+
+ private function lookForActiveSheet(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void
+ {
+ /** @var DOMElement $t */
+ foreach ($settings->getElementsByTagNameNS($configNs, 'config-item') as $t) {
+ if ($t->getAttributeNs($configNs, 'name') === 'ActiveTable') {
+ try {
+ $spreadsheet->setActiveSheetIndexByName($t->nodeValue);
+ } catch (Throwable $e) {
+ // do nothing
+ }
+
+ break;
+ }
+ }
+ }
+
+ private function lookForSelectedCells(DOMElement $settings, Spreadsheet $spreadsheet, string $configNs): void
+ {
+ /** @var DOMElement $t */
+ foreach ($settings->getElementsByTagNameNS($configNs, 'config-item-map-named') as $t) {
+ if ($t->getAttributeNs($configNs, 'name') === 'Tables') {
+ foreach ($t->getElementsByTagNameNS($configNs, 'config-item-map-entry') as $ws) {
+ $setRow = $setCol = '';
+ $wsname = $ws->getAttributeNs($configNs, 'name');
+ foreach ($ws->getElementsByTagNameNS($configNs, 'config-item') as $configItem) {
+ $attrName = $configItem->getAttributeNs($configNs, 'name');
+ if ($attrName === 'CursorPositionX') {
+ $setCol = $configItem->nodeValue;
+ }
+ if ($attrName === 'CursorPositionY') {
+ $setRow = $configItem->nodeValue;
+ }
+ }
+ $this->setSelected($spreadsheet, $wsname, $setCol, $setRow);
+ }
+
+ break;
+ }
+ }
+ }
+
+ private function setSelected(Spreadsheet $spreadsheet, string $wsname, string $setCol, string $setRow): void
+ {
+ if (is_numeric($setCol) && is_numeric($setRow)) {
+ try {
+ $spreadsheet->getSheetByName($wsname)->setSelectedCellByColumnAndRow($setCol + 1, $setRow + 1);
+ } catch (Throwable $e) {
+ // do nothing
+ }
+ }
+ }
+
+ /**
+ * Recursively scan element.
+ *
+ * @return string
+ */
+ protected function scanElementForText(DOMNode $element)
+ {
+ $str = '';
+ foreach ($element->childNodes as $child) {
+ /** @var DOMNode $child */
+ if ($child->nodeType == XML_TEXT_NODE) {
+ $str .= $child->nodeValue;
+ } elseif ($child->nodeType == XML_ELEMENT_NODE && $child->nodeName == 'text:s') {
+ // It's a space
+
+ // Multiple spaces?
+ /** @var DOMAttr $cAttr */
+ $cAttr = $child->attributes->getNamedItem('c');
+ if ($cAttr) {
+ $multiplier = (int) $cAttr->nodeValue;
+ } else {
+ $multiplier = 1;
+ }
+
+ $str .= str_repeat(' ', $multiplier);
+ }
+
+ if ($child->hasChildNodes()) {
+ $str .= $this->scanElementForText($child);
+ }
+ }
+
+ return $str;
+ }
+
+ /**
+ * @param string $is
+ *
+ * @return RichText
+ */
+ private function parseRichText($is)
+ {
+ $value = new RichText();
+ $value->createText($is);
+
+ return $value;
+ }
+
+ private function convertToExcelFormulaValue(string $openOfficeFormula): string
+ {
+ $temp = explode('"', $openOfficeFormula);
+ $tKey = false;
+ foreach ($temp as &$value) {
+ // Only replace in alternate array entries (i.e. non-quoted blocks)
+ if ($tKey = !$tKey) {
+ // Cell range reference in another sheet
+ $value = preg_replace('/\[\$?([^\.]+)\.([^\.]+):\.([^\.]+)\]/miu', '$1!$2:$3', $value);
+ // Cell reference in another sheet
+ $value = preg_replace('/\[\$?([^\.]+)\.([^\.]+)\]/miu', '$1!$2', $value ?? '');
+ // Cell range reference
+ $value = preg_replace('/\[\.([^\.]+):\.([^\.]+)\]/miu', '$1:$2', $value ?? '');
+ // Simple cell reference
+ $value = preg_replace('/\[\.([^\.]+)\]/miu', '$1', $value ?? '');
+ // Convert references to defined names/formulae
+ $value = str_replace('$$', '', $value ?? '');
+
+ $value = Calculation::translateSeparator(';', ',', $value, $inBraces);
+ }
+ }
+
+ // Then rebuild the formula string
+ $excelFormula = implode('"', $temp);
+
+ return $excelFormula;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/PageSettings.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/PageSettings.php
new file mode 100644
index 0000000..8d24fd0
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Ods/PageSettings.php
@@ -0,0 +1,139 @@
+setDomNameSpaces($styleDom);
+ $this->readPageSettingStyles($styleDom);
+ $this->readStyleMasterLookup($styleDom);
+ }
+
+ private function setDomNameSpaces(DOMDocument $styleDom): void
+ {
+ $this->officeNs = $styleDom->lookupNamespaceUri('office');
+ $this->stylesNs = $styleDom->lookupNamespaceUri('style');
+ $this->stylesFo = $styleDom->lookupNamespaceUri('fo');
+ }
+
+ private function readPageSettingStyles(DOMDocument $styleDom): void
+ {
+ $styles = $styleDom->getElementsByTagNameNS($this->officeNs, 'automatic-styles')
+ ->item(0)
+ ->getElementsByTagNameNS($this->stylesNs, 'page-layout');
+
+ foreach ($styles as $styleSet) {
+ $styleName = $styleSet->getAttributeNS($this->stylesNs, 'name');
+ $pageLayoutProperties = $styleSet->getElementsByTagNameNS($this->stylesNs, 'page-layout-properties')[0];
+ $styleOrientation = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'print-orientation');
+ $styleScale = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'scale-to');
+ $stylePrintOrder = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'print-page-order');
+ $centered = $pageLayoutProperties->getAttributeNS($this->stylesNs, 'table-centering');
+
+ $marginLeft = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-left');
+ $marginRight = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-right');
+ $marginTop = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-top');
+ $marginBottom = $pageLayoutProperties->getAttributeNS($this->stylesFo, 'margin-bottom');
+ $header = $styleSet->getElementsByTagNameNS($this->stylesNs, 'header-style')[0];
+ $headerProperties = $header->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0];
+ $marginHeader = isset($headerProperties) ? $headerProperties->getAttributeNS($this->stylesFo, 'min-height') : null;
+ $footer = $styleSet->getElementsByTagNameNS($this->stylesNs, 'footer-style')[0];
+ $footerProperties = $footer->getElementsByTagNameNS($this->stylesNs, 'header-footer-properties')[0];
+ $marginFooter = isset($footerProperties) ? $footerProperties->getAttributeNS($this->stylesFo, 'min-height') : null;
+
+ $this->pageLayoutStyles[$styleName] = (object) [
+ 'orientation' => $styleOrientation ?: PageSetup::ORIENTATION_DEFAULT,
+ 'scale' => $styleScale ?: 100,
+ 'printOrder' => $stylePrintOrder,
+ 'horizontalCentered' => $centered === 'horizontal' || $centered === 'both',
+ 'verticalCentered' => $centered === 'vertical' || $centered === 'both',
+ // margin size is already stored in inches, so no UOM conversion is required
+ 'marginLeft' => (float) $marginLeft ?? 0.7,
+ 'marginRight' => (float) $marginRight ?? 0.7,
+ 'marginTop' => (float) $marginTop ?? 0.3,
+ 'marginBottom' => (float) $marginBottom ?? 0.3,
+ 'marginHeader' => (float) $marginHeader ?? 0.45,
+ 'marginFooter' => (float) $marginFooter ?? 0.45,
+ ];
+ }
+ }
+
+ private function readStyleMasterLookup(DOMDocument $styleDom): void
+ {
+ $styleMasterLookup = $styleDom->getElementsByTagNameNS($this->officeNs, 'master-styles')
+ ->item(0)
+ ->getElementsByTagNameNS($this->stylesNs, 'master-page');
+
+ foreach ($styleMasterLookup as $styleMasterSet) {
+ $styleMasterName = $styleMasterSet->getAttributeNS($this->stylesNs, 'name');
+ $pageLayoutName = $styleMasterSet->getAttributeNS($this->stylesNs, 'page-layout-name');
+ $this->masterPrintStylesCrossReference[$styleMasterName] = $pageLayoutName;
+ }
+ }
+
+ public function readStyleCrossReferences(DOMDocument $contentDom): void
+ {
+ $styleXReferences = $contentDom->getElementsByTagNameNS($this->officeNs, 'automatic-styles')
+ ->item(0)
+ ->getElementsByTagNameNS($this->stylesNs, 'style');
+
+ foreach ($styleXReferences as $styleXreferenceSet) {
+ $styleXRefName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'name');
+ $stylePageLayoutName = $styleXreferenceSet->getAttributeNS($this->stylesNs, 'master-page-name');
+ if (!empty($stylePageLayoutName)) {
+ $this->masterStylesCrossReference[$styleXRefName] = $stylePageLayoutName;
+ }
+ }
+ }
+
+ public function setPrintSettingsForWorksheet(Worksheet $worksheet, string $styleName): void
+ {
+ if (!array_key_exists($styleName, $this->masterStylesCrossReference)) {
+ return;
+ }
+ $masterStyleName = $this->masterStylesCrossReference[$styleName];
+
+ if (!array_key_exists($masterStyleName, $this->masterPrintStylesCrossReference)) {
+ return;
+ }
+ $printSettingsIndex = $this->masterPrintStylesCrossReference[$masterStyleName];
+
+ if (!array_key_exists($printSettingsIndex, $this->pageLayoutStyles)) {
+ return;
+ }
+ $printSettings = $this->pageLayoutStyles[$printSettingsIndex];
+
+ $worksheet->getPageSetup()
+ ->setOrientation($printSettings->orientation ?? PageSetup::ORIENTATION_DEFAULT)
+ ->setPageOrder($printSettings->printOrder === 'ltr' ? PageSetup::PAGEORDER_OVER_THEN_DOWN : PageSetup::PAGEORDER_DOWN_THEN_OVER)
+ ->setScale((int) trim($printSettings->scale, '%'))
+ ->setHorizontalCentered($printSettings->horizontalCentered)
+ ->setVerticalCentered($printSettings->verticalCentered);
+
+ $worksheet->getPageMargins()
+ ->setLeft($printSettings->marginLeft)
+ ->setRight($printSettings->marginRight)
+ ->setTop($printSettings->marginTop)
+ ->setBottom($printSettings->marginBottom)
+ ->setHeader($printSettings->marginHeader)
+ ->setFooter($printSettings->marginFooter);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php
new file mode 100644
index 0000000..7daf723
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php
@@ -0,0 +1,32 @@
+ '#NULL!',
+ 0x07 => '#DIV/0!',
+ 0x0F => '#VALUE!',
+ 0x17 => '#REF!',
+ 0x1D => '#NAME?',
+ 0x24 => '#NUM!',
+ 0x2A => '#N/A',
+ ];
+
+ /**
+ * Map error code, e.g. '#N/A'.
+ *
+ * @param int $code
+ *
+ * @return bool|string
+ */
+ public static function lookup($code)
+ {
+ if (isset(self::$map[$code])) {
+ return self::$map[$code];
+ }
+
+ return false;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php
new file mode 100644
index 0000000..e9c95c8
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php
@@ -0,0 +1,624 @@
+object = $object;
+ }
+
+ private const WHICH_ROUTINE = [
+ self::DGGCONTAINER => 'readDggContainer',
+ self::DGG => 'readDgg',
+ self::BSTORECONTAINER => 'readBstoreContainer',
+ self::BSE => 'readBSE',
+ self::BLIPJPEG => 'readBlipJPEG',
+ self::BLIPPNG => 'readBlipPNG',
+ self::OPT => 'readOPT',
+ self::TERTIARYOPT => 'readTertiaryOPT',
+ self::SPLITMENUCOLORS => 'readSplitMenuColors',
+ self::DGCONTAINER => 'readDgContainer',
+ self::DG => 'readDg',
+ self::SPGRCONTAINER => 'readSpgrContainer',
+ self::SPCONTAINER => 'readSpContainer',
+ self::SPGR => 'readSpgr',
+ self::SP => 'readSp',
+ self::CLIENTTEXTBOX => 'readClientTextbox',
+ self::CLIENTANCHOR => 'readClientAnchor',
+ self::CLIENTDATA => 'readClientData',
+ ];
+
+ /**
+ * Load Escher stream data. May be a partial Escher stream.
+ *
+ * @param string $data
+ *
+ * @return BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer
+ */
+ public function load($data)
+ {
+ $this->data = $data;
+
+ // total byte size of Excel data (workbook global substream + sheet substreams)
+ $this->dataSize = strlen($this->data);
+
+ $this->pos = 0;
+
+ // Parse Escher stream
+ while ($this->pos < $this->dataSize) {
+ // offset: 2; size: 2: Record Type
+ $fbt = Xls::getUInt2d($this->data, $this->pos + 2);
+ $routine = self::WHICH_ROUTINE[$fbt] ?? 'readDefault';
+ if (method_exists($this, $routine)) {
+ $this->$routine();
+ }
+ }
+
+ return $this->object;
+ }
+
+ /**
+ * Read a generic record.
+ */
+ private function readDefault(): void
+ {
+ // offset 0; size: 2; recVer and recInstance
+ //$verInstance = Xls::getUInt2d($this->data, $this->pos);
+
+ // offset: 2; size: 2: Record Type
+ //$fbt = Xls::getUInt2d($this->data, $this->pos + 2);
+
+ // bit: 0-3; mask: 0x000F; recVer
+ //$recVer = (0x000F & $verInstance) >> 0;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ //$recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read DggContainer record (Drawing Group Container).
+ */
+ private function readDggContainer(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ // record is a container, read contents
+ $dggContainer = new DggContainer();
+ $this->applyAttribute('setDggContainer', $dggContainer);
+ $reader = new self($dggContainer);
+ $reader->load($recordData);
+ }
+
+ /**
+ * Read Dgg record (Drawing Group).
+ */
+ private function readDgg(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ //$recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read BstoreContainer record (Blip Store Container).
+ */
+ private function readBstoreContainer(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ // record is a container, read contents
+ $bstoreContainer = new BstoreContainer();
+ $this->applyAttribute('setBstoreContainer', $bstoreContainer);
+ $reader = new self($bstoreContainer);
+ $reader->load($recordData);
+ }
+
+ /**
+ * Read BSE record.
+ */
+ private function readBSE(): void
+ {
+ // offset: 0; size: 2; recVer and recInstance
+
+ // bit: 4-15; mask: 0xFFF0; recInstance
+ $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ // add BSE to BstoreContainer
+ $BSE = new BSE();
+ $this->applyAttribute('addBSE', $BSE);
+
+ $BSE->setBLIPType($recInstance);
+
+ // offset: 0; size: 1; btWin32 (MSOBLIPTYPE)
+ //$btWin32 = ord($recordData[0]);
+
+ // offset: 1; size: 1; btWin32 (MSOBLIPTYPE)
+ //$btMacOS = ord($recordData[1]);
+
+ // offset: 2; size: 16; MD4 digest
+ //$rgbUid = substr($recordData, 2, 16);
+
+ // offset: 18; size: 2; tag
+ //$tag = Xls::getUInt2d($recordData, 18);
+
+ // offset: 20; size: 4; size of BLIP in bytes
+ //$size = Xls::getInt4d($recordData, 20);
+
+ // offset: 24; size: 4; number of references to this BLIP
+ //$cRef = Xls::getInt4d($recordData, 24);
+
+ // offset: 28; size: 4; MSOFO file offset
+ //$foDelay = Xls::getInt4d($recordData, 28);
+
+ // offset: 32; size: 1; unused1
+ //$unused1 = ord($recordData[32]);
+
+ // offset: 33; size: 1; size of nameData in bytes (including null terminator)
+ $cbName = ord($recordData[33]);
+
+ // offset: 34; size: 1; unused2
+ //$unused2 = ord($recordData[34]);
+
+ // offset: 35; size: 1; unused3
+ //$unused3 = ord($recordData[35]);
+
+ // offset: 36; size: $cbName; nameData
+ //$nameData = substr($recordData, 36, $cbName);
+
+ // offset: 36 + $cbName, size: var; the BLIP data
+ $blipData = substr($recordData, 36 + $cbName);
+
+ // record is a container, read contents
+ $reader = new self($BSE);
+ $reader->load($blipData);
+ }
+
+ /**
+ * Read BlipJPEG record. Holds raw JPEG image data.
+ */
+ private function readBlipJPEG(): void
+ {
+ // offset: 0; size: 2; recVer and recInstance
+
+ // bit: 4-15; mask: 0xFFF0; recInstance
+ $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ $pos = 0;
+
+ // offset: 0; size: 16; rgbUid1 (MD4 digest of)
+ //$rgbUid1 = substr($recordData, 0, 16);
+ $pos += 16;
+
+ // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3
+ if (in_array($recInstance, [0x046B, 0x06E3])) {
+ //$rgbUid2 = substr($recordData, 16, 16);
+ $pos += 16;
+ }
+
+ // offset: var; size: 1; tag
+ //$tag = ord($recordData[$pos]);
+ ++$pos;
+
+ // offset: var; size: var; the raw image data
+ $data = substr($recordData, $pos);
+
+ $blip = new Blip();
+ $blip->setData($data);
+
+ $this->applyAttribute('setBlip', $blip);
+ }
+
+ /**
+ * Read BlipPNG record. Holds raw PNG image data.
+ */
+ private function readBlipPNG(): void
+ {
+ // offset: 0; size: 2; recVer and recInstance
+
+ // bit: 4-15; mask: 0xFFF0; recInstance
+ $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ $pos = 0;
+
+ // offset: 0; size: 16; rgbUid1 (MD4 digest of)
+ //$rgbUid1 = substr($recordData, 0, 16);
+ $pos += 16;
+
+ // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3
+ if ($recInstance == 0x06E1) {
+ //$rgbUid2 = substr($recordData, 16, 16);
+ $pos += 16;
+ }
+
+ // offset: var; size: 1; tag
+ //$tag = ord($recordData[$pos]);
+ ++$pos;
+
+ // offset: var; size: var; the raw image data
+ $data = substr($recordData, $pos);
+
+ $blip = new Blip();
+ $blip->setData($data);
+
+ $this->applyAttribute('setBlip', $blip);
+ }
+
+ /**
+ * Read OPT record. This record may occur within DggContainer record or SpContainer.
+ */
+ private function readOPT(): void
+ {
+ // offset: 0; size: 2; recVer and recInstance
+
+ // bit: 4-15; mask: 0xFFF0; recInstance
+ $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ $this->readOfficeArtRGFOPTE($recordData, $recInstance);
+ }
+
+ /**
+ * Read TertiaryOPT record.
+ */
+ private function readTertiaryOPT(): void
+ {
+ // offset: 0; size: 2; recVer and recInstance
+
+ // bit: 4-15; mask: 0xFFF0; recInstance
+ //$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ //$recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read SplitMenuColors record.
+ */
+ private function readSplitMenuColors(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ //$recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read DgContainer record (Drawing Container).
+ */
+ private function readDgContainer(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ // record is a container, read contents
+ $dgContainer = new DgContainer();
+ $this->applyAttribute('setDgContainer', $dgContainer);
+ $reader = new self($dgContainer);
+ $reader->load($recordData);
+ }
+
+ /**
+ * Read Dg record (Drawing).
+ */
+ private function readDg(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ //$recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read SpgrContainer record (Shape Group Container).
+ */
+ private function readSpgrContainer(): void
+ {
+ // context is either context DgContainer or SpgrContainer
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ // record is a container, read contents
+ $spgrContainer = new SpgrContainer();
+
+ if ($this->object instanceof DgContainer) {
+ // DgContainer
+ $this->object->setSpgrContainer($spgrContainer);
+ } elseif ($this->object instanceof SpgrContainer) {
+ // SpgrContainer
+ $this->object->addChild($spgrContainer);
+ }
+
+ $reader = new self($spgrContainer);
+ $reader->load($recordData);
+ }
+
+ /**
+ * Read SpContainer record (Shape Container).
+ */
+ private function readSpContainer(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // add spContainer to spgrContainer
+ $spContainer = new SpContainer();
+ $this->applyAttribute('addChild', $spContainer);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ // record is a container, read contents
+ $reader = new self($spContainer);
+ $reader->load($recordData);
+ }
+
+ /**
+ * Read Spgr record (Shape Group).
+ */
+ private function readSpgr(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ //$recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read Sp record (Shape).
+ */
+ private function readSp(): void
+ {
+ // offset: 0; size: 2; recVer and recInstance
+
+ // bit: 4-15; mask: 0xFFF0; recInstance
+ //$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ //$recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read ClientTextbox record.
+ */
+ private function readClientTextbox(): void
+ {
+ // offset: 0; size: 2; recVer and recInstance
+
+ // bit: 4-15; mask: 0xFFF0; recInstance
+ //$recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4;
+
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ //$recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read ClientAnchor record. This record holds information about where the shape is anchored in worksheet.
+ */
+ private function readClientAnchor(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ $recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+
+ // offset: 2; size: 2; upper-left corner column index (0-based)
+ $c1 = Xls::getUInt2d($recordData, 2);
+
+ // offset: 4; size: 2; upper-left corner horizontal offset in 1/1024 of column width
+ $startOffsetX = Xls::getUInt2d($recordData, 4);
+
+ // offset: 6; size: 2; upper-left corner row index (0-based)
+ $r1 = Xls::getUInt2d($recordData, 6);
+
+ // offset: 8; size: 2; upper-left corner vertical offset in 1/256 of row height
+ $startOffsetY = Xls::getUInt2d($recordData, 8);
+
+ // offset: 10; size: 2; bottom-right corner column index (0-based)
+ $c2 = Xls::getUInt2d($recordData, 10);
+
+ // offset: 12; size: 2; bottom-right corner horizontal offset in 1/1024 of column width
+ $endOffsetX = Xls::getUInt2d($recordData, 12);
+
+ // offset: 14; size: 2; bottom-right corner row index (0-based)
+ $r2 = Xls::getUInt2d($recordData, 14);
+
+ // offset: 16; size: 2; bottom-right corner vertical offset in 1/256 of row height
+ $endOffsetY = Xls::getUInt2d($recordData, 16);
+
+ $this->applyAttribute('setStartCoordinates', Coordinate::stringFromColumnIndex($c1 + 1) . ($r1 + 1));
+ $this->applyAttribute('setStartOffsetX', $startOffsetX);
+ $this->applyAttribute('setStartOffsetY', $startOffsetY);
+ $this->applyAttribute('setEndCoordinates', Coordinate::stringFromColumnIndex($c2 + 1) . ($r2 + 1));
+ $this->applyAttribute('setEndOffsetX', $endOffsetX);
+ $this->applyAttribute('setEndOffsetY', $endOffsetY);
+ }
+
+ /**
+ * @param mixed $value
+ */
+ private function applyAttribute(string $name, $value): void
+ {
+ if (method_exists($this->object, $name)) {
+ $this->object->$name($value);
+ }
+ }
+
+ /**
+ * Read ClientData record.
+ */
+ private function readClientData(): void
+ {
+ $length = Xls::getInt4d($this->data, $this->pos + 4);
+ //$recordData = substr($this->data, $this->pos + 8, $length);
+
+ // move stream pointer to next record
+ $this->pos += 8 + $length;
+ }
+
+ /**
+ * Read OfficeArtRGFOPTE table of property-value pairs.
+ *
+ * @param string $data Binary data
+ * @param int $n Number of properties
+ */
+ private function readOfficeArtRGFOPTE($data, $n): void
+ {
+ $splicedComplexData = substr($data, 6 * $n);
+
+ // loop through property-value pairs
+ for ($i = 0; $i < $n; ++$i) {
+ // read 6 bytes at a time
+ $fopte = substr($data, 6 * $i, 6);
+
+ // offset: 0; size: 2; opid
+ $opid = Xls::getUInt2d($fopte, 0);
+
+ // bit: 0-13; mask: 0x3FFF; opid.opid
+ $opidOpid = (0x3FFF & $opid) >> 0;
+
+ // bit: 14; mask 0x4000; 1 = value in op field is BLIP identifier
+ //$opidFBid = (0x4000 & $opid) >> 14;
+
+ // bit: 15; mask 0x8000; 1 = this is a complex property, op field specifies size of complex data
+ $opidFComplex = (0x8000 & $opid) >> 15;
+
+ // offset: 2; size: 4; the value for this property
+ $op = Xls::getInt4d($fopte, 2);
+
+ if ($opidFComplex) {
+ $complexData = substr($splicedComplexData, 0, $op);
+ $splicedComplexData = substr($splicedComplexData, $op);
+
+ // we store string value with complex data
+ $value = $complexData;
+ } else {
+ // we store integer value
+ $value = $op;
+ }
+
+ if (method_exists($this->object, 'setOPT')) {
+ $this->object->setOPT($opidOpid, $value);
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php
new file mode 100644
index 0000000..3e15f64
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php
@@ -0,0 +1,197 @@
+reset();
+ }
+
+ /**
+ * Reset the MD5 stream context.
+ */
+ public function reset(): void
+ {
+ $this->a = 0x67452301;
+ $this->b = 0xEFCDAB89;
+ $this->c = 0x98BADCFE;
+ $this->d = 0x10325476;
+ }
+
+ /**
+ * Get MD5 stream context.
+ *
+ * @return string
+ */
+ public function getContext()
+ {
+ $s = '';
+ foreach (['a', 'b', 'c', 'd'] as $i) {
+ $v = $this->{$i};
+ $s .= chr($v & 0xff);
+ $s .= chr(($v >> 8) & 0xff);
+ $s .= chr(($v >> 16) & 0xff);
+ $s .= chr(($v >> 24) & 0xff);
+ }
+
+ return $s;
+ }
+
+ /**
+ * Add data to context.
+ *
+ * @param string $data Data to add
+ */
+ public function add(string $data): void
+ {
+ $words = array_values(unpack('V16', $data));
+
+ $A = $this->a;
+ $B = $this->b;
+ $C = $this->c;
+ $D = $this->d;
+
+ $F = ['self', 'f'];
+ $G = ['self', 'g'];
+ $H = ['self', 'h'];
+ $I = ['self', 'i'];
+
+ // ROUND 1
+ self::step($F, $A, $B, $C, $D, $words[0], 7, 0xd76aa478);
+ self::step($F, $D, $A, $B, $C, $words[1], 12, 0xe8c7b756);
+ self::step($F, $C, $D, $A, $B, $words[2], 17, 0x242070db);
+ self::step($F, $B, $C, $D, $A, $words[3], 22, 0xc1bdceee);
+ self::step($F, $A, $B, $C, $D, $words[4], 7, 0xf57c0faf);
+ self::step($F, $D, $A, $B, $C, $words[5], 12, 0x4787c62a);
+ self::step($F, $C, $D, $A, $B, $words[6], 17, 0xa8304613);
+ self::step($F, $B, $C, $D, $A, $words[7], 22, 0xfd469501);
+ self::step($F, $A, $B, $C, $D, $words[8], 7, 0x698098d8);
+ self::step($F, $D, $A, $B, $C, $words[9], 12, 0x8b44f7af);
+ self::step($F, $C, $D, $A, $B, $words[10], 17, 0xffff5bb1);
+ self::step($F, $B, $C, $D, $A, $words[11], 22, 0x895cd7be);
+ self::step($F, $A, $B, $C, $D, $words[12], 7, 0x6b901122);
+ self::step($F, $D, $A, $B, $C, $words[13], 12, 0xfd987193);
+ self::step($F, $C, $D, $A, $B, $words[14], 17, 0xa679438e);
+ self::step($F, $B, $C, $D, $A, $words[15], 22, 0x49b40821);
+
+ // ROUND 2
+ self::step($G, $A, $B, $C, $D, $words[1], 5, 0xf61e2562);
+ self::step($G, $D, $A, $B, $C, $words[6], 9, 0xc040b340);
+ self::step($G, $C, $D, $A, $B, $words[11], 14, 0x265e5a51);
+ self::step($G, $B, $C, $D, $A, $words[0], 20, 0xe9b6c7aa);
+ self::step($G, $A, $B, $C, $D, $words[5], 5, 0xd62f105d);
+ self::step($G, $D, $A, $B, $C, $words[10], 9, 0x02441453);
+ self::step($G, $C, $D, $A, $B, $words[15], 14, 0xd8a1e681);
+ self::step($G, $B, $C, $D, $A, $words[4], 20, 0xe7d3fbc8);
+ self::step($G, $A, $B, $C, $D, $words[9], 5, 0x21e1cde6);
+ self::step($G, $D, $A, $B, $C, $words[14], 9, 0xc33707d6);
+ self::step($G, $C, $D, $A, $B, $words[3], 14, 0xf4d50d87);
+ self::step($G, $B, $C, $D, $A, $words[8], 20, 0x455a14ed);
+ self::step($G, $A, $B, $C, $D, $words[13], 5, 0xa9e3e905);
+ self::step($G, $D, $A, $B, $C, $words[2], 9, 0xfcefa3f8);
+ self::step($G, $C, $D, $A, $B, $words[7], 14, 0x676f02d9);
+ self::step($G, $B, $C, $D, $A, $words[12], 20, 0x8d2a4c8a);
+
+ // ROUND 3
+ self::step($H, $A, $B, $C, $D, $words[5], 4, 0xfffa3942);
+ self::step($H, $D, $A, $B, $C, $words[8], 11, 0x8771f681);
+ self::step($H, $C, $D, $A, $B, $words[11], 16, 0x6d9d6122);
+ self::step($H, $B, $C, $D, $A, $words[14], 23, 0xfde5380c);
+ self::step($H, $A, $B, $C, $D, $words[1], 4, 0xa4beea44);
+ self::step($H, $D, $A, $B, $C, $words[4], 11, 0x4bdecfa9);
+ self::step($H, $C, $D, $A, $B, $words[7], 16, 0xf6bb4b60);
+ self::step($H, $B, $C, $D, $A, $words[10], 23, 0xbebfbc70);
+ self::step($H, $A, $B, $C, $D, $words[13], 4, 0x289b7ec6);
+ self::step($H, $D, $A, $B, $C, $words[0], 11, 0xeaa127fa);
+ self::step($H, $C, $D, $A, $B, $words[3], 16, 0xd4ef3085);
+ self::step($H, $B, $C, $D, $A, $words[6], 23, 0x04881d05);
+ self::step($H, $A, $B, $C, $D, $words[9], 4, 0xd9d4d039);
+ self::step($H, $D, $A, $B, $C, $words[12], 11, 0xe6db99e5);
+ self::step($H, $C, $D, $A, $B, $words[15], 16, 0x1fa27cf8);
+ self::step($H, $B, $C, $D, $A, $words[2], 23, 0xc4ac5665);
+
+ // ROUND 4
+ self::step($I, $A, $B, $C, $D, $words[0], 6, 0xf4292244);
+ self::step($I, $D, $A, $B, $C, $words[7], 10, 0x432aff97);
+ self::step($I, $C, $D, $A, $B, $words[14], 15, 0xab9423a7);
+ self::step($I, $B, $C, $D, $A, $words[5], 21, 0xfc93a039);
+ self::step($I, $A, $B, $C, $D, $words[12], 6, 0x655b59c3);
+ self::step($I, $D, $A, $B, $C, $words[3], 10, 0x8f0ccc92);
+ self::step($I, $C, $D, $A, $B, $words[10], 15, 0xffeff47d);
+ self::step($I, $B, $C, $D, $A, $words[1], 21, 0x85845dd1);
+ self::step($I, $A, $B, $C, $D, $words[8], 6, 0x6fa87e4f);
+ self::step($I, $D, $A, $B, $C, $words[15], 10, 0xfe2ce6e0);
+ self::step($I, $C, $D, $A, $B, $words[6], 15, 0xa3014314);
+ self::step($I, $B, $C, $D, $A, $words[13], 21, 0x4e0811a1);
+ self::step($I, $A, $B, $C, $D, $words[4], 6, 0xf7537e82);
+ self::step($I, $D, $A, $B, $C, $words[11], 10, 0xbd3af235);
+ self::step($I, $C, $D, $A, $B, $words[2], 15, 0x2ad7d2bb);
+ self::step($I, $B, $C, $D, $A, $words[9], 21, 0xeb86d391);
+
+ $this->a = ($this->a + $A) & 0xffffffff;
+ $this->b = ($this->b + $B) & 0xffffffff;
+ $this->c = ($this->c + $C) & 0xffffffff;
+ $this->d = ($this->d + $D) & 0xffffffff;
+ }
+
+ private static function f(int $X, int $Y, int $Z)
+ {
+ return ($X & $Y) | ((~$X) & $Z); // X AND Y OR NOT X AND Z
+ }
+
+ private static function g(int $X, int $Y, int $Z)
+ {
+ return ($X & $Z) | ($Y & (~$Z)); // X AND Z OR Y AND NOT Z
+ }
+
+ private static function h(int $X, int $Y, int $Z)
+ {
+ return $X ^ $Y ^ $Z; // X XOR Y XOR Z
+ }
+
+ private static function i(int $X, int $Y, int $Z)
+ {
+ return $Y ^ ($X | (~$Z)); // Y XOR (X OR NOT Z)
+ }
+
+ private static function step($func, int &$A, int $B, int $C, int $D, int $M, int $s, int $t): void
+ {
+ $A = ($A + call_user_func($func, $B, $C, $D) + $M + $t) & 0xffffffff;
+ $A = self::rotate($A, $s);
+ $A = ($B + $A) & 0xffffffff;
+ }
+
+ private static function rotate(int $decimal, int $bits)
+ {
+ $binary = str_pad(decbin($decimal), 32, '0', STR_PAD_LEFT);
+
+ return bindec(substr($binary, $bits) . substr($binary, 0, $bits));
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php
new file mode 100644
index 0000000..32ab5c8
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php
@@ -0,0 +1,50 @@
+
+ */
+ protected static $fillPatternMap = [
+ 0x00 => Fill::FILL_NONE,
+ 0x01 => Fill::FILL_SOLID,
+ 0x02 => Fill::FILL_PATTERN_MEDIUMGRAY,
+ 0x03 => Fill::FILL_PATTERN_DARKGRAY,
+ 0x04 => Fill::FILL_PATTERN_LIGHTGRAY,
+ 0x05 => Fill::FILL_PATTERN_DARKHORIZONTAL,
+ 0x06 => Fill::FILL_PATTERN_DARKVERTICAL,
+ 0x07 => Fill::FILL_PATTERN_DARKDOWN,
+ 0x08 => Fill::FILL_PATTERN_DARKUP,
+ 0x09 => Fill::FILL_PATTERN_DARKGRID,
+ 0x0A => Fill::FILL_PATTERN_DARKTRELLIS,
+ 0x0B => Fill::FILL_PATTERN_LIGHTHORIZONTAL,
+ 0x0C => Fill::FILL_PATTERN_LIGHTVERTICAL,
+ 0x0D => Fill::FILL_PATTERN_LIGHTDOWN,
+ 0x0E => Fill::FILL_PATTERN_LIGHTUP,
+ 0x0F => Fill::FILL_PATTERN_LIGHTGRID,
+ 0x10 => Fill::FILL_PATTERN_LIGHTTRELLIS,
+ 0x11 => Fill::FILL_PATTERN_GRAY125,
+ 0x12 => Fill::FILL_PATTERN_GRAY0625,
+ ];
+
+ /**
+ * Get fill pattern from index
+ * OpenOffice documentation: 2.5.12.
+ *
+ * @param int $index
+ *
+ * @return string
+ */
+ public static function lookup($index)
+ {
+ if (isset(self::$fillPatternMap[$index])) {
+ return self::$fillPatternMap[$index];
+ }
+
+ return Fill::FILL_NONE;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php
new file mode 100644
index 0000000..8488499
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Hyperlinks.php
@@ -0,0 +1,64 @@
+worksheet = $workSheet;
+ }
+
+ public function readHyperlinks(SimpleXMLElement $relsWorksheet): void
+ {
+ foreach ($relsWorksheet->children(Namespaces::RELATIONSHIPS)->Relationship as $elementx) {
+ $element = Xlsx::getAttributes($elementx);
+ if ($element->Type == Namespaces::HYPERLINK) {
+ $this->hyperlinks[(string) $element->Id] = (string) $element->Target;
+ }
+ }
+ }
+
+ public function setHyperlinks(SimpleXMLElement $worksheetXml): void
+ {
+ foreach ($worksheetXml->children(Namespaces::MAIN)->hyperlink as $hyperlink) {
+ if ($hyperlink !== null) {
+ $this->setHyperlink($hyperlink, $this->worksheet);
+ }
+ }
+ }
+
+ private function setHyperlink(SimpleXMLElement $hyperlink, Worksheet $worksheet): void
+ {
+ // Link url
+ $linkRel = Xlsx::getAttributes($hyperlink, Namespaces::SCHEMA_OFFICE_DOCUMENT);
+
+ $attributes = Xlsx::getAttributes($hyperlink);
+ foreach (Coordinate::extractAllCellReferencesInRange($attributes->ref) as $cellReference) {
+ $cell = $worksheet->getCell($cellReference);
+ if (isset($linkRel['id'])) {
+ $hyperlinkUrl = $this->hyperlinks[(string) $linkRel['id']] ?? null;
+ if (isset($attributes['location'])) {
+ $hyperlinkUrl .= '#' . (string) $attributes['location'];
+ }
+ $cell->getHyperlink()->setUrl($hyperlinkUrl);
+ } elseif (isset($attributes['location'])) {
+ $cell->getHyperlink()->setUrl('sheet://' . (string) $attributes['location']);
+ }
+
+ // Tooltip
+ if (isset($attributes['tooltip'])) {
+ $cell->getHyperlink()->setTooltip((string) $attributes['tooltip']);
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php
new file mode 100644
index 0000000..c0713ae
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xlsx/Namespaces.php
@@ -0,0 +1,78 @@
+worksheet = $workSheet;
+ $this->worksheetXml = $worksheetXml;
+ }
+
+ public function load(array $unparsedLoadedData)
+ {
+ if (!$this->worksheetXml) {
+ return $unparsedLoadedData;
+ }
+
+ $this->margins($this->worksheetXml, $this->worksheet);
+ $unparsedLoadedData = $this->pageSetup($this->worksheetXml, $this->worksheet, $unparsedLoadedData);
+ $this->headerFooter($this->worksheetXml, $this->worksheet);
+ $this->pageBreaks($this->worksheetXml, $this->worksheet);
+
+ return $unparsedLoadedData;
+ }
+
+ private function margins(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ if ($xmlSheet->pageMargins) {
+ $docPageMargins = $worksheet->getPageMargins();
+ $docPageMargins->setLeft((float) ($xmlSheet->pageMargins['left']));
+ $docPageMargins->setRight((float) ($xmlSheet->pageMargins['right']));
+ $docPageMargins->setTop((float) ($xmlSheet->pageMargins['top']));
+ $docPageMargins->setBottom((float) ($xmlSheet->pageMargins['bottom']));
+ $docPageMargins->setHeader((float) ($xmlSheet->pageMargins['header']));
+ $docPageMargins->setFooter((float) ($xmlSheet->pageMargins['footer']));
+ }
+ }
+
+ private function pageSetup(SimpleXMLElement $xmlSheet, Worksheet $worksheet, array $unparsedLoadedData)
+ {
+ if ($xmlSheet->pageSetup) {
+ $docPageSetup = $worksheet->getPageSetup();
+
+ if (isset($xmlSheet->pageSetup['orientation'])) {
+ $docPageSetup->setOrientation((string) $xmlSheet->pageSetup['orientation']);
+ }
+ if (isset($xmlSheet->pageSetup['paperSize'])) {
+ $docPageSetup->setPaperSize((int) ($xmlSheet->pageSetup['paperSize']));
+ }
+ if (isset($xmlSheet->pageSetup['scale'])) {
+ $docPageSetup->setScale((int) ($xmlSheet->pageSetup['scale']), false);
+ }
+ if (isset($xmlSheet->pageSetup['fitToHeight']) && (int) ($xmlSheet->pageSetup['fitToHeight']) >= 0) {
+ $docPageSetup->setFitToHeight((int) ($xmlSheet->pageSetup['fitToHeight']), false);
+ }
+ if (isset($xmlSheet->pageSetup['fitToWidth']) && (int) ($xmlSheet->pageSetup['fitToWidth']) >= 0) {
+ $docPageSetup->setFitToWidth((int) ($xmlSheet->pageSetup['fitToWidth']), false);
+ }
+ if (
+ isset($xmlSheet->pageSetup['firstPageNumber'], $xmlSheet->pageSetup['useFirstPageNumber']) &&
+ self::boolean((string) $xmlSheet->pageSetup['useFirstPageNumber'])
+ ) {
+ $docPageSetup->setFirstPageNumber((int) ($xmlSheet->pageSetup['firstPageNumber']));
+ }
+ if (isset($xmlSheet->pageSetup['pageOrder'])) {
+ $docPageSetup->setPageOrder((string) $xmlSheet->pageSetup['pageOrder']);
+ }
+
+ $relAttributes = $xmlSheet->pageSetup->attributes(Namespaces::SCHEMA_OFFICE_DOCUMENT);
+ if (isset($relAttributes['id'])) {
+ $unparsedLoadedData['sheets'][$worksheet->getCodeName()]['pageSetupRelId'] = (string) $relAttributes['id'];
+ }
+ }
+
+ return $unparsedLoadedData;
+ }
+
+ private function headerFooter(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ if ($xmlSheet->headerFooter) {
+ $docHeaderFooter = $worksheet->getHeaderFooter();
+
+ if (
+ isset($xmlSheet->headerFooter['differentOddEven']) &&
+ self::boolean((string) $xmlSheet->headerFooter['differentOddEven'])
+ ) {
+ $docHeaderFooter->setDifferentOddEven(true);
+ } else {
+ $docHeaderFooter->setDifferentOddEven(false);
+ }
+ if (
+ isset($xmlSheet->headerFooter['differentFirst']) &&
+ self::boolean((string) $xmlSheet->headerFooter['differentFirst'])
+ ) {
+ $docHeaderFooter->setDifferentFirst(true);
+ } else {
+ $docHeaderFooter->setDifferentFirst(false);
+ }
+ if (
+ isset($xmlSheet->headerFooter['scaleWithDoc']) &&
+ !self::boolean((string) $xmlSheet->headerFooter['scaleWithDoc'])
+ ) {
+ $docHeaderFooter->setScaleWithDocument(false);
+ } else {
+ $docHeaderFooter->setScaleWithDocument(true);
+ }
+ if (
+ isset($xmlSheet->headerFooter['alignWithMargins']) &&
+ !self::boolean((string) $xmlSheet->headerFooter['alignWithMargins'])
+ ) {
+ $docHeaderFooter->setAlignWithMargins(false);
+ } else {
+ $docHeaderFooter->setAlignWithMargins(true);
+ }
+
+ $docHeaderFooter->setOddHeader((string) $xmlSheet->headerFooter->oddHeader);
+ $docHeaderFooter->setOddFooter((string) $xmlSheet->headerFooter->oddFooter);
+ $docHeaderFooter->setEvenHeader((string) $xmlSheet->headerFooter->evenHeader);
+ $docHeaderFooter->setEvenFooter((string) $xmlSheet->headerFooter->evenFooter);
+ $docHeaderFooter->setFirstHeader((string) $xmlSheet->headerFooter->firstHeader);
+ $docHeaderFooter->setFirstFooter((string) $xmlSheet->headerFooter->firstFooter);
+ }
+ }
+
+ private function pageBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ if ($xmlSheet->rowBreaks && $xmlSheet->rowBreaks->brk) {
+ $this->rowBreaks($xmlSheet, $worksheet);
+ }
+ if ($xmlSheet->colBreaks && $xmlSheet->colBreaks->brk) {
+ $this->columnBreaks($xmlSheet, $worksheet);
+ }
+ }
+
+ private function rowBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ foreach ($xmlSheet->rowBreaks->brk as $brk) {
+ if ($brk['man']) {
+ $worksheet->setBreak("A{$brk['id']}", Worksheet::BREAK_ROW);
+ }
+ }
+ }
+
+ private function columnBreaks(SimpleXMLElement $xmlSheet, Worksheet $worksheet): void
+ {
+ foreach ($xmlSheet->colBreaks->brk as $brk) {
+ if ($brk['man']) {
+ $worksheet->setBreak(
+ Coordinate::stringFromColumnIndex(((int) $brk['id']) + 1) . '1',
+ Worksheet::BREAK_COLUMN
+ );
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php
new file mode 100644
index 0000000..c0caca3
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/PageSettings.php
@@ -0,0 +1,134 @@
+pageSetup($xmlX, $namespaces, $this->getPrintDefaults());
+ $this->printSettings = $this->printSetup($xmlX, $printSettings);
+ }
+
+ public function loadPageSettings(Spreadsheet $spreadsheet): void
+ {
+ $spreadsheet->getActiveSheet()->getPageSetup()
+ ->setPaperSize($this->printSettings->paperSize)
+ ->setOrientation($this->printSettings->orientation)
+ ->setScale($this->printSettings->scale)
+ ->setVerticalCentered($this->printSettings->verticalCentered)
+ ->setHorizontalCentered($this->printSettings->horizontalCentered)
+ ->setPageOrder($this->printSettings->printOrder);
+ $spreadsheet->getActiveSheet()->getPageMargins()
+ ->setTop($this->printSettings->topMargin)
+ ->setHeader($this->printSettings->headerMargin)
+ ->setLeft($this->printSettings->leftMargin)
+ ->setRight($this->printSettings->rightMargin)
+ ->setBottom($this->printSettings->bottomMargin)
+ ->setFooter($this->printSettings->footerMargin);
+ }
+
+ private function getPrintDefaults(): stdClass
+ {
+ return (object) [
+ 'paperSize' => 9,
+ 'orientation' => PageSetup::ORIENTATION_DEFAULT,
+ 'scale' => 100,
+ 'horizontalCentered' => false,
+ 'verticalCentered' => false,
+ 'printOrder' => PageSetup::PAGEORDER_DOWN_THEN_OVER,
+ 'topMargin' => 0.75,
+ 'headerMargin' => 0.3,
+ 'leftMargin' => 0.7,
+ 'rightMargin' => 0.7,
+ 'bottomMargin' => 0.75,
+ 'footerMargin' => 0.3,
+ ];
+ }
+
+ private function pageSetup(SimpleXMLElement $xmlX, array $namespaces, stdClass $printDefaults): stdClass
+ {
+ if (isset($xmlX->WorksheetOptions->PageSetup)) {
+ foreach ($xmlX->WorksheetOptions->PageSetup as $pageSetupData) {
+ foreach ($pageSetupData as $pageSetupKey => $pageSetupValue) {
+ $pageSetupAttributes = $pageSetupValue->attributes($namespaces['x']);
+ if (!$pageSetupAttributes) {
+ continue;
+ }
+
+ switch ($pageSetupKey) {
+ case 'Layout':
+ $this->setLayout($printDefaults, $pageSetupAttributes);
+
+ break;
+ case 'Header':
+ $printDefaults->headerMargin = (float) $pageSetupAttributes->Margin ?: 1.0;
+
+ break;
+ case 'Footer':
+ $printDefaults->footerMargin = (float) $pageSetupAttributes->Margin ?: 1.0;
+
+ break;
+ case 'PageMargins':
+ $this->setMargins($printDefaults, $pageSetupAttributes);
+
+ break;
+ }
+ }
+ }
+ }
+
+ return $printDefaults;
+ }
+
+ private function printSetup(SimpleXMLElement $xmlX, stdClass $printDefaults): stdClass
+ {
+ if (isset($xmlX->WorksheetOptions->Print)) {
+ foreach ($xmlX->WorksheetOptions->Print as $printData) {
+ foreach ($printData as $printKey => $printValue) {
+ switch ($printKey) {
+ case 'LeftToRight':
+ $printDefaults->printOrder = PageSetup::PAGEORDER_OVER_THEN_DOWN;
+
+ break;
+ case 'PaperSizeIndex':
+ $printDefaults->paperSize = (int) $printValue ?: 9;
+
+ break;
+ case 'Scale':
+ $printDefaults->scale = (int) $printValue ?: 100;
+
+ break;
+ }
+ }
+ }
+ }
+
+ return $printDefaults;
+ }
+
+ private function setLayout(stdClass $printDefaults, SimpleXMLElement $pageSetupAttributes): void
+ {
+ $printDefaults->orientation = (string) strtolower($pageSetupAttributes->Orientation ?? '') ?: PageSetup::ORIENTATION_PORTRAIT;
+ $printDefaults->horizontalCentered = (bool) $pageSetupAttributes->CenterHorizontal ?: false;
+ $printDefaults->verticalCentered = (bool) $pageSetupAttributes->CenterVertical ?: false;
+ }
+
+ private function setMargins(stdClass $printDefaults, SimpleXMLElement $pageSetupAttributes): void
+ {
+ $printDefaults->leftMargin = (float) $pageSetupAttributes->Left ?: 1.0;
+ $printDefaults->rightMargin = (float) $pageSetupAttributes->Right ?: 1.0;
+ $printDefaults->topMargin = (float) $pageSetupAttributes->Top ?: 1.0;
+ $printDefaults->bottomMargin = (float) $pageSetupAttributes->Bottom ?: 1.0;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php
new file mode 100644
index 0000000..9a61215
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Fill.php
@@ -0,0 +1,63 @@
+ [
+ 'solid' => FillStyles::FILL_SOLID,
+ 'gray75' => FillStyles::FILL_PATTERN_DARKGRAY,
+ 'gray50' => FillStyles::FILL_PATTERN_MEDIUMGRAY,
+ 'gray25' => FillStyles::FILL_PATTERN_LIGHTGRAY,
+ 'gray125' => FillStyles::FILL_PATTERN_GRAY125,
+ 'gray0625' => FillStyles::FILL_PATTERN_GRAY0625,
+ 'horzstripe' => FillStyles::FILL_PATTERN_DARKHORIZONTAL, // horizontal stripe
+ 'vertstripe' => FillStyles::FILL_PATTERN_DARKVERTICAL, // vertical stripe
+ 'reversediagstripe' => FillStyles::FILL_PATTERN_DARKUP, // reverse diagonal stripe
+ 'diagstripe' => FillStyles::FILL_PATTERN_DARKDOWN, // diagonal stripe
+ 'diagcross' => FillStyles::FILL_PATTERN_DARKGRID, // diagoanl crosshatch
+ 'thickdiagcross' => FillStyles::FILL_PATTERN_DARKTRELLIS, // thick diagonal crosshatch
+ 'thinhorzstripe' => FillStyles::FILL_PATTERN_LIGHTHORIZONTAL,
+ 'thinvertstripe' => FillStyles::FILL_PATTERN_LIGHTVERTICAL,
+ 'thinreversediagstripe' => FillStyles::FILL_PATTERN_LIGHTUP,
+ 'thindiagstripe' => FillStyles::FILL_PATTERN_LIGHTDOWN,
+ 'thinhorzcross' => FillStyles::FILL_PATTERN_LIGHTGRID, // thin horizontal crosshatch
+ 'thindiagcross' => FillStyles::FILL_PATTERN_LIGHTTRELLIS, // thin diagonal crosshatch
+ ],
+ ];
+
+ public function parseStyle(SimpleXMLElement $styleAttributes): array
+ {
+ $style = [];
+
+ foreach ($styleAttributes as $styleAttributeKey => $styleAttributeValuex) {
+ $styleAttributeValue = (string) $styleAttributeValuex;
+ switch ($styleAttributeKey) {
+ case 'Color':
+ $style['fill']['endColor']['rgb'] = substr($styleAttributeValue, 1);
+ $style['fill']['startColor']['rgb'] = substr($styleAttributeValue, 1);
+
+ break;
+ case 'PatternColor':
+ $style['fill']['startColor']['rgb'] = substr($styleAttributeValue, 1);
+
+ break;
+ case 'Pattern':
+ $lcStyleAttributeValue = strtolower((string) $styleAttributeValue);
+ $style['fill']['fillType']
+ = self::FILL_MAPPINGS['fillType'][$lcStyleAttributeValue] ?? FillStyles::FILL_NONE;
+
+ break;
+ }
+ }
+
+ return $style;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php
new file mode 100644
index 0000000..16ab44d
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/Font.php
@@ -0,0 +1,79 @@
+ $styleAttributeValue) {
+ $styleAttributeValue = (string) $styleAttributeValue;
+ switch ($styleAttributeKey) {
+ case 'FontName':
+ $style['font']['name'] = $styleAttributeValue;
+
+ break;
+ case 'Size':
+ $style['font']['size'] = $styleAttributeValue;
+
+ break;
+ case 'Color':
+ $style['font']['color']['rgb'] = substr($styleAttributeValue, 1);
+
+ break;
+ case 'Bold':
+ $style['font']['bold'] = true;
+
+ break;
+ case 'Italic':
+ $style['font']['italic'] = true;
+
+ break;
+ case 'Underline':
+ $style = $this->parseUnderline($style, $styleAttributeValue);
+
+ break;
+ case 'VerticalAlign':
+ $style = $this->parseVerticalAlign($style, $styleAttributeValue);
+
+ break;
+ }
+ }
+
+ return $style;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php
new file mode 100644
index 0000000..a31aa9e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xml/Style/NumberFormat.php
@@ -0,0 +1,33 @@
+ $styleAttributeValue) {
+ $styleAttributeValue = str_replace($fromFormats, $toFormats, $styleAttributeValue);
+
+ switch ($styleAttributeValue) {
+ case 'Short Date':
+ $styleAttributeValue = 'dd/mm/yyyy';
+
+ break;
+ }
+
+ if ($styleAttributeValue > '') {
+ $style['numberFormat']['formatCode'] = $styleAttributeValue;
+ }
+ }
+
+ return $style;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php
new file mode 100644
index 0000000..39b70c8
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/RichText/ITextElement.php
@@ -0,0 +1,36 @@
+getName();
+ $size = $defaultFont->getSize();
+
+ if (isset(Font::$defaultColumnWidths[$name][$size])) {
+ // Exact width can be determined
+ return $pixelValue * Font::$defaultColumnWidths[$name][$size]['width']
+ / Font::$defaultColumnWidths[$name][$size]['px'];
+ }
+
+ // We don't have data for this particular font and size, use approximation by
+ // extrapolating from Calibri 11
+ return $pixelValue * 11 * Font::$defaultColumnWidths['Calibri'][11]['width']
+ / Font::$defaultColumnWidths['Calibri'][11]['px'] / $size;
+ }
+
+ /**
+ * Convert column width from (intrinsic) Excel units to pixels.
+ *
+ * @param float $cellWidth Value in cell dimension
+ * @param \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont Default font of the workbook
+ *
+ * @return int Value in pixels
+ */
+ public static function cellDimensionToPixels($cellWidth, \PhpOffice\PhpSpreadsheet\Style\Font $defaultFont)
+ {
+ // Font name and size
+ $name = $defaultFont->getName();
+ $size = $defaultFont->getSize();
+
+ if (isset(Font::$defaultColumnWidths[$name][$size])) {
+ // Exact width can be determined
+ $colWidth = $cellWidth * Font::$defaultColumnWidths[$name][$size]['px']
+ / Font::$defaultColumnWidths[$name][$size]['width'];
+ } else {
+ // We don't have data for this particular font and size, use approximation by
+ // extrapolating from Calibri 11
+ $colWidth = $cellWidth * $size * Font::$defaultColumnWidths['Calibri'][11]['px']
+ / Font::$defaultColumnWidths['Calibri'][11]['width'] / 11;
+ }
+
+ // Round pixels to closest integer
+ $colWidth = (int) round($colWidth);
+
+ return $colWidth;
+ }
+
+ /**
+ * Convert pixels to points.
+ *
+ * @param int $pixelValue Value in pixels
+ *
+ * @return float Value in points
+ */
+ public static function pixelsToPoints($pixelValue)
+ {
+ return $pixelValue * 0.75;
+ }
+
+ /**
+ * Convert points to pixels.
+ *
+ * @param int $pointValue Value in points
+ *
+ * @return int Value in pixels
+ */
+ public static function pointsToPixels($pointValue)
+ {
+ if ($pointValue != 0) {
+ return (int) ceil($pointValue / 0.75);
+ }
+
+ return 0;
+ }
+
+ /**
+ * Convert degrees to angle.
+ *
+ * @param int $degrees Degrees
+ *
+ * @return int Angle
+ */
+ public static function degreesToAngle($degrees)
+ {
+ return (int) round($degrees * 60000);
+ }
+
+ /**
+ * Convert angle to degrees.
+ *
+ * @param int|SimpleXMLElement $angle Angle
+ *
+ * @return int Degrees
+ */
+ public static function angleToDegrees($angle)
+ {
+ $angle = (int) $angle;
+ if ($angle != 0) {
+ return (int) round($angle / 60000);
+ }
+
+ return 0;
+ }
+
+ /**
+ * Create a new image from file. By alexander at alexauto dot nl.
+ *
+ * @see http://www.php.net/manual/en/function.imagecreatefromwbmp.php#86214
+ *
+ * @param string $bmpFilename Path to Windows DIB (BMP) image
+ *
+ * @return GdImage|resource
+ */
+ public static function imagecreatefrombmp($bmpFilename)
+ {
+ // Load the image into a string
+ $file = fopen($bmpFilename, 'rb');
+ $read = fread($file, 10);
+ while (!feof($file) && ($read != '')) {
+ $read .= fread($file, 1024);
+ }
+
+ $temp = unpack('H*', $read);
+ $hex = $temp[1];
+ $header = substr($hex, 0, 108);
+
+ // Process the header
+ // Structure: http://www.fastgraph.com/help/bmp_header_format.html
+ $width = 0;
+ $height = 0;
+ if (substr($header, 0, 4) == '424d') {
+ // Cut it in parts of 2 bytes
+ $header_parts = str_split($header, 2);
+
+ // Get the width 4 bytes
+ $width = hexdec($header_parts[19] . $header_parts[18]);
+
+ // Get the height 4 bytes
+ $height = hexdec($header_parts[23] . $header_parts[22]);
+
+ // Unset the header params
+ unset($header_parts);
+ }
+
+ // Define starting X and Y
+ $x = 0;
+ $y = 1;
+
+ // Create newimage
+ $image = imagecreatetruecolor($width, $height);
+
+ // Grab the body from the image
+ $body = substr($hex, 108);
+
+ // Calculate if padding at the end-line is needed
+ // Divided by two to keep overview.
+ // 1 byte = 2 HEX-chars
+ $body_size = (strlen($body) / 2);
+ $header_size = ($width * $height);
+
+ // Use end-line padding? Only when needed
+ $usePadding = ($body_size > ($header_size * 3) + 4);
+
+ // Using a for-loop with index-calculation instaid of str_split to avoid large memory consumption
+ // Calculate the next DWORD-position in the body
+ for ($i = 0; $i < $body_size; $i += 3) {
+ // Calculate line-ending and padding
+ if ($x >= $width) {
+ // If padding needed, ignore image-padding
+ // Shift i to the ending of the current 32-bit-block
+ if ($usePadding) {
+ $i += $width % 4;
+ }
+
+ // Reset horizontal position
+ $x = 0;
+
+ // Raise the height-position (bottom-up)
+ ++$y;
+
+ // Reached the image-height? Break the for-loop
+ if ($y > $height) {
+ break;
+ }
+ }
+
+ // Calculation of the RGB-pixel (defined as BGR in image-data)
+ // Define $i_pos as absolute position in the body
+ $i_pos = $i * 2;
+ $r = hexdec($body[$i_pos + 4] . $body[$i_pos + 5]);
+ $g = hexdec($body[$i_pos + 2] . $body[$i_pos + 3]);
+ $b = hexdec($body[$i_pos] . $body[$i_pos + 1]);
+
+ // Calculate and draw the pixel
+ $color = imagecolorallocate($image, $r, $g, $b);
+ imagesetpixel($image, $x, $height - $y, $color);
+
+ // Raise the horizontal position
+ ++$x;
+ }
+
+ // Unset the body / free the memory
+ unset($body);
+
+ // Return image-object
+ return $image;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php
new file mode 100644
index 0000000..c6d0a6f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher.php
@@ -0,0 +1,64 @@
+dggContainer;
+ }
+
+ /**
+ * Set Drawing Group Container.
+ *
+ * @param Escher\DggContainer $dggContainer
+ *
+ * @return Escher\DggContainer
+ */
+ public function setDggContainer($dggContainer)
+ {
+ return $this->dggContainer = $dggContainer;
+ }
+
+ /**
+ * Get Drawing Container.
+ *
+ * @return Escher\DgContainer
+ */
+ public function getDgContainer()
+ {
+ return $this->dgContainer;
+ }
+
+ /**
+ * Set Drawing Container.
+ *
+ * @param Escher\DgContainer $dgContainer
+ *
+ * @return Escher\DgContainer
+ */
+ public function setDgContainer($dgContainer)
+ {
+ return $this->dgContainer = $dgContainer;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php
new file mode 100644
index 0000000..f2fe8ca
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php
@@ -0,0 +1,185 @@
+open($zipFile);
+ if ($res === true) {
+ $returnValue = ($zip->getFromName($archiveFile) !== false);
+ $zip->close();
+
+ return $returnValue;
+ }
+ }
+
+ return false;
+ }
+
+ return file_exists($filename);
+ }
+
+ /**
+ * Returns canonicalized absolute pathname, also for ZIP archives.
+ */
+ public static function realpath(string $filename): string
+ {
+ // Returnvalue
+ $returnValue = '';
+
+ // Try using realpath()
+ if (file_exists($filename)) {
+ $returnValue = realpath($filename) ?: '';
+ }
+
+ // Found something?
+ if ($returnValue === '') {
+ $pathArray = explode('/', $filename);
+ while (in_array('..', $pathArray) && $pathArray[0] != '..') {
+ $iMax = count($pathArray);
+ for ($i = 0; $i < $iMax; ++$i) {
+ if ($pathArray[$i] == '..' && $i > 0) {
+ unset($pathArray[$i], $pathArray[$i - 1]);
+
+ break;
+ }
+ }
+ }
+ $returnValue = implode('/', $pathArray);
+ }
+
+ // Return
+ return $returnValue;
+ }
+
+ /**
+ * Get the systems temporary directory.
+ */
+ public static function sysGetTempDir(): string
+ {
+ $path = sys_get_temp_dir();
+ if (self::$useUploadTempDirectory) {
+ // use upload-directory when defined to allow running on environments having very restricted
+ // open_basedir configs
+ if (ini_get('upload_tmp_dir') !== false) {
+ if ($temp = ini_get('upload_tmp_dir')) {
+ if (file_exists($temp)) {
+ $path = $temp;
+ }
+ }
+ }
+ }
+
+ return realpath($path) ?: '';
+ }
+
+ public static function temporaryFilename(): string
+ {
+ $filename = tempnam(self::sysGetTempDir(), 'phpspreadsheet');
+ if ($filename === false) {
+ throw new Exception('Could not create temporary file');
+ }
+
+ return $filename;
+ }
+
+ /**
+ * Assert that given path is an existing file and is readable, otherwise throw exception.
+ */
+ public static function assertFile(string $filename, string $zipMember = ''): void
+ {
+ if (!is_file($filename)) {
+ throw new ReaderException('File "' . $filename . '" does not exist.');
+ }
+
+ if (!is_readable($filename)) {
+ throw new ReaderException('Could not open "' . $filename . '" for reading.');
+ }
+
+ if ($zipMember !== '') {
+ $zipfile = "zip://$filename#$zipMember";
+ if (!self::fileExists($zipfile)) {
+ throw new ReaderException("Could not find zip member $zipfile");
+ }
+ }
+ }
+
+ /**
+ * Same as assertFile, except return true/false and don't throw Exception.
+ */
+ public static function testFileNoThrow(string $filename, ?string $zipMember = null): bool
+ {
+ if (!is_file($filename)) {
+ return false;
+ }
+ if (!is_readable($filename)) {
+ return false;
+ }
+ if ($zipMember === null) {
+ return true;
+ }
+ // validate zip, but don't check specific member
+ if ($zipMember === '') {
+ return self::validateZipFirst4($filename);
+ }
+
+ return self::fileExists("zip://$filename#$zipMember");
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php
new file mode 100644
index 0000000..9a74bef
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php
@@ -0,0 +1,758 @@
+ [
+ 1 => ['px' => 24, 'width' => 12.00000000],
+ 2 => ['px' => 24, 'width' => 12.00000000],
+ 3 => ['px' => 32, 'width' => 10.66406250],
+ 4 => ['px' => 32, 'width' => 10.66406250],
+ 5 => ['px' => 40, 'width' => 10.00000000],
+ 6 => ['px' => 48, 'width' => 9.59765625],
+ 7 => ['px' => 48, 'width' => 9.59765625],
+ 8 => ['px' => 56, 'width' => 9.33203125],
+ 9 => ['px' => 64, 'width' => 9.14062500],
+ 10 => ['px' => 64, 'width' => 9.14062500],
+ ],
+ 'Calibri' => [
+ 1 => ['px' => 24, 'width' => 12.00000000],
+ 2 => ['px' => 24, 'width' => 12.00000000],
+ 3 => ['px' => 32, 'width' => 10.66406250],
+ 4 => ['px' => 32, 'width' => 10.66406250],
+ 5 => ['px' => 40, 'width' => 10.00000000],
+ 6 => ['px' => 48, 'width' => 9.59765625],
+ 7 => ['px' => 48, 'width' => 9.59765625],
+ 8 => ['px' => 56, 'width' => 9.33203125],
+ 9 => ['px' => 56, 'width' => 9.33203125],
+ 10 => ['px' => 64, 'width' => 9.14062500],
+ 11 => ['px' => 64, 'width' => 9.14062500],
+ ],
+ 'Verdana' => [
+ 1 => ['px' => 24, 'width' => 12.00000000],
+ 2 => ['px' => 24, 'width' => 12.00000000],
+ 3 => ['px' => 32, 'width' => 10.66406250],
+ 4 => ['px' => 32, 'width' => 10.66406250],
+ 5 => ['px' => 40, 'width' => 10.00000000],
+ 6 => ['px' => 48, 'width' => 9.59765625],
+ 7 => ['px' => 48, 'width' => 9.59765625],
+ 8 => ['px' => 64, 'width' => 9.14062500],
+ 9 => ['px' => 72, 'width' => 9.00000000],
+ 10 => ['px' => 72, 'width' => 9.00000000],
+ ],
+ ];
+
+ /**
+ * Set autoSize method.
+ *
+ * @param string $method see self::AUTOSIZE_METHOD_*
+ *
+ * @return bool Success or failure
+ */
+ public static function setAutoSizeMethod($method)
+ {
+ if (!in_array($method, self::$autoSizeMethods)) {
+ return false;
+ }
+ self::$autoSizeMethod = $method;
+
+ return true;
+ }
+
+ /**
+ * Get autoSize method.
+ *
+ * @return string
+ */
+ public static function getAutoSizeMethod()
+ {
+ return self::$autoSizeMethod;
+ }
+
+ /**
+ * Set the path to the folder containing .ttf files. There should be a trailing slash.
+ * Typical locations on variout some platforms:
+ *
+ *
C:/Windows/Fonts/
+ *
/usr/share/fonts/truetype/
+ *
~/.fonts/
+ *
.
+ *
+ * @param string $folderPath
+ */
+ public static function setTrueTypeFontPath($folderPath): void
+ {
+ self::$trueTypeFontPath = $folderPath;
+ }
+
+ /**
+ * Get the path to the folder containing .ttf files.
+ *
+ * @return string
+ */
+ public static function getTrueTypeFontPath()
+ {
+ return self::$trueTypeFontPath;
+ }
+
+ /**
+ * Calculate an (approximate) OpenXML column width, based on font size and text contained.
+ *
+ * @param FontStyle $font Font object
+ * @param RichText|string $cellText Text to calculate width
+ * @param int $rotation Rotation angle
+ * @param null|FontStyle $defaultFont Font object
+ *
+ * @return int Column width
+ */
+ public static function calculateColumnWidth(FontStyle $font, $cellText = '', $rotation = 0, ?FontStyle $defaultFont = null)
+ {
+ // If it is rich text, use plain text
+ if ($cellText instanceof RichText) {
+ $cellText = $cellText->getPlainText();
+ }
+
+ // Special case if there are one or more newline characters ("\n")
+ if (strpos($cellText ?? '', "\n") !== false) {
+ $lineTexts = explode("\n", $cellText);
+ $lineWidths = [];
+ foreach ($lineTexts as $lineText) {
+ $lineWidths[] = self::calculateColumnWidth($font, $lineText, $rotation = 0, $defaultFont);
+ }
+
+ return max($lineWidths); // width of longest line in cell
+ }
+
+ // Try to get the exact text width in pixels
+ $approximate = self::$autoSizeMethod == self::AUTOSIZE_METHOD_APPROX;
+ $columnWidth = 0;
+ if (!$approximate) {
+ $columnWidthAdjust = ceil(self::getTextWidthPixelsExact('n', $font, 0) * 1.07);
+
+ try {
+ // 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;
+ } catch (PhpSpreadsheetException $e) {
+ $approximate = true;
+ }
+ }
+
+ if ($approximate) {
+ $columnWidthAdjust = self::getTextWidthPixelsApprox('n', $font, 0);
+ // Width of text in pixels excl. padding, approximation
+ // and addition because Excel adds some padding, just use approx width of 'n' glyph
+ $columnWidth = self::getTextWidthPixelsApprox($cellText, $font, $rotation) + $columnWidthAdjust;
+ }
+
+ // Convert from pixel width to column width
+ $columnWidth = Drawing::pixelsToCellDimension((int) $columnWidth, $defaultFont);
+
+ // Return
+ return (int) round($columnWidth, 6);
+ }
+
+ /**
+ * Get GD text width in pixels for a string of text in a certain font at a certain rotation angle.
+ */
+ public static function getTextWidthPixelsExact(string $text, FontStyle $font, int $rotation = 0): int
+ {
+ if (!function_exists('imagettfbbox')) {
+ throw new PhpSpreadsheetException('GD library needs to be enabled');
+ }
+
+ // font size should really be supplied in pixels in GD2,
+ // but since GD2 seems to assume 72dpi, pixels and points are the same
+ $fontFile = self::getTrueTypeFontFileFromFont($font);
+ $textBox = imagettfbbox($font->getSize(), $rotation, $fontFile, $text);
+
+ // Get corners positions
+ $lowerLeftCornerX = $textBox[0];
+ $lowerRightCornerX = $textBox[2];
+ $upperRightCornerX = $textBox[4];
+ $upperLeftCornerX = $textBox[6];
+
+ // Consider the rotation when calculating the width
+ return max($lowerRightCornerX - $upperLeftCornerX, $upperRightCornerX - $lowerLeftCornerX);
+ }
+
+ /**
+ * Get approximate width in pixels for a string of text in a certain font at a certain rotation angle.
+ *
+ * @param string $columnText
+ * @param int $rotation
+ *
+ * @return int Text width in pixels (no padding added)
+ */
+ public static function getTextWidthPixelsApprox($columnText, FontStyle $font, $rotation = 0)
+ {
+ $fontName = $font->getName();
+ $fontSize = $font->getSize();
+
+ // Calculate column width in pixels. We assume fixed glyph width. 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 = $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 = $columnWidth * $fontSize / 10; // extrapolate from font size
+
+ break;
+ default:
+ // just assume Calibri
+ $columnWidth = (int) (8.26 * StringHelper::countCharacters($columnText));
+ $columnWidth = $columnWidth * $fontSize / 11; // extrapolate from font size
+
+ break;
+ }
+
+ // Calculate approximate rotated column width
+ if ($rotation !== 0) {
+ if ($rotation == Alignment::TEXTROTATION_STACK_PHPSPREADSHEET) {
+ // stacked text
+ $columnWidth = 4; // approximation
+ } else {
+ // rotated text
+ $columnWidth = $columnWidth * cos(deg2rad($rotation))
+ + $fontSize * abs(sin(deg2rad($rotation))) / 5; // approximation
+ }
+ }
+
+ // pixel width is an integer
+ return (int) $columnWidth;
+ }
+
+ /**
+ * Calculate an (approximate) pixel size, based on a font points size.
+ *
+ * @param int $fontSizeInPoints Font size (in points)
+ *
+ * @return int Font size (in pixels)
+ */
+ public static function fontSizeToPixels($fontSizeInPoints)
+ {
+ return (int) ((4 / 3) * $fontSizeInPoints);
+ }
+
+ /**
+ * Calculate an (approximate) pixel size, based on inch size.
+ *
+ * @param int $sizeInInch Font size (in inch)
+ *
+ * @return int Size (in pixels)
+ */
+ public static function inchSizeToPixels($sizeInInch)
+ {
+ return $sizeInInch * 96;
+ }
+
+ /**
+ * Calculate an (approximate) pixel size, based on centimeter size.
+ *
+ * @param int $sizeInCm Font size (in centimeters)
+ *
+ * @return float Size (in pixels)
+ */
+ public static function centimeterSizeToPixels($sizeInCm)
+ {
+ return $sizeInCm * 37.795275591;
+ }
+
+ /**
+ * Returns the font path given the font.
+ *
+ * @return string Path to TrueType font file
+ */
+ public static function getTrueTypeFontFileFromFont(FontStyle $font)
+ {
+ if (!file_exists(self::$trueTypeFontPath) || !is_dir(self::$trueTypeFontPath)) {
+ throw new PhpSpreadsheetException('Valid directory to TrueType Font files not specified');
+ }
+
+ $name = $font->getName();
+ $bold = $font->getBold();
+ $italic = $font->getItalic();
+
+ // Check if we can map font to true type font file
+ switch ($name) {
+ case 'Arial':
+ $fontFile = (
+ $bold ? ($italic ? self::ARIAL_BOLD_ITALIC : self::ARIAL_BOLD)
+ : ($italic ? self::ARIAL_ITALIC : self::ARIAL)
+ );
+
+ break;
+ case 'Calibri':
+ $fontFile = (
+ $bold ? ($italic ? self::CALIBRI_BOLD_ITALIC : self::CALIBRI_BOLD)
+ : ($italic ? self::CALIBRI_ITALIC : self::CALIBRI)
+ );
+
+ break;
+ case 'Courier New':
+ $fontFile = (
+ $bold ? ($italic ? self::COURIER_NEW_BOLD_ITALIC : self::COURIER_NEW_BOLD)
+ : ($italic ? self::COURIER_NEW_ITALIC : self::COURIER_NEW)
+ );
+
+ break;
+ case 'Comic Sans MS':
+ $fontFile = (
+ $bold ? self::COMIC_SANS_MS_BOLD : self::COMIC_SANS_MS
+ );
+
+ break;
+ case 'Georgia':
+ $fontFile = (
+ $bold ? ($italic ? self::GEORGIA_BOLD_ITALIC : self::GEORGIA_BOLD)
+ : ($italic ? self::GEORGIA_ITALIC : self::GEORGIA)
+ );
+
+ break;
+ case 'Impact':
+ $fontFile = self::IMPACT;
+
+ break;
+ case 'Liberation Sans':
+ $fontFile = (
+ $bold ? ($italic ? self::LIBERATION_SANS_BOLD_ITALIC : self::LIBERATION_SANS_BOLD)
+ : ($italic ? self::LIBERATION_SANS_ITALIC : self::LIBERATION_SANS)
+ );
+
+ break;
+ case 'Lucida Console':
+ $fontFile = self::LUCIDA_CONSOLE;
+
+ break;
+ case 'Lucida Sans Unicode':
+ $fontFile = self::LUCIDA_SANS_UNICODE;
+
+ break;
+ case 'Microsoft Sans Serif':
+ $fontFile = self::MICROSOFT_SANS_SERIF;
+
+ break;
+ case 'Palatino Linotype':
+ $fontFile = (
+ $bold ? ($italic ? self::PALATINO_LINOTYPE_BOLD_ITALIC : self::PALATINO_LINOTYPE_BOLD)
+ : ($italic ? self::PALATINO_LINOTYPE_ITALIC : self::PALATINO_LINOTYPE)
+ );
+
+ break;
+ case 'Symbol':
+ $fontFile = self::SYMBOL;
+
+ break;
+ case 'Tahoma':
+ $fontFile = (
+ $bold ? self::TAHOMA_BOLD : self::TAHOMA
+ );
+
+ break;
+ case 'Times New Roman':
+ $fontFile = (
+ $bold ? ($italic ? self::TIMES_NEW_ROMAN_BOLD_ITALIC : self::TIMES_NEW_ROMAN_BOLD)
+ : ($italic ? self::TIMES_NEW_ROMAN_ITALIC : self::TIMES_NEW_ROMAN)
+ );
+
+ break;
+ case 'Trebuchet MS':
+ $fontFile = (
+ $bold ? ($italic ? self::TREBUCHET_MS_BOLD_ITALIC : self::TREBUCHET_MS_BOLD)
+ : ($italic ? self::TREBUCHET_MS_ITALIC : self::TREBUCHET_MS)
+ );
+
+ break;
+ case 'Verdana':
+ $fontFile = (
+ $bold ? ($italic ? self::VERDANA_BOLD_ITALIC : self::VERDANA_BOLD)
+ : ($italic ? self::VERDANA_ITALIC : self::VERDANA)
+ );
+
+ break;
+ default:
+ throw new PhpSpreadsheetException('Unknown font name "' . $name . '". Cannot map to TrueType font file');
+
+ break;
+ }
+
+ $fontFile = self::$trueTypeFontPath . $fontFile;
+
+ // Check if file actually exists
+ if (!file_exists($fontFile)) {
+ throw new PhpSpreadsheetException('TrueType Font file not found');
+ }
+
+ return $fontFile;
+ }
+
+ /**
+ * Returns the associated charset for the font name.
+ *
+ * @param string $fontName Font name
+ *
+ * @return int Character set code
+ */
+ public static function getCharsetFromFontName($fontName)
+ {
+ switch ($fontName) {
+ // Add more cases. Check FONT records in real Excel files.
+ case 'EucrosiaUPC':
+ return self::CHARSET_ANSI_THAI;
+ case 'Wingdings':
+ return self::CHARSET_SYMBOL;
+ case 'Wingdings 2':
+ return self::CHARSET_SYMBOL;
+ case 'Wingdings 3':
+ return self::CHARSET_SYMBOL;
+ default:
+ return self::CHARSET_ANSI_LATIN;
+ }
+ }
+
+ /**
+ * Get the effective column width for columns without a column dimension or column with width -1
+ * For example, for Calibri 11 this is 9.140625 (64 px).
+ *
+ * @param FontStyle $font The workbooks default font
+ * @param bool $returnAsPixels true = return column width in pixels, false = return in OOXML units
+ *
+ * @return mixed Column width
+ */
+ public static function getDefaultColumnWidthByFont(FontStyle $font, $returnAsPixels = false)
+ {
+ if (isset(self::$defaultColumnWidths[$font->getName()][$font->getSize()])) {
+ // Exact width can be determined
+ $columnWidth = $returnAsPixels ?
+ self::$defaultColumnWidths[$font->getName()][$font->getSize()]['px']
+ : self::$defaultColumnWidths[$font->getName()][$font->getSize()]['width'];
+ } else {
+ // We don't have data for this particular font and size, use approximation by
+ // extrapolating from Calibri 11
+ $columnWidth = $returnAsPixels ?
+ self::$defaultColumnWidths['Calibri'][11]['px']
+ : self::$defaultColumnWidths['Calibri'][11]['width'];
+ $columnWidth = $columnWidth * $font->getSize() / 11;
+
+ // Round pixels to closest integer
+ if ($returnAsPixels) {
+ $columnWidth = (int) round($columnWidth);
+ }
+ }
+
+ return $columnWidth;
+ }
+
+ /**
+ * Get the effective row height for rows without a row dimension or rows with height -1
+ * For example, for Calibri 11 this is 15 points.
+ *
+ * @param FontStyle $font The workbooks default font
+ *
+ * @return float Row height in points
+ */
+ public static function getDefaultRowHeightByFont(FontStyle $font)
+ {
+ switch ($font->getName()) {
+ case 'Arial':
+ switch ($font->getSize()) {
+ case 10:
+ // inspection of Arial 10 workbook says 12.75pt ~17px
+ $rowHeight = 12.75;
+
+ break;
+ case 9:
+ // inspection of Arial 9 workbook says 12.00pt ~16px
+ $rowHeight = 12;
+
+ break;
+ case 8:
+ // inspection of Arial 8 workbook says 11.25pt ~15px
+ $rowHeight = 11.25;
+
+ break;
+ case 7:
+ // inspection of Arial 7 workbook says 9.00pt ~12px
+ $rowHeight = 9;
+
+ break;
+ case 6:
+ case 5:
+ // inspection of Arial 5,6 workbook says 8.25pt ~11px
+ $rowHeight = 8.25;
+
+ break;
+ case 4:
+ // inspection of Arial 4 workbook says 6.75pt ~9px
+ $rowHeight = 6.75;
+
+ break;
+ case 3:
+ // inspection of Arial 3 workbook says 6.00pt ~8px
+ $rowHeight = 6;
+
+ break;
+ case 2:
+ case 1:
+ // inspection of Arial 1,2 workbook says 5.25pt ~7px
+ $rowHeight = 5.25;
+
+ break;
+ default:
+ // use Arial 10 workbook as an approximation, extrapolation
+ $rowHeight = 12.75 * $font->getSize() / 10;
+
+ break;
+ }
+
+ break;
+ case 'Calibri':
+ switch ($font->getSize()) {
+ case 11:
+ // inspection of Calibri 11 workbook says 15.00pt ~20px
+ $rowHeight = 15;
+
+ break;
+ case 10:
+ // inspection of Calibri 10 workbook says 12.75pt ~17px
+ $rowHeight = 12.75;
+
+ break;
+ case 9:
+ // inspection of Calibri 9 workbook says 12.00pt ~16px
+ $rowHeight = 12;
+
+ break;
+ case 8:
+ // inspection of Calibri 8 workbook says 11.25pt ~15px
+ $rowHeight = 11.25;
+
+ break;
+ case 7:
+ // inspection of Calibri 7 workbook says 9.00pt ~12px
+ $rowHeight = 9;
+
+ break;
+ case 6:
+ case 5:
+ // inspection of Calibri 5,6 workbook says 8.25pt ~11px
+ $rowHeight = 8.25;
+
+ break;
+ case 4:
+ // inspection of Calibri 4 workbook says 6.75pt ~9px
+ $rowHeight = 6.75;
+
+ break;
+ case 3:
+ // inspection of Calibri 3 workbook says 6.00pt ~8px
+ $rowHeight = 6.00;
+
+ break;
+ case 2:
+ case 1:
+ // inspection of Calibri 1,2 workbook says 5.25pt ~7px
+ $rowHeight = 5.25;
+
+ break;
+ default:
+ // use Calibri 11 workbook as an approximation, extrapolation
+ $rowHeight = 15 * $font->getSize() / 11;
+
+ break;
+ }
+
+ break;
+ case 'Verdana':
+ switch ($font->getSize()) {
+ case 10:
+ // inspection of Verdana 10 workbook says 12.75pt ~17px
+ $rowHeight = 12.75;
+
+ break;
+ case 9:
+ // inspection of Verdana 9 workbook says 11.25pt ~15px
+ $rowHeight = 11.25;
+
+ break;
+ case 8:
+ // inspection of Verdana 8 workbook says 10.50pt ~14px
+ $rowHeight = 10.50;
+
+ break;
+ case 7:
+ // inspection of Verdana 7 workbook says 9.00pt ~12px
+ $rowHeight = 9.00;
+
+ break;
+ case 6:
+ case 5:
+ // inspection of Verdana 5,6 workbook says 8.25pt ~11px
+ $rowHeight = 8.25;
+
+ break;
+ case 4:
+ // inspection of Verdana 4 workbook says 6.75pt ~9px
+ $rowHeight = 6.75;
+
+ break;
+ case 3:
+ // inspection of Verdana 3 workbook says 6.00pt ~8px
+ $rowHeight = 6;
+
+ break;
+ case 2:
+ case 1:
+ // inspection of Verdana 1,2 workbook says 5.25pt ~7px
+ $rowHeight = 5.25;
+
+ break;
+ default:
+ // use Verdana 10 workbook as an approximation, extrapolation
+ $rowHeight = 12.75 * $font->getSize() / 10;
+
+ break;
+ }
+
+ break;
+ default:
+ // just use Calibri as an approximation
+ $rowHeight = 15 * $font->getSize() / 11;
+
+ break;
+ }
+
+ return $rowHeight;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php
new file mode 100644
index 0000000..060f09c
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/IntOrFloat.php
@@ -0,0 +1,21 @@
+d = $this->V[$this->n - 1];
+ $j = 0;
+ // Householder reduction to tridiagonal form.
+ for ($i = $this->n - 1; $i > 0; --$i) {
+ $i_ = $i - 1;
+ // Scale to avoid under/overflow.
+ $h = $scale = 0.0;
+ $scale += array_sum(array_map('abs', $this->d));
+ if ($scale == 0.0) {
+ $this->e[$i] = $this->d[$i_];
+ $this->d = array_slice($this->V[$i_], 0, $i_);
+ for ($j = 0; $j < $i; ++$j) {
+ $this->V[$j][$i] = $this->V[$i][$j] = 0.0;
+ }
+ } else {
+ // Generate Householder vector.
+ for ($k = 0; $k < $i; ++$k) {
+ $this->d[$k] /= $scale;
+ $h += $this->d[$k] ** 2;
+ }
+ $f = $this->d[$i_];
+ $g = sqrt($h);
+ if ($f > 0) {
+ $g = -$g;
+ }
+ $this->e[$i] = $scale * $g;
+ $h = $h - $f * $g;
+ $this->d[$i_] = $f - $g;
+ for ($j = 0; $j < $i; ++$j) {
+ $this->e[$j] = 0.0;
+ }
+ // Apply similarity transformation to remaining columns.
+ for ($j = 0; $j < $i; ++$j) {
+ $f = $this->d[$j];
+ $this->V[$j][$i] = $f;
+ $g = $this->e[$j] + $this->V[$j][$j] * $f;
+ for ($k = $j + 1; $k <= $i_; ++$k) {
+ $g += $this->V[$k][$j] * $this->d[$k];
+ $this->e[$k] += $this->V[$k][$j] * $f;
+ }
+ $this->e[$j] = $g;
+ }
+ $f = 0.0;
+ for ($j = 0; $j < $i; ++$j) {
+ $this->e[$j] /= $h;
+ $f += $this->e[$j] * $this->d[$j];
+ }
+ $hh = $f / (2 * $h);
+ for ($j = 0; $j < $i; ++$j) {
+ $this->e[$j] -= $hh * $this->d[$j];
+ }
+ for ($j = 0; $j < $i; ++$j) {
+ $f = $this->d[$j];
+ $g = $this->e[$j];
+ for ($k = $j; $k <= $i_; ++$k) {
+ $this->V[$k][$j] -= ($f * $this->e[$k] + $g * $this->d[$k]);
+ }
+ $this->d[$j] = $this->V[$i - 1][$j];
+ $this->V[$i][$j] = 0.0;
+ }
+ }
+ $this->d[$i] = $h;
+ }
+
+ // Accumulate transformations.
+ for ($i = 0; $i < $this->n - 1; ++$i) {
+ $this->V[$this->n - 1][$i] = $this->V[$i][$i];
+ $this->V[$i][$i] = 1.0;
+ $h = $this->d[$i + 1];
+ if ($h != 0.0) {
+ for ($k = 0; $k <= $i; ++$k) {
+ $this->d[$k] = $this->V[$k][$i + 1] / $h;
+ }
+ for ($j = 0; $j <= $i; ++$j) {
+ $g = 0.0;
+ for ($k = 0; $k <= $i; ++$k) {
+ $g += $this->V[$k][$i + 1] * $this->V[$k][$j];
+ }
+ for ($k = 0; $k <= $i; ++$k) {
+ $this->V[$k][$j] -= $g * $this->d[$k];
+ }
+ }
+ }
+ for ($k = 0; $k <= $i; ++$k) {
+ $this->V[$k][$i + 1] = 0.0;
+ }
+ }
+
+ $this->d = $this->V[$this->n - 1];
+ $this->V[$this->n - 1] = array_fill(0, $j, 0.0);
+ $this->V[$this->n - 1][$this->n - 1] = 1.0;
+ $this->e[0] = 0.0;
+ }
+
+ /**
+ * Symmetric tridiagonal QL algorithm.
+ *
+ * This is derived from the Algol procedures tql2, by
+ * Bowdler, Martin, Reinsch, and Wilkinson, Handbook for
+ * Auto. Comp., Vol.ii-Linear Algebra, and the corresponding
+ * Fortran subroutine in EISPACK.
+ */
+ private function tql2(): void
+ {
+ for ($i = 1; $i < $this->n; ++$i) {
+ $this->e[$i - 1] = $this->e[$i];
+ }
+ $this->e[$this->n - 1] = 0.0;
+ $f = 0.0;
+ $tst1 = 0.0;
+ $eps = 2.0 ** (-52.0);
+
+ for ($l = 0; $l < $this->n; ++$l) {
+ // Find small subdiagonal element
+ $tst1 = max($tst1, abs($this->d[$l]) + abs($this->e[$l]));
+ $m = $l;
+ while ($m < $this->n) {
+ if (abs($this->e[$m]) <= $eps * $tst1) {
+ break;
+ }
+ ++$m;
+ }
+ // If m == l, $this->d[l] is an eigenvalue,
+ // otherwise, iterate.
+ if ($m > $l) {
+ $iter = 0;
+ do {
+ // Could check iteration count here.
+ ++$iter;
+ // Compute implicit shift
+ $g = $this->d[$l];
+ $p = ($this->d[$l + 1] - $g) / (2.0 * $this->e[$l]);
+ $r = hypo($p, 1.0);
+ if ($p < 0) {
+ $r *= -1;
+ }
+ $this->d[$l] = $this->e[$l] / ($p + $r);
+ $this->d[$l + 1] = $this->e[$l] * ($p + $r);
+ $dl1 = $this->d[$l + 1];
+ $h = $g - $this->d[$l];
+ for ($i = $l + 2; $i < $this->n; ++$i) {
+ $this->d[$i] -= $h;
+ }
+ $f += $h;
+ // Implicit QL transformation.
+ $p = $this->d[$m];
+ $c = 1.0;
+ $c2 = $c3 = $c;
+ $el1 = $this->e[$l + 1];
+ $s = $s2 = 0.0;
+ for ($i = $m - 1; $i >= $l; --$i) {
+ $c3 = $c2;
+ $c2 = $c;
+ $s2 = $s;
+ $g = $c * $this->e[$i];
+ $h = $c * $p;
+ $r = hypo($p, $this->e[$i]);
+ $this->e[$i + 1] = $s * $r;
+ $s = $this->e[$i] / $r;
+ $c = $p / $r;
+ $p = $c * $this->d[$i] - $s * $g;
+ $this->d[$i + 1] = $h + $s * ($c * $g + $s * $this->d[$i]);
+ // Accumulate transformation.
+ for ($k = 0; $k < $this->n; ++$k) {
+ $h = $this->V[$k][$i + 1];
+ $this->V[$k][$i + 1] = $s * $this->V[$k][$i] + $c * $h;
+ $this->V[$k][$i] = $c * $this->V[$k][$i] - $s * $h;
+ }
+ }
+ $p = -$s * $s2 * $c3 * $el1 * $this->e[$l] / $dl1;
+ $this->e[$l] = $s * $p;
+ $this->d[$l] = $c * $p;
+ // Check for convergence.
+ } while (abs($this->e[$l]) > $eps * $tst1);
+ }
+ $this->d[$l] = $this->d[$l] + $f;
+ $this->e[$l] = 0.0;
+ }
+
+ // Sort eigenvalues and corresponding vectors.
+ for ($i = 0; $i < $this->n - 1; ++$i) {
+ $k = $i;
+ $p = $this->d[$i];
+ for ($j = $i + 1; $j < $this->n; ++$j) {
+ if ($this->d[$j] < $p) {
+ $k = $j;
+ $p = $this->d[$j];
+ }
+ }
+ if ($k != $i) {
+ $this->d[$k] = $this->d[$i];
+ $this->d[$i] = $p;
+ for ($j = 0; $j < $this->n; ++$j) {
+ $p = $this->V[$j][$i];
+ $this->V[$j][$i] = $this->V[$j][$k];
+ $this->V[$j][$k] = $p;
+ }
+ }
+ }
+ }
+
+ /**
+ * Nonsymmetric reduction to Hessenberg form.
+ *
+ * This is derived from the Algol procedures orthes and ortran,
+ * by Martin and Wilkinson, Handbook for Auto. Comp.,
+ * Vol.ii-Linear Algebra, and the corresponding
+ * Fortran subroutines in EISPACK.
+ */
+ private function orthes(): void
+ {
+ $low = 0;
+ $high = $this->n - 1;
+
+ for ($m = $low + 1; $m <= $high - 1; ++$m) {
+ // Scale column.
+ $scale = 0.0;
+ for ($i = $m; $i <= $high; ++$i) {
+ $scale = $scale + abs($this->H[$i][$m - 1]);
+ }
+ if ($scale != 0.0) {
+ // Compute Householder transformation.
+ $h = 0.0;
+ for ($i = $high; $i >= $m; --$i) {
+ $this->ort[$i] = $this->H[$i][$m - 1] / $scale;
+ $h += $this->ort[$i] * $this->ort[$i];
+ }
+ $g = sqrt($h);
+ if ($this->ort[$m] > 0) {
+ $g *= -1;
+ }
+ $h -= $this->ort[$m] * $g;
+ $this->ort[$m] -= $g;
+ // Apply Householder similarity transformation
+ // H = (I -u * u' / h) * H * (I -u * u') / h)
+ for ($j = $m; $j < $this->n; ++$j) {
+ $f = 0.0;
+ for ($i = $high; $i >= $m; --$i) {
+ $f += $this->ort[$i] * $this->H[$i][$j];
+ }
+ $f /= $h;
+ for ($i = $m; $i <= $high; ++$i) {
+ $this->H[$i][$j] -= $f * $this->ort[$i];
+ }
+ }
+ for ($i = 0; $i <= $high; ++$i) {
+ $f = 0.0;
+ for ($j = $high; $j >= $m; --$j) {
+ $f += $this->ort[$j] * $this->H[$i][$j];
+ }
+ $f = $f / $h;
+ for ($j = $m; $j <= $high; ++$j) {
+ $this->H[$i][$j] -= $f * $this->ort[$j];
+ }
+ }
+ $this->ort[$m] = $scale * $this->ort[$m];
+ $this->H[$m][$m - 1] = $scale * $g;
+ }
+ }
+
+ // Accumulate transformations (Algol's ortran).
+ for ($i = 0; $i < $this->n; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $this->V[$i][$j] = ($i == $j ? 1.0 : 0.0);
+ }
+ }
+ for ($m = $high - 1; $m >= $low + 1; --$m) {
+ if ($this->H[$m][$m - 1] != 0.0) {
+ for ($i = $m + 1; $i <= $high; ++$i) {
+ $this->ort[$i] = $this->H[$i][$m - 1];
+ }
+ for ($j = $m; $j <= $high; ++$j) {
+ $g = 0.0;
+ for ($i = $m; $i <= $high; ++$i) {
+ $g += $this->ort[$i] * $this->V[$i][$j];
+ }
+ // Double division avoids possible underflow
+ $g = ($g / $this->ort[$m]) / $this->H[$m][$m - 1];
+ for ($i = $m; $i <= $high; ++$i) {
+ $this->V[$i][$j] += $g * $this->ort[$i];
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Performs complex division.
+ *
+ * @param mixed $xr
+ * @param mixed $xi
+ * @param mixed $yr
+ * @param mixed $yi
+ */
+ private function cdiv($xr, $xi, $yr, $yi): void
+ {
+ if (abs($yr) > abs($yi)) {
+ $r = $yi / $yr;
+ $d = $yr + $r * $yi;
+ $this->cdivr = ($xr + $r * $xi) / $d;
+ $this->cdivi = ($xi - $r * $xr) / $d;
+ } else {
+ $r = $yr / $yi;
+ $d = $yi + $r * $yr;
+ $this->cdivr = ($r * $xr + $xi) / $d;
+ $this->cdivi = ($r * $xi - $xr) / $d;
+ }
+ }
+
+ /**
+ * Nonsymmetric reduction from Hessenberg to real Schur form.
+ *
+ * Code is derived from the Algol procedure hqr2,
+ * by Martin and Wilkinson, Handbook for Auto. Comp.,
+ * Vol.ii-Linear Algebra, and the corresponding
+ * Fortran subroutine in EISPACK.
+ */
+ private function hqr2(): void
+ {
+ // Initialize
+ $nn = $this->n;
+ $n = $nn - 1;
+ $low = 0;
+ $high = $nn - 1;
+ $eps = 2.0 ** (-52.0);
+ $exshift = 0.0;
+ $p = $q = $r = $s = $z = 0;
+ // Store roots isolated by balanc and compute matrix norm
+ $norm = 0.0;
+
+ for ($i = 0; $i < $nn; ++$i) {
+ if ($i > $high) {
+ $this->d[$i] = $this->H[$i][$i];
+ $this->e[$i] = 0.0;
+ }
+ for ($j = max($i - 1, 0); $j < $nn; ++$j) {
+ $norm = $norm + abs($this->H[$i][$j]);
+ }
+ }
+
+ // Outer loop over eigenvalue index
+ $iter = 0;
+ while ($n >= $low) {
+ // Look for single small sub-diagonal element
+ $l = $n;
+ while ($l > $low) {
+ $s = abs($this->H[$l - 1][$l - 1]) + abs($this->H[$l][$l]);
+ if ($s == 0.0) {
+ $s = $norm;
+ }
+ if (abs($this->H[$l][$l - 1]) < $eps * $s) {
+ break;
+ }
+ --$l;
+ }
+ // Check for convergence
+ // One root found
+ if ($l == $n) {
+ $this->H[$n][$n] = $this->H[$n][$n] + $exshift;
+ $this->d[$n] = $this->H[$n][$n];
+ $this->e[$n] = 0.0;
+ --$n;
+ $iter = 0;
+ // Two roots found
+ } elseif ($l == $n - 1) {
+ $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n];
+ $p = ($this->H[$n - 1][$n - 1] - $this->H[$n][$n]) / 2.0;
+ $q = $p * $p + $w;
+ $z = sqrt(abs($q));
+ $this->H[$n][$n] = $this->H[$n][$n] + $exshift;
+ $this->H[$n - 1][$n - 1] = $this->H[$n - 1][$n - 1] + $exshift;
+ $x = $this->H[$n][$n];
+ // Real pair
+ if ($q >= 0) {
+ if ($p >= 0) {
+ $z = $p + $z;
+ } else {
+ $z = $p - $z;
+ }
+ $this->d[$n - 1] = $x + $z;
+ $this->d[$n] = $this->d[$n - 1];
+ if ($z != 0.0) {
+ $this->d[$n] = $x - $w / $z;
+ }
+ $this->e[$n - 1] = 0.0;
+ $this->e[$n] = 0.0;
+ $x = $this->H[$n][$n - 1];
+ $s = abs($x) + abs($z);
+ $p = $x / $s;
+ $q = $z / $s;
+ $r = sqrt($p * $p + $q * $q);
+ $p = $p / $r;
+ $q = $q / $r;
+ // Row modification
+ for ($j = $n - 1; $j < $nn; ++$j) {
+ $z = $this->H[$n - 1][$j];
+ $this->H[$n - 1][$j] = $q * $z + $p * $this->H[$n][$j];
+ $this->H[$n][$j] = $q * $this->H[$n][$j] - $p * $z;
+ }
+ // Column modification
+ for ($i = 0; $i <= $n; ++$i) {
+ $z = $this->H[$i][$n - 1];
+ $this->H[$i][$n - 1] = $q * $z + $p * $this->H[$i][$n];
+ $this->H[$i][$n] = $q * $this->H[$i][$n] - $p * $z;
+ }
+ // Accumulate transformations
+ for ($i = $low; $i <= $high; ++$i) {
+ $z = $this->V[$i][$n - 1];
+ $this->V[$i][$n - 1] = $q * $z + $p * $this->V[$i][$n];
+ $this->V[$i][$n] = $q * $this->V[$i][$n] - $p * $z;
+ }
+ // Complex pair
+ } else {
+ $this->d[$n - 1] = $x + $p;
+ $this->d[$n] = $x + $p;
+ $this->e[$n - 1] = $z;
+ $this->e[$n] = -$z;
+ }
+ $n = $n - 2;
+ $iter = 0;
+ // No convergence yet
+ } else {
+ // Form shift
+ $x = $this->H[$n][$n];
+ $y = 0.0;
+ $w = 0.0;
+ if ($l < $n) {
+ $y = $this->H[$n - 1][$n - 1];
+ $w = $this->H[$n][$n - 1] * $this->H[$n - 1][$n];
+ }
+ // Wilkinson's original ad hoc shift
+ if ($iter == 10) {
+ $exshift += $x;
+ for ($i = $low; $i <= $n; ++$i) {
+ $this->H[$i][$i] -= $x;
+ }
+ $s = abs($this->H[$n][$n - 1]) + abs($this->H[$n - 1][$n - 2]);
+ $x = $y = 0.75 * $s;
+ $w = -0.4375 * $s * $s;
+ }
+ // MATLAB's new ad hoc shift
+ if ($iter == 30) {
+ $s = ($y - $x) / 2.0;
+ $s = $s * $s + $w;
+ if ($s > 0) {
+ $s = sqrt($s);
+ if ($y < $x) {
+ $s = -$s;
+ }
+ $s = $x - $w / (($y - $x) / 2.0 + $s);
+ for ($i = $low; $i <= $n; ++$i) {
+ $this->H[$i][$i] -= $s;
+ }
+ $exshift += $s;
+ $x = $y = $w = 0.964;
+ }
+ }
+ // Could check iteration count here.
+ $iter = $iter + 1;
+ // Look for two consecutive small sub-diagonal elements
+ $m = $n - 2;
+ while ($m >= $l) {
+ $z = $this->H[$m][$m];
+ $r = $x - $z;
+ $s = $y - $z;
+ $p = ($r * $s - $w) / $this->H[$m + 1][$m] + $this->H[$m][$m + 1];
+ $q = $this->H[$m + 1][$m + 1] - $z - $r - $s;
+ $r = $this->H[$m + 2][$m + 1];
+ $s = abs($p) + abs($q) + abs($r);
+ $p = $p / $s;
+ $q = $q / $s;
+ $r = $r / $s;
+ if ($m == $l) {
+ break;
+ }
+ if (
+ abs($this->H[$m][$m - 1]) * (abs($q) + abs($r)) <
+ $eps * (abs($p) * (abs($this->H[$m - 1][$m - 1]) + abs($z) + abs($this->H[$m + 1][$m + 1])))
+ ) {
+ break;
+ }
+ --$m;
+ }
+ for ($i = $m + 2; $i <= $n; ++$i) {
+ $this->H[$i][$i - 2] = 0.0;
+ if ($i > $m + 2) {
+ $this->H[$i][$i - 3] = 0.0;
+ }
+ }
+ // Double QR step involving rows l:n and columns m:n
+ for ($k = $m; $k <= $n - 1; ++$k) {
+ $notlast = ($k != $n - 1);
+ if ($k != $m) {
+ $p = $this->H[$k][$k - 1];
+ $q = $this->H[$k + 1][$k - 1];
+ $r = ($notlast ? $this->H[$k + 2][$k - 1] : 0.0);
+ $x = abs($p) + abs($q) + abs($r);
+ if ($x != 0.0) {
+ $p = $p / $x;
+ $q = $q / $x;
+ $r = $r / $x;
+ }
+ }
+ if ($x == 0.0) {
+ break;
+ }
+ $s = sqrt($p * $p + $q * $q + $r * $r);
+ if ($p < 0) {
+ $s = -$s;
+ }
+ if ($s != 0) {
+ if ($k != $m) {
+ $this->H[$k][$k - 1] = -$s * $x;
+ } elseif ($l != $m) {
+ $this->H[$k][$k - 1] = -$this->H[$k][$k - 1];
+ }
+ $p = $p + $s;
+ $x = $p / $s;
+ $y = $q / $s;
+ $z = $r / $s;
+ $q = $q / $p;
+ $r = $r / $p;
+ // Row modification
+ for ($j = $k; $j < $nn; ++$j) {
+ $p = $this->H[$k][$j] + $q * $this->H[$k + 1][$j];
+ if ($notlast) {
+ $p = $p + $r * $this->H[$k + 2][$j];
+ $this->H[$k + 2][$j] = $this->H[$k + 2][$j] - $p * $z;
+ }
+ $this->H[$k][$j] = $this->H[$k][$j] - $p * $x;
+ $this->H[$k + 1][$j] = $this->H[$k + 1][$j] - $p * $y;
+ }
+ // Column modification
+ $iMax = min($n, $k + 3);
+ for ($i = 0; $i <= $iMax; ++$i) {
+ $p = $x * $this->H[$i][$k] + $y * $this->H[$i][$k + 1];
+ if ($notlast) {
+ $p = $p + $z * $this->H[$i][$k + 2];
+ $this->H[$i][$k + 2] = $this->H[$i][$k + 2] - $p * $r;
+ }
+ $this->H[$i][$k] = $this->H[$i][$k] - $p;
+ $this->H[$i][$k + 1] = $this->H[$i][$k + 1] - $p * $q;
+ }
+ // Accumulate transformations
+ for ($i = $low; $i <= $high; ++$i) {
+ $p = $x * $this->V[$i][$k] + $y * $this->V[$i][$k + 1];
+ if ($notlast) {
+ $p = $p + $z * $this->V[$i][$k + 2];
+ $this->V[$i][$k + 2] = $this->V[$i][$k + 2] - $p * $r;
+ }
+ $this->V[$i][$k] = $this->V[$i][$k] - $p;
+ $this->V[$i][$k + 1] = $this->V[$i][$k + 1] - $p * $q;
+ }
+ } // ($s != 0)
+ } // k loop
+ } // check convergence
+ } // while ($n >= $low)
+
+ // Backsubstitute to find vectors of upper triangular form
+ if ($norm == 0.0) {
+ return;
+ }
+
+ for ($n = $nn - 1; $n >= 0; --$n) {
+ $p = $this->d[$n];
+ $q = $this->e[$n];
+ // Real vector
+ if ($q == 0) {
+ $l = $n;
+ $this->H[$n][$n] = 1.0;
+ for ($i = $n - 1; $i >= 0; --$i) {
+ $w = $this->H[$i][$i] - $p;
+ $r = 0.0;
+ for ($j = $l; $j <= $n; ++$j) {
+ $r = $r + $this->H[$i][$j] * $this->H[$j][$n];
+ }
+ if ($this->e[$i] < 0.0) {
+ $z = $w;
+ $s = $r;
+ } else {
+ $l = $i;
+ if ($this->e[$i] == 0.0) {
+ if ($w != 0.0) {
+ $this->H[$i][$n] = -$r / $w;
+ } else {
+ $this->H[$i][$n] = -$r / ($eps * $norm);
+ }
+ // Solve real equations
+ } else {
+ $x = $this->H[$i][$i + 1];
+ $y = $this->H[$i + 1][$i];
+ $q = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i];
+ $t = ($x * $s - $z * $r) / $q;
+ $this->H[$i][$n] = $t;
+ if (abs($x) > abs($z)) {
+ $this->H[$i + 1][$n] = (-$r - $w * $t) / $x;
+ } else {
+ $this->H[$i + 1][$n] = (-$s - $y * $t) / $z;
+ }
+ }
+ // Overflow control
+ $t = abs($this->H[$i][$n]);
+ if (($eps * $t) * $t > 1) {
+ for ($j = $i; $j <= $n; ++$j) {
+ $this->H[$j][$n] = $this->H[$j][$n] / $t;
+ }
+ }
+ }
+ }
+ // Complex vector
+ } elseif ($q < 0) {
+ $l = $n - 1;
+ // Last vector component imaginary so matrix is triangular
+ if (abs($this->H[$n][$n - 1]) > abs($this->H[$n - 1][$n])) {
+ $this->H[$n - 1][$n - 1] = $q / $this->H[$n][$n - 1];
+ $this->H[$n - 1][$n] = -($this->H[$n][$n] - $p) / $this->H[$n][$n - 1];
+ } else {
+ $this->cdiv(0.0, -$this->H[$n - 1][$n], $this->H[$n - 1][$n - 1] - $p, $q);
+ $this->H[$n - 1][$n - 1] = $this->cdivr;
+ $this->H[$n - 1][$n] = $this->cdivi;
+ }
+ $this->H[$n][$n - 1] = 0.0;
+ $this->H[$n][$n] = 1.0;
+ for ($i = $n - 2; $i >= 0; --$i) {
+ // double ra,sa,vr,vi;
+ $ra = 0.0;
+ $sa = 0.0;
+ for ($j = $l; $j <= $n; ++$j) {
+ $ra = $ra + $this->H[$i][$j] * $this->H[$j][$n - 1];
+ $sa = $sa + $this->H[$i][$j] * $this->H[$j][$n];
+ }
+ $w = $this->H[$i][$i] - $p;
+ if ($this->e[$i] < 0.0) {
+ $z = $w;
+ $r = $ra;
+ $s = $sa;
+ } else {
+ $l = $i;
+ if ($this->e[$i] == 0) {
+ $this->cdiv(-$ra, -$sa, $w, $q);
+ $this->H[$i][$n - 1] = $this->cdivr;
+ $this->H[$i][$n] = $this->cdivi;
+ } else {
+ // Solve complex equations
+ $x = $this->H[$i][$i + 1];
+ $y = $this->H[$i + 1][$i];
+ $vr = ($this->d[$i] - $p) * ($this->d[$i] - $p) + $this->e[$i] * $this->e[$i] - $q * $q;
+ $vi = ($this->d[$i] - $p) * 2.0 * $q;
+ if ($vr == 0.0 & $vi == 0.0) {
+ $vr = $eps * $norm * (abs($w) + abs($q) + abs($x) + abs($y) + abs($z));
+ }
+ $this->cdiv($x * $r - $z * $ra + $q * $sa, $x * $s - $z * $sa - $q * $ra, $vr, $vi);
+ $this->H[$i][$n - 1] = $this->cdivr;
+ $this->H[$i][$n] = $this->cdivi;
+ if (abs($x) > (abs($z) + abs($q))) {
+ $this->H[$i + 1][$n - 1] = (-$ra - $w * $this->H[$i][$n - 1] + $q * $this->H[$i][$n]) / $x;
+ $this->H[$i + 1][$n] = (-$sa - $w * $this->H[$i][$n] - $q * $this->H[$i][$n - 1]) / $x;
+ } else {
+ $this->cdiv(-$r - $y * $this->H[$i][$n - 1], -$s - $y * $this->H[$i][$n], $z, $q);
+ $this->H[$i + 1][$n - 1] = $this->cdivr;
+ $this->H[$i + 1][$n] = $this->cdivi;
+ }
+ }
+ // Overflow control
+ $t = max(abs($this->H[$i][$n - 1]), abs($this->H[$i][$n]));
+ if (($eps * $t) * $t > 1) {
+ for ($j = $i; $j <= $n; ++$j) {
+ $this->H[$j][$n - 1] = $this->H[$j][$n - 1] / $t;
+ $this->H[$j][$n] = $this->H[$j][$n] / $t;
+ }
+ }
+ } // end else
+ } // end for
+ } // end else for complex case
+ } // end for
+
+ // Vectors of isolated roots
+ for ($i = 0; $i < $nn; ++$i) {
+ if ($i > $high) {
+ for ($j = $i; $j < $nn; ++$j) {
+ $this->V[$i][$j] = $this->H[$i][$j];
+ }
+ }
+ }
+
+ // Back transformation to get eigenvectors of original matrix
+ for ($j = $nn - 1; $j >= $low; --$j) {
+ for ($i = $low; $i <= $high; ++$i) {
+ $z = 0.0;
+ $kMax = min($j, $high);
+ for ($k = $low; $k <= $kMax; ++$k) {
+ $z = $z + $this->V[$i][$k] * $this->H[$k][$j];
+ }
+ $this->V[$i][$j] = $z;
+ }
+ }
+ }
+
+ // end hqr2
+
+ /**
+ * Constructor: Check for symmetry, then construct the eigenvalue decomposition.
+ *
+ * @param Matrix $Arg A Square matrix
+ */
+ public function __construct(Matrix $Arg)
+ {
+ $this->A = $Arg->getArray();
+ $this->n = $Arg->getColumnDimension();
+
+ $issymmetric = true;
+ for ($j = 0; ($j < $this->n) & $issymmetric; ++$j) {
+ for ($i = 0; ($i < $this->n) & $issymmetric; ++$i) {
+ $issymmetric = ($this->A[$i][$j] == $this->A[$j][$i]);
+ }
+ }
+
+ if ($issymmetric) {
+ $this->V = $this->A;
+ // Tridiagonalize.
+ $this->tred2();
+ // Diagonalize.
+ $this->tql2();
+ } else {
+ $this->H = $this->A;
+ $this->ort = [];
+ // Reduce to Hessenberg form.
+ $this->orthes();
+ // Reduce Hessenberg to real Schur form.
+ $this->hqr2();
+ }
+ }
+
+ /**
+ * Return the eigenvector matrix.
+ *
+ * @return Matrix V
+ */
+ public function getV()
+ {
+ return new Matrix($this->V, $this->n, $this->n);
+ }
+
+ /**
+ * Return the real parts of the eigenvalues.
+ *
+ * @return array real(diag(D))
+ */
+ public function getRealEigenvalues()
+ {
+ return $this->d;
+ }
+
+ /**
+ * Return the imaginary parts of the eigenvalues.
+ *
+ * @return array imag(diag(D))
+ */
+ public function getImagEigenvalues()
+ {
+ return $this->e;
+ }
+
+ /**
+ * Return the block diagonal eigenvalue matrix.
+ *
+ * @return Matrix D
+ */
+ public function getD()
+ {
+ $D = [];
+ for ($i = 0; $i < $this->n; ++$i) {
+ $D[$i] = array_fill(0, $this->n, 0.0);
+ $D[$i][$i] = $this->d[$i];
+ if ($this->e[$i] == 0) {
+ continue;
+ }
+ $o = ($this->e[$i] > 0) ? $i + 1 : $i - 1;
+ $D[$i][$o] = $this->e[$i];
+ }
+
+ return new Matrix($D);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php
new file mode 100644
index 0000000..ecfe42b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php
@@ -0,0 +1,284 @@
+= n, the LU decomposition is an m-by-n
+ * unit lower triangular matrix L, an n-by-n upper triangular matrix U,
+ * and a permutation vector piv of length m so that A(piv,:) = L*U.
+ * If m < n, then L is m-by-m and U is m-by-n.
+ *
+ * The LU decompostion with pivoting always exists, even if the matrix is
+ * singular, so the constructor will never fail. The primary use of the
+ * LU decomposition is in the solution of square systems of simultaneous
+ * linear equations. This will fail if isNonsingular() returns false.
+ *
+ * @author Paul Meagher
+ * @author Bartosz Matosiuk
+ * @author Michael Bommarito
+ *
+ * @version 1.1
+ */
+class LUDecomposition
+{
+ const MATRIX_SINGULAR_EXCEPTION = 'Can only perform operation on singular matrix.';
+ const MATRIX_SQUARE_EXCEPTION = 'Mismatched Row dimension';
+
+ /**
+ * Decomposition storage.
+ *
+ * @var array
+ */
+ private $LU = [];
+
+ /**
+ * Row dimension.
+ *
+ * @var int
+ */
+ private $m;
+
+ /**
+ * Column dimension.
+ *
+ * @var int
+ */
+ private $n;
+
+ /**
+ * Pivot sign.
+ *
+ * @var int
+ */
+ private $pivsign;
+
+ /**
+ * Internal storage of pivot vector.
+ *
+ * @var array
+ */
+ private $piv = [];
+
+ /**
+ * LU Decomposition constructor.
+ *
+ * @param Matrix $A Rectangular matrix
+ */
+ public function __construct($A)
+ {
+ if ($A instanceof Matrix) {
+ // Use a "left-looking", dot-product, Crout/Doolittle algorithm.
+ $this->LU = $A->getArray();
+ $this->m = $A->getRowDimension();
+ $this->n = $A->getColumnDimension();
+ for ($i = 0; $i < $this->m; ++$i) {
+ $this->piv[$i] = $i;
+ }
+ $this->pivsign = 1;
+ $LUrowi = $LUcolj = [];
+
+ // Outer loop.
+ for ($j = 0; $j < $this->n; ++$j) {
+ // Make a copy of the j-th column to localize references.
+ for ($i = 0; $i < $this->m; ++$i) {
+ $LUcolj[$i] = &$this->LU[$i][$j];
+ }
+ // Apply previous transformations.
+ for ($i = 0; $i < $this->m; ++$i) {
+ $LUrowi = $this->LU[$i];
+ // Most of the time is spent in the following dot product.
+ $kmax = min($i, $j);
+ $s = 0.0;
+ for ($k = 0; $k < $kmax; ++$k) {
+ $s += $LUrowi[$k] * $LUcolj[$k];
+ }
+ $LUrowi[$j] = $LUcolj[$i] -= $s;
+ }
+ // Find pivot and exchange if necessary.
+ $p = $j;
+ for ($i = $j + 1; $i < $this->m; ++$i) {
+ if (abs($LUcolj[$i]) > abs($LUcolj[$p])) {
+ $p = $i;
+ }
+ }
+ if ($p != $j) {
+ for ($k = 0; $k < $this->n; ++$k) {
+ $t = $this->LU[$p][$k];
+ $this->LU[$p][$k] = $this->LU[$j][$k];
+ $this->LU[$j][$k] = $t;
+ }
+ $k = $this->piv[$p];
+ $this->piv[$p] = $this->piv[$j];
+ $this->piv[$j] = $k;
+ $this->pivsign = $this->pivsign * -1;
+ }
+ // Compute multipliers.
+ if (($j < $this->m) && ($this->LU[$j][$j] != 0.0)) {
+ for ($i = $j + 1; $i < $this->m; ++$i) {
+ $this->LU[$i][$j] /= $this->LU[$j][$j];
+ }
+ }
+ }
+ } else {
+ throw new CalculationException(Matrix::ARGUMENT_TYPE_EXCEPTION);
+ }
+ }
+
+ // function __construct()
+
+ /**
+ * Get lower triangular factor.
+ *
+ * @return Matrix Lower triangular factor
+ */
+ public function getL()
+ {
+ $L = [];
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ if ($i > $j) {
+ $L[$i][$j] = $this->LU[$i][$j];
+ } elseif ($i == $j) {
+ $L[$i][$j] = 1.0;
+ } else {
+ $L[$i][$j] = 0.0;
+ }
+ }
+ }
+
+ return new Matrix($L);
+ }
+
+ // function getL()
+
+ /**
+ * Get upper triangular factor.
+ *
+ * @return Matrix Upper triangular factor
+ */
+ public function getU()
+ {
+ $U = [];
+ for ($i = 0; $i < $this->n; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ if ($i <= $j) {
+ $U[$i][$j] = $this->LU[$i][$j];
+ } else {
+ $U[$i][$j] = 0.0;
+ }
+ }
+ }
+
+ return new Matrix($U);
+ }
+
+ // function getU()
+
+ /**
+ * Return pivot permutation vector.
+ *
+ * @return array Pivot vector
+ */
+ public function getPivot()
+ {
+ return $this->piv;
+ }
+
+ // function getPivot()
+
+ /**
+ * Alias for getPivot.
+ *
+ * @see getPivot
+ */
+ public function getDoublePivot()
+ {
+ return $this->getPivot();
+ }
+
+ // function getDoublePivot()
+
+ /**
+ * Is the matrix nonsingular?
+ *
+ * @return bool true if U, and hence A, is nonsingular
+ */
+ public function isNonsingular()
+ {
+ for ($j = 0; $j < $this->n; ++$j) {
+ if ($this->LU[$j][$j] == 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // function isNonsingular()
+
+ /**
+ * Count determinants.
+ *
+ * @return float
+ */
+ public function det()
+ {
+ if ($this->m == $this->n) {
+ $d = $this->pivsign;
+ for ($j = 0; $j < $this->n; ++$j) {
+ $d *= $this->LU[$j][$j];
+ }
+
+ return $d;
+ }
+
+ throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION);
+ }
+
+ // function det()
+
+ /**
+ * Solve A*X = B.
+ *
+ * @param Matrix $B a Matrix with as many rows as A and any number of columns
+ *
+ * @return Matrix X so that L*U*X = B(piv,:)
+ */
+ public function solve(Matrix $B)
+ {
+ if ($B->getRowDimension() == $this->m) {
+ if ($this->isNonsingular()) {
+ // Copy right hand side with pivoting
+ $nx = $B->getColumnDimension();
+ $X = $B->getMatrix($this->piv, 0, $nx - 1);
+ // Solve L*Y = B(piv,:)
+ for ($k = 0; $k < $this->n; ++$k) {
+ for ($i = $k + 1; $i < $this->n; ++$i) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X->A[$i][$j] -= $X->A[$k][$j] * $this->LU[$i][$k];
+ }
+ }
+ }
+ // Solve U*X = Y;
+ for ($k = $this->n - 1; $k >= 0; --$k) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X->A[$k][$j] /= $this->LU[$k][$k];
+ }
+ for ($i = 0; $i < $k; ++$i) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X->A[$i][$j] -= $X->A[$k][$j] * $this->LU[$i][$k];
+ }
+ }
+ }
+
+ return $X;
+ }
+
+ throw new CalculationException(self::MATRIX_SINGULAR_EXCEPTION);
+ }
+
+ throw new CalculationException(self::MATRIX_SQUARE_EXCEPTION);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/Matrix.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/Matrix.php
new file mode 100644
index 0000000..adf399a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/Matrix.php
@@ -0,0 +1,1189 @@
+ 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ //Rectangular matrix - m x n initialized from 2D array
+ case 'array':
+ $this->m = count($args[0]);
+ $this->n = count($args[0][0]);
+ $this->A = $args[0];
+
+ break;
+ //Square matrix - n x n
+ case 'integer':
+ $this->m = $args[0];
+ $this->n = $args[0];
+ $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0));
+
+ break;
+ //Rectangular matrix - m x n
+ case 'integer,integer':
+ $this->m = $args[0];
+ $this->n = $args[1];
+ $this->A = array_fill(0, $this->m, array_fill(0, $this->n, 0));
+
+ break;
+ //Rectangular matrix - m x n initialized from packed array
+ case 'array,integer':
+ $this->m = $args[1];
+ if ($this->m != 0) {
+ $this->n = count($args[0]) / $this->m;
+ } else {
+ $this->n = 0;
+ }
+ if (($this->m * $this->n) == count($args[0])) {
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $this->A[$i][$j] = $args[0][$i + $j * $this->m];
+ }
+ }
+ } else {
+ throw new CalculationException(self::ARRAY_LENGTH_EXCEPTION);
+ }
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ } else {
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+ }
+
+ /**
+ * getArray.
+ *
+ * @return array Matrix array
+ */
+ public function getArray()
+ {
+ return $this->A;
+ }
+
+ /**
+ * getRowDimension.
+ *
+ * @return int Row dimension
+ */
+ public function getRowDimension()
+ {
+ return $this->m;
+ }
+
+ /**
+ * getColumnDimension.
+ *
+ * @return int Column dimension
+ */
+ public function getColumnDimension()
+ {
+ return $this->n;
+ }
+
+ /**
+ * get.
+ *
+ * Get the i,j-th element of the matrix.
+ *
+ * @param int $i Row position
+ * @param int $j Column position
+ *
+ * @return float|int
+ */
+ public function get($i = null, $j = null)
+ {
+ return $this->A[$i][$j];
+ }
+
+ /**
+ * getMatrix.
+ *
+ * Get a submatrix
+ *
+ * @return Matrix Submatrix
+ */
+ public function getMatrix(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ //A($i0...; $j0...)
+ case 'integer,integer':
+ [$i0, $j0] = $args;
+ if ($i0 >= 0) {
+ $m = $this->m - $i0;
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ if ($j0 >= 0) {
+ $n = $this->n - $j0;
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ $R = new self($m, $n);
+ for ($i = $i0; $i < $this->m; ++$i) {
+ for ($j = $j0; $j < $this->n; ++$j) {
+ $R->set($i, $j, $this->A[$i][$j]);
+ }
+ }
+
+ return $R;
+
+ break;
+ //A($i0...$iF; $j0...$jF)
+ case 'integer,integer,integer,integer':
+ [$i0, $iF, $j0, $jF] = $args;
+ if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
+ $m = $iF - $i0;
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ if (($jF > $j0) && ($this->n >= $jF) && ($j0 >= 0)) {
+ $n = $jF - $j0;
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ $R = new self($m + 1, $n + 1);
+ for ($i = $i0; $i <= $iF; ++$i) {
+ for ($j = $j0; $j <= $jF; ++$j) {
+ $R->set($i - $i0, $j - $j0, $this->A[$i][$j]);
+ }
+ }
+
+ return $R;
+
+ break;
+ //$R = array of row indices; $C = array of column indices
+ case 'array,array':
+ [$RL, $CL] = $args;
+ if (count($RL) > 0) {
+ $m = count($RL);
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ if (count($CL) > 0) {
+ $n = count($CL);
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ $R = new self($m, $n);
+ for ($i = 0; $i < $m; ++$i) {
+ for ($j = 0; $j < $n; ++$j) {
+ $R->set($i, $j, $this->A[$RL[$i]][$CL[$j]]);
+ }
+ }
+
+ return $R;
+
+ break;
+ //A($i0...$iF); $CL = array of column indices
+ case 'integer,integer,array':
+ [$i0, $iF, $CL] = $args;
+ if (($iF > $i0) && ($this->m >= $iF) && ($i0 >= 0)) {
+ $m = $iF - $i0;
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ if (count($CL) > 0) {
+ $n = count($CL);
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ $R = new self($m, $n);
+ for ($i = $i0; $i < $iF; ++$i) {
+ for ($j = 0; $j < $n; ++$j) {
+ $R->set($i - $i0, $j, $this->A[$i][$CL[$j]]);
+ }
+ }
+
+ return $R;
+
+ break;
+ //$RL = array of row indices
+ case 'array,integer,integer':
+ [$RL, $j0, $jF] = $args;
+ if (count($RL) > 0) {
+ $m = count($RL);
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ if (($jF >= $j0) && ($this->n >= $jF) && ($j0 >= 0)) {
+ $n = $jF - $j0;
+ } else {
+ throw new CalculationException(self::ARGUMENT_BOUNDS_EXCEPTION);
+ }
+ $R = new self($m, $n + 1);
+ for ($i = 0; $i < $m; ++$i) {
+ for ($j = $j0; $j <= $jF; ++$j) {
+ $R->set($i, $j - $j0, $this->A[$RL[$i]][$j]);
+ }
+ }
+
+ return $R;
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ } else {
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+ }
+
+ /**
+ * checkMatrixDimensions.
+ *
+ * Is matrix B the same size?
+ *
+ * @param Matrix $B Matrix B
+ *
+ * @return bool
+ */
+ public function checkMatrixDimensions($B = null)
+ {
+ if ($B instanceof self) {
+ if (($this->m == $B->getRowDimension()) && ($this->n == $B->getColumnDimension())) {
+ return true;
+ }
+
+ throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
+ }
+
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ // function checkMatrixDimensions()
+
+ /**
+ * set.
+ *
+ * Set the i,j-th element of the matrix.
+ *
+ * @param int $i Row position
+ * @param int $j Column position
+ * @param float|int $c value
+ */
+ public function set($i = null, $j = null, $c = null): void
+ {
+ // Optimized set version just has this
+ $this->A[$i][$j] = $c;
+ }
+
+ // function set()
+
+ /**
+ * identity.
+ *
+ * Generate an identity matrix.
+ *
+ * @param int $m Row dimension
+ * @param int $n Column dimension
+ *
+ * @return Matrix Identity matrix
+ */
+ public function identity($m = null, $n = null)
+ {
+ return $this->diagonal($m, $n, 1);
+ }
+
+ /**
+ * diagonal.
+ *
+ * Generate a diagonal matrix
+ *
+ * @param int $m Row dimension
+ * @param int $n Column dimension
+ * @param mixed $c Diagonal value
+ *
+ * @return Matrix Diagonal matrix
+ */
+ public function diagonal($m = null, $n = null, $c = 1)
+ {
+ $R = new self($m, $n);
+ for ($i = 0; $i < $m; ++$i) {
+ $R->set($i, $i, $c);
+ }
+
+ return $R;
+ }
+
+ /**
+ * getMatrixByRow.
+ *
+ * Get a submatrix by row index/range
+ *
+ * @param int $i0 Initial row index
+ * @param int $iF Final row index
+ *
+ * @return Matrix Submatrix
+ */
+ public function getMatrixByRow($i0 = null, $iF = null)
+ {
+ if (is_int($i0)) {
+ if (is_int($iF)) {
+ return $this->getMatrix($i0, 0, $iF + 1, $this->n);
+ }
+
+ return $this->getMatrix($i0, 0, $i0 + 1, $this->n);
+ }
+
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ /**
+ * getMatrixByCol.
+ *
+ * Get a submatrix by column index/range
+ *
+ * @param int $j0 Initial column index
+ * @param int $jF Final column index
+ *
+ * @return Matrix Submatrix
+ */
+ public function getMatrixByCol($j0 = null, $jF = null)
+ {
+ if (is_int($j0)) {
+ if (is_int($jF)) {
+ return $this->getMatrix(0, $j0, $this->m, $jF + 1);
+ }
+
+ return $this->getMatrix(0, $j0, $this->m, $j0 + 1);
+ }
+
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ /**
+ * transpose.
+ *
+ * Tranpose matrix
+ *
+ * @return Matrix Transposed matrix
+ */
+ public function transpose()
+ {
+ $R = new self($this->n, $this->m);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $R->set($j, $i, $this->A[$i][$j]);
+ }
+ }
+
+ return $R;
+ }
+
+ // function transpose()
+
+ /**
+ * trace.
+ *
+ * Sum of diagonal elements
+ *
+ * @return float Sum of diagonal elements
+ */
+ public function trace()
+ {
+ $s = 0;
+ $n = min($this->m, $this->n);
+ for ($i = 0; $i < $n; ++$i) {
+ $s += $this->A[$i][$i];
+ }
+
+ return $s;
+ }
+
+ /**
+ * plus.
+ *
+ * A + B
+ *
+ * @return Matrix Sum
+ */
+ public function plus(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $M->set($i, $j, $M->get($i, $j) + $this->A[$i][$j]);
+ }
+ }
+
+ return $M;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * plusEquals.
+ *
+ * A = A + B
+ *
+ * @return $this
+ */
+ public function plusEquals(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $validValues = true;
+ $value = $M->get($i, $j);
+ if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
+ $this->A[$i][$j] = trim($this->A[$i][$j], '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
+ }
+ if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
+ $value = trim($value, '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($value);
+ }
+ if ($validValues) {
+ $this->A[$i][$j] += $value;
+ } else {
+ $this->A[$i][$j] = Functions::NAN();
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * minus.
+ *
+ * A - B
+ *
+ * @return Matrix Sum
+ */
+ public function minus(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $M->set($i, $j, $M->get($i, $j) - $this->A[$i][$j]);
+ }
+ }
+
+ return $M;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * minusEquals.
+ *
+ * A = A - B
+ *
+ * @return $this
+ */
+ public function minusEquals(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $validValues = true;
+ $value = $M->get($i, $j);
+ if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
+ $this->A[$i][$j] = trim($this->A[$i][$j], '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
+ }
+ if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
+ $value = trim($value, '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($value);
+ }
+ if ($validValues) {
+ $this->A[$i][$j] -= $value;
+ } else {
+ $this->A[$i][$j] = Functions::NAN();
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * arrayTimes.
+ *
+ * Element-by-element multiplication
+ * Cij = Aij * Bij
+ *
+ * @return Matrix Matrix Cij
+ */
+ public function arrayTimes(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $M->set($i, $j, $M->get($i, $j) * $this->A[$i][$j]);
+ }
+ }
+
+ return $M;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * arrayTimesEquals.
+ *
+ * Element-by-element multiplication
+ * Aij = Aij * Bij
+ *
+ * @return $this
+ */
+ public function arrayTimesEquals(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $validValues = true;
+ $value = $M->get($i, $j);
+ if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
+ $this->A[$i][$j] = trim($this->A[$i][$j], '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
+ }
+ if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
+ $value = trim($value, '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($value);
+ }
+ if ($validValues) {
+ $this->A[$i][$j] *= $value;
+ } else {
+ $this->A[$i][$j] = Functions::NAN();
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * arrayRightDivide.
+ *
+ * Element-by-element right division
+ * A / B
+ *
+ * @return Matrix Division result
+ */
+ public function arrayRightDivide(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $validValues = true;
+ $value = $M->get($i, $j);
+ if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
+ $this->A[$i][$j] = trim($this->A[$i][$j], '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
+ }
+ if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
+ $value = trim($value, '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($value);
+ }
+ if ($validValues) {
+ if ($value == 0) {
+ // Trap for Divide by Zero error
+ $M->set($i, $j, '#DIV/0!');
+ } else {
+ $M->set($i, $j, $this->A[$i][$j] / $value);
+ }
+ } else {
+ $M->set($i, $j, Functions::NAN());
+ }
+ }
+ }
+
+ return $M;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * arrayRightDivideEquals.
+ *
+ * Element-by-element right division
+ * Aij = Aij / Bij
+ *
+ * @return Matrix Matrix Aij
+ */
+ public function arrayRightDivideEquals(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $this->A[$i][$j] = $this->A[$i][$j] / $M->get($i, $j);
+ }
+ }
+
+ return $M;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * arrayLeftDivide.
+ *
+ * Element-by-element Left division
+ * A / B
+ *
+ * @return Matrix Division result
+ */
+ public function arrayLeftDivide(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $M->set($i, $j, $M->get($i, $j) / $this->A[$i][$j]);
+ }
+ }
+
+ return $M;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * arrayLeftDivideEquals.
+ *
+ * Element-by-element Left division
+ * Aij = Aij / Bij
+ *
+ * @return Matrix Matrix Aij
+ */
+ public function arrayLeftDivideEquals(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $this->A[$i][$j] = $M->get($i, $j) / $this->A[$i][$j];
+ }
+ }
+
+ return $M;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * times.
+ *
+ * Matrix multiplication
+ *
+ * @return Matrix Product
+ */
+ public function times(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $B = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+ if ($this->n == $B->m) {
+ $C = new self($this->m, $B->n);
+ for ($j = 0; $j < $B->n; ++$j) {
+ $Bcolj = [];
+ for ($k = 0; $k < $this->n; ++$k) {
+ $Bcolj[$k] = $B->A[$k][$j];
+ }
+ for ($i = 0; $i < $this->m; ++$i) {
+ $Arowi = $this->A[$i];
+ $s = 0;
+ for ($k = 0; $k < $this->n; ++$k) {
+ $s += $Arowi[$k] * $Bcolj[$k];
+ }
+ $C->A[$i][$j] = $s;
+ }
+ }
+
+ return $C;
+ }
+
+ throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
+ case 'array':
+ $B = new self($args[0]);
+ if ($this->n == $B->m) {
+ $C = new self($this->m, $B->n);
+ for ($i = 0; $i < $C->m; ++$i) {
+ for ($j = 0; $j < $C->n; ++$j) {
+ $s = '0';
+ for ($k = 0; $k < $C->n; ++$k) {
+ $s += $this->A[$i][$k] * $B->A[$k][$j];
+ }
+ $C->A[$i][$j] = $s;
+ }
+ }
+
+ return $C;
+ }
+
+ throw new CalculationException(self::MATRIX_DIMENSION_EXCEPTION);
+ case 'integer':
+ $C = new self($this->A);
+ for ($i = 0; $i < $C->m; ++$i) {
+ for ($j = 0; $j < $C->n; ++$j) {
+ $C->A[$i][$j] *= $args[0];
+ }
+ }
+
+ return $C;
+ case 'double':
+ $C = new self($this->m, $this->n);
+ for ($i = 0; $i < $C->m; ++$i) {
+ for ($j = 0; $j < $C->n; ++$j) {
+ $C->A[$i][$j] = $args[0] * $this->A[$i][$j];
+ }
+ }
+
+ return $C;
+ case 'float':
+ $C = new self($this->A);
+ for ($i = 0; $i < $C->m; ++$i) {
+ for ($j = 0; $j < $C->n; ++$j) {
+ $C->A[$i][$j] *= $args[0];
+ }
+ }
+
+ return $C;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+ } else {
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+ }
+
+ /**
+ * power.
+ *
+ * A = A ^ B
+ *
+ * @return $this
+ */
+ public function power(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $validValues = true;
+ $value = $M->get($i, $j);
+ if ((is_string($this->A[$i][$j])) && (strlen($this->A[$i][$j]) > 0) && (!is_numeric($this->A[$i][$j]))) {
+ $this->A[$i][$j] = trim($this->A[$i][$j], '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($this->A[$i][$j]);
+ }
+ if ((is_string($value)) && (strlen($value) > 0) && (!is_numeric($value))) {
+ $value = trim($value, '"');
+ $validValues &= StringHelper::convertToNumberIfFraction($value);
+ }
+ if ($validValues) {
+ $this->A[$i][$j] = $this->A[$i][$j] ** $value;
+ } else {
+ $this->A[$i][$j] = Functions::NAN();
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * concat.
+ *
+ * A = A & B
+ *
+ * @return $this
+ */
+ public function concat(...$args)
+ {
+ if (count($args) > 0) {
+ $match = implode(',', array_map('gettype', $args));
+
+ switch ($match) {
+ case 'object':
+ if ($args[0] instanceof self) {
+ $M = $args[0];
+ } else {
+ throw new CalculationException(self::ARGUMENT_TYPE_EXCEPTION);
+ }
+
+ break;
+ case 'array':
+ $M = new self($args[0]);
+
+ break;
+ default:
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+
+ break;
+ }
+ $this->checkMatrixDimensions($M);
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $this->A[$i][$j] = trim($this->A[$i][$j], '"') . trim($M->get($i, $j), '"');
+ }
+ }
+
+ return $this;
+ }
+
+ throw new CalculationException(self::POLYMORPHIC_ARGUMENT_EXCEPTION);
+ }
+
+ /**
+ * Solve A*X = B.
+ *
+ * @param Matrix $B Right hand side
+ *
+ * @return Matrix ... Solution if A is square, least squares solution otherwise
+ */
+ public function solve(self $B)
+ {
+ if ($this->m == $this->n) {
+ $LU = new LUDecomposition($this);
+
+ return $LU->solve($B);
+ }
+ $QR = new QRDecomposition($this);
+
+ return $QR->solve($B);
+ }
+
+ /**
+ * Matrix inverse or pseudoinverse.
+ *
+ * @return Matrix ... Inverse(A) if A is square, pseudoinverse otherwise.
+ */
+ public function inverse()
+ {
+ return $this->solve($this->identity($this->m, $this->m));
+ }
+
+ /**
+ * det.
+ *
+ * Calculate determinant
+ *
+ * @return float Determinant
+ */
+ public function det()
+ {
+ $L = new LUDecomposition($this);
+
+ return $L->det();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/utils/Maths.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/utils/Maths.php
new file mode 100644
index 0000000..49877b2
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/utils/Maths.php
@@ -0,0 +1,31 @@
+ abs($b)) {
+ $r = $b / $a;
+ $r = abs($a) * sqrt(1 + $r * $r);
+ } elseif ($b != 0) {
+ $r = $a / $b;
+ $r = abs($b) * sqrt(1 + $r * $r);
+ } else {
+ $r = 0.0;
+ }
+
+ return $r;
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php
new file mode 100644
index 0000000..ca97573
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php
@@ -0,0 +1,556 @@
+ |
+// | Based on OLE::Storage_Lite by Kawai, Takanori |
+// +----------------------------------------------------------------------+
+//
+
+use PhpOffice\PhpSpreadsheet\Exception;
+use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
+use PhpOffice\PhpSpreadsheet\Shared\OLE\ChainedBlockStream;
+use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root;
+
+/*
+ * Array for storing OLE instances that are accessed from
+ * OLE_ChainedBlockStream::stream_open().
+ *
+ * @var array
+ */
+$GLOBALS['_OLE_INSTANCES'] = [];
+
+/**
+ * OLE package base class.
+ *
+ * @author Xavier Noguer
+ * @author Christian Schmidt
+ */
+class OLE
+{
+ const OLE_PPS_TYPE_ROOT = 5;
+ const OLE_PPS_TYPE_DIR = 1;
+ const OLE_PPS_TYPE_FILE = 2;
+ const OLE_DATA_SIZE_SMALL = 0x1000;
+ const OLE_LONG_INT_SIZE = 4;
+ const OLE_PPS_SIZE = 0x80;
+
+ /**
+ * The file handle for reading an OLE container.
+ *
+ * @var resource
+ */
+ public $_file_handle;
+
+ /**
+ * Array of PPS's found on the OLE container.
+ *
+ * @var array
+ */
+ public $_list = [];
+
+ /**
+ * Root directory of OLE container.
+ *
+ * @var Root
+ */
+ public $root;
+
+ /**
+ * Big Block Allocation Table.
+ *
+ * @var array (blockId => nextBlockId)
+ */
+ public $bbat;
+
+ /**
+ * Short Block Allocation Table.
+ *
+ * @var array (blockId => nextBlockId)
+ */
+ public $sbat;
+
+ /**
+ * Size of big blocks. This is usually 512.
+ *
+ * @var int number of octets per block
+ */
+ public $bigBlockSize;
+
+ /**
+ * Size of small blocks. This is usually 64.
+ *
+ * @var int number of octets per block
+ */
+ public $smallBlockSize;
+
+ /**
+ * Threshold for big blocks.
+ *
+ * @var int
+ */
+ public $bigBlockThreshold;
+
+ /**
+ * Reads an OLE container from the contents of the file given.
+ *
+ * @acces public
+ *
+ * @param string $filename
+ *
+ * @return bool true on success, PEAR_Error on failure
+ */
+ public function read($filename)
+ {
+ $fh = fopen($filename, 'rb');
+ if (!$fh) {
+ throw new ReaderException("Can't open file $filename");
+ }
+ $this->_file_handle = $fh;
+
+ $signature = fread($fh, 8);
+ if ("\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1" != $signature) {
+ throw new ReaderException("File doesn't seem to be an OLE container.");
+ }
+ fseek($fh, 28);
+ if (fread($fh, 2) != "\xFE\xFF") {
+ // This shouldn't be a problem in practice
+ throw new ReaderException('Only Little-Endian encoding is supported.');
+ }
+ // Size of blocks and short blocks in bytes
+ $this->bigBlockSize = 2 ** self::readInt2($fh);
+ $this->smallBlockSize = 2 ** self::readInt2($fh);
+
+ // Skip UID, revision number and version number
+ fseek($fh, 44);
+ // Number of blocks in Big Block Allocation Table
+ $bbatBlockCount = self::readInt4($fh);
+
+ // Root chain 1st block
+ $directoryFirstBlockId = self::readInt4($fh);
+
+ // Skip unused bytes
+ fseek($fh, 56);
+ // Streams shorter than this are stored using small blocks
+ $this->bigBlockThreshold = self::readInt4($fh);
+ // Block id of first sector in Short Block Allocation Table
+ $sbatFirstBlockId = self::readInt4($fh);
+ // Number of blocks in Short Block Allocation Table
+ $sbbatBlockCount = self::readInt4($fh);
+ // Block id of first sector in Master Block Allocation Table
+ $mbatFirstBlockId = self::readInt4($fh);
+ // Number of blocks in Master Block Allocation Table
+ $mbbatBlockCount = self::readInt4($fh);
+ $this->bbat = [];
+
+ // Remaining 4 * 109 bytes of current block is beginning of Master
+ // Block Allocation Table
+ $mbatBlocks = [];
+ for ($i = 0; $i < 109; ++$i) {
+ $mbatBlocks[] = self::readInt4($fh);
+ }
+
+ // Read rest of Master Block Allocation Table (if any is left)
+ $pos = $this->getBlockOffset($mbatFirstBlockId);
+ for ($i = 0; $i < $mbbatBlockCount; ++$i) {
+ fseek($fh, $pos);
+ for ($j = 0; $j < $this->bigBlockSize / 4 - 1; ++$j) {
+ $mbatBlocks[] = self::readInt4($fh);
+ }
+ // Last block id in each block points to next block
+ $pos = $this->getBlockOffset(self::readInt4($fh));
+ }
+
+ // Read Big Block Allocation Table according to chain specified by $mbatBlocks
+ for ($i = 0; $i < $bbatBlockCount; ++$i) {
+ $pos = $this->getBlockOffset($mbatBlocks[$i]);
+ fseek($fh, $pos);
+ for ($j = 0; $j < $this->bigBlockSize / 4; ++$j) {
+ $this->bbat[] = self::readInt4($fh);
+ }
+ }
+
+ // Read short block allocation table (SBAT)
+ $this->sbat = [];
+ $shortBlockCount = $sbbatBlockCount * $this->bigBlockSize / 4;
+ $sbatFh = $this->getStream($sbatFirstBlockId);
+ for ($blockId = 0; $blockId < $shortBlockCount; ++$blockId) {
+ $this->sbat[$blockId] = self::readInt4($sbatFh);
+ }
+ fclose($sbatFh);
+
+ $this->readPpsWks($directoryFirstBlockId);
+
+ return true;
+ }
+
+ /**
+ * @param int $blockId byte offset from beginning of file
+ *
+ * @return int
+ */
+ public function getBlockOffset($blockId)
+ {
+ return 512 + $blockId * $this->bigBlockSize;
+ }
+
+ /**
+ * Returns a stream for use with fread() etc. External callers should
+ * use \PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File::getStream().
+ *
+ * @param int|OLE\PPS $blockIdOrPps block id or PPS
+ *
+ * @return resource read-only stream
+ */
+ public function getStream($blockIdOrPps)
+ {
+ static $isRegistered = false;
+ if (!$isRegistered) {
+ stream_wrapper_register('ole-chainedblockstream', ChainedBlockStream::class);
+ $isRegistered = true;
+ }
+
+ // Store current instance in global array, so that it can be accessed
+ // in OLE_ChainedBlockStream::stream_open().
+ // Object is removed from self::$instances in OLE_Stream::close().
+ $GLOBALS['_OLE_INSTANCES'][] = $this;
+ $keys = array_keys($GLOBALS['_OLE_INSTANCES']);
+ $instanceId = end($keys);
+
+ $path = 'ole-chainedblockstream://oleInstanceId=' . $instanceId;
+ if ($blockIdOrPps instanceof OLE\PPS) {
+ $path .= '&blockId=' . $blockIdOrPps->startBlock;
+ $path .= '&size=' . $blockIdOrPps->Size;
+ } else {
+ $path .= '&blockId=' . $blockIdOrPps;
+ }
+
+ return fopen($path, 'rb');
+ }
+
+ /**
+ * Reads a signed char.
+ *
+ * @param resource $fileHandle file handle
+ *
+ * @return int
+ */
+ private static function readInt1($fileHandle)
+ {
+ [, $tmp] = unpack('c', fread($fileHandle, 1));
+
+ return $tmp;
+ }
+
+ /**
+ * Reads an unsigned short (2 octets).
+ *
+ * @param resource $fileHandle file handle
+ *
+ * @return int
+ */
+ private static function readInt2($fileHandle)
+ {
+ [, $tmp] = unpack('v', fread($fileHandle, 2));
+
+ return $tmp;
+ }
+
+ /**
+ * Reads an unsigned long (4 octets).
+ *
+ * @param resource $fileHandle file handle
+ *
+ * @return int
+ */
+ private static function readInt4($fileHandle)
+ {
+ [, $tmp] = unpack('V', fread($fileHandle, 4));
+
+ return $tmp;
+ }
+
+ /**
+ * Gets information about all PPS's on the OLE container from the PPS WK's
+ * creates an OLE_PPS object for each one.
+ *
+ * @param int $blockId the block id of the first block
+ *
+ * @return bool true on success, PEAR_Error on failure
+ */
+ public function readPpsWks($blockId)
+ {
+ $fh = $this->getStream($blockId);
+ for ($pos = 0; true; $pos += 128) {
+ fseek($fh, $pos, SEEK_SET);
+ $nameUtf16 = fread($fh, 64);
+ $nameLength = self::readInt2($fh);
+ $nameUtf16 = substr($nameUtf16, 0, $nameLength - 2);
+ // Simple conversion from UTF-16LE to ISO-8859-1
+ $name = str_replace("\x00", '', $nameUtf16);
+ $type = self::readInt1($fh);
+ switch ($type) {
+ case self::OLE_PPS_TYPE_ROOT:
+ $pps = new OLE\PPS\Root(null, null, []);
+ $this->root = $pps;
+
+ break;
+ case self::OLE_PPS_TYPE_DIR:
+ $pps = new OLE\PPS(null, null, null, null, null, null, null, null, null, []);
+
+ break;
+ case self::OLE_PPS_TYPE_FILE:
+ $pps = new OLE\PPS\File($name);
+
+ break;
+ default:
+ throw new Exception('Unsupported PPS type');
+ }
+ fseek($fh, 1, SEEK_CUR);
+ $pps->Type = $type;
+ $pps->Name = $name;
+ $pps->PrevPps = self::readInt4($fh);
+ $pps->NextPps = self::readInt4($fh);
+ $pps->DirPps = self::readInt4($fh);
+ fseek($fh, 20, SEEK_CUR);
+ $pps->Time1st = self::OLE2LocalDate(fread($fh, 8));
+ $pps->Time2nd = self::OLE2LocalDate(fread($fh, 8));
+ $pps->startBlock = self::readInt4($fh);
+ $pps->Size = self::readInt4($fh);
+ $pps->No = count($this->_list);
+ $this->_list[] = $pps;
+
+ // check if the PPS tree (starting from root) is complete
+ if (isset($this->root) && $this->ppsTreeComplete($this->root->No)) {
+ break;
+ }
+ }
+ fclose($fh);
+
+ // Initialize $pps->children on directories
+ foreach ($this->_list as $pps) {
+ if ($pps->Type == self::OLE_PPS_TYPE_DIR || $pps->Type == self::OLE_PPS_TYPE_ROOT) {
+ $nos = [$pps->DirPps];
+ $pps->children = [];
+ while ($nos) {
+ $no = array_pop($nos);
+ if ($no != -1) {
+ $childPps = $this->_list[$no];
+ $nos[] = $childPps->PrevPps;
+ $nos[] = $childPps->NextPps;
+ $pps->children[] = $childPps;
+ }
+ }
+ }
+ }
+
+ return true;
+ }
+
+ /**
+ * It checks whether the PPS tree is complete (all PPS's read)
+ * starting with the given PPS (not necessarily root).
+ *
+ * @param int $index The index of the PPS from which we are checking
+ *
+ * @return bool Whether the PPS tree for the given PPS is complete
+ */
+ private function ppsTreeComplete($index)
+ {
+ return isset($this->_list[$index]) &&
+ ($pps = $this->_list[$index]) &&
+ ($pps->PrevPps == -1 ||
+ $this->ppsTreeComplete($pps->PrevPps)) &&
+ ($pps->NextPps == -1 ||
+ $this->ppsTreeComplete($pps->NextPps)) &&
+ ($pps->DirPps == -1 ||
+ $this->ppsTreeComplete($pps->DirPps));
+ }
+
+ /**
+ * Checks whether a PPS is a File PPS or not.
+ * If there is no PPS for the index given, it will return false.
+ *
+ * @param int $index The index for the PPS
+ *
+ * @return bool true if it's a File PPS, false otherwise
+ */
+ public function isFile($index)
+ {
+ if (isset($this->_list[$index])) {
+ return $this->_list[$index]->Type == self::OLE_PPS_TYPE_FILE;
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks whether a PPS is a Root PPS or not.
+ * If there is no PPS for the index given, it will return false.
+ *
+ * @param int $index the index for the PPS
+ *
+ * @return bool true if it's a Root PPS, false otherwise
+ */
+ public function isRoot($index)
+ {
+ if (isset($this->_list[$index])) {
+ return $this->_list[$index]->Type == self::OLE_PPS_TYPE_ROOT;
+ }
+
+ return false;
+ }
+
+ /**
+ * Gives the total number of PPS's found in the OLE container.
+ *
+ * @return int The total number of PPS's found in the OLE container
+ */
+ public function ppsTotal()
+ {
+ return count($this->_list);
+ }
+
+ /**
+ * Gets data from a PPS
+ * If there is no PPS for the index given, it will return an empty string.
+ *
+ * @param int $index The index for the PPS
+ * @param int $position The position from which to start reading
+ * (relative to the PPS)
+ * @param int $length The amount of bytes to read (at most)
+ *
+ * @return string The binary string containing the data requested
+ *
+ * @see OLE_PPS_File::getStream()
+ */
+ public function getData($index, $position, $length)
+ {
+ // if position is not valid return empty string
+ if (!isset($this->_list[$index]) || ($position >= $this->_list[$index]->Size) || ($position < 0)) {
+ return '';
+ }
+ $fh = $this->getStream($this->_list[$index]);
+ $data = stream_get_contents($fh, $length, $position);
+ fclose($fh);
+
+ return $data;
+ }
+
+ /**
+ * Gets the data length from a PPS
+ * If there is no PPS for the index given, it will return 0.
+ *
+ * @param int $index The index for the PPS
+ *
+ * @return int The amount of bytes in data the PPS has
+ */
+ public function getDataLength($index)
+ {
+ if (isset($this->_list[$index])) {
+ return $this->_list[$index]->Size;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Utility function to transform ASCII text to Unicode.
+ *
+ * @param string $ascii The ASCII string to transform
+ *
+ * @return string The string in Unicode
+ */
+ public static function ascToUcs($ascii)
+ {
+ $rawname = '';
+ $iMax = strlen($ascii);
+ for ($i = 0; $i < $iMax; ++$i) {
+ $rawname .= $ascii[$i]
+ . "\x00";
+ }
+
+ return $rawname;
+ }
+
+ /**
+ * Utility function
+ * Returns a string for the OLE container with the date given.
+ *
+ * @param float|int $date A timestamp
+ *
+ * @return string The string for the OLE container
+ */
+ public static function localDateToOLE($date)
+ {
+ if (!$date) {
+ return "\x00\x00\x00\x00\x00\x00\x00\x00";
+ }
+ $dateTime = Date::dateTimeFromTimestamp("$date");
+
+ // days from 1-1-1601 until the beggining of UNIX era
+ $days = 134774;
+ // calculate seconds
+ $big_date = $days * 24 * 3600 + (float) $dateTime->format('U');
+ // multiply just to make MS happy
+ $big_date *= 10000000;
+
+ // Make HEX string
+ $res = '';
+
+ $factor = 2 ** 56;
+ while ($factor >= 1) {
+ $hex = (int) floor($big_date / $factor);
+ $res = pack('c', $hex) . $res;
+ $big_date = fmod($big_date, $factor);
+ $factor /= 256;
+ }
+
+ return $res;
+ }
+
+ /**
+ * Returns a timestamp from an OLE container's date.
+ *
+ * @param string $oleTimestamp A binary string with the encoded date
+ *
+ * @return float|int The Unix timestamp corresponding to the string
+ */
+ public static function OLE2LocalDate($oleTimestamp)
+ {
+ if (strlen($oleTimestamp) != 8) {
+ throw new ReaderException('Expecting 8 byte string');
+ }
+
+ // convert to units of 100 ns since 1601:
+ $unpackedTimestamp = unpack('v4', $oleTimestamp);
+ $timestampHigh = (float) $unpackedTimestamp[4] * 65536 + (float) $unpackedTimestamp[3];
+ $timestampLow = (float) $unpackedTimestamp[2] * 65536 + (float) $unpackedTimestamp[1];
+
+ // translate to seconds since 1601:
+ $timestampHigh /= 10000000;
+ $timestampLow /= 10000000;
+
+ // days from 1601 to 1970:
+ $days = 134774;
+
+ // translate to seconds since 1970:
+ $unixTimestamp = floor(65536.0 * 65536.0 * $timestampHigh + $timestampLow - $days * 24 * 3600 + 0.5);
+
+ return IntOrFloat::evaluate($unixTimestamp);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php
new file mode 100644
index 0000000..dd1cda2
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php
@@ -0,0 +1,64 @@
+ |
+// | Based on OLE::Storage_Lite by Kawai, Takanori |
+// +----------------------------------------------------------------------+
+//
+use PhpOffice\PhpSpreadsheet\Shared\OLE;
+use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
+
+/**
+ * Class for creating File PPS's for OLE containers.
+ *
+ * @author Xavier Noguer
+ */
+class File extends PPS
+{
+ /**
+ * The constructor.
+ *
+ * @param string $name The name of the file (in Unicode)
+ *
+ * @see OLE::ascToUcs()
+ */
+ public function __construct($name)
+ {
+ parent::__construct(null, $name, OLE::OLE_PPS_TYPE_FILE, null, null, null, null, null, '', []);
+ }
+
+ /**
+ * Initialization method. Has to be called right after OLE_PPS_File().
+ *
+ * @return mixed true on success
+ */
+ public function init()
+ {
+ return true;
+ }
+
+ /**
+ * Append data to PPS.
+ *
+ * @param string $data The data to append
+ */
+ public function append($data): void
+ {
+ $this->_data .= $data;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php
new file mode 100644
index 0000000..91f1d04
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php
@@ -0,0 +1,347 @@
+data = file_get_contents($filename, false, null, 0, 8);
+
+ // Check OLE identifier
+ $identifierOle = pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1);
+ if ($this->data != $identifierOle) {
+ throw new ReaderException('The filename ' . $filename . ' is not recognised as an OLE file');
+ }
+
+ // Get the file data
+ $this->data = file_get_contents($filename);
+
+ // Total number of sectors used for the SAT
+ $this->numBigBlockDepotBlocks = self::getInt4d($this->data, self::NUM_BIG_BLOCK_DEPOT_BLOCKS_POS);
+
+ // SecID of the first sector of the directory stream
+ $this->rootStartBlock = self::getInt4d($this->data, self::ROOT_START_BLOCK_POS);
+
+ // SecID of the first sector of the SSAT (or -2 if not extant)
+ $this->sbdStartBlock = self::getInt4d($this->data, self::SMALL_BLOCK_DEPOT_BLOCK_POS);
+
+ // SecID of the first sector of the MSAT (or -2 if no additional sectors are used)
+ $this->extensionBlock = self::getInt4d($this->data, self::EXTENSION_BLOCK_POS);
+
+ // Total number of sectors used by MSAT
+ $this->numExtensionBlocks = self::getInt4d($this->data, self::NUM_EXTENSION_BLOCK_POS);
+
+ $bigBlockDepotBlocks = [];
+ $pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS;
+
+ $bbdBlocks = $this->numBigBlockDepotBlocks;
+
+ if ($this->numExtensionBlocks != 0) {
+ $bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS) / 4;
+ }
+
+ for ($i = 0; $i < $bbdBlocks; ++$i) {
+ $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
+ $pos += 4;
+ }
+
+ for ($j = 0; $j < $this->numExtensionBlocks; ++$j) {
+ $pos = ($this->extensionBlock + 1) * self::BIG_BLOCK_SIZE;
+ $blocksToRead = min($this->numBigBlockDepotBlocks - $bbdBlocks, self::BIG_BLOCK_SIZE / 4 - 1);
+
+ for ($i = $bbdBlocks; $i < $bbdBlocks + $blocksToRead; ++$i) {
+ $bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
+ $pos += 4;
+ }
+
+ $bbdBlocks += $blocksToRead;
+ if ($bbdBlocks < $this->numBigBlockDepotBlocks) {
+ $this->extensionBlock = self::getInt4d($this->data, $pos);
+ }
+ }
+
+ $pos = 0;
+ $this->bigBlockChain = '';
+ $bbs = self::BIG_BLOCK_SIZE / 4;
+ for ($i = 0; $i < $this->numBigBlockDepotBlocks; ++$i) {
+ $pos = ($bigBlockDepotBlocks[$i] + 1) * self::BIG_BLOCK_SIZE;
+
+ $this->bigBlockChain .= substr($this->data, $pos, 4 * $bbs);
+ $pos += 4 * $bbs;
+ }
+
+ $pos = 0;
+ $sbdBlock = $this->sbdStartBlock;
+ $this->smallBlockChain = '';
+ while ($sbdBlock != -2) {
+ $pos = ($sbdBlock + 1) * self::BIG_BLOCK_SIZE;
+
+ $this->smallBlockChain .= substr($this->data, $pos, 4 * $bbs);
+ $pos += 4 * $bbs;
+
+ $sbdBlock = self::getInt4d($this->bigBlockChain, $sbdBlock * 4);
+ }
+
+ // read the directory stream
+ $block = $this->rootStartBlock;
+ $this->entry = $this->readData($block);
+
+ $this->readPropertySets();
+ }
+
+ /**
+ * Extract binary stream data.
+ *
+ * @param int $stream
+ *
+ * @return null|string
+ */
+ public function getStream($stream)
+ {
+ if ($stream === null) {
+ return null;
+ }
+
+ $streamData = '';
+
+ if ($this->props[$stream]['size'] < self::SMALL_BLOCK_THRESHOLD) {
+ $rootdata = $this->readData($this->props[$this->rootentry]['startBlock']);
+
+ $block = $this->props[$stream]['startBlock'];
+
+ while ($block != -2) {
+ $pos = $block * self::SMALL_BLOCK_SIZE;
+ $streamData .= substr($rootdata, $pos, self::SMALL_BLOCK_SIZE);
+
+ $block = self::getInt4d($this->smallBlockChain, $block * 4);
+ }
+
+ return $streamData;
+ }
+ $numBlocks = $this->props[$stream]['size'] / self::BIG_BLOCK_SIZE;
+ if ($this->props[$stream]['size'] % self::BIG_BLOCK_SIZE != 0) {
+ ++$numBlocks;
+ }
+
+ if ($numBlocks == 0) {
+ return '';
+ }
+
+ $block = $this->props[$stream]['startBlock'];
+
+ while ($block != -2) {
+ $pos = ($block + 1) * self::BIG_BLOCK_SIZE;
+ $streamData .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
+ $block = self::getInt4d($this->bigBlockChain, $block * 4);
+ }
+
+ return $streamData;
+ }
+
+ /**
+ * Read a standard stream (by joining sectors using information from SAT).
+ *
+ * @param int $block Sector ID where the stream starts
+ *
+ * @return string Data for standard stream
+ */
+ private function readData($block)
+ {
+ $data = '';
+
+ while ($block != -2) {
+ $pos = ($block + 1) * self::BIG_BLOCK_SIZE;
+ $data .= substr($this->data, $pos, self::BIG_BLOCK_SIZE);
+ $block = self::getInt4d($this->bigBlockChain, $block * 4);
+ }
+
+ return $data;
+ }
+
+ /**
+ * Read entries in the directory stream.
+ */
+ private function readPropertySets(): void
+ {
+ $offset = 0;
+
+ // loop through entires, each entry is 128 bytes
+ $entryLen = strlen($this->entry);
+ while ($offset < $entryLen) {
+ // entry data (128 bytes)
+ $d = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE);
+
+ // size in bytes of name
+ $nameSize = ord($d[self::SIZE_OF_NAME_POS]) | (ord($d[self::SIZE_OF_NAME_POS + 1]) << 8);
+
+ // type of entry
+ $type = ord($d[self::TYPE_POS]);
+
+ // sectorID of first sector or short sector, if this entry refers to a stream (the case with workbook)
+ // sectorID of first sector of the short-stream container stream, if this entry is root entry
+ $startBlock = self::getInt4d($d, self::START_BLOCK_POS);
+
+ $size = self::getInt4d($d, self::SIZE_POS);
+
+ $name = str_replace("\x00", '', substr($d, 0, $nameSize));
+
+ $this->props[] = [
+ 'name' => $name,
+ 'type' => $type,
+ 'startBlock' => $startBlock,
+ 'size' => $size,
+ ];
+
+ // tmp helper to simplify checks
+ $upName = strtoupper($name);
+
+ // Workbook directory entry (BIFF5 uses Book, BIFF8 uses Workbook)
+ if (($upName === 'WORKBOOK') || ($upName === 'BOOK')) {
+ $this->wrkbook = count($this->props) - 1;
+ } elseif ($upName === 'ROOT ENTRY' || $upName === 'R') {
+ // Root entry
+ $this->rootentry = count($this->props) - 1;
+ }
+
+ // Summary information
+ if ($name == chr(5) . 'SummaryInformation') {
+ $this->summaryInformation = count($this->props) - 1;
+ }
+
+ // Additional Document Summary information
+ if ($name == chr(5) . 'DocumentSummaryInformation') {
+ $this->documentSummaryInformation = count($this->props) - 1;
+ }
+
+ $offset += self::PROPERTY_STORAGE_BLOCK_SIZE;
+ }
+ }
+
+ /**
+ * Read 4 bytes of data at specified position.
+ *
+ * @param string $data
+ * @param int $pos
+ *
+ * @return int
+ */
+ private static function getInt4d($data, $pos)
+ {
+ if ($pos < 0) {
+ // Invalid position
+ throw new ReaderException('Parameter pos=' . $pos . ' is invalid.');
+ }
+
+ $len = strlen($data);
+ if ($len < $pos + 4) {
+ $data .= str_repeat("\0", $pos + 4 - $len);
+ }
+
+ // FIX: represent numbers correctly on 64-bit system
+ // http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
+ // Changed by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
+ $_or_24 = ord($data[$pos + 3]);
+ if ($_or_24 >= 128) {
+ // negative number
+ $_ord_24 = -abs((256 - $_or_24) << 24);
+ } else {
+ $_ord_24 = ($_or_24 & 127) << 24;
+ }
+
+ return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $_ord_24;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php
new file mode 100644
index 0000000..0d58a86
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php
@@ -0,0 +1,109 @@
+ 'md2',
+ Protection::ALGORITHM_MD4 => 'md4',
+ Protection::ALGORITHM_MD5 => 'md5',
+ Protection::ALGORITHM_SHA_1 => 'sha1',
+ Protection::ALGORITHM_SHA_256 => 'sha256',
+ Protection::ALGORITHM_SHA_384 => 'sha384',
+ Protection::ALGORITHM_SHA_512 => 'sha512',
+ Protection::ALGORITHM_RIPEMD_128 => 'ripemd128',
+ Protection::ALGORITHM_RIPEMD_160 => 'ripemd160',
+ Protection::ALGORITHM_WHIRLPOOL => 'whirlpool',
+ ];
+
+ if (array_key_exists($algorithmName, $mapping)) {
+ return $mapping[$algorithmName];
+ }
+
+ throw new SpException('Unsupported password algorithm: ' . $algorithmName);
+ }
+
+ /**
+ * Create a password hash from a given string.
+ *
+ * This method is based on the spec at:
+ * https://interoperability.blob.core.windows.net/files/MS-OFFCRYPTO/[MS-OFFCRYPTO].pdf
+ * 2.3.7.1 Binary Document Password Verifier Derivation Method 1
+ *
+ * It replaces a method based on the algorithm provided by
+ * Daniel Rentz of OpenOffice and the PEAR package
+ * Spreadsheet_Excel_Writer by Xavier Noguer .
+ *
+ * Scrutinizer will squawk at the use of bitwise operations here,
+ * but it should ultimately pass.
+ *
+ * @param string $password Password to hash
+ */
+ private static function defaultHashPassword(string $password): string
+ {
+ $verifier = 0;
+ $pwlen = strlen($password);
+ $passwordArray = pack('c', $pwlen) . $password;
+ for ($i = $pwlen; $i >= 0; --$i) {
+ $intermediate1 = (($verifier & 0x4000) === 0) ? 0 : 1;
+ $intermediate2 = 2 * $verifier;
+ $intermediate2 = $intermediate2 & 0x7fff;
+ $intermediate3 = $intermediate1 | $intermediate2;
+ $verifier = $intermediate3 ^ ord($passwordArray[$i]);
+ }
+ $verifier ^= 0xCE4B;
+
+ return strtoupper(dechex($verifier));
+ }
+
+ /**
+ * Create a password hash from a given string by a specific algorithm.
+ *
+ * 2.4.2.4 ISO Write Protection Method
+ *
+ * @see https://docs.microsoft.com/en-us/openspecs/office_file_formats/ms-offcrypto/1357ea58-646e-4483-92ef-95d718079d6f
+ *
+ * @param string $password Password to hash
+ * @param string $algorithm Hash algorithm used to compute the password hash value
+ * @param string $salt Pseudorandom string
+ * @param int $spinCount Number of times to iterate on a hash of a password
+ *
+ * @return string Hashed password
+ */
+ public static function hashPassword(string $password, string $algorithm = '', string $salt = '', int $spinCount = 10000): string
+ {
+ if (strlen($password) > self::MAX_PASSWORD_LENGTH) {
+ throw new SpException('Password exceeds ' . self::MAX_PASSWORD_LENGTH . ' characters');
+ }
+ $phpAlgorithm = self::getAlgorithm($algorithm);
+ if (!$phpAlgorithm) {
+ return self::defaultHashPassword($password);
+ }
+
+ $saltValue = base64_decode($salt);
+ $encodedPassword = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8');
+
+ $hashValue = hash($phpAlgorithm, $saltValue . $encodedPassword, true);
+ for ($i = 0; $i < $spinCount; ++$i) {
+ $hashValue = hash($phpAlgorithm, $hashValue . pack('L', $i), true);
+ }
+
+ return base64_encode($hashValue);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php
new file mode 100644
index 0000000..eb8cd74
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php
@@ -0,0 +1,119 @@
+getIntersect() * $this->getSlope() ** ($xValue - $this->xOffset);
+ }
+
+ /**
+ * Return the X-Value for a specified value of Y.
+ *
+ * @param float $yValue Y-Value
+ *
+ * @return float X-Value
+ */
+ public function getValueOfXForY($yValue)
+ {
+ return log(($yValue + $this->yOffset) / $this->getIntersect()) / log($this->getSlope());
+ }
+
+ /**
+ * Return the Equation of the best-fit line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return string
+ */
+ public function getEquation($dp = 0)
+ {
+ $slope = $this->getSlope($dp);
+ $intersect = $this->getIntersect($dp);
+
+ return 'Y = ' . $intersect . ' * ' . $slope . '^X';
+ }
+
+ /**
+ * Return the Slope of the line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return float
+ */
+ public function getSlope($dp = 0)
+ {
+ if ($dp != 0) {
+ return round(exp($this->slope), $dp);
+ }
+
+ return exp($this->slope);
+ }
+
+ /**
+ * Return the Value of X where it intersects Y = 0.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return float
+ */
+ public function getIntersect($dp = 0)
+ {
+ if ($dp != 0) {
+ return round(exp($this->intersect), $dp);
+ }
+
+ return exp($this->intersect);
+ }
+
+ /**
+ * Execute the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ */
+ private function exponentialRegression(array $yValues, array $xValues, bool $const): void
+ {
+ $adjustedYValues = array_map(
+ function ($value) {
+ return ($value < 0.0) ? 0 - log(abs($value)) : log($value);
+ },
+ $yValues
+ );
+
+ $this->leastSquareFit($adjustedYValues, $xValues, $const);
+ }
+
+ /**
+ * Define the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ * @param bool $const
+ */
+ public function __construct($yValues, $xValues = [], $const = true)
+ {
+ parent::__construct($yValues, $xValues);
+
+ if (!$this->error) {
+ $this->exponentialRegression($yValues, $xValues, (bool) $const);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php
new file mode 100644
index 0000000..65d6b4f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php
@@ -0,0 +1,80 @@
+getIntersect() + $this->getSlope() * $xValue;
+ }
+
+ /**
+ * Return the X-Value for a specified value of Y.
+ *
+ * @param float $yValue Y-Value
+ *
+ * @return float X-Value
+ */
+ public function getValueOfXForY($yValue)
+ {
+ return ($yValue - $this->getIntersect()) / $this->getSlope();
+ }
+
+ /**
+ * Return the Equation of the best-fit line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return string
+ */
+ public function getEquation($dp = 0)
+ {
+ $slope = $this->getSlope($dp);
+ $intersect = $this->getIntersect($dp);
+
+ return 'Y = ' . $intersect . ' + ' . $slope . ' * X';
+ }
+
+ /**
+ * Execute the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ */
+ private function linearRegression(array $yValues, array $xValues, bool $const): void
+ {
+ $this->leastSquareFit($yValues, $xValues, $const);
+ }
+
+ /**
+ * Define the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ * @param bool $const
+ */
+ public function __construct($yValues, $xValues = [], $const = true)
+ {
+ parent::__construct($yValues, $xValues);
+
+ if (!$this->error) {
+ $this->linearRegression($yValues, $xValues, (bool) $const);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php
new file mode 100644
index 0000000..2366dc6
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php
@@ -0,0 +1,87 @@
+getIntersect() + $this->getSlope() * log($xValue - $this->xOffset);
+ }
+
+ /**
+ * Return the X-Value for a specified value of Y.
+ *
+ * @param float $yValue Y-Value
+ *
+ * @return float X-Value
+ */
+ public function getValueOfXForY($yValue)
+ {
+ return exp(($yValue - $this->getIntersect()) / $this->getSlope());
+ }
+
+ /**
+ * Return the Equation of the best-fit line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return string
+ */
+ public function getEquation($dp = 0)
+ {
+ $slope = $this->getSlope($dp);
+ $intersect = $this->getIntersect($dp);
+
+ return 'Y = ' . $slope . ' * log(' . $intersect . ' * X)';
+ }
+
+ /**
+ * Execute the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ */
+ private function logarithmicRegression(array $yValues, array $xValues, bool $const): void
+ {
+ $adjustedYValues = array_map(
+ function ($value) {
+ return ($value < 0.0) ? 0 - log(abs($value)) : log($value);
+ },
+ $yValues
+ );
+
+ $this->leastSquareFit($adjustedYValues, $xValues, $const);
+ }
+
+ /**
+ * Define the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ * @param bool $const
+ */
+ public function __construct($yValues, $xValues = [], $const = true)
+ {
+ parent::__construct($yValues, $xValues);
+
+ if (!$this->error) {
+ $this->logarithmicRegression($yValues, $xValues, (bool) $const);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php
new file mode 100644
index 0000000..3f063fe
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Duplicates.php
@@ -0,0 +1,78 @@
+ false,
+ 'unique' => true,
+ ];
+
+ /**
+ * @var bool
+ */
+ protected $inverse;
+
+ public function __construct(string $cellRange, bool $inverse = false)
+ {
+ parent::__construct($cellRange);
+ $this->inverse = $inverse;
+ }
+
+ protected function inverse(bool $inverse): void
+ {
+ $this->inverse = $inverse;
+ }
+
+ public function getConditional(): Conditional
+ {
+ $conditional = new Conditional();
+ $conditional->setConditionType(
+ $this->inverse ? Conditional::CONDITION_UNIQUE : Conditional::CONDITION_DUPLICATES
+ );
+ $conditional->setStyle($this->getStyle());
+ $conditional->setStopIfTrue($this->getStopIfTrue());
+
+ return $conditional;
+ }
+
+ public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
+ {
+ if (
+ $conditional->getConditionType() !== Conditional::CONDITION_DUPLICATES &&
+ $conditional->getConditionType() !== Conditional::CONDITION_UNIQUE
+ ) {
+ throw new Exception('Conditional is not a Duplicates CF Rule conditional');
+ }
+
+ $wizard = new self($cellRange);
+ $wizard->style = $conditional->getStyle();
+ $wizard->stopIfTrue = $conditional->getStopIfTrue();
+ $wizard->inverse = $conditional->getConditionType() === Conditional::CONDITION_UNIQUE;
+
+ return $wizard;
+ }
+
+ /**
+ * @param string $methodName
+ * @param mixed[] $arguments
+ */
+ public function __call($methodName, $arguments): self
+ {
+ if (!array_key_exists($methodName, self::OPERATORS)) {
+ throw new Exception('Invalid Operation for Errors CF Rule Wizard');
+ }
+
+ $this->inverse(self::OPERATORS[$methodName]);
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php
new file mode 100644
index 0000000..56b9086
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Errors.php
@@ -0,0 +1,95 @@
+ false,
+ 'isError' => true,
+ ];
+
+ protected const EXPRESSIONS = [
+ Wizard::NOT_ERRORS => 'NOT(ISERROR(%s))',
+ Wizard::ERRORS => 'ISERROR(%s)',
+ ];
+
+ /**
+ * @var bool
+ */
+ protected $inverse;
+
+ public function __construct(string $cellRange, bool $inverse = false)
+ {
+ parent::__construct($cellRange);
+ $this->inverse = $inverse;
+ }
+
+ protected function inverse(bool $inverse): void
+ {
+ $this->inverse = $inverse;
+ }
+
+ protected function getExpression(): void
+ {
+ $this->expression = sprintf(
+ self::EXPRESSIONS[$this->inverse ? Wizard::ERRORS : Wizard::NOT_ERRORS],
+ $this->referenceCell
+ );
+ }
+
+ public function getConditional(): Conditional
+ {
+ $this->getExpression();
+
+ $conditional = new Conditional();
+ $conditional->setConditionType(
+ $this->inverse ? Conditional::CONDITION_CONTAINSERRORS : Conditional::CONDITION_NOTCONTAINSERRORS
+ );
+ $conditional->setConditions([$this->expression]);
+ $conditional->setStyle($this->getStyle());
+ $conditional->setStopIfTrue($this->getStopIfTrue());
+
+ return $conditional;
+ }
+
+ public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
+ {
+ if (
+ $conditional->getConditionType() !== Conditional::CONDITION_CONTAINSERRORS &&
+ $conditional->getConditionType() !== Conditional::CONDITION_NOTCONTAINSERRORS
+ ) {
+ throw new Exception('Conditional is not an Errors CF Rule conditional');
+ }
+
+ $wizard = new self($cellRange);
+ $wizard->style = $conditional->getStyle();
+ $wizard->stopIfTrue = $conditional->getStopIfTrue();
+ $wizard->inverse = $conditional->getConditionType() === Conditional::CONDITION_CONTAINSERRORS;
+
+ return $wizard;
+ }
+
+ /**
+ * @param string $methodName
+ * @param mixed[] $arguments
+ */
+ public function __call($methodName, $arguments): self
+ {
+ if (!array_key_exists($methodName, self::OPERATORS)) {
+ throw new Exception('Invalid Operation for Errors CF Rule Wizard');
+ }
+
+ $this->inverse(self::OPERATORS[$methodName]);
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php
new file mode 100644
index 0000000..8c8c7f2
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/ConditionalFormatting/Wizard/Expression.php
@@ -0,0 +1,73 @@
+validateOperand($expression, Wizard::VALUE_TYPE_FORMULA);
+ $this->expression = $expression;
+
+ return $this;
+ }
+
+ public function getConditional(): Conditional
+ {
+ $expression = $this->adjustConditionsForCellReferences([$this->expression]);
+
+ $conditional = new Conditional();
+ $conditional->setConditionType(Conditional::CONDITION_EXPRESSION);
+ $conditional->setConditions($expression);
+ $conditional->setStyle($this->getStyle());
+ $conditional->setStopIfTrue($this->getStopIfTrue());
+
+ return $conditional;
+ }
+
+ public static function fromConditional(Conditional $conditional, string $cellRange = 'A1'): WizardInterface
+ {
+ if ($conditional->getConditionType() !== Conditional::CONDITION_EXPRESSION) {
+ throw new Exception('Conditional is not an Expression CF Rule conditional');
+ }
+
+ $wizard = new self($cellRange);
+ $wizard->style = $conditional->getStyle();
+ $wizard->stopIfTrue = $conditional->getStopIfTrue();
+ $wizard->expression = self::reverseAdjustCellRef($conditional->getConditions()[0], $cellRange);
+
+ return $wizard;
+ }
+
+ /**
+ * @param string $methodName
+ * @param mixed[] $arguments
+ */
+ public function __call($methodName, $arguments): self
+ {
+ if ($methodName !== 'formula') {
+ throw new Exception('Invalid Operation for Expression CF Rule Wizard');
+ }
+
+ $this->expression(...$arguments);
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php
new file mode 100644
index 0000000..bd87a79
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php
@@ -0,0 +1,347 @@
+fillType = null;
+ }
+ $this->startColor = new Color(Color::COLOR_WHITE, $isSupervisor, $isConditional);
+ $this->endColor = new Color(Color::COLOR_BLACK, $isSupervisor, $isConditional);
+
+ // bind parent if we are a supervisor
+ if ($isSupervisor) {
+ $this->startColor->bindParent($this, 'startColor');
+ $this->endColor->bindParent($this, 'endColor');
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Fill
+ */
+ public function getSharedComponent()
+ {
+ /** @var Style */
+ $parent = $this->parent;
+
+ return $parent->getSharedComponent()->getFill();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['fill' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getFill()->applyFromArray(
+ * [
+ * 'fillType' => Fill::FILL_GRADIENT_LINEAR,
+ * 'rotation' => 0.0,
+ * 'startColor' => [
+ * 'rgb' => '000000'
+ * ],
+ * 'endColor' => [
+ * 'argb' => 'FFFFFFFF'
+ * ]
+ * ]
+ * );
+ *
+ *
+ * @param array $styleArray Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $styleArray)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
+ } else {
+ if (isset($styleArray['fillType'])) {
+ $this->setFillType($styleArray['fillType']);
+ }
+ if (isset($styleArray['rotation'])) {
+ $this->setRotation($styleArray['rotation']);
+ }
+ if (isset($styleArray['startColor'])) {
+ $this->getStartColor()->applyFromArray($styleArray['startColor']);
+ }
+ if (isset($styleArray['endColor'])) {
+ $this->getEndColor()->applyFromArray($styleArray['endColor']);
+ }
+ if (isset($styleArray['color'])) {
+ $this->getStartColor()->applyFromArray($styleArray['color']);
+ $this->getEndColor()->applyFromArray($styleArray['color']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Fill Type.
+ *
+ * @return null|string
+ */
+ public function getFillType()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getFillType();
+ }
+
+ return $this->fillType;
+ }
+
+ /**
+ * Set Fill Type.
+ *
+ * @param string $fillType Fill type, see self::FILL_*
+ *
+ * @return $this
+ */
+ public function setFillType($fillType)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['fillType' => $fillType]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->fillType = $fillType;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Rotation.
+ *
+ * @return float
+ */
+ public function getRotation()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getRotation();
+ }
+
+ return $this->rotation;
+ }
+
+ /**
+ * Set Rotation.
+ *
+ * @param float $angleInDegrees
+ *
+ * @return $this
+ */
+ public function setRotation($angleInDegrees)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['rotation' => $angleInDegrees]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->rotation = $angleInDegrees;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Start Color.
+ *
+ * @return Color
+ */
+ public function getStartColor()
+ {
+ return $this->startColor;
+ }
+
+ /**
+ * Set Start Color.
+ *
+ * @return $this
+ */
+ public function setStartColor(Color $color)
+ {
+ $this->colorChanged = true;
+ // make sure parameter is a real color and not a supervisor
+ $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color;
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStartColor()->getStyleArray(['argb' => $color->getARGB()]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->startColor = $color;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get End Color.
+ *
+ * @return Color
+ */
+ public function getEndColor()
+ {
+ return $this->endColor;
+ }
+
+ /**
+ * Set End Color.
+ *
+ * @return $this
+ */
+ public function setEndColor(Color $color)
+ {
+ $this->colorChanged = true;
+ // make sure parameter is a real color and not a supervisor
+ $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color;
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getEndColor()->getStyleArray(['argb' => $color->getARGB()]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->endColor = $color;
+ }
+
+ return $this;
+ }
+
+ public function getColorsChanged(): bool
+ {
+ if ($this->isSupervisor) {
+ $changed = $this->getSharedComponent()->colorChanged;
+ } else {
+ $changed = $this->colorChanged;
+ }
+
+ return $changed || $this->startColor->getHasChanged() || $this->endColor->getHasChanged();
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+ // Note that we don't care about colours for fill type NONE, but could have duplicate NONEs with
+ // different hashes if we don't explicitly prevent this
+ return md5(
+ $this->getFillType() .
+ $this->getRotation() .
+ ($this->getFillType() !== self::FILL_NONE ? $this->getStartColor()->getHashCode() : '') .
+ ($this->getFillType() !== self::FILL_NONE ? $this->getEndColor()->getHashCode() : '') .
+ ((string) $this->getColorsChanged()) .
+ __CLASS__
+ );
+ }
+
+ protected function exportArray1(): array
+ {
+ $exportedArray = [];
+ $this->exportArray2($exportedArray, 'fillType', $this->getFillType());
+ $this->exportArray2($exportedArray, 'rotation', $this->getRotation());
+ if ($this->getColorsChanged()) {
+ $this->exportArray2($exportedArray, 'endColor', $this->getEndColor());
+ $this->exportArray2($exportedArray, 'startColor', $this->getStartColor());
+ }
+
+ return $exportedArray;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php
new file mode 100644
index 0000000..13fe2b6
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php
@@ -0,0 +1,568 @@
+name = null;
+ $this->size = null;
+ $this->bold = null;
+ $this->italic = null;
+ $this->superscript = null;
+ $this->subscript = null;
+ $this->underline = null;
+ $this->strikethrough = null;
+ $this->color = new Color(Color::COLOR_BLACK, $isSupervisor, $isConditional);
+ } else {
+ $this->color = new Color(Color::COLOR_BLACK, $isSupervisor);
+ }
+ // bind parent if we are a supervisor
+ if ($isSupervisor) {
+ $this->color->bindParent($this, 'color');
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Font
+ */
+ public function getSharedComponent()
+ {
+ /** @var Style */
+ $parent = $this->parent;
+
+ return $parent->getSharedComponent()->getFont();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['font' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getFont()->applyFromArray(
+ * [
+ * 'name' => 'Arial',
+ * 'bold' => TRUE,
+ * 'italic' => FALSE,
+ * 'underline' => \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE,
+ * 'strikethrough' => FALSE,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ]
+ * );
+ *
+ *
+ * @param array $styleArray Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $styleArray)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
+ } else {
+ if (isset($styleArray['name'])) {
+ $this->setName($styleArray['name']);
+ }
+ if (isset($styleArray['bold'])) {
+ $this->setBold($styleArray['bold']);
+ }
+ if (isset($styleArray['italic'])) {
+ $this->setItalic($styleArray['italic']);
+ }
+ if (isset($styleArray['superscript'])) {
+ $this->setSuperscript($styleArray['superscript']);
+ }
+ if (isset($styleArray['subscript'])) {
+ $this->setSubscript($styleArray['subscript']);
+ }
+ if (isset($styleArray['underline'])) {
+ $this->setUnderline($styleArray['underline']);
+ }
+ if (isset($styleArray['strikethrough'])) {
+ $this->setStrikethrough($styleArray['strikethrough']);
+ }
+ if (isset($styleArray['color'])) {
+ $this->getColor()->applyFromArray($styleArray['color']);
+ }
+ if (isset($styleArray['size'])) {
+ $this->setSize($styleArray['size']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Name.
+ *
+ * @return null|string
+ */
+ public function getName()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getName();
+ }
+
+ return $this->name;
+ }
+
+ /**
+ * Set Name.
+ *
+ * @param string $fontname
+ *
+ * @return $this
+ */
+ public function setName($fontname)
+ {
+ if ($fontname == '') {
+ $fontname = 'Calibri';
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['name' => $fontname]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->name = $fontname;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Size.
+ *
+ * @return null|float
+ */
+ public function getSize()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getSize();
+ }
+
+ return $this->size;
+ }
+
+ /**
+ * Set Size.
+ *
+ * @param mixed $sizeInPoints A float representing the value of a positive measurement in points (1/72 of an inch)
+ *
+ * @return $this
+ */
+ public function setSize($sizeInPoints)
+ {
+ if (is_string($sizeInPoints) || is_int($sizeInPoints)) {
+ $sizeInPoints = (float) $sizeInPoints; // $pValue = 0 if given string is not numeric
+ }
+
+ // Size must be a positive floating point number
+ // ECMA-376-1:2016, part 1, chapter 18.4.11 sz (Font Size), p. 1536
+ if (!is_float($sizeInPoints) || !($sizeInPoints > 0)) {
+ $sizeInPoints = 10.0;
+ }
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['size' => $sizeInPoints]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->size = $sizeInPoints;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Bold.
+ *
+ * @return null|bool
+ */
+ public function getBold()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getBold();
+ }
+
+ return $this->bold;
+ }
+
+ /**
+ * Set Bold.
+ *
+ * @param bool $bold
+ *
+ * @return $this
+ */
+ public function setBold($bold)
+ {
+ if ($bold == '') {
+ $bold = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['bold' => $bold]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->bold = $bold;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Italic.
+ *
+ * @return null|bool
+ */
+ public function getItalic()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getItalic();
+ }
+
+ return $this->italic;
+ }
+
+ /**
+ * Set Italic.
+ *
+ * @param bool $italic
+ *
+ * @return $this
+ */
+ public function setItalic($italic)
+ {
+ if ($italic == '') {
+ $italic = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['italic' => $italic]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->italic = $italic;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Superscript.
+ *
+ * @return null|bool
+ */
+ public function getSuperscript()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getSuperscript();
+ }
+
+ return $this->superscript;
+ }
+
+ /**
+ * Set Superscript.
+ *
+ * @return $this
+ */
+ public function setSuperscript(bool $superscript)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['superscript' => $superscript]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->superscript = $superscript;
+ if ($this->superscript) {
+ $this->subscript = false;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Subscript.
+ *
+ * @return null|bool
+ */
+ public function getSubscript()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getSubscript();
+ }
+
+ return $this->subscript;
+ }
+
+ /**
+ * Set Subscript.
+ *
+ * @return $this
+ */
+ public function setSubscript(bool $subscript)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['subscript' => $subscript]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->subscript = $subscript;
+ if ($this->subscript) {
+ $this->superscript = false;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Underline.
+ *
+ * @return null|string
+ */
+ public function getUnderline()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getUnderline();
+ }
+
+ return $this->underline;
+ }
+
+ /**
+ * Set Underline.
+ *
+ * @param bool|string $underlineStyle \PhpOffice\PhpSpreadsheet\Style\Font underline type
+ * If a boolean is passed, then TRUE equates to UNDERLINE_SINGLE,
+ * false equates to UNDERLINE_NONE
+ *
+ * @return $this
+ */
+ public function setUnderline($underlineStyle)
+ {
+ if (is_bool($underlineStyle)) {
+ $underlineStyle = ($underlineStyle) ? self::UNDERLINE_SINGLE : self::UNDERLINE_NONE;
+ } elseif ($underlineStyle == '') {
+ $underlineStyle = self::UNDERLINE_NONE;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['underline' => $underlineStyle]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->underline = $underlineStyle;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Strikethrough.
+ *
+ * @return null|bool
+ */
+ public function getStrikethrough()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getStrikethrough();
+ }
+
+ return $this->strikethrough;
+ }
+
+ /**
+ * Set Strikethrough.
+ *
+ * @param bool $strikethru
+ *
+ * @return $this
+ */
+ public function setStrikethrough($strikethru)
+ {
+ if ($strikethru == '') {
+ $strikethru = false;
+ }
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['strikethrough' => $strikethru]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->strikethrough = $strikethru;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Color.
+ *
+ * @return Color
+ */
+ public function getColor()
+ {
+ return $this->color;
+ }
+
+ /**
+ * Set Color.
+ *
+ * @return $this
+ */
+ public function setColor(Color $color)
+ {
+ // make sure parameter is a real color and not a supervisor
+ $color = $color->getIsSupervisor() ? $color->getSharedComponent() : $color;
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getColor()->getStyleArray(['argb' => $color->getARGB()]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->color = $color;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->name .
+ $this->size .
+ ($this->bold ? 't' : 'f') .
+ ($this->italic ? 't' : 'f') .
+ ($this->superscript ? 't' : 'f') .
+ ($this->subscript ? 't' : 'f') .
+ $this->underline .
+ ($this->strikethrough ? 't' : 'f') .
+ $this->color->getHashCode() .
+ __CLASS__
+ );
+ }
+
+ protected function exportArray1(): array
+ {
+ $exportedArray = [];
+ $this->exportArray2($exportedArray, 'bold', $this->getBold());
+ $this->exportArray2($exportedArray, 'color', $this->getColor());
+ $this->exportArray2($exportedArray, 'italic', $this->getItalic());
+ $this->exportArray2($exportedArray, 'name', $this->getName());
+ $this->exportArray2($exportedArray, 'size', $this->getSize());
+ $this->exportArray2($exportedArray, 'strikethrough', $this->getStrikethrough());
+ $this->exportArray2($exportedArray, 'subscript', $this->getSubscript());
+ $this->exportArray2($exportedArray, 'superscript', $this->getSuperscript());
+ $this->exportArray2($exportedArray, 'underline', $this->getUnderline());
+
+ return $exportedArray;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php
new file mode 100644
index 0000000..6f552cb
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php
@@ -0,0 +1,414 @@
+formatCode = null;
+ $this->builtInFormatCode = false;
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return NumberFormat
+ */
+ public function getSharedComponent()
+ {
+ /** @var Style */
+ $parent = $this->parent;
+
+ return $parent->getSharedComponent()->getNumberFormat();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['numberFormat' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getNumberFormat()->applyFromArray(
+ * [
+ * 'formatCode' => NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE
+ * ]
+ * );
+ *
+ *
+ * @param array $styleArray Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $styleArray)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($styleArray));
+ } else {
+ if (isset($styleArray['formatCode'])) {
+ $this->setFormatCode($styleArray['formatCode']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Format Code.
+ *
+ * @return null|string
+ */
+ public function getFormatCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getFormatCode();
+ }
+ if (is_int($this->builtInFormatCode)) {
+ return self::builtInFormatCode($this->builtInFormatCode);
+ }
+
+ return $this->formatCode;
+ }
+
+ /**
+ * Set Format Code.
+ *
+ * @param string $formatCode see self::FORMAT_*
+ *
+ * @return $this
+ */
+ public function setFormatCode($formatCode)
+ {
+ if ($formatCode == '') {
+ $formatCode = self::FORMAT_GENERAL;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['formatCode' => $formatCode]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->formatCode = $formatCode;
+ $this->builtInFormatCode = self::builtInFormatCodeIndex($formatCode);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Built-In Format Code.
+ *
+ * @return false|int
+ */
+ public function getBuiltInFormatCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getBuiltInFormatCode();
+ }
+
+ return $this->builtInFormatCode;
+ }
+
+ /**
+ * Set Built-In Format Code.
+ *
+ * @param int $formatCodeIndex
+ *
+ * @return $this
+ */
+ public function setBuiltInFormatCode($formatCodeIndex)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['formatCode' => self::builtInFormatCode($formatCodeIndex)]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->builtInFormatCode = $formatCodeIndex;
+ $this->formatCode = self::builtInFormatCode($formatCodeIndex);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Fill built-in format codes.
+ */
+ private static function fillBuiltInFormatCodes(): void
+ {
+ // [MS-OI29500: Microsoft Office Implementation Information for ISO/IEC-29500 Standard Compliance]
+ // 18.8.30. numFmt (Number Format)
+ //
+ // The ECMA standard defines built-in format IDs
+ // 14: "mm-dd-yy"
+ // 22: "m/d/yy h:mm"
+ // 37: "#,##0 ;(#,##0)"
+ // 38: "#,##0 ;[Red](#,##0)"
+ // 39: "#,##0.00;(#,##0.00)"
+ // 40: "#,##0.00;[Red](#,##0.00)"
+ // 47: "mmss.0"
+ // KOR fmt 55: "yyyy-mm-dd"
+ // Excel defines built-in format IDs
+ // 14: "m/d/yyyy"
+ // 22: "m/d/yyyy h:mm"
+ // 37: "#,##0_);(#,##0)"
+ // 38: "#,##0_);[Red](#,##0)"
+ // 39: "#,##0.00_);(#,##0.00)"
+ // 40: "#,##0.00_);[Red](#,##0.00)"
+ // 47: "mm:ss.0"
+ // KOR fmt 55: "yyyy/mm/dd"
+
+ // Built-in format codes
+ if (self::$builtInFormats === null) {
+ self::$builtInFormats = [];
+
+ // General
+ self::$builtInFormats[0] = self::FORMAT_GENERAL;
+ self::$builtInFormats[1] = '0';
+ self::$builtInFormats[2] = '0.00';
+ self::$builtInFormats[3] = '#,##0';
+ self::$builtInFormats[4] = '#,##0.00';
+
+ self::$builtInFormats[9] = '0%';
+ self::$builtInFormats[10] = '0.00%';
+ self::$builtInFormats[11] = '0.00E+00';
+ self::$builtInFormats[12] = '# ?/?';
+ self::$builtInFormats[13] = '# ??/??';
+ self::$builtInFormats[14] = 'm/d/yyyy'; // Despite ECMA 'mm-dd-yy';
+ self::$builtInFormats[15] = 'd-mmm-yy';
+ self::$builtInFormats[16] = 'd-mmm';
+ self::$builtInFormats[17] = 'mmm-yy';
+ self::$builtInFormats[18] = 'h:mm AM/PM';
+ self::$builtInFormats[19] = 'h:mm:ss AM/PM';
+ self::$builtInFormats[20] = 'h:mm';
+ self::$builtInFormats[21] = 'h:mm:ss';
+ self::$builtInFormats[22] = 'm/d/yyyy h:mm'; // Despite ECMA 'm/d/yy h:mm';
+
+ self::$builtInFormats[37] = '#,##0_);(#,##0)'; // Despite ECMA '#,##0 ;(#,##0)';
+ self::$builtInFormats[38] = '#,##0_);[Red](#,##0)'; // Despite ECMA '#,##0 ;[Red](#,##0)';
+ self::$builtInFormats[39] = '#,##0.00_);(#,##0.00)'; // Despite ECMA '#,##0.00;(#,##0.00)';
+ self::$builtInFormats[40] = '#,##0.00_);[Red](#,##0.00)'; // Despite ECMA '#,##0.00;[Red](#,##0.00)';
+
+ self::$builtInFormats[44] = '_("$"* #,##0.00_);_("$"* \(#,##0.00\);_("$"* "-"??_);_(@_)';
+ self::$builtInFormats[45] = 'mm:ss';
+ self::$builtInFormats[46] = '[h]:mm:ss';
+ self::$builtInFormats[47] = 'mm:ss.0'; // Despite ECMA 'mmss.0';
+ self::$builtInFormats[48] = '##0.0E+0';
+ self::$builtInFormats[49] = '@';
+
+ // CHT
+ self::$builtInFormats[27] = '[$-404]e/m/d';
+ self::$builtInFormats[30] = 'm/d/yy';
+ self::$builtInFormats[36] = '[$-404]e/m/d';
+ self::$builtInFormats[50] = '[$-404]e/m/d';
+ self::$builtInFormats[57] = '[$-404]e/m/d';
+
+ // THA
+ self::$builtInFormats[59] = 't0';
+ self::$builtInFormats[60] = 't0.00';
+ self::$builtInFormats[61] = 't#,##0';
+ self::$builtInFormats[62] = 't#,##0.00';
+ self::$builtInFormats[67] = 't0%';
+ self::$builtInFormats[68] = 't0.00%';
+ self::$builtInFormats[69] = 't# ?/?';
+ self::$builtInFormats[70] = 't# ??/??';
+
+ // JPN
+ self::$builtInFormats[28] = '[$-411]ggge"年"m"月"d"日"';
+ self::$builtInFormats[29] = '[$-411]ggge"年"m"月"d"日"';
+ self::$builtInFormats[31] = 'yyyy"年"m"月"d"日"';
+ self::$builtInFormats[32] = 'h"時"mm"分"';
+ self::$builtInFormats[33] = 'h"時"mm"分"ss"秒"';
+ self::$builtInFormats[34] = 'yyyy"年"m"月"';
+ self::$builtInFormats[35] = 'm"月"d"日"';
+ self::$builtInFormats[51] = '[$-411]ggge"年"m"月"d"日"';
+ self::$builtInFormats[52] = 'yyyy"年"m"月"';
+ self::$builtInFormats[53] = 'm"月"d"日"';
+ self::$builtInFormats[54] = '[$-411]ggge"年"m"月"d"日"';
+ self::$builtInFormats[55] = 'yyyy"年"m"月"';
+ self::$builtInFormats[56] = 'm"月"d"日"';
+ self::$builtInFormats[58] = '[$-411]ggge"年"m"月"d"日"';
+
+ // Flip array (for faster lookups)
+ self::$flippedBuiltInFormats = array_flip(self::$builtInFormats);
+ }
+ }
+
+ /**
+ * Get built-in format code.
+ *
+ * @param int $index
+ *
+ * @return string
+ */
+ public static function builtInFormatCode($index)
+ {
+ // Clean parameter
+ $index = (int) $index;
+
+ // Ensure built-in format codes are available
+ self::fillBuiltInFormatCodes();
+
+ // Lookup format code
+ if (isset(self::$builtInFormats[$index])) {
+ return self::$builtInFormats[$index];
+ }
+
+ return '';
+ }
+
+ /**
+ * Get built-in format code index.
+ *
+ * @param string $formatCodeIndex
+ *
+ * @return false|int
+ */
+ public static function builtInFormatCodeIndex($formatCodeIndex)
+ {
+ // Ensure built-in format codes are available
+ self::fillBuiltInFormatCodes();
+
+ // Lookup format code
+ if (array_key_exists($formatCodeIndex, self::$flippedBuiltInFormats)) {
+ return self::$flippedBuiltInFormats[$formatCodeIndex];
+ }
+
+ return false;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->formatCode .
+ $this->builtInFormatCode .
+ __CLASS__
+ );
+ }
+
+ /**
+ * Convert a value in a pre-defined format to a PHP string.
+ *
+ * @param mixed $value Value to format
+ * @param string $format Format code, see = self::FORMAT_*
+ * @param array $callBack Callback function for additional formatting of string
+ *
+ * @return string Formatted string
+ */
+ public static function toFormattedString($value, $format, $callBack = null)
+ {
+ return NumberFormat\Formatter::toFormattedString($value, $format, $callBack);
+ }
+
+ protected function exportArray1(): array
+ {
+ $exportedArray = [];
+ $this->exportArray2($exportedArray, 'formatCode', $this->getFormatCode());
+
+ return $exportedArray;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
new file mode 100644
index 0000000..01407e6
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/Formatter.php
@@ -0,0 +1,162 @@
+':
+ return $value > $val;
+
+ case '<':
+ return $value < $val;
+
+ case '<=':
+ return $value <= $val;
+
+ case '<>':
+ return $value != $val;
+
+ case '=':
+ return $value == $val;
+ }
+
+ return $value >= $val;
+ }
+
+ private static function splitFormat($sections, $value)
+ {
+ // Extract the relevant section depending on whether number is positive, negative, or zero?
+ // Text not supported yet.
+ // Here is how the sections apply to various values in Excel:
+ // 1 section: [POSITIVE/NEGATIVE/ZERO/TEXT]
+ // 2 sections: [POSITIVE/ZERO/TEXT] [NEGATIVE]
+ // 3 sections: [POSITIVE/TEXT] [NEGATIVE] [ZERO]
+ // 4 sections: [POSITIVE] [NEGATIVE] [ZERO] [TEXT]
+ $cnt = count($sections);
+ $color_regex = '/\\[(' . implode('|', Color::NAMED_COLORS) . ')\\]/mui';
+ $cond_regex = '/\\[(>|>=|<|<=|=|<>)([+-]?\\d+([.]\\d+)?)\\]/';
+ $colors = ['', '', '', '', ''];
+ $condops = ['', '', '', '', ''];
+ $condvals = [0, 0, 0, 0, 0];
+ for ($idx = 0; $idx < $cnt; ++$idx) {
+ if (preg_match($color_regex, $sections[$idx], $matches)) {
+ $colors[$idx] = $matches[0];
+ $sections[$idx] = preg_replace($color_regex, '', $sections[$idx]);
+ }
+ if (preg_match($cond_regex, $sections[$idx], $matches)) {
+ $condops[$idx] = $matches[1];
+ $condvals[$idx] = $matches[2];
+ $sections[$idx] = preg_replace($cond_regex, '', $sections[$idx]);
+ }
+ }
+ $color = $colors[0];
+ $format = $sections[0];
+ $absval = $value;
+ switch ($cnt) {
+ case 2:
+ $absval = abs($value);
+ if (!self::splitFormatCompare($value, $condops[0], $condvals[0], '>=', 0)) {
+ $color = $colors[1];
+ $format = $sections[1];
+ }
+
+ break;
+ case 3:
+ case 4:
+ $absval = abs($value);
+ if (!self::splitFormatCompare($value, $condops[0], $condvals[0], '>', 0)) {
+ if (self::splitFormatCompare($value, $condops[1], $condvals[1], '<', 0)) {
+ $color = $colors[1];
+ $format = $sections[1];
+ } else {
+ $color = $colors[2];
+ $format = $sections[2];
+ }
+ }
+
+ break;
+ }
+
+ return [$color, $format, $absval];
+ }
+
+ /**
+ * Convert a value in a pre-defined format to a PHP string.
+ *
+ * @param mixed $value Value to format
+ * @param string $format Format code, see = NumberFormat::FORMAT_*
+ * @param array $callBack Callback function for additional formatting of string
+ *
+ * @return string Formatted string
+ */
+ public static function toFormattedString($value, $format, $callBack = null)
+ {
+ // For now we do not treat strings although section 4 of a format code affects strings
+ if (!is_numeric($value)) {
+ return $value;
+ }
+
+ // For 'General' format code, we just pass the value although this is not entirely the way Excel does it,
+ // it seems to round numbers to a total of 10 digits.
+ if (($format === NumberFormat::FORMAT_GENERAL) || ($format === NumberFormat::FORMAT_TEXT)) {
+ return $value;
+ }
+
+ $format = preg_replace_callback(
+ '/(["])(?:(?=(\\\\?))\\2.)*?\\1/u',
+ function ($matches) {
+ return str_replace('.', chr(0x00), $matches[0]);
+ },
+ $format
+ );
+
+ // Convert any other escaped characters to quoted strings, e.g. (\T to "T")
+ $format = preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/ui', '"${2}"', $format);
+
+ // Get the sections, there can be up to four sections, separated with a semi-colon (but only if not a quoted literal)
+ $sections = preg_split('/(;)(?=(?:[^"]|"[^"]*")*$)/u', $format);
+
+ [$colors, $format, $value] = self::splitFormat($sections, $value);
+
+ // In Excel formats, "_" is used to add spacing,
+ // The following character indicates the size of the spacing, which we can't do in HTML, so we just use a standard space
+ $format = 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, $matches)) {
+ // datetime format
+ $value = DateFormatter::format($value, $format);
+ } else {
+ if (substr($format, 0, 1) === '"' && substr($format, -1, 1) === '"' && substr_count($format, '"') === 2) {
+ $value = substr($format, 1, -1);
+ } elseif (preg_match('/[0#, ]%/', $format)) {
+ // % number format
+ $value = PercentageFormatter::format($value, $format);
+ } else {
+ $value = NumberFormatter::format($value, $format);
+ }
+ }
+
+ // Additional formatting provided by callback function
+ if ($callBack !== null) {
+ [$writerInstance, $function] = $callBack;
+ $value = $writerInstance->$function($value, $colors);
+ }
+
+ $value = str_replace(chr(0x00), '.', $value);
+
+ return $value;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php
new file mode 100644
index 0000000..46f27cc
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/FractionFormatter.php
@@ -0,0 +1,64 @@
+ 0);
+
+ return [
+ implode('.', $masks),
+ implode('.', array_reverse($postDecimalMasks)),
+ ];
+ }
+
+ /**
+ * @param mixed $number
+ */
+ private static function processComplexNumberFormatMask($number, string $mask): string
+ {
+ /** @var string */
+ $result = $number;
+ $maskingBlockCount = preg_match_all('/0+/', $mask, $maskingBlocks, PREG_OFFSET_CAPTURE);
+
+ if ($maskingBlockCount > 1) {
+ $maskingBlocks = array_reverse($maskingBlocks[0]);
+
+ $offset = 0;
+ foreach ($maskingBlocks as $block) {
+ $size = strlen($block[0]);
+ $divisor = 10 ** $size;
+ $offset = $block[1];
+
+ /** @var float */
+ $numberFloat = $number;
+ $blockValue = sprintf("%0{$size}d", fmod($numberFloat, $divisor));
+ $number = floor($numberFloat / $divisor);
+ $mask = substr_replace($mask, $blockValue, $offset, $size);
+ }
+ /** @var string */
+ $numberString = $number;
+ if ($number > 0) {
+ $mask = substr_replace($mask, $numberString, $offset, 0);
+ }
+ $result = $mask;
+ }
+
+ return self::makeString($result);
+ }
+
+ /**
+ * @param mixed $number
+ */
+ private static function complexNumberFormatMask($number, string $mask, bool $splitOnPoint = true): string
+ {
+ $sign = ($number < 0.0) ? '-' : '';
+ /** @var float */
+ $numberFloat = $number;
+ $number = (string) abs($numberFloat);
+
+ if ($splitOnPoint && strpos($mask, '.') !== false && strpos($number, '.') !== false) {
+ $numbers = explode('.', $number);
+ $masks = explode('.', $mask);
+ if (count($masks) > 2) {
+ $masks = self::mergeComplexNumberFormatMasks($numbers, $masks);
+ }
+ $integerPart = self::complexNumberFormatMask($numbers[0], $masks[0], false);
+ $decimalPart = strrev(self::complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1]), false));
+
+ return "{$sign}{$integerPart}.{$decimalPart}";
+ }
+
+ $result = self::processComplexNumberFormatMask($number, $mask);
+
+ return "{$sign}{$result}";
+ }
+
+ /**
+ * @param mixed $value
+ */
+ private static function formatStraightNumericValue($value, string $format, array $matches, bool $useThousands): string
+ {
+ /** @var float */
+ $valueFloat = $value;
+ $left = $matches[1];
+ $dec = $matches[2];
+ $right = $matches[3];
+
+ // minimun width of formatted number (including dot)
+ $minWidth = strlen($left) + strlen($dec) + strlen($right);
+ if ($useThousands) {
+ $value = number_format(
+ $valueFloat,
+ strlen($right),
+ StringHelper::getDecimalSeparator(),
+ StringHelper::getThousandsSeparator()
+ );
+
+ return self::pregReplace(self::NUMBER_REGEX, $value, $format);
+ }
+
+ if (preg_match('/[0#]E[+-]0/i', $format)) {
+ // Scientific format
+ return sprintf('%5.2E', $valueFloat);
+ } elseif (preg_match('/0([^\d\.]+)0/', $format) || substr_count($format, '.') > 1) {
+ if ($value == (int) $valueFloat && substr_count($format, '.') === 1) {
+ $value *= 10 ** strlen(explode('.', $format)[1]);
+ }
+
+ return self::complexNumberFormatMask($value, $format);
+ }
+
+ $sprintf_pattern = "%0$minWidth." . strlen($right) . 'f';
+ /** @var float */
+ $valueFloat = $value;
+ $value = sprintf($sprintf_pattern, round($valueFloat, strlen($right)));
+
+ return self::pregReplace(self::NUMBER_REGEX, $value, $format);
+ }
+
+ /**
+ * @param mixed $value
+ */
+ public static function format($value, string $format): string
+ {
+ // The "_" in this string has already been stripped out,
+ // so this test is never true. Furthermore, testing
+ // on Excel shows this format uses Euro symbol, not "EUR".
+ //if ($format === NumberFormat::FORMAT_CURRENCY_EUR_SIMPLE) {
+ // return 'EUR ' . sprintf('%1.2f', $value);
+ //}
+
+ // Some non-number strings are quoted, so we'll get rid of the quotes, likewise any positional * symbols
+ $format = self::makeString(str_replace(['"', '*'], '', $format));
+
+ // Find out if we need thousands separator
+ // This is indicated by a comma enclosed by a digit placeholder:
+ // #,# or 0,0
+ $useThousands = (bool) preg_match('/(#,#|0,0)/', $format);
+ if ($useThousands) {
+ $format = self::pregReplace('/0,0/', '00', $format);
+ $format = self::pregReplace('/#,#/', '##', $format);
+ }
+
+ // Scale thousands, millions,...
+ // This is indicated by a number of commas after a digit placeholder:
+ // #, or 0.0,,
+ $scale = 1; // same as no scale
+ $matches = [];
+ if (preg_match('/(#|0)(,+)/', $format, $matches)) {
+ $scale = 1000 ** strlen($matches[2]);
+
+ // strip the commas
+ $format = self::pregReplace('/0,+/', '0', $format);
+ $format = self::pregReplace('/#,+/', '#', $format);
+ }
+ if (preg_match('/#?.*\?\/\?/', $format, $m)) {
+ $value = FractionFormatter::format($value, $format);
+ } else {
+ // Handle the number itself
+
+ // scale number
+ $value = $value / $scale;
+ // Strip #
+ $format = self::pregReplace('/\\#/', '0', $format);
+ // Remove locale code [$-###]
+ $format = self::pregReplace('/\[\$\-.*\]/', '', $format);
+
+ $n = '/\\[[^\\]]+\\]/';
+ $m = self::pregReplace($n, '', $format);
+ if (preg_match(self::NUMBER_REGEX, $m, $matches)) {
+ // There are placeholders for digits, so inject digits from the value into the mask
+ $value = self::formatStraightNumericValue($value, $format, $matches, $useThousands);
+ } elseif ($format !== NumberFormat::FORMAT_GENERAL) {
+ // Yes, I know that this is basically just a hack;
+ // if there's no placeholders for digits, just return the format mask "as is"
+ $value = self::makeString(str_replace('?', '', $format));
+ }
+ }
+
+ if (preg_match('/\[\$(.*)\]/u', $format, $m)) {
+ // Currency or Accounting
+ $currencyCode = $m[1];
+ [$currencyCode] = explode('-', $currencyCode);
+ if ($currencyCode == '') {
+ $currencyCode = StringHelper::getCurrencyCode();
+ }
+ $value = self::pregReplace('/\[\$([^\]]*)\]/u', $currencyCode, (string) $value);
+ }
+
+ return (string) $value;
+ }
+
+ /**
+ * @param array|string $value
+ */
+ private static function makeString($value): string
+ {
+ return is_array($value) ? '' : "$value";
+ }
+
+ private static function pregReplace(string $pattern, string $replacement, string $subject): string
+ {
+ return self::makeString(preg_replace($pattern, $replacement, $subject) ?? '');
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php
new file mode 100644
index 0000000..334c40d
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat/PercentageFormatter.php
@@ -0,0 +1,45 @@
+ IMAGETYPE_PNG,
+ IMAGETYPE_JPEG => IMAGETYPE_JPEG,
+ IMAGETYPE_PNG => IMAGETYPE_PNG,
+ IMAGETYPE_BMP => IMAGETYPE_PNG,
+ ];
+
+ /**
+ * Path.
+ *
+ * @var string
+ */
+ private $path;
+
+ /**
+ * Whether or not we are dealing with a URL.
+ *
+ * @var bool
+ */
+ private $isUrl;
+
+ /**
+ * Create a new Drawing.
+ */
+ public function __construct()
+ {
+ // Initialise values
+ $this->path = '';
+ $this->isUrl = false;
+
+ // Initialize parent
+ parent::__construct();
+ }
+
+ /**
+ * Get Filename.
+ *
+ * @return string
+ */
+ public function getFilename()
+ {
+ return basename($this->path);
+ }
+
+ /**
+ * Get indexed filename (using image index).
+ */
+ public function getIndexedFilename(): string
+ {
+ return md5($this->path) . '.' . $this->getExtension();
+ }
+
+ /**
+ * Get Extension.
+ *
+ * @return string
+ */
+ public function getExtension()
+ {
+ $exploded = explode('.', basename($this->path));
+
+ return $exploded[count($exploded) - 1];
+ }
+
+ /**
+ * Get full filepath to store drawing in zip archive.
+ *
+ * @return string
+ */
+ public function getMediaFilename()
+ {
+ if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) {
+ throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.');
+ }
+
+ return sprintf('image%d%s', $this->getImageIndex(), $this->getImageFileExtensionForSave());
+ }
+
+ /**
+ * Get Path.
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Set Path.
+ *
+ * @param string $path File path
+ * @param bool $verifyFile Verify file
+ * @param ZipArchive $zip Zip archive instance
+ *
+ * @return $this
+ */
+ public function setPath($path, $verifyFile = true, $zip = null)
+ {
+ if ($verifyFile) {
+ // Check if a URL has been passed. https://stackoverflow.com/a/2058596/1252979
+ if (filter_var($path, FILTER_VALIDATE_URL)) {
+ $this->path = $path;
+ // Implicit that it is a URL, rather store info than running check above on value in other places.
+ $this->isUrl = true;
+ $imageContents = file_get_contents($path);
+ $filePath = tempnam(sys_get_temp_dir(), 'Drawing');
+ if ($filePath) {
+ file_put_contents($filePath, $imageContents);
+ if (file_exists($filePath)) {
+ $this->setSizesAndType($filePath);
+ unlink($filePath);
+ }
+ }
+ } elseif (file_exists($path)) {
+ $this->path = $path;
+ $this->setSizesAndType($path);
+ } elseif ($zip instanceof ZipArchive) {
+ $zipPath = explode('#', $path)[1];
+ if ($zip->locateName($zipPath) !== false) {
+ $this->path = $path;
+ $this->setSizesAndType($path);
+ }
+ } else {
+ throw new PhpSpreadsheetException("File $path not found!");
+ }
+ } else {
+ $this->path = $path;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get isURL.
+ */
+ public function getIsURL(): bool
+ {
+ return $this->isUrl;
+ }
+
+ /**
+ * Set isURL.
+ *
+ * @return $this
+ */
+ public function setIsURL(bool $isUrl): self
+ {
+ $this->isUrl = $isUrl;
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->path .
+ parent::getHashCode() .
+ __CLASS__
+ );
+ }
+
+ /**
+ * Get Image Type for Save.
+ */
+ public function getImageTypeForSave(): int
+ {
+ if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) {
+ throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.');
+ }
+
+ return self::IMAGE_TYPES_CONVERTION_MAP[$this->type];
+ }
+
+ /**
+ * Get Image file extention for Save.
+ */
+ public function getImageFileExtensionForSave(bool $includeDot = true): string
+ {
+ if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) {
+ throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.');
+ }
+
+ $result = image_type_to_extension(self::IMAGE_TYPES_CONVERTION_MAP[$this->type], $includeDot);
+
+ return is_string($result) ? $result : '';
+ }
+
+ /**
+ * Get Image mime type.
+ */
+ public function getImageMimeType(): string
+ {
+ if (!array_key_exists($this->type, self::IMAGE_TYPES_CONVERTION_MAP)) {
+ throw new PhpSpreadsheetException('Unsupported image type in comment background. Supported types: PNG, JPEG, BMP, GIF.');
+ }
+
+ return image_type_to_mime_type(self::IMAGE_TYPES_CONVERTION_MAP[$this->type]);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php
new file mode 100644
index 0000000..c3504d8
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php
@@ -0,0 +1,490 @@
+
+ * Header/Footer Formatting Syntax taken from Office Open XML Part 4 - Markup Language Reference, page 1970:.
+ *
+ * There are a number of formatting codes that can be written inline with the actual header / footer text, which
+ * affect the formatting in the header or footer.
+ *
+ * Example: This example shows the text "Center Bold Header" on the first line (center section), and the date on
+ * the second line (center section).
+ * &CCenter &"-,Bold"Bold&"-,Regular"Header_x000A_&D
+ *
+ * General Rules:
+ * There is no required order in which these codes must appear.
+ *
+ * The first occurrence of the following codes turns the formatting ON, the second occurrence turns it OFF again:
+ * - strikethrough
+ * - superscript
+ * - subscript
+ * Superscript and subscript cannot both be ON at same time. Whichever comes first wins and the other is ignored,
+ * while the first is ON.
+ * &L - code for "left section" (there are three header / footer locations, "left", "center", and "right"). When
+ * two or more occurrences of this section marker exist, the contents from all markers are concatenated, in the
+ * order of appearance, and placed into the left section.
+ * &P - code for "current page #"
+ * &N - code for "total pages"
+ * &font size - code for "text font size", where font size is a font size in points.
+ * &K - code for "text font color"
+ * RGB Color is specified as RRGGBB
+ * Theme Color is specifed as TTSNN where TT is the theme color Id, S is either "+" or "-" of the tint/shade
+ * value, NN is the tint/shade value.
+ * &S - code for "text strikethrough" on / off
+ * &X - code for "text super script" on / off
+ * &Y - code for "text subscript" on / off
+ * &C - code for "center section". When two or more occurrences of this section marker exist, the contents
+ * from all markers are concatenated, in the order of appearance, and placed into the center section.
+ *
+ * &D - code for "date"
+ * &T - code for "time"
+ * &G - code for "picture as background"
+ * &U - code for "text single underline"
+ * &E - code for "double underline"
+ * &R - code for "right section". When two or more occurrences of this section marker exist, the contents
+ * from all markers are concatenated, in the order of appearance, and placed into the right section.
+ * &Z - code for "this workbook's file path"
+ * &F - code for "this workbook's file name"
+ * &A - code for "sheet tab name"
+ * &+ - code for add to page #.
+ * &- - code for subtract from page #.
+ * &"font name,font type" - code for "text font name" and "text font type", where font name and font type
+ * are strings specifying the name and type of the font, separated by a comma. When a hyphen appears in font
+ * name, it means "none specified". Both of font name and font type can be localized values.
+ * &"-,Bold" - code for "bold font style"
+ * &B - also means "bold font style".
+ * &"-,Regular" - code for "regular font style"
+ * &"-,Italic" - code for "italic font style"
+ * &I - also means "italic font style"
+ * &"-,Bold Italic" code for "bold italic font style"
+ * &O - code for "outline style"
+ * &H - code for "shadow style"
+ *
+ */
+class HeaderFooter
+{
+ // Header/footer image location
+ const IMAGE_HEADER_LEFT = 'LH';
+ const IMAGE_HEADER_CENTER = 'CH';
+ const IMAGE_HEADER_RIGHT = 'RH';
+ const IMAGE_FOOTER_LEFT = 'LF';
+ const IMAGE_FOOTER_CENTER = 'CF';
+ const IMAGE_FOOTER_RIGHT = 'RF';
+
+ /**
+ * OddHeader.
+ *
+ * @var string
+ */
+ private $oddHeader = '';
+
+ /**
+ * OddFooter.
+ *
+ * @var string
+ */
+ private $oddFooter = '';
+
+ /**
+ * EvenHeader.
+ *
+ * @var string
+ */
+ private $evenHeader = '';
+
+ /**
+ * EvenFooter.
+ *
+ * @var string
+ */
+ private $evenFooter = '';
+
+ /**
+ * FirstHeader.
+ *
+ * @var string
+ */
+ private $firstHeader = '';
+
+ /**
+ * FirstFooter.
+ *
+ * @var string
+ */
+ private $firstFooter = '';
+
+ /**
+ * Different header for Odd/Even, defaults to false.
+ *
+ * @var bool
+ */
+ private $differentOddEven = false;
+
+ /**
+ * Different header for first page, defaults to false.
+ *
+ * @var bool
+ */
+ private $differentFirst = false;
+
+ /**
+ * Scale with document, defaults to true.
+ *
+ * @var bool
+ */
+ private $scaleWithDocument = true;
+
+ /**
+ * Align with margins, defaults to true.
+ *
+ * @var bool
+ */
+ private $alignWithMargins = true;
+
+ /**
+ * Header/footer images.
+ *
+ * @var HeaderFooterDrawing[]
+ */
+ private $headerFooterImages = [];
+
+ /**
+ * Create a new HeaderFooter.
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * Get OddHeader.
+ *
+ * @return string
+ */
+ public function getOddHeader()
+ {
+ return $this->oddHeader;
+ }
+
+ /**
+ * Set OddHeader.
+ *
+ * @param string $oddHeader
+ *
+ * @return $this
+ */
+ public function setOddHeader($oddHeader)
+ {
+ $this->oddHeader = $oddHeader;
+
+ return $this;
+ }
+
+ /**
+ * Get OddFooter.
+ *
+ * @return string
+ */
+ public function getOddFooter()
+ {
+ return $this->oddFooter;
+ }
+
+ /**
+ * Set OddFooter.
+ *
+ * @param string $oddFooter
+ *
+ * @return $this
+ */
+ public function setOddFooter($oddFooter)
+ {
+ $this->oddFooter = $oddFooter;
+
+ return $this;
+ }
+
+ /**
+ * Get EvenHeader.
+ *
+ * @return string
+ */
+ public function getEvenHeader()
+ {
+ return $this->evenHeader;
+ }
+
+ /**
+ * Set EvenHeader.
+ *
+ * @param string $eventHeader
+ *
+ * @return $this
+ */
+ public function setEvenHeader($eventHeader)
+ {
+ $this->evenHeader = $eventHeader;
+
+ return $this;
+ }
+
+ /**
+ * Get EvenFooter.
+ *
+ * @return string
+ */
+ public function getEvenFooter()
+ {
+ return $this->evenFooter;
+ }
+
+ /**
+ * Set EvenFooter.
+ *
+ * @param string $evenFooter
+ *
+ * @return $this
+ */
+ public function setEvenFooter($evenFooter)
+ {
+ $this->evenFooter = $evenFooter;
+
+ return $this;
+ }
+
+ /**
+ * Get FirstHeader.
+ *
+ * @return string
+ */
+ public function getFirstHeader()
+ {
+ return $this->firstHeader;
+ }
+
+ /**
+ * Set FirstHeader.
+ *
+ * @param string $firstHeader
+ *
+ * @return $this
+ */
+ public function setFirstHeader($firstHeader)
+ {
+ $this->firstHeader = $firstHeader;
+
+ return $this;
+ }
+
+ /**
+ * Get FirstFooter.
+ *
+ * @return string
+ */
+ public function getFirstFooter()
+ {
+ return $this->firstFooter;
+ }
+
+ /**
+ * Set FirstFooter.
+ *
+ * @param string $firstFooter
+ *
+ * @return $this
+ */
+ public function setFirstFooter($firstFooter)
+ {
+ $this->firstFooter = $firstFooter;
+
+ return $this;
+ }
+
+ /**
+ * Get DifferentOddEven.
+ *
+ * @return bool
+ */
+ public function getDifferentOddEven()
+ {
+ return $this->differentOddEven;
+ }
+
+ /**
+ * Set DifferentOddEven.
+ *
+ * @param bool $differentOddEvent
+ *
+ * @return $this
+ */
+ public function setDifferentOddEven($differentOddEvent)
+ {
+ $this->differentOddEven = $differentOddEvent;
+
+ return $this;
+ }
+
+ /**
+ * Get DifferentFirst.
+ *
+ * @return bool
+ */
+ public function getDifferentFirst()
+ {
+ return $this->differentFirst;
+ }
+
+ /**
+ * Set DifferentFirst.
+ *
+ * @param bool $differentFirst
+ *
+ * @return $this
+ */
+ public function setDifferentFirst($differentFirst)
+ {
+ $this->differentFirst = $differentFirst;
+
+ return $this;
+ }
+
+ /**
+ * Get ScaleWithDocument.
+ *
+ * @return bool
+ */
+ public function getScaleWithDocument()
+ {
+ return $this->scaleWithDocument;
+ }
+
+ /**
+ * Set ScaleWithDocument.
+ *
+ * @param bool $scaleWithDocument
+ *
+ * @return $this
+ */
+ public function setScaleWithDocument($scaleWithDocument)
+ {
+ $this->scaleWithDocument = $scaleWithDocument;
+
+ return $this;
+ }
+
+ /**
+ * Get AlignWithMargins.
+ *
+ * @return bool
+ */
+ public function getAlignWithMargins()
+ {
+ return $this->alignWithMargins;
+ }
+
+ /**
+ * Set AlignWithMargins.
+ *
+ * @param bool $alignWithMargins
+ *
+ * @return $this
+ */
+ public function setAlignWithMargins($alignWithMargins)
+ {
+ $this->alignWithMargins = $alignWithMargins;
+
+ return $this;
+ }
+
+ /**
+ * Add header/footer image.
+ *
+ * @param string $location
+ *
+ * @return $this
+ */
+ public function addImage(HeaderFooterDrawing $image, $location = self::IMAGE_HEADER_LEFT)
+ {
+ $this->headerFooterImages[$location] = $image;
+
+ return $this;
+ }
+
+ /**
+ * Remove header/footer image.
+ *
+ * @param string $location
+ *
+ * @return $this
+ */
+ public function removeImage($location = self::IMAGE_HEADER_LEFT)
+ {
+ if (isset($this->headerFooterImages[$location])) {
+ unset($this->headerFooterImages[$location]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set header/footer images.
+ *
+ * @param HeaderFooterDrawing[] $images
+ *
+ * @return $this
+ */
+ public function setImages(array $images)
+ {
+ $this->headerFooterImages = $images;
+
+ return $this;
+ }
+
+ /**
+ * Get header/footer images.
+ *
+ * @return HeaderFooterDrawing[]
+ */
+ public function getImages()
+ {
+ // Sort array
+ $images = [];
+ if (isset($this->headerFooterImages[self::IMAGE_HEADER_LEFT])) {
+ $images[self::IMAGE_HEADER_LEFT] = $this->headerFooterImages[self::IMAGE_HEADER_LEFT];
+ }
+ if (isset($this->headerFooterImages[self::IMAGE_HEADER_CENTER])) {
+ $images[self::IMAGE_HEADER_CENTER] = $this->headerFooterImages[self::IMAGE_HEADER_CENTER];
+ }
+ if (isset($this->headerFooterImages[self::IMAGE_HEADER_RIGHT])) {
+ $images[self::IMAGE_HEADER_RIGHT] = $this->headerFooterImages[self::IMAGE_HEADER_RIGHT];
+ }
+ if (isset($this->headerFooterImages[self::IMAGE_FOOTER_LEFT])) {
+ $images[self::IMAGE_FOOTER_LEFT] = $this->headerFooterImages[self::IMAGE_FOOTER_LEFT];
+ }
+ if (isset($this->headerFooterImages[self::IMAGE_FOOTER_CENTER])) {
+ $images[self::IMAGE_FOOTER_CENTER] = $this->headerFooterImages[self::IMAGE_FOOTER_CENTER];
+ }
+ if (isset($this->headerFooterImages[self::IMAGE_FOOTER_RIGHT])) {
+ $images[self::IMAGE_FOOTER_RIGHT] = $this->headerFooterImages[self::IMAGE_FOOTER_RIGHT];
+ }
+ $this->headerFooterImages = $images;
+
+ return $this->headerFooterImages;
+ }
+
+ /**
+ * 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/HeaderFooterDrawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php
new file mode 100644
index 0000000..b42c732
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooterDrawing.php
@@ -0,0 +1,24 @@
+getPath() .
+ $this->name .
+ $this->offsetX .
+ $this->offsetY .
+ $this->width .
+ $this->height .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php
new file mode 100644
index 0000000..dc08204
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php
@@ -0,0 +1,74 @@
+
+ */
+class Iterator implements \Iterator
+{
+ /**
+ * Spreadsheet to iterate.
+ *
+ * @var Spreadsheet
+ */
+ private $subject;
+
+ /**
+ * Current iterator position.
+ *
+ * @var int
+ */
+ private $position = 0;
+
+ /**
+ * Create a new worksheet iterator.
+ */
+ public function __construct(Spreadsheet $subject)
+ {
+ // Set subject
+ $this->subject = $subject;
+ }
+
+ /**
+ * Rewind iterator.
+ */
+ public function rewind(): void
+ {
+ $this->position = 0;
+ }
+
+ /**
+ * Current Worksheet.
+ */
+ public function current(): Worksheet
+ {
+ return $this->subject->getSheet($this->position);
+ }
+
+ /**
+ * Current key.
+ */
+ public function key(): int
+ {
+ return $this->position;
+ }
+
+ /**
+ * Next value.
+ */
+ public function next(): void
+ {
+ ++$this->position;
+ }
+
+ /**
+ * Are there more Worksheet instances available?
+ */
+ public function valid(): bool
+ {
+ return $this->position < $this->subject->getSheetCount() && $this->position >= 0;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php
new file mode 100644
index 0000000..91acbb7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php
@@ -0,0 +1,230 @@
+renderingFunction = self::RENDERING_DEFAULT;
+ $this->mimeType = self::MIMETYPE_DEFAULT;
+ $this->uniqueName = md5(mt_rand(0, 9999) . time() . mt_rand(0, 9999));
+
+ // Initialize parent
+ parent::__construct();
+ }
+
+ public function __destruct()
+ {
+ if ($this->imageResource) {
+ imagedestroy($this->imageResource);
+ $this->imageResource = null;
+ }
+ }
+
+ public function __clone()
+ {
+ parent::__clone();
+ $this->cloneResource();
+ }
+
+ private function cloneResource(): void
+ {
+ if (!$this->imageResource) {
+ return;
+ }
+
+ $width = imagesx($this->imageResource);
+ $height = imagesy($this->imageResource);
+
+ if (imageistruecolor($this->imageResource)) {
+ $clone = imagecreatetruecolor($width, $height);
+ if (!$clone) {
+ throw new Exception('Could not clone image resource');
+ }
+
+ imagealphablending($clone, false);
+ imagesavealpha($clone, true);
+ } else {
+ $clone = imagecreate($width, $height);
+ if (!$clone) {
+ throw new Exception('Could not clone image resource');
+ }
+
+ // If the image has transparency...
+ $transparent = imagecolortransparent($this->imageResource);
+ if ($transparent >= 0) {
+ $rgb = imagecolorsforindex($this->imageResource, $transparent);
+ if (empty($rgb)) {
+ throw new Exception('Could not get image colors');
+ }
+
+ imagesavealpha($clone, true);
+ $color = imagecolorallocatealpha($clone, $rgb['red'], $rgb['green'], $rgb['blue'], $rgb['alpha']);
+ if ($color === false) {
+ throw new Exception('Could not get image alpha color');
+ }
+
+ imagefill($clone, 0, 0, $color);
+ }
+ }
+
+ //Create the Clone!!
+ imagecopy($clone, $this->imageResource, 0, 0, 0, 0, $width, $height);
+
+ $this->imageResource = $clone;
+ }
+
+ /**
+ * Get image resource.
+ *
+ * @return null|GdImage|resource
+ */
+ public function getImageResource()
+ {
+ return $this->imageResource;
+ }
+
+ /**
+ * Set image resource.
+ *
+ * @param GdImage|resource $value
+ *
+ * @return $this
+ */
+ public function setImageResource($value)
+ {
+ $this->imageResource = $value;
+
+ if ($this->imageResource !== null) {
+ // Get width/height
+ $this->width = imagesx($this->imageResource);
+ $this->height = imagesy($this->imageResource);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get rendering function.
+ *
+ * @return string
+ */
+ public function getRenderingFunction()
+ {
+ return $this->renderingFunction;
+ }
+
+ /**
+ * Set rendering function.
+ *
+ * @param string $value see self::RENDERING_*
+ *
+ * @return $this
+ */
+ public function setRenderingFunction($value)
+ {
+ $this->renderingFunction = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get mime type.
+ *
+ * @return string
+ */
+ public function getMimeType()
+ {
+ return $this->mimeType;
+ }
+
+ /**
+ * Set mime type.
+ *
+ * @param string $value see self::MIMETYPE_*
+ *
+ * @return $this
+ */
+ public function setMimeType($value)
+ {
+ $this->mimeType = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get indexed filename (using image index).
+ */
+ public function getIndexedFilename(): string
+ {
+ $extension = strtolower($this->getMimeType());
+ $extension = explode('/', $extension);
+ $extension = $extension[1];
+
+ return $this->uniqueName . $this->getImageIndex() . '.' . $extension;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->renderingFunction .
+ $this->mimeType .
+ $this->uniqueName .
+ parent::getHashCode() .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php
new file mode 100644
index 0000000..34e1145
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php
@@ -0,0 +1,244 @@
+left;
+ }
+
+ /**
+ * Set Left.
+ *
+ * @param float $left
+ *
+ * @return $this
+ */
+ public function setLeft($left)
+ {
+ $this->left = $left;
+
+ return $this;
+ }
+
+ /**
+ * Get Right.
+ *
+ * @return float
+ */
+ public function getRight()
+ {
+ return $this->right;
+ }
+
+ /**
+ * Set Right.
+ *
+ * @param float $right
+ *
+ * @return $this
+ */
+ public function setRight($right)
+ {
+ $this->right = $right;
+
+ return $this;
+ }
+
+ /**
+ * Get Top.
+ *
+ * @return float
+ */
+ public function getTop()
+ {
+ return $this->top;
+ }
+
+ /**
+ * Set Top.
+ *
+ * @param float $top
+ *
+ * @return $this
+ */
+ public function setTop($top)
+ {
+ $this->top = $top;
+
+ return $this;
+ }
+
+ /**
+ * Get Bottom.
+ *
+ * @return float
+ */
+ public function getBottom()
+ {
+ return $this->bottom;
+ }
+
+ /**
+ * Set Bottom.
+ *
+ * @param float $bottom
+ *
+ * @return $this
+ */
+ public function setBottom($bottom)
+ {
+ $this->bottom = $bottom;
+
+ return $this;
+ }
+
+ /**
+ * Get Header.
+ *
+ * @return float
+ */
+ public function getHeader()
+ {
+ return $this->header;
+ }
+
+ /**
+ * Set Header.
+ *
+ * @param float $header
+ *
+ * @return $this
+ */
+ public function setHeader($header)
+ {
+ $this->header = $header;
+
+ return $this;
+ }
+
+ /**
+ * Get Footer.
+ *
+ * @return float
+ */
+ public function getFooter()
+ {
+ return $this->footer;
+ }
+
+ /**
+ * Set Footer.
+ *
+ * @param float $footer
+ *
+ * @return $this
+ */
+ public function setFooter($footer)
+ {
+ $this->footer = $footer;
+
+ 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;
+ }
+
+ public static function toCentimeters(float $value): float
+ {
+ return $value * 2.54;
+ }
+
+ public static function fromMillimeters(float $value): float
+ {
+ return $value / 25.4;
+ }
+
+ public static function toMillimeters(float $value): float
+ {
+ return $value * 25.4;
+ }
+
+ public static function fromPoints(float $value): float
+ {
+ return $value / 72;
+ }
+
+ public static function toPoints(float $value): float
+ {
+ return $value * 72;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php
new file mode 100644
index 0000000..9640782
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php
@@ -0,0 +1,902 @@
+
+ * Paper size taken from Office Open XML Part 4 - Markup Language Reference, page 1988:.
+ *
+ * 1 = Letter paper (8.5 in. by 11 in.)
+ * 2 = Letter small paper (8.5 in. by 11 in.)
+ * 3 = Tabloid paper (11 in. by 17 in.)
+ * 4 = Ledger paper (17 in. by 11 in.)
+ * 5 = Legal paper (8.5 in. by 14 in.)
+ * 6 = Statement paper (5.5 in. by 8.5 in.)
+ * 7 = Executive paper (7.25 in. by 10.5 in.)
+ * 8 = A3 paper (297 mm by 420 mm)
+ * 9 = A4 paper (210 mm by 297 mm)
+ * 10 = A4 small paper (210 mm by 297 mm)
+ * 11 = A5 paper (148 mm by 210 mm)
+ * 12 = B4 paper (250 mm by 353 mm)
+ * 13 = B5 paper (176 mm by 250 mm)
+ * 14 = Folio paper (8.5 in. by 13 in.)
+ * 15 = Quarto paper (215 mm by 275 mm)
+ * 16 = Standard paper (10 in. by 14 in.)
+ * 17 = Standard paper (11 in. by 17 in.)
+ * 18 = Note paper (8.5 in. by 11 in.)
+ * 19 = #9 envelope (3.875 in. by 8.875 in.)
+ * 20 = #10 envelope (4.125 in. by 9.5 in.)
+ * 21 = #11 envelope (4.5 in. by 10.375 in.)
+ * 22 = #12 envelope (4.75 in. by 11 in.)
+ * 23 = #14 envelope (5 in. by 11.5 in.)
+ * 24 = C paper (17 in. by 22 in.)
+ * 25 = D paper (22 in. by 34 in.)
+ * 26 = E paper (34 in. by 44 in.)
+ * 27 = DL envelope (110 mm by 220 mm)
+ * 28 = C5 envelope (162 mm by 229 mm)
+ * 29 = C3 envelope (324 mm by 458 mm)
+ * 30 = C4 envelope (229 mm by 324 mm)
+ * 31 = C6 envelope (114 mm by 162 mm)
+ * 32 = C65 envelope (114 mm by 229 mm)
+ * 33 = B4 envelope (250 mm by 353 mm)
+ * 34 = B5 envelope (176 mm by 250 mm)
+ * 35 = B6 envelope (176 mm by 125 mm)
+ * 36 = Italy envelope (110 mm by 230 mm)
+ * 37 = Monarch envelope (3.875 in. by 7.5 in.).
+ * 38 = 6 3/4 envelope (3.625 in. by 6.5 in.)
+ * 39 = US standard fanfold (14.875 in. by 11 in.)
+ * 40 = German standard fanfold (8.5 in. by 12 in.)
+ * 41 = German legal fanfold (8.5 in. by 13 in.)
+ * 42 = ISO B4 (250 mm by 353 mm)
+ * 43 = Japanese double postcard (200 mm by 148 mm)
+ * 44 = Standard paper (9 in. by 11 in.)
+ * 45 = Standard paper (10 in. by 11 in.)
+ * 46 = Standard paper (15 in. by 11 in.)
+ * 47 = Invite envelope (220 mm by 220 mm)
+ * 50 = Letter extra paper (9.275 in. by 12 in.)
+ * 51 = Legal extra paper (9.275 in. by 15 in.)
+ * 52 = Tabloid extra paper (11.69 in. by 18 in.)
+ * 53 = A4 extra paper (236 mm by 322 mm)
+ * 54 = Letter transverse paper (8.275 in. by 11 in.)
+ * 55 = A4 transverse paper (210 mm by 297 mm)
+ * 56 = Letter extra transverse paper (9.275 in. by 12 in.)
+ * 57 = SuperA/SuperA/A4 paper (227 mm by 356 mm)
+ * 58 = SuperB/SuperB/A3 paper (305 mm by 487 mm)
+ * 59 = Letter plus paper (8.5 in. by 12.69 in.)
+ * 60 = A4 plus paper (210 mm by 330 mm)
+ * 61 = A5 transverse paper (148 mm by 210 mm)
+ * 62 = JIS B5 transverse paper (182 mm by 257 mm)
+ * 63 = A3 extra paper (322 mm by 445 mm)
+ * 64 = A5 extra paper (174 mm by 235 mm)
+ * 65 = ISO B5 extra paper (201 mm by 276 mm)
+ * 66 = A2 paper (420 mm by 594 mm)
+ * 67 = A3 transverse paper (297 mm by 420 mm)
+ * 68 = A3 extra transverse paper (322 mm by 445 mm)
+ *
+ */
+class PageSetup
+{
+ // Paper size
+ const PAPERSIZE_LETTER = 1;
+ const PAPERSIZE_LETTER_SMALL = 2;
+ const PAPERSIZE_TABLOID = 3;
+ const PAPERSIZE_LEDGER = 4;
+ const PAPERSIZE_LEGAL = 5;
+ const PAPERSIZE_STATEMENT = 6;
+ const PAPERSIZE_EXECUTIVE = 7;
+ const PAPERSIZE_A3 = 8;
+ const PAPERSIZE_A4 = 9;
+ const PAPERSIZE_A4_SMALL = 10;
+ const PAPERSIZE_A5 = 11;
+ const PAPERSIZE_B4 = 12;
+ const PAPERSIZE_B5 = 13;
+ const PAPERSIZE_FOLIO = 14;
+ const PAPERSIZE_QUARTO = 15;
+ const PAPERSIZE_STANDARD_1 = 16;
+ const PAPERSIZE_STANDARD_2 = 17;
+ const PAPERSIZE_NOTE = 18;
+ const PAPERSIZE_NO9_ENVELOPE = 19;
+ const PAPERSIZE_NO10_ENVELOPE = 20;
+ const PAPERSIZE_NO11_ENVELOPE = 21;
+ const PAPERSIZE_NO12_ENVELOPE = 22;
+ const PAPERSIZE_NO14_ENVELOPE = 23;
+ const PAPERSIZE_C = 24;
+ const PAPERSIZE_D = 25;
+ const PAPERSIZE_E = 26;
+ const PAPERSIZE_DL_ENVELOPE = 27;
+ const PAPERSIZE_C5_ENVELOPE = 28;
+ const PAPERSIZE_C3_ENVELOPE = 29;
+ const PAPERSIZE_C4_ENVELOPE = 30;
+ const PAPERSIZE_C6_ENVELOPE = 31;
+ const PAPERSIZE_C65_ENVELOPE = 32;
+ const PAPERSIZE_B4_ENVELOPE = 33;
+ const PAPERSIZE_B5_ENVELOPE = 34;
+ const PAPERSIZE_B6_ENVELOPE = 35;
+ const PAPERSIZE_ITALY_ENVELOPE = 36;
+ const PAPERSIZE_MONARCH_ENVELOPE = 37;
+ const PAPERSIZE_6_3_4_ENVELOPE = 38;
+ const PAPERSIZE_US_STANDARD_FANFOLD = 39;
+ const PAPERSIZE_GERMAN_STANDARD_FANFOLD = 40;
+ const PAPERSIZE_GERMAN_LEGAL_FANFOLD = 41;
+ const PAPERSIZE_ISO_B4 = 42;
+ const PAPERSIZE_JAPANESE_DOUBLE_POSTCARD = 43;
+ const PAPERSIZE_STANDARD_PAPER_1 = 44;
+ const PAPERSIZE_STANDARD_PAPER_2 = 45;
+ const PAPERSIZE_STANDARD_PAPER_3 = 46;
+ const PAPERSIZE_INVITE_ENVELOPE = 47;
+ const PAPERSIZE_LETTER_EXTRA_PAPER = 48;
+ const PAPERSIZE_LEGAL_EXTRA_PAPER = 49;
+ const PAPERSIZE_TABLOID_EXTRA_PAPER = 50;
+ const PAPERSIZE_A4_EXTRA_PAPER = 51;
+ const PAPERSIZE_LETTER_TRANSVERSE_PAPER = 52;
+ const PAPERSIZE_A4_TRANSVERSE_PAPER = 53;
+ const PAPERSIZE_LETTER_EXTRA_TRANSVERSE_PAPER = 54;
+ const PAPERSIZE_SUPERA_SUPERA_A4_PAPER = 55;
+ const PAPERSIZE_SUPERB_SUPERB_A3_PAPER = 56;
+ const PAPERSIZE_LETTER_PLUS_PAPER = 57;
+ const PAPERSIZE_A4_PLUS_PAPER = 58;
+ const PAPERSIZE_A5_TRANSVERSE_PAPER = 59;
+ const PAPERSIZE_JIS_B5_TRANSVERSE_PAPER = 60;
+ const PAPERSIZE_A3_EXTRA_PAPER = 61;
+ const PAPERSIZE_A5_EXTRA_PAPER = 62;
+ const PAPERSIZE_ISO_B5_EXTRA_PAPER = 63;
+ const PAPERSIZE_A2_PAPER = 64;
+ const PAPERSIZE_A3_TRANSVERSE_PAPER = 65;
+ const PAPERSIZE_A3_EXTRA_TRANSVERSE_PAPER = 66;
+
+ // Page orientation
+ const ORIENTATION_DEFAULT = 'default';
+ const ORIENTATION_LANDSCAPE = 'landscape';
+ const ORIENTATION_PORTRAIT = 'portrait';
+
+ // Print Range Set Method
+ const SETPRINTRANGE_OVERWRITE = 'O';
+ const SETPRINTRANGE_INSERT = 'I';
+
+ const PAGEORDER_OVER_THEN_DOWN = 'overThenDown';
+ const PAGEORDER_DOWN_THEN_OVER = 'downThenOver';
+
+ /**
+ * Paper size default.
+ *
+ * @var int
+ */
+ private static $paperSizeDefault = self::PAPERSIZE_LETTER;
+
+ /**
+ * Paper size.
+ *
+ * @var ?int
+ */
+ private $paperSize;
+
+ /**
+ * Orientation default.
+ *
+ * @var string
+ */
+ private static $orientationDefault = self::ORIENTATION_DEFAULT;
+
+ /**
+ * Orientation.
+ *
+ * @var string
+ */
+ private $orientation;
+
+ /**
+ * Scale (Print Scale).
+ *
+ * Print scaling. Valid values range from 10 to 400
+ * This setting is overridden when fitToWidth and/or fitToHeight are in use
+ *
+ * @var null|int
+ */
+ private $scale = 100;
+
+ /**
+ * Fit To Page
+ * Whether scale or fitToWith / fitToHeight applies.
+ *
+ * @var bool
+ */
+ private $fitToPage = false;
+
+ /**
+ * Fit To Height
+ * Number of vertical pages to fit on.
+ *
+ * @var null|int
+ */
+ private $fitToHeight = 1;
+
+ /**
+ * Fit To Width
+ * Number of horizontal pages to fit on.
+ *
+ * @var null|int
+ */
+ private $fitToWidth = 1;
+
+ /**
+ * Columns to repeat at left.
+ *
+ * @var array Containing start column and end column, empty array if option unset
+ */
+ private $columnsToRepeatAtLeft = ['', ''];
+
+ /**
+ * Rows to repeat at top.
+ *
+ * @var array Containing start row number and end row number, empty array if option unset
+ */
+ private $rowsToRepeatAtTop = [0, 0];
+
+ /**
+ * Center page horizontally.
+ *
+ * @var bool
+ */
+ private $horizontalCentered = false;
+
+ /**
+ * Center page vertically.
+ *
+ * @var bool
+ */
+ private $verticalCentered = false;
+
+ /**
+ * Print area.
+ *
+ * @var null|string
+ */
+ private $printArea;
+
+ /**
+ * First page number.
+ *
+ * @var int
+ */
+ private $firstPageNumber;
+
+ private $pageOrder = self::PAGEORDER_DOWN_THEN_OVER;
+
+ /**
+ * Create a new PageSetup.
+ */
+ public function __construct()
+ {
+ $this->orientation = self::$orientationDefault;
+ }
+
+ /**
+ * Get Paper Size.
+ *
+ * @return int
+ */
+ public function getPaperSize()
+ {
+ return $this->paperSize ?? self::$paperSizeDefault;
+ }
+
+ /**
+ * Set Paper Size.
+ *
+ * @param int $paperSize see self::PAPERSIZE_*
+ *
+ * @return $this
+ */
+ public function setPaperSize($paperSize)
+ {
+ $this->paperSize = $paperSize;
+
+ return $this;
+ }
+
+ /**
+ * Get Paper Size default.
+ */
+ public static function getPaperSizeDefault(): int
+ {
+ return self::$paperSizeDefault;
+ }
+
+ /**
+ * Set Paper Size Default.
+ */
+ public static function setPaperSizeDefault(int $paperSize): void
+ {
+ self::$paperSizeDefault = $paperSize;
+ }
+
+ /**
+ * Get Orientation.
+ *
+ * @return string
+ */
+ public function getOrientation()
+ {
+ return $this->orientation;
+ }
+
+ /**
+ * Set Orientation.
+ *
+ * @param string $orientation see self::ORIENTATION_*
+ *
+ * @return $this
+ */
+ public function setOrientation($orientation)
+ {
+ if ($orientation === self::ORIENTATION_LANDSCAPE || $orientation === self::ORIENTATION_PORTRAIT || $orientation === self::ORIENTATION_DEFAULT) {
+ $this->orientation = $orientation;
+ }
+
+ return $this;
+ }
+
+ public static function getOrientationDefault(): string
+ {
+ return self::$orientationDefault;
+ }
+
+ public static function setOrientationDefault(string $orientation): void
+ {
+ if ($orientation === self::ORIENTATION_LANDSCAPE || $orientation === self::ORIENTATION_PORTRAIT || $orientation === self::ORIENTATION_DEFAULT) {
+ self::$orientationDefault = $orientation;
+ }
+ }
+
+ /**
+ * Get Scale.
+ *
+ * @return null|int
+ */
+ public function getScale()
+ {
+ return $this->scale;
+ }
+
+ /**
+ * Set Scale.
+ * Print scaling. Valid values range from 10 to 400
+ * This setting is overridden when fitToWidth and/or fitToHeight are in use.
+ *
+ * @param null|int $scale
+ * @param bool $update Update fitToPage so scaling applies rather than fitToHeight / fitToWidth
+ *
+ * @return $this
+ */
+ public function setScale($scale, $update = true)
+ {
+ // Microsoft Office Excel 2007 only allows setting a scale between 10 and 400 via the user interface,
+ // but it is apparently still able to handle any scale >= 0, where 0 results in 100
+ if (($scale >= 0) || $scale === null) {
+ $this->scale = $scale;
+ if ($update) {
+ $this->fitToPage = false;
+ }
+ } else {
+ throw new PhpSpreadsheetException('Scale must not be negative');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Fit To Page.
+ *
+ * @return bool
+ */
+ public function getFitToPage()
+ {
+ return $this->fitToPage;
+ }
+
+ /**
+ * Set Fit To Page.
+ *
+ * @param bool $fitToPage
+ *
+ * @return $this
+ */
+ public function setFitToPage($fitToPage)
+ {
+ $this->fitToPage = $fitToPage;
+
+ return $this;
+ }
+
+ /**
+ * Get Fit To Height.
+ *
+ * @return null|int
+ */
+ public function getFitToHeight()
+ {
+ return $this->fitToHeight;
+ }
+
+ /**
+ * Set Fit To Height.
+ *
+ * @param null|int $fitToHeight
+ * @param bool $update Update fitToPage so it applies rather than scaling
+ *
+ * @return $this
+ */
+ public function setFitToHeight($fitToHeight, $update = true)
+ {
+ $this->fitToHeight = $fitToHeight;
+ if ($update) {
+ $this->fitToPage = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Fit To Width.
+ *
+ * @return null|int
+ */
+ public function getFitToWidth()
+ {
+ return $this->fitToWidth;
+ }
+
+ /**
+ * Set Fit To Width.
+ *
+ * @param null|int $value
+ * @param bool $update Update fitToPage so it applies rather than scaling
+ *
+ * @return $this
+ */
+ public function setFitToWidth($value, $update = true)
+ {
+ $this->fitToWidth = $value;
+ if ($update) {
+ $this->fitToPage = true;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Is Columns to repeat at left set?
+ *
+ * @return bool
+ */
+ public function isColumnsToRepeatAtLeftSet()
+ {
+ if (is_array($this->columnsToRepeatAtLeft)) {
+ if ($this->columnsToRepeatAtLeft[0] != '' && $this->columnsToRepeatAtLeft[1] != '') {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get Columns to repeat at left.
+ *
+ * @return array Containing start column and end column, empty array if option unset
+ */
+ public function getColumnsToRepeatAtLeft()
+ {
+ return $this->columnsToRepeatAtLeft;
+ }
+
+ /**
+ * Set Columns to repeat at left.
+ *
+ * @param array $columnsToRepeatAtLeft Containing start column and end column, empty array if option unset
+ *
+ * @return $this
+ */
+ public function setColumnsToRepeatAtLeft(array $columnsToRepeatAtLeft)
+ {
+ $this->columnsToRepeatAtLeft = $columnsToRepeatAtLeft;
+
+ return $this;
+ }
+
+ /**
+ * Set Columns to repeat at left by start and end.
+ *
+ * @param string $start eg: 'A'
+ * @param string $end eg: 'B'
+ *
+ * @return $this
+ */
+ public function setColumnsToRepeatAtLeftByStartAndEnd($start, $end)
+ {
+ $this->columnsToRepeatAtLeft = [$start, $end];
+
+ return $this;
+ }
+
+ /**
+ * Is Rows to repeat at top set?
+ *
+ * @return bool
+ */
+ public function isRowsToRepeatAtTopSet()
+ {
+ if (is_array($this->rowsToRepeatAtTop)) {
+ if ($this->rowsToRepeatAtTop[0] != 0 && $this->rowsToRepeatAtTop[1] != 0) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get Rows to repeat at top.
+ *
+ * @return array Containing start column and end column, empty array if option unset
+ */
+ public function getRowsToRepeatAtTop()
+ {
+ return $this->rowsToRepeatAtTop;
+ }
+
+ /**
+ * Set Rows to repeat at top.
+ *
+ * @param array $rowsToRepeatAtTop Containing start column and end column, empty array if option unset
+ *
+ * @return $this
+ */
+ public function setRowsToRepeatAtTop(array $rowsToRepeatAtTop)
+ {
+ $this->rowsToRepeatAtTop = $rowsToRepeatAtTop;
+
+ return $this;
+ }
+
+ /**
+ * Set Rows to repeat at top by start and end.
+ *
+ * @param int $start eg: 1
+ * @param int $end eg: 1
+ *
+ * @return $this
+ */
+ public function setRowsToRepeatAtTopByStartAndEnd($start, $end)
+ {
+ $this->rowsToRepeatAtTop = [$start, $end];
+
+ return $this;
+ }
+
+ /**
+ * Get center page horizontally.
+ *
+ * @return bool
+ */
+ public function getHorizontalCentered()
+ {
+ return $this->horizontalCentered;
+ }
+
+ /**
+ * Set center page horizontally.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setHorizontalCentered($value)
+ {
+ $this->horizontalCentered = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get center page vertically.
+ *
+ * @return bool
+ */
+ public function getVerticalCentered()
+ {
+ return $this->verticalCentered;
+ }
+
+ /**
+ * Set center page vertically.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setVerticalCentered($value)
+ {
+ $this->verticalCentered = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get print area.
+ *
+ * @param int $index Identifier for a specific print area range if several ranges have been set
+ * Default behaviour, or a index value of 0, will return all ranges as a comma-separated string
+ * Otherwise, the specific range identified by the value of $index will be returned
+ * Print areas are numbered from 1
+ *
+ * @return string
+ */
+ public function getPrintArea($index = 0)
+ {
+ if ($index == 0) {
+ return $this->printArea;
+ }
+ $printAreas = explode(',', $this->printArea);
+ if (isset($printAreas[$index - 1])) {
+ return $printAreas[$index - 1];
+ }
+
+ throw new PhpSpreadsheetException('Requested Print Area does not exist');
+ }
+
+ /**
+ * Is print area set?
+ *
+ * @param int $index Identifier for a specific print area range if several ranges have been set
+ * Default behaviour, or an index value of 0, will identify whether any print range is set
+ * Otherwise, existence of the range identified by the value of $index will be returned
+ * Print areas are numbered from 1
+ *
+ * @return bool
+ */
+ public function isPrintAreaSet($index = 0)
+ {
+ if ($index == 0) {
+ return $this->printArea !== null;
+ }
+ $printAreas = explode(',', $this->printArea);
+
+ return isset($printAreas[$index - 1]);
+ }
+
+ /**
+ * Clear a print area.
+ *
+ * @param int $index Identifier for a specific print area range if several ranges have been set
+ * Default behaviour, or an index value of 0, will clear all print ranges that are set
+ * Otherwise, the range identified by the value of $index will be removed from the series
+ * Print areas are numbered from 1
+ *
+ * @return $this
+ */
+ public function clearPrintArea($index = 0)
+ {
+ if ($index == 0) {
+ $this->printArea = null;
+ } else {
+ $printAreas = explode(',', $this->printArea);
+ if (isset($printAreas[$index - 1])) {
+ unset($printAreas[$index - 1]);
+ $this->printArea = implode(',', $printAreas);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set print area. e.g. 'A1:D10' or 'A1:D10,G5:M20'.
+ *
+ * @param string $value
+ * @param int $index Identifier for a specific print area range allowing several ranges to be set
+ * When the method is "O"verwrite, then a positive integer index will overwrite that indexed
+ * entry in the print areas list; a negative index value will identify which entry to
+ * overwrite working bacward through the print area to the list, with the last entry as -1.
+ * Specifying an index value of 0, will overwrite all existing print ranges.
+ * When the method is "I"nsert, then a positive index will insert after that indexed entry in
+ * the print areas list, while a negative index will insert before the indexed entry.
+ * Specifying an index value of 0, will always append the new print range at the end of the
+ * list.
+ * Print areas are numbered from 1
+ * @param string $method Determines the method used when setting multiple print areas
+ * Default behaviour, or the "O" method, overwrites existing print area
+ * The "I" method, inserts the new print area before any specified index, or at the end of the list
+ *
+ * @return $this
+ */
+ public function setPrintArea($value, $index = 0, $method = self::SETPRINTRANGE_OVERWRITE)
+ {
+ if (strpos($value, '!') !== false) {
+ throw new PhpSpreadsheetException('Cell coordinate must not specify a worksheet.');
+ } elseif (strpos($value, ':') === false) {
+ throw new PhpSpreadsheetException('Cell coordinate must be a range of cells.');
+ } elseif (strpos($value, '$') !== false) {
+ throw new PhpSpreadsheetException('Cell coordinate must not be absolute.');
+ }
+ $value = strtoupper($value);
+ if (!$this->printArea) {
+ $index = 0;
+ }
+
+ if ($method == self::SETPRINTRANGE_OVERWRITE) {
+ if ($index == 0) {
+ $this->printArea = $value;
+ } else {
+ $printAreas = explode(',', $this->printArea);
+ if ($index < 0) {
+ $index = count($printAreas) - abs($index) + 1;
+ }
+ if (($index <= 0) || ($index > count($printAreas))) {
+ throw new PhpSpreadsheetException('Invalid index for setting print range.');
+ }
+ $printAreas[$index - 1] = $value;
+ $this->printArea = implode(',', $printAreas);
+ }
+ } elseif ($method == self::SETPRINTRANGE_INSERT) {
+ if ($index == 0) {
+ $this->printArea = $this->printArea ? ($this->printArea . ',' . $value) : $value;
+ } else {
+ $printAreas = explode(',', $this->printArea);
+ if ($index < 0) {
+ $index = abs($index) - 1;
+ }
+ if ($index > count($printAreas)) {
+ throw new PhpSpreadsheetException('Invalid index for setting print range.');
+ }
+ $printAreas = array_merge(array_slice($printAreas, 0, $index), [$value], array_slice($printAreas, $index));
+ $this->printArea = implode(',', $printAreas);
+ }
+ } else {
+ throw new PhpSpreadsheetException('Invalid method for setting print range.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Add a new print area (e.g. 'A1:D10' or 'A1:D10,G5:M20') to the list of print areas.
+ *
+ * @param string $value
+ * @param int $index Identifier for a specific print area range allowing several ranges to be set
+ * A positive index will insert after that indexed entry in the print areas list, while a
+ * negative index will insert before the indexed entry.
+ * Specifying an index value of 0, will always append the new print range at the end of the
+ * list.
+ * Print areas are numbered from 1
+ *
+ * @return $this
+ */
+ public function addPrintArea($value, $index = -1)
+ {
+ return $this->setPrintArea($value, $index, self::SETPRINTRANGE_INSERT);
+ }
+
+ /**
+ * Set print area.
+ *
+ * @param int $column1 Column 1
+ * @param int $row1 Row 1
+ * @param int $column2 Column 2
+ * @param int $row2 Row 2
+ * @param int $index Identifier for a specific print area range allowing several ranges to be set
+ * When the method is "O"verwrite, then a positive integer index will overwrite that indexed
+ * entry in the print areas list; a negative index value will identify which entry to
+ * overwrite working backward through the print area to the list, with the last entry as -1.
+ * Specifying an index value of 0, will overwrite all existing print ranges.
+ * When the method is "I"nsert, then a positive index will insert after that indexed entry in
+ * the print areas list, while a negative index will insert before the indexed entry.
+ * Specifying an index value of 0, will always append the new print range at the end of the
+ * list.
+ * Print areas are numbered from 1
+ * @param string $method Determines the method used when setting multiple print areas
+ * Default behaviour, or the "O" method, overwrites existing print area
+ * The "I" method, inserts the new print area before any specified index, or at the end of the list
+ *
+ * @return $this
+ */
+ public function setPrintAreaByColumnAndRow($column1, $row1, $column2, $row2, $index = 0, $method = self::SETPRINTRANGE_OVERWRITE)
+ {
+ return $this->setPrintArea(
+ Coordinate::stringFromColumnIndex($column1) . $row1 . ':' . Coordinate::stringFromColumnIndex($column2) . $row2,
+ $index,
+ $method
+ );
+ }
+
+ /**
+ * Add a new print area to the list of print areas.
+ *
+ * @param int $column1 Start Column for the print area
+ * @param int $row1 Start Row for the print area
+ * @param int $column2 End Column for the print area
+ * @param int $row2 End Row for the print area
+ * @param int $index Identifier for a specific print area range allowing several ranges to be set
+ * A positive index will insert after that indexed entry in the print areas list, while a
+ * negative index will insert before the indexed entry.
+ * Specifying an index value of 0, will always append the new print range at the end of the
+ * list.
+ * Print areas are numbered from 1
+ *
+ * @return $this
+ */
+ public function addPrintAreaByColumnAndRow($column1, $row1, $column2, $row2, $index = -1)
+ {
+ return $this->setPrintArea(
+ Coordinate::stringFromColumnIndex($column1) . $row1 . ':' . Coordinate::stringFromColumnIndex($column2) . $row2,
+ $index,
+ self::SETPRINTRANGE_INSERT
+ );
+ }
+
+ /**
+ * Get first page number.
+ *
+ * @return int
+ */
+ public function getFirstPageNumber()
+ {
+ return $this->firstPageNumber;
+ }
+
+ /**
+ * Set first page number.
+ *
+ * @param int $value
+ *
+ * @return $this
+ */
+ public function setFirstPageNumber($value)
+ {
+ $this->firstPageNumber = $value;
+
+ return $this;
+ }
+
+ /**
+ * Reset first page number.
+ *
+ * @return $this
+ */
+ public function resetFirstPageNumber()
+ {
+ return $this->setFirstPageNumber(null);
+ }
+
+ public function getPageOrder(): string
+ {
+ return $this->pageOrder;
+ }
+
+ public function setPageOrder(?string $pageOrder): self
+ {
+ if ($pageOrder === null || $pageOrder === self::PAGEORDER_DOWN_THEN_OVER || $pageOrder === self::PAGEORDER_OVER_THEN_DOWN) {
+ $this->pageOrder = $pageOrder ?? self::PAGEORDER_DOWN_THEN_OVER;
+ }
+
+ 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/Writer/Exception.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php
new file mode 100644
index 0000000..92e6f5f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Exception.php
@@ -0,0 +1,9 @@
+spreadsheet = $spreadsheet;
+ $this->defaultFont = $this->spreadsheet->getDefaultStyle()->getFont();
+ }
+
+ /**
+ * Save Spreadsheet to file.
+ *
+ * @param resource|string $filename
+ */
+ public function save($filename, int $flags = 0): void
+ {
+ $this->processFlags($flags);
+
+ // Open file
+ $this->openFileHandle($filename);
+
+ // Write html
+ fwrite($this->fileHandle, $this->generateHTMLAll());
+
+ // Close file
+ $this->maybeCloseFileHandle();
+ }
+
+ /**
+ * Save Spreadsheet as html to variable.
+ *
+ * @return string
+ */
+ public function generateHtmlAll()
+ {
+ // garbage collect
+ $this->spreadsheet->garbageCollect();
+
+ $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
+ $saveArrayReturnType = Calculation::getArrayReturnType();
+ Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE);
+
+ // Build CSS
+ $this->buildCSS(!$this->useInlineCss);
+
+ $html = '';
+
+ // Write headers
+ $html .= $this->generateHTMLHeader(!$this->useInlineCss);
+
+ // Write navigation (tabs)
+ if ((!$this->isPdf) && ($this->generateSheetNavigationBlock)) {
+ $html .= $this->generateNavigation();
+ }
+
+ // Write data
+ $html .= $this->generateSheetData();
+
+ // Write footer
+ $html .= $this->generateHTMLFooter();
+ $callback = $this->editHtmlCallback;
+ if ($callback) {
+ $html = $callback($html);
+ }
+
+ Calculation::setArrayReturnType($saveArrayReturnType);
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
+
+ return $html;
+ }
+
+ /**
+ * Set a callback to edit the entire HTML.
+ *
+ * The callback must accept the HTML as string as first parameter,
+ * and it must return the edited HTML as string.
+ */
+ public function setEditHtmlCallback(?callable $callback): void
+ {
+ $this->editHtmlCallback = $callback;
+ }
+
+ const VALIGN_ARR = [
+ Alignment::VERTICAL_BOTTOM => 'bottom',
+ Alignment::VERTICAL_TOP => 'top',
+ Alignment::VERTICAL_CENTER => 'middle',
+ Alignment::VERTICAL_JUSTIFY => 'middle',
+ ];
+
+ /**
+ * Map VAlign.
+ *
+ * @param string $vAlign Vertical alignment
+ *
+ * @return string
+ */
+ private function mapVAlign($vAlign)
+ {
+ return array_key_exists($vAlign, self::VALIGN_ARR) ? self::VALIGN_ARR[$vAlign] : 'baseline';
+ }
+
+ const HALIGN_ARR = [
+ Alignment::HORIZONTAL_LEFT => 'left',
+ Alignment::HORIZONTAL_RIGHT => 'right',
+ Alignment::HORIZONTAL_CENTER => 'center',
+ Alignment::HORIZONTAL_CENTER_CONTINUOUS => 'center',
+ Alignment::HORIZONTAL_JUSTIFY => 'justify',
+ ];
+
+ /**
+ * Map HAlign.
+ *
+ * @param string $hAlign Horizontal alignment
+ *
+ * @return string
+ */
+ private function mapHAlign($hAlign)
+ {
+ return array_key_exists($hAlign, self::HALIGN_ARR) ? self::HALIGN_ARR[$hAlign] : '';
+ }
+
+ const BORDER_ARR = [
+ Border::BORDER_NONE => 'none',
+ Border::BORDER_DASHDOT => '1px dashed',
+ Border::BORDER_DASHDOTDOT => '1px dotted',
+ Border::BORDER_DASHED => '1px dashed',
+ Border::BORDER_DOTTED => '1px dotted',
+ Border::BORDER_DOUBLE => '3px double',
+ Border::BORDER_HAIR => '1px solid',
+ Border::BORDER_MEDIUM => '2px solid',
+ Border::BORDER_MEDIUMDASHDOT => '2px dashed',
+ Border::BORDER_MEDIUMDASHDOTDOT => '2px dotted',
+ Border::BORDER_SLANTDASHDOT => '2px dashed',
+ Border::BORDER_THICK => '3px solid',
+ ];
+
+ /**
+ * Map border style.
+ *
+ * @param int $borderStyle Sheet index
+ *
+ * @return string
+ */
+ private function mapBorderStyle($borderStyle)
+ {
+ return array_key_exists($borderStyle, self::BORDER_ARR) ? self::BORDER_ARR[$borderStyle] : '1px solid';
+ }
+
+ /**
+ * Get sheet index.
+ */
+ public function getSheetIndex(): ?int
+ {
+ return $this->sheetIndex;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param int $sheetIndex Sheet index
+ *
+ * @return $this
+ */
+ public function setSheetIndex($sheetIndex)
+ {
+ $this->sheetIndex = $sheetIndex;
+
+ return $this;
+ }
+
+ /**
+ * Get sheet index.
+ *
+ * @return bool
+ */
+ public function getGenerateSheetNavigationBlock()
+ {
+ return $this->generateSheetNavigationBlock;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param bool $generateSheetNavigationBlock Flag indicating whether the sheet navigation block should be generated or not
+ *
+ * @return $this
+ */
+ public function setGenerateSheetNavigationBlock($generateSheetNavigationBlock)
+ {
+ $this->generateSheetNavigationBlock = (bool) $generateSheetNavigationBlock;
+
+ return $this;
+ }
+
+ /**
+ * Write all sheets (resets sheetIndex to NULL).
+ *
+ * @return $this
+ */
+ public function writeAllSheets()
+ {
+ $this->sheetIndex = null;
+
+ return $this;
+ }
+
+ private static function generateMeta($val, $desc)
+ {
+ return $val
+ ? (' ' . PHP_EOL)
+ : '';
+ }
+
+ public const BODY_LINE = ' ' . PHP_EOL;
+
+ /**
+ * Generate HTML header.
+ *
+ * @param bool $includeStyles Include styles?
+ *
+ * @return string
+ */
+ public function generateHTMLHeader($includeStyles = false)
+ {
+ // Construct HTML
+ $properties = $this->spreadsheet->getProperties();
+ $html = '' . PHP_EOL;
+ $html .= '' . PHP_EOL;
+ $html .= ' ' . PHP_EOL;
+ $html .= ' ' . PHP_EOL;
+ $html .= ' ' . PHP_EOL;
+ $html .= ' ' . htmlspecialchars($properties->getTitle(), Settings::htmlEntityFlags()) . '' . PHP_EOL;
+ $html .= self::generateMeta($properties->getCreator(), 'author');
+ $html .= self::generateMeta($properties->getTitle(), 'title');
+ $html .= self::generateMeta($properties->getDescription(), 'description');
+ $html .= self::generateMeta($properties->getSubject(), 'subject');
+ $html .= self::generateMeta($properties->getKeywords(), 'keywords');
+ $html .= self::generateMeta($properties->getCategory(), 'category');
+ $html .= self::generateMeta($properties->getCompany(), 'company');
+ $html .= self::generateMeta($properties->getManager(), 'manager');
+
+ $html .= $includeStyles ? $this->generateStyles(true) : $this->generatePageDeclarations(true);
+
+ $html .= ' ' . PHP_EOL;
+ $html .= '' . PHP_EOL;
+ $html .= self::BODY_LINE;
+
+ return $html;
+ }
+
+ private function generateSheetPrep()
+ {
+ // Ensure that Spans have been calculated?
+ $this->calculateSpans();
+
+ // Fetch sheets
+ if ($this->sheetIndex === null) {
+ $sheets = $this->spreadsheet->getAllSheets();
+ } else {
+ $sheets = [$this->spreadsheet->getSheet($this->sheetIndex)];
+ }
+
+ return $sheets;
+ }
+
+ private function generateSheetStarts($sheet, $rowMin)
+ {
+ // calculate start of ,
+ $tbodyStart = $rowMin;
+ $theadStart = $theadEnd = 0; // default: no no
+ if ($sheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
+ $rowsToRepeatAtTop = $sheet->getPageSetup()->getRowsToRepeatAtTop();
+
+ // we can only support repeating rows that start at top row
+ if ($rowsToRepeatAtTop[0] == 1) {
+ $theadStart = $rowsToRepeatAtTop[0];
+ $theadEnd = $rowsToRepeatAtTop[1];
+ $tbodyStart = $rowsToRepeatAtTop[1] + 1;
+ }
+ }
+
+ return [$theadStart, $theadEnd, $tbodyStart];
+ }
+
+ private function generateSheetTags($row, $theadStart, $theadEnd, $tbodyStart)
+ {
+ // ?
+ $startTag = ($row == $theadStart) ? (' ' . PHP_EOL) : '';
+ if (!$startTag) {
+ $startTag = ($row == $tbodyStart) ? (' ' . PHP_EOL) : '';
+ }
+ $endTag = ($row == $theadEnd) ? (' ' . PHP_EOL) : '';
+ $cellType = ($row >= $tbodyStart) ? 'td' : 'th';
+
+ return [$cellType, $startTag, $endTag];
+ }
+
+ /**
+ * Generate sheet data.
+ *
+ * @return string
+ */
+ public function generateSheetData()
+ {
+ $sheets = $this->generateSheetPrep();
+
+ // Construct HTML
+ $html = '';
+
+ // Loop all sheets
+ $sheetId = 0;
+ foreach ($sheets as $sheet) {
+ // Write table header
+ $html .= $this->generateTableHeader($sheet);
+
+ // Get worksheet dimension
+ [$min, $max] = explode(':', $sheet->calculateWorksheetDataDimension());
+ [$minCol, $minRow] = Coordinate::indexesFromString($min);
+ [$maxCol, $maxRow] = Coordinate::indexesFromString($max);
+
+ [$theadStart, $theadEnd, $tbodyStart] = $this->generateSheetStarts($sheet, $minRow);
+
+ // Loop through cells
+ $row = $minRow - 1;
+ while ($row++ < $maxRow) {
+ [$cellType, $startTag, $endTag] = $this->generateSheetTags($row, $theadStart, $theadEnd, $tbodyStart);
+ $html .= $startTag;
+
+ // Write row if there are HTML table cells in it
+ if (!isset($this->isSpannedRow[$sheet->getParent()->getIndex($sheet)][$row])) {
+ // Start a new rowData
+ $rowData = [];
+ // Loop through columns
+ $column = $minCol;
+ while ($column <= $maxCol) {
+ // Cell exists?
+ if ($sheet->cellExistsByColumnAndRow($column, $row)) {
+ $rowData[$column] = Coordinate::stringFromColumnIndex($column) . $row;
+ } else {
+ $rowData[$column] = '';
+ }
+ ++$column;
+ }
+ $html .= $this->generateRow($sheet, $rowData, $row - 1, $cellType);
+ }
+
+ $html .= $endTag;
+ }
+ $html .= $this->extendRowsForChartsAndImages($sheet, $row);
+
+ // Write table footer
+ $html .= $this->generateTableFooter();
+ // Writing PDF?
+ if ($this->isPdf && $this->useInlineCss) {
+ if ($this->sheetIndex === null && $sheetId + 1 < $this->spreadsheet->getSheetCount()) {
+ $html .= '';
+ }
+ }
+
+ // Next sheet
+ ++$sheetId;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Generate sheet tabs.
+ *
+ * @return string
+ */
+ public function generateNavigation()
+ {
+ // Fetch sheets
+ $sheets = [];
+ if ($this->sheetIndex === null) {
+ $sheets = $this->spreadsheet->getAllSheets();
+ } else {
+ $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
+ }
+
+ // Construct HTML
+ $html = '';
+
+ // Only if there are more than 1 sheets
+ if (count($sheets) > 1) {
+ // Loop all sheets
+ $sheetId = 0;
+
+ $html .= '
+
+
diff --git a/vendor/topthink/think-worker/LICENSE b/vendor/topthink/think-worker/LICENSE
new file mode 100644
index 0000000..8dada3e
--- /dev/null
+++ b/vendor/topthink/think-worker/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "{}"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright {yyyy} {name of copyright owner}
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/vendor/topthink/think-worker/src/Events.php b/vendor/topthink/think-worker/src/Events.php
new file mode 100644
index 0000000..7aecf99
--- /dev/null
+++ b/vendor/topthink/think-worker/src/Events.php
@@ -0,0 +1,97 @@
+
+// +----------------------------------------------------------------------
+namespace think\worker;
+
+use GatewayWorker\Lib\Gateway;
+use Workerman\Worker;
+
+/**
+ * Worker 命令行服务类
+ */
+class Events
+{
+ /**
+ * onWorkerStart 事件回调
+ * 当businessWorker进程启动时触发。每个进程生命周期内都只会触发一次
+ *
+ * @access public
+ * @param \Workerman\Worker $businessWorker
+ * @return void
+ */
+ public static function onWorkerStart(Worker $businessWorker)
+ {
+ $app = new Application;
+ $app->initialize();
+ }
+
+ /**
+ * onConnect 事件回调
+ * 当客户端连接上gateway进程时(TCP三次握手完毕时)触发
+ *
+ * @access public
+ * @param int $client_id
+ * @return void
+ */
+ public static function onConnect($client_id)
+ {
+ Gateway::sendToCurrentClient("Your client_id is $client_id");
+ }
+
+ /**
+ * onWebSocketConnect 事件回调
+ * 当客户端连接上gateway完成websocket握手时触发
+ *
+ * @param integer $client_id 断开连接的客户端client_id
+ * @param mixed $data
+ * @return void
+ */
+ public static function onWebSocketConnect($client_id, $data)
+ {
+ var_export($data);
+ }
+
+ /**
+ * onMessage 事件回调
+ * 当客户端发来数据(Gateway进程收到数据)后触发
+ *
+ * @access public
+ * @param int $client_id
+ * @param mixed $data
+ * @return void
+ */
+ public static function onMessage($client_id, $data)
+ {
+ Gateway::sendToAll($data);
+ }
+
+ /**
+ * onClose 事件回调 当用户断开连接时触发的方法
+ *
+ * @param integer $client_id 断开连接的客户端client_id
+ * @return void
+ */
+ public static function onClose($client_id)
+ {
+ GateWay::sendToAll("client[$client_id] logout\n");
+ }
+
+ /**
+ * onWorkerStop 事件回调
+ * 当businessWorker进程退出时触发。每个进程生命周期内都只会触发一次。
+ *
+ * @param \Workerman\Worker $businessWorker
+ * @return void
+ */
+ public static function onWorkerStop(Worker $businessWorker)
+ {
+ echo "WorkerStop\n";
+ }
+}
diff --git a/vendor/topthink/think-worker/src/Http.php b/vendor/topthink/think-worker/src/Http.php
new file mode 100644
index 0000000..cccc50d
--- /dev/null
+++ b/vendor/topthink/think-worker/src/Http.php
@@ -0,0 +1,290 @@
+
+// +----------------------------------------------------------------------
+namespace think\worker;
+
+use RecursiveDirectoryIterator;
+use RecursiveIteratorIterator;
+use Workerman\Connection\TcpConnection;
+use Workerman\Lib\Timer;
+use Workerman\Protocols\Http as WorkerHttp;
+use Workerman\Worker;
+
+/**
+ * Worker http server 命令行服务类
+ */
+class Http extends Server
+{
+ protected $app;
+ protected $rootPath;
+ protected $root;
+ protected $appInit;
+ protected $monitor;
+ protected $lastMtime;
+ /** @var array Mime mapping. */
+ protected static $mimeTypeMap = [];
+
+ /**
+ * 架构函数
+ * @access public
+ * @param string $host 监听地址
+ * @param int $port 监听端口
+ * @param array $context 参数
+ */
+ public function __construct($host, $port, $context = [])
+ {
+ $this->worker = new Worker('http://' . $host . ':' . $port, $context);
+
+ // 设置回调
+ foreach ($this->event as $event) {
+ if (method_exists($this, $event)) {
+ $this->worker->$event = [$this, $event];
+ }
+ }
+ }
+
+ public function setRootPath($path)
+ {
+ $this->rootPath = $path;
+ }
+
+ public function appInit(\Closure $closure)
+ {
+ $this->appInit = $closure;
+ }
+
+ public function setRoot($path)
+ {
+ $this->root = $path;
+ }
+
+ public function setStaticOption($name, $value)
+ {
+ Worker::${$name} = $value;
+ }
+
+ public function setMonitor($interval = 2, $path = [])
+ {
+ $this->monitor['interval'] = $interval;
+ $this->monitor['path'] = (array) $path;
+ }
+
+ /**
+ * 设置参数
+ * @access public
+ * @param array $option 参数
+ * @return void
+ */
+ public function option(array $option)
+ {
+ // 设置参数
+ if (!empty($option)) {
+ foreach ($option as $key => $val) {
+ $this->worker->$key = $val;
+ }
+ }
+ }
+
+ /**
+ * onWorkerStart 事件回调
+ * @access public
+ * @param \Workerman\Worker $worker
+ * @return void
+ */
+ public function onWorkerStart($worker)
+ {
+ $this->initMimeTypeMap();
+ $this->app = new Application($this->rootPath);
+
+ if ($this->appInit) {
+ call_user_func_array($this->appInit, [$this->app]);
+ }
+
+ $this->app->initialize();
+
+ $this->lastMtime = time();
+
+ $this->app->workerman = $worker;
+
+ $this->app->bind([
+ 'think\Cookie' => Cookie::class,
+ ]);
+
+ if (0 == $worker->id && $this->monitor) {
+ $paths = $this->monitor['path'];
+ $timer = $this->monitor['interval'] ?: 2;
+
+ Timer::add($timer, function () use ($paths) {
+ foreach ($paths as $path) {
+ $dir = new RecursiveDirectoryIterator($path);
+ $iterator = new RecursiveIteratorIterator($dir);
+
+ foreach ($iterator as $file) {
+ if (pathinfo($file, PATHINFO_EXTENSION) != 'php') {
+ continue;
+ }
+
+ if ($this->lastMtime < $file->getMTime()) {
+ echo '[update]' . $file . "\n";
+ posix_kill(posix_getppid(), SIGUSR1);
+ $this->lastMtime = $file->getMTime();
+ return;
+ }
+ }
+ }
+ });
+ }
+ }
+
+ /**
+ * onMessage 事件回调
+ * @access public
+ * @param TcpConnection $connection
+ * @param mixed $data
+ * @return void
+ */
+ public function onMessage($connection, $data)
+ {
+ $uri = parse_url($_SERVER['REQUEST_URI']);
+ $path = $uri['path'] ?? '/';
+
+ $file = $this->root . $path;
+
+ if (!is_file($file)) {
+ $this->app->worker($connection, $data);
+ } else {
+ $this->sendFile($connection, $file);
+ }
+ }
+
+ /**
+ * 访问资源文件
+ * @access protected
+ * @param TcpConnection $connection
+ * @param string $file 文件名
+ * @return string
+ */
+ protected function sendFile($connection, $file)
+ {
+ $info = stat($file);
+ $modifiyTime = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' ' . date_default_timezone_get() : '';
+
+ if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info) {
+ // Http 304.
+ if ($modifiyTime === $_SERVER['HTTP_IF_MODIFIED_SINCE']) {
+ // 304
+ WorkerHttp::header('HTTP/1.1 304 Not Modified');
+ // Send nothing but http headers..
+ return $connection->close('');
+ }
+ }
+
+ $mimeType = $this->getMimeType($file);
+
+ WorkerHttp::header('HTTP/1.1 200 OK');
+ WorkerHttp::header('Connection: keep-alive');
+
+ if ($mimeType) {
+ WorkerHttp::header('Content-Type: ' . $mimeType);
+ } else {
+ WorkerHttp::header('Content-Type: application/octet-stream');
+ $fileinfo = pathinfo($file);
+ $filename = $fileinfo['filename'] ?? '';
+ WorkerHttp::header('Content-Disposition: attachment; filename="' . $filename . '"');
+ }
+
+ if ($modifiyTime) {
+ WorkerHttp::header('Last-Modified: ' . $modifiyTime);
+ }
+
+ WorkerHttp::header('Content-Length: ' . filesize($file));
+
+ ob_start();
+ readfile($file);
+ $content = ob_get_clean();
+
+ return $connection->send($content);
+ }
+
+ /**
+ * 获取文件类型信息
+ * @access protected
+ * @param string $filename 文件名
+ * @return string
+ */
+ protected function getMimeType(string $filename)
+ {
+ $file_info = pathinfo($filename);
+ $extension = $file_info['extension'] ?? '';
+
+ if (isset(self::$mimeTypeMap[$extension])) {
+ $mime = self::$mimeTypeMap[$extension];
+ } else {
+ $finfo = finfo_open(FILEINFO_MIME_TYPE);
+ $mime = finfo_file($finfo, $filename);
+ }
+
+ return $mime;
+ }
+
+ /**
+ * Init mime map.
+ *
+ * @return void
+ * @throws \Exception
+ */
+ protected function initMimeTypeMap()
+ {
+ $mime_file = WorkerHttp::getMimeTypesFile();
+
+ if (!is_file($mime_file)) {
+ Worker::log("$mime_file mime.type file not fond");
+ return;
+ }
+
+ $items = file($mime_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
+
+ if (!is_array($items)) {
+ Worker::log("get $mime_file mime.type content fail");
+ return;
+ }
+
+ foreach ($items as $content) {
+ if (preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) {
+ $mime_type = $match[1];
+ $workerman_file_extension_var = $match[2];
+ $workerman_file_extension_array = explode(' ', substr($workerman_file_extension_var, 0, -1));
+ foreach ($workerman_file_extension_array as $workerman_file_extension) {
+ self::$mimeTypeMap[$workerman_file_extension] = $mime_type;
+ }
+ }
+ }
+ }
+
+ /**
+ * 启动
+ * @access public
+ * @return void
+ */
+ public function start()
+ {
+ Worker::runAll();
+ }
+
+ /**
+ * 停止
+ * @access public
+ * @return void
+ */
+ public function stop()
+ {
+ Worker::stopAll();
+ }
+}
diff --git a/vendor/topthink/think-worker/src/command/GatewayWorker.php b/vendor/topthink/think-worker/src/command/GatewayWorker.php
new file mode 100644
index 0000000..3fca135
--- /dev/null
+++ b/vendor/topthink/think-worker/src/command/GatewayWorker.php
@@ -0,0 +1,201 @@
+
+// +----------------------------------------------------------------------
+
+namespace think\worker\command;
+
+use GatewayWorker\BusinessWorker;
+use GatewayWorker\Gateway;
+use GatewayWorker\Register;
+use think\console\Command;
+use think\console\Input;
+use think\console\input\Argument;
+use think\console\input\Option;
+use think\console\Output;
+use think\facade\Config;
+use Workerman\Worker;
+
+/**
+ * Worker 命令行类
+ */
+class GatewayWorker extends Command
+{
+ public function configure()
+ {
+ $this->setName('worker:gateway')
+ ->addArgument('action', Argument::OPTIONAL, "start|stop|restart|reload|status|connections", 'start')
+ ->addOption('host', 'H', Option::VALUE_OPTIONAL, 'the host of workerman server.', null)
+ ->addOption('port', 'p', Option::VALUE_OPTIONAL, 'the port of workerman server.', null)
+ ->addOption('daemon', 'd', Option::VALUE_NONE, 'Run the workerman server in daemon mode.')
+ ->setDescription('GatewayWorker Server for ThinkPHP');
+ }
+
+ public function execute(Input $input, Output $output)
+ {
+ $action = $input->getArgument('action');
+
+ if (DIRECTORY_SEPARATOR !== '\\') {
+ if (!in_array($action, ['start', 'stop', 'reload', 'restart', 'status', 'connections'])) {
+ $output->writeln("Invalid argument action:{$action}, Expected start|stop|restart|reload|status|connections .");
+ exit(1);
+ }
+
+ global $argv;
+ array_shift($argv);
+ array_shift($argv);
+ array_unshift($argv, 'think', $action);
+ } else {
+ $output->writeln("GatewayWorker Not Support On Windows.");
+ exit(1);
+ }
+
+ if ('start' == $action) {
+ $output->writeln('Starting GatewayWorker server...');
+ }
+
+ $option = Config::get('gateway_worker');
+
+ if ($input->hasOption('host')) {
+ $host = $input->getOption('host');
+ } else {
+ $host = !empty($option['host']) ? $option['host'] : '0.0.0.0';
+ }
+
+ if ($input->hasOption('port')) {
+ $port = $input->getOption('port');
+ } else {
+ $port = !empty($option['port']) ? $option['port'] : '2347';
+ }
+
+ $this->start($host, (int) $port, $option);
+ }
+
+ /**
+ * 启动
+ * @access public
+ * @param string $host 监听地址
+ * @param integer $port 监听端口
+ * @param array $option 参数
+ * @return void
+ */
+ public function start(string $host, int $port, array $option = [])
+ {
+ $registerAddress = !empty($option['registerAddress']) ? $option['registerAddress'] : '127.0.0.1:1236';
+
+ if (!empty($option['register_deploy'])) {
+ // 分布式部署的时候其它服务器可以关闭register服务
+ // 注意需要设置不同的lanIp
+ $this->register($registerAddress);
+ }
+
+ // 启动businessWorker
+ if (!empty($option['businessWorker_deploy'])) {
+ $this->businessWorker($registerAddress, $option['businessWorker'] ?? []);
+ }
+
+ // 启动gateway
+ if (!empty($option['gateway_deploy'])) {
+ $this->gateway($registerAddress, $host, $port, $option);
+ }
+
+ Worker::runAll();
+ }
+
+ /**
+ * 启动register
+ * @access public
+ * @param string $registerAddress
+ * @return void
+ */
+ public function register(string $registerAddress)
+ {
+ // 初始化register
+ new Register('text://' . $registerAddress);
+ }
+
+ /**
+ * 启动businessWorker
+ * @access public
+ * @param string $registerAddress registerAddress
+ * @param array $option 参数
+ * @return void
+ */
+ public function businessWorker(string $registerAddress, array $option = [])
+ {
+ // 初始化 bussinessWorker 进程
+ $worker = new BusinessWorker();
+
+ $this->option($worker, $option);
+
+ $worker->registerAddress = $registerAddress;
+ }
+
+ /**
+ * 启动gateway
+ * @access public
+ * @param string $registerAddress registerAddress
+ * @param string $host 服务地址
+ * @param integer $port 监听端口
+ * @param array $option 参数
+ * @return void
+ */
+ public function gateway(string $registerAddress, string $host, int $port, array $option = [])
+ {
+ // 初始化 gateway 进程
+ if (!empty($option['socket'])) {
+ $socket = $option['socket'];
+ unset($option['socket']);
+ } else {
+ $protocol = !empty($option['protocol']) ? $option['protocol'] : 'websocket';
+ $socket = $protocol . '://' . $host . ':' . $port;
+ unset($option['host'], $option['port'], $option['protocol']);
+ }
+
+ $gateway = new Gateway($socket, $option['context'] ?? []);
+
+ // 以下设置参数都可以在配置文件中重新定义覆盖
+ $gateway->name = 'Gateway';
+ $gateway->count = 4;
+ $gateway->lanIp = '127.0.0.1';
+ $gateway->startPort = 2000;
+ $gateway->pingInterval = 30;
+ $gateway->pingNotResponseLimit = 0;
+ $gateway->pingData = '{"type":"ping"}';
+ $gateway->registerAddress = $registerAddress;
+
+ // 全局静态属性设置
+ foreach ($option as $name => $val) {
+ if (in_array($name, ['stdoutFile', 'daemonize', 'pidFile', 'logFile'])) {
+ Worker::${$name} = $val;
+ unset($option[$name]);
+ }
+ }
+
+ $this->option($gateway, $option);
+ }
+
+ /**
+ * 设置参数
+ * @access protected
+ * @param Worker $worker Worker对象
+ * @param array $option 参数
+ * @return void
+ */
+ protected function option(Worker $worker, array $option = [])
+ {
+ // 设置参数
+ if (!empty($option)) {
+ foreach ($option as $key => $val) {
+ $worker->$key = $val;
+ }
+ }
+ }
+
+}
diff --git a/vendor/topthink/think-worker/src/config/gateway.php b/vendor/topthink/think-worker/src/config/gateway.php
new file mode 100644
index 0000000..21d1ebe
--- /dev/null
+++ b/vendor/topthink/think-worker/src/config/gateway.php
@@ -0,0 +1,45 @@
+
+// +----------------------------------------------------------------------
+// +----------------------------------------------------------------------
+// | Workerman设置 仅对 php think worker:gateway 指令有效
+// +----------------------------------------------------------------------
+return [
+ // 扩展自身需要的配置
+ 'protocol' => 'websocket', // 协议 支持 tcp udp unix http websocket text
+ 'host' => '0.0.0.0', // 监听地址
+ 'port' => 2348, // 监听端口
+ 'socket' => '', // 完整监听地址
+ 'context' => [], // socket 上下文选项
+ 'register_deploy' => true, // 是否需要部署register
+ 'businessWorker_deploy' => true, // 是否需要部署businessWorker
+ 'gateway_deploy' => true, // 是否需要部署gateway
+
+ // Register配置
+ 'registerAddress' => '127.0.0.1:1236',
+
+ // Gateway配置
+ 'name' => 'thinkphp',
+ 'count' => 1,
+ 'lanIp' => '127.0.0.1',
+ 'startPort' => 2000,
+ 'daemonize' => false,
+ 'pingInterval' => 30,
+ 'pingNotResponseLimit' => 0,
+ 'pingData' => '{"type":"ping"}',
+
+ // BusinsessWorker配置
+ 'businessWorker' => [
+ 'name' => 'BusinessWorker',
+ 'count' => 1,
+ 'eventHandler' => '\think\worker\Events',
+ ],
+
+];