, ...].
+ *
+ * @param array $pValue
+ */
+ public function setIDCLs($pValue): void
+ {
+ $this->IDCLs = $pValue;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php
new file mode 100644
index 0000000..9793889
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer.php
@@ -0,0 +1,34 @@
+BSECollection[] = $BSE;
+ $BSE->setParent($this);
+ }
+
+ /**
+ * Get the collection of BLIP Store Entries.
+ *
+ * @return BstoreContainer\BSE[]
+ */
+ public function getBSECollection()
+ {
+ return $this->BSECollection;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php
new file mode 100644
index 0000000..a56534a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE.php
@@ -0,0 +1,89 @@
+parent = $parent;
+ }
+
+ /**
+ * Get the BLIP.
+ *
+ * @return BSE\Blip
+ */
+ public function getBlip()
+ {
+ return $this->blip;
+ }
+
+ /**
+ * Set the BLIP.
+ *
+ * @param BSE\Blip $blip
+ */
+ public function setBlip($blip): void
+ {
+ $this->blip = $blip;
+ $blip->setParent($this);
+ }
+
+ /**
+ * Get the BLIP type.
+ *
+ * @return int
+ */
+ public function getBlipType()
+ {
+ return $this->blipType;
+ }
+
+ /**
+ * Set the BLIP type.
+ *
+ * @param int $blipType
+ */
+ public function setBlipType($blipType): void
+ {
+ $this->blipType = $blipType;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php
new file mode 100644
index 0000000..1e44ad2
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Escher/DggContainer/BstoreContainer/BSE/Blip.php
@@ -0,0 +1,60 @@
+data;
+ }
+
+ /**
+ * Set the raw image data.
+ *
+ * @param string $data
+ */
+ public function setData($data): void
+ {
+ $this->data = $data;
+ }
+
+ /**
+ * Set parent BSE.
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE $parent
+ */
+ public function setParent($parent): void
+ {
+ $this->parent = $parent;
+ }
+
+ /**
+ * Get parent BSE.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE $parent
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+}
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..948d119
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php
@@ -0,0 +1,142 @@
+open($zipFile) === true) {
+ $returnValue = ($zip->getFromName($archiveFile) !== false);
+ $zip->close();
+
+ return $returnValue;
+ }
+
+ return false;
+ }
+
+ return file_exists($pFilename);
+ }
+
+ /**
+ * Returns canonicalized absolute pathname, also for ZIP archives.
+ *
+ * @param string $pFilename
+ *
+ * @return string
+ */
+ public static function realpath($pFilename)
+ {
+ // Returnvalue
+ $returnValue = '';
+
+ // Try using realpath()
+ if (file_exists($pFilename)) {
+ $returnValue = realpath($pFilename);
+ }
+
+ // Found something?
+ if ($returnValue == '' || ($returnValue === null)) {
+ $pathArray = explode('/', $pFilename);
+ 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.
+ *
+ * @return string
+ */
+ public static function sysGetTempDir()
+ {
+ 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)) {
+ return realpath($temp);
+ }
+ }
+ }
+ }
+
+ return realpath(sys_get_temp_dir());
+ }
+
+ /**
+ * Assert that given path is an existing file and is readable, otherwise throw exception.
+ *
+ * @param string $filename
+ */
+ public static function assertFile($filename): void
+ {
+ if (!is_file($filename)) {
+ throw new InvalidArgumentException('File "' . $filename . '" does not exist.');
+ }
+
+ if (!is_readable($filename)) {
+ throw new InvalidArgumentException('Could not open "' . $filename . '" for reading.');
+ }
+ }
+}
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..88766a5
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Font.php
@@ -0,0 +1,763 @@
+ [
+ 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 $pValue see self::AUTOSIZE_METHOD_*
+ *
+ * @return bool Success or failure
+ */
+ public static function setAutoSizeMethod($pValue)
+ {
+ if (!in_array($pValue, self::$autoSizeMethods)) {
+ return false;
+ }
+ self::$autoSizeMethod = $pValue;
+
+ 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 $pValue
+ */
+ public static function setTrueTypeFontPath($pValue): void
+ {
+ self::$trueTypeFontPath = $pValue;
+ }
+
+ /**
+ * 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 \PhpOffice\PhpSpreadsheet\Style\Font $font Font object
+ * @param RichText|string $cellText Text to calculate width
+ * @param int $rotation Rotation angle
+ * @param null|\PhpOffice\PhpSpreadsheet\Style\Font $defaultFont Font object
+ *
+ * @return int Column width
+ */
+ public static function calculateColumnWidth(\PhpOffice\PhpSpreadsheet\Style\Font $font, $cellText = '', $rotation = 0, ?\PhpOffice\PhpSpreadsheet\Style\Font $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;
+ 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($columnWidth, $defaultFont);
+
+ // Return
+ return round($columnWidth, 6);
+ }
+
+ /**
+ * Get GD text width in pixels for a string of text in a certain font at a certain rotation angle.
+ *
+ * @param string $text
+ * @param \PhpOffice\PhpSpreadsheet\Style\Font
+ * @param int $rotation
+ *
+ * @return int
+ */
+ public static function getTextWidthPixelsExact($text, \PhpOffice\PhpSpreadsheet\Style\Font $font, $rotation = 0)
+ {
+ 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, \PhpOffice\PhpSpreadsheet\Style\Font $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 == -165) {
+ // 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.
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Style\Font $font
+ *
+ * @return string Path to TrueType font file
+ */
+ public static function getTrueTypeFontFileFromFont($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 $name Font name
+ *
+ * @return int Character set code
+ */
+ public static function getCharsetFromFontName($name)
+ {
+ switch ($name) {
+ // 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 \PhpOffice\PhpSpreadsheet\Style\Font $font The workbooks default font
+ * @param bool $pPixels true = return column width in pixels, false = return in OOXML units
+ *
+ * @return mixed Column width
+ */
+ public static function getDefaultColumnWidthByFont(\PhpOffice\PhpSpreadsheet\Style\Font $font, $pPixels = false)
+ {
+ if (isset(self::$defaultColumnWidths[$font->getName()][$font->getSize()])) {
+ // Exact width can be determined
+ $columnWidth = $pPixels ?
+ 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 = $pPixels ?
+ self::$defaultColumnWidths['Calibri'][11]['px']
+ : self::$defaultColumnWidths['Calibri'][11]['width'];
+ $columnWidth = $columnWidth * $font->getSize() / 11;
+
+ // Round pixels to closest integer
+ if ($pPixels) {
+ $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 \PhpOffice\PhpSpreadsheet\Style\Font $font The workbooks default font
+ *
+ * @return float Row height in points
+ */
+ public static function getDefaultRowHeightByFont(\PhpOffice\PhpSpreadsheet\Style\Font $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/JAMA/CHANGELOG.TXT b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CHANGELOG.TXT
new file mode 100644
index 0000000..2fc9cd4
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CHANGELOG.TXT
@@ -0,0 +1,16 @@
+Mar 1, 2005 11:15 AST by PM
+
++ For consistency, renamed Math.php to Maths.java, utils to util,
+ tests to test, docs to doc -
+
++ Removed conditional logic from top of Matrix class.
+
++ Switched to using hypo function in Maths.php for all php-hypot calls.
+ NOTE TO SELF: Need to make sure that all decompositions have been
+ switched over to using the bundled hypo.
+
+Feb 25, 2005 at 10:00 AST by PM
+
++ Recommend using simpler Error.php instead of JAMA_Error.php but
+ can be persuaded otherwise.
+
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CholeskyDecomposition.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CholeskyDecomposition.php
new file mode 100644
index 0000000..4f7c666
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/CholeskyDecomposition.php
@@ -0,0 +1,147 @@
+L = $A->getArray();
+ $this->m = $A->getRowDimension();
+
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = $i; $j < $this->m; ++$j) {
+ for ($sum = $this->L[$i][$j], $k = $i - 1; $k >= 0; --$k) {
+ $sum -= $this->L[$i][$k] * $this->L[$j][$k];
+ }
+ if ($i == $j) {
+ if ($sum >= 0) {
+ $this->L[$i][$i] = sqrt($sum);
+ } else {
+ $this->isspd = false;
+ }
+ } else {
+ if ($this->L[$i][$i] != 0) {
+ $this->L[$j][$i] = $sum / $this->L[$i][$i];
+ }
+ }
+ }
+
+ for ($k = $i + 1; $k < $this->m; ++$k) {
+ $this->L[$i][$k] = 0.0;
+ }
+ }
+ }
+
+ /**
+ * Is the matrix symmetric and positive definite?
+ *
+ * @return bool
+ */
+ public function isSPD()
+ {
+ return $this->isspd;
+ }
+
+ /**
+ * getL.
+ *
+ * Return triangular factor.
+ *
+ * @return Matrix Lower triangular matrix
+ */
+ public function getL()
+ {
+ return new Matrix($this->L);
+ }
+
+ /**
+ * Solve A*X = B.
+ *
+ * @param $B Row-equal matrix
+ *
+ * @return Matrix L * L' * X = B
+ */
+ public function solve(Matrix $B)
+ {
+ if ($B->getRowDimension() == $this->m) {
+ if ($this->isspd) {
+ $X = $B->getArrayCopy();
+ $nx = $B->getColumnDimension();
+
+ for ($k = 0; $k < $this->m; ++$k) {
+ for ($i = $k + 1; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X[$i][$j] -= $X[$k][$j] * $this->L[$i][$k];
+ }
+ }
+ for ($j = 0; $j < $nx; ++$j) {
+ $X[$k][$j] /= $this->L[$k][$k];
+ }
+ }
+
+ for ($k = $this->m - 1; $k >= 0; --$k) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X[$k][$j] /= $this->L[$k][$k];
+ }
+ for ($i = 0; $i < $k; ++$i) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X[$i][$j] -= $X[$k][$j] * $this->L[$k][$i];
+ }
+ }
+ }
+
+ return new Matrix($X, $this->m, $nx);
+ }
+
+ throw new CalculationException(Matrix::MATRIX_SPD_EXCEPTION);
+ }
+
+ throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php
new file mode 100644
index 0000000..a8fc240
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/EigenvalueDecomposition.php
@@ -0,0 +1,863 @@
+d = $this->V[$this->n - 1];
+ // 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 < $low) || ($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 < $low | $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 mixed $Arg A Square matrix
+ */
+ public function __construct($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()
+ {
+ 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..3871895
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/LUDecomposition.php
@@ -0,0 +1,282 @@
+= 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()
+ {
+ 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()
+ {
+ 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 array d matrix deterninat
+ */
+ 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 mixed $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($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..fc815f6
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/Matrix.php
@@ -0,0 +1,1202 @@
+ 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 mixed Element (int/float/double)
+ */
+ 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 mixed $c Int/float/double value
+ *
+ * @return mixed Element (int/float/double)
+ */
+ public function set($i = null, $j = null, $c = null)
+ {
+ // 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;
+ }
+
+ /**
+ * uminus.
+ *
+ * Unary minus matrix -A
+ *
+ * @return Matrix Unary minus matrix
+ */
+ public function uminus()
+ {
+ }
+
+ /**
+ * 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($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/QRDecomposition.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php
new file mode 100644
index 0000000..373c074
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/QRDecomposition.php
@@ -0,0 +1,249 @@
+= n, the QR decomposition is an m-by-n
+ * orthogonal matrix Q and an n-by-n upper triangular matrix R so that
+ * A = Q*R.
+ *
+ * The QR decompostion always exists, even if the matrix does not have
+ * full rank, so the constructor will never fail. The primary use of the
+ * QR decomposition is in the least squares solution of nonsquare systems
+ * of simultaneous linear equations. This will fail if isFullRank()
+ * returns false.
+ *
+ * @author Paul Meagher
+ *
+ * @version 1.1
+ */
+class QRDecomposition
+{
+ const MATRIX_RANK_EXCEPTION = 'Can only perform operation on full-rank matrix.';
+
+ /**
+ * Array for internal storage of decomposition.
+ *
+ * @var array
+ */
+ private $QR = [];
+
+ /**
+ * Row dimension.
+ *
+ * @var int
+ */
+ private $m;
+
+ /**
+ * Column dimension.
+ *
+ * @var int
+ */
+ private $n;
+
+ /**
+ * Array for internal storage of diagonal of R.
+ *
+ * @var array
+ */
+ private $Rdiag = [];
+
+ /**
+ * QR Decomposition computed by Householder reflections.
+ *
+ * @param matrix $A Rectangular matrix
+ */
+ public function __construct($A)
+ {
+ if ($A instanceof Matrix) {
+ // Initialize.
+ $this->QR = $A->getArray();
+ $this->m = $A->getRowDimension();
+ $this->n = $A->getColumnDimension();
+ // Main loop.
+ for ($k = 0; $k < $this->n; ++$k) {
+ // Compute 2-norm of k-th column without under/overflow.
+ $nrm = 0.0;
+ for ($i = $k; $i < $this->m; ++$i) {
+ $nrm = hypo($nrm, $this->QR[$i][$k]);
+ }
+ if ($nrm != 0.0) {
+ // Form k-th Householder vector.
+ if ($this->QR[$k][$k] < 0) {
+ $nrm = -$nrm;
+ }
+ for ($i = $k; $i < $this->m; ++$i) {
+ $this->QR[$i][$k] /= $nrm;
+ }
+ $this->QR[$k][$k] += 1.0;
+ // Apply transformation to remaining columns.
+ for ($j = $k + 1; $j < $this->n; ++$j) {
+ $s = 0.0;
+ for ($i = $k; $i < $this->m; ++$i) {
+ $s += $this->QR[$i][$k] * $this->QR[$i][$j];
+ }
+ $s = -$s / $this->QR[$k][$k];
+ for ($i = $k; $i < $this->m; ++$i) {
+ $this->QR[$i][$j] += $s * $this->QR[$i][$k];
+ }
+ }
+ }
+ $this->Rdiag[$k] = -$nrm;
+ }
+ } else {
+ throw new CalculationException(Matrix::ARGUMENT_TYPE_EXCEPTION);
+ }
+ }
+
+ // function __construct()
+
+ /**
+ * Is the matrix full rank?
+ *
+ * @return bool true if R, and hence A, has full rank, else false
+ */
+ public function isFullRank()
+ {
+ for ($j = 0; $j < $this->n; ++$j) {
+ if ($this->Rdiag[$j] == 0) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // function isFullRank()
+
+ /**
+ * Return the Householder vectors.
+ *
+ * @return Matrix Lower trapezoidal matrix whose columns define the reflections
+ */
+ public function getH()
+ {
+ $H = [];
+ for ($i = 0; $i < $this->m; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ if ($i >= $j) {
+ $H[$i][$j] = $this->QR[$i][$j];
+ } else {
+ $H[$i][$j] = 0.0;
+ }
+ }
+ }
+
+ return new Matrix($H);
+ }
+
+ // function getH()
+
+ /**
+ * Return the upper triangular factor.
+ *
+ * @return Matrix upper triangular factor
+ */
+ public function getR()
+ {
+ $R = [];
+ for ($i = 0; $i < $this->n; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ if ($i < $j) {
+ $R[$i][$j] = $this->QR[$i][$j];
+ } elseif ($i == $j) {
+ $R[$i][$j] = $this->Rdiag[$i];
+ } else {
+ $R[$i][$j] = 0.0;
+ }
+ }
+ }
+
+ return new Matrix($R);
+ }
+
+ // function getR()
+
+ /**
+ * Generate and return the (economy-sized) orthogonal factor.
+ *
+ * @return Matrix orthogonal factor
+ */
+ public function getQ()
+ {
+ $Q = [];
+ for ($k = $this->n - 1; $k >= 0; --$k) {
+ for ($i = 0; $i < $this->m; ++$i) {
+ $Q[$i][$k] = 0.0;
+ }
+ $Q[$k][$k] = 1.0;
+ for ($j = $k; $j < $this->n; ++$j) {
+ if ($this->QR[$k][$k] != 0) {
+ $s = 0.0;
+ for ($i = $k; $i < $this->m; ++$i) {
+ $s += $this->QR[$i][$k] * $Q[$i][$j];
+ }
+ $s = -$s / $this->QR[$k][$k];
+ for ($i = $k; $i < $this->m; ++$i) {
+ $Q[$i][$j] += $s * $this->QR[$i][$k];
+ }
+ }
+ }
+ }
+
+ return new Matrix($Q);
+ }
+
+ // function getQ()
+
+ /**
+ * Least squares solution of A*X = B.
+ *
+ * @param Matrix $B a Matrix with as many rows as A and any number of columns
+ *
+ * @return Matrix matrix that minimizes the two norm of Q*R*X-B
+ */
+ public function solve($B)
+ {
+ if ($B->getRowDimension() == $this->m) {
+ if ($this->isFullRank()) {
+ // Copy right hand side
+ $nx = $B->getColumnDimension();
+ $X = $B->getArrayCopy();
+ // Compute Y = transpose(Q)*B
+ for ($k = 0; $k < $this->n; ++$k) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $s = 0.0;
+ for ($i = $k; $i < $this->m; ++$i) {
+ $s += $this->QR[$i][$k] * $X[$i][$j];
+ }
+ $s = -$s / $this->QR[$k][$k];
+ for ($i = $k; $i < $this->m; ++$i) {
+ $X[$i][$j] += $s * $this->QR[$i][$k];
+ }
+ }
+ }
+ // Solve R*X = Y;
+ for ($k = $this->n - 1; $k >= 0; --$k) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X[$k][$j] /= $this->Rdiag[$k];
+ }
+ for ($i = 0; $i < $k; ++$i) {
+ for ($j = 0; $j < $nx; ++$j) {
+ $X[$i][$j] -= $X[$k][$j] * $this->QR[$i][$k];
+ }
+ }
+ }
+ $X = new Matrix($X);
+
+ return $X->getMatrix(0, $this->n - 1, 0, $nx);
+ }
+
+ throw new CalculationException(self::MATRIX_RANK_EXCEPTION);
+ }
+
+ throw new CalculationException(Matrix::MATRIX_DIMENSION_EXCEPTION);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php
new file mode 100644
index 0000000..a86bf08
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/JAMA/SingularValueDecomposition.php
@@ -0,0 +1,528 @@
+= n, the singular value decomposition is
+ * an m-by-n orthogonal matrix U, an n-by-n diagonal matrix S, and
+ * an n-by-n orthogonal matrix V so that A = U*S*V'.
+ *
+ * The singular values, sigma[$k] = S[$k][$k], are ordered so that
+ * sigma[0] >= sigma[1] >= ... >= sigma[n-1].
+ *
+ * The singular value decompostion always exists, so the constructor will
+ * never fail. The matrix condition number and the effective numerical
+ * rank can be computed from this decomposition.
+ *
+ * @author Paul Meagher
+ *
+ * @version 1.1
+ */
+class SingularValueDecomposition
+{
+ /**
+ * Internal storage of U.
+ *
+ * @var array
+ */
+ private $U = [];
+
+ /**
+ * Internal storage of V.
+ *
+ * @var array
+ */
+ private $V = [];
+
+ /**
+ * Internal storage of singular values.
+ *
+ * @var array
+ */
+ private $s = [];
+
+ /**
+ * Row dimension.
+ *
+ * @var int
+ */
+ private $m;
+
+ /**
+ * Column dimension.
+ *
+ * @var int
+ */
+ private $n;
+
+ /**
+ * Construct the singular value decomposition.
+ *
+ * Derived from LINPACK code.
+ *
+ * @param mixed $Arg Rectangular matrix
+ */
+ public function __construct($Arg)
+ {
+ // Initialize.
+ $A = $Arg->getArrayCopy();
+ $this->m = $Arg->getRowDimension();
+ $this->n = $Arg->getColumnDimension();
+ $nu = min($this->m, $this->n);
+ $e = [];
+ $work = [];
+ $wantu = true;
+ $wantv = true;
+ $nct = min($this->m - 1, $this->n);
+ $nrt = max(0, min($this->n - 2, $this->m));
+
+ // Reduce A to bidiagonal form, storing the diagonal elements
+ // in s and the super-diagonal elements in e.
+ $kMax = max($nct, $nrt);
+ for ($k = 0; $k < $kMax; ++$k) {
+ if ($k < $nct) {
+ // Compute the transformation for the k-th column and
+ // place the k-th diagonal in s[$k].
+ // Compute 2-norm of k-th column without under/overflow.
+ $this->s[$k] = 0;
+ for ($i = $k; $i < $this->m; ++$i) {
+ $this->s[$k] = hypo($this->s[$k], $A[$i][$k]);
+ }
+ if ($this->s[$k] != 0.0) {
+ if ($A[$k][$k] < 0.0) {
+ $this->s[$k] = -$this->s[$k];
+ }
+ for ($i = $k; $i < $this->m; ++$i) {
+ $A[$i][$k] /= $this->s[$k];
+ }
+ $A[$k][$k] += 1.0;
+ }
+ $this->s[$k] = -$this->s[$k];
+ }
+
+ for ($j = $k + 1; $j < $this->n; ++$j) {
+ if (($k < $nct) & ($this->s[$k] != 0.0)) {
+ // Apply the transformation.
+ $t = 0;
+ for ($i = $k; $i < $this->m; ++$i) {
+ $t += $A[$i][$k] * $A[$i][$j];
+ }
+ $t = -$t / $A[$k][$k];
+ for ($i = $k; $i < $this->m; ++$i) {
+ $A[$i][$j] += $t * $A[$i][$k];
+ }
+ // Place the k-th row of A into e for the
+ // subsequent calculation of the row transformation.
+ $e[$j] = $A[$k][$j];
+ }
+ }
+
+ if ($wantu && ($k < $nct)) {
+ // Place the transformation in U for subsequent back
+ // multiplication.
+ for ($i = $k; $i < $this->m; ++$i) {
+ $this->U[$i][$k] = $A[$i][$k];
+ }
+ }
+
+ if ($k < $nrt) {
+ // Compute the k-th row transformation and place the
+ // k-th super-diagonal in e[$k].
+ // Compute 2-norm without under/overflow.
+ $e[$k] = 0;
+ for ($i = $k + 1; $i < $this->n; ++$i) {
+ $e[$k] = hypo($e[$k], $e[$i]);
+ }
+ if ($e[$k] != 0.0) {
+ if ($e[$k + 1] < 0.0) {
+ $e[$k] = -$e[$k];
+ }
+ for ($i = $k + 1; $i < $this->n; ++$i) {
+ $e[$i] /= $e[$k];
+ }
+ $e[$k + 1] += 1.0;
+ }
+ $e[$k] = -$e[$k];
+ if (($k + 1 < $this->m) && ($e[$k] != 0.0)) {
+ // Apply the transformation.
+ for ($i = $k + 1; $i < $this->m; ++$i) {
+ $work[$i] = 0.0;
+ }
+ for ($j = $k + 1; $j < $this->n; ++$j) {
+ for ($i = $k + 1; $i < $this->m; ++$i) {
+ $work[$i] += $e[$j] * $A[$i][$j];
+ }
+ }
+ for ($j = $k + 1; $j < $this->n; ++$j) {
+ $t = -$e[$j] / $e[$k + 1];
+ for ($i = $k + 1; $i < $this->m; ++$i) {
+ $A[$i][$j] += $t * $work[$i];
+ }
+ }
+ }
+ if ($wantv) {
+ // Place the transformation in V for subsequent
+ // back multiplication.
+ for ($i = $k + 1; $i < $this->n; ++$i) {
+ $this->V[$i][$k] = $e[$i];
+ }
+ }
+ }
+ }
+
+ // Set up the final bidiagonal matrix or order p.
+ $p = min($this->n, $this->m + 1);
+ if ($nct < $this->n) {
+ $this->s[$nct] = $A[$nct][$nct];
+ }
+ if ($this->m < $p) {
+ $this->s[$p - 1] = 0.0;
+ }
+ if ($nrt + 1 < $p) {
+ $e[$nrt] = $A[$nrt][$p - 1];
+ }
+ $e[$p - 1] = 0.0;
+ // If required, generate U.
+ if ($wantu) {
+ for ($j = $nct; $j < $nu; ++$j) {
+ for ($i = 0; $i < $this->m; ++$i) {
+ $this->U[$i][$j] = 0.0;
+ }
+ $this->U[$j][$j] = 1.0;
+ }
+ for ($k = $nct - 1; $k >= 0; --$k) {
+ if ($this->s[$k] != 0.0) {
+ for ($j = $k + 1; $j < $nu; ++$j) {
+ $t = 0;
+ for ($i = $k; $i < $this->m; ++$i) {
+ $t += $this->U[$i][$k] * $this->U[$i][$j];
+ }
+ $t = -$t / $this->U[$k][$k];
+ for ($i = $k; $i < $this->m; ++$i) {
+ $this->U[$i][$j] += $t * $this->U[$i][$k];
+ }
+ }
+ for ($i = $k; $i < $this->m; ++$i) {
+ $this->U[$i][$k] = -$this->U[$i][$k];
+ }
+ $this->U[$k][$k] = 1.0 + $this->U[$k][$k];
+ for ($i = 0; $i < $k - 1; ++$i) {
+ $this->U[$i][$k] = 0.0;
+ }
+ } else {
+ for ($i = 0; $i < $this->m; ++$i) {
+ $this->U[$i][$k] = 0.0;
+ }
+ $this->U[$k][$k] = 1.0;
+ }
+ }
+ }
+
+ // If required, generate V.
+ if ($wantv) {
+ for ($k = $this->n - 1; $k >= 0; --$k) {
+ if (($k < $nrt) && ($e[$k] != 0.0)) {
+ for ($j = $k + 1; $j < $nu; ++$j) {
+ $t = 0;
+ for ($i = $k + 1; $i < $this->n; ++$i) {
+ $t += $this->V[$i][$k] * $this->V[$i][$j];
+ }
+ $t = -$t / $this->V[$k + 1][$k];
+ for ($i = $k + 1; $i < $this->n; ++$i) {
+ $this->V[$i][$j] += $t * $this->V[$i][$k];
+ }
+ }
+ }
+ for ($i = 0; $i < $this->n; ++$i) {
+ $this->V[$i][$k] = 0.0;
+ }
+ $this->V[$k][$k] = 1.0;
+ }
+ }
+
+ // Main iteration loop for the singular values.
+ $pp = $p - 1;
+ $iter = 0;
+ $eps = 2.0 ** (-52.0);
+
+ while ($p > 0) {
+ // Here is where a test for too many iterations would go.
+ // This section of the program inspects for negligible
+ // elements in the s and e arrays. On completion the
+ // variables kase and k are set as follows:
+ // kase = 1 if s(p) and e[k-1] are negligible and k= -1; --$k) {
+ if ($k == -1) {
+ break;
+ }
+ if (abs($e[$k]) <= $eps * (abs($this->s[$k]) + abs($this->s[$k + 1]))) {
+ $e[$k] = 0.0;
+
+ break;
+ }
+ }
+ if ($k == $p - 2) {
+ $kase = 4;
+ } else {
+ for ($ks = $p - 1; $ks >= $k; --$ks) {
+ if ($ks == $k) {
+ break;
+ }
+ $t = ($ks != $p ? abs($e[$ks]) : 0.) + ($ks != $k + 1 ? abs($e[$ks - 1]) : 0.);
+ if (abs($this->s[$ks]) <= $eps * $t) {
+ $this->s[$ks] = 0.0;
+
+ break;
+ }
+ }
+ if ($ks == $k) {
+ $kase = 3;
+ } elseif ($ks == $p - 1) {
+ $kase = 1;
+ } else {
+ $kase = 2;
+ $k = $ks;
+ }
+ }
+ ++$k;
+
+ // Perform the task indicated by kase.
+ switch ($kase) {
+ // Deflate negligible s(p).
+ case 1:
+ $f = $e[$p - 2];
+ $e[$p - 2] = 0.0;
+ for ($j = $p - 2; $j >= $k; --$j) {
+ $t = hypo($this->s[$j], $f);
+ $cs = $this->s[$j] / $t;
+ $sn = $f / $t;
+ $this->s[$j] = $t;
+ if ($j != $k) {
+ $f = -$sn * $e[$j - 1];
+ $e[$j - 1] = $cs * $e[$j - 1];
+ }
+ if ($wantv) {
+ for ($i = 0; $i < $this->n; ++$i) {
+ $t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$p - 1];
+ $this->V[$i][$p - 1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$p - 1];
+ $this->V[$i][$j] = $t;
+ }
+ }
+ }
+
+ break;
+ // Split at negligible s(k).
+ case 2:
+ $f = $e[$k - 1];
+ $e[$k - 1] = 0.0;
+ for ($j = $k; $j < $p; ++$j) {
+ $t = hypo($this->s[$j], $f);
+ $cs = $this->s[$j] / $t;
+ $sn = $f / $t;
+ $this->s[$j] = $t;
+ $f = -$sn * $e[$j];
+ $e[$j] = $cs * $e[$j];
+ if ($wantu) {
+ for ($i = 0; $i < $this->m; ++$i) {
+ $t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$k - 1];
+ $this->U[$i][$k - 1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$k - 1];
+ $this->U[$i][$j] = $t;
+ }
+ }
+ }
+
+ break;
+ // Perform one qr step.
+ case 3:
+ // Calculate the shift.
+ $scale = max(max(max(max(abs($this->s[$p - 1]), abs($this->s[$p - 2])), abs($e[$p - 2])), abs($this->s[$k])), abs($e[$k]));
+ $sp = $this->s[$p - 1] / $scale;
+ $spm1 = $this->s[$p - 2] / $scale;
+ $epm1 = $e[$p - 2] / $scale;
+ $sk = $this->s[$k] / $scale;
+ $ek = $e[$k] / $scale;
+ $b = (($spm1 + $sp) * ($spm1 - $sp) + $epm1 * $epm1) / 2.0;
+ $c = ($sp * $epm1) * ($sp * $epm1);
+ $shift = 0.0;
+ if (($b != 0.0) || ($c != 0.0)) {
+ $shift = sqrt($b * $b + $c);
+ if ($b < 0.0) {
+ $shift = -$shift;
+ }
+ $shift = $c / ($b + $shift);
+ }
+ $f = ($sk + $sp) * ($sk - $sp) + $shift;
+ $g = $sk * $ek;
+ // Chase zeros.
+ for ($j = $k; $j < $p - 1; ++$j) {
+ $t = hypo($f, $g);
+ $cs = $f / $t;
+ $sn = $g / $t;
+ if ($j != $k) {
+ $e[$j - 1] = $t;
+ }
+ $f = $cs * $this->s[$j] + $sn * $e[$j];
+ $e[$j] = $cs * $e[$j] - $sn * $this->s[$j];
+ $g = $sn * $this->s[$j + 1];
+ $this->s[$j + 1] = $cs * $this->s[$j + 1];
+ if ($wantv) {
+ for ($i = 0; $i < $this->n; ++$i) {
+ $t = $cs * $this->V[$i][$j] + $sn * $this->V[$i][$j + 1];
+ $this->V[$i][$j + 1] = -$sn * $this->V[$i][$j] + $cs * $this->V[$i][$j + 1];
+ $this->V[$i][$j] = $t;
+ }
+ }
+ $t = hypo($f, $g);
+ $cs = $f / $t;
+ $sn = $g / $t;
+ $this->s[$j] = $t;
+ $f = $cs * $e[$j] + $sn * $this->s[$j + 1];
+ $this->s[$j + 1] = -$sn * $e[$j] + $cs * $this->s[$j + 1];
+ $g = $sn * $e[$j + 1];
+ $e[$j + 1] = $cs * $e[$j + 1];
+ if ($wantu && ($j < $this->m - 1)) {
+ for ($i = 0; $i < $this->m; ++$i) {
+ $t = $cs * $this->U[$i][$j] + $sn * $this->U[$i][$j + 1];
+ $this->U[$i][$j + 1] = -$sn * $this->U[$i][$j] + $cs * $this->U[$i][$j + 1];
+ $this->U[$i][$j] = $t;
+ }
+ }
+ }
+ $e[$p - 2] = $f;
+ $iter = $iter + 1;
+
+ break;
+ // Convergence.
+ case 4:
+ // Make the singular values positive.
+ if ($this->s[$k] <= 0.0) {
+ $this->s[$k] = ($this->s[$k] < 0.0 ? -$this->s[$k] : 0.0);
+ if ($wantv) {
+ for ($i = 0; $i <= $pp; ++$i) {
+ $this->V[$i][$k] = -$this->V[$i][$k];
+ }
+ }
+ }
+ // Order the singular values.
+ while ($k < $pp) {
+ if ($this->s[$k] >= $this->s[$k + 1]) {
+ break;
+ }
+ $t = $this->s[$k];
+ $this->s[$k] = $this->s[$k + 1];
+ $this->s[$k + 1] = $t;
+ if ($wantv && ($k < $this->n - 1)) {
+ for ($i = 0; $i < $this->n; ++$i) {
+ $t = $this->V[$i][$k + 1];
+ $this->V[$i][$k + 1] = $this->V[$i][$k];
+ $this->V[$i][$k] = $t;
+ }
+ }
+ if ($wantu && ($k < $this->m - 1)) {
+ for ($i = 0; $i < $this->m; ++$i) {
+ $t = $this->U[$i][$k + 1];
+ $this->U[$i][$k + 1] = $this->U[$i][$k];
+ $this->U[$i][$k] = $t;
+ }
+ }
+ ++$k;
+ }
+ $iter = 0;
+ --$p;
+
+ break;
+ } // end switch
+ } // end while
+ }
+
+ /**
+ * Return the left singular vectors.
+ *
+ * @return Matrix U
+ */
+ public function getU()
+ {
+ return new Matrix($this->U, $this->m, min($this->m + 1, $this->n));
+ }
+
+ /**
+ * Return the right singular vectors.
+ *
+ * @return Matrix V
+ */
+ public function getV()
+ {
+ return new Matrix($this->V);
+ }
+
+ /**
+ * Return the one-dimensional array of singular values.
+ *
+ * @return array diagonal of S
+ */
+ public function getSingularValues()
+ {
+ return $this->s;
+ }
+
+ /**
+ * Return the diagonal matrix of singular values.
+ *
+ * @return Matrix S
+ */
+ public function getS()
+ {
+ for ($i = 0; $i < $this->n; ++$i) {
+ for ($j = 0; $j < $this->n; ++$j) {
+ $S[$i][$j] = 0.0;
+ }
+ $S[$i][$i] = $this->s[$i];
+ }
+
+ return new Matrix($S);
+ }
+
+ /**
+ * Two norm.
+ *
+ * @return float max(S)
+ */
+ public function norm2()
+ {
+ return $this->s[0];
+ }
+
+ /**
+ * Two norm condition number.
+ *
+ * @return float max(S)/min(S)
+ */
+ public function cond()
+ {
+ return $this->s[0] / $this->s[min($this->m, $this->n) - 1];
+ }
+
+ /**
+ * Effective numerical matrix rank.
+ *
+ * @return int Number of nonnegligible singular values
+ */
+ public function rank()
+ {
+ $eps = 2.0 ** (-52.0);
+ $tol = max($this->m, $this->n) * $this->s[0] * $eps;
+ $r = 0;
+ $iMax = count($this->s);
+ for ($i = 0; $i < $iMax; ++$i) {
+ if ($this->s[$i] > $tol) {
+ ++$r;
+ }
+ }
+
+ return $r;
+ }
+}
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..8e7c92b
--- /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..1e1fedd
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php
@@ -0,0 +1,566 @@
+ |
+// | Based on OLE::Storage_Lite by Kawai, Takanori |
+// +----------------------------------------------------------------------+
+//
+
+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 $file
+ *
+ * @return bool true on success, PEAR_Error on failure
+ */
+ public function read($file)
+ {
+ $fh = fopen($file, 'rb');
+ if (!$fh) {
+ throw new ReaderException("Can't open file $file");
+ }
+ $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;
+ $instanceId = end(array_keys($GLOBALS['_OLE_INSTANCES']));
+
+ $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 $fh file handle
+ *
+ * @return int
+ */
+ private static function readInt1($fh)
+ {
+ [, $tmp] = unpack('c', fread($fh, 1));
+
+ return $tmp;
+ }
+
+ /**
+ * Reads an unsigned short (2 octets).
+ *
+ * @param resource $fh file handle
+ *
+ * @return int
+ */
+ private static function readInt2($fh)
+ {
+ [, $tmp] = unpack('v', fread($fh, 2));
+
+ return $tmp;
+ }
+
+ /**
+ * Reads an unsigned long (4 octets).
+ *
+ * @param resource $fh file handle
+ *
+ * @return int
+ */
+ private static function readInt4($fh)
+ {
+ [, $tmp] = unpack('V', fread($fh, 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:
+ break;
+ }
+ 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 int $date A timestamp
+ *
+ * @return string The string for the OLE container
+ */
+ public static function localDateToOLE($date)
+ {
+ if (!isset($date)) {
+ return "\x00\x00\x00\x00\x00\x00\x00\x00";
+ }
+
+ // factor used for separating numbers into 4 bytes parts
+ $factor = 2 ** 32;
+
+ // days from 1-1-1601 until the beggining of UNIX era
+ $days = 134774;
+ // calculate seconds
+ $big_date = $days * 24 * 3600 + mktime((int) date('H', $date), (int) date('i', $date), (int) date('s', $date), (int) date('m', $date), (int) date('d', $date), (int) date('Y', $date));
+ // multiply just to make MS happy
+ $big_date *= 10000000;
+
+ $high_part = floor($big_date / $factor);
+ // lower 4 bytes
+ $low_part = floor((($big_date / $factor) - $high_part) * $factor);
+
+ // Make HEX string
+ $res = '';
+
+ for ($i = 0; $i < 4; ++$i) {
+ $hex = $low_part % 0x100;
+ $res .= pack('c', $hex);
+ $low_part /= 0x100;
+ }
+ for ($i = 0; $i < 4; ++$i) {
+ $hex = $high_part % 0x100;
+ $res .= pack('c', $hex);
+ $high_part /= 0x100;
+ }
+
+ return $res;
+ }
+
+ /**
+ * Returns a timestamp from an OLE container's date.
+ *
+ * @param string $oleTimestamp A binary string with the encoded date
+ *
+ * @return 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);
+
+ $iTimestamp = (int) $unixTimestamp;
+
+ // Overflow conditions can't happen on 64-bit system
+ return ($iTimestamp == $unixTimestamp) ? $iTimestamp : ($unixTimestamp >= 0.0 ? PHP_INT_MAX : PHP_INT_MIN);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php
new file mode 100644
index 0000000..7b98370
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php
@@ -0,0 +1,196 @@
+params);
+ if (!isset($this->params['oleInstanceId'], $this->params['blockId'], $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']])) {
+ if ($options & STREAM_REPORT_ERRORS) {
+ trigger_error('OLE stream not found', E_USER_WARNING);
+ }
+
+ return false;
+ }
+ $this->ole = $GLOBALS['_OLE_INSTANCES'][$this->params['oleInstanceId']];
+
+ $blockId = $this->params['blockId'];
+ $this->data = '';
+ if (isset($this->params['size']) && $this->params['size'] < $this->ole->bigBlockThreshold && $blockId != $this->ole->root->startBlock) {
+ // Block id refers to small blocks
+ $rootPos = $this->ole->getBlockOffset($this->ole->root->startBlock);
+ while ($blockId != -2) {
+ $pos = $rootPos + $blockId * $this->ole->bigBlockSize;
+ $blockId = $this->ole->sbat[$blockId];
+ fseek($this->ole->_file_handle, $pos);
+ $this->data .= fread($this->ole->_file_handle, $this->ole->bigBlockSize);
+ }
+ } else {
+ // Block id refers to big blocks
+ while ($blockId != -2) {
+ $pos = $this->ole->getBlockOffset($blockId);
+ fseek($this->ole->_file_handle, $pos);
+ $this->data .= fread($this->ole->_file_handle, $this->ole->bigBlockSize);
+ $blockId = $this->ole->bbat[$blockId];
+ }
+ }
+ if (isset($this->params['size'])) {
+ $this->data = substr($this->data, 0, $this->params['size']);
+ }
+
+ if ($options & STREAM_USE_PATH) {
+ $openedPath = $path;
+ }
+
+ return true;
+ }
+
+ /**
+ * Implements support for fclose().
+ */
+ public function stream_close(): void // @codingStandardsIgnoreLine
+ {
+ $this->ole = null;
+ unset($GLOBALS['_OLE_INSTANCES']);
+ }
+
+ /**
+ * Implements support for fread(), fgets() etc.
+ *
+ * @param int $count maximum number of bytes to read
+ *
+ * @return string
+ */
+ public function stream_read($count) // @codingStandardsIgnoreLine
+ {
+ if ($this->stream_eof()) {
+ return false;
+ }
+ $s = substr($this->data, $this->pos, $count);
+ $this->pos += $count;
+
+ return $s;
+ }
+
+ /**
+ * Implements support for feof().
+ *
+ * @return bool TRUE if the file pointer is at EOF; otherwise FALSE
+ */
+ public function stream_eof() // @codingStandardsIgnoreLine
+ {
+ return $this->pos >= strlen($this->data);
+ }
+
+ /**
+ * Returns the position of the file pointer, i.e. its offset into the file
+ * stream. Implements support for ftell().
+ *
+ * @return int
+ */
+ public function stream_tell() // @codingStandardsIgnoreLine
+ {
+ return $this->pos;
+ }
+
+ /**
+ * Implements support for fseek().
+ *
+ * @param int $offset byte offset
+ * @param int $whence SEEK_SET, SEEK_CUR or SEEK_END
+ *
+ * @return bool
+ */
+ public function stream_seek($offset, $whence) // @codingStandardsIgnoreLine
+ {
+ if ($whence == SEEK_SET && $offset >= 0) {
+ $this->pos = $offset;
+ } elseif ($whence == SEEK_CUR && -$offset <= $this->pos) {
+ $this->pos += $offset;
+ } elseif ($whence == SEEK_END && -$offset <= count($this->data)) {
+ $this->pos = strlen($this->data) + $offset;
+ } else {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Implements support for fstat(). Currently the only supported field is
+ * "size".
+ *
+ * @return array
+ */
+ public function stream_stat() // @codingStandardsIgnoreLine
+ {
+ return [
+ 'size' => strlen($this->data),
+ ];
+ }
+
+ // Methods used by stream_wrapper_register() that are not implemented:
+ // bool stream_flush ( void )
+ // int stream_write ( string data )
+ // bool rename ( string path_from, string path_to )
+ // bool mkdir ( string path, int mode, int options )
+ // bool rmdir ( string path, int options )
+ // bool dir_opendir ( string path, int options )
+ // array url_stat ( string path, int flags )
+ // string dir_readdir ( void )
+ // bool dir_rewinddir ( void )
+ // bool dir_closedir ( void )
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php
new file mode 100644
index 0000000..1832521
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php
@@ -0,0 +1,237 @@
+ |
+// | Based on OLE::Storage_Lite by Kawai, Takanori |
+// +----------------------------------------------------------------------+
+//
+use PhpOffice\PhpSpreadsheet\Shared\OLE;
+
+/**
+ * Class for creating PPS's for OLE containers.
+ *
+ * @author Xavier Noguer
+ */
+class PPS
+{
+ /**
+ * The PPS index.
+ *
+ * @var int
+ */
+ public $No;
+
+ /**
+ * The PPS name (in Unicode).
+ *
+ * @var string
+ */
+ public $Name;
+
+ /**
+ * The PPS type. Dir, Root or File.
+ *
+ * @var int
+ */
+ public $Type;
+
+ /**
+ * The index of the previous PPS.
+ *
+ * @var int
+ */
+ public $PrevPps;
+
+ /**
+ * The index of the next PPS.
+ *
+ * @var int
+ */
+ public $NextPps;
+
+ /**
+ * The index of it's first child if this is a Dir or Root PPS.
+ *
+ * @var int
+ */
+ public $DirPps;
+
+ /**
+ * A timestamp.
+ *
+ * @var int
+ */
+ public $Time1st;
+
+ /**
+ * A timestamp.
+ *
+ * @var int
+ */
+ public $Time2nd;
+
+ /**
+ * Starting block (small or big) for this PPS's data inside the container.
+ *
+ * @var int
+ */
+ public $startBlock;
+
+ /**
+ * The size of the PPS's data (in bytes).
+ *
+ * @var int
+ */
+ public $Size;
+
+ /**
+ * The PPS's data (only used if it's not using a temporary file).
+ *
+ * @var string
+ */
+ public $_data;
+
+ /**
+ * Array of child PPS's (only used by Root and Dir PPS's).
+ *
+ * @var array
+ */
+ public $children = [];
+
+ /**
+ * Pointer to OLE container.
+ *
+ * @var OLE
+ */
+ public $ole;
+
+ /**
+ * The constructor.
+ *
+ * @param int $No The PPS index
+ * @param string $name The PPS name
+ * @param int $type The PPS type. Dir, Root or File
+ * @param int $prev The index of the previous PPS
+ * @param int $next The index of the next PPS
+ * @param int $dir The index of it's first child if this is a Dir or Root PPS
+ * @param int $time_1st A timestamp
+ * @param int $time_2nd A timestamp
+ * @param string $data The (usually binary) source data of the PPS
+ * @param array $children Array containing children PPS for this PPS
+ */
+ public function __construct($No, $name, $type, $prev, $next, $dir, $time_1st, $time_2nd, $data, $children)
+ {
+ $this->No = $No;
+ $this->Name = $name;
+ $this->Type = $type;
+ $this->PrevPps = $prev;
+ $this->NextPps = $next;
+ $this->DirPps = $dir;
+ $this->Time1st = $time_1st;
+ $this->Time2nd = $time_2nd;
+ $this->_data = $data;
+ $this->children = $children;
+ if ($data != '') {
+ $this->Size = strlen($data);
+ } else {
+ $this->Size = 0;
+ }
+ }
+
+ /**
+ * Returns the amount of data saved for this PPS.
+ *
+ * @return int The amount of data (in bytes)
+ */
+ public function getDataLen()
+ {
+ if (!isset($this->_data)) {
+ return 0;
+ }
+
+ return strlen($this->_data);
+ }
+
+ /**
+ * Returns a string with the PPS's WK (What is a WK?).
+ *
+ * @return string The binary string
+ */
+ public function getPpsWk()
+ {
+ $ret = str_pad($this->Name, 64, "\x00");
+
+ $ret .= pack('v', strlen($this->Name) + 2) // 66
+ . pack('c', $this->Type) // 67
+ . pack('c', 0x00) //UK // 68
+ . pack('V', $this->PrevPps) //Prev // 72
+ . pack('V', $this->NextPps) //Next // 76
+ . pack('V', $this->DirPps) //Dir // 80
+ . "\x00\x09\x02\x00" // 84
+ . "\x00\x00\x00\x00" // 88
+ . "\xc0\x00\x00\x00" // 92
+ . "\x00\x00\x00\x46" // 96 // Seems to be ok only for Root
+ . "\x00\x00\x00\x00" // 100
+ . OLE::localDateToOLE($this->Time1st) // 108
+ . OLE::localDateToOLE($this->Time2nd) // 116
+ . pack('V', isset($this->startBlock) ? $this->startBlock : 0) // 120
+ . pack('V', $this->Size) // 124
+ . pack('V', 0); // 128
+
+ return $ret;
+ }
+
+ /**
+ * Updates index and pointers to previous, next and children PPS's for this
+ * PPS. I don't think it'll work with Dir PPS's.
+ *
+ * @param array &$raList Reference to the array of PPS's for the whole OLE
+ * container
+ * @param mixed $to_save
+ * @param mixed $depth
+ *
+ * @return int The index for this PPS
+ */
+ public static function savePpsSetPnt(&$raList, $to_save, $depth = 0)
+ {
+ if (!is_array($to_save) || (empty($to_save))) {
+ return 0xFFFFFFFF;
+ } elseif (count($to_save) == 1) {
+ $cnt = count($raList);
+ // If the first entry, it's the root... Don't clone it!
+ $raList[$cnt] = ($depth == 0) ? $to_save[0] : clone $to_save[0];
+ $raList[$cnt]->No = $cnt;
+ $raList[$cnt]->PrevPps = 0xFFFFFFFF;
+ $raList[$cnt]->NextPps = 0xFFFFFFFF;
+ $raList[$cnt]->DirPps = self::savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++);
+ } else {
+ $iPos = floor(count($to_save) / 2);
+ $aPrev = array_slice($to_save, 0, $iPos);
+ $aNext = array_slice($to_save, $iPos + 1);
+ $cnt = count($raList);
+ // If the first entry, it's the root... Don't clone it!
+ $raList[$cnt] = ($depth == 0) ? $to_save[$iPos] : clone $to_save[$iPos];
+ $raList[$cnt]->No = $cnt;
+ $raList[$cnt]->PrevPps = self::savePpsSetPnt($raList, $aPrev, $depth++);
+ $raList[$cnt]->NextPps = self::savePpsSetPnt($raList, $aNext, $depth++);
+ $raList[$cnt]->DirPps = self::savePpsSetPnt($raList, @$raList[$cnt]->children, $depth++);
+ }
+
+ return $cnt;
+ }
+}
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..006da1f
--- /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/OLE/PPS/Root.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php
new file mode 100644
index 0000000..0be80e9
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php
@@ -0,0 +1,426 @@
+ |
+// | Based on OLE::Storage_Lite by Kawai, Takanori |
+// +----------------------------------------------------------------------+
+//
+use PhpOffice\PhpSpreadsheet\Shared\OLE;
+use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
+
+/**
+ * Class for creating Root PPS's for OLE containers.
+ *
+ * @author Xavier Noguer
+ */
+class Root extends PPS
+{
+ /**
+ * @var resource
+ */
+ private $fileHandle;
+
+ /**
+ * @var int
+ */
+ private $smallBlockSize;
+
+ /**
+ * @var int
+ */
+ private $bigBlockSize;
+
+ /**
+ * @param int $time_1st A timestamp
+ * @param int $time_2nd A timestamp
+ * @param File[] $raChild
+ */
+ public function __construct($time_1st, $time_2nd, $raChild)
+ {
+ parent::__construct(null, OLE::ascToUcs('Root Entry'), OLE::OLE_PPS_TYPE_ROOT, null, null, null, $time_1st, $time_2nd, null, $raChild);
+ }
+
+ /**
+ * Method for saving the whole OLE container (including files).
+ * In fact, if called with an empty argument (or '-'), it saves to a
+ * temporary file and then outputs it's contents to stdout.
+ * If a resource pointer to a stream created by fopen() is passed
+ * it will be used, but you have to close such stream by yourself.
+ *
+ * @param resource $fileHandle the name of the file or stream where to save the OLE container
+ *
+ * @return bool true on success
+ */
+ public function save($fileHandle)
+ {
+ $this->fileHandle = $fileHandle;
+
+ // Initial Setting for saving
+ $this->bigBlockSize = 2 ** (
+ (isset($this->bigBlockSize)) ? self::adjust2($this->bigBlockSize) : 9
+ );
+ $this->smallBlockSize = 2 ** (
+ (isset($this->smallBlockSize)) ? self::adjust2($this->smallBlockSize) : 6
+ );
+
+ // Make an array of PPS's (for Save)
+ $aList = [];
+ PPS::savePpsSetPnt($aList, [$this]);
+ // calculate values for header
+ [$iSBDcnt, $iBBcnt, $iPPScnt] = $this->calcSize($aList); //, $rhInfo);
+ // Save Header
+ $this->saveHeader($iSBDcnt, $iBBcnt, $iPPScnt);
+
+ // Make Small Data string (write SBD)
+ $this->_data = $this->makeSmallData($aList);
+
+ // Write BB
+ $this->saveBigData($iSBDcnt, $aList);
+ // Write PPS
+ $this->savePps($aList);
+ // Write Big Block Depot and BDList and Adding Header informations
+ $this->saveBbd($iSBDcnt, $iBBcnt, $iPPScnt);
+
+ return true;
+ }
+
+ /**
+ * Calculate some numbers.
+ *
+ * @param array $raList Reference to an array of PPS's
+ *
+ * @return float[] The array of numbers
+ */
+ private function calcSize(&$raList)
+ {
+ // Calculate Basic Setting
+ [$iSBDcnt, $iBBcnt, $iPPScnt] = [0, 0, 0];
+ $iSmallLen = 0;
+ $iSBcnt = 0;
+ $iCount = count($raList);
+ for ($i = 0; $i < $iCount; ++$i) {
+ if ($raList[$i]->Type == OLE::OLE_PPS_TYPE_FILE) {
+ $raList[$i]->Size = $raList[$i]->getDataLen();
+ if ($raList[$i]->Size < OLE::OLE_DATA_SIZE_SMALL) {
+ $iSBcnt += floor($raList[$i]->Size / $this->smallBlockSize)
+ + (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0);
+ } else {
+ $iBBcnt += (floor($raList[$i]->Size / $this->bigBlockSize) +
+ (($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0));
+ }
+ }
+ }
+ $iSmallLen = $iSBcnt * $this->smallBlockSize;
+ $iSlCnt = floor($this->bigBlockSize / OLE::OLE_LONG_INT_SIZE);
+ $iSBDcnt = floor($iSBcnt / $iSlCnt) + (($iSBcnt % $iSlCnt) ? 1 : 0);
+ $iBBcnt += (floor($iSmallLen / $this->bigBlockSize) +
+ (($iSmallLen % $this->bigBlockSize) ? 1 : 0));
+ $iCnt = count($raList);
+ $iBdCnt = $this->bigBlockSize / OLE::OLE_PPS_SIZE;
+ $iPPScnt = (floor($iCnt / $iBdCnt) + (($iCnt % $iBdCnt) ? 1 : 0));
+
+ return [$iSBDcnt, $iBBcnt, $iPPScnt];
+ }
+
+ /**
+ * Helper function for caculating a magic value for block sizes.
+ *
+ * @param int $i2 The argument
+ *
+ * @return float
+ *
+ * @see save()
+ */
+ private static function adjust2($i2)
+ {
+ $iWk = log($i2) / log(2);
+
+ return ($iWk > floor($iWk)) ? floor($iWk) + 1 : $iWk;
+ }
+
+ /**
+ * Save OLE header.
+ *
+ * @param int $iSBDcnt
+ * @param int $iBBcnt
+ * @param int $iPPScnt
+ */
+ private function saveHeader($iSBDcnt, $iBBcnt, $iPPScnt): void
+ {
+ $FILE = $this->fileHandle;
+
+ // Calculate Basic Setting
+ $iBlCnt = $this->bigBlockSize / OLE::OLE_LONG_INT_SIZE;
+ $i1stBdL = ($this->bigBlockSize - 0x4C) / OLE::OLE_LONG_INT_SIZE;
+
+ $iBdExL = 0;
+ $iAll = $iBBcnt + $iPPScnt + $iSBDcnt;
+ $iAllW = $iAll;
+ $iBdCntW = floor($iAllW / $iBlCnt) + (($iAllW % $iBlCnt) ? 1 : 0);
+ $iBdCnt = floor(($iAll + $iBdCntW) / $iBlCnt) + ((($iAllW + $iBdCntW) % $iBlCnt) ? 1 : 0);
+
+ // Calculate BD count
+ if ($iBdCnt > $i1stBdL) {
+ while (1) {
+ ++$iBdExL;
+ ++$iAllW;
+ $iBdCntW = floor($iAllW / $iBlCnt) + (($iAllW % $iBlCnt) ? 1 : 0);
+ $iBdCnt = floor(($iAllW + $iBdCntW) / $iBlCnt) + ((($iAllW + $iBdCntW) % $iBlCnt) ? 1 : 0);
+ if ($iBdCnt <= ($iBdExL * $iBlCnt + $i1stBdL)) {
+ break;
+ }
+ }
+ }
+
+ // Save Header
+ fwrite(
+ $FILE,
+ "\xD0\xCF\x11\xE0\xA1\xB1\x1A\xE1"
+ . "\x00\x00\x00\x00"
+ . "\x00\x00\x00\x00"
+ . "\x00\x00\x00\x00"
+ . "\x00\x00\x00\x00"
+ . pack('v', 0x3b)
+ . pack('v', 0x03)
+ . pack('v', -2)
+ . pack('v', 9)
+ . pack('v', 6)
+ . pack('v', 0)
+ . "\x00\x00\x00\x00"
+ . "\x00\x00\x00\x00"
+ . pack('V', $iBdCnt)
+ . pack('V', $iBBcnt + $iSBDcnt) //ROOT START
+ . pack('V', 0)
+ . pack('V', 0x1000)
+ . pack('V', $iSBDcnt ? 0 : -2) //Small Block Depot
+ . pack('V', $iSBDcnt)
+ );
+ // Extra BDList Start, Count
+ if ($iBdCnt < $i1stBdL) {
+ fwrite(
+ $FILE,
+ pack('V', -2) // Extra BDList Start
+ . pack('V', 0)// Extra BDList Count
+ );
+ } else {
+ fwrite($FILE, pack('V', $iAll + $iBdCnt) . pack('V', $iBdExL));
+ }
+
+ // BDList
+ for ($i = 0; $i < $i1stBdL && $i < $iBdCnt; ++$i) {
+ fwrite($FILE, pack('V', $iAll + $i));
+ }
+ if ($i < $i1stBdL) {
+ $jB = $i1stBdL - $i;
+ for ($j = 0; $j < $jB; ++$j) {
+ fwrite($FILE, (pack('V', -1)));
+ }
+ }
+ }
+
+ /**
+ * Saving big data (PPS's with data bigger than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL).
+ *
+ * @param int $iStBlk
+ * @param array &$raList Reference to array of PPS's
+ */
+ private function saveBigData($iStBlk, &$raList): void
+ {
+ $FILE = $this->fileHandle;
+
+ // cycle through PPS's
+ $iCount = count($raList);
+ for ($i = 0; $i < $iCount; ++$i) {
+ if ($raList[$i]->Type != OLE::OLE_PPS_TYPE_DIR) {
+ $raList[$i]->Size = $raList[$i]->getDataLen();
+ if (($raList[$i]->Size >= OLE::OLE_DATA_SIZE_SMALL) || (($raList[$i]->Type == OLE::OLE_PPS_TYPE_ROOT) && isset($raList[$i]->_data))) {
+ fwrite($FILE, $raList[$i]->_data);
+
+ if ($raList[$i]->Size % $this->bigBlockSize) {
+ fwrite($FILE, str_repeat("\x00", $this->bigBlockSize - ($raList[$i]->Size % $this->bigBlockSize)));
+ }
+ // Set For PPS
+ $raList[$i]->startBlock = $iStBlk;
+ $iStBlk +=
+ (floor($raList[$i]->Size / $this->bigBlockSize) +
+ (($raList[$i]->Size % $this->bigBlockSize) ? 1 : 0));
+ }
+ }
+ }
+ }
+
+ /**
+ * get small data (PPS's with data smaller than \PhpOffice\PhpSpreadsheet\Shared\OLE::OLE_DATA_SIZE_SMALL).
+ *
+ * @param array &$raList Reference to array of PPS's
+ *
+ * @return string
+ */
+ private function makeSmallData(&$raList)
+ {
+ $sRes = '';
+ $FILE = $this->fileHandle;
+ $iSmBlk = 0;
+
+ $iCount = count($raList);
+ for ($i = 0; $i < $iCount; ++$i) {
+ // Make SBD, small data string
+ if ($raList[$i]->Type == OLE::OLE_PPS_TYPE_FILE) {
+ if ($raList[$i]->Size <= 0) {
+ continue;
+ }
+ if ($raList[$i]->Size < OLE::OLE_DATA_SIZE_SMALL) {
+ $iSmbCnt = floor($raList[$i]->Size / $this->smallBlockSize)
+ + (($raList[$i]->Size % $this->smallBlockSize) ? 1 : 0);
+ // Add to SBD
+ $jB = $iSmbCnt - 1;
+ for ($j = 0; $j < $jB; ++$j) {
+ fwrite($FILE, pack('V', $j + $iSmBlk + 1));
+ }
+ fwrite($FILE, pack('V', -2));
+
+ // Add to Data String(this will be written for RootEntry)
+ $sRes .= $raList[$i]->_data;
+ if ($raList[$i]->Size % $this->smallBlockSize) {
+ $sRes .= str_repeat("\x00", $this->smallBlockSize - ($raList[$i]->Size % $this->smallBlockSize));
+ }
+ // Set for PPS
+ $raList[$i]->startBlock = $iSmBlk;
+ $iSmBlk += $iSmbCnt;
+ }
+ }
+ }
+ $iSbCnt = floor($this->bigBlockSize / OLE::OLE_LONG_INT_SIZE);
+ if ($iSmBlk % $iSbCnt) {
+ $iB = $iSbCnt - ($iSmBlk % $iSbCnt);
+ for ($i = 0; $i < $iB; ++$i) {
+ fwrite($FILE, pack('V', -1));
+ }
+ }
+
+ return $sRes;
+ }
+
+ /**
+ * Saves all the PPS's WKs.
+ *
+ * @param array $raList Reference to an array with all PPS's
+ */
+ private function savePps(&$raList): void
+ {
+ // Save each PPS WK
+ $iC = count($raList);
+ for ($i = 0; $i < $iC; ++$i) {
+ fwrite($this->fileHandle, $raList[$i]->getPpsWk());
+ }
+ // Adjust for Block
+ $iCnt = count($raList);
+ $iBCnt = $this->bigBlockSize / OLE::OLE_PPS_SIZE;
+ if ($iCnt % $iBCnt) {
+ fwrite($this->fileHandle, str_repeat("\x00", ($iBCnt - ($iCnt % $iBCnt)) * OLE::OLE_PPS_SIZE));
+ }
+ }
+
+ /**
+ * Saving Big Block Depot.
+ *
+ * @param int $iSbdSize
+ * @param int $iBsize
+ * @param int $iPpsCnt
+ */
+ private function saveBbd($iSbdSize, $iBsize, $iPpsCnt): void
+ {
+ $FILE = $this->fileHandle;
+ // Calculate Basic Setting
+ $iBbCnt = $this->bigBlockSize / OLE::OLE_LONG_INT_SIZE;
+ $i1stBdL = ($this->bigBlockSize - 0x4C) / OLE::OLE_LONG_INT_SIZE;
+
+ $iBdExL = 0;
+ $iAll = $iBsize + $iPpsCnt + $iSbdSize;
+ $iAllW = $iAll;
+ $iBdCntW = floor($iAllW / $iBbCnt) + (($iAllW % $iBbCnt) ? 1 : 0);
+ $iBdCnt = floor(($iAll + $iBdCntW) / $iBbCnt) + ((($iAllW + $iBdCntW) % $iBbCnt) ? 1 : 0);
+ // Calculate BD count
+ if ($iBdCnt > $i1stBdL) {
+ while (1) {
+ ++$iBdExL;
+ ++$iAllW;
+ $iBdCntW = floor($iAllW / $iBbCnt) + (($iAllW % $iBbCnt) ? 1 : 0);
+ $iBdCnt = floor(($iAllW + $iBdCntW) / $iBbCnt) + ((($iAllW + $iBdCntW) % $iBbCnt) ? 1 : 0);
+ if ($iBdCnt <= ($iBdExL * $iBbCnt + $i1stBdL)) {
+ break;
+ }
+ }
+ }
+
+ // Making BD
+ // Set for SBD
+ if ($iSbdSize > 0) {
+ for ($i = 0; $i < ($iSbdSize - 1); ++$i) {
+ fwrite($FILE, pack('V', $i + 1));
+ }
+ fwrite($FILE, pack('V', -2));
+ }
+ // Set for B
+ for ($i = 0; $i < ($iBsize - 1); ++$i) {
+ fwrite($FILE, pack('V', $i + $iSbdSize + 1));
+ }
+ fwrite($FILE, pack('V', -2));
+
+ // Set for PPS
+ for ($i = 0; $i < ($iPpsCnt - 1); ++$i) {
+ fwrite($FILE, pack('V', $i + $iSbdSize + $iBsize + 1));
+ }
+ fwrite($FILE, pack('V', -2));
+ // Set for BBD itself ( 0xFFFFFFFD : BBD)
+ for ($i = 0; $i < $iBdCnt; ++$i) {
+ fwrite($FILE, pack('V', 0xFFFFFFFD));
+ }
+ // Set for ExtraBDList
+ for ($i = 0; $i < $iBdExL; ++$i) {
+ fwrite($FILE, pack('V', 0xFFFFFFFC));
+ }
+ // Adjust for Block
+ if (($iAllW + $iBdCnt) % $iBbCnt) {
+ $iBlock = ($iBbCnt - (($iAllW + $iBdCnt) % $iBbCnt));
+ for ($i = 0; $i < $iBlock; ++$i) {
+ fwrite($FILE, pack('V', -1));
+ }
+ }
+ // Extra BDList
+ if ($iBdCnt > $i1stBdL) {
+ $iN = 0;
+ $iNb = 0;
+ for ($i = $i1stBdL; $i < $iBdCnt; $i++, ++$iN) {
+ if ($iN >= ($iBbCnt - 1)) {
+ $iN = 0;
+ ++$iNb;
+ fwrite($FILE, pack('V', $iAll + $iBdCnt + $iNb));
+ }
+ fwrite($FILE, pack('V', $iBsize + $iSbdSize + $iPpsCnt + $i));
+ }
+ if (($iBdCnt - $i1stBdL) % ($iBbCnt - 1)) {
+ $iB = ($iBbCnt - 1) - (($iBdCnt - $i1stBdL) % ($iBbCnt - 1));
+ for ($i = 0; $i < $iB; ++$i) {
+ fwrite($FILE, pack('V', -1));
+ }
+ }
+ fwrite($FILE, pack('V', -2));
+ }
+ }
+}
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..16b00bb
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php
@@ -0,0 +1,350 @@
+data = file_get_contents($pFilename, 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 ' . $pFilename . ' is not recognised as an OLE file');
+ }
+
+ // Get the file data
+ $this->data = file_get_contents($pFilename);
+
+ // 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 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 $bl Sector ID where the stream starts
+ *
+ * @return string Data for standard stream
+ */
+ private function readData($bl)
+ {
+ $block = $bl;
+ $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..894503e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/PasswordHasher.php
@@ -0,0 +1,100 @@
+ '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 Exception('Unsupported password algorithm: ' . $algorithmName);
+ }
+
+ /**
+ * Create a password hash from a given string.
+ *
+ * This method is based on the algorithm provided by
+ * Daniel Rentz of OpenOffice and the PEAR package
+ * Spreadsheet_Excel_Writer by Xavier Noguer .
+ *
+ * @param string $pPassword Password to hash
+ */
+ private static function defaultHashPassword(string $pPassword): string
+ {
+ $password = 0x0000;
+ $charPos = 1; // char position
+
+ // split the plain text password in its component characters
+ $chars = preg_split('//', $pPassword, -1, PREG_SPLIT_NO_EMPTY);
+ foreach ($chars as $char) {
+ $value = ord($char) << $charPos++; // shifted ASCII value
+ $rotated_bits = $value >> 15; // rotated bits beyond bit 15
+ $value &= 0x7fff; // first 15 bits
+ $password ^= ($value | $rotated_bits);
+ }
+
+ $password ^= strlen($pPassword);
+ $password ^= 0xCE4B;
+
+ return strtoupper(dechex($password));
+ }
+
+ /**
+ * 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
+ {
+ $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/StringHelper.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php
new file mode 100644
index 0000000..c60c194
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/StringHelper.php
@@ -0,0 +1,722 @@
+ chr(0),
+ "\x1B 1" => chr(1),
+ "\x1B 2" => chr(2),
+ "\x1B 3" => chr(3),
+ "\x1B 4" => chr(4),
+ "\x1B 5" => chr(5),
+ "\x1B 6" => chr(6),
+ "\x1B 7" => chr(7),
+ "\x1B 8" => chr(8),
+ "\x1B 9" => chr(9),
+ "\x1B :" => chr(10),
+ "\x1B ;" => chr(11),
+ "\x1B <" => chr(12),
+ "\x1B =" => chr(13),
+ "\x1B >" => chr(14),
+ "\x1B ?" => chr(15),
+ "\x1B!0" => chr(16),
+ "\x1B!1" => chr(17),
+ "\x1B!2" => chr(18),
+ "\x1B!3" => chr(19),
+ "\x1B!4" => chr(20),
+ "\x1B!5" => chr(21),
+ "\x1B!6" => chr(22),
+ "\x1B!7" => chr(23),
+ "\x1B!8" => chr(24),
+ "\x1B!9" => chr(25),
+ "\x1B!:" => chr(26),
+ "\x1B!;" => chr(27),
+ "\x1B!<" => chr(28),
+ "\x1B!=" => chr(29),
+ "\x1B!>" => chr(30),
+ "\x1B!?" => chr(31),
+ "\x1B'?" => chr(127),
+ "\x1B(0" => '€', // 128 in CP1252
+ "\x1B(2" => '‚', // 130 in CP1252
+ "\x1B(3" => 'ƒ', // 131 in CP1252
+ "\x1B(4" => '„', // 132 in CP1252
+ "\x1B(5" => '…', // 133 in CP1252
+ "\x1B(6" => '†', // 134 in CP1252
+ "\x1B(7" => '‡', // 135 in CP1252
+ "\x1B(8" => 'ˆ', // 136 in CP1252
+ "\x1B(9" => '‰', // 137 in CP1252
+ "\x1B(:" => 'Š', // 138 in CP1252
+ "\x1B(;" => '‹', // 139 in CP1252
+ "\x1BNj" => 'Œ', // 140 in CP1252
+ "\x1B(>" => 'Ž', // 142 in CP1252
+ "\x1B)1" => '‘', // 145 in CP1252
+ "\x1B)2" => '’', // 146 in CP1252
+ "\x1B)3" => '“', // 147 in CP1252
+ "\x1B)4" => '”', // 148 in CP1252
+ "\x1B)5" => '•', // 149 in CP1252
+ "\x1B)6" => '–', // 150 in CP1252
+ "\x1B)7" => '—', // 151 in CP1252
+ "\x1B)8" => '˜', // 152 in CP1252
+ "\x1B)9" => '™', // 153 in CP1252
+ "\x1B):" => 'š', // 154 in CP1252
+ "\x1B);" => '›', // 155 in CP1252
+ "\x1BNz" => 'œ', // 156 in CP1252
+ "\x1B)>" => 'ž', // 158 in CP1252
+ "\x1B)?" => 'Ÿ', // 159 in CP1252
+ "\x1B*0" => ' ', // 160 in CP1252
+ "\x1BN!" => '¡', // 161 in CP1252
+ "\x1BN\"" => '¢', // 162 in CP1252
+ "\x1BN#" => '£', // 163 in CP1252
+ "\x1BN(" => '¤', // 164 in CP1252
+ "\x1BN%" => '¥', // 165 in CP1252
+ "\x1B*6" => '¦', // 166 in CP1252
+ "\x1BN'" => '§', // 167 in CP1252
+ "\x1BNH " => '¨', // 168 in CP1252
+ "\x1BNS" => '©', // 169 in CP1252
+ "\x1BNc" => 'ª', // 170 in CP1252
+ "\x1BN+" => '«', // 171 in CP1252
+ "\x1B*<" => '¬', // 172 in CP1252
+ "\x1B*=" => '', // 173 in CP1252
+ "\x1BNR" => '®', // 174 in CP1252
+ "\x1B*?" => '¯', // 175 in CP1252
+ "\x1BN0" => '°', // 176 in CP1252
+ "\x1BN1" => '±', // 177 in CP1252
+ "\x1BN2" => '²', // 178 in CP1252
+ "\x1BN3" => '³', // 179 in CP1252
+ "\x1BNB " => '´', // 180 in CP1252
+ "\x1BN5" => 'µ', // 181 in CP1252
+ "\x1BN6" => '¶', // 182 in CP1252
+ "\x1BN7" => '·', // 183 in CP1252
+ "\x1B+8" => '¸', // 184 in CP1252
+ "\x1BNQ" => '¹', // 185 in CP1252
+ "\x1BNk" => 'º', // 186 in CP1252
+ "\x1BN;" => '»', // 187 in CP1252
+ "\x1BN<" => '¼', // 188 in CP1252
+ "\x1BN=" => '½', // 189 in CP1252
+ "\x1BN>" => '¾', // 190 in CP1252
+ "\x1BN?" => '¿', // 191 in CP1252
+ "\x1BNAA" => 'À', // 192 in CP1252
+ "\x1BNBA" => 'Á', // 193 in CP1252
+ "\x1BNCA" => 'Â', // 194 in CP1252
+ "\x1BNDA" => 'Ã', // 195 in CP1252
+ "\x1BNHA" => 'Ä', // 196 in CP1252
+ "\x1BNJA" => 'Å', // 197 in CP1252
+ "\x1BNa" => 'Æ', // 198 in CP1252
+ "\x1BNKC" => 'Ç', // 199 in CP1252
+ "\x1BNAE" => 'È', // 200 in CP1252
+ "\x1BNBE" => 'É', // 201 in CP1252
+ "\x1BNCE" => 'Ê', // 202 in CP1252
+ "\x1BNHE" => 'Ë', // 203 in CP1252
+ "\x1BNAI" => 'Ì', // 204 in CP1252
+ "\x1BNBI" => 'Í', // 205 in CP1252
+ "\x1BNCI" => 'Î', // 206 in CP1252
+ "\x1BNHI" => 'Ï', // 207 in CP1252
+ "\x1BNb" => 'Ð', // 208 in CP1252
+ "\x1BNDN" => 'Ñ', // 209 in CP1252
+ "\x1BNAO" => 'Ò', // 210 in CP1252
+ "\x1BNBO" => 'Ó', // 211 in CP1252
+ "\x1BNCO" => 'Ô', // 212 in CP1252
+ "\x1BNDO" => 'Õ', // 213 in CP1252
+ "\x1BNHO" => 'Ö', // 214 in CP1252
+ "\x1B-7" => '×', // 215 in CP1252
+ "\x1BNi" => 'Ø', // 216 in CP1252
+ "\x1BNAU" => 'Ù', // 217 in CP1252
+ "\x1BNBU" => 'Ú', // 218 in CP1252
+ "\x1BNCU" => 'Û', // 219 in CP1252
+ "\x1BNHU" => 'Ü', // 220 in CP1252
+ "\x1B-=" => 'Ý', // 221 in CP1252
+ "\x1BNl" => 'Þ', // 222 in CP1252
+ "\x1BN{" => 'ß', // 223 in CP1252
+ "\x1BNAa" => 'à', // 224 in CP1252
+ "\x1BNBa" => 'á', // 225 in CP1252
+ "\x1BNCa" => 'â', // 226 in CP1252
+ "\x1BNDa" => 'ã', // 227 in CP1252
+ "\x1BNHa" => 'ä', // 228 in CP1252
+ "\x1BNJa" => 'å', // 229 in CP1252
+ "\x1BNq" => 'æ', // 230 in CP1252
+ "\x1BNKc" => 'ç', // 231 in CP1252
+ "\x1BNAe" => 'è', // 232 in CP1252
+ "\x1BNBe" => 'é', // 233 in CP1252
+ "\x1BNCe" => 'ê', // 234 in CP1252
+ "\x1BNHe" => 'ë', // 235 in CP1252
+ "\x1BNAi" => 'ì', // 236 in CP1252
+ "\x1BNBi" => 'í', // 237 in CP1252
+ "\x1BNCi" => 'î', // 238 in CP1252
+ "\x1BNHi" => 'ï', // 239 in CP1252
+ "\x1BNs" => 'ð', // 240 in CP1252
+ "\x1BNDn" => 'ñ', // 241 in CP1252
+ "\x1BNAo" => 'ò', // 242 in CP1252
+ "\x1BNBo" => 'ó', // 243 in CP1252
+ "\x1BNCo" => 'ô', // 244 in CP1252
+ "\x1BNDo" => 'õ', // 245 in CP1252
+ "\x1BNHo" => 'ö', // 246 in CP1252
+ "\x1B/7" => '÷', // 247 in CP1252
+ "\x1BNy" => 'ø', // 248 in CP1252
+ "\x1BNAu" => 'ù', // 249 in CP1252
+ "\x1BNBu" => 'ú', // 250 in CP1252
+ "\x1BNCu" => 'û', // 251 in CP1252
+ "\x1BNHu" => 'ü', // 252 in CP1252
+ "\x1B/=" => 'ý', // 253 in CP1252
+ "\x1BN|" => 'þ', // 254 in CP1252
+ "\x1BNHy" => 'ÿ', // 255 in CP1252
+ ];
+ }
+
+ /**
+ * Get whether iconv extension is available.
+ *
+ * @return bool
+ */
+ public static function getIsIconvEnabled()
+ {
+ if (isset(self::$isIconvEnabled)) {
+ return self::$isIconvEnabled;
+ }
+
+ // Assume no problems with iconv
+ self::$isIconvEnabled = true;
+
+ // Fail if iconv doesn't exist
+ if (!function_exists('iconv')) {
+ self::$isIconvEnabled = false;
+ } elseif (!@iconv('UTF-8', 'UTF-16LE', 'x')) {
+ // Sometimes iconv is not working, and e.g. iconv('UTF-8', 'UTF-16LE', 'x') just returns false,
+ self::$isIconvEnabled = false;
+ } elseif (defined('PHP_OS') && @stristr(PHP_OS, 'AIX') && defined('ICONV_IMPL') && (@strcasecmp(ICONV_IMPL, 'unknown') == 0) && defined('ICONV_VERSION') && (@strcasecmp(ICONV_VERSION, 'unknown') == 0)) {
+ // CUSTOM: IBM AIX iconv() does not work
+ self::$isIconvEnabled = false;
+ }
+
+ // Deactivate iconv default options if they fail (as seen on IMB i)
+ if (self::$isIconvEnabled && !@iconv('UTF-8', 'UTF-16LE' . self::$iconvOptions, 'x')) {
+ self::$iconvOptions = '';
+ }
+
+ return self::$isIconvEnabled;
+ }
+
+ private static function buildCharacterSets(): void
+ {
+ if (empty(self::$controlCharacters)) {
+ self::buildControlCharacters();
+ }
+
+ if (empty(self::$SYLKCharacters)) {
+ self::buildSYLKCharacters();
+ }
+ }
+
+ /**
+ * Convert from OpenXML escaped control character to PHP control character.
+ *
+ * Excel 2007 team:
+ * ----------------
+ * That's correct, control characters are stored directly in the shared-strings table.
+ * We do encode characters that cannot be represented in XML using the following escape sequence:
+ * _xHHHH_ where H represents a hexadecimal character in the character's value...
+ * So you could end up with something like _x0008_ in a string (either in a cell value ()
+ * element or in the shared string element.
+ *
+ * @param string $value Value to unescape
+ *
+ * @return string
+ */
+ public static function controlCharacterOOXML2PHP($value)
+ {
+ self::buildCharacterSets();
+
+ return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $value);
+ }
+
+ /**
+ * Convert from PHP control character to OpenXML escaped control character.
+ *
+ * Excel 2007 team:
+ * ----------------
+ * That's correct, control characters are stored directly in the shared-strings table.
+ * We do encode characters that cannot be represented in XML using the following escape sequence:
+ * _xHHHH_ where H represents a hexadecimal character in the character's value...
+ * So you could end up with something like _x0008_ in a string (either in a cell value ()
+ * element or in the shared string element.
+ *
+ * @param string $value Value to escape
+ *
+ * @return string
+ */
+ public static function controlCharacterPHP2OOXML($value)
+ {
+ self::buildCharacterSets();
+
+ return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $value);
+ }
+
+ /**
+ * Try to sanitize UTF8, stripping invalid byte sequences. Not perfect. Does not surrogate characters.
+ *
+ * @param string $value
+ *
+ * @return string
+ */
+ public static function sanitizeUTF8($value)
+ {
+ if (self::getIsIconvEnabled()) {
+ $value = @iconv('UTF-8', 'UTF-8', $value);
+
+ return $value;
+ }
+
+ $value = mb_convert_encoding($value, 'UTF-8', 'UTF-8');
+
+ return $value;
+ }
+
+ /**
+ * Check if a string contains UTF8 data.
+ *
+ * @param string $value
+ *
+ * @return bool
+ */
+ public static function isUTF8($value)
+ {
+ return $value === '' || preg_match('/^./su', $value) === 1;
+ }
+
+ /**
+ * Formats a numeric value as a string for output in various output writers forcing
+ * point as decimal separator in case locale is other than English.
+ *
+ * @param mixed $value
+ *
+ * @return string
+ */
+ public static function formatNumber($value)
+ {
+ if (is_float($value)) {
+ return str_replace(',', '.', $value);
+ }
+
+ return (string) $value;
+ }
+
+ /**
+ * Converts a UTF-8 string into BIFF8 Unicode string data (8-bit string length)
+ * Writes the string using uncompressed notation, no rich text, no Asian phonetics
+ * If mbstring extension is not available, ASCII is assumed, and compressed notation is used
+ * although this will give wrong results for non-ASCII strings
+ * see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3.
+ *
+ * @param string $value UTF-8 encoded string
+ * @param mixed[] $arrcRuns Details of rich text runs in $value
+ *
+ * @return string
+ */
+ public static function UTF8toBIFF8UnicodeShort($value, $arrcRuns = [])
+ {
+ // character count
+ $ln = self::countCharacters($value, 'UTF-8');
+ // option flags
+ if (empty($arrcRuns)) {
+ $data = pack('CC', $ln, 0x0001);
+ // characters
+ $data .= self::convertEncoding($value, 'UTF-16LE', 'UTF-8');
+ } else {
+ $data = pack('vC', $ln, 0x09);
+ $data .= pack('v', count($arrcRuns));
+ // characters
+ $data .= self::convertEncoding($value, 'UTF-16LE', 'UTF-8');
+ foreach ($arrcRuns as $cRun) {
+ $data .= pack('v', $cRun['strlen']);
+ $data .= pack('v', $cRun['fontidx']);
+ }
+ }
+
+ return $data;
+ }
+
+ /**
+ * Converts a UTF-8 string into BIFF8 Unicode string data (16-bit string length)
+ * Writes the string using uncompressed notation, no rich text, no Asian phonetics
+ * If mbstring extension is not available, ASCII is assumed, and compressed notation is used
+ * although this will give wrong results for non-ASCII strings
+ * see OpenOffice.org's Documentation of the Microsoft Excel File Format, sect. 2.5.3.
+ *
+ * @param string $value UTF-8 encoded string
+ *
+ * @return string
+ */
+ public static function UTF8toBIFF8UnicodeLong($value)
+ {
+ // character count
+ $ln = self::countCharacters($value, 'UTF-8');
+
+ // characters
+ $chars = self::convertEncoding($value, 'UTF-16LE', 'UTF-8');
+
+ return pack('vC', $ln, 0x0001) . $chars;
+ }
+
+ /**
+ * Convert string from one encoding to another.
+ *
+ * @param string $value
+ * @param string $to Encoding to convert to, e.g. 'UTF-8'
+ * @param string $from Encoding to convert from, e.g. 'UTF-16LE'
+ *
+ * @return string
+ */
+ public static function convertEncoding($value, $to, $from)
+ {
+ if (self::getIsIconvEnabled()) {
+ $result = iconv($from, $to . self::$iconvOptions, $value);
+ if (false !== $result) {
+ return $result;
+ }
+ }
+
+ return mb_convert_encoding($value, $to, $from);
+ }
+
+ /**
+ * Get character count.
+ *
+ * @param string $value
+ * @param string $enc Encoding
+ *
+ * @return int Character count
+ */
+ public static function countCharacters($value, $enc = 'UTF-8')
+ {
+ return mb_strlen($value, $enc);
+ }
+
+ /**
+ * Get a substring of a UTF-8 encoded string.
+ *
+ * @param string $pValue UTF-8 encoded string
+ * @param int $pStart Start offset
+ * @param int $pLength Maximum number of characters in substring
+ *
+ * @return string
+ */
+ public static function substring($pValue, $pStart, $pLength = 0)
+ {
+ return mb_substr($pValue, $pStart, $pLength, 'UTF-8');
+ }
+
+ /**
+ * Convert a UTF-8 encoded string to upper case.
+ *
+ * @param string $pValue UTF-8 encoded string
+ *
+ * @return string
+ */
+ public static function strToUpper($pValue)
+ {
+ return mb_convert_case($pValue, MB_CASE_UPPER, 'UTF-8');
+ }
+
+ /**
+ * Convert a UTF-8 encoded string to lower case.
+ *
+ * @param string $pValue UTF-8 encoded string
+ *
+ * @return string
+ */
+ public static function strToLower($pValue)
+ {
+ return mb_convert_case($pValue, MB_CASE_LOWER, 'UTF-8');
+ }
+
+ /**
+ * Convert a UTF-8 encoded string to title/proper case
+ * (uppercase every first character in each word, lower case all other characters).
+ *
+ * @param string $pValue UTF-8 encoded string
+ *
+ * @return string
+ */
+ public static function strToTitle($pValue)
+ {
+ return mb_convert_case($pValue, MB_CASE_TITLE, 'UTF-8');
+ }
+
+ public static function mbIsUpper($char)
+ {
+ return mb_strtolower($char, 'UTF-8') != $char;
+ }
+
+ public static function mbStrSplit($string)
+ {
+ // Split at all position not after the start: ^
+ // and not before the end: $
+ return preg_split('/(?_calculateFormulaValue($fractionFormula);
+
+ return true;
+ }
+
+ return false;
+ }
+
+ // function convertToNumberIfFraction()
+
+ /**
+ * Get the decimal separator. If it has not yet been set explicitly, try to obtain number
+ * formatting information from locale.
+ *
+ * @return string
+ */
+ public static function getDecimalSeparator()
+ {
+ if (!isset(self::$decimalSeparator)) {
+ $localeconv = localeconv();
+ self::$decimalSeparator = ($localeconv['decimal_point'] != '')
+ ? $localeconv['decimal_point'] : $localeconv['mon_decimal_point'];
+
+ if (self::$decimalSeparator == '') {
+ // Default to .
+ self::$decimalSeparator = '.';
+ }
+ }
+
+ return self::$decimalSeparator;
+ }
+
+ /**
+ * Set the decimal separator. Only used by NumberFormat::toFormattedString()
+ * to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
+ *
+ * @param string $pValue Character for decimal separator
+ */
+ public static function setDecimalSeparator($pValue): void
+ {
+ self::$decimalSeparator = $pValue;
+ }
+
+ /**
+ * Get the thousands separator. If it has not yet been set explicitly, try to obtain number
+ * formatting information from locale.
+ *
+ * @return string
+ */
+ public static function getThousandsSeparator()
+ {
+ if (!isset(self::$thousandsSeparator)) {
+ $localeconv = localeconv();
+ self::$thousandsSeparator = ($localeconv['thousands_sep'] != '')
+ ? $localeconv['thousands_sep'] : $localeconv['mon_thousands_sep'];
+
+ if (self::$thousandsSeparator == '') {
+ // Default to .
+ self::$thousandsSeparator = ',';
+ }
+ }
+
+ return self::$thousandsSeparator;
+ }
+
+ /**
+ * Set the thousands separator. Only used by NumberFormat::toFormattedString()
+ * to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
+ *
+ * @param string $pValue Character for thousands separator
+ */
+ public static function setThousandsSeparator($pValue): void
+ {
+ self::$thousandsSeparator = $pValue;
+ }
+
+ /**
+ * Get the currency code. If it has not yet been set explicitly, try to obtain the
+ * symbol information from locale.
+ *
+ * @return string
+ */
+ public static function getCurrencyCode()
+ {
+ if (!empty(self::$currencyCode)) {
+ return self::$currencyCode;
+ }
+ self::$currencyCode = '$';
+ $localeconv = localeconv();
+ if (!empty($localeconv['currency_symbol'])) {
+ self::$currencyCode = $localeconv['currency_symbol'];
+
+ return self::$currencyCode;
+ }
+ if (!empty($localeconv['int_curr_symbol'])) {
+ self::$currencyCode = $localeconv['int_curr_symbol'];
+
+ return self::$currencyCode;
+ }
+
+ return self::$currencyCode;
+ }
+
+ /**
+ * Set the currency code. Only used by NumberFormat::toFormattedString()
+ * to format output by \PhpOffice\PhpSpreadsheet\Writer\Html and \PhpOffice\PhpSpreadsheet\Writer\Pdf.
+ *
+ * @param string $pValue Character for currency code
+ */
+ public static function setCurrencyCode($pValue): void
+ {
+ self::$currencyCode = $pValue;
+ }
+
+ /**
+ * Convert SYLK encoded string to UTF-8.
+ *
+ * @param string $pValue
+ *
+ * @return string UTF-8 encoded string
+ */
+ public static function SYLKtoUTF8($pValue)
+ {
+ self::buildCharacterSets();
+
+ // If there is no escape character in the string there is nothing to do
+ if (strpos($pValue, '') === false) {
+ return $pValue;
+ }
+
+ foreach (self::$SYLKCharacters as $k => $v) {
+ $pValue = str_replace($k, $v, $pValue);
+ }
+
+ return $pValue;
+ }
+
+ /**
+ * Retrieve any leading numeric part of a string, or return the full string if no leading numeric
+ * (handles basic integer or float, but not exponent or non decimal).
+ *
+ * @param string $value
+ *
+ * @return mixed string or only the leading numeric part of the string
+ */
+ public static function testStringAsNumeric($value)
+ {
+ if (is_numeric($value)) {
+ return $value;
+ }
+ $v = (float) $value;
+
+ return (is_numeric(substr($value, 0, strlen($v)))) ? $v : $value;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php
new file mode 100644
index 0000000..b57af21
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/TimeZone.php
@@ -0,0 +1,81 @@
+getTransitions($timestamp, $timestamp);
+
+ return (count($transitions) > 0) ? $transitions[0]['offset'] : 0;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php
new file mode 100644
index 0000000..7d90323
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/BestFit.php
@@ -0,0 +1,463 @@
+error;
+ }
+
+ public function getBestFitType()
+ {
+ return $this->bestFitType;
+ }
+
+ /**
+ * Return the Y-Value for a specified value of X.
+ *
+ * @param float $xValue X-Value
+ *
+ * @return bool Y-Value
+ */
+ public function getValueOfYForX($xValue)
+ {
+ return false;
+ }
+
+ /**
+ * Return the X-Value for a specified value of Y.
+ *
+ * @param float $yValue Y-Value
+ *
+ * @return bool X-Value
+ */
+ public function getValueOfXForY($yValue)
+ {
+ return false;
+ }
+
+ /**
+ * Return the original set of X-Values.
+ *
+ * @return float[] X-Values
+ */
+ public function getXValues()
+ {
+ return $this->xValues;
+ }
+
+ /**
+ * Return the Equation of the best-fit line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return bool
+ */
+ public function getEquation($dp = 0)
+ {
+ return false;
+ }
+
+ /**
+ * 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($this->slope, $dp);
+ }
+
+ return $this->slope;
+ }
+
+ /**
+ * Return the standard error of the Slope.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return float
+ */
+ public function getSlopeSE($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->slopeSE, $dp);
+ }
+
+ return $this->slopeSE;
+ }
+
+ /**
+ * 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($this->intersect, $dp);
+ }
+
+ return $this->intersect;
+ }
+
+ /**
+ * Return the standard error of the Intersect.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return float
+ */
+ public function getIntersectSE($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->intersectSE, $dp);
+ }
+
+ return $this->intersectSE;
+ }
+
+ /**
+ * Return the goodness of fit for this regression.
+ *
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getGoodnessOfFit($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->goodnessOfFit, $dp);
+ }
+
+ return $this->goodnessOfFit;
+ }
+
+ /**
+ * Return the goodness of fit for this regression.
+ *
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getGoodnessOfFitPercent($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->goodnessOfFit * 100, $dp);
+ }
+
+ return $this->goodnessOfFit * 100;
+ }
+
+ /**
+ * Return the standard deviation of the residuals for this regression.
+ *
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getStdevOfResiduals($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->stdevOfResiduals, $dp);
+ }
+
+ return $this->stdevOfResiduals;
+ }
+
+ /**
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getSSRegression($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->SSRegression, $dp);
+ }
+
+ return $this->SSRegression;
+ }
+
+ /**
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getSSResiduals($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->SSResiduals, $dp);
+ }
+
+ return $this->SSResiduals;
+ }
+
+ /**
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getDFResiduals($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->DFResiduals, $dp);
+ }
+
+ return $this->DFResiduals;
+ }
+
+ /**
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getF($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->f, $dp);
+ }
+
+ return $this->f;
+ }
+
+ /**
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getCovariance($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->covariance, $dp);
+ }
+
+ return $this->covariance;
+ }
+
+ /**
+ * @param int $dp Number of places of decimal precision to return
+ *
+ * @return float
+ */
+ public function getCorrelation($dp = 0)
+ {
+ if ($dp != 0) {
+ return round($this->correlation, $dp);
+ }
+
+ return $this->correlation;
+ }
+
+ /**
+ * @return float[]
+ */
+ public function getYBestFitValues()
+ {
+ return $this->yBestFitValues;
+ }
+
+ protected function calculateGoodnessOfFit($sumX, $sumY, $sumX2, $sumY2, $sumXY, $meanX, $meanY, $const): void
+ {
+ $SSres = $SScov = $SScor = $SStot = $SSsex = 0.0;
+ foreach ($this->xValues as $xKey => $xValue) {
+ $bestFitY = $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
+
+ $SSres += ($this->yValues[$xKey] - $bestFitY) * ($this->yValues[$xKey] - $bestFitY);
+ if ($const) {
+ $SStot += ($this->yValues[$xKey] - $meanY) * ($this->yValues[$xKey] - $meanY);
+ } else {
+ $SStot += $this->yValues[$xKey] * $this->yValues[$xKey];
+ }
+ $SScov += ($this->xValues[$xKey] - $meanX) * ($this->yValues[$xKey] - $meanY);
+ if ($const) {
+ $SSsex += ($this->xValues[$xKey] - $meanX) * ($this->xValues[$xKey] - $meanX);
+ } else {
+ $SSsex += $this->xValues[$xKey] * $this->xValues[$xKey];
+ }
+ }
+
+ $this->SSResiduals = $SSres;
+ $this->DFResiduals = $this->valueCount - 1 - $const;
+
+ if ($this->DFResiduals == 0.0) {
+ $this->stdevOfResiduals = 0.0;
+ } else {
+ $this->stdevOfResiduals = sqrt($SSres / $this->DFResiduals);
+ }
+ if (($SStot == 0.0) || ($SSres == $SStot)) {
+ $this->goodnessOfFit = 1;
+ } else {
+ $this->goodnessOfFit = 1 - ($SSres / $SStot);
+ }
+
+ $this->SSRegression = $this->goodnessOfFit * $SStot;
+ $this->covariance = $SScov / $this->valueCount;
+ $this->correlation = ($this->valueCount * $sumXY - $sumX * $sumY) / sqrt(($this->valueCount * $sumX2 - $sumX ** 2) * ($this->valueCount * $sumY2 - $sumY ** 2));
+ $this->slopeSE = $this->stdevOfResiduals / sqrt($SSsex);
+ $this->intersectSE = $this->stdevOfResiduals * sqrt(1 / ($this->valueCount - ($sumX * $sumX) / $sumX2));
+ if ($this->SSResiduals != 0.0) {
+ if ($this->DFResiduals == 0.0) {
+ $this->f = 0.0;
+ } else {
+ $this->f = $this->SSRegression / ($this->SSResiduals / $this->DFResiduals);
+ }
+ } else {
+ if ($this->DFResiduals == 0.0) {
+ $this->f = 0.0;
+ } else {
+ $this->f = $this->SSRegression / $this->DFResiduals;
+ }
+ }
+ }
+
+ /**
+ * @param float[] $yValues
+ * @param float[] $xValues
+ * @param bool $const
+ */
+ protected function leastSquareFit(array $yValues, array $xValues, $const): void
+ {
+ // calculate sums
+ $x_sum = array_sum($xValues);
+ $y_sum = array_sum($yValues);
+ $meanX = $x_sum / $this->valueCount;
+ $meanY = $y_sum / $this->valueCount;
+ $mBase = $mDivisor = $xx_sum = $xy_sum = $yy_sum = 0.0;
+ for ($i = 0; $i < $this->valueCount; ++$i) {
+ $xy_sum += $xValues[$i] * $yValues[$i];
+ $xx_sum += $xValues[$i] * $xValues[$i];
+ $yy_sum += $yValues[$i] * $yValues[$i];
+
+ if ($const) {
+ $mBase += ($xValues[$i] - $meanX) * ($yValues[$i] - $meanY);
+ $mDivisor += ($xValues[$i] - $meanX) * ($xValues[$i] - $meanX);
+ } else {
+ $mBase += $xValues[$i] * $yValues[$i];
+ $mDivisor += $xValues[$i] * $xValues[$i];
+ }
+ }
+
+ // calculate slope
+ $this->slope = $mBase / $mDivisor;
+
+ // calculate intersect
+ if ($const) {
+ $this->intersect = $meanY - ($this->slope * $meanX);
+ } else {
+ $this->intersect = 0;
+ }
+
+ $this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, $meanX, $meanY, $const);
+ }
+
+ /**
+ * Define the regression.
+ *
+ * @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)
+ {
+ // Calculate number of points
+ $nY = count($yValues);
+ $nX = count($xValues);
+
+ // Define X Values if necessary
+ if ($nX == 0) {
+ $xValues = range(1, $nY);
+ } elseif ($nY != $nX) {
+ // Ensure both arrays of points are the same size
+ $this->error = true;
+ }
+
+ $this->valueCount = $nY;
+ $this->xValues = $xValues;
+ $this->yValues = $yValues;
+ }
+}
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..854915d
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/ExponentialBestFit.php
@@ -0,0 +1,122 @@
+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
+ * @param bool $const
+ */
+ private function exponentialRegression($yValues, $xValues, $const): void
+ {
+ foreach ($yValues as &$value) {
+ if ($value < 0.0) {
+ $value = 0 - log(abs($value));
+ } elseif ($value > 0.0) {
+ $value = log($value);
+ }
+ }
+ unset($value);
+
+ $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->exponentialRegression($yValues, $xValues, $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..83bc179
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LinearBestFit.php
@@ -0,0 +1,81 @@
+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
+ * @param bool $const
+ */
+ private function linearRegression($yValues, $xValues, $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, $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..4f2c805
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/LogarithmicBestFit.php
@@ -0,0 +1,90 @@
+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 = ' . $intersect . ' + ' . $slope . ' * log(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
+ * @param bool $const
+ */
+ private function logarithmicRegression($yValues, $xValues, $const): void
+ {
+ foreach ($xValues as &$value) {
+ if ($value < 0.0) {
+ $value = 0 - log(abs($value));
+ } elseif ($value > 0.0) {
+ $value = log($value);
+ }
+ }
+ unset($value);
+
+ $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->logarithmicRegression($yValues, $xValues, $const);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php
new file mode 100644
index 0000000..5389501
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PolynomialBestFit.php
@@ -0,0 +1,200 @@
+order;
+ }
+
+ /**
+ * Return the Y-Value for a specified value of X.
+ *
+ * @param float $xValue X-Value
+ *
+ * @return float Y-Value
+ */
+ public function getValueOfYForX($xValue)
+ {
+ $retVal = $this->getIntersect();
+ $slope = $this->getSlope();
+ foreach ($slope as $key => $value) {
+ if ($value != 0.0) {
+ $retVal += $value * $xValue ** ($key + 1);
+ }
+ }
+
+ return $retVal;
+ }
+
+ /**
+ * 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);
+
+ $equation = 'Y = ' . $intersect;
+ foreach ($slope as $key => $value) {
+ if ($value != 0.0) {
+ $equation .= ' + ' . $value . ' * X';
+ if ($key > 0) {
+ $equation .= '^' . ($key + 1);
+ }
+ }
+ }
+
+ return $equation;
+ }
+
+ /**
+ * Return the Slope of the line.
+ *
+ * @param int $dp Number of places of decimal precision to display
+ *
+ * @return string
+ */
+ public function getSlope($dp = 0)
+ {
+ if ($dp != 0) {
+ $coefficients = [];
+ foreach ($this->slope as $coefficient) {
+ $coefficients[] = round($coefficient, $dp);
+ }
+
+ return $coefficients;
+ }
+
+ return $this->slope;
+ }
+
+ public function getCoefficients($dp = 0)
+ {
+ return array_merge([$this->getIntersect($dp)], $this->getSlope($dp));
+ }
+
+ /**
+ * Execute the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param int $order Order of Polynomial for this regression
+ * @param float[] $yValues The set of Y-values for this regression
+ * @param float[] $xValues The set of X-values for this regression
+ */
+ private function polynomialRegression($order, $yValues, $xValues): void
+ {
+ // calculate sums
+ $x_sum = array_sum($xValues);
+ $y_sum = array_sum($yValues);
+ $xx_sum = $xy_sum = $yy_sum = 0;
+ for ($i = 0; $i < $this->valueCount; ++$i) {
+ $xy_sum += $xValues[$i] * $yValues[$i];
+ $xx_sum += $xValues[$i] * $xValues[$i];
+ $yy_sum += $yValues[$i] * $yValues[$i];
+ }
+ /*
+ * This routine uses logic from the PHP port of polyfit version 0.1
+ * written by Michael Bommarito and Paul Meagher
+ *
+ * The function fits a polynomial function of order $order through
+ * a series of x-y data points using least squares.
+ *
+ */
+ $A = [];
+ $B = [];
+ for ($i = 0; $i < $this->valueCount; ++$i) {
+ for ($j = 0; $j <= $order; ++$j) {
+ $A[$i][$j] = $xValues[$i] ** $j;
+ }
+ }
+ for ($i = 0; $i < $this->valueCount; ++$i) {
+ $B[$i] = [$yValues[$i]];
+ }
+ $matrixA = new Matrix($A);
+ $matrixB = new Matrix($B);
+ $C = $matrixA->solve($matrixB);
+
+ $coefficients = [];
+ for ($i = 0; $i < $C->getRowDimension(); ++$i) {
+ $r = $C->get($i, 0);
+ if (abs($r) <= 10 ** (-9)) {
+ $r = 0;
+ }
+ $coefficients[] = $r;
+ }
+
+ $this->intersect = array_shift($coefficients);
+ $this->slope = $coefficients;
+
+ $this->calculateGoodnessOfFit($x_sum, $y_sum, $xx_sum, $yy_sum, $xy_sum, 0, 0, 0);
+ foreach ($this->xValues as $xKey => $xValue) {
+ $this->yBestFitValues[$xKey] = $this->getValueOfYForX($xValue);
+ }
+ }
+
+ /**
+ * Define the regression and calculate the goodness of fit for a set of X and Y data values.
+ *
+ * @param int $order Order of Polynomial for this regression
+ * @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($order, $yValues, $xValues = [], $const = true)
+ {
+ parent::__construct($yValues, $xValues);
+
+ if (!$this->error) {
+ if ($order < $this->valueCount) {
+ $this->bestFitType .= '_' . $order;
+ $this->order = $order;
+ $this->polynomialRegression($order, $yValues, $xValues);
+ if (($this->getGoodnessOfFit() < 0.0) || ($this->getGoodnessOfFit() > 1.0)) {
+ $this->error = true;
+ }
+ } else {
+ $this->error = true;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php
new file mode 100644
index 0000000..38c67f6
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/PowerBestFit.php
@@ -0,0 +1,114 @@
+getIntersect() * ($xValue - $this->xOffset) ** $this->getSlope();
+ }
+
+ /**
+ * 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->yOffset) / $this->getIntersect()) ** (1 / $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 . ' * X^' . $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
+ * @param bool $const
+ */
+ private function powerRegression($yValues, $xValues, $const): void
+ {
+ foreach ($xValues as &$value) {
+ if ($value < 0.0) {
+ $value = 0 - log(abs($value));
+ } elseif ($value > 0.0) {
+ $value = log($value);
+ }
+ }
+ unset($value);
+ foreach ($yValues as &$value) {
+ if ($value < 0.0) {
+ $value = 0 - log(abs($value));
+ } elseif ($value > 0.0) {
+ $value = log($value);
+ }
+ }
+ unset($value);
+
+ $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->powerRegression($yValues, $xValues, $const);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php
new file mode 100644
index 0000000..f696b3e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Trend/Trend.php
@@ -0,0 +1,120 @@
+getGoodnessOfFit();
+ }
+ if ($trendType != self::TREND_BEST_FIT_NO_POLY) {
+ foreach (self::$trendTypePolynomialOrders as $trendMethod) {
+ $order = substr($trendMethod, -1);
+ $bestFit[$trendMethod] = new PolynomialBestFit($order, $yValues, $xValues, $const);
+ if ($bestFit[$trendMethod]->getError()) {
+ unset($bestFit[$trendMethod]);
+ } else {
+ $bestFitValue[$trendMethod] = $bestFit[$trendMethod]->getGoodnessOfFit();
+ }
+ }
+ }
+ // Determine which of our Trend lines is the best fit, and then we return the instance of that Trend class
+ arsort($bestFitValue);
+ $bestFitType = key($bestFitValue);
+
+ return $bestFit[$bestFitType];
+ default:
+ return false;
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php
new file mode 100644
index 0000000..a42cc47
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/XMLWriter.php
@@ -0,0 +1,92 @@
+openMemory();
+ } else {
+ // Create temporary filename
+ if ($pTemporaryStorageFolder === null) {
+ $pTemporaryStorageFolder = File::sysGetTempDir();
+ }
+ $this->tempFileName = @tempnam($pTemporaryStorageFolder, 'xml');
+
+ // Open storage
+ if ($this->openUri($this->tempFileName) === false) {
+ // Fallback to memory...
+ $this->openMemory();
+ }
+ }
+
+ // Set default values
+ if (self::$debugEnabled) {
+ $this->setIndent(true);
+ }
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ // Unlink temporary files
+ if ($this->tempFileName != '') {
+ @unlink($this->tempFileName);
+ }
+ }
+
+ /**
+ * Get written data.
+ *
+ * @return string
+ */
+ public function getData()
+ {
+ if ($this->tempFileName == '') {
+ return $this->outputMemory(true);
+ }
+ $this->flush();
+
+ return file_get_contents($this->tempFileName);
+ }
+
+ /**
+ * Wrapper method for writeRaw.
+ *
+ * @param string|string[] $text
+ *
+ * @return bool
+ */
+ public function writeRawData($text)
+ {
+ if (is_array($text)) {
+ $text = implode("\n", $text);
+ }
+
+ return $this->writeRaw(htmlspecialchars($text));
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php
new file mode 100644
index 0000000..f2a5576
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php
@@ -0,0 +1,279 @@
+getParent()->getDefaultStyle()->getFont();
+
+ $columnDimensions = $sheet->getColumnDimensions();
+
+ // first find the true column width in pixels (uncollapsed and unhidden)
+ if (isset($columnDimensions[$col]) && $columnDimensions[$col]->getWidth() != -1) {
+ // then we have column dimension with explicit width
+ $columnDimension = $columnDimensions[$col];
+ $width = $columnDimension->getWidth();
+ $pixelWidth = Drawing::cellDimensionToPixels($width, $font);
+ } elseif ($sheet->getDefaultColumnDimension()->getWidth() != -1) {
+ // then we have default column dimension with explicit width
+ $defaultColumnDimension = $sheet->getDefaultColumnDimension();
+ $width = $defaultColumnDimension->getWidth();
+ $pixelWidth = Drawing::cellDimensionToPixels($width, $font);
+ } else {
+ // we don't even have any default column dimension. Width depends on default font
+ $pixelWidth = Font::getDefaultColumnWidthByFont($font, true);
+ }
+
+ // now find the effective column width in pixels
+ if (isset($columnDimensions[$col]) && !$columnDimensions[$col]->getVisible()) {
+ $effectivePixelWidth = 0;
+ } else {
+ $effectivePixelWidth = $pixelWidth;
+ }
+
+ return $effectivePixelWidth;
+ }
+
+ /**
+ * Convert the height of a cell from user's units to pixels. By interpolation
+ * the relationship is: y = 4/3x. If the height hasn't been set by the user we
+ * use the default value. If the row is hidden we use a value of zero.
+ *
+ * @param Worksheet $sheet The sheet
+ * @param int $row The row index (1-based)
+ *
+ * @return int The width in pixels
+ */
+ public static function sizeRow($sheet, $row = 1)
+ {
+ // default font of the workbook
+ $font = $sheet->getParent()->getDefaultStyle()->getFont();
+
+ $rowDimensions = $sheet->getRowDimensions();
+
+ // first find the true row height in pixels (uncollapsed and unhidden)
+ if (isset($rowDimensions[$row]) && $rowDimensions[$row]->getRowHeight() != -1) {
+ // then we have a row dimension
+ $rowDimension = $rowDimensions[$row];
+ $rowHeight = $rowDimension->getRowHeight();
+ $pixelRowHeight = (int) ceil(4 * $rowHeight / 3); // here we assume Arial 10
+ } elseif ($sheet->getDefaultRowDimension()->getRowHeight() != -1) {
+ // then we have a default row dimension with explicit height
+ $defaultRowDimension = $sheet->getDefaultRowDimension();
+ $rowHeight = $defaultRowDimension->getRowHeight();
+ $pixelRowHeight = Drawing::pointsToPixels($rowHeight);
+ } else {
+ // we don't even have any default row dimension. Height depends on default font
+ $pointRowHeight = Font::getDefaultRowHeightByFont($font);
+ $pixelRowHeight = Font::fontSizeToPixels($pointRowHeight);
+ }
+
+ // now find the effective row height in pixels
+ if (isset($rowDimensions[$row]) && !$rowDimensions[$row]->getVisible()) {
+ $effectivePixelRowHeight = 0;
+ } else {
+ $effectivePixelRowHeight = $pixelRowHeight;
+ }
+
+ return $effectivePixelRowHeight;
+ }
+
+ /**
+ * Get the horizontal distance in pixels between two anchors
+ * The distanceX is found as sum of all the spanning columns widths minus correction for the two offsets.
+ *
+ * @param string $startColumn
+ * @param int $startOffsetX Offset within start cell measured in 1/1024 of the cell width
+ * @param string $endColumn
+ * @param int $endOffsetX Offset within end cell measured in 1/1024 of the cell width
+ *
+ * @return int Horizontal measured in pixels
+ */
+ public static function getDistanceX(Worksheet $sheet, $startColumn = 'A', $startOffsetX = 0, $endColumn = 'A', $endOffsetX = 0)
+ {
+ $distanceX = 0;
+
+ // add the widths of the spanning columns
+ $startColumnIndex = Coordinate::columnIndexFromString($startColumn);
+ $endColumnIndex = Coordinate::columnIndexFromString($endColumn);
+ for ($i = $startColumnIndex; $i <= $endColumnIndex; ++$i) {
+ $distanceX += self::sizeCol($sheet, Coordinate::stringFromColumnIndex($i));
+ }
+
+ // correct for offsetX in startcell
+ $distanceX -= (int) floor(self::sizeCol($sheet, $startColumn) * $startOffsetX / 1024);
+
+ // correct for offsetX in endcell
+ $distanceX -= (int) floor(self::sizeCol($sheet, $endColumn) * (1 - $endOffsetX / 1024));
+
+ return $distanceX;
+ }
+
+ /**
+ * Get the vertical distance in pixels between two anchors
+ * The distanceY is found as sum of all the spanning rows minus two offsets.
+ *
+ * @param int $startRow (1-based)
+ * @param int $startOffsetY Offset within start cell measured in 1/256 of the cell height
+ * @param int $endRow (1-based)
+ * @param int $endOffsetY Offset within end cell measured in 1/256 of the cell height
+ *
+ * @return int Vertical distance measured in pixels
+ */
+ public static function getDistanceY(Worksheet $sheet, $startRow = 1, $startOffsetY = 0, $endRow = 1, $endOffsetY = 0)
+ {
+ $distanceY = 0;
+
+ // add the widths of the spanning rows
+ for ($row = $startRow; $row <= $endRow; ++$row) {
+ $distanceY += self::sizeRow($sheet, $row);
+ }
+
+ // correct for offsetX in startcell
+ $distanceY -= (int) floor(self::sizeRow($sheet, $startRow) * $startOffsetY / 256);
+
+ // correct for offsetX in endcell
+ $distanceY -= (int) floor(self::sizeRow($sheet, $endRow) * (1 - $endOffsetY / 256));
+
+ return $distanceY;
+ }
+
+ /**
+ * Convert 1-cell anchor coordinates to 2-cell anchor coordinates
+ * This function is ported from PEAR Spreadsheet_Writer_Excel with small modifications.
+ *
+ * Calculate the vertices that define the position of the image as required by
+ * the OBJ record.
+ *
+ * +------------+------------+
+ * | A | B |
+ * +-----+------------+------------+
+ * | |(x1,y1) | |
+ * | 1 |(A1)._______|______ |
+ * | | | | |
+ * | | | | |
+ * +-----+----| BITMAP |-----+
+ * | | | | |
+ * | 2 | |______________. |
+ * | | | (B2)|
+ * | | | (x2,y2)|
+ * +---- +------------+------------+
+ *
+ * Example of a bitmap that covers some of the area from cell A1 to cell B2.
+ *
+ * Based on the width and height of the bitmap we need to calculate 8 vars:
+ * $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
+ * The width and height of the cells are also variable and have to be taken into
+ * account.
+ * The values of $col_start and $row_start are passed in from the calling
+ * function. The values of $col_end and $row_end are calculated by subtracting
+ * the width and height of the bitmap from the width and height of the
+ * underlying cells.
+ * The vertices are expressed as a percentage of the underlying cell width as
+ * follows (rhs values are in pixels):
+ *
+ * x1 = X / W *1024
+ * y1 = Y / H *256
+ * x2 = (X-1) / W *1024
+ * y2 = (Y-1) / H *256
+ *
+ * Where: X is distance from the left side of the underlying cell
+ * Y is distance from the top of the underlying cell
+ * W is the width of the cell
+ * H is the height of the cell
+ *
+ * @param Worksheet $sheet
+ * @param string $coordinates E.g. 'A1'
+ * @param int $offsetX Horizontal offset in pixels
+ * @param int $offsetY Vertical offset in pixels
+ * @param int $width Width in pixels
+ * @param int $height Height in pixels
+ *
+ * @return array
+ */
+ public static function oneAnchor2twoAnchor($sheet, $coordinates, $offsetX, $offsetY, $width, $height)
+ {
+ [$column, $row] = Coordinate::coordinateFromString($coordinates);
+ $col_start = Coordinate::columnIndexFromString($column);
+ $row_start = $row - 1;
+
+ $x1 = $offsetX;
+ $y1 = $offsetY;
+
+ // Initialise end cell to the same as the start cell
+ $col_end = $col_start; // Col containing lower right corner of object
+ $row_end = $row_start; // Row containing bottom right corner of object
+
+ // Zero the specified offset if greater than the cell dimensions
+ if ($x1 >= self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_start))) {
+ $x1 = 0;
+ }
+ if ($y1 >= self::sizeRow($sheet, $row_start + 1)) {
+ $y1 = 0;
+ }
+
+ $width = $width + $x1 - 1;
+ $height = $height + $y1 - 1;
+
+ // Subtract the underlying cell widths to find the end cell of the image
+ while ($width >= self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end))) {
+ $width -= self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end));
+ ++$col_end;
+ }
+
+ // Subtract the underlying cell heights to find the end cell of the image
+ while ($height >= self::sizeRow($sheet, $row_end + 1)) {
+ $height -= self::sizeRow($sheet, $row_end + 1);
+ ++$row_end;
+ }
+
+ // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
+ // with zero height or width.
+ if (self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_start)) == 0) {
+ return;
+ }
+ if (self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end)) == 0) {
+ return;
+ }
+ if (self::sizeRow($sheet, $row_start + 1) == 0) {
+ return;
+ }
+ if (self::sizeRow($sheet, $row_end + 1) == 0) {
+ return;
+ }
+
+ // Convert the pixel values to the percentage value expected by Excel
+ $x1 = $x1 / self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_start)) * 1024;
+ $y1 = $y1 / self::sizeRow($sheet, $row_start + 1) * 256;
+ $x2 = ($width + 1) / self::sizeCol($sheet, Coordinate::stringFromColumnIndex($col_end)) * 1024; // Distance to right side of object
+ $y2 = ($height + 1) / self::sizeRow($sheet, $row_end + 1) * 256; // Distance to bottom of object
+
+ $startCoordinates = Coordinate::stringFromColumnIndex($col_start) . ($row_start + 1);
+ $endCoordinates = Coordinate::stringFromColumnIndex($col_end) . ($row_end + 1);
+
+ return [
+ 'startCoordinates' => $startCoordinates,
+ 'startOffsetX' => $x1,
+ 'startOffsetY' => $y1,
+ 'endCoordinates' => $endCoordinates,
+ 'endOffsetX' => $x2,
+ 'endOffsetY' => $y2,
+ ];
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php
new file mode 100644
index 0000000..b40a51d
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Spreadsheet.php
@@ -0,0 +1,1591 @@
+hasMacros;
+ }
+
+ /**
+ * Define if a workbook has macros.
+ *
+ * @param bool $hasMacros true|false
+ */
+ public function setHasMacros($hasMacros): void
+ {
+ $this->hasMacros = (bool) $hasMacros;
+ }
+
+ /**
+ * Set the macros code.
+ *
+ * @param string $macroCode string|null
+ */
+ public function setMacrosCode($macroCode): void
+ {
+ $this->macrosCode = $macroCode;
+ $this->setHasMacros($macroCode !== null);
+ }
+
+ /**
+ * Return the macros code.
+ *
+ * @return null|string
+ */
+ public function getMacrosCode()
+ {
+ return $this->macrosCode;
+ }
+
+ /**
+ * Set the macros certificate.
+ *
+ * @param null|string $certificate
+ */
+ public function setMacrosCertificate($certificate): void
+ {
+ $this->macrosCertificate = $certificate;
+ }
+
+ /**
+ * Is the project signed ?
+ *
+ * @return bool true|false
+ */
+ public function hasMacrosCertificate()
+ {
+ return $this->macrosCertificate !== null;
+ }
+
+ /**
+ * Return the macros certificate.
+ *
+ * @return null|string
+ */
+ public function getMacrosCertificate()
+ {
+ return $this->macrosCertificate;
+ }
+
+ /**
+ * Remove all macros, certificate from spreadsheet.
+ */
+ public function discardMacros(): void
+ {
+ $this->hasMacros = false;
+ $this->macrosCode = null;
+ $this->macrosCertificate = null;
+ }
+
+ /**
+ * set ribbon XML data.
+ *
+ * @param null|mixed $target
+ * @param null|mixed $xmlData
+ */
+ public function setRibbonXMLData($target, $xmlData): void
+ {
+ if ($target !== null && $xmlData !== null) {
+ $this->ribbonXMLData = ['target' => $target, 'data' => $xmlData];
+ } else {
+ $this->ribbonXMLData = null;
+ }
+ }
+
+ /**
+ * retrieve ribbon XML Data.
+ *
+ * return string|null|array
+ *
+ * @param string $what
+ *
+ * @return string
+ */
+ public function getRibbonXMLData($what = 'all') //we need some constants here...
+ {
+ $returnData = null;
+ $what = strtolower($what);
+ switch ($what) {
+ case 'all':
+ $returnData = $this->ribbonXMLData;
+
+ break;
+ case 'target':
+ case 'data':
+ if (is_array($this->ribbonXMLData) && isset($this->ribbonXMLData[$what])) {
+ $returnData = $this->ribbonXMLData[$what];
+ }
+
+ break;
+ }
+
+ return $returnData;
+ }
+
+ /**
+ * store binaries ribbon objects (pictures).
+ *
+ * @param null|mixed $BinObjectsNames
+ * @param null|mixed $BinObjectsData
+ */
+ public function setRibbonBinObjects($BinObjectsNames, $BinObjectsData): void
+ {
+ if ($BinObjectsNames !== null && $BinObjectsData !== null) {
+ $this->ribbonBinObjects = ['names' => $BinObjectsNames, 'data' => $BinObjectsData];
+ } else {
+ $this->ribbonBinObjects = null;
+ }
+ }
+
+ /**
+ * List of unparsed loaded data for export to same format with better compatibility.
+ * It has to be minimized when the library start to support currently unparsed data.
+ *
+ * @internal
+ *
+ * @return array
+ */
+ public function getUnparsedLoadedData()
+ {
+ return $this->unparsedLoadedData;
+ }
+
+ /**
+ * List of unparsed loaded data for export to same format with better compatibility.
+ * It has to be minimized when the library start to support currently unparsed data.
+ *
+ * @internal
+ */
+ public function setUnparsedLoadedData(array $unparsedLoadedData): void
+ {
+ $this->unparsedLoadedData = $unparsedLoadedData;
+ }
+
+ /**
+ * return the extension of a filename. Internal use for a array_map callback (php<5.3 don't like lambda function).
+ *
+ * @param mixed $path
+ *
+ * @return string
+ */
+ private function getExtensionOnly($path)
+ {
+ return pathinfo($path, PATHINFO_EXTENSION);
+ }
+
+ /**
+ * retrieve Binaries Ribbon Objects.
+ *
+ * @param string $what
+ *
+ * @return null|array
+ */
+ public function getRibbonBinObjects($what = 'all')
+ {
+ $ReturnData = null;
+ $what = strtolower($what);
+ switch ($what) {
+ case 'all':
+ return $this->ribbonBinObjects;
+
+ break;
+ case 'names':
+ case 'data':
+ if (is_array($this->ribbonBinObjects) && isset($this->ribbonBinObjects[$what])) {
+ $ReturnData = $this->ribbonBinObjects[$what];
+ }
+
+ break;
+ case 'types':
+ if (
+ is_array($this->ribbonBinObjects) &&
+ isset($this->ribbonBinObjects['data']) && is_array($this->ribbonBinObjects['data'])
+ ) {
+ $tmpTypes = array_keys($this->ribbonBinObjects['data']);
+ $ReturnData = array_unique(array_map([$this, 'getExtensionOnly'], $tmpTypes));
+ } else {
+ $ReturnData = []; // the caller want an array... not null if empty
+ }
+
+ break;
+ }
+
+ return $ReturnData;
+ }
+
+ /**
+ * This workbook have a custom UI ?
+ *
+ * @return bool
+ */
+ public function hasRibbon()
+ {
+ return $this->ribbonXMLData !== null;
+ }
+
+ /**
+ * This workbook have additionnal object for the ribbon ?
+ *
+ * @return bool
+ */
+ public function hasRibbonBinObjects()
+ {
+ return $this->ribbonBinObjects !== null;
+ }
+
+ /**
+ * Check if a sheet with a specified code name already exists.
+ *
+ * @param string $pSheetCodeName Name of the worksheet to check
+ *
+ * @return bool
+ */
+ public function sheetCodeNameExists($pSheetCodeName)
+ {
+ return $this->getSheetByCodeName($pSheetCodeName) !== null;
+ }
+
+ /**
+ * Get sheet by code name. Warning : sheet don't have always a code name !
+ *
+ * @param string $pName Sheet name
+ *
+ * @return Worksheet
+ */
+ public function getSheetByCodeName($pName)
+ {
+ $worksheetCount = count($this->workSheetCollection);
+ for ($i = 0; $i < $worksheetCount; ++$i) {
+ if ($this->workSheetCollection[$i]->getCodeName() == $pName) {
+ return $this->workSheetCollection[$i];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Create a new PhpSpreadsheet with one Worksheet.
+ */
+ public function __construct()
+ {
+ $this->uniqueID = uniqid('', true);
+ $this->calculationEngine = new Calculation($this);
+
+ // Initialise worksheet collection and add one worksheet
+ $this->workSheetCollection = [];
+ $this->workSheetCollection[] = new Worksheet($this);
+ $this->activeSheetIndex = 0;
+
+ // Create document properties
+ $this->properties = new Document\Properties();
+
+ // Create document security
+ $this->security = new Document\Security();
+
+ // Set defined names
+ $this->definedNames = [];
+
+ // Create the cellXf supervisor
+ $this->cellXfSupervisor = new Style(true);
+ $this->cellXfSupervisor->bindParent($this);
+
+ // Create the default style
+ $this->addCellXf(new Style());
+ $this->addCellStyleXf(new Style());
+ }
+
+ /**
+ * Code to execute when this worksheet is unset().
+ */
+ public function __destruct()
+ {
+ $this->calculationEngine = null;
+ $this->disconnectWorksheets();
+ }
+
+ /**
+ * Disconnect all worksheets from this PhpSpreadsheet workbook object,
+ * typically so that the PhpSpreadsheet object can be unset.
+ */
+ public function disconnectWorksheets(): void
+ {
+ $worksheet = null;
+ foreach ($this->workSheetCollection as $k => &$worksheet) {
+ $worksheet->disconnectCells();
+ $this->workSheetCollection[$k] = null;
+ }
+ unset($worksheet);
+ $this->workSheetCollection = [];
+ }
+
+ /**
+ * Return the calculation engine for this worksheet.
+ *
+ * @return Calculation
+ */
+ public function getCalculationEngine()
+ {
+ return $this->calculationEngine;
+ }
+
+ /**
+ * Get properties.
+ *
+ * @return Document\Properties
+ */
+ public function getProperties()
+ {
+ return $this->properties;
+ }
+
+ /**
+ * Set properties.
+ */
+ public function setProperties(Document\Properties $pValue): void
+ {
+ $this->properties = $pValue;
+ }
+
+ /**
+ * Get security.
+ *
+ * @return Document\Security
+ */
+ public function getSecurity()
+ {
+ return $this->security;
+ }
+
+ /**
+ * Set security.
+ */
+ public function setSecurity(Document\Security $pValue): void
+ {
+ $this->security = $pValue;
+ }
+
+ /**
+ * Get active sheet.
+ *
+ * @return Worksheet
+ */
+ public function getActiveSheet()
+ {
+ return $this->getSheet($this->activeSheetIndex);
+ }
+
+ /**
+ * Create sheet and add it to this workbook.
+ *
+ * @param null|int $sheetIndex Index where sheet should go (0,1,..., or null for last)
+ *
+ * @return Worksheet
+ */
+ public function createSheet($sheetIndex = null)
+ {
+ $newSheet = new Worksheet($this);
+ $this->addSheet($newSheet, $sheetIndex);
+
+ return $newSheet;
+ }
+
+ /**
+ * Check if a sheet with a specified name already exists.
+ *
+ * @param string $pSheetName Name of the worksheet to check
+ *
+ * @return bool
+ */
+ public function sheetNameExists($pSheetName)
+ {
+ return $this->getSheetByName($pSheetName) !== null;
+ }
+
+ /**
+ * Add sheet.
+ *
+ * @param null|int $iSheetIndex Index where sheet should go (0,1,..., or null for last)
+ *
+ * @return Worksheet
+ */
+ public function addSheet(Worksheet $pSheet, $iSheetIndex = null)
+ {
+ if ($this->sheetNameExists($pSheet->getTitle())) {
+ throw new Exception(
+ "Workbook already contains a worksheet named '{$pSheet->getTitle()}'. Rename this worksheet first."
+ );
+ }
+
+ if ($iSheetIndex === null) {
+ if ($this->activeSheetIndex < 0) {
+ $this->activeSheetIndex = 0;
+ }
+ $this->workSheetCollection[] = $pSheet;
+ } else {
+ // Insert the sheet at the requested index
+ array_splice(
+ $this->workSheetCollection,
+ $iSheetIndex,
+ 0,
+ [$pSheet]
+ );
+
+ // Adjust active sheet index if necessary
+ if ($this->activeSheetIndex >= $iSheetIndex) {
+ ++$this->activeSheetIndex;
+ }
+ }
+
+ if ($pSheet->getParent() === null) {
+ $pSheet->rebindParent($this);
+ }
+
+ return $pSheet;
+ }
+
+ /**
+ * Remove sheet by index.
+ *
+ * @param int $pIndex Active sheet index
+ */
+ public function removeSheetByIndex($pIndex): void
+ {
+ $numSheets = count($this->workSheetCollection);
+ if ($pIndex > $numSheets - 1) {
+ throw new Exception(
+ "You tried to remove a sheet by the out of bounds index: {$pIndex}. The actual number of sheets is {$numSheets}."
+ );
+ }
+ array_splice($this->workSheetCollection, $pIndex, 1);
+
+ // Adjust active sheet index if necessary
+ if (
+ ($this->activeSheetIndex >= $pIndex) &&
+ ($pIndex > count($this->workSheetCollection) - 1)
+ ) {
+ --$this->activeSheetIndex;
+ }
+ }
+
+ /**
+ * Get sheet by index.
+ *
+ * @param int $pIndex Sheet index
+ *
+ * @return Worksheet
+ */
+ public function getSheet($pIndex)
+ {
+ if (!isset($this->workSheetCollection[$pIndex])) {
+ $numSheets = $this->getSheetCount();
+
+ throw new Exception(
+ "Your requested sheet index: {$pIndex} is out of bounds. The actual number of sheets is {$numSheets}."
+ );
+ }
+
+ return $this->workSheetCollection[$pIndex];
+ }
+
+ /**
+ * Get all sheets.
+ *
+ * @return Worksheet[]
+ */
+ public function getAllSheets()
+ {
+ return $this->workSheetCollection;
+ }
+
+ /**
+ * Get sheet by name.
+ *
+ * @param string $pName Sheet name
+ *
+ * @return null|Worksheet
+ */
+ public function getSheetByName($pName)
+ {
+ $worksheetCount = count($this->workSheetCollection);
+ for ($i = 0; $i < $worksheetCount; ++$i) {
+ if ($this->workSheetCollection[$i]->getTitle() === trim($pName, "'")) {
+ return $this->workSheetCollection[$i];
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Get index for sheet.
+ *
+ * @return int index
+ */
+ public function getIndex(Worksheet $pSheet)
+ {
+ foreach ($this->workSheetCollection as $key => $value) {
+ if ($value->getHashCode() === $pSheet->getHashCode()) {
+ return $key;
+ }
+ }
+
+ throw new Exception('Sheet does not exist.');
+ }
+
+ /**
+ * Set index for sheet by sheet name.
+ *
+ * @param string $sheetName Sheet name to modify index for
+ * @param int $newIndex New index for the sheet
+ *
+ * @return int New sheet index
+ */
+ public function setIndexByName($sheetName, $newIndex)
+ {
+ $oldIndex = $this->getIndex($this->getSheetByName($sheetName));
+ $pSheet = array_splice(
+ $this->workSheetCollection,
+ $oldIndex,
+ 1
+ );
+ array_splice(
+ $this->workSheetCollection,
+ $newIndex,
+ 0,
+ $pSheet
+ );
+
+ return $newIndex;
+ }
+
+ /**
+ * Get sheet count.
+ *
+ * @return int
+ */
+ public function getSheetCount()
+ {
+ return count($this->workSheetCollection);
+ }
+
+ /**
+ * Get active sheet index.
+ *
+ * @return int Active sheet index
+ */
+ public function getActiveSheetIndex()
+ {
+ return $this->activeSheetIndex;
+ }
+
+ /**
+ * Set active sheet index.
+ *
+ * @param int $pIndex Active sheet index
+ *
+ * @return Worksheet
+ */
+ public function setActiveSheetIndex($pIndex)
+ {
+ $numSheets = count($this->workSheetCollection);
+
+ if ($pIndex > $numSheets - 1) {
+ throw new Exception(
+ "You tried to set a sheet active by the out of bounds index: {$pIndex}. The actual number of sheets is {$numSheets}."
+ );
+ }
+ $this->activeSheetIndex = $pIndex;
+
+ return $this->getActiveSheet();
+ }
+
+ /**
+ * Set active sheet index by name.
+ *
+ * @param string $pValue Sheet title
+ *
+ * @return Worksheet
+ */
+ public function setActiveSheetIndexByName($pValue)
+ {
+ if (($worksheet = $this->getSheetByName($pValue)) instanceof Worksheet) {
+ $this->setActiveSheetIndex($this->getIndex($worksheet));
+
+ return $worksheet;
+ }
+
+ throw new Exception('Workbook does not contain sheet:' . $pValue);
+ }
+
+ /**
+ * Get sheet names.
+ *
+ * @return string[]
+ */
+ public function getSheetNames()
+ {
+ $returnValue = [];
+ $worksheetCount = $this->getSheetCount();
+ for ($i = 0; $i < $worksheetCount; ++$i) {
+ $returnValue[] = $this->getSheet($i)->getTitle();
+ }
+
+ return $returnValue;
+ }
+
+ /**
+ * Add external sheet.
+ *
+ * @param Worksheet $pSheet External sheet to add
+ * @param null|int $iSheetIndex Index where sheet should go (0,1,..., or null for last)
+ *
+ * @return Worksheet
+ */
+ public function addExternalSheet(Worksheet $pSheet, $iSheetIndex = null)
+ {
+ if ($this->sheetNameExists($pSheet->getTitle())) {
+ throw new Exception("Workbook already contains a worksheet named '{$pSheet->getTitle()}'. Rename the external sheet first.");
+ }
+
+ // count how many cellXfs there are in this workbook currently, we will need this below
+ $countCellXfs = count($this->cellXfCollection);
+
+ // copy all the shared cellXfs from the external workbook and append them to the current
+ foreach ($pSheet->getParent()->getCellXfCollection() as $cellXf) {
+ $this->addCellXf(clone $cellXf);
+ }
+
+ // move sheet to this workbook
+ $pSheet->rebindParent($this);
+
+ // update the cellXfs
+ foreach ($pSheet->getCoordinates(false) as $coordinate) {
+ $cell = $pSheet->getCell($coordinate);
+ $cell->setXfIndex($cell->getXfIndex() + $countCellXfs);
+ }
+
+ return $this->addSheet($pSheet, $iSheetIndex);
+ }
+
+ /**
+ * Get an array of all Named Ranges.
+ *
+ * @return NamedRange[]
+ */
+ public function getNamedRanges(): array
+ {
+ return array_filter(
+ $this->definedNames,
+ function (DefinedName $definedName) {
+ return $definedName->isFormula() === self::DEFINED_NAME_IS_RANGE;
+ }
+ );
+ }
+
+ /**
+ * Get an array of all Named Formulae.
+ *
+ * @return NamedFormula[]
+ */
+ public function getNamedFormulae(): array
+ {
+ return array_filter(
+ $this->definedNames,
+ function (DefinedName $definedName) {
+ return $definedName->isFormula() === self::DEFINED_NAME_IS_FORMULA;
+ }
+ );
+ }
+
+ /**
+ * Get an array of all Defined Names (both named ranges and named formulae).
+ *
+ * @return DefinedName[]
+ */
+ public function getDefinedNames(): array
+ {
+ return $this->definedNames;
+ }
+
+ /**
+ * Add a named range.
+ * If a named range with this name already exists, then this will replace the existing value.
+ */
+ public function addNamedRange(NamedRange $namedRange): void
+ {
+ $this->addDefinedName($namedRange);
+ }
+
+ /**
+ * Add a named formula.
+ * If a named formula with this name already exists, then this will replace the existing value.
+ */
+ public function addNamedFormula(NamedFormula $namedFormula): void
+ {
+ $this->addDefinedName($namedFormula);
+ }
+
+ /**
+ * Add a defined name (either a named range or a named formula).
+ * If a defined named with this name already exists, then this will replace the existing value.
+ */
+ public function addDefinedName(DefinedName $definedName): void
+ {
+ $upperCaseName = StringHelper::strToUpper($definedName->getName());
+ if ($definedName->getScope() == null) {
+ // global scope
+ $this->definedNames[$upperCaseName] = $definedName;
+ } else {
+ // local scope
+ $this->definedNames[$definedName->getScope()->getTitle() . '!' . $upperCaseName] = $definedName;
+ }
+ }
+
+ /**
+ * Get named range.
+ *
+ * @param null|Worksheet $pSheet Scope. Use null for global scope
+ */
+ public function getNamedRange(string $namedRange, ?Worksheet $pSheet = null): ?NamedRange
+ {
+ $returnValue = null;
+
+ if ($namedRange !== '') {
+ $namedRange = StringHelper::strToUpper($namedRange);
+ // first look for global named range
+ $returnValue = $this->getGlobalDefinedNameByType($namedRange, self::DEFINED_NAME_IS_RANGE);
+ // then look for local named range (has priority over global named range if both names exist)
+ $returnValue = $this->getLocalDefinedNameByType($namedRange, self::DEFINED_NAME_IS_RANGE, $pSheet) ?: $returnValue;
+ }
+
+ return $returnValue instanceof NamedRange ? $returnValue : null;
+ }
+
+ /**
+ * Get named formula.
+ *
+ * @param null|Worksheet $pSheet Scope. Use null for global scope
+ */
+ public function getNamedFormula(string $namedFormula, ?Worksheet $pSheet = null): ?NamedFormula
+ {
+ $returnValue = null;
+
+ if ($namedFormula !== '') {
+ $namedFormula = StringHelper::strToUpper($namedFormula);
+ // first look for global named formula
+ $returnValue = $this->getGlobalDefinedNameByType($namedFormula, self::DEFINED_NAME_IS_FORMULA);
+ // then look for local named formula (has priority over global named formula if both names exist)
+ $returnValue = $this->getLocalDefinedNameByType($namedFormula, self::DEFINED_NAME_IS_FORMULA, $pSheet) ?: $returnValue;
+ }
+
+ return $returnValue instanceof NamedFormula ? $returnValue : null;
+ }
+
+ private function getGlobalDefinedNameByType(string $name, bool $type): ?DefinedName
+ {
+ if (isset($this->definedNames[$name]) && $this->definedNames[$name]->isFormula() === $type) {
+ return $this->definedNames[$name];
+ }
+
+ return null;
+ }
+
+ private function getLocalDefinedNameByType(string $name, bool $type, ?Worksheet $pSheet = null): ?DefinedName
+ {
+ if (
+ ($pSheet !== null) && isset($this->definedNames[$pSheet->getTitle() . '!' . $name])
+ && $this->definedNames[$pSheet->getTitle() . '!' . $name]->isFormula() === $type
+ ) {
+ return $this->definedNames[$pSheet->getTitle() . '!' . $name];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get named range.
+ *
+ * @param null|Worksheet $pSheet Scope. Use null for global scope
+ */
+ public function getDefinedName(string $definedName, ?Worksheet $pSheet = null): ?DefinedName
+ {
+ $returnValue = null;
+
+ if ($definedName !== '') {
+ $definedName = StringHelper::strToUpper($definedName);
+ // first look for global defined name
+ if (isset($this->definedNames[$definedName])) {
+ $returnValue = $this->definedNames[$definedName];
+ }
+
+ // then look for local defined name (has priority over global defined name if both names exist)
+ if (($pSheet !== null) && isset($this->definedNames[$pSheet->getTitle() . '!' . $definedName])) {
+ $returnValue = $this->definedNames[$pSheet->getTitle() . '!' . $definedName];
+ }
+ }
+
+ return $returnValue;
+ }
+
+ /**
+ * Remove named range.
+ *
+ * @param null|Worksheet $pSheet scope: use null for global scope
+ *
+ * @return $this
+ */
+ public function removeNamedRange(string $namedRange, ?Worksheet $pSheet = null): self
+ {
+ if ($this->getNamedRange($namedRange, $pSheet) === null) {
+ return $this;
+ }
+
+ return $this->removeDefinedName($namedRange, $pSheet);
+ }
+
+ /**
+ * Remove named formula.
+ *
+ * @param null|Worksheet $pSheet scope: use null for global scope
+ *
+ * @return $this
+ */
+ public function removeNamedFormula(string $namedFormula, ?Worksheet $pSheet = null): self
+ {
+ if ($this->getNamedFormula($namedFormula, $pSheet) === null) {
+ return $this;
+ }
+
+ return $this->removeDefinedName($namedFormula, $pSheet);
+ }
+
+ /**
+ * Remove defined name.
+ *
+ * @param null|Worksheet $pSheet scope: use null for global scope
+ *
+ * @return $this
+ */
+ public function removeDefinedName(string $definedName, ?Worksheet $pSheet = null): self
+ {
+ $definedName = StringHelper::strToUpper($definedName);
+
+ if ($pSheet === null) {
+ if (isset($this->definedNames[$definedName])) {
+ unset($this->definedNames[$definedName]);
+ }
+ } else {
+ if (isset($this->definedNames[$pSheet->getTitle() . '!' . $definedName])) {
+ unset($this->definedNames[$pSheet->getTitle() . '!' . $definedName]);
+ } elseif (isset($this->definedNames[$definedName])) {
+ unset($this->definedNames[$definedName]);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get worksheet iterator.
+ *
+ * @return Iterator
+ */
+ public function getWorksheetIterator()
+ {
+ return new Iterator($this);
+ }
+
+ /**
+ * Copy workbook (!= clone!).
+ *
+ * @return Spreadsheet
+ */
+ public function copy()
+ {
+ $copied = clone $this;
+
+ $worksheetCount = count($this->workSheetCollection);
+ for ($i = 0; $i < $worksheetCount; ++$i) {
+ $this->workSheetCollection[$i] = $this->workSheetCollection[$i]->copy();
+ $this->workSheetCollection[$i]->rebindParent($this);
+ }
+
+ return $copied;
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone()
+ {
+ foreach ($this as $key => $val) {
+ if (is_object($val) || (is_array($val))) {
+ $this->{$key} = unserialize(serialize($val));
+ }
+ }
+ }
+
+ /**
+ * Get the workbook collection of cellXfs.
+ *
+ * @return Style[]
+ */
+ public function getCellXfCollection()
+ {
+ return $this->cellXfCollection;
+ }
+
+ /**
+ * Get cellXf by index.
+ *
+ * @param int $pIndex
+ *
+ * @return Style
+ */
+ public function getCellXfByIndex($pIndex)
+ {
+ return $this->cellXfCollection[$pIndex];
+ }
+
+ /**
+ * Get cellXf by hash code.
+ *
+ * @param string $pValue
+ *
+ * @return false|Style
+ */
+ public function getCellXfByHashCode($pValue)
+ {
+ foreach ($this->cellXfCollection as $cellXf) {
+ if ($cellXf->getHashCode() === $pValue) {
+ return $cellXf;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check if style exists in style collection.
+ *
+ * @param Style $pCellStyle
+ *
+ * @return bool
+ */
+ public function cellXfExists($pCellStyle)
+ {
+ return in_array($pCellStyle, $this->cellXfCollection, true);
+ }
+
+ /**
+ * Get default style.
+ *
+ * @return Style
+ */
+ public function getDefaultStyle()
+ {
+ if (isset($this->cellXfCollection[0])) {
+ return $this->cellXfCollection[0];
+ }
+
+ throw new Exception('No default style found for this workbook');
+ }
+
+ /**
+ * Add a cellXf to the workbook.
+ */
+ public function addCellXf(Style $style): void
+ {
+ $this->cellXfCollection[] = $style;
+ $style->setIndex(count($this->cellXfCollection) - 1);
+ }
+
+ /**
+ * Remove cellXf by index. It is ensured that all cells get their xf index updated.
+ *
+ * @param int $pIndex Index to cellXf
+ */
+ public function removeCellXfByIndex($pIndex): void
+ {
+ if ($pIndex > count($this->cellXfCollection) - 1) {
+ throw new Exception('CellXf index is out of bounds.');
+ }
+
+ // first remove the cellXf
+ array_splice($this->cellXfCollection, $pIndex, 1);
+
+ // then update cellXf indexes for cells
+ foreach ($this->workSheetCollection as $worksheet) {
+ foreach ($worksheet->getCoordinates(false) as $coordinate) {
+ $cell = $worksheet->getCell($coordinate);
+ $xfIndex = $cell->getXfIndex();
+ if ($xfIndex > $pIndex) {
+ // decrease xf index by 1
+ $cell->setXfIndex($xfIndex - 1);
+ } elseif ($xfIndex == $pIndex) {
+ // set to default xf index 0
+ $cell->setXfIndex(0);
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the cellXf supervisor.
+ *
+ * @return Style
+ */
+ public function getCellXfSupervisor()
+ {
+ return $this->cellXfSupervisor;
+ }
+
+ /**
+ * Get the workbook collection of cellStyleXfs.
+ *
+ * @return Style[]
+ */
+ public function getCellStyleXfCollection()
+ {
+ return $this->cellStyleXfCollection;
+ }
+
+ /**
+ * Get cellStyleXf by index.
+ *
+ * @param int $pIndex Index to cellXf
+ *
+ * @return Style
+ */
+ public function getCellStyleXfByIndex($pIndex)
+ {
+ return $this->cellStyleXfCollection[$pIndex];
+ }
+
+ /**
+ * Get cellStyleXf by hash code.
+ *
+ * @param string $pValue
+ *
+ * @return false|Style
+ */
+ public function getCellStyleXfByHashCode($pValue)
+ {
+ foreach ($this->cellStyleXfCollection as $cellStyleXf) {
+ if ($cellStyleXf->getHashCode() === $pValue) {
+ return $cellStyleXf;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Add a cellStyleXf to the workbook.
+ */
+ public function addCellStyleXf(Style $pStyle): void
+ {
+ $this->cellStyleXfCollection[] = $pStyle;
+ $pStyle->setIndex(count($this->cellStyleXfCollection) - 1);
+ }
+
+ /**
+ * Remove cellStyleXf by index.
+ *
+ * @param int $pIndex Index to cellXf
+ */
+ public function removeCellStyleXfByIndex($pIndex): void
+ {
+ if ($pIndex > count($this->cellStyleXfCollection) - 1) {
+ throw new Exception('CellStyleXf index is out of bounds.');
+ }
+ array_splice($this->cellStyleXfCollection, $pIndex, 1);
+ }
+
+ /**
+ * Eliminate all unneeded cellXf and afterwards update the xfIndex for all cells
+ * and columns in the workbook.
+ */
+ public function garbageCollect(): void
+ {
+ // how many references are there to each cellXf ?
+ $countReferencesCellXf = [];
+ foreach ($this->cellXfCollection as $index => $cellXf) {
+ $countReferencesCellXf[$index] = 0;
+ }
+
+ foreach ($this->getWorksheetIterator() as $sheet) {
+ // from cells
+ foreach ($sheet->getCoordinates(false) as $coordinate) {
+ $cell = $sheet->getCell($coordinate);
+ ++$countReferencesCellXf[$cell->getXfIndex()];
+ }
+
+ // from row dimensions
+ foreach ($sheet->getRowDimensions() as $rowDimension) {
+ if ($rowDimension->getXfIndex() !== null) {
+ ++$countReferencesCellXf[$rowDimension->getXfIndex()];
+ }
+ }
+
+ // from column dimensions
+ foreach ($sheet->getColumnDimensions() as $columnDimension) {
+ ++$countReferencesCellXf[$columnDimension->getXfIndex()];
+ }
+ }
+
+ // remove cellXfs without references and create mapping so we can update xfIndex
+ // for all cells and columns
+ $countNeededCellXfs = 0;
+ foreach ($this->cellXfCollection as $index => $cellXf) {
+ if ($countReferencesCellXf[$index] > 0 || $index == 0) { // we must never remove the first cellXf
+ ++$countNeededCellXfs;
+ } else {
+ unset($this->cellXfCollection[$index]);
+ }
+ $map[$index] = $countNeededCellXfs - 1;
+ }
+ $this->cellXfCollection = array_values($this->cellXfCollection);
+
+ // update the index for all cellXfs
+ foreach ($this->cellXfCollection as $i => $cellXf) {
+ $cellXf->setIndex($i);
+ }
+
+ // make sure there is always at least one cellXf (there should be)
+ if (empty($this->cellXfCollection)) {
+ $this->cellXfCollection[] = new Style();
+ }
+
+ // update the xfIndex for all cells, row dimensions, column dimensions
+ foreach ($this->getWorksheetIterator() as $sheet) {
+ // for all cells
+ foreach ($sheet->getCoordinates(false) as $coordinate) {
+ $cell = $sheet->getCell($coordinate);
+ $cell->setXfIndex($map[$cell->getXfIndex()]);
+ }
+
+ // for all row dimensions
+ foreach ($sheet->getRowDimensions() as $rowDimension) {
+ if ($rowDimension->getXfIndex() !== null) {
+ $rowDimension->setXfIndex($map[$rowDimension->getXfIndex()]);
+ }
+ }
+
+ // for all column dimensions
+ foreach ($sheet->getColumnDimensions() as $columnDimension) {
+ $columnDimension->setXfIndex($map[$columnDimension->getXfIndex()]);
+ }
+
+ // also do garbage collection for all the sheets
+ $sheet->garbageCollect();
+ }
+ }
+
+ /**
+ * Return the unique ID value assigned to this spreadsheet workbook.
+ *
+ * @return string
+ */
+ public function getID()
+ {
+ return $this->uniqueID;
+ }
+
+ /**
+ * Get the visibility of the horizonal scroll bar in the application.
+ *
+ * @return bool True if horizonal scroll bar is visible
+ */
+ public function getShowHorizontalScroll()
+ {
+ return $this->showHorizontalScroll;
+ }
+
+ /**
+ * Set the visibility of the horizonal scroll bar in the application.
+ *
+ * @param bool $showHorizontalScroll True if horizonal scroll bar is visible
+ */
+ public function setShowHorizontalScroll($showHorizontalScroll): void
+ {
+ $this->showHorizontalScroll = (bool) $showHorizontalScroll;
+ }
+
+ /**
+ * Get the visibility of the vertical scroll bar in the application.
+ *
+ * @return bool True if vertical scroll bar is visible
+ */
+ public function getShowVerticalScroll()
+ {
+ return $this->showVerticalScroll;
+ }
+
+ /**
+ * Set the visibility of the vertical scroll bar in the application.
+ *
+ * @param bool $showVerticalScroll True if vertical scroll bar is visible
+ */
+ public function setShowVerticalScroll($showVerticalScroll): void
+ {
+ $this->showVerticalScroll = (bool) $showVerticalScroll;
+ }
+
+ /**
+ * Get the visibility of the sheet tabs in the application.
+ *
+ * @return bool True if the sheet tabs are visible
+ */
+ public function getShowSheetTabs()
+ {
+ return $this->showSheetTabs;
+ }
+
+ /**
+ * Set the visibility of the sheet tabs in the application.
+ *
+ * @param bool $showSheetTabs True if sheet tabs are visible
+ */
+ public function setShowSheetTabs($showSheetTabs): void
+ {
+ $this->showSheetTabs = (bool) $showSheetTabs;
+ }
+
+ /**
+ * Return whether the workbook window is minimized.
+ *
+ * @return bool true if workbook window is minimized
+ */
+ public function getMinimized()
+ {
+ return $this->minimized;
+ }
+
+ /**
+ * Set whether the workbook window is minimized.
+ *
+ * @param bool $minimized true if workbook window is minimized
+ */
+ public function setMinimized($minimized): void
+ {
+ $this->minimized = (bool) $minimized;
+ }
+
+ /**
+ * Return whether to group dates when presenting the user with
+ * filtering optiomd in the user interface.
+ *
+ * @return bool true if workbook window is minimized
+ */
+ public function getAutoFilterDateGrouping()
+ {
+ return $this->autoFilterDateGrouping;
+ }
+
+ /**
+ * Set whether to group dates when presenting the user with
+ * filtering optiomd in the user interface.
+ *
+ * @param bool $autoFilterDateGrouping true if workbook window is minimized
+ */
+ public function setAutoFilterDateGrouping($autoFilterDateGrouping): void
+ {
+ $this->autoFilterDateGrouping = (bool) $autoFilterDateGrouping;
+ }
+
+ /**
+ * Return the first sheet in the book view.
+ *
+ * @return int First sheet in book view
+ */
+ public function getFirstSheetIndex()
+ {
+ return $this->firstSheetIndex;
+ }
+
+ /**
+ * Set the first sheet in the book view.
+ *
+ * @param int $firstSheetIndex First sheet in book view
+ */
+ public function setFirstSheetIndex($firstSheetIndex): void
+ {
+ if ($firstSheetIndex >= 0) {
+ $this->firstSheetIndex = (int) $firstSheetIndex;
+ } else {
+ throw new Exception('First sheet index must be a positive integer.');
+ }
+ }
+
+ /**
+ * Return the visibility status of the workbook.
+ *
+ * This may be one of the following three values:
+ * - visibile
+ *
+ * @return string Visible status
+ */
+ public function getVisibility()
+ {
+ return $this->visibility;
+ }
+
+ /**
+ * Set the visibility status of the workbook.
+ *
+ * Valid values are:
+ * - 'visible' (self::VISIBILITY_VISIBLE):
+ * Workbook window is visible
+ * - 'hidden' (self::VISIBILITY_HIDDEN):
+ * Workbook window is hidden, but can be shown by the user
+ * via the user interface
+ * - 'veryHidden' (self::VISIBILITY_VERY_HIDDEN):
+ * Workbook window is hidden and cannot be shown in the
+ * user interface.
+ *
+ * @param string $visibility visibility status of the workbook
+ */
+ public function setVisibility($visibility): void
+ {
+ if ($visibility === null) {
+ $visibility = self::VISIBILITY_VISIBLE;
+ }
+
+ if (in_array($visibility, self::$workbookViewVisibilityValues)) {
+ $this->visibility = $visibility;
+ } else {
+ throw new Exception('Invalid visibility value.');
+ }
+ }
+
+ /**
+ * Get the ratio between the workbook tabs bar and the horizontal scroll bar.
+ * TabRatio is assumed to be out of 1000 of the horizontal window width.
+ *
+ * @return int Ratio between the workbook tabs bar and the horizontal scroll bar
+ */
+ public function getTabRatio()
+ {
+ return $this->tabRatio;
+ }
+
+ /**
+ * Set the ratio between the workbook tabs bar and the horizontal scroll bar
+ * TabRatio is assumed to be out of 1000 of the horizontal window width.
+ *
+ * @param int $tabRatio Ratio between the tabs bar and the horizontal scroll bar
+ */
+ public function setTabRatio($tabRatio): void
+ {
+ if ($tabRatio >= 0 || $tabRatio <= 1000) {
+ $this->tabRatio = (int) $tabRatio;
+ } else {
+ throw new Exception('Tab ratio must be between 0 and 1000.');
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php
new file mode 100644
index 0000000..d639275
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Alignment.php
@@ -0,0 +1,464 @@
+horizontal = null;
+ $this->vertical = null;
+ $this->textRotation = null;
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Alignment
+ */
+ public function getSharedComponent()
+ {
+ return $this->parent->getSharedComponent()->getAlignment();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['alignment' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getAlignment()->applyFromArray(
+ * [
+ * 'horizontal' => \PhpOffice\PhpSpreadsheet\Style\Alignment::HORIZONTAL_CENTER,
+ * 'vertical' => \PhpOffice\PhpSpreadsheet\Style\Alignment::VERTICAL_CENTER,
+ * 'textRotation' => 0,
+ * 'wrapText' => TRUE
+ * ]
+ * );
+ *
+ *
+ * @param array $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())
+ ->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['horizontal'])) {
+ $this->setHorizontal($pStyles['horizontal']);
+ }
+ if (isset($pStyles['vertical'])) {
+ $this->setVertical($pStyles['vertical']);
+ }
+ if (isset($pStyles['textRotation'])) {
+ $this->setTextRotation($pStyles['textRotation']);
+ }
+ if (isset($pStyles['wrapText'])) {
+ $this->setWrapText($pStyles['wrapText']);
+ }
+ if (isset($pStyles['shrinkToFit'])) {
+ $this->setShrinkToFit($pStyles['shrinkToFit']);
+ }
+ if (isset($pStyles['indent'])) {
+ $this->setIndent($pStyles['indent']);
+ }
+ if (isset($pStyles['readOrder'])) {
+ $this->setReadOrder($pStyles['readOrder']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Horizontal.
+ *
+ * @return string
+ */
+ public function getHorizontal()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHorizontal();
+ }
+
+ return $this->horizontal;
+ }
+
+ /**
+ * Set Horizontal.
+ *
+ * @param string $pValue see self::HORIZONTAL_*
+ *
+ * @return $this
+ */
+ public function setHorizontal($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = self::HORIZONTAL_GENERAL;
+ }
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['horizontal' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->horizontal = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Vertical.
+ *
+ * @return string
+ */
+ public function getVertical()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getVertical();
+ }
+
+ return $this->vertical;
+ }
+
+ /**
+ * Set Vertical.
+ *
+ * @param string $pValue see self::VERTICAL_*
+ *
+ * @return $this
+ */
+ public function setVertical($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = self::VERTICAL_BOTTOM;
+ }
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['vertical' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->vertical = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get TextRotation.
+ *
+ * @return int
+ */
+ public function getTextRotation()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getTextRotation();
+ }
+
+ return $this->textRotation;
+ }
+
+ /**
+ * Set TextRotation.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setTextRotation($pValue)
+ {
+ // Excel2007 value 255 => PhpSpreadsheet value -165
+ if ($pValue == 255) {
+ $pValue = -165;
+ }
+
+ // Set rotation
+ if (($pValue >= -90 && $pValue <= 90) || $pValue == -165) {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['textRotation' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->textRotation = $pValue;
+ }
+ } else {
+ throw new PhpSpreadsheetException('Text rotation should be a value between -90 and 90.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Wrap Text.
+ *
+ * @return bool
+ */
+ public function getWrapText()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getWrapText();
+ }
+
+ return $this->wrapText;
+ }
+
+ /**
+ * Set Wrap Text.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setWrapText($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['wrapText' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->wrapText = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Shrink to fit.
+ *
+ * @return bool
+ */
+ public function getShrinkToFit()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getShrinkToFit();
+ }
+
+ return $this->shrinkToFit;
+ }
+
+ /**
+ * Set Shrink to fit.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setShrinkToFit($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['shrinkToFit' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->shrinkToFit = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get indent.
+ *
+ * @return int
+ */
+ public function getIndent()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getIndent();
+ }
+
+ return $this->indent;
+ }
+
+ /**
+ * Set indent.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setIndent($pValue)
+ {
+ if ($pValue > 0) {
+ if (
+ $this->getHorizontal() != self::HORIZONTAL_GENERAL &&
+ $this->getHorizontal() != self::HORIZONTAL_LEFT &&
+ $this->getHorizontal() != self::HORIZONTAL_RIGHT
+ ) {
+ $pValue = 0; // indent not supported
+ }
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['indent' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->indent = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get read order.
+ *
+ * @return int
+ */
+ public function getReadOrder()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getReadOrder();
+ }
+
+ return $this->readOrder;
+ }
+
+ /**
+ * Set read order.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setReadOrder($pValue)
+ {
+ if ($pValue < 0 || $pValue > 2) {
+ $pValue = 0;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['readOrder' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->readOrder = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->horizontal .
+ $this->vertical .
+ $this->textRotation .
+ ($this->wrapText ? 't' : 'f') .
+ ($this->shrinkToFit ? 't' : 'f') .
+ $this->indent .
+ $this->readOrder .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php
new file mode 100644
index 0000000..6f3a624
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Border.php
@@ -0,0 +1,231 @@
+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 Border
+ */
+ public function getSharedComponent()
+ {
+ switch ($this->parentPropertyName) {
+ case 'allBorders':
+ case 'horizontal':
+ case 'inside':
+ case 'outline':
+ case 'vertical':
+ throw new PhpSpreadsheetException('Cannot get shared component for a pseudo-border.');
+
+ break;
+ case 'bottom':
+ return $this->parent->getSharedComponent()->getBottom();
+ case 'diagonal':
+ return $this->parent->getSharedComponent()->getDiagonal();
+ case 'left':
+ return $this->parent->getSharedComponent()->getLeft();
+ case 'right':
+ return $this->parent->getSharedComponent()->getRight();
+ case 'top':
+ return $this->parent->getSharedComponent()->getTop();
+ }
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return $this->parent->getStyleArray([$this->parentPropertyName => $array]);
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->getTop()->applyFromArray(
+ * [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ]
+ * );
+ *
+ *
+ * @param array $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['borderStyle'])) {
+ $this->setBorderStyle($pStyles['borderStyle']);
+ }
+ if (isset($pStyles['color'])) {
+ $this->getColor()->applyFromArray($pStyles['color']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Border style.
+ *
+ * @return string
+ */
+ public function getBorderStyle()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getBorderStyle();
+ }
+
+ return $this->borderStyle;
+ }
+
+ /**
+ * Set Border style.
+ *
+ * @param bool|string $pValue
+ * When passing a boolean, FALSE equates Border::BORDER_NONE
+ * and TRUE to Border::BORDER_MEDIUM
+ *
+ * @return $this
+ */
+ public function setBorderStyle($pValue)
+ {
+ if (empty($pValue)) {
+ $pValue = self::BORDER_NONE;
+ } elseif (is_bool($pValue) && $pValue) {
+ $pValue = self::BORDER_MEDIUM;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['borderStyle' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->borderStyle = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Border Color.
+ *
+ * @return Color
+ */
+ public function getColor()
+ {
+ return $this->color;
+ }
+
+ /**
+ * Set Border Color.
+ *
+ * @return $this
+ */
+ public function setColor(Color $pValue)
+ {
+ // make sure parameter is a real color and not a supervisor
+ $color = $pValue->getIsSupervisor() ? $pValue->getSharedComponent() : $pValue;
+
+ 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->borderStyle .
+ $this->color->getHashCode() .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php
new file mode 100644
index 0000000..b1aaeaa
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Borders.php
@@ -0,0 +1,411 @@
+left = new Border($isSupervisor, $isConditional);
+ $this->right = new Border($isSupervisor, $isConditional);
+ $this->top = new Border($isSupervisor, $isConditional);
+ $this->bottom = new Border($isSupervisor, $isConditional);
+ $this->diagonal = new Border($isSupervisor, $isConditional);
+ $this->diagonalDirection = self::DIAGONAL_NONE;
+
+ // Specially for supervisor
+ if ($isSupervisor) {
+ // Initialize pseudo-borders
+ $this->allBorders = new Border(true);
+ $this->outline = new Border(true);
+ $this->inside = new Border(true);
+ $this->vertical = new Border(true);
+ $this->horizontal = new Border(true);
+
+ // bind parent if we are a supervisor
+ $this->left->bindParent($this, 'left');
+ $this->right->bindParent($this, 'right');
+ $this->top->bindParent($this, 'top');
+ $this->bottom->bindParent($this, 'bottom');
+ $this->diagonal->bindParent($this, 'diagonal');
+ $this->allBorders->bindParent($this, 'allBorders');
+ $this->outline->bindParent($this, 'outline');
+ $this->inside->bindParent($this, 'inside');
+ $this->vertical->bindParent($this, 'vertical');
+ $this->horizontal->bindParent($this, 'horizontal');
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Borders
+ */
+ public function getSharedComponent()
+ {
+ return $this->parent->getSharedComponent()->getBorders();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['borders' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->applyFromArray(
+ * [
+ * 'bottom' => [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ],
+ * 'top' => [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ]
+ * ]
+ * );
+ *
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getBorders()->applyFromArray(
+ * [
+ * 'allBorders' => [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ]
+ * ]
+ * );
+ *
+ *
+ * @param array $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['left'])) {
+ $this->getLeft()->applyFromArray($pStyles['left']);
+ }
+ if (isset($pStyles['right'])) {
+ $this->getRight()->applyFromArray($pStyles['right']);
+ }
+ if (isset($pStyles['top'])) {
+ $this->getTop()->applyFromArray($pStyles['top']);
+ }
+ if (isset($pStyles['bottom'])) {
+ $this->getBottom()->applyFromArray($pStyles['bottom']);
+ }
+ if (isset($pStyles['diagonal'])) {
+ $this->getDiagonal()->applyFromArray($pStyles['diagonal']);
+ }
+ if (isset($pStyles['diagonalDirection'])) {
+ $this->setDiagonalDirection($pStyles['diagonalDirection']);
+ }
+ if (isset($pStyles['allBorders'])) {
+ $this->getLeft()->applyFromArray($pStyles['allBorders']);
+ $this->getRight()->applyFromArray($pStyles['allBorders']);
+ $this->getTop()->applyFromArray($pStyles['allBorders']);
+ $this->getBottom()->applyFromArray($pStyles['allBorders']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Left.
+ *
+ * @return Border
+ */
+ public function getLeft()
+ {
+ return $this->left;
+ }
+
+ /**
+ * Get Right.
+ *
+ * @return Border
+ */
+ public function getRight()
+ {
+ return $this->right;
+ }
+
+ /**
+ * Get Top.
+ *
+ * @return Border
+ */
+ public function getTop()
+ {
+ return $this->top;
+ }
+
+ /**
+ * Get Bottom.
+ *
+ * @return Border
+ */
+ public function getBottom()
+ {
+ return $this->bottom;
+ }
+
+ /**
+ * Get Diagonal.
+ *
+ * @return Border
+ */
+ public function getDiagonal()
+ {
+ return $this->diagonal;
+ }
+
+ /**
+ * Get AllBorders (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getAllBorders()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->allBorders;
+ }
+
+ /**
+ * Get Outline (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getOutline()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->outline;
+ }
+
+ /**
+ * Get Inside (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getInside()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->inside;
+ }
+
+ /**
+ * Get Vertical (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getVertical()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->vertical;
+ }
+
+ /**
+ * Get Horizontal (pseudo-border). Only applies to supervisor.
+ *
+ * @return Border
+ */
+ public function getHorizontal()
+ {
+ if (!$this->isSupervisor) {
+ throw new PhpSpreadsheetException('Can only get pseudo-border for supervisor.');
+ }
+
+ return $this->horizontal;
+ }
+
+ /**
+ * Get DiagonalDirection.
+ *
+ * @return int
+ */
+ public function getDiagonalDirection()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getDiagonalDirection();
+ }
+
+ return $this->diagonalDirection;
+ }
+
+ /**
+ * Set DiagonalDirection.
+ *
+ * @param int $pValue see self::DIAGONAL_*
+ *
+ * @return $this
+ */
+ public function setDiagonalDirection($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = self::DIAGONAL_NONE;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['diagonalDirection' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->diagonalDirection = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashcode();
+ }
+
+ return md5(
+ $this->getLeft()->getHashCode() .
+ $this->getRight()->getHashCode() .
+ $this->getTop()->getHashCode() .
+ $this->getBottom()->getHashCode() .
+ $this->getDiagonal()->getHashCode() .
+ $this->getDiagonalDirection() .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php
new file mode 100644
index 0000000..599776b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Color.php
@@ -0,0 +1,407 @@
+argb = $pARGB;
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Color
+ */
+ public function getSharedComponent()
+ {
+ switch ($this->parentPropertyName) {
+ case 'endColor':
+ return $this->parent->getSharedComponent()->getEndColor();
+ case 'color':
+ return $this->parent->getSharedComponent()->getColor();
+ case 'startColor':
+ return $this->parent->getSharedComponent()->getStartColor();
+ }
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return $this->parent->getStyleArray([$this->parentPropertyName => $array]);
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getFont()->getColor()->applyFromArray(['rgb' => '808080']);
+ *
+ *
+ * @param array $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['rgb'])) {
+ $this->setRGB($pStyles['rgb']);
+ }
+ if (isset($pStyles['argb'])) {
+ $this->setARGB($pStyles['argb']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get ARGB.
+ *
+ * @return string
+ */
+ public function getARGB()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getARGB();
+ }
+
+ return $this->argb;
+ }
+
+ /**
+ * Set ARGB.
+ *
+ * @param string $pValue see self::COLOR_*
+ *
+ * @return $this
+ */
+ public function setARGB($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = self::COLOR_BLACK;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['argb' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->argb = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get RGB.
+ *
+ * @return string
+ */
+ public function getRGB()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getRGB();
+ }
+
+ return substr($this->argb, 2);
+ }
+
+ /**
+ * Set RGB.
+ *
+ * @param string $pValue RGB value
+ *
+ * @return $this
+ */
+ public function setRGB($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = '000000';
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['argb' => 'FF' . $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->argb = 'FF' . $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get a specified colour component of an RGB value.
+ *
+ * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param int $offset Position within the RGB value to extract
+ * @param bool $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ *
+ * @return string The extracted colour component
+ */
+ private static function getColourComponent($RGB, $offset, $hex = true)
+ {
+ $colour = substr($RGB, $offset, 2);
+
+ return ($hex) ? $colour : hexdec($colour);
+ }
+
+ /**
+ * Get the red colour component of an RGB value.
+ *
+ * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param bool $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ *
+ * @return string The red colour component
+ */
+ public static function getRed($RGB, $hex = true)
+ {
+ return self::getColourComponent($RGB, strlen($RGB) - 6, $hex);
+ }
+
+ /**
+ * Get the green colour component of an RGB value.
+ *
+ * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param bool $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ *
+ * @return string The green colour component
+ */
+ public static function getGreen($RGB, $hex = true)
+ {
+ return self::getColourComponent($RGB, strlen($RGB) - 4, $hex);
+ }
+
+ /**
+ * Get the blue colour component of an RGB value.
+ *
+ * @param string $RGB The colour as an RGB value (e.g. FF00CCCC or CCDDEE
+ * @param bool $hex Flag indicating whether the component should be returned as a hex or a
+ * decimal value
+ *
+ * @return string The blue colour component
+ */
+ public static function getBlue($RGB, $hex = true)
+ {
+ return self::getColourComponent($RGB, strlen($RGB) - 2, $hex);
+ }
+
+ /**
+ * Adjust the brightness of a color.
+ *
+ * @param string $hex The colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
+ * @param float $adjustPercentage The percentage by which to adjust the colour as a float from -1 to 1
+ *
+ * @return string The adjusted colour as an RGBA or RGB value (e.g. FF00CCCC or CCDDEE)
+ */
+ public static function changeBrightness($hex, $adjustPercentage)
+ {
+ $rgba = (strlen($hex) === 8);
+
+ $red = self::getRed($hex, false);
+ $green = self::getGreen($hex, false);
+ $blue = self::getBlue($hex, false);
+ if ($adjustPercentage > 0) {
+ $red += (255 - $red) * $adjustPercentage;
+ $green += (255 - $green) * $adjustPercentage;
+ $blue += (255 - $blue) * $adjustPercentage;
+ } else {
+ $red += $red * $adjustPercentage;
+ $green += $green * $adjustPercentage;
+ $blue += $blue * $adjustPercentage;
+ }
+
+ if ($red < 0) {
+ $red = 0;
+ } elseif ($red > 255) {
+ $red = 255;
+ }
+ if ($green < 0) {
+ $green = 0;
+ } elseif ($green > 255) {
+ $green = 255;
+ }
+ if ($blue < 0) {
+ $blue = 0;
+ } elseif ($blue > 255) {
+ $blue = 255;
+ }
+
+ $rgb = strtoupper(
+ str_pad(dechex((int) $red), 2, '0', 0) .
+ str_pad(dechex((int) $green), 2, '0', 0) .
+ str_pad(dechex((int) $blue), 2, '0', 0)
+ );
+
+ return (($rgba) ? 'FF' : '') . $rgb;
+ }
+
+ /**
+ * Get indexed color.
+ *
+ * @param int $pIndex Index entry point into the colour array
+ * @param bool $background Flag to indicate whether default background or foreground colour
+ * should be returned if the indexed colour doesn't exist
+ *
+ * @return self
+ */
+ public static function indexedColor($pIndex, $background = false)
+ {
+ // Clean parameter
+ $pIndex = (int) $pIndex;
+
+ // Indexed colors
+ if (self::$indexedColors === null) {
+ self::$indexedColors = [
+ 1 => 'FF000000', // System Colour #1 - Black
+ 2 => 'FFFFFFFF', // System Colour #2 - White
+ 3 => 'FFFF0000', // System Colour #3 - Red
+ 4 => 'FF00FF00', // System Colour #4 - Green
+ 5 => 'FF0000FF', // System Colour #5 - Blue
+ 6 => 'FFFFFF00', // System Colour #6 - Yellow
+ 7 => 'FFFF00FF', // System Colour #7- Magenta
+ 8 => 'FF00FFFF', // System Colour #8- Cyan
+ 9 => 'FF800000', // Standard Colour #9
+ 10 => 'FF008000', // Standard Colour #10
+ 11 => 'FF000080', // Standard Colour #11
+ 12 => 'FF808000', // Standard Colour #12
+ 13 => 'FF800080', // Standard Colour #13
+ 14 => 'FF008080', // Standard Colour #14
+ 15 => 'FFC0C0C0', // Standard Colour #15
+ 16 => 'FF808080', // Standard Colour #16
+ 17 => 'FF9999FF', // Chart Fill Colour #17
+ 18 => 'FF993366', // Chart Fill Colour #18
+ 19 => 'FFFFFFCC', // Chart Fill Colour #19
+ 20 => 'FFCCFFFF', // Chart Fill Colour #20
+ 21 => 'FF660066', // Chart Fill Colour #21
+ 22 => 'FFFF8080', // Chart Fill Colour #22
+ 23 => 'FF0066CC', // Chart Fill Colour #23
+ 24 => 'FFCCCCFF', // Chart Fill Colour #24
+ 25 => 'FF000080', // Chart Line Colour #25
+ 26 => 'FFFF00FF', // Chart Line Colour #26
+ 27 => 'FFFFFF00', // Chart Line Colour #27
+ 28 => 'FF00FFFF', // Chart Line Colour #28
+ 29 => 'FF800080', // Chart Line Colour #29
+ 30 => 'FF800000', // Chart Line Colour #30
+ 31 => 'FF008080', // Chart Line Colour #31
+ 32 => 'FF0000FF', // Chart Line Colour #32
+ 33 => 'FF00CCFF', // Standard Colour #33
+ 34 => 'FFCCFFFF', // Standard Colour #34
+ 35 => 'FFCCFFCC', // Standard Colour #35
+ 36 => 'FFFFFF99', // Standard Colour #36
+ 37 => 'FF99CCFF', // Standard Colour #37
+ 38 => 'FFFF99CC', // Standard Colour #38
+ 39 => 'FFCC99FF', // Standard Colour #39
+ 40 => 'FFFFCC99', // Standard Colour #40
+ 41 => 'FF3366FF', // Standard Colour #41
+ 42 => 'FF33CCCC', // Standard Colour #42
+ 43 => 'FF99CC00', // Standard Colour #43
+ 44 => 'FFFFCC00', // Standard Colour #44
+ 45 => 'FFFF9900', // Standard Colour #45
+ 46 => 'FFFF6600', // Standard Colour #46
+ 47 => 'FF666699', // Standard Colour #47
+ 48 => 'FF969696', // Standard Colour #48
+ 49 => 'FF003366', // Standard Colour #49
+ 50 => 'FF339966', // Standard Colour #50
+ 51 => 'FF003300', // Standard Colour #51
+ 52 => 'FF333300', // Standard Colour #52
+ 53 => 'FF993300', // Standard Colour #53
+ 54 => 'FF993366', // Standard Colour #54
+ 55 => 'FF333399', // Standard Colour #55
+ 56 => 'FF333333', // Standard Colour #56
+ ];
+ }
+
+ if (isset(self::$indexedColors[$pIndex])) {
+ return new self(self::$indexedColors[$pIndex]);
+ }
+
+ if ($background) {
+ return new self(self::COLOR_WHITE);
+ }
+
+ return new self(self::COLOR_BLACK);
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->argb .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php
new file mode 100644
index 0000000..92085c8
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Conditional.php
@@ -0,0 +1,274 @@
+style = new Style(false, true);
+ }
+
+ /**
+ * Get Condition type.
+ *
+ * @return string
+ */
+ public function getConditionType()
+ {
+ return $this->conditionType;
+ }
+
+ /**
+ * Set Condition type.
+ *
+ * @param string $pValue Condition type, see self::CONDITION_*
+ *
+ * @return $this
+ */
+ public function setConditionType($pValue)
+ {
+ $this->conditionType = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Operator type.
+ *
+ * @return string
+ */
+ public function getOperatorType()
+ {
+ return $this->operatorType;
+ }
+
+ /**
+ * Set Operator type.
+ *
+ * @param string $pValue Conditional operator type, see self::OPERATOR_*
+ *
+ * @return $this
+ */
+ public function setOperatorType($pValue)
+ {
+ $this->operatorType = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get text.
+ *
+ * @return string
+ */
+ public function getText()
+ {
+ return $this->text;
+ }
+
+ /**
+ * Set text.
+ *
+ * @param string $value
+ *
+ * @return $this
+ */
+ public function setText($value)
+ {
+ $this->text = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get StopIfTrue.
+ *
+ * @return bool
+ */
+ public function getStopIfTrue()
+ {
+ return $this->stopIfTrue;
+ }
+
+ /**
+ * Set StopIfTrue.
+ *
+ * @param bool $value
+ *
+ * @return $this
+ */
+ public function setStopIfTrue($value)
+ {
+ $this->stopIfTrue = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get Conditions.
+ *
+ * @return string[]
+ */
+ public function getConditions()
+ {
+ return $this->condition;
+ }
+
+ /**
+ * Set Conditions.
+ *
+ * @param string[] $pValue Condition
+ *
+ * @return $this
+ */
+ public function setConditions($pValue)
+ {
+ if (!is_array($pValue)) {
+ $pValue = [$pValue];
+ }
+ $this->condition = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Add Condition.
+ *
+ * @param string $pValue Condition
+ *
+ * @return $this
+ */
+ public function addCondition($pValue)
+ {
+ $this->condition[] = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Style.
+ *
+ * @return Style
+ */
+ public function getStyle()
+ {
+ return $this->style;
+ }
+
+ /**
+ * Set Style.
+ *
+ * @param Style $pValue
+ *
+ * @return $this
+ */
+ public function setStyle(?Style $pValue = null)
+ {
+ $this->style = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->conditionType .
+ $this->operatorType .
+ implode(';', $this->condition) .
+ $this->style->getHashCode() .
+ __CLASS__
+ );
+ }
+
+ /**
+ * 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/Style/Fill.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php
new file mode 100644
index 0000000..9ddcf83
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Fill.php
@@ -0,0 +1,314 @@
+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()
+ {
+ return $this->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,
+ * 'startColor' => [
+ * 'rgb' => '000000'
+ * ],
+ * 'endColor' => [
+ * 'argb' => 'FFFFFFFF'
+ * ]
+ * ]
+ * );
+ *
+ *
+ * @param array $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['fillType'])) {
+ $this->setFillType($pStyles['fillType']);
+ }
+ if (isset($pStyles['rotation'])) {
+ $this->setRotation($pStyles['rotation']);
+ }
+ if (isset($pStyles['startColor'])) {
+ $this->getStartColor()->applyFromArray($pStyles['startColor']);
+ }
+ if (isset($pStyles['endColor'])) {
+ $this->getEndColor()->applyFromArray($pStyles['endColor']);
+ }
+ if (isset($pStyles['color'])) {
+ $this->getStartColor()->applyFromArray($pStyles['color']);
+ $this->getEndColor()->applyFromArray($pStyles['color']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Fill Type.
+ *
+ * @return string
+ */
+ public function getFillType()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getFillType();
+ }
+
+ return $this->fillType;
+ }
+
+ /**
+ * Set Fill Type.
+ *
+ * @param string $pValue Fill type, see self::FILL_*
+ *
+ * @return $this
+ */
+ public function setFillType($pValue)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['fillType' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->fillType = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Rotation.
+ *
+ * @return float
+ */
+ public function getRotation()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getRotation();
+ }
+
+ return $this->rotation;
+ }
+
+ /**
+ * Set Rotation.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setRotation($pValue)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['rotation' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->rotation = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Start Color.
+ *
+ * @return Color
+ */
+ public function getStartColor()
+ {
+ return $this->startColor;
+ }
+
+ /**
+ * Set Start Color.
+ *
+ * @return $this
+ */
+ public function setStartColor(Color $pValue)
+ {
+ // make sure parameter is a real color and not a supervisor
+ $color = $pValue->getIsSupervisor() ? $pValue->getSharedComponent() : $pValue;
+
+ 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 $pValue)
+ {
+ // make sure parameter is a real color and not a supervisor
+ $color = $pValue->getIsSupervisor() ? $pValue->getSharedComponent() : $pValue;
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getEndColor()->getStyleArray(['argb' => $color->getARGB()]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->endColor = $color;
+ }
+
+ return $this;
+ }
+
+ /**
+ * 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() : '') .
+ __CLASS__
+ );
+ }
+}
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..7b735bc
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Font.php
@@ -0,0 +1,542 @@
+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()
+ {
+ return $this->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 $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['name'])) {
+ $this->setName($pStyles['name']);
+ }
+ if (isset($pStyles['bold'])) {
+ $this->setBold($pStyles['bold']);
+ }
+ if (isset($pStyles['italic'])) {
+ $this->setItalic($pStyles['italic']);
+ }
+ if (isset($pStyles['superscript'])) {
+ $this->setSuperscript($pStyles['superscript']);
+ }
+ if (isset($pStyles['subscript'])) {
+ $this->setSubscript($pStyles['subscript']);
+ }
+ if (isset($pStyles['underline'])) {
+ $this->setUnderline($pStyles['underline']);
+ }
+ if (isset($pStyles['strikethrough'])) {
+ $this->setStrikethrough($pStyles['strikethrough']);
+ }
+ if (isset($pStyles['color'])) {
+ $this->getColor()->applyFromArray($pStyles['color']);
+ }
+ if (isset($pStyles['size'])) {
+ $this->setSize($pStyles['size']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getName();
+ }
+
+ return $this->name;
+ }
+
+ /**
+ * Set Name.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setName($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = 'Calibri';
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['name' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->name = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Size.
+ *
+ * @return float
+ */
+ public function getSize()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getSize();
+ }
+
+ return $this->size;
+ }
+
+ /**
+ * Set Size.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setSize($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = 10;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['size' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->size = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Bold.
+ *
+ * @return bool
+ */
+ public function getBold()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getBold();
+ }
+
+ return $this->bold;
+ }
+
+ /**
+ * Set Bold.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setBold($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['bold' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->bold = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Italic.
+ *
+ * @return bool
+ */
+ public function getItalic()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getItalic();
+ }
+
+ return $this->italic;
+ }
+
+ /**
+ * Set Italic.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setItalic($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['italic' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->italic = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Superscript.
+ *
+ * @return bool
+ */
+ public function getSuperscript()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getSuperscript();
+ }
+
+ return $this->superscript;
+ }
+
+ /**
+ * Set Superscript.
+ *
+ * @return $this
+ */
+ public function setSuperscript(bool $pValue)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['superscript' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->superscript = $pValue;
+ if ($this->superscript) {
+ $this->subscript = false;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Subscript.
+ *
+ * @return bool
+ */
+ public function getSubscript()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getSubscript();
+ }
+
+ return $this->subscript;
+ }
+
+ /**
+ * Set Subscript.
+ *
+ * @return $this
+ */
+ public function setSubscript(bool $pValue)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['subscript' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->subscript = $pValue;
+ if ($this->subscript) {
+ $this->superscript = false;
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Underline.
+ *
+ * @return string
+ */
+ public function getUnderline()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getUnderline();
+ }
+
+ return $this->underline;
+ }
+
+ /**
+ * Set Underline.
+ *
+ * @param bool|string $pValue \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($pValue)
+ {
+ if (is_bool($pValue)) {
+ $pValue = ($pValue) ? self::UNDERLINE_SINGLE : self::UNDERLINE_NONE;
+ } elseif ($pValue == '') {
+ $pValue = self::UNDERLINE_NONE;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['underline' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->underline = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Strikethrough.
+ *
+ * @return bool
+ */
+ public function getStrikethrough()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getStrikethrough();
+ }
+
+ return $this->strikethrough;
+ }
+
+ /**
+ * Set Strikethrough.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setStrikethrough($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = false;
+ }
+
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['strikethrough' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->strikethrough = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Color.
+ *
+ * @return Color
+ */
+ public function getColor()
+ {
+ return $this->color;
+ }
+
+ /**
+ * Set Color.
+ *
+ * @return $this
+ */
+ public function setColor(Color $pValue)
+ {
+ // make sure parameter is a real color and not a supervisor
+ $color = $pValue->getIsSupervisor() ? $pValue->getSharedComponent() : $pValue;
+
+ 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__
+ );
+ }
+}
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..f5a2de7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/NumberFormat.php
@@ -0,0 +1,873 @@
+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()
+ {
+ return $this->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 $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['formatCode'])) {
+ $this->setFormatCode($pStyles['formatCode']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Format Code.
+ *
+ * @return string
+ */
+ public function getFormatCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getFormatCode();
+ }
+ if ($this->builtInFormatCode !== false) {
+ return self::builtInFormatCode($this->builtInFormatCode);
+ }
+
+ return $this->formatCode;
+ }
+
+ /**
+ * Set Format Code.
+ *
+ * @param string $pValue see self::FORMAT_*
+ *
+ * @return $this
+ */
+ public function setFormatCode($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = self::FORMAT_GENERAL;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['formatCode' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->formatCode = $pValue;
+ $this->builtInFormatCode = self::builtInFormatCodeIndex($pValue);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Built-In Format Code.
+ *
+ * @return int
+ */
+ public function getBuiltInFormatCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getBuiltInFormatCode();
+ }
+
+ return $this->builtInFormatCode;
+ }
+
+ /**
+ * Set Built-In Format Code.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setBuiltInFormatCode($pValue)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['formatCode' => self::builtInFormatCode($pValue)]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->builtInFormatCode = $pValue;
+ $this->formatCode = self::builtInFormatCode($pValue);
+ }
+
+ 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 $pIndex
+ *
+ * @return string
+ */
+ public static function builtInFormatCode($pIndex)
+ {
+ // Clean parameter
+ $pIndex = (int) $pIndex;
+
+ // Ensure built-in format codes are available
+ self::fillBuiltInFormatCodes();
+
+ // Lookup format code
+ if (isset(self::$builtInFormats[$pIndex])) {
+ return self::$builtInFormats[$pIndex];
+ }
+
+ return '';
+ }
+
+ /**
+ * Get built-in format code index.
+ *
+ * @param string $formatCode
+ *
+ * @return bool|int
+ */
+ public static function builtInFormatCodeIndex($formatCode)
+ {
+ // Ensure built-in format codes are available
+ self::fillBuiltInFormatCodes();
+
+ // Lookup format code
+ if (array_key_exists($formatCode, self::$flippedBuiltInFormats)) {
+ return self::$flippedBuiltInFormats[$formatCode];
+ }
+
+ 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__
+ );
+ }
+
+ /**
+ * Search/replace values to convert Excel date/time format masks to PHP format masks.
+ *
+ * @var array
+ */
+ private static $dateFormatReplacements = [
+ // first remove escapes related to non-format characters
+ '\\' => '',
+ // 12-hour suffix
+ 'am/pm' => 'A',
+ // 4-digit year
+ 'e' => 'Y',
+ 'yyyy' => 'Y',
+ // 2-digit year
+ 'yy' => 'y',
+ // first letter of month - no php equivalent
+ 'mmmmm' => 'M',
+ // full month name
+ 'mmmm' => 'F',
+ // short month name
+ 'mmm' => 'M',
+ // mm is minutes if time, but can also be month w/leading zero
+ // so we try to identify times be the inclusion of a : separator in the mask
+ // It isn't perfect, but the best way I know how
+ ':mm' => ':i',
+ 'mm:' => 'i:',
+ // month leading zero
+ 'mm' => 'm',
+ // month no leading zero
+ 'm' => 'n',
+ // full day of week name
+ 'dddd' => 'l',
+ // short day of week name
+ 'ddd' => 'D',
+ // days leading zero
+ 'dd' => 'd',
+ // days no leading zero
+ 'd' => 'j',
+ // seconds
+ 'ss' => 's',
+ // fractional seconds - no php equivalent
+ '.s' => '',
+ ];
+
+ /**
+ * Search/replace values to convert Excel date/time format masks hours to PHP format masks (24 hr clock).
+ *
+ * @var array
+ */
+ private static $dateFormatReplacements24 = [
+ 'hh' => 'H',
+ 'h' => 'G',
+ ];
+
+ /**
+ * Search/replace values to convert Excel date/time format masks hours to PHP format masks (12 hr clock).
+ *
+ * @var array
+ */
+ private static $dateFormatReplacements12 = [
+ 'hh' => 'h',
+ 'h' => 'g',
+ ];
+
+ private static function setLowercaseCallback($matches)
+ {
+ return mb_strtolower($matches[0]);
+ }
+
+ private static function escapeQuotesCallback($matches)
+ {
+ return '\\' . implode('\\', str_split($matches[1]));
+ }
+
+ private static function formatAsDate(&$value, &$format): void
+ {
+ // strip off first part containing e.g. [$-F800] or [$USD-409]
+ // general syntax: [$-]
+ // language info is in hexadecimal
+ // strip off chinese part like [DBNum1][$-804]
+ $format = preg_replace('/^(\[[0-9A-Za-z]*\])*(\[\$[A-Z]*-[0-9A-F]*\])/i', '', $format);
+
+ // OpenOffice.org uses upper-case number formats, e.g. 'YYYY', convert to lower-case;
+ // but we don't want to change any quoted strings
+ $format = preg_replace_callback('/(?:^|")([^"]*)(?:$|")/', ['self', 'setLowercaseCallback'], $format);
+
+ // Only process the non-quoted blocks for date format characters
+ $blocks = explode('"', $format);
+ foreach ($blocks as $key => &$block) {
+ if ($key % 2 == 0) {
+ $block = strtr($block, self::$dateFormatReplacements);
+ if (!strpos($block, 'A')) {
+ // 24-hour time format
+ // when [h]:mm format, the [h] should replace to the hours of the value * 24
+ if (false !== strpos($block, '[h]')) {
+ $hours = (int) ($value * 24);
+ $block = str_replace('[h]', $hours, $block);
+
+ continue;
+ }
+ $block = strtr($block, self::$dateFormatReplacements24);
+ } else {
+ // 12-hour time format
+ $block = strtr($block, self::$dateFormatReplacements12);
+ }
+ }
+ }
+ $format = implode('"', $blocks);
+
+ // escape any quoted characters so that DateTime format() will render them correctly
+ $format = preg_replace_callback('/"(.*)"/U', ['self', 'escapeQuotesCallback'], $format);
+
+ $dateObj = Date::excelToDateTimeObject($value);
+ $value = $dateObj->format($format);
+ }
+
+ private static function formatAsPercentage(&$value, &$format): void
+ {
+ if ($format === self::FORMAT_PERCENTAGE) {
+ $value = round((100 * $value), 0) . '%';
+ } else {
+ if (preg_match('/\.[#0]+/', $format, $m)) {
+ $s = substr($m[0], 0, 1) . (strlen($m[0]) - 1);
+ $format = str_replace($m[0], $s, $format);
+ }
+ if (preg_match('/^[#0]+/', $format, $m)) {
+ $format = str_replace($m[0], strlen($m[0]), $format);
+ }
+ $format = '%' . str_replace('%', 'f%%', $format);
+
+ $value = sprintf($format, 100 * $value);
+ }
+ }
+
+ private static function formatAsFraction(&$value, &$format): void
+ {
+ $sign = ($value < 0) ? '-' : '';
+
+ $integerPart = floor(abs($value));
+ $decimalPart = trim(fmod(abs($value), 1), '0.');
+ $decimalLength = strlen($decimalPart);
+ $decimalDivisor = 10 ** $decimalLength;
+
+ $GCD = MathTrig::GCD($decimalPart, $decimalDivisor);
+
+ $adjustedDecimalPart = $decimalPart / $GCD;
+ $adjustedDecimalDivisor = $decimalDivisor / $GCD;
+
+ if ((strpos($format, '0') !== false)) {
+ $value = "$sign$integerPart $adjustedDecimalPart/$adjustedDecimalDivisor";
+ } elseif ((strpos($format, '#') !== false)) {
+ if ($integerPart == 0) {
+ $value = "$sign$adjustedDecimalPart/$adjustedDecimalDivisor";
+ } else {
+ $value = "$sign$integerPart $adjustedDecimalPart/$adjustedDecimalDivisor";
+ }
+ } elseif ((substr($format, 0, 3) == '? ?')) {
+ if ($integerPart == 0) {
+ $integerPart = '';
+ }
+ $value = "$sign$integerPart $adjustedDecimalPart/$adjustedDecimalDivisor";
+ } else {
+ $adjustedDecimalPart += $integerPart * $adjustedDecimalDivisor;
+ $value = "$sign$adjustedDecimalPart/$adjustedDecimalDivisor";
+ }
+ }
+
+ private static function mergeComplexNumberFormatMasks($numbers, $masks)
+ {
+ $decimalCount = strlen($numbers[1]);
+ $postDecimalMasks = [];
+
+ do {
+ $tempMask = array_pop($masks);
+ $postDecimalMasks[] = $tempMask;
+ $decimalCount -= strlen($tempMask);
+ } while ($decimalCount > 0);
+
+ return [
+ implode('.', $masks),
+ implode('.', array_reverse($postDecimalMasks)),
+ ];
+ }
+
+ private static function processComplexNumberFormatMask($number, $mask)
+ {
+ $result = $number;
+ $maskingBlockCount = preg_match_all('/0+/', $mask, $maskingBlocks, PREG_OFFSET_CAPTURE);
+
+ if ($maskingBlockCount > 1) {
+ $maskingBlocks = array_reverse($maskingBlocks[0]);
+
+ foreach ($maskingBlocks as $block) {
+ $divisor = 1 . $block[0];
+ $size = strlen($block[0]);
+ $offset = $block[1];
+
+ $blockValue = sprintf(
+ '%0' . $size . 'd',
+ fmod($number, $divisor)
+ );
+ $number = floor($number / $divisor);
+ $mask = substr_replace($mask, $blockValue, $offset, $size);
+ }
+ if ($number > 0) {
+ $mask = substr_replace($mask, $number, $offset, 0);
+ }
+ $result = $mask;
+ }
+
+ return $result;
+ }
+
+ private static function complexNumberFormatMask($number, $mask, $splitOnPoint = true)
+ {
+ $sign = ($number < 0.0);
+ $number = abs($number);
+
+ if ($splitOnPoint && strpos($mask, '.') !== false && strpos($number, '.') !== false) {
+ $numbers = explode('.', $number);
+ $masks = explode('.', $mask);
+ if (count($masks) > 2) {
+ $masks = self::mergeComplexNumberFormatMasks($numbers, $masks);
+ }
+ $result1 = self::complexNumberFormatMask($numbers[0], $masks[0], false);
+ $result2 = strrev(self::complexNumberFormatMask(strrev($numbers[1]), strrev($masks[1]), false));
+
+ return (($sign) ? '-' : '') . $result1 . '.' . $result2;
+ }
+
+ $result = self::processComplexNumberFormatMask($number, $mask);
+
+ return (($sign) ? '-' : '') . $result;
+ }
+
+ private static function formatStraightNumericValue($value, $format, array $matches, $useThousands, $number_regex)
+ {
+ $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(
+ $value,
+ strlen($right),
+ StringHelper::getDecimalSeparator(),
+ StringHelper::getThousandsSeparator()
+ );
+ $value = preg_replace($number_regex, $value, $format);
+ } else {
+ if (preg_match('/[0#]E[+-]0/i', $format)) {
+ // Scientific format
+ $value = sprintf('%5.2E', $value);
+ } elseif (preg_match('/0([^\d\.]+)0/', $format) || substr_count($format, '.') > 1) {
+ if ($value == (int) $value && substr_count($format, '.') === 1) {
+ $value *= 10 ** strlen(explode('.', $format)[1]);
+ }
+ $value = self::complexNumberFormatMask($value, $format);
+ } else {
+ $sprintf_pattern = "%0$minWidth." . strlen($right) . 'f';
+ $value = sprintf($sprintf_pattern, $value);
+ $value = preg_replace($number_regex, $value, $format);
+ }
+ }
+
+ return $value;
+ }
+
+ private static function formatAsNumber($value, $format)
+ {
+ // 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 === self::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 = 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 = preg_match('/(#,#|0,0)/', $format);
+ if ($useThousands) {
+ $format = preg_replace('/0,0/', '00', $format);
+ $format = preg_replace('/#,#/', '##', $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 = preg_replace('/0,+/', '0', $format);
+ $format = preg_replace('/#,+/', '#', $format);
+ }
+
+ if (preg_match('/#?.*\?\/\?/', $format, $m)) {
+ if ($value != (int) $value) {
+ self::formatAsFraction($value, $format);
+ }
+ } else {
+ // Handle the number itself
+
+ // scale number
+ $value = $value / $scale;
+ // Strip #
+ $format = preg_replace('/\\#/', '0', $format);
+ // Remove locale code [$-###]
+ $format = preg_replace('/\[\$\-.*\]/', '', $format);
+
+ $n = '/\\[[^\\]]+\\]/';
+ $m = preg_replace($n, '', $format);
+ $number_regex = '/(0+)(\\.?)(0*)/';
+ if (preg_match($number_regex, $m, $matches)) {
+ $value = self::formatStraightNumericValue($value, $format, $matches, $useThousands, $number_regex);
+ }
+ }
+
+ if (preg_match('/\[\$(.*)\]/u', $format, $m)) {
+ // Currency or Accounting
+ $currencyCode = $m[1];
+ [$currencyCode] = explode('-', $currencyCode);
+ if ($currencyCode == '') {
+ $currencyCode = StringHelper::getCurrencyCode();
+ }
+ $value = preg_replace('/\[\$([^\]]*)\]/u', $currencyCode, $value);
+ }
+
+ return $value;
+ }
+
+ private static function splitFormatCompare($value, $cond, $val, $dfcond, $dfval)
+ {
+ if (!$cond) {
+ $cond = $dfcond;
+ $val = $dfval;
+ }
+ switch ($cond) {
+ case '>':
+ 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) . ')\\]/';
+ $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 = self::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 === self::FORMAT_GENERAL) || ($format === self::FORMAT_TEXT)) {
+ return $value;
+ }
+
+ // Convert any other escaped characters to quoted strings, e.g. (\T to "T")
+ $format = preg_replace('/(\\\(((.)(?!((AM\/PM)|(A\/P))))|([^ ])))(?=(?:[^"]|"[^"]*")*$)/u', '"${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('/_./', ' ', $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
+ self::formatAsDate($value, $format);
+ } else {
+ if (substr($format, 0, 1) === '"' && substr($format, -1, 1) === '"') {
+ $value = substr($format, 1, -1);
+ } elseif (preg_match('/%$/', $format)) {
+ // % number format
+ self::formatAsPercentage($value, $format);
+ } else {
+ $value = self::formatAsNumber($value, $format);
+ }
+ }
+
+ // Additional formatting provided by callback function
+ if ($callBack !== null) {
+ [$writerInstance, $function] = $callBack;
+ $value = $writerInstance->$function($value, $colors);
+ }
+
+ return $value;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Protection.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Protection.php
new file mode 100644
index 0000000..e65ca80
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Protection.php
@@ -0,0 +1,186 @@
+locked = self::PROTECTION_INHERIT;
+ $this->hidden = self::PROTECTION_INHERIT;
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Protection
+ */
+ public function getSharedComponent()
+ {
+ return $this->parent->getSharedComponent()->getProtection();
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['protection' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->getLocked()->applyFromArray(
+ * [
+ * 'locked' => TRUE,
+ * 'hidden' => FALSE
+ * ]
+ * );
+ *
+ *
+ * @param array $pStyles Array containing style information
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles)
+ {
+ if ($this->isSupervisor) {
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($this->getStyleArray($pStyles));
+ } else {
+ if (isset($pStyles['locked'])) {
+ $this->setLocked($pStyles['locked']);
+ }
+ if (isset($pStyles['hidden'])) {
+ $this->setHidden($pStyles['hidden']);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get locked.
+ *
+ * @return string
+ */
+ public function getLocked()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getLocked();
+ }
+
+ return $this->locked;
+ }
+
+ /**
+ * Set locked.
+ *
+ * @param string $pValue see self::PROTECTION_*
+ *
+ * @return $this
+ */
+ public function setLocked($pValue)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['locked' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->locked = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hidden.
+ *
+ * @return string
+ */
+ public function getHidden()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHidden();
+ }
+
+ return $this->hidden;
+ }
+
+ /**
+ * Set hidden.
+ *
+ * @param string $pValue see self::PROTECTION_*
+ *
+ * @return $this
+ */
+ public function setHidden($pValue)
+ {
+ if ($this->isSupervisor) {
+ $styleArray = $this->getStyleArray(['hidden' => $pValue]);
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->hidden = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getHashCode();
+ }
+
+ return md5(
+ $this->locked .
+ $this->hidden .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php
new file mode 100644
index 0000000..fe05c7b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Style.php
@@ -0,0 +1,639 @@
+conditionalStyles = [];
+ $this->font = new Font($isSupervisor, $isConditional);
+ $this->fill = new Fill($isSupervisor, $isConditional);
+ $this->borders = new Borders($isSupervisor, $isConditional);
+ $this->alignment = new Alignment($isSupervisor, $isConditional);
+ $this->numberFormat = new NumberFormat($isSupervisor, $isConditional);
+ $this->protection = new Protection($isSupervisor, $isConditional);
+
+ // bind parent if we are a supervisor
+ if ($isSupervisor) {
+ $this->font->bindParent($this);
+ $this->fill->bindParent($this);
+ $this->borders->bindParent($this);
+ $this->alignment->bindParent($this);
+ $this->numberFormat->bindParent($this);
+ $this->protection->bindParent($this);
+ }
+ }
+
+ /**
+ * Get the shared style component for the currently active cell in currently active sheet.
+ * Only used for style supervisor.
+ *
+ * @return Style
+ */
+ public function getSharedComponent()
+ {
+ $activeSheet = $this->getActiveSheet();
+ $selectedCell = $this->getActiveCell(); // e.g. 'A1'
+
+ if ($activeSheet->cellExists($selectedCell)) {
+ $xfIndex = $activeSheet->getCell($selectedCell)->getXfIndex();
+ } else {
+ $xfIndex = 0;
+ }
+
+ return $this->parent->getCellXfByIndex($xfIndex);
+ }
+
+ /**
+ * Get parent. Only used for style supervisor.
+ *
+ * @return Spreadsheet
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Build style array from subcomponents.
+ *
+ * @param array $array
+ *
+ * @return array
+ */
+ public function getStyleArray($array)
+ {
+ return ['quotePrefix' => $array];
+ }
+
+ /**
+ * Apply styles from array.
+ *
+ *
+ * $spreadsheet->getActiveSheet()->getStyle('B2')->applyFromArray(
+ * [
+ * 'font' => [
+ * 'name' => 'Arial',
+ * 'bold' => true,
+ * 'italic' => false,
+ * 'underline' => Font::UNDERLINE_DOUBLE,
+ * 'strikethrough' => false,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ],
+ * 'borders' => [
+ * 'bottom' => [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ],
+ * 'top' => [
+ * 'borderStyle' => Border::BORDER_DASHDOT,
+ * 'color' => [
+ * 'rgb' => '808080'
+ * ]
+ * ]
+ * ],
+ * 'alignment' => [
+ * 'horizontal' => Alignment::HORIZONTAL_CENTER,
+ * 'vertical' => Alignment::VERTICAL_CENTER,
+ * 'wrapText' => true,
+ * ],
+ * 'quotePrefix' => true
+ * ]
+ * );
+ *
+ *
+ * @param array $pStyles Array containing style information
+ * @param bool $pAdvanced advanced mode for setting borders
+ *
+ * @return $this
+ */
+ public function applyFromArray(array $pStyles, $pAdvanced = true)
+ {
+ if ($this->isSupervisor) {
+ $pRange = $this->getSelectedCells();
+
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ // Is it a cell range or a single cell?
+ if (strpos($pRange, ':') === false) {
+ $rangeA = $pRange;
+ $rangeB = $pRange;
+ } else {
+ [$rangeA, $rangeB] = explode(':', $pRange);
+ }
+
+ // Calculate range outer borders
+ $rangeStart = Coordinate::coordinateFromString($rangeA);
+ $rangeEnd = Coordinate::coordinateFromString($rangeB);
+
+ // Translate column into index
+ $rangeStart[0] = Coordinate::columnIndexFromString($rangeStart[0]);
+ $rangeEnd[0] = Coordinate::columnIndexFromString($rangeEnd[0]);
+
+ // Make sure we can loop upwards on rows and columns
+ if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
+ $tmp = $rangeStart;
+ $rangeStart = $rangeEnd;
+ $rangeEnd = $tmp;
+ }
+
+ // ADVANCED MODE:
+ if ($pAdvanced && isset($pStyles['borders'])) {
+ // 'allBorders' is a shorthand property for 'outline' and 'inside' and
+ // it applies to components that have not been set explicitly
+ if (isset($pStyles['borders']['allBorders'])) {
+ foreach (['outline', 'inside'] as $component) {
+ if (!isset($pStyles['borders'][$component])) {
+ $pStyles['borders'][$component] = $pStyles['borders']['allBorders'];
+ }
+ }
+ unset($pStyles['borders']['allBorders']); // not needed any more
+ }
+ // 'outline' is a shorthand property for 'top', 'right', 'bottom', 'left'
+ // it applies to components that have not been set explicitly
+ if (isset($pStyles['borders']['outline'])) {
+ foreach (['top', 'right', 'bottom', 'left'] as $component) {
+ if (!isset($pStyles['borders'][$component])) {
+ $pStyles['borders'][$component] = $pStyles['borders']['outline'];
+ }
+ }
+ unset($pStyles['borders']['outline']); // not needed any more
+ }
+ // 'inside' is a shorthand property for 'vertical' and 'horizontal'
+ // it applies to components that have not been set explicitly
+ if (isset($pStyles['borders']['inside'])) {
+ foreach (['vertical', 'horizontal'] as $component) {
+ if (!isset($pStyles['borders'][$component])) {
+ $pStyles['borders'][$component] = $pStyles['borders']['inside'];
+ }
+ }
+ unset($pStyles['borders']['inside']); // not needed any more
+ }
+ // width and height characteristics of selection, 1, 2, or 3 (for 3 or more)
+ $xMax = min($rangeEnd[0] - $rangeStart[0] + 1, 3);
+ $yMax = min($rangeEnd[1] - $rangeStart[1] + 1, 3);
+
+ // loop through up to 3 x 3 = 9 regions
+ for ($x = 1; $x <= $xMax; ++$x) {
+ // start column index for region
+ $colStart = ($x == 3) ?
+ Coordinate::stringFromColumnIndex($rangeEnd[0])
+ : Coordinate::stringFromColumnIndex($rangeStart[0] + $x - 1);
+ // end column index for region
+ $colEnd = ($x == 1) ?
+ Coordinate::stringFromColumnIndex($rangeStart[0])
+ : Coordinate::stringFromColumnIndex($rangeEnd[0] - $xMax + $x);
+
+ for ($y = 1; $y <= $yMax; ++$y) {
+ // which edges are touching the region
+ $edges = [];
+ if ($x == 1) {
+ // are we at left edge
+ $edges[] = 'left';
+ }
+ if ($x == $xMax) {
+ // are we at right edge
+ $edges[] = 'right';
+ }
+ if ($y == 1) {
+ // are we at top edge?
+ $edges[] = 'top';
+ }
+ if ($y == $yMax) {
+ // are we at bottom edge?
+ $edges[] = 'bottom';
+ }
+
+ // start row index for region
+ $rowStart = ($y == 3) ?
+ $rangeEnd[1] : $rangeStart[1] + $y - 1;
+
+ // end row index for region
+ $rowEnd = ($y == 1) ?
+ $rangeStart[1] : $rangeEnd[1] - $yMax + $y;
+
+ // build range for region
+ $range = $colStart . $rowStart . ':' . $colEnd . $rowEnd;
+
+ // retrieve relevant style array for region
+ $regionStyles = $pStyles;
+ unset($regionStyles['borders']['inside']);
+
+ // what are the inner edges of the region when looking at the selection
+ $innerEdges = array_diff(['top', 'right', 'bottom', 'left'], $edges);
+
+ // inner edges that are not touching the region should take the 'inside' border properties if they have been set
+ foreach ($innerEdges as $innerEdge) {
+ switch ($innerEdge) {
+ case 'top':
+ case 'bottom':
+ // should pick up 'horizontal' border property if set
+ if (isset($pStyles['borders']['horizontal'])) {
+ $regionStyles['borders'][$innerEdge] = $pStyles['borders']['horizontal'];
+ } else {
+ unset($regionStyles['borders'][$innerEdge]);
+ }
+
+ break;
+ case 'left':
+ case 'right':
+ // should pick up 'vertical' border property if set
+ if (isset($pStyles['borders']['vertical'])) {
+ $regionStyles['borders'][$innerEdge] = $pStyles['borders']['vertical'];
+ } else {
+ unset($regionStyles['borders'][$innerEdge]);
+ }
+
+ break;
+ }
+ }
+
+ // apply region style to region by calling applyFromArray() in simple mode
+ $this->getActiveSheet()->getStyle($range)->applyFromArray($regionStyles, false);
+ }
+ }
+
+ // restore initial cell selection range
+ $this->getActiveSheet()->getStyle($pRange);
+
+ return $this;
+ }
+
+ // SIMPLE MODE:
+ // Selection type, inspect
+ if (preg_match('/^[A-Z]+1:[A-Z]+1048576$/', $pRange)) {
+ $selectionType = 'COLUMN';
+ } elseif (preg_match('/^A\d+:XFD\d+$/', $pRange)) {
+ $selectionType = 'ROW';
+ } else {
+ $selectionType = 'CELL';
+ }
+
+ // First loop through columns, rows, or cells to find out which styles are affected by this operation
+ switch ($selectionType) {
+ case 'COLUMN':
+ $oldXfIndexes = [];
+ for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
+ $oldXfIndexes[$this->getActiveSheet()->getColumnDimensionByColumn($col)->getXfIndex()] = true;
+ }
+
+ break;
+ case 'ROW':
+ $oldXfIndexes = [];
+ for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
+ if ($this->getActiveSheet()->getRowDimension($row)->getXfIndex() == null) {
+ $oldXfIndexes[0] = true; // row without explicit style should be formatted based on default style
+ } else {
+ $oldXfIndexes[$this->getActiveSheet()->getRowDimension($row)->getXfIndex()] = true;
+ }
+ }
+
+ break;
+ case 'CELL':
+ $oldXfIndexes = [];
+ for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
+ for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
+ $oldXfIndexes[$this->getActiveSheet()->getCellByColumnAndRow($col, $row)->getXfIndex()] = true;
+ }
+ }
+
+ break;
+ }
+
+ // clone each of the affected styles, apply the style array, and add the new styles to the workbook
+ $workbook = $this->getActiveSheet()->getParent();
+ foreach ($oldXfIndexes as $oldXfIndex => $dummy) {
+ $style = $workbook->getCellXfByIndex($oldXfIndex);
+ $newStyle = clone $style;
+ $newStyle->applyFromArray($pStyles);
+
+ if ($existingStyle = $workbook->getCellXfByHashCode($newStyle->getHashCode())) {
+ // there is already such cell Xf in our collection
+ $newXfIndexes[$oldXfIndex] = $existingStyle->getIndex();
+ } else {
+ // we don't have such a cell Xf, need to add
+ $workbook->addCellXf($newStyle);
+ $newXfIndexes[$oldXfIndex] = $newStyle->getIndex();
+ }
+ }
+
+ // Loop through columns, rows, or cells again and update the XF index
+ switch ($selectionType) {
+ case 'COLUMN':
+ for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
+ $columnDimension = $this->getActiveSheet()->getColumnDimensionByColumn($col);
+ $oldXfIndex = $columnDimension->getXfIndex();
+ $columnDimension->setXfIndex($newXfIndexes[$oldXfIndex]);
+ }
+
+ break;
+ case 'ROW':
+ for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
+ $rowDimension = $this->getActiveSheet()->getRowDimension($row);
+ $oldXfIndex = $rowDimension->getXfIndex() === null ?
+ 0 : $rowDimension->getXfIndex(); // row without explicit style should be formatted based on default style
+ $rowDimension->setXfIndex($newXfIndexes[$oldXfIndex]);
+ }
+
+ break;
+ case 'CELL':
+ for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
+ for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
+ $cell = $this->getActiveSheet()->getCellByColumnAndRow($col, $row);
+ $oldXfIndex = $cell->getXfIndex();
+ $cell->setXfIndex($newXfIndexes[$oldXfIndex]);
+ }
+ }
+
+ break;
+ }
+ } else {
+ // not a supervisor, just apply the style array directly on style object
+ if (isset($pStyles['fill'])) {
+ $this->getFill()->applyFromArray($pStyles['fill']);
+ }
+ if (isset($pStyles['font'])) {
+ $this->getFont()->applyFromArray($pStyles['font']);
+ }
+ if (isset($pStyles['borders'])) {
+ $this->getBorders()->applyFromArray($pStyles['borders']);
+ }
+ if (isset($pStyles['alignment'])) {
+ $this->getAlignment()->applyFromArray($pStyles['alignment']);
+ }
+ if (isset($pStyles['numberFormat'])) {
+ $this->getNumberFormat()->applyFromArray($pStyles['numberFormat']);
+ }
+ if (isset($pStyles['protection'])) {
+ $this->getProtection()->applyFromArray($pStyles['protection']);
+ }
+ if (isset($pStyles['quotePrefix'])) {
+ $this->quotePrefix = $pStyles['quotePrefix'];
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Fill.
+ *
+ * @return Fill
+ */
+ public function getFill()
+ {
+ return $this->fill;
+ }
+
+ /**
+ * Get Font.
+ *
+ * @return Font
+ */
+ public function getFont()
+ {
+ return $this->font;
+ }
+
+ /**
+ * Set font.
+ *
+ * @return $this
+ */
+ public function setFont(Font $font)
+ {
+ $this->font = $font;
+
+ return $this;
+ }
+
+ /**
+ * Get Borders.
+ *
+ * @return Borders
+ */
+ public function getBorders()
+ {
+ return $this->borders;
+ }
+
+ /**
+ * Get Alignment.
+ *
+ * @return Alignment
+ */
+ public function getAlignment()
+ {
+ return $this->alignment;
+ }
+
+ /**
+ * Get Number Format.
+ *
+ * @return NumberFormat
+ */
+ public function getNumberFormat()
+ {
+ return $this->numberFormat;
+ }
+
+ /**
+ * Get Conditional Styles. Only used on supervisor.
+ *
+ * @return Conditional[]
+ */
+ public function getConditionalStyles()
+ {
+ return $this->getActiveSheet()->getConditionalStyles($this->getActiveCell());
+ }
+
+ /**
+ * Set Conditional Styles. Only used on supervisor.
+ *
+ * @param Conditional[] $pValue Array of conditional styles
+ *
+ * @return $this
+ */
+ public function setConditionalStyles(array $pValue)
+ {
+ $this->getActiveSheet()->setConditionalStyles($this->getSelectedCells(), $pValue);
+
+ return $this;
+ }
+
+ /**
+ * Get Protection.
+ *
+ * @return Protection
+ */
+ public function getProtection()
+ {
+ return $this->protection;
+ }
+
+ /**
+ * Get quote prefix.
+ *
+ * @return bool
+ */
+ public function getQuotePrefix()
+ {
+ if ($this->isSupervisor) {
+ return $this->getSharedComponent()->getQuotePrefix();
+ }
+
+ return $this->quotePrefix;
+ }
+
+ /**
+ * Set quote prefix.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setQuotePrefix($pValue)
+ {
+ if ($pValue == '') {
+ $pValue = false;
+ }
+ if ($this->isSupervisor) {
+ $styleArray = ['quotePrefix' => $pValue];
+ $this->getActiveSheet()->getStyle($this->getSelectedCells())->applyFromArray($styleArray);
+ } else {
+ $this->quotePrefix = (bool) $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ $hashConditionals = '';
+ foreach ($this->conditionalStyles as $conditional) {
+ $hashConditionals .= $conditional->getHashCode();
+ }
+
+ return md5(
+ $this->fill->getHashCode() .
+ $this->font->getHashCode() .
+ $this->borders->getHashCode() .
+ $this->alignment->getHashCode() .
+ $this->numberFormat->getHashCode() .
+ $hashConditionals .
+ $this->protection->getHashCode() .
+ ($this->quotePrefix ? 't' : 'f') .
+ __CLASS__
+ );
+ }
+
+ /**
+ * Get own index in style collection.
+ *
+ * @return int
+ */
+ public function getIndex()
+ {
+ return $this->index;
+ }
+
+ /**
+ * Set own index in style collection.
+ *
+ * @param int $pValue
+ */
+ public function setIndex($pValue): void
+ {
+ $this->index = $pValue;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php
new file mode 100644
index 0000000..9639772
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Style/Supervisor.php
@@ -0,0 +1,117 @@
+isSupervisor = $isSupervisor;
+ }
+
+ /**
+ * Bind parent. Only used for supervisor.
+ *
+ * @param Spreadsheet|Style $parent
+ * @param null|string $parentPropertyName
+ *
+ * @return $this
+ */
+ public function bindParent($parent, $parentPropertyName = null)
+ {
+ $this->parent = $parent;
+ $this->parentPropertyName = $parentPropertyName;
+
+ return $this;
+ }
+
+ /**
+ * Is this a supervisor or a cell style component?
+ *
+ * @return bool
+ */
+ public function getIsSupervisor()
+ {
+ return $this->isSupervisor;
+ }
+
+ /**
+ * Get the currently active sheet. Only used for supervisor.
+ *
+ * @return Worksheet
+ */
+ public function getActiveSheet()
+ {
+ return $this->parent->getActiveSheet();
+ }
+
+ /**
+ * Get the currently active cell coordinate in currently active sheet.
+ * Only used for supervisor.
+ *
+ * @return string E.g. 'A1'
+ */
+ public function getSelectedCells()
+ {
+ return $this->getActiveSheet()->getSelectedCells();
+ }
+
+ /**
+ * Get the currently active cell coordinate in currently active sheet.
+ * Only used for supervisor.
+ *
+ * @return string E.g. 'A1'
+ */
+ public function getActiveCell()
+ {
+ return $this->getActiveSheet()->getActiveCell();
+ }
+
+ /**
+ * 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)) && ($key != 'parent')) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php
new file mode 100644
index 0000000..877b3d1
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter.php
@@ -0,0 +1,871 @@
+range = $pRange;
+ $this->workSheet = $pSheet;
+ }
+
+ /**
+ * Get AutoFilter Parent Worksheet.
+ *
+ * @return Worksheet
+ */
+ public function getParent()
+ {
+ return $this->workSheet;
+ }
+
+ /**
+ * Set AutoFilter Parent Worksheet.
+ *
+ * @param Worksheet $pSheet
+ *
+ * @return $this
+ */
+ public function setParent(?Worksheet $pSheet = null)
+ {
+ $this->workSheet = $pSheet;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Range.
+ *
+ * @return string
+ */
+ public function getRange()
+ {
+ return $this->range;
+ }
+
+ /**
+ * Set AutoFilter Range.
+ *
+ * @param string $pRange Cell range (i.e. A1:E10)
+ *
+ * @return $this
+ */
+ public function setRange($pRange)
+ {
+ // extract coordinate
+ [$worksheet, $pRange] = Worksheet::extractSheetTitle($pRange, true);
+
+ if (strpos($pRange, ':') !== false) {
+ $this->range = $pRange;
+ } elseif (empty($pRange)) {
+ $this->range = '';
+ } else {
+ throw new PhpSpreadsheetException('Autofilter must be set on a range of cells.');
+ }
+
+ if (empty($pRange)) {
+ // Discard all column rules
+ $this->columns = [];
+ } else {
+ // Discard any column rules that are no longer valid within this range
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
+ foreach ($this->columns as $key => $value) {
+ $colIndex = Coordinate::columnIndexFromString($key);
+ if (($rangeStart[0] > $colIndex) || ($rangeEnd[0] < $colIndex)) {
+ unset($this->columns[$key]);
+ }
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get all AutoFilter Columns.
+ *
+ * @return AutoFilter\Column[]
+ */
+ public function getColumns()
+ {
+ return $this->columns;
+ }
+
+ /**
+ * Validate that the specified column is in the AutoFilter range.
+ *
+ * @param string $column Column name (e.g. A)
+ *
+ * @return int The column offset within the autofilter range
+ */
+ public function testColumnInRange($column)
+ {
+ if (empty($this->range)) {
+ throw new PhpSpreadsheetException('No autofilter range is defined.');
+ }
+
+ $columnIndex = Coordinate::columnIndexFromString($column);
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
+ if (($rangeStart[0] > $columnIndex) || ($rangeEnd[0] < $columnIndex)) {
+ throw new PhpSpreadsheetException('Column is outside of current autofilter range.');
+ }
+
+ return $columnIndex - $rangeStart[0];
+ }
+
+ /**
+ * Get a specified AutoFilter Column Offset within the defined AutoFilter range.
+ *
+ * @param string $pColumn Column name (e.g. A)
+ *
+ * @return int The offset of the specified column within the autofilter range
+ */
+ public function getColumnOffset($pColumn)
+ {
+ return $this->testColumnInRange($pColumn);
+ }
+
+ /**
+ * Get a specified AutoFilter Column.
+ *
+ * @param string $pColumn Column name (e.g. A)
+ *
+ * @return AutoFilter\Column
+ */
+ public function getColumn($pColumn)
+ {
+ $this->testColumnInRange($pColumn);
+
+ if (!isset($this->columns[$pColumn])) {
+ $this->columns[$pColumn] = new AutoFilter\Column($pColumn, $this);
+ }
+
+ return $this->columns[$pColumn];
+ }
+
+ /**
+ * Get a specified AutoFilter Column by it's offset.
+ *
+ * @param int $pColumnOffset Column offset within range (starting from 0)
+ *
+ * @return AutoFilter\Column
+ */
+ public function getColumnByOffset($pColumnOffset)
+ {
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
+ $pColumn = Coordinate::stringFromColumnIndex($rangeStart[0] + $pColumnOffset);
+
+ return $this->getColumn($pColumn);
+ }
+
+ /**
+ * Set AutoFilter.
+ *
+ * @param AutoFilter\Column|string $pColumn
+ * A simple string containing a Column ID like 'A' is permitted
+ *
+ * @return $this
+ */
+ public function setColumn($pColumn)
+ {
+ if ((is_string($pColumn)) && (!empty($pColumn))) {
+ $column = $pColumn;
+ } elseif (is_object($pColumn) && ($pColumn instanceof AutoFilter\Column)) {
+ $column = $pColumn->getColumnIndex();
+ } else {
+ throw new PhpSpreadsheetException('Column is not within the autofilter range.');
+ }
+ $this->testColumnInRange($column);
+
+ if (is_string($pColumn)) {
+ $this->columns[$pColumn] = new AutoFilter\Column($pColumn, $this);
+ } elseif (is_object($pColumn) && ($pColumn instanceof AutoFilter\Column)) {
+ $pColumn->setParent($this);
+ $this->columns[$column] = $pColumn;
+ }
+ ksort($this->columns);
+
+ return $this;
+ }
+
+ /**
+ * Clear a specified AutoFilter Column.
+ *
+ * @param string $pColumn Column name (e.g. A)
+ *
+ * @return $this
+ */
+ public function clearColumn($pColumn)
+ {
+ $this->testColumnInRange($pColumn);
+
+ if (isset($this->columns[$pColumn])) {
+ unset($this->columns[$pColumn]);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Shift an AutoFilter Column Rule to a different column.
+ *
+ * Note: This method bypasses validation of the destination column to ensure it is within this AutoFilter range.
+ * Nor does it verify whether any column rule already exists at $toColumn, but will simply override any existing value.
+ * Use with caution.
+ *
+ * @param string $fromColumn Column name (e.g. A)
+ * @param string $toColumn Column name (e.g. B)
+ *
+ * @return $this
+ */
+ public function shiftColumn($fromColumn, $toColumn)
+ {
+ $fromColumn = strtoupper($fromColumn);
+ $toColumn = strtoupper($toColumn);
+
+ if (($fromColumn !== null) && (isset($this->columns[$fromColumn])) && ($toColumn !== null)) {
+ $this->columns[$fromColumn]->setParent();
+ $this->columns[$fromColumn]->setColumnIndex($toColumn);
+ $this->columns[$toColumn] = $this->columns[$fromColumn];
+ $this->columns[$toColumn]->setParent($this);
+ unset($this->columns[$fromColumn]);
+
+ ksort($this->columns);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Test if cell value is in the defined set of values.
+ *
+ * @param mixed $cellValue
+ * @param mixed[] $dataSet
+ *
+ * @return bool
+ */
+ private static function filterTestInSimpleDataSet($cellValue, $dataSet)
+ {
+ $dataSetValues = $dataSet['filterValues'];
+ $blanks = $dataSet['blanks'];
+ if (($cellValue == '') || ($cellValue === null)) {
+ return $blanks;
+ }
+
+ return in_array($cellValue, $dataSetValues);
+ }
+
+ /**
+ * Test if cell value is in the defined set of Excel date values.
+ *
+ * @param mixed $cellValue
+ * @param mixed[] $dataSet
+ *
+ * @return bool
+ */
+ private static function filterTestInDateGroupSet($cellValue, $dataSet)
+ {
+ $dateSet = $dataSet['filterValues'];
+ $blanks = $dataSet['blanks'];
+ if (($cellValue == '') || ($cellValue === null)) {
+ return $blanks;
+ }
+
+ if (is_numeric($cellValue)) {
+ $dateValue = Date::excelToTimestamp($cellValue);
+ if ($cellValue < 1) {
+ // Just the time part
+ $dtVal = date('His', $dateValue);
+ $dateSet = $dateSet['time'];
+ } elseif ($cellValue == floor($cellValue)) {
+ // Just the date part
+ $dtVal = date('Ymd', $dateValue);
+ $dateSet = $dateSet['date'];
+ } else {
+ // date and time parts
+ $dtVal = date('YmdHis', $dateValue);
+ $dateSet = $dateSet['dateTime'];
+ }
+ foreach ($dateSet as $dateValue) {
+ // Use of substr to extract value at the appropriate group level
+ if (substr($dtVal, 0, strlen($dateValue)) == $dateValue) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Test if cell value is within a set of values defined by a ruleset.
+ *
+ * @param mixed $cellValue
+ * @param mixed[] $ruleSet
+ *
+ * @return bool
+ */
+ private static function filterTestInCustomDataSet($cellValue, $ruleSet)
+ {
+ $dataSet = $ruleSet['filterRules'];
+ $join = $ruleSet['join'];
+ $customRuleForBlanks = $ruleSet['customRuleForBlanks'] ?? false;
+
+ if (!$customRuleForBlanks) {
+ // Blank cells are always ignored, so return a FALSE
+ if (($cellValue == '') || ($cellValue === null)) {
+ return false;
+ }
+ }
+ $returnVal = ($join == AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND);
+ foreach ($dataSet as $rule) {
+ $retVal = false;
+
+ if (is_numeric($rule['value'])) {
+ // Numeric values are tested using the appropriate operator
+ switch ($rule['operator']) {
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL:
+ $retVal = ($cellValue == $rule['value']);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL:
+ $retVal = ($cellValue != $rule['value']);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN:
+ $retVal = ($cellValue > $rule['value']);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL:
+ $retVal = ($cellValue >= $rule['value']);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN:
+ $retVal = ($cellValue < $rule['value']);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL:
+ $retVal = ($cellValue <= $rule['value']);
+
+ break;
+ }
+ } elseif ($rule['value'] == '') {
+ switch ($rule['operator']) {
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_EQUAL:
+ $retVal = (($cellValue == '') || ($cellValue === null));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_NOTEQUAL:
+ $retVal = (($cellValue != '') && ($cellValue !== null));
+
+ break;
+ default:
+ $retVal = true;
+
+ break;
+ }
+ } else {
+ // String values are always tested for equality, factoring in for wildcards (hence a regexp test)
+ $retVal = preg_match('/^' . $rule['value'] . '$/i', $cellValue);
+ }
+ // If there are multiple conditions, then we need to test both using the appropriate join operator
+ switch ($join) {
+ case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR:
+ $returnVal = $returnVal || $retVal;
+ // Break as soon as we have a TRUE match for OR joins,
+ // to avoid unnecessary additional code execution
+ if ($returnVal) {
+ return $returnVal;
+ }
+
+ break;
+ case AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND:
+ $returnVal = $returnVal && $retVal;
+
+ break;
+ }
+ }
+
+ return $returnVal;
+ }
+
+ /**
+ * Test if cell date value is matches a set of values defined by a set of months.
+ *
+ * @param mixed $cellValue
+ * @param mixed[] $monthSet
+ *
+ * @return bool
+ */
+ private static function filterTestInPeriodDateSet($cellValue, $monthSet)
+ {
+ // Blank cells are always ignored, so return a FALSE
+ if (($cellValue == '') || ($cellValue === null)) {
+ return false;
+ }
+
+ if (is_numeric($cellValue)) {
+ $dateValue = date('m', Date::excelToTimestamp($cellValue));
+ if (in_array($dateValue, $monthSet)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Search/Replace arrays to convert Excel wildcard syntax to a regexp syntax for preg_matching.
+ *
+ * @var array
+ */
+ private static $fromReplace = ['\*', '\?', '~~', '~.*', '~.?'];
+
+ private static $toReplace = ['.*', '.', '~', '\*', '\?'];
+
+ /**
+ * Convert a dynamic rule daterange to a custom filter range expression for ease of calculation.
+ *
+ * @param string $dynamicRuleType
+ * @param AutoFilter\Column $filterColumn
+ *
+ * @return mixed[]
+ */
+ private function dynamicFilterDateRange($dynamicRuleType, &$filterColumn)
+ {
+ $rDateType = Functions::getReturnDateType();
+ Functions::setReturnDateType(Functions::RETURNDATE_PHP_NUMERIC);
+ $val = $maxVal = null;
+
+ $ruleValues = [];
+ $baseDate = DateTime::DATENOW();
+ // Calculate start/end dates for the required date range based on current date
+ switch ($dynamicRuleType) {
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK:
+ $baseDate = strtotime('-7 days', $baseDate);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK:
+ $baseDate = strtotime('-7 days', $baseDate);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH:
+ $baseDate = strtotime('-1 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH:
+ $baseDate = strtotime('+1 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER:
+ $baseDate = strtotime('-3 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER:
+ $baseDate = strtotime('+3 month', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR:
+ $baseDate = strtotime('-1 year', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR:
+ $baseDate = strtotime('+1 year', gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
+
+ break;
+ }
+
+ switch ($dynamicRuleType) {
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TODAY:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW:
+ $maxVal = (int) Date::PHPtoExcel(strtotime('+1 day', $baseDate));
+ $val = (int) Date::PHPToExcel($baseDate);
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YEARTODATE:
+ $maxVal = (int) Date::PHPtoExcel(strtotime('+1 day', $baseDate));
+ $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1, date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISYEAR:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTYEAR:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTYEAR:
+ $maxVal = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 31, 12, date('Y', $baseDate)));
+ ++$maxVal;
+ $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1, date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISQUARTER:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTQUARTER:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTQUARTER:
+ $thisMonth = date('m', $baseDate);
+ $thisQuarter = floor(--$thisMonth / 3);
+ $maxVal = (int) Date::PHPtoExcel(gmmktime(0, 0, 0, date('t', $baseDate), (1 + $thisQuarter) * 3, date('Y', $baseDate)));
+ ++$maxVal;
+ $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, 1 + $thisQuarter * 3, date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISMONTH:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTMONTH:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTMONTH:
+ $maxVal = (int) Date::PHPtoExcel(gmmktime(0, 0, 0, date('t', $baseDate), date('m', $baseDate), date('Y', $baseDate)));
+ ++$maxVal;
+ $val = (int) Date::PHPToExcel(gmmktime(0, 0, 0, 1, date('m', $baseDate), date('Y', $baseDate)));
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_THISWEEK:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_LASTWEEK:
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_NEXTWEEK:
+ $dayOfWeek = date('w', $baseDate);
+ $val = (int) Date::PHPToExcel($baseDate) - $dayOfWeek;
+ $maxVal = $val + 7;
+
+ break;
+ }
+
+ switch ($dynamicRuleType) {
+ // Adjust Today dates for Yesterday and Tomorrow
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_YESTERDAY:
+ --$maxVal;
+ --$val;
+
+ break;
+ case AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_TOMORROW:
+ ++$maxVal;
+ ++$val;
+
+ break;
+ }
+
+ // Set the filter column rule attributes ready for writing
+ $filterColumn->setAttributes(['val' => $val, 'maxVal' => $maxVal]);
+
+ // Set the rules for identifying rows for hide/show
+ $ruleValues[] = ['operator' => AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL, 'value' => $val];
+ $ruleValues[] = ['operator' => AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN, 'value' => $maxVal];
+ Functions::setReturnDateType($rDateType);
+
+ return ['method' => 'filterTestInCustomDataSet', 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_AND]];
+ }
+
+ private function calculateTopTenValue($columnID, $startRow, $endRow, $ruleType, $ruleValue)
+ {
+ $range = $columnID . $startRow . ':' . $columnID . $endRow;
+ $dataValues = Functions::flattenArray($this->workSheet->rangeToArray($range, null, true, false));
+
+ $dataValues = array_filter($dataValues);
+ if ($ruleType == AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) {
+ rsort($dataValues);
+ } else {
+ sort($dataValues);
+ }
+
+ return array_pop(array_slice($dataValues, 0, $ruleValue));
+ }
+
+ /**
+ * Apply the AutoFilter rules to the AutoFilter Range.
+ *
+ * @return $this
+ */
+ public function showHideRows()
+ {
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($this->range);
+
+ // The heading row should always be visible
+ $this->workSheet->getRowDimension($rangeStart[1])->setVisible(true);
+
+ $columnFilterTests = [];
+ foreach ($this->columns as $columnID => $filterColumn) {
+ $rules = $filterColumn->getRules();
+ switch ($filterColumn->getFilterType()) {
+ case AutoFilter\Column::AUTOFILTER_FILTERTYPE_FILTER:
+ $ruleType = null;
+ $ruleValues = [];
+ // Build a list of the filter value selections
+ foreach ($rules as $rule) {
+ $ruleType = $rule->getRuleType();
+ $ruleValues[] = $rule->getValue();
+ }
+ // Test if we want to include blanks in our filter criteria
+ $blanks = false;
+ $ruleDataSet = array_filter($ruleValues);
+ if (count($ruleValues) != count($ruleDataSet)) {
+ $blanks = true;
+ }
+ if ($ruleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_FILTER) {
+ // Filter on absolute values
+ $columnFilterTests[$columnID] = [
+ 'method' => 'filterTestInSimpleDataSet',
+ 'arguments' => ['filterValues' => $ruleDataSet, 'blanks' => $blanks],
+ ];
+ } else {
+ // Filter on date group values
+ $arguments = [
+ 'date' => [],
+ 'time' => [],
+ 'dateTime' => [],
+ ];
+ foreach ($ruleDataSet as $ruleValue) {
+ $date = $time = '';
+ if (
+ (isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR])) &&
+ ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR] !== '')
+ ) {
+ $date .= sprintf('%04d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_YEAR]);
+ }
+ if (
+ (isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH])) &&
+ ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH] != '')
+ ) {
+ $date .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MONTH]);
+ }
+ if (
+ (isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY])) &&
+ ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY] !== '')
+ ) {
+ $date .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_DAY]);
+ }
+ if (
+ (isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR])) &&
+ ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR] !== '')
+ ) {
+ $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_HOUR]);
+ }
+ if (
+ (isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE])) &&
+ ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE] !== '')
+ ) {
+ $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_MINUTE]);
+ }
+ if (
+ (isset($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND])) &&
+ ($ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND] !== '')
+ ) {
+ $time .= sprintf('%02d', $ruleValue[AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DATEGROUP_SECOND]);
+ }
+ $dateTime = $date . $time;
+ $arguments['date'][] = $date;
+ $arguments['time'][] = $time;
+ $arguments['dateTime'][] = $dateTime;
+ }
+ // Remove empty elements
+ $arguments['date'] = array_filter($arguments['date']);
+ $arguments['time'] = array_filter($arguments['time']);
+ $arguments['dateTime'] = array_filter($arguments['dateTime']);
+ $columnFilterTests[$columnID] = [
+ 'method' => 'filterTestInDateGroupSet',
+ 'arguments' => ['filterValues' => $arguments, 'blanks' => $blanks],
+ ];
+ }
+
+ break;
+ case AutoFilter\Column::AUTOFILTER_FILTERTYPE_CUSTOMFILTER:
+ $customRuleForBlanks = false;
+ $ruleValues = [];
+ // Build a list of the filter value selections
+ foreach ($rules as $rule) {
+ $ruleValue = $rule->getValue();
+ if (!is_numeric($ruleValue)) {
+ // Convert to a regexp allowing for regexp reserved characters, wildcards and escaped wildcards
+ $ruleValue = preg_quote($ruleValue);
+ $ruleValue = str_replace(self::$fromReplace, self::$toReplace, $ruleValue);
+ if (trim($ruleValue) == '') {
+ $customRuleForBlanks = true;
+ $ruleValue = trim($ruleValue);
+ }
+ }
+ $ruleValues[] = ['operator' => $rule->getOperator(), 'value' => $ruleValue];
+ }
+ $join = $filterColumn->getJoin();
+ $columnFilterTests[$columnID] = [
+ 'method' => 'filterTestInCustomDataSet',
+ 'arguments' => ['filterRules' => $ruleValues, 'join' => $join, 'customRuleForBlanks' => $customRuleForBlanks],
+ ];
+
+ break;
+ case AutoFilter\Column::AUTOFILTER_FILTERTYPE_DYNAMICFILTER:
+ $ruleValues = [];
+ foreach ($rules as $rule) {
+ // We should only ever have one Dynamic Filter Rule anyway
+ $dynamicRuleType = $rule->getGrouping();
+ if (
+ ($dynamicRuleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE) ||
+ ($dynamicRuleType == AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_BELOWAVERAGE)
+ ) {
+ // Number (Average) based
+ // Calculate the average
+ $averageFormula = '=AVERAGE(' . $columnID . ($rangeStart[1] + 1) . ':' . $columnID . $rangeEnd[1] . ')';
+ $average = Calculation::getInstance()->calculateFormula($averageFormula, null, $this->workSheet->getCell('A1'));
+ // Set above/below rule based on greaterThan or LessTan
+ $operator = ($dynamicRuleType === AutoFilter\Column\Rule::AUTOFILTER_RULETYPE_DYNAMIC_ABOVEAVERAGE)
+ ? AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHAN
+ : AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHAN;
+ $ruleValues[] = [
+ 'operator' => $operator,
+ 'value' => $average,
+ ];
+ $columnFilterTests[$columnID] = [
+ 'method' => 'filterTestInCustomDataSet',
+ 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR],
+ ];
+ } else {
+ // Date based
+ if ($dynamicRuleType[0] == 'M' || $dynamicRuleType[0] == 'Q') {
+ $periodType = '';
+ $period = 0;
+ // Month or Quarter
+ sscanf($dynamicRuleType, '%[A-Z]%d', $periodType, $period);
+ if ($periodType == 'M') {
+ $ruleValues = [$period];
+ } else {
+ --$period;
+ $periodEnd = (1 + $period) * 3;
+ $periodStart = 1 + $period * 3;
+ $ruleValues = range($periodStart, $periodEnd);
+ }
+ $columnFilterTests[$columnID] = [
+ 'method' => 'filterTestInPeriodDateSet',
+ 'arguments' => $ruleValues,
+ ];
+ $filterColumn->setAttributes([]);
+ } else {
+ // Date Range
+ $columnFilterTests[$columnID] = $this->dynamicFilterDateRange($dynamicRuleType, $filterColumn);
+
+ break;
+ }
+ }
+ }
+
+ break;
+ case AutoFilter\Column::AUTOFILTER_FILTERTYPE_TOPTENFILTER:
+ $ruleValues = [];
+ $dataRowCount = $rangeEnd[1] - $rangeStart[1];
+ foreach ($rules as $rule) {
+ // We should only ever have one Dynamic Filter Rule anyway
+ $toptenRuleType = $rule->getGrouping();
+ $ruleValue = $rule->getValue();
+ $ruleOperator = $rule->getOperator();
+ }
+ if ($ruleOperator === AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) {
+ $ruleValue = floor($ruleValue * ($dataRowCount / 100));
+ }
+ if ($ruleValue < 1) {
+ $ruleValue = 1;
+ }
+ if ($ruleValue > 500) {
+ $ruleValue = 500;
+ }
+
+ $maxVal = $this->calculateTopTenValue($columnID, $rangeStart[1] + 1, $rangeEnd[1], $toptenRuleType, $ruleValue);
+
+ $operator = ($toptenRuleType == AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP)
+ ? AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL
+ : AutoFilter\Column\Rule::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL;
+ $ruleValues[] = ['operator' => $operator, 'value' => $maxVal];
+ $columnFilterTests[$columnID] = [
+ 'method' => 'filterTestInCustomDataSet',
+ 'arguments' => ['filterRules' => $ruleValues, 'join' => AutoFilter\Column::AUTOFILTER_COLUMN_JOIN_OR],
+ ];
+ $filterColumn->setAttributes(['maxVal' => $maxVal]);
+
+ break;
+ }
+ }
+
+ // Execute the column tests for each row in the autoFilter range to determine show/hide,
+ for ($row = $rangeStart[1] + 1; $row <= $rangeEnd[1]; ++$row) {
+ $result = true;
+ foreach ($columnFilterTests as $columnID => $columnFilterTest) {
+ $cellValue = $this->workSheet->getCell($columnID . $row)->getCalculatedValue();
+ // Execute the filter test
+ $result = $result &&
+ call_user_func_array(
+ [self::class, $columnFilterTest['method']],
+ [$cellValue, $columnFilterTest['arguments']]
+ );
+ // If filter test has resulted in FALSE, exit the loop straightaway rather than running any more tests
+ if (!$result) {
+ break;
+ }
+ }
+ // Set show/hide for the row based on the result of the autoFilter result
+ $this->workSheet->getRowDimension($row)->setVisible($result);
+ }
+
+ 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)) {
+ if ($key === 'workSheet') {
+ // Detach from worksheet
+ $this->{$key} = null;
+ } else {
+ $this->{$key} = clone $value;
+ }
+ } elseif ((is_array($value)) && ($key == 'columns')) {
+ // The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\AutoFilter objects
+ $this->{$key} = [];
+ foreach ($value as $k => $v) {
+ $this->{$key}[$k] = clone $v;
+ // attach the new cloned Column to this new cloned Autofilter object
+ $this->{$key}[$k]->setParent($this);
+ }
+ } else {
+ $this->{$key} = $value;
+ }
+ }
+ }
+
+ /**
+ * toString method replicates previous behavior by returning the range if object is
+ * referenced as a property of its parent.
+ */
+ public function __toString()
+ {
+ return (string) $this->range;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php
new file mode 100644
index 0000000..bcde501
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column.php
@@ -0,0 +1,380 @@
+columnIndex = $pColumn;
+ $this->parent = $pParent;
+ }
+
+ /**
+ * Get AutoFilter column index as string eg: 'A'.
+ *
+ * @return string
+ */
+ public function getColumnIndex()
+ {
+ return $this->columnIndex;
+ }
+
+ /**
+ * Set AutoFilter column index as string eg: 'A'.
+ *
+ * @param string $pColumn Column (e.g. A)
+ *
+ * @return $this
+ */
+ public function setColumnIndex($pColumn)
+ {
+ // Uppercase coordinate
+ $pColumn = strtoupper($pColumn);
+ if ($this->parent !== null) {
+ $this->parent->testColumnInRange($pColumn);
+ }
+
+ $this->columnIndex = $pColumn;
+
+ return $this;
+ }
+
+ /**
+ * Get this Column's AutoFilter Parent.
+ *
+ * @return AutoFilter
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Set this Column's AutoFilter Parent.
+ *
+ * @param AutoFilter $pParent
+ *
+ * @return $this
+ */
+ public function setParent(?AutoFilter $pParent = null)
+ {
+ $this->parent = $pParent;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Type.
+ *
+ * @return string
+ */
+ public function getFilterType()
+ {
+ return $this->filterType;
+ }
+
+ /**
+ * Set AutoFilter Type.
+ *
+ * @param string $pFilterType
+ *
+ * @return $this
+ */
+ public function setFilterType($pFilterType)
+ {
+ if (!in_array($pFilterType, self::$filterTypes)) {
+ throw new PhpSpreadsheetException('Invalid filter type for column AutoFilter.');
+ }
+
+ $this->filterType = $pFilterType;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Multiple Rules And/Or Join.
+ *
+ * @return string
+ */
+ public function getJoin()
+ {
+ return $this->join;
+ }
+
+ /**
+ * Set AutoFilter Multiple Rules And/Or.
+ *
+ * @param string $pJoin And/Or
+ *
+ * @return $this
+ */
+ public function setJoin($pJoin)
+ {
+ // Lowercase And/Or
+ $pJoin = strtolower($pJoin);
+ if (!in_array($pJoin, self::$ruleJoins)) {
+ throw new PhpSpreadsheetException('Invalid rule connection for column AutoFilter.');
+ }
+
+ $this->join = $pJoin;
+
+ return $this;
+ }
+
+ /**
+ * Set AutoFilter Attributes.
+ *
+ * @param string[] $attributes
+ *
+ * @return $this
+ */
+ public function setAttributes(array $attributes)
+ {
+ $this->attributes = $attributes;
+
+ return $this;
+ }
+
+ /**
+ * Set An AutoFilter Attribute.
+ *
+ * @param string $pName Attribute Name
+ * @param string $pValue Attribute Value
+ *
+ * @return $this
+ */
+ public function setAttribute($pName, $pValue)
+ {
+ $this->attributes[$pName] = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Column Attributes.
+ *
+ * @return string[]
+ */
+ public function getAttributes()
+ {
+ return $this->attributes;
+ }
+
+ /**
+ * Get specific AutoFilter Column Attribute.
+ *
+ * @param string $pName Attribute Name
+ *
+ * @return string
+ */
+ public function getAttribute($pName)
+ {
+ if (isset($this->attributes[$pName])) {
+ return $this->attributes[$pName];
+ }
+
+ return null;
+ }
+
+ /**
+ * Get all AutoFilter Column Rules.
+ *
+ * @return Column\Rule[]
+ */
+ public function getRules()
+ {
+ return $this->ruleset;
+ }
+
+ /**
+ * Get a specified AutoFilter Column Rule.
+ *
+ * @param int $pIndex Rule index in the ruleset array
+ *
+ * @return Column\Rule
+ */
+ public function getRule($pIndex)
+ {
+ if (!isset($this->ruleset[$pIndex])) {
+ $this->ruleset[$pIndex] = new Column\Rule($this);
+ }
+
+ return $this->ruleset[$pIndex];
+ }
+
+ /**
+ * Create a new AutoFilter Column Rule in the ruleset.
+ *
+ * @return Column\Rule
+ */
+ public function createRule()
+ {
+ $this->ruleset[] = new Column\Rule($this);
+
+ return end($this->ruleset);
+ }
+
+ /**
+ * Add a new AutoFilter Column Rule to the ruleset.
+ *
+ * @return $this
+ */
+ public function addRule(Column\Rule $pRule)
+ {
+ $pRule->setParent($this);
+ $this->ruleset[] = $pRule;
+
+ return $this;
+ }
+
+ /**
+ * Delete a specified AutoFilter Column Rule
+ * If the number of rules is reduced to 1, then we reset And/Or logic to Or.
+ *
+ * @param int $pIndex Rule index in the ruleset array
+ *
+ * @return $this
+ */
+ public function deleteRule($pIndex)
+ {
+ if (isset($this->ruleset[$pIndex])) {
+ unset($this->ruleset[$pIndex]);
+ // If we've just deleted down to a single rule, then reset And/Or joining to Or
+ if (count($this->ruleset) <= 1) {
+ $this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Delete all AutoFilter Column Rules.
+ *
+ * @return $this
+ */
+ public function clearRules()
+ {
+ $this->ruleset = [];
+ $this->setJoin(self::AUTOFILTER_COLUMN_JOIN_OR);
+
+ 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 ($key === 'parent') {
+ // Detach from autofilter parent
+ $this->parent = null;
+ } elseif ($key === 'ruleset') {
+ // The columns array of \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\AutoFilter objects
+ $this->ruleset = [];
+ foreach ($value as $k => $v) {
+ $cloned = clone $v;
+ $cloned->setParent($this); // attach the new cloned Rule to this new cloned Autofilter Cloned object
+ $this->ruleset[$k] = $cloned;
+ }
+ } elseif (is_object($value)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php
new file mode 100644
index 0000000..a893a85
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/AutoFilter/Column/Rule.php
@@ -0,0 +1,449 @@
+
+ *
+ *
+ *
+ *
+ *
+ */
+ const AUTOFILTER_COLUMN_RULE_EQUAL = 'equal';
+ const AUTOFILTER_COLUMN_RULE_NOTEQUAL = 'notEqual';
+ const AUTOFILTER_COLUMN_RULE_GREATERTHAN = 'greaterThan';
+ const AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL = 'greaterThanOrEqual';
+ const AUTOFILTER_COLUMN_RULE_LESSTHAN = 'lessThan';
+ const AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL = 'lessThanOrEqual';
+
+ private static $operators = [
+ self::AUTOFILTER_COLUMN_RULE_EQUAL,
+ self::AUTOFILTER_COLUMN_RULE_NOTEQUAL,
+ self::AUTOFILTER_COLUMN_RULE_GREATERTHAN,
+ self::AUTOFILTER_COLUMN_RULE_GREATERTHANOREQUAL,
+ self::AUTOFILTER_COLUMN_RULE_LESSTHAN,
+ self::AUTOFILTER_COLUMN_RULE_LESSTHANOREQUAL,
+ ];
+
+ const AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE = 'byValue';
+ const AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT = 'byPercent';
+
+ private static $topTenValue = [
+ self::AUTOFILTER_COLUMN_RULE_TOPTEN_BY_VALUE,
+ self::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT,
+ ];
+
+ const AUTOFILTER_COLUMN_RULE_TOPTEN_TOP = 'top';
+ const AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM = 'bottom';
+
+ private static $topTenType = [
+ self::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP,
+ self::AUTOFILTER_COLUMN_RULE_TOPTEN_BOTTOM,
+ ];
+
+ // Rule Operators (Numeric, Boolean etc)
+// const AUTOFILTER_COLUMN_RULE_BETWEEN = 'between'; // greaterThanOrEqual 1 && lessThanOrEqual 2
+ // Rule Operators (Numeric Special) which are translated to standard numeric operators with calculated values
+// const AUTOFILTER_COLUMN_RULE_TOPTEN = 'topTen'; // greaterThan calculated value
+// const AUTOFILTER_COLUMN_RULE_TOPTENPERCENT = 'topTenPercent'; // greaterThan calculated value
+// const AUTOFILTER_COLUMN_RULE_ABOVEAVERAGE = 'aboveAverage'; // Value is calculated as the average
+// const AUTOFILTER_COLUMN_RULE_BELOWAVERAGE = 'belowAverage'; // Value is calculated as the average
+ // Rule Operators (String) which are set as wild-carded values
+// const AUTOFILTER_COLUMN_RULE_BEGINSWITH = 'beginsWith'; // A*
+// const AUTOFILTER_COLUMN_RULE_ENDSWITH = 'endsWith'; // *Z
+// const AUTOFILTER_COLUMN_RULE_CONTAINS = 'contains'; // *B*
+// const AUTOFILTER_COLUMN_RULE_DOESNTCONTAIN = 'notEqual'; // notEqual *B*
+ // Rule Operators (Date Special) which are translated to standard numeric operators with calculated values
+// const AUTOFILTER_COLUMN_RULE_BEFORE = 'lessThan';
+// const AUTOFILTER_COLUMN_RULE_AFTER = 'greaterThan';
+// const AUTOFILTER_COLUMN_RULE_YESTERDAY = 'yesterday';
+// const AUTOFILTER_COLUMN_RULE_TODAY = 'today';
+// const AUTOFILTER_COLUMN_RULE_TOMORROW = 'tomorrow';
+// const AUTOFILTER_COLUMN_RULE_LASTWEEK = 'lastWeek';
+// const AUTOFILTER_COLUMN_RULE_THISWEEK = 'thisWeek';
+// const AUTOFILTER_COLUMN_RULE_NEXTWEEK = 'nextWeek';
+// const AUTOFILTER_COLUMN_RULE_LASTMONTH = 'lastMonth';
+// const AUTOFILTER_COLUMN_RULE_THISMONTH = 'thisMonth';
+// const AUTOFILTER_COLUMN_RULE_NEXTMONTH = 'nextMonth';
+// const AUTOFILTER_COLUMN_RULE_LASTQUARTER = 'lastQuarter';
+// const AUTOFILTER_COLUMN_RULE_THISQUARTER = 'thisQuarter';
+// const AUTOFILTER_COLUMN_RULE_NEXTQUARTER = 'nextQuarter';
+// const AUTOFILTER_COLUMN_RULE_LASTYEAR = 'lastYear';
+// const AUTOFILTER_COLUMN_RULE_THISYEAR = 'thisYear';
+// const AUTOFILTER_COLUMN_RULE_NEXTYEAR = 'nextYear';
+// const AUTOFILTER_COLUMN_RULE_YEARTODATE = 'yearToDate'; //
+// const AUTOFILTER_COLUMN_RULE_ALLDATESINMONTH = 'allDatesInMonth'; // for Month/February
+// const AUTOFILTER_COLUMN_RULE_ALLDATESINQUARTER = 'allDatesInQuarter'; // for Quarter 2
+
+ /**
+ * Autofilter Column.
+ *
+ * @var Column
+ */
+ private $parent;
+
+ /**
+ * Autofilter Rule Type.
+ *
+ * @var string
+ */
+ private $ruleType = self::AUTOFILTER_RULETYPE_FILTER;
+
+ /**
+ * Autofilter Rule Value.
+ *
+ * @var string
+ */
+ private $value = '';
+
+ /**
+ * Autofilter Rule Operator.
+ *
+ * @var string
+ */
+ private $operator = self::AUTOFILTER_COLUMN_RULE_EQUAL;
+
+ /**
+ * DateTimeGrouping Group Value.
+ *
+ * @var string
+ */
+ private $grouping = '';
+
+ /**
+ * Create a new Rule.
+ *
+ * @param Column $pParent
+ */
+ public function __construct(?Column $pParent = null)
+ {
+ $this->parent = $pParent;
+ }
+
+ /**
+ * Get AutoFilter Rule Type.
+ *
+ * @return string
+ */
+ public function getRuleType()
+ {
+ return $this->ruleType;
+ }
+
+ /**
+ * Set AutoFilter Rule Type.
+ *
+ * @param string $pRuleType see self::AUTOFILTER_RULETYPE_*
+ *
+ * @return $this
+ */
+ public function setRuleType($pRuleType)
+ {
+ if (!in_array($pRuleType, self::$ruleTypes)) {
+ throw new PhpSpreadsheetException('Invalid rule type for column AutoFilter Rule.');
+ }
+
+ $this->ruleType = $pRuleType;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Rule Value.
+ *
+ * @return string
+ */
+ public function getValue()
+ {
+ return $this->value;
+ }
+
+ /**
+ * Set AutoFilter Rule Value.
+ *
+ * @param string|string[] $pValue
+ *
+ * @return $this
+ */
+ public function setValue($pValue)
+ {
+ if (is_array($pValue)) {
+ $grouping = -1;
+ foreach ($pValue as $key => $value) {
+ // Validate array entries
+ if (!in_array($key, self::$dateTimeGroups)) {
+ // Remove any invalid entries from the value array
+ unset($pValue[$key]);
+ } else {
+ // Work out what the dateTime grouping will be
+ $grouping = max($grouping, array_search($key, self::$dateTimeGroups));
+ }
+ }
+ if (count($pValue) == 0) {
+ throw new PhpSpreadsheetException('Invalid rule value for column AutoFilter Rule.');
+ }
+ // Set the dateTime grouping that we've anticipated
+ $this->setGrouping(self::$dateTimeGroups[$grouping]);
+ }
+ $this->value = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Rule Operator.
+ *
+ * @return string
+ */
+ public function getOperator()
+ {
+ return $this->operator;
+ }
+
+ /**
+ * Set AutoFilter Rule Operator.
+ *
+ * @param string $pOperator see self::AUTOFILTER_COLUMN_RULE_*
+ *
+ * @return $this
+ */
+ public function setOperator($pOperator)
+ {
+ if (empty($pOperator)) {
+ $pOperator = self::AUTOFILTER_COLUMN_RULE_EQUAL;
+ }
+ if (
+ (!in_array($pOperator, self::$operators)) &&
+ (!in_array($pOperator, self::$topTenValue))
+ ) {
+ throw new PhpSpreadsheetException('Invalid operator for column AutoFilter Rule.');
+ }
+ $this->operator = $pOperator;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter Rule Grouping.
+ *
+ * @return string
+ */
+ public function getGrouping()
+ {
+ return $this->grouping;
+ }
+
+ /**
+ * Set AutoFilter Rule Grouping.
+ *
+ * @param string $pGrouping
+ *
+ * @return $this
+ */
+ public function setGrouping($pGrouping)
+ {
+ if (
+ ($pGrouping !== null) &&
+ (!in_array($pGrouping, self::$dateTimeGroups)) &&
+ (!in_array($pGrouping, self::$dynamicTypes)) &&
+ (!in_array($pGrouping, self::$topTenType))
+ ) {
+ throw new PhpSpreadsheetException('Invalid rule type for column AutoFilter Rule.');
+ }
+ $this->grouping = $pGrouping;
+
+ return $this;
+ }
+
+ /**
+ * Set AutoFilter Rule.
+ *
+ * @param string $pOperator see self::AUTOFILTER_COLUMN_RULE_*
+ * @param string|string[] $pValue
+ * @param string $pGrouping
+ *
+ * @return $this
+ */
+ public function setRule($pOperator, $pValue, $pGrouping = null)
+ {
+ $this->setOperator($pOperator);
+ $this->setValue($pValue);
+ // Only set grouping if it's been passed in as a user-supplied argument,
+ // otherwise we're calculating it when we setValue() and don't want to overwrite that
+ // If the user supplies an argumnet for grouping, then on their own head be it
+ if ($pGrouping !== null) {
+ $this->setGrouping($pGrouping);
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get this Rule's AutoFilter Column Parent.
+ *
+ * @return Column
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Set this Rule's AutoFilter Column Parent.
+ *
+ * @param Column $pParent
+ *
+ * @return $this
+ */
+ public function setParent(?Column $pParent = null)
+ {
+ $this->parent = $pParent;
+
+ 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)) {
+ if ($key == 'parent') {
+ // Detach from autofilter column parent
+ $this->$key = null;
+ } else {
+ $this->$key = clone $value;
+ }
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php
new file mode 100644
index 0000000..e7064af
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/BaseDrawing.php
@@ -0,0 +1,532 @@
+name = '';
+ $this->description = '';
+ $this->worksheet = null;
+ $this->coordinates = 'A1';
+ $this->offsetX = 0;
+ $this->offsetY = 0;
+ $this->width = 0;
+ $this->height = 0;
+ $this->resizeProportional = true;
+ $this->rotation = 0;
+ $this->shadow = new Drawing\Shadow();
+
+ // Set image index
+ ++self::$imageCounter;
+ $this->imageIndex = self::$imageCounter;
+ }
+
+ /**
+ * Get image index.
+ *
+ * @return int
+ */
+ public function getImageIndex()
+ {
+ return $this->imageIndex;
+ }
+
+ /**
+ * Get Name.
+ *
+ * @return string
+ */
+ public function getName()
+ {
+ return $this->name;
+ }
+
+ /**
+ * Set Name.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setName($pValue)
+ {
+ $this->name = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Description.
+ *
+ * @return string
+ */
+ public function getDescription()
+ {
+ return $this->description;
+ }
+
+ /**
+ * Set Description.
+ *
+ * @param string $description
+ *
+ * @return $this
+ */
+ public function setDescription($description)
+ {
+ $this->description = $description;
+
+ return $this;
+ }
+
+ /**
+ * Get Worksheet.
+ *
+ * @return Worksheet
+ */
+ public function getWorksheet()
+ {
+ return $this->worksheet;
+ }
+
+ /**
+ * Set Worksheet.
+ *
+ * @param Worksheet $pValue
+ * @param bool $pOverrideOld If a Worksheet has already been assigned, overwrite it and remove image from old Worksheet?
+ *
+ * @return $this
+ */
+ public function setWorksheet(?Worksheet $pValue = null, $pOverrideOld = false)
+ {
+ if ($this->worksheet === null) {
+ // Add drawing to \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ $this->worksheet = $pValue;
+ $this->worksheet->getCell($this->coordinates);
+ $this->worksheet->getDrawingCollection()->append($this);
+ } else {
+ if ($pOverrideOld) {
+ // Remove drawing from old \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ $iterator = $this->worksheet->getDrawingCollection()->getIterator();
+
+ while ($iterator->valid()) {
+ if ($iterator->current()->getHashCode() === $this->getHashCode()) {
+ $this->worksheet->getDrawingCollection()->offsetUnset($iterator->key());
+ $this->worksheet = null;
+
+ break;
+ }
+ }
+
+ // Set new \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ $this->setWorksheet($pValue);
+ } else {
+ throw new PhpSpreadsheetException('A Worksheet has already been assigned. Drawings can only exist on one \\PhpOffice\\PhpSpreadsheet\\Worksheet.');
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get Coordinates.
+ *
+ * @return string
+ */
+ public function getCoordinates()
+ {
+ return $this->coordinates;
+ }
+
+ /**
+ * Set Coordinates.
+ *
+ * @param string $pValue eg: 'A1'
+ *
+ * @return $this
+ */
+ public function setCoordinates($pValue)
+ {
+ $this->coordinates = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get OffsetX.
+ *
+ * @return int
+ */
+ public function getOffsetX()
+ {
+ return $this->offsetX;
+ }
+
+ /**
+ * Set OffsetX.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setOffsetX($pValue)
+ {
+ $this->offsetX = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get OffsetY.
+ *
+ * @return int
+ */
+ public function getOffsetY()
+ {
+ return $this->offsetY;
+ }
+
+ /**
+ * Set OffsetY.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setOffsetY($pValue)
+ {
+ $this->offsetY = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Width.
+ *
+ * @return int
+ */
+ public function getWidth()
+ {
+ return $this->width;
+ }
+
+ /**
+ * Set Width.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setWidth($pValue)
+ {
+ // Resize proportional?
+ if ($this->resizeProportional && $pValue != 0) {
+ $ratio = $this->height / ($this->width != 0 ? $this->width : 1);
+ $this->height = round($ratio * $pValue);
+ }
+
+ // Set width
+ $this->width = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Height.
+ *
+ * @return int
+ */
+ public function getHeight()
+ {
+ return $this->height;
+ }
+
+ /**
+ * Set Height.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setHeight($pValue)
+ {
+ // Resize proportional?
+ if ($this->resizeProportional && $pValue != 0) {
+ $ratio = $this->width / ($this->height != 0 ? $this->height : 1);
+ $this->width = round($ratio * $pValue);
+ }
+
+ // Set height
+ $this->height = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Set width and height with proportional resize.
+ *
+ * Example:
+ *
+ * $objDrawing->setResizeProportional(true);
+ * $objDrawing->setWidthAndHeight(160,120);
+ *
+ *
+ * @author Vincent@luo MSN:kele_100@hotmail.com
+ *
+ * @param int $width
+ * @param int $height
+ *
+ * @return $this
+ */
+ public function setWidthAndHeight($width, $height)
+ {
+ $xratio = $width / ($this->width != 0 ? $this->width : 1);
+ $yratio = $height / ($this->height != 0 ? $this->height : 1);
+ if ($this->resizeProportional && !($width == 0 || $height == 0)) {
+ if (($xratio * $this->height) < $height) {
+ $this->height = ceil($xratio * $this->height);
+ $this->width = $width;
+ } else {
+ $this->width = ceil($yratio * $this->width);
+ $this->height = $height;
+ }
+ } else {
+ $this->width = $width;
+ $this->height = $height;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get ResizeProportional.
+ *
+ * @return bool
+ */
+ public function getResizeProportional()
+ {
+ return $this->resizeProportional;
+ }
+
+ /**
+ * Set ResizeProportional.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setResizeProportional($pValue)
+ {
+ $this->resizeProportional = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Rotation.
+ *
+ * @return int
+ */
+ public function getRotation()
+ {
+ return $this->rotation;
+ }
+
+ /**
+ * Set Rotation.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setRotation($pValue)
+ {
+ $this->rotation = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Shadow.
+ *
+ * @return Drawing\Shadow
+ */
+ public function getShadow()
+ {
+ return $this->shadow;
+ }
+
+ /**
+ * Set Shadow.
+ *
+ * @param Drawing\Shadow $pValue
+ *
+ * @return $this
+ */
+ public function setShadow(?Drawing\Shadow $pValue = null)
+ {
+ $this->shadow = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->name .
+ $this->description .
+ $this->worksheet->getHashCode() .
+ $this->coordinates .
+ $this->offsetX .
+ $this->offsetY .
+ $this->width .
+ $this->height .
+ $this->rotation .
+ $this->shadow->getHashCode() .
+ __CLASS__
+ );
+ }
+
+ /**
+ * 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 ($key == 'worksheet') {
+ $this->worksheet = null;
+ } elseif (is_object($value)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+
+ public function setHyperlink(?Hyperlink $pHyperlink = null): void
+ {
+ $this->hyperlink = $pHyperlink;
+ }
+
+ /**
+ * @return null|Hyperlink
+ */
+ public function getHyperlink()
+ {
+ return $this->hyperlink;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php
new file mode 100644
index 0000000..8ea5ac7
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/CellIterator.php
@@ -0,0 +1,57 @@
+worksheet = null;
+ }
+
+ /**
+ * Get loop only existing cells.
+ *
+ * @return bool
+ */
+ public function getIterateOnlyExistingCells()
+ {
+ return $this->onlyExistingCells;
+ }
+
+ /**
+ * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary.
+ */
+ abstract protected function adjustForExistingOnlyRange();
+
+ /**
+ * Set the iterator to loop only existing cells.
+ *
+ * @param bool $value
+ */
+ public function setIterateOnlyExistingCells($value): void
+ {
+ $this->onlyExistingCells = (bool) $value;
+
+ $this->adjustForExistingOnlyRange();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php
new file mode 100644
index 0000000..ebe5677
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Column.php
@@ -0,0 +1,64 @@
+parent = $parent;
+ $this->columnIndex = $columnIndex;
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ $this->parent = null;
+ }
+
+ /**
+ * Get column index as string eg: 'A'.
+ *
+ * @return string
+ */
+ public function getColumnIndex()
+ {
+ return $this->columnIndex;
+ }
+
+ /**
+ * Get cell iterator.
+ *
+ * @param int $startRow The row number at which to start iterating
+ * @param int $endRow Optionally, the row number at which to stop iterating
+ *
+ * @return ColumnCellIterator
+ */
+ public function getCellIterator($startRow = 1, $endRow = null)
+ {
+ return new ColumnCellIterator($this->parent, $this->columnIndex, $startRow, $endRow);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php
new file mode 100644
index 0000000..fa719c3
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnCellIterator.php
@@ -0,0 +1,197 @@
+worksheet = $subject;
+ $this->columnIndex = Coordinate::columnIndexFromString($columnIndex);
+ $this->resetEnd($endRow);
+ $this->resetStart($startRow);
+ }
+
+ /**
+ * (Re)Set the start row and the current row pointer.
+ *
+ * @param int $startRow The row number at which to start iterating
+ *
+ * @return $this
+ */
+ public function resetStart($startRow = 1)
+ {
+ $this->startRow = $startRow;
+ $this->adjustForExistingOnlyRange();
+ $this->seek($startRow);
+
+ return $this;
+ }
+
+ /**
+ * (Re)Set the end row.
+ *
+ * @param int $endRow The row number at which to stop iterating
+ *
+ * @return $this
+ */
+ public function resetEnd($endRow = null)
+ {
+ $this->endRow = ($endRow) ? $endRow : $this->worksheet->getHighestRow();
+ $this->adjustForExistingOnlyRange();
+
+ return $this;
+ }
+
+ /**
+ * Set the row pointer to the selected row.
+ *
+ * @param int $row The row number to set the current pointer at
+ *
+ * @return $this
+ */
+ public function seek($row = 1)
+ {
+ if (($row < $this->startRow) || ($row > $this->endRow)) {
+ throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})");
+ } elseif ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $row))) {
+ throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist');
+ }
+ $this->currentRow = $row;
+
+ return $this;
+ }
+
+ /**
+ * Rewind the iterator to the starting row.
+ */
+ public function rewind(): void
+ {
+ $this->currentRow = $this->startRow;
+ }
+
+ /**
+ * Return the current cell in this worksheet column.
+ *
+ * @return null|\PhpOffice\PhpSpreadsheet\Cell\Cell
+ */
+ public function current()
+ {
+ return $this->worksheet->getCellByColumnAndRow($this->columnIndex, $this->currentRow);
+ }
+
+ /**
+ * Return the current iterator key.
+ *
+ * @return int
+ */
+ public function key()
+ {
+ return $this->currentRow;
+ }
+
+ /**
+ * Set the iterator to its next value.
+ */
+ public function next(): void
+ {
+ do {
+ ++$this->currentRow;
+ } while (
+ ($this->onlyExistingCells) &&
+ (!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->currentRow)) &&
+ ($this->currentRow <= $this->endRow)
+ );
+ }
+
+ /**
+ * Set the iterator to its previous value.
+ */
+ public function prev(): void
+ {
+ do {
+ --$this->currentRow;
+ } while (
+ ($this->onlyExistingCells) &&
+ (!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->currentRow)) &&
+ ($this->currentRow >= $this->startRow)
+ );
+ }
+
+ /**
+ * Indicate if more rows exist in the worksheet range of rows that we're iterating.
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return $this->currentRow <= $this->endRow && $this->currentRow >= $this->startRow;
+ }
+
+ /**
+ * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary.
+ */
+ protected function adjustForExistingOnlyRange(): void
+ {
+ if ($this->onlyExistingCells) {
+ while (
+ (!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->startRow)) &&
+ ($this->startRow <= $this->endRow)
+ ) {
+ ++$this->startRow;
+ }
+ if ($this->startRow > $this->endRow) {
+ throw new PhpSpreadsheetException('No cells exist within the specified range');
+ }
+ while (
+ (!$this->worksheet->cellExistsByColumnAndRow($this->columnIndex, $this->endRow)) &&
+ ($this->endRow >= $this->startRow)
+ ) {
+ --$this->endRow;
+ }
+ if ($this->endRow < $this->startRow) {
+ throw new PhpSpreadsheetException('No cells exist within the specified range');
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php
new file mode 100644
index 0000000..1a5afb4
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnDimension.php
@@ -0,0 +1,115 @@
+columnIndex = $pIndex;
+
+ // set dimension as unformatted by default
+ parent::__construct(0);
+ }
+
+ /**
+ * Get column index as string eg: 'A'.
+ *
+ * @return string
+ */
+ public function getColumnIndex()
+ {
+ return $this->columnIndex;
+ }
+
+ /**
+ * Set column index as string eg: 'A'.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setColumnIndex($pValue)
+ {
+ $this->columnIndex = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Width.
+ *
+ * @return float
+ */
+ public function getWidth()
+ {
+ return $this->width;
+ }
+
+ /**
+ * Set Width.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setWidth($pValue)
+ {
+ $this->width = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Auto Size.
+ *
+ * @return bool
+ */
+ public function getAutoSize()
+ {
+ return $this->autoSize;
+ }
+
+ /**
+ * Set Auto Size.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setAutoSize($pValue)
+ {
+ $this->autoSize = $pValue;
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php
new file mode 100644
index 0000000..353b272
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/ColumnIterator.php
@@ -0,0 +1,172 @@
+worksheet = $worksheet;
+ $this->resetEnd($endColumn);
+ $this->resetStart($startColumn);
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ $this->worksheet = null;
+ }
+
+ /**
+ * (Re)Set the start column and the current column pointer.
+ *
+ * @param string $startColumn The column address at which to start iterating
+ *
+ * @return $this
+ */
+ public function resetStart($startColumn = 'A')
+ {
+ $startColumnIndex = Coordinate::columnIndexFromString($startColumn);
+ if ($startColumnIndex > Coordinate::columnIndexFromString($this->worksheet->getHighestColumn())) {
+ throw new Exception("Start column ({$startColumn}) is beyond highest column ({$this->worksheet->getHighestColumn()})");
+ }
+
+ $this->startColumnIndex = $startColumnIndex;
+ if ($this->endColumnIndex < $this->startColumnIndex) {
+ $this->endColumnIndex = $this->startColumnIndex;
+ }
+ $this->seek($startColumn);
+
+ return $this;
+ }
+
+ /**
+ * (Re)Set the end column.
+ *
+ * @param string $endColumn The column address at which to stop iterating
+ *
+ * @return $this
+ */
+ public function resetEnd($endColumn = null)
+ {
+ $endColumn = $endColumn ? $endColumn : $this->worksheet->getHighestColumn();
+ $this->endColumnIndex = Coordinate::columnIndexFromString($endColumn);
+
+ return $this;
+ }
+
+ /**
+ * Set the column pointer to the selected column.
+ *
+ * @param string $column The column address to set the current pointer at
+ *
+ * @return $this
+ */
+ public function seek($column = 'A')
+ {
+ $column = Coordinate::columnIndexFromString($column);
+ if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) {
+ throw new PhpSpreadsheetException("Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})");
+ }
+ $this->currentColumnIndex = $column;
+
+ return $this;
+ }
+
+ /**
+ * Rewind the iterator to the starting column.
+ */
+ public function rewind(): void
+ {
+ $this->currentColumnIndex = $this->startColumnIndex;
+ }
+
+ /**
+ * Return the current column in this worksheet.
+ *
+ * @return Column
+ */
+ public function current()
+ {
+ return new Column($this->worksheet, Coordinate::stringFromColumnIndex($this->currentColumnIndex));
+ }
+
+ /**
+ * Return the current iterator key.
+ *
+ * @return string
+ */
+ public function key()
+ {
+ return Coordinate::stringFromColumnIndex($this->currentColumnIndex);
+ }
+
+ /**
+ * Set the iterator to its next value.
+ */
+ public function next(): void
+ {
+ ++$this->currentColumnIndex;
+ }
+
+ /**
+ * Set the iterator to its previous value.
+ */
+ public function prev(): void
+ {
+ --$this->currentColumnIndex;
+ }
+
+ /**
+ * Indicate if more columns exist in the worksheet range of columns that we're iterating.
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php
new file mode 100644
index 0000000..ca1b9b6
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Dimension.php
@@ -0,0 +1,163 @@
+xfIndex = $initialValue;
+ }
+
+ /**
+ * Get Visible.
+ *
+ * @return bool
+ */
+ public function getVisible()
+ {
+ return $this->visible;
+ }
+
+ /**
+ * Set Visible.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setVisible($pValue)
+ {
+ $this->visible = (bool) $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Outline Level.
+ *
+ * @return int
+ */
+ public function getOutlineLevel()
+ {
+ return $this->outlineLevel;
+ }
+
+ /**
+ * Set Outline Level.
+ * Value must be between 0 and 7.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setOutlineLevel($pValue)
+ {
+ if ($pValue < 0 || $pValue > 7) {
+ throw new PhpSpreadsheetException('Outline level must range between 0 and 7.');
+ }
+
+ $this->outlineLevel = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Collapsed.
+ *
+ * @return bool
+ */
+ public function getCollapsed()
+ {
+ return $this->collapsed;
+ }
+
+ /**
+ * Set Collapsed.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setCollapsed($pValue)
+ {
+ $this->collapsed = (bool) $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get index to cellXf.
+ *
+ * @return int
+ */
+ public function getXfIndex()
+ {
+ return $this->xfIndex;
+ }
+
+ /**
+ * Set index to cellXf.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setXfIndex($pValue)
+ {
+ $this->xfIndex = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone()
+ {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if (is_object($value)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php
new file mode 100644
index 0000000..5eb9c6d
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing.php
@@ -0,0 +1,114 @@
+path = '';
+
+ // Initialize parent
+ parent::__construct();
+ }
+
+ /**
+ * Get Filename.
+ *
+ * @return string
+ */
+ public function getFilename()
+ {
+ return basename($this->path);
+ }
+
+ /**
+ * Get indexed filename (using image index).
+ *
+ * @return string
+ */
+ public function getIndexedFilename()
+ {
+ $fileName = $this->getFilename();
+ $fileName = str_replace(' ', '_', $fileName);
+
+ return str_replace('.' . $this->getExtension(), '', $fileName) . $this->getImageIndex() . '.' . $this->getExtension();
+ }
+
+ /**
+ * Get Extension.
+ *
+ * @return string
+ */
+ public function getExtension()
+ {
+ $exploded = explode('.', basename($this->path));
+
+ return $exploded[count($exploded) - 1];
+ }
+
+ /**
+ * Get Path.
+ *
+ * @return string
+ */
+ public function getPath()
+ {
+ return $this->path;
+ }
+
+ /**
+ * Set Path.
+ *
+ * @param string $pValue File path
+ * @param bool $pVerifyFile Verify file
+ *
+ * @return $this
+ */
+ public function setPath($pValue, $pVerifyFile = true)
+ {
+ if ($pVerifyFile) {
+ if (file_exists($pValue)) {
+ $this->path = $pValue;
+
+ if ($this->width == 0 && $this->height == 0) {
+ // Get width/height
+ [$this->width, $this->height] = getimagesize($pValue);
+ }
+ } else {
+ throw new PhpSpreadsheetException("File $pValue not found!");
+ }
+ } else {
+ $this->path = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ $this->path .
+ parent::getHashCode() .
+ __CLASS__
+ );
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php
new file mode 100644
index 0000000..f664efe
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Drawing/Shadow.php
@@ -0,0 +1,289 @@
+visible = false;
+ $this->blurRadius = 6;
+ $this->distance = 2;
+ $this->direction = 0;
+ $this->alignment = self::SHADOW_BOTTOM_RIGHT;
+ $this->color = new Color(Color::COLOR_BLACK);
+ $this->alpha = 50;
+ }
+
+ /**
+ * Get Visible.
+ *
+ * @return bool
+ */
+ public function getVisible()
+ {
+ return $this->visible;
+ }
+
+ /**
+ * Set Visible.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setVisible($pValue)
+ {
+ $this->visible = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Blur radius.
+ *
+ * @return int
+ */
+ public function getBlurRadius()
+ {
+ return $this->blurRadius;
+ }
+
+ /**
+ * Set Blur radius.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setBlurRadius($pValue)
+ {
+ $this->blurRadius = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Shadow distance.
+ *
+ * @return int
+ */
+ public function getDistance()
+ {
+ return $this->distance;
+ }
+
+ /**
+ * Set Shadow distance.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setDistance($pValue)
+ {
+ $this->distance = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Shadow direction (in degrees).
+ *
+ * @return int
+ */
+ public function getDirection()
+ {
+ return $this->direction;
+ }
+
+ /**
+ * Set Shadow direction (in degrees).
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setDirection($pValue)
+ {
+ $this->direction = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Shadow alignment.
+ *
+ * @return int
+ */
+ public function getAlignment()
+ {
+ return $this->alignment;
+ }
+
+ /**
+ * Set Shadow alignment.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setAlignment($pValue)
+ {
+ $this->alignment = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Color.
+ *
+ * @return Color
+ */
+ public function getColor()
+ {
+ return $this->color;
+ }
+
+ /**
+ * Set Color.
+ *
+ * @param Color $pValue
+ *
+ * @return $this
+ */
+ public function setColor(?Color $pValue = null)
+ {
+ $this->color = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Alpha.
+ *
+ * @return int
+ */
+ public function getAlpha()
+ {
+ return $this->alpha;
+ }
+
+ /**
+ * Set Alpha.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setAlpha($pValue)
+ {
+ $this->alpha = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ return md5(
+ ($this->visible ? 't' : 'f') .
+ $this->blurRadius .
+ $this->distance .
+ $this->direction .
+ $this->alignment .
+ $this->color->getHashCode() .
+ $this->alpha .
+ __CLASS__
+ );
+ }
+
+ /**
+ * 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/HeaderFooter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/HeaderFooter.php
new file mode 100644
index 0000000..b2fa548
--- /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 $pValue
+ *
+ * @return $this
+ */
+ public function setOddHeader($pValue)
+ {
+ $this->oddHeader = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get OddFooter.
+ *
+ * @return string
+ */
+ public function getOddFooter()
+ {
+ return $this->oddFooter;
+ }
+
+ /**
+ * Set OddFooter.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setOddFooter($pValue)
+ {
+ $this->oddFooter = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get EvenHeader.
+ *
+ * @return string
+ */
+ public function getEvenHeader()
+ {
+ return $this->evenHeader;
+ }
+
+ /**
+ * Set EvenHeader.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setEvenHeader($pValue)
+ {
+ $this->evenHeader = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get EvenFooter.
+ *
+ * @return string
+ */
+ public function getEvenFooter()
+ {
+ return $this->evenFooter;
+ }
+
+ /**
+ * Set EvenFooter.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setEvenFooter($pValue)
+ {
+ $this->evenFooter = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get FirstHeader.
+ *
+ * @return string
+ */
+ public function getFirstHeader()
+ {
+ return $this->firstHeader;
+ }
+
+ /**
+ * Set FirstHeader.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setFirstHeader($pValue)
+ {
+ $this->firstHeader = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get FirstFooter.
+ *
+ * @return string
+ */
+ public function getFirstFooter()
+ {
+ return $this->firstFooter;
+ }
+
+ /**
+ * Set FirstFooter.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setFirstFooter($pValue)
+ {
+ $this->firstFooter = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get DifferentOddEven.
+ *
+ * @return bool
+ */
+ public function getDifferentOddEven()
+ {
+ return $this->differentOddEven;
+ }
+
+ /**
+ * Set DifferentOddEven.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setDifferentOddEven($pValue)
+ {
+ $this->differentOddEven = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get DifferentFirst.
+ *
+ * @return bool
+ */
+ public function getDifferentFirst()
+ {
+ return $this->differentFirst;
+ }
+
+ /**
+ * Set DifferentFirst.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setDifferentFirst($pValue)
+ {
+ $this->differentFirst = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get ScaleWithDocument.
+ *
+ * @return bool
+ */
+ public function getScaleWithDocument()
+ {
+ return $this->scaleWithDocument;
+ }
+
+ /**
+ * Set ScaleWithDocument.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setScaleWithDocument($pValue)
+ {
+ $this->scaleWithDocument = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get AlignWithMargins.
+ *
+ * @return bool
+ */
+ public function getAlignWithMargins()
+ {
+ return $this->alignWithMargins;
+ }
+
+ /**
+ * Set AlignWithMargins.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setAlignWithMargins($pValue)
+ {
+ $this->alignWithMargins = $pValue;
+
+ 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..83d3b4f
--- /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..2c8d945
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Iterator.php
@@ -0,0 +1,85 @@
+subject = $subject;
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ $this->subject = null;
+ }
+
+ /**
+ * Rewind iterator.
+ */
+ public function rewind(): void
+ {
+ $this->position = 0;
+ }
+
+ /**
+ * Current Worksheet.
+ *
+ * @return Worksheet
+ */
+ public function current()
+ {
+ return $this->subject->getSheet($this->position);
+ }
+
+ /**
+ * Current key.
+ *
+ * @return int
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Next value.
+ */
+ public function next(): void
+ {
+ ++$this->position;
+ }
+
+ /**
+ * Are there more Worksheet instances available?
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ 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..59e383d
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/MemoryDrawing.php
@@ -0,0 +1,169 @@
+imageResource = null;
+ $this->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();
+ }
+
+ /**
+ * Get image resource.
+ *
+ * @return resource
+ */
+ public function getImageResource()
+ {
+ return $this->imageResource;
+ }
+
+ /**
+ * Set image resource.
+ *
+ * @param 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).
+ *
+ * @return string
+ */
+ public function getIndexedFilename()
+ {
+ $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..0cb5997
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageMargins.php
@@ -0,0 +1,244 @@
+left;
+ }
+
+ /**
+ * Set Left.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setLeft($pValue)
+ {
+ $this->left = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Right.
+ *
+ * @return float
+ */
+ public function getRight()
+ {
+ return $this->right;
+ }
+
+ /**
+ * Set Right.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setRight($pValue)
+ {
+ $this->right = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Top.
+ *
+ * @return float
+ */
+ public function getTop()
+ {
+ return $this->top;
+ }
+
+ /**
+ * Set Top.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setTop($pValue)
+ {
+ $this->top = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Bottom.
+ *
+ * @return float
+ */
+ public function getBottom()
+ {
+ return $this->bottom;
+ }
+
+ /**
+ * Set Bottom.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setBottom($pValue)
+ {
+ $this->bottom = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Header.
+ *
+ * @return float
+ */
+ public function getHeader()
+ {
+ return $this->header;
+ }
+
+ /**
+ * Set Header.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setHeader($pValue)
+ {
+ $this->header = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Footer.
+ *
+ * @return float
+ */
+ public function getFooter()
+ {
+ return $this->footer;
+ }
+
+ /**
+ * Set Footer.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setFooter($pValue)
+ {
+ $this->footer = $pValue;
+
+ 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..e9f7bd0
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/PageSetup.php
@@ -0,0 +1,857 @@
+
+ * 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.
+ *
+ * @var int
+ */
+ private $paperSize = self::PAPERSIZE_LETTER;
+
+ /**
+ * Orientation.
+ *
+ * @var string
+ */
+ private $orientation = self::ORIENTATION_DEFAULT;
+
+ /**
+ * 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 string
+ */
+ private $printArea;
+
+ /**
+ * First page number.
+ *
+ * @var int
+ */
+ private $firstPageNumber;
+
+ private $pageOrder = self::PAGEORDER_DOWN_THEN_OVER;
+
+ /**
+ * Create a new PageSetup.
+ */
+ public function __construct()
+ {
+ }
+
+ /**
+ * Get Paper Size.
+ *
+ * @return int
+ */
+ public function getPaperSize()
+ {
+ return $this->paperSize;
+ }
+
+ /**
+ * Set Paper Size.
+ *
+ * @param int $pValue see self::PAPERSIZE_*
+ *
+ * @return $this
+ */
+ public function setPaperSize($pValue)
+ {
+ $this->paperSize = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Orientation.
+ *
+ * @return string
+ */
+ public function getOrientation()
+ {
+ return $this->orientation;
+ }
+
+ /**
+ * Set Orientation.
+ *
+ * @param string $pValue see self::ORIENTATION_*
+ *
+ * @return $this
+ */
+ public function setOrientation($pValue)
+ {
+ $this->orientation = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * 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 $pValue
+ * @param bool $pUpdate Update fitToPage so scaling applies rather than fitToHeight / fitToWidth
+ *
+ * @return $this
+ */
+ public function setScale($pValue, $pUpdate = 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 (($pValue >= 0) || $pValue === null) {
+ $this->scale = $pValue;
+ if ($pUpdate) {
+ $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 $pValue
+ *
+ * @return $this
+ */
+ public function setFitToPage($pValue)
+ {
+ $this->fitToPage = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Fit To Height.
+ *
+ * @return null|int
+ */
+ public function getFitToHeight()
+ {
+ return $this->fitToHeight;
+ }
+
+ /**
+ * Set Fit To Height.
+ *
+ * @param null|int $pValue
+ * @param bool $pUpdate Update fitToPage so it applies rather than scaling
+ *
+ * @return $this
+ */
+ public function setFitToHeight($pValue, $pUpdate = true)
+ {
+ $this->fitToHeight = $pValue;
+ if ($pUpdate) {
+ $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 $pValue
+ * @param bool $pUpdate Update fitToPage so it applies rather than scaling
+ *
+ * @return $this
+ */
+ public function setFitToWidth($pValue, $pUpdate = true)
+ {
+ $this->fitToWidth = $pValue;
+ if ($pUpdate) {
+ $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 $pValue Containing start column and end column, empty array if option unset
+ *
+ * @return $this
+ */
+ public function setColumnsToRepeatAtLeft(array $pValue)
+ {
+ $this->columnsToRepeatAtLeft = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Set Columns to repeat at left by start and end.
+ *
+ * @param string $pStart eg: 'A'
+ * @param string $pEnd eg: 'B'
+ *
+ * @return $this
+ */
+ public function setColumnsToRepeatAtLeftByStartAndEnd($pStart, $pEnd)
+ {
+ $this->columnsToRepeatAtLeft = [$pStart, $pEnd];
+
+ 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 $pValue Containing start column and end column, empty array if option unset
+ *
+ * @return $this
+ */
+ public function setRowsToRepeatAtTop(array $pValue)
+ {
+ $this->rowsToRepeatAtTop = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Set Rows to repeat at top by start and end.
+ *
+ * @param int $pStart eg: 1
+ * @param int $pEnd eg: 1
+ *
+ * @return $this
+ */
+ public function setRowsToRepeatAtTopByStartAndEnd($pStart, $pEnd)
+ {
+ $this->rowsToRepeatAtTop = [$pStart, $pEnd];
+
+ 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/Worksheet/Protection.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php
new file mode 100644
index 0000000..6b106e6
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Protection.php
@@ -0,0 +1,691 @@
+sheet ||
+ $this->objects ||
+ $this->scenarios ||
+ $this->formatCells ||
+ $this->formatColumns ||
+ $this->formatRows ||
+ $this->insertColumns ||
+ $this->insertRows ||
+ $this->insertHyperlinks ||
+ $this->deleteColumns ||
+ $this->deleteRows ||
+ $this->selectLockedCells ||
+ $this->sort ||
+ $this->autoFilter ||
+ $this->pivotTables ||
+ $this->selectUnlockedCells;
+ }
+
+ /**
+ * Get Sheet.
+ *
+ * @return bool
+ */
+ public function getSheet()
+ {
+ return $this->sheet;
+ }
+
+ /**
+ * Set Sheet.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setSheet($pValue)
+ {
+ $this->sheet = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Objects.
+ *
+ * @return bool
+ */
+ public function getObjects()
+ {
+ return $this->objects;
+ }
+
+ /**
+ * Set Objects.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setObjects($pValue)
+ {
+ $this->objects = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Scenarios.
+ *
+ * @return bool
+ */
+ public function getScenarios()
+ {
+ return $this->scenarios;
+ }
+
+ /**
+ * Set Scenarios.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setScenarios($pValue)
+ {
+ $this->scenarios = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get FormatCells.
+ *
+ * @return bool
+ */
+ public function getFormatCells()
+ {
+ return $this->formatCells;
+ }
+
+ /**
+ * Set FormatCells.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setFormatCells($pValue)
+ {
+ $this->formatCells = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get FormatColumns.
+ *
+ * @return bool
+ */
+ public function getFormatColumns()
+ {
+ return $this->formatColumns;
+ }
+
+ /**
+ * Set FormatColumns.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setFormatColumns($pValue)
+ {
+ $this->formatColumns = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get FormatRows.
+ *
+ * @return bool
+ */
+ public function getFormatRows()
+ {
+ return $this->formatRows;
+ }
+
+ /**
+ * Set FormatRows.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setFormatRows($pValue)
+ {
+ $this->formatRows = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get InsertColumns.
+ *
+ * @return bool
+ */
+ public function getInsertColumns()
+ {
+ return $this->insertColumns;
+ }
+
+ /**
+ * Set InsertColumns.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setInsertColumns($pValue)
+ {
+ $this->insertColumns = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get InsertRows.
+ *
+ * @return bool
+ */
+ public function getInsertRows()
+ {
+ return $this->insertRows;
+ }
+
+ /**
+ * Set InsertRows.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setInsertRows($pValue)
+ {
+ $this->insertRows = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get InsertHyperlinks.
+ *
+ * @return bool
+ */
+ public function getInsertHyperlinks()
+ {
+ return $this->insertHyperlinks;
+ }
+
+ /**
+ * Set InsertHyperlinks.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setInsertHyperlinks($pValue)
+ {
+ $this->insertHyperlinks = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get DeleteColumns.
+ *
+ * @return bool
+ */
+ public function getDeleteColumns()
+ {
+ return $this->deleteColumns;
+ }
+
+ /**
+ * Set DeleteColumns.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setDeleteColumns($pValue)
+ {
+ $this->deleteColumns = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get DeleteRows.
+ *
+ * @return bool
+ */
+ public function getDeleteRows()
+ {
+ return $this->deleteRows;
+ }
+
+ /**
+ * Set DeleteRows.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setDeleteRows($pValue)
+ {
+ $this->deleteRows = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get SelectLockedCells.
+ *
+ * @return bool
+ */
+ public function getSelectLockedCells()
+ {
+ return $this->selectLockedCells;
+ }
+
+ /**
+ * Set SelectLockedCells.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setSelectLockedCells($pValue)
+ {
+ $this->selectLockedCells = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Sort.
+ *
+ * @return bool
+ */
+ public function getSort()
+ {
+ return $this->sort;
+ }
+
+ /**
+ * Set Sort.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setSort($pValue)
+ {
+ $this->sort = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get AutoFilter.
+ *
+ * @return bool
+ */
+ public function getAutoFilter()
+ {
+ return $this->autoFilter;
+ }
+
+ /**
+ * Set AutoFilter.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setAutoFilter($pValue)
+ {
+ $this->autoFilter = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get PivotTables.
+ *
+ * @return bool
+ */
+ public function getPivotTables()
+ {
+ return $this->pivotTables;
+ }
+
+ /**
+ * Set PivotTables.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setPivotTables($pValue)
+ {
+ $this->pivotTables = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get SelectUnlockedCells.
+ *
+ * @return bool
+ */
+ public function getSelectUnlockedCells()
+ {
+ return $this->selectUnlockedCells;
+ }
+
+ /**
+ * Set SelectUnlockedCells.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setSelectUnlockedCells($pValue)
+ {
+ $this->selectUnlockedCells = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get hashed password.
+ *
+ * @return string
+ */
+ public function getPassword()
+ {
+ return $this->password;
+ }
+
+ /**
+ * Set Password.
+ *
+ * @param string $pValue
+ * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
+ *
+ * @return $this
+ */
+ public function setPassword($pValue, $pAlreadyHashed = false)
+ {
+ if (!$pAlreadyHashed) {
+ $salt = $this->generateSalt();
+ $this->setSalt($salt);
+ $pValue = PasswordHasher::hashPassword($pValue, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount());
+ }
+
+ $this->password = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Create a pseudorandom string.
+ */
+ private function generateSalt(): string
+ {
+ return base64_encode(random_bytes(16));
+ }
+
+ /**
+ * Get algorithm name.
+ */
+ public function getAlgorithm(): string
+ {
+ return $this->algorithm;
+ }
+
+ /**
+ * Set algorithm name.
+ */
+ public function setAlgorithm(string $algorithm): void
+ {
+ $this->algorithm = $algorithm;
+ }
+
+ /**
+ * Get salt value.
+ */
+ public function getSalt(): string
+ {
+ return $this->salt;
+ }
+
+ /**
+ * Set salt value.
+ */
+ public function setSalt(string $salt): void
+ {
+ $this->salt = $salt;
+ }
+
+ /**
+ * Get spin count.
+ */
+ public function getSpinCount(): int
+ {
+ return $this->spinCount;
+ }
+
+ /**
+ * Set spin count.
+ */
+ public function setSpinCount(int $spinCount): void
+ {
+ $this->spinCount = $spinCount;
+ }
+
+ /**
+ * Verify that the given non-hashed password can "unlock" the protection.
+ */
+ public function verify(string $password): bool
+ {
+ if (!$this->isProtectionEnabled()) {
+ return true;
+ }
+
+ $hash = PasswordHasher::hashPassword($password, $this->getAlgorithm(), $this->getSalt(), $this->getSpinCount());
+
+ return $this->getPassword() === $hash;
+ }
+
+ /**
+ * 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/Row.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php
new file mode 100644
index 0000000..a622a4b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Row.php
@@ -0,0 +1,74 @@
+worksheet = $worksheet;
+ $this->rowIndex = $rowIndex;
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ $this->worksheet = null;
+ }
+
+ /**
+ * Get row index.
+ *
+ * @return int
+ */
+ public function getRowIndex()
+ {
+ return $this->rowIndex;
+ }
+
+ /**
+ * Get cell iterator.
+ *
+ * @param string $startColumn The column address at which to start iterating
+ * @param string $endColumn Optionally, the column address at which to stop iterating
+ *
+ * @return RowCellIterator
+ */
+ public function getCellIterator($startColumn = 'A', $endColumn = null)
+ {
+ return new RowCellIterator($this->worksheet, $this->rowIndex, $startColumn, $endColumn);
+ }
+
+ /**
+ * Returns bound worksheet.
+ *
+ * @return Worksheet
+ */
+ public function getWorksheet()
+ {
+ return $this->worksheet;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php
new file mode 100644
index 0000000..1ac758a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowCellIterator.php
@@ -0,0 +1,195 @@
+worksheet = $worksheet;
+ $this->rowIndex = $rowIndex;
+ $this->resetEnd($endColumn);
+ $this->resetStart($startColumn);
+ }
+
+ /**
+ * (Re)Set the start column and the current column pointer.
+ *
+ * @param string $startColumn The column address at which to start iterating
+ *
+ * @return $this
+ */
+ public function resetStart($startColumn = 'A')
+ {
+ $this->startColumnIndex = Coordinate::columnIndexFromString($startColumn);
+ $this->adjustForExistingOnlyRange();
+ $this->seek(Coordinate::stringFromColumnIndex($this->startColumnIndex));
+
+ return $this;
+ }
+
+ /**
+ * (Re)Set the end column.
+ *
+ * @param string $endColumn The column address at which to stop iterating
+ *
+ * @return $this
+ */
+ public function resetEnd($endColumn = null)
+ {
+ $endColumn = $endColumn ? $endColumn : $this->worksheet->getHighestColumn();
+ $this->endColumnIndex = Coordinate::columnIndexFromString($endColumn);
+ $this->adjustForExistingOnlyRange();
+
+ return $this;
+ }
+
+ /**
+ * Set the column pointer to the selected column.
+ *
+ * @param string $column The column address to set the current pointer at
+ *
+ * @return $this
+ */
+ public function seek($column = 'A')
+ {
+ $column = Coordinate::columnIndexFromString($column);
+ if (($column < $this->startColumnIndex) || ($column > $this->endColumnIndex)) {
+ throw new PhpSpreadsheetException("Column $column is out of range ({$this->startColumnIndex} - {$this->endColumnIndex})");
+ } elseif ($this->onlyExistingCells && !($this->worksheet->cellExistsByColumnAndRow($column, $this->rowIndex))) {
+ throw new PhpSpreadsheetException('In "IterateOnlyExistingCells" mode and Cell does not exist');
+ }
+ $this->currentColumnIndex = $column;
+
+ return $this;
+ }
+
+ /**
+ * Rewind the iterator to the starting column.
+ */
+ public function rewind(): void
+ {
+ $this->currentColumnIndex = $this->startColumnIndex;
+ }
+
+ /**
+ * Return the current cell in this worksheet row.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Cell\Cell
+ */
+ public function current()
+ {
+ return $this->worksheet->getCellByColumnAndRow($this->currentColumnIndex, $this->rowIndex);
+ }
+
+ /**
+ * Return the current iterator key.
+ *
+ * @return string
+ */
+ public function key()
+ {
+ return Coordinate::stringFromColumnIndex($this->currentColumnIndex);
+ }
+
+ /**
+ * Set the iterator to its next value.
+ */
+ public function next(): void
+ {
+ do {
+ ++$this->currentColumnIndex;
+ } while (($this->onlyExistingCells) && (!$this->worksheet->cellExistsByColumnAndRow($this->currentColumnIndex, $this->rowIndex)) && ($this->currentColumnIndex <= $this->endColumnIndex));
+ }
+
+ /**
+ * Set the iterator to its previous value.
+ */
+ public function prev(): void
+ {
+ do {
+ --$this->currentColumnIndex;
+ } while (($this->onlyExistingCells) && (!$this->worksheet->cellExistsByColumnAndRow($this->currentColumnIndex, $this->rowIndex)) && ($this->currentColumnIndex >= $this->startColumnIndex));
+ }
+
+ /**
+ * Indicate if more columns exist in the worksheet range of columns that we're iterating.
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return $this->currentColumnIndex <= $this->endColumnIndex && $this->currentColumnIndex >= $this->startColumnIndex;
+ }
+
+ /**
+ * Return the current iterator position.
+ *
+ * @return int
+ */
+ public function getCurrentColumnIndex()
+ {
+ return $this->currentColumnIndex;
+ }
+
+ /**
+ * Validate start/end values for "IterateOnlyExistingCells" mode, and adjust if necessary.
+ */
+ protected function adjustForExistingOnlyRange(): void
+ {
+ if ($this->onlyExistingCells) {
+ while ((!$this->worksheet->cellExistsByColumnAndRow($this->startColumnIndex, $this->rowIndex)) && ($this->startColumnIndex <= $this->endColumnIndex)) {
+ ++$this->startColumnIndex;
+ }
+ if ($this->startColumnIndex > $this->endColumnIndex) {
+ throw new PhpSpreadsheetException('No cells exist within the specified range');
+ }
+ while ((!$this->worksheet->cellExistsByColumnAndRow($this->endColumnIndex, $this->rowIndex)) && ($this->endColumnIndex >= $this->startColumnIndex)) {
+ --$this->endColumnIndex;
+ }
+ if ($this->endColumnIndex < $this->startColumnIndex) {
+ throw new PhpSpreadsheetException('No cells exist within the specified range');
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php
new file mode 100644
index 0000000..b485106
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowDimension.php
@@ -0,0 +1,115 @@
+rowIndex = $pIndex;
+
+ // set dimension as unformatted by default
+ parent::__construct(null);
+ }
+
+ /**
+ * Get Row Index.
+ *
+ * @return int
+ */
+ public function getRowIndex()
+ {
+ return $this->rowIndex;
+ }
+
+ /**
+ * Set Row Index.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setRowIndex($pValue)
+ {
+ $this->rowIndex = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Row Height.
+ *
+ * @return float
+ */
+ public function getRowHeight()
+ {
+ return $this->height;
+ }
+
+ /**
+ * Set Row Height.
+ *
+ * @param float $pValue
+ *
+ * @return $this
+ */
+ public function setRowHeight($pValue)
+ {
+ $this->height = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get ZeroHeight.
+ *
+ * @return bool
+ */
+ public function getZeroHeight()
+ {
+ return $this->zeroHeight;
+ }
+
+ /**
+ * Set ZeroHeight.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setZeroHeight($pValue)
+ {
+ $this->zeroHeight = $pValue;
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php
new file mode 100644
index 0000000..00af693
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/RowIterator.php
@@ -0,0 +1,167 @@
+subject = $subject;
+ $this->resetEnd($endRow);
+ $this->resetStart($startRow);
+ }
+
+ /**
+ * Destructor.
+ */
+ public function __destruct()
+ {
+ $this->subject = null;
+ }
+
+ /**
+ * (Re)Set the start row and the current row pointer.
+ *
+ * @param int $startRow The row number at which to start iterating
+ *
+ * @return $this
+ */
+ public function resetStart($startRow = 1)
+ {
+ if ($startRow > $this->subject->getHighestRow()) {
+ throw new PhpSpreadsheetException("Start row ({$startRow}) is beyond highest row ({$this->subject->getHighestRow()})");
+ }
+
+ $this->startRow = $startRow;
+ if ($this->endRow < $this->startRow) {
+ $this->endRow = $this->startRow;
+ }
+ $this->seek($startRow);
+
+ return $this;
+ }
+
+ /**
+ * (Re)Set the end row.
+ *
+ * @param int $endRow The row number at which to stop iterating
+ *
+ * @return $this
+ */
+ public function resetEnd($endRow = null)
+ {
+ $this->endRow = ($endRow) ? $endRow : $this->subject->getHighestRow();
+
+ return $this;
+ }
+
+ /**
+ * Set the row pointer to the selected row.
+ *
+ * @param int $row The row number to set the current pointer at
+ *
+ * @return $this
+ */
+ public function seek($row = 1)
+ {
+ if (($row < $this->startRow) || ($row > $this->endRow)) {
+ throw new PhpSpreadsheetException("Row $row is out of range ({$this->startRow} - {$this->endRow})");
+ }
+ $this->position = $row;
+
+ return $this;
+ }
+
+ /**
+ * Rewind the iterator to the starting row.
+ */
+ public function rewind(): void
+ {
+ $this->position = $this->startRow;
+ }
+
+ /**
+ * Return the current row in this worksheet.
+ *
+ * @return Row
+ */
+ public function current()
+ {
+ return new Row($this->subject, $this->position);
+ }
+
+ /**
+ * Return the current iterator key.
+ *
+ * @return int
+ */
+ public function key()
+ {
+ return $this->position;
+ }
+
+ /**
+ * Set the iterator to its next value.
+ */
+ public function next(): void
+ {
+ ++$this->position;
+ }
+
+ /**
+ * Set the iterator to its previous value.
+ */
+ public function prev(): void
+ {
+ --$this->position;
+ }
+
+ /**
+ * Indicate if more rows exist in the worksheet range of rows that we're iterating.
+ *
+ * @return bool
+ */
+ public function valid()
+ {
+ return $this->position <= $this->endRow && $this->position >= $this->startRow;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php
new file mode 100644
index 0000000..e76ec58
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/SheetView.php
@@ -0,0 +1,193 @@
+zoomScale;
+ }
+
+ /**
+ * Set ZoomScale.
+ * Valid values range from 10 to 400.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setZoomScale($pValue)
+ {
+ // 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 >= 1
+ if (($pValue >= 1) || $pValue === null) {
+ $this->zoomScale = $pValue;
+ } else {
+ throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get ZoomScaleNormal.
+ *
+ * @return int
+ */
+ public function getZoomScaleNormal()
+ {
+ return $this->zoomScaleNormal;
+ }
+
+ /**
+ * Set ZoomScale.
+ * Valid values range from 10 to 400.
+ *
+ * @param int $pValue
+ *
+ * @return $this
+ */
+ public function setZoomScaleNormal($pValue)
+ {
+ if (($pValue >= 1) || $pValue === null) {
+ $this->zoomScaleNormal = $pValue;
+ } else {
+ throw new PhpSpreadsheetException('Scale must be greater than or equal to 1.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set ShowZeroes setting.
+ *
+ * @param bool $pValue
+ */
+ public function setShowZeros($pValue): void
+ {
+ $this->showZeros = $pValue;
+ }
+
+ /**
+ * @return bool
+ */
+ public function getShowZeros()
+ {
+ return $this->showZeros;
+ }
+
+ /**
+ * Get View.
+ *
+ * @return string
+ */
+ public function getView()
+ {
+ return $this->sheetviewType;
+ }
+
+ /**
+ * Set View.
+ *
+ * Valid values are
+ * 'normal' self::SHEETVIEW_NORMAL
+ * 'pageLayout' self::SHEETVIEW_PAGE_LAYOUT
+ * 'pageBreakPreview' self::SHEETVIEW_PAGE_BREAK_PREVIEW
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setView($pValue)
+ {
+ // MS Excel 2007 allows setting the view to 'normal', 'pageLayout' or 'pageBreakPreview' via the user interface
+ if ($pValue === null) {
+ $pValue = self::SHEETVIEW_NORMAL;
+ }
+ if (in_array($pValue, self::$sheetViewTypes)) {
+ $this->sheetviewType = $pValue;
+ } else {
+ throw new PhpSpreadsheetException('Invalid sheetview layout type.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone()
+ {
+ $vars = get_object_vars($this);
+ foreach ($vars as $key => $value) {
+ if (is_object($value)) {
+ $this->$key = clone $value;
+ } else {
+ $this->$key = $value;
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php
new file mode 100644
index 0000000..1cbecea
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Worksheet/Worksheet.php
@@ -0,0 +1,3019 @@
+parent = $parent;
+ $this->setTitle($pTitle, false);
+ // setTitle can change $pTitle
+ $this->setCodeName($this->getTitle());
+ $this->setSheetState(self::SHEETSTATE_VISIBLE);
+
+ $this->cellCollection = CellsFactory::getInstance($this);
+ // Set page setup
+ $this->pageSetup = new PageSetup();
+ // Set page margins
+ $this->pageMargins = new PageMargins();
+ // Set page header/footer
+ $this->headerFooter = new HeaderFooter();
+ // Set sheet view
+ $this->sheetView = new SheetView();
+ // Drawing collection
+ $this->drawingCollection = new ArrayObject();
+ // Chart collection
+ $this->chartCollection = new ArrayObject();
+ // Protection
+ $this->protection = new Protection();
+ // Default row dimension
+ $this->defaultRowDimension = new RowDimension(null);
+ // Default column dimension
+ $this->defaultColumnDimension = new ColumnDimension(null);
+ $this->autoFilter = new AutoFilter(null, $this);
+ }
+
+ /**
+ * Disconnect all cells from this Worksheet object,
+ * typically so that the worksheet object can be unset.
+ */
+ public function disconnectCells(): void
+ {
+ if ($this->cellCollection !== null) {
+ $this->cellCollection->unsetWorksheetCells();
+ $this->cellCollection = null;
+ }
+ // detach ourself from the workbook, so that it can then delete this worksheet successfully
+ $this->parent = null;
+ }
+
+ /**
+ * Code to execute when this worksheet is unset().
+ */
+ public function __destruct()
+ {
+ Calculation::getInstance($this->parent)->clearCalculationCacheForWorksheet($this->title);
+
+ $this->disconnectCells();
+ }
+
+ /**
+ * Return the cell collection.
+ *
+ * @return Cells
+ */
+ public function getCellCollection()
+ {
+ return $this->cellCollection;
+ }
+
+ /**
+ * Get array of invalid characters for sheet title.
+ *
+ * @return array
+ */
+ public static function getInvalidCharacters()
+ {
+ return self::$invalidCharacters;
+ }
+
+ /**
+ * Check sheet code name for valid Excel syntax.
+ *
+ * @param string $pValue The string to check
+ *
+ * @return string The valid string
+ */
+ private static function checkSheetCodeName($pValue)
+ {
+ $CharCount = Shared\StringHelper::countCharacters($pValue);
+ if ($CharCount == 0) {
+ throw new Exception('Sheet code name cannot be empty.');
+ }
+ // Some of the printable ASCII characters are invalid: * : / \ ? [ ] and first and last characters cannot be a "'"
+ if (
+ (str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) ||
+ (Shared\StringHelper::substring($pValue, -1, 1) == '\'') ||
+ (Shared\StringHelper::substring($pValue, 0, 1) == '\'')
+ ) {
+ throw new Exception('Invalid character found in sheet code name');
+ }
+
+ // Enforce maximum characters allowed for sheet title
+ if ($CharCount > self::SHEET_TITLE_MAXIMUM_LENGTH) {
+ throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet code name.');
+ }
+
+ return $pValue;
+ }
+
+ /**
+ * Check sheet title for valid Excel syntax.
+ *
+ * @param string $pValue The string to check
+ *
+ * @return string The valid string
+ */
+ private static function checkSheetTitle($pValue)
+ {
+ // Some of the printable ASCII characters are invalid: * : / \ ? [ ]
+ if (str_replace(self::$invalidCharacters, '', $pValue) !== $pValue) {
+ throw new Exception('Invalid character found in sheet title');
+ }
+
+ // Enforce maximum characters allowed for sheet title
+ if (Shared\StringHelper::countCharacters($pValue) > self::SHEET_TITLE_MAXIMUM_LENGTH) {
+ throw new Exception('Maximum ' . self::SHEET_TITLE_MAXIMUM_LENGTH . ' characters allowed in sheet title.');
+ }
+
+ return $pValue;
+ }
+
+ /**
+ * Get a sorted list of all cell coordinates currently held in the collection by row and column.
+ *
+ * @param bool $sorted Also sort the cell collection?
+ *
+ * @return string[]
+ */
+ public function getCoordinates($sorted = true)
+ {
+ if ($this->cellCollection == null) {
+ return [];
+ }
+
+ if ($sorted) {
+ return $this->cellCollection->getSortedCoordinates();
+ }
+
+ return $this->cellCollection->getCoordinates();
+ }
+
+ /**
+ * Get collection of row dimensions.
+ *
+ * @return RowDimension[]
+ */
+ public function getRowDimensions()
+ {
+ return $this->rowDimensions;
+ }
+
+ /**
+ * Get default row dimension.
+ *
+ * @return RowDimension
+ */
+ public function getDefaultRowDimension()
+ {
+ return $this->defaultRowDimension;
+ }
+
+ /**
+ * Get collection of column dimensions.
+ *
+ * @return ColumnDimension[]
+ */
+ public function getColumnDimensions()
+ {
+ return $this->columnDimensions;
+ }
+
+ /**
+ * Get default column dimension.
+ *
+ * @return ColumnDimension
+ */
+ public function getDefaultColumnDimension()
+ {
+ return $this->defaultColumnDimension;
+ }
+
+ /**
+ * Get collection of drawings.
+ *
+ * @return BaseDrawing[]
+ */
+ public function getDrawingCollection()
+ {
+ return $this->drawingCollection;
+ }
+
+ /**
+ * Get collection of charts.
+ *
+ * @return Chart[]
+ */
+ public function getChartCollection()
+ {
+ return $this->chartCollection;
+ }
+
+ /**
+ * Add chart.
+ *
+ * @param null|int $iChartIndex Index where chart should go (0,1,..., or null for last)
+ *
+ * @return Chart
+ */
+ public function addChart(Chart $pChart, $iChartIndex = null)
+ {
+ $pChart->setWorksheet($this);
+ if ($iChartIndex === null) {
+ $this->chartCollection[] = $pChart;
+ } else {
+ // Insert the chart at the requested index
+ array_splice($this->chartCollection, $iChartIndex, 0, [$pChart]);
+ }
+
+ return $pChart;
+ }
+
+ /**
+ * Return the count of charts on this worksheet.
+ *
+ * @return int The number of charts
+ */
+ public function getChartCount()
+ {
+ return count($this->chartCollection);
+ }
+
+ /**
+ * Get a chart by its index position.
+ *
+ * @param string $index Chart index position
+ *
+ * @return Chart|false
+ */
+ public function getChartByIndex($index)
+ {
+ $chartCount = count($this->chartCollection);
+ if ($chartCount == 0) {
+ return false;
+ }
+ if ($index === null) {
+ $index = --$chartCount;
+ }
+ if (!isset($this->chartCollection[$index])) {
+ return false;
+ }
+
+ return $this->chartCollection[$index];
+ }
+
+ /**
+ * Return an array of the names of charts on this worksheet.
+ *
+ * @return string[] The names of charts
+ */
+ public function getChartNames()
+ {
+ $chartNames = [];
+ foreach ($this->chartCollection as $chart) {
+ $chartNames[] = $chart->getName();
+ }
+
+ return $chartNames;
+ }
+
+ /**
+ * Get a chart by name.
+ *
+ * @param string $chartName Chart name
+ *
+ * @return Chart|false
+ */
+ public function getChartByName($chartName)
+ {
+ $chartCount = count($this->chartCollection);
+ if ($chartCount == 0) {
+ return false;
+ }
+ foreach ($this->chartCollection as $index => $chart) {
+ if ($chart->getName() == $chartName) {
+ return $this->chartCollection[$index];
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Refresh column dimensions.
+ *
+ * @return $this
+ */
+ public function refreshColumnDimensions()
+ {
+ $currentColumnDimensions = $this->getColumnDimensions();
+ $newColumnDimensions = [];
+
+ foreach ($currentColumnDimensions as $objColumnDimension) {
+ $newColumnDimensions[$objColumnDimension->getColumnIndex()] = $objColumnDimension;
+ }
+
+ $this->columnDimensions = $newColumnDimensions;
+
+ return $this;
+ }
+
+ /**
+ * Refresh row dimensions.
+ *
+ * @return $this
+ */
+ public function refreshRowDimensions()
+ {
+ $currentRowDimensions = $this->getRowDimensions();
+ $newRowDimensions = [];
+
+ foreach ($currentRowDimensions as $objRowDimension) {
+ $newRowDimensions[$objRowDimension->getRowIndex()] = $objRowDimension;
+ }
+
+ $this->rowDimensions = $newRowDimensions;
+
+ return $this;
+ }
+
+ /**
+ * Calculate worksheet dimension.
+ *
+ * @return string String containing the dimension of this worksheet
+ */
+ public function calculateWorksheetDimension()
+ {
+ // Return
+ return 'A1:' . $this->getHighestColumn() . $this->getHighestRow();
+ }
+
+ /**
+ * Calculate worksheet data dimension.
+ *
+ * @return string String containing the dimension of this worksheet that actually contain data
+ */
+ public function calculateWorksheetDataDimension()
+ {
+ // Return
+ return 'A1:' . $this->getHighestDataColumn() . $this->getHighestDataRow();
+ }
+
+ /**
+ * Calculate widths for auto-size columns.
+ *
+ * @return $this
+ */
+ public function calculateColumnWidths()
+ {
+ // initialize $autoSizes array
+ $autoSizes = [];
+ foreach ($this->getColumnDimensions() as $colDimension) {
+ if ($colDimension->getAutoSize()) {
+ $autoSizes[$colDimension->getColumnIndex()] = -1;
+ }
+ }
+
+ // There is only something to do if there are some auto-size columns
+ if (!empty($autoSizes)) {
+ // build list of cells references that participate in a merge
+ $isMergeCell = [];
+ foreach ($this->getMergeCells() as $cells) {
+ foreach (Coordinate::extractAllCellReferencesInRange($cells) as $cellReference) {
+ $isMergeCell[$cellReference] = true;
+ }
+ }
+
+ // loop through all cells in the worksheet
+ foreach ($this->getCoordinates(false) as $coordinate) {
+ $cell = $this->getCell($coordinate, false);
+ if ($cell !== null && isset($autoSizes[$this->cellCollection->getCurrentColumn()])) {
+ //Determine if cell is in merge range
+ $isMerged = isset($isMergeCell[$this->cellCollection->getCurrentCoordinate()]);
+
+ //By default merged cells should be ignored
+ $isMergedButProceed = false;
+
+ //The only exception is if it's a merge range value cell of a 'vertical' randge (1 column wide)
+ if ($isMerged && $cell->isMergeRangeValueCell()) {
+ $range = $cell->getMergeRange();
+ $rangeBoundaries = Coordinate::rangeDimension($range);
+ if ($rangeBoundaries[0] == 1) {
+ $isMergedButProceed = true;
+ }
+ }
+
+ // Determine width if cell does not participate in a merge or does and is a value cell of 1-column wide range
+ if (!$isMerged || $isMergedButProceed) {
+ // Calculated value
+ // To formatted string
+ $cellValue = NumberFormat::toFormattedString(
+ $cell->getCalculatedValue(),
+ $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode()
+ );
+
+ $autoSizes[$this->cellCollection->getCurrentColumn()] = max(
+ (float) $autoSizes[$this->cellCollection->getCurrentColumn()],
+ (float) Shared\Font::calculateColumnWidth(
+ $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont(),
+ $cellValue,
+ $this->getParent()->getCellXfByIndex($cell->getXfIndex())->getAlignment()->getTextRotation(),
+ $this->getParent()->getDefaultStyle()->getFont()
+ )
+ );
+ }
+ }
+ }
+
+ // adjust column widths
+ foreach ($autoSizes as $columnIndex => $width) {
+ if ($width == -1) {
+ $width = $this->getDefaultColumnDimension()->getWidth();
+ }
+ $this->getColumnDimension($columnIndex)->setWidth($width);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get parent.
+ *
+ * @return Spreadsheet
+ */
+ public function getParent()
+ {
+ return $this->parent;
+ }
+
+ /**
+ * Re-bind parent.
+ *
+ * @return $this
+ */
+ public function rebindParent(Spreadsheet $parent)
+ {
+ if ($this->parent !== null) {
+ $definedNames = $this->parent->getDefinedNames();
+ foreach ($definedNames as $definedName) {
+ $parent->addDefinedName($definedName);
+ }
+
+ $this->parent->removeSheetByIndex(
+ $this->parent->getIndex($this)
+ );
+ }
+ $this->parent = $parent;
+
+ return $this;
+ }
+
+ /**
+ * Get title.
+ *
+ * @return string
+ */
+ public function getTitle()
+ {
+ return $this->title;
+ }
+
+ /**
+ * Set title.
+ *
+ * @param string $pValue String containing the dimension of this worksheet
+ * @param bool $updateFormulaCellReferences Flag indicating whether cell references in formulae should
+ * be updated to reflect the new sheet name.
+ * This should be left as the default true, unless you are
+ * certain that no formula cells on any worksheet contain
+ * references to this worksheet
+ * @param bool $validate False to skip validation of new title. WARNING: This should only be set
+ * at parse time (by Readers), where titles can be assumed to be valid.
+ *
+ * @return $this
+ */
+ public function setTitle($pValue, $updateFormulaCellReferences = true, $validate = true)
+ {
+ // Is this a 'rename' or not?
+ if ($this->getTitle() == $pValue) {
+ return $this;
+ }
+
+ // Old title
+ $oldTitle = $this->getTitle();
+
+ if ($validate) {
+ // Syntax check
+ self::checkSheetTitle($pValue);
+
+ if ($this->parent) {
+ // Is there already such sheet name?
+ if ($this->parent->sheetNameExists($pValue)) {
+ // Use name, but append with lowest possible integer
+
+ if (Shared\StringHelper::countCharacters($pValue) > 29) {
+ $pValue = Shared\StringHelper::substring($pValue, 0, 29);
+ }
+ $i = 1;
+ while ($this->parent->sheetNameExists($pValue . ' ' . $i)) {
+ ++$i;
+ if ($i == 10) {
+ if (Shared\StringHelper::countCharacters($pValue) > 28) {
+ $pValue = Shared\StringHelper::substring($pValue, 0, 28);
+ }
+ } elseif ($i == 100) {
+ if (Shared\StringHelper::countCharacters($pValue) > 27) {
+ $pValue = Shared\StringHelper::substring($pValue, 0, 27);
+ }
+ }
+ }
+
+ $pValue .= " $i";
+ }
+ }
+ }
+
+ // Set title
+ $this->title = $pValue;
+ $this->dirty = true;
+
+ if ($this->parent && $this->parent->getCalculationEngine()) {
+ // New title
+ $newTitle = $this->getTitle();
+ $this->parent->getCalculationEngine()
+ ->renameCalculationCacheForWorksheet($oldTitle, $newTitle);
+ if ($updateFormulaCellReferences) {
+ ReferenceHelper::getInstance()->updateNamedFormulas($this->parent, $oldTitle, $newTitle);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Get sheet state.
+ *
+ * @return string Sheet state (visible, hidden, veryHidden)
+ */
+ public function getSheetState()
+ {
+ return $this->sheetState;
+ }
+
+ /**
+ * Set sheet state.
+ *
+ * @param string $value Sheet state (visible, hidden, veryHidden)
+ *
+ * @return $this
+ */
+ public function setSheetState($value)
+ {
+ $this->sheetState = $value;
+
+ return $this;
+ }
+
+ /**
+ * Get page setup.
+ *
+ * @return PageSetup
+ */
+ public function getPageSetup()
+ {
+ return $this->pageSetup;
+ }
+
+ /**
+ * Set page setup.
+ *
+ * @return $this
+ */
+ public function setPageSetup(PageSetup $pValue)
+ {
+ $this->pageSetup = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get page margins.
+ *
+ * @return PageMargins
+ */
+ public function getPageMargins()
+ {
+ return $this->pageMargins;
+ }
+
+ /**
+ * Set page margins.
+ *
+ * @return $this
+ */
+ public function setPageMargins(PageMargins $pValue)
+ {
+ $this->pageMargins = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get page header/footer.
+ *
+ * @return HeaderFooter
+ */
+ public function getHeaderFooter()
+ {
+ return $this->headerFooter;
+ }
+
+ /**
+ * Set page header/footer.
+ *
+ * @return $this
+ */
+ public function setHeaderFooter(HeaderFooter $pValue)
+ {
+ $this->headerFooter = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get sheet view.
+ *
+ * @return SheetView
+ */
+ public function getSheetView()
+ {
+ return $this->sheetView;
+ }
+
+ /**
+ * Set sheet view.
+ *
+ * @return $this
+ */
+ public function setSheetView(SheetView $pValue)
+ {
+ $this->sheetView = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Protection.
+ *
+ * @return Protection
+ */
+ public function getProtection()
+ {
+ return $this->protection;
+ }
+
+ /**
+ * Set Protection.
+ *
+ * @return $this
+ */
+ public function setProtection(Protection $pValue)
+ {
+ $this->protection = $pValue;
+ $this->dirty = true;
+
+ return $this;
+ }
+
+ /**
+ * Get highest worksheet column.
+ *
+ * @param string $row Return the data highest column for the specified row,
+ * or the highest column of any row if no row number is passed
+ *
+ * @return string Highest column name
+ */
+ public function getHighestColumn($row = null)
+ {
+ if ($row == null) {
+ return $this->cachedHighestColumn;
+ }
+
+ return $this->getHighestDataColumn($row);
+ }
+
+ /**
+ * Get highest worksheet column that contains data.
+ *
+ * @param string $row Return the highest data column for the specified row,
+ * or the highest data column of any row if no row number is passed
+ *
+ * @return string Highest column name that contains data
+ */
+ public function getHighestDataColumn($row = null)
+ {
+ return $this->cellCollection->getHighestColumn($row);
+ }
+
+ /**
+ * Get highest worksheet row.
+ *
+ * @param string $column Return the highest data row for the specified column,
+ * or the highest row of any column if no column letter is passed
+ *
+ * @return int Highest row number
+ */
+ public function getHighestRow($column = null)
+ {
+ if ($column == null) {
+ return $this->cachedHighestRow;
+ }
+
+ return $this->getHighestDataRow($column);
+ }
+
+ /**
+ * Get highest worksheet row that contains data.
+ *
+ * @param string $column Return the highest data row for the specified column,
+ * or the highest data row of any column if no column letter is passed
+ *
+ * @return int Highest row number that contains data
+ */
+ public function getHighestDataRow($column = null)
+ {
+ return $this->cellCollection->getHighestRow($column);
+ }
+
+ /**
+ * Get highest worksheet column and highest row that have cell records.
+ *
+ * @return array Highest column name and highest row number
+ */
+ public function getHighestRowAndColumn()
+ {
+ return $this->cellCollection->getHighestRowAndColumn();
+ }
+
+ /**
+ * Set a cell value.
+ *
+ * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
+ * @param mixed $pValue Value of the cell
+ *
+ * @return $this
+ */
+ public function setCellValue($pCoordinate, $pValue)
+ {
+ $this->getCell($pCoordinate)->setValue($pValue);
+
+ return $this;
+ }
+
+ /**
+ * Set a cell value by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ * @param mixed $value Value of the cell
+ *
+ * @return $this
+ */
+ public function setCellValueByColumnAndRow($columnIndex, $row, $value)
+ {
+ $this->getCellByColumnAndRow($columnIndex, $row)->setValue($value);
+
+ return $this;
+ }
+
+ /**
+ * Set a cell value.
+ *
+ * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
+ * @param mixed $pValue Value of the cell
+ * @param string $pDataType Explicit data type, see DataType::TYPE_*
+ *
+ * @return $this
+ */
+ public function setCellValueExplicit($pCoordinate, $pValue, $pDataType)
+ {
+ // Set value
+ $this->getCell($pCoordinate)->setValueExplicit($pValue, $pDataType);
+
+ return $this;
+ }
+
+ /**
+ * Set a cell value by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ * @param mixed $value Value of the cell
+ * @param string $dataType Explicit data type, see DataType::TYPE_*
+ *
+ * @return $this
+ */
+ public function setCellValueExplicitByColumnAndRow($columnIndex, $row, $value, $dataType)
+ {
+ $this->getCellByColumnAndRow($columnIndex, $row)->setValueExplicit($value, $dataType);
+
+ return $this;
+ }
+
+ /**
+ * Get cell at a specific coordinate.
+ *
+ * @param string $pCoordinate Coordinate of the cell, eg: 'A1'
+ * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't
+ * already exist, or a null should be returned instead
+ *
+ * @return null|Cell Cell that was found/created or null
+ */
+ public function getCell($pCoordinate, $createIfNotExists = true)
+ {
+ // Uppercase coordinate
+ $pCoordinateUpper = strtoupper($pCoordinate);
+
+ // Check cell collection
+ if ($this->cellCollection->has($pCoordinateUpper)) {
+ return $this->cellCollection->get($pCoordinateUpper);
+ }
+
+ // Worksheet reference?
+ if (strpos($pCoordinate, '!') !== false) {
+ $worksheetReference = self::extractSheetTitle($pCoordinate, true);
+
+ return $this->parent->getSheetByName($worksheetReference[0])->getCell(strtoupper($worksheetReference[1]), $createIfNotExists);
+ }
+
+ // Named range?
+ if (
+ (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
+ (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/i', $pCoordinate, $matches))
+ ) {
+ $namedRange = DefinedName::resolveName($pCoordinate, $this);
+ if ($namedRange !== null) {
+ $pCoordinate = $namedRange->getValue();
+
+ return $namedRange->getWorksheet()->getCell($pCoordinate, $createIfNotExists);
+ }
+ }
+
+ if (Coordinate::coordinateIsRange($pCoordinate)) {
+ throw new Exception('Cell coordinate can not be a range of cells.');
+ } elseif (strpos($pCoordinate, '$') !== false) {
+ throw new Exception('Cell coordinate must not be absolute.');
+ }
+
+ // Create new cell object, if required
+ return $createIfNotExists ? $this->createNewCell($pCoordinateUpper) : null;
+ }
+
+ /**
+ * Get cell at a specific coordinate by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ * @param bool $createIfNotExists Flag indicating whether a new cell should be created if it doesn't
+ * already exist, or a null should be returned instead
+ *
+ * @return null|Cell Cell that was found/created or null
+ */
+ public function getCellByColumnAndRow($columnIndex, $row, $createIfNotExists = true)
+ {
+ $columnLetter = Coordinate::stringFromColumnIndex($columnIndex);
+ $coordinate = $columnLetter . $row;
+
+ if ($this->cellCollection->has($coordinate)) {
+ return $this->cellCollection->get($coordinate);
+ }
+
+ // Create new cell object, if required
+ return $createIfNotExists ? $this->createNewCell($coordinate) : null;
+ }
+
+ /**
+ * Create a new cell at the specified coordinate.
+ *
+ * @param string $pCoordinate Coordinate of the cell
+ *
+ * @return Cell Cell that was created
+ */
+ private function createNewCell($pCoordinate)
+ {
+ $cell = new Cell(null, DataType::TYPE_NULL, $this);
+ $this->cellCollection->add($pCoordinate, $cell);
+ $this->cellCollectionIsSorted = false;
+
+ // Coordinates
+ $aCoordinates = Coordinate::coordinateFromString($pCoordinate);
+ if (Coordinate::columnIndexFromString($this->cachedHighestColumn) < Coordinate::columnIndexFromString($aCoordinates[0])) {
+ $this->cachedHighestColumn = $aCoordinates[0];
+ }
+ if ($aCoordinates[1] > $this->cachedHighestRow) {
+ $this->cachedHighestRow = $aCoordinates[1];
+ }
+
+ // Cell needs appropriate xfIndex from dimensions records
+ // but don't create dimension records if they don't already exist
+ $rowDimension = $this->getRowDimension($aCoordinates[1], false);
+ $columnDimension = $this->getColumnDimension($aCoordinates[0], false);
+
+ if ($rowDimension !== null && $rowDimension->getXfIndex() > 0) {
+ // then there is a row dimension with explicit style, assign it to the cell
+ $cell->setXfIndex($rowDimension->getXfIndex());
+ } elseif ($columnDimension !== null && $columnDimension->getXfIndex() > 0) {
+ // then there is a column dimension, assign it to the cell
+ $cell->setXfIndex($columnDimension->getXfIndex());
+ }
+
+ return $cell;
+ }
+
+ /**
+ * Does the cell at a specific coordinate exist?
+ *
+ * @param string $pCoordinate Coordinate of the cell eg: 'A1'
+ *
+ * @return bool
+ */
+ public function cellExists($pCoordinate)
+ {
+ // Worksheet reference?
+ if (strpos($pCoordinate, '!') !== false) {
+ $worksheetReference = self::extractSheetTitle($pCoordinate, true);
+
+ return $this->parent->getSheetByName($worksheetReference[0])->cellExists(strtoupper($worksheetReference[1]));
+ }
+
+ // Named range?
+ if (
+ (!preg_match('/^' . Calculation::CALCULATION_REGEXP_CELLREF . '$/i', $pCoordinate, $matches)) &&
+ (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/i', $pCoordinate, $matches))
+ ) {
+ $namedRange = DefinedName::resolveName($pCoordinate, $this);
+ if ($namedRange !== null) {
+ $pCoordinate = $namedRange->getValue();
+ if ($this->getHashCode() != $namedRange->getWorksheet()->getHashCode()) {
+ if (!$namedRange->getLocalOnly()) {
+ return $namedRange->getWorksheet()->cellExists($pCoordinate);
+ }
+
+ throw new Exception('Named range ' . $namedRange->getName() . ' is not accessible from within sheet ' . $this->getTitle());
+ }
+ } else {
+ return false;
+ }
+ }
+
+ // Uppercase coordinate
+ $pCoordinate = strtoupper($pCoordinate);
+
+ if (Coordinate::coordinateIsRange($pCoordinate)) {
+ throw new Exception('Cell coordinate can not be a range of cells.');
+ } elseif (strpos($pCoordinate, '$') !== false) {
+ throw new Exception('Cell coordinate must not be absolute.');
+ }
+
+ // Cell exists?
+ return $this->cellCollection->has($pCoordinate);
+ }
+
+ /**
+ * Cell at a specific coordinate by using numeric cell coordinates exists?
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ *
+ * @return bool
+ */
+ public function cellExistsByColumnAndRow($columnIndex, $row)
+ {
+ return $this->cellExists(Coordinate::stringFromColumnIndex($columnIndex) . $row);
+ }
+
+ /**
+ * Get row dimension at a specific row.
+ *
+ * @param int $pRow Numeric index of the row
+ * @param bool $create
+ *
+ * @return RowDimension
+ */
+ public function getRowDimension($pRow, $create = true)
+ {
+ // Found
+ $found = null;
+
+ // Get row dimension
+ if (!isset($this->rowDimensions[$pRow])) {
+ if (!$create) {
+ return null;
+ }
+ $this->rowDimensions[$pRow] = new RowDimension($pRow);
+
+ $this->cachedHighestRow = max($this->cachedHighestRow, $pRow);
+ }
+
+ return $this->rowDimensions[$pRow];
+ }
+
+ /**
+ * Get column dimension at a specific column.
+ *
+ * @param string $pColumn String index of the column eg: 'A'
+ * @param bool $create
+ *
+ * @return ColumnDimension
+ */
+ public function getColumnDimension($pColumn, $create = true)
+ {
+ // Uppercase coordinate
+ $pColumn = strtoupper($pColumn);
+
+ // Fetch dimensions
+ if (!isset($this->columnDimensions[$pColumn])) {
+ if (!$create) {
+ return null;
+ }
+ $this->columnDimensions[$pColumn] = new ColumnDimension($pColumn);
+
+ if (Coordinate::columnIndexFromString($this->cachedHighestColumn) < Coordinate::columnIndexFromString($pColumn)) {
+ $this->cachedHighestColumn = $pColumn;
+ }
+ }
+
+ return $this->columnDimensions[$pColumn];
+ }
+
+ /**
+ * Get column dimension at a specific column by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ *
+ * @return ColumnDimension
+ */
+ public function getColumnDimensionByColumn($columnIndex)
+ {
+ return $this->getColumnDimension(Coordinate::stringFromColumnIndex($columnIndex));
+ }
+
+ /**
+ * Get styles.
+ *
+ * @return Style[]
+ */
+ public function getStyles()
+ {
+ return $this->styles;
+ }
+
+ /**
+ * Get style for cell.
+ *
+ * @param string $pCellCoordinate Cell coordinate (or range) to get style for, eg: 'A1'
+ *
+ * @return Style
+ */
+ public function getStyle($pCellCoordinate)
+ {
+ // set this sheet as active
+ $this->parent->setActiveSheetIndex($this->parent->getIndex($this));
+
+ // set cell coordinate as active
+ $this->setSelectedCells($pCellCoordinate);
+
+ return $this->parent->getCellXfSupervisor();
+ }
+
+ /**
+ * Get conditional styles for a cell.
+ *
+ * @param string $pCoordinate eg: 'A1'
+ *
+ * @return Conditional[]
+ */
+ public function getConditionalStyles($pCoordinate)
+ {
+ $pCoordinate = strtoupper($pCoordinate);
+ if (!isset($this->conditionalStylesCollection[$pCoordinate])) {
+ $this->conditionalStylesCollection[$pCoordinate] = [];
+ }
+
+ return $this->conditionalStylesCollection[$pCoordinate];
+ }
+
+ /**
+ * Do conditional styles exist for this cell?
+ *
+ * @param string $pCoordinate eg: 'A1'
+ *
+ * @return bool
+ */
+ public function conditionalStylesExists($pCoordinate)
+ {
+ return isset($this->conditionalStylesCollection[strtoupper($pCoordinate)]);
+ }
+
+ /**
+ * Removes conditional styles for a cell.
+ *
+ * @param string $pCoordinate eg: 'A1'
+ *
+ * @return $this
+ */
+ public function removeConditionalStyles($pCoordinate)
+ {
+ unset($this->conditionalStylesCollection[strtoupper($pCoordinate)]);
+
+ return $this;
+ }
+
+ /**
+ * Get collection of conditional styles.
+ *
+ * @return array
+ */
+ public function getConditionalStylesCollection()
+ {
+ return $this->conditionalStylesCollection;
+ }
+
+ /**
+ * Set conditional styles.
+ *
+ * @param string $pCoordinate eg: 'A1'
+ * @param $pValue Conditional[]
+ *
+ * @return $this
+ */
+ public function setConditionalStyles($pCoordinate, $pValue)
+ {
+ $this->conditionalStylesCollection[strtoupper($pCoordinate)] = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get style for cell by using numeric cell coordinates.
+ *
+ * @param int $columnIndex1 Numeric column coordinate of the cell
+ * @param int $row1 Numeric row coordinate of the cell
+ * @param null|int $columnIndex2 Numeric column coordinate of the range cell
+ * @param null|int $row2 Numeric row coordinate of the range cell
+ *
+ * @return Style
+ */
+ public function getStyleByColumnAndRow($columnIndex1, $row1, $columnIndex2 = null, $row2 = null)
+ {
+ if ($columnIndex2 !== null && $row2 !== null) {
+ $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
+
+ return $this->getStyle($cellRange);
+ }
+
+ return $this->getStyle(Coordinate::stringFromColumnIndex($columnIndex1) . $row1);
+ }
+
+ /**
+ * Duplicate cell style to a range of cells.
+ *
+ * Please note that this will overwrite existing cell styles for cells in range!
+ *
+ * @param Style $pCellStyle Cell style to duplicate
+ * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
+ *
+ * @return $this
+ */
+ public function duplicateStyle(Style $pCellStyle, $pRange)
+ {
+ // Add the style to the workbook if necessary
+ $workbook = $this->parent;
+ if ($existingStyle = $this->parent->getCellXfByHashCode($pCellStyle->getHashCode())) {
+ // there is already such cell Xf in our collection
+ $xfIndex = $existingStyle->getIndex();
+ } else {
+ // we don't have such a cell Xf, need to add
+ $workbook->addCellXf($pCellStyle);
+ $xfIndex = $pCellStyle->getIndex();
+ }
+
+ // Calculate range outer borders
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange . ':' . $pRange);
+
+ // Make sure we can loop upwards on rows and columns
+ if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
+ $tmp = $rangeStart;
+ $rangeStart = $rangeEnd;
+ $rangeEnd = $tmp;
+ }
+
+ // Loop through cells and apply styles
+ for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
+ for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
+ $this->getCell(Coordinate::stringFromColumnIndex($col) . $row)->setXfIndex($xfIndex);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Duplicate conditional style to a range of cells.
+ *
+ * Please note that this will overwrite existing cell styles for cells in range!
+ *
+ * @param Conditional[] $pCellStyle Cell style to duplicate
+ * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
+ *
+ * @return $this
+ */
+ public function duplicateConditionalStyle(array $pCellStyle, $pRange = '')
+ {
+ foreach ($pCellStyle as $cellStyle) {
+ if (!($cellStyle instanceof Conditional)) {
+ throw new Exception('Style is not a conditional style');
+ }
+ }
+
+ // Calculate range outer borders
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange . ':' . $pRange);
+
+ // Make sure we can loop upwards on rows and columns
+ if ($rangeStart[0] > $rangeEnd[0] && $rangeStart[1] > $rangeEnd[1]) {
+ $tmp = $rangeStart;
+ $rangeStart = $rangeEnd;
+ $rangeEnd = $tmp;
+ }
+
+ // Loop through cells and apply styles
+ for ($col = $rangeStart[0]; $col <= $rangeEnd[0]; ++$col) {
+ for ($row = $rangeStart[1]; $row <= $rangeEnd[1]; ++$row) {
+ $this->setConditionalStyles(Coordinate::stringFromColumnIndex($col) . $row, $pCellStyle);
+ }
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set break on a cell.
+ *
+ * @param string $pCoordinate Cell coordinate (e.g. A1)
+ * @param int $pBreak Break type (type of Worksheet::BREAK_*)
+ *
+ * @return $this
+ */
+ public function setBreak($pCoordinate, $pBreak)
+ {
+ // Uppercase coordinate
+ $pCoordinate = strtoupper($pCoordinate);
+
+ if ($pCoordinate != '') {
+ if ($pBreak == self::BREAK_NONE) {
+ if (isset($this->breaks[$pCoordinate])) {
+ unset($this->breaks[$pCoordinate]);
+ }
+ } else {
+ $this->breaks[$pCoordinate] = $pBreak;
+ }
+ } else {
+ throw new Exception('No cell coordinate specified.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set break on a cell by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ * @param int $break Break type (type of Worksheet::BREAK_*)
+ *
+ * @return $this
+ */
+ public function setBreakByColumnAndRow($columnIndex, $row, $break)
+ {
+ return $this->setBreak(Coordinate::stringFromColumnIndex($columnIndex) . $row, $break);
+ }
+
+ /**
+ * Get breaks.
+ *
+ * @return array[]
+ */
+ public function getBreaks()
+ {
+ return $this->breaks;
+ }
+
+ /**
+ * Set merge on a cell range.
+ *
+ * @param string $pRange Cell range (e.g. A1:E1)
+ *
+ * @return $this
+ */
+ public function mergeCells($pRange)
+ {
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ if (strpos($pRange, ':') !== false) {
+ $this->mergeCells[$pRange] = $pRange;
+
+ // make sure cells are created
+
+ // get the cells in the range
+ $aReferences = Coordinate::extractAllCellReferencesInRange($pRange);
+
+ // create upper left cell if it does not already exist
+ $upperLeft = $aReferences[0];
+ if (!$this->cellExists($upperLeft)) {
+ $this->getCell($upperLeft)->setValueExplicit(null, DataType::TYPE_NULL);
+ }
+
+ // Blank out the rest of the cells in the range (if they exist)
+ $count = count($aReferences);
+ for ($i = 1; $i < $count; ++$i) {
+ if ($this->cellExists($aReferences[$i])) {
+ $this->getCell($aReferences[$i])->setValueExplicit(null, DataType::TYPE_NULL);
+ }
+ }
+ } else {
+ throw new Exception('Merge must be set on a range of cells.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set merge on a cell range by using numeric cell coordinates.
+ *
+ * @param int $columnIndex1 Numeric column coordinate of the first cell
+ * @param int $row1 Numeric row coordinate of the first cell
+ * @param int $columnIndex2 Numeric column coordinate of the last cell
+ * @param int $row2 Numeric row coordinate of the last cell
+ *
+ * @return $this
+ */
+ public function mergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
+ {
+ $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
+
+ return $this->mergeCells($cellRange);
+ }
+
+ /**
+ * Remove merge on a cell range.
+ *
+ * @param string $pRange Cell range (e.g. A1:E1)
+ *
+ * @return $this
+ */
+ public function unmergeCells($pRange)
+ {
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ if (strpos($pRange, ':') !== false) {
+ if (isset($this->mergeCells[$pRange])) {
+ unset($this->mergeCells[$pRange]);
+ } else {
+ throw new Exception('Cell range ' . $pRange . ' not known as merged.');
+ }
+ } else {
+ throw new Exception('Merge can only be removed from a range of cells.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Remove merge on a cell range by using numeric cell coordinates.
+ *
+ * @param int $columnIndex1 Numeric column coordinate of the first cell
+ * @param int $row1 Numeric row coordinate of the first cell
+ * @param int $columnIndex2 Numeric column coordinate of the last cell
+ * @param int $row2 Numeric row coordinate of the last cell
+ *
+ * @return $this
+ */
+ public function unmergeCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
+ {
+ $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
+
+ return $this->unmergeCells($cellRange);
+ }
+
+ /**
+ * Get merge cells array.
+ *
+ * @return string[]
+ */
+ public function getMergeCells()
+ {
+ return $this->mergeCells;
+ }
+
+ /**
+ * Set merge cells array for the entire sheet. Use instead mergeCells() to merge
+ * a single cell range.
+ *
+ * @param string[] $pValue
+ *
+ * @return $this
+ */
+ public function setMergeCells(array $pValue)
+ {
+ $this->mergeCells = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Set protection on a cell range.
+ *
+ * @param string $pRange Cell (e.g. A1) or cell range (e.g. A1:E1)
+ * @param string $pPassword Password to unlock the protection
+ * @param bool $pAlreadyHashed If the password has already been hashed, set this to true
+ *
+ * @return $this
+ */
+ public function protectCells($pRange, $pPassword, $pAlreadyHashed = false)
+ {
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ if (!$pAlreadyHashed) {
+ $pPassword = Shared\PasswordHasher::hashPassword($pPassword);
+ }
+ $this->protectedCells[$pRange] = $pPassword;
+
+ return $this;
+ }
+
+ /**
+ * Set protection on a cell range by using numeric cell coordinates.
+ *
+ * @param int $columnIndex1 Numeric column coordinate of the first cell
+ * @param int $row1 Numeric row coordinate of the first cell
+ * @param int $columnIndex2 Numeric column coordinate of the last cell
+ * @param int $row2 Numeric row coordinate of the last cell
+ * @param string $password Password to unlock the protection
+ * @param bool $alreadyHashed If the password has already been hashed, set this to true
+ *
+ * @return $this
+ */
+ public function protectCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2, $password, $alreadyHashed = false)
+ {
+ $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
+
+ return $this->protectCells($cellRange, $password, $alreadyHashed);
+ }
+
+ /**
+ * Remove protection on a cell range.
+ *
+ * @param string $pRange Cell (e.g. A1) or cell range (e.g. A1:E1)
+ *
+ * @return $this
+ */
+ public function unprotectCells($pRange)
+ {
+ // Uppercase coordinate
+ $pRange = strtoupper($pRange);
+
+ if (isset($this->protectedCells[$pRange])) {
+ unset($this->protectedCells[$pRange]);
+ } else {
+ throw new Exception('Cell range ' . $pRange . ' not known as protected.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Remove protection on a cell range by using numeric cell coordinates.
+ *
+ * @param int $columnIndex1 Numeric column coordinate of the first cell
+ * @param int $row1 Numeric row coordinate of the first cell
+ * @param int $columnIndex2 Numeric column coordinate of the last cell
+ * @param int $row2 Numeric row coordinate of the last cell
+ *
+ * @return $this
+ */
+ public function unprotectCellsByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
+ {
+ $cellRange = Coordinate::stringFromColumnIndex($columnIndex1) . $row1 . ':' . Coordinate::stringFromColumnIndex($columnIndex2) . $row2;
+
+ return $this->unprotectCells($cellRange);
+ }
+
+ /**
+ * Get protected cells.
+ *
+ * @return array[]
+ */
+ public function getProtectedCells()
+ {
+ return $this->protectedCells;
+ }
+
+ /**
+ * Get Autofilter.
+ *
+ * @return AutoFilter
+ */
+ public function getAutoFilter()
+ {
+ return $this->autoFilter;
+ }
+
+ /**
+ * Set AutoFilter.
+ *
+ * @param AutoFilter|string $pValue
+ * A simple string containing a Cell range like 'A1:E10' is permitted for backward compatibility
+ *
+ * @return $this
+ */
+ public function setAutoFilter($pValue)
+ {
+ if (is_string($pValue)) {
+ $this->autoFilter->setRange($pValue);
+ } elseif (is_object($pValue) && ($pValue instanceof AutoFilter)) {
+ $this->autoFilter = $pValue;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Set Autofilter Range by using numeric cell coordinates.
+ *
+ * @param int $columnIndex1 Numeric column coordinate of the first cell
+ * @param int $row1 Numeric row coordinate of the first cell
+ * @param int $columnIndex2 Numeric column coordinate of the second cell
+ * @param int $row2 Numeric row coordinate of the second cell
+ *
+ * @return $this
+ */
+ public function setAutoFilterByColumnAndRow($columnIndex1, $row1, $columnIndex2, $row2)
+ {
+ return $this->setAutoFilter(
+ Coordinate::stringFromColumnIndex($columnIndex1) . $row1
+ . ':' .
+ Coordinate::stringFromColumnIndex($columnIndex2) . $row2
+ );
+ }
+
+ /**
+ * Remove autofilter.
+ *
+ * @return $this
+ */
+ public function removeAutoFilter()
+ {
+ $this->autoFilter->setRange(null);
+
+ return $this;
+ }
+
+ /**
+ * Get Freeze Pane.
+ *
+ * @return string
+ */
+ public function getFreezePane()
+ {
+ return $this->freezePane;
+ }
+
+ /**
+ * Freeze Pane.
+ *
+ * Examples:
+ *
+ * - A2 will freeze the rows above cell A2 (i.e row 1)
+ * - B1 will freeze the columns to the left of cell B1 (i.e column A)
+ * - B2 will freeze the rows above and to the left of cell B2 (i.e row 1 and column A)
+ *
+ * @param null|string $cell Position of the split
+ * @param null|string $topLeftCell default position of the right bottom pane
+ *
+ * @return $this
+ */
+ public function freezePane($cell, $topLeftCell = null)
+ {
+ if (is_string($cell) && Coordinate::coordinateIsRange($cell)) {
+ throw new Exception('Freeze pane can not be set on a range of cells.');
+ }
+
+ if ($cell !== null && $topLeftCell === null) {
+ $coordinate = Coordinate::coordinateFromString($cell);
+ $topLeftCell = $coordinate[0] . $coordinate[1];
+ }
+
+ $this->freezePane = $cell;
+ $this->topLeftCell = $topLeftCell;
+
+ return $this;
+ }
+
+ /**
+ * Freeze Pane by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ *
+ * @return $this
+ */
+ public function freezePaneByColumnAndRow($columnIndex, $row)
+ {
+ return $this->freezePane(Coordinate::stringFromColumnIndex($columnIndex) . $row);
+ }
+
+ /**
+ * Unfreeze Pane.
+ *
+ * @return $this
+ */
+ public function unfreezePane()
+ {
+ return $this->freezePane(null);
+ }
+
+ /**
+ * Get the default position of the right bottom pane.
+ *
+ * @return int
+ */
+ public function getTopLeftCell()
+ {
+ return $this->topLeftCell;
+ }
+
+ /**
+ * Insert a new row, updating all possible related data.
+ *
+ * @param int $pBefore Insert before this one
+ * @param int $pNumRows Number of rows to insert
+ *
+ * @return $this
+ */
+ public function insertNewRowBefore($pBefore, $pNumRows = 1)
+ {
+ if ($pBefore >= 1) {
+ $objReferenceHelper = ReferenceHelper::getInstance();
+ $objReferenceHelper->insertNewBefore('A' . $pBefore, 0, $pNumRows, $this);
+ } else {
+ throw new Exception('Rows can only be inserted before at least row 1.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Insert a new column, updating all possible related data.
+ *
+ * @param string $pBefore Insert before this one, eg: 'A'
+ * @param int $pNumCols Number of columns to insert
+ *
+ * @return $this
+ */
+ public function insertNewColumnBefore($pBefore, $pNumCols = 1)
+ {
+ if (!is_numeric($pBefore)) {
+ $objReferenceHelper = ReferenceHelper::getInstance();
+ $objReferenceHelper->insertNewBefore($pBefore . '1', $pNumCols, 0, $this);
+ } else {
+ throw new Exception('Column references should not be numeric.');
+ }
+
+ return $this;
+ }
+
+ /**
+ * Insert a new column, updating all possible related data.
+ *
+ * @param int $beforeColumnIndex Insert before this one (numeric column coordinate of the cell)
+ * @param int $pNumCols Number of columns to insert
+ *
+ * @return $this
+ */
+ public function insertNewColumnBeforeByIndex($beforeColumnIndex, $pNumCols = 1)
+ {
+ if ($beforeColumnIndex >= 1) {
+ return $this->insertNewColumnBefore(Coordinate::stringFromColumnIndex($beforeColumnIndex), $pNumCols);
+ }
+
+ throw new Exception('Columns can only be inserted before at least column A (1).');
+ }
+
+ /**
+ * Delete a row, updating all possible related data.
+ *
+ * @param int $pRow Remove starting with this one
+ * @param int $pNumRows Number of rows to remove
+ *
+ * @return $this
+ */
+ public function removeRow($pRow, $pNumRows = 1)
+ {
+ if ($pRow < 1) {
+ throw new Exception('Rows to be deleted should at least start from row 1.');
+ }
+
+ $highestRow = $this->getHighestDataRow();
+ $removedRowsCounter = 0;
+
+ for ($r = 0; $r < $pNumRows; ++$r) {
+ if ($pRow + $r <= $highestRow) {
+ $this->getCellCollection()->removeRow($pRow + $r);
+ ++$removedRowsCounter;
+ }
+ }
+
+ $objReferenceHelper = ReferenceHelper::getInstance();
+ $objReferenceHelper->insertNewBefore('A' . ($pRow + $pNumRows), 0, -$pNumRows, $this);
+ for ($r = 0; $r < $removedRowsCounter; ++$r) {
+ $this->getCellCollection()->removeRow($highestRow);
+ --$highestRow;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Remove a column, updating all possible related data.
+ *
+ * @param string $pColumn Remove starting with this one, eg: 'A'
+ * @param int $pNumCols Number of columns to remove
+ *
+ * @return $this
+ */
+ public function removeColumn($pColumn, $pNumCols = 1)
+ {
+ if (is_numeric($pColumn)) {
+ throw new Exception('Column references should not be numeric.');
+ }
+
+ $highestColumn = $this->getHighestDataColumn();
+ $highestColumnIndex = Coordinate::columnIndexFromString($highestColumn);
+ $pColumnIndex = Coordinate::columnIndexFromString($pColumn);
+
+ if ($pColumnIndex > $highestColumnIndex) {
+ return $this;
+ }
+
+ $pColumn = Coordinate::stringFromColumnIndex($pColumnIndex + $pNumCols);
+ $objReferenceHelper = ReferenceHelper::getInstance();
+ $objReferenceHelper->insertNewBefore($pColumn . '1', -$pNumCols, 0, $this);
+
+ $maxPossibleColumnsToBeRemoved = $highestColumnIndex - $pColumnIndex + 1;
+
+ for ($c = 0, $n = min($maxPossibleColumnsToBeRemoved, $pNumCols); $c < $n; ++$c) {
+ $this->getCellCollection()->removeColumn($highestColumn);
+ $highestColumn = Coordinate::stringFromColumnIndex(Coordinate::columnIndexFromString($highestColumn) - 1);
+ }
+
+ $this->garbageCollect();
+
+ return $this;
+ }
+
+ /**
+ * Remove a column, updating all possible related data.
+ *
+ * @param int $columnIndex Remove starting with this one (numeric column coordinate of the cell)
+ * @param int $numColumns Number of columns to remove
+ *
+ * @return $this
+ */
+ public function removeColumnByIndex($columnIndex, $numColumns = 1)
+ {
+ if ($columnIndex >= 1) {
+ return $this->removeColumn(Coordinate::stringFromColumnIndex($columnIndex), $numColumns);
+ }
+
+ throw new Exception('Columns to be deleted should at least start from column A (1)');
+ }
+
+ /**
+ * Show gridlines?
+ *
+ * @return bool
+ */
+ public function getShowGridlines()
+ {
+ return $this->showGridlines;
+ }
+
+ /**
+ * Set show gridlines.
+ *
+ * @param bool $pValue Show gridlines (true/false)
+ *
+ * @return $this
+ */
+ public function setShowGridlines($pValue)
+ {
+ $this->showGridlines = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Print gridlines?
+ *
+ * @return bool
+ */
+ public function getPrintGridlines()
+ {
+ return $this->printGridlines;
+ }
+
+ /**
+ * Set print gridlines.
+ *
+ * @param bool $pValue Print gridlines (true/false)
+ *
+ * @return $this
+ */
+ public function setPrintGridlines($pValue)
+ {
+ $this->printGridlines = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Show row and column headers?
+ *
+ * @return bool
+ */
+ public function getShowRowColHeaders()
+ {
+ return $this->showRowColHeaders;
+ }
+
+ /**
+ * Set show row and column headers.
+ *
+ * @param bool $pValue Show row and column headers (true/false)
+ *
+ * @return $this
+ */
+ public function setShowRowColHeaders($pValue)
+ {
+ $this->showRowColHeaders = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Show summary below? (Row/Column outlining).
+ *
+ * @return bool
+ */
+ public function getShowSummaryBelow()
+ {
+ return $this->showSummaryBelow;
+ }
+
+ /**
+ * Set show summary below.
+ *
+ * @param bool $pValue Show summary below (true/false)
+ *
+ * @return $this
+ */
+ public function setShowSummaryBelow($pValue)
+ {
+ $this->showSummaryBelow = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Show summary right? (Row/Column outlining).
+ *
+ * @return bool
+ */
+ public function getShowSummaryRight()
+ {
+ return $this->showSummaryRight;
+ }
+
+ /**
+ * Set show summary right.
+ *
+ * @param bool $pValue Show summary right (true/false)
+ *
+ * @return $this
+ */
+ public function setShowSummaryRight($pValue)
+ {
+ $this->showSummaryRight = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get comments.
+ *
+ * @return Comment[]
+ */
+ public function getComments()
+ {
+ return $this->comments;
+ }
+
+ /**
+ * Set comments array for the entire sheet.
+ *
+ * @param Comment[] $pValue
+ *
+ * @return $this
+ */
+ public function setComments(array $pValue)
+ {
+ $this->comments = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get comment for cell.
+ *
+ * @param string $pCellCoordinate Cell coordinate to get comment for, eg: 'A1'
+ *
+ * @return Comment
+ */
+ public function getComment($pCellCoordinate)
+ {
+ // Uppercase coordinate
+ $pCellCoordinate = strtoupper($pCellCoordinate);
+
+ if (Coordinate::coordinateIsRange($pCellCoordinate)) {
+ throw new Exception('Cell coordinate string can not be a range of cells.');
+ } elseif (strpos($pCellCoordinate, '$') !== false) {
+ throw new Exception('Cell coordinate string must not be absolute.');
+ } elseif ($pCellCoordinate == '') {
+ throw new Exception('Cell coordinate can not be zero-length string.');
+ }
+
+ // Check if we already have a comment for this cell.
+ if (isset($this->comments[$pCellCoordinate])) {
+ return $this->comments[$pCellCoordinate];
+ }
+
+ // If not, create a new comment.
+ $newComment = new Comment();
+ $this->comments[$pCellCoordinate] = $newComment;
+
+ return $newComment;
+ }
+
+ /**
+ * Get comment for cell by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ *
+ * @return Comment
+ */
+ public function getCommentByColumnAndRow($columnIndex, $row)
+ {
+ return $this->getComment(Coordinate::stringFromColumnIndex($columnIndex) . $row);
+ }
+
+ /**
+ * Get active cell.
+ *
+ * @return string Example: 'A1'
+ */
+ public function getActiveCell()
+ {
+ return $this->activeCell;
+ }
+
+ /**
+ * Get selected cells.
+ *
+ * @return string
+ */
+ public function getSelectedCells()
+ {
+ return $this->selectedCells;
+ }
+
+ /**
+ * Selected cell.
+ *
+ * @param string $pCoordinate Cell (i.e. A1)
+ *
+ * @return $this
+ */
+ public function setSelectedCell($pCoordinate)
+ {
+ return $this->setSelectedCells($pCoordinate);
+ }
+
+ /**
+ * Select a range of cells.
+ *
+ * @param string $pCoordinate Cell range, examples: 'A1', 'B2:G5', 'A:C', '3:6'
+ *
+ * @return $this
+ */
+ public function setSelectedCells($pCoordinate)
+ {
+ // Uppercase coordinate
+ $pCoordinate = strtoupper($pCoordinate);
+
+ // Convert 'A' to 'A:A'
+ $pCoordinate = preg_replace('/^([A-Z]+)$/', '${1}:${1}', $pCoordinate);
+
+ // Convert '1' to '1:1'
+ $pCoordinate = preg_replace('/^(\d+)$/', '${1}:${1}', $pCoordinate);
+
+ // Convert 'A:C' to 'A1:C1048576'
+ $pCoordinate = preg_replace('/^([A-Z]+):([A-Z]+)$/', '${1}1:${2}1048576', $pCoordinate);
+
+ // Convert '1:3' to 'A1:XFD3'
+ $pCoordinate = preg_replace('/^(\d+):(\d+)$/', 'A${1}:XFD${2}', $pCoordinate);
+
+ if (Coordinate::coordinateIsRange($pCoordinate)) {
+ [$first] = Coordinate::splitRange($pCoordinate);
+ $this->activeCell = $first[0];
+ } else {
+ $this->activeCell = $pCoordinate;
+ }
+ $this->selectedCells = $pCoordinate;
+
+ return $this;
+ }
+
+ /**
+ * Selected cell by using numeric cell coordinates.
+ *
+ * @param int $columnIndex Numeric column coordinate of the cell
+ * @param int $row Numeric row coordinate of the cell
+ *
+ * @return $this
+ */
+ public function setSelectedCellByColumnAndRow($columnIndex, $row)
+ {
+ return $this->setSelectedCells(Coordinate::stringFromColumnIndex($columnIndex) . $row);
+ }
+
+ /**
+ * Get right-to-left.
+ *
+ * @return bool
+ */
+ public function getRightToLeft()
+ {
+ return $this->rightToLeft;
+ }
+
+ /**
+ * Set right-to-left.
+ *
+ * @param bool $value Right-to-left true/false
+ *
+ * @return $this
+ */
+ public function setRightToLeft($value)
+ {
+ $this->rightToLeft = $value;
+
+ return $this;
+ }
+
+ /**
+ * Fill worksheet from values in array.
+ *
+ * @param array $source Source array
+ * @param mixed $nullValue Value in source array that stands for blank cell
+ * @param string $startCell Insert array starting from this cell address as the top left coordinate
+ * @param bool $strictNullComparison Apply strict comparison when testing for null values in the array
+ *
+ * @return $this
+ */
+ public function fromArray(array $source, $nullValue = null, $startCell = 'A1', $strictNullComparison = false)
+ {
+ // Convert a 1-D array to 2-D (for ease of looping)
+ if (!is_array(end($source))) {
+ $source = [$source];
+ }
+
+ // start coordinate
+ [$startColumn, $startRow] = Coordinate::coordinateFromString($startCell);
+
+ // Loop through $source
+ foreach ($source as $rowData) {
+ $currentColumn = $startColumn;
+ foreach ($rowData as $cellValue) {
+ if ($strictNullComparison) {
+ if ($cellValue !== $nullValue) {
+ // Set cell value
+ $this->getCell($currentColumn . $startRow)->setValue($cellValue);
+ }
+ } else {
+ if ($cellValue != $nullValue) {
+ // Set cell value
+ $this->getCell($currentColumn . $startRow)->setValue($cellValue);
+ }
+ }
+ ++$currentColumn;
+ }
+ ++$startRow;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Create array from a range of cells.
+ *
+ * @param string $pRange Range of cells (i.e. "A1:B10"), or just one cell (i.e. "A1")
+ * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
+ * @param bool $calculateFormulas Should formulas be calculated?
+ * @param bool $formatData Should formatting be applied to cell values?
+ * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
+ * True - Return rows and columns indexed by their actual row and column IDs
+ *
+ * @return array
+ */
+ public function rangeToArray($pRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
+ {
+ // Returnvalue
+ $returnValue = [];
+ // Identify the range that we need to extract from the worksheet
+ [$rangeStart, $rangeEnd] = Coordinate::rangeBoundaries($pRange);
+ $minCol = Coordinate::stringFromColumnIndex($rangeStart[0]);
+ $minRow = $rangeStart[1];
+ $maxCol = Coordinate::stringFromColumnIndex($rangeEnd[0]);
+ $maxRow = $rangeEnd[1];
+
+ ++$maxCol;
+ // Loop through rows
+ $r = -1;
+ for ($row = $minRow; $row <= $maxRow; ++$row) {
+ $rRef = $returnCellRef ? $row : ++$r;
+ $c = -1;
+ // Loop through columns in the current row
+ for ($col = $minCol; $col != $maxCol; ++$col) {
+ $cRef = $returnCellRef ? $col : ++$c;
+ // Using getCell() will create a new cell if it doesn't already exist. We don't want that to happen
+ // so we test and retrieve directly against cellCollection
+ if ($this->cellCollection->has($col . $row)) {
+ // Cell exists
+ $cell = $this->cellCollection->get($col . $row);
+ if ($cell->getValue() !== null) {
+ if ($cell->getValue() instanceof RichText) {
+ $returnValue[$rRef][$cRef] = $cell->getValue()->getPlainText();
+ } else {
+ if ($calculateFormulas) {
+ $returnValue[$rRef][$cRef] = $cell->getCalculatedValue();
+ } else {
+ $returnValue[$rRef][$cRef] = $cell->getValue();
+ }
+ }
+
+ if ($formatData) {
+ $style = $this->parent->getCellXfByIndex($cell->getXfIndex());
+ $returnValue[$rRef][$cRef] = NumberFormat::toFormattedString(
+ $returnValue[$rRef][$cRef],
+ ($style && $style->getNumberFormat()) ? $style->getNumberFormat()->getFormatCode() : NumberFormat::FORMAT_GENERAL
+ );
+ }
+ } else {
+ // Cell holds a NULL
+ $returnValue[$rRef][$cRef] = $nullValue;
+ }
+ } else {
+ // Cell doesn't exist
+ $returnValue[$rRef][$cRef] = $nullValue;
+ }
+ }
+ }
+
+ // Return
+ return $returnValue;
+ }
+
+ /**
+ * Create array from a range of cells.
+ *
+ * @param string $pNamedRange Name of the Named Range
+ * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
+ * @param bool $calculateFormulas Should formulas be calculated?
+ * @param bool $formatData Should formatting be applied to cell values?
+ * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
+ * True - Return rows and columns indexed by their actual row and column IDs
+ *
+ * @return array
+ */
+ public function namedRangeToArray($pNamedRange, $nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
+ {
+ $namedRange = DefinedName::resolveName($pNamedRange, $this);
+ if ($namedRange !== null) {
+ $pWorkSheet = $namedRange->getWorksheet();
+ $pCellRange = $namedRange->getValue();
+
+ return $pWorkSheet->rangeToArray($pCellRange, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
+ }
+
+ throw new Exception('Named Range ' . $pNamedRange . ' does not exist.');
+ }
+
+ /**
+ * Create array from worksheet.
+ *
+ * @param mixed $nullValue Value returned in the array entry if a cell doesn't exist
+ * @param bool $calculateFormulas Should formulas be calculated?
+ * @param bool $formatData Should formatting be applied to cell values?
+ * @param bool $returnCellRef False - Return a simple array of rows and columns indexed by number counting from zero
+ * True - Return rows and columns indexed by their actual row and column IDs
+ *
+ * @return array
+ */
+ public function toArray($nullValue = null, $calculateFormulas = true, $formatData = true, $returnCellRef = false)
+ {
+ // Garbage collect...
+ $this->garbageCollect();
+
+ // Identify the range that we need to extract from the worksheet
+ $maxCol = $this->getHighestColumn();
+ $maxRow = $this->getHighestRow();
+
+ // Return
+ return $this->rangeToArray('A1:' . $maxCol . $maxRow, $nullValue, $calculateFormulas, $formatData, $returnCellRef);
+ }
+
+ /**
+ * Get row iterator.
+ *
+ * @param int $startRow The row number at which to start iterating
+ * @param int $endRow The row number at which to stop iterating
+ *
+ * @return RowIterator
+ */
+ public function getRowIterator($startRow = 1, $endRow = null)
+ {
+ return new RowIterator($this, $startRow, $endRow);
+ }
+
+ /**
+ * Get column iterator.
+ *
+ * @param string $startColumn The column address at which to start iterating
+ * @param string $endColumn The column address at which to stop iterating
+ *
+ * @return ColumnIterator
+ */
+ public function getColumnIterator($startColumn = 'A', $endColumn = null)
+ {
+ return new ColumnIterator($this, $startColumn, $endColumn);
+ }
+
+ /**
+ * Run PhpSpreadsheet garbage collector.
+ *
+ * @return $this
+ */
+ public function garbageCollect()
+ {
+ // Flush cache
+ $this->cellCollection->get('A1');
+
+ // Lookup highest column and highest row if cells are cleaned
+ $colRow = $this->cellCollection->getHighestRowAndColumn();
+ $highestRow = $colRow['row'];
+ $highestColumn = Coordinate::columnIndexFromString($colRow['column']);
+
+ // Loop through column dimensions
+ foreach ($this->columnDimensions as $dimension) {
+ $highestColumn = max($highestColumn, Coordinate::columnIndexFromString($dimension->getColumnIndex()));
+ }
+
+ // Loop through row dimensions
+ foreach ($this->rowDimensions as $dimension) {
+ $highestRow = max($highestRow, $dimension->getRowIndex());
+ }
+
+ // Cache values
+ if ($highestColumn < 1) {
+ $this->cachedHighestColumn = 'A';
+ } else {
+ $this->cachedHighestColumn = Coordinate::stringFromColumnIndex($highestColumn);
+ }
+ $this->cachedHighestRow = $highestRow;
+
+ // Return
+ return $this;
+ }
+
+ /**
+ * Get hash code.
+ *
+ * @return string Hash code
+ */
+ public function getHashCode()
+ {
+ if ($this->dirty) {
+ $this->hash = md5($this->title . $this->autoFilter . ($this->protection->isProtectionEnabled() ? 't' : 'f') . __CLASS__);
+ $this->dirty = false;
+ }
+
+ return $this->hash;
+ }
+
+ /**
+ * Extract worksheet title from range.
+ *
+ * Example: extractSheetTitle("testSheet!A1") ==> 'A1'
+ * Example: extractSheetTitle("'testSheet 1'!A1", true) ==> ['testSheet 1', 'A1'];
+ *
+ * @param string $pRange Range to extract title from
+ * @param bool $returnRange Return range? (see example)
+ *
+ * @return mixed
+ */
+ public static function extractSheetTitle($pRange, $returnRange = false)
+ {
+ // Sheet title included?
+ if (($sep = strrpos($pRange, '!')) === false) {
+ return $returnRange ? ['', $pRange] : '';
+ }
+
+ if ($returnRange) {
+ return [substr($pRange, 0, $sep), substr($pRange, $sep + 1)];
+ }
+
+ return substr($pRange, $sep + 1);
+ }
+
+ /**
+ * Get hyperlink.
+ *
+ * @param string $pCellCoordinate Cell coordinate to get hyperlink for, eg: 'A1'
+ *
+ * @return Hyperlink
+ */
+ public function getHyperlink($pCellCoordinate)
+ {
+ // return hyperlink if we already have one
+ if (isset($this->hyperlinkCollection[$pCellCoordinate])) {
+ return $this->hyperlinkCollection[$pCellCoordinate];
+ }
+
+ // else create hyperlink
+ $this->hyperlinkCollection[$pCellCoordinate] = new Hyperlink();
+
+ return $this->hyperlinkCollection[$pCellCoordinate];
+ }
+
+ /**
+ * Set hyperlink.
+ *
+ * @param string $pCellCoordinate Cell coordinate to insert hyperlink, eg: 'A1'
+ *
+ * @return $this
+ */
+ public function setHyperlink($pCellCoordinate, ?Hyperlink $pHyperlink = null)
+ {
+ if ($pHyperlink === null) {
+ unset($this->hyperlinkCollection[$pCellCoordinate]);
+ } else {
+ $this->hyperlinkCollection[$pCellCoordinate] = $pHyperlink;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Hyperlink at a specific coordinate exists?
+ *
+ * @param string $pCoordinate eg: 'A1'
+ *
+ * @return bool
+ */
+ public function hyperlinkExists($pCoordinate)
+ {
+ return isset($this->hyperlinkCollection[$pCoordinate]);
+ }
+
+ /**
+ * Get collection of hyperlinks.
+ *
+ * @return Hyperlink[]
+ */
+ public function getHyperlinkCollection()
+ {
+ return $this->hyperlinkCollection;
+ }
+
+ /**
+ * Get data validation.
+ *
+ * @param string $pCellCoordinate Cell coordinate to get data validation for, eg: 'A1'
+ *
+ * @return DataValidation
+ */
+ public function getDataValidation($pCellCoordinate)
+ {
+ // return data validation if we already have one
+ if (isset($this->dataValidationCollection[$pCellCoordinate])) {
+ return $this->dataValidationCollection[$pCellCoordinate];
+ }
+
+ // else create data validation
+ $this->dataValidationCollection[$pCellCoordinate] = new DataValidation();
+
+ return $this->dataValidationCollection[$pCellCoordinate];
+ }
+
+ /**
+ * Set data validation.
+ *
+ * @param string $pCellCoordinate Cell coordinate to insert data validation, eg: 'A1'
+ *
+ * @return $this
+ */
+ public function setDataValidation($pCellCoordinate, ?DataValidation $pDataValidation = null)
+ {
+ if ($pDataValidation === null) {
+ unset($this->dataValidationCollection[$pCellCoordinate]);
+ } else {
+ $this->dataValidationCollection[$pCellCoordinate] = $pDataValidation;
+ }
+
+ return $this;
+ }
+
+ /**
+ * Data validation at a specific coordinate exists?
+ *
+ * @param string $pCoordinate eg: 'A1'
+ *
+ * @return bool
+ */
+ public function dataValidationExists($pCoordinate)
+ {
+ return isset($this->dataValidationCollection[$pCoordinate]);
+ }
+
+ /**
+ * Get collection of data validations.
+ *
+ * @return DataValidation[]
+ */
+ public function getDataValidationCollection()
+ {
+ return $this->dataValidationCollection;
+ }
+
+ /**
+ * Accepts a range, returning it as a range that falls within the current highest row and column of the worksheet.
+ *
+ * @param string $range
+ *
+ * @return string Adjusted range value
+ */
+ public function shrinkRangeToFit($range)
+ {
+ $maxCol = $this->getHighestColumn();
+ $maxRow = $this->getHighestRow();
+ $maxCol = Coordinate::columnIndexFromString($maxCol);
+
+ $rangeBlocks = explode(' ', $range);
+ foreach ($rangeBlocks as &$rangeSet) {
+ $rangeBoundaries = Coordinate::getRangeBoundaries($rangeSet);
+
+ if (Coordinate::columnIndexFromString($rangeBoundaries[0][0]) > $maxCol) {
+ $rangeBoundaries[0][0] = Coordinate::stringFromColumnIndex($maxCol);
+ }
+ if ($rangeBoundaries[0][1] > $maxRow) {
+ $rangeBoundaries[0][1] = $maxRow;
+ }
+ if (Coordinate::columnIndexFromString($rangeBoundaries[1][0]) > $maxCol) {
+ $rangeBoundaries[1][0] = Coordinate::stringFromColumnIndex($maxCol);
+ }
+ if ($rangeBoundaries[1][1] > $maxRow) {
+ $rangeBoundaries[1][1] = $maxRow;
+ }
+ $rangeSet = $rangeBoundaries[0][0] . $rangeBoundaries[0][1] . ':' . $rangeBoundaries[1][0] . $rangeBoundaries[1][1];
+ }
+ unset($rangeSet);
+
+ return implode(' ', $rangeBlocks);
+ }
+
+ /**
+ * Get tab color.
+ *
+ * @return Color
+ */
+ public function getTabColor()
+ {
+ if ($this->tabColor === null) {
+ $this->tabColor = new Color();
+ }
+
+ return $this->tabColor;
+ }
+
+ /**
+ * Reset tab color.
+ *
+ * @return $this
+ */
+ public function resetTabColor()
+ {
+ $this->tabColor = null;
+ $this->tabColor = null;
+
+ return $this;
+ }
+
+ /**
+ * Tab color set?
+ *
+ * @return bool
+ */
+ public function isTabColorSet()
+ {
+ return $this->tabColor !== null;
+ }
+
+ /**
+ * Copy worksheet (!= clone!).
+ *
+ * @return static
+ */
+ public function copy()
+ {
+ return clone $this;
+ }
+
+ /**
+ * Implement PHP __clone to create a deep clone, not just a shallow copy.
+ */
+ public function __clone()
+ {
+ foreach ($this as $key => $val) {
+ if ($key == 'parent') {
+ continue;
+ }
+
+ if (is_object($val) || (is_array($val))) {
+ if ($key == 'cellCollection') {
+ $newCollection = $this->cellCollection->cloneCellCollection($this);
+ $this->cellCollection = $newCollection;
+ } elseif ($key == 'drawingCollection') {
+ $currentCollection = $this->drawingCollection;
+ $this->drawingCollection = new ArrayObject();
+ foreach ($currentCollection as $item) {
+ if (is_object($item)) {
+ $newDrawing = clone $item;
+ $newDrawing->setWorksheet($this);
+ }
+ }
+ } elseif (($key == 'autoFilter') && ($this->autoFilter instanceof AutoFilter)) {
+ $newAutoFilter = clone $this->autoFilter;
+ $this->autoFilter = $newAutoFilter;
+ $this->autoFilter->setParent($this);
+ } else {
+ $this->{$key} = unserialize(serialize($val));
+ }
+ }
+ }
+ }
+
+ /**
+ * Define the code name of the sheet.
+ *
+ * @param string $pValue Same rule as Title minus space not allowed (but, like Excel, change
+ * silently space to underscore)
+ * @param bool $validate False to skip validation of new title. WARNING: This should only be set
+ * at parse time (by Readers), where titles can be assumed to be valid.
+ *
+ * @return $this
+ */
+ public function setCodeName($pValue, $validate = true)
+ {
+ // Is this a 'rename' or not?
+ if ($this->getCodeName() == $pValue) {
+ return $this;
+ }
+
+ if ($validate) {
+ $pValue = str_replace(' ', '_', $pValue); //Excel does this automatically without flinching, we are doing the same
+
+ // Syntax check
+ // throw an exception if not valid
+ self::checkSheetCodeName($pValue);
+
+ // We use the same code that setTitle to find a valid codeName else not using a space (Excel don't like) but a '_'
+
+ if ($this->getParent()) {
+ // Is there already such sheet name?
+ if ($this->getParent()->sheetCodeNameExists($pValue)) {
+ // Use name, but append with lowest possible integer
+
+ if (Shared\StringHelper::countCharacters($pValue) > 29) {
+ $pValue = Shared\StringHelper::substring($pValue, 0, 29);
+ }
+ $i = 1;
+ while ($this->getParent()->sheetCodeNameExists($pValue . '_' . $i)) {
+ ++$i;
+ if ($i == 10) {
+ if (Shared\StringHelper::countCharacters($pValue) > 28) {
+ $pValue = Shared\StringHelper::substring($pValue, 0, 28);
+ }
+ } elseif ($i == 100) {
+ if (Shared\StringHelper::countCharacters($pValue) > 27) {
+ $pValue = Shared\StringHelper::substring($pValue, 0, 27);
+ }
+ }
+ }
+
+ $pValue .= '_' . $i; // ok, we have a valid name
+ }
+ }
+ }
+
+ $this->codeName = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Return the code name of the sheet.
+ *
+ * @return null|string
+ */
+ public function getCodeName()
+ {
+ return $this->codeName;
+ }
+
+ /**
+ * Sheet has a code name ?
+ *
+ * @return bool
+ */
+ public function hasCodeName()
+ {
+ return $this->codeName !== null;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php
new file mode 100644
index 0000000..2d23a5f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/BaseWriter.php
@@ -0,0 +1,131 @@
+includeCharts;
+ }
+
+ public function setIncludeCharts($pValue)
+ {
+ $this->includeCharts = (bool) $pValue;
+
+ return $this;
+ }
+
+ public function getPreCalculateFormulas()
+ {
+ return $this->preCalculateFormulas;
+ }
+
+ public function setPreCalculateFormulas($pValue)
+ {
+ $this->preCalculateFormulas = (bool) $pValue;
+
+ return $this;
+ }
+
+ public function getUseDiskCaching()
+ {
+ return $this->useDiskCaching;
+ }
+
+ public function setUseDiskCaching($pValue, $pDirectory = null)
+ {
+ $this->useDiskCaching = $pValue;
+
+ if ($pDirectory !== null) {
+ if (is_dir($pDirectory)) {
+ $this->diskCachingDirectory = $pDirectory;
+ } else {
+ throw new Exception("Directory does not exist: $pDirectory");
+ }
+ }
+
+ return $this;
+ }
+
+ public function getDiskCachingDirectory()
+ {
+ return $this->diskCachingDirectory;
+ }
+
+ /**
+ * Open file handle.
+ *
+ * @param resource|string $filename
+ */
+ public function openFileHandle($filename): void
+ {
+ if (is_resource($filename)) {
+ $this->fileHandle = $filename;
+ $this->shouldCloseFile = false;
+
+ return;
+ }
+
+ $fileHandle = $filename ? fopen($filename, 'wb+') : false;
+ if ($fileHandle === false) {
+ throw new Exception('Could not open file "' . $filename . '" for writing.');
+ }
+
+ $this->fileHandle = $fileHandle;
+ $this->shouldCloseFile = true;
+ }
+
+ /**
+ * Close file handle only if we opened it ourselves.
+ */
+ protected function maybeCloseFileHandle(): void
+ {
+ if ($this->shouldCloseFile) {
+ if (!fclose($this->fileHandle)) {
+ throw new Exception('Could not close file after writing.');
+ }
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php
new file mode 100644
index 0000000..b3a6de5
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Csv.php
@@ -0,0 +1,352 @@
+spreadsheet = $spreadsheet;
+ }
+
+ /**
+ * Save PhpSpreadsheet to file.
+ *
+ * @param resource|string $pFilename
+ */
+ public function save($pFilename): void
+ {
+ // Fetch sheet
+ $sheet = $this->spreadsheet->getSheet($this->sheetIndex);
+
+ $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
+ $saveArrayReturnType = Calculation::getArrayReturnType();
+ Calculation::setArrayReturnType(Calculation::RETURN_ARRAY_AS_VALUE);
+
+ // Open file
+ $this->openFileHandle($pFilename);
+
+ if ($this->excelCompatibility) {
+ $this->setUseBOM(true); // Enforce UTF-8 BOM Header
+ $this->setIncludeSeparatorLine(true); // Set separator line
+ $this->setEnclosure('"'); // Set enclosure to "
+ $this->setDelimiter(';'); // Set delimiter to a semi-colon
+ $this->setLineEnding("\r\n");
+ }
+
+ if ($this->useBOM) {
+ // Write the UTF-8 BOM code if required
+ fwrite($this->fileHandle, "\xEF\xBB\xBF");
+ }
+
+ if ($this->includeSeparatorLine) {
+ // Write the separator line if required
+ fwrite($this->fileHandle, 'sep=' . $this->getDelimiter() . $this->lineEnding);
+ }
+
+ // Identify the range that we need to extract from the worksheet
+ $maxCol = $sheet->getHighestDataColumn();
+ $maxRow = $sheet->getHighestDataRow();
+
+ // Write rows to file
+ for ($row = 1; $row <= $maxRow; ++$row) {
+ // Convert the row to an array...
+ $cellsArray = $sheet->rangeToArray('A' . $row . ':' . $maxCol . $row, '', $this->preCalculateFormulas);
+ // ... and write to the file
+ $this->writeLine($this->fileHandle, $cellsArray[0]);
+ }
+
+ $this->maybeCloseFileHandle();
+ Calculation::setArrayReturnType($saveArrayReturnType);
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
+ }
+
+ /**
+ * Get delimiter.
+ *
+ * @return string
+ */
+ public function getDelimiter()
+ {
+ return $this->delimiter;
+ }
+
+ /**
+ * Set delimiter.
+ *
+ * @param string $pValue Delimiter, defaults to ','
+ *
+ * @return $this
+ */
+ public function setDelimiter($pValue)
+ {
+ $this->delimiter = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get enclosure.
+ *
+ * @return string
+ */
+ public function getEnclosure()
+ {
+ return $this->enclosure;
+ }
+
+ /**
+ * Set enclosure.
+ *
+ * @param string $pValue Enclosure, defaults to "
+ *
+ * @return $this
+ */
+ public function setEnclosure($pValue = '"')
+ {
+ $this->enclosure = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get line ending.
+ *
+ * @return string
+ */
+ public function getLineEnding()
+ {
+ return $this->lineEnding;
+ }
+
+ /**
+ * Set line ending.
+ *
+ * @param string $pValue Line ending, defaults to OS line ending (PHP_EOL)
+ *
+ * @return $this
+ */
+ public function setLineEnding($pValue)
+ {
+ $this->lineEnding = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get whether BOM should be used.
+ *
+ * @return bool
+ */
+ public function getUseBOM()
+ {
+ return $this->useBOM;
+ }
+
+ /**
+ * Set whether BOM should be used.
+ *
+ * @param bool $pValue Use UTF-8 byte-order mark? Defaults to false
+ *
+ * @return $this
+ */
+ public function setUseBOM($pValue)
+ {
+ $this->useBOM = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get whether a separator line should be included.
+ *
+ * @return bool
+ */
+ public function getIncludeSeparatorLine()
+ {
+ return $this->includeSeparatorLine;
+ }
+
+ /**
+ * Set whether a separator line should be included as the first line of the file.
+ *
+ * @param bool $pValue Use separator line? Defaults to false
+ *
+ * @return $this
+ */
+ public function setIncludeSeparatorLine($pValue)
+ {
+ $this->includeSeparatorLine = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get whether the file should be saved with full Excel Compatibility.
+ *
+ * @return bool
+ */
+ public function getExcelCompatibility()
+ {
+ return $this->excelCompatibility;
+ }
+
+ /**
+ * Set whether the file should be saved with full Excel Compatibility.
+ *
+ * @param bool $pValue Set the file to be written as a fully Excel compatible csv file
+ * Note that this overrides other settings such as useBOM, enclosure and delimiter
+ *
+ * @return $this
+ */
+ public function setExcelCompatibility($pValue)
+ {
+ $this->excelCompatibility = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get sheet index.
+ *
+ * @return int
+ */
+ public function getSheetIndex()
+ {
+ return $this->sheetIndex;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param int $pValue Sheet index
+ *
+ * @return $this
+ */
+ public function setSheetIndex($pValue)
+ {
+ $this->sheetIndex = $pValue;
+
+ return $this;
+ }
+
+ private $enclosureRequired = true;
+
+ public function setEnclosureRequired(bool $value): self
+ {
+ $this->enclosureRequired = $value;
+
+ return $this;
+ }
+
+ public function getEnclosureRequired(): bool
+ {
+ return $this->enclosureRequired;
+ }
+
+ /**
+ * Write line to CSV file.
+ *
+ * @param resource $pFileHandle PHP filehandle
+ * @param array $pValues Array containing values in a row
+ */
+ private function writeLine($pFileHandle, array $pValues): void
+ {
+ // No leading delimiter
+ $delimiter = '';
+
+ // Build the line
+ $line = '';
+
+ foreach ($pValues as $element) {
+ // Add delimiter
+ $line .= $delimiter;
+ $delimiter = $this->delimiter;
+ // Escape enclosures
+ $enclosure = $this->enclosure;
+ if ($enclosure) {
+ // If enclosure is not required, use enclosure only if
+ // element contains newline, delimiter, or enclosure.
+ if (!$this->enclosureRequired && strpbrk($element, "$delimiter$enclosure\n") === false) {
+ $enclosure = '';
+ } else {
+ $element = str_replace($enclosure, $enclosure . $enclosure, $element);
+ }
+ }
+ // Add enclosed string
+ $line .= $enclosure . $element . $enclosure;
+ }
+
+ // Add line ending
+ $line .= $this->lineEnding;
+
+ // Write to file
+ fwrite($pFileHandle, $line);
+ }
+}
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..8d1067c
--- /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 $pFilename
+ */
+ public function save($pFilename): void
+ {
+ // Open file
+ $this->openFileHandle($pFilename);
+
+ // 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.
+ *
+ * @return int
+ */
+ public function getSheetIndex()
+ {
+ return $this->sheetIndex;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param int $pValue Sheet index
+ *
+ * @return $this
+ */
+ public function setSheetIndex($pValue)
+ {
+ $this->sheetIndex = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get sheet index.
+ *
+ * @return bool
+ */
+ public function getGenerateSheetNavigationBlock()
+ {
+ return $this->generateSheetNavigationBlock;
+ }
+
+ /**
+ * Set sheet index.
+ *
+ * @param bool $pValue Flag indicating whether the sheet navigation block should be generated or not
+ *
+ * @return $this
+ */
+ public function setGenerateSheetNavigationBlock($pValue)
+ {
+ $this->generateSheetNavigationBlock = (bool) $pValue;
+
+ 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) : '';
+ }
+
+ /**
+ * Generate HTML header.
+ *
+ * @param bool $pIncludeStyles Include styles?
+ *
+ * @return string
+ */
+ public function generateHTMLHeader($pIncludeStyles = 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()) . '' . 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 .= $pIncludeStyles ? $this->generateStyles(true) : $this->generatePageDeclarations(true);
+
+ $html .= ' ' . PHP_EOL;
+ $html .= '' . PHP_EOL;
+ $html .= ' ' . PHP_EOL;
+
+ 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::coordinateFromString($min);
+ $minCol = Coordinate::columnIndexFromString($minCol);
+ [$maxCol, $maxRow] = Coordinate::coordinateFromString($max);
+ $maxCol = Coordinate::columnIndexFromString($maxCol);
+
+ [$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 .= '' . PHP_EOL;
+
+ foreach ($sheets as $sheet) {
+ $html .= ' - ' . $sheet->getTitle() . '
' . PHP_EOL;
+ ++$sheetId;
+ }
+
+ $html .= '
' . PHP_EOL;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Extend Row if chart is placed after nominal end of row.
+ * This code should be exercised by sample:
+ * Chart/32_Chart_read_write_PDF.php.
+ * However, that test is suppressed due to out-of-date
+ * Jpgraph code issuing warnings. So, don't measure
+ * code coverage for this function till that is fixed.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param int $row Row to check for charts
+ *
+ * @return array
+ *
+ * @codeCoverageIgnore
+ */
+ private function extendRowsForCharts(Worksheet $pSheet, int $row)
+ {
+ $rowMax = $row;
+ $colMax = 'A';
+ $anyfound = false;
+ if ($this->includeCharts) {
+ foreach ($pSheet->getChartCollection() as $chart) {
+ if ($chart instanceof Chart) {
+ $anyfound = true;
+ $chartCoordinates = $chart->getTopLeftPosition();
+ $chartTL = Coordinate::coordinateFromString($chartCoordinates['cell']);
+ $chartCol = Coordinate::columnIndexFromString($chartTL[0]);
+ if ($chartTL[1] > $rowMax) {
+ $rowMax = $chartTL[1];
+ if ($chartCol > Coordinate::columnIndexFromString($colMax)) {
+ $colMax = $chartTL[0];
+ }
+ }
+ }
+ }
+ }
+
+ return [$rowMax, $colMax, $anyfound];
+ }
+
+ private function extendRowsForChartsAndImages(Worksheet $pSheet, int $row): string
+ {
+ [$rowMax, $colMax, $anyfound] = $this->extendRowsForCharts($pSheet, $row);
+
+ foreach ($pSheet->getDrawingCollection() as $drawing) {
+ $anyfound = true;
+ $imageTL = Coordinate::coordinateFromString($drawing->getCoordinates());
+ $imageCol = Coordinate::columnIndexFromString($imageTL[0]);
+ if ($imageTL[1] > $rowMax) {
+ $rowMax = $imageTL[1];
+ if ($imageCol > Coordinate::columnIndexFromString($colMax)) {
+ $colMax = $imageTL[0];
+ }
+ }
+ }
+
+ // Don't extend rows if not needed
+ if ($row === $rowMax || !$anyfound) {
+ return '';
+ }
+
+ $html = '';
+ ++$colMax;
+ ++$row;
+ while ($row <= $rowMax) {
+ $html .= '';
+ for ($col = 'A'; $col != $colMax; ++$col) {
+ $htmlx = $this->writeImageInCell($pSheet, $col . $row);
+ $htmlx .= $this->includeCharts ? $this->writeChartInCell($pSheet, $col . $row) : '';
+ if ($htmlx) {
+ $html .= "$htmlx | ";
+ } else {
+ $html .= " | ";
+ }
+ }
+ ++$row;
+ $html .= '
' . PHP_EOL;
+ }
+
+ return $html;
+ }
+
+ /**
+ * Convert Windows file name to file protocol URL.
+ *
+ * @param string $filename file name on local system
+ *
+ * @return string
+ */
+ public static function winFileToUrl($filename)
+ {
+ // Windows filename
+ if (substr($filename, 1, 2) === ':\\') {
+ $filename = 'file:///' . str_replace('\\', '/', $filename);
+ }
+
+ return $filename;
+ }
+
+ /**
+ * Generate image tag in cell.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param string $coordinates Cell coordinates
+ *
+ * @return string
+ */
+ private function writeImageInCell(Worksheet $pSheet, $coordinates)
+ {
+ // Construct HTML
+ $html = '';
+
+ // Write images
+ foreach ($pSheet->getDrawingCollection() as $drawing) {
+ if ($drawing->getCoordinates() != $coordinates) {
+ continue;
+ }
+ $filedesc = $drawing->getDescription();
+ $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded image';
+ if ($drawing instanceof Drawing) {
+ $filename = $drawing->getPath();
+
+ // Strip off eventual '.'
+ $filename = preg_replace('/^[.]/', '', $filename);
+
+ // Prepend images root
+ $filename = $this->getImagesRoot() . $filename;
+
+ // Strip off eventual '.' if followed by non-/
+ $filename = preg_replace('@^[.]([^/])@', '$1', $filename);
+
+ // Convert UTF8 data to PCDATA
+ $filename = htmlspecialchars($filename);
+
+ $html .= PHP_EOL;
+ $imageData = self::winFileToUrl($filename);
+
+ if ($this->embedImages && !$this->isPdf) {
+ $picture = @file_get_contents($filename);
+ if ($picture !== false) {
+ $imageDetails = getimagesize($filename);
+ // base64 encode the binary data
+ $base64 = base64_encode($picture);
+ $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64;
+ }
+ }
+
+ $html .= '';
+ } elseif ($drawing instanceof MemoryDrawing) {
+ ob_start(); // Let's start output buffering.
+ imagepng($drawing->getImageResource()); // This will normally output the image, but because of ob_start(), it won't.
+ $contents = ob_get_contents(); // Instead, output above is saved to $contents
+ ob_end_clean(); // End the output buffer.
+
+ $dataUri = 'data:image/jpeg;base64,' . base64_encode($contents);
+
+ // Because of the nature of tables, width is more important than height.
+ // max-width: 100% ensures that image doesnt overflow containing cell
+ // width: X sets width of supplied image.
+ // As a result, images bigger than cell will be contained and images smaller will not get stretched
+ $html .= '';
+ }
+ }
+
+ return $html;
+ }
+
+ /**
+ * Generate chart tag in cell.
+ * This code should be exercised by sample:
+ * Chart/32_Chart_read_write_PDF.php.
+ * However, that test is suppressed due to out-of-date
+ * Jpgraph code issuing warnings. So, don't measure
+ * code coverage for this function till that is fixed.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param string $coordinates Cell coordinates
+ *
+ * @return string
+ *
+ * @codeCoverageIgnore
+ */
+ private function writeChartInCell(Worksheet $pSheet, $coordinates)
+ {
+ // Construct HTML
+ $html = '';
+
+ // Write charts
+ foreach ($pSheet->getChartCollection() as $chart) {
+ if ($chart instanceof Chart) {
+ $chartCoordinates = $chart->getTopLeftPosition();
+ if ($chartCoordinates['cell'] == $coordinates) {
+ $chartFileName = File::sysGetTempDir() . '/' . uniqid('', true) . '.png';
+ if (!$chart->render($chartFileName)) {
+ return;
+ }
+
+ $html .= PHP_EOL;
+ $imageDetails = getimagesize($chartFileName);
+ $filedesc = $chart->getTitle();
+ $filedesc = $filedesc ? self::getChartCaption($filedesc->getCaption()) : '';
+ $filedesc = $filedesc ? htmlspecialchars($filedesc, ENT_QUOTES) : 'Embedded chart';
+ if ($fp = fopen($chartFileName, 'rb', 0)) {
+ $picture = fread($fp, filesize($chartFileName));
+ fclose($fp);
+ // base64 encode the binary data
+ $base64 = base64_encode($picture);
+ $imageData = 'data:' . $imageDetails['mime'] . ';base64,' . $base64;
+
+ $html .= '' . PHP_EOL;
+
+ unlink($chartFileName);
+ }
+ }
+ }
+ }
+
+ // Return
+ return $html;
+ }
+
+ /**
+ * Extend Row if chart is placed after nominal end of row.
+ * This code should be exercised by sample:
+ * Chart/32_Chart_read_write_PDF.php.
+ * However, that test is suppressed due to out-of-date
+ * Jpgraph code issuing warnings. So, don't measure
+ * code coverage for this function till that is fixed.
+ * Caption is described in documentation as fixed,
+ * but in 32_Chart it is somehow an array of RichText.
+ *
+ * @param mixed $cap
+ *
+ * @return string
+ *
+ * @codeCoverageIgnore
+ */
+ private static function getChartCaption($cap)
+ {
+ return is_array($cap) ? implode(' ', $cap) : $cap;
+ }
+
+ /**
+ * Generate CSS styles.
+ *
+ * @param bool $generateSurroundingHTML Generate surrounding HTML tags? (<style> and </style>)
+ *
+ * @return string
+ */
+ public function generateStyles($generateSurroundingHTML = true)
+ {
+ // Build CSS
+ $css = $this->buildCSS($generateSurroundingHTML);
+
+ // Construct HTML
+ $html = '';
+
+ // Start styles
+ if ($generateSurroundingHTML) {
+ $html .= ' ' . PHP_EOL;
+ }
+
+ // Return
+ return $html;
+ }
+
+ private function buildCssRowHeights(Worksheet $sheet, array &$css, int $sheetIndex): void
+ {
+ // Calculate row heights
+ foreach ($sheet->getRowDimensions() as $rowDimension) {
+ $row = $rowDimension->getRowIndex() - 1;
+
+ // table.sheetN tr.rowYYYYYY { }
+ $css['table.sheet' . $sheetIndex . ' tr.row' . $row] = [];
+
+ if ($rowDimension->getRowHeight() != -1) {
+ $pt_height = $rowDimension->getRowHeight();
+ $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['height'] = $pt_height . 'pt';
+ }
+ if ($rowDimension->getVisible() === false) {
+ $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['display'] = 'none';
+ $css['table.sheet' . $sheetIndex . ' tr.row' . $row]['visibility'] = 'hidden';
+ }
+ }
+ }
+
+ private function buildCssPerSheet(Worksheet $sheet, array &$css): void
+ {
+ // Calculate hash code
+ $sheetIndex = $sheet->getParent()->getIndex($sheet);
+
+ // Build styles
+ // Calculate column widths
+ $sheet->calculateColumnWidths();
+
+ // col elements, initialize
+ $highestColumnIndex = Coordinate::columnIndexFromString($sheet->getHighestColumn()) - 1;
+ $column = -1;
+ while ($column++ < $highestColumnIndex) {
+ $this->columnWidths[$sheetIndex][$column] = 42; // approximation
+ $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = '42pt';
+ }
+
+ // col elements, loop through columnDimensions and set width
+ foreach ($sheet->getColumnDimensions() as $columnDimension) {
+ $column = Coordinate::columnIndexFromString($columnDimension->getColumnIndex()) - 1;
+ $width = SharedDrawing::cellDimensionToPixels($columnDimension->getWidth(), $this->defaultFont);
+ $width = SharedDrawing::pixelsToPoints($width);
+ if ($columnDimension->getVisible() === false) {
+ $css['table.sheet' . $sheetIndex . ' .column' . $column]['display'] = 'none';
+ }
+ if ($width >= 0) {
+ $this->columnWidths[$sheetIndex][$column] = $width;
+ $css['table.sheet' . $sheetIndex . ' col.col' . $column]['width'] = $width . 'pt';
+ }
+ }
+
+ // Default row height
+ $rowDimension = $sheet->getDefaultRowDimension();
+
+ // table.sheetN tr { }
+ $css['table.sheet' . $sheetIndex . ' tr'] = [];
+
+ if ($rowDimension->getRowHeight() == -1) {
+ $pt_height = SharedFont::getDefaultRowHeightByFont($this->spreadsheet->getDefaultStyle()->getFont());
+ } else {
+ $pt_height = $rowDimension->getRowHeight();
+ }
+ $css['table.sheet' . $sheetIndex . ' tr']['height'] = $pt_height . 'pt';
+ if ($rowDimension->getVisible() === false) {
+ $css['table.sheet' . $sheetIndex . ' tr']['display'] = 'none';
+ $css['table.sheet' . $sheetIndex . ' tr']['visibility'] = 'hidden';
+ }
+
+ $this->buildCssRowHeights($sheet, $css, $sheetIndex);
+ }
+
+ /**
+ * Build CSS styles.
+ *
+ * @param bool $generateSurroundingHTML Generate surrounding HTML style? (html { })
+ *
+ * @return array
+ */
+ public function buildCSS($generateSurroundingHTML = true)
+ {
+ // Cached?
+ if ($this->cssStyles !== null) {
+ return $this->cssStyles;
+ }
+
+ // Ensure that spans have been calculated
+ $this->calculateSpans();
+
+ // Construct CSS
+ $css = [];
+
+ // Start styles
+ if ($generateSurroundingHTML) {
+ // html { }
+ $css['html']['font-family'] = 'Calibri, Arial, Helvetica, sans-serif';
+ $css['html']['font-size'] = '11pt';
+ $css['html']['background-color'] = 'white';
+ }
+
+ // CSS for comments as found in LibreOffice
+ $css['a.comment-indicator:hover + div.comment'] = [
+ 'background' => '#ffd',
+ 'position' => 'absolute',
+ 'display' => 'block',
+ 'border' => '1px solid black',
+ 'padding' => '0.5em',
+ ];
+
+ $css['a.comment-indicator'] = [
+ 'background' => 'red',
+ 'display' => 'inline-block',
+ 'border' => '1px solid black',
+ 'width' => '0.5em',
+ 'height' => '0.5em',
+ ];
+
+ $css['div.comment']['display'] = 'none';
+
+ // table { }
+ $css['table']['border-collapse'] = 'collapse';
+
+ // .b {}
+ $css['.b']['text-align'] = 'center'; // BOOL
+
+ // .e {}
+ $css['.e']['text-align'] = 'center'; // ERROR
+
+ // .f {}
+ $css['.f']['text-align'] = 'right'; // FORMULA
+
+ // .inlineStr {}
+ $css['.inlineStr']['text-align'] = 'left'; // INLINE
+
+ // .n {}
+ $css['.n']['text-align'] = 'right'; // NUMERIC
+
+ // .s {}
+ $css['.s']['text-align'] = 'left'; // STRING
+
+ // Calculate cell style hashes
+ foreach ($this->spreadsheet->getCellXfCollection() as $index => $style) {
+ $css['td.style' . $index] = $this->createCSSStyle($style);
+ $css['th.style' . $index] = $this->createCSSStyle($style);
+ }
+
+ // Fetch sheets
+ $sheets = [];
+ if ($this->sheetIndex === null) {
+ $sheets = $this->spreadsheet->getAllSheets();
+ } else {
+ $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
+ }
+
+ // Build styles per sheet
+ foreach ($sheets as $sheet) {
+ $this->buildCssPerSheet($sheet, $css);
+ }
+
+ // Cache
+ if ($this->cssStyles === null) {
+ $this->cssStyles = $css;
+ }
+
+ // Return
+ return $css;
+ }
+
+ /**
+ * Create CSS style.
+ *
+ * @return array
+ */
+ private function createCSSStyle(Style $pStyle)
+ {
+ // Create CSS
+ return array_merge(
+ $this->createCSSStyleAlignment($pStyle->getAlignment()),
+ $this->createCSSStyleBorders($pStyle->getBorders()),
+ $this->createCSSStyleFont($pStyle->getFont()),
+ $this->createCSSStyleFill($pStyle->getFill())
+ );
+ }
+
+ /**
+ * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Alignment).
+ *
+ * @param Alignment $pStyle \PhpOffice\PhpSpreadsheet\Style\Alignment
+ *
+ * @return array
+ */
+ private function createCSSStyleAlignment(Alignment $pStyle)
+ {
+ // Construct CSS
+ $css = [];
+
+ // Create CSS
+ $css['vertical-align'] = $this->mapVAlign($pStyle->getVertical());
+ $textAlign = $this->mapHAlign($pStyle->getHorizontal());
+ if ($textAlign) {
+ $css['text-align'] = $textAlign;
+ if (in_array($textAlign, ['left', 'right'])) {
+ $css['padding-' . $textAlign] = (string) ((int) $pStyle->getIndent() * 9) . 'px';
+ }
+ }
+
+ return $css;
+ }
+
+ /**
+ * Create CSS style (\PhpOffice\PhpSpreadsheet\Style\Font).
+ *
+ * @return array
+ */
+ private function createCSSStyleFont(Font $pStyle)
+ {
+ // Construct CSS
+ $css = [];
+
+ // Create CSS
+ if ($pStyle->getBold()) {
+ $css['font-weight'] = 'bold';
+ }
+ if ($pStyle->getUnderline() != Font::UNDERLINE_NONE && $pStyle->getStrikethrough()) {
+ $css['text-decoration'] = 'underline line-through';
+ } elseif ($pStyle->getUnderline() != Font::UNDERLINE_NONE) {
+ $css['text-decoration'] = 'underline';
+ } elseif ($pStyle->getStrikethrough()) {
+ $css['text-decoration'] = 'line-through';
+ }
+ if ($pStyle->getItalic()) {
+ $css['font-style'] = 'italic';
+ }
+
+ $css['color'] = '#' . $pStyle->getColor()->getRGB();
+ $css['font-family'] = '\'' . $pStyle->getName() . '\'';
+ $css['font-size'] = $pStyle->getSize() . 'pt';
+
+ return $css;
+ }
+
+ /**
+ * Create CSS style (Borders).
+ *
+ * @param Borders $pStyle Borders
+ *
+ * @return array
+ */
+ private function createCSSStyleBorders(Borders $pStyle)
+ {
+ // Construct CSS
+ $css = [];
+
+ // Create CSS
+ $css['border-bottom'] = $this->createCSSStyleBorder($pStyle->getBottom());
+ $css['border-top'] = $this->createCSSStyleBorder($pStyle->getTop());
+ $css['border-left'] = $this->createCSSStyleBorder($pStyle->getLeft());
+ $css['border-right'] = $this->createCSSStyleBorder($pStyle->getRight());
+
+ return $css;
+ }
+
+ /**
+ * Create CSS style (Border).
+ *
+ * @param Border $pStyle Border
+ *
+ * @return string
+ */
+ private function createCSSStyleBorder(Border $pStyle)
+ {
+ // Create CSS - add !important to non-none border styles for merged cells
+ $borderStyle = $this->mapBorderStyle($pStyle->getBorderStyle());
+
+ return $borderStyle . ' #' . $pStyle->getColor()->getRGB() . (($borderStyle == 'none') ? '' : ' !important');
+ }
+
+ /**
+ * Create CSS style (Fill).
+ *
+ * @param Fill $pStyle Fill
+ *
+ * @return array
+ */
+ private function createCSSStyleFill(Fill $pStyle)
+ {
+ // Construct HTML
+ $css = [];
+
+ // Create CSS
+ $value = $pStyle->getFillType() == Fill::FILL_NONE ?
+ 'white' : '#' . $pStyle->getStartColor()->getRGB();
+ $css['background-color'] = $value;
+
+ return $css;
+ }
+
+ /**
+ * Generate HTML footer.
+ */
+ public function generateHTMLFooter()
+ {
+ // Construct HTML
+ $html = '';
+ $html .= ' ' . PHP_EOL;
+ $html .= '' . PHP_EOL;
+
+ return $html;
+ }
+
+ private function generateTableTagInline($pSheet, $id)
+ {
+ $style = isset($this->cssStyles['table']) ?
+ $this->assembleCSS($this->cssStyles['table']) : '';
+
+ $prntgrid = $pSheet->getPrintGridlines();
+ $viewgrid = $this->isPdf ? $prntgrid : $pSheet->getShowGridlines();
+ if ($viewgrid && $prntgrid) {
+ $html = " " . PHP_EOL;
+ } elseif ($viewgrid) {
+ $html = " " . PHP_EOL;
+ } elseif ($prntgrid) {
+ $html = " " . PHP_EOL;
+ } else {
+ $html = " " . PHP_EOL;
+ }
+
+ return $html;
+ }
+
+ private function generateTableTag($pSheet, $id, &$html, $sheetIndex): void
+ {
+ if (!$this->useInlineCss) {
+ $gridlines = $pSheet->getShowGridlines() ? ' gridlines' : '';
+ $gridlinesp = $pSheet->getPrintGridlines() ? ' gridlinesp' : '';
+ $html .= " " . PHP_EOL;
+ } else {
+ $html .= $this->generateTableTagInline($pSheet, $id);
+ }
+ }
+
+ /**
+ * Generate table header.
+ *
+ * @param Worksheet $pSheet The worksheet for the table we are writing
+ * @param bool $showid whether or not to add id to table tag
+ *
+ * @return string
+ */
+ private function generateTableHeader($pSheet, $showid = true)
+ {
+ $sheetIndex = $pSheet->getParent()->getIndex($pSheet);
+
+ // Construct HTML
+ $html = '';
+ $id = $showid ? "id='sheet$sheetIndex'" : '';
+ if ($showid) {
+ $html .= "\n";
+ } else {
+ $html .= "
\n";
+ }
+
+ $this->generateTableTag($pSheet, $id, $html, $sheetIndex);
+
+ // Write
elements
+ $highestColumnIndex = Coordinate::columnIndexFromString($pSheet->getHighestColumn()) - 1;
+ $i = -1;
+ while ($i++ < $highestColumnIndex) {
+ if (!$this->useInlineCss) {
+ $html .= ' ' . PHP_EOL;
+ } else {
+ $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) ?
+ $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' col.col' . $i]) : '';
+ $html .= ' ' . PHP_EOL;
+ }
+ }
+
+ return $html;
+ }
+
+ /**
+ * Generate table footer.
+ */
+ private function generateTableFooter()
+ {
+ return '
' . PHP_EOL . '' . PHP_EOL;
+ }
+
+ /**
+ * Generate row start.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param int $sheetIndex Sheet index (0-based)
+ * @param int $pRow row number
+ *
+ * @return string
+ */
+ private function generateRowStart(Worksheet $pSheet, $sheetIndex, $pRow)
+ {
+ $html = '';
+ if (count($pSheet->getBreaks()) > 0) {
+ $breaks = $pSheet->getBreaks();
+
+ // check if a break is needed before this row
+ if (isset($breaks['A' . $pRow])) {
+ // close table:
+ $html .= $this->generateTableFooter();
+ if ($this->isPdf && $this->useInlineCss) {
+ $html .= '';
+ }
+
+ // open table again: + etc.
+ $html .= $this->generateTableHeader($pSheet, false);
+ $html .= '' . PHP_EOL;
+ }
+ }
+
+ // Write row start
+ if (!$this->useInlineCss) {
+ $html .= ' ' . PHP_EOL;
+ } else {
+ $style = isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow])
+ ? $this->assembleCSS($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]) : '';
+
+ $html .= '
' . PHP_EOL;
+ }
+
+ return $html;
+ }
+
+ private function generateRowCellCss($pSheet, $cellAddress, $pRow, $colNum)
+ {
+ $cell = ($cellAddress > '') ? $pSheet->getCell($cellAddress) : '';
+ $coordinate = Coordinate::stringFromColumnIndex($colNum + 1) . ($pRow + 1);
+ if (!$this->useInlineCss) {
+ $cssClass = 'column' . $colNum;
+ } else {
+ $cssClass = [];
+ // The statements below do nothing.
+ // Commenting out the code rather than deleting it
+ // in case someone can figure out what their intent was.
+ //if ($cellType == 'th') {
+ // if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum])) {
+ // $this->cssStyles['table.sheet' . $sheetIndex . ' th.column' . $colNum];
+ // }
+ //} else {
+ // if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum])) {
+ // $this->cssStyles['table.sheet' . $sheetIndex . ' td.column' . $colNum];
+ // }
+ //}
+ // End of mystery statements.
+ }
+
+ return [$cell, $cssClass, $coordinate];
+ }
+
+ private function generateRowCellDataValueRich($cell, &$cellData): void
+ {
+ // Loop through rich text elements
+ $elements = $cell->getValue()->getRichTextElements();
+ foreach ($elements as $element) {
+ // Rich text start?
+ if ($element instanceof Run) {
+ $cellData .= '';
+
+ $cellEnd = '';
+ if ($element->getFont()->getSuperscript()) {
+ $cellData .= '';
+ $cellEnd = '';
+ } elseif ($element->getFont()->getSubscript()) {
+ $cellData .= '';
+ $cellEnd = '';
+ }
+
+ // Convert UTF8 data to PCDATA
+ $cellText = $element->getText();
+ $cellData .= htmlspecialchars($cellText);
+
+ $cellData .= $cellEnd;
+
+ $cellData .= '';
+ } else {
+ // Convert UTF8 data to PCDATA
+ $cellText = $element->getText();
+ $cellData .= htmlspecialchars($cellText);
+ }
+ }
+ }
+
+ private function generateRowCellDataValue($pSheet, $cell, &$cellData): void
+ {
+ if ($cell->getValue() instanceof RichText) {
+ $this->generateRowCellDataValueRich($cell, $cellData);
+ } else {
+ $origData = $this->preCalculateFormulas ? $cell->getCalculatedValue() : $cell->getValue();
+ $cellData = NumberFormat::toFormattedString(
+ $origData,
+ $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getNumberFormat()->getFormatCode(),
+ [$this, 'formatColor']
+ );
+ if ($cellData === $origData) {
+ $cellData = htmlspecialchars($cellData);
+ }
+ if ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSuperscript()) {
+ $cellData = '' . $cellData . '';
+ } elseif ($pSheet->getParent()->getCellXfByIndex($cell->getXfIndex())->getFont()->getSubscript()) {
+ $cellData = '' . $cellData . '';
+ }
+ }
+ }
+
+ private function generateRowCellData($pSheet, $cell, &$cssClass, $cellType)
+ {
+ $cellData = ' ';
+ if ($cell instanceof Cell) {
+ $cellData = '';
+ // Don't know what this does, and no test cases.
+ //if ($cell->getParent() === null) {
+ // $cell->attach($pSheet);
+ //}
+ // Value
+ $this->generateRowCellDataValue($pSheet, $cell, $cellData);
+
+ // Converts the cell content so that spaces occuring at beginning of each new line are replaced by
+ // Example: " Hello\n to the world" is converted to " Hello\n to the world"
+ $cellData = preg_replace('/(?m)(?:^|\\G) /', ' ', $cellData);
+
+ // convert newline "\n" to '
'
+ $cellData = nl2br($cellData);
+
+ // Extend CSS class?
+ if (!$this->useInlineCss) {
+ $cssClass .= ' style' . $cell->getXfIndex();
+ $cssClass .= ' ' . $cell->getDataType();
+ } else {
+ if ($cellType == 'th') {
+ if (isset($this->cssStyles['th.style' . $cell->getXfIndex()])) {
+ $cssClass = array_merge($cssClass, $this->cssStyles['th.style' . $cell->getXfIndex()]);
+ }
+ } else {
+ if (isset($this->cssStyles['td.style' . $cell->getXfIndex()])) {
+ $cssClass = array_merge($cssClass, $this->cssStyles['td.style' . $cell->getXfIndex()]);
+ }
+ }
+
+ // General horizontal alignment: Actual horizontal alignment depends on dataType
+ $sharedStyle = $pSheet->getParent()->getCellXfByIndex($cell->getXfIndex());
+ if (
+ $sharedStyle->getAlignment()->getHorizontal() == Alignment::HORIZONTAL_GENERAL
+ && isset($this->cssStyles['.' . $cell->getDataType()]['text-align'])
+ ) {
+ $cssClass['text-align'] = $this->cssStyles['.' . $cell->getDataType()]['text-align'];
+ }
+ }
+ } else {
+ // Use default borders for empty cell
+ if (is_string($cssClass)) {
+ $cssClass .= ' style0';
+ }
+ }
+
+ return $cellData;
+ }
+
+ private function generateRowIncludeCharts($pSheet, $coordinate)
+ {
+ return $this->includeCharts ? $this->writeChartInCell($pSheet, $coordinate) : '';
+ }
+
+ private function generateRowSpans($html, $rowSpan, $colSpan)
+ {
+ $html .= ($colSpan > 1) ? (' colspan="' . $colSpan . '"') : '';
+ $html .= ($rowSpan > 1) ? (' rowspan="' . $rowSpan . '"') : '';
+
+ return $html;
+ }
+
+ private function generateRowWriteCell(&$html, $pSheet, $coordinate, $cellType, $cellData, $colSpan, $rowSpan, $cssClass, $colNum, $sheetIndex, $pRow): void
+ {
+ // Image?
+ $htmlx = $this->writeImageInCell($pSheet, $coordinate);
+ // Chart?
+ $htmlx .= $this->generateRowIncludeCharts($pSheet, $coordinate);
+ // Column start
+ $html .= ' <' . $cellType;
+ if (!$this->useInlineCss && !$this->isPdf) {
+ $html .= ' class="' . $cssClass . '"';
+ if ($htmlx) {
+ $html .= " style='position: relative;'";
+ }
+ } else {
+ //** Necessary redundant code for the sake of \PhpOffice\PhpSpreadsheet\Writer\Pdf **
+ // We must explicitly write the width of the element because TCPDF
+ // does not recognize e.g.
+ if ($this->useInlineCss) {
+ $xcssClass = $cssClass;
+ } else {
+ $html .= ' class="' . $cssClass . '"';
+ $xcssClass = [];
+ }
+ $width = 0;
+ $i = $colNum - 1;
+ $e = $colNum + $colSpan - 1;
+ while ($i++ < $e) {
+ if (isset($this->columnWidths[$sheetIndex][$i])) {
+ $width += $this->columnWidths[$sheetIndex][$i];
+ }
+ }
+ $xcssClass['width'] = $width . 'pt';
+
+ // We must also explicitly write the height of the | element because TCPDF
+ // does not recognize e.g. |
+ if (isset($this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'])) {
+ $height = $this->cssStyles['table.sheet' . $sheetIndex . ' tr.row' . $pRow]['height'];
+ $xcssClass['height'] = $height;
+ }
+ //** end of redundant code **
+
+ if ($htmlx) {
+ $xcssClass['position'] = 'relative';
+ }
+ $html .= ' style="' . $this->assembleCSS($xcssClass) . '"';
+ }
+ $html = $this->generateRowSpans($html, $rowSpan, $colSpan);
+
+ $html .= '>';
+ $html .= $htmlx;
+
+ $html .= $this->writeComment($pSheet, $coordinate);
+
+ // Cell data
+ $html .= $cellData;
+
+ // Column end
+ $html .= '' . $cellType . '>' . PHP_EOL;
+ }
+
+ /**
+ * Generate row.
+ *
+ * @param Worksheet $pSheet \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ * @param array $pValues Array containing cells in a row
+ * @param int $pRow Row number (0-based)
+ * @param string $cellType eg: 'td'
+ *
+ * @return string
+ */
+ private function generateRow(Worksheet $pSheet, array $pValues, $pRow, $cellType)
+ {
+ // Sheet index
+ $sheetIndex = $pSheet->getParent()->getIndex($pSheet);
+ $html = $this->generateRowStart($pSheet, $sheetIndex, $pRow);
+
+ // Write cells
+ $colNum = 0;
+ foreach ($pValues as $cellAddress) {
+ [$cell, $cssClass, $coordinate] = $this->generateRowCellCss($pSheet, $cellAddress, $pRow, $colNum);
+
+ $colSpan = 1;
+ $rowSpan = 1;
+
+ // Cell Data
+ $cellData = $this->generateRowCellData($pSheet, $cell, $cssClass, $cellType);
+
+ // Hyperlink?
+ if ($pSheet->hyperlinkExists($coordinate) && !$pSheet->getHyperlink($coordinate)->isInternal()) {
+ $cellData = '' . $cellData . '';
+ }
+
+ // Should the cell be written or is it swallowed by a rowspan or colspan?
+ $writeCell = !(isset($this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum])
+ && $this->isSpannedCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum]);
+
+ // Colspan and Rowspan
+ $colspan = 1;
+ $rowspan = 1;
+ if (isset($this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum])) {
+ $spans = $this->isBaseCell[$pSheet->getParent()->getIndex($pSheet)][$pRow + 1][$colNum];
+ $rowSpan = $spans['rowspan'];
+ $colSpan = $spans['colspan'];
+
+ // Also apply style from last cell in merge to fix borders -
+ // relies on !important for non-none border declarations in createCSSStyleBorder
+ $endCellCoord = Coordinate::stringFromColumnIndex($colNum + $colSpan) . ($pRow + $rowSpan);
+ if (!$this->useInlineCss) {
+ $cssClass .= ' style' . $pSheet->getCell($endCellCoord)->getXfIndex();
+ }
+ }
+
+ // Write
+ if ($writeCell) {
+ $this->generateRowWriteCell($html, $pSheet, $coordinate, $cellType, $cellData, $colSpan, $rowSpan, $cssClass, $colNum, $sheetIndex, $pRow);
+ }
+
+ // Next column
+ ++$colNum;
+ }
+
+ // Write row end
+ $html .= '
' . PHP_EOL;
+
+ // Return
+ return $html;
+ }
+
+ /**
+ * Takes array where of CSS properties / values and converts to CSS string.
+ *
+ * @return string
+ */
+ private function assembleCSS(array $pValue = [])
+ {
+ $pairs = [];
+ foreach ($pValue as $property => $value) {
+ $pairs[] = $property . ':' . $value;
+ }
+ $string = implode('; ', $pairs);
+
+ return $string;
+ }
+
+ /**
+ * Get images root.
+ *
+ * @return string
+ */
+ public function getImagesRoot()
+ {
+ return $this->imagesRoot;
+ }
+
+ /**
+ * Set images root.
+ *
+ * @param string $pValue
+ *
+ * @return $this
+ */
+ public function setImagesRoot($pValue)
+ {
+ $this->imagesRoot = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get embed images.
+ *
+ * @return bool
+ */
+ public function getEmbedImages()
+ {
+ return $this->embedImages;
+ }
+
+ /**
+ * Set embed images.
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setEmbedImages($pValue)
+ {
+ $this->embedImages = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get use inline CSS?
+ *
+ * @return bool
+ */
+ public function getUseInlineCss()
+ {
+ return $this->useInlineCss;
+ }
+
+ /**
+ * Set use inline CSS?
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ */
+ public function setUseInlineCss($pValue)
+ {
+ $this->useInlineCss = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get use embedded CSS?
+ *
+ * @return bool
+ *
+ * @codeCoverageIgnore
+ *
+ * @deprecated no longer used
+ */
+ public function getUseEmbeddedCSS()
+ {
+ return $this->useEmbeddedCSS;
+ }
+
+ /**
+ * Set use embedded CSS?
+ *
+ * @param bool $pValue
+ *
+ * @return $this
+ *
+ * @codeCoverageIgnore
+ *
+ * @deprecated no longer used
+ */
+ public function setUseEmbeddedCSS($pValue)
+ {
+ $this->useEmbeddedCSS = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Add color to formatted string as inline style.
+ *
+ * @param string $pValue Plain formatted value without color
+ * @param string $pFormat Format code
+ *
+ * @return string
+ */
+ public function formatColor($pValue, $pFormat)
+ {
+ // Color information, e.g. [Red] is always at the beginning
+ $color = null; // initialize
+ $matches = [];
+
+ $color_regex = '/^\\[[a-zA-Z]+\\]/';
+ if (preg_match($color_regex, $pFormat, $matches)) {
+ $color = str_replace(['[', ']'], '', $matches[0]);
+ $color = strtolower($color);
+ }
+
+ // convert to PCDATA
+ $value = htmlspecialchars($pValue);
+
+ // color span tag
+ if ($color !== null) {
+ $value = '' . $value . '';
+ }
+
+ return $value;
+ }
+
+ /**
+ * Calculate information about HTML colspan and rowspan which is not always the same as Excel's.
+ */
+ private function calculateSpans(): void
+ {
+ if ($this->spansAreCalculated) {
+ return;
+ }
+ // Identify all cells that should be omitted in HTML due to cell merge.
+ // In HTML only the upper-left cell should be written and it should have
+ // appropriate rowspan / colspan attribute
+ $sheetIndexes = $this->sheetIndex !== null ?
+ [$this->sheetIndex] : range(0, $this->spreadsheet->getSheetCount() - 1);
+
+ foreach ($sheetIndexes as $sheetIndex) {
+ $sheet = $this->spreadsheet->getSheet($sheetIndex);
+
+ $candidateSpannedRow = [];
+
+ // loop through all Excel merged cells
+ foreach ($sheet->getMergeCells() as $cells) {
+ [$cells] = Coordinate::splitRange($cells);
+ $first = $cells[0];
+ $last = $cells[1];
+
+ [$fc, $fr] = Coordinate::coordinateFromString($first);
+ $fc = Coordinate::columnIndexFromString($fc) - 1;
+
+ [$lc, $lr] = Coordinate::coordinateFromString($last);
+ $lc = Coordinate::columnIndexFromString($lc) - 1;
+
+ // loop through the individual cells in the individual merge
+ $r = $fr - 1;
+ while ($r++ < $lr) {
+ // also, flag this row as a HTML row that is candidate to be omitted
+ $candidateSpannedRow[$r] = $r;
+
+ $c = $fc - 1;
+ while ($c++ < $lc) {
+ if (!($c == $fc && $r == $fr)) {
+ // not the upper-left cell (should not be written in HTML)
+ $this->isSpannedCell[$sheetIndex][$r][$c] = [
+ 'baseCell' => [$fr, $fc],
+ ];
+ } else {
+ // upper-left is the base cell that should hold the colspan/rowspan attribute
+ $this->isBaseCell[$sheetIndex][$r][$c] = [
+ 'xlrowspan' => $lr - $fr + 1, // Excel rowspan
+ 'rowspan' => $lr - $fr + 1, // HTML rowspan, value may change
+ 'xlcolspan' => $lc - $fc + 1, // Excel colspan
+ 'colspan' => $lc - $fc + 1, // HTML colspan, value may change
+ ];
+ }
+ }
+ }
+ }
+
+ $this->calculateSpansOmitRows($sheet, $sheetIndex, $candidateSpannedRow);
+
+ // TODO: Same for columns
+ }
+
+ // We have calculated the spans
+ $this->spansAreCalculated = true;
+ }
+
+ private function calculateSpansOmitRows($sheet, $sheetIndex, $candidateSpannedRow): void
+ {
+ // Identify which rows should be omitted in HTML. These are the rows where all the cells
+ // participate in a merge and the where base cells are somewhere above.
+ $countColumns = Coordinate::columnIndexFromString($sheet->getHighestColumn());
+ foreach ($candidateSpannedRow as $rowIndex) {
+ if (isset($this->isSpannedCell[$sheetIndex][$rowIndex])) {
+ if (count($this->isSpannedCell[$sheetIndex][$rowIndex]) == $countColumns) {
+ $this->isSpannedRow[$sheetIndex][$rowIndex] = $rowIndex;
+ }
+ }
+ }
+
+ // For each of the omitted rows we found above, the affected rowspans should be subtracted by 1
+ if (isset($this->isSpannedRow[$sheetIndex])) {
+ foreach ($this->isSpannedRow[$sheetIndex] as $rowIndex) {
+ $adjustedBaseCells = [];
+ $c = -1;
+ $e = $countColumns - 1;
+ while ($c++ < $e) {
+ $baseCell = $this->isSpannedCell[$sheetIndex][$rowIndex][$c]['baseCell'];
+
+ if (!in_array($baseCell, $adjustedBaseCells)) {
+ // subtract rowspan by 1
+ --$this->isBaseCell[$sheetIndex][$baseCell[0]][$baseCell[1]]['rowspan'];
+ $adjustedBaseCells[] = $baseCell;
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Write a comment in the same format as LibreOffice.
+ *
+ * @see https://github.com/LibreOffice/core/blob/9fc9bf3240f8c62ad7859947ab8a033ac1fe93fa/sc/source/filter/html/htmlexp.cxx#L1073-L1092
+ *
+ * @param string $coordinate
+ *
+ * @return string
+ */
+ private function writeComment(Worksheet $pSheet, $coordinate)
+ {
+ $result = '';
+ if (!$this->isPdf && isset($pSheet->getComments()[$coordinate])) {
+ $result .= '';
+ $result .= '';
+ $result .= PHP_EOL;
+ }
+
+ return $result;
+ }
+
+ /**
+ * Generate @page declarations.
+ *
+ * @param bool $generateSurroundingHTML
+ *
+ * @return string
+ */
+ private function generatePageDeclarations($generateSurroundingHTML)
+ {
+ // Ensure that Spans have been calculated?
+ $this->calculateSpans();
+
+ // Fetch sheets
+ $sheets = [];
+ if ($this->sheetIndex === null) {
+ $sheets = $this->spreadsheet->getAllSheets();
+ } else {
+ $sheets[] = $this->spreadsheet->getSheet($this->sheetIndex);
+ }
+
+ // Construct HTML
+ $htmlPage = $generateSurroundingHTML ? ('' . PHP_EOL) : '';
+
+ return $htmlPage;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php
new file mode 100644
index 0000000..7f1b404
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/IWriter.php
@@ -0,0 +1,87 @@
+setSpreadsheet($spreadsheet);
+
+ $writerPartsArray = [
+ 'content' => Content::class,
+ 'meta' => Meta::class,
+ 'meta_inf' => MetaInf::class,
+ 'mimetype' => Mimetype::class,
+ 'settings' => Settings::class,
+ 'styles' => Styles::class,
+ 'thumbnails' => Thumbnails::class,
+ ];
+
+ foreach ($writerPartsArray as $writer => $class) {
+ $this->writerParts[$writer] = new $class($this);
+ }
+ }
+
+ /**
+ * Get writer part.
+ *
+ * @param string $pPartName Writer part name
+ *
+ * @return null|Ods\WriterPart
+ */
+ public function getWriterPart($pPartName)
+ {
+ if ($pPartName != '' && isset($this->writerParts[strtolower($pPartName)])) {
+ return $this->writerParts[strtolower($pPartName)];
+ }
+
+ return null;
+ }
+
+ /**
+ * Save PhpSpreadsheet to file.
+ *
+ * @param resource|string $pFilename
+ */
+ public function save($pFilename): void
+ {
+ if (!$this->spreadSheet) {
+ throw new WriterException('PhpSpreadsheet object unassigned.');
+ }
+
+ // garbage collect
+ $this->spreadSheet->garbageCollect();
+
+ $this->openFileHandle($pFilename);
+
+ $zip = $this->createZip();
+
+ $zip->addFile('META-INF/manifest.xml', $this->getWriterPart('meta_inf')->writeManifest());
+ $zip->addFile('Thumbnails/thumbnail.png', $this->getWriterPart('thumbnails')->writeThumbnail());
+ $zip->addFile('content.xml', $this->getWriterPart('content')->write());
+ $zip->addFile('meta.xml', $this->getWriterPart('meta')->write());
+ $zip->addFile('mimetype', $this->getWriterPart('mimetype')->write());
+ $zip->addFile('settings.xml', $this->getWriterPart('settings')->write());
+ $zip->addFile('styles.xml', $this->getWriterPart('styles')->write());
+
+ // Close file
+ try {
+ $zip->finish();
+ } catch (OverflowException $e) {
+ throw new WriterException('Could not close resource.');
+ }
+
+ $this->maybeCloseFileHandle();
+ }
+
+ /**
+ * Create zip object.
+ *
+ * @return ZipStream
+ */
+ private function createZip()
+ {
+ // Try opening the ZIP file
+ if (!is_resource($this->fileHandle)) {
+ throw new WriterException('Could not open resource for writing.');
+ }
+
+ // Create new ZIP stream
+ $options = new Archive();
+ $options->setEnableZip64(false);
+ $options->setOutputStream($this->fileHandle);
+
+ return new ZipStream(null, $options);
+ }
+
+ /**
+ * Get Spreadsheet object.
+ *
+ * @return Spreadsheet
+ */
+ public function getSpreadsheet()
+ {
+ if ($this->spreadSheet !== null) {
+ return $this->spreadSheet;
+ }
+
+ throw new WriterException('No PhpSpreadsheet assigned.');
+ }
+
+ /**
+ * Set Spreadsheet object.
+ *
+ * @param Spreadsheet $spreadsheet PhpSpreadsheet object
+ *
+ * @return $this
+ */
+ public function setSpreadsheet(Spreadsheet $spreadsheet)
+ {
+ $this->spreadSheet = $spreadsheet;
+
+ return $this;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php
new file mode 100644
index 0000000..ffee89f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Cell/Comment.php
@@ -0,0 +1,30 @@
+
+ */
+class Comment
+{
+ public static function write(XMLWriter $objWriter, Cell $cell): void
+ {
+ $comments = $cell->getWorksheet()->getComments();
+ if (!isset($comments[$cell->getCoordinate()])) {
+ return;
+ }
+ $comment = $comments[$cell->getCoordinate()];
+
+ $objWriter->startElement('office:annotation');
+ $objWriter->writeAttribute('svg:width', $comment->getWidth());
+ $objWriter->writeAttribute('svg:height', $comment->getHeight());
+ $objWriter->writeAttribute('svg:x', $comment->getMarginLeft());
+ $objWriter->writeAttribute('svg:y', $comment->getMarginTop());
+ $objWriter->writeElement('dc:creator', $comment->getAuthor());
+ $objWriter->writeElement('text:p', $comment->getText()->getPlainText());
+ $objWriter->endElement();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php
new file mode 100644
index 0000000..fe9118f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Content.php
@@ -0,0 +1,382 @@
+
+ */
+class Content extends WriterPart
+{
+ const NUMBER_COLS_REPEATED_MAX = 1024;
+ const NUMBER_ROWS_REPEATED_MAX = 1048576;
+ const CELL_STYLE_PREFIX = 'ce';
+
+ private $formulaConvertor;
+
+ /**
+ * Set parent Ods writer.
+ */
+ public function __construct(Ods $writer)
+ {
+ parent::__construct($writer);
+
+ $this->formulaConvertor = new Formula($this->getParentWriter()->getSpreadsheet()->getDefinedNames());
+ }
+
+ /**
+ * Write content.xml to XML format.
+ *
+ * @return string XML Output
+ */
+ public function write()
+ {
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8');
+
+ // Content
+ $objWriter->startElement('office:document-content');
+ $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
+ $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0');
+ $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0');
+ $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0');
+ $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0');
+ $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0');
+ $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
+ $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
+ $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0');
+ $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0');
+ $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0');
+ $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0');
+ $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0');
+ $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML');
+ $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0');
+ $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0');
+ $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
+ $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer');
+ $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc');
+ $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events');
+ $objWriter->writeAttribute('xmlns:xforms', 'http://www.w3.org/2002/xforms');
+ $objWriter->writeAttribute('xmlns:xsd', 'http://www.w3.org/2001/XMLSchema');
+ $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
+ $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report');
+ $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2');
+ $objWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml');
+ $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#');
+ $objWriter->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table');
+ $objWriter->writeAttribute('xmlns:field', 'urn:openoffice:names:experimental:ooo-ms-interop:xmlns:field:1.0');
+ $objWriter->writeAttribute('xmlns:formx', 'urn:openoffice:names:experimental:ooxml-odf-interop:xmlns:form:1.0');
+ $objWriter->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/');
+ $objWriter->writeAttribute('office:version', '1.2');
+
+ $objWriter->writeElement('office:scripts');
+ $objWriter->writeElement('office:font-face-decls');
+
+ // Styles XF
+ $objWriter->startElement('office:automatic-styles');
+ $this->writeXfStyles($objWriter, $this->getParentWriter()->getSpreadsheet());
+ $objWriter->endElement();
+
+ $objWriter->startElement('office:body');
+ $objWriter->startElement('office:spreadsheet');
+ $objWriter->writeElement('table:calculation-settings');
+
+ $this->writeSheets($objWriter);
+
+ // Defined names (ranges and formulae)
+ (new NamedExpressions($objWriter, $this->getParentWriter()->getSpreadsheet(), $this->formulaConvertor))->write();
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write sheets.
+ */
+ private function writeSheets(XMLWriter $objWriter): void
+ {
+ $spreadsheet = $this->getParentWriter()->getSpreadsheet(); /** @var Spreadsheet $spreadsheet */
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $objWriter->startElement('table:table');
+ $objWriter->writeAttribute('table:name', $spreadsheet->getSheet($i)->getTitle());
+ $objWriter->writeElement('office:forms');
+ $objWriter->startElement('table:table-column');
+ $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
+ $objWriter->endElement();
+ $this->writeRows($objWriter, $spreadsheet->getSheet($i));
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write rows of the specified sheet.
+ */
+ private function writeRows(XMLWriter $objWriter, Worksheet $sheet): void
+ {
+ $numberRowsRepeated = self::NUMBER_ROWS_REPEATED_MAX;
+ $span_row = 0;
+ $rows = $sheet->getRowIterator();
+ while ($rows->valid()) {
+ --$numberRowsRepeated;
+ $row = $rows->current();
+ if ($row->getCellIterator()->valid()) {
+ if ($span_row) {
+ $objWriter->startElement('table:table-row');
+ if ($span_row > 1) {
+ $objWriter->writeAttribute('table:number-rows-repeated', $span_row);
+ }
+ $objWriter->startElement('table:table-cell');
+ $objWriter->writeAttribute('table:number-columns-repeated', self::NUMBER_COLS_REPEATED_MAX);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $span_row = 0;
+ }
+ $objWriter->startElement('table:table-row');
+ $this->writeCells($objWriter, $row);
+ $objWriter->endElement();
+ } else {
+ ++$span_row;
+ }
+ $rows->next();
+ }
+ }
+
+ /**
+ * Write cells of the specified row.
+ */
+ private function writeCells(XMLWriter $objWriter, Row $row): void
+ {
+ $numberColsRepeated = self::NUMBER_COLS_REPEATED_MAX;
+ $prevColumn = -1;
+ $cells = $row->getCellIterator();
+ while ($cells->valid()) {
+ /** @var \PhpOffice\PhpSpreadsheet\Cell\Cell $cell */
+ $cell = $cells->current();
+ $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1;
+
+ $this->writeCellSpan($objWriter, $column, $prevColumn);
+ $objWriter->startElement('table:table-cell');
+ $this->writeCellMerge($objWriter, $cell);
+
+ // Style XF
+ $style = $cell->getXfIndex();
+ if ($style !== null) {
+ $objWriter->writeAttribute('table:style-name', self::CELL_STYLE_PREFIX . $style);
+ }
+
+ switch ($cell->getDataType()) {
+ case DataType::TYPE_BOOL:
+ $objWriter->writeAttribute('office:value-type', 'boolean');
+ $objWriter->writeAttribute('office:value', $cell->getValue());
+ $objWriter->writeElement('text:p', $cell->getValue());
+
+ break;
+ case DataType::TYPE_ERROR:
+ throw new Exception('Writing of error not implemented yet.');
+
+ break;
+ case DataType::TYPE_FORMULA:
+ $formulaValue = $cell->getValue();
+ if ($this->getParentWriter()->getPreCalculateFormulas()) {
+ try {
+ $formulaValue = $cell->getCalculatedValue();
+ } catch (Exception $e) {
+ // don't do anything
+ }
+ }
+ $objWriter->writeAttribute('table:formula', $this->formulaConvertor->convertFormula($cell->getValue()));
+ if (is_numeric($formulaValue)) {
+ $objWriter->writeAttribute('office:value-type', 'float');
+ } else {
+ $objWriter->writeAttribute('office:value-type', 'string');
+ }
+ $objWriter->writeAttribute('office:value', $formulaValue);
+ $objWriter->writeElement('text:p', $formulaValue);
+
+ break;
+ case DataType::TYPE_INLINE:
+ throw new Exception('Writing of inline not implemented yet.');
+
+ break;
+ case DataType::TYPE_NUMERIC:
+ $objWriter->writeAttribute('office:value-type', 'float');
+ $objWriter->writeAttribute('office:value', $cell->getValue());
+ $objWriter->writeElement('text:p', $cell->getValue());
+
+ break;
+ case DataType::TYPE_STRING:
+ $objWriter->writeAttribute('office:value-type', 'string');
+ $objWriter->writeElement('text:p', $cell->getValue());
+
+ break;
+ }
+ Comment::write($objWriter, $cell);
+ $objWriter->endElement();
+ $prevColumn = $column;
+ $cells->next();
+ }
+ $numberColsRepeated = $numberColsRepeated - $prevColumn - 1;
+ if ($numberColsRepeated > 0) {
+ if ($numberColsRepeated > 1) {
+ $objWriter->startElement('table:table-cell');
+ $objWriter->writeAttribute('table:number-columns-repeated', $numberColsRepeated);
+ $objWriter->endElement();
+ } else {
+ $objWriter->writeElement('table:table-cell');
+ }
+ }
+ }
+
+ /**
+ * Write span.
+ *
+ * @param int $curColumn
+ * @param int $prevColumn
+ */
+ private function writeCellSpan(XMLWriter $objWriter, $curColumn, $prevColumn): void
+ {
+ $diff = $curColumn - $prevColumn - 1;
+ if (1 === $diff) {
+ $objWriter->writeElement('table:table-cell');
+ } elseif ($diff > 1) {
+ $objWriter->startElement('table:table-cell');
+ $objWriter->writeAttribute('table:number-columns-repeated', $diff);
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write XF cell styles.
+ */
+ private function writeXfStyles(XMLWriter $writer, Spreadsheet $spreadsheet): void
+ {
+ foreach ($spreadsheet->getCellXfCollection() as $style) {
+ $writer->startElement('style:style');
+ $writer->writeAttribute('style:name', self::CELL_STYLE_PREFIX . $style->getIndex());
+ $writer->writeAttribute('style:family', 'table-cell');
+ $writer->writeAttribute('style:parent-style-name', 'Default');
+
+ // style:text-properties
+
+ // Font
+ $writer->startElement('style:text-properties');
+
+ $font = $style->getFont();
+
+ if ($font->getBold()) {
+ $writer->writeAttribute('fo:font-weight', 'bold');
+ $writer->writeAttribute('style:font-weight-complex', 'bold');
+ $writer->writeAttribute('style:font-weight-asian', 'bold');
+ }
+
+ if ($font->getItalic()) {
+ $writer->writeAttribute('fo:font-style', 'italic');
+ }
+
+ if ($color = $font->getColor()) {
+ $writer->writeAttribute('fo:color', sprintf('#%s', $color->getRGB()));
+ }
+
+ if ($family = $font->getName()) {
+ $writer->writeAttribute('fo:font-family', $family);
+ }
+
+ if ($size = $font->getSize()) {
+ $writer->writeAttribute('fo:font-size', sprintf('%.1Fpt', $size));
+ }
+
+ if ($font->getUnderline() && $font->getUnderline() != Font::UNDERLINE_NONE) {
+ $writer->writeAttribute('style:text-underline-style', 'solid');
+ $writer->writeAttribute('style:text-underline-width', 'auto');
+ $writer->writeAttribute('style:text-underline-color', 'font-color');
+
+ switch ($font->getUnderline()) {
+ case Font::UNDERLINE_DOUBLE:
+ $writer->writeAttribute('style:text-underline-type', 'double');
+
+ break;
+ case Font::UNDERLINE_SINGLE:
+ $writer->writeAttribute('style:text-underline-type', 'single');
+
+ break;
+ }
+ }
+
+ $writer->endElement(); // Close style:text-properties
+
+ // style:table-cell-properties
+
+ $writer->startElement('style:table-cell-properties');
+ $writer->writeAttribute('style:rotation-align', 'none');
+
+ // Fill
+ if ($fill = $style->getFill()) {
+ switch ($fill->getFillType()) {
+ case Fill::FILL_SOLID:
+ $writer->writeAttribute('fo:background-color', sprintf(
+ '#%s',
+ strtolower($fill->getStartColor()->getRGB())
+ ));
+
+ break;
+ case Fill::FILL_GRADIENT_LINEAR:
+ case Fill::FILL_GRADIENT_PATH:
+ /// TODO :: To be implemented
+ break;
+ case Fill::FILL_NONE:
+ default:
+ }
+ }
+
+ $writer->endElement(); // Close style:table-cell-properties
+
+ // End
+
+ $writer->endElement(); // Close style:style
+ }
+ }
+
+ /**
+ * Write attributes for merged cell.
+ */
+ private function writeCellMerge(XMLWriter $objWriter, Cell $cell): void
+ {
+ if (!$cell->isMergeRangeValueCell()) {
+ return;
+ }
+
+ $mergeRange = Coordinate::splitRange($cell->getMergeRange());
+ [$startCell, $endCell] = $mergeRange[0];
+ $start = Coordinate::coordinateFromString($startCell);
+ $end = Coordinate::coordinateFromString($endCell);
+ $columnSpan = Coordinate::columnIndexFromString($end[0]) - Coordinate::columnIndexFromString($start[0]) + 1;
+ $rowSpan = $end[1] - $start[1] + 1;
+
+ $objWriter->writeAttribute('table:number-columns-spanned', $columnSpan);
+ $objWriter->writeAttribute('table:number-rows-spanned', $rowSpan);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Formula.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Formula.php
new file mode 100644
index 0000000..7f02898
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Formula.php
@@ -0,0 +1,119 @@
+definedNames[] = $definedName->getName();
+ }
+ }
+
+ public function convertFormula(string $formula, string $worksheetName = ''): string
+ {
+ $formula = $this->convertCellReferences($formula, $worksheetName);
+ $formula = $this->convertDefinedNames($formula);
+
+ if (substr($formula, 0, 1) !== '=') {
+ $formula = '=' . $formula;
+ }
+
+ return 'of:' . $formula;
+ }
+
+ private function convertDefinedNames(string $formula): string
+ {
+ $splitCount = preg_match_all(
+ '/' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '/mui',
+ $formula,
+ $splitRanges,
+ PREG_OFFSET_CAPTURE
+ );
+
+ $lengths = array_map('strlen', array_column($splitRanges[0], 0));
+ $offsets = array_column($splitRanges[0], 1);
+ $values = array_column($splitRanges[0], 0);
+
+ while ($splitCount > 0) {
+ --$splitCount;
+ $length = $lengths[$splitCount];
+ $offset = $offsets[$splitCount];
+ $value = $values[$splitCount];
+
+ if (in_array($value, $this->definedNames, true)) {
+ $formula = substr($formula, 0, $offset) . '$$' . $value . substr($formula, $offset + $length);
+ }
+ }
+
+ return $formula;
+ }
+
+ private function convertCellReferences(string $formula, string $worksheetName): string
+ {
+ $splitCount = preg_match_all(
+ '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui',
+ $formula,
+ $splitRanges,
+ PREG_OFFSET_CAPTURE
+ );
+
+ $lengths = array_map('strlen', array_column($splitRanges[0], 0));
+ $offsets = array_column($splitRanges[0], 1);
+
+ $worksheets = $splitRanges[2];
+ $columns = $splitRanges[6];
+ $rows = $splitRanges[7];
+
+ // Replace any commas in the formula with semi-colons for Ods
+ // If by chance there are commas in worksheet names, then they will be "fixed" again in the loop
+ // because we've already extracted worksheet names with our preg_match_all()
+ $formula = str_replace(',', ';', $formula);
+ while ($splitCount > 0) {
+ --$splitCount;
+ $length = $lengths[$splitCount];
+ $offset = $offsets[$splitCount];
+ $worksheet = $worksheets[$splitCount][0];
+ $column = $columns[$splitCount][0];
+ $row = $rows[$splitCount][0];
+
+ $newRange = '';
+ if (empty($worksheet)) {
+ if (($offset === 0) || ($formula[$offset - 1] !== ':')) {
+ // We need a worksheet
+ $worksheet = $worksheetName;
+ }
+ } else {
+ $worksheet = str_replace("''", "'", trim($worksheet, "'"));
+ }
+ if (!empty($worksheet)) {
+ $newRange = "['" . str_replace("'", "''", $worksheet) . "'";
+ } elseif (substr($formula, $offset - 1, 1) !== ':') {
+ $newRange = '[';
+ }
+ $newRange .= '.';
+
+ if (!empty($column)) {
+ $newRange .= $column;
+ }
+ if (!empty($row)) {
+ $newRange .= $row;
+ }
+ // close the wrapping [] unless this is the first part of a range
+ $newRange .= substr($formula, $offset + $length, 1) !== ':' ? ']' : '';
+
+ $formula = substr($formula, 0, $offset) . $newRange . substr($formula, $offset + $length);
+ }
+
+ return $formula;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php
new file mode 100644
index 0000000..67eafdb
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Meta.php
@@ -0,0 +1,75 @@
+getParentWriter()->getSpreadsheet();
+ }
+
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8');
+
+ // Meta
+ $objWriter->startElement('office:document-meta');
+
+ $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
+ $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
+ $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
+ $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
+ $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#');
+ $objWriter->writeAttribute('office:version', '1.2');
+
+ $objWriter->startElement('office:meta');
+
+ $objWriter->writeElement('meta:initial-creator', $spreadsheet->getProperties()->getCreator());
+ $objWriter->writeElement('dc:creator', $spreadsheet->getProperties()->getCreator());
+ $objWriter->writeElement('meta:creation-date', date(DATE_W3C, $spreadsheet->getProperties()->getCreated()));
+ $objWriter->writeElement('dc:date', date(DATE_W3C, $spreadsheet->getProperties()->getCreated()));
+ $objWriter->writeElement('dc:title', $spreadsheet->getProperties()->getTitle());
+ $objWriter->writeElement('dc:description', $spreadsheet->getProperties()->getDescription());
+ $objWriter->writeElement('dc:subject', $spreadsheet->getProperties()->getSubject());
+ $keywords = explode(' ', $spreadsheet->getProperties()->getKeywords());
+ foreach ($keywords as $keyword) {
+ $objWriter->writeElement('meta:keyword', $keyword);
+ }
+
+ //
+ $objWriter->startElement('meta:user-defined');
+ $objWriter->writeAttribute('meta:name', 'Company');
+ $objWriter->writeRaw($spreadsheet->getProperties()->getCompany());
+ $objWriter->endElement();
+
+ $objWriter->startElement('meta:user-defined');
+ $objWriter->writeAttribute('meta:name', 'category');
+ $objWriter->writeRaw($spreadsheet->getProperties()->getCategory());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php
new file mode 100644
index 0000000..aafa58b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/MetaInf.php
@@ -0,0 +1,60 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8');
+
+ // Manifest
+ $objWriter->startElement('manifest:manifest');
+ $objWriter->writeAttribute('xmlns:manifest', 'urn:oasis:names:tc:opendocument:xmlns:manifest:1.0');
+ $objWriter->writeAttribute('manifest:version', '1.2');
+
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', '/');
+ $objWriter->writeAttribute('manifest:version', '1.2');
+ $objWriter->writeAttribute('manifest:media-type', 'application/vnd.oasis.opendocument.spreadsheet');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'meta.xml');
+ $objWriter->writeAttribute('manifest:media-type', 'text/xml');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'settings.xml');
+ $objWriter->writeAttribute('manifest:media-type', 'text/xml');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'content.xml');
+ $objWriter->writeAttribute('manifest:media-type', 'text/xml');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'Thumbnails/thumbnail.png');
+ $objWriter->writeAttribute('manifest:media-type', 'image/png');
+ $objWriter->endElement();
+ $objWriter->startElement('manifest:file-entry');
+ $objWriter->writeAttribute('manifest:full-path', 'styles.xml');
+ $objWriter->writeAttribute('manifest:media-type', 'text/xml');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php
new file mode 100644
index 0000000..2840a60
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Mimetype.php
@@ -0,0 +1,20 @@
+objWriter = $objWriter;
+ $this->spreadsheet = $spreadsheet;
+ $this->formulaConvertor = $formulaConvertor;
+ }
+
+ public function write(): void
+ {
+ $this->objWriter->startElement('table:named-expressions');
+ $this->writeExpressions();
+ $this->objWriter->endElement();
+ }
+
+ private function writeExpressions(): void
+ {
+ $definedNames = $this->spreadsheet->getDefinedNames();
+
+ foreach ($definedNames as $definedName) {
+ if ($definedName->isFormula()) {
+ $this->objWriter->startElement('table:named-expression');
+ $this->writeNamedFormula($definedName, $this->spreadsheet->getActiveSheet());
+ } else {
+ $this->objWriter->startElement('table:named-range');
+ $this->writeNamedRange($definedName);
+ }
+
+ $this->objWriter->endElement();
+ }
+ }
+
+ private function writeNamedFormula(DefinedName $definedName, Worksheet $defaultWorksheet): void
+ {
+ $this->objWriter->writeAttribute('table:name', $definedName->getName());
+ $this->objWriter->writeAttribute(
+ 'table:expression',
+ $this->formulaConvertor->convertFormula($definedName->getValue(), $definedName->getWorksheet()->getTitle())
+ );
+ $this->objWriter->writeAttribute('table:base-cell-address', $this->convertAddress(
+ $definedName,
+ "'" . (($definedName->getWorksheet() !== null) ? $definedName->getWorksheet()->getTitle() : $defaultWorksheet->getTitle()) . "'!\$A\$1"
+ ));
+ }
+
+ private function writeNamedRange(DefinedName $definedName): void
+ {
+ $this->objWriter->writeAttribute('table:name', $definedName->getName());
+ $this->objWriter->writeAttribute('table:base-cell-address', $this->convertAddress(
+ $definedName,
+ "'" . $definedName->getWorksheet()->getTitle() . "'!\$A\$1"
+ ));
+ $this->objWriter->writeAttribute('table:cell-range-address', $this->convertAddress($definedName, $definedName->getValue()));
+ }
+
+ private function convertAddress(DefinedName $definedName, string $address): string
+ {
+ $splitCount = preg_match_all(
+ '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui',
+ $address,
+ $splitRanges,
+ PREG_OFFSET_CAPTURE
+ );
+
+ $lengths = array_map('strlen', array_column($splitRanges[0], 0));
+ $offsets = array_column($splitRanges[0], 1);
+
+ $worksheets = $splitRanges[2];
+ $columns = $splitRanges[6];
+ $rows = $splitRanges[7];
+
+ while ($splitCount > 0) {
+ --$splitCount;
+ $length = $lengths[$splitCount];
+ $offset = $offsets[$splitCount];
+ $worksheet = $worksheets[$splitCount][0];
+ $column = $columns[$splitCount][0];
+ $row = $rows[$splitCount][0];
+
+ $newRange = '';
+ if (empty($worksheet)) {
+ if (($offset === 0) || ($address[$offset - 1] !== ':')) {
+ // We need a worksheet
+ $worksheet = $definedName->getWorksheet()->getTitle();
+ }
+ } else {
+ $worksheet = str_replace("''", "'", trim($worksheet, "'"));
+ }
+ if (!empty($worksheet)) {
+ $newRange = "'" . str_replace("'", "''", $worksheet) . "'.";
+ }
+
+ if (!empty($column)) {
+ $newRange .= $column;
+ }
+ if (!empty($row)) {
+ $newRange .= $row;
+ }
+
+ $address = substr($address, 0, $offset) . $newRange . substr($address, $offset + $length);
+ }
+
+ if (substr($address, 0, 1) === '=') {
+ $address = substr($address, 1);
+ }
+
+ return $address;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php
new file mode 100644
index 0000000..4b562e5
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Settings.php
@@ -0,0 +1,52 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8');
+
+ // Settings
+ $objWriter->startElement('office:document-settings');
+ $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
+ $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $objWriter->writeAttribute('xmlns:config', 'urn:oasis:names:tc:opendocument:xmlns:config:1.0');
+ $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
+ $objWriter->writeAttribute('office:version', '1.2');
+
+ $objWriter->startElement('office:settings');
+ $objWriter->startElement('config:config-item-set');
+ $objWriter->writeAttribute('config:name', 'ooo:view-settings');
+ $objWriter->startElement('config:config-item-map-indexed');
+ $objWriter->writeAttribute('config:name', 'Views');
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->startElement('config:config-item-set');
+ $objWriter->writeAttribute('config:name', 'ooo:configuration-settings');
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php
new file mode 100644
index 0000000..e492a7a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Styles.php
@@ -0,0 +1,68 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8');
+
+ // Content
+ $objWriter->startElement('office:document-styles');
+ $objWriter->writeAttribute('xmlns:office', 'urn:oasis:names:tc:opendocument:xmlns:office:1.0');
+ $objWriter->writeAttribute('xmlns:style', 'urn:oasis:names:tc:opendocument:xmlns:style:1.0');
+ $objWriter->writeAttribute('xmlns:text', 'urn:oasis:names:tc:opendocument:xmlns:text:1.0');
+ $objWriter->writeAttribute('xmlns:table', 'urn:oasis:names:tc:opendocument:xmlns:table:1.0');
+ $objWriter->writeAttribute('xmlns:draw', 'urn:oasis:names:tc:opendocument:xmlns:drawing:1.0');
+ $objWriter->writeAttribute('xmlns:fo', 'urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0');
+ $objWriter->writeAttribute('xmlns:xlink', 'http://www.w3.org/1999/xlink');
+ $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
+ $objWriter->writeAttribute('xmlns:meta', 'urn:oasis:names:tc:opendocument:xmlns:meta:1.0');
+ $objWriter->writeAttribute('xmlns:number', 'urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0');
+ $objWriter->writeAttribute('xmlns:presentation', 'urn:oasis:names:tc:opendocument:xmlns:presentation:1.0');
+ $objWriter->writeAttribute('xmlns:svg', 'urn:oasis:names:tc:opendocument:xmlns:svg-compatible:1.0');
+ $objWriter->writeAttribute('xmlns:chart', 'urn:oasis:names:tc:opendocument:xmlns:chart:1.0');
+ $objWriter->writeAttribute('xmlns:dr3d', 'urn:oasis:names:tc:opendocument:xmlns:dr3d:1.0');
+ $objWriter->writeAttribute('xmlns:math', 'http://www.w3.org/1998/Math/MathML');
+ $objWriter->writeAttribute('xmlns:form', 'urn:oasis:names:tc:opendocument:xmlns:form:1.0');
+ $objWriter->writeAttribute('xmlns:script', 'urn:oasis:names:tc:opendocument:xmlns:script:1.0');
+ $objWriter->writeAttribute('xmlns:ooo', 'http://openoffice.org/2004/office');
+ $objWriter->writeAttribute('xmlns:ooow', 'http://openoffice.org/2004/writer');
+ $objWriter->writeAttribute('xmlns:oooc', 'http://openoffice.org/2004/calc');
+ $objWriter->writeAttribute('xmlns:dom', 'http://www.w3.org/2001/xml-events');
+ $objWriter->writeAttribute('xmlns:rpt', 'http://openoffice.org/2005/report');
+ $objWriter->writeAttribute('xmlns:of', 'urn:oasis:names:tc:opendocument:xmlns:of:1.2');
+ $objWriter->writeAttribute('xmlns:xhtml', 'http://www.w3.org/1999/xhtml');
+ $objWriter->writeAttribute('xmlns:grddl', 'http://www.w3.org/2003/g/data-view#');
+ $objWriter->writeAttribute('xmlns:tableooo', 'http://openoffice.org/2009/table');
+ $objWriter->writeAttribute('xmlns:css3t', 'http://www.w3.org/TR/css3-text/');
+ $objWriter->writeAttribute('office:version', '1.2');
+
+ $objWriter->writeElement('office:font-face-decls');
+ $objWriter->writeElement('office:styles');
+ $objWriter->writeElement('office:automatic-styles');
+ $objWriter->writeElement('office:master-styles');
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php
new file mode 100644
index 0000000..76d1395
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Ods/Thumbnails.php
@@ -0,0 +1,20 @@
+parentWriter;
+ }
+
+ /**
+ * Set parent Ods writer.
+ */
+ public function __construct(Ods $writer)
+ {
+ $this->parentWriter = $writer;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php
new file mode 100644
index 0000000..7777f40
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf.php
@@ -0,0 +1,253 @@
+ 'LETTER', // (8.5 in. by 11 in.)
+ PageSetup::PAPERSIZE_LETTER_SMALL => 'LETTER', // (8.5 in. by 11 in.)
+ PageSetup::PAPERSIZE_TABLOID => [792.00, 1224.00], // (11 in. by 17 in.)
+ PageSetup::PAPERSIZE_LEDGER => [1224.00, 792.00], // (17 in. by 11 in.)
+ PageSetup::PAPERSIZE_LEGAL => 'LEGAL', // (8.5 in. by 14 in.)
+ PageSetup::PAPERSIZE_STATEMENT => [396.00, 612.00], // (5.5 in. by 8.5 in.)
+ PageSetup::PAPERSIZE_EXECUTIVE => 'EXECUTIVE', // (7.25 in. by 10.5 in.)
+ PageSetup::PAPERSIZE_A3 => 'A3', // (297 mm by 420 mm)
+ PageSetup::PAPERSIZE_A4 => 'A4', // (210 mm by 297 mm)
+ PageSetup::PAPERSIZE_A4_SMALL => 'A4', // (210 mm by 297 mm)
+ PageSetup::PAPERSIZE_A5 => 'A5', // (148 mm by 210 mm)
+ PageSetup::PAPERSIZE_B4 => 'B4', // (250 mm by 353 mm)
+ PageSetup::PAPERSIZE_B5 => 'B5', // (176 mm by 250 mm)
+ PageSetup::PAPERSIZE_FOLIO => 'FOLIO', // (8.5 in. by 13 in.)
+ PageSetup::PAPERSIZE_QUARTO => [609.45, 779.53], // (215 mm by 275 mm)
+ PageSetup::PAPERSIZE_STANDARD_1 => [720.00, 1008.00], // (10 in. by 14 in.)
+ PageSetup::PAPERSIZE_STANDARD_2 => [792.00, 1224.00], // (11 in. by 17 in.)
+ PageSetup::PAPERSIZE_NOTE => 'LETTER', // (8.5 in. by 11 in.)
+ PageSetup::PAPERSIZE_NO9_ENVELOPE => [279.00, 639.00], // (3.875 in. by 8.875 in.)
+ PageSetup::PAPERSIZE_NO10_ENVELOPE => [297.00, 684.00], // (4.125 in. by 9.5 in.)
+ PageSetup::PAPERSIZE_NO11_ENVELOPE => [324.00, 747.00], // (4.5 in. by 10.375 in.)
+ PageSetup::PAPERSIZE_NO12_ENVELOPE => [342.00, 792.00], // (4.75 in. by 11 in.)
+ PageSetup::PAPERSIZE_NO14_ENVELOPE => [360.00, 828.00], // (5 in. by 11.5 in.)
+ PageSetup::PAPERSIZE_C => [1224.00, 1584.00], // (17 in. by 22 in.)
+ PageSetup::PAPERSIZE_D => [1584.00, 2448.00], // (22 in. by 34 in.)
+ PageSetup::PAPERSIZE_E => [2448.00, 3168.00], // (34 in. by 44 in.)
+ PageSetup::PAPERSIZE_DL_ENVELOPE => [311.81, 623.62], // (110 mm by 220 mm)
+ PageSetup::PAPERSIZE_C5_ENVELOPE => 'C5', // (162 mm by 229 mm)
+ PageSetup::PAPERSIZE_C3_ENVELOPE => 'C3', // (324 mm by 458 mm)
+ PageSetup::PAPERSIZE_C4_ENVELOPE => 'C4', // (229 mm by 324 mm)
+ PageSetup::PAPERSIZE_C6_ENVELOPE => 'C6', // (114 mm by 162 mm)
+ PageSetup::PAPERSIZE_C65_ENVELOPE => [323.15, 649.13], // (114 mm by 229 mm)
+ PageSetup::PAPERSIZE_B4_ENVELOPE => 'B4', // (250 mm by 353 mm)
+ PageSetup::PAPERSIZE_B5_ENVELOPE => 'B5', // (176 mm by 250 mm)
+ PageSetup::PAPERSIZE_B6_ENVELOPE => [498.90, 354.33], // (176 mm by 125 mm)
+ PageSetup::PAPERSIZE_ITALY_ENVELOPE => [311.81, 651.97], // (110 mm by 230 mm)
+ PageSetup::PAPERSIZE_MONARCH_ENVELOPE => [279.00, 540.00], // (3.875 in. by 7.5 in.)
+ PageSetup::PAPERSIZE_6_3_4_ENVELOPE => [261.00, 468.00], // (3.625 in. by 6.5 in.)
+ PageSetup::PAPERSIZE_US_STANDARD_FANFOLD => [1071.00, 792.00], // (14.875 in. by 11 in.)
+ PageSetup::PAPERSIZE_GERMAN_STANDARD_FANFOLD => [612.00, 864.00], // (8.5 in. by 12 in.)
+ PageSetup::PAPERSIZE_GERMAN_LEGAL_FANFOLD => 'FOLIO', // (8.5 in. by 13 in.)
+ PageSetup::PAPERSIZE_ISO_B4 => 'B4', // (250 mm by 353 mm)
+ PageSetup::PAPERSIZE_JAPANESE_DOUBLE_POSTCARD => [566.93, 419.53], // (200 mm by 148 mm)
+ PageSetup::PAPERSIZE_STANDARD_PAPER_1 => [648.00, 792.00], // (9 in. by 11 in.)
+ PageSetup::PAPERSIZE_STANDARD_PAPER_2 => [720.00, 792.00], // (10 in. by 11 in.)
+ PageSetup::PAPERSIZE_STANDARD_PAPER_3 => [1080.00, 792.00], // (15 in. by 11 in.)
+ PageSetup::PAPERSIZE_INVITE_ENVELOPE => [623.62, 623.62], // (220 mm by 220 mm)
+ PageSetup::PAPERSIZE_LETTER_EXTRA_PAPER => [667.80, 864.00], // (9.275 in. by 12 in.)
+ PageSetup::PAPERSIZE_LEGAL_EXTRA_PAPER => [667.80, 1080.00], // (9.275 in. by 15 in.)
+ PageSetup::PAPERSIZE_TABLOID_EXTRA_PAPER => [841.68, 1296.00], // (11.69 in. by 18 in.)
+ PageSetup::PAPERSIZE_A4_EXTRA_PAPER => [668.98, 912.76], // (236 mm by 322 mm)
+ PageSetup::PAPERSIZE_LETTER_TRANSVERSE_PAPER => [595.80, 792.00], // (8.275 in. by 11 in.)
+ PageSetup::PAPERSIZE_A4_TRANSVERSE_PAPER => 'A4', // (210 mm by 297 mm)
+ PageSetup::PAPERSIZE_LETTER_EXTRA_TRANSVERSE_PAPER => [667.80, 864.00], // (9.275 in. by 12 in.)
+ PageSetup::PAPERSIZE_SUPERA_SUPERA_A4_PAPER => [643.46, 1009.13], // (227 mm by 356 mm)
+ PageSetup::PAPERSIZE_SUPERB_SUPERB_A3_PAPER => [864.57, 1380.47], // (305 mm by 487 mm)
+ PageSetup::PAPERSIZE_LETTER_PLUS_PAPER => [612.00, 913.68], // (8.5 in. by 12.69 in.)
+ PageSetup::PAPERSIZE_A4_PLUS_PAPER => [595.28, 935.43], // (210 mm by 330 mm)
+ PageSetup::PAPERSIZE_A5_TRANSVERSE_PAPER => 'A5', // (148 mm by 210 mm)
+ PageSetup::PAPERSIZE_JIS_B5_TRANSVERSE_PAPER => [515.91, 728.50], // (182 mm by 257 mm)
+ PageSetup::PAPERSIZE_A3_EXTRA_PAPER => [912.76, 1261.42], // (322 mm by 445 mm)
+ PageSetup::PAPERSIZE_A5_EXTRA_PAPER => [493.23, 666.14], // (174 mm by 235 mm)
+ PageSetup::PAPERSIZE_ISO_B5_EXTRA_PAPER => [569.76, 782.36], // (201 mm by 276 mm)
+ PageSetup::PAPERSIZE_A2_PAPER => 'A2', // (420 mm by 594 mm)
+ PageSetup::PAPERSIZE_A3_TRANSVERSE_PAPER => 'A3', // (297 mm by 420 mm)
+ PageSetup::PAPERSIZE_A3_EXTRA_TRANSVERSE_PAPER => [912.76, 1261.42], // (322 mm by 445 mm)
+ ];
+
+ /**
+ * Create a new PDF Writer instance.
+ *
+ * @param Spreadsheet $spreadsheet Spreadsheet object
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ parent::__construct($spreadsheet);
+ //$this->setUseInlineCss(true);
+ $this->tempDir = File::sysGetTempDir() . '/phpsppdf';
+ $this->isPdf = true;
+ }
+
+ /**
+ * Get Font.
+ *
+ * @return string
+ */
+ public function getFont()
+ {
+ return $this->font;
+ }
+
+ /**
+ * Set font. Examples:
+ * 'arialunicid0-chinese-simplified'
+ * 'arialunicid0-chinese-traditional'
+ * 'arialunicid0-korean'
+ * 'arialunicid0-japanese'.
+ *
+ * @param string $fontName
+ *
+ * @return $this
+ */
+ public function setFont($fontName)
+ {
+ $this->font = $fontName;
+
+ return $this;
+ }
+
+ /**
+ * Get Paper Size.
+ *
+ * @return int
+ */
+ public function getPaperSize()
+ {
+ return $this->paperSize;
+ }
+
+ /**
+ * Set Paper Size.
+ *
+ * @param string $pValue Paper size see PageSetup::PAPERSIZE_*
+ *
+ * @return self
+ */
+ public function setPaperSize($pValue)
+ {
+ $this->paperSize = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get Orientation.
+ *
+ * @return string
+ */
+ public function getOrientation()
+ {
+ return $this->orientation;
+ }
+
+ /**
+ * Set Orientation.
+ *
+ * @param string $pValue Page orientation see PageSetup::ORIENTATION_*
+ *
+ * @return self
+ */
+ public function setOrientation($pValue)
+ {
+ $this->orientation = $pValue;
+
+ return $this;
+ }
+
+ /**
+ * Get temporary storage directory.
+ *
+ * @return string
+ */
+ public function getTempDir()
+ {
+ return $this->tempDir;
+ }
+
+ /**
+ * Set temporary storage directory.
+ *
+ * @param string $pValue Temporary storage directory
+ *
+ * @return self
+ */
+ public function setTempDir($pValue)
+ {
+ if (is_dir($pValue)) {
+ $this->tempDir = $pValue;
+ } else {
+ throw new WriterException("Directory does not exist: $pValue");
+ }
+
+ return $this;
+ }
+
+ /**
+ * Save Spreadsheet to PDF file, pre-save.
+ *
+ * @param string $pFilename Name of the file to save as
+ *
+ * @return resource
+ */
+ protected function prepareForSave($pFilename)
+ {
+ // Open file
+ $this->openFileHandle($pFilename);
+
+ return $this->fileHandle;
+ }
+
+ /**
+ * Save PhpSpreadsheet to PDF file, post-save.
+ */
+ protected function restoreStateAfterSave(): void
+ {
+ $this->maybeCloseFileHandle();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php
new file mode 100644
index 0000000..721eead
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Dompdf.php
@@ -0,0 +1,72 @@
+getSheetIndex() === null) {
+ $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize();
+ } else {
+ $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize();
+ }
+
+ $orientation = ($orientation == 'L') ? 'landscape' : 'portrait';
+
+ // Override Page Orientation
+ if ($this->getOrientation() !== null) {
+ $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_DEFAULT)
+ ? PageSetup::ORIENTATION_PORTRAIT
+ : $this->getOrientation();
+ }
+ // Override Paper Size
+ if ($this->getPaperSize() !== null) {
+ $printPaperSize = $this->getPaperSize();
+ }
+
+ if (isset(self::$paperSizes[$printPaperSize])) {
+ $paperSize = self::$paperSizes[$printPaperSize];
+ }
+
+ // Create PDF
+ $pdf = $this->createExternalWriterInstance();
+ $pdf->setPaper(strtolower($paperSize), $orientation);
+
+ $pdf->loadHtml($this->generateHTMLAll());
+ $pdf->render();
+
+ // Write to file
+ fwrite($fileHandle, $pdf->output());
+
+ parent::restoreStateAfterSave();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php
new file mode 100644
index 0000000..b0b4c03
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Mpdf.php
@@ -0,0 +1,106 @@
+getSheetIndex()) {
+ $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize();
+ } else {
+ $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize();
+ }
+ $this->setOrientation($orientation);
+
+ // Override Page Orientation
+ if (null !== $this->getOrientation()) {
+ $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_DEFAULT)
+ ? PageSetup::ORIENTATION_PORTRAIT
+ : $this->getOrientation();
+ }
+ $orientation = strtoupper($orientation);
+
+ // Override Paper Size
+ if (null !== $this->getPaperSize()) {
+ $printPaperSize = $this->getPaperSize();
+ }
+
+ if (isset(self::$paperSizes[$printPaperSize])) {
+ $paperSize = self::$paperSizes[$printPaperSize];
+ }
+
+ // Create PDF
+ $config = ['tempDir' => $this->tempDir . '/mpdf'];
+ $pdf = $this->createExternalWriterInstance($config);
+ $ortmp = $orientation;
+ $pdf->_setPageSize(strtoupper($paperSize), $ortmp);
+ $pdf->DefOrientation = $orientation;
+ $pdf->AddPageByArray([
+ 'orientation' => $orientation,
+ 'margin-left' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getLeft()),
+ 'margin-right' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getRight()),
+ 'margin-top' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getTop()),
+ 'margin-bottom' => $this->inchesToMm($this->spreadsheet->getActiveSheet()->getPageMargins()->getBottom()),
+ ]);
+
+ // Document info
+ $pdf->SetTitle($this->spreadsheet->getProperties()->getTitle());
+ $pdf->SetAuthor($this->spreadsheet->getProperties()->getCreator());
+ $pdf->SetSubject($this->spreadsheet->getProperties()->getSubject());
+ $pdf->SetKeywords($this->spreadsheet->getProperties()->getKeywords());
+ $pdf->SetCreator($this->spreadsheet->getProperties()->getCreator());
+
+ $html = $this->generateHTMLAll();
+ foreach (\array_chunk(\explode(PHP_EOL, $html), 1000) as $lines) {
+ $pdf->WriteHTML(\implode(PHP_EOL, $lines));
+ }
+
+ // Write to file
+ fwrite($fileHandle, $pdf->Output('', 'S'));
+
+ parent::restoreStateAfterSave();
+ }
+
+ /**
+ * Convert inches to mm.
+ *
+ * @param float $inches
+ *
+ * @return float
+ */
+ private function inchesToMm($inches)
+ {
+ return $inches * 25.4;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php
new file mode 100644
index 0000000..1ebae0e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Pdf/Tcpdf.php
@@ -0,0 +1,104 @@
+setUseInlineCss(true);
+ }
+
+ /**
+ * Gets the implementation of external PDF library that should be used.
+ *
+ * @param string $orientation Page orientation
+ * @param string $unit Unit measure
+ * @param string $paperSize Paper size
+ *
+ * @return \TCPDF implementation
+ */
+ protected function createExternalWriterInstance($orientation, $unit, $paperSize)
+ {
+ return new \TCPDF($orientation, $unit, $paperSize);
+ }
+
+ /**
+ * Save Spreadsheet to file.
+ *
+ * @param string $pFilename Name of the file to save as
+ */
+ public function save($pFilename): void
+ {
+ $fileHandle = parent::prepareForSave($pFilename);
+
+ // Default PDF paper size
+ $paperSize = 'LETTER'; // Letter (8.5 in. by 11 in.)
+
+ // Check for paper size and page orientation
+ if ($this->getSheetIndex() === null) {
+ $orientation = ($this->spreadsheet->getSheet(0)->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet(0)->getPageSetup()->getPaperSize();
+ $printMargins = $this->spreadsheet->getSheet(0)->getPageMargins();
+ } else {
+ $orientation = ($this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getOrientation()
+ == PageSetup::ORIENTATION_LANDSCAPE) ? 'L' : 'P';
+ $printPaperSize = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageSetup()->getPaperSize();
+ $printMargins = $this->spreadsheet->getSheet($this->getSheetIndex())->getPageMargins();
+ }
+
+ // Override Page Orientation
+ if ($this->getOrientation() !== null) {
+ $orientation = ($this->getOrientation() == PageSetup::ORIENTATION_LANDSCAPE)
+ ? 'L'
+ : 'P';
+ }
+ // Override Paper Size
+ if ($this->getPaperSize() !== null) {
+ $printPaperSize = $this->getPaperSize();
+ }
+
+ if (isset(self::$paperSizes[$printPaperSize])) {
+ $paperSize = self::$paperSizes[$printPaperSize];
+ }
+
+ // Create PDF
+ $pdf = $this->createExternalWriterInstance($orientation, 'pt', $paperSize);
+ $pdf->setFontSubsetting(false);
+ // Set margins, converting inches to points (using 72 dpi)
+ $pdf->SetMargins($printMargins->getLeft() * 72, $printMargins->getTop() * 72, $printMargins->getRight() * 72);
+ $pdf->SetAutoPageBreak(true, $printMargins->getBottom() * 72);
+
+ $pdf->setPrintHeader(false);
+ $pdf->setPrintFooter(false);
+
+ $pdf->AddPage();
+
+ // Set the appropriate font
+ $pdf->SetFont($this->getFont());
+ $pdf->writeHTML($this->generateHTMLAll());
+
+ // Document info
+ $pdf->SetTitle($this->spreadsheet->getProperties()->getTitle());
+ $pdf->SetAuthor($this->spreadsheet->getProperties()->getCreator());
+ $pdf->SetSubject($this->spreadsheet->getProperties()->getSubject());
+ $pdf->SetKeywords($this->spreadsheet->getProperties()->getKeywords());
+ $pdf->SetCreator($this->spreadsheet->getProperties()->getCreator());
+
+ // Write to file
+ fwrite($fileHandle, $pdf->output($pFilename, 'S'));
+
+ parent::restoreStateAfterSave();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php
new file mode 100644
index 0000000..ceadc8b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php
@@ -0,0 +1,901 @@
+spreadsheet = $spreadsheet;
+
+ $this->parser = new Xls\Parser($spreadsheet);
+ }
+
+ /**
+ * Save Spreadsheet to file.
+ *
+ * @param resource|string $pFilename
+ */
+ public function save($pFilename): void
+ {
+ // garbage collect
+ $this->spreadsheet->garbageCollect();
+
+ $saveDebugLog = Calculation::getInstance($this->spreadsheet)->getDebugLog()->getWriteDebugLog();
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog(false);
+ $saveDateReturnType = Functions::getReturnDateType();
+ Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
+
+ // initialize colors array
+ $this->colors = [];
+
+ // Initialise workbook writer
+ $this->writerWorkbook = new Xls\Workbook($this->spreadsheet, $this->strTotal, $this->strUnique, $this->strTable, $this->colors, $this->parser);
+
+ // Initialise worksheet writers
+ $countSheets = $this->spreadsheet->getSheetCount();
+ for ($i = 0; $i < $countSheets; ++$i) {
+ $this->writerWorksheets[$i] = new Xls\Worksheet($this->strTotal, $this->strUnique, $this->strTable, $this->colors, $this->parser, $this->preCalculateFormulas, $this->spreadsheet->getSheet($i));
+ }
+
+ // build Escher objects. Escher objects for workbooks needs to be build before Escher object for workbook.
+ $this->buildWorksheetEschers();
+ $this->buildWorkbookEscher();
+
+ // add 15 identical cell style Xfs
+ // for now, we use the first cellXf instead of cellStyleXf
+ $cellXfCollection = $this->spreadsheet->getCellXfCollection();
+ for ($i = 0; $i < 15; ++$i) {
+ $this->writerWorkbook->addXfWriter($cellXfCollection[0], true);
+ }
+
+ // add all the cell Xfs
+ foreach ($this->spreadsheet->getCellXfCollection() as $style) {
+ $this->writerWorkbook->addXfWriter($style, false);
+ }
+
+ // add fonts from rich text eleemnts
+ for ($i = 0; $i < $countSheets; ++$i) {
+ foreach ($this->writerWorksheets[$i]->phpSheet->getCoordinates() as $coordinate) {
+ $cell = $this->writerWorksheets[$i]->phpSheet->getCell($coordinate);
+ $cVal = $cell->getValue();
+ if ($cVal instanceof RichText) {
+ $elements = $cVal->getRichTextElements();
+ foreach ($elements as $element) {
+ if ($element instanceof Run) {
+ $font = $element->getFont();
+ $this->writerWorksheets[$i]->fontHashIndex[$font->getHashCode()] = $this->writerWorkbook->addFont($font);
+ }
+ }
+ }
+ }
+ }
+
+ // initialize OLE file
+ $workbookStreamName = 'Workbook';
+ $OLE = new File(OLE::ascToUcs($workbookStreamName));
+
+ // Write the worksheet streams before the global workbook stream,
+ // because the byte sizes of these are needed in the global workbook stream
+ $worksheetSizes = [];
+ for ($i = 0; $i < $countSheets; ++$i) {
+ $this->writerWorksheets[$i]->close();
+ $worksheetSizes[] = $this->writerWorksheets[$i]->_datasize;
+ }
+
+ // add binary data for global workbook stream
+ $OLE->append($this->writerWorkbook->writeWorkbook($worksheetSizes));
+
+ // add binary data for sheet streams
+ for ($i = 0; $i < $countSheets; ++$i) {
+ $OLE->append($this->writerWorksheets[$i]->getData());
+ }
+
+ $this->documentSummaryInformation = $this->writeDocumentSummaryInformation();
+ // initialize OLE Document Summary Information
+ if (isset($this->documentSummaryInformation) && !empty($this->documentSummaryInformation)) {
+ $OLE_DocumentSummaryInformation = new File(OLE::ascToUcs(chr(5) . 'DocumentSummaryInformation'));
+ $OLE_DocumentSummaryInformation->append($this->documentSummaryInformation);
+ }
+
+ $this->summaryInformation = $this->writeSummaryInformation();
+ // initialize OLE Summary Information
+ if (isset($this->summaryInformation) && !empty($this->summaryInformation)) {
+ $OLE_SummaryInformation = new File(OLE::ascToUcs(chr(5) . 'SummaryInformation'));
+ $OLE_SummaryInformation->append($this->summaryInformation);
+ }
+
+ // define OLE Parts
+ $arrRootData = [$OLE];
+ // initialize OLE Properties file
+ if (isset($OLE_SummaryInformation)) {
+ $arrRootData[] = $OLE_SummaryInformation;
+ }
+ // initialize OLE Extended Properties file
+ if (isset($OLE_DocumentSummaryInformation)) {
+ $arrRootData[] = $OLE_DocumentSummaryInformation;
+ }
+
+ $root = new Root(time(), time(), $arrRootData);
+ // save the OLE file
+ $this->openFileHandle($pFilename);
+ $root->save($this->fileHandle);
+ $this->maybeCloseFileHandle();
+
+ Functions::setReturnDateType($saveDateReturnType);
+ Calculation::getInstance($this->spreadsheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
+ }
+
+ /**
+ * Build the Worksheet Escher objects.
+ */
+ private function buildWorksheetEschers(): void
+ {
+ // 1-based index to BstoreContainer
+ $blipIndex = 0;
+ $lastReducedSpId = 0;
+ $lastSpId = 0;
+
+ foreach ($this->spreadsheet->getAllsheets() as $sheet) {
+ // sheet index
+ $sheetIndex = $sheet->getParent()->getIndex($sheet);
+
+ $escher = null;
+
+ // check if there are any shapes for this sheet
+ $filterRange = $sheet->getAutoFilter()->getRange();
+ if (count($sheet->getDrawingCollection()) == 0 && empty($filterRange)) {
+ continue;
+ }
+
+ // create intermediate Escher object
+ $escher = new Escher();
+
+ // dgContainer
+ $dgContainer = new DgContainer();
+
+ // set the drawing index (we use sheet index + 1)
+ $dgId = $sheet->getParent()->getIndex($sheet) + 1;
+ $dgContainer->setDgId($dgId);
+ $escher->setDgContainer($dgContainer);
+
+ // spgrContainer
+ $spgrContainer = new SpgrContainer();
+ $dgContainer->setSpgrContainer($spgrContainer);
+
+ // add one shape which is the group shape
+ $spContainer = new SpContainer();
+ $spContainer->setSpgr(true);
+ $spContainer->setSpType(0);
+ $spContainer->setSpId(($sheet->getParent()->getIndex($sheet) + 1) << 10);
+ $spgrContainer->addChild($spContainer);
+
+ // add the shapes
+
+ $countShapes[$sheetIndex] = 0; // count number of shapes (minus group shape), in sheet
+
+ foreach ($sheet->getDrawingCollection() as $drawing) {
+ ++$blipIndex;
+
+ ++$countShapes[$sheetIndex];
+
+ // add the shape
+ $spContainer = new SpContainer();
+
+ // set the shape type
+ $spContainer->setSpType(0x004B);
+ // set the shape flag
+ $spContainer->setSpFlag(0x02);
+
+ // set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index)
+ $reducedSpId = $countShapes[$sheetIndex];
+ $spId = $reducedSpId | ($sheet->getParent()->getIndex($sheet) + 1) << 10;
+ $spContainer->setSpId($spId);
+
+ // keep track of last reducedSpId
+ $lastReducedSpId = $reducedSpId;
+
+ // keep track of last spId
+ $lastSpId = $spId;
+
+ // set the BLIP index
+ $spContainer->setOPT(0x4104, $blipIndex);
+
+ // set coordinates and offsets, client anchor
+ $coordinates = $drawing->getCoordinates();
+ $offsetX = $drawing->getOffsetX();
+ $offsetY = $drawing->getOffsetY();
+ $width = $drawing->getWidth();
+ $height = $drawing->getHeight();
+
+ $twoAnchor = \PhpOffice\PhpSpreadsheet\Shared\Xls::oneAnchor2twoAnchor($sheet, $coordinates, $offsetX, $offsetY, $width, $height);
+
+ $spContainer->setStartCoordinates($twoAnchor['startCoordinates']);
+ $spContainer->setStartOffsetX($twoAnchor['startOffsetX']);
+ $spContainer->setStartOffsetY($twoAnchor['startOffsetY']);
+ $spContainer->setEndCoordinates($twoAnchor['endCoordinates']);
+ $spContainer->setEndOffsetX($twoAnchor['endOffsetX']);
+ $spContainer->setEndOffsetY($twoAnchor['endOffsetY']);
+
+ $spgrContainer->addChild($spContainer);
+ }
+
+ // AutoFilters
+ if (!empty($filterRange)) {
+ $rangeBounds = Coordinate::rangeBoundaries($filterRange);
+ $iNumColStart = $rangeBounds[0][0];
+ $iNumColEnd = $rangeBounds[1][0];
+
+ $iInc = $iNumColStart;
+ while ($iInc <= $iNumColEnd) {
+ ++$countShapes[$sheetIndex];
+
+ // create an Drawing Object for the dropdown
+ $oDrawing = new BaseDrawing();
+ // get the coordinates of drawing
+ $cDrawing = Coordinate::stringFromColumnIndex($iInc) . $rangeBounds[0][1];
+ $oDrawing->setCoordinates($cDrawing);
+ $oDrawing->setWorksheet($sheet);
+
+ // add the shape
+ $spContainer = new SpContainer();
+ // set the shape type
+ $spContainer->setSpType(0x00C9);
+ // set the shape flag
+ $spContainer->setSpFlag(0x01);
+
+ // set the shape index (we combine 1-based sheet index and $countShapes to create unique shape index)
+ $reducedSpId = $countShapes[$sheetIndex];
+ $spId = $reducedSpId | ($sheet->getParent()->getIndex($sheet) + 1) << 10;
+ $spContainer->setSpId($spId);
+
+ // keep track of last reducedSpId
+ $lastReducedSpId = $reducedSpId;
+
+ // keep track of last spId
+ $lastSpId = $spId;
+
+ $spContainer->setOPT(0x007F, 0x01040104); // Protection -> fLockAgainstGrouping
+ $spContainer->setOPT(0x00BF, 0x00080008); // Text -> fFitTextToShape
+ $spContainer->setOPT(0x01BF, 0x00010000); // Fill Style -> fNoFillHitTest
+ $spContainer->setOPT(0x01FF, 0x00080000); // Line Style -> fNoLineDrawDash
+ $spContainer->setOPT(0x03BF, 0x000A0000); // Group Shape -> fPrint
+
+ // set coordinates and offsets, client anchor
+ $endCoordinates = Coordinate::stringFromColumnIndex($iInc);
+ $endCoordinates .= $rangeBounds[0][1] + 1;
+
+ $spContainer->setStartCoordinates($cDrawing);
+ $spContainer->setStartOffsetX(0);
+ $spContainer->setStartOffsetY(0);
+ $spContainer->setEndCoordinates($endCoordinates);
+ $spContainer->setEndOffsetX(0);
+ $spContainer->setEndOffsetY(0);
+
+ $spgrContainer->addChild($spContainer);
+ ++$iInc;
+ }
+ }
+
+ // identifier clusters, used for workbook Escher object
+ $this->IDCLs[$dgId] = $lastReducedSpId;
+
+ // set last shape index
+ $dgContainer->setLastSpId($lastSpId);
+
+ // set the Escher object
+ $this->writerWorksheets[$sheetIndex]->setEscher($escher);
+ }
+ }
+
+ private function processMemoryDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing, string $renderingFunctionx): void
+ {
+ switch ($renderingFunctionx) {
+ case MemoryDrawing::RENDERING_JPEG:
+ $blipType = BSE::BLIPTYPE_JPEG;
+ $renderingFunction = 'imagejpeg';
+
+ break;
+ default:
+ $blipType = BSE::BLIPTYPE_PNG;
+ $renderingFunction = 'imagepng';
+
+ break;
+ }
+
+ ob_start();
+ call_user_func($renderingFunction, $drawing->getImageResource());
+ $blipData = ob_get_contents();
+ ob_end_clean();
+
+ $blip = new Blip();
+ $blip->setData($blipData);
+
+ $BSE = new BSE();
+ $BSE->setBlipType($blipType);
+ $BSE->setBlip($blip);
+
+ $bstoreContainer->addBSE($BSE);
+ }
+
+ private function processDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing): void
+ {
+ $blipData = '';
+ $filename = $drawing->getPath();
+
+ [$imagesx, $imagesy, $imageFormat] = getimagesize($filename);
+
+ switch ($imageFormat) {
+ case 1: // GIF, not supported by BIFF8, we convert to PNG
+ $blipType = BSE::BLIPTYPE_PNG;
+ ob_start();
+ imagepng(imagecreatefromgif($filename));
+ $blipData = ob_get_contents();
+ ob_end_clean();
+
+ break;
+ case 2: // JPEG
+ $blipType = BSE::BLIPTYPE_JPEG;
+ $blipData = file_get_contents($filename);
+
+ break;
+ case 3: // PNG
+ $blipType = BSE::BLIPTYPE_PNG;
+ $blipData = file_get_contents($filename);
+
+ break;
+ case 6: // Windows DIB (BMP), we convert to PNG
+ $blipType = BSE::BLIPTYPE_PNG;
+ ob_start();
+ imagepng(SharedDrawing::imagecreatefrombmp($filename));
+ $blipData = ob_get_contents();
+ ob_end_clean();
+
+ break;
+ }
+ if ($blipData) {
+ $blip = new Blip();
+ $blip->setData($blipData);
+
+ $BSE = new BSE();
+ $BSE->setBlipType($blipType);
+ $BSE->setBlip($blip);
+
+ $bstoreContainer->addBSE($BSE);
+ }
+ }
+
+ private function processBaseDrawing(BstoreContainer &$bstoreContainer, BaseDrawing $drawing): void
+ {
+ if ($drawing instanceof Drawing) {
+ $this->processDrawing($bstoreContainer, $drawing);
+ } elseif ($drawing instanceof MemoryDrawing) {
+ $this->processMemoryDrawing($bstoreContainer, $drawing, $drawing->getRenderingFunction());
+ }
+ }
+
+ private function checkForDrawings(): bool
+ {
+ // any drawings in this workbook?
+ $found = false;
+ foreach ($this->spreadsheet->getAllSheets() as $sheet) {
+ if (count($sheet->getDrawingCollection()) > 0) {
+ $found = true;
+
+ break;
+ }
+ }
+
+ return $found;
+ }
+
+ /**
+ * Build the Escher object corresponding to the MSODRAWINGGROUP record.
+ */
+ private function buildWorkbookEscher(): void
+ {
+ // nothing to do if there are no drawings
+ if (!$this->checkForDrawings()) {
+ return;
+ }
+
+ // if we reach here, then there are drawings in the workbook
+ $escher = new Escher();
+
+ // dggContainer
+ $dggContainer = new DggContainer();
+ $escher->setDggContainer($dggContainer);
+
+ // set IDCLs (identifier clusters)
+ $dggContainer->setIDCLs($this->IDCLs);
+
+ // this loop is for determining maximum shape identifier of all drawing
+ $spIdMax = 0;
+ $totalCountShapes = 0;
+ $countDrawings = 0;
+
+ foreach ($this->spreadsheet->getAllsheets() as $sheet) {
+ $sheetCountShapes = 0; // count number of shapes (minus group shape), in sheet
+
+ $addCount = 0;
+ foreach ($sheet->getDrawingCollection() as $drawing) {
+ $addCount = 1;
+ ++$sheetCountShapes;
+ ++$totalCountShapes;
+
+ $spId = $sheetCountShapes | ($this->spreadsheet->getIndex($sheet) + 1) << 10;
+ $spIdMax = max($spId, $spIdMax);
+ }
+ $countDrawings += $addCount;
+ }
+
+ $dggContainer->setSpIdMax($spIdMax + 1);
+ $dggContainer->setCDgSaved($countDrawings);
+ $dggContainer->setCSpSaved($totalCountShapes + $countDrawings); // total number of shapes incl. one group shapes per drawing
+
+ // bstoreContainer
+ $bstoreContainer = new BstoreContainer();
+ $dggContainer->setBstoreContainer($bstoreContainer);
+
+ // the BSE's (all the images)
+ foreach ($this->spreadsheet->getAllsheets() as $sheet) {
+ foreach ($sheet->getDrawingCollection() as $drawing) {
+ $this->processBaseDrawing($bstoreContainer, $drawing);
+ }
+ }
+
+ // Set the Escher object
+ $this->writerWorkbook->setEscher($escher);
+ }
+
+ /**
+ * Build the OLE Part for DocumentSummary Information.
+ *
+ * @return string
+ */
+ private function writeDocumentSummaryInformation()
+ {
+ // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
+ $data = pack('v', 0xFFFE);
+ // offset: 2; size: 2;
+ $data .= pack('v', 0x0000);
+ // offset: 4; size: 2; OS version
+ $data .= pack('v', 0x0106);
+ // offset: 6; size: 2; OS indicator
+ $data .= pack('v', 0x0002);
+ // offset: 8; size: 16
+ $data .= pack('VVVV', 0x00, 0x00, 0x00, 0x00);
+ // offset: 24; size: 4; section count
+ $data .= pack('V', 0x0001);
+
+ // offset: 28; size: 16; first section's class id: 02 d5 cd d5 9c 2e 1b 10 93 97 08 00 2b 2c f9 ae
+ $data .= pack('vvvvvvvv', 0xD502, 0xD5CD, 0x2E9C, 0x101B, 0x9793, 0x0008, 0x2C2B, 0xAEF9);
+ // offset: 44; size: 4; offset of the start
+ $data .= pack('V', 0x30);
+
+ // SECTION
+ $dataSection = [];
+ $dataSection_NumProps = 0;
+ $dataSection_Summary = '';
+ $dataSection_Content = '';
+
+ // GKPIDDSI_CODEPAGE: CodePage
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x01],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x02], // 2 byte signed integer
+ 'data' => ['data' => 1252],
+ ];
+ ++$dataSection_NumProps;
+
+ // GKPIDDSI_CATEGORY : Category
+ $dataProp = $this->spreadsheet->getProperties()->getCategory();
+ if ($dataProp) {
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x02],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x1E],
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ // GKPIDDSI_VERSION :Version of the application that wrote the property storage
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x17],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x03],
+ 'data' => ['pack' => 'V', 'data' => 0x000C0000],
+ ];
+ ++$dataSection_NumProps;
+ // GKPIDDSI_SCALE : FALSE
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x0B],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x0B],
+ 'data' => ['data' => false],
+ ];
+ ++$dataSection_NumProps;
+ // GKPIDDSI_LINKSDIRTY : True if any of the values for the linked properties have changed outside of the application
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x10],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x0B],
+ 'data' => ['data' => false],
+ ];
+ ++$dataSection_NumProps;
+ // GKPIDDSI_SHAREDOC : FALSE
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x13],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x0B],
+ 'data' => ['data' => false],
+ ];
+ ++$dataSection_NumProps;
+ // GKPIDDSI_HYPERLINKSCHANGED : True if any of the values for the _PID_LINKS (hyperlink text) have changed outside of the application
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x16],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x0B],
+ 'data' => ['data' => false],
+ ];
+ ++$dataSection_NumProps;
+
+ // GKPIDDSI_DOCSPARTS
+ // MS-OSHARED p75 (2.3.3.2.2.1)
+ // Structure is VtVecUnalignedLpstrValue (2.3.3.1.9)
+ // cElements
+ $dataProp = pack('v', 0x0001);
+ $dataProp .= pack('v', 0x0000);
+ // array of UnalignedLpstr
+ // cch
+ $dataProp .= pack('v', 0x000A);
+ $dataProp .= pack('v', 0x0000);
+ // value
+ $dataProp .= 'Worksheet' . chr(0);
+
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x0D],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x101E],
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+
+ // GKPIDDSI_HEADINGPAIR
+ // VtVecHeadingPairValue
+ // cElements
+ $dataProp = pack('v', 0x0002);
+ $dataProp .= pack('v', 0x0000);
+ // Array of vtHeadingPair
+ // vtUnalignedString - headingString
+ // stringType
+ $dataProp .= pack('v', 0x001E);
+ // padding
+ $dataProp .= pack('v', 0x0000);
+ // UnalignedLpstr
+ // cch
+ $dataProp .= pack('v', 0x0013);
+ $dataProp .= pack('v', 0x0000);
+ // value
+ $dataProp .= 'Feuilles de calcul';
+ // vtUnalignedString - headingParts
+ // wType : 0x0003 = 32 bit signed integer
+ $dataProp .= pack('v', 0x0300);
+ // padding
+ $dataProp .= pack('v', 0x0000);
+ // value
+ $dataProp .= pack('v', 0x0100);
+ $dataProp .= pack('v', 0x0000);
+ $dataProp .= pack('v', 0x0000);
+ $dataProp .= pack('v', 0x0000);
+
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x0C],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x100C],
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+
+ // 4 Section Length
+ // 4 Property count
+ // 8 * $dataSection_NumProps (8 = ID (4) + OffSet(4))
+ $dataSection_Content_Offset = 8 + $dataSection_NumProps * 8;
+ foreach ($dataSection as $dataProp) {
+ // Summary
+ $dataSection_Summary .= pack($dataProp['summary']['pack'], $dataProp['summary']['data']);
+ // Offset
+ $dataSection_Summary .= pack($dataProp['offset']['pack'], $dataSection_Content_Offset);
+ // DataType
+ $dataSection_Content .= pack($dataProp['type']['pack'], $dataProp['type']['data']);
+ // Data
+ if ($dataProp['type']['data'] == 0x02) { // 2 byte signed integer
+ $dataSection_Content .= pack('V', $dataProp['data']['data']);
+
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x03) { // 4 byte signed integer
+ $dataSection_Content .= pack('V', $dataProp['data']['data']);
+
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x0B) { // Boolean
+ $dataSection_Content .= pack('V', (int) $dataProp['data']['data']);
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length
+ // Null-terminated string
+ $dataProp['data']['data'] .= chr(0);
+ ++$dataProp['data']['length'];
+ // Complete the string with null string for being a %4
+ $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4) == 4 ? 0 : (4 - $dataProp['data']['length'] % 4));
+ $dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT);
+
+ $dataSection_Content .= pack('V', $dataProp['data']['length']);
+ $dataSection_Content .= $dataProp['data']['data'];
+
+ $dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']);
+ // Condition below can never be true
+ //} elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
+ // $dataSection_Content .= $dataProp['data']['data'];
+
+ // $dataSection_Content_Offset += 4 + 8;
+ } else {
+ $dataSection_Content .= $dataProp['data']['data'];
+
+ $dataSection_Content_Offset += 4 + $dataProp['data']['length'];
+ }
+ }
+ // Now $dataSection_Content_Offset contains the size of the content
+
+ // section header
+ // offset: $secOffset; size: 4; section length
+ // + x Size of the content (summary + content)
+ $data .= pack('V', $dataSection_Content_Offset);
+ // offset: $secOffset+4; size: 4; property count
+ $data .= pack('V', $dataSection_NumProps);
+ // Section Summary
+ $data .= $dataSection_Summary;
+ // Section Content
+ $data .= $dataSection_Content;
+
+ return $data;
+ }
+
+ private function writeSummaryPropOle(int $dataProp, int &$dataSection_NumProps, array &$dataSection, int $sumdata, int $typdata): void
+ {
+ if ($dataProp) {
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => $sumdata],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => $typdata], // null-terminated string prepended by dword string length
+ 'data' => ['data' => OLE::localDateToOLE($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ }
+
+ private function writeSummaryProp(string $dataProp, int &$dataSection_NumProps, array &$dataSection, int $sumdata, int $typdata): void
+ {
+ if ($dataProp) {
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => $sumdata],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => $typdata], // null-terminated string prepended by dword string length
+ 'data' => ['data' => $dataProp, 'length' => strlen($dataProp)],
+ ];
+ ++$dataSection_NumProps;
+ }
+ }
+
+ /**
+ * Build the OLE Part for Summary Information.
+ *
+ * @return string
+ */
+ private function writeSummaryInformation()
+ {
+ // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark)
+ $data = pack('v', 0xFFFE);
+ // offset: 2; size: 2;
+ $data .= pack('v', 0x0000);
+ // offset: 4; size: 2; OS version
+ $data .= pack('v', 0x0106);
+ // offset: 6; size: 2; OS indicator
+ $data .= pack('v', 0x0002);
+ // offset: 8; size: 16
+ $data .= pack('VVVV', 0x00, 0x00, 0x00, 0x00);
+ // offset: 24; size: 4; section count
+ $data .= pack('V', 0x0001);
+
+ // offset: 28; size: 16; first section's class id: e0 85 9f f2 f9 4f 68 10 ab 91 08 00 2b 27 b3 d9
+ $data .= pack('vvvvvvvv', 0x85E0, 0xF29F, 0x4FF9, 0x1068, 0x91AB, 0x0008, 0x272B, 0xD9B3);
+ // offset: 44; size: 4; offset of the start
+ $data .= pack('V', 0x30);
+
+ // SECTION
+ $dataSection = [];
+ $dataSection_NumProps = 0;
+ $dataSection_Summary = '';
+ $dataSection_Content = '';
+
+ // CodePage : CP-1252
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x01],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x02], // 2 byte signed integer
+ 'data' => ['data' => 1252],
+ ];
+ ++$dataSection_NumProps;
+
+ $props = $this->spreadsheet->getProperties();
+ $this->writeSummaryProp($props->getTitle(), $dataSection_NumProps, $dataSection, 0x02, 0x1e);
+ $this->writeSummaryProp($props->getSubject(), $dataSection_NumProps, $dataSection, 0x03, 0x1e);
+ $this->writeSummaryProp($props->getCreator(), $dataSection_NumProps, $dataSection, 0x04, 0x1e);
+ $this->writeSummaryProp($props->getKeywords(), $dataSection_NumProps, $dataSection, 0x05, 0x1e);
+ $this->writeSummaryProp($props->getDescription(), $dataSection_NumProps, $dataSection, 0x06, 0x1e);
+ $this->writeSummaryProp($props->getLastModifiedBy(), $dataSection_NumProps, $dataSection, 0x08, 0x1e);
+ $this->writeSummaryPropOle($props->getCreated(), $dataSection_NumProps, $dataSection, 0x0c, 0x40);
+ $this->writeSummaryPropOle($props->getModified(), $dataSection_NumProps, $dataSection, 0x0d, 0x40);
+
+ // Security
+ $dataSection[] = [
+ 'summary' => ['pack' => 'V', 'data' => 0x13],
+ 'offset' => ['pack' => 'V'],
+ 'type' => ['pack' => 'V', 'data' => 0x03], // 4 byte signed integer
+ 'data' => ['data' => 0x00],
+ ];
+ ++$dataSection_NumProps;
+
+ // 4 Section Length
+ // 4 Property count
+ // 8 * $dataSection_NumProps (8 = ID (4) + OffSet(4))
+ $dataSection_Content_Offset = 8 + $dataSection_NumProps * 8;
+ foreach ($dataSection as $dataProp) {
+ // Summary
+ $dataSection_Summary .= pack($dataProp['summary']['pack'], $dataProp['summary']['data']);
+ // Offset
+ $dataSection_Summary .= pack($dataProp['offset']['pack'], $dataSection_Content_Offset);
+ // DataType
+ $dataSection_Content .= pack($dataProp['type']['pack'], $dataProp['type']['data']);
+ // Data
+ if ($dataProp['type']['data'] == 0x02) { // 2 byte signed integer
+ $dataSection_Content .= pack('V', $dataProp['data']['data']);
+
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x03) { // 4 byte signed integer
+ $dataSection_Content .= pack('V', $dataProp['data']['data']);
+
+ $dataSection_Content_Offset += 4 + 4;
+ } elseif ($dataProp['type']['data'] == 0x1E) { // null-terminated string prepended by dword string length
+ // Null-terminated string
+ $dataProp['data']['data'] .= chr(0);
+ ++$dataProp['data']['length'];
+ // Complete the string with null string for being a %4
+ $dataProp['data']['length'] = $dataProp['data']['length'] + ((4 - $dataProp['data']['length'] % 4) == 4 ? 0 : (4 - $dataProp['data']['length'] % 4));
+ $dataProp['data']['data'] = str_pad($dataProp['data']['data'], $dataProp['data']['length'], chr(0), STR_PAD_RIGHT);
+
+ $dataSection_Content .= pack('V', $dataProp['data']['length']);
+ $dataSection_Content .= $dataProp['data']['data'];
+
+ $dataSection_Content_Offset += 4 + 4 + strlen($dataProp['data']['data']);
+ } elseif ($dataProp['type']['data'] == 0x40) { // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601)
+ $dataSection_Content .= $dataProp['data']['data'];
+
+ $dataSection_Content_Offset += 4 + 8;
+ }
+ // Data Type Not Used at the moment
+ }
+ // Now $dataSection_Content_Offset contains the size of the content
+
+ // section header
+ // offset: $secOffset; size: 4; section length
+ // + x Size of the content (summary + content)
+ $data .= pack('V', $dataSection_Content_Offset);
+ // offset: $secOffset+4; size: 4; property count
+ $data .= pack('V', $dataSection_NumProps);
+ // Section Summary
+ $data .= $dataSection_Summary;
+ // Section Content
+ $data .= $dataSection_Content;
+
+ return $data;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php
new file mode 100644
index 0000000..f989a0e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php
@@ -0,0 +1,224 @@
+
+// *
+// * The majority of this is _NOT_ my code. I simply ported it from the
+// * PERL Spreadsheet::WriteExcel module.
+// *
+// * The author of the Spreadsheet::WriteExcel module is John McNamara
+// *
+// *
+// * I _DO_ maintain this code, and John McNamara has nothing to do with the
+// * porting of this code to PHP. Any questions directly related to this
+// * class library should be directed to me.
+// *
+// * License Information:
+// *
+// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
+// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
+// *
+// * This library is free software; you can redistribute it and/or
+// * modify it under the terms of the GNU Lesser General Public
+// * License as published by the Free Software Foundation; either
+// * version 2.1 of the License, or (at your option) any later version.
+// *
+// * This library is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// * Lesser General Public License for more details.
+// *
+// * You should have received a copy of the GNU Lesser General Public
+// * License along with this library; if not, write to the Free Software
+// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// */
+class BIFFwriter
+{
+ /**
+ * The byte order of this architecture. 0 => little endian, 1 => big endian.
+ *
+ * @var int
+ */
+ private static $byteOrder;
+
+ /**
+ * The string containing the data of the BIFF stream.
+ *
+ * @var string
+ */
+ public $_data;
+
+ /**
+ * The size of the data in bytes. Should be the same as strlen($this->_data).
+ *
+ * @var int
+ */
+ public $_datasize;
+
+ /**
+ * The maximum length for a BIFF record (excluding record header and length field). See addContinue().
+ *
+ * @var int
+ *
+ * @see addContinue()
+ */
+ private $limit = 8224;
+
+ /**
+ * Constructor.
+ */
+ public function __construct()
+ {
+ $this->_data = '';
+ $this->_datasize = 0;
+ }
+
+ /**
+ * Determine the byte order and store it as class data to avoid
+ * recalculating it for each call to new().
+ *
+ * @return int
+ */
+ public static function getByteOrder()
+ {
+ if (!isset(self::$byteOrder)) {
+ // Check if "pack" gives the required IEEE 64bit float
+ $teststr = pack('d', 1.2345);
+ $number = pack('C8', 0x8D, 0x97, 0x6E, 0x12, 0x83, 0xC0, 0xF3, 0x3F);
+ if ($number == $teststr) {
+ $byte_order = 0; // Little Endian
+ } elseif ($number == strrev($teststr)) {
+ $byte_order = 1; // Big Endian
+ } else {
+ // Give up. I'll fix this in a later version.
+ throw new WriterException('Required floating point format not supported on this platform.');
+ }
+ self::$byteOrder = $byte_order;
+ }
+
+ return self::$byteOrder;
+ }
+
+ /**
+ * General storage function.
+ *
+ * @param string $data binary data to append
+ */
+ protected function append($data): void
+ {
+ if (strlen($data) - 4 > $this->limit) {
+ $data = $this->addContinue($data);
+ }
+ $this->_data .= $data;
+ $this->_datasize += strlen($data);
+ }
+
+ /**
+ * General storage function like append, but returns string instead of modifying $this->_data.
+ *
+ * @param string $data binary data to write
+ *
+ * @return string
+ */
+ public function writeData($data)
+ {
+ if (strlen($data) - 4 > $this->limit) {
+ $data = $this->addContinue($data);
+ }
+ $this->_datasize += strlen($data);
+
+ return $data;
+ }
+
+ /**
+ * Writes Excel BOF record to indicate the beginning of a stream or
+ * sub-stream in the BIFF file.
+ *
+ * @param int $type type of BIFF file to write: 0x0005 Workbook,
+ * 0x0010 Worksheet
+ */
+ protected function storeBof($type): void
+ {
+ $record = 0x0809; // Record identifier (BIFF5-BIFF8)
+ $length = 0x0010;
+
+ // by inspection of real files, MS Office Excel 2007 writes the following
+ $unknown = pack('VV', 0x000100D1, 0x00000406);
+
+ $build = 0x0DBB; // Excel 97
+ $year = 0x07CC; // Excel 97
+
+ $version = 0x0600; // BIFF8
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvv', $version, $type, $build, $year);
+ $this->append($header . $data . $unknown);
+ }
+
+ /**
+ * Writes Excel EOF record to indicate the end of a BIFF stream.
+ */
+ protected function storeEof(): void
+ {
+ $record = 0x000A; // Record identifier
+ $length = 0x0000; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $this->append($header);
+ }
+
+ /**
+ * Writes Excel EOF record to indicate the end of a BIFF stream.
+ */
+ public function writeEof()
+ {
+ $record = 0x000A; // Record identifier
+ $length = 0x0000; // Number of bytes to follow
+ $header = pack('vv', $record, $length);
+
+ return $this->writeData($header);
+ }
+
+ /**
+ * Excel limits the size of BIFF records. In Excel 5 the limit is 2084 bytes. In
+ * Excel 97 the limit is 8228 bytes. Records that are longer than these limits
+ * must be split up into CONTINUE blocks.
+ *
+ * This function takes a long BIFF record and inserts CONTINUE records as
+ * necessary.
+ *
+ * @param string $data The original binary data to be written
+ *
+ * @return string A very convenient string of continue blocks
+ */
+ private function addContinue($data)
+ {
+ $limit = $this->limit;
+ $record = 0x003C; // Record identifier
+
+ // The first 2080/8224 bytes remain intact. However, we have to change
+ // the length field of the record.
+ $tmp = substr($data, 0, 2) . pack('v', $limit) . substr($data, 4, $limit);
+
+ $header = pack('vv', $record, $limit); // Headers for continue records
+
+ // Retrieve chunks of 2080/8224 bytes +4 for the header.
+ $data_length = strlen($data);
+ for ($i = $limit + 4; $i < ($data_length - $limit); $i += $limit) {
+ $tmp .= $header;
+ $tmp .= substr($data, $i, $limit);
+ }
+
+ // Retrieve the last chunk of data
+ $header = pack('vv', $record, strlen($data) - $i);
+ $tmp .= $header;
+ $tmp .= substr($data, $i);
+
+ return $tmp;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php
new file mode 100644
index 0000000..f8af656
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php
@@ -0,0 +1,510 @@
+object = $object;
+ }
+
+ /**
+ * Process the object to be written.
+ *
+ * @return string
+ */
+ public function close()
+ {
+ // initialize
+ $this->data = '';
+
+ switch (get_class($this->object)) {
+ case \PhpOffice\PhpSpreadsheet\Shared\Escher::class:
+ if ($dggContainer = $this->object->getDggContainer()) {
+ $writer = new self($dggContainer);
+ $this->data = $writer->close();
+ } elseif ($dgContainer = $this->object->getDgContainer()) {
+ $writer = new self($dgContainer);
+ $this->data = $writer->close();
+ $this->spOffsets = $writer->getSpOffsets();
+ $this->spTypes = $writer->getSpTypes();
+ }
+
+ break;
+ case DggContainer::class:
+ // this is a container record
+
+ // initialize
+ $innerData = '';
+
+ // write the dgg
+ $recVer = 0x0;
+ $recInstance = 0x0000;
+ $recType = 0xF006;
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ // dgg data
+ $dggData =
+ pack(
+ 'VVVV',
+ $this->object->getSpIdMax(), // maximum shape identifier increased by one
+ $this->object->getCDgSaved() + 1, // number of file identifier clusters increased by one
+ $this->object->getCSpSaved(),
+ $this->object->getCDgSaved() // count total number of drawings saved
+ );
+
+ // add file identifier clusters (one per drawing)
+ $IDCLs = $this->object->getIDCLs();
+
+ foreach ($IDCLs as $dgId => $maxReducedSpId) {
+ $dggData .= pack('VV', $dgId, $maxReducedSpId + 1);
+ }
+
+ $header = pack('vvV', $recVerInstance, $recType, strlen($dggData));
+ $innerData .= $header . $dggData;
+
+ // write the bstoreContainer
+ if ($bstoreContainer = $this->object->getBstoreContainer()) {
+ $writer = new self($bstoreContainer);
+ $innerData .= $writer->close();
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = 0x0000;
+ $recType = 0xF000;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $innerData;
+
+ break;
+ case BstoreContainer::class:
+ // this is a container record
+
+ // initialize
+ $innerData = '';
+
+ // treat the inner data
+ if ($BSECollection = $this->object->getBSECollection()) {
+ foreach ($BSECollection as $BSE) {
+ $writer = new self($BSE);
+ $innerData .= $writer->close();
+ }
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = count($this->object->getBSECollection());
+ $recType = 0xF001;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $innerData;
+
+ break;
+ case BSE::class:
+ // this is a semi-container record
+
+ // initialize
+ $innerData = '';
+
+ // here we treat the inner data
+ if ($blip = $this->object->getBlip()) {
+ $writer = new self($blip);
+ $innerData .= $writer->close();
+ }
+
+ // initialize
+ $data = '';
+
+ $btWin32 = $this->object->getBlipType();
+ $btMacOS = $this->object->getBlipType();
+ $data .= pack('CC', $btWin32, $btMacOS);
+
+ $rgbUid = pack('VVVV', 0, 0, 0, 0); // todo
+ $data .= $rgbUid;
+
+ $tag = 0;
+ $size = strlen($innerData);
+ $cRef = 1;
+ $foDelay = 0; //todo
+ $unused1 = 0x0;
+ $cbName = 0x0;
+ $unused2 = 0x0;
+ $unused3 = 0x0;
+ $data .= pack('vVVVCCCC', $tag, $size, $cRef, $foDelay, $unused1, $cbName, $unused2, $unused3);
+
+ $data .= $innerData;
+
+ // write the record
+ $recVer = 0x2;
+ $recInstance = $this->object->getBlipType();
+ $recType = 0xF007;
+ $length = strlen($data);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header;
+
+ $this->data .= $data;
+
+ break;
+ case Blip::class:
+ // this is an atom record
+
+ // write the record
+ switch ($this->object->getParent()->getBlipType()) {
+ case BSE::BLIPTYPE_JPEG:
+ // initialize
+ $innerData = '';
+
+ $rgbUid1 = pack('VVVV', 0, 0, 0, 0); // todo
+ $innerData .= $rgbUid1;
+
+ $tag = 0xFF; // todo
+ $innerData .= pack('C', $tag);
+
+ $innerData .= $this->object->getData();
+
+ $recVer = 0x0;
+ $recInstance = 0x46A;
+ $recType = 0xF01D;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header;
+
+ $this->data .= $innerData;
+
+ break;
+ case BSE::BLIPTYPE_PNG:
+ // initialize
+ $innerData = '';
+
+ $rgbUid1 = pack('VVVV', 0, 0, 0, 0); // todo
+ $innerData .= $rgbUid1;
+
+ $tag = 0xFF; // todo
+ $innerData .= pack('C', $tag);
+
+ $innerData .= $this->object->getData();
+
+ $recVer = 0x0;
+ $recInstance = 0x6E0;
+ $recType = 0xF01E;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header;
+
+ $this->data .= $innerData;
+
+ break;
+ }
+
+ break;
+ case DgContainer::class:
+ // this is a container record
+
+ // initialize
+ $innerData = '';
+
+ // write the dg
+ $recVer = 0x0;
+ $recInstance = $this->object->getDgId();
+ $recType = 0xF008;
+ $length = 8;
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ // number of shapes in this drawing (including group shape)
+ $countShapes = count($this->object->getSpgrContainer()->getChildren());
+ $innerData .= $header . pack('VV', $countShapes, $this->object->getLastSpId());
+
+ // write the spgrContainer
+ if ($spgrContainer = $this->object->getSpgrContainer()) {
+ $writer = new self($spgrContainer);
+ $innerData .= $writer->close();
+
+ // get the shape offsets relative to the spgrContainer record
+ $spOffsets = $writer->getSpOffsets();
+ $spTypes = $writer->getSpTypes();
+
+ // save the shape offsets relative to dgContainer
+ foreach ($spOffsets as &$spOffset) {
+ $spOffset += 24; // add length of dgContainer header data (8 bytes) plus dg data (16 bytes)
+ }
+
+ $this->spOffsets = $spOffsets;
+ $this->spTypes = $spTypes;
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = 0x0000;
+ $recType = 0xF002;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $innerData;
+
+ break;
+ case SpgrContainer::class:
+ // this is a container record
+
+ // initialize
+ $innerData = '';
+
+ // initialize spape offsets
+ $totalSize = 8;
+ $spOffsets = [];
+ $spTypes = [];
+
+ // treat the inner data
+ foreach ($this->object->getChildren() as $spContainer) {
+ $writer = new self($spContainer);
+ $spData = $writer->close();
+ $innerData .= $spData;
+
+ // save the shape offsets (where new shape records begin)
+ $totalSize += strlen($spData);
+ $spOffsets[] = $totalSize;
+
+ $spTypes = array_merge($spTypes, $writer->getSpTypes());
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = 0x0000;
+ $recType = 0xF003;
+ $length = strlen($innerData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $innerData;
+ $this->spOffsets = $spOffsets;
+ $this->spTypes = $spTypes;
+
+ break;
+ case SpContainer::class:
+ // initialize
+ $data = '';
+
+ // build the data
+
+ // write group shape record, if necessary?
+ if ($this->object->getSpgr()) {
+ $recVer = 0x1;
+ $recInstance = 0x0000;
+ $recType = 0xF009;
+ $length = 0x00000010;
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $data .= $header . pack('VVVV', 0, 0, 0, 0);
+ }
+ $this->spTypes[] = ($this->object->getSpType());
+
+ // write the shape record
+ $recVer = 0x2;
+ $recInstance = $this->object->getSpType(); // shape type
+ $recType = 0xF00A;
+ $length = 0x00000008;
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $data .= $header . pack('VV', $this->object->getSpId(), $this->object->getSpgr() ? 0x0005 : 0x0A00);
+
+ // the options
+ if ($this->object->getOPTCollection()) {
+ $optData = '';
+
+ $recVer = 0x3;
+ $recInstance = count($this->object->getOPTCollection());
+ $recType = 0xF00B;
+ foreach ($this->object->getOPTCollection() as $property => $value) {
+ $optData .= pack('vV', $property, $value);
+ }
+ $length = strlen($optData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+ $data .= $header . $optData;
+ }
+
+ // the client anchor
+ if ($this->object->getStartCoordinates()) {
+ $clientAnchorData = '';
+
+ $recVer = 0x0;
+ $recInstance = 0x0;
+ $recType = 0xF010;
+
+ // start coordinates
+ [$column, $row] = Coordinate::coordinateFromString($this->object->getStartCoordinates());
+ $c1 = Coordinate::columnIndexFromString($column) - 1;
+ $r1 = $row - 1;
+
+ // start offsetX
+ $startOffsetX = $this->object->getStartOffsetX();
+
+ // start offsetY
+ $startOffsetY = $this->object->getStartOffsetY();
+
+ // end coordinates
+ [$column, $row] = Coordinate::coordinateFromString($this->object->getEndCoordinates());
+ $c2 = Coordinate::columnIndexFromString($column) - 1;
+ $r2 = $row - 1;
+
+ // end offsetX
+ $endOffsetX = $this->object->getEndOffsetX();
+
+ // end offsetY
+ $endOffsetY = $this->object->getEndOffsetY();
+
+ $clientAnchorData = pack('vvvvvvvvv', $this->object->getSpFlag(), $c1, $startOffsetX, $r1, $startOffsetY, $c2, $endOffsetX, $r2, $endOffsetY);
+
+ $length = strlen($clientAnchorData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+ $data .= $header . $clientAnchorData;
+ }
+
+ // the client data, just empty for now
+ if (!$this->object->getSpgr()) {
+ $clientDataData = '';
+
+ $recVer = 0x0;
+ $recInstance = 0x0;
+ $recType = 0xF011;
+
+ $length = strlen($clientDataData);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+ $data .= $header . $clientDataData;
+ }
+
+ // write the record
+ $recVer = 0xF;
+ $recInstance = 0x0000;
+ $recType = 0xF004;
+ $length = strlen($data);
+
+ $recVerInstance = $recVer;
+ $recVerInstance |= $recInstance << 4;
+
+ $header = pack('vvV', $recVerInstance, $recType, $length);
+
+ $this->data = $header . $data;
+
+ break;
+ }
+
+ return $this->data;
+ }
+
+ /**
+ * Gets the shape offsets.
+ *
+ * @return array
+ */
+ public function getSpOffsets()
+ {
+ return $this->spOffsets;
+ }
+
+ /**
+ * Gets the shape types.
+ *
+ * @return array
+ */
+ public function getSpTypes()
+ {
+ return $this->spTypes;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php
new file mode 100644
index 0000000..501f03b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php
@@ -0,0 +1,147 @@
+colorIndex = 0x7FFF;
+ $this->font = $font;
+ }
+
+ /**
+ * Set the color index.
+ *
+ * @param int $colorIndex
+ */
+ public function setColorIndex($colorIndex): void
+ {
+ $this->colorIndex = $colorIndex;
+ }
+
+ /**
+ * Get font record data.
+ *
+ * @return string
+ */
+ public function writeFont()
+ {
+ $font_outline = 0;
+ $font_shadow = 0;
+
+ $icv = $this->colorIndex; // Index to color palette
+ if ($this->font->getSuperscript()) {
+ $sss = 1;
+ } elseif ($this->font->getSubscript()) {
+ $sss = 2;
+ } else {
+ $sss = 0;
+ }
+ $bFamily = 0; // Font family
+ $bCharSet = \PhpOffice\PhpSpreadsheet\Shared\Font::getCharsetFromFontName($this->font->getName()); // Character set
+
+ $record = 0x31; // Record identifier
+ $reserved = 0x00; // Reserved
+ $grbit = 0x00; // Font attributes
+ if ($this->font->getItalic()) {
+ $grbit |= 0x02;
+ }
+ if ($this->font->getStrikethrough()) {
+ $grbit |= 0x08;
+ }
+ if ($font_outline) {
+ $grbit |= 0x10;
+ }
+ if ($font_shadow) {
+ $grbit |= 0x20;
+ }
+
+ $data = pack(
+ 'vvvvvCCCC',
+ // Fontsize (in twips)
+ $this->font->getSize() * 20,
+ $grbit,
+ // Colour
+ $icv,
+ // Font weight
+ self::mapBold($this->font->getBold()),
+ // Superscript/Subscript
+ $sss,
+ self::mapUnderline($this->font->getUnderline()),
+ $bFamily,
+ $bCharSet,
+ $reserved
+ );
+ $data .= StringHelper::UTF8toBIFF8UnicodeShort($this->font->getName());
+
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ return $header . $data;
+ }
+
+ /**
+ * Map to BIFF5-BIFF8 codes for bold.
+ *
+ * @param bool $bold
+ *
+ * @return int
+ */
+ private static function mapBold($bold)
+ {
+ if ($bold) {
+ return 0x2BC; // 700 = Bold font weight
+ }
+
+ return 0x190; // 400 = Normal font weight
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for underline styles.
+ *
+ * @var array of int
+ */
+ private static $mapUnderline = [
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE => 0x00,
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE => 0x01,
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE => 0x02,
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLEACCOUNTING => 0x21,
+ \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLEACCOUNTING => 0x22,
+ ];
+
+ /**
+ * Map underline.
+ *
+ * @param string $underline
+ *
+ * @return int
+ */
+ private static function mapUnderline($underline)
+ {
+ if (isset(self::$mapUnderline[$underline])) {
+ return self::$mapUnderline[$underline];
+ }
+
+ return 0x00;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php
new file mode 100644
index 0000000..b5a3758
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php
@@ -0,0 +1,1483 @@
+=,;#()"{}
+ const REGEX_SHEET_TITLE_UNQUOTED = '[^\*\:\/\\\\\?\[\]\+\-\% \\\'\^\&\<\>\=\,\;\#\(\)\"\{\}]+';
+
+ // Sheet title in quoted form (without surrounding quotes)
+ // Invalid sheet title characters cannot occur in the sheet title:
+ // *:/\?[] (usual invalid sheet title characters)
+ // Single quote is represented as a pair ''
+ const REGEX_SHEET_TITLE_QUOTED = '(([^\*\:\/\\\\\?\[\]\\\'])+|(\\\'\\\')+)+';
+
+ /**
+ * The index of the character we are currently looking at.
+ *
+ * @var int
+ */
+ public $currentCharacter;
+
+ /**
+ * The token we are working on.
+ *
+ * @var string
+ */
+ public $currentToken;
+
+ /**
+ * The formula to parse.
+ *
+ * @var string
+ */
+ private $formula;
+
+ /**
+ * The character ahead of the current char.
+ *
+ * @var string
+ */
+ public $lookAhead;
+
+ /**
+ * The parse tree to be generated.
+ *
+ * @var string
+ */
+ public $parseTree;
+
+ /**
+ * Array of external sheets.
+ *
+ * @var array
+ */
+ private $externalSheets;
+
+ /**
+ * Array of sheet references in the form of REF structures.
+ *
+ * @var array
+ */
+ public $references;
+
+ /**
+ * The Excel ptg indices.
+ *
+ * @var array
+ */
+ private $ptg = [
+ 'ptgExp' => 0x01,
+ 'ptgTbl' => 0x02,
+ 'ptgAdd' => 0x03,
+ 'ptgSub' => 0x04,
+ 'ptgMul' => 0x05,
+ 'ptgDiv' => 0x06,
+ 'ptgPower' => 0x07,
+ 'ptgConcat' => 0x08,
+ 'ptgLT' => 0x09,
+ 'ptgLE' => 0x0A,
+ 'ptgEQ' => 0x0B,
+ 'ptgGE' => 0x0C,
+ 'ptgGT' => 0x0D,
+ 'ptgNE' => 0x0E,
+ 'ptgIsect' => 0x0F,
+ 'ptgUnion' => 0x10,
+ 'ptgRange' => 0x11,
+ 'ptgUplus' => 0x12,
+ 'ptgUminus' => 0x13,
+ 'ptgPercent' => 0x14,
+ 'ptgParen' => 0x15,
+ 'ptgMissArg' => 0x16,
+ 'ptgStr' => 0x17,
+ 'ptgAttr' => 0x19,
+ 'ptgSheet' => 0x1A,
+ 'ptgEndSheet' => 0x1B,
+ 'ptgErr' => 0x1C,
+ 'ptgBool' => 0x1D,
+ 'ptgInt' => 0x1E,
+ 'ptgNum' => 0x1F,
+ 'ptgArray' => 0x20,
+ 'ptgFunc' => 0x21,
+ 'ptgFuncVar' => 0x22,
+ 'ptgName' => 0x23,
+ 'ptgRef' => 0x24,
+ 'ptgArea' => 0x25,
+ 'ptgMemArea' => 0x26,
+ 'ptgMemErr' => 0x27,
+ 'ptgMemNoMem' => 0x28,
+ 'ptgMemFunc' => 0x29,
+ 'ptgRefErr' => 0x2A,
+ 'ptgAreaErr' => 0x2B,
+ 'ptgRefN' => 0x2C,
+ 'ptgAreaN' => 0x2D,
+ 'ptgMemAreaN' => 0x2E,
+ 'ptgMemNoMemN' => 0x2F,
+ 'ptgNameX' => 0x39,
+ 'ptgRef3d' => 0x3A,
+ 'ptgArea3d' => 0x3B,
+ 'ptgRefErr3d' => 0x3C,
+ 'ptgAreaErr3d' => 0x3D,
+ 'ptgArrayV' => 0x40,
+ 'ptgFuncV' => 0x41,
+ 'ptgFuncVarV' => 0x42,
+ 'ptgNameV' => 0x43,
+ 'ptgRefV' => 0x44,
+ 'ptgAreaV' => 0x45,
+ 'ptgMemAreaV' => 0x46,
+ 'ptgMemErrV' => 0x47,
+ 'ptgMemNoMemV' => 0x48,
+ 'ptgMemFuncV' => 0x49,
+ 'ptgRefErrV' => 0x4A,
+ 'ptgAreaErrV' => 0x4B,
+ 'ptgRefNV' => 0x4C,
+ 'ptgAreaNV' => 0x4D,
+ 'ptgMemAreaNV' => 0x4E,
+ 'ptgMemNoMemNV' => 0x4F,
+ 'ptgFuncCEV' => 0x58,
+ 'ptgNameXV' => 0x59,
+ 'ptgRef3dV' => 0x5A,
+ 'ptgArea3dV' => 0x5B,
+ 'ptgRefErr3dV' => 0x5C,
+ 'ptgAreaErr3dV' => 0x5D,
+ 'ptgArrayA' => 0x60,
+ 'ptgFuncA' => 0x61,
+ 'ptgFuncVarA' => 0x62,
+ 'ptgNameA' => 0x63,
+ 'ptgRefA' => 0x64,
+ 'ptgAreaA' => 0x65,
+ 'ptgMemAreaA' => 0x66,
+ 'ptgMemErrA' => 0x67,
+ 'ptgMemNoMemA' => 0x68,
+ 'ptgMemFuncA' => 0x69,
+ 'ptgRefErrA' => 0x6A,
+ 'ptgAreaErrA' => 0x6B,
+ 'ptgRefNA' => 0x6C,
+ 'ptgAreaNA' => 0x6D,
+ 'ptgMemAreaNA' => 0x6E,
+ 'ptgMemNoMemNA' => 0x6F,
+ 'ptgFuncCEA' => 0x78,
+ 'ptgNameXA' => 0x79,
+ 'ptgRef3dA' => 0x7A,
+ 'ptgArea3dA' => 0x7B,
+ 'ptgRefErr3dA' => 0x7C,
+ 'ptgAreaErr3dA' => 0x7D,
+ ];
+
+ /**
+ * Thanks to Michael Meeks and Gnumeric for the initial arg values.
+ *
+ * The following hash was generated by "function_locale.pl" in the distro.
+ * Refer to function_locale.pl for non-English function names.
+ *
+ * The array elements are as follow:
+ * ptg: The Excel function ptg code.
+ * args: The number of arguments that the function takes:
+ * >=0 is a fixed number of arguments.
+ * -1 is a variable number of arguments.
+ * class: The reference, value or array class of the function args.
+ * vol: The function is volatile.
+ *
+ * @var array
+ */
+ private $functions = [
+ // function ptg args class vol
+ 'COUNT' => [0, -1, 0, 0],
+ 'IF' => [1, -1, 1, 0],
+ 'ISNA' => [2, 1, 1, 0],
+ 'ISERROR' => [3, 1, 1, 0],
+ 'SUM' => [4, -1, 0, 0],
+ 'AVERAGE' => [5, -1, 0, 0],
+ 'MIN' => [6, -1, 0, 0],
+ 'MAX' => [7, -1, 0, 0],
+ 'ROW' => [8, -1, 0, 0],
+ 'COLUMN' => [9, -1, 0, 0],
+ 'NA' => [10, 0, 0, 0],
+ 'NPV' => [11, -1, 1, 0],
+ 'STDEV' => [12, -1, 0, 0],
+ 'DOLLAR' => [13, -1, 1, 0],
+ 'FIXED' => [14, -1, 1, 0],
+ 'SIN' => [15, 1, 1, 0],
+ 'COS' => [16, 1, 1, 0],
+ 'TAN' => [17, 1, 1, 0],
+ 'ATAN' => [18, 1, 1, 0],
+ 'PI' => [19, 0, 1, 0],
+ 'SQRT' => [20, 1, 1, 0],
+ 'EXP' => [21, 1, 1, 0],
+ 'LN' => [22, 1, 1, 0],
+ 'LOG10' => [23, 1, 1, 0],
+ 'ABS' => [24, 1, 1, 0],
+ 'INT' => [25, 1, 1, 0],
+ 'SIGN' => [26, 1, 1, 0],
+ 'ROUND' => [27, 2, 1, 0],
+ 'LOOKUP' => [28, -1, 0, 0],
+ 'INDEX' => [29, -1, 0, 1],
+ 'REPT' => [30, 2, 1, 0],
+ 'MID' => [31, 3, 1, 0],
+ 'LEN' => [32, 1, 1, 0],
+ 'VALUE' => [33, 1, 1, 0],
+ 'TRUE' => [34, 0, 1, 0],
+ 'FALSE' => [35, 0, 1, 0],
+ 'AND' => [36, -1, 0, 0],
+ 'OR' => [37, -1, 0, 0],
+ 'NOT' => [38, 1, 1, 0],
+ 'MOD' => [39, 2, 1, 0],
+ 'DCOUNT' => [40, 3, 0, 0],
+ 'DSUM' => [41, 3, 0, 0],
+ 'DAVERAGE' => [42, 3, 0, 0],
+ 'DMIN' => [43, 3, 0, 0],
+ 'DMAX' => [44, 3, 0, 0],
+ 'DSTDEV' => [45, 3, 0, 0],
+ 'VAR' => [46, -1, 0, 0],
+ 'DVAR' => [47, 3, 0, 0],
+ 'TEXT' => [48, 2, 1, 0],
+ 'LINEST' => [49, -1, 0, 0],
+ 'TREND' => [50, -1, 0, 0],
+ 'LOGEST' => [51, -1, 0, 0],
+ 'GROWTH' => [52, -1, 0, 0],
+ 'PV' => [56, -1, 1, 0],
+ 'FV' => [57, -1, 1, 0],
+ 'NPER' => [58, -1, 1, 0],
+ 'PMT' => [59, -1, 1, 0],
+ 'RATE' => [60, -1, 1, 0],
+ 'MIRR' => [61, 3, 0, 0],
+ 'IRR' => [62, -1, 0, 0],
+ 'RAND' => [63, 0, 1, 1],
+ 'MATCH' => [64, -1, 0, 0],
+ 'DATE' => [65, 3, 1, 0],
+ 'TIME' => [66, 3, 1, 0],
+ 'DAY' => [67, 1, 1, 0],
+ 'MONTH' => [68, 1, 1, 0],
+ 'YEAR' => [69, 1, 1, 0],
+ 'WEEKDAY' => [70, -1, 1, 0],
+ 'HOUR' => [71, 1, 1, 0],
+ 'MINUTE' => [72, 1, 1, 0],
+ 'SECOND' => [73, 1, 1, 0],
+ 'NOW' => [74, 0, 1, 1],
+ 'AREAS' => [75, 1, 0, 1],
+ 'ROWS' => [76, 1, 0, 1],
+ 'COLUMNS' => [77, 1, 0, 1],
+ 'OFFSET' => [78, -1, 0, 1],
+ 'SEARCH' => [82, -1, 1, 0],
+ 'TRANSPOSE' => [83, 1, 1, 0],
+ 'TYPE' => [86, 1, 1, 0],
+ 'ATAN2' => [97, 2, 1, 0],
+ 'ASIN' => [98, 1, 1, 0],
+ 'ACOS' => [99, 1, 1, 0],
+ 'CHOOSE' => [100, -1, 1, 0],
+ 'HLOOKUP' => [101, -1, 0, 0],
+ 'VLOOKUP' => [102, -1, 0, 0],
+ 'ISREF' => [105, 1, 0, 0],
+ 'LOG' => [109, -1, 1, 0],
+ 'CHAR' => [111, 1, 1, 0],
+ 'LOWER' => [112, 1, 1, 0],
+ 'UPPER' => [113, 1, 1, 0],
+ 'PROPER' => [114, 1, 1, 0],
+ 'LEFT' => [115, -1, 1, 0],
+ 'RIGHT' => [116, -1, 1, 0],
+ 'EXACT' => [117, 2, 1, 0],
+ 'TRIM' => [118, 1, 1, 0],
+ 'REPLACE' => [119, 4, 1, 0],
+ 'SUBSTITUTE' => [120, -1, 1, 0],
+ 'CODE' => [121, 1, 1, 0],
+ 'FIND' => [124, -1, 1, 0],
+ 'CELL' => [125, -1, 0, 1],
+ 'ISERR' => [126, 1, 1, 0],
+ 'ISTEXT' => [127, 1, 1, 0],
+ 'ISNUMBER' => [128, 1, 1, 0],
+ 'ISBLANK' => [129, 1, 1, 0],
+ 'T' => [130, 1, 0, 0],
+ 'N' => [131, 1, 0, 0],
+ 'DATEVALUE' => [140, 1, 1, 0],
+ 'TIMEVALUE' => [141, 1, 1, 0],
+ 'SLN' => [142, 3, 1, 0],
+ 'SYD' => [143, 4, 1, 0],
+ 'DDB' => [144, -1, 1, 0],
+ 'INDIRECT' => [148, -1, 1, 1],
+ 'CALL' => [150, -1, 1, 0],
+ 'CLEAN' => [162, 1, 1, 0],
+ 'MDETERM' => [163, 1, 2, 0],
+ 'MINVERSE' => [164, 1, 2, 0],
+ 'MMULT' => [165, 2, 2, 0],
+ 'IPMT' => [167, -1, 1, 0],
+ 'PPMT' => [168, -1, 1, 0],
+ 'COUNTA' => [169, -1, 0, 0],
+ 'PRODUCT' => [183, -1, 0, 0],
+ 'FACT' => [184, 1, 1, 0],
+ 'DPRODUCT' => [189, 3, 0, 0],
+ 'ISNONTEXT' => [190, 1, 1, 0],
+ 'STDEVP' => [193, -1, 0, 0],
+ 'VARP' => [194, -1, 0, 0],
+ 'DSTDEVP' => [195, 3, 0, 0],
+ 'DVARP' => [196, 3, 0, 0],
+ 'TRUNC' => [197, -1, 1, 0],
+ 'ISLOGICAL' => [198, 1, 1, 0],
+ 'DCOUNTA' => [199, 3, 0, 0],
+ 'USDOLLAR' => [204, -1, 1, 0],
+ 'FINDB' => [205, -1, 1, 0],
+ 'SEARCHB' => [206, -1, 1, 0],
+ 'REPLACEB' => [207, 4, 1, 0],
+ 'LEFTB' => [208, -1, 1, 0],
+ 'RIGHTB' => [209, -1, 1, 0],
+ 'MIDB' => [210, 3, 1, 0],
+ 'LENB' => [211, 1, 1, 0],
+ 'ROUNDUP' => [212, 2, 1, 0],
+ 'ROUNDDOWN' => [213, 2, 1, 0],
+ 'ASC' => [214, 1, 1, 0],
+ 'DBCS' => [215, 1, 1, 0],
+ 'RANK' => [216, -1, 0, 0],
+ 'ADDRESS' => [219, -1, 1, 0],
+ 'DAYS360' => [220, -1, 1, 0],
+ 'TODAY' => [221, 0, 1, 1],
+ 'VDB' => [222, -1, 1, 0],
+ 'MEDIAN' => [227, -1, 0, 0],
+ 'SUMPRODUCT' => [228, -1, 2, 0],
+ 'SINH' => [229, 1, 1, 0],
+ 'COSH' => [230, 1, 1, 0],
+ 'TANH' => [231, 1, 1, 0],
+ 'ASINH' => [232, 1, 1, 0],
+ 'ACOSH' => [233, 1, 1, 0],
+ 'ATANH' => [234, 1, 1, 0],
+ 'DGET' => [235, 3, 0, 0],
+ 'INFO' => [244, 1, 1, 1],
+ 'DB' => [247, -1, 1, 0],
+ 'FREQUENCY' => [252, 2, 0, 0],
+ 'ERROR.TYPE' => [261, 1, 1, 0],
+ 'REGISTER.ID' => [267, -1, 1, 0],
+ 'AVEDEV' => [269, -1, 0, 0],
+ 'BETADIST' => [270, -1, 1, 0],
+ 'GAMMALN' => [271, 1, 1, 0],
+ 'BETAINV' => [272, -1, 1, 0],
+ 'BINOMDIST' => [273, 4, 1, 0],
+ 'CHIDIST' => [274, 2, 1, 0],
+ 'CHIINV' => [275, 2, 1, 0],
+ 'COMBIN' => [276, 2, 1, 0],
+ 'CONFIDENCE' => [277, 3, 1, 0],
+ 'CRITBINOM' => [278, 3, 1, 0],
+ 'EVEN' => [279, 1, 1, 0],
+ 'EXPONDIST' => [280, 3, 1, 0],
+ 'FDIST' => [281, 3, 1, 0],
+ 'FINV' => [282, 3, 1, 0],
+ 'FISHER' => [283, 1, 1, 0],
+ 'FISHERINV' => [284, 1, 1, 0],
+ 'FLOOR' => [285, 2, 1, 0],
+ 'GAMMADIST' => [286, 4, 1, 0],
+ 'GAMMAINV' => [287, 3, 1, 0],
+ 'CEILING' => [288, 2, 1, 0],
+ 'HYPGEOMDIST' => [289, 4, 1, 0],
+ 'LOGNORMDIST' => [290, 3, 1, 0],
+ 'LOGINV' => [291, 3, 1, 0],
+ 'NEGBINOMDIST' => [292, 3, 1, 0],
+ 'NORMDIST' => [293, 4, 1, 0],
+ 'NORMSDIST' => [294, 1, 1, 0],
+ 'NORMINV' => [295, 3, 1, 0],
+ 'NORMSINV' => [296, 1, 1, 0],
+ 'STANDARDIZE' => [297, 3, 1, 0],
+ 'ODD' => [298, 1, 1, 0],
+ 'PERMUT' => [299, 2, 1, 0],
+ 'POISSON' => [300, 3, 1, 0],
+ 'TDIST' => [301, 3, 1, 0],
+ 'WEIBULL' => [302, 4, 1, 0],
+ 'SUMXMY2' => [303, 2, 2, 0],
+ 'SUMX2MY2' => [304, 2, 2, 0],
+ 'SUMX2PY2' => [305, 2, 2, 0],
+ 'CHITEST' => [306, 2, 2, 0],
+ 'CORREL' => [307, 2, 2, 0],
+ 'COVAR' => [308, 2, 2, 0],
+ 'FORECAST' => [309, 3, 2, 0],
+ 'FTEST' => [310, 2, 2, 0],
+ 'INTERCEPT' => [311, 2, 2, 0],
+ 'PEARSON' => [312, 2, 2, 0],
+ 'RSQ' => [313, 2, 2, 0],
+ 'STEYX' => [314, 2, 2, 0],
+ 'SLOPE' => [315, 2, 2, 0],
+ 'TTEST' => [316, 4, 2, 0],
+ 'PROB' => [317, -1, 2, 0],
+ 'DEVSQ' => [318, -1, 0, 0],
+ 'GEOMEAN' => [319, -1, 0, 0],
+ 'HARMEAN' => [320, -1, 0, 0],
+ 'SUMSQ' => [321, -1, 0, 0],
+ 'KURT' => [322, -1, 0, 0],
+ 'SKEW' => [323, -1, 0, 0],
+ 'ZTEST' => [324, -1, 0, 0],
+ 'LARGE' => [325, 2, 0, 0],
+ 'SMALL' => [326, 2, 0, 0],
+ 'QUARTILE' => [327, 2, 0, 0],
+ 'PERCENTILE' => [328, 2, 0, 0],
+ 'PERCENTRANK' => [329, -1, 0, 0],
+ 'MODE' => [330, -1, 2, 0],
+ 'TRIMMEAN' => [331, 2, 0, 0],
+ 'TINV' => [332, 2, 1, 0],
+ 'CONCATENATE' => [336, -1, 1, 0],
+ 'POWER' => [337, 2, 1, 0],
+ 'RADIANS' => [342, 1, 1, 0],
+ 'DEGREES' => [343, 1, 1, 0],
+ 'SUBTOTAL' => [344, -1, 0, 0],
+ 'SUMIF' => [345, -1, 0, 0],
+ 'COUNTIF' => [346, 2, 0, 0],
+ 'COUNTBLANK' => [347, 1, 0, 0],
+ 'ISPMT' => [350, 4, 1, 0],
+ 'DATEDIF' => [351, 3, 1, 0],
+ 'DATESTRING' => [352, 1, 1, 0],
+ 'NUMBERSTRING' => [353, 2, 1, 0],
+ 'ROMAN' => [354, -1, 1, 0],
+ 'GETPIVOTDATA' => [358, -1, 0, 0],
+ 'HYPERLINK' => [359, -1, 1, 0],
+ 'PHONETIC' => [360, 1, 0, 0],
+ 'AVERAGEA' => [361, -1, 0, 0],
+ 'MAXA' => [362, -1, 0, 0],
+ 'MINA' => [363, -1, 0, 0],
+ 'STDEVPA' => [364, -1, 0, 0],
+ 'VARPA' => [365, -1, 0, 0],
+ 'STDEVA' => [366, -1, 0, 0],
+ 'VARA' => [367, -1, 0, 0],
+ 'BAHTTEXT' => [368, 1, 0, 0],
+ ];
+
+ private $spreadsheet;
+
+ /**
+ * The class constructor.
+ */
+ public function __construct(Spreadsheet $spreadsheet)
+ {
+ $this->spreadsheet = $spreadsheet;
+
+ $this->currentCharacter = 0;
+ $this->currentToken = ''; // The token we are working on.
+ $this->formula = ''; // The formula to parse.
+ $this->lookAhead = ''; // The character ahead of the current char.
+ $this->parseTree = ''; // The parse tree to be generated.
+ $this->externalSheets = [];
+ $this->references = [];
+ }
+
+ /**
+ * Convert a token to the proper ptg value.
+ *
+ * @param mixed $token the token to convert
+ *
+ * @return mixed the converted token on success
+ */
+ private function convert($token)
+ {
+ if (preg_match('/"([^"]|""){0,255}"/', $token)) {
+ return $this->convertString($token);
+ } elseif (is_numeric($token)) {
+ return $this->convertNumber($token);
+ // match references like A1 or $A$1
+ } elseif (preg_match('/^\$?([A-Ia-i]?[A-Za-z])\$?(\d+)$/', $token)) {
+ return $this->convertRef2d($token);
+ // match external references like Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?(\\d+)$/u', $token)) {
+ return $this->convertRef3d($token);
+ // match external references like 'Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?(\\d+)$/u", $token)) {
+ return $this->convertRef3d($token);
+ // match ranges like A1:B2 or $A$1:$B$2
+ } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)\:(\$)?[A-Ia-i]?[A-Za-z](\$)?(\d+)$/', $token)) {
+ return $this->convertRange2d($token);
+ // match external ranges like Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?([A-Ia-i]?[A-Za-z])?\$?(\\d+)\\:\$?([A-Ia-i]?[A-Za-z])?\$?(\\d+)$/u', $token)) {
+ return $this->convertRange3d($token);
+ // match external ranges like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?(\\d+)\\:\\$?([A-Ia-i]?[A-Za-z])?\\$?(\\d+)$/u", $token)) {
+ return $this->convertRange3d($token);
+ // operators (including parentheses)
+ } elseif (isset($this->ptg[$token])) {
+ return pack('C', $this->ptg[$token]);
+ // match error codes
+ } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $token) || $token == '#N/A') {
+ return $this->convertError($token);
+ } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/mui', $token) && $this->spreadsheet->getDefinedName($token) !== null) {
+ return $this->convertDefinedName($token);
+ // commented so argument number can be processed correctly. See toReversePolish().
+ /*elseif (preg_match("/[A-Z0-9\xc0-\xdc\.]+/", $token))
+ {
+ return($this->convertFunction($token, $this->_func_args));
+ }*/
+ // if it's an argument, ignore the token (the argument remains)
+ } elseif ($token == 'arg') {
+ return '';
+ }
+
+ // TODO: use real error codes
+ throw new WriterException("Unknown token $token");
+ }
+
+ /**
+ * Convert a number token to ptgInt or ptgNum.
+ *
+ * @param mixed $num an integer or double for conversion to its ptg value
+ *
+ * @return string
+ */
+ private function convertNumber($num)
+ {
+ // Integer in the range 0..2**16-1
+ if ((preg_match('/^\\d+$/', $num)) && ($num <= 65535)) {
+ return pack('Cv', $this->ptg['ptgInt'], $num);
+ }
+
+ // A float
+ if (BIFFwriter::getByteOrder()) { // if it's Big Endian
+ $num = strrev($num);
+ }
+
+ return pack('Cd', $this->ptg['ptgNum'], $num);
+ }
+
+ /**
+ * Convert a string token to ptgStr.
+ *
+ * @param string $string a string for conversion to its ptg value
+ *
+ * @return mixed the converted token on success
+ */
+ private function convertString($string)
+ {
+ // chop away beggining and ending quotes
+ $string = substr($string, 1, -1);
+ if (strlen($string) > 255) {
+ throw new WriterException('String is too long');
+ }
+
+ return pack('C', $this->ptg['ptgStr']) . StringHelper::UTF8toBIFF8UnicodeShort($string);
+ }
+
+ /**
+ * Convert a function to a ptgFunc or ptgFuncVarV depending on the number of
+ * args that it takes.
+ *
+ * @param string $token the name of the function for convertion to ptg value
+ * @param int $num_args the number of arguments the function receives
+ *
+ * @return string The packed ptg for the function
+ */
+ private function convertFunction($token, $num_args)
+ {
+ $args = $this->functions[$token][1];
+
+ // Fixed number of args eg. TIME($i, $j, $k).
+ if ($args >= 0) {
+ return pack('Cv', $this->ptg['ptgFuncV'], $this->functions[$token][0]);
+ }
+ // Variable number of args eg. SUM($i, $j, $k, ..).
+ if ($args == -1) {
+ return pack('CCv', $this->ptg['ptgFuncVarV'], $num_args, $this->functions[$token][0]);
+ }
+ }
+
+ /**
+ * Convert an Excel range such as A1:D4 to a ptgRefV.
+ *
+ * @param string $range An Excel range in the A1:A2
+ * @param int $class
+ *
+ * @return string
+ */
+ private function convertRange2d($range, $class = 0)
+ {
+ // TODO: possible class value 0,1,2 check Formula.pm
+ // Split the range into 2 cell refs
+ if (preg_match('/^(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)\:(\$)?([A-Ia-i]?[A-Za-z])(\$)?(\d+)$/', $range)) {
+ [$cell1, $cell2] = explode(':', $range);
+ } else {
+ // TODO: use real error codes
+ throw new WriterException('Unknown range separator');
+ }
+
+ // Convert the cell references
+ [$row1, $col1] = $this->cellToPackedRowcol($cell1);
+ [$row2, $col2] = $this->cellToPackedRowcol($cell2);
+
+ // The ptg value depends on the class of the ptg.
+ if ($class == 0) {
+ $ptgArea = pack('C', $this->ptg['ptgArea']);
+ } elseif ($class == 1) {
+ $ptgArea = pack('C', $this->ptg['ptgAreaV']);
+ } elseif ($class == 2) {
+ $ptgArea = pack('C', $this->ptg['ptgAreaA']);
+ } else {
+ // TODO: use real error codes
+ throw new WriterException("Unknown class $class");
+ }
+
+ return $ptgArea . $row1 . $row2 . $col1 . $col2;
+ }
+
+ /**
+ * Convert an Excel 3d range such as "Sheet1!A1:D4" or "Sheet1:Sheet2!A1:D4" to
+ * a ptgArea3d.
+ *
+ * @param string $token an Excel range in the Sheet1!A1:A2 format
+ *
+ * @return mixed the packed ptgArea3d token on success
+ */
+ private function convertRange3d($token)
+ {
+ // Split the ref at the ! symbol
+ [$ext_ref, $range] = PhpspreadsheetWorksheet::extractSheetTitle($token, true);
+
+ // Convert the external reference part (different for BIFF8)
+ $ext_ref = $this->getRefIndex($ext_ref);
+
+ // Split the range into 2 cell refs
+ [$cell1, $cell2] = explode(':', $range);
+
+ // Convert the cell references
+ if (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?(\\d+)$/', $cell1)) {
+ [$row1, $col1] = $this->cellToPackedRowcol($cell1);
+ [$row2, $col2] = $this->cellToPackedRowcol($cell2);
+ } else { // It's a rows range (like 26:27)
+ [$row1, $col1, $row2, $col2] = $this->rangeToPackedRange($cell1 . ':' . $cell2);
+ }
+
+ // The ptg value depends on the class of the ptg.
+ $ptgArea = pack('C', $this->ptg['ptgArea3d']);
+
+ return $ptgArea . $ext_ref . $row1 . $row2 . $col1 . $col2;
+ }
+
+ /**
+ * Convert an Excel reference such as A1, $B2, C$3 or $D$4 to a ptgRefV.
+ *
+ * @param string $cell An Excel cell reference
+ *
+ * @return string The cell in packed() format with the corresponding ptg
+ */
+ private function convertRef2d($cell)
+ {
+ // Convert the cell reference
+ $cell_array = $this->cellToPackedRowcol($cell);
+ [$row, $col] = $cell_array;
+
+ // The ptg value depends on the class of the ptg.
+ $ptgRef = pack('C', $this->ptg['ptgRefA']);
+
+ return $ptgRef . $row . $col;
+ }
+
+ /**
+ * Convert an Excel 3d reference such as "Sheet1!A1" or "Sheet1:Sheet2!A1" to a
+ * ptgRef3d.
+ *
+ * @param string $cell An Excel cell reference
+ *
+ * @return mixed the packed ptgRef3d token on success
+ */
+ private function convertRef3d($cell)
+ {
+ // Split the ref at the ! symbol
+ [$ext_ref, $cell] = PhpspreadsheetWorksheet::extractSheetTitle($cell, true);
+
+ // Convert the external reference part (different for BIFF8)
+ $ext_ref = $this->getRefIndex($ext_ref);
+
+ // Convert the cell reference part
+ [$row, $col] = $this->cellToPackedRowcol($cell);
+
+ // The ptg value depends on the class of the ptg.
+ $ptgRef = pack('C', $this->ptg['ptgRef3dA']);
+
+ return $ptgRef . $ext_ref . $row . $col;
+ }
+
+ /**
+ * Convert an error code to a ptgErr.
+ *
+ * @param string $errorCode The error code for conversion to its ptg value
+ *
+ * @return string The error code ptgErr
+ */
+ private function convertError($errorCode)
+ {
+ switch ($errorCode) {
+ case '#NULL!':
+ return pack('C', 0x00);
+ case '#DIV/0!':
+ return pack('C', 0x07);
+ case '#VALUE!':
+ return pack('C', 0x0F);
+ case '#REF!':
+ return pack('C', 0x17);
+ case '#NAME?':
+ return pack('C', 0x1D);
+ case '#NUM!':
+ return pack('C', 0x24);
+ case '#N/A':
+ return pack('C', 0x2A);
+ }
+
+ return pack('C', 0xFF);
+ }
+
+ private function convertDefinedName(string $name): void
+ {
+ if (strlen($name) > 255) {
+ throw new WriterException('Defined Name is too long');
+ }
+
+ $nameReference = 1;
+ foreach ($this->spreadsheet->getDefinedNames() as $definedName) {
+ if ($name === $definedName->getName()) {
+ break;
+ }
+ ++$nameReference;
+ }
+
+ $ptgRef = pack('Cvxx', $this->ptg['ptgName'], $nameReference);
+
+ throw new WriterException('Cannot yet write formulae with defined names to Xls');
+// return $ptgRef;
+ }
+
+ /**
+ * Look up the REF index that corresponds to an external sheet name
+ * (or range). If it doesn't exist yet add it to the workbook's references
+ * array. It assumes all sheet names given must exist.
+ *
+ * @param string $ext_ref The name of the external reference
+ *
+ * @return mixed The reference index in packed() format on success
+ */
+ private function getRefIndex($ext_ref)
+ {
+ $ext_ref = preg_replace("/^'/", '', $ext_ref); // Remove leading ' if any.
+ $ext_ref = preg_replace("/'$/", '', $ext_ref); // Remove trailing ' if any.
+ $ext_ref = str_replace('\'\'', '\'', $ext_ref); // Replace escaped '' with '
+
+ // Check if there is a sheet range eg., Sheet1:Sheet2.
+ if (preg_match('/:/', $ext_ref)) {
+ [$sheet_name1, $sheet_name2] = explode(':', $ext_ref);
+
+ $sheet1 = $this->getSheetIndex($sheet_name1);
+ if ($sheet1 == -1) {
+ throw new WriterException("Unknown sheet name $sheet_name1 in formula");
+ }
+ $sheet2 = $this->getSheetIndex($sheet_name2);
+ if ($sheet2 == -1) {
+ throw new WriterException("Unknown sheet name $sheet_name2 in formula");
+ }
+
+ // Reverse max and min sheet numbers if necessary
+ if ($sheet1 > $sheet2) {
+ [$sheet1, $sheet2] = [$sheet2, $sheet1];
+ }
+ } else { // Single sheet name only.
+ $sheet1 = $this->getSheetIndex($ext_ref);
+ if ($sheet1 == -1) {
+ throw new WriterException("Unknown sheet name $ext_ref in formula");
+ }
+ $sheet2 = $sheet1;
+ }
+
+ // assume all references belong to this document
+ $supbook_index = 0x00;
+ $ref = pack('vvv', $supbook_index, $sheet1, $sheet2);
+ $totalreferences = count($this->references);
+ $index = -1;
+ for ($i = 0; $i < $totalreferences; ++$i) {
+ if ($ref == $this->references[$i]) {
+ $index = $i;
+
+ break;
+ }
+ }
+ // if REF was not found add it to references array
+ if ($index == -1) {
+ $this->references[$totalreferences] = $ref;
+ $index = $totalreferences;
+ }
+
+ return pack('v', $index);
+ }
+
+ /**
+ * Look up the index that corresponds to an external sheet name. The hash of
+ * sheet names is updated by the addworksheet() method of the
+ * \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook class.
+ *
+ * @param string $sheet_name Sheet name
+ *
+ * @return int The sheet index, -1 if the sheet was not found
+ */
+ private function getSheetIndex($sheet_name)
+ {
+ if (!isset($this->externalSheets[$sheet_name])) {
+ return -1;
+ }
+
+ return $this->externalSheets[$sheet_name];
+ }
+
+ /**
+ * This method is used to update the array of sheet names. It is
+ * called by the addWorksheet() method of the
+ * \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook class.
+ *
+ * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::addWorksheet()
+ *
+ * @param string $name The name of the worksheet being added
+ * @param int $index The index of the worksheet being added
+ */
+ public function setExtSheet($name, $index): void
+ {
+ $this->externalSheets[$name] = $index;
+ }
+
+ /**
+ * pack() row and column into the required 3 or 4 byte format.
+ *
+ * @param string $cell The Excel cell reference to be packed
+ *
+ * @return array Array containing the row and column in packed() format
+ */
+ private function cellToPackedRowcol($cell)
+ {
+ $cell = strtoupper($cell);
+ [$row, $col, $row_rel, $col_rel] = $this->cellToRowcol($cell);
+ if ($col >= 256) {
+ throw new WriterException("Column in: $cell greater than 255");
+ }
+ if ($row >= 65536) {
+ throw new WriterException("Row in: $cell greater than 65536 ");
+ }
+
+ // Set the high bits to indicate if row or col are relative.
+ $col |= $col_rel << 14;
+ $col |= $row_rel << 15;
+ $col = pack('v', $col);
+
+ $row = pack('v', $row);
+
+ return [$row, $col];
+ }
+
+ /**
+ * pack() row range into the required 3 or 4 byte format.
+ * Just using maximum col/rows, which is probably not the correct solution.
+ *
+ * @param string $range The Excel range to be packed
+ *
+ * @return array Array containing (row1,col1,row2,col2) in packed() format
+ */
+ private function rangeToPackedRange($range)
+ {
+ preg_match('/(\$)?(\d+)\:(\$)?(\d+)/', $range, $match);
+ // return absolute rows if there is a $ in the ref
+ $row1_rel = empty($match[1]) ? 1 : 0;
+ $row1 = $match[2];
+ $row2_rel = empty($match[3]) ? 1 : 0;
+ $row2 = $match[4];
+ // Convert 1-index to zero-index
+ --$row1;
+ --$row2;
+ // Trick poor inocent Excel
+ $col1 = 0;
+ $col2 = 65535; // FIXME: maximum possible value for Excel 5 (change this!!!)
+
+ // FIXME: this changes for BIFF8
+ if (($row1 >= 65536) || ($row2 >= 65536)) {
+ throw new WriterException("Row in: $range greater than 65536 ");
+ }
+
+ // Set the high bits to indicate if rows are relative.
+ $col1 |= $row1_rel << 15;
+ $col2 |= $row2_rel << 15;
+ $col1 = pack('v', $col1);
+ $col2 = pack('v', $col2);
+
+ $row1 = pack('v', $row1);
+ $row2 = pack('v', $row2);
+
+ return [$row1, $col1, $row2, $col2];
+ }
+
+ /**
+ * Convert an Excel cell reference such as A1 or $B2 or C$3 or $D$4 to a zero
+ * indexed row and column number. Also returns two (0,1) values to indicate
+ * whether the row or column are relative references.
+ *
+ * @param string $cell the Excel cell reference in A1 format
+ *
+ * @return array
+ */
+ private function cellToRowcol($cell)
+ {
+ preg_match('/(\$)?([A-I]?[A-Z])(\$)?(\d+)/', $cell, $match);
+ // return absolute column if there is a $ in the ref
+ $col_rel = empty($match[1]) ? 1 : 0;
+ $col_ref = $match[2];
+ $row_rel = empty($match[3]) ? 1 : 0;
+ $row = $match[4];
+
+ // Convert base26 column string to a number.
+ $expn = strlen($col_ref) - 1;
+ $col = 0;
+ $col_ref_length = strlen($col_ref);
+ for ($i = 0; $i < $col_ref_length; ++$i) {
+ $col += (ord($col_ref[$i]) - 64) * 26 ** $expn;
+ --$expn;
+ }
+
+ // Convert 1-index to zero-index
+ --$row;
+ --$col;
+
+ return [$row, $col, $row_rel, $col_rel];
+ }
+
+ /**
+ * Advance to the next valid token.
+ */
+ private function advance()
+ {
+ $i = $this->currentCharacter;
+ $formula_length = strlen($this->formula);
+ // eat up white spaces
+ if ($i < $formula_length) {
+ while ($this->formula[$i] == ' ') {
+ ++$i;
+ }
+
+ if ($i < ($formula_length - 1)) {
+ $this->lookAhead = $this->formula[$i + 1];
+ }
+ $token = '';
+ }
+
+ while ($i < $formula_length) {
+ $token .= $this->formula[$i];
+
+ if ($i < ($formula_length - 1)) {
+ $this->lookAhead = $this->formula[$i + 1];
+ } else {
+ $this->lookAhead = '';
+ }
+
+ if ($this->match($token) != '') {
+ $this->currentCharacter = $i + 1;
+ $this->currentToken = $token;
+
+ return 1;
+ }
+
+ if ($i < ($formula_length - 2)) {
+ $this->lookAhead = $this->formula[$i + 2];
+ } else { // if we run out of characters lookAhead becomes empty
+ $this->lookAhead = '';
+ }
+ ++$i;
+ }
+ //die("Lexical error ".$this->currentCharacter);
+ }
+
+ /**
+ * Checks if it's a valid token.
+ *
+ * @param mixed $token the token to check
+ *
+ * @return mixed The checked token or false on failure
+ */
+ private function match($token)
+ {
+ switch ($token) {
+ case '+':
+ case '-':
+ case '*':
+ case '/':
+ case '(':
+ case ')':
+ case ',':
+ case ';':
+ case '>=':
+ case '<=':
+ case '=':
+ case '<>':
+ case '^':
+ case '&':
+ case '%':
+ return $token;
+
+ break;
+ case '>':
+ if ($this->lookAhead === '=') { // it's a GE token
+ break;
+ }
+
+ return $token;
+
+ break;
+ case '<':
+ // it's a LE or a NE token
+ if (($this->lookAhead === '=') || ($this->lookAhead === '>')) {
+ break;
+ }
+
+ return $token;
+
+ break;
+ default:
+ // if it's a reference A1 or $A$1 or $A1 or A$1
+ if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?\d+$/', $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.') && ($this->lookAhead !== '!')) {
+ return $token;
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?\\d+$/u', $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.')) {
+ // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1)
+ return $token;
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?\\d+$/u", $token) && !preg_match('/\d/', $this->lookAhead) && ($this->lookAhead !== ':') && ($this->lookAhead !== '.')) {
+ // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1)
+ return $token;
+ } elseif (preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+:(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $token) && !preg_match('/\d/', $this->lookAhead)) {
+ // if it's a range A1:A2 or $A$1:$A$2
+ return $token;
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?([A-Ia-i]?[A-Za-z])?\$?\\d+:\$?([A-Ia-i]?[A-Za-z])?\$?\\d+$/u', $token) && !preg_match('/\d/', $this->lookAhead)) {
+ // If it's an external range like Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2
+ return $token;
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+:\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+$/u", $token) && !preg_match('/\d/', $this->lookAhead)) {
+ // If it's an external range like 'Sheet1'!A1:B2 or 'Sheet1:Sheet2'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1:Sheet2'!$A$1:$B$2
+ return $token;
+ } elseif (is_numeric($token) && (!is_numeric($token . $this->lookAhead) || ($this->lookAhead == '')) && ($this->lookAhead !== '!') && ($this->lookAhead !== ':')) {
+ // If it's a number (check that it's not a sheet name or range)
+ return $token;
+ } elseif (preg_match('/"([^"]|""){0,255}"/', $token) && $this->lookAhead !== '"' && (substr_count($token, '"') % 2 == 0)) {
+ // If it's a string (of maximum 255 characters)
+ return $token;
+ } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $token) || $token === '#N/A') {
+ // If it's an error code
+ return $token;
+ } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/i", $token) && ($this->lookAhead === '(')) {
+ // if it's a function call
+ return $token;
+ } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $token) && $this->spreadsheet->getDefinedName($token) !== null) {
+ return $token;
+ } elseif (substr($token, -1) === ')') {
+ // It's an argument of some description (e.g. a named range),
+ // precise nature yet to be determined
+ return $token;
+ }
+
+ return '';
+ }
+ }
+
+ /**
+ * The parsing method. It parses a formula.
+ *
+ * @param string $formula the formula to parse, without the initial equal
+ * sign (=)
+ *
+ * @return mixed true on success
+ */
+ public function parse($formula)
+ {
+ $this->currentCharacter = 0;
+ $this->formula = (string) $formula;
+ $this->lookAhead = $formula[1] ?? '';
+ $this->advance();
+ $this->parseTree = $this->condition();
+
+ return true;
+ }
+
+ /**
+ * It parses a condition. It assumes the following rule:
+ * Cond -> Expr [(">" | "<") Expr].
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function condition()
+ {
+ $result = $this->expression();
+ if ($this->currentToken == '<') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgLT', $result, $result2);
+ } elseif ($this->currentToken == '>') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgGT', $result, $result2);
+ } elseif ($this->currentToken == '<=') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgLE', $result, $result2);
+ } elseif ($this->currentToken == '>=') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgGE', $result, $result2);
+ } elseif ($this->currentToken == '=') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgEQ', $result, $result2);
+ } elseif ($this->currentToken == '<>') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgNE', $result, $result2);
+ } elseif ($this->currentToken == '&') {
+ $this->advance();
+ $result2 = $this->expression();
+ $result = $this->createTree('ptgConcat', $result, $result2);
+ }
+
+ return $result;
+ }
+
+ /**
+ * It parses a expression. It assumes the following rule:
+ * Expr -> Term [("+" | "-") Term]
+ * -> "string"
+ * -> "-" Term : Negative value
+ * -> "+" Term : Positive value
+ * -> Error code.
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function expression()
+ {
+ // If it's a string return a string node
+ if (preg_match('/"([^"]|""){0,255}"/', $this->currentToken)) {
+ $tmp = str_replace('""', '"', $this->currentToken);
+ if (($tmp == '"') || ($tmp == '')) {
+ // Trap for "" that has been used for an empty string
+ $tmp = '""';
+ }
+ $result = $this->createTree($tmp, '', '');
+ $this->advance();
+
+ return $result;
+ // If it's an error code
+ } elseif (preg_match('/^#[A-Z0\\/]{3,5}[!?]{1}$/', $this->currentToken) || $this->currentToken == '#N/A') {
+ $result = $this->createTree($this->currentToken, 'ptgErr', '');
+ $this->advance();
+
+ return $result;
+ // If it's a negative value
+ } elseif ($this->currentToken == '-') {
+ // catch "-" Term
+ $this->advance();
+ $result2 = $this->expression();
+
+ return $this->createTree('ptgUminus', $result2, '');
+ // If it's a positive value
+ } elseif ($this->currentToken == '+') {
+ // catch "+" Term
+ $this->advance();
+ $result2 = $this->expression();
+
+ return $this->createTree('ptgUplus', $result2, '');
+ }
+ $result = $this->term();
+ while (
+ ($this->currentToken == '+') ||
+ ($this->currentToken == '-') ||
+ ($this->currentToken == '^')
+ ) {
+ if ($this->currentToken == '+') {
+ $this->advance();
+ $result2 = $this->term();
+ $result = $this->createTree('ptgAdd', $result, $result2);
+ } elseif ($this->currentToken == '-') {
+ $this->advance();
+ $result2 = $this->term();
+ $result = $this->createTree('ptgSub', $result, $result2);
+ } else {
+ $this->advance();
+ $result2 = $this->term();
+ $result = $this->createTree('ptgPower', $result, $result2);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * This function just introduces a ptgParen element in the tree, so that Excel
+ * doesn't get confused when working with a parenthesized formula afterwards.
+ *
+ * @see fact()
+ *
+ * @return array The parsed ptg'd tree
+ */
+ private function parenthesizedExpression()
+ {
+ return $this->createTree('ptgParen', $this->expression(), '');
+ }
+
+ /**
+ * It parses a term. It assumes the following rule:
+ * Term -> Fact [("*" | "/") Fact].
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function term()
+ {
+ $result = $this->fact();
+ while (
+ ($this->currentToken == '*') ||
+ ($this->currentToken == '/')
+ ) {
+ if ($this->currentToken == '*') {
+ $this->advance();
+ $result2 = $this->fact();
+ $result = $this->createTree('ptgMul', $result, $result2);
+ } else {
+ $this->advance();
+ $result2 = $this->fact();
+ $result = $this->createTree('ptgDiv', $result, $result2);
+ }
+ }
+
+ return $result;
+ }
+
+ /**
+ * It parses a factor. It assumes the following rule:
+ * Fact -> ( Expr )
+ * | CellRef
+ * | CellRange
+ * | Number
+ * | Function.
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function fact()
+ {
+ if ($this->currentToken === '(') {
+ $this->advance(); // eat the "("
+ $result = $this->parenthesizedExpression();
+ if ($this->currentToken !== ')') {
+ throw new WriterException("')' token expected.");
+ }
+ $this->advance(); // eat the ")"
+
+ return $result;
+ }
+ // if it's a reference
+ if (preg_match('/^\$?[A-Ia-i]?[A-Za-z]\$?\d+$/', $this->currentToken)) {
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?[A-Ia-i]?[A-Za-z]\$?\\d+$/u', $this->currentToken)) {
+ // If it's an external reference (Sheet1!A1 or Sheet1:Sheet2!A1 or Sheet1!$A$1 or Sheet1:Sheet2!$A$1)
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?[A-Ia-i]?[A-Za-z]\\$?\\d+$/u", $this->currentToken)) {
+ // If it's an external reference ('Sheet1'!A1 or 'Sheet1:Sheet2'!A1 or 'Sheet1'!$A$1 or 'Sheet1:Sheet2'!$A$1)
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (
+ preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+:(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $this->currentToken) ||
+ preg_match('/^(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+\.\.(\$)?[A-Ia-i]?[A-Za-z](\$)?\d+$/', $this->currentToken)
+ ) {
+ // if it's a range A1:B2 or $A$1:$B$2
+ // must be an error?
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match('/^' . self::REGEX_SHEET_TITLE_UNQUOTED . '(\\:' . self::REGEX_SHEET_TITLE_UNQUOTED . ')?\\!\$?([A-Ia-i]?[A-Za-z])?\$?\\d+:\$?([A-Ia-i]?[A-Za-z])?\$?\\d+$/u', $this->currentToken)) {
+ // If it's an external range (Sheet1!A1:B2 or Sheet1:Sheet2!A1:B2 or Sheet1!$A$1:$B$2 or Sheet1:Sheet2!$A$1:$B$2)
+ // must be an error?
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match("/^'" . self::REGEX_SHEET_TITLE_QUOTED . '(\\:' . self::REGEX_SHEET_TITLE_QUOTED . ")?'\\!\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+:\\$?([A-Ia-i]?[A-Za-z])?\\$?\\d+$/u", $this->currentToken)) {
+ // If it's an external range ('Sheet1'!A1:B2 or 'Sheet1'!A1:B2 or 'Sheet1'!$A$1:$B$2 or 'Sheet1'!$A$1:$B$2)
+ // must be an error?
+ $result = $this->createTree($this->currentToken, '', '');
+ $this->advance();
+
+ return $result;
+ } elseif (is_numeric($this->currentToken)) {
+ // If it's a number or a percent
+ if ($this->lookAhead === '%') {
+ $result = $this->createTree('ptgPercent', $this->currentToken, '');
+ $this->advance(); // Skip the percentage operator once we've pre-built that tree
+ } else {
+ $result = $this->createTree($this->currentToken, '', '');
+ }
+ $this->advance();
+
+ return $result;
+ } elseif (preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/i", $this->currentToken) && ($this->lookAhead === '(')) {
+ // if it's a function call
+ return $this->func();
+ } elseif (preg_match('/^' . Calculation::CALCULATION_REGEXP_DEFINEDNAME . '$/miu', $this->currentToken) && $this->spreadsheet->getDefinedName($this->currentToken) !== null) {
+ $result = $this->createTree('ptgName', $this->currentToken, '');
+ $this->advance();
+
+ return $result;
+ }
+
+ throw new WriterException('Syntax error: ' . $this->currentToken . ', lookahead: ' . $this->lookAhead . ', current char: ' . $this->currentCharacter);
+ }
+
+ /**
+ * It parses a function call. It assumes the following rule:
+ * Func -> ( Expr [,Expr]* ).
+ *
+ * @return mixed The parsed ptg'd tree on success
+ */
+ private function func()
+ {
+ $num_args = 0; // number of arguments received
+ $function = strtoupper($this->currentToken);
+ $result = ''; // initialize result
+ $this->advance();
+ $this->advance(); // eat the "("
+ while ($this->currentToken !== ')') {
+ if ($num_args > 0) {
+ if ($this->currentToken === ',' || $this->currentToken === ';') {
+ $this->advance(); // eat the "," or ";"
+ } else {
+ throw new WriterException("Syntax error: comma expected in function $function, arg #{$num_args}");
+ }
+ $result2 = $this->condition();
+ $result = $this->createTree('arg', $result, $result2);
+ } else { // first argument
+ $result2 = $this->condition();
+ $result = $this->createTree('arg', '', $result2);
+ }
+ ++$num_args;
+ }
+ if (!isset($this->functions[$function])) {
+ throw new WriterException("Function $function() doesn't exist");
+ }
+ $args = $this->functions[$function][1];
+ // If fixed number of args eg. TIME($i, $j, $k). Check that the number of args is valid.
+ if (($args >= 0) && ($args != $num_args)) {
+ throw new WriterException("Incorrect number of arguments in function $function() ");
+ }
+
+ $result = $this->createTree($function, $result, $num_args);
+ $this->advance(); // eat the ")"
+
+ return $result;
+ }
+
+ /**
+ * Creates a tree. In fact an array which may have one or two arrays (sub-trees)
+ * as elements.
+ *
+ * @param mixed $value the value of this node
+ * @param mixed $left the left array (sub-tree) or a final node
+ * @param mixed $right the right array (sub-tree) or a final node
+ *
+ * @return array A tree
+ */
+ private function createTree($value, $left, $right)
+ {
+ return ['value' => $value, 'left' => $left, 'right' => $right];
+ }
+
+ /**
+ * Builds a string containing the tree in reverse polish notation (What you
+ * would use in a HP calculator stack).
+ * The following tree:.
+ *
+ * +
+ * / \
+ * 2 3
+ *
+ * produces: "23+"
+ *
+ * The following tree:
+ *
+ * +
+ * / \
+ * 3 *
+ * / \
+ * 6 A1
+ *
+ * produces: "36A1*+"
+ *
+ * In fact all operands, functions, references, etc... are written as ptg's
+ *
+ * @param array $tree the optional tree to convert
+ *
+ * @return string The tree in reverse polish notation
+ */
+ public function toReversePolish($tree = [])
+ {
+ $polish = ''; // the string we are going to return
+ if (empty($tree)) { // If it's the first call use parseTree
+ $tree = $this->parseTree;
+ }
+
+ if (is_array($tree['left'])) {
+ $converted_tree = $this->toReversePolish($tree['left']);
+ $polish .= $converted_tree;
+ } elseif ($tree['left'] != '') { // It's a final node
+ $converted_tree = $this->convert($tree['left']);
+ $polish .= $converted_tree;
+ }
+ if (is_array($tree['right'])) {
+ $converted_tree = $this->toReversePolish($tree['right']);
+ $polish .= $converted_tree;
+ } elseif ($tree['right'] != '') { // It's a final node
+ $converted_tree = $this->convert($tree['right']);
+ $polish .= $converted_tree;
+ }
+ // if it's a function convert it here (so we can set it's arguments)
+ if (
+ preg_match("/^[A-Z0-9\xc0-\xdc\\.]+$/", $tree['value']) &&
+ !preg_match('/^([A-Ia-i]?[A-Za-z])(\d+)$/', $tree['value']) &&
+ !preg_match('/^[A-Ia-i]?[A-Za-z](\\d+)\\.\\.[A-Ia-i]?[A-Za-z](\\d+)$/', $tree['value']) &&
+ !is_numeric($tree['value']) &&
+ !isset($this->ptg[$tree['value']])
+ ) {
+ // left subtree for a function is always an array.
+ if ($tree['left'] != '') {
+ $left_tree = $this->toReversePolish($tree['left']);
+ } else {
+ $left_tree = '';
+ }
+ // add it's left subtree and return.
+ return $left_tree . $this->convertFunction($tree['value'], $tree['right']);
+ }
+ $converted_tree = $this->convert($tree['value']);
+
+ return $polish . $converted_tree;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php
new file mode 100644
index 0000000..9b6f6de
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php
@@ -0,0 +1,1191 @@
+
+// *
+// * The majority of this is _NOT_ my code. I simply ported it from the
+// * PERL Spreadsheet::WriteExcel module.
+// *
+// * The author of the Spreadsheet::WriteExcel module is John McNamara
+// *
+// *
+// * I _DO_ maintain this code, and John McNamara has nothing to do with the
+// * porting of this code to PHP. Any questions directly related to this
+// * class library should be directed to me.
+// *
+// * License Information:
+// *
+// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
+// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
+// *
+// * This library is free software; you can redistribute it and/or
+// * modify it under the terms of the GNU Lesser General Public
+// * License as published by the Free Software Foundation; either
+// * version 2.1 of the License, or (at your option) any later version.
+// *
+// * This library is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// * Lesser General Public License for more details.
+// *
+// * You should have received a copy of the GNU Lesser General Public
+// * License along with this library; if not, write to the Free Software
+// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// */
+class Workbook extends BIFFwriter
+{
+ /**
+ * Formula parser.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser
+ */
+ private $parser;
+
+ /**
+ * The BIFF file size for the workbook.
+ *
+ * @var int
+ *
+ * @see calcSheetOffsets()
+ */
+ private $biffSize;
+
+ /**
+ * XF Writers.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Xf[]
+ */
+ private $xfWriters = [];
+
+ /**
+ * Array containing the colour palette.
+ *
+ * @var array
+ */
+ private $palette;
+
+ /**
+ * The codepage indicates the text encoding used for strings.
+ *
+ * @var int
+ */
+ private $codepage;
+
+ /**
+ * The country code used for localization.
+ *
+ * @var int
+ */
+ private $countryCode;
+
+ /**
+ * Workbook.
+ *
+ * @var Spreadsheet
+ */
+ private $spreadsheet;
+
+ /**
+ * Fonts writers.
+ *
+ * @var Font[]
+ */
+ private $fontWriters = [];
+
+ /**
+ * Added fonts. Maps from font's hash => index in workbook.
+ *
+ * @var array
+ */
+ private $addedFonts = [];
+
+ /**
+ * Shared number formats.
+ *
+ * @var array
+ */
+ private $numberFormats = [];
+
+ /**
+ * Added number formats. Maps from numberFormat's hash => index in workbook.
+ *
+ * @var array
+ */
+ private $addedNumberFormats = [];
+
+ /**
+ * Sizes of the binary worksheet streams.
+ *
+ * @var array
+ */
+ private $worksheetSizes = [];
+
+ /**
+ * Offsets of the binary worksheet streams relative to the start of the global workbook stream.
+ *
+ * @var array
+ */
+ private $worksheetOffsets = [];
+
+ /**
+ * Total number of shared strings in workbook.
+ *
+ * @var int
+ */
+ private $stringTotal;
+
+ /**
+ * Number of unique shared strings in workbook.
+ *
+ * @var int
+ */
+ private $stringUnique;
+
+ /**
+ * Array of unique shared strings in workbook.
+ *
+ * @var array
+ */
+ private $stringTable;
+
+ /**
+ * Color cache.
+ */
+ private $colors;
+
+ /**
+ * Escher object corresponding to MSODRAWINGGROUP.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Shared\Escher
+ */
+ private $escher;
+
+ /**
+ * Class constructor.
+ *
+ * @param Spreadsheet $spreadsheet The Workbook
+ * @param int $str_total Total number of strings
+ * @param int $str_unique Total number of unique strings
+ * @param array $str_table String Table
+ * @param array $colors Colour Table
+ * @param Parser $parser The formula parser created for the Workbook
+ */
+ public function __construct(Spreadsheet $spreadsheet, &$str_total, &$str_unique, &$str_table, &$colors, Parser $parser)
+ {
+ // It needs to call its parent's constructor explicitly
+ parent::__construct();
+
+ $this->parser = $parser;
+ $this->biffSize = 0;
+ $this->palette = [];
+ $this->countryCode = -1;
+
+ $this->stringTotal = &$str_total;
+ $this->stringUnique = &$str_unique;
+ $this->stringTable = &$str_table;
+ $this->colors = &$colors;
+ $this->setPaletteXl97();
+
+ $this->spreadsheet = $spreadsheet;
+
+ $this->codepage = 0x04B0;
+
+ // Add empty sheets and Build color cache
+ $countSheets = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $countSheets; ++$i) {
+ $phpSheet = $spreadsheet->getSheet($i);
+
+ $this->parser->setExtSheet($phpSheet->getTitle(), $i); // Register worksheet name with parser
+
+ $supbook_index = 0x00;
+ $ref = pack('vvv', $supbook_index, $i, $i);
+ $this->parser->references[] = $ref; // Register reference with parser
+
+ // Sheet tab colors?
+ if ($phpSheet->isTabColorSet()) {
+ $this->addColor($phpSheet->getTabColor()->getRGB());
+ }
+ }
+ }
+
+ /**
+ * Add a new XF writer.
+ *
+ * @param bool $isStyleXf Is it a style XF?
+ *
+ * @return int Index to XF record
+ */
+ public function addXfWriter(Style $style, $isStyleXf = false)
+ {
+ $xfWriter = new Xf($style);
+ $xfWriter->setIsStyleXf($isStyleXf);
+
+ // Add the font if not already added
+ $fontIndex = $this->addFont($style->getFont());
+
+ // Assign the font index to the xf record
+ $xfWriter->setFontIndex($fontIndex);
+
+ // Background colors, best to treat these after the font so black will come after white in custom palette
+ $xfWriter->setFgColor($this->addColor($style->getFill()->getStartColor()->getRGB()));
+ $xfWriter->setBgColor($this->addColor($style->getFill()->getEndColor()->getRGB()));
+ $xfWriter->setBottomColor($this->addColor($style->getBorders()->getBottom()->getColor()->getRGB()));
+ $xfWriter->setTopColor($this->addColor($style->getBorders()->getTop()->getColor()->getRGB()));
+ $xfWriter->setRightColor($this->addColor($style->getBorders()->getRight()->getColor()->getRGB()));
+ $xfWriter->setLeftColor($this->addColor($style->getBorders()->getLeft()->getColor()->getRGB()));
+ $xfWriter->setDiagColor($this->addColor($style->getBorders()->getDiagonal()->getColor()->getRGB()));
+
+ // Add the number format if it is not a built-in one and not already added
+ if ($style->getNumberFormat()->getBuiltInFormatCode() === false) {
+ $numberFormatHashCode = $style->getNumberFormat()->getHashCode();
+
+ if (isset($this->addedNumberFormats[$numberFormatHashCode])) {
+ $numberFormatIndex = $this->addedNumberFormats[$numberFormatHashCode];
+ } else {
+ $numberFormatIndex = 164 + count($this->numberFormats);
+ $this->numberFormats[$numberFormatIndex] = $style->getNumberFormat();
+ $this->addedNumberFormats[$numberFormatHashCode] = $numberFormatIndex;
+ }
+ } else {
+ $numberFormatIndex = (int) $style->getNumberFormat()->getBuiltInFormatCode();
+ }
+
+ // Assign the number format index to xf record
+ $xfWriter->setNumberFormatIndex($numberFormatIndex);
+
+ $this->xfWriters[] = $xfWriter;
+
+ return count($this->xfWriters) - 1;
+ }
+
+ /**
+ * Add a font to added fonts.
+ *
+ * @return int Index to FONT record
+ */
+ public function addFont(\PhpOffice\PhpSpreadsheet\Style\Font $font)
+ {
+ $fontHashCode = $font->getHashCode();
+ if (isset($this->addedFonts[$fontHashCode])) {
+ $fontIndex = $this->addedFonts[$fontHashCode];
+ } else {
+ $countFonts = count($this->fontWriters);
+ $fontIndex = ($countFonts < 4) ? $countFonts : $countFonts + 1;
+
+ $fontWriter = new Font($font);
+ $fontWriter->setColorIndex($this->addColor($font->getColor()->getRGB()));
+ $this->fontWriters[] = $fontWriter;
+
+ $this->addedFonts[$fontHashCode] = $fontIndex;
+ }
+
+ return $fontIndex;
+ }
+
+ /**
+ * Alter color palette adding a custom color.
+ *
+ * @param string $rgb E.g. 'FF00AA'
+ *
+ * @return int Color index
+ */
+ private function addColor($rgb)
+ {
+ if (!isset($this->colors[$rgb])) {
+ $color =
+ [
+ hexdec(substr($rgb, 0, 2)),
+ hexdec(substr($rgb, 2, 2)),
+ hexdec(substr($rgb, 4)),
+ 0,
+ ];
+ $colorIndex = array_search($color, $this->palette);
+ if ($colorIndex) {
+ $this->colors[$rgb] = $colorIndex;
+ } else {
+ if (count($this->colors) === 0) {
+ $lastColor = 7;
+ } else {
+ $lastColor = end($this->colors);
+ }
+ if ($lastColor < 57) {
+ // then we add a custom color altering the palette
+ $colorIndex = $lastColor + 1;
+ $this->palette[$colorIndex] = $color;
+ $this->colors[$rgb] = $colorIndex;
+ } else {
+ // no room for more custom colors, just map to black
+ $colorIndex = 0;
+ }
+ }
+ } else {
+ // fetch already added custom color
+ $colorIndex = $this->colors[$rgb];
+ }
+
+ return $colorIndex;
+ }
+
+ /**
+ * Sets the colour palette to the Excel 97+ default.
+ */
+ private function setPaletteXl97(): void
+ {
+ $this->palette = [
+ 0x08 => [0x00, 0x00, 0x00, 0x00],
+ 0x09 => [0xff, 0xff, 0xff, 0x00],
+ 0x0A => [0xff, 0x00, 0x00, 0x00],
+ 0x0B => [0x00, 0xff, 0x00, 0x00],
+ 0x0C => [0x00, 0x00, 0xff, 0x00],
+ 0x0D => [0xff, 0xff, 0x00, 0x00],
+ 0x0E => [0xff, 0x00, 0xff, 0x00],
+ 0x0F => [0x00, 0xff, 0xff, 0x00],
+ 0x10 => [0x80, 0x00, 0x00, 0x00],
+ 0x11 => [0x00, 0x80, 0x00, 0x00],
+ 0x12 => [0x00, 0x00, 0x80, 0x00],
+ 0x13 => [0x80, 0x80, 0x00, 0x00],
+ 0x14 => [0x80, 0x00, 0x80, 0x00],
+ 0x15 => [0x00, 0x80, 0x80, 0x00],
+ 0x16 => [0xc0, 0xc0, 0xc0, 0x00],
+ 0x17 => [0x80, 0x80, 0x80, 0x00],
+ 0x18 => [0x99, 0x99, 0xff, 0x00],
+ 0x19 => [0x99, 0x33, 0x66, 0x00],
+ 0x1A => [0xff, 0xff, 0xcc, 0x00],
+ 0x1B => [0xcc, 0xff, 0xff, 0x00],
+ 0x1C => [0x66, 0x00, 0x66, 0x00],
+ 0x1D => [0xff, 0x80, 0x80, 0x00],
+ 0x1E => [0x00, 0x66, 0xcc, 0x00],
+ 0x1F => [0xcc, 0xcc, 0xff, 0x00],
+ 0x20 => [0x00, 0x00, 0x80, 0x00],
+ 0x21 => [0xff, 0x00, 0xff, 0x00],
+ 0x22 => [0xff, 0xff, 0x00, 0x00],
+ 0x23 => [0x00, 0xff, 0xff, 0x00],
+ 0x24 => [0x80, 0x00, 0x80, 0x00],
+ 0x25 => [0x80, 0x00, 0x00, 0x00],
+ 0x26 => [0x00, 0x80, 0x80, 0x00],
+ 0x27 => [0x00, 0x00, 0xff, 0x00],
+ 0x28 => [0x00, 0xcc, 0xff, 0x00],
+ 0x29 => [0xcc, 0xff, 0xff, 0x00],
+ 0x2A => [0xcc, 0xff, 0xcc, 0x00],
+ 0x2B => [0xff, 0xff, 0x99, 0x00],
+ 0x2C => [0x99, 0xcc, 0xff, 0x00],
+ 0x2D => [0xff, 0x99, 0xcc, 0x00],
+ 0x2E => [0xcc, 0x99, 0xff, 0x00],
+ 0x2F => [0xff, 0xcc, 0x99, 0x00],
+ 0x30 => [0x33, 0x66, 0xff, 0x00],
+ 0x31 => [0x33, 0xcc, 0xcc, 0x00],
+ 0x32 => [0x99, 0xcc, 0x00, 0x00],
+ 0x33 => [0xff, 0xcc, 0x00, 0x00],
+ 0x34 => [0xff, 0x99, 0x00, 0x00],
+ 0x35 => [0xff, 0x66, 0x00, 0x00],
+ 0x36 => [0x66, 0x66, 0x99, 0x00],
+ 0x37 => [0x96, 0x96, 0x96, 0x00],
+ 0x38 => [0x00, 0x33, 0x66, 0x00],
+ 0x39 => [0x33, 0x99, 0x66, 0x00],
+ 0x3A => [0x00, 0x33, 0x00, 0x00],
+ 0x3B => [0x33, 0x33, 0x00, 0x00],
+ 0x3C => [0x99, 0x33, 0x00, 0x00],
+ 0x3D => [0x99, 0x33, 0x66, 0x00],
+ 0x3E => [0x33, 0x33, 0x99, 0x00],
+ 0x3F => [0x33, 0x33, 0x33, 0x00],
+ ];
+ }
+
+ /**
+ * Assemble worksheets into a workbook and send the BIFF data to an OLE
+ * storage.
+ *
+ * @param array $pWorksheetSizes The sizes in bytes of the binary worksheet streams
+ *
+ * @return string Binary data for workbook stream
+ */
+ public function writeWorkbook(array $pWorksheetSizes)
+ {
+ $this->worksheetSizes = $pWorksheetSizes;
+
+ // Calculate the number of selected worksheet tabs and call the finalization
+ // methods for each worksheet
+ $total_worksheets = $this->spreadsheet->getSheetCount();
+
+ // Add part 1 of the Workbook globals, what goes before the SHEET records
+ $this->storeBof(0x0005);
+ $this->writeCodepage();
+ $this->writeWindow1();
+
+ $this->writeDateMode();
+ $this->writeAllFonts();
+ $this->writeAllNumberFormats();
+ $this->writeAllXfs();
+ $this->writeAllStyles();
+ $this->writePalette();
+
+ // Prepare part 3 of the workbook global stream, what goes after the SHEET records
+ $part3 = '';
+ if ($this->countryCode !== -1) {
+ $part3 .= $this->writeCountry();
+ }
+ $part3 .= $this->writeRecalcId();
+
+ $part3 .= $this->writeSupbookInternal();
+ /* TODO: store external SUPBOOK records and XCT and CRN records
+ in case of external references for BIFF8 */
+ $part3 .= $this->writeExternalsheetBiff8();
+ $part3 .= $this->writeAllDefinedNamesBiff8();
+ $part3 .= $this->writeMsoDrawingGroup();
+ $part3 .= $this->writeSharedStringsTable();
+
+ $part3 .= $this->writeEof();
+
+ // Add part 2 of the Workbook globals, the SHEET records
+ $this->calcSheetOffsets();
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $this->writeBoundSheet($this->spreadsheet->getSheet($i), $this->worksheetOffsets[$i]);
+ }
+
+ // Add part 3 of the Workbook globals
+ $this->_data .= $part3;
+
+ return $this->_data;
+ }
+
+ /**
+ * Calculate offsets for Worksheet BOF records.
+ */
+ private function calcSheetOffsets(): void
+ {
+ $boundsheet_length = 10; // fixed length for a BOUNDSHEET record
+
+ // size of Workbook globals part 1 + 3
+ $offset = $this->_datasize;
+
+ // add size of Workbook globals part 2, the length of the SHEET records
+ $total_worksheets = count($this->spreadsheet->getAllSheets());
+ foreach ($this->spreadsheet->getWorksheetIterator() as $sheet) {
+ $offset += $boundsheet_length + strlen(StringHelper::UTF8toBIFF8UnicodeShort($sheet->getTitle()));
+ }
+
+ // add the sizes of each of the Sheet substreams, respectively
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $this->worksheetOffsets[$i] = $offset;
+ $offset += $this->worksheetSizes[$i];
+ }
+ $this->biffSize = $offset;
+ }
+
+ /**
+ * Store the Excel FONT records.
+ */
+ private function writeAllFonts(): void
+ {
+ foreach ($this->fontWriters as $fontWriter) {
+ $this->append($fontWriter->writeFont());
+ }
+ }
+
+ /**
+ * Store user defined numerical formats i.e. FORMAT records.
+ */
+ private function writeAllNumberFormats(): void
+ {
+ foreach ($this->numberFormats as $numberFormatIndex => $numberFormat) {
+ $this->writeNumberFormat($numberFormat->getFormatCode(), $numberFormatIndex);
+ }
+ }
+
+ /**
+ * Write all XF records.
+ */
+ private function writeAllXfs(): void
+ {
+ foreach ($this->xfWriters as $xfWriter) {
+ $this->append($xfWriter->writeXf());
+ }
+ }
+
+ /**
+ * Write all STYLE records.
+ */
+ private function writeAllStyles(): void
+ {
+ $this->writeStyle();
+ }
+
+ private function parseDefinedNameValue(DefinedName $pDefinedName): string
+ {
+ $definedRange = $pDefinedName->getValue();
+ $splitCount = preg_match_all(
+ '/' . Calculation::CALCULATION_REGEXP_CELLREF . '/mui',
+ $definedRange,
+ $splitRanges,
+ PREG_OFFSET_CAPTURE
+ );
+
+ $lengths = array_map('strlen', array_column($splitRanges[0], 0));
+ $offsets = array_column($splitRanges[0], 1);
+
+ $worksheets = $splitRanges[2];
+ $columns = $splitRanges[6];
+ $rows = $splitRanges[7];
+
+ while ($splitCount > 0) {
+ --$splitCount;
+ $length = $lengths[$splitCount];
+ $offset = $offsets[$splitCount];
+ $worksheet = $worksheets[$splitCount][0];
+ $column = $columns[$splitCount][0];
+ $row = $rows[$splitCount][0];
+
+ $newRange = '';
+ if (empty($worksheet)) {
+ if (($offset === 0) || ($definedRange[$offset - 1] !== ':')) {
+ // We need a worksheet
+ $worksheet = $pDefinedName->getWorksheet()->getTitle();
+ }
+ } else {
+ $worksheet = str_replace("''", "'", trim($worksheet, "'"));
+ }
+ if (!empty($worksheet)) {
+ $newRange = "'" . str_replace("'", "''", $worksheet) . "'!";
+ }
+
+ if (!empty($column)) {
+ $newRange .= "\${$column}";
+ }
+ if (!empty($row)) {
+ $newRange .= "\${$row}";
+ }
+
+ $definedRange = substr($definedRange, 0, $offset) . $newRange . substr($definedRange, $offset + $length);
+ }
+
+ return $definedRange;
+ }
+
+ /**
+ * Writes all the DEFINEDNAME records (BIFF8).
+ * So far this is only used for repeating rows/columns (print titles) and print areas.
+ */
+ private function writeAllDefinedNamesBiff8()
+ {
+ $chunk = '';
+
+ // Named ranges
+ $definedNames = $this->spreadsheet->getDefinedNames();
+ if (count($definedNames) > 0) {
+ // Loop named ranges
+ foreach ($definedNames as $definedName) {
+ $range = $this->parseDefinedNameValue($definedName);
+
+ // parse formula
+ try {
+ $error = $this->parser->parse($range);
+ $formulaData = $this->parser->toReversePolish();
+
+ // make sure tRef3d is of type tRef3dR (0x3A)
+ if (isset($formulaData[0]) && ($formulaData[0] == "\x7A" || $formulaData[0] == "\x5A")) {
+ $formulaData = "\x3A" . substr($formulaData, 1);
+ }
+
+ if ($definedName->getLocalOnly()) {
+ // local scope
+ $scope = $this->spreadsheet->getIndex($definedName->getScope()) + 1;
+ } else {
+ // global scope
+ $scope = 0;
+ }
+ $chunk .= $this->writeData($this->writeDefinedNameBiff8($definedName->getName(), $formulaData, $scope, false));
+ } catch (PhpSpreadsheetException $e) {
+ // do nothing
+ }
+ }
+ }
+
+ // total number of sheets
+ $total_worksheets = $this->spreadsheet->getSheetCount();
+
+ // write the print titles (repeating rows, columns), if any
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
+ // simultaneous repeatColumns repeatRows
+ if ($sheetSetup->isColumnsToRepeatAtLeftSet() && $sheetSetup->isRowsToRepeatAtTopSet()) {
+ $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
+ $colmin = Coordinate::columnIndexFromString($repeat[0]) - 1;
+ $colmax = Coordinate::columnIndexFromString($repeat[1]) - 1;
+
+ $repeat = $sheetSetup->getRowsToRepeatAtTop();
+ $rowmin = $repeat[0] - 1;
+ $rowmax = $repeat[1] - 1;
+
+ // construct formula data manually
+ $formulaData = pack('Cv', 0x29, 0x17); // tMemFunc
+ $formulaData .= pack('Cvvvvv', 0x3B, $i, 0, 65535, $colmin, $colmax); // tArea3d
+ $formulaData .= pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, 0, 255); // tArea3d
+ $formulaData .= pack('C', 0x10); // tList
+
+ // store the DEFINEDNAME record
+ $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
+
+ // (exclusive) either repeatColumns or repeatRows
+ } elseif ($sheetSetup->isColumnsToRepeatAtLeftSet() || $sheetSetup->isRowsToRepeatAtTopSet()) {
+ // Columns to repeat
+ if ($sheetSetup->isColumnsToRepeatAtLeftSet()) {
+ $repeat = $sheetSetup->getColumnsToRepeatAtLeft();
+ $colmin = Coordinate::columnIndexFromString($repeat[0]) - 1;
+ $colmax = Coordinate::columnIndexFromString($repeat[1]) - 1;
+ } else {
+ $colmin = 0;
+ $colmax = 255;
+ }
+ // Rows to repeat
+ if ($sheetSetup->isRowsToRepeatAtTopSet()) {
+ $repeat = $sheetSetup->getRowsToRepeatAtTop();
+ $rowmin = $repeat[0] - 1;
+ $rowmax = $repeat[1] - 1;
+ } else {
+ $rowmin = 0;
+ $rowmax = 65535;
+ }
+
+ // construct formula data manually because parser does not recognize absolute 3d cell references
+ $formulaData = pack('Cvvvvv', 0x3B, $i, $rowmin, $rowmax, $colmin, $colmax);
+
+ // store the DEFINEDNAME record
+ $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x07), $formulaData, $i + 1, true));
+ }
+ }
+
+ // write the print areas, if any
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $sheetSetup = $this->spreadsheet->getSheet($i)->getPageSetup();
+ if ($sheetSetup->isPrintAreaSet()) {
+ // Print area, e.g. A3:J6,H1:X20
+ $printArea = Coordinate::splitRange($sheetSetup->getPrintArea());
+ $countPrintArea = count($printArea);
+
+ $formulaData = '';
+ for ($j = 0; $j < $countPrintArea; ++$j) {
+ $printAreaRect = $printArea[$j]; // e.g. A3:J6
+ $printAreaRect[0] = Coordinate::coordinateFromString($printAreaRect[0]);
+ $printAreaRect[1] = Coordinate::coordinateFromString($printAreaRect[1]);
+
+ $print_rowmin = $printAreaRect[0][1] - 1;
+ $print_rowmax = $printAreaRect[1][1] - 1;
+ $print_colmin = Coordinate::columnIndexFromString($printAreaRect[0][0]) - 1;
+ $print_colmax = Coordinate::columnIndexFromString($printAreaRect[1][0]) - 1;
+
+ // construct formula data manually because parser does not recognize absolute 3d cell references
+ $formulaData .= pack('Cvvvvv', 0x3B, $i, $print_rowmin, $print_rowmax, $print_colmin, $print_colmax);
+
+ if ($j > 0) {
+ $formulaData .= pack('C', 0x10); // list operator token ','
+ }
+ }
+
+ // store the DEFINEDNAME record
+ $chunk .= $this->writeData($this->writeDefinedNameBiff8(pack('C', 0x06), $formulaData, $i + 1, true));
+ }
+ }
+
+ // write autofilters, if any
+ for ($i = 0; $i < $total_worksheets; ++$i) {
+ $sheetAutoFilter = $this->spreadsheet->getSheet($i)->getAutoFilter();
+ $autoFilterRange = $sheetAutoFilter->getRange();
+ if (!empty($autoFilterRange)) {
+ $rangeBounds = Coordinate::rangeBoundaries($autoFilterRange);
+
+ //Autofilter built in name
+ $name = pack('C', 0x0D);
+
+ $chunk .= $this->writeData($this->writeShortNameBiff8($name, $i + 1, $rangeBounds, true));
+ }
+ }
+
+ return $chunk;
+ }
+
+ /**
+ * Write a DEFINEDNAME record for BIFF8 using explicit binary formula data.
+ *
+ * @param string $name The name in UTF-8
+ * @param string $formulaData The binary formula data
+ * @param int $sheetIndex 1-based sheet index the defined name applies to. 0 = global
+ * @param bool $isBuiltIn Built-in name?
+ *
+ * @return string Complete binary record data
+ */
+ private function writeDefinedNameBiff8($name, $formulaData, $sheetIndex = 0, $isBuiltIn = false)
+ {
+ $record = 0x0018;
+
+ // option flags
+ $options = $isBuiltIn ? 0x20 : 0x00;
+
+ // length of the name, character count
+ $nlen = StringHelper::countCharacters($name);
+
+ // name with stripped length field
+ $name = substr(StringHelper::UTF8toBIFF8UnicodeLong($name), 2);
+
+ // size of the formula (in bytes)
+ $sz = strlen($formulaData);
+
+ // combine the parts
+ $data = pack('vCCvvvCCCC', $options, 0, $nlen, $sz, 0, $sheetIndex, 0, 0, 0, 0)
+ . $name . $formulaData;
+ $length = strlen($data);
+
+ $header = pack('vv', $record, $length);
+
+ return $header . $data;
+ }
+
+ /**
+ * Write a short NAME record.
+ *
+ * @param string $name
+ * @param string $sheetIndex 1-based sheet index the defined name applies to. 0 = global
+ * @param integer[][] $rangeBounds range boundaries
+ * @param bool $isHidden
+ *
+ * @return string Complete binary record data
+ * */
+ private function writeShortNameBiff8($name, $sheetIndex, $rangeBounds, $isHidden = false)
+ {
+ $record = 0x0018;
+
+ // option flags
+ $options = ($isHidden ? 0x21 : 0x00);
+
+ $extra = pack(
+ 'Cvvvvv',
+ 0x3B,
+ $sheetIndex - 1,
+ $rangeBounds[0][1] - 1,
+ $rangeBounds[1][1] - 1,
+ $rangeBounds[0][0] - 1,
+ $rangeBounds[1][0] - 1
+ );
+
+ // size of the formula (in bytes)
+ $sz = strlen($extra);
+
+ // combine the parts
+ $data = pack('vCCvvvCCCCC', $options, 0, 1, $sz, 0, $sheetIndex, 0, 0, 0, 0, 0)
+ . $name . $extra;
+ $length = strlen($data);
+
+ $header = pack('vv', $record, $length);
+
+ return $header . $data;
+ }
+
+ /**
+ * Stores the CODEPAGE biff record.
+ */
+ private function writeCodepage(): void
+ {
+ $record = 0x0042; // Record identifier
+ $length = 0x0002; // Number of bytes to follow
+ $cv = $this->codepage; // The code page
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $cv);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write Excel BIFF WINDOW1 record.
+ */
+ private function writeWindow1(): void
+ {
+ $record = 0x003D; // Record identifier
+ $length = 0x0012; // Number of bytes to follow
+
+ $xWn = 0x0000; // Horizontal position of window
+ $yWn = 0x0000; // Vertical position of window
+ $dxWn = 0x25BC; // Width of window
+ $dyWn = 0x1572; // Height of window
+
+ $grbit = 0x0038; // Option flags
+
+ // not supported by PhpSpreadsheet, so there is only one selected sheet, the active
+ $ctabsel = 1; // Number of workbook tabs selected
+
+ $wTabRatio = 0x0258; // Tab to scrollbar ratio
+
+ // not supported by PhpSpreadsheet, set to 0
+ $itabFirst = 0; // 1st displayed worksheet
+ $itabCur = $this->spreadsheet->getActiveSheetIndex(); // Active worksheet
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvvvvvvv', $xWn, $yWn, $dxWn, $dyWn, $grbit, $itabCur, $itabFirst, $ctabsel, $wTabRatio);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Writes Excel BIFF BOUNDSHEET record.
+ *
+ * @param Worksheet $sheet Worksheet name
+ * @param int $offset Location of worksheet BOF
+ */
+ private function writeBoundSheet($sheet, $offset): void
+ {
+ $sheetname = $sheet->getTitle();
+ $record = 0x0085; // Record identifier
+
+ // sheet state
+ switch ($sheet->getSheetState()) {
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_VISIBLE:
+ $ss = 0x00;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_HIDDEN:
+ $ss = 0x01;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::SHEETSTATE_VERYHIDDEN:
+ $ss = 0x02;
+
+ break;
+ default:
+ $ss = 0x00;
+
+ break;
+ }
+
+ // sheet type
+ $st = 0x00;
+
+ $grbit = 0x0000; // Visibility and sheet type
+
+ $data = pack('VCC', $offset, $ss, $st);
+ $data .= StringHelper::UTF8toBIFF8UnicodeShort($sheetname);
+
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write Internal SUPBOOK record.
+ */
+ private function writeSupbookInternal()
+ {
+ $record = 0x01AE; // Record identifier
+ $length = 0x0004; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vv', $this->spreadsheet->getSheetCount(), 0x0401);
+
+ return $this->writeData($header . $data);
+ }
+
+ /**
+ * Writes the Excel BIFF EXTERNSHEET record. These references are used by
+ * formulas.
+ */
+ private function writeExternalsheetBiff8()
+ {
+ $totalReferences = count($this->parser->references);
+ $record = 0x0017; // Record identifier
+ $length = 2 + 6 * $totalReferences; // Number of bytes to follow
+
+ $supbook_index = 0; // FIXME: only using internal SUPBOOK record
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $totalReferences);
+ for ($i = 0; $i < $totalReferences; ++$i) {
+ $data .= $this->parser->references[$i];
+ }
+
+ return $this->writeData($header . $data);
+ }
+
+ /**
+ * Write Excel BIFF STYLE records.
+ */
+ private function writeStyle(): void
+ {
+ $record = 0x0293; // Record identifier
+ $length = 0x0004; // Bytes to follow
+
+ $ixfe = 0x8000; // Index to cell style XF
+ $BuiltIn = 0x00; // Built-in style
+ $iLevel = 0xff; // Outline style level
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vCC', $ixfe, $BuiltIn, $iLevel);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Writes Excel FORMAT record for non "built-in" numerical formats.
+ *
+ * @param string $format Custom format string
+ * @param int $ifmt Format index code
+ */
+ private function writeNumberFormat($format, $ifmt): void
+ {
+ $record = 0x041E; // Record identifier
+
+ $numberFormatString = StringHelper::UTF8toBIFF8UnicodeLong($format);
+ $length = 2 + strlen($numberFormatString); // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $ifmt) . $numberFormatString;
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write DATEMODE record to indicate the date system in use (1904 or 1900).
+ */
+ private function writeDateMode(): void
+ {
+ $record = 0x0022; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $f1904 = (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904)
+ ? 1
+ : 0; // Flag for 1904 date system
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $f1904);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Stores the COUNTRY record for localization.
+ *
+ * @return string
+ */
+ private function writeCountry()
+ {
+ $record = 0x008C; // Record identifier
+ $length = 4; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ // using the same country code always for simplicity
+ $data = pack('vv', $this->countryCode, $this->countryCode);
+
+ return $this->writeData($header . $data);
+ }
+
+ /**
+ * Write the RECALCID record.
+ *
+ * @return string
+ */
+ private function writeRecalcId()
+ {
+ $record = 0x01C1; // Record identifier
+ $length = 8; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+
+ // by inspection of real Excel files, MS Office Excel 2007 writes this
+ $data = pack('VV', 0x000001C1, 0x00001E667);
+
+ return $this->writeData($header . $data);
+ }
+
+ /**
+ * Stores the PALETTE biff record.
+ */
+ private function writePalette(): void
+ {
+ $aref = $this->palette;
+
+ $record = 0x0092; // Record identifier
+ $length = 2 + 4 * count($aref); // Number of bytes to follow
+ $ccv = count($aref); // Number of RGB values to follow
+ $data = ''; // The RGB data
+
+ // Pack the RGB data
+ foreach ($aref as $color) {
+ foreach ($color as $byte) {
+ $data .= pack('C', $byte);
+ }
+ }
+
+ $header = pack('vvv', $record, $length, $ccv);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Handling of the SST continue blocks is complicated by the need to include an
+ * additional continuation byte depending on whether the string is split between
+ * blocks or whether it starts at the beginning of the block. (There are also
+ * additional complications that will arise later when/if Rich Strings are
+ * supported).
+ *
+ * The Excel documentation says that the SST record should be followed by an
+ * EXTSST record. The EXTSST record is a hash table that is used to optimise
+ * access to SST. However, despite the documentation it doesn't seem to be
+ * required so we will ignore it.
+ *
+ * @return string Binary data
+ */
+ private function writeSharedStringsTable()
+ {
+ // maximum size of record data (excluding record header)
+ $continue_limit = 8224;
+
+ // initialize array of record data blocks
+ $recordDatas = [];
+
+ // start SST record data block with total number of strings, total number of unique strings
+ $recordData = pack('VV', $this->stringTotal, $this->stringUnique);
+
+ // loop through all (unique) strings in shared strings table
+ foreach (array_keys($this->stringTable) as $string) {
+ // here $string is a BIFF8 encoded string
+
+ // length = character count
+ $headerinfo = unpack('vlength/Cencoding', $string);
+
+ // currently, this is always 1 = uncompressed
+ $encoding = $headerinfo['encoding'];
+
+ // initialize finished writing current $string
+ $finished = false;
+
+ while ($finished === false) {
+ // normally, there will be only one cycle, but if string cannot immediately be written as is
+ // there will be need for more than one cylcle, if string longer than one record data block, there
+ // may be need for even more cycles
+
+ if (strlen($recordData) + strlen($string) <= $continue_limit) {
+ // then we can write the string (or remainder of string) without any problems
+ $recordData .= $string;
+
+ if (strlen($recordData) + strlen($string) == $continue_limit) {
+ // we close the record data block, and initialize a new one
+ $recordDatas[] = $recordData;
+ $recordData = '';
+ }
+
+ // we are finished writing this string
+ $finished = true;
+ } else {
+ // special treatment writing the string (or remainder of the string)
+ // If the string is very long it may need to be written in more than one CONTINUE record.
+
+ // check how many bytes more there is room for in the current record
+ $space_remaining = $continue_limit - strlen($recordData);
+
+ // minimum space needed
+ // uncompressed: 2 byte string length length field + 1 byte option flags + 2 byte character
+ // compressed: 2 byte string length length field + 1 byte option flags + 1 byte character
+ $min_space_needed = ($encoding == 1) ? 5 : 4;
+
+ // We have two cases
+ // 1. space remaining is less than minimum space needed
+ // here we must waste the space remaining and move to next record data block
+ // 2. space remaining is greater than or equal to minimum space needed
+ // here we write as much as we can in the current block, then move to next record data block
+
+ // 1. space remaining is less than minimum space needed
+ if ($space_remaining < $min_space_needed) {
+ // we close the block, store the block data
+ $recordDatas[] = $recordData;
+
+ // and start new record data block where we start writing the string
+ $recordData = '';
+
+ // 2. space remaining is greater than or equal to minimum space needed
+ } else {
+ // initialize effective remaining space, for Unicode strings this may need to be reduced by 1, see below
+ $effective_space_remaining = $space_remaining;
+
+ // for uncompressed strings, sometimes effective space remaining is reduced by 1
+ if ($encoding == 1 && (strlen($string) - $space_remaining) % 2 == 1) {
+ --$effective_space_remaining;
+ }
+
+ // one block fininshed, store the block data
+ $recordData .= substr($string, 0, $effective_space_remaining);
+
+ $string = substr($string, $effective_space_remaining); // for next cycle in while loop
+ $recordDatas[] = $recordData;
+
+ // start new record data block with the repeated option flags
+ $recordData = pack('C', $encoding);
+ }
+ }
+ }
+ }
+
+ // Store the last record data block unless it is empty
+ // if there was no need for any continue records, this will be the for SST record data block itself
+ if (strlen($recordData) > 0) {
+ $recordDatas[] = $recordData;
+ }
+
+ // combine into one chunk with all the blocks SST, CONTINUE,...
+ $chunk = '';
+ foreach ($recordDatas as $i => $recordData) {
+ // first block should have the SST record header, remaing should have CONTINUE header
+ $record = ($i == 0) ? 0x00FC : 0x003C;
+
+ $header = pack('vv', $record, strlen($recordData));
+ $data = $header . $recordData;
+
+ $chunk .= $this->writeData($data);
+ }
+
+ return $chunk;
+ }
+
+ /**
+ * Writes the MSODRAWINGGROUP record if needed. Possibly split using CONTINUE records.
+ */
+ private function writeMsoDrawingGroup()
+ {
+ // write the Escher stream if necessary
+ if (isset($this->escher)) {
+ $writer = new Escher($this->escher);
+ $data = $writer->close();
+
+ $record = 0x00EB;
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ return $this->writeData($header . $data);
+ }
+
+ return '';
+ }
+
+ /**
+ * Get Escher object.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Shared\Escher
+ */
+ public function getEscher()
+ {
+ return $this->escher;
+ }
+
+ /**
+ * Set Escher object.
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Shared\Escher $pValue
+ */
+ public function setEscher(?\PhpOffice\PhpSpreadsheet\Shared\Escher $pValue = null): void
+ {
+ $this->escher = $pValue;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php
new file mode 100644
index 0000000..a5680e3
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php
@@ -0,0 +1,4490 @@
+
+// *
+// * The majority of this is _NOT_ my code. I simply ported it from the
+// * PERL Spreadsheet::WriteExcel module.
+// *
+// * The author of the Spreadsheet::WriteExcel module is John McNamara
+// *
+// *
+// * I _DO_ maintain this code, and John McNamara has nothing to do with the
+// * porting of this code to PHP. Any questions directly related to this
+// * class library should be directed to me.
+// *
+// * License Information:
+// *
+// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
+// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
+// *
+// * This library is free software; you can redistribute it and/or
+// * modify it under the terms of the GNU Lesser General Public
+// * License as published by the Free Software Foundation; either
+// * version 2.1 of the License, or (at your option) any later version.
+// *
+// * This library is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// * Lesser General Public License for more details.
+// *
+// * You should have received a copy of the GNU Lesser General Public
+// * License along with this library; if not, write to the Free Software
+// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// */
+class Worksheet extends BIFFwriter
+{
+ /**
+ * Formula parser.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser
+ */
+ private $parser;
+
+ /**
+ * Maximum number of characters for a string (LABEL record in BIFF5).
+ *
+ * @var int
+ */
+ private $xlsStringMaxLength;
+
+ /**
+ * Array containing format information for columns.
+ *
+ * @var array
+ */
+ private $columnInfo;
+
+ /**
+ * Array containing the selected area for the worksheet.
+ *
+ * @var array
+ */
+ private $selection;
+
+ /**
+ * The active pane for the worksheet.
+ *
+ * @var int
+ */
+ private $activePane;
+
+ /**
+ * Whether to use outline.
+ *
+ * @var int
+ */
+ private $outlineOn;
+
+ /**
+ * Auto outline styles.
+ *
+ * @var bool
+ */
+ private $outlineStyle;
+
+ /**
+ * Whether to have outline summary below.
+ *
+ * @var bool
+ */
+ private $outlineBelow;
+
+ /**
+ * Whether to have outline summary at the right.
+ *
+ * @var bool
+ */
+ private $outlineRight;
+
+ /**
+ * Reference to the total number of strings in the workbook.
+ *
+ * @var int
+ */
+ private $stringTotal;
+
+ /**
+ * Reference to the number of unique strings in the workbook.
+ *
+ * @var int
+ */
+ private $stringUnique;
+
+ /**
+ * Reference to the array containing all the unique strings in the workbook.
+ *
+ * @var array
+ */
+ private $stringTable;
+
+ /**
+ * Color cache.
+ */
+ private $colors;
+
+ /**
+ * Index of first used row (at least 0).
+ *
+ * @var int
+ */
+ private $firstRowIndex;
+
+ /**
+ * Index of last used row. (no used rows means -1).
+ *
+ * @var int
+ */
+ private $lastRowIndex;
+
+ /**
+ * Index of first used column (at least 0).
+ *
+ * @var int
+ */
+ private $firstColumnIndex;
+
+ /**
+ * Index of last used column (no used columns means -1).
+ *
+ * @var int
+ */
+ private $lastColumnIndex;
+
+ /**
+ * Sheet object.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet
+ */
+ public $phpSheet;
+
+ /**
+ * Count cell style Xfs.
+ *
+ * @var int
+ */
+ private $countCellStyleXfs;
+
+ /**
+ * Escher object corresponding to MSODRAWING.
+ *
+ * @var \PhpOffice\PhpSpreadsheet\Shared\Escher
+ */
+ private $escher;
+
+ /**
+ * Array of font hashes associated to FONT records index.
+ *
+ * @var array
+ */
+ public $fontHashIndex;
+
+ /**
+ * @var bool
+ */
+ private $preCalculateFormulas;
+
+ /**
+ * @var int
+ */
+ private $printHeaders;
+
+ /**
+ * Constructor.
+ *
+ * @param int $str_total Total number of strings
+ * @param int $str_unique Total number of unique strings
+ * @param array &$str_table String Table
+ * @param array &$colors Colour Table
+ * @param Parser $parser The formula parser created for the Workbook
+ * @param bool $preCalculateFormulas Flag indicating whether formulas should be calculated or just written
+ * @param \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $phpSheet The worksheet to write
+ */
+ public function __construct(&$str_total, &$str_unique, &$str_table, &$colors, Parser $parser, $preCalculateFormulas, \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $phpSheet)
+ {
+ // It needs to call its parent's constructor explicitly
+ parent::__construct();
+
+ $this->preCalculateFormulas = $preCalculateFormulas;
+ $this->stringTotal = &$str_total;
+ $this->stringUnique = &$str_unique;
+ $this->stringTable = &$str_table;
+ $this->colors = &$colors;
+ $this->parser = $parser;
+
+ $this->phpSheet = $phpSheet;
+
+ $this->xlsStringMaxLength = 255;
+ $this->columnInfo = [];
+ $this->selection = [0, 0, 0, 0];
+ $this->activePane = 3;
+
+ $this->printHeaders = 0;
+
+ $this->outlineStyle = 0;
+ $this->outlineBelow = 1;
+ $this->outlineRight = 1;
+ $this->outlineOn = 1;
+
+ $this->fontHashIndex = [];
+
+ // calculate values for DIMENSIONS record
+ $minR = 1;
+ $minC = 'A';
+
+ $maxR = $this->phpSheet->getHighestRow();
+ $maxC = $this->phpSheet->getHighestColumn();
+
+ // Determine lowest and highest column and row
+ $this->lastRowIndex = ($maxR > 65535) ? 65535 : $maxR;
+
+ $this->firstColumnIndex = Coordinate::columnIndexFromString($minC);
+ $this->lastColumnIndex = Coordinate::columnIndexFromString($maxC);
+
+// if ($this->firstColumnIndex > 255) $this->firstColumnIndex = 255;
+ if ($this->lastColumnIndex > 255) {
+ $this->lastColumnIndex = 255;
+ }
+
+ $this->countCellStyleXfs = count($phpSheet->getParent()->getCellStyleXfCollection());
+ }
+
+ /**
+ * Add data to the beginning of the workbook (note the reverse order)
+ * and to the end of the workbook.
+ *
+ * @see \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook::storeWorkbook()
+ */
+ public function close(): void
+ {
+ $phpSheet = $this->phpSheet;
+
+ // Storing selected cells and active sheet because it changes while parsing cells with formulas.
+ $selectedCells = $this->phpSheet->getSelectedCells();
+ $activeSheetIndex = $this->phpSheet->getParent()->getActiveSheetIndex();
+
+ // Write BOF record
+ $this->storeBof(0x0010);
+
+ // Write PRINTHEADERS
+ $this->writePrintHeaders();
+
+ // Write PRINTGRIDLINES
+ $this->writePrintGridlines();
+
+ // Write GRIDSET
+ $this->writeGridset();
+
+ // Calculate column widths
+ $phpSheet->calculateColumnWidths();
+
+ // Column dimensions
+ if (($defaultWidth = $phpSheet->getDefaultColumnDimension()->getWidth()) < 0) {
+ $defaultWidth = \PhpOffice\PhpSpreadsheet\Shared\Font::getDefaultColumnWidthByFont($phpSheet->getParent()->getDefaultStyle()->getFont());
+ }
+
+ $columnDimensions = $phpSheet->getColumnDimensions();
+ $maxCol = $this->lastColumnIndex - 1;
+ for ($i = 0; $i <= $maxCol; ++$i) {
+ $hidden = 0;
+ $level = 0;
+ $xfIndex = 15; // there are 15 cell style Xfs
+
+ $width = $defaultWidth;
+
+ $columnLetter = Coordinate::stringFromColumnIndex($i + 1);
+ if (isset($columnDimensions[$columnLetter])) {
+ $columnDimension = $columnDimensions[$columnLetter];
+ if ($columnDimension->getWidth() >= 0) {
+ $width = $columnDimension->getWidth();
+ }
+ $hidden = $columnDimension->getVisible() ? 0 : 1;
+ $level = $columnDimension->getOutlineLevel();
+ $xfIndex = $columnDimension->getXfIndex() + 15; // there are 15 cell style Xfs
+ }
+
+ // Components of columnInfo:
+ // $firstcol first column on the range
+ // $lastcol last column on the range
+ // $width width to set
+ // $xfIndex The optional cell style Xf index to apply to the columns
+ // $hidden The optional hidden atribute
+ // $level The optional outline level
+ $this->columnInfo[] = [$i, $i, $width, $xfIndex, $hidden, $level];
+ }
+
+ // Write GUTS
+ $this->writeGuts();
+
+ // Write DEFAULTROWHEIGHT
+ $this->writeDefaultRowHeight();
+ // Write WSBOOL
+ $this->writeWsbool();
+ // Write horizontal and vertical page breaks
+ $this->writeBreaks();
+ // Write page header
+ $this->writeHeader();
+ // Write page footer
+ $this->writeFooter();
+ // Write page horizontal centering
+ $this->writeHcenter();
+ // Write page vertical centering
+ $this->writeVcenter();
+ // Write left margin
+ $this->writeMarginLeft();
+ // Write right margin
+ $this->writeMarginRight();
+ // Write top margin
+ $this->writeMarginTop();
+ // Write bottom margin
+ $this->writeMarginBottom();
+ // Write page setup
+ $this->writeSetup();
+ // Write sheet protection
+ $this->writeProtect();
+ // Write SCENPROTECT
+ $this->writeScenProtect();
+ // Write OBJECTPROTECT
+ $this->writeObjectProtect();
+ // Write sheet password
+ $this->writePassword();
+ // Write DEFCOLWIDTH record
+ $this->writeDefcol();
+
+ // Write the COLINFO records if they exist
+ if (!empty($this->columnInfo)) {
+ $colcount = count($this->columnInfo);
+ for ($i = 0; $i < $colcount; ++$i) {
+ $this->writeColinfo($this->columnInfo[$i]);
+ }
+ }
+ $autoFilterRange = $phpSheet->getAutoFilter()->getRange();
+ if (!empty($autoFilterRange)) {
+ // Write AUTOFILTERINFO
+ $this->writeAutoFilterInfo();
+ }
+
+ // Write sheet dimensions
+ $this->writeDimensions();
+
+ // Row dimensions
+ foreach ($phpSheet->getRowDimensions() as $rowDimension) {
+ $xfIndex = $rowDimension->getXfIndex() + 15; // there are 15 cellXfs
+ $this->writeRow($rowDimension->getRowIndex() - 1, $rowDimension->getRowHeight(), $xfIndex, ($rowDimension->getVisible() ? '0' : '1'), $rowDimension->getOutlineLevel());
+ }
+
+ // Write Cells
+ foreach ($phpSheet->getCoordinates() as $coordinate) {
+ $cell = $phpSheet->getCell($coordinate);
+ $row = $cell->getRow() - 1;
+ $column = Coordinate::columnIndexFromString($cell->getColumn()) - 1;
+
+ // Don't break Excel break the code!
+ if ($row > 65535 || $column > 255) {
+ throw new WriterException('Rows or columns overflow! Excel5 has limit to 65535 rows and 255 columns. Use XLSX instead.');
+ }
+
+ // Write cell value
+ $xfIndex = $cell->getXfIndex() + 15; // there are 15 cell style Xfs
+
+ $cVal = $cell->getValue();
+ if ($cVal instanceof RichText) {
+ $arrcRun = [];
+ $str_len = StringHelper::countCharacters($cVal->getPlainText(), 'UTF-8');
+ $str_pos = 0;
+ $elements = $cVal->getRichTextElements();
+ foreach ($elements as $element) {
+ // FONT Index
+ if ($element instanceof Run) {
+ $str_fontidx = $this->fontHashIndex[$element->getFont()->getHashCode()];
+ } else {
+ $str_fontidx = 0;
+ }
+ $arrcRun[] = ['strlen' => $str_pos, 'fontidx' => $str_fontidx];
+ // Position FROM
+ $str_pos += StringHelper::countCharacters($element->getText(), 'UTF-8');
+ }
+ $this->writeRichTextString($row, $column, $cVal->getPlainText(), $xfIndex, $arrcRun);
+ } else {
+ switch ($cell->getDatatype()) {
+ case DataType::TYPE_STRING:
+ case DataType::TYPE_NULL:
+ if ($cVal === '' || $cVal === null) {
+ $this->writeBlank($row, $column, $xfIndex);
+ } else {
+ $this->writeString($row, $column, $cVal, $xfIndex);
+ }
+
+ break;
+ case DataType::TYPE_NUMERIC:
+ $this->writeNumber($row, $column, $cVal, $xfIndex);
+
+ break;
+ case DataType::TYPE_FORMULA:
+ $calculatedValue = $this->preCalculateFormulas ?
+ $cell->getCalculatedValue() : null;
+ if (self::WRITE_FORMULA_EXCEPTION == $this->writeFormula($row, $column, $cVal, $xfIndex, $calculatedValue)) {
+ if ($calculatedValue === null) {
+ $calculatedValue = $cell->getCalculatedValue();
+ }
+ $calctype = gettype($calculatedValue);
+ switch ($calctype) {
+ case 'integer':
+ case 'double':
+ $this->writeNumber($row, $column, $calculatedValue, $xfIndex);
+
+ break;
+ case 'string':
+ $this->writeString($row, $column, $calculatedValue, $xfIndex);
+
+ break;
+ case 'boolean':
+ $this->writeBoolErr($row, $column, $calculatedValue, 0, $xfIndex);
+
+ break;
+ default:
+ $this->writeString($row, $column, $cVal, $xfIndex);
+ }
+ }
+
+ break;
+ case DataType::TYPE_BOOL:
+ $this->writeBoolErr($row, $column, $cVal, 0, $xfIndex);
+
+ break;
+ case DataType::TYPE_ERROR:
+ $this->writeBoolErr($row, $column, self::mapErrorCode($cVal), 1, $xfIndex);
+
+ break;
+ }
+ }
+ }
+
+ // Append
+ $this->writeMsoDrawing();
+
+ // Restoring active sheet.
+ $this->phpSheet->getParent()->setActiveSheetIndex($activeSheetIndex);
+
+ // Write WINDOW2 record
+ $this->writeWindow2();
+
+ // Write PLV record
+ $this->writePageLayoutView();
+
+ // Write ZOOM record
+ $this->writeZoom();
+ if ($phpSheet->getFreezePane()) {
+ $this->writePanes();
+ }
+
+ // Restoring selected cells.
+ $this->phpSheet->setSelectedCells($selectedCells);
+
+ // Write SELECTION record
+ $this->writeSelection();
+
+ // Write MergedCellsTable Record
+ $this->writeMergedCells();
+
+ // Hyperlinks
+ foreach ($phpSheet->getHyperLinkCollection() as $coordinate => $hyperlink) {
+ [$column, $row] = Coordinate::coordinateFromString($coordinate);
+
+ $url = $hyperlink->getUrl();
+
+ if (strpos($url, 'sheet://') !== false) {
+ // internal to current workbook
+ $url = str_replace('sheet://', 'internal:', $url);
+ } elseif (preg_match('/^(http:|https:|ftp:|mailto:)/', $url)) {
+ // URL
+ } else {
+ // external (local file)
+ $url = 'external:' . $url;
+ }
+
+ $this->writeUrl($row - 1, Coordinate::columnIndexFromString($column) - 1, $url);
+ }
+
+ $this->writeDataValidity();
+ $this->writeSheetLayout();
+
+ // Write SHEETPROTECTION record
+ $this->writeSheetProtection();
+ $this->writeRangeProtection();
+
+ $arrConditionalStyles = $phpSheet->getConditionalStylesCollection();
+ if (!empty($arrConditionalStyles)) {
+ $arrConditional = [];
+ // @TODO CFRule & CFHeader
+ // Write CFHEADER record
+ $this->writeCFHeader();
+ // Write ConditionalFormattingTable records
+ foreach ($arrConditionalStyles as $cellCoordinate => $conditionalStyles) {
+ foreach ($conditionalStyles as $conditional) {
+ if (
+ $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION
+ || $conditional->getConditionType() == Conditional::CONDITION_CELLIS
+ ) {
+ if (!isset($arrConditional[$conditional->getHashCode()])) {
+ // This hash code has been handled
+ $arrConditional[$conditional->getHashCode()] = true;
+
+ // Write CFRULE record
+ $this->writeCFRule($conditional);
+ }
+ }
+ }
+ }
+ }
+
+ $this->storeEof();
+ }
+
+ /**
+ * Write a cell range address in BIFF8
+ * always fixed range
+ * See section 2.5.14 in OpenOffice.org's Documentation of the Microsoft Excel File Format.
+ *
+ * @param string $range E.g. 'A1' or 'A1:B6'
+ *
+ * @return string Binary data
+ */
+ private function writeBIFF8CellRangeAddressFixed($range)
+ {
+ $explodes = explode(':', $range);
+
+ // extract first cell, e.g. 'A1'
+ $firstCell = $explodes[0];
+
+ // extract last cell, e.g. 'B6'
+ if (count($explodes) == 1) {
+ $lastCell = $firstCell;
+ } else {
+ $lastCell = $explodes[1];
+ }
+
+ $firstCellCoordinates = Coordinate::coordinateFromString($firstCell); // e.g. [0, 1]
+ $lastCellCoordinates = Coordinate::coordinateFromString($lastCell); // e.g. [1, 6]
+
+ return pack('vvvv', $firstCellCoordinates[1] - 1, $lastCellCoordinates[1] - 1, Coordinate::columnIndexFromString($firstCellCoordinates[0]) - 1, Coordinate::columnIndexFromString($lastCellCoordinates[0]) - 1);
+ }
+
+ /**
+ * Retrieves data from memory in one chunk, or from disk in $buffer
+ * sized chunks.
+ *
+ * @return string The data
+ */
+ public function getData()
+ {
+ $buffer = 4096;
+
+ // Return data stored in memory
+ if (isset($this->_data)) {
+ $tmp = $this->_data;
+ $this->_data = null;
+
+ return $tmp;
+ }
+
+ // No data to return
+ return false;
+ }
+
+ /**
+ * Set the option to print the row and column headers on the printed page.
+ *
+ * @param int $print Whether to print the headers or not. Defaults to 1 (print).
+ */
+ public function printRowColHeaders($print = 1): void
+ {
+ $this->printHeaders = $print;
+ }
+
+ /**
+ * This method sets the properties for outlining and grouping. The defaults
+ * correspond to Excel's defaults.
+ *
+ * @param bool $visible
+ * @param bool $symbols_below
+ * @param bool $symbols_right
+ * @param bool $auto_style
+ */
+ public function setOutline($visible = true, $symbols_below = true, $symbols_right = true, $auto_style = false): void
+ {
+ $this->outlineOn = $visible;
+ $this->outlineBelow = $symbols_below;
+ $this->outlineRight = $symbols_right;
+ $this->outlineStyle = $auto_style;
+
+ // Ensure this is a boolean vale for Window2
+ if ($this->outlineOn) {
+ $this->outlineOn = 1;
+ }
+ }
+
+ /**
+ * Write a double to the specified row and column (zero indexed).
+ * An integer can be written as a double. Excel will display an
+ * integer. $format is optional.
+ *
+ * Returns 0 : normal termination
+ * -2 : row or column out of range
+ *
+ * @param int $row Zero indexed row
+ * @param int $col Zero indexed column
+ * @param float $num The number to write
+ * @param mixed $xfIndex The optional XF format
+ *
+ * @return int
+ */
+ private function writeNumber($row, $col, $num, $xfIndex)
+ {
+ $record = 0x0203; // Record identifier
+ $length = 0x000E; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvv', $row, $col, $xfIndex);
+ $xl_double = pack('d', $num);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $xl_double = strrev($xl_double);
+ }
+
+ $this->append($header . $data . $xl_double);
+
+ return 0;
+ }
+
+ /**
+ * Write a LABELSST record or a LABEL record. Which one depends on BIFF version.
+ *
+ * @param int $row Row index (0-based)
+ * @param int $col Column index (0-based)
+ * @param string $str The string
+ * @param int $xfIndex Index to XF record
+ */
+ private function writeString($row, $col, $str, $xfIndex): void
+ {
+ $this->writeLabelSst($row, $col, $str, $xfIndex);
+ }
+
+ /**
+ * Write a LABELSST record or a LABEL record. Which one depends on BIFF version
+ * It differs from writeString by the writing of rich text strings.
+ *
+ * @param int $row Row index (0-based)
+ * @param int $col Column index (0-based)
+ * @param string $str The string
+ * @param int $xfIndex The XF format index for the cell
+ * @param array $arrcRun Index to Font record and characters beginning
+ */
+ private function writeRichTextString($row, $col, $str, $xfIndex, $arrcRun): void
+ {
+ $record = 0x00FD; // Record identifier
+ $length = 0x000A; // Bytes to follow
+ $str = StringHelper::UTF8toBIFF8UnicodeShort($str, $arrcRun);
+
+ // check if string is already present
+ if (!isset($this->stringTable[$str])) {
+ $this->stringTable[$str] = $this->stringUnique++;
+ }
+ ++$this->stringTotal;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvV', $row, $col, $xfIndex, $this->stringTable[$str]);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write a string to the specified row and column (zero indexed).
+ * This is the BIFF8 version (no 255 chars limit).
+ * $format is optional.
+ *
+ * @param int $row Zero indexed row
+ * @param int $col Zero indexed column
+ * @param string $str The string to write
+ * @param mixed $xfIndex The XF format index for the cell
+ */
+ private function writeLabelSst($row, $col, $str, $xfIndex): void
+ {
+ $record = 0x00FD; // Record identifier
+ $length = 0x000A; // Bytes to follow
+
+ $str = StringHelper::UTF8toBIFF8UnicodeLong($str);
+
+ // check if string is already present
+ if (!isset($this->stringTable[$str])) {
+ $this->stringTable[$str] = $this->stringUnique++;
+ }
+ ++$this->stringTotal;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvV', $row, $col, $xfIndex, $this->stringTable[$str]);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write a blank cell to the specified row and column (zero indexed).
+ * A blank cell is used to specify formatting without adding a string
+ * or a number.
+ *
+ * A blank cell without a format serves no purpose. Therefore, we don't write
+ * a BLANK record unless a format is specified.
+ *
+ * Returns 0 : normal termination (including no format)
+ * -1 : insufficient number of arguments
+ * -2 : row or column out of range
+ *
+ * @param int $row Zero indexed row
+ * @param int $col Zero indexed column
+ * @param mixed $xfIndex The XF format index
+ *
+ * @return int
+ */
+ public function writeBlank($row, $col, $xfIndex)
+ {
+ $record = 0x0201; // Record identifier
+ $length = 0x0006; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvv', $row, $col, $xfIndex);
+ $this->append($header . $data);
+
+ return 0;
+ }
+
+ /**
+ * Write a boolean or an error type to the specified row and column (zero indexed).
+ *
+ * @param int $row Row index (0-based)
+ * @param int $col Column index (0-based)
+ * @param int $value
+ * @param bool $isError Error or Boolean?
+ * @param int $xfIndex
+ *
+ * @return int
+ */
+ private function writeBoolErr($row, $col, $value, $isError, $xfIndex)
+ {
+ $record = 0x0205;
+ $length = 8;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvCC', $row, $col, $xfIndex, $value, $isError);
+ $this->append($header . $data);
+
+ return 0;
+ }
+
+ const WRITE_FORMULA_NORMAL = 0;
+ const WRITE_FORMULA_ERRORS = -1;
+ const WRITE_FORMULA_RANGE = -2;
+ const WRITE_FORMULA_EXCEPTION = -3;
+
+ /**
+ * Write a formula to the specified row and column (zero indexed).
+ * The textual representation of the formula is passed to the parser in
+ * Parser.php which returns a packed binary string.
+ *
+ * Returns 0 : WRITE_FORMULA_NORMAL normal termination
+ * -1 : WRITE_FORMULA_ERRORS formula errors (bad formula)
+ * -2 : WRITE_FORMULA_RANGE row or column out of range
+ * -3 : WRITE_FORMULA_EXCEPTION parse raised exception, probably due to definedname
+ *
+ * @param int $row Zero indexed row
+ * @param int $col Zero indexed column
+ * @param string $formula The formula text string
+ * @param mixed $xfIndex The XF format index
+ * @param mixed $calculatedValue Calculated value
+ *
+ * @return int
+ */
+ private function writeFormula($row, $col, $formula, $xfIndex, $calculatedValue)
+ {
+ $record = 0x0006; // Record identifier
+ // Initialize possible additional value for STRING record that should be written after the FORMULA record?
+ $stringValue = null;
+
+ // calculated value
+ if (isset($calculatedValue)) {
+ // Since we can't yet get the data type of the calculated value,
+ // we use best effort to determine data type
+ if (is_bool($calculatedValue)) {
+ // Boolean value
+ $num = pack('CCCvCv', 0x01, 0x00, (int) $calculatedValue, 0x00, 0x00, 0xFFFF);
+ } elseif (is_int($calculatedValue) || is_float($calculatedValue)) {
+ // Numeric value
+ $num = pack('d', $calculatedValue);
+ } elseif (is_string($calculatedValue)) {
+ $errorCodes = DataType::getErrorCodes();
+ if (isset($errorCodes[$calculatedValue])) {
+ // Error value
+ $num = pack('CCCvCv', 0x02, 0x00, self::mapErrorCode($calculatedValue), 0x00, 0x00, 0xFFFF);
+ } elseif ($calculatedValue === '') {
+ // Empty string (and BIFF8)
+ $num = pack('CCCvCv', 0x03, 0x00, 0x00, 0x00, 0x00, 0xFFFF);
+ } else {
+ // Non-empty string value (or empty string BIFF5)
+ $stringValue = $calculatedValue;
+ $num = pack('CCCvCv', 0x00, 0x00, 0x00, 0x00, 0x00, 0xFFFF);
+ }
+ } else {
+ // We are really not supposed to reach here
+ $num = pack('d', 0x00);
+ }
+ } else {
+ $num = pack('d', 0x00);
+ }
+
+ $grbit = 0x03; // Option flags
+ $unknown = 0x0000; // Must be zero
+
+ // Strip the '=' or '@' sign at the beginning of the formula string
+ if ($formula[0] == '=') {
+ $formula = substr($formula, 1);
+ } else {
+ // Error handling
+ $this->writeString($row, $col, 'Unrecognised character for formula', 0);
+
+ return self::WRITE_FORMULA_ERRORS;
+ }
+
+ // Parse the formula using the parser in Parser.php
+ try {
+ $error = $this->parser->parse($formula);
+ $formula = $this->parser->toReversePolish();
+
+ $formlen = strlen($formula); // Length of the binary string
+ $length = 0x16 + $formlen; // Length of the record data
+
+ $header = pack('vv', $record, $length);
+
+ $data = pack('vvv', $row, $col, $xfIndex)
+ . $num
+ . pack('vVv', $grbit, $unknown, $formlen);
+ $this->append($header . $data . $formula);
+
+ // Append also a STRING record if necessary
+ if ($stringValue !== null) {
+ $this->writeStringRecord($stringValue);
+ }
+
+ return self::WRITE_FORMULA_NORMAL;
+ } catch (PhpSpreadsheetException $e) {
+ return self::WRITE_FORMULA_EXCEPTION;
+ }
+ }
+
+ /**
+ * Write a STRING record. This.
+ *
+ * @param string $stringValue
+ */
+ private function writeStringRecord($stringValue): void
+ {
+ $record = 0x0207; // Record identifier
+ $data = StringHelper::UTF8toBIFF8UnicodeLong($stringValue);
+
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write a hyperlink.
+ * This is comprised of two elements: the visible label and
+ * the invisible link. The visible label is the same as the link unless an
+ * alternative string is specified. The label is written using the
+ * writeString() method. Therefore the 255 characters string limit applies.
+ * $string and $format are optional.
+ *
+ * The hyperlink can be to a http, ftp, mail, internal sheet (not yet), or external
+ * directory url.
+ *
+ * Returns 0 : normal termination
+ * -2 : row or column out of range
+ * -3 : long string truncated to 255 chars
+ *
+ * @param int $row Row
+ * @param int $col Column
+ * @param string $url URL string
+ *
+ * @return int
+ */
+ private function writeUrl($row, $col, $url)
+ {
+ // Add start row and col to arg list
+ return $this->writeUrlRange($row, $col, $row, $col, $url);
+ }
+
+ /**
+ * This is the more general form of writeUrl(). It allows a hyperlink to be
+ * written to a range of cells. This function also decides the type of hyperlink
+ * to be written. These are either, Web (http, ftp, mailto), Internal
+ * (Sheet1!A1) or external ('c:\temp\foo.xls#Sheet1!A1').
+ *
+ * @param int $row1 Start row
+ * @param int $col1 Start column
+ * @param int $row2 End row
+ * @param int $col2 End column
+ * @param string $url URL string
+ *
+ * @return int
+ *
+ * @see writeUrl()
+ */
+ public function writeUrlRange($row1, $col1, $row2, $col2, $url)
+ {
+ // Check for internal/external sheet links or default to web link
+ if (preg_match('[^internal:]', $url)) {
+ return $this->writeUrlInternal($row1, $col1, $row2, $col2, $url);
+ }
+ if (preg_match('[^external:]', $url)) {
+ return $this->writeUrlExternal($row1, $col1, $row2, $col2, $url);
+ }
+
+ return $this->writeUrlWeb($row1, $col1, $row2, $col2, $url);
+ }
+
+ /**
+ * Used to write http, ftp and mailto hyperlinks.
+ * The link type ($options) is 0x03 is the same as absolute dir ref without
+ * sheet. However it is differentiated by the $unknown2 data stream.
+ *
+ * @param int $row1 Start row
+ * @param int $col1 Start column
+ * @param int $row2 End row
+ * @param int $col2 End column
+ * @param string $url URL string
+ *
+ * @return int
+ *
+ * @see writeUrl()
+ */
+ public function writeUrlWeb($row1, $col1, $row2, $col2, $url)
+ {
+ $record = 0x01B8; // Record identifier
+ $length = 0x00000; // Bytes to follow
+
+ // Pack the undocumented parts of the hyperlink stream
+ $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000');
+ $unknown2 = pack('H*', 'E0C9EA79F9BACE118C8200AA004BA90B');
+
+ // Pack the option flags
+ $options = pack('V', 0x03);
+
+ // Convert URL to a null terminated wchar string
+ $url = implode("\0", preg_split("''", $url, -1, PREG_SPLIT_NO_EMPTY));
+ $url = $url . "\0\0\0";
+
+ // Pack the length of the URL
+ $url_len = pack('V', strlen($url));
+
+ // Calculate the data length
+ $length = 0x34 + strlen($url);
+
+ // Pack the header data
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvv', $row1, $row2, $col1, $col2);
+
+ // Write the packed data
+ $this->append($header . $data . $unknown1 . $options . $unknown2 . $url_len . $url);
+
+ return 0;
+ }
+
+ /**
+ * Used to write internal reference hyperlinks such as "Sheet1!A1".
+ *
+ * @param int $row1 Start row
+ * @param int $col1 Start column
+ * @param int $row2 End row
+ * @param int $col2 End column
+ * @param string $url URL string
+ *
+ * @return int
+ *
+ * @see writeUrl()
+ */
+ public function writeUrlInternal($row1, $col1, $row2, $col2, $url)
+ {
+ $record = 0x01B8; // Record identifier
+ $length = 0x00000; // Bytes to follow
+
+ // Strip URL type
+ $url = preg_replace('/^internal:/', '', $url);
+
+ // Pack the undocumented parts of the hyperlink stream
+ $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000');
+
+ // Pack the option flags
+ $options = pack('V', 0x08);
+
+ // Convert the URL type and to a null terminated wchar string
+ $url .= "\0";
+
+ // character count
+ $url_len = StringHelper::countCharacters($url);
+ $url_len = pack('V', $url_len);
+
+ $url = StringHelper::convertEncoding($url, 'UTF-16LE', 'UTF-8');
+
+ // Calculate the data length
+ $length = 0x24 + strlen($url);
+
+ // Pack the header data
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvv', $row1, $row2, $col1, $col2);
+
+ // Write the packed data
+ $this->append($header . $data . $unknown1 . $options . $url_len . $url);
+
+ return 0;
+ }
+
+ /**
+ * Write links to external directory names such as 'c:\foo.xls',
+ * c:\foo.xls#Sheet1!A1', '../../foo.xls'. and '../../foo.xls#Sheet1!A1'.
+ *
+ * Note: Excel writes some relative links with the $dir_long string. We ignore
+ * these cases for the sake of simpler code.
+ *
+ * @param int $row1 Start row
+ * @param int $col1 Start column
+ * @param int $row2 End row
+ * @param int $col2 End column
+ * @param string $url URL string
+ *
+ * @return int
+ *
+ * @see writeUrl()
+ */
+ public function writeUrlExternal($row1, $col1, $row2, $col2, $url)
+ {
+ // Network drives are different. We will handle them separately
+ // MS/Novell network drives and shares start with \\
+ if (preg_match('[^external:\\\\]', $url)) {
+ return; //($this->writeUrlExternal_net($row1, $col1, $row2, $col2, $url, $str, $format));
+ }
+
+ $record = 0x01B8; // Record identifier
+ $length = 0x00000; // Bytes to follow
+
+ // Strip URL type and change Unix dir separator to Dos style (if needed)
+ //
+ $url = preg_replace('/^external:/', '', $url);
+ $url = preg_replace('/\//', '\\', $url);
+
+ // Determine if the link is relative or absolute:
+ // relative if link contains no dir separator, "somefile.xls"
+ // relative if link starts with up-dir, "..\..\somefile.xls"
+ // otherwise, absolute
+
+ $absolute = 0x00; // relative path
+ if (preg_match('/^[A-Z]:/', $url)) {
+ $absolute = 0x02; // absolute path on Windows, e.g. C:\...
+ }
+ $link_type = 0x01 | $absolute;
+
+ // Determine if the link contains a sheet reference and change some of the
+ // parameters accordingly.
+ // Split the dir name and sheet name (if it exists)
+ $dir_long = $url;
+ if (preg_match('/\\#/', $url)) {
+ $link_type |= 0x08;
+ }
+
+ // Pack the link type
+ $link_type = pack('V', $link_type);
+
+ // Calculate the up-level dir count e.g.. (..\..\..\ == 3)
+ $up_count = preg_match_all('/\\.\\.\\\\/', $dir_long, $useless);
+ $up_count = pack('v', $up_count);
+
+ // Store the short dos dir name (null terminated)
+ $dir_short = preg_replace('/\\.\\.\\\\/', '', $dir_long) . "\0";
+
+ // Store the long dir name as a wchar string (non-null terminated)
+ $dir_long = $dir_long . "\0";
+
+ // Pack the lengths of the dir strings
+ $dir_short_len = pack('V', strlen($dir_short));
+ $dir_long_len = pack('V', strlen($dir_long));
+ $stream_len = pack('V', 0); //strlen($dir_long) + 0x06);
+
+ // Pack the undocumented parts of the hyperlink stream
+ $unknown1 = pack('H*', 'D0C9EA79F9BACE118C8200AA004BA90B02000000');
+ $unknown2 = pack('H*', '0303000000000000C000000000000046');
+ $unknown3 = pack('H*', 'FFFFADDE000000000000000000000000000000000000000');
+ $unknown4 = pack('v', 0x03);
+
+ // Pack the main data stream
+ $data = pack('vvvv', $row1, $row2, $col1, $col2) .
+ $unknown1 .
+ $link_type .
+ $unknown2 .
+ $up_count .
+ $dir_short_len .
+ $dir_short .
+ $unknown3 .
+ $stream_len; /*.
+ $dir_long_len .
+ $unknown4 .
+ $dir_long .
+ $sheet_len .
+ $sheet ;*/
+
+ // Pack the header data
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ // Write the packed data
+ $this->append($header . $data);
+
+ return 0;
+ }
+
+ /**
+ * This method is used to set the height and format for a row.
+ *
+ * @param int $row The row to set
+ * @param int $height Height we are giving to the row.
+ * Use null to set XF without setting height
+ * @param int $xfIndex The optional cell style Xf index to apply to the columns
+ * @param bool $hidden The optional hidden attribute
+ * @param int $level The optional outline level for row, in range [0,7]
+ */
+ private function writeRow($row, $height, $xfIndex, $hidden = false, $level = 0): void
+ {
+ $record = 0x0208; // Record identifier
+ $length = 0x0010; // Number of bytes to follow
+
+ $colMic = 0x0000; // First defined column
+ $colMac = 0x0000; // Last defined column
+ $irwMac = 0x0000; // Used by Excel to optimise loading
+ $reserved = 0x0000; // Reserved
+ $grbit = 0x0000; // Option flags
+ $ixfe = $xfIndex;
+
+ if ($height < 0) {
+ $height = null;
+ }
+
+ // Use writeRow($row, null, $XF) to set XF format without setting height
+ if ($height != null) {
+ $miyRw = $height * 20; // row height
+ } else {
+ $miyRw = 0xff; // default row height is 256
+ }
+
+ // Set the options flags. fUnsynced is used to show that the font and row
+ // heights are not compatible. This is usually the case for WriteExcel.
+ // The collapsed flag 0x10 doesn't seem to be used to indicate that a row
+ // is collapsed. Instead it is used to indicate that the previous row is
+ // collapsed. The zero height flag, 0x20, is used to collapse a row.
+
+ $grbit |= $level;
+ if ($hidden) {
+ $grbit |= 0x0030;
+ }
+ if ($height !== null) {
+ $grbit |= 0x0040; // fUnsynced
+ }
+ if ($xfIndex !== 0xF) {
+ $grbit |= 0x0080;
+ }
+ $grbit |= 0x0100;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvvvvvv', $row, $colMic, $colMac, $miyRw, $irwMac, $reserved, $grbit, $ixfe);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Writes Excel DIMENSIONS to define the area in which there is data.
+ */
+ private function writeDimensions(): void
+ {
+ $record = 0x0200; // Record identifier
+
+ $length = 0x000E;
+ $data = pack('VVvvv', $this->firstRowIndex, $this->lastRowIndex + 1, $this->firstColumnIndex, $this->lastColumnIndex + 1, 0x0000); // reserved
+
+ $header = pack('vv', $record, $length);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record Window2.
+ */
+ private function writeWindow2(): void
+ {
+ $record = 0x023E; // Record identifier
+ $length = 0x0012;
+
+ $grbit = 0x00B6; // Option flags
+ $rwTop = 0x0000; // Top row visible in window
+ $colLeft = 0x0000; // Leftmost column visible in window
+
+ // The options flags that comprise $grbit
+ $fDspFmla = 0; // 0 - bit
+ $fDspGrid = $this->phpSheet->getShowGridlines() ? 1 : 0; // 1
+ $fDspRwCol = $this->phpSheet->getShowRowColHeaders() ? 1 : 0; // 2
+ $fFrozen = $this->phpSheet->getFreezePane() ? 1 : 0; // 3
+ $fDspZeros = 1; // 4
+ $fDefaultHdr = 1; // 5
+ $fArabic = $this->phpSheet->getRightToLeft() ? 1 : 0; // 6
+ $fDspGuts = $this->outlineOn; // 7
+ $fFrozenNoSplit = 0; // 0 - bit
+ // no support in PhpSpreadsheet for selected sheet, therefore sheet is only selected if it is the active sheet
+ $fSelected = ($this->phpSheet === $this->phpSheet->getParent()->getActiveSheet()) ? 1 : 0;
+ $fPageBreakPreview = $this->phpSheet->getSheetView()->getView() === SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW;
+
+ $grbit = $fDspFmla;
+ $grbit |= $fDspGrid << 1;
+ $grbit |= $fDspRwCol << 2;
+ $grbit |= $fFrozen << 3;
+ $grbit |= $fDspZeros << 4;
+ $grbit |= $fDefaultHdr << 5;
+ $grbit |= $fArabic << 6;
+ $grbit |= $fDspGuts << 7;
+ $grbit |= $fFrozenNoSplit << 8;
+ $grbit |= $fSelected << 9; // Selected sheets.
+ $grbit |= $fSelected << 10; // Active sheet.
+ $grbit |= $fPageBreakPreview << 11;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvv', $grbit, $rwTop, $colLeft);
+
+ // FIXME !!!
+ $rgbHdr = 0x0040; // Row/column heading and gridline color index
+ $zoom_factor_page_break = ($fPageBreakPreview ? $this->phpSheet->getSheetView()->getZoomScale() : 0x0000);
+ $zoom_factor_normal = $this->phpSheet->getSheetView()->getZoomScaleNormal();
+
+ $data .= pack('vvvvV', $rgbHdr, 0x0000, $zoom_factor_page_break, $zoom_factor_normal, 0x00000000);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record DEFAULTROWHEIGHT.
+ */
+ private function writeDefaultRowHeight(): void
+ {
+ $defaultRowHeight = $this->phpSheet->getDefaultRowDimension()->getRowHeight();
+
+ if ($defaultRowHeight < 0) {
+ return;
+ }
+
+ // convert to twips
+ $defaultRowHeight = (int) 20 * $defaultRowHeight;
+
+ $record = 0x0225; // Record identifier
+ $length = 0x0004; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vv', 1, $defaultRowHeight);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record DEFCOLWIDTH if COLINFO records are in use.
+ */
+ private function writeDefcol(): void
+ {
+ $defaultColWidth = 8;
+
+ $record = 0x0055; // Record identifier
+ $length = 0x0002; // Number of bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $defaultColWidth);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record COLINFO to define column widths.
+ *
+ * Note: The SDK says the record length is 0x0B but Excel writes a 0x0C
+ * length record.
+ *
+ * @param array $col_array This is the only parameter received and is composed of the following:
+ * 0 => First formatted column,
+ * 1 => Last formatted column,
+ * 2 => Col width (8.43 is Excel default),
+ * 3 => The optional XF format of the column,
+ * 4 => Option flags.
+ * 5 => Optional outline level
+ */
+ private function writeColinfo($col_array): void
+ {
+ if (isset($col_array[0])) {
+ $colFirst = $col_array[0];
+ }
+ if (isset($col_array[1])) {
+ $colLast = $col_array[1];
+ }
+ if (isset($col_array[2])) {
+ $coldx = $col_array[2];
+ } else {
+ $coldx = 8.43;
+ }
+ if (isset($col_array[3])) {
+ $xfIndex = $col_array[3];
+ } else {
+ $xfIndex = 15;
+ }
+ if (isset($col_array[4])) {
+ $grbit = $col_array[4];
+ } else {
+ $grbit = 0;
+ }
+ if (isset($col_array[5])) {
+ $level = $col_array[5];
+ } else {
+ $level = 0;
+ }
+ $record = 0x007D; // Record identifier
+ $length = 0x000C; // Number of bytes to follow
+
+ $coldx *= 256; // Convert to units of 1/256 of a char
+
+ $ixfe = $xfIndex;
+ $reserved = 0x0000; // Reserved
+
+ $level = max(0, min($level, 7));
+ $grbit |= $level << 8;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvvvv', $colFirst, $colLast, $coldx, $ixfe, $grbit, $reserved);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write BIFF record SELECTION.
+ */
+ private function writeSelection(): void
+ {
+ // look up the selected cell range
+ $selectedCells = Coordinate::splitRange($this->phpSheet->getSelectedCells());
+ $selectedCells = $selectedCells[0];
+ if (count($selectedCells) == 2) {
+ [$first, $last] = $selectedCells;
+ } else {
+ $first = $selectedCells[0];
+ $last = $selectedCells[0];
+ }
+
+ [$colFirst, $rwFirst] = Coordinate::coordinateFromString($first);
+ $colFirst = Coordinate::columnIndexFromString($colFirst) - 1; // base 0 column index
+ --$rwFirst; // base 0 row index
+
+ [$colLast, $rwLast] = Coordinate::coordinateFromString($last);
+ $colLast = Coordinate::columnIndexFromString($colLast) - 1; // base 0 column index
+ --$rwLast; // base 0 row index
+
+ // make sure we are not out of bounds
+ $colFirst = min($colFirst, 255);
+ $colLast = min($colLast, 255);
+
+ $rwFirst = min($rwFirst, 65535);
+ $rwLast = min($rwLast, 65535);
+
+ $record = 0x001D; // Record identifier
+ $length = 0x000F; // Number of bytes to follow
+
+ $pnn = $this->activePane; // Pane position
+ $rwAct = $rwFirst; // Active row
+ $colAct = $colFirst; // Active column
+ $irefAct = 0; // Active cell ref
+ $cref = 1; // Number of refs
+
+ if (!isset($rwLast)) {
+ $rwLast = $rwFirst; // Last row in reference
+ }
+ if (!isset($colLast)) {
+ $colLast = $colFirst; // Last col in reference
+ }
+
+ // Swap last row/col for first row/col as necessary
+ if ($rwFirst > $rwLast) {
+ [$rwFirst, $rwLast] = [$rwLast, $rwFirst];
+ }
+
+ if ($colFirst > $colLast) {
+ [$colFirst, $colLast] = [$colLast, $colFirst];
+ }
+
+ $header = pack('vv', $record, $length);
+ $data = pack('CvvvvvvCC', $pnn, $rwAct, $colAct, $irefAct, $cref, $rwFirst, $rwLast, $colFirst, $colLast);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the MERGEDCELLS records for all ranges of merged cells.
+ */
+ private function writeMergedCells(): void
+ {
+ $mergeCells = $this->phpSheet->getMergeCells();
+ $countMergeCells = count($mergeCells);
+
+ if ($countMergeCells == 0) {
+ return;
+ }
+
+ // maximum allowed number of merged cells per record
+ $maxCountMergeCellsPerRecord = 1027;
+
+ // record identifier
+ $record = 0x00E5;
+
+ // counter for total number of merged cells treated so far by the writer
+ $i = 0;
+
+ // counter for number of merged cells written in record currently being written
+ $j = 0;
+
+ // initialize record data
+ $recordData = '';
+
+ // loop through the merged cells
+ foreach ($mergeCells as $mergeCell) {
+ ++$i;
+ ++$j;
+
+ // extract the row and column indexes
+ $range = Coordinate::splitRange($mergeCell);
+ [$first, $last] = $range[0];
+ [$firstColumn, $firstRow] = Coordinate::coordinateFromString($first);
+ [$lastColumn, $lastRow] = Coordinate::coordinateFromString($last);
+
+ $recordData .= pack('vvvv', $firstRow - 1, $lastRow - 1, Coordinate::columnIndexFromString($firstColumn) - 1, Coordinate::columnIndexFromString($lastColumn) - 1);
+
+ // flush record if we have reached limit for number of merged cells, or reached final merged cell
+ if ($j == $maxCountMergeCellsPerRecord || $i == $countMergeCells) {
+ $recordData = pack('v', $j) . $recordData;
+ $length = strlen($recordData);
+ $header = pack('vv', $record, $length);
+ $this->append($header . $recordData);
+
+ // initialize for next record, if any
+ $recordData = '';
+ $j = 0;
+ }
+ }
+ }
+
+ /**
+ * Write SHEETLAYOUT record.
+ */
+ private function writeSheetLayout(): void
+ {
+ if (!$this->phpSheet->isTabColorSet()) {
+ return;
+ }
+
+ $recordData = pack(
+ 'vvVVVvv',
+ 0x0862,
+ 0x0000, // unused
+ 0x00000000, // unused
+ 0x00000000, // unused
+ 0x00000014, // size of record data
+ $this->colors[$this->phpSheet->getTabColor()->getRGB()], // color index
+ 0x0000 // unused
+ );
+
+ $length = strlen($recordData);
+
+ $record = 0x0862; // Record identifier
+ $header = pack('vv', $record, $length);
+ $this->append($header . $recordData);
+ }
+
+ /**
+ * Write SHEETPROTECTION.
+ */
+ private function writeSheetProtection(): void
+ {
+ // record identifier
+ $record = 0x0867;
+
+ // prepare options
+ $options = (int) !$this->phpSheet->getProtection()->getObjects()
+ | (int) !$this->phpSheet->getProtection()->getScenarios() << 1
+ | (int) !$this->phpSheet->getProtection()->getFormatCells() << 2
+ | (int) !$this->phpSheet->getProtection()->getFormatColumns() << 3
+ | (int) !$this->phpSheet->getProtection()->getFormatRows() << 4
+ | (int) !$this->phpSheet->getProtection()->getInsertColumns() << 5
+ | (int) !$this->phpSheet->getProtection()->getInsertRows() << 6
+ | (int) !$this->phpSheet->getProtection()->getInsertHyperlinks() << 7
+ | (int) !$this->phpSheet->getProtection()->getDeleteColumns() << 8
+ | (int) !$this->phpSheet->getProtection()->getDeleteRows() << 9
+ | (int) !$this->phpSheet->getProtection()->getSelectLockedCells() << 10
+ | (int) !$this->phpSheet->getProtection()->getSort() << 11
+ | (int) !$this->phpSheet->getProtection()->getAutoFilter() << 12
+ | (int) !$this->phpSheet->getProtection()->getPivotTables() << 13
+ | (int) !$this->phpSheet->getProtection()->getSelectUnlockedCells() << 14;
+
+ // record data
+ $recordData = pack(
+ 'vVVCVVvv',
+ 0x0867, // repeated record identifier
+ 0x0000, // not used
+ 0x0000, // not used
+ 0x00, // not used
+ 0x01000200, // unknown data
+ 0xFFFFFFFF, // unknown data
+ $options, // options
+ 0x0000 // not used
+ );
+
+ $length = strlen($recordData);
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $recordData);
+ }
+
+ /**
+ * Write BIFF record RANGEPROTECTION.
+ *
+ * Openoffice.org's Documentaion of the Microsoft Excel File Format uses term RANGEPROTECTION for these records
+ * Microsoft Office Excel 97-2007 Binary File Format Specification uses term FEAT for these records
+ */
+ private function writeRangeProtection(): void
+ {
+ foreach ($this->phpSheet->getProtectedCells() as $range => $password) {
+ // number of ranges, e.g. 'A1:B3 C20:D25'
+ $cellRanges = explode(' ', $range);
+ $cref = count($cellRanges);
+
+ $recordData = pack(
+ 'vvVVvCVvVv',
+ 0x0868,
+ 0x00,
+ 0x0000,
+ 0x0000,
+ 0x02,
+ 0x0,
+ 0x0000,
+ $cref,
+ 0x0000,
+ 0x00
+ );
+
+ foreach ($cellRanges as $cellRange) {
+ $recordData .= $this->writeBIFF8CellRangeAddressFixed($cellRange);
+ }
+
+ // the rgbFeat structure
+ $recordData .= pack(
+ 'VV',
+ 0x0000,
+ hexdec($password)
+ );
+
+ $recordData .= StringHelper::UTF8toBIFF8UnicodeLong('p' . md5($recordData));
+
+ $length = strlen($recordData);
+
+ $record = 0x0868; // Record identifier
+ $header = pack('vv', $record, $length);
+ $this->append($header . $recordData);
+ }
+ }
+
+ /**
+ * Writes the Excel BIFF PANE record.
+ * The panes can either be frozen or thawed (unfrozen).
+ * Frozen panes are specified in terms of an integer number of rows and columns.
+ * Thawed panes are specified in terms of Excel's units for rows and columns.
+ */
+ private function writePanes(): void
+ {
+ $panes = [];
+ if ($this->phpSheet->getFreezePane()) {
+ [$column, $row] = Coordinate::coordinateFromString($this->phpSheet->getFreezePane());
+ $panes[0] = Coordinate::columnIndexFromString($column) - 1;
+ $panes[1] = $row - 1;
+
+ [$leftMostColumn, $topRow] = Coordinate::coordinateFromString($this->phpSheet->getTopLeftCell());
+ //Coordinates are zero-based in xls files
+ $panes[2] = $topRow - 1;
+ $panes[3] = Coordinate::columnIndexFromString($leftMostColumn) - 1;
+ } else {
+ // thaw panes
+ return;
+ }
+
+ $x = $panes[0] ?? null;
+ $y = $panes[1] ?? null;
+ $rwTop = $panes[2] ?? null;
+ $colLeft = $panes[3] ?? null;
+ if (count($panes) > 4) { // if Active pane was received
+ $pnnAct = $panes[4];
+ } else {
+ $pnnAct = null;
+ }
+ $record = 0x0041; // Record identifier
+ $length = 0x000A; // Number of bytes to follow
+
+ // Code specific to frozen or thawed panes.
+ if ($this->phpSheet->getFreezePane()) {
+ // Set default values for $rwTop and $colLeft
+ if (!isset($rwTop)) {
+ $rwTop = $y;
+ }
+ if (!isset($colLeft)) {
+ $colLeft = $x;
+ }
+ } else {
+ // Set default values for $rwTop and $colLeft
+ if (!isset($rwTop)) {
+ $rwTop = 0;
+ }
+ if (!isset($colLeft)) {
+ $colLeft = 0;
+ }
+
+ // Convert Excel's row and column units to the internal units.
+ // The default row height is 12.75
+ // The default column width is 8.43
+ // The following slope and intersection values were interpolated.
+ //
+ $y = 20 * $y + 255;
+ $x = 113.879 * $x + 390;
+ }
+
+ // Determine which pane should be active. There is also the undocumented
+ // option to override this should it be necessary: may be removed later.
+ //
+ if (!isset($pnnAct)) {
+ if ($x != 0 && $y != 0) {
+ $pnnAct = 0; // Bottom right
+ }
+ if ($x != 0 && $y == 0) {
+ $pnnAct = 1; // Top right
+ }
+ if ($x == 0 && $y != 0) {
+ $pnnAct = 2; // Bottom left
+ }
+ if ($x == 0 && $y == 0) {
+ $pnnAct = 3; // Top left
+ }
+ }
+
+ $this->activePane = $pnnAct; // Used in writeSelection
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvvv', $x, $y, $rwTop, $colLeft, $pnnAct);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the page setup SETUP BIFF record.
+ */
+ private function writeSetup(): void
+ {
+ $record = 0x00A1; // Record identifier
+ $length = 0x0022; // Number of bytes to follow
+
+ $iPaperSize = $this->phpSheet->getPageSetup()->getPaperSize(); // Paper size
+
+ $iScale = $this->phpSheet->getPageSetup()->getScale() ?
+ $this->phpSheet->getPageSetup()->getScale() : 100; // Print scaling factor
+
+ $iPageStart = 0x01; // Starting page number
+ $iFitWidth = (int) $this->phpSheet->getPageSetup()->getFitToWidth(); // Fit to number of pages wide
+ $iFitHeight = (int) $this->phpSheet->getPageSetup()->getFitToHeight(); // Fit to number of pages high
+ $grbit = 0x00; // Option flags
+ $iRes = 0x0258; // Print resolution
+ $iVRes = 0x0258; // Vertical print resolution
+
+ $numHdr = $this->phpSheet->getPageMargins()->getHeader(); // Header Margin
+
+ $numFtr = $this->phpSheet->getPageMargins()->getFooter(); // Footer Margin
+ $iCopies = 0x01; // Number of copies
+
+ // Order of printing pages
+ $fLeftToRight = $this->phpSheet->getPageSetup()->getPageOrder() === PageSetup::PAGEORDER_DOWN_THEN_OVER
+ ? 0x1 : 0x0;
+ // Page orientation
+ $fLandscape = ($this->phpSheet->getPageSetup()->getOrientation() == PageSetup::ORIENTATION_LANDSCAPE)
+ ? 0x0 : 0x1;
+
+ $fNoPls = 0x0; // Setup not read from printer
+ $fNoColor = 0x0; // Print black and white
+ $fDraft = 0x0; // Print draft quality
+ $fNotes = 0x0; // Print notes
+ $fNoOrient = 0x0; // Orientation not set
+ $fUsePage = 0x0; // Use custom starting page
+
+ $grbit = $fLeftToRight;
+ $grbit |= $fLandscape << 1;
+ $grbit |= $fNoPls << 2;
+ $grbit |= $fNoColor << 3;
+ $grbit |= $fDraft << 4;
+ $grbit |= $fNotes << 5;
+ $grbit |= $fNoOrient << 6;
+ $grbit |= $fUsePage << 7;
+
+ $numHdr = pack('d', $numHdr);
+ $numFtr = pack('d', $numFtr);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $numHdr = strrev($numHdr);
+ $numFtr = strrev($numFtr);
+ }
+
+ $header = pack('vv', $record, $length);
+ $data1 = pack('vvvvvvvv', $iPaperSize, $iScale, $iPageStart, $iFitWidth, $iFitHeight, $grbit, $iRes, $iVRes);
+ $data2 = $numHdr . $numFtr;
+ $data3 = pack('v', $iCopies);
+ $this->append($header . $data1 . $data2 . $data3);
+ }
+
+ /**
+ * Store the header caption BIFF record.
+ */
+ private function writeHeader(): void
+ {
+ $record = 0x0014; // Record identifier
+
+ /* removing for now
+ // need to fix character count (multibyte!)
+ if (strlen($this->phpSheet->getHeaderFooter()->getOddHeader()) <= 255) {
+ $str = $this->phpSheet->getHeaderFooter()->getOddHeader(); // header string
+ } else {
+ $str = '';
+ }
+ */
+
+ $recordData = StringHelper::UTF8toBIFF8UnicodeLong($this->phpSheet->getHeaderFooter()->getOddHeader());
+ $length = strlen($recordData);
+
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $recordData);
+ }
+
+ /**
+ * Store the footer caption BIFF record.
+ */
+ private function writeFooter(): void
+ {
+ $record = 0x0015; // Record identifier
+
+ /* removing for now
+ // need to fix character count (multibyte!)
+ if (strlen($this->phpSheet->getHeaderFooter()->getOddFooter()) <= 255) {
+ $str = $this->phpSheet->getHeaderFooter()->getOddFooter();
+ } else {
+ $str = '';
+ }
+ */
+
+ $recordData = StringHelper::UTF8toBIFF8UnicodeLong($this->phpSheet->getHeaderFooter()->getOddFooter());
+ $length = strlen($recordData);
+
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $recordData);
+ }
+
+ /**
+ * Store the horizontal centering HCENTER BIFF record.
+ */
+ private function writeHcenter(): void
+ {
+ $record = 0x0083; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fHCenter = $this->phpSheet->getPageSetup()->getHorizontalCentered() ? 1 : 0; // Horizontal centering
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fHCenter);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the vertical centering VCENTER BIFF record.
+ */
+ private function writeVcenter(): void
+ {
+ $record = 0x0084; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fVCenter = $this->phpSheet->getPageSetup()->getVerticalCentered() ? 1 : 0; // Horizontal centering
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fVCenter);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the LEFTMARGIN BIFF record.
+ */
+ private function writeMarginLeft(): void
+ {
+ $record = 0x0026; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $margin = $this->phpSheet->getPageMargins()->getLeft(); // Margin in inches
+
+ $header = pack('vv', $record, $length);
+ $data = pack('d', $margin);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $data = strrev($data);
+ }
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the RIGHTMARGIN BIFF record.
+ */
+ private function writeMarginRight(): void
+ {
+ $record = 0x0027; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $margin = $this->phpSheet->getPageMargins()->getRight(); // Margin in inches
+
+ $header = pack('vv', $record, $length);
+ $data = pack('d', $margin);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $data = strrev($data);
+ }
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the TOPMARGIN BIFF record.
+ */
+ private function writeMarginTop(): void
+ {
+ $record = 0x0028; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $margin = $this->phpSheet->getPageMargins()->getTop(); // Margin in inches
+
+ $header = pack('vv', $record, $length);
+ $data = pack('d', $margin);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $data = strrev($data);
+ }
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Store the BOTTOMMARGIN BIFF record.
+ */
+ private function writeMarginBottom(): void
+ {
+ $record = 0x0029; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $margin = $this->phpSheet->getPageMargins()->getBottom(); // Margin in inches
+
+ $header = pack('vv', $record, $length);
+ $data = pack('d', $margin);
+ if (self::getByteOrder()) { // if it's Big Endian
+ $data = strrev($data);
+ }
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the PRINTHEADERS BIFF record.
+ */
+ private function writePrintHeaders(): void
+ {
+ $record = 0x002a; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fPrintRwCol = $this->printHeaders; // Boolean flag
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fPrintRwCol);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the PRINTGRIDLINES BIFF record. Must be used in conjunction with the
+ * GRIDSET record.
+ */
+ private function writePrintGridlines(): void
+ {
+ $record = 0x002b; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fPrintGrid = $this->phpSheet->getPrintGridlines() ? 1 : 0; // Boolean flag
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fPrintGrid);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the GRIDSET BIFF record. Must be used in conjunction with the
+ * PRINTGRIDLINES record.
+ */
+ private function writeGridset(): void
+ {
+ $record = 0x0082; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fGridSet = !$this->phpSheet->getPrintGridlines(); // Boolean flag
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fGridSet);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the AUTOFILTERINFO BIFF record. This is used to configure the number of autofilter select used in the sheet.
+ */
+ private function writeAutoFilterInfo(): void
+ {
+ $record = 0x009D; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $rangeBounds = Coordinate::rangeBoundaries($this->phpSheet->getAutoFilter()->getRange());
+ $iNumFilters = 1 + $rangeBounds[1][0] - $rangeBounds[0][0];
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $iNumFilters);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the GUTS BIFF record. This is used to configure the gutter margins
+ * where Excel outline symbols are displayed. The visibility of the gutters is
+ * controlled by a flag in WSBOOL.
+ *
+ * @see writeWsbool()
+ */
+ private function writeGuts(): void
+ {
+ $record = 0x0080; // Record identifier
+ $length = 0x0008; // Bytes to follow
+
+ $dxRwGut = 0x0000; // Size of row gutter
+ $dxColGut = 0x0000; // Size of col gutter
+
+ // determine maximum row outline level
+ $maxRowOutlineLevel = 0;
+ foreach ($this->phpSheet->getRowDimensions() as $rowDimension) {
+ $maxRowOutlineLevel = max($maxRowOutlineLevel, $rowDimension->getOutlineLevel());
+ }
+
+ $col_level = 0;
+
+ // Calculate the maximum column outline level. The equivalent calculation
+ // for the row outline level is carried out in writeRow().
+ $colcount = count($this->columnInfo);
+ for ($i = 0; $i < $colcount; ++$i) {
+ $col_level = max($this->columnInfo[$i][5], $col_level);
+ }
+
+ // Set the limits for the outline levels (0 <= x <= 7).
+ $col_level = max(0, min($col_level, 7));
+
+ // The displayed level is one greater than the max outline levels
+ if ($maxRowOutlineLevel) {
+ ++$maxRowOutlineLevel;
+ }
+ if ($col_level) {
+ ++$col_level;
+ }
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvvv', $dxRwGut, $dxColGut, $maxRowOutlineLevel, $col_level);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the WSBOOL BIFF record, mainly for fit-to-page. Used in conjunction
+ * with the SETUP record.
+ */
+ private function writeWsbool(): void
+ {
+ $record = 0x0081; // Record identifier
+ $length = 0x0002; // Bytes to follow
+ $grbit = 0x0000;
+
+ // The only option that is of interest is the flag for fit to page. So we
+ // set all the options in one go.
+ //
+ // Set the option flags
+ $grbit |= 0x0001; // Auto page breaks visible
+ if ($this->outlineStyle) {
+ $grbit |= 0x0020; // Auto outline styles
+ }
+ if ($this->phpSheet->getShowSummaryBelow()) {
+ $grbit |= 0x0040; // Outline summary below
+ }
+ if ($this->phpSheet->getShowSummaryRight()) {
+ $grbit |= 0x0080; // Outline summary right
+ }
+ if ($this->phpSheet->getPageSetup()->getFitToPage()) {
+ $grbit |= 0x0100; // Page setup fit to page
+ }
+ if ($this->outlineOn) {
+ $grbit |= 0x0400; // Outline symbols displayed
+ }
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $grbit);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the HORIZONTALPAGEBREAKS and VERTICALPAGEBREAKS BIFF records.
+ */
+ private function writeBreaks(): void
+ {
+ // initialize
+ $vbreaks = [];
+ $hbreaks = [];
+
+ foreach ($this->phpSheet->getBreaks() as $cell => $breakType) {
+ // Fetch coordinates
+ $coordinates = Coordinate::coordinateFromString($cell);
+
+ // Decide what to do by the type of break
+ switch ($breakType) {
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_COLUMN:
+ // Add to list of vertical breaks
+ $vbreaks[] = Coordinate::columnIndexFromString($coordinates[0]) - 1;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_ROW:
+ // Add to list of horizontal breaks
+ $hbreaks[] = $coordinates[1];
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet::BREAK_NONE:
+ default:
+ // Nothing to do
+ break;
+ }
+ }
+
+ //horizontal page breaks
+ if (!empty($hbreaks)) {
+ // Sort and filter array of page breaks
+ sort($hbreaks, SORT_NUMERIC);
+ if ($hbreaks[0] == 0) { // don't use first break if it's 0
+ array_shift($hbreaks);
+ }
+
+ $record = 0x001b; // Record identifier
+ $cbrk = count($hbreaks); // Number of page breaks
+ $length = 2 + 6 * $cbrk; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $cbrk);
+
+ // Append each page break
+ foreach ($hbreaks as $hbreak) {
+ $data .= pack('vvv', $hbreak, 0x0000, 0x00ff);
+ }
+
+ $this->append($header . $data);
+ }
+
+ // vertical page breaks
+ if (!empty($vbreaks)) {
+ // 1000 vertical pagebreaks appears to be an internal Excel 5 limit.
+ // It is slightly higher in Excel 97/200, approx. 1026
+ $vbreaks = array_slice($vbreaks, 0, 1000);
+
+ // Sort and filter array of page breaks
+ sort($vbreaks, SORT_NUMERIC);
+ if ($vbreaks[0] == 0) { // don't use first break if it's 0
+ array_shift($vbreaks);
+ }
+
+ $record = 0x001a; // Record identifier
+ $cbrk = count($vbreaks); // Number of page breaks
+ $length = 2 + 6 * $cbrk; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $cbrk);
+
+ // Append each page break
+ foreach ($vbreaks as $vbreak) {
+ $data .= pack('vvv', $vbreak, 0x0000, 0xffff);
+ }
+
+ $this->append($header . $data);
+ }
+ }
+
+ /**
+ * Set the Biff PROTECT record to indicate that the worksheet is protected.
+ */
+ private function writeProtect(): void
+ {
+ // Exit unless sheet protection has been specified
+ if (!$this->phpSheet->getProtection()->getSheet()) {
+ return;
+ }
+
+ $record = 0x0012; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $fLock = 1; // Worksheet is protected
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $fLock);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write SCENPROTECT.
+ */
+ private function writeScenProtect(): void
+ {
+ // Exit if sheet protection is not active
+ if (!$this->phpSheet->getProtection()->getSheet()) {
+ return;
+ }
+
+ // Exit if scenarios are not protected
+ if (!$this->phpSheet->getProtection()->getScenarios()) {
+ return;
+ }
+
+ $record = 0x00DD; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', 1);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write OBJECTPROTECT.
+ */
+ private function writeObjectProtect(): void
+ {
+ // Exit if sheet protection is not active
+ if (!$this->phpSheet->getProtection()->getSheet()) {
+ return;
+ }
+
+ // Exit if objects are not protected
+ if (!$this->phpSheet->getProtection()->getObjects()) {
+ return;
+ }
+
+ $record = 0x0063; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', 1);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write the worksheet PASSWORD record.
+ */
+ private function writePassword(): void
+ {
+ // Exit unless sheet protection and password have been specified
+ if (!$this->phpSheet->getProtection()->getSheet() || !$this->phpSheet->getProtection()->getPassword()) {
+ return;
+ }
+
+ $record = 0x0013; // Record identifier
+ $length = 0x0002; // Bytes to follow
+
+ $wPassword = hexdec($this->phpSheet->getProtection()->getPassword()); // Encoded password
+
+ $header = pack('vv', $record, $length);
+ $data = pack('v', $wPassword);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Insert a 24bit bitmap image in a worksheet.
+ *
+ * @param int $row The row we are going to insert the bitmap into
+ * @param int $col The column we are going to insert the bitmap into
+ * @param mixed $bitmap The bitmap filename or GD-image resource
+ * @param int $x the horizontal position (offset) of the image inside the cell
+ * @param int $y the vertical position (offset) of the image inside the cell
+ * @param float $scale_x The horizontal scale
+ * @param float $scale_y The vertical scale
+ */
+ public function insertBitmap($row, $col, $bitmap, $x = 0, $y = 0, $scale_x = 1, $scale_y = 1): void
+ {
+ $bitmap_array = (is_resource($bitmap) ? $this->processBitmapGd($bitmap) : $this->processBitmap($bitmap));
+ [$width, $height, $size, $data] = $bitmap_array;
+
+ // Scale the frame of the image.
+ $width *= $scale_x;
+ $height *= $scale_y;
+
+ // Calculate the vertices of the image and write the OBJ record
+ $this->positionImage($col, $row, $x, $y, $width, $height);
+
+ // Write the IMDATA record to store the bitmap data
+ $record = 0x007f;
+ $length = 8 + $size;
+ $cf = 0x09;
+ $env = 0x01;
+ $lcb = $size;
+
+ $header = pack('vvvvV', $record, $length, $cf, $env, $lcb);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Calculate the vertices that define the position of the image as required by
+ * the OBJ record.
+ *
+ * +------------+------------+
+ * | A | B |
+ * +-----+------------+------------+
+ * | |(x1,y1) | |
+ * | 1 |(A1)._______|______ |
+ * | | | | |
+ * | | | | |
+ * +-----+----| BITMAP |-----+
+ * | | | | |
+ * | 2 | |______________. |
+ * | | | (B2)|
+ * | | | (x2,y2)|
+ * +---- +------------+------------+
+ *
+ * Example of a bitmap that covers some of the area from cell A1 to cell B2.
+ *
+ * Based on the width and height of the bitmap we need to calculate 8 vars:
+ * $col_start, $row_start, $col_end, $row_end, $x1, $y1, $x2, $y2.
+ * The width and height of the cells are also variable and have to be taken into
+ * account.
+ * The values of $col_start and $row_start are passed in from the calling
+ * function. The values of $col_end and $row_end are calculated by subtracting
+ * the width and height of the bitmap from the width and height of the
+ * underlying cells.
+ * The vertices are expressed as a percentage of the underlying cell width as
+ * follows (rhs values are in pixels):
+ *
+ * x1 = X / W *1024
+ * y1 = Y / H *256
+ * x2 = (X-1) / W *1024
+ * y2 = (Y-1) / H *256
+ *
+ * Where: X is distance from the left side of the underlying cell
+ * Y is distance from the top of the underlying cell
+ * W is the width of the cell
+ * H is the height of the cell
+ * The SDK incorrectly states that the height should be expressed as a
+ * percentage of 1024.
+ *
+ * @param int $col_start Col containing upper left corner of object
+ * @param int $row_start Row containing top left corner of object
+ * @param int $x1 Distance to left side of object
+ * @param int $y1 Distance to top of object
+ * @param int $width Width of image frame
+ * @param int $height Height of image frame
+ */
+ public function positionImage($col_start, $row_start, $x1, $y1, $width, $height): void
+ {
+ // Initialise end cell to the same as the start cell
+ $col_end = $col_start; // Col containing lower right corner of object
+ $row_end = $row_start; // Row containing bottom right corner of object
+
+ // Zero the specified offset if greater than the cell dimensions
+ if ($x1 >= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1))) {
+ $x1 = 0;
+ }
+ if ($y1 >= Xls::sizeRow($this->phpSheet, $row_start + 1)) {
+ $y1 = 0;
+ }
+
+ $width = $width + $x1 - 1;
+ $height = $height + $y1 - 1;
+
+ // Subtract the underlying cell widths to find the end cell of the image
+ while ($width >= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1))) {
+ $width -= Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1));
+ ++$col_end;
+ }
+
+ // Subtract the underlying cell heights to find the end cell of the image
+ while ($height >= Xls::sizeRow($this->phpSheet, $row_end + 1)) {
+ $height -= Xls::sizeRow($this->phpSheet, $row_end + 1);
+ ++$row_end;
+ }
+
+ // Bitmap isn't allowed to start or finish in a hidden cell, i.e. a cell
+ // with zero eight or width.
+ //
+ if (Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1)) == 0) {
+ return;
+ }
+ if (Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1)) == 0) {
+ return;
+ }
+ if (Xls::sizeRow($this->phpSheet, $row_start + 1) == 0) {
+ return;
+ }
+ if (Xls::sizeRow($this->phpSheet, $row_end + 1) == 0) {
+ return;
+ }
+
+ // Convert the pixel values to the percentage value expected by Excel
+ $x1 = $x1 / Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_start + 1)) * 1024;
+ $y1 = $y1 / Xls::sizeRow($this->phpSheet, $row_start + 1) * 256;
+ $x2 = $width / Xls::sizeCol($this->phpSheet, Coordinate::stringFromColumnIndex($col_end + 1)) * 1024; // Distance to right side of object
+ $y2 = $height / Xls::sizeRow($this->phpSheet, $row_end + 1) * 256; // Distance to bottom of object
+
+ $this->writeObjPicture($col_start, $x1, $row_start, $y1, $col_end, $x2, $row_end, $y2);
+ }
+
+ /**
+ * Store the OBJ record that precedes an IMDATA record. This could be generalise
+ * to support other Excel objects.
+ *
+ * @param int $colL Column containing upper left corner of object
+ * @param int $dxL Distance from left side of cell
+ * @param int $rwT Row containing top left corner of object
+ * @param int $dyT Distance from top of cell
+ * @param int $colR Column containing lower right corner of object
+ * @param int $dxR Distance from right of cell
+ * @param int $rwB Row containing bottom right corner of object
+ * @param int $dyB Distance from bottom of cell
+ */
+ private function writeObjPicture($colL, $dxL, $rwT, $dyT, $colR, $dxR, $rwB, $dyB): void
+ {
+ $record = 0x005d; // Record identifier
+ $length = 0x003c; // Bytes to follow
+
+ $cObj = 0x0001; // Count of objects in file (set to 1)
+ $OT = 0x0008; // Object type. 8 = Picture
+ $id = 0x0001; // Object ID
+ $grbit = 0x0614; // Option flags
+
+ $cbMacro = 0x0000; // Length of FMLA structure
+ $Reserved1 = 0x0000; // Reserved
+ $Reserved2 = 0x0000; // Reserved
+
+ $icvBack = 0x09; // Background colour
+ $icvFore = 0x09; // Foreground colour
+ $fls = 0x00; // Fill pattern
+ $fAuto = 0x00; // Automatic fill
+ $icv = 0x08; // Line colour
+ $lns = 0xff; // Line style
+ $lnw = 0x01; // Line weight
+ $fAutoB = 0x00; // Automatic border
+ $frs = 0x0000; // Frame style
+ $cf = 0x0009; // Image format, 9 = bitmap
+ $Reserved3 = 0x0000; // Reserved
+ $cbPictFmla = 0x0000; // Length of FMLA structure
+ $Reserved4 = 0x0000; // Reserved
+ $grbit2 = 0x0001; // Option flags
+ $Reserved5 = 0x0000; // Reserved
+
+ $header = pack('vv', $record, $length);
+ $data = pack('V', $cObj);
+ $data .= pack('v', $OT);
+ $data .= pack('v', $id);
+ $data .= pack('v', $grbit);
+ $data .= pack('v', $colL);
+ $data .= pack('v', $dxL);
+ $data .= pack('v', $rwT);
+ $data .= pack('v', $dyT);
+ $data .= pack('v', $colR);
+ $data .= pack('v', $dxR);
+ $data .= pack('v', $rwB);
+ $data .= pack('v', $dyB);
+ $data .= pack('v', $cbMacro);
+ $data .= pack('V', $Reserved1);
+ $data .= pack('v', $Reserved2);
+ $data .= pack('C', $icvBack);
+ $data .= pack('C', $icvFore);
+ $data .= pack('C', $fls);
+ $data .= pack('C', $fAuto);
+ $data .= pack('C', $icv);
+ $data .= pack('C', $lns);
+ $data .= pack('C', $lnw);
+ $data .= pack('C', $fAutoB);
+ $data .= pack('v', $frs);
+ $data .= pack('V', $cf);
+ $data .= pack('v', $Reserved3);
+ $data .= pack('v', $cbPictFmla);
+ $data .= pack('v', $Reserved4);
+ $data .= pack('v', $grbit2);
+ $data .= pack('V', $Reserved5);
+
+ $this->append($header . $data);
+ }
+
+ /**
+ * Convert a GD-image into the internal format.
+ *
+ * @param resource $image The image to process
+ *
+ * @return array Array with data and properties of the bitmap
+ */
+ public function processBitmapGd($image)
+ {
+ $width = imagesx($image);
+ $height = imagesy($image);
+
+ $data = pack('Vvvvv', 0x000c, $width, $height, 0x01, 0x18);
+ for ($j = $height; --$j;) {
+ for ($i = 0; $i < $width; ++$i) {
+ $color = imagecolorsforindex($image, imagecolorat($image, $i, $j));
+ foreach (['red', 'green', 'blue'] as $key) {
+ $color[$key] = $color[$key] + round((255 - $color[$key]) * $color['alpha'] / 127);
+ }
+ $data .= chr($color['blue']) . chr($color['green']) . chr($color['red']);
+ }
+ if (3 * $width % 4) {
+ $data .= str_repeat("\x00", 4 - 3 * $width % 4);
+ }
+ }
+
+ return [$width, $height, strlen($data), $data];
+ }
+
+ /**
+ * Convert a 24 bit bitmap into the modified internal format used by Windows.
+ * This is described in BITMAPCOREHEADER and BITMAPCOREINFO structures in the
+ * MSDN library.
+ *
+ * @param string $bitmap The bitmap to process
+ *
+ * @return array Array with data and properties of the bitmap
+ */
+ public function processBitmap($bitmap)
+ {
+ // Open file.
+ $bmp_fd = @fopen($bitmap, 'rb');
+ if (!$bmp_fd) {
+ throw new WriterException("Couldn't import $bitmap");
+ }
+
+ // Slurp the file into a string.
+ $data = fread($bmp_fd, filesize($bitmap));
+
+ // Check that the file is big enough to be a bitmap.
+ if (strlen($data) <= 0x36) {
+ throw new WriterException("$bitmap doesn't contain enough data.\n");
+ }
+
+ // The first 2 bytes are used to identify the bitmap.
+ $identity = unpack('A2ident', $data);
+ if ($identity['ident'] != 'BM') {
+ throw new WriterException("$bitmap doesn't appear to be a valid bitmap image.\n");
+ }
+
+ // Remove bitmap data: ID.
+ $data = substr($data, 2);
+
+ // Read and remove the bitmap size. This is more reliable than reading
+ // the data size at offset 0x22.
+ //
+ $size_array = unpack('Vsa', substr($data, 0, 4));
+ $size = $size_array['sa'];
+ $data = substr($data, 4);
+ $size -= 0x36; // Subtract size of bitmap header.
+ $size += 0x0C; // Add size of BIFF header.
+
+ // Remove bitmap data: reserved, offset, header length.
+ $data = substr($data, 12);
+
+ // Read and remove the bitmap width and height. Verify the sizes.
+ $width_and_height = unpack('V2', substr($data, 0, 8));
+ $width = $width_and_height[1];
+ $height = $width_and_height[2];
+ $data = substr($data, 8);
+ if ($width > 0xFFFF) {
+ throw new WriterException("$bitmap: largest image width supported is 65k.\n");
+ }
+ if ($height > 0xFFFF) {
+ throw new WriterException("$bitmap: largest image height supported is 65k.\n");
+ }
+
+ // Read and remove the bitmap planes and bpp data. Verify them.
+ $planes_and_bitcount = unpack('v2', substr($data, 0, 4));
+ $data = substr($data, 4);
+ if ($planes_and_bitcount[2] != 24) { // Bitcount
+ throw new WriterException("$bitmap isn't a 24bit true color bitmap.\n");
+ }
+ if ($planes_and_bitcount[1] != 1) {
+ throw new WriterException("$bitmap: only 1 plane supported in bitmap image.\n");
+ }
+
+ // Read and remove the bitmap compression. Verify compression.
+ $compression = unpack('Vcomp', substr($data, 0, 4));
+ $data = substr($data, 4);
+
+ if ($compression['comp'] != 0) {
+ throw new WriterException("$bitmap: compression not supported in bitmap image.\n");
+ }
+
+ // Remove bitmap data: data size, hres, vres, colours, imp. colours.
+ $data = substr($data, 20);
+
+ // Add the BITMAPCOREHEADER data
+ $header = pack('Vvvvv', 0x000c, $width, $height, 0x01, 0x18);
+ $data = $header . $data;
+
+ return [$width, $height, $size, $data];
+ }
+
+ /**
+ * Store the window zoom factor. This should be a reduced fraction but for
+ * simplicity we will store all fractions with a numerator of 100.
+ */
+ private function writeZoom(): void
+ {
+ // If scale is 100 we don't need to write a record
+ if ($this->phpSheet->getSheetView()->getZoomScale() == 100) {
+ return;
+ }
+
+ $record = 0x00A0; // Record identifier
+ $length = 0x0004; // Bytes to follow
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vv', $this->phpSheet->getSheetView()->getZoomScale(), 100);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Get Escher object.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Shared\Escher
+ */
+ public function getEscher()
+ {
+ return $this->escher;
+ }
+
+ /**
+ * Set Escher object.
+ *
+ * @param \PhpOffice\PhpSpreadsheet\Shared\Escher $pValue
+ */
+ public function setEscher(?\PhpOffice\PhpSpreadsheet\Shared\Escher $pValue = null): void
+ {
+ $this->escher = $pValue;
+ }
+
+ /**
+ * Write MSODRAWING record.
+ */
+ private function writeMsoDrawing(): void
+ {
+ // write the Escher stream if necessary
+ if (isset($this->escher)) {
+ $writer = new Escher($this->escher);
+ $data = $writer->close();
+ $spOffsets = $writer->getSpOffsets();
+ $spTypes = $writer->getSpTypes();
+ // write the neccesary MSODRAWING, OBJ records
+
+ // split the Escher stream
+ $spOffsets[0] = 0;
+ $nm = count($spOffsets) - 1; // number of shapes excluding first shape
+ for ($i = 1; $i <= $nm; ++$i) {
+ // MSODRAWING record
+ $record = 0x00EC; // Record identifier
+
+ // chunk of Escher stream for one shape
+ $dataChunk = substr($data, $spOffsets[$i - 1], $spOffsets[$i] - $spOffsets[$i - 1]);
+
+ $length = strlen($dataChunk);
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $dataChunk);
+
+ // OBJ record
+ $record = 0x005D; // record identifier
+ $objData = '';
+
+ // ftCmo
+ if ($spTypes[$i] == 0x00C9) {
+ // Add ftCmo (common object data) subobject
+ $objData .=
+ pack(
+ 'vvvvvVVV',
+ 0x0015, // 0x0015 = ftCmo
+ 0x0012, // length of ftCmo data
+ 0x0014, // object type, 0x0014 = filter
+ $i, // object id number, Excel seems to use 1-based index, local for the sheet
+ 0x2101, // option flags, 0x2001 is what OpenOffice.org uses
+ 0, // reserved
+ 0, // reserved
+ 0 // reserved
+ );
+
+ // Add ftSbs Scroll bar subobject
+ $objData .= pack('vv', 0x00C, 0x0014);
+ $objData .= pack('H*', '0000000000000000640001000A00000010000100');
+ // Add ftLbsData (List box data) subobject
+ $objData .= pack('vv', 0x0013, 0x1FEE);
+ $objData .= pack('H*', '00000000010001030000020008005700');
+ } else {
+ // Add ftCmo (common object data) subobject
+ $objData .=
+ pack(
+ 'vvvvvVVV',
+ 0x0015, // 0x0015 = ftCmo
+ 0x0012, // length of ftCmo data
+ 0x0008, // object type, 0x0008 = picture
+ $i, // object id number, Excel seems to use 1-based index, local for the sheet
+ 0x6011, // option flags, 0x6011 is what OpenOffice.org uses
+ 0, // reserved
+ 0, // reserved
+ 0 // reserved
+ );
+ }
+
+ // ftEnd
+ $objData .=
+ pack(
+ 'vv',
+ 0x0000, // 0x0000 = ftEnd
+ 0x0000 // length of ftEnd data
+ );
+
+ $length = strlen($objData);
+ $header = pack('vv', $record, $length);
+ $this->append($header . $objData);
+ }
+ }
+ }
+
+ /**
+ * Store the DATAVALIDATIONS and DATAVALIDATION records.
+ */
+ private function writeDataValidity(): void
+ {
+ // Datavalidation collection
+ $dataValidationCollection = $this->phpSheet->getDataValidationCollection();
+
+ // Write data validations?
+ if (!empty($dataValidationCollection)) {
+ // DATAVALIDATIONS record
+ $record = 0x01B2; // Record identifier
+ $length = 0x0012; // Bytes to follow
+
+ $grbit = 0x0000; // Prompt box at cell, no cached validity data at DV records
+ $horPos = 0x00000000; // Horizontal position of prompt box, if fixed position
+ $verPos = 0x00000000; // Vertical position of prompt box, if fixed position
+ $objId = 0xFFFFFFFF; // Object identifier of drop down arrow object, or -1 if not visible
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vVVVV', $grbit, $horPos, $verPos, $objId, count($dataValidationCollection));
+ $this->append($header . $data);
+
+ // DATAVALIDATION records
+ $record = 0x01BE; // Record identifier
+
+ foreach ($dataValidationCollection as $cellCoordinate => $dataValidation) {
+ // initialize record data
+ $data = '';
+
+ // options
+ $options = 0x00000000;
+
+ // data type
+ $type = 0x00;
+ switch ($dataValidation->getType()) {
+ case DataValidation::TYPE_NONE:
+ $type = 0x00;
+
+ break;
+ case DataValidation::TYPE_WHOLE:
+ $type = 0x01;
+
+ break;
+ case DataValidation::TYPE_DECIMAL:
+ $type = 0x02;
+
+ break;
+ case DataValidation::TYPE_LIST:
+ $type = 0x03;
+
+ break;
+ case DataValidation::TYPE_DATE:
+ $type = 0x04;
+
+ break;
+ case DataValidation::TYPE_TIME:
+ $type = 0x05;
+
+ break;
+ case DataValidation::TYPE_TEXTLENGTH:
+ $type = 0x06;
+
+ break;
+ case DataValidation::TYPE_CUSTOM:
+ $type = 0x07;
+
+ break;
+ }
+
+ $options |= $type << 0;
+
+ // error style
+ $errorStyle = 0x00;
+ switch ($dataValidation->getErrorStyle()) {
+ case DataValidation::STYLE_STOP:
+ $errorStyle = 0x00;
+
+ break;
+ case DataValidation::STYLE_WARNING:
+ $errorStyle = 0x01;
+
+ break;
+ case DataValidation::STYLE_INFORMATION:
+ $errorStyle = 0x02;
+
+ break;
+ }
+
+ $options |= $errorStyle << 4;
+
+ // explicit formula?
+ if ($type == 0x03 && preg_match('/^\".*\"$/', $dataValidation->getFormula1())) {
+ $options |= 0x01 << 7;
+ }
+
+ // empty cells allowed
+ $options |= $dataValidation->getAllowBlank() << 8;
+
+ // show drop down
+ $options |= (!$dataValidation->getShowDropDown()) << 9;
+
+ // show input message
+ $options |= $dataValidation->getShowInputMessage() << 18;
+
+ // show error message
+ $options |= $dataValidation->getShowErrorMessage() << 19;
+
+ // condition operator
+ $operator = 0x00;
+ switch ($dataValidation->getOperator()) {
+ case DataValidation::OPERATOR_BETWEEN:
+ $operator = 0x00;
+
+ break;
+ case DataValidation::OPERATOR_NOTBETWEEN:
+ $operator = 0x01;
+
+ break;
+ case DataValidation::OPERATOR_EQUAL:
+ $operator = 0x02;
+
+ break;
+ case DataValidation::OPERATOR_NOTEQUAL:
+ $operator = 0x03;
+
+ break;
+ case DataValidation::OPERATOR_GREATERTHAN:
+ $operator = 0x04;
+
+ break;
+ case DataValidation::OPERATOR_LESSTHAN:
+ $operator = 0x05;
+
+ break;
+ case DataValidation::OPERATOR_GREATERTHANOREQUAL:
+ $operator = 0x06;
+
+ break;
+ case DataValidation::OPERATOR_LESSTHANOREQUAL:
+ $operator = 0x07;
+
+ break;
+ }
+
+ $options |= $operator << 20;
+
+ $data = pack('V', $options);
+
+ // prompt title
+ $promptTitle = $dataValidation->getPromptTitle() !== '' ?
+ $dataValidation->getPromptTitle() : chr(0);
+ $data .= StringHelper::UTF8toBIFF8UnicodeLong($promptTitle);
+
+ // error title
+ $errorTitle = $dataValidation->getErrorTitle() !== '' ?
+ $dataValidation->getErrorTitle() : chr(0);
+ $data .= StringHelper::UTF8toBIFF8UnicodeLong($errorTitle);
+
+ // prompt text
+ $prompt = $dataValidation->getPrompt() !== '' ?
+ $dataValidation->getPrompt() : chr(0);
+ $data .= StringHelper::UTF8toBIFF8UnicodeLong($prompt);
+
+ // error text
+ $error = $dataValidation->getError() !== '' ?
+ $dataValidation->getError() : chr(0);
+ $data .= StringHelper::UTF8toBIFF8UnicodeLong($error);
+
+ // formula 1
+ try {
+ $formula1 = $dataValidation->getFormula1();
+ if ($type == 0x03) { // list type
+ $formula1 = str_replace(',', chr(0), $formula1);
+ }
+ $this->parser->parse($formula1);
+ $formula1 = $this->parser->toReversePolish();
+ $sz1 = strlen($formula1);
+ } catch (PhpSpreadsheetException $e) {
+ $sz1 = 0;
+ $formula1 = '';
+ }
+ $data .= pack('vv', $sz1, 0x0000);
+ $data .= $formula1;
+
+ // formula 2
+ try {
+ $formula2 = $dataValidation->getFormula2();
+ if ($formula2 === '') {
+ throw new WriterException('No formula2');
+ }
+ $this->parser->parse($formula2);
+ $formula2 = $this->parser->toReversePolish();
+ $sz2 = strlen($formula2);
+ } catch (PhpSpreadsheetException $e) {
+ $sz2 = 0;
+ $formula2 = '';
+ }
+ $data .= pack('vv', $sz2, 0x0000);
+ $data .= $formula2;
+
+ // cell range address list
+ $data .= pack('v', 0x0001);
+ $data .= $this->writeBIFF8CellRangeAddressFixed($cellCoordinate);
+
+ $length = strlen($data);
+ $header = pack('vv', $record, $length);
+
+ $this->append($header . $data);
+ }
+ }
+ }
+
+ /**
+ * Map Error code.
+ *
+ * @param string $errorCode
+ *
+ * @return int
+ */
+ private static function mapErrorCode($errorCode)
+ {
+ switch ($errorCode) {
+ case '#NULL!':
+ return 0x00;
+ case '#DIV/0!':
+ return 0x07;
+ case '#VALUE!':
+ return 0x0F;
+ case '#REF!':
+ return 0x17;
+ case '#NAME?':
+ return 0x1D;
+ case '#NUM!':
+ return 0x24;
+ case '#N/A':
+ return 0x2A;
+ }
+
+ return 0;
+ }
+
+ /**
+ * Write PLV Record.
+ */
+ private function writePageLayoutView(): void
+ {
+ $record = 0x088B; // Record identifier
+ $length = 0x0010; // Bytes to follow
+
+ $rt = 0x088B; // 2
+ $grbitFrt = 0x0000; // 2
+ $reserved = 0x0000000000000000; // 8
+ $wScalvePLV = $this->phpSheet->getSheetView()->getZoomScale(); // 2
+
+ // The options flags that comprise $grbit
+ if ($this->phpSheet->getSheetView()->getView() == SheetView::SHEETVIEW_PAGE_LAYOUT) {
+ $fPageLayoutView = 1;
+ } else {
+ $fPageLayoutView = 0;
+ }
+ $fRulerVisible = 0;
+ $fWhitespaceHidden = 0;
+
+ $grbit = $fPageLayoutView; // 2
+ $grbit |= $fRulerVisible << 1;
+ $grbit |= $fWhitespaceHidden << 3;
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vvVVvv', $rt, $grbitFrt, 0x00000000, 0x00000000, $wScalvePLV, $grbit);
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write CFRule Record.
+ */
+ private function writeCFRule(Conditional $conditional): void
+ {
+ $record = 0x01B1; // Record identifier
+
+ // $type : Type of the CF
+ // $operatorType : Comparison operator
+ if ($conditional->getConditionType() == Conditional::CONDITION_EXPRESSION) {
+ $type = 0x02;
+ $operatorType = 0x00;
+ } elseif ($conditional->getConditionType() == Conditional::CONDITION_CELLIS) {
+ $type = 0x01;
+
+ switch ($conditional->getOperatorType()) {
+ case Conditional::OPERATOR_NONE:
+ $operatorType = 0x00;
+
+ break;
+ case Conditional::OPERATOR_EQUAL:
+ $operatorType = 0x03;
+
+ break;
+ case Conditional::OPERATOR_GREATERTHAN:
+ $operatorType = 0x05;
+
+ break;
+ case Conditional::OPERATOR_GREATERTHANOREQUAL:
+ $operatorType = 0x07;
+
+ break;
+ case Conditional::OPERATOR_LESSTHAN:
+ $operatorType = 0x06;
+
+ break;
+ case Conditional::OPERATOR_LESSTHANOREQUAL:
+ $operatorType = 0x08;
+
+ break;
+ case Conditional::OPERATOR_NOTEQUAL:
+ $operatorType = 0x04;
+
+ break;
+ case Conditional::OPERATOR_BETWEEN:
+ $operatorType = 0x01;
+
+ break;
+ // not OPERATOR_NOTBETWEEN 0x02
+ }
+ }
+
+ // $szValue1 : size of the formula data for first value or formula
+ // $szValue2 : size of the formula data for second value or formula
+ $arrConditions = $conditional->getConditions();
+ $numConditions = count($arrConditions);
+ if ($numConditions == 1) {
+ $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000);
+ $szValue2 = 0x0000;
+ $operand1 = pack('Cv', 0x1E, $arrConditions[0]);
+ $operand2 = null;
+ } elseif ($numConditions == 2 && ($conditional->getOperatorType() == Conditional::OPERATOR_BETWEEN)) {
+ $szValue1 = ($arrConditions[0] <= 65535 ? 3 : 0x0000);
+ $szValue2 = ($arrConditions[1] <= 65535 ? 3 : 0x0000);
+ $operand1 = pack('Cv', 0x1E, $arrConditions[0]);
+ $operand2 = pack('Cv', 0x1E, $arrConditions[1]);
+ } else {
+ $szValue1 = 0x0000;
+ $szValue2 = 0x0000;
+ $operand1 = null;
+ $operand2 = null;
+ }
+
+ // $flags : Option flags
+ // Alignment
+ $bAlignHz = ($conditional->getStyle()->getAlignment()->getHorizontal() == null ? 1 : 0);
+ $bAlignVt = ($conditional->getStyle()->getAlignment()->getVertical() == null ? 1 : 0);
+ $bAlignWrapTx = ($conditional->getStyle()->getAlignment()->getWrapText() == false ? 1 : 0);
+ $bTxRotation = ($conditional->getStyle()->getAlignment()->getTextRotation() == null ? 1 : 0);
+ $bIndent = ($conditional->getStyle()->getAlignment()->getIndent() == 0 ? 1 : 0);
+ $bShrinkToFit = ($conditional->getStyle()->getAlignment()->getShrinkToFit() == false ? 1 : 0);
+ if ($bAlignHz == 0 || $bAlignVt == 0 || $bAlignWrapTx == 0 || $bTxRotation == 0 || $bIndent == 0 || $bShrinkToFit == 0) {
+ $bFormatAlign = 1;
+ } else {
+ $bFormatAlign = 0;
+ }
+ // Protection
+ $bProtLocked = ($conditional->getStyle()->getProtection()->getLocked() == null ? 1 : 0);
+ $bProtHidden = ($conditional->getStyle()->getProtection()->getHidden() == null ? 1 : 0);
+ if ($bProtLocked == 0 || $bProtHidden == 0) {
+ $bFormatProt = 1;
+ } else {
+ $bFormatProt = 0;
+ }
+ // Border
+ $bBorderLeft = ($conditional->getStyle()->getBorders()->getLeft()->getColor()->getARGB() == Color::COLOR_BLACK
+ && $conditional->getStyle()->getBorders()->getLeft()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
+ $bBorderRight = ($conditional->getStyle()->getBorders()->getRight()->getColor()->getARGB() == Color::COLOR_BLACK
+ && $conditional->getStyle()->getBorders()->getRight()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
+ $bBorderTop = ($conditional->getStyle()->getBorders()->getTop()->getColor()->getARGB() == Color::COLOR_BLACK
+ && $conditional->getStyle()->getBorders()->getTop()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
+ $bBorderBottom = ($conditional->getStyle()->getBorders()->getBottom()->getColor()->getARGB() == Color::COLOR_BLACK
+ && $conditional->getStyle()->getBorders()->getBottom()->getBorderStyle() == Border::BORDER_NONE ? 1 : 0);
+ if ($bBorderLeft == 0 || $bBorderRight == 0 || $bBorderTop == 0 || $bBorderBottom == 0) {
+ $bFormatBorder = 1;
+ } else {
+ $bFormatBorder = 0;
+ }
+ // Pattern
+ $bFillStyle = ($conditional->getStyle()->getFill()->getFillType() == null ? 0 : 1);
+ $bFillColor = ($conditional->getStyle()->getFill()->getStartColor()->getARGB() == null ? 0 : 1);
+ $bFillColorBg = ($conditional->getStyle()->getFill()->getEndColor()->getARGB() == null ? 0 : 1);
+ if ($bFillStyle == 0 || $bFillColor == 0 || $bFillColorBg == 0) {
+ $bFormatFill = 1;
+ } else {
+ $bFormatFill = 0;
+ }
+ // Font
+ if (
+ $conditional->getStyle()->getFont()->getName() != null
+ || $conditional->getStyle()->getFont()->getSize() != null
+ || $conditional->getStyle()->getFont()->getBold() != null
+ || $conditional->getStyle()->getFont()->getItalic() != null
+ || $conditional->getStyle()->getFont()->getSuperscript() != null
+ || $conditional->getStyle()->getFont()->getSubscript() != null
+ || $conditional->getStyle()->getFont()->getUnderline() != null
+ || $conditional->getStyle()->getFont()->getStrikethrough() != null
+ || $conditional->getStyle()->getFont()->getColor()->getARGB() != null
+ ) {
+ $bFormatFont = 1;
+ } else {
+ $bFormatFont = 0;
+ }
+ // Alignment
+ $flags = 0;
+ $flags |= (1 == $bAlignHz ? 0x00000001 : 0);
+ $flags |= (1 == $bAlignVt ? 0x00000002 : 0);
+ $flags |= (1 == $bAlignWrapTx ? 0x00000004 : 0);
+ $flags |= (1 == $bTxRotation ? 0x00000008 : 0);
+ // Justify last line flag
+ $flags |= (1 == 1 ? 0x00000010 : 0);
+ $flags |= (1 == $bIndent ? 0x00000020 : 0);
+ $flags |= (1 == $bShrinkToFit ? 0x00000040 : 0);
+ // Default
+ $flags |= (1 == 1 ? 0x00000080 : 0);
+ // Protection
+ $flags |= (1 == $bProtLocked ? 0x00000100 : 0);
+ $flags |= (1 == $bProtHidden ? 0x00000200 : 0);
+ // Border
+ $flags |= (1 == $bBorderLeft ? 0x00000400 : 0);
+ $flags |= (1 == $bBorderRight ? 0x00000800 : 0);
+ $flags |= (1 == $bBorderTop ? 0x00001000 : 0);
+ $flags |= (1 == $bBorderBottom ? 0x00002000 : 0);
+ $flags |= (1 == 1 ? 0x00004000 : 0); // Top left to Bottom right border
+ $flags |= (1 == 1 ? 0x00008000 : 0); // Bottom left to Top right border
+ // Pattern
+ $flags |= (1 == $bFillStyle ? 0x00010000 : 0);
+ $flags |= (1 == $bFillColor ? 0x00020000 : 0);
+ $flags |= (1 == $bFillColorBg ? 0x00040000 : 0);
+ $flags |= (1 == 1 ? 0x00380000 : 0);
+ // Font
+ $flags |= (1 == $bFormatFont ? 0x04000000 : 0);
+ // Alignment:
+ $flags |= (1 == $bFormatAlign ? 0x08000000 : 0);
+ // Border
+ $flags |= (1 == $bFormatBorder ? 0x10000000 : 0);
+ // Pattern
+ $flags |= (1 == $bFormatFill ? 0x20000000 : 0);
+ // Protection
+ $flags |= (1 == $bFormatProt ? 0x40000000 : 0);
+ // Text direction
+ $flags |= (1 == 0 ? 0x80000000 : 0);
+
+ // Data Blocks
+ if ($bFormatFont == 1) {
+ // Font Name
+ if ($conditional->getStyle()->getFont()->getName() == null) {
+ $dataBlockFont = pack('VVVVVVVV', 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000);
+ $dataBlockFont .= pack('VVVVVVVV', 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000);
+ } else {
+ $dataBlockFont = StringHelper::UTF8toBIFF8UnicodeLong($conditional->getStyle()->getFont()->getName());
+ }
+ // Font Size
+ if ($conditional->getStyle()->getFont()->getSize() == null) {
+ $dataBlockFont .= pack('V', 20 * 11);
+ } else {
+ $dataBlockFont .= pack('V', 20 * $conditional->getStyle()->getFont()->getSize());
+ }
+ // Font Options
+ $dataBlockFont .= pack('V', 0);
+ // Font weight
+ if ($conditional->getStyle()->getFont()->getBold() == true) {
+ $dataBlockFont .= pack('v', 0x02BC);
+ } else {
+ $dataBlockFont .= pack('v', 0x0190);
+ }
+ // Escapement type
+ if ($conditional->getStyle()->getFont()->getSubscript() == true) {
+ $dataBlockFont .= pack('v', 0x02);
+ $fontEscapement = 0;
+ } elseif ($conditional->getStyle()->getFont()->getSuperscript() == true) {
+ $dataBlockFont .= pack('v', 0x01);
+ $fontEscapement = 0;
+ } else {
+ $dataBlockFont .= pack('v', 0x00);
+ $fontEscapement = 1;
+ }
+ // Underline type
+ switch ($conditional->getStyle()->getFont()->getUnderline()) {
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_NONE:
+ $dataBlockFont .= pack('C', 0x00);
+ $fontUnderline = 0;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLE:
+ $dataBlockFont .= pack('C', 0x02);
+ $fontUnderline = 0;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_DOUBLEACCOUNTING:
+ $dataBlockFont .= pack('C', 0x22);
+ $fontUnderline = 0;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLE:
+ $dataBlockFont .= pack('C', 0x01);
+ $fontUnderline = 0;
+
+ break;
+ case \PhpOffice\PhpSpreadsheet\Style\Font::UNDERLINE_SINGLEACCOUNTING:
+ $dataBlockFont .= pack('C', 0x21);
+ $fontUnderline = 0;
+
+ break;
+ default:
+ $dataBlockFont .= pack('C', 0x00);
+ $fontUnderline = 1;
+
+ break;
+ }
+ // Not used (3)
+ $dataBlockFont .= pack('vC', 0x0000, 0x00);
+ // Font color index
+ switch ($conditional->getStyle()->getFont()->getColor()->getRGB()) {
+ case '000000':
+ $colorIdx = 0x08;
+
+ break;
+ case 'FFFFFF':
+ $colorIdx = 0x09;
+
+ break;
+ case 'FF0000':
+ $colorIdx = 0x0A;
+
+ break;
+ case '00FF00':
+ $colorIdx = 0x0B;
+
+ break;
+ case '0000FF':
+ $colorIdx = 0x0C;
+
+ break;
+ case 'FFFF00':
+ $colorIdx = 0x0D;
+
+ break;
+ case 'FF00FF':
+ $colorIdx = 0x0E;
+
+ break;
+ case '00FFFF':
+ $colorIdx = 0x0F;
+
+ break;
+ case '800000':
+ $colorIdx = 0x10;
+
+ break;
+ case '008000':
+ $colorIdx = 0x11;
+
+ break;
+ case '000080':
+ $colorIdx = 0x12;
+
+ break;
+ case '808000':
+ $colorIdx = 0x13;
+
+ break;
+ case '800080':
+ $colorIdx = 0x14;
+
+ break;
+ case '008080':
+ $colorIdx = 0x15;
+
+ break;
+ case 'C0C0C0':
+ $colorIdx = 0x16;
+
+ break;
+ case '808080':
+ $colorIdx = 0x17;
+
+ break;
+ case '9999FF':
+ $colorIdx = 0x18;
+
+ break;
+ case '993366':
+ $colorIdx = 0x19;
+
+ break;
+ case 'FFFFCC':
+ $colorIdx = 0x1A;
+
+ break;
+ case 'CCFFFF':
+ $colorIdx = 0x1B;
+
+ break;
+ case '660066':
+ $colorIdx = 0x1C;
+
+ break;
+ case 'FF8080':
+ $colorIdx = 0x1D;
+
+ break;
+ case '0066CC':
+ $colorIdx = 0x1E;
+
+ break;
+ case 'CCCCFF':
+ $colorIdx = 0x1F;
+
+ break;
+ case '000080':
+ $colorIdx = 0x20;
+
+ break;
+ case 'FF00FF':
+ $colorIdx = 0x21;
+
+ break;
+ case 'FFFF00':
+ $colorIdx = 0x22;
+
+ break;
+ case '00FFFF':
+ $colorIdx = 0x23;
+
+ break;
+ case '800080':
+ $colorIdx = 0x24;
+
+ break;
+ case '800000':
+ $colorIdx = 0x25;
+
+ break;
+ case '008080':
+ $colorIdx = 0x26;
+
+ break;
+ case '0000FF':
+ $colorIdx = 0x27;
+
+ break;
+ case '00CCFF':
+ $colorIdx = 0x28;
+
+ break;
+ case 'CCFFFF':
+ $colorIdx = 0x29;
+
+ break;
+ case 'CCFFCC':
+ $colorIdx = 0x2A;
+
+ break;
+ case 'FFFF99':
+ $colorIdx = 0x2B;
+
+ break;
+ case '99CCFF':
+ $colorIdx = 0x2C;
+
+ break;
+ case 'FF99CC':
+ $colorIdx = 0x2D;
+
+ break;
+ case 'CC99FF':
+ $colorIdx = 0x2E;
+
+ break;
+ case 'FFCC99':
+ $colorIdx = 0x2F;
+
+ break;
+ case '3366FF':
+ $colorIdx = 0x30;
+
+ break;
+ case '33CCCC':
+ $colorIdx = 0x31;
+
+ break;
+ case '99CC00':
+ $colorIdx = 0x32;
+
+ break;
+ case 'FFCC00':
+ $colorIdx = 0x33;
+
+ break;
+ case 'FF9900':
+ $colorIdx = 0x34;
+
+ break;
+ case 'FF6600':
+ $colorIdx = 0x35;
+
+ break;
+ case '666699':
+ $colorIdx = 0x36;
+
+ break;
+ case '969696':
+ $colorIdx = 0x37;
+
+ break;
+ case '003366':
+ $colorIdx = 0x38;
+
+ break;
+ case '339966':
+ $colorIdx = 0x39;
+
+ break;
+ case '003300':
+ $colorIdx = 0x3A;
+
+ break;
+ case '333300':
+ $colorIdx = 0x3B;
+
+ break;
+ case '993300':
+ $colorIdx = 0x3C;
+
+ break;
+ case '993366':
+ $colorIdx = 0x3D;
+
+ break;
+ case '333399':
+ $colorIdx = 0x3E;
+
+ break;
+ case '333333':
+ $colorIdx = 0x3F;
+
+ break;
+ default:
+ $colorIdx = 0x00;
+
+ break;
+ }
+ $dataBlockFont .= pack('V', $colorIdx);
+ // Not used (4)
+ $dataBlockFont .= pack('V', 0x00000000);
+ // Options flags for modified font attributes
+ $optionsFlags = 0;
+ $optionsFlagsBold = ($conditional->getStyle()->getFont()->getBold() == null ? 1 : 0);
+ $optionsFlags |= (1 == $optionsFlagsBold ? 0x00000002 : 0);
+ $optionsFlags |= (1 == 1 ? 0x00000008 : 0);
+ $optionsFlags |= (1 == 1 ? 0x00000010 : 0);
+ $optionsFlags |= (1 == 0 ? 0x00000020 : 0);
+ $optionsFlags |= (1 == 1 ? 0x00000080 : 0);
+ $dataBlockFont .= pack('V', $optionsFlags);
+ // Escapement type
+ $dataBlockFont .= pack('V', $fontEscapement);
+ // Underline type
+ $dataBlockFont .= pack('V', $fontUnderline);
+ // Always
+ $dataBlockFont .= pack('V', 0x00000000);
+ // Always
+ $dataBlockFont .= pack('V', 0x00000000);
+ // Not used (8)
+ $dataBlockFont .= pack('VV', 0x00000000, 0x00000000);
+ // Always
+ $dataBlockFont .= pack('v', 0x0001);
+ }
+ if ($bFormatAlign == 1) {
+ $blockAlign = 0;
+ // Alignment and text break
+ switch ($conditional->getStyle()->getAlignment()->getHorizontal()) {
+ case Alignment::HORIZONTAL_GENERAL:
+ $blockAlign = 0;
+
+ break;
+ case Alignment::HORIZONTAL_LEFT:
+ $blockAlign = 1;
+
+ break;
+ case Alignment::HORIZONTAL_RIGHT:
+ $blockAlign = 3;
+
+ break;
+ case Alignment::HORIZONTAL_CENTER:
+ $blockAlign = 2;
+
+ break;
+ case Alignment::HORIZONTAL_CENTER_CONTINUOUS:
+ $blockAlign = 6;
+
+ break;
+ case Alignment::HORIZONTAL_JUSTIFY:
+ $blockAlign = 5;
+
+ break;
+ }
+ if ($conditional->getStyle()->getAlignment()->getWrapText() == true) {
+ $blockAlign |= 1 << 3;
+ } else {
+ $blockAlign |= 0 << 3;
+ }
+ switch ($conditional->getStyle()->getAlignment()->getVertical()) {
+ case Alignment::VERTICAL_BOTTOM:
+ $blockAlign = 2 << 4;
+
+ break;
+ case Alignment::VERTICAL_TOP:
+ $blockAlign = 0 << 4;
+
+ break;
+ case Alignment::VERTICAL_CENTER:
+ $blockAlign = 1 << 4;
+
+ break;
+ case Alignment::VERTICAL_JUSTIFY:
+ $blockAlign = 3 << 4;
+
+ break;
+ }
+ $blockAlign |= 0 << 7;
+
+ // Text rotation angle
+ $blockRotation = $conditional->getStyle()->getAlignment()->getTextRotation();
+
+ // Indentation
+ $blockIndent = $conditional->getStyle()->getAlignment()->getIndent();
+ if ($conditional->getStyle()->getAlignment()->getShrinkToFit() == true) {
+ $blockIndent |= 1 << 4;
+ } else {
+ $blockIndent |= 0 << 4;
+ }
+ $blockIndent |= 0 << 6;
+
+ // Relative indentation
+ $blockIndentRelative = 255;
+
+ $dataBlockAlign = pack('CCvvv', $blockAlign, $blockRotation, $blockIndent, $blockIndentRelative, 0x0000);
+ }
+ if ($bFormatBorder == 1) {
+ $blockLineStyle = 0;
+ switch ($conditional->getStyle()->getBorders()->getLeft()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockLineStyle |= 0x00;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockLineStyle |= 0x01;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockLineStyle |= 0x02;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockLineStyle |= 0x03;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockLineStyle |= 0x04;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockLineStyle |= 0x05;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockLineStyle |= 0x06;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockLineStyle |= 0x07;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockLineStyle |= 0x08;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockLineStyle |= 0x09;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockLineStyle |= 0x0A;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockLineStyle |= 0x0B;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockLineStyle |= 0x0C;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockLineStyle |= 0x0D;
+
+ break;
+ }
+ switch ($conditional->getStyle()->getBorders()->getRight()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockLineStyle |= 0x00 << 4;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockLineStyle |= 0x01 << 4;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockLineStyle |= 0x02 << 4;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockLineStyle |= 0x03 << 4;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockLineStyle |= 0x04 << 4;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockLineStyle |= 0x05 << 4;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockLineStyle |= 0x06 << 4;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockLineStyle |= 0x07 << 4;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockLineStyle |= 0x08 << 4;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockLineStyle |= 0x09 << 4;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockLineStyle |= 0x0A << 4;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockLineStyle |= 0x0B << 4;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockLineStyle |= 0x0C << 4;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockLineStyle |= 0x0D << 4;
+
+ break;
+ }
+ switch ($conditional->getStyle()->getBorders()->getTop()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockLineStyle |= 0x00 << 8;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockLineStyle |= 0x01 << 8;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockLineStyle |= 0x02 << 8;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockLineStyle |= 0x03 << 8;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockLineStyle |= 0x04 << 8;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockLineStyle |= 0x05 << 8;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockLineStyle |= 0x06 << 8;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockLineStyle |= 0x07 << 8;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockLineStyle |= 0x08 << 8;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockLineStyle |= 0x09 << 8;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockLineStyle |= 0x0A << 8;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockLineStyle |= 0x0B << 8;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockLineStyle |= 0x0C << 8;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockLineStyle |= 0x0D << 8;
+
+ break;
+ }
+ switch ($conditional->getStyle()->getBorders()->getBottom()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockLineStyle |= 0x00 << 12;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockLineStyle |= 0x01 << 12;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockLineStyle |= 0x02 << 12;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockLineStyle |= 0x03 << 12;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockLineStyle |= 0x04 << 12;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockLineStyle |= 0x05 << 12;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockLineStyle |= 0x06 << 12;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockLineStyle |= 0x07 << 12;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockLineStyle |= 0x08 << 12;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockLineStyle |= 0x09 << 12;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockLineStyle |= 0x0A << 12;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockLineStyle |= 0x0B << 12;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockLineStyle |= 0x0C << 12;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockLineStyle |= 0x0D << 12;
+
+ break;
+ }
+
+ // TODO writeCFRule() => $blockLineStyle => Index Color for left line
+ // TODO writeCFRule() => $blockLineStyle => Index Color for right line
+ // TODO writeCFRule() => $blockLineStyle => Top-left to bottom-right on/off
+ // TODO writeCFRule() => $blockLineStyle => Bottom-left to top-right on/off
+ $blockColor = 0;
+ // TODO writeCFRule() => $blockColor => Index Color for top line
+ // TODO writeCFRule() => $blockColor => Index Color for bottom line
+ // TODO writeCFRule() => $blockColor => Index Color for diagonal line
+ switch ($conditional->getStyle()->getBorders()->getDiagonal()->getBorderStyle()) {
+ case Border::BORDER_NONE:
+ $blockColor |= 0x00 << 21;
+
+ break;
+ case Border::BORDER_THIN:
+ $blockColor |= 0x01 << 21;
+
+ break;
+ case Border::BORDER_MEDIUM:
+ $blockColor |= 0x02 << 21;
+
+ break;
+ case Border::BORDER_DASHED:
+ $blockColor |= 0x03 << 21;
+
+ break;
+ case Border::BORDER_DOTTED:
+ $blockColor |= 0x04 << 21;
+
+ break;
+ case Border::BORDER_THICK:
+ $blockColor |= 0x05 << 21;
+
+ break;
+ case Border::BORDER_DOUBLE:
+ $blockColor |= 0x06 << 21;
+
+ break;
+ case Border::BORDER_HAIR:
+ $blockColor |= 0x07 << 21;
+
+ break;
+ case Border::BORDER_MEDIUMDASHED:
+ $blockColor |= 0x08 << 21;
+
+ break;
+ case Border::BORDER_DASHDOT:
+ $blockColor |= 0x09 << 21;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOT:
+ $blockColor |= 0x0A << 21;
+
+ break;
+ case Border::BORDER_DASHDOTDOT:
+ $blockColor |= 0x0B << 21;
+
+ break;
+ case Border::BORDER_MEDIUMDASHDOTDOT:
+ $blockColor |= 0x0C << 21;
+
+ break;
+ case Border::BORDER_SLANTDASHDOT:
+ $blockColor |= 0x0D << 21;
+
+ break;
+ }
+ $dataBlockBorder = pack('vv', $blockLineStyle, $blockColor);
+ }
+ if ($bFormatFill == 1) {
+ // Fill Patern Style
+ $blockFillPatternStyle = 0;
+ switch ($conditional->getStyle()->getFill()->getFillType()) {
+ case Fill::FILL_NONE:
+ $blockFillPatternStyle = 0x00;
+
+ break;
+ case Fill::FILL_SOLID:
+ $blockFillPatternStyle = 0x01;
+
+ break;
+ case Fill::FILL_PATTERN_MEDIUMGRAY:
+ $blockFillPatternStyle = 0x02;
+
+ break;
+ case Fill::FILL_PATTERN_DARKGRAY:
+ $blockFillPatternStyle = 0x03;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTGRAY:
+ $blockFillPatternStyle = 0x04;
+
+ break;
+ case Fill::FILL_PATTERN_DARKHORIZONTAL:
+ $blockFillPatternStyle = 0x05;
+
+ break;
+ case Fill::FILL_PATTERN_DARKVERTICAL:
+ $blockFillPatternStyle = 0x06;
+
+ break;
+ case Fill::FILL_PATTERN_DARKDOWN:
+ $blockFillPatternStyle = 0x07;
+
+ break;
+ case Fill::FILL_PATTERN_DARKUP:
+ $blockFillPatternStyle = 0x08;
+
+ break;
+ case Fill::FILL_PATTERN_DARKGRID:
+ $blockFillPatternStyle = 0x09;
+
+ break;
+ case Fill::FILL_PATTERN_DARKTRELLIS:
+ $blockFillPatternStyle = 0x0A;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTHORIZONTAL:
+ $blockFillPatternStyle = 0x0B;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTVERTICAL:
+ $blockFillPatternStyle = 0x0C;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTDOWN:
+ $blockFillPatternStyle = 0x0D;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTUP:
+ $blockFillPatternStyle = 0x0E;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTGRID:
+ $blockFillPatternStyle = 0x0F;
+
+ break;
+ case Fill::FILL_PATTERN_LIGHTTRELLIS:
+ $blockFillPatternStyle = 0x10;
+
+ break;
+ case Fill::FILL_PATTERN_GRAY125:
+ $blockFillPatternStyle = 0x11;
+
+ break;
+ case Fill::FILL_PATTERN_GRAY0625:
+ $blockFillPatternStyle = 0x12;
+
+ break;
+ case Fill::FILL_GRADIENT_LINEAR:
+ $blockFillPatternStyle = 0x00;
+
+ break; // does not exist in BIFF8
+ case Fill::FILL_GRADIENT_PATH:
+ $blockFillPatternStyle = 0x00;
+
+ break; // does not exist in BIFF8
+ default:
+ $blockFillPatternStyle = 0x00;
+
+ break;
+ }
+ // Color
+ switch ($conditional->getStyle()->getFill()->getStartColor()->getRGB()) {
+ case '000000':
+ $colorIdxBg = 0x08;
+
+ break;
+ case 'FFFFFF':
+ $colorIdxBg = 0x09;
+
+ break;
+ case 'FF0000':
+ $colorIdxBg = 0x0A;
+
+ break;
+ case '00FF00':
+ $colorIdxBg = 0x0B;
+
+ break;
+ case '0000FF':
+ $colorIdxBg = 0x0C;
+
+ break;
+ case 'FFFF00':
+ $colorIdxBg = 0x0D;
+
+ break;
+ case 'FF00FF':
+ $colorIdxBg = 0x0E;
+
+ break;
+ case '00FFFF':
+ $colorIdxBg = 0x0F;
+
+ break;
+ case '800000':
+ $colorIdxBg = 0x10;
+
+ break;
+ case '008000':
+ $colorIdxBg = 0x11;
+
+ break;
+ case '000080':
+ $colorIdxBg = 0x12;
+
+ break;
+ case '808000':
+ $colorIdxBg = 0x13;
+
+ break;
+ case '800080':
+ $colorIdxBg = 0x14;
+
+ break;
+ case '008080':
+ $colorIdxBg = 0x15;
+
+ break;
+ case 'C0C0C0':
+ $colorIdxBg = 0x16;
+
+ break;
+ case '808080':
+ $colorIdxBg = 0x17;
+
+ break;
+ case '9999FF':
+ $colorIdxBg = 0x18;
+
+ break;
+ case '993366':
+ $colorIdxBg = 0x19;
+
+ break;
+ case 'FFFFCC':
+ $colorIdxBg = 0x1A;
+
+ break;
+ case 'CCFFFF':
+ $colorIdxBg = 0x1B;
+
+ break;
+ case '660066':
+ $colorIdxBg = 0x1C;
+
+ break;
+ case 'FF8080':
+ $colorIdxBg = 0x1D;
+
+ break;
+ case '0066CC':
+ $colorIdxBg = 0x1E;
+
+ break;
+ case 'CCCCFF':
+ $colorIdxBg = 0x1F;
+
+ break;
+ case '000080':
+ $colorIdxBg = 0x20;
+
+ break;
+ case 'FF00FF':
+ $colorIdxBg = 0x21;
+
+ break;
+ case 'FFFF00':
+ $colorIdxBg = 0x22;
+
+ break;
+ case '00FFFF':
+ $colorIdxBg = 0x23;
+
+ break;
+ case '800080':
+ $colorIdxBg = 0x24;
+
+ break;
+ case '800000':
+ $colorIdxBg = 0x25;
+
+ break;
+ case '008080':
+ $colorIdxBg = 0x26;
+
+ break;
+ case '0000FF':
+ $colorIdxBg = 0x27;
+
+ break;
+ case '00CCFF':
+ $colorIdxBg = 0x28;
+
+ break;
+ case 'CCFFFF':
+ $colorIdxBg = 0x29;
+
+ break;
+ case 'CCFFCC':
+ $colorIdxBg = 0x2A;
+
+ break;
+ case 'FFFF99':
+ $colorIdxBg = 0x2B;
+
+ break;
+ case '99CCFF':
+ $colorIdxBg = 0x2C;
+
+ break;
+ case 'FF99CC':
+ $colorIdxBg = 0x2D;
+
+ break;
+ case 'CC99FF':
+ $colorIdxBg = 0x2E;
+
+ break;
+ case 'FFCC99':
+ $colorIdxBg = 0x2F;
+
+ break;
+ case '3366FF':
+ $colorIdxBg = 0x30;
+
+ break;
+ case '33CCCC':
+ $colorIdxBg = 0x31;
+
+ break;
+ case '99CC00':
+ $colorIdxBg = 0x32;
+
+ break;
+ case 'FFCC00':
+ $colorIdxBg = 0x33;
+
+ break;
+ case 'FF9900':
+ $colorIdxBg = 0x34;
+
+ break;
+ case 'FF6600':
+ $colorIdxBg = 0x35;
+
+ break;
+ case '666699':
+ $colorIdxBg = 0x36;
+
+ break;
+ case '969696':
+ $colorIdxBg = 0x37;
+
+ break;
+ case '003366':
+ $colorIdxBg = 0x38;
+
+ break;
+ case '339966':
+ $colorIdxBg = 0x39;
+
+ break;
+ case '003300':
+ $colorIdxBg = 0x3A;
+
+ break;
+ case '333300':
+ $colorIdxBg = 0x3B;
+
+ break;
+ case '993300':
+ $colorIdxBg = 0x3C;
+
+ break;
+ case '993366':
+ $colorIdxBg = 0x3D;
+
+ break;
+ case '333399':
+ $colorIdxBg = 0x3E;
+
+ break;
+ case '333333':
+ $colorIdxBg = 0x3F;
+
+ break;
+ default:
+ $colorIdxBg = 0x41;
+
+ break;
+ }
+ // Fg Color
+ switch ($conditional->getStyle()->getFill()->getEndColor()->getRGB()) {
+ case '000000':
+ $colorIdxFg = 0x08;
+
+ break;
+ case 'FFFFFF':
+ $colorIdxFg = 0x09;
+
+ break;
+ case 'FF0000':
+ $colorIdxFg = 0x0A;
+
+ break;
+ case '00FF00':
+ $colorIdxFg = 0x0B;
+
+ break;
+ case '0000FF':
+ $colorIdxFg = 0x0C;
+
+ break;
+ case 'FFFF00':
+ $colorIdxFg = 0x0D;
+
+ break;
+ case 'FF00FF':
+ $colorIdxFg = 0x0E;
+
+ break;
+ case '00FFFF':
+ $colorIdxFg = 0x0F;
+
+ break;
+ case '800000':
+ $colorIdxFg = 0x10;
+
+ break;
+ case '008000':
+ $colorIdxFg = 0x11;
+
+ break;
+ case '000080':
+ $colorIdxFg = 0x12;
+
+ break;
+ case '808000':
+ $colorIdxFg = 0x13;
+
+ break;
+ case '800080':
+ $colorIdxFg = 0x14;
+
+ break;
+ case '008080':
+ $colorIdxFg = 0x15;
+
+ break;
+ case 'C0C0C0':
+ $colorIdxFg = 0x16;
+
+ break;
+ case '808080':
+ $colorIdxFg = 0x17;
+
+ break;
+ case '9999FF':
+ $colorIdxFg = 0x18;
+
+ break;
+ case '993366':
+ $colorIdxFg = 0x19;
+
+ break;
+ case 'FFFFCC':
+ $colorIdxFg = 0x1A;
+
+ break;
+ case 'CCFFFF':
+ $colorIdxFg = 0x1B;
+
+ break;
+ case '660066':
+ $colorIdxFg = 0x1C;
+
+ break;
+ case 'FF8080':
+ $colorIdxFg = 0x1D;
+
+ break;
+ case '0066CC':
+ $colorIdxFg = 0x1E;
+
+ break;
+ case 'CCCCFF':
+ $colorIdxFg = 0x1F;
+
+ break;
+ case '000080':
+ $colorIdxFg = 0x20;
+
+ break;
+ case 'FF00FF':
+ $colorIdxFg = 0x21;
+
+ break;
+ case 'FFFF00':
+ $colorIdxFg = 0x22;
+
+ break;
+ case '00FFFF':
+ $colorIdxFg = 0x23;
+
+ break;
+ case '800080':
+ $colorIdxFg = 0x24;
+
+ break;
+ case '800000':
+ $colorIdxFg = 0x25;
+
+ break;
+ case '008080':
+ $colorIdxFg = 0x26;
+
+ break;
+ case '0000FF':
+ $colorIdxFg = 0x27;
+
+ break;
+ case '00CCFF':
+ $colorIdxFg = 0x28;
+
+ break;
+ case 'CCFFFF':
+ $colorIdxFg = 0x29;
+
+ break;
+ case 'CCFFCC':
+ $colorIdxFg = 0x2A;
+
+ break;
+ case 'FFFF99':
+ $colorIdxFg = 0x2B;
+
+ break;
+ case '99CCFF':
+ $colorIdxFg = 0x2C;
+
+ break;
+ case 'FF99CC':
+ $colorIdxFg = 0x2D;
+
+ break;
+ case 'CC99FF':
+ $colorIdxFg = 0x2E;
+
+ break;
+ case 'FFCC99':
+ $colorIdxFg = 0x2F;
+
+ break;
+ case '3366FF':
+ $colorIdxFg = 0x30;
+
+ break;
+ case '33CCCC':
+ $colorIdxFg = 0x31;
+
+ break;
+ case '99CC00':
+ $colorIdxFg = 0x32;
+
+ break;
+ case 'FFCC00':
+ $colorIdxFg = 0x33;
+
+ break;
+ case 'FF9900':
+ $colorIdxFg = 0x34;
+
+ break;
+ case 'FF6600':
+ $colorIdxFg = 0x35;
+
+ break;
+ case '666699':
+ $colorIdxFg = 0x36;
+
+ break;
+ case '969696':
+ $colorIdxFg = 0x37;
+
+ break;
+ case '003366':
+ $colorIdxFg = 0x38;
+
+ break;
+ case '339966':
+ $colorIdxFg = 0x39;
+
+ break;
+ case '003300':
+ $colorIdxFg = 0x3A;
+
+ break;
+ case '333300':
+ $colorIdxFg = 0x3B;
+
+ break;
+ case '993300':
+ $colorIdxFg = 0x3C;
+
+ break;
+ case '993366':
+ $colorIdxFg = 0x3D;
+
+ break;
+ case '333399':
+ $colorIdxFg = 0x3E;
+
+ break;
+ case '333333':
+ $colorIdxFg = 0x3F;
+
+ break;
+ default:
+ $colorIdxFg = 0x40;
+
+ break;
+ }
+ $dataBlockFill = pack('v', $blockFillPatternStyle);
+ $dataBlockFill .= pack('v', $colorIdxFg | ($colorIdxBg << 7));
+ }
+ if ($bFormatProt == 1) {
+ $dataBlockProtection = 0;
+ if ($conditional->getStyle()->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED) {
+ $dataBlockProtection = 1;
+ }
+ if ($conditional->getStyle()->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED) {
+ $dataBlockProtection = 1 << 1;
+ }
+ }
+
+ $data = pack('CCvvVv', $type, $operatorType, $szValue1, $szValue2, $flags, 0x0000);
+ if ($bFormatFont == 1) { // Block Formatting : OK
+ $data .= $dataBlockFont;
+ }
+ if ($bFormatAlign == 1) {
+ $data .= $dataBlockAlign;
+ }
+ if ($bFormatBorder == 1) {
+ $data .= $dataBlockBorder;
+ }
+ if ($bFormatFill == 1) { // Block Formatting : OK
+ $data .= $dataBlockFill;
+ }
+ if ($bFormatProt == 1) {
+ $data .= $dataBlockProtection;
+ }
+ if ($operand1 !== null) {
+ $data .= $operand1;
+ }
+ if ($operand2 !== null) {
+ $data .= $operand2;
+ }
+ $header = pack('vv', $record, strlen($data));
+ $this->append($header . $data);
+ }
+
+ /**
+ * Write CFHeader record.
+ */
+ private function writeCFHeader(): void
+ {
+ $record = 0x01B0; // Record identifier
+ $length = 0x0016; // Bytes to follow
+
+ $numColumnMin = null;
+ $numColumnMax = null;
+ $numRowMin = null;
+ $numRowMax = null;
+ $arrConditional = [];
+ foreach ($this->phpSheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) {
+ foreach ($conditionalStyles as $conditional) {
+ if (
+ $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION
+ || $conditional->getConditionType() == Conditional::CONDITION_CELLIS
+ ) {
+ if (!in_array($conditional->getHashCode(), $arrConditional)) {
+ $arrConditional[] = $conditional->getHashCode();
+ }
+ // Cells
+ $arrCoord = Coordinate::coordinateFromString($cellCoordinate);
+ if (!is_numeric($arrCoord[0])) {
+ $arrCoord[0] = Coordinate::columnIndexFromString($arrCoord[0]);
+ }
+ if ($numColumnMin === null || ($numColumnMin > $arrCoord[0])) {
+ $numColumnMin = $arrCoord[0];
+ }
+ if ($numColumnMax === null || ($numColumnMax < $arrCoord[0])) {
+ $numColumnMax = $arrCoord[0];
+ }
+ if ($numRowMin === null || ($numRowMin > $arrCoord[1])) {
+ $numRowMin = $arrCoord[1];
+ }
+ if ($numRowMax === null || ($numRowMax < $arrCoord[1])) {
+ $numRowMax = $arrCoord[1];
+ }
+ }
+ }
+ }
+ $needRedraw = 1;
+ $cellRange = pack('vvvv', $numRowMin - 1, $numRowMax - 1, $numColumnMin - 1, $numColumnMax - 1);
+
+ $header = pack('vv', $record, $length);
+ $data = pack('vv', count($arrConditional), $needRedraw);
+ $data .= $cellRange;
+ $data .= pack('v', 0x0001);
+ $data .= $cellRange;
+ $this->append($header . $data);
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php
new file mode 100644
index 0000000..ba584b0
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php
@@ -0,0 +1,548 @@
+
+// *
+// * The majority of this is _NOT_ my code. I simply ported it from the
+// * PERL Spreadsheet::WriteExcel module.
+// *
+// * The author of the Spreadsheet::WriteExcel module is John McNamara
+// *
+// *
+// * I _DO_ maintain this code, and John McNamara has nothing to do with the
+// * porting of this code to PHP. Any questions directly related to this
+// * class library should be directed to me.
+// *
+// * License Information:
+// *
+// * Spreadsheet_Excel_Writer: A library for generating Excel Spreadsheets
+// * Copyright (c) 2002-2003 Xavier Noguer xnoguer@rezebra.com
+// *
+// * This library is free software; you can redistribute it and/or
+// * modify it under the terms of the GNU Lesser General Public
+// * License as published by the Free Software Foundation; either
+// * version 2.1 of the License, or (at your option) any later version.
+// *
+// * This library is distributed in the hope that it will be useful,
+// * but WITHOUT ANY WARRANTY; without even the implied warranty of
+// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// * Lesser General Public License for more details.
+// *
+// * You should have received a copy of the GNU Lesser General Public
+// * License along with this library; if not, write to the Free Software
+// * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+// */
+class Xf
+{
+ /**
+ * Style XF or a cell XF ?
+ *
+ * @var bool
+ */
+ private $isStyleXf;
+
+ /**
+ * Index to the FONT record. Index 4 does not exist.
+ *
+ * @var int
+ */
+ private $fontIndex;
+
+ /**
+ * An index (2 bytes) to a FORMAT record (number format).
+ *
+ * @var int
+ */
+ private $numberFormatIndex;
+
+ /**
+ * 1 bit, apparently not used.
+ *
+ * @var int
+ */
+ private $textJustLast;
+
+ /**
+ * The cell's foreground color.
+ *
+ * @var int
+ */
+ private $foregroundColor;
+
+ /**
+ * The cell's background color.
+ *
+ * @var int
+ */
+ private $backgroundColor;
+
+ /**
+ * Color of the bottom border of the cell.
+ *
+ * @var int
+ */
+ private $bottomBorderColor;
+
+ /**
+ * Color of the top border of the cell.
+ *
+ * @var int
+ */
+ private $topBorderColor;
+
+ /**
+ * Color of the left border of the cell.
+ *
+ * @var int
+ */
+ private $leftBorderColor;
+
+ /**
+ * Color of the right border of the cell.
+ *
+ * @var int
+ */
+ private $rightBorderColor;
+
+ /**
+ * Constructor.
+ *
+ * @param Style $style The XF format
+ */
+ public function __construct(Style $style)
+ {
+ $this->isStyleXf = false;
+ $this->fontIndex = 0;
+
+ $this->numberFormatIndex = 0;
+
+ $this->textJustLast = 0;
+
+ $this->foregroundColor = 0x40;
+ $this->backgroundColor = 0x41;
+
+ $this->_diag = 0;
+
+ $this->bottomBorderColor = 0x40;
+ $this->topBorderColor = 0x40;
+ $this->leftBorderColor = 0x40;
+ $this->rightBorderColor = 0x40;
+ $this->_diag_color = 0x40;
+ $this->_style = $style;
+ }
+
+ /**
+ * Generate an Excel BIFF XF record (style or cell).
+ *
+ * @return string The XF record
+ */
+ public function writeXf()
+ {
+ // Set the type of the XF record and some of the attributes.
+ if ($this->isStyleXf) {
+ $style = 0xFFF5;
+ } else {
+ $style = self::mapLocked($this->_style->getProtection()->getLocked());
+ $style |= self::mapHidden($this->_style->getProtection()->getHidden()) << 1;
+ }
+
+ // Flags to indicate if attributes have been set.
+ $atr_num = ($this->numberFormatIndex != 0) ? 1 : 0;
+ $atr_fnt = ($this->fontIndex != 0) ? 1 : 0;
+ $atr_alc = ((int) $this->_style->getAlignment()->getWrapText()) ? 1 : 0;
+ $atr_bdr = (self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) ||
+ self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) ||
+ self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()) ||
+ self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle())) ? 1 : 0;
+ $atr_pat = (($this->foregroundColor != 0x40) ||
+ ($this->backgroundColor != 0x41) ||
+ self::mapFillType($this->_style->getFill()->getFillType())) ? 1 : 0;
+ $atr_prot = self::mapLocked($this->_style->getProtection()->getLocked())
+ | self::mapHidden($this->_style->getProtection()->getHidden());
+
+ // Zero the default border colour if the border has not been set.
+ if (self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) == 0) {
+ $this->bottomBorderColor = 0;
+ }
+ if (self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) == 0) {
+ $this->topBorderColor = 0;
+ }
+ if (self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle()) == 0) {
+ $this->rightBorderColor = 0;
+ }
+ if (self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()) == 0) {
+ $this->leftBorderColor = 0;
+ }
+ if (self::mapBorderStyle($this->_style->getBorders()->getDiagonal()->getBorderStyle()) == 0) {
+ $this->_diag_color = 0;
+ }
+
+ $record = 0x00E0; // Record identifier
+ $length = 0x0014; // Number of bytes to follow
+
+ $ifnt = $this->fontIndex; // Index to FONT record
+ $ifmt = $this->numberFormatIndex; // Index to FORMAT record
+
+ $align = $this->mapHAlign($this->_style->getAlignment()->getHorizontal()); // Alignment
+ $align |= (int) $this->_style->getAlignment()->getWrapText() << 3;
+ $align |= self::mapVAlign($this->_style->getAlignment()->getVertical()) << 4;
+ $align |= $this->textJustLast << 7;
+
+ $used_attrib = $atr_num << 2;
+ $used_attrib |= $atr_fnt << 3;
+ $used_attrib |= $atr_alc << 4;
+ $used_attrib |= $atr_bdr << 5;
+ $used_attrib |= $atr_pat << 6;
+ $used_attrib |= $atr_prot << 7;
+
+ $icv = $this->foregroundColor; // fg and bg pattern colors
+ $icv |= $this->backgroundColor << 7;
+
+ $border1 = self::mapBorderStyle($this->_style->getBorders()->getLeft()->getBorderStyle()); // Border line style and color
+ $border1 |= self::mapBorderStyle($this->_style->getBorders()->getRight()->getBorderStyle()) << 4;
+ $border1 |= self::mapBorderStyle($this->_style->getBorders()->getTop()->getBorderStyle()) << 8;
+ $border1 |= self::mapBorderStyle($this->_style->getBorders()->getBottom()->getBorderStyle()) << 12;
+ $border1 |= $this->leftBorderColor << 16;
+ $border1 |= $this->rightBorderColor << 23;
+
+ $diagonalDirection = $this->_style->getBorders()->getDiagonalDirection();
+ $diag_tl_to_rb = $diagonalDirection == Borders::DIAGONAL_BOTH
+ || $diagonalDirection == Borders::DIAGONAL_DOWN;
+ $diag_tr_to_lb = $diagonalDirection == Borders::DIAGONAL_BOTH
+ || $diagonalDirection == Borders::DIAGONAL_UP;
+ $border1 |= $diag_tl_to_rb << 30;
+ $border1 |= $diag_tr_to_lb << 31;
+
+ $border2 = $this->topBorderColor; // Border color
+ $border2 |= $this->bottomBorderColor << 7;
+ $border2 |= $this->_diag_color << 14;
+ $border2 |= self::mapBorderStyle($this->_style->getBorders()->getDiagonal()->getBorderStyle()) << 21;
+ $border2 |= self::mapFillType($this->_style->getFill()->getFillType()) << 26;
+
+ $header = pack('vv', $record, $length);
+
+ //BIFF8 options: identation, shrinkToFit and text direction
+ $biff8_options = $this->_style->getAlignment()->getIndent();
+ $biff8_options |= (int) $this->_style->getAlignment()->getShrinkToFit() << 4;
+
+ $data = pack('vvvC', $ifnt, $ifmt, $style, $align);
+ $data .= pack('CCC', self::mapTextRotation($this->_style->getAlignment()->getTextRotation()), $biff8_options, $used_attrib);
+ $data .= pack('VVv', $border1, $border2, $icv);
+
+ return $header . $data;
+ }
+
+ /**
+ * Is this a style XF ?
+ *
+ * @param bool $value
+ */
+ public function setIsStyleXf($value): void
+ {
+ $this->isStyleXf = $value;
+ }
+
+ /**
+ * Sets the cell's bottom border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setBottomColor($colorIndex): void
+ {
+ $this->bottomBorderColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's top border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setTopColor($colorIndex): void
+ {
+ $this->topBorderColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's left border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setLeftColor($colorIndex): void
+ {
+ $this->leftBorderColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's right border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setRightColor($colorIndex): void
+ {
+ $this->rightBorderColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's diagonal border color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setDiagColor($colorIndex): void
+ {
+ $this->_diag_color = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's foreground color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setFgColor($colorIndex): void
+ {
+ $this->foregroundColor = $colorIndex;
+ }
+
+ /**
+ * Sets the cell's background color.
+ *
+ * @param int $colorIndex Color index
+ */
+ public function setBgColor($colorIndex): void
+ {
+ $this->backgroundColor = $colorIndex;
+ }
+
+ /**
+ * Sets the index to the number format record
+ * It can be date, time, currency, etc...
+ *
+ * @param int $numberFormatIndex Index to format record
+ */
+ public function setNumberFormatIndex($numberFormatIndex): void
+ {
+ $this->numberFormatIndex = $numberFormatIndex;
+ }
+
+ /**
+ * Set the font index.
+ *
+ * @param int $value Font index, note that value 4 does not exist
+ */
+ public function setFontIndex($value): void
+ {
+ $this->fontIndex = $value;
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for border styles.
+ *
+ * @var array of int
+ */
+ private static $mapBorderStyles = [
+ Border::BORDER_NONE => 0x00,
+ Border::BORDER_THIN => 0x01,
+ Border::BORDER_MEDIUM => 0x02,
+ Border::BORDER_DASHED => 0x03,
+ Border::BORDER_DOTTED => 0x04,
+ Border::BORDER_THICK => 0x05,
+ Border::BORDER_DOUBLE => 0x06,
+ Border::BORDER_HAIR => 0x07,
+ Border::BORDER_MEDIUMDASHED => 0x08,
+ Border::BORDER_DASHDOT => 0x09,
+ Border::BORDER_MEDIUMDASHDOT => 0x0A,
+ Border::BORDER_DASHDOTDOT => 0x0B,
+ Border::BORDER_MEDIUMDASHDOTDOT => 0x0C,
+ Border::BORDER_SLANTDASHDOT => 0x0D,
+ ];
+
+ /**
+ * Map border style.
+ *
+ * @param string $borderStyle
+ *
+ * @return int
+ */
+ private static function mapBorderStyle($borderStyle)
+ {
+ if (isset(self::$mapBorderStyles[$borderStyle])) {
+ return self::$mapBorderStyles[$borderStyle];
+ }
+
+ return 0x00;
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for fill types.
+ *
+ * @var array of int
+ */
+ private static $mapFillTypes = [
+ Fill::FILL_NONE => 0x00,
+ Fill::FILL_SOLID => 0x01,
+ Fill::FILL_PATTERN_MEDIUMGRAY => 0x02,
+ Fill::FILL_PATTERN_DARKGRAY => 0x03,
+ Fill::FILL_PATTERN_LIGHTGRAY => 0x04,
+ Fill::FILL_PATTERN_DARKHORIZONTAL => 0x05,
+ Fill::FILL_PATTERN_DARKVERTICAL => 0x06,
+ Fill::FILL_PATTERN_DARKDOWN => 0x07,
+ Fill::FILL_PATTERN_DARKUP => 0x08,
+ Fill::FILL_PATTERN_DARKGRID => 0x09,
+ Fill::FILL_PATTERN_DARKTRELLIS => 0x0A,
+ Fill::FILL_PATTERN_LIGHTHORIZONTAL => 0x0B,
+ Fill::FILL_PATTERN_LIGHTVERTICAL => 0x0C,
+ Fill::FILL_PATTERN_LIGHTDOWN => 0x0D,
+ Fill::FILL_PATTERN_LIGHTUP => 0x0E,
+ Fill::FILL_PATTERN_LIGHTGRID => 0x0F,
+ Fill::FILL_PATTERN_LIGHTTRELLIS => 0x10,
+ Fill::FILL_PATTERN_GRAY125 => 0x11,
+ Fill::FILL_PATTERN_GRAY0625 => 0x12,
+ Fill::FILL_GRADIENT_LINEAR => 0x00, // does not exist in BIFF8
+ Fill::FILL_GRADIENT_PATH => 0x00, // does not exist in BIFF8
+ ];
+
+ /**
+ * Map fill type.
+ *
+ * @param string $fillType
+ *
+ * @return int
+ */
+ private static function mapFillType($fillType)
+ {
+ if (isset(self::$mapFillTypes[$fillType])) {
+ return self::$mapFillTypes[$fillType];
+ }
+
+ return 0x00;
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for horizontal alignment.
+ *
+ * @var array of int
+ */
+ private static $mapHAlignments = [
+ Alignment::HORIZONTAL_GENERAL => 0,
+ Alignment::HORIZONTAL_LEFT => 1,
+ Alignment::HORIZONTAL_CENTER => 2,
+ Alignment::HORIZONTAL_RIGHT => 3,
+ Alignment::HORIZONTAL_FILL => 4,
+ Alignment::HORIZONTAL_JUSTIFY => 5,
+ Alignment::HORIZONTAL_CENTER_CONTINUOUS => 6,
+ ];
+
+ /**
+ * Map to BIFF2-BIFF8 codes for horizontal alignment.
+ *
+ * @param string $hAlign
+ *
+ * @return int
+ */
+ private function mapHAlign($hAlign)
+ {
+ if (isset(self::$mapHAlignments[$hAlign])) {
+ return self::$mapHAlignments[$hAlign];
+ }
+
+ return 0;
+ }
+
+ /**
+ * Map of BIFF2-BIFF8 codes for vertical alignment.
+ *
+ * @var array of int
+ */
+ private static $mapVAlignments = [
+ Alignment::VERTICAL_TOP => 0,
+ Alignment::VERTICAL_CENTER => 1,
+ Alignment::VERTICAL_BOTTOM => 2,
+ Alignment::VERTICAL_JUSTIFY => 3,
+ ];
+
+ /**
+ * Map to BIFF2-BIFF8 codes for vertical alignment.
+ *
+ * @param string $vAlign
+ *
+ * @return int
+ */
+ private static function mapVAlign($vAlign)
+ {
+ if (isset(self::$mapVAlignments[$vAlign])) {
+ return self::$mapVAlignments[$vAlign];
+ }
+
+ return 2;
+ }
+
+ /**
+ * Map to BIFF8 codes for text rotation angle.
+ *
+ * @param int $textRotation
+ *
+ * @return int
+ */
+ private static function mapTextRotation($textRotation)
+ {
+ if ($textRotation >= 0) {
+ return $textRotation;
+ } elseif ($textRotation == -165) {
+ return 255;
+ } elseif ($textRotation < 0) {
+ return 90 - $textRotation;
+ }
+ }
+
+ /**
+ * Map locked.
+ *
+ * @param string $locked
+ *
+ * @return int
+ */
+ private static function mapLocked($locked)
+ {
+ switch ($locked) {
+ case Protection::PROTECTION_INHERIT:
+ return 1;
+ case Protection::PROTECTION_PROTECTED:
+ return 1;
+ case Protection::PROTECTION_UNPROTECTED:
+ return 0;
+ default:
+ return 1;
+ }
+ }
+
+ /**
+ * Map hidden.
+ *
+ * @param string $hidden
+ *
+ * @return int
+ */
+ private static function mapHidden($hidden)
+ {
+ switch ($hidden) {
+ case Protection::PROTECTION_INHERIT:
+ return 0;
+ case Protection::PROTECTION_PROTECTED:
+ return 1;
+ case Protection::PROTECTION_UNPROTECTED:
+ return 0;
+ default:
+ return 0;
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php
new file mode 100644
index 0000000..f34e233
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx.php
@@ -0,0 +1,548 @@
+setSpreadsheet($spreadsheet);
+
+ $writerPartsArray = [
+ 'stringtable' => StringTable::class,
+ 'contenttypes' => ContentTypes::class,
+ 'docprops' => DocProps::class,
+ 'rels' => Rels::class,
+ 'theme' => Theme::class,
+ 'style' => Style::class,
+ 'workbook' => Workbook::class,
+ 'worksheet' => Worksheet::class,
+ 'drawing' => Drawing::class,
+ 'comments' => Comments::class,
+ 'chart' => Chart::class,
+ 'relsvba' => RelsVBA::class,
+ 'relsribbonobjects' => RelsRibbon::class,
+ ];
+
+ // Initialise writer parts
+ // and Assign their parent IWriters
+ foreach ($writerPartsArray as $writer => $class) {
+ $this->writerParts[$writer] = new $class($this);
+ }
+
+ $hashTablesArray = ['stylesConditionalHashTable', 'fillHashTable', 'fontHashTable',
+ 'bordersHashTable', 'numFmtHashTable', 'drawingHashTable',
+ 'styleHashTable',
+ ];
+
+ // Set HashTable variables
+ foreach ($hashTablesArray as $tableName) {
+ $this->$tableName = new HashTable();
+ }
+ }
+
+ /**
+ * Get writer part.
+ *
+ * @param string $pPartName Writer part name
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Writer\Xlsx\WriterPart
+ */
+ public function getWriterPart($pPartName)
+ {
+ if ($pPartName != '' && isset($this->writerParts[strtolower($pPartName)])) {
+ return $this->writerParts[strtolower($pPartName)];
+ }
+
+ return null;
+ }
+
+ /**
+ * Save PhpSpreadsheet to file.
+ *
+ * @param resource|string $pFilename
+ */
+ public function save($pFilename): void
+ {
+ // garbage collect
+ $this->pathNames = [];
+ $this->spreadSheet->garbageCollect();
+
+ $this->openFileHandle($pFilename);
+
+ $saveDebugLog = Calculation::getInstance($this->spreadSheet)->getDebugLog()->getWriteDebugLog();
+ Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog(false);
+ $saveDateReturnType = Functions::getReturnDateType();
+ Functions::setReturnDateType(Functions::RETURNDATE_EXCEL);
+
+ // Create string lookup table
+ $this->stringTable = [];
+ for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
+ $this->stringTable = $this->getWriterPart('StringTable')->createStringTable($this->spreadSheet->getSheet($i), $this->stringTable);
+ }
+
+ // Create styles dictionaries
+ $this->styleHashTable->addFromSource($this->getWriterPart('Style')->allStyles($this->spreadSheet));
+ $this->stylesConditionalHashTable->addFromSource($this->getWriterPart('Style')->allConditionalStyles($this->spreadSheet));
+ $this->fillHashTable->addFromSource($this->getWriterPart('Style')->allFills($this->spreadSheet));
+ $this->fontHashTable->addFromSource($this->getWriterPart('Style')->allFonts($this->spreadSheet));
+ $this->bordersHashTable->addFromSource($this->getWriterPart('Style')->allBorders($this->spreadSheet));
+ $this->numFmtHashTable->addFromSource($this->getWriterPart('Style')->allNumberFormats($this->spreadSheet));
+
+ // Create drawing dictionary
+ $this->drawingHashTable->addFromSource($this->getWriterPart('Drawing')->allDrawings($this->spreadSheet));
+
+ $options = new Archive();
+ $options->setEnableZip64(false);
+ $options->setOutputStream($this->fileHandle);
+
+ $this->zip = new ZipStream(null, $options);
+
+ // Add [Content_Types].xml to ZIP file
+ $this->addZipFile('[Content_Types].xml', $this->getWriterPart('ContentTypes')->writeContentTypes($this->spreadSheet, $this->includeCharts));
+
+ //if hasMacros, add the vbaProject.bin file, Certificate file(if exists)
+ if ($this->spreadSheet->hasMacros()) {
+ $macrosCode = $this->spreadSheet->getMacrosCode();
+ if ($macrosCode !== null) {
+ // we have the code ?
+ $this->addZipFile('xl/vbaProject.bin', $macrosCode); //allways in 'xl', allways named vbaProject.bin
+ if ($this->spreadSheet->hasMacrosCertificate()) {
+ //signed macros ?
+ // Yes : add the certificate file and the related rels file
+ $this->addZipFile('xl/vbaProjectSignature.bin', $this->spreadSheet->getMacrosCertificate());
+ $this->addZipFile('xl/_rels/vbaProject.bin.rels', $this->getWriterPart('RelsVBA')->writeVBARelationships($this->spreadSheet));
+ }
+ }
+ }
+ //a custom UI in this workbook ? add it ("base" xml and additional objects (pictures) and rels)
+ if ($this->spreadSheet->hasRibbon()) {
+ $tmpRibbonTarget = $this->spreadSheet->getRibbonXMLData('target');
+ $this->addZipFile($tmpRibbonTarget, $this->spreadSheet->getRibbonXMLData('data'));
+ if ($this->spreadSheet->hasRibbonBinObjects()) {
+ $tmpRootPath = dirname($tmpRibbonTarget) . '/';
+ $ribbonBinObjects = $this->spreadSheet->getRibbonBinObjects('data'); //the files to write
+ foreach ($ribbonBinObjects as $aPath => $aContent) {
+ $this->addZipFile($tmpRootPath . $aPath, $aContent);
+ }
+ //the rels for files
+ $this->addZipFile($tmpRootPath . '_rels/' . basename($tmpRibbonTarget) . '.rels', $this->getWriterPart('RelsRibbonObjects')->writeRibbonRelationships($this->spreadSheet));
+ }
+ }
+
+ // Add relationships to ZIP file
+ $this->addZipFile('_rels/.rels', $this->getWriterPart('Rels')->writeRelationships($this->spreadSheet));
+ $this->addZipFile('xl/_rels/workbook.xml.rels', $this->getWriterPart('Rels')->writeWorkbookRelationships($this->spreadSheet));
+
+ // Add document properties to ZIP file
+ $this->addZipFile('docProps/app.xml', $this->getWriterPart('DocProps')->writeDocPropsApp($this->spreadSheet));
+ $this->addZipFile('docProps/core.xml', $this->getWriterPart('DocProps')->writeDocPropsCore($this->spreadSheet));
+ $customPropertiesPart = $this->getWriterPart('DocProps')->writeDocPropsCustom($this->spreadSheet);
+ if ($customPropertiesPart !== null) {
+ $this->addZipFile('docProps/custom.xml', $customPropertiesPart);
+ }
+
+ // Add theme to ZIP file
+ $this->addZipFile('xl/theme/theme1.xml', $this->getWriterPart('Theme')->writeTheme($this->spreadSheet));
+
+ // Add string table to ZIP file
+ $this->addZipFile('xl/sharedStrings.xml', $this->getWriterPart('StringTable')->writeStringTable($this->stringTable));
+
+ // Add styles to ZIP file
+ $this->addZipFile('xl/styles.xml', $this->getWriterPart('Style')->writeStyles($this->spreadSheet));
+
+ // Add workbook to ZIP file
+ $this->addZipFile('xl/workbook.xml', $this->getWriterPart('Workbook')->writeWorkbook($this->spreadSheet, $this->preCalculateFormulas));
+
+ $chartCount = 0;
+ // Add worksheets
+ for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
+ $this->addZipFile('xl/worksheets/sheet' . ($i + 1) . '.xml', $this->getWriterPart('Worksheet')->writeWorksheet($this->spreadSheet->getSheet($i), $this->stringTable, $this->includeCharts));
+ if ($this->includeCharts) {
+ $charts = $this->spreadSheet->getSheet($i)->getChartCollection();
+ if (count($charts) > 0) {
+ foreach ($charts as $chart) {
+ $this->addZipFile('xl/charts/chart' . ($chartCount + 1) . '.xml', $this->getWriterPart('Chart')->writeChart($chart, $this->preCalculateFormulas));
+ ++$chartCount;
+ }
+ }
+ }
+ }
+
+ $chartRef1 = 0;
+ // Add worksheet relationships (drawings, ...)
+ for ($i = 0; $i < $this->spreadSheet->getSheetCount(); ++$i) {
+ // Add relationships
+ $this->addZipFile('xl/worksheets/_rels/sheet' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeWorksheetRelationships($this->spreadSheet->getSheet($i), ($i + 1), $this->includeCharts));
+
+ // Add unparsedLoadedData
+ $sheetCodeName = $this->spreadSheet->getSheet($i)->getCodeName();
+ $unparsedLoadedData = $this->spreadSheet->getUnparsedLoadedData();
+ if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'])) {
+ foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['ctrlProps'] as $ctrlProp) {
+ $this->addZipFile($ctrlProp['filePath'], $ctrlProp['content']);
+ }
+ }
+ if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'])) {
+ foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['printerSettings'] as $ctrlProp) {
+ $this->addZipFile($ctrlProp['filePath'], $ctrlProp['content']);
+ }
+ }
+
+ $drawings = $this->spreadSheet->getSheet($i)->getDrawingCollection();
+ $drawingCount = count($drawings);
+ if ($this->includeCharts) {
+ $chartCount = $this->spreadSheet->getSheet($i)->getChartCount();
+ }
+
+ // Add drawing and image relationship parts
+ if (($drawingCount > 0) || ($chartCount > 0)) {
+ // Drawing relationships
+ $this->addZipFile('xl/drawings/_rels/drawing' . ($i + 1) . '.xml.rels', $this->getWriterPart('Rels')->writeDrawingRelationships($this->spreadSheet->getSheet($i), $chartRef1, $this->includeCharts));
+
+ // Drawings
+ $this->addZipFile('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts));
+ } elseif (isset($unparsedLoadedData['sheets'][$sheetCodeName]['drawingAlternateContents'])) {
+ // Drawings
+ $this->addZipFile('xl/drawings/drawing' . ($i + 1) . '.xml', $this->getWriterPart('Drawing')->writeDrawings($this->spreadSheet->getSheet($i), $this->includeCharts));
+ }
+
+ // Add unparsed drawings
+ if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'])) {
+ foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['Drawings'] as $relId => $drawingXml) {
+ $drawingFile = array_search($relId, $unparsedLoadedData['sheets'][$sheetCodeName]['drawingOriginalIds']);
+ if ($drawingFile !== false) {
+ $drawingFile = ltrim($drawingFile, '.');
+ $this->addZipFile('xl' . $drawingFile, $drawingXml);
+ }
+ }
+ }
+
+ // Add comment relationship parts
+ if (count($this->spreadSheet->getSheet($i)->getComments()) > 0) {
+ // VML Comments
+ $this->addZipFile('xl/drawings/vmlDrawing' . ($i + 1) . '.vml', $this->getWriterPart('Comments')->writeVMLComments($this->spreadSheet->getSheet($i)));
+
+ // Comments
+ $this->addZipFile('xl/comments' . ($i + 1) . '.xml', $this->getWriterPart('Comments')->writeComments($this->spreadSheet->getSheet($i)));
+ }
+
+ // Add unparsed relationship parts
+ if (isset($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'])) {
+ foreach ($unparsedLoadedData['sheets'][$sheetCodeName]['vmlDrawings'] as $vmlDrawing) {
+ $this->addZipFile($vmlDrawing['filePath'], $vmlDrawing['content']);
+ }
+ }
+
+ // Add header/footer relationship parts
+ if (count($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) {
+ // VML Drawings
+ $this->addZipFile('xl/drawings/vmlDrawingHF' . ($i + 1) . '.vml', $this->getWriterPart('Drawing')->writeVMLHeaderFooterImages($this->spreadSheet->getSheet($i)));
+
+ // VML Drawing relationships
+ $this->addZipFile('xl/drawings/_rels/vmlDrawingHF' . ($i + 1) . '.vml.rels', $this->getWriterPart('Rels')->writeHeaderFooterDrawingRelationships($this->spreadSheet->getSheet($i)));
+
+ // Media
+ foreach ($this->spreadSheet->getSheet($i)->getHeaderFooter()->getImages() as $image) {
+ $this->addZipFile('xl/media/' . $image->getIndexedFilename(), file_get_contents($image->getPath()));
+ }
+ }
+ }
+
+ // Add media
+ for ($i = 0; $i < $this->getDrawingHashTable()->count(); ++$i) {
+ if ($this->getDrawingHashTable()->getByIndex($i) instanceof WorksheetDrawing) {
+ $imageContents = null;
+ $imagePath = $this->getDrawingHashTable()->getByIndex($i)->getPath();
+ if (strpos($imagePath, 'zip://') !== false) {
+ $imagePath = substr($imagePath, 6);
+ $imagePathSplitted = explode('#', $imagePath);
+
+ $imageZip = new ZipArchive();
+ $imageZip->open($imagePathSplitted[0]);
+ $imageContents = $imageZip->getFromName($imagePathSplitted[1]);
+ $imageZip->close();
+ unset($imageZip);
+ } else {
+ $imageContents = file_get_contents($imagePath);
+ }
+
+ $this->addZipFile('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents);
+ } elseif ($this->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) {
+ ob_start();
+ call_user_func(
+ $this->getDrawingHashTable()->getByIndex($i)->getRenderingFunction(),
+ $this->getDrawingHashTable()->getByIndex($i)->getImageResource()
+ );
+ $imageContents = ob_get_contents();
+ ob_end_clean();
+
+ $this->addZipFile('xl/media/' . str_replace(' ', '_', $this->getDrawingHashTable()->getByIndex($i)->getIndexedFilename()), $imageContents);
+ }
+ }
+
+ Functions::setReturnDateType($saveDateReturnType);
+ Calculation::getInstance($this->spreadSheet)->getDebugLog()->setWriteDebugLog($saveDebugLog);
+
+ // Close file
+ try {
+ $this->zip->finish();
+ } catch (OverflowException $e) {
+ throw new WriterException('Could not close resource.');
+ }
+
+ $this->maybeCloseFileHandle();
+ }
+
+ /**
+ * Get Spreadsheet object.
+ *
+ * @return Spreadsheet
+ */
+ public function getSpreadsheet()
+ {
+ return $this->spreadSheet;
+ }
+
+ /**
+ * Set Spreadsheet object.
+ *
+ * @param Spreadsheet $spreadsheet PhpSpreadsheet object
+ *
+ * @return $this
+ */
+ public function setSpreadsheet(Spreadsheet $spreadsheet)
+ {
+ $this->spreadSheet = $spreadsheet;
+
+ return $this;
+ }
+
+ /**
+ * Get string table.
+ *
+ * @return string[]
+ */
+ public function getStringTable()
+ {
+ return $this->stringTable;
+ }
+
+ /**
+ * Get Style HashTable.
+ *
+ * @return HashTable
+ */
+ public function getStyleHashTable()
+ {
+ return $this->styleHashTable;
+ }
+
+ /**
+ * Get Conditional HashTable.
+ *
+ * @return HashTable
+ */
+ public function getStylesConditionalHashTable()
+ {
+ return $this->stylesConditionalHashTable;
+ }
+
+ /**
+ * Get Fill HashTable.
+ *
+ * @return HashTable
+ */
+ public function getFillHashTable()
+ {
+ return $this->fillHashTable;
+ }
+
+ /**
+ * Get \PhpOffice\PhpSpreadsheet\Style\Font HashTable.
+ *
+ * @return HashTable
+ */
+ public function getFontHashTable()
+ {
+ return $this->fontHashTable;
+ }
+
+ /**
+ * Get Borders HashTable.
+ *
+ * @return HashTable
+ */
+ public function getBordersHashTable()
+ {
+ return $this->bordersHashTable;
+ }
+
+ /**
+ * Get NumberFormat HashTable.
+ *
+ * @return HashTable
+ */
+ public function getNumFmtHashTable()
+ {
+ return $this->numFmtHashTable;
+ }
+
+ /**
+ * Get \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet\BaseDrawing HashTable.
+ *
+ * @return HashTable
+ */
+ public function getDrawingHashTable()
+ {
+ return $this->drawingHashTable;
+ }
+
+ /**
+ * Get Office2003 compatibility.
+ *
+ * @return bool
+ */
+ public function getOffice2003Compatibility()
+ {
+ return $this->office2003compatibility;
+ }
+
+ /**
+ * Set Office2003 compatibility.
+ *
+ * @param bool $pValue Office2003 compatibility?
+ *
+ * @return $this
+ */
+ public function setOffice2003Compatibility($pValue)
+ {
+ $this->office2003compatibility = $pValue;
+
+ return $this;
+ }
+
+ private $pathNames = [];
+
+ private function addZipFile(string $path, string $content): void
+ {
+ if (!in_array($path, $this->pathNames)) {
+ $this->pathNames[] = $path;
+ $this->zip->addFile($path, $content);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
new file mode 100644
index 0000000..4ac780b
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Chart.php
@@ -0,0 +1,1516 @@
+calculateCellValues = $calculateCellValues;
+
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+ // Ensure that data series values are up-to-date before we save
+ if ($this->calculateCellValues) {
+ $pChart->refresh();
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // c:chartSpace
+ $objWriter->startElement('c:chartSpace');
+ $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart');
+ $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+
+ $objWriter->startElement('c:date1904');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ $objWriter->startElement('c:lang');
+ $objWriter->writeAttribute('val', 'en-GB');
+ $objWriter->endElement();
+ $objWriter->startElement('c:roundedCorners');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $this->writeAlternateContent($objWriter);
+
+ $objWriter->startElement('c:chart');
+
+ $this->writeTitle($objWriter, $pChart->getTitle());
+
+ $objWriter->startElement('c:autoTitleDeleted');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $this->writePlotArea($objWriter, $pChart->getWorksheet(), $pChart->getPlotArea(), $pChart->getXAxisLabel(), $pChart->getYAxisLabel(), $pChart->getChartAxisX(), $pChart->getChartAxisY(), $pChart->getMajorGridlines(), $pChart->getMinorGridlines());
+
+ $this->writeLegend($objWriter, $pChart->getLegend());
+
+ $objWriter->startElement('c:plotVisOnly');
+ $objWriter->writeAttribute('val', (int) $pChart->getPlotVisibleOnly());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:dispBlanksAs');
+ $objWriter->writeAttribute('val', $pChart->getDisplayBlanksAs());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showDLblsOverMax');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $this->writePrintSettings($objWriter);
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write Chart Title.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Title $title
+ */
+ private function writeTitle(XMLWriter $objWriter, ?Title $title = null): void
+ {
+ if ($title === null) {
+ return;
+ }
+
+ $objWriter->startElement('c:title');
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:rich');
+
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+
+ $caption = $title->getCaption();
+ if ((is_array($caption)) && (count($caption) > 0)) {
+ $caption = $caption[0];
+ }
+ $this->getParentWriter()->getWriterPart('stringtable')->writeRichTextForCharts($objWriter, $caption, 'a');
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $this->writeLayout($objWriter, $title->getLayout());
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Chart Legend.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Legend $legend
+ */
+ private function writeLegend(XMLWriter $objWriter, ?Legend $legend = null): void
+ {
+ if ($legend === null) {
+ return;
+ }
+
+ $objWriter->startElement('c:legend');
+
+ $objWriter->startElement('c:legendPos');
+ $objWriter->writeAttribute('val', $legend->getPosition());
+ $objWriter->endElement();
+
+ $this->writeLayout($objWriter, $legend->getLayout());
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', ($legend->getOverlay()) ? '1' : '0');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:txPr');
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+ $objWriter->startElement('a:pPr');
+ $objWriter->writeAttribute('rtl', 0);
+
+ $objWriter->startElement('a:defRPr');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:endParaRPr');
+ $objWriter->writeAttribute('lang', 'en-US');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Chart Plot Area.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Title $xAxisLabel
+ * @param Title $yAxisLabel
+ * @param Axis $xAxis
+ * @param Axis $yAxis
+ */
+ private function writePlotArea(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pSheet, PlotArea $plotArea, ?Title $xAxisLabel = null, ?Title $yAxisLabel = null, ?Axis $xAxis = null, ?Axis $yAxis = null, ?GridLines $majorGridlines = null, ?GridLines $minorGridlines = null): void
+ {
+ if ($plotArea === null) {
+ return;
+ }
+
+ $id1 = $id2 = 0;
+ $this->seriesIndex = 0;
+ $objWriter->startElement('c:plotArea');
+
+ $layout = $plotArea->getLayout();
+
+ $this->writeLayout($objWriter, $layout);
+
+ $chartTypes = self::getChartType($plotArea);
+ $catIsMultiLevelSeries = $valIsMultiLevelSeries = false;
+ $plotGroupingType = '';
+ foreach ($chartTypes as $chartType) {
+ $objWriter->startElement('c:' . $chartType);
+
+ $groupCount = $plotArea->getPlotGroupCount();
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $plotGroup = $plotArea->getPlotGroupByIndex($i);
+ $groupType = $plotGroup->getPlotType();
+ if ($groupType == $chartType) {
+ $plotStyle = $plotGroup->getPlotStyle();
+ if ($groupType === DataSeries::TYPE_RADARCHART) {
+ $objWriter->startElement('c:radarStyle');
+ $objWriter->writeAttribute('val', $plotStyle);
+ $objWriter->endElement();
+ } elseif ($groupType === DataSeries::TYPE_SCATTERCHART) {
+ $objWriter->startElement('c:scatterStyle');
+ $objWriter->writeAttribute('val', $plotStyle);
+ $objWriter->endElement();
+ }
+
+ $this->writePlotGroup($plotGroup, $chartType, $objWriter, $catIsMultiLevelSeries, $valIsMultiLevelSeries, $plotGroupingType);
+ }
+ }
+
+ $this->writeDataLabels($objWriter, $layout);
+
+ if ($chartType === DataSeries::TYPE_LINECHART) {
+ // Line only, Line3D can't be smoothed
+ $objWriter->startElement('c:smooth');
+ $objWriter->writeAttribute('val', (int) $plotGroup->getSmoothLine());
+ $objWriter->endElement();
+ } elseif (($chartType === DataSeries::TYPE_BARCHART) || ($chartType === DataSeries::TYPE_BARCHART_3D)) {
+ $objWriter->startElement('c:gapWidth');
+ $objWriter->writeAttribute('val', 150);
+ $objWriter->endElement();
+
+ if ($plotGroupingType == 'percentStacked' || $plotGroupingType == 'stacked') {
+ $objWriter->startElement('c:overlap');
+ $objWriter->writeAttribute('val', 100);
+ $objWriter->endElement();
+ }
+ } elseif ($chartType === DataSeries::TYPE_BUBBLECHART) {
+ $objWriter->startElement('c:bubbleScale');
+ $objWriter->writeAttribute('val', 25);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showNegBubbles');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ } elseif ($chartType === DataSeries::TYPE_STOCKCHART) {
+ $objWriter->startElement('c:hiLowLines');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:upDownBars');
+
+ $objWriter->startElement('c:gapWidth');
+ $objWriter->writeAttribute('val', 300);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:upBars');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:downBars');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ // Generate 2 unique numbers to use for axId values
+ $id1 = '75091328';
+ $id2 = '75089408';
+
+ if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) {
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id1);
+ $objWriter->endElement();
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:firstSliceAng');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ if ($chartType === DataSeries::TYPE_DONUTCHART) {
+ $objWriter->startElement('c:holeSize');
+ $objWriter->writeAttribute('val', 50);
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ if (($chartType !== DataSeries::TYPE_PIECHART) && ($chartType !== DataSeries::TYPE_PIECHART_3D) && ($chartType !== DataSeries::TYPE_DONUTCHART)) {
+ if ($chartType === DataSeries::TYPE_BUBBLECHART) {
+ $this->writeValueAxis($objWriter, $xAxisLabel, $chartType, $id1, $id2, $catIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines);
+ } else {
+ $this->writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $catIsMultiLevelSeries, $yAxis);
+ }
+
+ $this->writeValueAxis($objWriter, $yAxisLabel, $chartType, $id1, $id2, $valIsMultiLevelSeries, $xAxis, $majorGridlines, $minorGridlines);
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Data Labels.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param \PhpOffice\PhpSpreadsheet\Chart\Layout $chartLayout Chart layout
+ */
+ private function writeDataLabels(XMLWriter $objWriter, ?Layout $chartLayout = null): void
+ {
+ $objWriter->startElement('c:dLbls');
+
+ $objWriter->startElement('c:showLegendKey');
+ $showLegendKey = (empty($chartLayout)) ? 0 : $chartLayout->getShowLegendKey();
+ $objWriter->writeAttribute('val', ((empty($showLegendKey)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showVal');
+ $showVal = (empty($chartLayout)) ? 0 : $chartLayout->getShowVal();
+ $objWriter->writeAttribute('val', ((empty($showVal)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showCatName');
+ $showCatName = (empty($chartLayout)) ? 0 : $chartLayout->getShowCatName();
+ $objWriter->writeAttribute('val', ((empty($showCatName)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showSerName');
+ $showSerName = (empty($chartLayout)) ? 0 : $chartLayout->getShowSerName();
+ $objWriter->writeAttribute('val', ((empty($showSerName)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showPercent');
+ $showPercent = (empty($chartLayout)) ? 0 : $chartLayout->getShowPercent();
+ $objWriter->writeAttribute('val', ((empty($showPercent)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showBubbleSize');
+ $showBubbleSize = (empty($chartLayout)) ? 0 : $chartLayout->getShowBubbleSize();
+ $objWriter->writeAttribute('val', ((empty($showBubbleSize)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:showLeaderLines');
+ $showLeaderLines = (empty($chartLayout)) ? 1 : $chartLayout->getShowLeaderLines();
+ $objWriter->writeAttribute('val', ((empty($showLeaderLines)) ? 0 : 1));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Category Axis.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Title $xAxisLabel
+ * @param string $id1
+ * @param string $id2
+ * @param bool $isMultiLevelSeries
+ */
+ private function writeCategoryAxis($objWriter, $xAxisLabel, $id1, $id2, $isMultiLevelSeries, Axis $yAxis): void
+ {
+ $objWriter->startElement('c:catAx');
+
+ if ($id1 > 0) {
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id1);
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:scaling');
+ $objWriter->startElement('c:orientation');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('orientation'));
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:delete');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:axPos');
+ $objWriter->writeAttribute('val', 'b');
+ $objWriter->endElement();
+
+ if ($xAxisLabel !== null) {
+ $objWriter->startElement('c:title');
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:rich');
+
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+ $objWriter->startElement('a:r');
+
+ $caption = $xAxisLabel->getCaption();
+ if (is_array($caption)) {
+ $caption = $caption[0];
+ }
+ $objWriter->startElement('a:t');
+ $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($caption));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $layout = $xAxisLabel->getLayout();
+ $this->writeLayout($objWriter, $layout);
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:numFmt');
+ $objWriter->writeAttribute('formatCode', $yAxis->getAxisNumberFormat());
+ $objWriter->writeAttribute('sourceLinked', $yAxis->getAxisNumberSourceLinked());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:majorTickMark');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('major_tick_mark'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:minorTickMark');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('minor_tick_mark'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:tickLblPos');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('axis_labels'));
+ $objWriter->endElement();
+
+ if ($id2 > 0) {
+ $objWriter->startElement('c:crossAx');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:crosses');
+ $objWriter->writeAttribute('val', $yAxis->getAxisOptionsProperty('horizontal_crosses'));
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:auto');
+ $objWriter->writeAttribute('val', 1);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:lblAlgn');
+ $objWriter->writeAttribute('val', 'ctr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:lblOffset');
+ $objWriter->writeAttribute('val', 100);
+ $objWriter->endElement();
+
+ if ($isMultiLevelSeries) {
+ $objWriter->startElement('c:noMultiLvlLbl');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Value Axis.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Title $yAxisLabel
+ * @param string $groupType Chart type
+ * @param string $id1
+ * @param string $id2
+ * @param bool $isMultiLevelSeries
+ */
+ private function writeValueAxis($objWriter, $yAxisLabel, $groupType, $id1, $id2, $isMultiLevelSeries, Axis $xAxis, GridLines $majorGridlines, GridLines $minorGridlines): void
+ {
+ $objWriter->startElement('c:valAx');
+
+ if ($id2 > 0) {
+ $objWriter->startElement('c:axId');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:scaling');
+
+ if ($xAxis->getAxisOptionsProperty('maximum') !== null) {
+ $objWriter->startElement('c:max');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('maximum'));
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getAxisOptionsProperty('minimum') !== null) {
+ $objWriter->startElement('c:min');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minimum'));
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:orientation');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('orientation'));
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:delete');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:axPos');
+ $objWriter->writeAttribute('val', 'l');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:majorGridlines');
+ $objWriter->startElement('c:spPr');
+
+ if ($majorGridlines->getLineColorProperty('value') !== null) {
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', $majorGridlines->getLineStyleProperty('width'));
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement("a:{$majorGridlines->getLineColorProperty('type')}");
+ $objWriter->writeAttribute('val', $majorGridlines->getLineColorProperty('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $majorGridlines->getLineColorProperty('alpha'));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end srgbClr
+ $objWriter->endElement(); //end solidFill
+
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', $majorGridlines->getLineStyleProperty('dash'));
+ $objWriter->endElement();
+
+ if ($majorGridlines->getLineStyleProperty('join') == 'miter') {
+ $objWriter->startElement('a:miter');
+ $objWriter->writeAttribute('lim', '800000');
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('a:bevel');
+ $objWriter->endElement();
+ }
+
+ if ($majorGridlines->getLineStyleProperty(['arrow', 'head', 'type']) !== null) {
+ $objWriter->startElement('a:headEnd');
+ $objWriter->writeAttribute('type', $majorGridlines->getLineStyleProperty(['arrow', 'head', 'type']));
+ $objWriter->writeAttribute('w', $majorGridlines->getLineStyleArrowParameters('head', 'w'));
+ $objWriter->writeAttribute('len', $majorGridlines->getLineStyleArrowParameters('head', 'len'));
+ $objWriter->endElement();
+ }
+
+ if ($majorGridlines->getLineStyleProperty(['arrow', 'end', 'type']) !== null) {
+ $objWriter->startElement('a:tailEnd');
+ $objWriter->writeAttribute('type', $majorGridlines->getLineStyleProperty(['arrow', 'end', 'type']));
+ $objWriter->writeAttribute('w', $majorGridlines->getLineStyleArrowParameters('end', 'w'));
+ $objWriter->writeAttribute('len', $majorGridlines->getLineStyleArrowParameters('end', 'len'));
+ $objWriter->endElement();
+ }
+ $objWriter->endElement(); //end ln
+ }
+ $objWriter->startElement('a:effectLst');
+
+ if ($majorGridlines->getGlowSize() !== null) {
+ $objWriter->startElement('a:glow');
+ $objWriter->writeAttribute('rad', $majorGridlines->getGlowSize());
+ $objWriter->startElement("a:{$majorGridlines->getGlowColor('type')}");
+ $objWriter->writeAttribute('val', $majorGridlines->getGlowColor('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $majorGridlines->getGlowColor('alpha'));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end schemeClr
+ $objWriter->endElement(); //end glow
+ }
+
+ if ($majorGridlines->getShadowProperty('presets') !== null) {
+ $objWriter->startElement("a:{$majorGridlines->getShadowProperty('effect')}");
+ if ($majorGridlines->getShadowProperty('blur') !== null) {
+ $objWriter->writeAttribute('blurRad', $majorGridlines->getShadowProperty('blur'));
+ }
+ if ($majorGridlines->getShadowProperty('distance') !== null) {
+ $objWriter->writeAttribute('dist', $majorGridlines->getShadowProperty('distance'));
+ }
+ if ($majorGridlines->getShadowProperty('direction') !== null) {
+ $objWriter->writeAttribute('dir', $majorGridlines->getShadowProperty('direction'));
+ }
+ if ($majorGridlines->getShadowProperty('algn') !== null) {
+ $objWriter->writeAttribute('algn', $majorGridlines->getShadowProperty('algn'));
+ }
+ if ($majorGridlines->getShadowProperty(['size', 'sx']) !== null) {
+ $objWriter->writeAttribute('sx', $majorGridlines->getShadowProperty(['size', 'sx']));
+ }
+ if ($majorGridlines->getShadowProperty(['size', 'sy']) !== null) {
+ $objWriter->writeAttribute('sy', $majorGridlines->getShadowProperty(['size', 'sy']));
+ }
+ if ($majorGridlines->getShadowProperty(['size', 'kx']) !== null) {
+ $objWriter->writeAttribute('kx', $majorGridlines->getShadowProperty(['size', 'kx']));
+ }
+ if ($majorGridlines->getShadowProperty('rotWithShape') !== null) {
+ $objWriter->writeAttribute('rotWithShape', $majorGridlines->getShadowProperty('rotWithShape'));
+ }
+ $objWriter->startElement("a:{$majorGridlines->getShadowProperty(['color', 'type'])}");
+ $objWriter->writeAttribute('val', $majorGridlines->getShadowProperty(['color', 'value']));
+
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $majorGridlines->getShadowProperty(['color', 'alpha']));
+ $objWriter->endElement(); //end alpha
+
+ $objWriter->endElement(); //end color:type
+ $objWriter->endElement(); //end shadow
+ }
+
+ if ($majorGridlines->getSoftEdgesSize() !== null) {
+ $objWriter->startElement('a:softEdge');
+ $objWriter->writeAttribute('rad', $majorGridlines->getSoftEdgesSize());
+ $objWriter->endElement(); //end softEdge
+ }
+
+ $objWriter->endElement(); //end effectLst
+ $objWriter->endElement(); //end spPr
+ $objWriter->endElement(); //end majorGridLines
+
+ if ($minorGridlines->getObjectState()) {
+ $objWriter->startElement('c:minorGridlines');
+ $objWriter->startElement('c:spPr');
+
+ if ($minorGridlines->getLineColorProperty('value') !== null) {
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', $minorGridlines->getLineStyleProperty('width'));
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement("a:{$minorGridlines->getLineColorProperty('type')}");
+ $objWriter->writeAttribute('val', $minorGridlines->getLineColorProperty('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $minorGridlines->getLineColorProperty('alpha'));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end srgbClr
+ $objWriter->endElement(); //end solidFill
+
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', $minorGridlines->getLineStyleProperty('dash'));
+ $objWriter->endElement();
+
+ if ($minorGridlines->getLineStyleProperty('join') == 'miter') {
+ $objWriter->startElement('a:miter');
+ $objWriter->writeAttribute('lim', '800000');
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('a:bevel');
+ $objWriter->endElement();
+ }
+
+ if ($minorGridlines->getLineStyleProperty(['arrow', 'head', 'type']) !== null) {
+ $objWriter->startElement('a:headEnd');
+ $objWriter->writeAttribute('type', $minorGridlines->getLineStyleProperty(['arrow', 'head', 'type']));
+ $objWriter->writeAttribute('w', $minorGridlines->getLineStyleArrowParameters('head', 'w'));
+ $objWriter->writeAttribute('len', $minorGridlines->getLineStyleArrowParameters('head', 'len'));
+ $objWriter->endElement();
+ }
+
+ if ($minorGridlines->getLineStyleProperty(['arrow', 'end', 'type']) !== null) {
+ $objWriter->startElement('a:tailEnd');
+ $objWriter->writeAttribute('type', $minorGridlines->getLineStyleProperty(['arrow', 'end', 'type']));
+ $objWriter->writeAttribute('w', $minorGridlines->getLineStyleArrowParameters('end', 'w'));
+ $objWriter->writeAttribute('len', $minorGridlines->getLineStyleArrowParameters('end', 'len'));
+ $objWriter->endElement();
+ }
+ $objWriter->endElement(); //end ln
+ }
+
+ $objWriter->startElement('a:effectLst');
+
+ if ($minorGridlines->getGlowSize() !== null) {
+ $objWriter->startElement('a:glow');
+ $objWriter->writeAttribute('rad', $minorGridlines->getGlowSize());
+ $objWriter->startElement("a:{$minorGridlines->getGlowColor('type')}");
+ $objWriter->writeAttribute('val', $minorGridlines->getGlowColor('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $minorGridlines->getGlowColor('alpha'));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end schemeClr
+ $objWriter->endElement(); //end glow
+ }
+
+ if ($minorGridlines->getShadowProperty('presets') !== null) {
+ $objWriter->startElement("a:{$minorGridlines->getShadowProperty('effect')}");
+ if ($minorGridlines->getShadowProperty('blur') !== null) {
+ $objWriter->writeAttribute('blurRad', $minorGridlines->getShadowProperty('blur'));
+ }
+ if ($minorGridlines->getShadowProperty('distance') !== null) {
+ $objWriter->writeAttribute('dist', $minorGridlines->getShadowProperty('distance'));
+ }
+ if ($minorGridlines->getShadowProperty('direction') !== null) {
+ $objWriter->writeAttribute('dir', $minorGridlines->getShadowProperty('direction'));
+ }
+ if ($minorGridlines->getShadowProperty('algn') !== null) {
+ $objWriter->writeAttribute('algn', $minorGridlines->getShadowProperty('algn'));
+ }
+ if ($minorGridlines->getShadowProperty(['size', 'sx']) !== null) {
+ $objWriter->writeAttribute('sx', $minorGridlines->getShadowProperty(['size', 'sx']));
+ }
+ if ($minorGridlines->getShadowProperty(['size', 'sy']) !== null) {
+ $objWriter->writeAttribute('sy', $minorGridlines->getShadowProperty(['size', 'sy']));
+ }
+ if ($minorGridlines->getShadowProperty(['size', 'kx']) !== null) {
+ $objWriter->writeAttribute('kx', $minorGridlines->getShadowProperty(['size', 'kx']));
+ }
+ if ($minorGridlines->getShadowProperty('rotWithShape') !== null) {
+ $objWriter->writeAttribute('rotWithShape', $minorGridlines->getShadowProperty('rotWithShape'));
+ }
+ $objWriter->startElement("a:{$minorGridlines->getShadowProperty(['color', 'type'])}");
+ $objWriter->writeAttribute('val', $minorGridlines->getShadowProperty(['color', 'value']));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $minorGridlines->getShadowProperty(['color', 'alpha']));
+ $objWriter->endElement(); //end alpha
+ $objWriter->endElement(); //end color:type
+ $objWriter->endElement(); //end shadow
+ }
+
+ if ($minorGridlines->getSoftEdgesSize() !== null) {
+ $objWriter->startElement('a:softEdge');
+ $objWriter->writeAttribute('rad', $minorGridlines->getSoftEdgesSize());
+ $objWriter->endElement(); //end softEdge
+ }
+
+ $objWriter->endElement(); //end effectLst
+ $objWriter->endElement(); //end spPr
+ $objWriter->endElement(); //end minorGridLines
+ }
+
+ if ($yAxisLabel !== null) {
+ $objWriter->startElement('c:title');
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:rich');
+
+ $objWriter->startElement('a:bodyPr');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:lstStyle');
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:p');
+ $objWriter->startElement('a:r');
+
+ $caption = $yAxisLabel->getCaption();
+ if (is_array($caption)) {
+ $caption = $caption[0];
+ }
+
+ $objWriter->startElement('a:t');
+ $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($caption));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ if ($groupType !== DataSeries::TYPE_BUBBLECHART) {
+ $layout = $yAxisLabel->getLayout();
+ $this->writeLayout($objWriter, $layout);
+ }
+
+ $objWriter->startElement('c:overlay');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:numFmt');
+ $objWriter->writeAttribute('formatCode', $xAxis->getAxisNumberFormat());
+ $objWriter->writeAttribute('sourceLinked', $xAxis->getAxisNumberSourceLinked());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:majorTickMark');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_tick_mark'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:minorTickMark');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_tick_mark'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:tickLblPos');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('axis_labels'));
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:spPr');
+
+ if ($xAxis->getFillProperty('value') !== null) {
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:' . $xAxis->getFillProperty('type'));
+ $objWriter->writeAttribute('val', $xAxis->getFillProperty('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $xAxis->getFillProperty('alpha'));
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('a:ln');
+
+ $objWriter->writeAttribute('w', $xAxis->getLineStyleProperty('width'));
+ $objWriter->writeAttribute('cap', $xAxis->getLineStyleProperty('cap'));
+ $objWriter->writeAttribute('cmpd', $xAxis->getLineStyleProperty('compound'));
+
+ if ($xAxis->getLineProperty('value') !== null) {
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:' . $xAxis->getLineProperty('type'));
+ $objWriter->writeAttribute('val', $xAxis->getLineProperty('value'));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $xAxis->getLineProperty('alpha'));
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', $xAxis->getLineStyleProperty('dash'));
+ $objWriter->endElement();
+
+ if ($xAxis->getLineStyleProperty('join') == 'miter') {
+ $objWriter->startElement('a:miter');
+ $objWriter->writeAttribute('lim', '800000');
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('a:bevel');
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getLineStyleProperty(['arrow', 'head', 'type']) !== null) {
+ $objWriter->startElement('a:headEnd');
+ $objWriter->writeAttribute('type', $xAxis->getLineStyleProperty(['arrow', 'head', 'type']));
+ $objWriter->writeAttribute('w', $xAxis->getLineStyleArrowWidth('head'));
+ $objWriter->writeAttribute('len', $xAxis->getLineStyleArrowLength('head'));
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getLineStyleProperty(['arrow', 'end', 'type']) !== null) {
+ $objWriter->startElement('a:tailEnd');
+ $objWriter->writeAttribute('type', $xAxis->getLineStyleProperty(['arrow', 'end', 'type']));
+ $objWriter->writeAttribute('w', $xAxis->getLineStyleArrowWidth('end'));
+ $objWriter->writeAttribute('len', $xAxis->getLineStyleArrowLength('end'));
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:effectLst');
+
+ if ($xAxis->getGlowProperty('size') !== null) {
+ $objWriter->startElement('a:glow');
+ $objWriter->writeAttribute('rad', $xAxis->getGlowProperty('size'));
+ $objWriter->startElement("a:{$xAxis->getGlowProperty(['color', 'type'])}");
+ $objWriter->writeAttribute('val', $xAxis->getGlowProperty(['color', 'value']));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $xAxis->getGlowProperty(['color', 'alpha']));
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getShadowProperty('presets') !== null) {
+ $objWriter->startElement("a:{$xAxis->getShadowProperty('effect')}");
+
+ if ($xAxis->getShadowProperty('blur') !== null) {
+ $objWriter->writeAttribute('blurRad', $xAxis->getShadowProperty('blur'));
+ }
+ if ($xAxis->getShadowProperty('distance') !== null) {
+ $objWriter->writeAttribute('dist', $xAxis->getShadowProperty('distance'));
+ }
+ if ($xAxis->getShadowProperty('direction') !== null) {
+ $objWriter->writeAttribute('dir', $xAxis->getShadowProperty('direction'));
+ }
+ if ($xAxis->getShadowProperty('algn') !== null) {
+ $objWriter->writeAttribute('algn', $xAxis->getShadowProperty('algn'));
+ }
+ if ($xAxis->getShadowProperty(['size', 'sx']) !== null) {
+ $objWriter->writeAttribute('sx', $xAxis->getShadowProperty(['size', 'sx']));
+ }
+ if ($xAxis->getShadowProperty(['size', 'sy']) !== null) {
+ $objWriter->writeAttribute('sy', $xAxis->getShadowProperty(['size', 'sy']));
+ }
+ if ($xAxis->getShadowProperty(['size', 'kx']) !== null) {
+ $objWriter->writeAttribute('kx', $xAxis->getShadowProperty(['size', 'kx']));
+ }
+ if ($xAxis->getShadowProperty('rotWithShape') !== null) {
+ $objWriter->writeAttribute('rotWithShape', $xAxis->getShadowProperty('rotWithShape'));
+ }
+
+ $objWriter->startElement("a:{$xAxis->getShadowProperty(['color', 'type'])}");
+ $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'value']));
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $xAxis->getShadowProperty(['color', 'alpha']));
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getSoftEdgesSize() !== null) {
+ $objWriter->startElement('a:softEdge');
+ $objWriter->writeAttribute('rad', $xAxis->getSoftEdgesSize());
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement(); //effectList
+ $objWriter->endElement(); //end spPr
+
+ if ($id1 > 0) {
+ $objWriter->startElement('c:crossAx');
+ $objWriter->writeAttribute('val', $id2);
+ $objWriter->endElement();
+
+ if ($xAxis->getAxisOptionsProperty('horizontal_crosses_value') !== null) {
+ $objWriter->startElement('c:crossesAt');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('horizontal_crosses_value'));
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:crosses');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('horizontal_crosses'));
+ $objWriter->endElement();
+ }
+
+ $objWriter->startElement('c:crossBetween');
+ $objWriter->writeAttribute('val', 'midCat');
+ $objWriter->endElement();
+
+ if ($xAxis->getAxisOptionsProperty('major_unit') !== null) {
+ $objWriter->startElement('c:majorUnit');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('major_unit'));
+ $objWriter->endElement();
+ }
+
+ if ($xAxis->getAxisOptionsProperty('minor_unit') !== null) {
+ $objWriter->startElement('c:minorUnit');
+ $objWriter->writeAttribute('val', $xAxis->getAxisOptionsProperty('minor_unit'));
+ $objWriter->endElement();
+ }
+ }
+
+ if ($isMultiLevelSeries) {
+ if ($groupType !== DataSeries::TYPE_BUBBLECHART) {
+ $objWriter->startElement('c:noMultiLvlLbl');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Get the data series type(s) for a chart plot series.
+ *
+ * @param PlotArea $plotArea
+ *
+ * @return array|string
+ */
+ private static function getChartType($plotArea)
+ {
+ $groupCount = $plotArea->getPlotGroupCount();
+
+ if ($groupCount == 1) {
+ $chartType = [$plotArea->getPlotGroupByIndex(0)->getPlotType()];
+ } else {
+ $chartTypes = [];
+ for ($i = 0; $i < $groupCount; ++$i) {
+ $chartTypes[] = $plotArea->getPlotGroupByIndex($i)->getPlotType();
+ }
+ $chartType = array_unique($chartTypes);
+ if (count($chartTypes) == 0) {
+ throw new WriterException('Chart is not yet implemented');
+ }
+ }
+
+ return $chartType;
+ }
+
+ /**
+ * Method writing plot series values.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $val value for idx (default: 3)
+ * @param string $fillColor hex color (default: FF9900)
+ *
+ * @return XMLWriter XML Writer
+ */
+ private function writePlotSeriesValuesElement($objWriter, $val = 3, $fillColor = 'FF9900')
+ {
+ $objWriter->startElement('c:dPt');
+ $objWriter->startElement('c:idx');
+ $objWriter->writeAttribute('val', $val);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:bubble3D');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:spPr');
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', $fillColor);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter;
+ }
+
+ /**
+ * Write Plot Group (series of related plots).
+ *
+ * @param DataSeries $plotGroup
+ * @param string $groupType Type of plot for dataseries
+ * @param XMLWriter $objWriter XML Writer
+ * @param bool &$catIsMultiLevelSeries Is category a multi-series category
+ * @param bool &$valIsMultiLevelSeries Is value set a multi-series set
+ * @param string &$plotGroupingType Type of grouping for multi-series values
+ */
+ private function writePlotGroup($plotGroup, $groupType, $objWriter, &$catIsMultiLevelSeries, &$valIsMultiLevelSeries, &$plotGroupingType): void
+ {
+ if ($plotGroup === null) {
+ return;
+ }
+
+ if (($groupType == DataSeries::TYPE_BARCHART) || ($groupType == DataSeries::TYPE_BARCHART_3D)) {
+ $objWriter->startElement('c:barDir');
+ $objWriter->writeAttribute('val', $plotGroup->getPlotDirection());
+ $objWriter->endElement();
+ }
+
+ if ($plotGroup->getPlotGrouping() !== null) {
+ $plotGroupingType = $plotGroup->getPlotGrouping();
+ $objWriter->startElement('c:grouping');
+ $objWriter->writeAttribute('val', $plotGroupingType);
+ $objWriter->endElement();
+ }
+
+ // Get these details before the loop, because we can use the count to check for varyColors
+ $plotSeriesOrder = $plotGroup->getPlotOrder();
+ $plotSeriesCount = count($plotSeriesOrder);
+
+ if (($groupType !== DataSeries::TYPE_RADARCHART) && ($groupType !== DataSeries::TYPE_STOCKCHART)) {
+ if ($groupType !== DataSeries::TYPE_LINECHART) {
+ if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART) || ($plotSeriesCount > 1)) {
+ $objWriter->startElement('c:varyColors');
+ $objWriter->writeAttribute('val', 1);
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:varyColors');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ foreach ($plotSeriesOrder as $plotSeriesIdx => $plotSeriesRef) {
+ $objWriter->startElement('c:ser');
+
+ $plotLabel = $plotGroup->getPlotLabelByIndex($plotSeriesIdx);
+ if ($plotLabel) {
+ $fillColor = $plotLabel->getFillColor();
+ if ($fillColor !== null && !is_array($fillColor)) {
+ $objWriter->startElement('c:spPr');
+ $objWriter->startElement('a:solidFill');
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', $fillColor);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->startElement('c:idx');
+ $objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesIdx);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:order');
+ $objWriter->writeAttribute('val', $this->seriesIndex + $plotSeriesRef);
+ $objWriter->endElement();
+
+ // Values
+ $plotSeriesValues = $plotGroup->getPlotValuesByIndex($plotSeriesRef);
+
+ if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) {
+ $fillColorValues = $plotSeriesValues->getFillColor();
+ if ($fillColorValues !== null && is_array($fillColorValues)) {
+ foreach ($plotSeriesValues->getDataValues() as $dataKey => $dataValue) {
+ $this->writePlotSeriesValuesElement($objWriter, $dataKey, ($fillColorValues[$dataKey] ?? 'FF9900'));
+ }
+ } else {
+ $this->writePlotSeriesValuesElement($objWriter);
+ }
+ }
+
+ // Labels
+ $plotSeriesLabel = $plotGroup->getPlotLabelByIndex($plotSeriesRef);
+ if ($plotSeriesLabel && ($plotSeriesLabel->getPointCount() > 0)) {
+ $objWriter->startElement('c:tx');
+ $objWriter->startElement('c:strRef');
+ $this->writePlotSeriesLabel($plotSeriesLabel, $objWriter);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ // Formatting for the points
+ if (($groupType == DataSeries::TYPE_LINECHART) || ($groupType == DataSeries::TYPE_STOCKCHART)) {
+ $plotLineWidth = 12700;
+ if ($plotSeriesValues) {
+ $plotLineWidth = $plotSeriesValues->getLineWidth();
+ }
+
+ $objWriter->startElement('c:spPr');
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', $plotLineWidth);
+ if ($groupType == DataSeries::TYPE_STOCKCHART) {
+ $objWriter->startElement('a:noFill');
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+
+ if ($plotSeriesValues) {
+ $plotSeriesMarker = $plotSeriesValues->getPointMarker();
+ if ($plotSeriesMarker) {
+ $objWriter->startElement('c:marker');
+ $objWriter->startElement('c:symbol');
+ $objWriter->writeAttribute('val', $plotSeriesMarker);
+ $objWriter->endElement();
+
+ if ($plotSeriesMarker !== 'none') {
+ $objWriter->startElement('c:size');
+ $objWriter->writeAttribute('val', 3);
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ if (($groupType === DataSeries::TYPE_BARCHART) || ($groupType === DataSeries::TYPE_BARCHART_3D) || ($groupType === DataSeries::TYPE_BUBBLECHART)) {
+ $objWriter->startElement('c:invertIfNegative');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+
+ // Category Labels
+ $plotSeriesCategory = $plotGroup->getPlotCategoryByIndex($plotSeriesRef);
+ if ($plotSeriesCategory && ($plotSeriesCategory->getPointCount() > 0)) {
+ $catIsMultiLevelSeries = $catIsMultiLevelSeries || $plotSeriesCategory->isMultiLevelSeries();
+
+ if (($groupType == DataSeries::TYPE_PIECHART) || ($groupType == DataSeries::TYPE_PIECHART_3D) || ($groupType == DataSeries::TYPE_DONUTCHART)) {
+ if ($plotGroup->getPlotStyle() !== null) {
+ $plotStyle = $plotGroup->getPlotStyle();
+ if ($plotStyle) {
+ $objWriter->startElement('c:explosion');
+ $objWriter->writeAttribute('val', 25);
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) {
+ $objWriter->startElement('c:xVal');
+ } else {
+ $objWriter->startElement('c:cat');
+ }
+
+ $this->writePlotSeriesValues($plotSeriesCategory, $objWriter, $groupType, 'str');
+ $objWriter->endElement();
+ }
+
+ // Values
+ if ($plotSeriesValues) {
+ $valIsMultiLevelSeries = $valIsMultiLevelSeries || $plotSeriesValues->isMultiLevelSeries();
+
+ if (($groupType === DataSeries::TYPE_BUBBLECHART) || ($groupType === DataSeries::TYPE_SCATTERCHART)) {
+ $objWriter->startElement('c:yVal');
+ } else {
+ $objWriter->startElement('c:val');
+ }
+
+ $this->writePlotSeriesValues($plotSeriesValues, $objWriter, $groupType, 'num');
+ $objWriter->endElement();
+ }
+
+ if ($groupType === DataSeries::TYPE_BUBBLECHART) {
+ $this->writeBubbles($plotSeriesValues, $objWriter);
+ }
+
+ $objWriter->endElement();
+ }
+
+ $this->seriesIndex += $plotSeriesIdx + 1;
+ }
+
+ /**
+ * Write Plot Series Label.
+ *
+ * @param DataSeriesValues $plotSeriesLabel
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writePlotSeriesLabel($plotSeriesLabel, $objWriter): void
+ {
+ if ($plotSeriesLabel === null) {
+ return;
+ }
+
+ $objWriter->startElement('c:f');
+ $objWriter->writeRawData($plotSeriesLabel->getDataSource());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:strCache');
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesLabel->getPointCount());
+ $objWriter->endElement();
+
+ foreach ($plotSeriesLabel->getDataValues() as $plotLabelKey => $plotLabelValue) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotLabelKey);
+
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData($plotLabelValue);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Plot Series Values.
+ *
+ * @param DataSeriesValues $plotSeriesValues
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $groupType Type of plot for dataseries
+ * @param string $dataType Datatype of series values
+ */
+ private function writePlotSeriesValues($plotSeriesValues, XMLWriter $objWriter, $groupType, $dataType = 'str'): void
+ {
+ if ($plotSeriesValues === null) {
+ return;
+ }
+
+ if ($plotSeriesValues->isMultiLevelSeries()) {
+ $levelCount = $plotSeriesValues->multiLevelCount();
+
+ $objWriter->startElement('c:multiLvlStrRef');
+
+ $objWriter->startElement('c:f');
+ $objWriter->writeRawData($plotSeriesValues->getDataSource());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:multiLvlStrCache');
+
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
+ $objWriter->endElement();
+
+ for ($level = 0; $level < $levelCount; ++$level) {
+ $objWriter->startElement('c:lvl');
+
+ foreach ($plotSeriesValues->getDataValues() as $plotSeriesKey => $plotSeriesValue) {
+ if (isset($plotSeriesValue[$level])) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotSeriesKey);
+
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData($plotSeriesValue[$level]);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ } else {
+ $objWriter->startElement('c:' . $dataType . 'Ref');
+
+ $objWriter->startElement('c:f');
+ $objWriter->writeRawData($plotSeriesValues->getDataSource());
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:' . $dataType . 'Cache');
+
+ if (($groupType != DataSeries::TYPE_PIECHART) && ($groupType != DataSeries::TYPE_PIECHART_3D) && ($groupType != DataSeries::TYPE_DONUTCHART)) {
+ if (($plotSeriesValues->getFormatCode() !== null) && ($plotSeriesValues->getFormatCode() !== '')) {
+ $objWriter->startElement('c:formatCode');
+ $objWriter->writeRawData($plotSeriesValues->getFormatCode());
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
+ $objWriter->endElement();
+
+ $dataValues = $plotSeriesValues->getDataValues();
+ if (!empty($dataValues)) {
+ if (is_array($dataValues)) {
+ foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotSeriesKey);
+
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData($plotSeriesValue);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Bubble Chart Details.
+ *
+ * @param DataSeriesValues $plotSeriesValues
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeBubbles($plotSeriesValues, $objWriter): void
+ {
+ if ($plotSeriesValues === null) {
+ return;
+ }
+
+ $objWriter->startElement('c:bubbleSize');
+ $objWriter->startElement('c:numLit');
+
+ $objWriter->startElement('c:formatCode');
+ $objWriter->writeRawData('General');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:ptCount');
+ $objWriter->writeAttribute('val', $plotSeriesValues->getPointCount());
+ $objWriter->endElement();
+
+ $dataValues = $plotSeriesValues->getDataValues();
+ if (!empty($dataValues)) {
+ if (is_array($dataValues)) {
+ foreach ($dataValues as $plotSeriesKey => $plotSeriesValue) {
+ $objWriter->startElement('c:pt');
+ $objWriter->writeAttribute('idx', $plotSeriesKey);
+ $objWriter->startElement('c:v');
+ $objWriter->writeRawData(1);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ }
+ }
+ }
+
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:bubble3D');
+ $objWriter->writeAttribute('val', 0);
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Layout.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Layout $layout
+ */
+ private function writeLayout(XMLWriter $objWriter, ?Layout $layout = null): void
+ {
+ $objWriter->startElement('c:layout');
+
+ if ($layout !== null) {
+ $objWriter->startElement('c:manualLayout');
+
+ $layoutTarget = $layout->getLayoutTarget();
+ if ($layoutTarget !== null) {
+ $objWriter->startElement('c:layoutTarget');
+ $objWriter->writeAttribute('val', $layoutTarget);
+ $objWriter->endElement();
+ }
+
+ $xMode = $layout->getXMode();
+ if ($xMode !== null) {
+ $objWriter->startElement('c:xMode');
+ $objWriter->writeAttribute('val', $xMode);
+ $objWriter->endElement();
+ }
+
+ $yMode = $layout->getYMode();
+ if ($yMode !== null) {
+ $objWriter->startElement('c:yMode');
+ $objWriter->writeAttribute('val', $yMode);
+ $objWriter->endElement();
+ }
+
+ $x = $layout->getXPosition();
+ if ($x !== null) {
+ $objWriter->startElement('c:x');
+ $objWriter->writeAttribute('val', $x);
+ $objWriter->endElement();
+ }
+
+ $y = $layout->getYPosition();
+ if ($y !== null) {
+ $objWriter->startElement('c:y');
+ $objWriter->writeAttribute('val', $y);
+ $objWriter->endElement();
+ }
+
+ $w = $layout->getWidth();
+ if ($w !== null) {
+ $objWriter->startElement('c:w');
+ $objWriter->writeAttribute('val', $w);
+ $objWriter->endElement();
+ }
+
+ $h = $layout->getHeight();
+ if ($h !== null) {
+ $objWriter->startElement('c:h');
+ $objWriter->writeAttribute('val', $h);
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Alternate Content block.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeAlternateContent($objWriter): void
+ {
+ $objWriter->startElement('mc:AlternateContent');
+ $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006');
+
+ $objWriter->startElement('mc:Choice');
+ $objWriter->writeAttribute('xmlns:c14', 'http://schemas.microsoft.com/office/drawing/2007/8/2/chart');
+ $objWriter->writeAttribute('Requires', 'c14');
+
+ $objWriter->startElement('c14:style');
+ $objWriter->writeAttribute('val', '102');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('mc:Fallback');
+ $objWriter->startElement('c:style');
+ $objWriter->writeAttribute('val', '2');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Printer Settings.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writePrintSettings($objWriter): void
+ {
+ $objWriter->startElement('c:printSettings');
+
+ $objWriter->startElement('c:headerFooter');
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:pageMargins');
+ $objWriter->writeAttribute('footer', 0.3);
+ $objWriter->writeAttribute('header', 0.3);
+ $objWriter->writeAttribute('r', 0.7);
+ $objWriter->writeAttribute('l', 0.7);
+ $objWriter->writeAttribute('t', 0.75);
+ $objWriter->writeAttribute('b', 0.75);
+ $objWriter->endElement();
+
+ $objWriter->startElement('c:pageSetup');
+ $objWriter->writeAttribute('orientation', 'portrait');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php
new file mode 100644
index 0000000..3d012fe
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Comments.php
@@ -0,0 +1,232 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Comments cache
+ $comments = $pWorksheet->getComments();
+
+ // Authors cache
+ $authors = [];
+ $authorId = 0;
+ foreach ($comments as $comment) {
+ if (!isset($authors[$comment->getAuthor()])) {
+ $authors[$comment->getAuthor()] = $authorId++;
+ }
+ }
+
+ // comments
+ $objWriter->startElement('comments');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+
+ // Loop through authors
+ $objWriter->startElement('authors');
+ foreach ($authors as $author => $index) {
+ $objWriter->writeElement('author', $author);
+ }
+ $objWriter->endElement();
+
+ // Loop through comments
+ $objWriter->startElement('commentList');
+ foreach ($comments as $key => $value) {
+ $this->writeComment($objWriter, $key, $value, $authors);
+ }
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write comment to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pCellReference Cell reference
+ * @param Comment $pComment Comment
+ * @param array $pAuthors Array of authors
+ */
+ private function writeComment(XMLWriter $objWriter, $pCellReference, Comment $pComment, array $pAuthors): void
+ {
+ // comment
+ $objWriter->startElement('comment');
+ $objWriter->writeAttribute('ref', $pCellReference);
+ $objWriter->writeAttribute('authorId', $pAuthors[$pComment->getAuthor()]);
+
+ // text
+ $objWriter->startElement('text');
+ $this->getParentWriter()->getWriterPart('stringtable')->writeRichText($objWriter, $pComment->getText());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write VML comments to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeVMLComments(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Comments cache
+ $comments = $pWorksheet->getComments();
+
+ // xml
+ $objWriter->startElement('xml');
+ $objWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml');
+ $objWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office');
+ $objWriter->writeAttribute('xmlns:x', 'urn:schemas-microsoft-com:office:excel');
+
+ // o:shapelayout
+ $objWriter->startElement('o:shapelayout');
+ $objWriter->writeAttribute('v:ext', 'edit');
+
+ // o:idmap
+ $objWriter->startElement('o:idmap');
+ $objWriter->writeAttribute('v:ext', 'edit');
+ $objWriter->writeAttribute('data', '1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // v:shapetype
+ $objWriter->startElement('v:shapetype');
+ $objWriter->writeAttribute('id', '_x0000_t202');
+ $objWriter->writeAttribute('coordsize', '21600,21600');
+ $objWriter->writeAttribute('o:spt', '202');
+ $objWriter->writeAttribute('path', 'm,l,21600r21600,l21600,xe');
+
+ // v:stroke
+ $objWriter->startElement('v:stroke');
+ $objWriter->writeAttribute('joinstyle', 'miter');
+ $objWriter->endElement();
+
+ // v:path
+ $objWriter->startElement('v:path');
+ $objWriter->writeAttribute('gradientshapeok', 't');
+ $objWriter->writeAttribute('o:connecttype', 'rect');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Loop through comments
+ foreach ($comments as $key => $value) {
+ $this->writeVMLComment($objWriter, $key, $value);
+ }
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write VML comment to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pCellReference Cell reference, eg: 'A1'
+ * @param Comment $pComment Comment
+ */
+ private function writeVMLComment(XMLWriter $objWriter, $pCellReference, Comment $pComment): void
+ {
+ // Metadata
+ [$column, $row] = Coordinate::coordinateFromString($pCellReference);
+ $column = Coordinate::columnIndexFromString($column);
+ $id = 1024 + $column + $row;
+ $id = substr($id, 0, 4);
+
+ // v:shape
+ $objWriter->startElement('v:shape');
+ $objWriter->writeAttribute('id', '_x0000_s' . $id);
+ $objWriter->writeAttribute('type', '#_x0000_t202');
+ $objWriter->writeAttribute('style', 'position:absolute;margin-left:' . $pComment->getMarginLeft() . ';margin-top:' . $pComment->getMarginTop() . ';width:' . $pComment->getWidth() . ';height:' . $pComment->getHeight() . ';z-index:1;visibility:' . ($pComment->getVisible() ? 'visible' : 'hidden'));
+ $objWriter->writeAttribute('fillcolor', '#' . $pComment->getFillColor()->getRGB());
+ $objWriter->writeAttribute('o:insetmode', 'auto');
+
+ // v:fill
+ $objWriter->startElement('v:fill');
+ $objWriter->writeAttribute('color2', '#' . $pComment->getFillColor()->getRGB());
+ $objWriter->endElement();
+
+ // v:shadow
+ $objWriter->startElement('v:shadow');
+ $objWriter->writeAttribute('on', 't');
+ $objWriter->writeAttribute('color', 'black');
+ $objWriter->writeAttribute('obscured', 't');
+ $objWriter->endElement();
+
+ // v:path
+ $objWriter->startElement('v:path');
+ $objWriter->writeAttribute('o:connecttype', 'none');
+ $objWriter->endElement();
+
+ // v:textbox
+ $objWriter->startElement('v:textbox');
+ $objWriter->writeAttribute('style', 'mso-direction-alt:auto');
+
+ // div
+ $objWriter->startElement('div');
+ $objWriter->writeAttribute('style', 'text-align:left');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // x:ClientData
+ $objWriter->startElement('x:ClientData');
+ $objWriter->writeAttribute('ObjectType', 'Note');
+
+ // x:MoveWithCells
+ $objWriter->writeElement('x:MoveWithCells', '');
+
+ // x:SizeWithCells
+ $objWriter->writeElement('x:SizeWithCells', '');
+
+ // x:AutoFill
+ $objWriter->writeElement('x:AutoFill', 'False');
+
+ // x:Row
+ $objWriter->writeElement('x:Row', ($row - 1));
+
+ // x:Column
+ $objWriter->writeElement('x:Column', ($column - 1));
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php
new file mode 100644
index 0000000..2499d97
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/ContentTypes.php
@@ -0,0 +1,240 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Types
+ $objWriter->startElement('Types');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/content-types');
+
+ // Theme
+ $this->writeOverrideContentType($objWriter, '/xl/theme/theme1.xml', 'application/vnd.openxmlformats-officedocument.theme+xml');
+
+ // Styles
+ $this->writeOverrideContentType($objWriter, '/xl/styles.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml');
+
+ // Rels
+ $this->writeDefaultContentType($objWriter, 'rels', 'application/vnd.openxmlformats-package.relationships+xml');
+
+ // XML
+ $this->writeDefaultContentType($objWriter, 'xml', 'application/xml');
+
+ // VML
+ $this->writeDefaultContentType($objWriter, 'vml', 'application/vnd.openxmlformats-officedocument.vmlDrawing');
+
+ // Workbook
+ if ($spreadsheet->hasMacros()) { //Macros in workbook ?
+ // Yes : not standard content but "macroEnabled"
+ $this->writeOverrideContentType($objWriter, '/xl/workbook.xml', 'application/vnd.ms-excel.sheet.macroEnabled.main+xml');
+ //... and define a new type for the VBA project
+ // Better use Override, because we can use 'bin' also for xl\printerSettings\printerSettings1.bin
+ $this->writeOverrideContentType($objWriter, '/xl/vbaProject.bin', 'application/vnd.ms-office.vbaProject');
+ if ($spreadsheet->hasMacrosCertificate()) {
+ // signed macros ?
+ // Yes : add needed information
+ $this->writeOverrideContentType($objWriter, '/xl/vbaProjectSignature.bin', 'application/vnd.ms-office.vbaProjectSignature');
+ }
+ } else {
+ // no macros in workbook, so standard type
+ $this->writeOverrideContentType($objWriter, '/xl/workbook.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml');
+ }
+
+ // DocProps
+ $this->writeOverrideContentType($objWriter, '/docProps/app.xml', 'application/vnd.openxmlformats-officedocument.extended-properties+xml');
+
+ $this->writeOverrideContentType($objWriter, '/docProps/core.xml', 'application/vnd.openxmlformats-package.core-properties+xml');
+
+ $customPropertyList = $spreadsheet->getProperties()->getCustomProperties();
+ if (!empty($customPropertyList)) {
+ $this->writeOverrideContentType($objWriter, '/docProps/custom.xml', 'application/vnd.openxmlformats-officedocument.custom-properties+xml');
+ }
+
+ // Worksheets
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $this->writeOverrideContentType($objWriter, '/xl/worksheets/sheet' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml');
+ }
+
+ // Shared strings
+ $this->writeOverrideContentType($objWriter, '/xl/sharedStrings.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sharedStrings+xml');
+
+ // Add worksheet relationship content types
+ $unparsedLoadedData = $spreadsheet->getUnparsedLoadedData();
+ $chart = 1;
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $drawings = $spreadsheet->getSheet($i)->getDrawingCollection();
+ $drawingCount = count($drawings);
+ $chartCount = ($includeCharts) ? $spreadsheet->getSheet($i)->getChartCount() : 0;
+ $hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$spreadsheet->getSheet($i)->getCodeName()]['drawingOriginalIds']);
+
+ // We need a drawing relationship for the worksheet if we have either drawings or charts
+ if (($drawingCount > 0) || ($chartCount > 0) || $hasUnparsedDrawing) {
+ $this->writeOverrideContentType($objWriter, '/xl/drawings/drawing' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.drawing+xml');
+ }
+
+ // If we have charts, then we need a chart relationship for every individual chart
+ if ($chartCount > 0) {
+ for ($c = 0; $c < $chartCount; ++$c) {
+ $this->writeOverrideContentType($objWriter, '/xl/charts/chart' . $chart++ . '.xml', 'application/vnd.openxmlformats-officedocument.drawingml.chart+xml');
+ }
+ }
+ }
+
+ // Comments
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ if (count($spreadsheet->getSheet($i)->getComments()) > 0) {
+ $this->writeOverrideContentType($objWriter, '/xl/comments' . ($i + 1) . '.xml', 'application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml');
+ }
+ }
+
+ // Add media content-types
+ $aMediaContentTypes = [];
+ $mediaCount = $this->getParentWriter()->getDrawingHashTable()->count();
+ for ($i = 0; $i < $mediaCount; ++$i) {
+ $extension = '';
+ $mimeType = '';
+
+ if ($this->getParentWriter()->getDrawingHashTable()->getByIndex($i) instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing) {
+ $extension = strtolower($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getExtension());
+ $mimeType = $this->getImageMimeType($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getPath());
+ } elseif ($this->getParentWriter()->getDrawingHashTable()->getByIndex($i) instanceof MemoryDrawing) {
+ $extension = strtolower($this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getMimeType());
+ $extension = explode('/', $extension);
+ $extension = $extension[1];
+
+ $mimeType = $this->getParentWriter()->getDrawingHashTable()->getByIndex($i)->getMimeType();
+ }
+
+ if (!isset($aMediaContentTypes[$extension])) {
+ $aMediaContentTypes[$extension] = $mimeType;
+
+ $this->writeDefaultContentType($objWriter, $extension, $mimeType);
+ }
+ }
+ if ($spreadsheet->hasRibbonBinObjects()) {
+ // Some additional objects in the ribbon ?
+ // we need to write "Extension" but not already write for media content
+ $tabRibbonTypes = array_diff($spreadsheet->getRibbonBinObjects('types'), array_keys($aMediaContentTypes));
+ foreach ($tabRibbonTypes as $aRibbonType) {
+ $mimeType = 'image/.' . $aRibbonType; //we wrote $mimeType like customUI Editor
+ $this->writeDefaultContentType($objWriter, $aRibbonType, $mimeType);
+ }
+ }
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ if (count($spreadsheet->getSheet($i)->getHeaderFooter()->getImages()) > 0) {
+ foreach ($spreadsheet->getSheet($i)->getHeaderFooter()->getImages() as $image) {
+ if (!isset($aMediaContentTypes[strtolower($image->getExtension())])) {
+ $aMediaContentTypes[strtolower($image->getExtension())] = $this->getImageMimeType($image->getPath());
+
+ $this->writeDefaultContentType($objWriter, strtolower($image->getExtension()), $aMediaContentTypes[strtolower($image->getExtension())]);
+ }
+ }
+ }
+ }
+
+ // unparsed defaults
+ if (isset($unparsedLoadedData['default_content_types'])) {
+ foreach ($unparsedLoadedData['default_content_types'] as $extName => $contentType) {
+ $this->writeDefaultContentType($objWriter, $extName, $contentType);
+ }
+ }
+
+ // unparsed overrides
+ if (isset($unparsedLoadedData['override_content_types'])) {
+ foreach ($unparsedLoadedData['override_content_types'] as $partName => $overrideType) {
+ $this->writeOverrideContentType($objWriter, $partName, $overrideType);
+ }
+ }
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Get image mime type.
+ *
+ * @param string $pFile Filename
+ *
+ * @return string Mime Type
+ */
+ private function getImageMimeType($pFile)
+ {
+ if (File::fileExists($pFile)) {
+ $image = getimagesize($pFile);
+
+ return image_type_to_mime_type($image[2]);
+ }
+
+ throw new WriterException("File $pFile does not exist");
+ }
+
+ /**
+ * Write Default content type.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pPartname Part name
+ * @param string $pContentType Content type
+ */
+ private function writeDefaultContentType(XMLWriter $objWriter, $pPartname, $pContentType): void
+ {
+ if ($pPartname != '' && $pContentType != '') {
+ // Write content type
+ $objWriter->startElement('Default');
+ $objWriter->writeAttribute('Extension', $pPartname);
+ $objWriter->writeAttribute('ContentType', $pContentType);
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+
+ /**
+ * Write Override content type.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pPartname Part name
+ * @param string $pContentType Content type
+ */
+ private function writeOverrideContentType(XMLWriter $objWriter, $pPartname, $pContentType): void
+ {
+ if ($pPartname != '' && $pContentType != '') {
+ // Write content type
+ $objWriter->startElement('Override');
+ $objWriter->writeAttribute('PartName', $pPartname);
+ $objWriter->writeAttribute('ContentType', $pContentType);
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php
new file mode 100644
index 0000000..43a8d2e
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DefinedNames.php
@@ -0,0 +1,223 @@
+objWriter = $objWriter;
+ $this->spreadsheet = $spreadsheet;
+ }
+
+ public function write(): void
+ {
+ // Write defined names
+ $this->objWriter->startElement('definedNames');
+
+ // Named ranges
+ if (count($this->spreadsheet->getDefinedNames()) > 0) {
+ // Named ranges
+ $this->writeNamedRangesAndFormulae();
+ }
+
+ // Other defined names
+ $sheetCount = $this->spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ // NamedRange for autoFilter
+ $this->writeNamedRangeForAutofilter($this->spreadsheet->getSheet($i), $i);
+
+ // NamedRange for Print_Titles
+ $this->writeNamedRangeForPrintTitles($this->spreadsheet->getSheet($i), $i);
+
+ // NamedRange for Print_Area
+ $this->writeNamedRangeForPrintArea($this->spreadsheet->getSheet($i), $i);
+ }
+
+ $this->objWriter->endElement();
+ }
+
+ /**
+ * Write defined names.
+ */
+ private function writeNamedRangesAndFormulae(): void
+ {
+ // Loop named ranges
+ $definedNames = $this->spreadsheet->getDefinedNames();
+ foreach ($definedNames as $definedName) {
+ $this->writeDefinedName($definedName);
+ }
+ }
+
+ /**
+ * Write Defined Name for named range.
+ */
+ private function writeDefinedName(DefinedName $pDefinedName): void
+ {
+ // definedName for named range
+ $this->objWriter->startElement('definedName');
+ $this->objWriter->writeAttribute('name', $pDefinedName->getName());
+ if ($pDefinedName->getLocalOnly() && $pDefinedName->getScope() !== null) {
+ $this->objWriter->writeAttribute('localSheetId', $pDefinedName->getScope()->getParent()->getIndex($pDefinedName->getScope()));
+ }
+
+ $definedRange = $pDefinedName->getValue();
+ $splitCount = preg_match_all(
+ '/' . Calculation::CALCULATION_REGEXP_CELLREF_RELATIVE . '/mui',
+ $definedRange,
+ $splitRanges,
+ PREG_OFFSET_CAPTURE
+ );
+
+ $lengths = array_map('strlen', array_column($splitRanges[0], 0));
+ $offsets = array_column($splitRanges[0], 1);
+
+ $worksheets = $splitRanges[2];
+ $columns = $splitRanges[6];
+ $rows = $splitRanges[7];
+
+ while ($splitCount > 0) {
+ --$splitCount;
+ $length = $lengths[$splitCount];
+ $offset = $offsets[$splitCount];
+ $worksheet = $worksheets[$splitCount][0];
+ $column = $columns[$splitCount][0];
+ $row = $rows[$splitCount][0];
+
+ $newRange = '';
+ if (empty($worksheet)) {
+ if (($offset === 0) || ($definedRange[$offset - 1] !== ':')) {
+ // We should have a worksheet
+ $worksheet = $pDefinedName->getWorksheet()->getTitle();
+ }
+ } else {
+ $worksheet = str_replace("''", "'", trim($worksheet, "'"));
+ }
+ if (!empty($worksheet)) {
+ $newRange = "'" . str_replace("'", "''", $worksheet) . "'!";
+ }
+
+ if (!empty($column)) {
+ $newRange .= $column;
+ }
+ if (!empty($row)) {
+ $newRange .= $row;
+ }
+
+ $definedRange = substr($definedRange, 0, $offset) . $newRange . substr($definedRange, $offset + $length);
+ }
+
+ if (substr($definedRange, 0, 1) === '=') {
+ $definedRange = substr($definedRange, 1);
+ }
+
+ $this->objWriter->writeRawData($definedRange);
+
+ $this->objWriter->endElement();
+ }
+
+ /**
+ * Write Defined Name for autoFilter.
+ */
+ private function writeNamedRangeForAutofilter(Worksheet $pSheet, int $pSheetId = 0): void
+ {
+ // NamedRange for autoFilter
+ $autoFilterRange = $pSheet->getAutoFilter()->getRange();
+ if (!empty($autoFilterRange)) {
+ $this->objWriter->startElement('definedName');
+ $this->objWriter->writeAttribute('name', '_xlnm._FilterDatabase');
+ $this->objWriter->writeAttribute('localSheetId', $pSheetId);
+ $this->objWriter->writeAttribute('hidden', '1');
+
+ // Create absolute coordinate and write as raw text
+ $range = Coordinate::splitRange($autoFilterRange);
+ $range = $range[0];
+ // Strip any worksheet ref so we can make the cell ref absolute
+ [$ws, $range[0]] = Worksheet::extractSheetTitle($range[0], true);
+
+ $range[0] = Coordinate::absoluteCoordinate($range[0]);
+ $range[1] = Coordinate::absoluteCoordinate($range[1]);
+ $range = implode(':', $range);
+
+ $this->objWriter->writeRawData('\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!' . $range);
+
+ $this->objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Defined Name for PrintTitles.
+ */
+ private function writeNamedRangeForPrintTitles(Worksheet $pSheet, int $pSheetId = 0): void
+ {
+ // NamedRange for PrintTitles
+ if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet() || $pSheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
+ $this->objWriter->startElement('definedName');
+ $this->objWriter->writeAttribute('name', '_xlnm.Print_Titles');
+ $this->objWriter->writeAttribute('localSheetId', $pSheetId);
+
+ // Setting string
+ $settingString = '';
+
+ // Columns to repeat
+ if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) {
+ $repeat = $pSheet->getPageSetup()->getColumnsToRepeatAtLeft();
+
+ $settingString .= '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1];
+ }
+
+ // Rows to repeat
+ if ($pSheet->getPageSetup()->isRowsToRepeatAtTopSet()) {
+ if ($pSheet->getPageSetup()->isColumnsToRepeatAtLeftSet()) {
+ $settingString .= ',';
+ }
+
+ $repeat = $pSheet->getPageSetup()->getRowsToRepeatAtTop();
+
+ $settingString .= '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!$' . $repeat[0] . ':$' . $repeat[1];
+ }
+
+ $this->objWriter->writeRawData($settingString);
+
+ $this->objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Defined Name for PrintTitles.
+ */
+ private function writeNamedRangeForPrintArea(Worksheet $pSheet, int $pSheetId = 0): void
+ {
+ // NamedRange for PrintArea
+ if ($pSheet->getPageSetup()->isPrintAreaSet()) {
+ $this->objWriter->startElement('definedName');
+ $this->objWriter->writeAttribute('name', '_xlnm.Print_Area');
+ $this->objWriter->writeAttribute('localSheetId', $pSheetId);
+
+ // Print area
+ $printArea = Coordinate::splitRange($pSheet->getPageSetup()->getPrintArea());
+
+ $chunks = [];
+ foreach ($printArea as $printAreaRect) {
+ $printAreaRect[0] = Coordinate::absoluteReference($printAreaRect[0]);
+ $printAreaRect[1] = Coordinate::absoluteReference($printAreaRect[1]);
+ $chunks[] = '\'' . str_replace("'", "''", $pSheet->getTitle()) . '\'!' . implode(':', $printAreaRect);
+ }
+
+ $this->objWriter->writeRawData(implode(',', $chunks));
+
+ $this->objWriter->endElement();
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php
new file mode 100644
index 0000000..7125b70
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/DocProps.php
@@ -0,0 +1,239 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Properties
+ $objWriter->startElement('Properties');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/extended-properties');
+ $objWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes');
+
+ // Application
+ $objWriter->writeElement('Application', 'Microsoft Excel');
+
+ // DocSecurity
+ $objWriter->writeElement('DocSecurity', '0');
+
+ // ScaleCrop
+ $objWriter->writeElement('ScaleCrop', 'false');
+
+ // HeadingPairs
+ $objWriter->startElement('HeadingPairs');
+
+ // Vector
+ $objWriter->startElement('vt:vector');
+ $objWriter->writeAttribute('size', '2');
+ $objWriter->writeAttribute('baseType', 'variant');
+
+ // Variant
+ $objWriter->startElement('vt:variant');
+ $objWriter->writeElement('vt:lpstr', 'Worksheets');
+ $objWriter->endElement();
+
+ // Variant
+ $objWriter->startElement('vt:variant');
+ $objWriter->writeElement('vt:i4', $spreadsheet->getSheetCount());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // TitlesOfParts
+ $objWriter->startElement('TitlesOfParts');
+
+ // Vector
+ $objWriter->startElement('vt:vector');
+ $objWriter->writeAttribute('size', $spreadsheet->getSheetCount());
+ $objWriter->writeAttribute('baseType', 'lpstr');
+
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $objWriter->writeElement('vt:lpstr', $spreadsheet->getSheet($i)->getTitle());
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Company
+ $objWriter->writeElement('Company', $spreadsheet->getProperties()->getCompany());
+
+ // Company
+ $objWriter->writeElement('Manager', $spreadsheet->getProperties()->getManager());
+
+ // LinksUpToDate
+ $objWriter->writeElement('LinksUpToDate', 'false');
+
+ // SharedDoc
+ $objWriter->writeElement('SharedDoc', 'false');
+
+ // HyperlinksChanged
+ $objWriter->writeElement('HyperlinksChanged', 'false');
+
+ // AppVersion
+ $objWriter->writeElement('AppVersion', '12.0000');
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write docProps/core.xml to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeDocPropsCore(Spreadsheet $spreadsheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // cp:coreProperties
+ $objWriter->startElement('cp:coreProperties');
+ $objWriter->writeAttribute('xmlns:cp', 'http://schemas.openxmlformats.org/package/2006/metadata/core-properties');
+ $objWriter->writeAttribute('xmlns:dc', 'http://purl.org/dc/elements/1.1/');
+ $objWriter->writeAttribute('xmlns:dcterms', 'http://purl.org/dc/terms/');
+ $objWriter->writeAttribute('xmlns:dcmitype', 'http://purl.org/dc/dcmitype/');
+ $objWriter->writeAttribute('xmlns:xsi', 'http://www.w3.org/2001/XMLSchema-instance');
+
+ // dc:creator
+ $objWriter->writeElement('dc:creator', $spreadsheet->getProperties()->getCreator());
+
+ // cp:lastModifiedBy
+ $objWriter->writeElement('cp:lastModifiedBy', $spreadsheet->getProperties()->getLastModifiedBy());
+
+ // dcterms:created
+ $objWriter->startElement('dcterms:created');
+ $objWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF');
+ $objWriter->writeRawData(date(DATE_W3C, $spreadsheet->getProperties()->getCreated()));
+ $objWriter->endElement();
+
+ // dcterms:modified
+ $objWriter->startElement('dcterms:modified');
+ $objWriter->writeAttribute('xsi:type', 'dcterms:W3CDTF');
+ $objWriter->writeRawData(date(DATE_W3C, $spreadsheet->getProperties()->getModified()));
+ $objWriter->endElement();
+
+ // dc:title
+ $objWriter->writeElement('dc:title', $spreadsheet->getProperties()->getTitle());
+
+ // dc:description
+ $objWriter->writeElement('dc:description', $spreadsheet->getProperties()->getDescription());
+
+ // dc:subject
+ $objWriter->writeElement('dc:subject', $spreadsheet->getProperties()->getSubject());
+
+ // cp:keywords
+ $objWriter->writeElement('cp:keywords', $spreadsheet->getProperties()->getKeywords());
+
+ // cp:category
+ $objWriter->writeElement('cp:category', $spreadsheet->getProperties()->getCategory());
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write docProps/custom.xml to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeDocPropsCustom(Spreadsheet $spreadsheet)
+ {
+ $customPropertyList = $spreadsheet->getProperties()->getCustomProperties();
+ if (empty($customPropertyList)) {
+ return;
+ }
+
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // cp:coreProperties
+ $objWriter->startElement('Properties');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/officeDocument/2006/custom-properties');
+ $objWriter->writeAttribute('xmlns:vt', 'http://schemas.openxmlformats.org/officeDocument/2006/docPropsVTypes');
+
+ foreach ($customPropertyList as $key => $customProperty) {
+ $propertyValue = $spreadsheet->getProperties()->getCustomPropertyValue($customProperty);
+ $propertyType = $spreadsheet->getProperties()->getCustomPropertyType($customProperty);
+
+ $objWriter->startElement('property');
+ $objWriter->writeAttribute('fmtid', '{D5CDD505-2E9C-101B-9397-08002B2CF9AE}');
+ $objWriter->writeAttribute('pid', $key + 2);
+ $objWriter->writeAttribute('name', $customProperty);
+
+ switch ($propertyType) {
+ case 'i':
+ $objWriter->writeElement('vt:i4', $propertyValue);
+
+ break;
+ case 'f':
+ $objWriter->writeElement('vt:r8', $propertyValue);
+
+ break;
+ case 'b':
+ $objWriter->writeElement('vt:bool', ($propertyValue) ? 'true' : 'false');
+
+ break;
+ case 'd':
+ $objWriter->startElement('vt:filetime');
+ $objWriter->writeRawData(date(DATE_W3C, $propertyValue));
+ $objWriter->endElement();
+
+ break;
+ default:
+ $objWriter->writeElement('vt:lpwstr', $propertyValue);
+
+ break;
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
new file mode 100644
index 0000000..3f989df
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Drawing.php
@@ -0,0 +1,505 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // xdr:wsDr
+ $objWriter->startElement('xdr:wsDr');
+ $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
+ $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
+
+ // Loop through images and write drawings
+ $i = 1;
+ $iterator = $pWorksheet->getDrawingCollection()->getIterator();
+ while ($iterator->valid()) {
+ /** @var BaseDrawing $pDrawing */
+ $pDrawing = $iterator->current();
+ $pRelationId = $i;
+ $hlinkClickId = $pDrawing->getHyperlink() === null ? null : ++$i;
+
+ $this->writeDrawing($objWriter, $pDrawing, $pRelationId, $hlinkClickId);
+
+ $iterator->next();
+ ++$i;
+ }
+
+ if ($includeCharts) {
+ $chartCount = $pWorksheet->getChartCount();
+ // Loop through charts and write the chart position
+ if ($chartCount > 0) {
+ for ($c = 0; $c < $chartCount; ++$c) {
+ $this->writeChart($objWriter, $pWorksheet->getChartByIndex($c), $c + $i);
+ }
+ }
+ }
+
+ // unparsed AlternateContent
+ $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
+ if (isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'])) {
+ foreach ($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingAlternateContents'] as $drawingAlternateContent) {
+ $objWriter->writeRaw($drawingAlternateContent);
+ }
+ }
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write drawings to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $pRelationId
+ */
+ public function writeChart(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Chart\Chart $pChart, $pRelationId = -1): void
+ {
+ $tl = $pChart->getTopLeftPosition();
+ $tl['colRow'] = Coordinate::coordinateFromString($tl['cell']);
+ $br = $pChart->getBottomRightPosition();
+ $br['colRow'] = Coordinate::coordinateFromString($br['cell']);
+
+ $objWriter->startElement('xdr:twoCellAnchor');
+
+ $objWriter->startElement('xdr:from');
+ $objWriter->writeElement('xdr:col', Coordinate::columnIndexFromString($tl['colRow'][0]) - 1);
+ $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['xOffset']));
+ $objWriter->writeElement('xdr:row', $tl['colRow'][1] - 1);
+ $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($tl['yOffset']));
+ $objWriter->endElement();
+ $objWriter->startElement('xdr:to');
+ $objWriter->writeElement('xdr:col', Coordinate::columnIndexFromString($br['colRow'][0]) - 1);
+ $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['xOffset']));
+ $objWriter->writeElement('xdr:row', $br['colRow'][1] - 1);
+ $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($br['yOffset']));
+ $objWriter->endElement();
+
+ $objWriter->startElement('xdr:graphicFrame');
+ $objWriter->writeAttribute('macro', '');
+ $objWriter->startElement('xdr:nvGraphicFramePr');
+ $objWriter->startElement('xdr:cNvPr');
+ $objWriter->writeAttribute('name', 'Chart ' . $pRelationId);
+ $objWriter->writeAttribute('id', 1025 * $pRelationId);
+ $objWriter->endElement();
+ $objWriter->startElement('xdr:cNvGraphicFramePr');
+ $objWriter->startElement('a:graphicFrameLocks');
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('xdr:xfrm');
+ $objWriter->startElement('a:off');
+ $objWriter->writeAttribute('x', '0');
+ $objWriter->writeAttribute('y', '0');
+ $objWriter->endElement();
+ $objWriter->startElement('a:ext');
+ $objWriter->writeAttribute('cx', '0');
+ $objWriter->writeAttribute('cy', '0');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('a:graphic');
+ $objWriter->startElement('a:graphicData');
+ $objWriter->writeAttribute('uri', 'http://schemas.openxmlformats.org/drawingml/2006/chart');
+ $objWriter->startElement('c:chart');
+ $objWriter->writeAttribute('xmlns:c', 'http://schemas.openxmlformats.org/drawingml/2006/chart');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+ $objWriter->writeAttribute('r:id', 'rId' . $pRelationId);
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ $objWriter->startElement('xdr:clientData');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write drawings to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $pRelationId
+ * @param null|int $hlinkClickId
+ */
+ public function writeDrawing(XMLWriter $objWriter, BaseDrawing $pDrawing, $pRelationId = -1, $hlinkClickId = null): void
+ {
+ if ($pRelationId >= 0) {
+ // xdr:oneCellAnchor
+ $objWriter->startElement('xdr:oneCellAnchor');
+ // Image location
+ $aCoordinates = Coordinate::coordinateFromString($pDrawing->getCoordinates());
+ $aCoordinates[0] = Coordinate::columnIndexFromString($aCoordinates[0]);
+
+ // xdr:from
+ $objWriter->startElement('xdr:from');
+ $objWriter->writeElement('xdr:col', $aCoordinates[0] - 1);
+ $objWriter->writeElement('xdr:colOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getOffsetX()));
+ $objWriter->writeElement('xdr:row', $aCoordinates[1] - 1);
+ $objWriter->writeElement('xdr:rowOff', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getOffsetY()));
+ $objWriter->endElement();
+
+ // xdr:ext
+ $objWriter->startElement('xdr:ext');
+ $objWriter->writeAttribute('cx', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getWidth()));
+ $objWriter->writeAttribute('cy', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getHeight()));
+ $objWriter->endElement();
+
+ // xdr:pic
+ $objWriter->startElement('xdr:pic');
+
+ // xdr:nvPicPr
+ $objWriter->startElement('xdr:nvPicPr');
+
+ // xdr:cNvPr
+ $objWriter->startElement('xdr:cNvPr');
+ $objWriter->writeAttribute('id', $pRelationId);
+ $objWriter->writeAttribute('name', $pDrawing->getName());
+ $objWriter->writeAttribute('descr', $pDrawing->getDescription());
+
+ //a:hlinkClick
+ $this->writeHyperLinkDrawing($objWriter, $hlinkClickId);
+
+ $objWriter->endElement();
+
+ // xdr:cNvPicPr
+ $objWriter->startElement('xdr:cNvPicPr');
+
+ // a:picLocks
+ $objWriter->startElement('a:picLocks');
+ $objWriter->writeAttribute('noChangeAspect', '1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // xdr:blipFill
+ $objWriter->startElement('xdr:blipFill');
+
+ // a:blip
+ $objWriter->startElement('a:blip');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+ $objWriter->writeAttribute('r:embed', 'rId' . $pRelationId);
+ $objWriter->endElement();
+
+ // a:stretch
+ $objWriter->startElement('a:stretch');
+ $objWriter->writeElement('a:fillRect', null);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // xdr:spPr
+ $objWriter->startElement('xdr:spPr');
+
+ // a:xfrm
+ $objWriter->startElement('a:xfrm');
+ $objWriter->writeAttribute('rot', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($pDrawing->getRotation()));
+ $objWriter->endElement();
+
+ // a:prstGeom
+ $objWriter->startElement('a:prstGeom');
+ $objWriter->writeAttribute('prst', 'rect');
+
+ // a:avLst
+ $objWriter->writeElement('a:avLst', null);
+
+ $objWriter->endElement();
+
+ if ($pDrawing->getShadow()->getVisible()) {
+ // a:effectLst
+ $objWriter->startElement('a:effectLst');
+
+ // a:outerShdw
+ $objWriter->startElement('a:outerShdw');
+ $objWriter->writeAttribute('blurRad', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getShadow()->getBlurRadius()));
+ $objWriter->writeAttribute('dist', \PhpOffice\PhpSpreadsheet\Shared\Drawing::pixelsToEMU($pDrawing->getShadow()->getDistance()));
+ $objWriter->writeAttribute('dir', \PhpOffice\PhpSpreadsheet\Shared\Drawing::degreesToAngle($pDrawing->getShadow()->getDirection()));
+ $objWriter->writeAttribute('algn', $pDrawing->getShadow()->getAlignment());
+ $objWriter->writeAttribute('rotWithShape', '0');
+
+ // a:srgbClr
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', $pDrawing->getShadow()->getColor()->getRGB());
+
+ // a:alpha
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', $pDrawing->getShadow()->getAlpha() * 1000);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // xdr:clientData
+ $objWriter->writeElement('xdr:clientData', null);
+
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+
+ /**
+ * Write VML header/footer images to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeVMLHeaderFooterImages(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Header/footer images
+ $images = $pWorksheet->getHeaderFooter()->getImages();
+
+ // xml
+ $objWriter->startElement('xml');
+ $objWriter->writeAttribute('xmlns:v', 'urn:schemas-microsoft-com:vml');
+ $objWriter->writeAttribute('xmlns:o', 'urn:schemas-microsoft-com:office:office');
+ $objWriter->writeAttribute('xmlns:x', 'urn:schemas-microsoft-com:office:excel');
+
+ // o:shapelayout
+ $objWriter->startElement('o:shapelayout');
+ $objWriter->writeAttribute('v:ext', 'edit');
+
+ // o:idmap
+ $objWriter->startElement('o:idmap');
+ $objWriter->writeAttribute('v:ext', 'edit');
+ $objWriter->writeAttribute('data', '1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // v:shapetype
+ $objWriter->startElement('v:shapetype');
+ $objWriter->writeAttribute('id', '_x0000_t75');
+ $objWriter->writeAttribute('coordsize', '21600,21600');
+ $objWriter->writeAttribute('o:spt', '75');
+ $objWriter->writeAttribute('o:preferrelative', 't');
+ $objWriter->writeAttribute('path', 'm@4@5l@4@11@9@11@9@5xe');
+ $objWriter->writeAttribute('filled', 'f');
+ $objWriter->writeAttribute('stroked', 'f');
+
+ // v:stroke
+ $objWriter->startElement('v:stroke');
+ $objWriter->writeAttribute('joinstyle', 'miter');
+ $objWriter->endElement();
+
+ // v:formulas
+ $objWriter->startElement('v:formulas');
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'if lineDrawn pixelLineWidth 0');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum @0 1 0');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum 0 0 @1');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @2 1 2');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @3 21600 pixelWidth');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @3 21600 pixelHeight');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum @0 0 1');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @6 1 2');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @7 21600 pixelWidth');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum @8 21600 0');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'prod @7 21600 pixelHeight');
+ $objWriter->endElement();
+
+ // v:f
+ $objWriter->startElement('v:f');
+ $objWriter->writeAttribute('eqn', 'sum @10 21600 0');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // v:path
+ $objWriter->startElement('v:path');
+ $objWriter->writeAttribute('o:extrusionok', 'f');
+ $objWriter->writeAttribute('gradientshapeok', 't');
+ $objWriter->writeAttribute('o:connecttype', 'rect');
+ $objWriter->endElement();
+
+ // o:lock
+ $objWriter->startElement('o:lock');
+ $objWriter->writeAttribute('v:ext', 'edit');
+ $objWriter->writeAttribute('aspectratio', 't');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Loop through images
+ foreach ($images as $key => $value) {
+ $this->writeVMLHeaderFooterImage($objWriter, $key, $value);
+ }
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write VML comment to XML format.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pReference Reference
+ * @param HeaderFooterDrawing $pImage Image
+ */
+ private function writeVMLHeaderFooterImage(XMLWriter $objWriter, $pReference, HeaderFooterDrawing $pImage): void
+ {
+ // Calculate object id
+ preg_match('{(\d+)}', md5($pReference), $m);
+ $id = 1500 + (substr($m[1], 0, 2) * 1);
+
+ // Calculate offset
+ $width = $pImage->getWidth();
+ $height = $pImage->getHeight();
+ $marginLeft = $pImage->getOffsetX();
+ $marginTop = $pImage->getOffsetY();
+
+ // v:shape
+ $objWriter->startElement('v:shape');
+ $objWriter->writeAttribute('id', $pReference);
+ $objWriter->writeAttribute('o:spid', '_x0000_s' . $id);
+ $objWriter->writeAttribute('type', '#_x0000_t75');
+ $objWriter->writeAttribute('style', "position:absolute;margin-left:{$marginLeft}px;margin-top:{$marginTop}px;width:{$width}px;height:{$height}px;z-index:1");
+
+ // v:imagedata
+ $objWriter->startElement('v:imagedata');
+ $objWriter->writeAttribute('o:relid', 'rId' . $pReference);
+ $objWriter->writeAttribute('o:title', $pImage->getName());
+ $objWriter->endElement();
+
+ // o:lock
+ $objWriter->startElement('o:lock');
+ $objWriter->writeAttribute('v:ext', 'edit');
+ $objWriter->writeAttribute('textRotation', 't');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Get an array of all drawings.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Worksheet\Drawing[] All drawings in PhpSpreadsheet
+ */
+ public function allDrawings(Spreadsheet $spreadsheet)
+ {
+ // Get an array of all drawings
+ $aDrawings = [];
+
+ // Loop through PhpSpreadsheet
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ // Loop through images and add to array
+ $iterator = $spreadsheet->getSheet($i)->getDrawingCollection()->getIterator();
+ while ($iterator->valid()) {
+ $aDrawings[] = $iterator->current();
+
+ $iterator->next();
+ }
+ }
+
+ return $aDrawings;
+ }
+
+ /**
+ * @param null|int $hlinkClickId
+ */
+ private function writeHyperLinkDrawing(XMLWriter $objWriter, $hlinkClickId): void
+ {
+ if ($hlinkClickId === null) {
+ return;
+ }
+
+ $objWriter->startElement('a:hlinkClick');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+ $objWriter->writeAttribute('r:id', 'rId' . $hlinkClickId);
+ $objWriter->endElement();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php
new file mode 100644
index 0000000..3d14f9a
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Rels.php
@@ -0,0 +1,451 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ $customPropertyList = $spreadsheet->getProperties()->getCustomProperties();
+ if (!empty($customPropertyList)) {
+ // Relationship docProps/app.xml
+ $this->writeRelationship(
+ $objWriter,
+ 4,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties',
+ 'docProps/custom.xml'
+ );
+ }
+
+ // Relationship docProps/app.xml
+ $this->writeRelationship(
+ $objWriter,
+ 3,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties',
+ 'docProps/app.xml'
+ );
+
+ // Relationship docProps/core.xml
+ $this->writeRelationship(
+ $objWriter,
+ 2,
+ 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties',
+ 'docProps/core.xml'
+ );
+
+ // Relationship xl/workbook.xml
+ $this->writeRelationship(
+ $objWriter,
+ 1,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument',
+ 'xl/workbook.xml'
+ );
+ // a custom UI in workbook ?
+ if ($spreadsheet->hasRibbon()) {
+ $this->writeRelationShip(
+ $objWriter,
+ 5,
+ 'http://schemas.microsoft.com/office/2006/relationships/ui/extensibility',
+ $spreadsheet->getRibbonXMLData('target')
+ );
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write workbook relationships to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeWorkbookRelationships(Spreadsheet $spreadsheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ // Relationship styles.xml
+ $this->writeRelationship(
+ $objWriter,
+ 1,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles',
+ 'styles.xml'
+ );
+
+ // Relationship theme/theme1.xml
+ $this->writeRelationship(
+ $objWriter,
+ 2,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/theme',
+ 'theme/theme1.xml'
+ );
+
+ // Relationship sharedStrings.xml
+ $this->writeRelationship(
+ $objWriter,
+ 3,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/sharedStrings',
+ 'sharedStrings.xml'
+ );
+
+ // Relationships with sheets
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ $this->writeRelationship(
+ $objWriter,
+ ($i + 1 + 3),
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet',
+ 'worksheets/sheet' . ($i + 1) . '.xml'
+ );
+ }
+ // Relationships for vbaProject if needed
+ // id : just after the last sheet
+ if ($spreadsheet->hasMacros()) {
+ $this->writeRelationShip(
+ $objWriter,
+ ($i + 1 + 3),
+ 'http://schemas.microsoft.com/office/2006/relationships/vbaProject',
+ 'vbaProject.bin'
+ );
+ ++$i; //increment i if needed for an another relation
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write worksheet relationships to XML format.
+ *
+ * Numbering is as follows:
+ * rId1 - Drawings
+ * rId_hyperlink_x - Hyperlinks
+ *
+ * @param int $pWorksheetId
+ * @param bool $includeCharts Flag indicating if we should write charts
+ *
+ * @return string XML Output
+ */
+ public function writeWorksheetRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, $pWorksheetId = 1, $includeCharts = false)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ // Write drawing relationships?
+ $drawingOriginalIds = [];
+ $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
+ if (isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'])) {
+ $drawingOriginalIds = $unparsedLoadedData['sheets'][$pWorksheet->getCodeName()]['drawingOriginalIds'];
+ }
+
+ if ($includeCharts) {
+ $charts = $pWorksheet->getChartCollection();
+ } else {
+ $charts = [];
+ }
+
+ if (($pWorksheet->getDrawingCollection()->count() > 0) || (count($charts) > 0) || $drawingOriginalIds) {
+ $rId = 1;
+
+ // Use original $relPath to get original $rId.
+ // Take first. In future can be overwritten.
+ // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Worksheet::writeDrawings)
+ reset($drawingOriginalIds);
+ $relPath = key($drawingOriginalIds);
+ if (isset($drawingOriginalIds[$relPath])) {
+ $rId = (int) (substr($drawingOriginalIds[$relPath], 3));
+ }
+
+ // Generate new $relPath to write drawing relationship
+ $relPath = '../drawings/drawing' . $pWorksheetId . '.xml';
+ $this->writeRelationship(
+ $objWriter,
+ $rId,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/drawing',
+ $relPath
+ );
+ }
+
+ // Write hyperlink relationships?
+ $i = 1;
+ foreach ($pWorksheet->getHyperlinkCollection() as $hyperlink) {
+ if (!$hyperlink->isInternal()) {
+ $this->writeRelationship(
+ $objWriter,
+ '_hyperlink_' . $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink',
+ $hyperlink->getUrl(),
+ 'External'
+ );
+
+ ++$i;
+ }
+ }
+
+ // Write comments relationship?
+ $i = 1;
+ if (count($pWorksheet->getComments()) > 0) {
+ $this->writeRelationship(
+ $objWriter,
+ '_comments_vml' . $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing',
+ '../drawings/vmlDrawing' . $pWorksheetId . '.vml'
+ );
+
+ $this->writeRelationship(
+ $objWriter,
+ '_comments' . $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments',
+ '../comments' . $pWorksheetId . '.xml'
+ );
+ }
+
+ // Write header/footer relationship?
+ $i = 1;
+ if (count($pWorksheet->getHeaderFooter()->getImages()) > 0) {
+ $this->writeRelationship(
+ $objWriter,
+ '_headerfooter_vml' . $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing',
+ '../drawings/vmlDrawingHF' . $pWorksheetId . '.vml'
+ );
+ }
+
+ $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'ctrlProps', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/ctrlProp');
+ $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'vmlDrawings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/vmlDrawing');
+ $this->writeUnparsedRelationship($pWorksheet, $objWriter, 'printerSettings', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/printerSettings');
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ private function writeUnparsedRelationship(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, XMLWriter $objWriter, $relationship, $type): void
+ {
+ $unparsedLoadedData = $pWorksheet->getParent()->getUnparsedLoadedData();
+ if (!isset($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()][$relationship])) {
+ return;
+ }
+
+ foreach ($unparsedLoadedData['sheets'][$pWorksheet->getCodeName()][$relationship] as $rId => $value) {
+ $this->writeRelationship(
+ $objWriter,
+ $rId,
+ $type,
+ $value['relFilePath']
+ );
+ }
+ }
+
+ /**
+ * Write drawing relationships to XML format.
+ *
+ * @param int &$chartRef Chart ID
+ * @param bool $includeCharts Flag indicating if we should write charts
+ *
+ * @return string XML Output
+ */
+ public function writeDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet, &$chartRef, $includeCharts = false)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ // Loop through images and write relationships
+ $i = 1;
+ $iterator = $pWorksheet->getDrawingCollection()->getIterator();
+ while ($iterator->valid()) {
+ if (
+ $iterator->current() instanceof \PhpOffice\PhpSpreadsheet\Worksheet\Drawing
+ || $iterator->current() instanceof MemoryDrawing
+ ) {
+ // Write relationship for image drawing
+ /** @var \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing */
+ $drawing = $iterator->current();
+ $this->writeRelationship(
+ $objWriter,
+ $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
+ '../media/' . str_replace(' ', '', $drawing->getIndexedFilename())
+ );
+
+ $i = $this->writeDrawingHyperLink($objWriter, $drawing, $i);
+ }
+
+ $iterator->next();
+ ++$i;
+ }
+
+ if ($includeCharts) {
+ // Loop through charts and write relationships
+ $chartCount = $pWorksheet->getChartCount();
+ if ($chartCount > 0) {
+ for ($c = 0; $c < $chartCount; ++$c) {
+ $this->writeRelationship(
+ $objWriter,
+ $i++,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart',
+ '../charts/chart' . ++$chartRef . '.xml'
+ );
+ }
+ }
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write header/footer drawing relationships to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeHeaderFooterDrawingRelationships(\PhpOffice\PhpSpreadsheet\Worksheet\Worksheet $pWorksheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+
+ // Loop through images and write relationships
+ foreach ($pWorksheet->getHeaderFooter()->getImages() as $key => $value) {
+ // Write relationship for image drawing
+ $this->writeRelationship(
+ $objWriter,
+ $key,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image',
+ '../media/' . $value->getIndexedFilename()
+ );
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write Override content type.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param int $pId Relationship ID. rId will be prepended!
+ * @param string $pType Relationship type
+ * @param string $pTarget Relationship target
+ * @param string $pTargetMode Relationship target mode
+ */
+ private function writeRelationship(XMLWriter $objWriter, $pId, $pType, $pTarget, $pTargetMode = ''): void
+ {
+ if ($pType != '' && $pTarget != '') {
+ // Write relationship
+ $objWriter->startElement('Relationship');
+ $objWriter->writeAttribute('Id', 'rId' . $pId);
+ $objWriter->writeAttribute('Type', $pType);
+ $objWriter->writeAttribute('Target', $pTarget);
+
+ if ($pTargetMode != '') {
+ $objWriter->writeAttribute('TargetMode', $pTargetMode);
+ }
+
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+
+ /**
+ * @param $objWriter
+ * @param \PhpOffice\PhpSpreadsheet\Worksheet\Drawing $drawing
+ * @param $i
+ *
+ * @return int
+ */
+ private function writeDrawingHyperLink($objWriter, $drawing, $i)
+ {
+ if ($drawing->getHyperlink() === null) {
+ return $i;
+ }
+
+ ++$i;
+ $this->writeRelationship(
+ $objWriter,
+ $i,
+ 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink',
+ $drawing->getHyperlink()->getUrl(),
+ $drawing->getHyperlink()->getTypeHyperlink()
+ );
+
+ return $i;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php
new file mode 100644
index 0000000..66d9d77
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsRibbon.php
@@ -0,0 +1,45 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+ $localRels = $spreadsheet->getRibbonBinObjects('names');
+ if (is_array($localRels)) {
+ foreach ($localRels as $aId => $aTarget) {
+ $objWriter->startElement('Relationship');
+ $objWriter->writeAttribute('Id', $aId);
+ $objWriter->writeAttribute('Type', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/image');
+ $objWriter->writeAttribute('Target', $aTarget);
+ $objWriter->endElement();
+ }
+ }
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php
new file mode 100644
index 0000000..4ffb558
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/RelsVBA.php
@@ -0,0 +1,40 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Relationships
+ $objWriter->startElement('Relationships');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/package/2006/relationships');
+ $objWriter->startElement('Relationship');
+ $objWriter->writeAttribute('Id', 'rId1');
+ $objWriter->writeAttribute('Type', 'http://schemas.microsoft.com/office/2006/relationships/vbaProjectSignature');
+ $objWriter->writeAttribute('Target', 'vbaProjectSignature.bin');
+ $objWriter->endElement();
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
new file mode 100644
index 0000000..310e073
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/StringTable.php
@@ -0,0 +1,282 @@
+flipStringTable($aStringTable);
+
+ // Loop through cells
+ foreach ($pSheet->getCoordinates() as $coordinate) {
+ $cell = $pSheet->getCell($coordinate);
+ $cellValue = $cell->getValue();
+ if (
+ !is_object($cellValue) &&
+ ($cellValue !== null) &&
+ $cellValue !== '' &&
+ !isset($aFlippedStringTable[$cellValue]) &&
+ ($cell->getDataType() == DataType::TYPE_STRING || $cell->getDataType() == DataType::TYPE_STRING2 || $cell->getDataType() == DataType::TYPE_NULL)
+ ) {
+ $aStringTable[] = $cellValue;
+ $aFlippedStringTable[$cellValue] = true;
+ } elseif (
+ $cellValue instanceof RichText &&
+ ($cellValue !== null) &&
+ !isset($aFlippedStringTable[$cellValue->getHashCode()])
+ ) {
+ $aStringTable[] = $cellValue;
+ $aFlippedStringTable[$cellValue->getHashCode()] = true;
+ }
+ }
+
+ return $aStringTable;
+ }
+
+ /**
+ * Write string table to XML format.
+ *
+ * @param string[] $pStringTable
+ *
+ * @return string XML Output
+ */
+ public function writeStringTable(array $pStringTable)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // String table
+ $objWriter->startElement('sst');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+ $objWriter->writeAttribute('uniqueCount', count($pStringTable));
+
+ // Loop through string table
+ foreach ($pStringTable as $textElement) {
+ $objWriter->startElement('si');
+
+ if (!$textElement instanceof RichText) {
+ $textToWrite = StringHelper::controlCharacterPHP2OOXML($textElement);
+ $objWriter->startElement('t');
+ if ($textToWrite !== trim($textToWrite)) {
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ }
+ $objWriter->writeRawData($textToWrite);
+ $objWriter->endElement();
+ } elseif ($textElement instanceof RichText) {
+ $this->writeRichText($objWriter, $textElement);
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write Rich Text.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param RichText $pRichText Rich text
+ * @param string $prefix Optional Namespace prefix
+ */
+ public function writeRichText(XMLWriter $objWriter, RichText $pRichText, $prefix = null): void
+ {
+ if ($prefix !== null) {
+ $prefix .= ':';
+ }
+
+ // Loop through rich text elements
+ $elements = $pRichText->getRichTextElements();
+ foreach ($elements as $element) {
+ // r
+ $objWriter->startElement($prefix . 'r');
+
+ // rPr
+ if ($element instanceof Run) {
+ // rPr
+ $objWriter->startElement($prefix . 'rPr');
+
+ // rFont
+ $objWriter->startElement($prefix . 'rFont');
+ $objWriter->writeAttribute('val', $element->getFont()->getName());
+ $objWriter->endElement();
+
+ // Bold
+ $objWriter->startElement($prefix . 'b');
+ $objWriter->writeAttribute('val', ($element->getFont()->getBold() ? 'true' : 'false'));
+ $objWriter->endElement();
+
+ // Italic
+ $objWriter->startElement($prefix . 'i');
+ $objWriter->writeAttribute('val', ($element->getFont()->getItalic() ? 'true' : 'false'));
+ $objWriter->endElement();
+
+ // Superscript / subscript
+ if ($element->getFont()->getSuperscript() || $element->getFont()->getSubscript()) {
+ $objWriter->startElement($prefix . 'vertAlign');
+ if ($element->getFont()->getSuperscript()) {
+ $objWriter->writeAttribute('val', 'superscript');
+ } elseif ($element->getFont()->getSubscript()) {
+ $objWriter->writeAttribute('val', 'subscript');
+ }
+ $objWriter->endElement();
+ }
+
+ // Strikethrough
+ $objWriter->startElement($prefix . 'strike');
+ $objWriter->writeAttribute('val', ($element->getFont()->getStrikethrough() ? 'true' : 'false'));
+ $objWriter->endElement();
+
+ // Color
+ $objWriter->startElement($prefix . 'color');
+ $objWriter->writeAttribute('rgb', $element->getFont()->getColor()->getARGB());
+ $objWriter->endElement();
+
+ // Size
+ $objWriter->startElement($prefix . 'sz');
+ $objWriter->writeAttribute('val', $element->getFont()->getSize());
+ $objWriter->endElement();
+
+ // Underline
+ $objWriter->startElement($prefix . 'u');
+ $objWriter->writeAttribute('val', $element->getFont()->getUnderline());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ // t
+ $objWriter->startElement($prefix . 't');
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($element->getText()));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Rich Text.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param RichText|string $pRichText text string or Rich text
+ * @param string $prefix Optional Namespace prefix
+ */
+ public function writeRichTextForCharts(XMLWriter $objWriter, $pRichText = null, $prefix = null): void
+ {
+ if (!$pRichText instanceof RichText) {
+ $textRun = $pRichText;
+ $pRichText = new RichText();
+ $pRichText->createTextRun($textRun);
+ }
+
+ if ($prefix !== null) {
+ $prefix .= ':';
+ }
+
+ // Loop through rich text elements
+ $elements = $pRichText->getRichTextElements();
+ foreach ($elements as $element) {
+ // r
+ $objWriter->startElement($prefix . 'r');
+
+ // rPr
+ $objWriter->startElement($prefix . 'rPr');
+
+ // Bold
+ $objWriter->writeAttribute('b', ($element->getFont()->getBold() ? 1 : 0));
+ // Italic
+ $objWriter->writeAttribute('i', ($element->getFont()->getItalic() ? 1 : 0));
+ // Underline
+ $underlineType = $element->getFont()->getUnderline();
+ switch ($underlineType) {
+ case 'single':
+ $underlineType = 'sng';
+
+ break;
+ case 'double':
+ $underlineType = 'dbl';
+
+ break;
+ }
+ $objWriter->writeAttribute('u', $underlineType);
+ // Strikethrough
+ $objWriter->writeAttribute('strike', ($element->getFont()->getStrikethrough() ? 'sngStrike' : 'noStrike'));
+
+ // rFont
+ $objWriter->startElement($prefix . 'latin');
+ $objWriter->writeAttribute('typeface', $element->getFont()->getName());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // t
+ $objWriter->startElement($prefix . 't');
+ $objWriter->writeRawData(StringHelper::controlCharacterPHP2OOXML($element->getText()));
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Flip string table (for index searching).
+ *
+ * @param array $stringTable Stringtable
+ *
+ * @return array
+ */
+ public function flipStringTable(array $stringTable)
+ {
+ // Return value
+ $returnValue = [];
+
+ // Loop through stringtable and add flipped items to $returnValue
+ foreach ($stringTable as $key => $value) {
+ if (!$value instanceof RichText) {
+ $returnValue[$value] = $key;
+ } elseif ($value instanceof RichText) {
+ $returnValue[$value->getHashCode()] = $key;
+ }
+ }
+
+ return $returnValue;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php
new file mode 100644
index 0000000..69d20a9
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Style.php
@@ -0,0 +1,676 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // styleSheet
+ $objWriter->startElement('styleSheet');
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+
+ // numFmts
+ $objWriter->startElement('numFmts');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getNumFmtHashTable()->count());
+
+ // numFmt
+ for ($i = 0; $i < $this->getParentWriter()->getNumFmtHashTable()->count(); ++$i) {
+ $this->writeNumFmt($objWriter, $this->getParentWriter()->getNumFmtHashTable()->getByIndex($i), $i);
+ }
+
+ $objWriter->endElement();
+
+ // fonts
+ $objWriter->startElement('fonts');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getFontHashTable()->count());
+
+ // font
+ for ($i = 0; $i < $this->getParentWriter()->getFontHashTable()->count(); ++$i) {
+ $this->writeFont($objWriter, $this->getParentWriter()->getFontHashTable()->getByIndex($i));
+ }
+
+ $objWriter->endElement();
+
+ // fills
+ $objWriter->startElement('fills');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getFillHashTable()->count());
+
+ // fill
+ for ($i = 0; $i < $this->getParentWriter()->getFillHashTable()->count(); ++$i) {
+ $this->writeFill($objWriter, $this->getParentWriter()->getFillHashTable()->getByIndex($i));
+ }
+
+ $objWriter->endElement();
+
+ // borders
+ $objWriter->startElement('borders');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getBordersHashTable()->count());
+
+ // border
+ for ($i = 0; $i < $this->getParentWriter()->getBordersHashTable()->count(); ++$i) {
+ $this->writeBorder($objWriter, $this->getParentWriter()->getBordersHashTable()->getByIndex($i));
+ }
+
+ $objWriter->endElement();
+
+ // cellStyleXfs
+ $objWriter->startElement('cellStyleXfs');
+ $objWriter->writeAttribute('count', 1);
+
+ // xf
+ $objWriter->startElement('xf');
+ $objWriter->writeAttribute('numFmtId', 0);
+ $objWriter->writeAttribute('fontId', 0);
+ $objWriter->writeAttribute('fillId', 0);
+ $objWriter->writeAttribute('borderId', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // cellXfs
+ $objWriter->startElement('cellXfs');
+ $objWriter->writeAttribute('count', count($spreadsheet->getCellXfCollection()));
+
+ // xf
+ foreach ($spreadsheet->getCellXfCollection() as $cellXf) {
+ $this->writeCellStyleXf($objWriter, $cellXf, $spreadsheet);
+ }
+
+ $objWriter->endElement();
+
+ // cellStyles
+ $objWriter->startElement('cellStyles');
+ $objWriter->writeAttribute('count', 1);
+
+ // cellStyle
+ $objWriter->startElement('cellStyle');
+ $objWriter->writeAttribute('name', 'Normal');
+ $objWriter->writeAttribute('xfId', 0);
+ $objWriter->writeAttribute('builtinId', 0);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // dxfs
+ $objWriter->startElement('dxfs');
+ $objWriter->writeAttribute('count', $this->getParentWriter()->getStylesConditionalHashTable()->count());
+
+ // dxf
+ for ($i = 0; $i < $this->getParentWriter()->getStylesConditionalHashTable()->count(); ++$i) {
+ $this->writeCellStyleDxf($objWriter, $this->getParentWriter()->getStylesConditionalHashTable()->getByIndex($i)->getStyle());
+ }
+
+ $objWriter->endElement();
+
+ // tableStyles
+ $objWriter->startElement('tableStyles');
+ $objWriter->writeAttribute('defaultTableStyle', 'TableStyleMedium9');
+ $objWriter->writeAttribute('defaultPivotStyle', 'PivotTableStyle1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write Fill.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Fill $pFill Fill style
+ */
+ private function writeFill(XMLWriter $objWriter, Fill $pFill): void
+ {
+ // Check if this is a pattern type or gradient type
+ if (
+ $pFill->getFillType() === Fill::FILL_GRADIENT_LINEAR ||
+ $pFill->getFillType() === Fill::FILL_GRADIENT_PATH
+ ) {
+ // Gradient fill
+ $this->writeGradientFill($objWriter, $pFill);
+ } elseif ($pFill->getFillType() !== null) {
+ // Pattern fill
+ $this->writePatternFill($objWriter, $pFill);
+ }
+ }
+
+ /**
+ * Write Gradient Fill.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Fill $pFill Fill style
+ */
+ private function writeGradientFill(XMLWriter $objWriter, Fill $pFill): void
+ {
+ // fill
+ $objWriter->startElement('fill');
+
+ // gradientFill
+ $objWriter->startElement('gradientFill');
+ $objWriter->writeAttribute('type', $pFill->getFillType());
+ $objWriter->writeAttribute('degree', $pFill->getRotation());
+
+ // stop
+ $objWriter->startElement('stop');
+ $objWriter->writeAttribute('position', '0');
+
+ // color
+ $objWriter->startElement('color');
+ $objWriter->writeAttribute('rgb', $pFill->getStartColor()->getARGB());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // stop
+ $objWriter->startElement('stop');
+ $objWriter->writeAttribute('position', '1');
+
+ // color
+ $objWriter->startElement('color');
+ $objWriter->writeAttribute('rgb', $pFill->getEndColor()->getARGB());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Pattern Fill.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Fill $pFill Fill style
+ */
+ private function writePatternFill(XMLWriter $objWriter, Fill $pFill): void
+ {
+ // fill
+ $objWriter->startElement('fill');
+
+ // patternFill
+ $objWriter->startElement('patternFill');
+ $objWriter->writeAttribute('patternType', $pFill->getFillType());
+
+ if ($pFill->getFillType() !== Fill::FILL_NONE) {
+ // fgColor
+ if ($pFill->getStartColor()->getARGB()) {
+ $objWriter->startElement('fgColor');
+ $objWriter->writeAttribute('rgb', $pFill->getStartColor()->getARGB());
+ $objWriter->endElement();
+ }
+ }
+ if ($pFill->getFillType() !== Fill::FILL_NONE) {
+ // bgColor
+ if ($pFill->getEndColor()->getARGB()) {
+ $objWriter->startElement('bgColor');
+ $objWriter->writeAttribute('rgb', $pFill->getEndColor()->getARGB());
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Font.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Font $pFont Font style
+ */
+ private function writeFont(XMLWriter $objWriter, Font $pFont): void
+ {
+ // font
+ $objWriter->startElement('font');
+ // Weird! The order of these elements actually makes a difference when opening Xlsx
+ // files in Excel2003 with the compatibility pack. It's not documented behaviour,
+ // and makes for a real WTF!
+
+ // Bold. We explicitly write this element also when false (like MS Office Excel 2007 does
+ // for conditional formatting). Otherwise it will apparently not be picked up in conditional
+ // formatting style dialog
+ if ($pFont->getBold() !== null) {
+ $objWriter->startElement('b');
+ $objWriter->writeAttribute('val', $pFont->getBold() ? '1' : '0');
+ $objWriter->endElement();
+ }
+
+ // Italic
+ if ($pFont->getItalic() !== null) {
+ $objWriter->startElement('i');
+ $objWriter->writeAttribute('val', $pFont->getItalic() ? '1' : '0');
+ $objWriter->endElement();
+ }
+
+ // Strikethrough
+ if ($pFont->getStrikethrough() !== null) {
+ $objWriter->startElement('strike');
+ $objWriter->writeAttribute('val', $pFont->getStrikethrough() ? '1' : '0');
+ $objWriter->endElement();
+ }
+
+ // Underline
+ if ($pFont->getUnderline() !== null) {
+ $objWriter->startElement('u');
+ $objWriter->writeAttribute('val', $pFont->getUnderline());
+ $objWriter->endElement();
+ }
+
+ // Superscript / subscript
+ if ($pFont->getSuperscript() === true || $pFont->getSubscript() === true) {
+ $objWriter->startElement('vertAlign');
+ if ($pFont->getSuperscript() === true) {
+ $objWriter->writeAttribute('val', 'superscript');
+ } elseif ($pFont->getSubscript() === true) {
+ $objWriter->writeAttribute('val', 'subscript');
+ }
+ $objWriter->endElement();
+ }
+
+ // Size
+ if ($pFont->getSize() !== null) {
+ $objWriter->startElement('sz');
+ $objWriter->writeAttribute('val', StringHelper::formatNumber($pFont->getSize()));
+ $objWriter->endElement();
+ }
+
+ // Foreground color
+ if ($pFont->getColor()->getARGB() !== null) {
+ $objWriter->startElement('color');
+ $objWriter->writeAttribute('rgb', $pFont->getColor()->getARGB());
+ $objWriter->endElement();
+ }
+
+ // Name
+ if ($pFont->getName() !== null) {
+ $objWriter->startElement('name');
+ $objWriter->writeAttribute('val', $pFont->getName());
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Border.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param Borders $pBorders Borders style
+ */
+ private function writeBorder(XMLWriter $objWriter, Borders $pBorders): void
+ {
+ // Write border
+ $objWriter->startElement('border');
+ // Diagonal?
+ switch ($pBorders->getDiagonalDirection()) {
+ case Borders::DIAGONAL_UP:
+ $objWriter->writeAttribute('diagonalUp', 'true');
+ $objWriter->writeAttribute('diagonalDown', 'false');
+
+ break;
+ case Borders::DIAGONAL_DOWN:
+ $objWriter->writeAttribute('diagonalUp', 'false');
+ $objWriter->writeAttribute('diagonalDown', 'true');
+
+ break;
+ case Borders::DIAGONAL_BOTH:
+ $objWriter->writeAttribute('diagonalUp', 'true');
+ $objWriter->writeAttribute('diagonalDown', 'true');
+
+ break;
+ }
+
+ // BorderPr
+ $this->writeBorderPr($objWriter, 'left', $pBorders->getLeft());
+ $this->writeBorderPr($objWriter, 'right', $pBorders->getRight());
+ $this->writeBorderPr($objWriter, 'top', $pBorders->getTop());
+ $this->writeBorderPr($objWriter, 'bottom', $pBorders->getBottom());
+ $this->writeBorderPr($objWriter, 'diagonal', $pBorders->getDiagonal());
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Cell Style Xf.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param \PhpOffice\PhpSpreadsheet\Style\Style $pStyle Style
+ * @param Spreadsheet $spreadsheet Workbook
+ */
+ private function writeCellStyleXf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $pStyle, Spreadsheet $spreadsheet): void
+ {
+ // xf
+ $objWriter->startElement('xf');
+ $objWriter->writeAttribute('xfId', 0);
+ $objWriter->writeAttribute('fontId', (int) $this->getParentWriter()->getFontHashTable()->getIndexForHashCode($pStyle->getFont()->getHashCode()));
+ if ($pStyle->getQuotePrefix()) {
+ $objWriter->writeAttribute('quotePrefix', 1);
+ }
+
+ if ($pStyle->getNumberFormat()->getBuiltInFormatCode() === false) {
+ $objWriter->writeAttribute('numFmtId', (int) ($this->getParentWriter()->getNumFmtHashTable()->getIndexForHashCode($pStyle->getNumberFormat()->getHashCode()) + 164));
+ } else {
+ $objWriter->writeAttribute('numFmtId', (int) $pStyle->getNumberFormat()->getBuiltInFormatCode());
+ }
+
+ $objWriter->writeAttribute('fillId', (int) $this->getParentWriter()->getFillHashTable()->getIndexForHashCode($pStyle->getFill()->getHashCode()));
+ $objWriter->writeAttribute('borderId', (int) $this->getParentWriter()->getBordersHashTable()->getIndexForHashCode($pStyle->getBorders()->getHashCode()));
+
+ // Apply styles?
+ $objWriter->writeAttribute('applyFont', ($spreadsheet->getDefaultStyle()->getFont()->getHashCode() != $pStyle->getFont()->getHashCode()) ? '1' : '0');
+ $objWriter->writeAttribute('applyNumberFormat', ($spreadsheet->getDefaultStyle()->getNumberFormat()->getHashCode() != $pStyle->getNumberFormat()->getHashCode()) ? '1' : '0');
+ $objWriter->writeAttribute('applyFill', ($spreadsheet->getDefaultStyle()->getFill()->getHashCode() != $pStyle->getFill()->getHashCode()) ? '1' : '0');
+ $objWriter->writeAttribute('applyBorder', ($spreadsheet->getDefaultStyle()->getBorders()->getHashCode() != $pStyle->getBorders()->getHashCode()) ? '1' : '0');
+ $objWriter->writeAttribute('applyAlignment', ($spreadsheet->getDefaultStyle()->getAlignment()->getHashCode() != $pStyle->getAlignment()->getHashCode()) ? '1' : '0');
+ if ($pStyle->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $pStyle->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) {
+ $objWriter->writeAttribute('applyProtection', 'true');
+ }
+
+ // alignment
+ $objWriter->startElement('alignment');
+ $objWriter->writeAttribute('horizontal', $pStyle->getAlignment()->getHorizontal());
+ $objWriter->writeAttribute('vertical', $pStyle->getAlignment()->getVertical());
+
+ $textRotation = 0;
+ if ($pStyle->getAlignment()->getTextRotation() >= 0) {
+ $textRotation = $pStyle->getAlignment()->getTextRotation();
+ } elseif ($pStyle->getAlignment()->getTextRotation() < 0) {
+ $textRotation = 90 - $pStyle->getAlignment()->getTextRotation();
+ }
+ $objWriter->writeAttribute('textRotation', $textRotation);
+
+ $objWriter->writeAttribute('wrapText', ($pStyle->getAlignment()->getWrapText() ? 'true' : 'false'));
+ $objWriter->writeAttribute('shrinkToFit', ($pStyle->getAlignment()->getShrinkToFit() ? 'true' : 'false'));
+
+ if ($pStyle->getAlignment()->getIndent() > 0) {
+ $objWriter->writeAttribute('indent', $pStyle->getAlignment()->getIndent());
+ }
+ if ($pStyle->getAlignment()->getReadOrder() > 0) {
+ $objWriter->writeAttribute('readingOrder', $pStyle->getAlignment()->getReadOrder());
+ }
+ $objWriter->endElement();
+
+ // protection
+ if ($pStyle->getProtection()->getLocked() != Protection::PROTECTION_INHERIT || $pStyle->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) {
+ $objWriter->startElement('protection');
+ if ($pStyle->getProtection()->getLocked() != Protection::PROTECTION_INHERIT) {
+ $objWriter->writeAttribute('locked', ($pStyle->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED ? 'true' : 'false'));
+ }
+ if ($pStyle->getProtection()->getHidden() != Protection::PROTECTION_INHERIT) {
+ $objWriter->writeAttribute('hidden', ($pStyle->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED ? 'true' : 'false'));
+ }
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Cell Style Dxf.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param \PhpOffice\PhpSpreadsheet\Style\Style $pStyle Style
+ */
+ private function writeCellStyleDxf(XMLWriter $objWriter, \PhpOffice\PhpSpreadsheet\Style\Style $pStyle): void
+ {
+ // dxf
+ $objWriter->startElement('dxf');
+
+ // font
+ $this->writeFont($objWriter, $pStyle->getFont());
+
+ // numFmt
+ $this->writeNumFmt($objWriter, $pStyle->getNumberFormat());
+
+ // fill
+ $this->writeFill($objWriter, $pStyle->getFill());
+
+ // alignment
+ $objWriter->startElement('alignment');
+ if ($pStyle->getAlignment()->getHorizontal() !== null) {
+ $objWriter->writeAttribute('horizontal', $pStyle->getAlignment()->getHorizontal());
+ }
+ if ($pStyle->getAlignment()->getVertical() !== null) {
+ $objWriter->writeAttribute('vertical', $pStyle->getAlignment()->getVertical());
+ }
+
+ if ($pStyle->getAlignment()->getTextRotation() !== null) {
+ $textRotation = 0;
+ if ($pStyle->getAlignment()->getTextRotation() >= 0) {
+ $textRotation = $pStyle->getAlignment()->getTextRotation();
+ } elseif ($pStyle->getAlignment()->getTextRotation() < 0) {
+ $textRotation = 90 - $pStyle->getAlignment()->getTextRotation();
+ }
+ $objWriter->writeAttribute('textRotation', $textRotation);
+ }
+ $objWriter->endElement();
+
+ // border
+ $this->writeBorder($objWriter, $pStyle->getBorders());
+
+ // protection
+ if (($pStyle->getProtection()->getLocked() !== null) || ($pStyle->getProtection()->getHidden() !== null)) {
+ if (
+ $pStyle->getProtection()->getLocked() !== Protection::PROTECTION_INHERIT ||
+ $pStyle->getProtection()->getHidden() !== Protection::PROTECTION_INHERIT
+ ) {
+ $objWriter->startElement('protection');
+ if (
+ ($pStyle->getProtection()->getLocked() !== null) &&
+ ($pStyle->getProtection()->getLocked() !== Protection::PROTECTION_INHERIT)
+ ) {
+ $objWriter->writeAttribute('locked', ($pStyle->getProtection()->getLocked() == Protection::PROTECTION_PROTECTED ? 'true' : 'false'));
+ }
+ if (
+ ($pStyle->getProtection()->getHidden() !== null) &&
+ ($pStyle->getProtection()->getHidden() !== Protection::PROTECTION_INHERIT)
+ ) {
+ $objWriter->writeAttribute('hidden', ($pStyle->getProtection()->getHidden() == Protection::PROTECTION_PROTECTED ? 'true' : 'false'));
+ }
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write BorderPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pName Element name
+ * @param Border $pBorder Border style
+ */
+ private function writeBorderPr(XMLWriter $objWriter, $pName, Border $pBorder): void
+ {
+ // Write BorderPr
+ if ($pBorder->getBorderStyle() != Border::BORDER_NONE) {
+ $objWriter->startElement($pName);
+ $objWriter->writeAttribute('style', $pBorder->getBorderStyle());
+
+ // color
+ $objWriter->startElement('color');
+ $objWriter->writeAttribute('rgb', $pBorder->getColor()->getARGB());
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write NumberFormat.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param NumberFormat $pNumberFormat Number Format
+ * @param int $pId Number Format identifier
+ */
+ private function writeNumFmt(XMLWriter $objWriter, NumberFormat $pNumberFormat, $pId = 0): void
+ {
+ // Translate formatcode
+ $formatCode = $pNumberFormat->getFormatCode();
+
+ // numFmt
+ if ($formatCode !== null) {
+ $objWriter->startElement('numFmt');
+ $objWriter->writeAttribute('numFmtId', ($pId + 164));
+ $objWriter->writeAttribute('formatCode', $formatCode);
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Get an array of all styles.
+ *
+ * @return \PhpOffice\PhpSpreadsheet\Style\Style[] All styles in PhpSpreadsheet
+ */
+ public function allStyles(Spreadsheet $spreadsheet)
+ {
+ return $spreadsheet->getCellXfCollection();
+ }
+
+ /**
+ * Get an array of all conditional styles.
+ *
+ * @return Conditional[] All conditional styles in PhpSpreadsheet
+ */
+ public function allConditionalStyles(Spreadsheet $spreadsheet)
+ {
+ // Get an array of all styles
+ $aStyles = [];
+
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ foreach ($spreadsheet->getSheet($i)->getConditionalStylesCollection() as $conditionalStyles) {
+ foreach ($conditionalStyles as $conditionalStyle) {
+ $aStyles[] = $conditionalStyle;
+ }
+ }
+ }
+
+ return $aStyles;
+ }
+
+ /**
+ * Get an array of all fills.
+ *
+ * @return Fill[] All fills in PhpSpreadsheet
+ */
+ public function allFills(Spreadsheet $spreadsheet)
+ {
+ // Get an array of unique fills
+ $aFills = [];
+
+ // Two first fills are predefined
+ $fill0 = new Fill();
+ $fill0->setFillType(Fill::FILL_NONE);
+ $aFills[] = $fill0;
+
+ $fill1 = new Fill();
+ $fill1->setFillType(Fill::FILL_PATTERN_GRAY125);
+ $aFills[] = $fill1;
+ // The remaining fills
+ $aStyles = $this->allStyles($spreadsheet);
+ /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */
+ foreach ($aStyles as $style) {
+ if (!isset($aFills[$style->getFill()->getHashCode()])) {
+ $aFills[$style->getFill()->getHashCode()] = $style->getFill();
+ }
+ }
+
+ return $aFills;
+ }
+
+ /**
+ * Get an array of all fonts.
+ *
+ * @return Font[] All fonts in PhpSpreadsheet
+ */
+ public function allFonts(Spreadsheet $spreadsheet)
+ {
+ // Get an array of unique fonts
+ $aFonts = [];
+ $aStyles = $this->allStyles($spreadsheet);
+
+ /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */
+ foreach ($aStyles as $style) {
+ if (!isset($aFonts[$style->getFont()->getHashCode()])) {
+ $aFonts[$style->getFont()->getHashCode()] = $style->getFont();
+ }
+ }
+
+ return $aFonts;
+ }
+
+ /**
+ * Get an array of all borders.
+ *
+ * @return Borders[] All borders in PhpSpreadsheet
+ */
+ public function allBorders(Spreadsheet $spreadsheet)
+ {
+ // Get an array of unique borders
+ $aBorders = [];
+ $aStyles = $this->allStyles($spreadsheet);
+
+ /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */
+ foreach ($aStyles as $style) {
+ if (!isset($aBorders[$style->getBorders()->getHashCode()])) {
+ $aBorders[$style->getBorders()->getHashCode()] = $style->getBorders();
+ }
+ }
+
+ return $aBorders;
+ }
+
+ /**
+ * Get an array of all number formats.
+ *
+ * @return NumberFormat[] All number formats in PhpSpreadsheet
+ */
+ public function allNumberFormats(Spreadsheet $spreadsheet)
+ {
+ // Get an array of unique number formats
+ $aNumFmts = [];
+ $aStyles = $this->allStyles($spreadsheet);
+
+ /** @var \PhpOffice\PhpSpreadsheet\Style\Style $style */
+ foreach ($aStyles as $style) {
+ if ($style->getNumberFormat()->getBuiltInFormatCode() === false && !isset($aNumFmts[$style->getNumberFormat()->getHashCode()])) {
+ $aNumFmts[$style->getNumberFormat()->getHashCode()] = $style->getNumberFormat();
+ }
+ }
+
+ return $aNumFmts;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php
new file mode 100644
index 0000000..b06ef41
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Theme.php
@@ -0,0 +1,837 @@
+ 'MS Pゴシック',
+ 'Hang' => '맑은 고딕',
+ 'Hans' => '宋体',
+ 'Hant' => '新細明體',
+ 'Arab' => 'Times New Roman',
+ 'Hebr' => 'Times New Roman',
+ 'Thai' => 'Tahoma',
+ 'Ethi' => 'Nyala',
+ 'Beng' => 'Vrinda',
+ 'Gujr' => 'Shruti',
+ 'Khmr' => 'MoolBoran',
+ 'Knda' => 'Tunga',
+ 'Guru' => 'Raavi',
+ 'Cans' => 'Euphemia',
+ 'Cher' => 'Plantagenet Cherokee',
+ 'Yiii' => 'Microsoft Yi Baiti',
+ 'Tibt' => 'Microsoft Himalaya',
+ 'Thaa' => 'MV Boli',
+ 'Deva' => 'Mangal',
+ 'Telu' => 'Gautami',
+ 'Taml' => 'Latha',
+ 'Syrc' => 'Estrangelo Edessa',
+ 'Orya' => 'Kalinga',
+ 'Mlym' => 'Kartika',
+ 'Laoo' => 'DokChampa',
+ 'Sinh' => 'Iskoola Pota',
+ 'Mong' => 'Mongolian Baiti',
+ 'Viet' => 'Times New Roman',
+ 'Uigh' => 'Microsoft Uighur',
+ 'Geor' => 'Sylfaen',
+ ];
+
+ /**
+ * Map of Minor fonts to write.
+ *
+ * @var array of string
+ */
+ private static $minorFonts = [
+ 'Jpan' => 'MS Pゴシック',
+ 'Hang' => '맑은 고딕',
+ 'Hans' => '宋体',
+ 'Hant' => '新細明體',
+ 'Arab' => 'Arial',
+ 'Hebr' => 'Arial',
+ 'Thai' => 'Tahoma',
+ 'Ethi' => 'Nyala',
+ 'Beng' => 'Vrinda',
+ 'Gujr' => 'Shruti',
+ 'Khmr' => 'DaunPenh',
+ 'Knda' => 'Tunga',
+ 'Guru' => 'Raavi',
+ 'Cans' => 'Euphemia',
+ 'Cher' => 'Plantagenet Cherokee',
+ 'Yiii' => 'Microsoft Yi Baiti',
+ 'Tibt' => 'Microsoft Himalaya',
+ 'Thaa' => 'MV Boli',
+ 'Deva' => 'Mangal',
+ 'Telu' => 'Gautami',
+ 'Taml' => 'Latha',
+ 'Syrc' => 'Estrangelo Edessa',
+ 'Orya' => 'Kalinga',
+ 'Mlym' => 'Kartika',
+ 'Laoo' => 'DokChampa',
+ 'Sinh' => 'Iskoola Pota',
+ 'Mong' => 'Mongolian Baiti',
+ 'Viet' => 'Arial',
+ 'Uigh' => 'Microsoft Uighur',
+ 'Geor' => 'Sylfaen',
+ ];
+
+ /**
+ * Map of core colours.
+ *
+ * @var array of string
+ */
+ private static $colourScheme = [
+ 'dk2' => '1F497D',
+ 'lt2' => 'EEECE1',
+ 'accent1' => '4F81BD',
+ 'accent2' => 'C0504D',
+ 'accent3' => '9BBB59',
+ 'accent4' => '8064A2',
+ 'accent5' => '4BACC6',
+ 'accent6' => 'F79646',
+ 'hlink' => '0000FF',
+ 'folHlink' => '800080',
+ ];
+
+ /**
+ * Write theme to XML format.
+ *
+ * @return string XML Output
+ */
+ public function writeTheme(Spreadsheet $spreadsheet)
+ {
+ // Create XML writer
+ $objWriter = null;
+ if ($this->getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // a:theme
+ $objWriter->startElement('a:theme');
+ $objWriter->writeAttribute('xmlns:a', 'http://schemas.openxmlformats.org/drawingml/2006/main');
+ $objWriter->writeAttribute('name', 'Office Theme');
+
+ // a:themeElements
+ $objWriter->startElement('a:themeElements');
+
+ // a:clrScheme
+ $objWriter->startElement('a:clrScheme');
+ $objWriter->writeAttribute('name', 'Office');
+
+ // a:dk1
+ $objWriter->startElement('a:dk1');
+
+ // a:sysClr
+ $objWriter->startElement('a:sysClr');
+ $objWriter->writeAttribute('val', 'windowText');
+ $objWriter->writeAttribute('lastClr', '000000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lt1
+ $objWriter->startElement('a:lt1');
+
+ // a:sysClr
+ $objWriter->startElement('a:sysClr');
+ $objWriter->writeAttribute('val', 'window');
+ $objWriter->writeAttribute('lastClr', 'FFFFFF');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:dk2
+ $this->writeColourScheme($objWriter);
+
+ $objWriter->endElement();
+
+ // a:fontScheme
+ $objWriter->startElement('a:fontScheme');
+ $objWriter->writeAttribute('name', 'Office');
+
+ // a:majorFont
+ $objWriter->startElement('a:majorFont');
+ $this->writeFonts($objWriter, 'Cambria', self::$majorFonts);
+ $objWriter->endElement();
+
+ // a:minorFont
+ $objWriter->startElement('a:minorFont');
+ $this->writeFonts($objWriter, 'Calibri', self::$minorFonts);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:fmtScheme
+ $objWriter->startElement('a:fmtScheme');
+ $objWriter->writeAttribute('name', 'Office');
+
+ // a:fillStyleLst
+ $objWriter->startElement('a:fillStyleLst');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gradFill
+ $objWriter->startElement('a:gradFill');
+ $objWriter->writeAttribute('rotWithShape', '1');
+
+ // a:gsLst
+ $objWriter->startElement('a:gsLst');
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '0');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '50000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '300000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '35000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '37000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '300000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '100000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '15000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '350000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lin
+ $objWriter->startElement('a:lin');
+ $objWriter->writeAttribute('ang', '16200000');
+ $objWriter->writeAttribute('scaled', '1');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gradFill
+ $objWriter->startElement('a:gradFill');
+ $objWriter->writeAttribute('rotWithShape', '1');
+
+ // a:gsLst
+ $objWriter->startElement('a:gsLst');
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '0');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '51000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '130000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '80000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '93000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '130000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '100000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '94000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '135000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lin
+ $objWriter->startElement('a:lin');
+ $objWriter->writeAttribute('ang', '16200000');
+ $objWriter->writeAttribute('scaled', '0');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lnStyleLst
+ $objWriter->startElement('a:lnStyleLst');
+
+ // a:ln
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', '9525');
+ $objWriter->writeAttribute('cap', 'flat');
+ $objWriter->writeAttribute('cmpd', 'sng');
+ $objWriter->writeAttribute('algn', 'ctr');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '95000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '105000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:prstDash
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', 'solid');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:ln
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', '25400');
+ $objWriter->writeAttribute('cap', 'flat');
+ $objWriter->writeAttribute('cmpd', 'sng');
+ $objWriter->writeAttribute('algn', 'ctr');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:prstDash
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', 'solid');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:ln
+ $objWriter->startElement('a:ln');
+ $objWriter->writeAttribute('w', '38100');
+ $objWriter->writeAttribute('cap', 'flat');
+ $objWriter->writeAttribute('cmpd', 'sng');
+ $objWriter->writeAttribute('algn', 'ctr');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:prstDash
+ $objWriter->startElement('a:prstDash');
+ $objWriter->writeAttribute('val', 'solid');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:effectStyleLst
+ $objWriter->startElement('a:effectStyleLst');
+
+ // a:effectStyle
+ $objWriter->startElement('a:effectStyle');
+
+ // a:effectLst
+ $objWriter->startElement('a:effectLst');
+
+ // a:outerShdw
+ $objWriter->startElement('a:outerShdw');
+ $objWriter->writeAttribute('blurRad', '40000');
+ $objWriter->writeAttribute('dist', '20000');
+ $objWriter->writeAttribute('dir', '5400000');
+ $objWriter->writeAttribute('rotWithShape', '0');
+
+ // a:srgbClr
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', '000000');
+
+ // a:alpha
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', '38000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:effectStyle
+ $objWriter->startElement('a:effectStyle');
+
+ // a:effectLst
+ $objWriter->startElement('a:effectLst');
+
+ // a:outerShdw
+ $objWriter->startElement('a:outerShdw');
+ $objWriter->writeAttribute('blurRad', '40000');
+ $objWriter->writeAttribute('dist', '23000');
+ $objWriter->writeAttribute('dir', '5400000');
+ $objWriter->writeAttribute('rotWithShape', '0');
+
+ // a:srgbClr
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', '000000');
+
+ // a:alpha
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', '35000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:effectStyle
+ $objWriter->startElement('a:effectStyle');
+
+ // a:effectLst
+ $objWriter->startElement('a:effectLst');
+
+ // a:outerShdw
+ $objWriter->startElement('a:outerShdw');
+ $objWriter->writeAttribute('blurRad', '40000');
+ $objWriter->writeAttribute('dist', '23000');
+ $objWriter->writeAttribute('dir', '5400000');
+ $objWriter->writeAttribute('rotWithShape', '0');
+
+ // a:srgbClr
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', '000000');
+
+ // a:alpha
+ $objWriter->startElement('a:alpha');
+ $objWriter->writeAttribute('val', '35000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:scene3d
+ $objWriter->startElement('a:scene3d');
+
+ // a:camera
+ $objWriter->startElement('a:camera');
+ $objWriter->writeAttribute('prst', 'orthographicFront');
+
+ // a:rot
+ $objWriter->startElement('a:rot');
+ $objWriter->writeAttribute('lat', '0');
+ $objWriter->writeAttribute('lon', '0');
+ $objWriter->writeAttribute('rev', '0');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:lightRig
+ $objWriter->startElement('a:lightRig');
+ $objWriter->writeAttribute('rig', 'threePt');
+ $objWriter->writeAttribute('dir', 't');
+
+ // a:rot
+ $objWriter->startElement('a:rot');
+ $objWriter->writeAttribute('lat', '0');
+ $objWriter->writeAttribute('lon', '0');
+ $objWriter->writeAttribute('rev', '1200000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:sp3d
+ $objWriter->startElement('a:sp3d');
+
+ // a:bevelT
+ $objWriter->startElement('a:bevelT');
+ $objWriter->writeAttribute('w', '63500');
+ $objWriter->writeAttribute('h', '25400');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:bgFillStyleLst
+ $objWriter->startElement('a:bgFillStyleLst');
+
+ // a:solidFill
+ $objWriter->startElement('a:solidFill');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gradFill
+ $objWriter->startElement('a:gradFill');
+ $objWriter->writeAttribute('rotWithShape', '1');
+
+ // a:gsLst
+ $objWriter->startElement('a:gsLst');
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '0');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '40000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '350000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '40000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '45000');
+ $objWriter->endElement();
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '99000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '350000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '100000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '20000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '255000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:path
+ $objWriter->startElement('a:path');
+ $objWriter->writeAttribute('path', 'circle');
+
+ // a:fillToRect
+ $objWriter->startElement('a:fillToRect');
+ $objWriter->writeAttribute('l', '50000');
+ $objWriter->writeAttribute('t', '-80000');
+ $objWriter->writeAttribute('r', '50000');
+ $objWriter->writeAttribute('b', '180000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gradFill
+ $objWriter->startElement('a:gradFill');
+ $objWriter->writeAttribute('rotWithShape', '1');
+
+ // a:gsLst
+ $objWriter->startElement('a:gsLst');
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '0');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:tint
+ $objWriter->startElement('a:tint');
+ $objWriter->writeAttribute('val', '80000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '300000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:gs
+ $objWriter->startElement('a:gs');
+ $objWriter->writeAttribute('pos', '100000');
+
+ // a:schemeClr
+ $objWriter->startElement('a:schemeClr');
+ $objWriter->writeAttribute('val', 'phClr');
+
+ // a:shade
+ $objWriter->startElement('a:shade');
+ $objWriter->writeAttribute('val', '30000');
+ $objWriter->endElement();
+
+ // a:satMod
+ $objWriter->startElement('a:satMod');
+ $objWriter->writeAttribute('val', '200000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:path
+ $objWriter->startElement('a:path');
+ $objWriter->writeAttribute('path', 'circle');
+
+ // a:fillToRect
+ $objWriter->startElement('a:fillToRect');
+ $objWriter->writeAttribute('l', '50000');
+ $objWriter->writeAttribute('t', '50000');
+ $objWriter->writeAttribute('r', '50000');
+ $objWriter->writeAttribute('b', '50000');
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ // a:objectDefaults
+ $objWriter->writeElement('a:objectDefaults', null);
+
+ // a:extraClrSchemeLst
+ $objWriter->writeElement('a:extraClrSchemeLst', null);
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write fonts to XML format.
+ *
+ * @param XMLWriter $objWriter
+ * @param string $latinFont
+ * @param array of string $fontSet
+ *
+ * @return string XML Output
+ */
+ private function writeFonts($objWriter, $latinFont, $fontSet)
+ {
+ // a:latin
+ $objWriter->startElement('a:latin');
+ $objWriter->writeAttribute('typeface', $latinFont);
+ $objWriter->endElement();
+
+ // a:ea
+ $objWriter->startElement('a:ea');
+ $objWriter->writeAttribute('typeface', '');
+ $objWriter->endElement();
+
+ // a:cs
+ $objWriter->startElement('a:cs');
+ $objWriter->writeAttribute('typeface', '');
+ $objWriter->endElement();
+
+ foreach ($fontSet as $fontScript => $typeface) {
+ $objWriter->startElement('a:font');
+ $objWriter->writeAttribute('script', $fontScript);
+ $objWriter->writeAttribute('typeface', $typeface);
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write colour scheme to XML format.
+ *
+ * @param XMLWriter $objWriter
+ *
+ * @return string XML Output
+ */
+ private function writeColourScheme($objWriter)
+ {
+ foreach (self::$colourScheme as $colourName => $colourValue) {
+ $objWriter->startElement('a:' . $colourName);
+
+ $objWriter->startElement('a:srgbClr');
+ $objWriter->writeAttribute('val', $colourValue);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php
new file mode 100644
index 0000000..12cd159
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Workbook.php
@@ -0,0 +1,225 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // workbook
+ $objWriter->startElement('workbook');
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+
+ // fileVersion
+ $this->writeFileVersion($objWriter);
+
+ // workbookPr
+ $this->writeWorkbookPr($objWriter);
+
+ // workbookProtection
+ $this->writeWorkbookProtection($objWriter, $spreadsheet);
+
+ // bookViews
+ if ($this->getParentWriter()->getOffice2003Compatibility() === false) {
+ $this->writeBookViews($objWriter, $spreadsheet);
+ }
+
+ // sheets
+ $this->writeSheets($objWriter, $spreadsheet);
+
+ // definedNames
+ (new DefinedNamesWriter($objWriter, $spreadsheet))->write();
+
+ // calcPr
+ $this->writeCalcPr($objWriter, $recalcRequired);
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write file version.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeFileVersion(XMLWriter $objWriter): void
+ {
+ $objWriter->startElement('fileVersion');
+ $objWriter->writeAttribute('appName', 'xl');
+ $objWriter->writeAttribute('lastEdited', '4');
+ $objWriter->writeAttribute('lowestEdited', '4');
+ $objWriter->writeAttribute('rupBuild', '4505');
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write WorkbookPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeWorkbookPr(XMLWriter $objWriter): void
+ {
+ $objWriter->startElement('workbookPr');
+
+ if (Date::getExcelCalendar() === Date::CALENDAR_MAC_1904) {
+ $objWriter->writeAttribute('date1904', '1');
+ }
+
+ $objWriter->writeAttribute('codeName', 'ThisWorkbook');
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write BookViews.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeBookViews(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
+ {
+ // bookViews
+ $objWriter->startElement('bookViews');
+
+ // workbookView
+ $objWriter->startElement('workbookView');
+
+ $objWriter->writeAttribute('activeTab', $spreadsheet->getActiveSheetIndex());
+ $objWriter->writeAttribute('autoFilterDateGrouping', ($spreadsheet->getAutoFilterDateGrouping() ? 'true' : 'false'));
+ $objWriter->writeAttribute('firstSheet', $spreadsheet->getFirstSheetIndex());
+ $objWriter->writeAttribute('minimized', ($spreadsheet->getMinimized() ? 'true' : 'false'));
+ $objWriter->writeAttribute('showHorizontalScroll', ($spreadsheet->getShowHorizontalScroll() ? 'true' : 'false'));
+ $objWriter->writeAttribute('showSheetTabs', ($spreadsheet->getShowSheetTabs() ? 'true' : 'false'));
+ $objWriter->writeAttribute('showVerticalScroll', ($spreadsheet->getShowVerticalScroll() ? 'true' : 'false'));
+ $objWriter->writeAttribute('tabRatio', $spreadsheet->getTabRatio());
+ $objWriter->writeAttribute('visibility', $spreadsheet->getVisibility());
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write WorkbookProtection.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeWorkbookProtection(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
+ {
+ if ($spreadsheet->getSecurity()->isSecurityEnabled()) {
+ $objWriter->startElement('workbookProtection');
+ $objWriter->writeAttribute('lockRevision', ($spreadsheet->getSecurity()->getLockRevision() ? 'true' : 'false'));
+ $objWriter->writeAttribute('lockStructure', ($spreadsheet->getSecurity()->getLockStructure() ? 'true' : 'false'));
+ $objWriter->writeAttribute('lockWindows', ($spreadsheet->getSecurity()->getLockWindows() ? 'true' : 'false'));
+
+ if ($spreadsheet->getSecurity()->getRevisionsPassword() != '') {
+ $objWriter->writeAttribute('revisionsPassword', $spreadsheet->getSecurity()->getRevisionsPassword());
+ }
+
+ if ($spreadsheet->getSecurity()->getWorkbookPassword() != '') {
+ $objWriter->writeAttribute('workbookPassword', $spreadsheet->getSecurity()->getWorkbookPassword());
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write calcPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param bool $recalcRequired Indicate whether formulas should be recalculated before writing
+ */
+ private function writeCalcPr(XMLWriter $objWriter, $recalcRequired = true): void
+ {
+ $objWriter->startElement('calcPr');
+
+ // Set the calcid to a higher value than Excel itself will use, otherwise Excel will always recalc
+ // If MS Excel does do a recalc, then users opening a file in MS Excel will be prompted to save on exit
+ // because the file has changed
+ $objWriter->writeAttribute('calcId', '999999');
+ $objWriter->writeAttribute('calcMode', 'auto');
+ // fullCalcOnLoad isn't needed if we've recalculating for the save
+ $objWriter->writeAttribute('calcCompleted', ($recalcRequired) ? 1 : 0);
+ $objWriter->writeAttribute('fullCalcOnLoad', ($recalcRequired) ? 0 : 1);
+ $objWriter->writeAttribute('forceFullCalc', ($recalcRequired) ? 0 : 1);
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write sheets.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ */
+ private function writeSheets(XMLWriter $objWriter, Spreadsheet $spreadsheet): void
+ {
+ // Write sheets
+ $objWriter->startElement('sheets');
+ $sheetCount = $spreadsheet->getSheetCount();
+ for ($i = 0; $i < $sheetCount; ++$i) {
+ // sheet
+ $this->writeSheet(
+ $objWriter,
+ $spreadsheet->getSheet($i)->getTitle(),
+ ($i + 1),
+ ($i + 1 + 3),
+ $spreadsheet->getSheet($i)->getSheetState()
+ );
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write sheet.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param string $pSheetname Sheet name
+ * @param int $pSheetId Sheet id
+ * @param int $pRelId Relationship ID
+ * @param string $sheetState Sheet state (visible, hidden, veryHidden)
+ */
+ private function writeSheet(XMLWriter $objWriter, $pSheetname, $pSheetId = 1, $pRelId = 1, $sheetState = 'visible'): void
+ {
+ if ($pSheetname != '') {
+ // Write sheet
+ $objWriter->startElement('sheet');
+ $objWriter->writeAttribute('name', $pSheetname);
+ $objWriter->writeAttribute('sheetId', $pSheetId);
+ if ($sheetState !== 'visible' && $sheetState != '') {
+ $objWriter->writeAttribute('state', $sheetState);
+ }
+ $objWriter->writeAttribute('r:id', 'rId' . $pRelId);
+ $objWriter->endElement();
+ } else {
+ throw new WriterException('Invalid parameters passed.');
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
new file mode 100644
index 0000000..2b89709
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Worksheet.php
@@ -0,0 +1,1282 @@
+getParentWriter()->getUseDiskCaching()) {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_DISK, $this->getParentWriter()->getDiskCachingDirectory());
+ } else {
+ $objWriter = new XMLWriter(XMLWriter::STORAGE_MEMORY);
+ }
+
+ // XML header
+ $objWriter->startDocument('1.0', 'UTF-8', 'yes');
+
+ // Worksheet
+ $objWriter->startElement('worksheet');
+ $objWriter->writeAttribute('xml:space', 'preserve');
+ $objWriter->writeAttribute('xmlns', 'http://schemas.openxmlformats.org/spreadsheetml/2006/main');
+ $objWriter->writeAttribute('xmlns:r', 'http://schemas.openxmlformats.org/officeDocument/2006/relationships');
+
+ $objWriter->writeAttribute('xmlns:xdr', 'http://schemas.openxmlformats.org/drawingml/2006/spreadsheetDrawing');
+ $objWriter->writeAttribute('xmlns:x14', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/main');
+ $objWriter->writeAttribute('xmlns:mc', 'http://schemas.openxmlformats.org/markup-compatibility/2006');
+ $objWriter->writeAttribute('mc:Ignorable', 'x14ac');
+ $objWriter->writeAttribute('xmlns:x14ac', 'http://schemas.microsoft.com/office/spreadsheetml/2009/9/ac');
+
+ // sheetPr
+ $this->writeSheetPr($objWriter, $pSheet);
+
+ // Dimension
+ $this->writeDimension($objWriter, $pSheet);
+
+ // sheetViews
+ $this->writeSheetViews($objWriter, $pSheet);
+
+ // sheetFormatPr
+ $this->writeSheetFormatPr($objWriter, $pSheet);
+
+ // cols
+ $this->writeCols($objWriter, $pSheet);
+
+ // sheetData
+ $this->writeSheetData($objWriter, $pSheet, $pStringTable);
+
+ // sheetProtection
+ $this->writeSheetProtection($objWriter, $pSheet);
+
+ // protectedRanges
+ $this->writeProtectedRanges($objWriter, $pSheet);
+
+ // autoFilter
+ $this->writeAutoFilter($objWriter, $pSheet);
+
+ // mergeCells
+ $this->writeMergeCells($objWriter, $pSheet);
+
+ // conditionalFormatting
+ $this->writeConditionalFormatting($objWriter, $pSheet);
+
+ // dataValidations
+ $this->writeDataValidations($objWriter, $pSheet);
+
+ // hyperlinks
+ $this->writeHyperlinks($objWriter, $pSheet);
+
+ // Print options
+ $this->writePrintOptions($objWriter, $pSheet);
+
+ // Page margins
+ $this->writePageMargins($objWriter, $pSheet);
+
+ // Page setup
+ $this->writePageSetup($objWriter, $pSheet);
+
+ // Header / footer
+ $this->writeHeaderFooter($objWriter, $pSheet);
+
+ // Breaks
+ $this->writeBreaks($objWriter, $pSheet);
+
+ // Drawings and/or Charts
+ $this->writeDrawings($objWriter, $pSheet, $includeCharts);
+
+ // LegacyDrawing
+ $this->writeLegacyDrawing($objWriter, $pSheet);
+
+ // LegacyDrawingHF
+ $this->writeLegacyDrawingHF($objWriter, $pSheet);
+
+ // AlternateContent
+ $this->writeAlternateContent($objWriter, $pSheet);
+
+ $objWriter->endElement();
+
+ // Return
+ return $objWriter->getData();
+ }
+
+ /**
+ * Write SheetPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeSheetPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // sheetPr
+ $objWriter->startElement('sheetPr');
+ if ($pSheet->getParent()->hasMacros()) {
+ //if the workbook have macros, we need to have codeName for the sheet
+ if (!$pSheet->hasCodeName()) {
+ $pSheet->setCodeName($pSheet->getTitle());
+ }
+ $objWriter->writeAttribute('codeName', $pSheet->getCodeName());
+ }
+ $autoFilterRange = $pSheet->getAutoFilter()->getRange();
+ if (!empty($autoFilterRange)) {
+ $objWriter->writeAttribute('filterMode', 1);
+ $pSheet->getAutoFilter()->showHideRows();
+ }
+
+ // tabColor
+ if ($pSheet->isTabColorSet()) {
+ $objWriter->startElement('tabColor');
+ $objWriter->writeAttribute('rgb', $pSheet->getTabColor()->getARGB());
+ $objWriter->endElement();
+ }
+
+ // outlinePr
+ $objWriter->startElement('outlinePr');
+ $objWriter->writeAttribute('summaryBelow', ($pSheet->getShowSummaryBelow() ? '1' : '0'));
+ $objWriter->writeAttribute('summaryRight', ($pSheet->getShowSummaryRight() ? '1' : '0'));
+ $objWriter->endElement();
+
+ // pageSetUpPr
+ if ($pSheet->getPageSetup()->getFitToPage()) {
+ $objWriter->startElement('pageSetUpPr');
+ $objWriter->writeAttribute('fitToPage', '1');
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Dimension.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeDimension(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // dimension
+ $objWriter->startElement('dimension');
+ $objWriter->writeAttribute('ref', $pSheet->calculateWorksheetDimension());
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write SheetViews.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeSheetViews(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // sheetViews
+ $objWriter->startElement('sheetViews');
+
+ // Sheet selected?
+ $sheetSelected = false;
+ if ($this->getParentWriter()->getSpreadsheet()->getIndex($pSheet) == $this->getParentWriter()->getSpreadsheet()->getActiveSheetIndex()) {
+ $sheetSelected = true;
+ }
+
+ // sheetView
+ $objWriter->startElement('sheetView');
+ $objWriter->writeAttribute('tabSelected', $sheetSelected ? '1' : '0');
+ $objWriter->writeAttribute('workbookViewId', '0');
+
+ // Zoom scales
+ if ($pSheet->getSheetView()->getZoomScale() != 100) {
+ $objWriter->writeAttribute('zoomScale', $pSheet->getSheetView()->getZoomScale());
+ }
+ if ($pSheet->getSheetView()->getZoomScaleNormal() != 100) {
+ $objWriter->writeAttribute('zoomScaleNormal', $pSheet->getSheetView()->getZoomScaleNormal());
+ }
+
+ // Show zeros (Excel also writes this attribute only if set to false)
+ if ($pSheet->getSheetView()->getShowZeros() === false) {
+ $objWriter->writeAttribute('showZeros', 0);
+ }
+
+ // View Layout Type
+ if ($pSheet->getSheetView()->getView() !== SheetView::SHEETVIEW_NORMAL) {
+ $objWriter->writeAttribute('view', $pSheet->getSheetView()->getView());
+ }
+
+ // Gridlines
+ if ($pSheet->getShowGridlines()) {
+ $objWriter->writeAttribute('showGridLines', 'true');
+ } else {
+ $objWriter->writeAttribute('showGridLines', 'false');
+ }
+
+ // Row and column headers
+ if ($pSheet->getShowRowColHeaders()) {
+ $objWriter->writeAttribute('showRowColHeaders', '1');
+ } else {
+ $objWriter->writeAttribute('showRowColHeaders', '0');
+ }
+
+ // Right-to-left
+ if ($pSheet->getRightToLeft()) {
+ $objWriter->writeAttribute('rightToLeft', 'true');
+ }
+
+ $activeCell = $pSheet->getActiveCell();
+ $sqref = $pSheet->getSelectedCells();
+
+ // Pane
+ $pane = '';
+ if ($pSheet->getFreezePane()) {
+ [$xSplit, $ySplit] = Coordinate::coordinateFromString($pSheet->getFreezePane());
+ $xSplit = Coordinate::columnIndexFromString($xSplit);
+ --$xSplit;
+ --$ySplit;
+
+ $topLeftCell = $pSheet->getTopLeftCell();
+
+ // pane
+ $pane = 'topRight';
+ $objWriter->startElement('pane');
+ if ($xSplit > 0) {
+ $objWriter->writeAttribute('xSplit', $xSplit);
+ }
+ if ($ySplit > 0) {
+ $objWriter->writeAttribute('ySplit', $ySplit);
+ $pane = ($xSplit > 0) ? 'bottomRight' : 'bottomLeft';
+ }
+ $objWriter->writeAttribute('topLeftCell', $topLeftCell);
+ $objWriter->writeAttribute('activePane', $pane);
+ $objWriter->writeAttribute('state', 'frozen');
+ $objWriter->endElement();
+
+ if (($xSplit > 0) && ($ySplit > 0)) {
+ // Write additional selections if more than two panes (ie both an X and a Y split)
+ $objWriter->startElement('selection');
+ $objWriter->writeAttribute('pane', 'topRight');
+ $objWriter->endElement();
+ $objWriter->startElement('selection');
+ $objWriter->writeAttribute('pane', 'bottomLeft');
+ $objWriter->endElement();
+ }
+ }
+
+ // Selection
+ // Only need to write selection element if we have a split pane
+ // We cheat a little by over-riding the active cell selection, setting it to the split cell
+ $objWriter->startElement('selection');
+ if ($pane != '') {
+ $objWriter->writeAttribute('pane', $pane);
+ }
+ $objWriter->writeAttribute('activeCell', $activeCell);
+ $objWriter->writeAttribute('sqref', $sqref);
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write SheetFormatPr.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeSheetFormatPr(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // sheetFormatPr
+ $objWriter->startElement('sheetFormatPr');
+
+ // Default row height
+ if ($pSheet->getDefaultRowDimension()->getRowHeight() >= 0) {
+ $objWriter->writeAttribute('customHeight', 'true');
+ $objWriter->writeAttribute('defaultRowHeight', StringHelper::formatNumber($pSheet->getDefaultRowDimension()->getRowHeight()));
+ } else {
+ $objWriter->writeAttribute('defaultRowHeight', '14.4');
+ }
+
+ // Set Zero Height row
+ if (
+ (string) $pSheet->getDefaultRowDimension()->getZeroHeight() === '1' ||
+ strtolower((string) $pSheet->getDefaultRowDimension()->getZeroHeight()) == 'true'
+ ) {
+ $objWriter->writeAttribute('zeroHeight', '1');
+ }
+
+ // Default column width
+ if ($pSheet->getDefaultColumnDimension()->getWidth() >= 0) {
+ $objWriter->writeAttribute('defaultColWidth', StringHelper::formatNumber($pSheet->getDefaultColumnDimension()->getWidth()));
+ }
+
+ // Outline level - row
+ $outlineLevelRow = 0;
+ foreach ($pSheet->getRowDimensions() as $dimension) {
+ if ($dimension->getOutlineLevel() > $outlineLevelRow) {
+ $outlineLevelRow = $dimension->getOutlineLevel();
+ }
+ }
+ $objWriter->writeAttribute('outlineLevelRow', (int) $outlineLevelRow);
+
+ // Outline level - column
+ $outlineLevelCol = 0;
+ foreach ($pSheet->getColumnDimensions() as $dimension) {
+ if ($dimension->getOutlineLevel() > $outlineLevelCol) {
+ $outlineLevelCol = $dimension->getOutlineLevel();
+ }
+ }
+ $objWriter->writeAttribute('outlineLevelCol', (int) $outlineLevelCol);
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Cols.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeCols(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // cols
+ if (count($pSheet->getColumnDimensions()) > 0) {
+ $objWriter->startElement('cols');
+
+ $pSheet->calculateColumnWidths();
+
+ // Loop through column dimensions
+ foreach ($pSheet->getColumnDimensions() as $colDimension) {
+ // col
+ $objWriter->startElement('col');
+ $objWriter->writeAttribute('min', Coordinate::columnIndexFromString($colDimension->getColumnIndex()));
+ $objWriter->writeAttribute('max', Coordinate::columnIndexFromString($colDimension->getColumnIndex()));
+
+ if ($colDimension->getWidth() < 0) {
+ // No width set, apply default of 10
+ $objWriter->writeAttribute('width', '9.10');
+ } else {
+ // Width set
+ $objWriter->writeAttribute('width', StringHelper::formatNumber($colDimension->getWidth()));
+ }
+
+ // Column visibility
+ if ($colDimension->getVisible() === false) {
+ $objWriter->writeAttribute('hidden', 'true');
+ }
+
+ // Auto size?
+ if ($colDimension->getAutoSize()) {
+ $objWriter->writeAttribute('bestFit', 'true');
+ }
+
+ // Custom width?
+ if ($colDimension->getWidth() != $pSheet->getDefaultColumnDimension()->getWidth()) {
+ $objWriter->writeAttribute('customWidth', 'true');
+ }
+
+ // Collapsed
+ if ($colDimension->getCollapsed() === true) {
+ $objWriter->writeAttribute('collapsed', 'true');
+ }
+
+ // Outline level
+ if ($colDimension->getOutlineLevel() > 0) {
+ $objWriter->writeAttribute('outlineLevel', $colDimension->getOutlineLevel());
+ }
+
+ // Style
+ $objWriter->writeAttribute('style', $colDimension->getXfIndex());
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write SheetProtection.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeSheetProtection(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // sheetProtection
+ $objWriter->startElement('sheetProtection');
+
+ $protection = $pSheet->getProtection();
+
+ if ($protection->getAlgorithm()) {
+ $objWriter->writeAttribute('algorithmName', $protection->getAlgorithm());
+ $objWriter->writeAttribute('hashValue', $protection->getPassword());
+ $objWriter->writeAttribute('saltValue', $protection->getSalt());
+ $objWriter->writeAttribute('spinCount', $protection->getSpinCount());
+ } elseif ($protection->getPassword() !== '') {
+ $objWriter->writeAttribute('password', $protection->getPassword());
+ }
+
+ $objWriter->writeAttribute('sheet', ($protection->getSheet() ? 'true' : 'false'));
+ $objWriter->writeAttribute('objects', ($protection->getObjects() ? 'true' : 'false'));
+ $objWriter->writeAttribute('scenarios', ($protection->getScenarios() ? 'true' : 'false'));
+ $objWriter->writeAttribute('formatCells', ($protection->getFormatCells() ? 'true' : 'false'));
+ $objWriter->writeAttribute('formatColumns', ($protection->getFormatColumns() ? 'true' : 'false'));
+ $objWriter->writeAttribute('formatRows', ($protection->getFormatRows() ? 'true' : 'false'));
+ $objWriter->writeAttribute('insertColumns', ($protection->getInsertColumns() ? 'true' : 'false'));
+ $objWriter->writeAttribute('insertRows', ($protection->getInsertRows() ? 'true' : 'false'));
+ $objWriter->writeAttribute('insertHyperlinks', ($protection->getInsertHyperlinks() ? 'true' : 'false'));
+ $objWriter->writeAttribute('deleteColumns', ($protection->getDeleteColumns() ? 'true' : 'false'));
+ $objWriter->writeAttribute('deleteRows', ($protection->getDeleteRows() ? 'true' : 'false'));
+ $objWriter->writeAttribute('selectLockedCells', ($protection->getSelectLockedCells() ? 'true' : 'false'));
+ $objWriter->writeAttribute('sort', ($protection->getSort() ? 'true' : 'false'));
+ $objWriter->writeAttribute('autoFilter', ($protection->getAutoFilter() ? 'true' : 'false'));
+ $objWriter->writeAttribute('pivotTables', ($protection->getPivotTables() ? 'true' : 'false'));
+ $objWriter->writeAttribute('selectUnlockedCells', ($protection->getSelectUnlockedCells() ? 'true' : 'false'));
+ $objWriter->endElement();
+ }
+
+ private static function writeAttributeIf(XMLWriter $objWriter, $condition, string $attr, string $val): void
+ {
+ if ($condition) {
+ $objWriter->writeAttribute($attr, $val);
+ }
+ }
+
+ private static function writeElementIf(XMLWriter $objWriter, $condition, string $attr, string $val): void
+ {
+ if ($condition) {
+ $objWriter->writeElement($attr, $val);
+ }
+ }
+
+ private static function writeOtherCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void
+ {
+ if (
+ $conditional->getConditionType() == Conditional::CONDITION_CELLIS
+ || $conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT
+ || $conditional->getConditionType() == Conditional::CONDITION_EXPRESSION
+ ) {
+ foreach ($conditional->getConditions() as $formula) {
+ // Formula
+ $objWriter->writeElement('formula', Xlfn::addXlfn($formula));
+ }
+ } elseif ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSBLANKS) {
+ // formula copied from ms xlsx xml source file
+ $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))=0');
+ } elseif ($conditional->getConditionType() == Conditional::CONDITION_NOTCONTAINSBLANKS) {
+ // formula copied from ms xlsx xml source file
+ $objWriter->writeElement('formula', 'LEN(TRIM(' . $cellCoordinate . '))>0');
+ }
+ }
+
+ private static function writeTextCondElements(XMLWriter $objWriter, Conditional $conditional, string $cellCoordinate): void
+ {
+ $txt = $conditional->getText();
+ if ($txt !== null) {
+ $objWriter->writeAttribute('text', $txt);
+ if ($conditional->getOperatorType() == Conditional::OPERATOR_CONTAINSTEXT) {
+ $objWriter->writeElement('formula', 'NOT(ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . ')))');
+ } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_BEGINSWITH) {
+ $objWriter->writeElement('formula', 'LEFT(' . $cellCoordinate . ',' . strlen($txt) . ')="' . $txt . '"');
+ } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_ENDSWITH) {
+ $objWriter->writeElement('formula', 'RIGHT(' . $cellCoordinate . ',' . strlen($txt) . ')="' . $txt . '"');
+ } elseif ($conditional->getOperatorType() == Conditional::OPERATOR_NOTCONTAINS) {
+ $objWriter->writeElement('formula', 'ISERROR(SEARCH("' . $txt . '",' . $cellCoordinate . '))');
+ }
+ }
+ }
+
+ /**
+ * Write ConditionalFormatting.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeConditionalFormatting(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // Conditional id
+ $id = 1;
+
+ // Loop through styles in the current worksheet
+ foreach ($pSheet->getConditionalStylesCollection() as $cellCoordinate => $conditionalStyles) {
+ foreach ($conditionalStyles as $conditional) {
+ // WHY was this again?
+ // if ($this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()) == '') {
+ // continue;
+ // }
+ if ($conditional->getConditionType() != Conditional::CONDITION_NONE) {
+ // conditionalFormatting
+ $objWriter->startElement('conditionalFormatting');
+ $objWriter->writeAttribute('sqref', $cellCoordinate);
+
+ // cfRule
+ $objWriter->startElement('cfRule');
+ $objWriter->writeAttribute('type', $conditional->getConditionType());
+ $objWriter->writeAttribute('dxfId', $this->getParentWriter()->getStylesConditionalHashTable()->getIndexForHashCode($conditional->getHashCode()));
+ $objWriter->writeAttribute('priority', $id++);
+
+ self::writeAttributeif(
+ $objWriter,
+ ($conditional->getConditionType() == Conditional::CONDITION_CELLIS || $conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT)
+ && $conditional->getOperatorType() != Conditional::OPERATOR_NONE,
+ 'operator',
+ $conditional->getOperatorType()
+ );
+
+ self::writeAttributeIf($objWriter, $conditional->getStopIfTrue(), 'stopIfTrue', '1');
+
+ if ($conditional->getConditionType() == Conditional::CONDITION_CONTAINSTEXT) {
+ self::writeTextCondElements($objWriter, $conditional, $cellCoordinate);
+ } else {
+ self::writeOtherCondElements($objWriter, $conditional, $cellCoordinate);
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+ }
+ }
+
+ /**
+ * Write DataValidations.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeDataValidations(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // Datavalidation collection
+ $dataValidationCollection = $pSheet->getDataValidationCollection();
+
+ // Write data validations?
+ if (!empty($dataValidationCollection)) {
+ $dataValidationCollection = Coordinate::mergeRangesInCollection($dataValidationCollection);
+ $objWriter->startElement('dataValidations');
+ $objWriter->writeAttribute('count', count($dataValidationCollection));
+
+ foreach ($dataValidationCollection as $coordinate => $dv) {
+ $objWriter->startElement('dataValidation');
+
+ if ($dv->getType() != '') {
+ $objWriter->writeAttribute('type', $dv->getType());
+ }
+
+ if ($dv->getErrorStyle() != '') {
+ $objWriter->writeAttribute('errorStyle', $dv->getErrorStyle());
+ }
+
+ if ($dv->getOperator() != '') {
+ $objWriter->writeAttribute('operator', $dv->getOperator());
+ }
+
+ $objWriter->writeAttribute('allowBlank', ($dv->getAllowBlank() ? '1' : '0'));
+ $objWriter->writeAttribute('showDropDown', (!$dv->getShowDropDown() ? '1' : '0'));
+ $objWriter->writeAttribute('showInputMessage', ($dv->getShowInputMessage() ? '1' : '0'));
+ $objWriter->writeAttribute('showErrorMessage', ($dv->getShowErrorMessage() ? '1' : '0'));
+
+ if ($dv->getErrorTitle() !== '') {
+ $objWriter->writeAttribute('errorTitle', $dv->getErrorTitle());
+ }
+ if ($dv->getError() !== '') {
+ $objWriter->writeAttribute('error', $dv->getError());
+ }
+ if ($dv->getPromptTitle() !== '') {
+ $objWriter->writeAttribute('promptTitle', $dv->getPromptTitle());
+ }
+ if ($dv->getPrompt() !== '') {
+ $objWriter->writeAttribute('prompt', $dv->getPrompt());
+ }
+
+ $objWriter->writeAttribute('sqref', $coordinate);
+
+ if ($dv->getFormula1() !== '') {
+ $objWriter->writeElement('formula1', $dv->getFormula1());
+ }
+ if ($dv->getFormula2() !== '') {
+ $objWriter->writeElement('formula2', $dv->getFormula2());
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write Hyperlinks.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeHyperlinks(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // Hyperlink collection
+ $hyperlinkCollection = $pSheet->getHyperlinkCollection();
+
+ // Relation ID
+ $relationId = 1;
+
+ // Write hyperlinks?
+ if (!empty($hyperlinkCollection)) {
+ $objWriter->startElement('hyperlinks');
+
+ foreach ($hyperlinkCollection as $coordinate => $hyperlink) {
+ $objWriter->startElement('hyperlink');
+
+ $objWriter->writeAttribute('ref', $coordinate);
+ if (!$hyperlink->isInternal()) {
+ $objWriter->writeAttribute('r:id', 'rId_hyperlink_' . $relationId);
+ ++$relationId;
+ } else {
+ $objWriter->writeAttribute('location', str_replace('sheet://', '', $hyperlink->getUrl()));
+ }
+
+ if ($hyperlink->getTooltip() !== '') {
+ $objWriter->writeAttribute('tooltip', $hyperlink->getTooltip());
+ $objWriter->writeAttribute('display', $hyperlink->getTooltip());
+ }
+
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write ProtectedRanges.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeProtectedRanges(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ if (count($pSheet->getProtectedCells()) > 0) {
+ // protectedRanges
+ $objWriter->startElement('protectedRanges');
+
+ // Loop protectedRanges
+ foreach ($pSheet->getProtectedCells() as $protectedCell => $passwordHash) {
+ // protectedRange
+ $objWriter->startElement('protectedRange');
+ $objWriter->writeAttribute('name', 'p' . md5($protectedCell));
+ $objWriter->writeAttribute('sqref', $protectedCell);
+ if (!empty($passwordHash)) {
+ $objWriter->writeAttribute('password', $passwordHash);
+ }
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write MergeCells.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeMergeCells(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ if (count($pSheet->getMergeCells()) > 0) {
+ // mergeCells
+ $objWriter->startElement('mergeCells');
+
+ // Loop mergeCells
+ foreach ($pSheet->getMergeCells() as $mergeCell) {
+ // mergeCell
+ $objWriter->startElement('mergeCell');
+ $objWriter->writeAttribute('ref', $mergeCell);
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write PrintOptions.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writePrintOptions(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // printOptions
+ $objWriter->startElement('printOptions');
+
+ $objWriter->writeAttribute('gridLines', ($pSheet->getPrintGridlines() ? 'true' : 'false'));
+ $objWriter->writeAttribute('gridLinesSet', 'true');
+
+ if ($pSheet->getPageSetup()->getHorizontalCentered()) {
+ $objWriter->writeAttribute('horizontalCentered', 'true');
+ }
+
+ if ($pSheet->getPageSetup()->getVerticalCentered()) {
+ $objWriter->writeAttribute('verticalCentered', 'true');
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write PageMargins.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writePageMargins(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // pageMargins
+ $objWriter->startElement('pageMargins');
+ $objWriter->writeAttribute('left', StringHelper::formatNumber($pSheet->getPageMargins()->getLeft()));
+ $objWriter->writeAttribute('right', StringHelper::formatNumber($pSheet->getPageMargins()->getRight()));
+ $objWriter->writeAttribute('top', StringHelper::formatNumber($pSheet->getPageMargins()->getTop()));
+ $objWriter->writeAttribute('bottom', StringHelper::formatNumber($pSheet->getPageMargins()->getBottom()));
+ $objWriter->writeAttribute('header', StringHelper::formatNumber($pSheet->getPageMargins()->getHeader()));
+ $objWriter->writeAttribute('footer', StringHelper::formatNumber($pSheet->getPageMargins()->getFooter()));
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write AutoFilter.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeAutoFilter(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ $autoFilterRange = $pSheet->getAutoFilter()->getRange();
+ if (!empty($autoFilterRange)) {
+ // autoFilter
+ $objWriter->startElement('autoFilter');
+
+ // Strip any worksheet reference from the filter coordinates
+ $range = Coordinate::splitRange($autoFilterRange);
+ $range = $range[0];
+ // Strip any worksheet ref
+ [$ws, $range[0]] = PhpspreadsheetWorksheet::extractSheetTitle($range[0], true);
+ $range = implode(':', $range);
+
+ $objWriter->writeAttribute('ref', str_replace('$', '', $range));
+
+ $columns = $pSheet->getAutoFilter()->getColumns();
+ if (count($columns) > 0) {
+ foreach ($columns as $columnID => $column) {
+ $rules = $column->getRules();
+ if (count($rules) > 0) {
+ $objWriter->startElement('filterColumn');
+ $objWriter->writeAttribute('colId', $pSheet->getAutoFilter()->getColumnOffset($columnID));
+
+ $objWriter->startElement($column->getFilterType());
+ if ($column->getJoin() == Column::AUTOFILTER_COLUMN_JOIN_AND) {
+ $objWriter->writeAttribute('and', 1);
+ }
+
+ foreach ($rules as $rule) {
+ if (
+ ($column->getFilterType() === Column::AUTOFILTER_FILTERTYPE_FILTER) &&
+ ($rule->getOperator() === Rule::AUTOFILTER_COLUMN_RULE_EQUAL) &&
+ ($rule->getValue() === '')
+ ) {
+ // Filter rule for Blanks
+ $objWriter->writeAttribute('blank', 1);
+ } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DYNAMICFILTER) {
+ // Dynamic Filter Rule
+ $objWriter->writeAttribute('type', $rule->getGrouping());
+ $val = $column->getAttribute('val');
+ if ($val !== null) {
+ $objWriter->writeAttribute('val', $val);
+ }
+ $maxVal = $column->getAttribute('maxVal');
+ if ($maxVal !== null) {
+ $objWriter->writeAttribute('maxVal', $maxVal);
+ }
+ } elseif ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_TOPTENFILTER) {
+ // Top 10 Filter Rule
+ $objWriter->writeAttribute('val', $rule->getValue());
+ $objWriter->writeAttribute('percent', (($rule->getOperator() === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_PERCENT) ? '1' : '0'));
+ $objWriter->writeAttribute('top', (($rule->getGrouping() === Rule::AUTOFILTER_COLUMN_RULE_TOPTEN_TOP) ? '1' : '0'));
+ } else {
+ // Filter, DateGroupItem or CustomFilter
+ $objWriter->startElement($rule->getRuleType());
+
+ if ($rule->getOperator() !== Rule::AUTOFILTER_COLUMN_RULE_EQUAL) {
+ $objWriter->writeAttribute('operator', $rule->getOperator());
+ }
+ if ($rule->getRuleType() === Rule::AUTOFILTER_RULETYPE_DATEGROUP) {
+ // Date Group filters
+ foreach ($rule->getValue() as $key => $value) {
+ if ($value > '') {
+ $objWriter->writeAttribute($key, $value);
+ }
+ }
+ $objWriter->writeAttribute('dateTimeGrouping', $rule->getGrouping());
+ } else {
+ $objWriter->writeAttribute('val', $rule->getValue());
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+
+ $objWriter->endElement();
+ }
+ }
+ }
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write PageSetup.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writePageSetup(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // pageSetup
+ $objWriter->startElement('pageSetup');
+ $objWriter->writeAttribute('paperSize', $pSheet->getPageSetup()->getPaperSize());
+ $objWriter->writeAttribute('orientation', $pSheet->getPageSetup()->getOrientation());
+
+ if ($pSheet->getPageSetup()->getScale() !== null) {
+ $objWriter->writeAttribute('scale', $pSheet->getPageSetup()->getScale());
+ }
+ if ($pSheet->getPageSetup()->getFitToHeight() !== null) {
+ $objWriter->writeAttribute('fitToHeight', $pSheet->getPageSetup()->getFitToHeight());
+ } else {
+ $objWriter->writeAttribute('fitToHeight', '0');
+ }
+ if ($pSheet->getPageSetup()->getFitToWidth() !== null) {
+ $objWriter->writeAttribute('fitToWidth', $pSheet->getPageSetup()->getFitToWidth());
+ } else {
+ $objWriter->writeAttribute('fitToWidth', '0');
+ }
+ if ($pSheet->getPageSetup()->getFirstPageNumber() !== null) {
+ $objWriter->writeAttribute('firstPageNumber', $pSheet->getPageSetup()->getFirstPageNumber());
+ $objWriter->writeAttribute('useFirstPageNumber', '1');
+ }
+ $objWriter->writeAttribute('pageOrder', $pSheet->getPageSetup()->getPageOrder());
+
+ $getUnparsedLoadedData = $pSheet->getParent()->getUnparsedLoadedData();
+ if (isset($getUnparsedLoadedData['sheets'][$pSheet->getCodeName()]['pageSetupRelId'])) {
+ $objWriter->writeAttribute('r:id', $getUnparsedLoadedData['sheets'][$pSheet->getCodeName()]['pageSetupRelId']);
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Header / Footer.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeHeaderFooter(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // headerFooter
+ $objWriter->startElement('headerFooter');
+ $objWriter->writeAttribute('differentOddEven', ($pSheet->getHeaderFooter()->getDifferentOddEven() ? 'true' : 'false'));
+ $objWriter->writeAttribute('differentFirst', ($pSheet->getHeaderFooter()->getDifferentFirst() ? 'true' : 'false'));
+ $objWriter->writeAttribute('scaleWithDoc', ($pSheet->getHeaderFooter()->getScaleWithDocument() ? 'true' : 'false'));
+ $objWriter->writeAttribute('alignWithMargins', ($pSheet->getHeaderFooter()->getAlignWithMargins() ? 'true' : 'false'));
+
+ $objWriter->writeElement('oddHeader', $pSheet->getHeaderFooter()->getOddHeader());
+ $objWriter->writeElement('oddFooter', $pSheet->getHeaderFooter()->getOddFooter());
+ $objWriter->writeElement('evenHeader', $pSheet->getHeaderFooter()->getEvenHeader());
+ $objWriter->writeElement('evenFooter', $pSheet->getHeaderFooter()->getEvenFooter());
+ $objWriter->writeElement('firstHeader', $pSheet->getHeaderFooter()->getFirstHeader());
+ $objWriter->writeElement('firstFooter', $pSheet->getHeaderFooter()->getFirstFooter());
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Breaks.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeBreaks(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // Get row and column breaks
+ $aRowBreaks = [];
+ $aColumnBreaks = [];
+ foreach ($pSheet->getBreaks() as $cell => $breakType) {
+ if ($breakType == PhpspreadsheetWorksheet::BREAK_ROW) {
+ $aRowBreaks[] = $cell;
+ } elseif ($breakType == PhpspreadsheetWorksheet::BREAK_COLUMN) {
+ $aColumnBreaks[] = $cell;
+ }
+ }
+
+ // rowBreaks
+ if (!empty($aRowBreaks)) {
+ $objWriter->startElement('rowBreaks');
+ $objWriter->writeAttribute('count', count($aRowBreaks));
+ $objWriter->writeAttribute('manualBreakCount', count($aRowBreaks));
+
+ foreach ($aRowBreaks as $cell) {
+ $coords = Coordinate::coordinateFromString($cell);
+
+ $objWriter->startElement('brk');
+ $objWriter->writeAttribute('id', $coords[1]);
+ $objWriter->writeAttribute('man', '1');
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+
+ // Second, write column breaks
+ if (!empty($aColumnBreaks)) {
+ $objWriter->startElement('colBreaks');
+ $objWriter->writeAttribute('count', count($aColumnBreaks));
+ $objWriter->writeAttribute('manualBreakCount', count($aColumnBreaks));
+
+ foreach ($aColumnBreaks as $cell) {
+ $coords = Coordinate::coordinateFromString($cell);
+
+ $objWriter->startElement('brk');
+ $objWriter->writeAttribute('id', Coordinate::columnIndexFromString($coords[0]) - 1);
+ $objWriter->writeAttribute('man', '1');
+ $objWriter->endElement();
+ }
+
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write SheetData.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ * @param string[] $pStringTable String table
+ */
+ private function writeSheetData(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, array $pStringTable): void
+ {
+ // Flipped stringtable, for faster index searching
+ $aFlippedStringTable = $this->getParentWriter()->getWriterPart('stringtable')->flipStringTable($pStringTable);
+
+ // sheetData
+ $objWriter->startElement('sheetData');
+
+ // Get column count
+ $colCount = Coordinate::columnIndexFromString($pSheet->getHighestColumn());
+
+ // Highest row number
+ $highestRow = $pSheet->getHighestRow();
+
+ // Loop through cells
+ $cellsByRow = [];
+ foreach ($pSheet->getCoordinates() as $coordinate) {
+ $cellAddress = Coordinate::coordinateFromString($coordinate);
+ $cellsByRow[$cellAddress[1]][] = $coordinate;
+ }
+
+ $currentRow = 0;
+ while ($currentRow++ < $highestRow) {
+ // Get row dimension
+ $rowDimension = $pSheet->getRowDimension($currentRow);
+
+ // Write current row?
+ $writeCurrentRow = isset($cellsByRow[$currentRow]) || $rowDimension->getRowHeight() >= 0 || $rowDimension->getVisible() == false || $rowDimension->getCollapsed() == true || $rowDimension->getOutlineLevel() > 0 || $rowDimension->getXfIndex() !== null;
+
+ if ($writeCurrentRow) {
+ // Start a new row
+ $objWriter->startElement('row');
+ $objWriter->writeAttribute('r', $currentRow);
+ $objWriter->writeAttribute('spans', '1:' . $colCount);
+
+ // Row dimensions
+ if ($rowDimension->getRowHeight() >= 0) {
+ $objWriter->writeAttribute('customHeight', '1');
+ $objWriter->writeAttribute('ht', StringHelper::formatNumber($rowDimension->getRowHeight()));
+ }
+
+ // Row visibility
+ if (!$rowDimension->getVisible() === true) {
+ $objWriter->writeAttribute('hidden', 'true');
+ }
+
+ // Collapsed
+ if ($rowDimension->getCollapsed() === true) {
+ $objWriter->writeAttribute('collapsed', 'true');
+ }
+
+ // Outline level
+ if ($rowDimension->getOutlineLevel() > 0) {
+ $objWriter->writeAttribute('outlineLevel', $rowDimension->getOutlineLevel());
+ }
+
+ // Style
+ if ($rowDimension->getXfIndex() !== null) {
+ $objWriter->writeAttribute('s', $rowDimension->getXfIndex());
+ $objWriter->writeAttribute('customFormat', '1');
+ }
+
+ // Write cells
+ if (isset($cellsByRow[$currentRow])) {
+ foreach ($cellsByRow[$currentRow] as $cellAddress) {
+ // Write cell
+ $this->writeCell($objWriter, $pSheet, $cellAddress, $aFlippedStringTable);
+ }
+ }
+
+ // End row
+ $objWriter->endElement();
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * @param RichText|string $cellValue
+ */
+ private function writeCellInlineStr(XMLWriter $objWriter, string $mappedType, $cellValue): void
+ {
+ $objWriter->writeAttribute('t', $mappedType);
+ if (!$cellValue instanceof RichText) {
+ $objWriter->writeElement('t', StringHelper::controlCharacterPHP2OOXML(htmlspecialchars($cellValue)));
+ } elseif ($cellValue instanceof RichText) {
+ $objWriter->startElement('is');
+ $this->getParentWriter()->getWriterPart('stringtable')->writeRichText($objWriter, $cellValue);
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * @param RichText|string $cellValue
+ * @param string[] $pFlippedStringTable
+ */
+ private function writeCellString(XMLWriter $objWriter, string $mappedType, $cellValue, array $pFlippedStringTable): void
+ {
+ $objWriter->writeAttribute('t', $mappedType);
+ if (!$cellValue instanceof RichText) {
+ self::writeElementIf($objWriter, isset($pFlippedStringTable[$cellValue]), 'v', $pFlippedStringTable[$cellValue]);
+ } else {
+ $objWriter->writeElement('v', $pFlippedStringTable[$cellValue->getHashCode()]);
+ }
+ }
+
+ /**
+ * @param float|int $cellValue
+ */
+ private function writeCellNumeric(XMLWriter $objWriter, $cellValue): void
+ {
+ //force a decimal to be written if the type is float
+ if (is_float($cellValue)) {
+ // force point as decimal separator in case current locale uses comma
+ $cellValue = str_replace(',', '.', (string) $cellValue);
+ if (strpos($cellValue, '.') === false) {
+ $cellValue = $cellValue . '.0';
+ }
+ }
+ $objWriter->writeElement('v', $cellValue);
+ }
+
+ private function writeCellBoolean(XMLWriter $objWriter, string $mappedType, bool $cellValue): void
+ {
+ $objWriter->writeAttribute('t', $mappedType);
+ $objWriter->writeElement('v', $cellValue ? '1' : '0');
+ }
+
+ private function writeCellError(XMLWriter $objWriter, string $mappedType, string $cellValue, string $formulaerr = '#NULL!'): void
+ {
+ $objWriter->writeAttribute('t', $mappedType);
+ $cellIsFormula = substr($cellValue, 0, 1) === '=';
+ self::writeElementIf($objWriter, $cellIsFormula, 'f', Xlfn::addXlfnStripEquals($cellValue));
+ $objWriter->writeElement('v', $cellIsFormula ? $formulaerr : $cellValue);
+ }
+
+ private function writeCellFormula(XMLWriter $objWriter, string $cellValue, Cell $pCell): void
+ {
+ $calculatedValue = $this->getParentWriter()->getPreCalculateFormulas() ? $pCell->getCalculatedValue() : $cellValue;
+ if (is_string($calculatedValue)) {
+ if (\PhpOffice\PhpSpreadsheet\Calculation\Functions::isError($calculatedValue)) {
+ $this->writeCellError($objWriter, 'e', $cellValue, $calculatedValue);
+
+ return;
+ }
+ $objWriter->writeAttribute('t', 'str');
+ } elseif (is_bool($calculatedValue)) {
+ $objWriter->writeAttribute('t', 'b');
+ }
+ // array values are not yet supported
+ //$attributes = $pCell->getFormulaAttributes();
+ //if (($attributes['t'] ?? null) === 'array') {
+ // $objWriter->startElement('f');
+ // $objWriter->writeAttribute('t', 'array');
+ // $objWriter->writeAttribute('ref', $pCellAddress);
+ // $objWriter->writeAttribute('aca', '1');
+ // $objWriter->writeAttribute('ca', '1');
+ // $objWriter->text(substr($cellValue, 1));
+ // $objWriter->endElement();
+ //} else {
+ // $objWriter->writeElement('f', Xlfn::addXlfnStripEquals($cellValue));
+ //}
+ $objWriter->writeElement('f', Xlfn::addXlfnStripEquals($cellValue));
+ self::writeElementIf(
+ $objWriter,
+ $this->getParentWriter()->getOffice2003Compatibility() === false,
+ 'v',
+ ($this->getParentWriter()->getPreCalculateFormulas() && !is_array($calculatedValue) && substr($calculatedValue, 0, 1) !== '#')
+ ? StringHelper::formatNumber($calculatedValue) : '0'
+ );
+ }
+
+ /**
+ * Write Cell.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ * @param string $pCellAddress Cell Address
+ * @param string[] $pFlippedStringTable String table (flipped), for faster index searching
+ */
+ private function writeCell(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, string $pCellAddress, array $pFlippedStringTable): void
+ {
+ // Cell
+ $pCell = $pSheet->getCell($pCellAddress);
+ $objWriter->startElement('c');
+ $objWriter->writeAttribute('r', $pCellAddress);
+
+ // Sheet styles
+ $xfi = $pCell->getXfIndex();
+ self::writeAttributeIf($objWriter, $xfi, 's', $xfi);
+
+ // If cell value is supplied, write cell value
+ $cellValue = $pCell->getValue();
+ if (is_object($cellValue) || $cellValue !== '') {
+ // Map type
+ $mappedType = $pCell->getDataType();
+
+ // Write data depending on its type
+ switch (strtolower($mappedType)) {
+ case 'inlinestr': // Inline string
+ $this->writeCellInlineStr($objWriter, $mappedType, $cellValue);
+
+ break;
+ case 's': // String
+ $this->writeCellString($objWriter, $mappedType, $cellValue, $pFlippedStringTable);
+
+ break;
+ case 'f': // Formula
+ $this->writeCellFormula($objWriter, $cellValue, $pCell);
+
+ break;
+ case 'n': // Numeric
+ $this->writeCellNumeric($objWriter, $cellValue);
+
+ break;
+ case 'b': // Boolean
+ $this->writeCellBoolean($objWriter, $mappedType, $cellValue);
+
+ break;
+ case 'e': // Error
+ $this->writeCellError($objWriter, $mappedType, $cellValue);
+ }
+ }
+
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write Drawings.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ * @param bool $includeCharts Flag indicating if we should include drawing details for charts
+ */
+ private function writeDrawings(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet, $includeCharts = false): void
+ {
+ $unparsedLoadedData = $pSheet->getParent()->getUnparsedLoadedData();
+ $hasUnparsedDrawing = isset($unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds']);
+ $chartCount = ($includeCharts) ? $pSheet->getChartCollection()->count() : 0;
+ if ($chartCount == 0 && $pSheet->getDrawingCollection()->count() == 0 && !$hasUnparsedDrawing) {
+ return;
+ }
+
+ // If sheet contains drawings, add the relationships
+ $objWriter->startElement('drawing');
+
+ $rId = 'rId1';
+ if (isset($unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'])) {
+ $drawingOriginalIds = $unparsedLoadedData['sheets'][$pSheet->getCodeName()]['drawingOriginalIds'];
+ // take first. In future can be overriten
+ // (! synchronize with \PhpOffice\PhpSpreadsheet\Writer\Xlsx\Rels::writeWorksheetRelationships)
+ $rId = reset($drawingOriginalIds);
+ }
+
+ $objWriter->writeAttribute('r:id', $rId);
+ $objWriter->endElement();
+ }
+
+ /**
+ * Write LegacyDrawing.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeLegacyDrawing(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // If sheet contains comments, add the relationships
+ if (count($pSheet->getComments()) > 0) {
+ $objWriter->startElement('legacyDrawing');
+ $objWriter->writeAttribute('r:id', 'rId_comments_vml1');
+ $objWriter->endElement();
+ }
+ }
+
+ /**
+ * Write LegacyDrawingHF.
+ *
+ * @param XMLWriter $objWriter XML Writer
+ * @param PhpspreadsheetWorksheet $pSheet Worksheet
+ */
+ private function writeLegacyDrawingHF(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ // If sheet contains images, add the relationships
+ if (count($pSheet->getHeaderFooter()->getImages()) > 0) {
+ $objWriter->startElement('legacyDrawingHF');
+ $objWriter->writeAttribute('r:id', 'rId_headerfooter_vml1');
+ $objWriter->endElement();
+ }
+ }
+
+ private function writeAlternateContent(XMLWriter $objWriter, PhpspreadsheetWorksheet $pSheet): void
+ {
+ if (empty($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['AlternateContents'])) {
+ return;
+ }
+
+ foreach ($pSheet->getParent()->getUnparsedLoadedData()['sheets'][$pSheet->getCodeName()]['AlternateContents'] as $alternateContent) {
+ $objWriter->writeRaw($alternateContent);
+ }
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php
new file mode 100644
index 0000000..803662f
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/WriterPart.php
@@ -0,0 +1,33 @@
+parentWriter;
+ }
+
+ /**
+ * Set parent Xlsx object.
+ */
+ public function __construct(Xlsx $pWriter)
+ {
+ $this->parentWriter = $pWriter;
+ }
+}
diff --git a/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php
new file mode 100644
index 0000000..9c56ff4
--- /dev/null
+++ b/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xlsx/Xlfn.php
@@ -0,0 +1,159 @@
+