diff --git a/lib/phpspreadsheet/readme_moodle.txt b/lib/phpspreadsheet/readme_moodle.txt index 0abc9f0552d..ba8a11411a1 100644 --- a/lib/phpspreadsheet/readme_moodle.txt +++ b/lib/phpspreadsheet/readme_moodle.txt @@ -4,20 +4,22 @@ Last release package can be found in https://github.com/PHPOffice/PhpSpreadsheet NOTICE: * Before running composer command, make sure you have the composer version updated. - * Composer version 1.9.1 2019-11-01 17:20:17 + * Composer version 2.0.8 2020-12-03 17:20:38 STEPS: * Create a temporary folder outside your moodle installation * Execute `composer require phpoffice/phpspreadsheet` * Remove the old 'vendor' directory in lib/phpspreadsheet/ * Copy contents of 'vendor' directory + * Create a commit with only the library changes * Update lib/thirdpartylibs.xml * Apply the modifications described in the CHANGES section + * Create another commit with the previous two steps of changes * Go to http://localhost/lib/tests/other/spreadsheettestpage.php and test the generated files CHANGES: - * Remove the following folders (and their content): + * If the following exist, remove the following folders (and their content): - vendor/phpoffice/phpspreadsheet/bin - vendor/phpoffice/phpspreadsheet/docs - vendor/phpoffice/phpspreadsheet/samples @@ -29,6 +31,7 @@ CHANGES: - .sami.php - .scrutinizer.yml - .travis.yml + - .phpcs.xml.dist * Add the next Moodle hack at the beginning of the function sysGetTempDir() located in vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php @@ -53,6 +56,10 @@ located in vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php - PhpSpreadsheet/Writer/Xls.php - PhpSpreadsheet/Writer/Xls/* -* Remove the next files in vendor/markbaker/ related to external testing that we don't need matrix/: + * Remove the next files in vendor/markbaker/ related to external testing that we don't need matrix/: - infection.json.dist (PHP mutation testing framework configuration file) - phpstan.neon (PHP static analyzer configuration file) + + * Remove the following directories if they exist, These imported libraries already exist in Moodle core: + - /vendor/myclabs/* + - /vendor/maennchen/* diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/.github/FUNDING.yml b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/.github/FUNDING.yml deleted file mode 100644 index d807a3f33f9..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/.github/FUNDING.yml +++ /dev/null @@ -1 +0,0 @@ -open_collective: zipstream diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE.md b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE.md deleted file mode 100644 index 3c2e66d6f5c..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/.github/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,12 +0,0 @@ -# Description of the problem - -Please be very descriptive and include as much details as possible. - -# Example code - -# Informations - -* ZipStream-PHP version: -* PHP version: - -Please include any supplemental information you deem relevant to this issue. diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/.gitignore b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/.gitignore deleted file mode 100644 index 5210738a9c9..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -clover.xml -composer.lock -coverage.clover -.idea -phpunit.xml -vendor diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/.travis.yml b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/.travis.yml deleted file mode 100644 index 12c8a640e11..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/.travis.yml +++ /dev/null @@ -1,12 +0,0 @@ -language: php -dist: trusty -sudo: false -php: - - 7.1 - - 7.2 - - 7.3 -install: composer install -script: ./vendor/bin/phpunit --coverage-clover=coverage.clover -after_script: - - wget https://scrutinizer-ci.com/ocular.phar - - php ocular.phar code-coverage:upload --format=php-clover coverage.clover diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/CHANGELOG.md b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/CHANGELOG.md deleted file mode 100644 index b1979abcc89..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/CHANGELOG.md +++ /dev/null @@ -1,51 +0,0 @@ -# CHANGELOG for ZipStream-PHP - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) -and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). - -## [2.1.0] - 2020-06-01 -### Changed -- Don't execute ob_flush() when output buffering is not enabled (#152) -- Fix inconsistent return type on 32-bit systems (#149) Fix #144 -- Use mbstring polyfill (#151) -- Promote 7zip usage over unzip to avoid UTF-8 issues (#147) - -## [2.0.0] - 2020-02-22 -### Breaking change -- Only the self opened streams will be closed (#139) -If you were relying on ZipStream to close streams that the library didn't open, -you'll need to close them yourself now. - -### Changed -- Minor change to data descriptor (#136) - -## [1.2.0] - 2019-07-11 - -### Added -- Option to flush output buffer after every write (#122) - -## [1.1.0] - 2019-04-30 - -### Fixed -- Honor last-modified timestamps set via `ZipStream\Option\File::setTime()` (#106) -- Documentation regarding output of HTTP headers -- Test warnings with PHPUnit (#109) - -### Added -- Test for FileNotReadableException (#114) -- Size attribute to File options (#113) -- Tests on PHP 7.3 (#108) - -## [1.0.0] - 2019-04-17 - -### Breaking changes -- Mininum PHP version is now 7.1 -- Options are now passed to the ZipStream object via the Option\Archive object. See the wiki for available options and code examples - -### Added -- Add large file support with Zip64 headers - -### Changed -- Major refactoring and code cleanup diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/CONTRIBUTING.md b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/CONTRIBUTING.md deleted file mode 100644 index f8ef5a57318..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/CONTRIBUTING.md +++ /dev/null @@ -1,25 +0,0 @@ -# ZipStream Readme for Contributors -## Code styling -### Indention -For spaces are used to indent code. The convention is [K&R](http://en.wikipedia.org/wiki/Indent_style#K&R) - -### Comments -Double Slashes are used for an one line comment. - -Classes, Variables, Methods etc: - -```php -/** - * My comment - * - * @myanotation like @param etc. - */ -``` - -## Pull requests -Feel free to submit pull requests. - -## Testing -For every new feature please write a new PHPUnit test. - -Before every commit execute `./vendor/bin/phpunit` to check if your changes wrecked something: diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/LICENSE b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/LICENSE deleted file mode 100644 index ebe7fe2f8a1..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/LICENSE +++ /dev/null @@ -1,24 +0,0 @@ -MIT License - -Copyright (C) 2007-2009 Paul Duncan -Copyright (C) 2014 Jonatan Männchen -Copyright (C) 2014 Jesse G. Donat -Copyright (C) 2018 Nicolas CARPi - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/README.md b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/README.md deleted file mode 100644 index c2e832b6fbe..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/README.md +++ /dev/null @@ -1,123 +0,0 @@ -# ZipStream-PHP - -[![Build Status](https://travis-ci.org/maennchen/ZipStream-PHP.svg?branch=master)](https://travis-ci.org/maennchen/ZipStream-PHP) -[![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/) -[![Code Coverage](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/badges/coverage.png?b=master)](https://scrutinizer-ci.com/g/maennchen/ZipStream-PHP/) -[![Latest Stable Version](https://poser.pugx.org/maennchen/zipstream-php/v/stable)](https://packagist.org/packages/maennchen/zipstream-php) -[![Total Downloads](https://poser.pugx.org/maennchen/zipstream-php/downloads)](https://packagist.org/packages/maennchen/zipstream-php) -[![Financial Contributors on Open Collective](https://opencollective.com/zipstream/all/badge.svg?label=financial+contributors)](https://opencollective.com/zipstream) [![License](https://img.shields.io/github/license/maennchen/zipstream-php.svg)](LICENSE) - -## Overview - -A fast and simple streaming zip file downloader for PHP. Using this library will save you from having to write the Zip to disk. You can directly send it to the user, which is much faster. It can work with S3 buckets or any PSR7 Stream. - -Please see the [LICENSE](LICENSE) file for licensing and warranty information. - -## Installation - -Simply add a dependency on maennchen/zipstream-php to your project's composer.json file if you use Composer to manage the dependencies of your project. Use following command to add the package to your project's dependencies: - -```bash -composer require maennchen/zipstream-php -``` - -## Usage and options - -Here's a simple example: - -```php -// Autoload the dependencies -require 'vendor/autoload.php'; - -// enable output of HTTP headers -$options = new ZipStream\Option\Archive(); -$options->setSendHttpHeaders(true); - -// create a new zipstream object -$zip = new ZipStream\ZipStream('example.zip', $options); - -// create a file named 'hello.txt' -$zip->addFile('hello.txt', 'This is the contents of hello.txt'); - -// add a file named 'some_image.jpg' from a local file 'path/to/image.jpg' -$zip->addFileFromPath('some_image.jpg', 'path/to/image.jpg'); - -// add a file named 'goodbye.txt' from an open stream resource -$fp = tmpfile(); -fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); -rewind($fp); -$zip->addFileFromStream('goodbye.txt', $fp); -fclose($fp); - -// finish the zip stream -$zip->finish(); -``` - -You can also add comments, modify file timestamps, and customize (or -disable) the HTTP headers. It is also possible to specify the storage method when adding files, -the current default storage method is 'deflate' i.e files are stored with Compression mode 0x08. - -See the [Wiki](https://github.com/maennchen/ZipStream-PHP/wiki) for details. - -## Known issue - -The native Mac OS archive extraction tool might not open archives in some conditions. A workaround is to disable the Zip64 feature with the option `$opt->setEnableZip64(false)`. This limits the archive to 4 Gb and 64k files but will allow Mac OS users to open them without issue. See #116. - -The linux `unzip` utility might not handle properly unicode characters. It is recommended to extract with another tool like [7-zip](https://www.7-zip.org/). See #146. - -## Upgrade to version 2.0.0 - -* Only the self opened streams will be closed (#139) -If you were relying on ZipStream to close streams that the library didn't open, -you'll need to close them yourself now. - -## Upgrade to version 1.0.0 - -* All options parameters to all function have been moved from an `array` to structured option objects. See [the wiki](https://github.com/maennchen/ZipStream-PHP/wiki/Available-options) for examples. -* The whole library has been refactored. The minimal PHP requirement has been raised to PHP 7.1. - -## Usage with Symfony and S3 - -You can find example code on [the wiki](https://github.com/maennchen/ZipStream-PHP/wiki/Symfony-example). - -## Contributing - -ZipStream-PHP is a collaborative project. Please take a look at the [CONTRIBUTING.md](CONTRIBUTING.md) file. - -## About the Authors - -* Paul Duncan - https://pablotron.org/ -* Jonatan Männchen - https://maennchen.dev -* Jesse G. Donat - https://donatstudios.com -* Nicolas CARPi - https://www.deltablot.com -* Nik Barham - https://www.brokencube.co.uk - -## Contributors - -### Code Contributors - -This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)]. - - -### Financial Contributors - -Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/zipstream/contribute)] - -#### Individuals - - - -#### Organizations - -Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/zipstream/contribute)] - - - - - - - - - - - diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/composer.json b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/composer.json deleted file mode 100644 index 103c78c7056..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/composer.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "name": "maennchen/zipstream-php", - "description": "ZipStream is a library for dynamically streaming dynamic zip files from PHP without writing to the disk at all on the server.", - "keywords": ["zip", "stream"], - "type": "library", - "license": "MIT", - "authors": [{ - "name": "Paul Duncan", - "email": "pabs@pablotron.org" - }, - { - "name": "Jonatan Männchen", - "email": "jonatan@maennchen.ch" - }, - { - "name": "Jesse Donat", - "email": "donatj@gmail.com" - }, - { - "name": "András Kolesár", - "email": "kolesar@kolesar.hu" - } - ], - "require": { - "php": ">= 7.1", - "symfony/polyfill-mbstring": "^1.0", - "psr/http-message": "^1.0", - "myclabs/php-enum": "^1.5" - }, - "require-dev": { - "phpunit/phpunit": ">= 7.5", - "guzzlehttp/guzzle": ">= 6.3", - "ext-zip": "*", - "mikey179/vfsstream": "^1.6" - }, - "autoload": { - "psr-4": { - "ZipStream\\": "src/" - } - } -} diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/phpunit.xml.dist b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/phpunit.xml.dist deleted file mode 100644 index f6e722796ee..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/phpunit.xml.dist +++ /dev/null @@ -1,17 +0,0 @@ - - - - test - - - - - - - - - - src - - - diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/psalm.xml b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/psalm.xml deleted file mode 100644 index 42f355b2cec..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/psalm.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Bigint.php b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Bigint.php deleted file mode 100644 index 52ccfd2ea48..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Bigint.php +++ /dev/null @@ -1,172 +0,0 @@ -fillBytes($value, 0, 8); - } - - /** - * Fill the bytes field with int - * - * @param int $value - * @param int $start - * @param int $count - * @return void - */ - protected function fillBytes(int $value, int $start, int $count): void - { - for ($i = 0; $i < $count; $i++) { - $this->bytes[$start + $i] = $i >= PHP_INT_SIZE ? 0 : $value & 0xFF; - $value >>= 8; - } - } - - /** - * Get an instance - * - * @param int $value - * @return Bigint - */ - public static function init(int $value = 0): self - { - return new self($value); - } - - /** - * Fill bytes from low to high - * - * @param int $low - * @param int $high - * @return Bigint - */ - public static function fromLowHigh(int $low, int $high): self - { - $bigint = new Bigint(); - $bigint->fillBytes($low, 0, 4); - $bigint->fillBytes($high, 4, 4); - return $bigint; - } - - /** - * Get high 32 - * - * @return int - */ - public function getHigh32(): int - { - return $this->getValue(4, 4); - } - - /** - * Get value from bytes array - * - * @param int $end - * @param int $length - * @return int - */ - public function getValue(int $end = 0, int $length = 8): int - { - $result = 0; - for ($i = $end + $length - 1; $i >= $end; $i--) { - $result <<= 8; - $result |= $this->bytes[$i]; - } - return $result; - } - - /** - * Get low FF - * - * @param bool $force - * @return float - */ - public function getLowFF(bool $force = false): float - { - if ($force || $this->isOver32()) { - return (float)0xFFFFFFFF; - } - return (float)$this->getLow32(); - } - - /** - * Check if is over 32 - * - * @param bool $force - * @return bool - */ - public function isOver32(bool $force = false): bool - { - // value 0xFFFFFFFF already needs a Zip64 header - return $force || - max(array_slice($this->bytes, 4, 4)) > 0 || - min(array_slice($this->bytes, 0, 4)) === 0xFF; - } - - /** - * Get low 32 - * - * @return int - */ - public function getLow32(): int - { - return $this->getValue(0, 4); - } - - /** - * Get hexadecimal - * - * @return string - */ - public function getHex64(): string - { - $result = '0x'; - for ($i = 7; $i >= 0; $i--) { - $result .= sprintf('%02X', $this->bytes[$i]); - } - return $result; - } - - /** - * Add - * - * @param Bigint $other - * @return Bigint - */ - public function add(Bigint $other): Bigint - { - $result = clone $this; - $overflow = false; - for ($i = 0; $i < 8; $i++) { - $result->bytes[$i] += $other->bytes[$i]; - if ($overflow) { - $result->bytes[$i]++; - $overflow = false; - } - if ($result->bytes[$i] & 0x100) { - $overflow = true; - $result->bytes[$i] &= 0xFF; - } - } - if ($overflow) { - throw new OverflowException; - } - return $result; - } -} diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/DeflateStream.php b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/DeflateStream.php deleted file mode 100644 index d6c272885b7..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/DeflateStream.php +++ /dev/null @@ -1,70 +0,0 @@ -filter) { - $this->removeDeflateFilter(); - $this->seek(0); - $this->addDeflateFilter($this->options); - } else { - rewind($this->stream); - } - } - - /** - * Remove the deflate filter - * - * @return void - */ - public function removeDeflateFilter(): void - { - if (!$this->filter) { - return; - } - stream_filter_remove($this->filter); - $this->filter = null; - } - - /** - * Add a deflate filter - * - * @param Option\File $options - * @return void - */ - public function addDeflateFilter(Option\File $options): void - { - $this->options = $options; - // parameter 4 for stream_filter_append expects array - // so we convert the option object in an array - $optionsArr = [ - 'comment' => $options->getComment(), - 'method' => $options->getMethod(), - 'deflateLevel' => $options->getDeflateLevel(), - 'time' => $options->getTime() - ]; - $this->filter = stream_filter_append( - $this->stream, - 'zlib.deflate', - STREAM_FILTER_READ, - $optionsArr - ); - } -} diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Exception.php b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Exception.php deleted file mode 100644 index 18ccfbbdc1f..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Exception.php +++ /dev/null @@ -1,11 +0,0 @@ -zip = $zip; - - $this->name = $name; - $this->opt = $opt ?: new FileOptions(); - $this->method = $this->opt->getMethod(); - $this->version = Version::STORE(); - $this->ofs = new Bigint(); - } - - public function processPath(string $path): void - { - if (!is_readable($path)) { - if (!file_exists($path)) { - throw new FileNotFoundException($path); - } - throw new FileNotReadableException($path); - } - if ($this->zip->isLargeFile($path) === false) { - $data = file_get_contents($path); - $this->processData($data); - } else { - $this->method = $this->zip->opt->getLargeFileMethod(); - - $stream = new DeflateStream(fopen($path, 'rb')); - $this->processStream($stream); - $stream->close(); - } - } - - public function processData(string $data): void - { - $this->len = new Bigint(strlen($data)); - $this->crc = crc32($data); - - // compress data if needed - if ($this->method->equals(Method::DEFLATE())) { - $data = gzdeflate($data); - } - - $this->zlen = new Bigint(strlen($data)); - $this->addFileHeader(); - $this->zip->send($data); - $this->addFileFooter(); - } - - /** - * Create and send zip header for this file. - * - * @return void - * @throws \ZipStream\Exception\EncodingException - */ - public function addFileHeader(): void - { - $name = static::filterFilename($this->name); - - // calculate name length - $nameLength = strlen($name); - - // create dos timestamp - $time = static::dosTime($this->opt->getTime()->getTimestamp()); - - $comment = $this->opt->getComment(); - - if (!mb_check_encoding($name, 'ASCII') || - !mb_check_encoding($comment, 'ASCII')) { - // Sets Bit 11: Language encoding flag (EFS). If this bit is set, - // the filename and comment fields for this file - // MUST be encoded using UTF-8. (see APPENDIX D) - if (!mb_check_encoding($name, 'UTF-8') || - !mb_check_encoding($comment, 'UTF-8')) { - throw new EncodingException( - 'File name and comment should use UTF-8 ' . - 'if one of them does not fit into ASCII range.' - ); - } - $this->bits |= self::BIT_EFS_UTF8; - } - - if ($this->method->equals(Method::DEFLATE())) { - $this->version = Version::DEFLATE(); - } - - $force = (boolean)($this->bits & self::BIT_ZERO_HEADER) && - $this->zip->opt->isEnableZip64(); - - $footer = $this->buildZip64ExtraBlock($force); - - // If this file will start over 4GB limit in ZIP file, - // CDR record will have to use Zip64 extension to describe offset - // to keep consistency we use the same value here - if ($this->zip->ofs->isOver32()) { - $this->version = Version::ZIP64(); - } - - $fields = [ - ['V', ZipStream::FILE_HEADER_SIGNATURE], - ['v', $this->version->getValue()], // Version needed to Extract - ['v', $this->bits], // General purpose bit flags - data descriptor flag set - ['v', $this->method->getValue()], // Compression method - ['V', $time], // Timestamp (DOS Format) - ['V', $this->crc], // CRC32 of data (0 -> moved to data descriptor footer) - ['V', $this->zlen->getLowFF($force)], // Length of compressed data (forced to 0xFFFFFFFF for zero header) - ['V', $this->len->getLowFF($force)], // Length of original data (forced to 0xFFFFFFFF for zero header) - ['v', $nameLength], // Length of filename - ['v', strlen($footer)], // Extra data (see above) - ]; - - // pack fields and calculate "total" length - $header = ZipStream::packFields($fields); - - // print header and filename - $data = $header . $name . $footer; - $this->zip->send($data); - - // save header length - $this->hlen = Bigint::init(strlen($data)); - } - - /** - * Strip characters that are not legal in Windows filenames - * to prevent compatibility issues - * - * @param string $filename Unprocessed filename - * @return string - */ - public static function filterFilename(string $filename): string - { - // strip leading slashes from file name - // (fixes bug in windows archive viewer) - $filename = preg_replace('/^\\/+/', '', $filename); - - return str_replace(['\\', ':', '*', '?', '"', '<', '>', '|'], '_', $filename); - } - - /** - * Convert a UNIX timestamp to a DOS timestamp. - * - * @param int $when - * @return int DOS Timestamp - */ - final protected static function dosTime(int $when): int - { - // get date array for timestamp - $d = getdate($when); - - // set lower-bound on dates - if ($d['year'] < 1980) { - $d = array( - 'year' => 1980, - 'mon' => 1, - 'mday' => 1, - 'hours' => 0, - 'minutes' => 0, - 'seconds' => 0 - ); - } - - // remove extra years from 1980 - $d['year'] -= 1980; - - // return date string - return - ($d['year'] << 25) | - ($d['mon'] << 21) | - ($d['mday'] << 16) | - ($d['hours'] << 11) | - ($d['minutes'] << 5) | - ($d['seconds'] >> 1); - } - - protected function buildZip64ExtraBlock(bool $force = false): string - { - - $fields = []; - if ($this->len->isOver32($force)) { - $fields[] = ['P', $this->len]; // Length of original data - } - - if ($this->len->isOver32($force)) { - $fields[] = ['P', $this->zlen]; // Length of compressed data - } - - if ($this->ofs->isOver32()) { - $fields[] = ['P', $this->ofs]; // Offset of local header record - } - - if (!empty($fields)) { - if (!$this->zip->opt->isEnableZip64()) { - throw new OverflowException(); - } - - array_unshift( - $fields, - ['v', 0x0001], // 64 bit extension - ['v', count($fields) * 8] // Length of data block - ); - $this->version = Version::ZIP64(); - } - - return ZipStream::packFields($fields); - } - - /** - * Create and send data descriptor footer for this file. - * - * @return void - */ - - public function addFileFooter(): void - { - - if ($this->bits & self::BIT_ZERO_HEADER) { - // compressed and uncompressed size - $sizeFormat = 'V'; - if ($this->zip->opt->isEnableZip64()) { - $sizeFormat = 'P'; - } - $fields = [ - ['V', ZipStream::DATA_DESCRIPTOR_SIGNATURE], - ['V', $this->crc], // CRC32 - [$sizeFormat, $this->zlen], // Length of compressed data - [$sizeFormat, $this->len], // Length of original data - ]; - - $footer = ZipStream::packFields($fields); - $this->zip->send($footer); - } else { - $footer = ''; - } - $this->totalLength = $this->hlen->add($this->zlen)->add(Bigint::init(strlen($footer))); - $this->zip->addToCdr($this); - } - - public function processStream(StreamInterface $stream): void - { - $this->zlen = new Bigint(); - $this->len = new Bigint(); - - if ($this->zip->opt->isZeroHeader()) { - $this->processStreamWithZeroHeader($stream); - } else { - $this->processStreamWithComputedHeader($stream); - } - } - - protected function processStreamWithZeroHeader(StreamInterface $stream): void - { - $this->bits |= self::BIT_ZERO_HEADER; - $this->addFileHeader(); - $this->readStream($stream, self::COMPUTE | self::SEND); - $this->addFileFooter(); - } - - protected function readStream(StreamInterface $stream, ?int $options = null): void - { - $this->deflateInit(); - $total = 0; - $size = $this->opt->getSize(); - while (!$stream->eof() && ($size === 0 || $total < $size)) { - $data = $stream->read(self::CHUNKED_READ_BLOCK_SIZE); - $total += strlen($data); - if ($size > 0 && $total > $size) { - $data = substr($data, 0 , strlen($data)-($total - $size)); - } - $this->deflateData($stream, $data, $options); - if ($options & self::SEND) { - $this->zip->send($data); - } - } - $this->deflateFinish($options); - } - - protected function deflateInit(): void - { - $this->hash = hash_init(self::HASH_ALGORITHM); - if ($this->method->equals(Method::DEFLATE())) { - $this->deflate = deflate_init( - ZLIB_ENCODING_RAW, - ['level' => $this->opt->getDeflateLevel()] - ); - } - } - - protected function deflateData(StreamInterface $stream, string &$data, ?int $options = null): void - { - if ($options & self::COMPUTE) { - $this->len = $this->len->add(Bigint::init(strlen($data))); - hash_update($this->hash, $data); - } - if ($this->deflate) { - $data = deflate_add( - $this->deflate, - $data, - $stream->eof() - ? ZLIB_FINISH - : ZLIB_NO_FLUSH - ); - } - if ($options & self::COMPUTE) { - $this->zlen = $this->zlen->add(Bigint::init(strlen($data))); - } - } - - protected function deflateFinish(?int $options = null): void - { - if ($options & self::COMPUTE) { - $this->crc = hexdec(hash_final($this->hash)); - } - } - - protected function processStreamWithComputedHeader(StreamInterface $stream): void - { - $this->readStream($stream, self::COMPUTE); - $stream->rewind(); - - // incremental compression with deflate_add - // makes this second read unnecessary - // but it is only available from PHP 7.0 - if (!$this->deflate && $stream instanceof DeflateStream && $this->method->equals(Method::DEFLATE())) { - $stream->addDeflateFilter($this->opt); - $this->zlen = new Bigint(); - while (!$stream->eof()) { - $data = $stream->read(self::CHUNKED_READ_BLOCK_SIZE); - $this->zlen = $this->zlen->add(Bigint::init(strlen($data))); - } - $stream->rewind(); - } - - $this->addFileHeader(); - $this->readStream($stream, self::SEND); - $this->addFileFooter(); - } - - /** - * Send CDR record for specified file. - * - * @return string - */ - public function getCdrFile(): string - { - $name = static::filterFilename($this->name); - - // get attributes - $comment = $this->opt->getComment(); - - // get dos timestamp - $time = static::dosTime($this->opt->getTime()->getTimestamp()); - - $footer = $this->buildZip64ExtraBlock(); - - $fields = [ - ['V', ZipStream::CDR_FILE_SIGNATURE], // Central file header signature - ['v', ZipStream::ZIP_VERSION_MADE_BY], // Made by version - ['v', $this->version->getValue()], // Extract by version - ['v', $this->bits], // General purpose bit flags - data descriptor flag set - ['v', $this->method->getValue()], // Compression method - ['V', $time], // Timestamp (DOS Format) - ['V', $this->crc], // CRC32 - ['V', $this->zlen->getLowFF()], // Compressed Data Length - ['V', $this->len->getLowFF()], // Original Data Length - ['v', strlen($name)], // Length of filename - ['v', strlen($footer)], // Extra data len (see above) - ['v', strlen($comment)], // Length of comment - ['v', 0], // Disk number - ['v', 0], // Internal File Attributes - ['V', 32], // External File Attributes - ['V', $this->ofs->getLowFF()] // Relative offset of local header - ]; - - // pack fields, then append name and comment - $header = ZipStream::packFields($fields); - - return $header . $name . $footer . $comment; - } - - /** - * @return Bigint - */ - public function getTotalLength(): Bigint - { - return $this->totalLength; - } -} diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Option/Archive.php b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Option/Archive.php deleted file mode 100644 index b6b95cce54c..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Option/Archive.php +++ /dev/null @@ -1,261 +0,0 @@ - 4 GB or file count > 64k) - * - * @var bool - */ - private $enableZip64 = true; - /** - * Enable streaming files with single read where - * general purpose bit 3 indicates local file header - * contain zero values in crc and size fields, - * these appear only after file contents - * in data descriptor block. - * - * @var bool - */ - private $zeroHeader = false; - /** - * Enable reading file stat for determining file size. - * When a 32-bit system reads file size that is - * over 2 GB, invalid value appears in file size - * due to integer overflow. Should be disabled on - * 32-bit systems with method addFileFromPath - * if any file may exceed 2 GB. In this case file - * will be read in blocks and correct size will be - * determined from content. - * - * @var bool - */ - private $statFiles = true; - /** - * Enable flush after every write to output stream. - * @var bool - */ - private $flushOutput = false; - /** - * HTTP Content-Disposition. Defaults to - * 'attachment', where - * FILENAME is the specified filename. - * - * Note that this does nothing if you are - * not sending HTTP headers. - * - * @var string - */ - private $contentDisposition = 'attachment'; - /** - * Note that this does nothing if you are - * not sending HTTP headers. - * - * @var string - */ - private $contentType = 'application/x-zip'; - /** - * @var int - */ - private $deflateLevel = 6; - - /** - * @var resource - */ - private $outputStream; - - /** - * Options constructor. - */ - public function __construct() - { - $this->largeFileMethod = Method::STORE(); - $this->outputStream = fopen('php://output', 'wb'); - } - - public function getComment(): string - { - return $this->comment; - } - - public function setComment(string $comment): void - { - $this->comment = $comment; - } - - public function getLargeFileSize(): int - { - return $this->largeFileSize; - } - - public function setLargeFileSize(int $largeFileSize): void - { - $this->largeFileSize = $largeFileSize; - } - - public function getLargeFileMethod(): Method - { - return $this->largeFileMethod; - } - - public function setLargeFileMethod(Method $largeFileMethod): void - { - $this->largeFileMethod = $largeFileMethod; - } - - public function isSendHttpHeaders(): bool - { - return $this->sendHttpHeaders; - } - - public function setSendHttpHeaders(bool $sendHttpHeaders): void - { - $this->sendHttpHeaders = $sendHttpHeaders; - } - - public function getHttpHeaderCallback(): Callable - { - return $this->httpHeaderCallback; - } - - public function setHttpHeaderCallback(Callable $httpHeaderCallback): void - { - $this->httpHeaderCallback = $httpHeaderCallback; - } - - public function isEnableZip64(): bool - { - return $this->enableZip64; - } - - public function setEnableZip64(bool $enableZip64): void - { - $this->enableZip64 = $enableZip64; - } - - public function isZeroHeader(): bool - { - return $this->zeroHeader; - } - - public function setZeroHeader(bool $zeroHeader): void - { - $this->zeroHeader = $zeroHeader; - } - - public function isFlushOutput(): bool - { - return $this->flushOutput; - } - - public function setFlushOutput(bool $flushOutput): void - { - $this->flushOutput = $flushOutput; - } - - public function isStatFiles(): bool - { - return $this->statFiles; - } - - public function setStatFiles(bool $statFiles): void - { - $this->statFiles = $statFiles; - } - - public function getContentDisposition(): string - { - return $this->contentDisposition; - } - - public function setContentDisposition(string $contentDisposition): void - { - $this->contentDisposition = $contentDisposition; - } - - public function getContentType(): string - { - return $this->contentType; - } - - public function setContentType(string $contentType): void - { - $this->contentType = $contentType; - } - - /** - * @return resource - */ - public function getOutputStream() - { - return $this->outputStream; - } - - /** - * @param resource $outputStream - */ - public function setOutputStream($outputStream): void - { - $this->outputStream = $outputStream; - } - - /** - * @return int - */ - public function getDeflateLevel(): int - { - return $this->deflateLevel; - } - - /** - * @param int $deflateLevel - */ - public function setDeflateLevel(int $deflateLevel): void - { - $this->deflateLevel = $deflateLevel; - } -} diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Option/File.php b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Option/File.php deleted file mode 100644 index 7fd29ea55c5..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Option/File.php +++ /dev/null @@ -1,116 +0,0 @@ -deflateLevel = $this->deflateLevel ?: $archiveOptions->getDeflateLevel(); - $this->time = $this->time ?: new DateTime(); - } - - /** - * @return string - */ - public function getComment(): string - { - return $this->comment; - } - - /** - * @param string $comment - */ - public function setComment(string $comment): void - { - $this->comment = $comment; - } - - /** - * @return Method - */ - public function getMethod(): Method - { - return $this->method ?: Method::DEFLATE(); - } - - /** - * @param Method $method - */ - public function setMethod(Method $method): void - { - $this->method = $method; - } - - /** - * @return int - */ - public function getDeflateLevel(): int - { - return $this->deflateLevel ?: Archive::DEFAULT_DEFLATE_LEVEL; - } - - /** - * @param int $deflateLevel - */ - public function setDeflateLevel(int $deflateLevel): void - { - $this->deflateLevel = $deflateLevel; - } - - /** - * @return DateTime - */ - public function getTime(): DateTime - { - return $this->time; - } - - /** - * @param DateTime $time - */ - public function setTime(DateTime $time): void - { - $this->time = $time; - } - - /** - * @return int - */ - public function getSize(): int - { - return $this->size; - } - - /** - * @param int $size - */ - public function setSize(int $size): void - { - $this->size = $size; - } -} diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Option/Method.php b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Option/Method.php deleted file mode 100644 index bbec84c00df..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/Option/Method.php +++ /dev/null @@ -1,19 +0,0 @@ -stream = $stream; - } - - /** - * Closes the stream and any underlying resources. - * - * @return void - */ - public function close(): void - { - if (is_resource($this->stream)) { - fclose($this->stream); - } - $this->detach(); - } - - /** - * Separates any underlying resources from the stream. - * - * After the stream has been detached, the stream is in an unusable state. - * - * @return resource|null Underlying PHP stream, if any - */ - public function detach() - { - $result = $this->stream; - $this->stream = null; - return $result; - } - - /** - * Reads all data from the stream into a string, from the beginning to end. - * - * This method MUST attempt to seek to the beginning of the stream before - * reading data and read the stream until the end is reached. - * - * Warning: This could attempt to load a large amount of data into memory. - * - * This method MUST NOT raise an exception in order to conform with PHP's - * string casting operations. - * - * @see http://php.net/manual/en/language.oop5.magic.php#object.tostring - * @return string - */ - public function __toString(): string - { - try { - $this->seek(0); - } catch (\RuntimeException $e) {} - return (string) stream_get_contents($this->stream); - } - - /** - * Seek to a position in the stream. - * - * @link http://www.php.net/manual/en/function.fseek.php - * @param int $offset Stream offset - * @param int $whence Specifies how the cursor position will be calculated - * based on the seek offset. Valid values are identical to the built-in - * PHP $whence values for `fseek()`. SEEK_SET: Set position equal to - * offset bytes SEEK_CUR: Set position to current location plus offset - * SEEK_END: Set position to end-of-stream plus offset. - * @throws \RuntimeException on failure. - */ - public function seek($offset, $whence = SEEK_SET): void - { - if (!$this->isSeekable()) { - throw new RuntimeException; - } - if (fseek($this->stream, $offset, $whence) !== 0) { - throw new RuntimeException; - } - } - - /** - * Returns whether or not the stream is seekable. - * - * @return bool - */ - public function isSeekable(): bool - { - return (bool)$this->getMetadata('seekable'); - } - - /** - * Get stream metadata as an associative array or retrieve a specific key. - * - * The keys returned are identical to the keys returned from PHP's - * stream_get_meta_data() function. - * - * @link http://php.net/manual/en/function.stream-get-meta-data.php - * @param string $key Specific metadata to retrieve. - * @return array|mixed|null Returns an associative array if no key is - * provided. Returns a specific key value if a key is provided and the - * value is found, or null if the key is not found. - */ - public function getMetadata($key = null) - { - $metadata = stream_get_meta_data($this->stream); - return $key !== null ? @$metadata[$key] : $metadata; - } - - /** - * Get the size of the stream if known. - * - * @return int|null Returns the size in bytes if known, or null if unknown. - */ - public function getSize(): ?int - { - $stats = fstat($this->stream); - return $stats['size']; - } - - /** - * Returns the current position of the file read/write pointer - * - * @return int Position of the file pointer - * @throws \RuntimeException on error. - */ - public function tell(): int - { - $position = ftell($this->stream); - if ($position === false) { - throw new RuntimeException; - } - return $position; - } - - /** - * Returns true if the stream is at the end of the stream. - * - * @return bool - */ - public function eof(): bool - { - return feof($this->stream); - } - - /** - * Seek to the beginning of the stream. - * - * If the stream is not seekable, this method will raise an exception; - * otherwise, it will perform a seek(0). - * - * @see seek() - * @link http://www.php.net/manual/en/function.fseek.php - * @throws \RuntimeException on failure. - */ - public function rewind(): void - { - $this->seek(0); - } - - /** - * Write data to the stream. - * - * @param string $string The string that is to be written. - * @return int Returns the number of bytes written to the stream. - * @throws \RuntimeException on failure. - */ - public function write($string): int - { - if (!$this->isWritable()) { - throw new RuntimeException; - } - if (fwrite($this->stream, $string) === false) { - throw new RuntimeException; - } - return \mb_strlen($string); - } - - /** - * Returns whether or not the stream is writable. - * - * @return bool - */ - public function isWritable(): bool - { - return preg_match('/[waxc+]/', $this->getMetadata('mode')) === 1; - } - - /** - * Read data from the stream. - * - * @param int $length Read up to $length bytes from the object and return - * them. Fewer than $length bytes may be returned if underlying stream - * call returns fewer bytes. - * @return string Returns the data read from the stream, or an empty string - * if no bytes are available. - * @throws \RuntimeException if an error occurs. - */ - public function read($length): string - { - if (!$this->isReadable()) { - throw new RuntimeException; - } - $result = fread($this->stream, $length); - if ($result === false) { - throw new RuntimeException; - } - return $result; - } - - /** - * Returns whether or not the stream is readable. - * - * @return bool - */ - public function isReadable(): bool - { - return preg_match('/[r+]/', $this->getMetadata('mode')) === 1; - } - - /** - * Returns the remaining contents in a string - * - * @return string - * @throws \RuntimeException if unable to read or an error occurs while - * reading. - */ - public function getContents(): string - { - if (!$this->isReadable()) { - throw new RuntimeException; - } - $result = stream_get_contents($this->stream); - if ($result === false) { - throw new RuntimeException; - } - return $result; - } -} diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/ZipStream.php b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/ZipStream.php deleted file mode 100644 index e83038cf016..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/src/ZipStream.php +++ /dev/null @@ -1,599 +0,0 @@ -addFile('some_file.gif', $data); - * - * * add second file - * $data = file_get_contents('some_file.gif'); - * $zip->addFile('another_file.png', $data); - * - * 3. Finish the zip stream: - * - * $zip->finish(); - * - * You can also add an archive comment, add comments to individual files, - * and adjust the timestamp of files. See the API documentation for each - * method below for additional information. - * - * Example: - * - * // create a new zip stream object - * $zip = new ZipStream('some_files.zip'); - * - * // list of local files - * $files = array('foo.txt', 'bar.jpg'); - * - * // read and add each file to the archive - * foreach ($files as $path) - * $zip->addFile($path, file_get_contents($path)); - * - * // write archive footer to stream - * $zip->finish(); - */ -class ZipStream -{ - /** - * This number corresponds to the ZIP version/OS used (2 bytes) - * From: https://www.iana.org/assignments/media-types/application/zip - * The upper byte (leftmost one) indicates the host system (OS) for the - * file. Software can use this information to determine - * the line record format for text files etc. The current - * mappings are: - * - * 0 - MS-DOS and OS/2 (F.A.T. file systems) - * 1 - Amiga 2 - VAX/VMS - * 3 - *nix 4 - VM/CMS - * 5 - Atari ST 6 - OS/2 H.P.F.S. - * 7 - Macintosh 8 - Z-System - * 9 - CP/M 10 thru 255 - unused - * - * The lower byte (rightmost one) indicates the version number of the - * software used to encode the file. The value/10 - * indicates the major version number, and the value - * mod 10 is the minor version number. - * Here we are using 6 for the OS, indicating OS/2 H.P.F.S. - * to prevent file permissions issues upon extract (see #84) - * 0x603 is 00000110 00000011 in binary, so 6 and 3 - */ - const ZIP_VERSION_MADE_BY = 0x603; - - /** - * The following signatures end with 0x4b50, which in ASCII is PK, - * the initials of the inventor Phil Katz. - * See https://en.wikipedia.org/wiki/Zip_(file_format)#File_headers - */ - const FILE_HEADER_SIGNATURE = 0x04034b50; - const CDR_FILE_SIGNATURE = 0x02014b50; - const CDR_EOF_SIGNATURE = 0x06054b50; - const DATA_DESCRIPTOR_SIGNATURE = 0x08074b50; - const ZIP64_CDR_EOF_SIGNATURE = 0x06064b50; - const ZIP64_CDR_LOCATOR_SIGNATURE = 0x07064b50; - - /** - * Global Options - * - * @var ArchiveOptions - */ - public $opt; - - /** - * @var array - */ - public $files = []; - - /** - * @var Bigint - */ - public $cdr_ofs; - - /** - * @var Bigint - */ - public $ofs; - - /** - * @var bool - */ - protected $need_headers; - - /** - * @var null|String - */ - protected $output_name; - - /** - * Create a new ZipStream object. - * - * Parameters: - * - * @param String $name - Name of output file (optional). - * @param ArchiveOptions $opt - Archive Options - * - * Large File Support: - * - * By default, the method addFileFromPath() will send send files - * larger than 20 megabytes along raw rather than attempting to - * compress them. You can change both the maximum size and the - * compression behavior using the largeFile* options above, with the - * following caveats: - * - * * For "small" files (e.g. files smaller than largeFileSize), the - * memory use can be up to twice that of the actual file. In other - * words, adding a 10 megabyte file to the archive could potentially - * occupy 20 megabytes of memory. - * - * * Enabling compression on large files (e.g. files larger than - * large_file_size) is extremely slow, because ZipStream has to pass - * over the large file once to calculate header information, and then - * again to compress and send the actual data. - * - * Examples: - * - * // create a new zip file named 'foo.zip' - * $zip = new ZipStream('foo.zip'); - * - * // create a new zip file named 'bar.zip' with a comment - * $opt->setComment = 'this is a comment for the zip file.'; - * $zip = new ZipStream('bar.zip', $opt); - * - * Notes: - * - * In order to let this library send HTTP headers, a filename must be given - * _and_ the option `sendHttpHeaders` must be `true`. This behavior is to - * allow software to send its own headers (including the filename), and - * still use this library. - */ - public function __construct(?string $name = null, ?ArchiveOptions $opt = null) - { - $this->opt = $opt ?: new ArchiveOptions(); - - $this->output_name = $name; - $this->need_headers = $name && $this->opt->isSendHttpHeaders(); - - $this->cdr_ofs = new Bigint(); - $this->ofs = new Bigint(); - } - - /** - * addFile - * - * Add a file to the archive. - * - * @param String $name - path of file in archive (including directory). - * @param String $data - contents of file - * @param FileOptions $options - * - * File Options: - * time - Last-modified timestamp (seconds since the epoch) of - * this file. Defaults to the current time. - * comment - Comment related to this file. - * method - Storage method for file ("store" or "deflate") - * - * Examples: - * - * // add a file named 'foo.txt' - * $data = file_get_contents('foo.txt'); - * $zip->addFile('foo.txt', $data); - * - * // add a file named 'bar.jpg' with a comment and a last-modified - * // time of two hours ago - * $data = file_get_contents('bar.jpg'); - * $opt->setTime = time() - 2 * 3600; - * $opt->setComment = 'this is a comment about bar.jpg'; - * $zip->addFile('bar.jpg', $data, $opt); - */ - public function addFile(string $name, string $data, ?FileOptions $options = null): void - { - $options = $options ?: new FileOptions(); - $options->defaultTo($this->opt); - - $file = new File($this, $name, $options); - $file->processData($data); - } - - /** - * addFileFromPath - * - * Add a file at path to the archive. - * - * Note that large files may be compressed differently than smaller - * files; see the "Large File Support" section above for more - * information. - * - * @param String $name - name of file in archive (including directory path). - * @param String $path - path to file on disk (note: paths should be encoded using - * UNIX-style forward slashes -- e.g '/path/to/some/file'). - * @param FileOptions $options - * - * File Options: - * time - Last-modified timestamp (seconds since the epoch) of - * this file. Defaults to the current time. - * comment - Comment related to this file. - * method - Storage method for file ("store" or "deflate") - * - * Examples: - * - * // add a file named 'foo.txt' from the local file '/tmp/foo.txt' - * $zip->addFileFromPath('foo.txt', '/tmp/foo.txt'); - * - * // add a file named 'bigfile.rar' from the local file - * // '/usr/share/bigfile.rar' with a comment and a last-modified - * // time of two hours ago - * $path = '/usr/share/bigfile.rar'; - * $opt->setTime = time() - 2 * 3600; - * $opt->setComment = 'this is a comment about bar.jpg'; - * $zip->addFileFromPath('bigfile.rar', $path, $opt); - * - * @return void - * @throws \ZipStream\Exception\FileNotFoundException - * @throws \ZipStream\Exception\FileNotReadableException - */ - public function addFileFromPath(string $name, string $path, ?FileOptions $options = null): void - { - $options = $options ?: new FileOptions(); - $options->defaultTo($this->opt); - - $file = new File($this, $name, $options); - $file->processPath($path); - } - - /** - * addFileFromStream - * - * Add an open stream to the archive. - * - * @param String $name - path of file in archive (including directory). - * @param resource $stream - contents of file as a stream resource - * @param FileOptions $options - * - * File Options: - * time - Last-modified timestamp (seconds since the epoch) of - * this file. Defaults to the current time. - * comment - Comment related to this file. - * - * Examples: - * - * // create a temporary file stream and write text to it - * $fp = tmpfile(); - * fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); - * - * // add a file named 'streamfile.txt' from the content of the stream - * $x->addFileFromStream('streamfile.txt', $fp); - * - * @return void - */ - public function addFileFromStream(string $name, $stream, ?FileOptions $options = null): void - { - $options = $options ?: new FileOptions(); - $options->defaultTo($this->opt); - - $file = new File($this, $name, $options); - $file->processStream(new DeflateStream($stream)); - } - - /** - * addFileFromPsr7Stream - * - * Add an open stream to the archive. - * - * @param String $name - path of file in archive (including directory). - * @param StreamInterface $stream - contents of file as a stream resource - * @param FileOptions $options - * - * File Options: - * time - Last-modified timestamp (seconds since the epoch) of - * this file. Defaults to the current time. - * comment - Comment related to this file. - * - * Examples: - * - * // create a temporary file stream and write text to it - * $fp = tmpfile(); - * fwrite($fp, 'The quick brown fox jumped over the lazy dog.'); - * - * // add a file named 'streamfile.txt' from the content of the stream - * $x->addFileFromPsr7Stream('streamfile.txt', $fp); - * - * @return void - */ - public function addFileFromPsr7Stream( - string $name, - StreamInterface $stream, - ?FileOptions $options = null - ): void { - $options = $options ?: new FileOptions(); - $options->defaultTo($this->opt); - - $file = new File($this, $name, $options); - $file->processStream($stream); - } - - /** - * finish - * - * Write zip footer to stream. - * - * Example: - * - * // add a list of files to the archive - * $files = array('foo.txt', 'bar.jpg'); - * foreach ($files as $path) - * $zip->addFile($path, file_get_contents($path)); - * - * // write footer to stream - * $zip->finish(); - * @return void - * - * @throws OverflowException - */ - public function finish(): void - { - // add trailing cdr file records - foreach ($this->files as $cdrFile) { - $this->send($cdrFile); - $this->cdr_ofs = $this->cdr_ofs->add(Bigint::init(strlen($cdrFile))); - } - - // Add 64bit headers (if applicable) - if (count($this->files) >= 0xFFFF || - $this->cdr_ofs->isOver32() || - $this->ofs->isOver32()) { - if (!$this->opt->isEnableZip64()) { - throw new OverflowException(); - } - - $this->addCdr64Eof(); - $this->addCdr64Locator(); - } - - // add trailing cdr eof record - $this->addCdrEof(); - - // The End - $this->clear(); - } - - /** - * Send ZIP64 CDR EOF (Central Directory Record End-of-File) record. - * - * @return void - */ - protected function addCdr64Eof(): void - { - $num_files = count($this->files); - $cdr_length = $this->cdr_ofs; - $cdr_offset = $this->ofs; - - $fields = [ - ['V', static::ZIP64_CDR_EOF_SIGNATURE], // ZIP64 end of central file header signature - ['P', 44], // Length of data below this header (length of block - 12) = 44 - ['v', static::ZIP_VERSION_MADE_BY], // Made by version - ['v', Version::ZIP64], // Extract by version - ['V', 0x00], // disk number - ['V', 0x00], // no of disks - ['P', $num_files], // no of entries on disk - ['P', $num_files], // no of entries in cdr - ['P', $cdr_length], // CDR size - ['P', $cdr_offset], // CDR offset - ]; - - $ret = static::packFields($fields); - $this->send($ret); - } - - /** - * Create a format string and argument list for pack(), then call - * pack() and return the result. - * - * @param array $fields - * @return string - */ - public static function packFields(array $fields): string - { - $fmt = ''; - $args = []; - - // populate format string and argument list - foreach ($fields as [$format, $value]) { - if ($format === 'P') { - $fmt .= 'VV'; - if ($value instanceof Bigint) { - $args[] = $value->getLow32(); - $args[] = $value->getHigh32(); - } else { - $args[] = $value; - $args[] = 0; - } - } else { - if ($value instanceof Bigint) { - $value = $value->getLow32(); - } - $fmt .= $format; - $args[] = $value; - } - } - - // prepend format string to argument list - array_unshift($args, $fmt); - - // build output string from header and compressed data - return pack(...$args); - } - - /** - * Send string, sending HTTP headers if necessary. - * Flush output after write if configure option is set. - * - * @param String $str - * @return void - */ - public function send(string $str): void - { - if ($this->need_headers) { - $this->sendHttpHeaders(); - } - $this->need_headers = false; - - fwrite($this->opt->getOutputStream(), $str); - - if ($this->opt->isFlushOutput()) { - // flush output buffer if it is on and flushable - $status = ob_get_status(); - if (isset($status['flags']) && ($status['flags'] & PHP_OUTPUT_HANDLER_FLUSHABLE)) { - ob_flush(); - } - - // Flush system buffers after flushing userspace output buffer - flush(); - } - } - - /** - * Send HTTP headers for this stream. - * - * @return void - */ - protected function sendHttpHeaders(): void - { - // grab content disposition - $disposition = $this->opt->getContentDisposition(); - - if ($this->output_name) { - // Various different browsers dislike various characters here. Strip them all for safety. - $safe_output = trim(str_replace(['"', "'", '\\', ';', "\n", "\r"], '', $this->output_name)); - - // Check if we need to UTF-8 encode the filename - $urlencoded = rawurlencode($safe_output); - $disposition .= "; filename*=UTF-8''{$urlencoded}"; - } - - $headers = array( - 'Content-Type' => $this->opt->getContentType(), - 'Content-Disposition' => $disposition, - 'Pragma' => 'public', - 'Cache-Control' => 'public, must-revalidate', - 'Content-Transfer-Encoding' => 'binary' - ); - - $call = $this->opt->getHttpHeaderCallback(); - foreach ($headers as $key => $val) { - $call("$key: $val"); - } - } - - /** - * Send ZIP64 CDR Locator (Central Directory Record Locator) record. - * - * @return void - */ - protected function addCdr64Locator(): void - { - $cdr_offset = $this->ofs->add($this->cdr_ofs); - - $fields = [ - ['V', static::ZIP64_CDR_LOCATOR_SIGNATURE], // ZIP64 end of central file header signature - ['V', 0x00], // Disc number containing CDR64EOF - ['P', $cdr_offset], // CDR offset - ['V', 1], // Total number of disks - ]; - - $ret = static::packFields($fields); - $this->send($ret); - } - - /** - * Send CDR EOF (Central Directory Record End-of-File) record. - * - * @return void - */ - protected function addCdrEof(): void - { - $num_files = count($this->files); - $cdr_length = $this->cdr_ofs; - $cdr_offset = $this->ofs; - - // grab comment (if specified) - $comment = $this->opt->getComment(); - - $fields = [ - ['V', static::CDR_EOF_SIGNATURE], // end of central file header signature - ['v', 0x00], // disk number - ['v', 0x00], // no of disks - ['v', min($num_files, 0xFFFF)], // no of entries on disk - ['v', min($num_files, 0xFFFF)], // no of entries in cdr - ['V', $cdr_length->getLowFF()], // CDR size - ['V', $cdr_offset->getLowFF()], // CDR offset - ['v', strlen($comment)], // Zip Comment size - ]; - - $ret = static::packFields($fields) . $comment; - $this->send($ret); - } - - /** - * Clear all internal variables. Note that the stream object is not - * usable after this. - * - * @return void - */ - protected function clear(): void - { - $this->files = []; - $this->ofs = new Bigint(); - $this->cdr_ofs = new Bigint(); - $this->opt = new ArchiveOptions(); - } - - /** - * Is this file larger than large_file_size? - * - * @param string $path - * @return bool - */ - public function isLargeFile(string $path): bool - { - if (!$this->opt->isStatFiles()) { - return false; - } - $stat = stat($path); - return $stat['size'] > $this->opt->getLargeFileSize(); - } - - /** - * Save file attributes for trailing CDR record. - * - * @param File $file - * @return void - */ - public function addToCdr(File $file): void - { - $file->ofs = $this->ofs; - $this->ofs = $this->ofs->add($file->getTotalLength()); - $this->files[] = $file->getCdrFile(); - } -} diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/test/BigintTest.php b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/test/BigintTest.php deleted file mode 100644 index ac9c7c28ba0..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/test/BigintTest.php +++ /dev/null @@ -1,65 +0,0 @@ -assertSame('0x0000000012345678', $bigint->getHex64()); - $this->assertSame(0x12345678, $bigint->getLow32()); - $this->assertSame(0, $bigint->getHigh32()); - } - - public function testConstructLarge(): void - { - $bigint = new Bigint(0x87654321); - $this->assertSame('0x0000000087654321', $bigint->getHex64()); - $this->assertSame('87654321', bin2hex(pack('N', $bigint->getLow32()))); - $this->assertSame(0, $bigint->getHigh32()); - } - - public function testAddSmallValue(): void - { - $bigint = new Bigint(1); - $bigint = $bigint->add(Bigint::init(2)); - $this->assertSame(3, $bigint->getLow32()); - $this->assertFalse($bigint->isOver32()); - $this->assertTrue($bigint->isOver32(true)); - $this->assertSame($bigint->getLowFF(), (float)$bigint->getLow32()); - $this->assertSame($bigint->getLowFF(true), (float)0xFFFFFFFF); - } - - public function testAddWithOverflowAtLowestByte(): void - { - $bigint = new Bigint(0xFF); - $bigint = $bigint->add(Bigint::init(0x01)); - $this->assertSame(0x100, $bigint->getLow32()); - } - - public function testAddWithOverflowAtInteger32(): void - { - $bigint = new Bigint(0xFFFFFFFE); - $this->assertFalse($bigint->isOver32()); - $bigint = $bigint->add(Bigint::init(0x01)); - $this->assertTrue($bigint->isOver32()); - $bigint = $bigint->add(Bigint::init(0x01)); - $this->assertSame('0x0000000100000000', $bigint->getHex64()); - $this->assertTrue($bigint->isOver32()); - $this->assertSame((float)0xFFFFFFFF, $bigint->getLowFF()); - } - - public function testAddWithOverflowAtInteger64(): void - { - $bigint = Bigint::fromLowHigh(0xFFFFFFFF, 0xFFFFFFFF); - $this->assertSame('0xFFFFFFFFFFFFFFFF', $bigint->getHex64()); - $this->expectException(OverflowException::class); - $bigint->add(Bigint::init(1)); - } -} diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/test/ZipStreamTest.php b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/test/ZipStreamTest.php deleted file mode 100644 index 6549b0b4691..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/test/ZipStreamTest.php +++ /dev/null @@ -1,586 +0,0 @@ -expectException(\ZipStream\Exception\FileNotFoundException::class); - // Get ZipStream Object - $zip = new ZipStream(); - - // Trigger error by adding a file which doesn't exist - $zip->addFileFromPath('foobar.php', '/foo/bar/foobar.php'); - } - - public function testFileNotReadableException(): void - { - // create new virtual filesystem - $root = vfsStream::setup('vfs'); - // create a virtual file with no permissions - $file = vfsStream::newFile('foo.txt', 0000)->at($root)->setContent('bar'); - $zip = new ZipStream(); - $this->expectException(\ZipStream\Exception\FileNotReadableException::class); - $zip->addFileFromPath('foo.txt', $file->url()); - } - - public function testDostime(): void - { - // Allows testing of protected method - $class = new \ReflectionClass(File::class); - $method = $class->getMethod('dostime'); - $method->setAccessible(true); - - $this->assertSame($method->invoke(null, 1416246368), 1165069764); - - // January 1 1980 - DOS Epoch. - $this->assertSame($method->invoke(null, 315532800), 2162688); - - // January 1 1970 -> January 1 1980 due to minimum DOS Epoch. @todo Throw Exception? - $this->assertSame($method->invoke(null, 0), 2162688); - } - - public function testAddFile(): void - { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - $zip->addFile('sample.txt', 'Sample String Data'); - $zip->addFile('test/sample.txt', 'More Simple Sample Data'); - - $zip->finish(); - fclose($stream); - - $tmpDir = $this->validateAndExtractZip($tmp); - - $files = $this->getRecursiveFileList($tmpDir); - $this->assertEquals(['sample.txt', 'test/sample.txt'], $files); - - $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); - $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); - } - - /** - * @return array - */ - protected function getTmpFileStream(): array - { - $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); - $stream = fopen($tmp, 'wb+'); - - return array($tmp, $stream); - } - - /** - * @param string $tmp - * @return string - */ - protected function validateAndExtractZip($tmp): string - { - $tmpDir = $this->getTmpDir(); - - $zipArch = new \ZipArchive; - $res = $zipArch->open($tmp); - - if ($res !== true) { - $this->fail("Failed to open {$tmp}. Code: $res"); - - return $tmpDir; - } - - $this->assertEquals(0, $zipArch->status); - $this->assertEquals(0, $zipArch->statusSys); - - $zipArch->extractTo($tmpDir); - $zipArch->close(); - - return $tmpDir; - } - - protected function getTmpDir(): string - { - $tmp = tempnam(sys_get_temp_dir(), 'zipstreamtest'); - unlink($tmp); - mkdir($tmp) or $this->fail('Failed to make directory'); - - return $tmp; - } - - /** - * @param string $path - * @return string[] - */ - protected function getRecursiveFileList(string $path): array - { - $data = array(); - $path = (string)realpath($path); - $files = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($path)); - - $pathLen = strlen($path); - foreach ($files as $file) { - $filePath = $file->getRealPath(); - if (!is_dir($filePath)) { - $data[] = substr($filePath, $pathLen + 1); - } - } - - sort($data); - - return $data; - } - - public function testAddFileUtf8NameComment(): void - { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - $name = 'árvíztűrő tükörfúrógép.txt'; - $content = 'Sample String Data'; - $comment = - 'Filename has every special characters ' . - 'from Hungarian language in lowercase. ' . - 'In uppercase: ÁÍŰŐÜÖÚÓÉ'; - - $fileOptions = new FileOptions(); - $fileOptions->setComment($comment); - - $zip->addFile($name, $content, $fileOptions); - $zip->finish(); - fclose($stream); - - $tmpDir = $this->validateAndExtractZip($tmp); - - $files = $this->getRecursiveFileList($tmpDir); - $this->assertEquals(array($name), $files); - $this->assertStringEqualsFile($tmpDir . '/' . $name, $content); - - $zipArch = new \ZipArchive(); - $zipArch->open($tmp); - $this->assertEquals($comment, $zipArch->getCommentName($name)); - } - - public function testAddFileUtf8NameNonUtfComment(): void - { - $this->expectException(\ZipStream\Exception\EncodingException::class); - - $stream = $this->getTmpFileStream()[1]; - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - $name = 'á.txt'; - $content = 'any'; - $comment = 'á'; - - $fileOptions = new FileOptions(); - $fileOptions->setComment(mb_convert_encoding($comment, 'ISO-8859-2', 'UTF-8')); - - $zip->addFile($name, $content, $fileOptions); - } - - public function testAddFileNonUtf8NameUtfComment(): void - { - $this->expectException(\ZipStream\Exception\EncodingException::class); - - $stream = $this->getTmpFileStream()[1]; - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - $name = 'á.txt'; - $content = 'any'; - $comment = 'á'; - - $fileOptions = new FileOptions(); - $fileOptions->setComment($comment); - - $zip->addFile(mb_convert_encoding($name, 'ISO-8859-2', 'UTF-8'), $content, $fileOptions); - } - - public function testAddFileWithStorageMethod(): void - { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - $fileOptions = new FileOptions(); - $fileOptions->setMethod(Method::STORE()); - - $zip->addFile('sample.txt', 'Sample String Data', $fileOptions); - $zip->addFile('test/sample.txt', 'More Simple Sample Data'); - $zip->finish(); - fclose($stream); - - $zipArch = new \ZipArchive(); - $zipArch->open($tmp); - - $sample1 = $zipArch->statName('sample.txt'); - $sample12 = $zipArch->statName('test/sample.txt'); - $this->assertEquals($sample1['comp_method'], Method::STORE); - $this->assertEquals($sample12['comp_method'], Method::DEFLATE); - - $zipArch->close(); - } - - public function testDecompressFileWithMacUnarchiver(): void - { - if (!file_exists(self::OSX_ARCHIVE_UTILITY)) { - $this->markTestSkipped('The Mac OSX Archive Utility is not available.'); - } - - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - $folder = uniqid('', true); - - $zip->addFile($folder . '/sample.txt', 'Sample Data'); - $zip->finish(); - fclose($stream); - - exec(escapeshellarg(self::OSX_ARCHIVE_UTILITY) . ' ' . escapeshellarg($tmp), $output, $returnStatus); - - $this->assertEquals(0, $returnStatus); - $this->assertCount(0, $output); - - $this->assertFileExists(dirname($tmp) . '/' . $folder . '/sample.txt'); - $this->assertStringEqualsFile(dirname($tmp) . '/' . $folder . '/sample.txt', 'Sample Data'); - } - - public function testAddFileFromPath(): void - { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - [$tmpExample, $streamExample] = $this->getTmpFileStream(); - fwrite($streamExample, 'Sample String Data'); - fclose($streamExample); - $zip->addFileFromPath('sample.txt', $tmpExample); - - [$tmpExample, $streamExample] = $this->getTmpFileStream(); - fwrite($streamExample, 'More Simple Sample Data'); - fclose($streamExample); - $zip->addFileFromPath('test/sample.txt', $tmpExample); - - $zip->finish(); - fclose($stream); - - $tmpDir = $this->validateAndExtractZip($tmp); - - $files = $this->getRecursiveFileList($tmpDir); - $this->assertEquals(array('sample.txt', 'test/sample.txt'), $files); - - $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); - $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); - } - - public function testAddFileFromPathWithStorageMethod(): void - { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - $fileOptions = new FileOptions(); - $fileOptions->setMethod(Method::STORE()); - - [$tmpExample, $streamExample] = $this->getTmpFileStream(); - fwrite($streamExample, 'Sample String Data'); - fclose($streamExample); - $zip->addFileFromPath('sample.txt', $tmpExample, $fileOptions); - - [$tmpExample, $streamExample] = $this->getTmpFileStream(); - fwrite($streamExample, 'More Simple Sample Data'); - fclose($streamExample); - $zip->addFileFromPath('test/sample.txt', $tmpExample); - - $zip->finish(); - fclose($stream); - - $zipArch = new \ZipArchive(); - $zipArch->open($tmp); - - $sample1 = $zipArch->statName('sample.txt'); - $this->assertEquals(Method::STORE, $sample1['comp_method']); - - $sample2 = $zipArch->statName('test/sample.txt'); - $this->assertEquals(Method::DEFLATE, $sample2['comp_method']); - - $zipArch->close(); - } - - public function testAddLargeFileFromPath(): void - { - $methods = [Method::DEFLATE(), Method::STORE()]; - $falseTrue = [false, true]; - foreach ($methods as $method) { - foreach ($falseTrue as $zeroHeader) { - foreach ($falseTrue as $zip64) { - if ($zeroHeader && $method->equals(Method::DEFLATE())) { - continue; - } - $this->addLargeFileFileFromPath($method, $zeroHeader, $zip64); - } - } - } - } - - protected function addLargeFileFileFromPath($method, $zeroHeader, $zip64): void - { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - $options->setLargeFileMethod($method); - $options->setLargeFileSize(5); - $options->setZeroHeader($zeroHeader); - $options->setEnableZip64($zip64); - - $zip = new ZipStream(null, $options); - - [$tmpExample, $streamExample] = $this->getTmpFileStream(); - for ($i = 0; $i <= 10000; $i++) { - fwrite($streamExample, sha1((string)$i)); - if ($i % 100 === 0) { - fwrite($streamExample, "\n"); - } - } - fclose($streamExample); - $shaExample = sha1_file($tmpExample); - $zip->addFileFromPath('sample.txt', $tmpExample); - unlink($tmpExample); - - $zip->finish(); - fclose($stream); - - $tmpDir = $this->validateAndExtractZip($tmp); - - $files = $this->getRecursiveFileList($tmpDir); - $this->assertEquals(array('sample.txt'), $files); - - $this->assertEquals(sha1_file($tmpDir . '/sample.txt'), $shaExample, "SHA-1 Mismatch Method: {$method}"); - } - - public function testAddFileFromStream(): void - { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - // In this test we can't use temporary stream to feed data - // because zlib.deflate filter gives empty string before PHP 7 - // it works fine with file stream - $streamExample = fopen(__FILE__, 'rb'); - $zip->addFileFromStream('sample.txt', $streamExample); -// fclose($streamExample); - - $fileOptions = new FileOptions(); - $fileOptions->setMethod(Method::STORE()); - - $streamExample2 = fopen('php://temp', 'wb+'); - fwrite($streamExample2, 'More Simple Sample Data'); - rewind($streamExample2); // move the pointer back to the beginning of file. - $zip->addFileFromStream('test/sample.txt', $streamExample2, $fileOptions); -// fclose($streamExample2); - - $zip->finish(); - fclose($stream); - - $tmpDir = $this->validateAndExtractZip($tmp); - - $files = $this->getRecursiveFileList($tmpDir); - $this->assertEquals(array('sample.txt', 'test/sample.txt'), $files); - - $this->assertStringEqualsFile(__FILE__, file_get_contents($tmpDir . '/sample.txt')); - $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); - } - - public function testAddFileFromStreamWithStorageMethod(): void - { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - $fileOptions = new FileOptions(); - $fileOptions->setMethod(Method::STORE()); - - $streamExample = fopen('php://temp', 'wb+'); - fwrite($streamExample, 'Sample String Data'); - rewind($streamExample); // move the pointer back to the beginning of file. - $zip->addFileFromStream('sample.txt', $streamExample, $fileOptions); -// fclose($streamExample); - - $streamExample2 = fopen('php://temp', 'bw+'); - fwrite($streamExample2, 'More Simple Sample Data'); - rewind($streamExample2); // move the pointer back to the beginning of file. - $zip->addFileFromStream('test/sample.txt', $streamExample2); -// fclose($streamExample2); - - $zip->finish(); - fclose($stream); - - $zipArch = new \ZipArchive(); - $zipArch->open($tmp); - - $sample1 = $zipArch->statName('sample.txt'); - $this->assertEquals(Method::STORE, $sample1['comp_method']); - - $sample2 = $zipArch->statName('test/sample.txt'); - $this->assertEquals(Method::DEFLATE, $sample2['comp_method']); - - $zipArch->close(); - } - - public function testAddFileFromPsr7Stream(): void - { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - $body = 'Sample String Data'; - $response = new Response(200, [], $body); - - $fileOptions = new FileOptions(); - $fileOptions->setMethod(Method::STORE()); - - $zip->addFileFromPsr7Stream('sample.json', $response->getBody(), $fileOptions); - $zip->finish(); - fclose($stream); - - $tmpDir = $this->validateAndExtractZip($tmp); - - $files = $this->getRecursiveFileList($tmpDir); - $this->assertEquals(array('sample.json'), $files); - $this->assertStringEqualsFile($tmpDir . '/sample.json', $body); - } - - public function testAddFileFromPsr7StreamWithFileSizeSet(): void - { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - - $zip = new ZipStream(null, $options); - - $body = 'Sample String Data'; - $fileSize = strlen($body); - // Add fake padding - $fakePadding = "\0\0\0\0\0\0"; - $response = new Response(200, [], $body . $fakePadding); - - $fileOptions = new FileOptions(); - $fileOptions->setMethod(Method::STORE()); - $fileOptions->setSize($fileSize); - $zip->addFileFromPsr7Stream('sample.json', $response->getBody(), $fileOptions); - $zip->finish(); - fclose($stream); - - $tmpDir = $this->validateAndExtractZip($tmp); - - $files = $this->getRecursiveFileList($tmpDir); - $this->assertEquals(array('sample.json'), $files); - $this->assertStringEqualsFile($tmpDir . '/sample.json', $body); - } - - public function testCreateArchiveWithFlushOptionSet(): void - { - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - $options->setFlushOutput(true); - - $zip = new ZipStream(null, $options); - - $zip->addFile('sample.txt', 'Sample String Data'); - $zip->addFile('test/sample.txt', 'More Simple Sample Data'); - - $zip->finish(); - fclose($stream); - - $tmpDir = $this->validateAndExtractZip($tmp); - - $files = $this->getRecursiveFileList($tmpDir); - $this->assertEquals(['sample.txt', 'test/sample.txt'], $files); - - $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); - $this->assertStringEqualsFile($tmpDir . '/test/sample.txt', 'More Simple Sample Data'); - } - - public function testCreateArchiveWithOutputBufferingOffAndFlushOptionSet(): void - { - // WORKAROUND (1/2): remove phpunit's output buffer in order to run test without any buffering - ob_end_flush(); - $this->assertEquals(0, ob_get_level()); - - [$tmp, $stream] = $this->getTmpFileStream(); - - $options = new ArchiveOptions(); - $options->setOutputStream($stream); - $options->setFlushOutput(true); - - $zip = new ZipStream(null, $options); - - $zip->addFile('sample.txt', 'Sample String Data'); - - $zip->finish(); - fclose($stream); - - $tmpDir = $this->validateAndExtractZip($tmp); - $this->assertStringEqualsFile($tmpDir . '/sample.txt', 'Sample String Data'); - - // WORKAROUND (2/2): add back output buffering so that PHPUnit doesn't complain that it is missing - ob_start(); - } -} diff --git a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/test/bootstrap.php b/lib/phpspreadsheet/vendor/maennchen/zipstream-php/test/bootstrap.php deleted file mode 100644 index b43c9bd6cbb..00000000000 --- a/lib/phpspreadsheet/vendor/maennchen/zipstream-php/test/bootstrap.php +++ /dev/null @@ -1,6 +0,0 @@ -setOutputStream(fopen('php://memory', 'wb')); - $fileOpt->setTime(clone $expectedTime); - - $zip = new ZipStream(null, $archiveOpt); - - $zip->addFile('sample.txt', 'Sample', $fileOpt); - - $zip->finish(); - - $this->assertEquals($expectedTime, $fileOpt->getTime()); - } -} diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/infection.json.dist b/lib/phpspreadsheet/vendor/markbaker/matrix/infection.json.dist deleted file mode 100644 index eddaa70a2ef..00000000000 --- a/lib/phpspreadsheet/vendor/markbaker/matrix/infection.json.dist +++ /dev/null @@ -1,17 +0,0 @@ -{ - "timeout": 1, - "source": { - "directories": [ - "classes\/src" - ] - }, - "logs": { - "text": "build/infection/text.log", - "summary": "build/infection/summary.log", - "debug": "build/infection/debug.log", - "perMutator": "build/infection/perMutator.md" - }, - "mutators": { - "@default": true - } -} diff --git a/lib/phpspreadsheet/vendor/markbaker/matrix/phpstan.neon b/lib/phpspreadsheet/vendor/markbaker/matrix/phpstan.neon deleted file mode 100644 index 1607abd7c0c..00000000000 --- a/lib/phpspreadsheet/vendor/markbaker/matrix/phpstan.neon +++ /dev/null @@ -1,5 +0,0 @@ -parameters: - ignoreErrors: - - '#Property [A-Za-z\\]+::\$[A-Za-z]+ has no typehint specified#' - - '#Method [A-Za-z\\]+::[A-Za-z]+\(\) has no return typehint specified#' - checkMissingIterableValueType: false diff --git a/lib/phpspreadsheet/vendor/myclabs/php-enum/LICENSE b/lib/phpspreadsheet/vendor/myclabs/php-enum/LICENSE deleted file mode 100644 index 2a8cf22ec64..00000000000 --- a/lib/phpspreadsheet/vendor/myclabs/php-enum/LICENSE +++ /dev/null @@ -1,18 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2015 My C-Labs - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and -associated documentation files (the "Software"), to deal in the Software without restriction, -including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, -and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial -portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT -NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, -WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE -SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/lib/phpspreadsheet/vendor/myclabs/php-enum/README.md b/lib/phpspreadsheet/vendor/myclabs/php-enum/README.md deleted file mode 100644 index bf1c91dd553..00000000000 --- a/lib/phpspreadsheet/vendor/myclabs/php-enum/README.md +++ /dev/null @@ -1,134 +0,0 @@ -# PHP Enum implementation inspired from SplEnum - -[![Build Status](https://travis-ci.org/myclabs/php-enum.png?branch=master)](https://travis-ci.org/myclabs/php-enum) -[![Latest Stable Version](https://poser.pugx.org/myclabs/php-enum/version.png)](https://packagist.org/packages/myclabs/php-enum) -[![Total Downloads](https://poser.pugx.org/myclabs/php-enum/downloads.png)](https://packagist.org/packages/myclabs/php-enum) -[![psalm](https://shepherd.dev/github/myclabs/php-enum/coverage.svg)](https://shepherd.dev/github/myclabs/php-enum) - -Maintenance for this project is [supported via Tidelift](https://tidelift.com/subscription/pkg/packagist-myclabs-php-enum?utm_source=packagist-myclabs-php-enum&utm_medium=referral&utm_campaign=readme). - -## Why? - -First, and mainly, `SplEnum` is not integrated to PHP, you have to install the extension separately. - -Using an enum instead of class constants provides the following advantages: - -- You can use an enum as a parameter type: `function setAction(Action $action) {` -- You can use an enum as a return type: `function getAction() : Action {` -- You can enrich the enum with methods (e.g. `format`, `parse`, …) -- You can extend the enum to add new values (make your enum `final` to prevent it) -- You can get a list of all the possible values (see below) - -This Enum class is not intended to replace class constants, but only to be used when it makes sense. - -## Installation - -``` -composer require myclabs/php-enum -``` - -## Declaration - -```php -use MyCLabs\Enum\Enum; - -/** - * Action enum - */ -class Action extends Enum -{ - private const VIEW = 'view'; - private const EDIT = 'edit'; -} -``` - -## Usage - -```php -$action = Action::VIEW(); - -// or with a dynamic key: -$action = Action::$key(); -// or with a dynamic value: -$action = new Action($value); -``` - -As you can see, static methods are automatically implemented to provide quick access to an enum value. - -One advantage over using class constants is to be able to use an enum as a parameter type: - -```php -function setAction(Action $action) { - // ... -} -``` - -## Documentation - -- `__construct()` The constructor checks that the value exist in the enum -- `__toString()` You can `echo $myValue`, it will display the enum value (value of the constant) -- `getValue()` Returns the current value of the enum -- `getKey()` Returns the key of the current value on Enum -- `equals()` Tests whether enum instances are equal (returns `true` if enum values are equal, `false` otherwise) - -Static methods: - -- `toArray()` method Returns all possible values as an array (constant name in key, constant value in value) -- `keys()` Returns the names (keys) of all constants in the Enum class -- `values()` Returns instances of the Enum class of all Enum constants (constant name in key, Enum instance in value) -- `isValid()` Check if tested value is valid on enum set -- `isValidKey()` Check if tested key is valid on enum set -- `search()` Return key for searched value - -### Static methods - -```php -class Action extends Enum -{ - private const VIEW = 'view'; - private const EDIT = 'edit'; -} - -// Static method: -$action = Action::VIEW(); -$action = Action::EDIT(); -``` - -Static method helpers are implemented using [`__callStatic()`](http://www.php.net/manual/en/language.oop5.overloading.php#object.callstatic). - -If you care about IDE autocompletion, you can either implement the static methods yourself: - -```php -class Action extends Enum -{ - private const VIEW = 'view'; - - /** - * @return Action - */ - public static function VIEW() { - return new Action(self::VIEW); - } -} -``` - -or you can use phpdoc (this is supported in PhpStorm for example): - -```php -/** - * @method static Action VIEW() - * @method static Action EDIT() - */ -class Action extends Enum -{ - private const VIEW = 'view'; - private const EDIT = 'edit'; -} -``` - -## Related projects - -- [Doctrine enum mapping](https://github.com/acelaya/doctrine-enum-type) -- [Symfony ParamConverter integration](https://github.com/Ex3v/MyCLabsEnumParamConverter) -- [PHPStan integration](https://github.com/timeweb/phpstan-enum) -- [Yii2 enum mapping](https://github.com/KartaviK/yii2-enum) diff --git a/lib/phpspreadsheet/vendor/myclabs/php-enum/SECURITY.md b/lib/phpspreadsheet/vendor/myclabs/php-enum/SECURITY.md deleted file mode 100644 index 84fd4e32021..00000000000 --- a/lib/phpspreadsheet/vendor/myclabs/php-enum/SECURITY.md +++ /dev/null @@ -1,11 +0,0 @@ -# Security Policy - -## Supported Versions - -Only the latest stable release is supported. - -## Reporting a Vulnerability - -To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). - -Tidelift will coordinate the fix and disclosure. diff --git a/lib/phpspreadsheet/vendor/myclabs/php-enum/composer.json b/lib/phpspreadsheet/vendor/myclabs/php-enum/composer.json deleted file mode 100644 index 6861a5ce5fb..00000000000 --- a/lib/phpspreadsheet/vendor/myclabs/php-enum/composer.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "myclabs/php-enum", - "type": "library", - "description": "PHP Enum implementation", - "keywords": ["enum"], - "homepage": "http://github.com/myclabs/php-enum", - "license": "MIT", - "authors": [ - { - "name": "PHP Enum contributors", - "homepage": "https://github.com/myclabs/php-enum/graphs/contributors" - } - ], - "autoload": { - "psr-4": { - "MyCLabs\\Enum\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "MyCLabs\\Tests\\Enum\\": "tests/" - } - }, - "require": { - "php": ">=7.1", - "ext-json": "*" - }, - "require-dev": { - "phpunit/phpunit": "^7", - "squizlabs/php_codesniffer": "1.*", - "vimeo/psalm": "^3.8" - } -} diff --git a/lib/phpspreadsheet/vendor/myclabs/php-enum/psalm.xml b/lib/phpspreadsheet/vendor/myclabs/php-enum/psalm.xml deleted file mode 100644 index b07e9294a43..00000000000 --- a/lib/phpspreadsheet/vendor/myclabs/php-enum/psalm.xml +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - - - - - - - - - diff --git a/lib/phpspreadsheet/vendor/myclabs/php-enum/src/Enum.php b/lib/phpspreadsheet/vendor/myclabs/php-enum/src/Enum.php deleted file mode 100644 index b8b93277e2c..00000000000 --- a/lib/phpspreadsheet/vendor/myclabs/php-enum/src/Enum.php +++ /dev/null @@ -1,250 +0,0 @@ - - * @author Daniel Costa - * @author Mirosław Filip - * - * @psalm-template T - * @psalm-immutable - */ -abstract class Enum implements \JsonSerializable -{ - /** - * Enum value - * - * @var mixed - * @psalm-var T - */ - protected $value; - - /** - * Store existing constants in a static cache per object. - * - * - * @var array - * @psalm-var array> - */ - protected static $cache = []; - - /** - * Cache of instances of the Enum class - * - * @var array - * @psalm-var array> - */ - protected static $instances = []; - - /** - * Creates a new value of some type - * - * @psalm-pure - * @param mixed $value - * - * @psalm-param static|T $value - * @throws \UnexpectedValueException if incompatible type is given. - */ - public function __construct($value) - { - if ($value instanceof static) { - /** @psalm-var T */ - $value = $value->getValue(); - } - - if (!$this->isValid($value)) { - /** @psalm-suppress InvalidCast */ - throw new \UnexpectedValueException("Value '$value' is not part of the enum " . static::class); - } - - /** @psalm-var T */ - $this->value = $value; - } - - /** - * @psalm-pure - * @return mixed - * @psalm-return T - */ - public function getValue() - { - return $this->value; - } - - /** - * Returns the enum key (i.e. the constant name). - * - * @psalm-pure - * @return mixed - */ - public function getKey() - { - return static::search($this->value); - } - - /** - * @psalm-pure - * @psalm-suppress InvalidCast - * @return string - */ - public function __toString() - { - return (string)$this->value; - } - - /** - * Determines if Enum should be considered equal with the variable passed as a parameter. - * Returns false if an argument is an object of different class or not an object. - * - * This method is final, for more information read https://github.com/myclabs/php-enum/issues/4 - * - * @psalm-pure - * @psalm-param mixed $variable - * @return bool - */ - final public function equals($variable = null): bool - { - return $variable instanceof self - && $this->getValue() === $variable->getValue() - && static::class === \get_class($variable); - } - - /** - * Returns the names (keys) of all constants in the Enum class - * - * @psalm-pure - * @psalm-return list - * @return array - */ - public static function keys() - { - return \array_keys(static::toArray()); - } - - /** - * Returns instances of the Enum class of all Enum constants - * - * @psalm-pure - * @psalm-return array - * @return static[] Constant name in key, Enum instance in value - */ - public static function values() - { - $values = array(); - - /** @psalm-var T $value */ - foreach (static::toArray() as $key => $value) { - $values[$key] = new static($value); - } - - return $values; - } - - /** - * Returns all possible values as an array - * - * @psalm-pure - * @psalm-suppress ImpureStaticProperty - * - * @psalm-return array - * @return array Constant name in key, constant value in value - */ - public static function toArray() - { - $class = static::class; - - if (!isset(static::$cache[$class])) { - $reflection = new \ReflectionClass($class); - static::$cache[$class] = $reflection->getConstants(); - } - - return static::$cache[$class]; - } - - /** - * Check if is valid enum value - * - * @param $value - * @psalm-param mixed $value - * @psalm-pure - * @return bool - */ - public static function isValid($value) - { - return \in_array($value, static::toArray(), true); - } - - /** - * Check if is valid enum key - * - * @param $key - * @psalm-param string $key - * @psalm-pure - * @return bool - */ - public static function isValidKey($key) - { - $array = static::toArray(); - - return isset($array[$key]) || \array_key_exists($key, $array); - } - - /** - * Return key for value - * - * @param $value - * - * @psalm-param mixed $value - * @psalm-pure - * @return mixed - */ - public static function search($value) - { - return \array_search($value, static::toArray(), true); - } - - /** - * Returns a value when called statically like so: MyEnum::SOME_VALUE() given SOME_VALUE is a class constant - * - * @param string $name - * @param array $arguments - * - * @return static - * @throws \BadMethodCallException - */ - public static function __callStatic($name, $arguments) - { - $class = static::class; - if (!isset(self::$instances[$class][$name])) { - $array = static::toArray(); - if (!isset($array[$name]) && !\array_key_exists($name, $array)) { - $message = "No static method or enum constant '$name' in class " . static::class; - throw new \BadMethodCallException($message); - } - return self::$instances[$class][$name] = new static($array[$name]); - } - return clone self::$instances[$class][$name]; - } - - /** - * Specify data which should be serialized to JSON. This method returns data that can be serialized by json_encode() - * natively. - * - * @return mixed - * @link http://php.net/manual/en/jsonserializable.jsonserialize.php - * @psalm-pure - */ - public function jsonSerialize() - { - return $this->getValue(); - } -} diff --git a/lib/phpspreadsheet/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php b/lib/phpspreadsheet/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php deleted file mode 100644 index 302bf80ebf5..00000000000 --- a/lib/phpspreadsheet/vendor/myclabs/php-enum/src/PHPUnit/Comparator.php +++ /dev/null @@ -1,54 +0,0 @@ -register(new \MyCLabs\Enum\PHPUnit\Comparator()); - */ -final class Comparator extends \SebastianBergmann\Comparator\Comparator -{ - public function accepts($expected, $actual) - { - return $expected instanceof Enum && ( - $actual instanceof Enum || $actual === null - ); - } - - /** - * @param Enum $expected - * @param Enum|null $actual - * - * @return void - */ - public function assertEquals($expected, $actual, $delta = 0.0, $canonicalize = false, $ignoreCase = false) - { - if ($expected->equals($actual)) { - return; - } - - throw new ComparisonFailure( - $expected, - $actual, - $this->formatEnum($expected), - $this->formatEnum($actual), - false, - 'Failed asserting that two Enums are equal.' - ); - } - - private function formatEnum(Enum $enum = null) - { - if ($enum === null) { - return "null"; - } - - return get_class($enum)."::{$enum->getKey()}()"; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/.phpcs.xml.dist b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/.phpcs.xml.dist deleted file mode 100644 index 3eafb6ca19f..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/.phpcs.xml.dist +++ /dev/null @@ -1,22 +0,0 @@ - - - - samples - src - tests - - samples/Header.php - */tests/Core/*/*Test\.(inc|css|js)$ - - - - - - - - - - - - diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php deleted file mode 100644 index 023806d6c20..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls.php +++ /dev/null @@ -1,7977 +0,0 @@ -data. - * - * @var int - */ - private $dataSize; - - /** - * Current position in stream. - * - * @var int - */ - private $pos; - - /** - * Workbook to be returned by the reader. - * - * @var Spreadsheet - */ - private $spreadsheet; - - /** - * Worksheet that is currently being built by the reader. - * - * @var Worksheet - */ - private $phpSheet; - - /** - * BIFF version. - * - * @var int - */ - private $version; - - /** - * Codepage set in the Excel file being read. Only important for BIFF5 (Excel 5.0 - Excel 95) - * For BIFF8 (Excel 97 - Excel 2003) this will always have the value 'UTF-16LE'. - * - * @var string - */ - private $codepage; - - /** - * Shared formats. - * - * @var array - */ - private $formats; - - /** - * Shared fonts. - * - * @var array - */ - private $objFonts; - - /** - * Color palette. - * - * @var array - */ - private $palette; - - /** - * Worksheets. - * - * @var array - */ - private $sheets; - - /** - * External books. - * - * @var array - */ - private $externalBooks; - - /** - * REF structures. Only applies to BIFF8. - * - * @var array - */ - private $ref; - - /** - * External names. - * - * @var array - */ - private $externalNames; - - /** - * Defined names. - * - * @var array - */ - private $definedname; - - /** - * Shared strings. Only applies to BIFF8. - * - * @var array - */ - private $sst; - - /** - * Panes are frozen? (in sheet currently being read). See WINDOW2 record. - * - * @var bool - */ - private $frozen; - - /** - * Fit printout to number of pages? (in sheet currently being read). See SHEETPR record. - * - * @var bool - */ - private $isFitToPages; - - /** - * Objects. One OBJ record contributes with one entry. - * - * @var array - */ - private $objs; - - /** - * Text Objects. One TXO record corresponds with one entry. - * - * @var array - */ - private $textObjects; - - /** - * Cell Annotations (BIFF8). - * - * @var array - */ - private $cellNotes; - - /** - * The combined MSODRAWINGGROUP data. - * - * @var string - */ - private $drawingGroupData; - - /** - * The combined MSODRAWING data (per sheet). - * - * @var string - */ - private $drawingData; - - /** - * Keep track of XF index. - * - * @var int - */ - private $xfIndex; - - /** - * Mapping of XF index (that is a cell XF) to final index in cellXf collection. - * - * @var array - */ - private $mapCellXfIndex; - - /** - * Mapping of XF index (that is a style XF) to final index in cellStyleXf collection. - * - * @var array - */ - private $mapCellStyleXfIndex; - - /** - * The shared formulas in a sheet. One SHAREDFMLA record contributes with one value. - * - * @var array - */ - private $sharedFormulas; - - /** - * The shared formula parts in a sheet. One FORMULA record contributes with one value if it - * refers to a shared formula. - * - * @var array - */ - private $sharedFormulaParts; - - /** - * The type of encryption in use. - * - * @var int - */ - private $encryption = 0; - - /** - * The position in the stream after which contents are encrypted. - * - * @var int - */ - private $encryptionStartPos = false; - - /** - * The current RC4 decryption object. - * - * @var Xls\RC4 - */ - private $rc4Key; - - /** - * The position in the stream that the RC4 decryption object was left at. - * - * @var int - */ - private $rc4Pos = 0; - - /** - * The current MD5 context state. - * - * @var string - */ - private $md5Ctxt; - - /** - * @var int - */ - private $textObjRef; - - /** - * @var string - */ - private $baseCell; - - /** - * Create a new Xls Reader instance. - */ - public function __construct() - { - parent::__construct(); - } - - /** - * Can the current IReader read the file? - * - * @param string $pFilename - * - * @return bool - */ - public function canRead($pFilename) - { - File::assertFile($pFilename); - - try { - // Use ParseXL for the hard work. - $ole = new OLERead(); - - // get excel data - $ole->read($pFilename); - - return true; - } catch (PhpSpreadsheetException $e) { - return false; - } - } - - public function setCodepage(string $codepage): void - { - if (!CodePage::validate($codepage)) { - throw new PhpSpreadsheetException('Unknown codepage: ' . $codepage); - } - - $this->codepage = $codepage; - } - - /** - * Reads names of the worksheets from a file, without parsing the whole file to a PhpSpreadsheet object. - * - * @param string $pFilename - * - * @return array - */ - public function listWorksheetNames($pFilename) - { - File::assertFile($pFilename); - - $worksheetNames = []; - - // Read the OLE file - $this->loadOLE($pFilename); - - // total byte size of Excel data (workbook global substream + sheet substreams) - $this->dataSize = strlen($this->data); - - $this->pos = 0; - $this->sheets = []; - - // Parse Workbook Global Substream - while ($this->pos < $this->dataSize) { - $code = self::getUInt2d($this->data, $this->pos); - - switch ($code) { - case self::XLS_TYPE_BOF: - $this->readBof(); - - break; - case self::XLS_TYPE_SHEET: - $this->readSheet(); - - break; - case self::XLS_TYPE_EOF: - $this->readDefault(); - - break 2; - default: - $this->readDefault(); - - break; - } - } - - foreach ($this->sheets as $sheet) { - if ($sheet['sheetType'] != 0x00) { - // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module - continue; - } - - $worksheetNames[] = $sheet['name']; - } - - return $worksheetNames; - } - - /** - * Return worksheet info (Name, Last Column Letter, Last Column Index, Total Rows, Total Columns). - * - * @param string $pFilename - * - * @return array - */ - public function listWorksheetInfo($pFilename) - { - File::assertFile($pFilename); - - $worksheetInfo = []; - - // Read the OLE file - $this->loadOLE($pFilename); - - // total byte size of Excel data (workbook global substream + sheet substreams) - $this->dataSize = strlen($this->data); - - // initialize - $this->pos = 0; - $this->sheets = []; - - // Parse Workbook Global Substream - while ($this->pos < $this->dataSize) { - $code = self::getUInt2d($this->data, $this->pos); - - switch ($code) { - case self::XLS_TYPE_BOF: - $this->readBof(); - - break; - case self::XLS_TYPE_SHEET: - $this->readSheet(); - - break; - case self::XLS_TYPE_EOF: - $this->readDefault(); - - break 2; - default: - $this->readDefault(); - - break; - } - } - - // Parse the individual sheets - foreach ($this->sheets as $sheet) { - if ($sheet['sheetType'] != 0x00) { - // 0x00: Worksheet - // 0x02: Chart - // 0x06: Visual Basic module - continue; - } - - $tmpInfo = []; - $tmpInfo['worksheetName'] = $sheet['name']; - $tmpInfo['lastColumnLetter'] = 'A'; - $tmpInfo['lastColumnIndex'] = 0; - $tmpInfo['totalRows'] = 0; - $tmpInfo['totalColumns'] = 0; - - $this->pos = $sheet['offset']; - - while ($this->pos <= $this->dataSize - 4) { - $code = self::getUInt2d($this->data, $this->pos); - - switch ($code) { - case self::XLS_TYPE_RK: - case self::XLS_TYPE_LABELSST: - case self::XLS_TYPE_NUMBER: - case self::XLS_TYPE_FORMULA: - case self::XLS_TYPE_BOOLERR: - case self::XLS_TYPE_LABEL: - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - $rowIndex = self::getUInt2d($recordData, 0) + 1; - $columnIndex = self::getUInt2d($recordData, 2); - - $tmpInfo['totalRows'] = max($tmpInfo['totalRows'], $rowIndex); - $tmpInfo['lastColumnIndex'] = max($tmpInfo['lastColumnIndex'], $columnIndex); - - break; - case self::XLS_TYPE_BOF: - $this->readBof(); - - break; - case self::XLS_TYPE_EOF: - $this->readDefault(); - - break 2; - default: - $this->readDefault(); - - break; - } - } - - $tmpInfo['lastColumnLetter'] = Coordinate::stringFromColumnIndex($tmpInfo['lastColumnIndex'] + 1); - $tmpInfo['totalColumns'] = $tmpInfo['lastColumnIndex'] + 1; - - $worksheetInfo[] = $tmpInfo; - } - - return $worksheetInfo; - } - - /** - * Loads PhpSpreadsheet from file. - * - * @param string $pFilename - * - * @return Spreadsheet - */ - public function load($pFilename) - { - // Read the OLE file - $this->loadOLE($pFilename); - - // Initialisations - $this->spreadsheet = new Spreadsheet(); - $this->spreadsheet->removeSheetByIndex(0); // remove 1st sheet - if (!$this->readDataOnly) { - $this->spreadsheet->removeCellStyleXfByIndex(0); // remove the default style - $this->spreadsheet->removeCellXfByIndex(0); // remove the default style - } - - // Read the summary information stream (containing meta data) - $this->readSummaryInformation(); - - // Read the Additional document summary information stream (containing application-specific meta data) - $this->readDocumentSummaryInformation(); - - // total byte size of Excel data (workbook global substream + sheet substreams) - $this->dataSize = strlen($this->data); - - // initialize - $this->pos = 0; - $this->codepage = $this->codepage ?: CodePage::DEFAULT_CODE_PAGE; - $this->formats = []; - $this->objFonts = []; - $this->palette = []; - $this->sheets = []; - $this->externalBooks = []; - $this->ref = []; - $this->definedname = []; - $this->sst = []; - $this->drawingGroupData = ''; - $this->xfIndex = ''; - $this->mapCellXfIndex = []; - $this->mapCellStyleXfIndex = []; - - // Parse Workbook Global Substream - while ($this->pos < $this->dataSize) { - $code = self::getUInt2d($this->data, $this->pos); - - switch ($code) { - case self::XLS_TYPE_BOF: - $this->readBof(); - - break; - case self::XLS_TYPE_FILEPASS: - $this->readFilepass(); - - break; - case self::XLS_TYPE_CODEPAGE: - $this->readCodepage(); - - break; - case self::XLS_TYPE_DATEMODE: - $this->readDateMode(); - - break; - case self::XLS_TYPE_FONT: - $this->readFont(); - - break; - case self::XLS_TYPE_FORMAT: - $this->readFormat(); - - break; - case self::XLS_TYPE_XF: - $this->readXf(); - - break; - case self::XLS_TYPE_XFEXT: - $this->readXfExt(); - - break; - case self::XLS_TYPE_STYLE: - $this->readStyle(); - - break; - case self::XLS_TYPE_PALETTE: - $this->readPalette(); - - break; - case self::XLS_TYPE_SHEET: - $this->readSheet(); - - break; - case self::XLS_TYPE_EXTERNALBOOK: - $this->readExternalBook(); - - break; - case self::XLS_TYPE_EXTERNNAME: - $this->readExternName(); - - break; - case self::XLS_TYPE_EXTERNSHEET: - $this->readExternSheet(); - - break; - case self::XLS_TYPE_DEFINEDNAME: - $this->readDefinedName(); - - break; - case self::XLS_TYPE_MSODRAWINGGROUP: - $this->readMsoDrawingGroup(); - - break; - case self::XLS_TYPE_SST: - $this->readSst(); - - break; - case self::XLS_TYPE_EOF: - $this->readDefault(); - - break 2; - default: - $this->readDefault(); - - break; - } - } - - // Resolve indexed colors for font, fill, and border colors - // Cannot be resolved already in XF record, because PALETTE record comes afterwards - if (!$this->readDataOnly) { - foreach ($this->objFonts as $objFont) { - if (isset($objFont->colorIndex)) { - $color = Xls\Color::map($objFont->colorIndex, $this->palette, $this->version); - $objFont->getColor()->setRGB($color['rgb']); - } - } - - foreach ($this->spreadsheet->getCellXfCollection() as $objStyle) { - // fill start and end color - $fill = $objStyle->getFill(); - - if (isset($fill->startcolorIndex)) { - $startColor = Xls\Color::map($fill->startcolorIndex, $this->palette, $this->version); - $fill->getStartColor()->setRGB($startColor['rgb']); - } - if (isset($fill->endcolorIndex)) { - $endColor = Xls\Color::map($fill->endcolorIndex, $this->palette, $this->version); - $fill->getEndColor()->setRGB($endColor['rgb']); - } - - // border colors - $top = $objStyle->getBorders()->getTop(); - $right = $objStyle->getBorders()->getRight(); - $bottom = $objStyle->getBorders()->getBottom(); - $left = $objStyle->getBorders()->getLeft(); - $diagonal = $objStyle->getBorders()->getDiagonal(); - - if (isset($top->colorIndex)) { - $borderTopColor = Xls\Color::map($top->colorIndex, $this->palette, $this->version); - $top->getColor()->setRGB($borderTopColor['rgb']); - } - if (isset($right->colorIndex)) { - $borderRightColor = Xls\Color::map($right->colorIndex, $this->palette, $this->version); - $right->getColor()->setRGB($borderRightColor['rgb']); - } - if (isset($bottom->colorIndex)) { - $borderBottomColor = Xls\Color::map($bottom->colorIndex, $this->palette, $this->version); - $bottom->getColor()->setRGB($borderBottomColor['rgb']); - } - if (isset($left->colorIndex)) { - $borderLeftColor = Xls\Color::map($left->colorIndex, $this->palette, $this->version); - $left->getColor()->setRGB($borderLeftColor['rgb']); - } - if (isset($diagonal->colorIndex)) { - $borderDiagonalColor = Xls\Color::map($diagonal->colorIndex, $this->palette, $this->version); - $diagonal->getColor()->setRGB($borderDiagonalColor['rgb']); - } - } - } - - // treat MSODRAWINGGROUP records, workbook-level Escher - if (!$this->readDataOnly && $this->drawingGroupData) { - $escherWorkbook = new Escher(); - $reader = new Xls\Escher($escherWorkbook); - $escherWorkbook = $reader->load($this->drawingGroupData); - } - - // Parse the individual sheets - foreach ($this->sheets as $sheet) { - if ($sheet['sheetType'] != 0x00) { - // 0x00: Worksheet, 0x02: Chart, 0x06: Visual Basic module - continue; - } - - // check if sheet should be skipped - if (isset($this->loadSheetsOnly) && !in_array($sheet['name'], $this->loadSheetsOnly)) { - continue; - } - - // add sheet to PhpSpreadsheet object - $this->phpSheet = $this->spreadsheet->createSheet(); - // Use false for $updateFormulaCellReferences to prevent adjustment of worksheet references in formula - // cells... during the load, all formulae should be correct, and we're simply bringing the worksheet - // name in line with the formula, not the reverse - $this->phpSheet->setTitle($sheet['name'], false, false); - $this->phpSheet->setSheetState($sheet['sheetState']); - - $this->pos = $sheet['offset']; - - // Initialize isFitToPages. May change after reading SHEETPR record. - $this->isFitToPages = false; - - // Initialize drawingData - $this->drawingData = ''; - - // Initialize objs - $this->objs = []; - - // Initialize shared formula parts - $this->sharedFormulaParts = []; - - // Initialize shared formulas - $this->sharedFormulas = []; - - // Initialize text objs - $this->textObjects = []; - - // Initialize cell annotations - $this->cellNotes = []; - $this->textObjRef = -1; - - while ($this->pos <= $this->dataSize - 4) { - $code = self::getUInt2d($this->data, $this->pos); - - switch ($code) { - case self::XLS_TYPE_BOF: - $this->readBof(); - - break; - case self::XLS_TYPE_PRINTGRIDLINES: - $this->readPrintGridlines(); - - break; - case self::XLS_TYPE_DEFAULTROWHEIGHT: - $this->readDefaultRowHeight(); - - break; - case self::XLS_TYPE_SHEETPR: - $this->readSheetPr(); - - break; - case self::XLS_TYPE_HORIZONTALPAGEBREAKS: - $this->readHorizontalPageBreaks(); - - break; - case self::XLS_TYPE_VERTICALPAGEBREAKS: - $this->readVerticalPageBreaks(); - - break; - case self::XLS_TYPE_HEADER: - $this->readHeader(); - - break; - case self::XLS_TYPE_FOOTER: - $this->readFooter(); - - break; - case self::XLS_TYPE_HCENTER: - $this->readHcenter(); - - break; - case self::XLS_TYPE_VCENTER: - $this->readVcenter(); - - break; - case self::XLS_TYPE_LEFTMARGIN: - $this->readLeftMargin(); - - break; - case self::XLS_TYPE_RIGHTMARGIN: - $this->readRightMargin(); - - break; - case self::XLS_TYPE_TOPMARGIN: - $this->readTopMargin(); - - break; - case self::XLS_TYPE_BOTTOMMARGIN: - $this->readBottomMargin(); - - break; - case self::XLS_TYPE_PAGESETUP: - $this->readPageSetup(); - - break; - case self::XLS_TYPE_PROTECT: - $this->readProtect(); - - break; - case self::XLS_TYPE_SCENPROTECT: - $this->readScenProtect(); - - break; - case self::XLS_TYPE_OBJECTPROTECT: - $this->readObjectProtect(); - - break; - case self::XLS_TYPE_PASSWORD: - $this->readPassword(); - - break; - case self::XLS_TYPE_DEFCOLWIDTH: - $this->readDefColWidth(); - - break; - case self::XLS_TYPE_COLINFO: - $this->readColInfo(); - - break; - case self::XLS_TYPE_DIMENSION: - $this->readDefault(); - - break; - case self::XLS_TYPE_ROW: - $this->readRow(); - - break; - case self::XLS_TYPE_DBCELL: - $this->readDefault(); - - break; - case self::XLS_TYPE_RK: - $this->readRk(); - - break; - case self::XLS_TYPE_LABELSST: - $this->readLabelSst(); - - break; - case self::XLS_TYPE_MULRK: - $this->readMulRk(); - - break; - case self::XLS_TYPE_NUMBER: - $this->readNumber(); - - break; - case self::XLS_TYPE_FORMULA: - $this->readFormula(); - - break; - case self::XLS_TYPE_SHAREDFMLA: - $this->readSharedFmla(); - - break; - case self::XLS_TYPE_BOOLERR: - $this->readBoolErr(); - - break; - case self::XLS_TYPE_MULBLANK: - $this->readMulBlank(); - - break; - case self::XLS_TYPE_LABEL: - $this->readLabel(); - - break; - case self::XLS_TYPE_BLANK: - $this->readBlank(); - - break; - case self::XLS_TYPE_MSODRAWING: - $this->readMsoDrawing(); - - break; - case self::XLS_TYPE_OBJ: - $this->readObj(); - - break; - case self::XLS_TYPE_WINDOW2: - $this->readWindow2(); - - break; - case self::XLS_TYPE_PAGELAYOUTVIEW: - $this->readPageLayoutView(); - - break; - case self::XLS_TYPE_SCL: - $this->readScl(); - - break; - case self::XLS_TYPE_PANE: - $this->readPane(); - - break; - case self::XLS_TYPE_SELECTION: - $this->readSelection(); - - break; - case self::XLS_TYPE_MERGEDCELLS: - $this->readMergedCells(); - - break; - case self::XLS_TYPE_HYPERLINK: - $this->readHyperLink(); - - break; - case self::XLS_TYPE_DATAVALIDATIONS: - $this->readDataValidations(); - - break; - case self::XLS_TYPE_DATAVALIDATION: - $this->readDataValidation(); - - break; - case self::XLS_TYPE_SHEETLAYOUT: - $this->readSheetLayout(); - - break; - case self::XLS_TYPE_SHEETPROTECTION: - $this->readSheetProtection(); - - break; - case self::XLS_TYPE_RANGEPROTECTION: - $this->readRangeProtection(); - - break; - case self::XLS_TYPE_NOTE: - $this->readNote(); - - break; - case self::XLS_TYPE_TXO: - $this->readTextObject(); - - break; - case self::XLS_TYPE_CONTINUE: - $this->readContinue(); - - break; - case self::XLS_TYPE_EOF: - $this->readDefault(); - - break 2; - default: - $this->readDefault(); - - break; - } - } - - // treat MSODRAWING records, sheet-level Escher - if (!$this->readDataOnly && $this->drawingData) { - $escherWorksheet = new Escher(); - $reader = new Xls\Escher($escherWorksheet); - $escherWorksheet = $reader->load($this->drawingData); - - // get all spContainers in one long array, so they can be mapped to OBJ records - $allSpContainers = $escherWorksheet->getDgContainer()->getSpgrContainer()->getAllSpContainers(); - } - - // treat OBJ records - foreach ($this->objs as $n => $obj) { - // the first shape container never has a corresponding OBJ record, hence $n + 1 - if (isset($allSpContainers[$n + 1]) && is_object($allSpContainers[$n + 1])) { - $spContainer = $allSpContainers[$n + 1]; - - // we skip all spContainers that are a part of a group shape since we cannot yet handle those - if ($spContainer->getNestingLevel() > 1) { - continue; - } - - // calculate the width and height of the shape - [$startColumn, $startRow] = Coordinate::coordinateFromString($spContainer->getStartCoordinates()); - [$endColumn, $endRow] = Coordinate::coordinateFromString($spContainer->getEndCoordinates()); - - $startOffsetX = $spContainer->getStartOffsetX(); - $startOffsetY = $spContainer->getStartOffsetY(); - $endOffsetX = $spContainer->getEndOffsetX(); - $endOffsetY = $spContainer->getEndOffsetY(); - - $width = \PhpOffice\PhpSpreadsheet\Shared\Xls::getDistanceX($this->phpSheet, $startColumn, $startOffsetX, $endColumn, $endOffsetX); - $height = \PhpOffice\PhpSpreadsheet\Shared\Xls::getDistanceY($this->phpSheet, $startRow, $startOffsetY, $endRow, $endOffsetY); - - // calculate offsetX and offsetY of the shape - $offsetX = $startOffsetX * \PhpOffice\PhpSpreadsheet\Shared\Xls::sizeCol($this->phpSheet, $startColumn) / 1024; - $offsetY = $startOffsetY * \PhpOffice\PhpSpreadsheet\Shared\Xls::sizeRow($this->phpSheet, $startRow) / 256; - - switch ($obj['otObjType']) { - case 0x19: - // Note - if (isset($this->cellNotes[$obj['idObjID']])) { - $cellNote = $this->cellNotes[$obj['idObjID']]; - - if (isset($this->textObjects[$obj['idObjID']])) { - $textObject = $this->textObjects[$obj['idObjID']]; - $this->cellNotes[$obj['idObjID']]['objTextData'] = $textObject; - } - } - - break; - case 0x08: - // picture - // get index to BSE entry (1-based) - $BSEindex = $spContainer->getOPT(0x0104); - - // If there is no BSE Index, we will fail here and other fields are not read. - // Fix by checking here. - // TODO: Why is there no BSE Index? Is this a new Office Version? Password protected field? - // More likely : a uncompatible picture - if (!$BSEindex) { - continue 2; - } - - $BSECollection = $escherWorkbook->getDggContainer()->getBstoreContainer()->getBSECollection(); - $BSE = $BSECollection[$BSEindex - 1]; - $blipType = $BSE->getBlipType(); - - // need check because some blip types are not supported by Escher reader such as EMF - if ($blip = $BSE->getBlip()) { - $ih = imagecreatefromstring($blip->getData()); - $drawing = new MemoryDrawing(); - $drawing->setImageResource($ih); - - // width, height, offsetX, offsetY - $drawing->setResizeProportional(false); - $drawing->setWidth($width); - $drawing->setHeight($height); - $drawing->setOffsetX($offsetX); - $drawing->setOffsetY($offsetY); - - switch ($blipType) { - case BSE::BLIPTYPE_JPEG: - $drawing->setRenderingFunction(MemoryDrawing::RENDERING_JPEG); - $drawing->setMimeType(MemoryDrawing::MIMETYPE_JPEG); - - break; - case BSE::BLIPTYPE_PNG: - $drawing->setRenderingFunction(MemoryDrawing::RENDERING_PNG); - $drawing->setMimeType(MemoryDrawing::MIMETYPE_PNG); - - break; - } - - $drawing->setWorksheet($this->phpSheet); - $drawing->setCoordinates($spContainer->getStartCoordinates()); - } - - break; - default: - // other object type - break; - } - } - } - - // treat SHAREDFMLA records - if ($this->version == self::XLS_BIFF8) { - foreach ($this->sharedFormulaParts as $cell => $baseCell) { - [$column, $row] = Coordinate::coordinateFromString($cell); - if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) { - $formula = $this->getFormulaFromStructure($this->sharedFormulas[$baseCell], $cell); - $this->phpSheet->getCell($cell)->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA); - } - } - } - - if (!empty($this->cellNotes)) { - foreach ($this->cellNotes as $note => $noteDetails) { - if (!isset($noteDetails['objTextData'])) { - if (isset($this->textObjects[$note])) { - $textObject = $this->textObjects[$note]; - $noteDetails['objTextData'] = $textObject; - } else { - $noteDetails['objTextData']['text'] = ''; - } - } - $cellAddress = str_replace('$', '', $noteDetails['cellRef']); - $this->phpSheet->getComment($cellAddress)->setAuthor($noteDetails['author'])->setText($this->parseRichText($noteDetails['objTextData']['text'])); - } - } - } - - // add the named ranges (defined names) - foreach ($this->definedname as $definedName) { - if ($definedName['isBuiltInName']) { - switch ($definedName['name']) { - case pack('C', 0x06): - // print area - // in general, formula looks like this: Foo!$C$7:$J$66,Bar!$A$1:$IV$2 - $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma? - - $extractedRanges = []; - foreach ($ranges as $range) { - // $range should look like one of these - // Foo!$C$7:$J$66 - // Bar!$A$1:$IV$2 - $explodes = Worksheet::extractSheetTitle($range, true); - $sheetName = trim($explodes[0], "'"); - if (count($explodes) == 2) { - if (strpos($explodes[1], ':') === false) { - $explodes[1] = $explodes[1] . ':' . $explodes[1]; - } - $extractedRanges[] = str_replace('$', '', $explodes[1]); // C7:J66 - } - } - if ($docSheet = $this->spreadsheet->getSheetByName($sheetName)) { - $docSheet->getPageSetup()->setPrintArea(implode(',', $extractedRanges)); // C7:J66,A1:IV2 - } - - break; - case pack('C', 0x07): - // print titles (repeating rows) - // Assuming BIFF8, there are 3 cases - // 1. repeating rows - // formula looks like this: Sheet!$A$1:$IV$2 - // rows 1-2 repeat - // 2. repeating columns - // formula looks like this: Sheet!$A$1:$B$65536 - // columns A-B repeat - // 3. both repeating rows and repeating columns - // formula looks like this: Sheet!$A$1:$B$65536,Sheet!$A$1:$IV$2 - $ranges = explode(',', $definedName['formula']); // FIXME: what if sheetname contains comma? - foreach ($ranges as $range) { - // $range should look like this one of these - // Sheet!$A$1:$B$65536 - // Sheet!$A$1:$IV$2 - if (strpos($range, '!') !== false) { - $explodes = Worksheet::extractSheetTitle($range, true); - if ($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) { - $extractedRange = $explodes[1]; - $extractedRange = str_replace('$', '', $extractedRange); - - $coordinateStrings = explode(':', $extractedRange); - if (count($coordinateStrings) == 2) { - [$firstColumn, $firstRow] = Coordinate::coordinateFromString($coordinateStrings[0]); - [$lastColumn, $lastRow] = Coordinate::coordinateFromString($coordinateStrings[1]); - - if ($firstColumn == 'A' && $lastColumn == 'IV') { - // then we have repeating rows - $docSheet->getPageSetup()->setRowsToRepeatAtTop([$firstRow, $lastRow]); - } elseif ($firstRow == 1 && $lastRow == 65536) { - // then we have repeating columns - $docSheet->getPageSetup()->setColumnsToRepeatAtLeft([$firstColumn, $lastColumn]); - } - } - } - } - } - - break; - } - } else { - // Extract range - if (strpos($definedName['formula'], '!') !== false) { - $explodes = Worksheet::extractSheetTitle($definedName['formula'], true); - if ( - ($docSheet = $this->spreadsheet->getSheetByName($explodes[0])) || - ($docSheet = $this->spreadsheet->getSheetByName(trim($explodes[0], "'"))) - ) { - $extractedRange = $explodes[1]; - $extractedRange = str_replace('$', '', $extractedRange); - - $localOnly = ($definedName['scope'] == 0) ? false : true; - - $scope = ($definedName['scope'] == 0) ? null : $this->spreadsheet->getSheetByName($this->sheets[$definedName['scope'] - 1]['name']); - - $this->spreadsheet->addNamedRange(new NamedRange((string) $definedName['name'], $docSheet, $extractedRange, $localOnly, $scope)); - } - } - // Named Value - // TODO Provide support for named values - } - } - $this->data = null; - - return $this->spreadsheet; - } - - /** - * Read record data from stream, decrypting as required. - * - * @param string $data Data stream to read from - * @param int $pos Position to start reading from - * @param int $len Record data length - * - * @return string Record data - */ - private function readRecordData($data, $pos, $len) - { - $data = substr($data, $pos, $len); - - // File not encrypted, or record before encryption start point - if ($this->encryption == self::MS_BIFF_CRYPTO_NONE || $pos < $this->encryptionStartPos) { - return $data; - } - - $recordData = ''; - if ($this->encryption == self::MS_BIFF_CRYPTO_RC4) { - $oldBlock = floor($this->rc4Pos / self::REKEY_BLOCK); - $block = floor($pos / self::REKEY_BLOCK); - $endBlock = floor(($pos + $len) / self::REKEY_BLOCK); - - // Spin an RC4 decryptor to the right spot. If we have a decryptor sitting - // at a point earlier in the current block, re-use it as we can save some time. - if ($block != $oldBlock || $pos < $this->rc4Pos || !$this->rc4Key) { - $this->rc4Key = $this->makeKey($block, $this->md5Ctxt); - $step = $pos % self::REKEY_BLOCK; - } else { - $step = $pos - $this->rc4Pos; - } - $this->rc4Key->RC4(str_repeat("\0", $step)); - - // Decrypt record data (re-keying at the end of every block) - while ($block != $endBlock) { - $step = self::REKEY_BLOCK - ($pos % self::REKEY_BLOCK); - $recordData .= $this->rc4Key->RC4(substr($data, 0, $step)); - $data = substr($data, $step); - $pos += $step; - $len -= $step; - ++$block; - $this->rc4Key = $this->makeKey($block, $this->md5Ctxt); - } - $recordData .= $this->rc4Key->RC4(substr($data, 0, $len)); - - // Keep track of the position of this decryptor. - // We'll try and re-use it later if we can to speed things up - $this->rc4Pos = $pos + $len; - } elseif ($this->encryption == self::MS_BIFF_CRYPTO_XOR) { - throw new Exception('XOr encryption not supported'); - } - - return $recordData; - } - - /** - * Use OLE reader to extract the relevant data streams from the OLE file. - * - * @param string $pFilename - */ - private function loadOLE($pFilename): void - { - // OLE reader - $ole = new OLERead(); - // get excel data, - $ole->read($pFilename); - // Get workbook data: workbook stream + sheet streams - $this->data = $ole->getStream($ole->wrkbook); - // Get summary information data - $this->summaryInformation = $ole->getStream($ole->summaryInformation); - // Get additional document summary information data - $this->documentSummaryInformation = $ole->getStream($ole->documentSummaryInformation); - } - - /** - * Read summary information. - */ - private function readSummaryInformation(): void - { - if (!isset($this->summaryInformation)) { - return; - } - - // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark) - // offset: 2; size: 2; - // offset: 4; size: 2; OS version - // offset: 6; size: 2; OS indicator - // offset: 8; size: 16 - // offset: 24; size: 4; section count - $secCount = self::getInt4d($this->summaryInformation, 24); - - // 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 - // offset: 44; size: 4 - $secOffset = self::getInt4d($this->summaryInformation, 44); - - // section header - // offset: $secOffset; size: 4; section length - $secLength = self::getInt4d($this->summaryInformation, $secOffset); - - // offset: $secOffset+4; size: 4; property count - $countProperties = self::getInt4d($this->summaryInformation, $secOffset + 4); - - // initialize code page (used to resolve string values) - $codePage = 'CP1252'; - - // offset: ($secOffset+8); size: var - // loop through property decarations and properties - for ($i = 0; $i < $countProperties; ++$i) { - // offset: ($secOffset+8) + (8 * $i); size: 4; property ID - $id = self::getInt4d($this->summaryInformation, ($secOffset + 8) + (8 * $i)); - - // Use value of property id as appropriate - // offset: ($secOffset+12) + (8 * $i); size: 4; offset from beginning of section (48) - $offset = self::getInt4d($this->summaryInformation, ($secOffset + 12) + (8 * $i)); - - $type = self::getInt4d($this->summaryInformation, $secOffset + $offset); - - // initialize property value - $value = null; - - // extract property value based on property type - switch ($type) { - case 0x02: // 2 byte signed integer - $value = self::getUInt2d($this->summaryInformation, $secOffset + 4 + $offset); - - break; - case 0x03: // 4 byte signed integer - $value = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset); - - break; - case 0x13: // 4 byte unsigned integer - // not needed yet, fix later if necessary - break; - case 0x1E: // null-terminated string prepended by dword string length - $byteLength = self::getInt4d($this->summaryInformation, $secOffset + 4 + $offset); - $value = substr($this->summaryInformation, $secOffset + 8 + $offset, $byteLength); - $value = StringHelper::convertEncoding($value, 'UTF-8', $codePage); - $value = rtrim($value); - - break; - case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) - // PHP-time - $value = OLE::OLE2LocalDate(substr($this->summaryInformation, $secOffset + 4 + $offset, 8)); - - break; - case 0x47: // Clipboard format - // not needed yet, fix later if necessary - break; - } - - switch ($id) { - case 0x01: // Code Page - $codePage = CodePage::numberToName($value); - - break; - case 0x02: // Title - $this->spreadsheet->getProperties()->setTitle($value); - - break; - case 0x03: // Subject - $this->spreadsheet->getProperties()->setSubject($value); - - break; - case 0x04: // Author (Creator) - $this->spreadsheet->getProperties()->setCreator($value); - - break; - case 0x05: // Keywords - $this->spreadsheet->getProperties()->setKeywords($value); - - break; - case 0x06: // Comments (Description) - $this->spreadsheet->getProperties()->setDescription($value); - - break; - case 0x07: // Template - // Not supported by PhpSpreadsheet - break; - case 0x08: // Last Saved By (LastModifiedBy) - $this->spreadsheet->getProperties()->setLastModifiedBy($value); - - break; - case 0x09: // Revision - // Not supported by PhpSpreadsheet - break; - case 0x0A: // Total Editing Time - // Not supported by PhpSpreadsheet - break; - case 0x0B: // Last Printed - // Not supported by PhpSpreadsheet - break; - case 0x0C: // Created Date/Time - $this->spreadsheet->getProperties()->setCreated($value); - - break; - case 0x0D: // Modified Date/Time - $this->spreadsheet->getProperties()->setModified($value); - - break; - case 0x0E: // Number of Pages - // Not supported by PhpSpreadsheet - break; - case 0x0F: // Number of Words - // Not supported by PhpSpreadsheet - break; - case 0x10: // Number of Characters - // Not supported by PhpSpreadsheet - break; - case 0x11: // Thumbnail - // Not supported by PhpSpreadsheet - break; - case 0x12: // Name of creating application - // Not supported by PhpSpreadsheet - break; - case 0x13: // Security - // Not supported by PhpSpreadsheet - break; - } - } - } - - /** - * Read additional document summary information. - */ - private function readDocumentSummaryInformation(): void - { - if (!isset($this->documentSummaryInformation)) { - return; - } - - // offset: 0; size: 2; must be 0xFE 0xFF (UTF-16 LE byte order mark) - // offset: 2; size: 2; - // offset: 4; size: 2; OS version - // offset: 6; size: 2; OS indicator - // offset: 8; size: 16 - // offset: 24; size: 4; section count - $secCount = self::getInt4d($this->documentSummaryInformation, 24); - - // 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 - // offset: 44; size: 4; first section offset - $secOffset = self::getInt4d($this->documentSummaryInformation, 44); - - // section header - // offset: $secOffset; size: 4; section length - $secLength = self::getInt4d($this->documentSummaryInformation, $secOffset); - - // offset: $secOffset+4; size: 4; property count - $countProperties = self::getInt4d($this->documentSummaryInformation, $secOffset + 4); - - // initialize code page (used to resolve string values) - $codePage = 'CP1252'; - - // offset: ($secOffset+8); size: var - // loop through property decarations and properties - for ($i = 0; $i < $countProperties; ++$i) { - // offset: ($secOffset+8) + (8 * $i); size: 4; property ID - $id = self::getInt4d($this->documentSummaryInformation, ($secOffset + 8) + (8 * $i)); - - // Use value of property id as appropriate - // offset: 60 + 8 * $i; size: 4; offset from beginning of section (48) - $offset = self::getInt4d($this->documentSummaryInformation, ($secOffset + 12) + (8 * $i)); - - $type = self::getInt4d($this->documentSummaryInformation, $secOffset + $offset); - - // initialize property value - $value = null; - - // extract property value based on property type - switch ($type) { - case 0x02: // 2 byte signed integer - $value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset); - - break; - case 0x03: // 4 byte signed integer - $value = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset); - - break; - case 0x0B: // Boolean - $value = self::getUInt2d($this->documentSummaryInformation, $secOffset + 4 + $offset); - $value = ($value == 0 ? false : true); - - break; - case 0x13: // 4 byte unsigned integer - // not needed yet, fix later if necessary - break; - case 0x1E: // null-terminated string prepended by dword string length - $byteLength = self::getInt4d($this->documentSummaryInformation, $secOffset + 4 + $offset); - $value = substr($this->documentSummaryInformation, $secOffset + 8 + $offset, $byteLength); - $value = StringHelper::convertEncoding($value, 'UTF-8', $codePage); - $value = rtrim($value); - - break; - case 0x40: // Filetime (64-bit value representing the number of 100-nanosecond intervals since January 1, 1601) - // PHP-Time - $value = OLE::OLE2LocalDate(substr($this->documentSummaryInformation, $secOffset + 4 + $offset, 8)); - - break; - case 0x47: // Clipboard format - // not needed yet, fix later if necessary - break; - } - - switch ($id) { - case 0x01: // Code Page - $codePage = CodePage::numberToName($value); - - break; - case 0x02: // Category - $this->spreadsheet->getProperties()->setCategory($value); - - break; - case 0x03: // Presentation Target - // Not supported by PhpSpreadsheet - break; - case 0x04: // Bytes - // Not supported by PhpSpreadsheet - break; - case 0x05: // Lines - // Not supported by PhpSpreadsheet - break; - case 0x06: // Paragraphs - // Not supported by PhpSpreadsheet - break; - case 0x07: // Slides - // Not supported by PhpSpreadsheet - break; - case 0x08: // Notes - // Not supported by PhpSpreadsheet - break; - case 0x09: // Hidden Slides - // Not supported by PhpSpreadsheet - break; - case 0x0A: // MM Clips - // Not supported by PhpSpreadsheet - break; - case 0x0B: // Scale Crop - // Not supported by PhpSpreadsheet - break; - case 0x0C: // Heading Pairs - // Not supported by PhpSpreadsheet - break; - case 0x0D: // Titles of Parts - // Not supported by PhpSpreadsheet - break; - case 0x0E: // Manager - $this->spreadsheet->getProperties()->setManager($value); - - break; - case 0x0F: // Company - $this->spreadsheet->getProperties()->setCompany($value); - - break; - case 0x10: // Links up-to-date - // Not supported by PhpSpreadsheet - break; - } - } - } - - /** - * Reads a general type of BIFF record. Does nothing except for moving stream pointer forward to next record. - */ - private function readDefault(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - - // move stream pointer to next record - $this->pos += 4 + $length; - } - - /** - * The NOTE record specifies a comment associated with a particular cell. In Excel 95 (BIFF7) and earlier versions, - * this record stores a note (cell note). This feature was significantly enhanced in Excel 97. - */ - private function readNote(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if ($this->readDataOnly) { - return; - } - - $cellAddress = $this->readBIFF8CellAddress(substr($recordData, 0, 4)); - if ($this->version == self::XLS_BIFF8) { - $noteObjID = self::getUInt2d($recordData, 6); - $noteAuthor = self::readUnicodeStringLong(substr($recordData, 8)); - $noteAuthor = $noteAuthor['value']; - $this->cellNotes[$noteObjID] = [ - 'cellRef' => $cellAddress, - 'objectID' => $noteObjID, - 'author' => $noteAuthor, - ]; - } else { - $extension = false; - if ($cellAddress == '$B$65536') { - // If the address row is -1 and the column is 0, (which translates as $B$65536) then this is a continuation - // note from the previous cell annotation. We're not yet handling this, so annotations longer than the - // max 2048 bytes will probably throw a wobbly. - $row = self::getUInt2d($recordData, 0); - $extension = true; - $cellAddress = array_pop(array_keys($this->phpSheet->getComments())); - } - - $cellAddress = str_replace('$', '', $cellAddress); - $noteLength = self::getUInt2d($recordData, 4); - $noteText = trim(substr($recordData, 6)); - - if ($extension) { - // Concatenate this extension with the currently set comment for the cell - $comment = $this->phpSheet->getComment($cellAddress); - $commentText = $comment->getText()->getPlainText(); - $comment->setText($this->parseRichText($commentText . $noteText)); - } else { - // Set comment for the cell - $this->phpSheet->getComment($cellAddress)->setText($this->parseRichText($noteText)); -// ->setAuthor($author) - } - } - } - - /** - * The TEXT Object record contains the text associated with a cell annotation. - */ - private function readTextObject(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if ($this->readDataOnly) { - return; - } - - // recordData consists of an array of subrecords looking like this: - // grbit: 2 bytes; Option Flags - // rot: 2 bytes; rotation - // cchText: 2 bytes; length of the text (in the first continue record) - // cbRuns: 2 bytes; length of the formatting (in the second continue record) - // followed by the continuation records containing the actual text and formatting - $grbitOpts = self::getUInt2d($recordData, 0); - $rot = self::getUInt2d($recordData, 2); - $cchText = self::getUInt2d($recordData, 10); - $cbRuns = self::getUInt2d($recordData, 12); - $text = $this->getSplicedRecordData(); - - $textByte = $text['spliceOffsets'][1] - $text['spliceOffsets'][0] - 1; - $textStr = substr($text['recordData'], $text['spliceOffsets'][0] + 1, $textByte); - // get 1 byte - $is16Bit = ord($text['recordData'][0]); - // it is possible to use a compressed format, - // which omits the high bytes of all characters, if they are all zero - if (($is16Bit & 0x01) === 0) { - $textStr = StringHelper::ConvertEncoding($textStr, 'UTF-8', 'ISO-8859-1'); - } else { - $textStr = $this->decodeCodepage($textStr); - } - - $this->textObjects[$this->textObjRef] = [ - 'text' => $textStr, - 'format' => substr($text['recordData'], $text['spliceOffsets'][1], $cbRuns), - 'alignment' => $grbitOpts, - 'rotation' => $rot, - ]; - } - - /** - * Read BOF. - */ - private function readBof(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = substr($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 2; size: 2; type of the following data - $substreamType = self::getUInt2d($recordData, 2); - - switch ($substreamType) { - case self::XLS_WORKBOOKGLOBALS: - $version = self::getUInt2d($recordData, 0); - if (($version != self::XLS_BIFF8) && ($version != self::XLS_BIFF7)) { - throw new Exception('Cannot read this Excel file. Version is too old.'); - } - $this->version = $version; - - break; - case self::XLS_WORKSHEET: - // do not use this version information for anything - // it is unreliable (OpenOffice doc, 5.8), use only version information from the global stream - break; - default: - // substream, e.g. chart - // just skip the entire substream - do { - $code = self::getUInt2d($this->data, $this->pos); - $this->readDefault(); - } while ($code != self::XLS_TYPE_EOF && $this->pos < $this->dataSize); - - break; - } - } - - /** - * FILEPASS. - * - * This record is part of the File Protection Block. It - * contains information about the read/write password of the - * file. All record contents following this record will be - * encrypted. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - * - * The decryption functions and objects used from here on in - * are based on the source of Spreadsheet-ParseExcel: - * https://metacpan.org/release/Spreadsheet-ParseExcel - */ - private function readFilepass(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - - if ($length != 54) { - throw new Exception('Unexpected file pass record length'); - } - - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->verifyPassword('VelvetSweatshop', substr($recordData, 6, 16), substr($recordData, 22, 16), substr($recordData, 38, 16), $this->md5Ctxt)) { - throw new Exception('Decryption password incorrect'); - } - - $this->encryption = self::MS_BIFF_CRYPTO_RC4; - - // Decryption required from the record after next onwards - $this->encryptionStartPos = $this->pos + self::getUInt2d($this->data, $this->pos + 2); - } - - /** - * Make an RC4 decryptor for the given block. - * - * @param int $block Block for which to create decrypto - * @param string $valContext MD5 context state - * - * @return Xls\RC4 - */ - private function makeKey($block, $valContext) - { - $pwarray = str_repeat("\0", 64); - - for ($i = 0; $i < 5; ++$i) { - $pwarray[$i] = $valContext[$i]; - } - - $pwarray[5] = chr($block & 0xff); - $pwarray[6] = chr(($block >> 8) & 0xff); - $pwarray[7] = chr(($block >> 16) & 0xff); - $pwarray[8] = chr(($block >> 24) & 0xff); - - $pwarray[9] = "\x80"; - $pwarray[56] = "\x48"; - - $md5 = new Xls\MD5(); - $md5->add($pwarray); - - $s = $md5->getContext(); - - return new Xls\RC4($s); - } - - /** - * Verify RC4 file password. - * - * @param string $password Password to check - * @param string $docid Document id - * @param string $salt_data Salt data - * @param string $hashedsalt_data Hashed salt data - * @param string $valContext Set to the MD5 context of the value - * - * @return bool Success - */ - private function verifyPassword($password, $docid, $salt_data, $hashedsalt_data, &$valContext) - { - $pwarray = str_repeat("\0", 64); - - $iMax = strlen($password); - for ($i = 0; $i < $iMax; ++$i) { - $o = ord(substr($password, $i, 1)); - $pwarray[2 * $i] = chr($o & 0xff); - $pwarray[2 * $i + 1] = chr(($o >> 8) & 0xff); - } - $pwarray[2 * $i] = chr(0x80); - $pwarray[56] = chr(($i << 4) & 0xff); - - $md5 = new Xls\MD5(); - $md5->add($pwarray); - - $mdContext1 = $md5->getContext(); - - $offset = 0; - $keyoffset = 0; - $tocopy = 5; - - $md5->reset(); - - while ($offset != 16) { - if ((64 - $offset) < 5) { - $tocopy = 64 - $offset; - } - for ($i = 0; $i <= $tocopy; ++$i) { - $pwarray[$offset + $i] = $mdContext1[$keyoffset + $i]; - } - $offset += $tocopy; - - if ($offset == 64) { - $md5->add($pwarray); - $keyoffset = $tocopy; - $tocopy = 5 - $tocopy; - $offset = 0; - - continue; - } - - $keyoffset = 0; - $tocopy = 5; - for ($i = 0; $i < 16; ++$i) { - $pwarray[$offset + $i] = $docid[$i]; - } - $offset += 16; - } - - $pwarray[16] = "\x80"; - for ($i = 0; $i < 47; ++$i) { - $pwarray[17 + $i] = "\0"; - } - $pwarray[56] = "\x80"; - $pwarray[57] = "\x0a"; - - $md5->add($pwarray); - $valContext = $md5->getContext(); - - $key = $this->makeKey(0, $valContext); - - $salt = $key->RC4($salt_data); - $hashedsalt = $key->RC4($hashedsalt_data); - - $salt .= "\x80" . str_repeat("\0", 47); - $salt[56] = "\x80"; - - $md5->reset(); - $md5->add($salt); - $mdContext2 = $md5->getContext(); - - return $mdContext2 == $hashedsalt; - } - - /** - * CODEPAGE. - * - * This record stores the text encoding used to write byte - * strings, stored as MS Windows code page identifier. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readCodepage(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; code page identifier - $codepage = self::getUInt2d($recordData, 0); - - $this->codepage = CodePage::numberToName($codepage); - } - - /** - * DATEMODE. - * - * This record specifies the base date for displaying date - * values. All dates are stored as count of days past this - * base date. In BIFF2-BIFF4 this record is part of the - * Calculation Settings Block. In BIFF5-BIFF8 it is - * stored in the Workbook Globals Substream. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readDateMode(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; 0 = base 1900, 1 = base 1904 - Date::setExcelCalendar(Date::CALENDAR_WINDOWS_1900); - if (ord($recordData[0]) == 1) { - Date::setExcelCalendar(Date::CALENDAR_MAC_1904); - } - } - - /** - * Read a FONT record. - */ - private function readFont(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - $objFont = new Font(); - - // offset: 0; size: 2; height of the font (in twips = 1/20 of a point) - $size = self::getUInt2d($recordData, 0); - $objFont->setSize($size / 20); - - // offset: 2; size: 2; option flags - // bit: 0; mask 0x0001; bold (redundant in BIFF5-BIFF8) - // bit: 1; mask 0x0002; italic - $isItalic = (0x0002 & self::getUInt2d($recordData, 2)) >> 1; - if ($isItalic) { - $objFont->setItalic(true); - } - - // bit: 2; mask 0x0004; underlined (redundant in BIFF5-BIFF8) - // bit: 3; mask 0x0008; strikethrough - $isStrike = (0x0008 & self::getUInt2d($recordData, 2)) >> 3; - if ($isStrike) { - $objFont->setStrikethrough(true); - } - - // offset: 4; size: 2; colour index - $colorIndex = self::getUInt2d($recordData, 4); - $objFont->colorIndex = $colorIndex; - - // offset: 6; size: 2; font weight - $weight = self::getUInt2d($recordData, 6); - switch ($weight) { - case 0x02BC: - $objFont->setBold(true); - - break; - } - - // offset: 8; size: 2; escapement type - $escapement = self::getUInt2d($recordData, 8); - switch ($escapement) { - case 0x0001: - $objFont->setSuperscript(true); - - break; - case 0x0002: - $objFont->setSubscript(true); - - break; - } - - // offset: 10; size: 1; underline type - $underlineType = ord($recordData[10]); - switch ($underlineType) { - case 0x00: - break; // no underline - case 0x01: - $objFont->setUnderline(Font::UNDERLINE_SINGLE); - - break; - case 0x02: - $objFont->setUnderline(Font::UNDERLINE_DOUBLE); - - break; - case 0x21: - $objFont->setUnderline(Font::UNDERLINE_SINGLEACCOUNTING); - - break; - case 0x22: - $objFont->setUnderline(Font::UNDERLINE_DOUBLEACCOUNTING); - - break; - } - - // offset: 11; size: 1; font family - // offset: 12; size: 1; character set - // offset: 13; size: 1; not used - // offset: 14; size: var; font name - if ($this->version == self::XLS_BIFF8) { - $string = self::readUnicodeStringShort(substr($recordData, 14)); - } else { - $string = $this->readByteStringShort(substr($recordData, 14)); - } - $objFont->setName($string['value']); - - $this->objFonts[] = $objFont; - } - } - - /** - * FORMAT. - * - * This record contains information about a number format. - * All FORMAT records occur together in a sequential list. - * - * In BIFF2-BIFF4 other records referencing a FORMAT record - * contain a zero-based index into this list. From BIFF5 on - * the FORMAT record contains the index itself that will be - * used by other records. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readFormat(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - $indexCode = self::getUInt2d($recordData, 0); - - if ($this->version == self::XLS_BIFF8) { - $string = self::readUnicodeStringLong(substr($recordData, 2)); - } else { - // BIFF7 - $string = $this->readByteStringShort(substr($recordData, 2)); - } - - $formatString = $string['value']; - $this->formats[$indexCode] = $formatString; - } - } - - /** - * XF - Extended Format. - * - * This record contains formatting information for cells, rows, columns or styles. - * According to https://support.microsoft.com/en-us/help/147732 there are always at least 15 cell style XF - * and 1 cell XF. - * Inspection of Excel files generated by MS Office Excel shows that XF records 0-14 are cell style XF - * and XF record 15 is a cell XF - * We only read the first cell style XF and skip the remaining cell style XF records - * We read all cell XF records. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readXf(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - $objStyle = new Style(); - - if (!$this->readDataOnly) { - // offset: 0; size: 2; Index to FONT record - if (self::getUInt2d($recordData, 0) < 4) { - $fontIndex = self::getUInt2d($recordData, 0); - } else { - // this has to do with that index 4 is omitted in all BIFF versions for some strange reason - // check the OpenOffice documentation of the FONT record - $fontIndex = self::getUInt2d($recordData, 0) - 1; - } - $objStyle->setFont($this->objFonts[$fontIndex]); - - // offset: 2; size: 2; Index to FORMAT record - $numberFormatIndex = self::getUInt2d($recordData, 2); - if (isset($this->formats[$numberFormatIndex])) { - // then we have user-defined format code - $numberFormat = ['formatCode' => $this->formats[$numberFormatIndex]]; - } elseif (($code = NumberFormat::builtInFormatCode($numberFormatIndex)) !== '') { - // then we have built-in format code - $numberFormat = ['formatCode' => $code]; - } else { - // we set the general format code - $numberFormat = ['formatCode' => 'General']; - } - $objStyle->getNumberFormat()->setFormatCode($numberFormat['formatCode']); - - // offset: 4; size: 2; XF type, cell protection, and parent style XF - // bit 2-0; mask 0x0007; XF_TYPE_PROT - $xfTypeProt = self::getUInt2d($recordData, 4); - // bit 0; mask 0x01; 1 = cell is locked - $isLocked = (0x01 & $xfTypeProt) >> 0; - $objStyle->getProtection()->setLocked($isLocked ? Protection::PROTECTION_INHERIT : Protection::PROTECTION_UNPROTECTED); - - // bit 1; mask 0x02; 1 = Formula is hidden - $isHidden = (0x02 & $xfTypeProt) >> 1; - $objStyle->getProtection()->setHidden($isHidden ? Protection::PROTECTION_PROTECTED : Protection::PROTECTION_UNPROTECTED); - - // bit 2; mask 0x04; 0 = Cell XF, 1 = Cell Style XF - $isCellStyleXf = (0x04 & $xfTypeProt) >> 2; - - // offset: 6; size: 1; Alignment and text break - // bit 2-0, mask 0x07; horizontal alignment - $horAlign = (0x07 & ord($recordData[6])) >> 0; - switch ($horAlign) { - case 0: - $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_GENERAL); - - break; - case 1: - $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_LEFT); - - break; - case 2: - $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER); - - break; - case 3: - $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_RIGHT); - - break; - case 4: - $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_FILL); - - break; - case 5: - $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_JUSTIFY); - - break; - case 6: - $objStyle->getAlignment()->setHorizontal(Alignment::HORIZONTAL_CENTER_CONTINUOUS); - - break; - } - // bit 3, mask 0x08; wrap text - $wrapText = (0x08 & ord($recordData[6])) >> 3; - switch ($wrapText) { - case 0: - $objStyle->getAlignment()->setWrapText(false); - - break; - case 1: - $objStyle->getAlignment()->setWrapText(true); - - break; - } - // bit 6-4, mask 0x70; vertical alignment - $vertAlign = (0x70 & ord($recordData[6])) >> 4; - switch ($vertAlign) { - case 0: - $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_TOP); - - break; - case 1: - $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_CENTER); - - break; - case 2: - $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_BOTTOM); - - break; - case 3: - $objStyle->getAlignment()->setVertical(Alignment::VERTICAL_JUSTIFY); - - break; - } - - if ($this->version == self::XLS_BIFF8) { - // offset: 7; size: 1; XF_ROTATION: Text rotation angle - $angle = ord($recordData[7]); - $rotation = 0; - if ($angle <= 90) { - $rotation = $angle; - } elseif ($angle <= 180) { - $rotation = 90 - $angle; - } elseif ($angle == 255) { - $rotation = -165; - } - $objStyle->getAlignment()->setTextRotation($rotation); - - // offset: 8; size: 1; Indentation, shrink to cell size, and text direction - // bit: 3-0; mask: 0x0F; indent level - $indent = (0x0F & ord($recordData[8])) >> 0; - $objStyle->getAlignment()->setIndent($indent); - - // bit: 4; mask: 0x10; 1 = shrink content to fit into cell - $shrinkToFit = (0x10 & ord($recordData[8])) >> 4; - switch ($shrinkToFit) { - case 0: - $objStyle->getAlignment()->setShrinkToFit(false); - - break; - case 1: - $objStyle->getAlignment()->setShrinkToFit(true); - - break; - } - - // offset: 9; size: 1; Flags used for attribute groups - - // offset: 10; size: 4; Cell border lines and background area - // bit: 3-0; mask: 0x0000000F; left style - if ($bordersLeftStyle = Xls\Style\Border::lookup((0x0000000F & self::getInt4d($recordData, 10)) >> 0)) { - $objStyle->getBorders()->getLeft()->setBorderStyle($bordersLeftStyle); - } - // bit: 7-4; mask: 0x000000F0; right style - if ($bordersRightStyle = Xls\Style\Border::lookup((0x000000F0 & self::getInt4d($recordData, 10)) >> 4)) { - $objStyle->getBorders()->getRight()->setBorderStyle($bordersRightStyle); - } - // bit: 11-8; mask: 0x00000F00; top style - if ($bordersTopStyle = Xls\Style\Border::lookup((0x00000F00 & self::getInt4d($recordData, 10)) >> 8)) { - $objStyle->getBorders()->getTop()->setBorderStyle($bordersTopStyle); - } - // bit: 15-12; mask: 0x0000F000; bottom style - if ($bordersBottomStyle = Xls\Style\Border::lookup((0x0000F000 & self::getInt4d($recordData, 10)) >> 12)) { - $objStyle->getBorders()->getBottom()->setBorderStyle($bordersBottomStyle); - } - // bit: 22-16; mask: 0x007F0000; left color - $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & self::getInt4d($recordData, 10)) >> 16; - - // bit: 29-23; mask: 0x3F800000; right color - $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & self::getInt4d($recordData, 10)) >> 23; - - // bit: 30; mask: 0x40000000; 1 = diagonal line from top left to right bottom - $diagonalDown = (0x40000000 & self::getInt4d($recordData, 10)) >> 30 ? true : false; - - // bit: 31; mask: 0x80000000; 1 = diagonal line from bottom left to top right - $diagonalUp = (0x80000000 & self::getInt4d($recordData, 10)) >> 31 ? true : false; - - if ($diagonalUp == false && $diagonalDown == false) { - $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_NONE); - } elseif ($diagonalUp == true && $diagonalDown == false) { - $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_UP); - } elseif ($diagonalUp == false && $diagonalDown == true) { - $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_DOWN); - } elseif ($diagonalUp == true && $diagonalDown == true) { - $objStyle->getBorders()->setDiagonalDirection(Borders::DIAGONAL_BOTH); - } - - // offset: 14; size: 4; - // bit: 6-0; mask: 0x0000007F; top color - $objStyle->getBorders()->getTop()->colorIndex = (0x0000007F & self::getInt4d($recordData, 14)) >> 0; - - // bit: 13-7; mask: 0x00003F80; bottom color - $objStyle->getBorders()->getBottom()->colorIndex = (0x00003F80 & self::getInt4d($recordData, 14)) >> 7; - - // bit: 20-14; mask: 0x001FC000; diagonal color - $objStyle->getBorders()->getDiagonal()->colorIndex = (0x001FC000 & self::getInt4d($recordData, 14)) >> 14; - - // bit: 24-21; mask: 0x01E00000; diagonal style - if ($bordersDiagonalStyle = Xls\Style\Border::lookup((0x01E00000 & self::getInt4d($recordData, 14)) >> 21)) { - $objStyle->getBorders()->getDiagonal()->setBorderStyle($bordersDiagonalStyle); - } - - // bit: 31-26; mask: 0xFC000000 fill pattern - if ($fillType = Xls\Style\FillPattern::lookup((0xFC000000 & self::getInt4d($recordData, 14)) >> 26)) { - $objStyle->getFill()->setFillType($fillType); - } - // offset: 18; size: 2; pattern and background colour - // bit: 6-0; mask: 0x007F; color index for pattern color - $objStyle->getFill()->startcolorIndex = (0x007F & self::getUInt2d($recordData, 18)) >> 0; - - // bit: 13-7; mask: 0x3F80; color index for pattern background - $objStyle->getFill()->endcolorIndex = (0x3F80 & self::getUInt2d($recordData, 18)) >> 7; - } else { - // BIFF5 - - // offset: 7; size: 1; Text orientation and flags - $orientationAndFlags = ord($recordData[7]); - - // bit: 1-0; mask: 0x03; XF_ORIENTATION: Text orientation - $xfOrientation = (0x03 & $orientationAndFlags) >> 0; - switch ($xfOrientation) { - case 0: - $objStyle->getAlignment()->setTextRotation(0); - - break; - case 1: - $objStyle->getAlignment()->setTextRotation(-165); - - break; - case 2: - $objStyle->getAlignment()->setTextRotation(90); - - break; - case 3: - $objStyle->getAlignment()->setTextRotation(-90); - - break; - } - - // offset: 8; size: 4; cell border lines and background area - $borderAndBackground = self::getInt4d($recordData, 8); - - // bit: 6-0; mask: 0x0000007F; color index for pattern color - $objStyle->getFill()->startcolorIndex = (0x0000007F & $borderAndBackground) >> 0; - - // bit: 13-7; mask: 0x00003F80; color index for pattern background - $objStyle->getFill()->endcolorIndex = (0x00003F80 & $borderAndBackground) >> 7; - - // bit: 21-16; mask: 0x003F0000; fill pattern - $objStyle->getFill()->setFillType(Xls\Style\FillPattern::lookup((0x003F0000 & $borderAndBackground) >> 16)); - - // bit: 24-22; mask: 0x01C00000; bottom line style - $objStyle->getBorders()->getBottom()->setBorderStyle(Xls\Style\Border::lookup((0x01C00000 & $borderAndBackground) >> 22)); - - // bit: 31-25; mask: 0xFE000000; bottom line color - $objStyle->getBorders()->getBottom()->colorIndex = (0xFE000000 & $borderAndBackground) >> 25; - - // offset: 12; size: 4; cell border lines - $borderLines = self::getInt4d($recordData, 12); - - // bit: 2-0; mask: 0x00000007; top line style - $objStyle->getBorders()->getTop()->setBorderStyle(Xls\Style\Border::lookup((0x00000007 & $borderLines) >> 0)); - - // bit: 5-3; mask: 0x00000038; left line style - $objStyle->getBorders()->getLeft()->setBorderStyle(Xls\Style\Border::lookup((0x00000038 & $borderLines) >> 3)); - - // bit: 8-6; mask: 0x000001C0; right line style - $objStyle->getBorders()->getRight()->setBorderStyle(Xls\Style\Border::lookup((0x000001C0 & $borderLines) >> 6)); - - // bit: 15-9; mask: 0x0000FE00; top line color index - $objStyle->getBorders()->getTop()->colorIndex = (0x0000FE00 & $borderLines) >> 9; - - // bit: 22-16; mask: 0x007F0000; left line color index - $objStyle->getBorders()->getLeft()->colorIndex = (0x007F0000 & $borderLines) >> 16; - - // bit: 29-23; mask: 0x3F800000; right line color index - $objStyle->getBorders()->getRight()->colorIndex = (0x3F800000 & $borderLines) >> 23; - } - - // add cellStyleXf or cellXf and update mapping - if ($isCellStyleXf) { - // we only read one style XF record which is always the first - if ($this->xfIndex == 0) { - $this->spreadsheet->addCellStyleXf($objStyle); - $this->mapCellStyleXfIndex[$this->xfIndex] = 0; - } - } else { - // we read all cell XF records - $this->spreadsheet->addCellXf($objStyle); - $this->mapCellXfIndex[$this->xfIndex] = count($this->spreadsheet->getCellXfCollection()) - 1; - } - - // update XF index for when we read next record - ++$this->xfIndex; - } - } - - private function readXfExt(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 2; 0x087D = repeated header - - // offset: 2; size: 2 - - // offset: 4; size: 8; not used - - // offset: 12; size: 2; record version - - // offset: 14; size: 2; index to XF record which this record modifies - $ixfe = self::getUInt2d($recordData, 14); - - // offset: 16; size: 2; not used - - // offset: 18; size: 2; number of extension properties that follow - $cexts = self::getUInt2d($recordData, 18); - - // start reading the actual extension data - $offset = 20; - while ($offset < $length) { - // extension type - $extType = self::getUInt2d($recordData, $offset); - - // extension length - $cb = self::getUInt2d($recordData, $offset + 2); - - // extension data - $extData = substr($recordData, $offset + 4, $cb); - - switch ($extType) { - case 4: // fill start color - $xclfType = self::getUInt2d($extData, 0); // color type - $xclrValue = substr($extData, 4, 4); // color value (value based on color type) - - if ($xclfType == 2) { - $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); - - // modify the relevant style property - if (isset($this->mapCellXfIndex[$ixfe])) { - $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill(); - $fill->getStartColor()->setRGB($rgb); - $fill->startcolorIndex = null; // normal color index does not apply, discard - } - } - - break; - case 5: // fill end color - $xclfType = self::getUInt2d($extData, 0); // color type - $xclrValue = substr($extData, 4, 4); // color value (value based on color type) - - if ($xclfType == 2) { - $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); - - // modify the relevant style property - if (isset($this->mapCellXfIndex[$ixfe])) { - $fill = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFill(); - $fill->getEndColor()->setRGB($rgb); - $fill->endcolorIndex = null; // normal color index does not apply, discard - } - } - - break; - case 7: // border color top - $xclfType = self::getUInt2d($extData, 0); // color type - $xclrValue = substr($extData, 4, 4); // color value (value based on color type) - - if ($xclfType == 2) { - $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); - - // modify the relevant style property - if (isset($this->mapCellXfIndex[$ixfe])) { - $top = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getTop(); - $top->getColor()->setRGB($rgb); - $top->colorIndex = null; // normal color index does not apply, discard - } - } - - break; - case 8: // border color bottom - $xclfType = self::getUInt2d($extData, 0); // color type - $xclrValue = substr($extData, 4, 4); // color value (value based on color type) - - if ($xclfType == 2) { - $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); - - // modify the relevant style property - if (isset($this->mapCellXfIndex[$ixfe])) { - $bottom = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getBottom(); - $bottom->getColor()->setRGB($rgb); - $bottom->colorIndex = null; // normal color index does not apply, discard - } - } - - break; - case 9: // border color left - $xclfType = self::getUInt2d($extData, 0); // color type - $xclrValue = substr($extData, 4, 4); // color value (value based on color type) - - if ($xclfType == 2) { - $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); - - // modify the relevant style property - if (isset($this->mapCellXfIndex[$ixfe])) { - $left = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getLeft(); - $left->getColor()->setRGB($rgb); - $left->colorIndex = null; // normal color index does not apply, discard - } - } - - break; - case 10: // border color right - $xclfType = self::getUInt2d($extData, 0); // color type - $xclrValue = substr($extData, 4, 4); // color value (value based on color type) - - if ($xclfType == 2) { - $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); - - // modify the relevant style property - if (isset($this->mapCellXfIndex[$ixfe])) { - $right = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getRight(); - $right->getColor()->setRGB($rgb); - $right->colorIndex = null; // normal color index does not apply, discard - } - } - - break; - case 11: // border color diagonal - $xclfType = self::getUInt2d($extData, 0); // color type - $xclrValue = substr($extData, 4, 4); // color value (value based on color type) - - if ($xclfType == 2) { - $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); - - // modify the relevant style property - if (isset($this->mapCellXfIndex[$ixfe])) { - $diagonal = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getBorders()->getDiagonal(); - $diagonal->getColor()->setRGB($rgb); - $diagonal->colorIndex = null; // normal color index does not apply, discard - } - } - - break; - case 13: // font color - $xclfType = self::getUInt2d($extData, 0); // color type - $xclrValue = substr($extData, 4, 4); // color value (value based on color type) - - if ($xclfType == 2) { - $rgb = sprintf('%02X%02X%02X', ord($xclrValue[0]), ord($xclrValue[1]), ord($xclrValue[2])); - - // modify the relevant style property - if (isset($this->mapCellXfIndex[$ixfe])) { - $font = $this->spreadsheet->getCellXfByIndex($this->mapCellXfIndex[$ixfe])->getFont(); - $font->getColor()->setRGB($rgb); - $font->colorIndex = null; // normal color index does not apply, discard - } - } - - break; - } - - $offset += $cb; - } - } - } - - /** - * Read STYLE record. - */ - private function readStyle(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 2; index to XF record and flag for built-in style - $ixfe = self::getUInt2d($recordData, 0); - - // bit: 11-0; mask 0x0FFF; index to XF record - $xfIndex = (0x0FFF & $ixfe) >> 0; - - // bit: 15; mask 0x8000; 0 = user-defined style, 1 = built-in style - $isBuiltIn = (bool) ((0x8000 & $ixfe) >> 15); - - if ($isBuiltIn) { - // offset: 2; size: 1; identifier for built-in style - $builtInId = ord($recordData[2]); - - switch ($builtInId) { - case 0x00: - // currently, we are not using this for anything - break; - default: - break; - } - } - // user-defined; not supported by PhpSpreadsheet - } - } - - /** - * Read PALETTE record. - */ - private function readPalette(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 2; number of following colors - $nm = self::getUInt2d($recordData, 0); - - // list of RGB colors - for ($i = 0; $i < $nm; ++$i) { - $rgb = substr($recordData, 2 + 4 * $i, 4); - $this->palette[] = self::readRGB($rgb); - } - } - } - - /** - * SHEET. - * - * This record is located in the Workbook Globals - * Substream and represents a sheet inside the workbook. - * One SHEET record is written for each sheet. It stores the - * sheet name and a stream offset to the BOF record of the - * respective Sheet Substream within the Workbook Stream. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readSheet(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // offset: 0; size: 4; absolute stream position of the BOF record of the sheet - // NOTE: not encrypted - $rec_offset = self::getInt4d($this->data, $this->pos + 4); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 4; size: 1; sheet state - switch (ord($recordData[4])) { - case 0x00: - $sheetState = Worksheet::SHEETSTATE_VISIBLE; - - break; - case 0x01: - $sheetState = Worksheet::SHEETSTATE_HIDDEN; - - break; - case 0x02: - $sheetState = Worksheet::SHEETSTATE_VERYHIDDEN; - - break; - default: - $sheetState = Worksheet::SHEETSTATE_VISIBLE; - - break; - } - - // offset: 5; size: 1; sheet type - $sheetType = ord($recordData[5]); - - // offset: 6; size: var; sheet name - if ($this->version == self::XLS_BIFF8) { - $string = self::readUnicodeStringShort(substr($recordData, 6)); - $rec_name = $string['value']; - } elseif ($this->version == self::XLS_BIFF7) { - $string = $this->readByteStringShort(substr($recordData, 6)); - $rec_name = $string['value']; - } - - $this->sheets[] = [ - 'name' => $rec_name, - 'offset' => $rec_offset, - 'sheetState' => $sheetState, - 'sheetType' => $sheetType, - ]; - } - - /** - * Read EXTERNALBOOK record. - */ - private function readExternalBook(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset within record data - $offset = 0; - - // there are 4 types of records - if (strlen($recordData) > 4) { - // external reference - // offset: 0; size: 2; number of sheet names ($nm) - $nm = self::getUInt2d($recordData, 0); - $offset += 2; - - // offset: 2; size: var; encoded URL without sheet name (Unicode string, 16-bit length) - $encodedUrlString = self::readUnicodeStringLong(substr($recordData, 2)); - $offset += $encodedUrlString['size']; - - // offset: var; size: var; list of $nm sheet names (Unicode strings, 16-bit length) - $externalSheetNames = []; - for ($i = 0; $i < $nm; ++$i) { - $externalSheetNameString = self::readUnicodeStringLong(substr($recordData, $offset)); - $externalSheetNames[] = $externalSheetNameString['value']; - $offset += $externalSheetNameString['size']; - } - - // store the record data - $this->externalBooks[] = [ - 'type' => 'external', - 'encodedUrl' => $encodedUrlString['value'], - 'externalSheetNames' => $externalSheetNames, - ]; - } elseif (substr($recordData, 2, 2) == pack('CC', 0x01, 0x04)) { - // internal reference - // offset: 0; size: 2; number of sheet in this document - // offset: 2; size: 2; 0x01 0x04 - $this->externalBooks[] = [ - 'type' => 'internal', - ]; - } elseif (substr($recordData, 0, 4) == pack('vCC', 0x0001, 0x01, 0x3A)) { - // add-in function - // offset: 0; size: 2; 0x0001 - $this->externalBooks[] = [ - 'type' => 'addInFunction', - ]; - } elseif (substr($recordData, 0, 2) == pack('v', 0x0000)) { - // DDE links, OLE links - // offset: 0; size: 2; 0x0000 - // offset: 2; size: var; encoded source document name - $this->externalBooks[] = [ - 'type' => 'DDEorOLE', - ]; - } - } - - /** - * Read EXTERNNAME record. - */ - private function readExternName(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // external sheet references provided for named cells - if ($this->version == self::XLS_BIFF8) { - // offset: 0; size: 2; options - $options = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; - - // offset: 4; size: 2; not used - - // offset: 6; size: var - $nameString = self::readUnicodeStringShort(substr($recordData, 6)); - - // offset: var; size: var; formula data - $offset = 6 + $nameString['size']; - $formula = $this->getFormulaFromStructure(substr($recordData, $offset)); - - $this->externalNames[] = [ - 'name' => $nameString['value'], - 'formula' => $formula, - ]; - } - } - - /** - * Read EXTERNSHEET record. - */ - private function readExternSheet(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // external sheet references provided for named cells - if ($this->version == self::XLS_BIFF8) { - // offset: 0; size: 2; number of following ref structures - $nm = self::getUInt2d($recordData, 0); - for ($i = 0; $i < $nm; ++$i) { - $this->ref[] = [ - // offset: 2 + 6 * $i; index to EXTERNALBOOK record - 'externalBookIndex' => self::getUInt2d($recordData, 2 + 6 * $i), - // offset: 4 + 6 * $i; index to first sheet in EXTERNALBOOK record - 'firstSheetIndex' => self::getUInt2d($recordData, 4 + 6 * $i), - // offset: 6 + 6 * $i; index to last sheet in EXTERNALBOOK record - 'lastSheetIndex' => self::getUInt2d($recordData, 6 + 6 * $i), - ]; - } - } - } - - /** - * DEFINEDNAME. - * - * This record is part of a Link Table. It contains the name - * and the token array of an internal defined name. Token - * arrays of defined names contain tokens with aberrant - * token classes. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readDefinedName(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if ($this->version == self::XLS_BIFF8) { - // retrieves named cells - - // offset: 0; size: 2; option flags - $opts = self::getUInt2d($recordData, 0); - - // bit: 5; mask: 0x0020; 0 = user-defined name, 1 = built-in-name - $isBuiltInName = (0x0020 & $opts) >> 5; - - // offset: 2; size: 1; keyboard shortcut - - // offset: 3; size: 1; length of the name (character count) - $nlen = ord($recordData[3]); - - // offset: 4; size: 2; size of the formula data (it can happen that this is zero) - // note: there can also be additional data, this is not included in $flen - $flen = self::getUInt2d($recordData, 4); - - // offset: 8; size: 2; 0=Global name, otherwise index to sheet (1-based) - $scope = self::getUInt2d($recordData, 8); - - // offset: 14; size: var; Name (Unicode string without length field) - $string = self::readUnicodeString(substr($recordData, 14), $nlen); - - // offset: var; size: $flen; formula data - $offset = 14 + $string['size']; - $formulaStructure = pack('v', $flen) . substr($recordData, $offset); - - try { - $formula = $this->getFormulaFromStructure($formulaStructure); - } catch (PhpSpreadsheetException $e) { - $formula = ''; - } - - $this->definedname[] = [ - 'isBuiltInName' => $isBuiltInName, - 'name' => $string['value'], - 'formula' => $formula, - 'scope' => $scope, - ]; - } - } - - /** - * Read MSODRAWINGGROUP record. - */ - private function readMsoDrawingGroup(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - - // get spliced record data - $splicedRecordData = $this->getSplicedRecordData(); - $recordData = $splicedRecordData['recordData']; - - $this->drawingGroupData .= $recordData; - } - - /** - * SST - Shared String Table. - * - * This record contains a list of all strings used anywhere - * in the workbook. Each string occurs only once. The - * workbook uses indexes into the list to reference the - * strings. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readSst(): void - { - // offset within (spliced) record data - $pos = 0; - - // Limit global SST position, further control for bad SST Length in BIFF8 data - $limitposSST = 0; - - // get spliced record data - $splicedRecordData = $this->getSplicedRecordData(); - - $recordData = $splicedRecordData['recordData']; - $spliceOffsets = $splicedRecordData['spliceOffsets']; - - // offset: 0; size: 4; total number of strings in the workbook - $pos += 4; - - // offset: 4; size: 4; number of following strings ($nm) - $nm = self::getInt4d($recordData, 4); - $pos += 4; - - // look up limit position - foreach ($spliceOffsets as $spliceOffset) { - // it can happen that the string is empty, therefore we need - // <= and not just < - if ($pos <= $spliceOffset) { - $limitposSST = $spliceOffset; - } - } - - // loop through the Unicode strings (16-bit length) - for ($i = 0; $i < $nm && $pos < $limitposSST; ++$i) { - // number of characters in the Unicode string - $numChars = self::getUInt2d($recordData, $pos); - $pos += 2; - - // option flags - $optionFlags = ord($recordData[$pos]); - ++$pos; - - // bit: 0; mask: 0x01; 0 = compressed; 1 = uncompressed - $isCompressed = (($optionFlags & 0x01) == 0); - - // bit: 2; mask: 0x02; 0 = ordinary; 1 = Asian phonetic - $hasAsian = (($optionFlags & 0x04) != 0); - - // bit: 3; mask: 0x03; 0 = ordinary; 1 = Rich-Text - $hasRichText = (($optionFlags & 0x08) != 0); - - if ($hasRichText) { - // number of Rich-Text formatting runs - $formattingRuns = self::getUInt2d($recordData, $pos); - $pos += 2; - } - - if ($hasAsian) { - // size of Asian phonetic setting - $extendedRunLength = self::getInt4d($recordData, $pos); - $pos += 4; - } - - // expected byte length of character array if not split - $len = ($isCompressed) ? $numChars : $numChars * 2; - - // look up limit position - Check it again to be sure that no error occurs when parsing SST structure - foreach ($spliceOffsets as $spliceOffset) { - // it can happen that the string is empty, therefore we need - // <= and not just < - if ($pos <= $spliceOffset) { - $limitpos = $spliceOffset; - - break; - } - } - - if ($pos + $len <= $limitpos) { - // character array is not split between records - - $retstr = substr($recordData, $pos, $len); - $pos += $len; - } else { - // character array is split between records - - // first part of character array - $retstr = substr($recordData, $pos, $limitpos - $pos); - - $bytesRead = $limitpos - $pos; - - // remaining characters in Unicode string - $charsLeft = $numChars - (($isCompressed) ? $bytesRead : ($bytesRead / 2)); - - $pos = $limitpos; - - // keep reading the characters - while ($charsLeft > 0) { - // look up next limit position, in case the string span more than one continue record - foreach ($spliceOffsets as $spliceOffset) { - if ($pos < $spliceOffset) { - $limitpos = $spliceOffset; - - break; - } - } - - // repeated option flags - // OpenOffice.org documentation 5.21 - $option = ord($recordData[$pos]); - ++$pos; - - if ($isCompressed && ($option == 0)) { - // 1st fragment compressed - // this fragment compressed - $len = min($charsLeft, $limitpos - $pos); - $retstr .= substr($recordData, $pos, $len); - $charsLeft -= $len; - $isCompressed = true; - } elseif (!$isCompressed && ($option != 0)) { - // 1st fragment uncompressed - // this fragment uncompressed - $len = min($charsLeft * 2, $limitpos - $pos); - $retstr .= substr($recordData, $pos, $len); - $charsLeft -= $len / 2; - $isCompressed = false; - } elseif (!$isCompressed && ($option == 0)) { - // 1st fragment uncompressed - // this fragment compressed - $len = min($charsLeft, $limitpos - $pos); - for ($j = 0; $j < $len; ++$j) { - $retstr .= $recordData[$pos + $j] - . chr(0); - } - $charsLeft -= $len; - $isCompressed = false; - } else { - // 1st fragment compressed - // this fragment uncompressed - $newstr = ''; - $jMax = strlen($retstr); - for ($j = 0; $j < $jMax; ++$j) { - $newstr .= $retstr[$j] . chr(0); - } - $retstr = $newstr; - $len = min($charsLeft * 2, $limitpos - $pos); - $retstr .= substr($recordData, $pos, $len); - $charsLeft -= $len / 2; - $isCompressed = false; - } - - $pos += $len; - } - } - - // convert to UTF-8 - $retstr = self::encodeUTF16($retstr, $isCompressed); - - // read additional Rich-Text information, if any - $fmtRuns = []; - if ($hasRichText) { - // list of formatting runs - for ($j = 0; $j < $formattingRuns; ++$j) { - // first formatted character; zero-based - $charPos = self::getUInt2d($recordData, $pos + $j * 4); - - // index to font record - $fontIndex = self::getUInt2d($recordData, $pos + 2 + $j * 4); - - $fmtRuns[] = [ - 'charPos' => $charPos, - 'fontIndex' => $fontIndex, - ]; - } - $pos += 4 * $formattingRuns; - } - - // read additional Asian phonetics information, if any - if ($hasAsian) { - // For Asian phonetic settings, we skip the extended string data - $pos += $extendedRunLength; - } - - // store the shared sting - $this->sst[] = [ - 'value' => $retstr, - 'fmtRuns' => $fmtRuns, - ]; - } - - // getSplicedRecordData() takes care of moving current position in data stream - } - - /** - * Read PRINTGRIDLINES record. - */ - private function readPrintGridlines(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) { - // offset: 0; size: 2; 0 = do not print sheet grid lines; 1 = print sheet gridlines - $printGridlines = (bool) self::getUInt2d($recordData, 0); - $this->phpSheet->setPrintGridlines($printGridlines); - } - } - - /** - * Read DEFAULTROWHEIGHT record. - */ - private function readDefaultRowHeight(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; option flags - // offset: 2; size: 2; default height for unused rows, (twips 1/20 point) - $height = self::getUInt2d($recordData, 2); - $this->phpSheet->getDefaultRowDimension()->setRowHeight($height / 20); - } - - /** - * Read SHEETPR record. - */ - private function readSheetPr(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2 - - // bit: 6; mask: 0x0040; 0 = outline buttons above outline group - $isSummaryBelow = (0x0040 & self::getUInt2d($recordData, 0)) >> 6; - $this->phpSheet->setShowSummaryBelow($isSummaryBelow); - - // bit: 7; mask: 0x0080; 0 = outline buttons left of outline group - $isSummaryRight = (0x0080 & self::getUInt2d($recordData, 0)) >> 7; - $this->phpSheet->setShowSummaryRight($isSummaryRight); - - // bit: 8; mask: 0x100; 0 = scale printout in percent, 1 = fit printout to number of pages - // this corresponds to radio button setting in page setup dialog in Excel - $this->isFitToPages = (bool) ((0x0100 & self::getUInt2d($recordData, 0)) >> 8); - } - - /** - * Read HORIZONTALPAGEBREAKS record. - */ - private function readHorizontalPageBreaks(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) { - // offset: 0; size: 2; number of the following row index structures - $nm = self::getUInt2d($recordData, 0); - - // offset: 2; size: 6 * $nm; list of $nm row index structures - for ($i = 0; $i < $nm; ++$i) { - $r = self::getUInt2d($recordData, 2 + 6 * $i); - $cf = self::getUInt2d($recordData, 2 + 6 * $i + 2); - $cl = self::getUInt2d($recordData, 2 + 6 * $i + 4); - - // not sure why two column indexes are necessary? - $this->phpSheet->setBreakByColumnAndRow($cf + 1, $r, Worksheet::BREAK_ROW); - } - } - } - - /** - * Read VERTICALPAGEBREAKS record. - */ - private function readVerticalPageBreaks(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) { - // offset: 0; size: 2; number of the following column index structures - $nm = self::getUInt2d($recordData, 0); - - // offset: 2; size: 6 * $nm; list of $nm row index structures - for ($i = 0; $i < $nm; ++$i) { - $c = self::getUInt2d($recordData, 2 + 6 * $i); - $rf = self::getUInt2d($recordData, 2 + 6 * $i + 2); - $rl = self::getUInt2d($recordData, 2 + 6 * $i + 4); - - // not sure why two row indexes are necessary? - $this->phpSheet->setBreakByColumnAndRow($c + 1, $rf, Worksheet::BREAK_COLUMN); - } - } - } - - /** - * Read HEADER record. - */ - private function readHeader(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: var - // realized that $recordData can be empty even when record exists - if ($recordData) { - if ($this->version == self::XLS_BIFF8) { - $string = self::readUnicodeStringLong($recordData); - } else { - $string = $this->readByteStringShort($recordData); - } - - $this->phpSheet->getHeaderFooter()->setOddHeader($string['value']); - $this->phpSheet->getHeaderFooter()->setEvenHeader($string['value']); - } - } - } - - /** - * Read FOOTER record. - */ - private function readFooter(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: var - // realized that $recordData can be empty even when record exists - if ($recordData) { - if ($this->version == self::XLS_BIFF8) { - $string = self::readUnicodeStringLong($recordData); - } else { - $string = $this->readByteStringShort($recordData); - } - $this->phpSheet->getHeaderFooter()->setOddFooter($string['value']); - $this->phpSheet->getHeaderFooter()->setEvenFooter($string['value']); - } - } - } - - /** - * Read HCENTER record. - */ - private function readHcenter(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 2; 0 = print sheet left aligned, 1 = print sheet centered horizontally - $isHorizontalCentered = (bool) self::getUInt2d($recordData, 0); - - $this->phpSheet->getPageSetup()->setHorizontalCentered($isHorizontalCentered); - } - } - - /** - * Read VCENTER record. - */ - private function readVcenter(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 2; 0 = print sheet aligned at top page border, 1 = print sheet vertically centered - $isVerticalCentered = (bool) self::getUInt2d($recordData, 0); - - $this->phpSheet->getPageSetup()->setVerticalCentered($isVerticalCentered); - } - } - - /** - * Read LEFTMARGIN record. - */ - private function readLeftMargin(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 8 - $this->phpSheet->getPageMargins()->setLeft(self::extractNumber($recordData)); - } - } - - /** - * Read RIGHTMARGIN record. - */ - private function readRightMargin(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 8 - $this->phpSheet->getPageMargins()->setRight(self::extractNumber($recordData)); - } - } - - /** - * Read TOPMARGIN record. - */ - private function readTopMargin(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 8 - $this->phpSheet->getPageMargins()->setTop(self::extractNumber($recordData)); - } - } - - /** - * Read BOTTOMMARGIN record. - */ - private function readBottomMargin(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 8 - $this->phpSheet->getPageMargins()->setBottom(self::extractNumber($recordData)); - } - } - - /** - * Read PAGESETUP record. - */ - private function readPageSetup(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 2; paper size - $paperSize = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; scaling factor - $scale = self::getUInt2d($recordData, 2); - - // offset: 6; size: 2; fit worksheet width to this number of pages, 0 = use as many as needed - $fitToWidth = self::getUInt2d($recordData, 6); - - // offset: 8; size: 2; fit worksheet height to this number of pages, 0 = use as many as needed - $fitToHeight = self::getUInt2d($recordData, 8); - - // offset: 10; size: 2; option flags - - // bit: 0; mask: 0x0001; 0=down then over, 1=over then down - $isOverThenDown = (0x0001 & self::getUInt2d($recordData, 10)); - - // bit: 1; mask: 0x0002; 0=landscape, 1=portrait - $isPortrait = (0x0002 & self::getUInt2d($recordData, 10)) >> 1; - - // bit: 2; mask: 0x0004; 1= paper size, scaling factor, paper orient. not init - // when this bit is set, do not use flags for those properties - $isNotInit = (0x0004 & self::getUInt2d($recordData, 10)) >> 2; - - if (!$isNotInit) { - $this->phpSheet->getPageSetup()->setPaperSize($paperSize); - $this->phpSheet->getPageSetup()->setPageOrder(((bool) $isOverThenDown) ? PageSetup::PAGEORDER_OVER_THEN_DOWN : PageSetup::PAGEORDER_DOWN_THEN_OVER); - $this->phpSheet->getPageSetup()->setOrientation(((bool) $isPortrait) ? PageSetup::ORIENTATION_PORTRAIT : PageSetup::ORIENTATION_LANDSCAPE); - - $this->phpSheet->getPageSetup()->setScale($scale, false); - $this->phpSheet->getPageSetup()->setFitToPage((bool) $this->isFitToPages); - $this->phpSheet->getPageSetup()->setFitToWidth($fitToWidth, false); - $this->phpSheet->getPageSetup()->setFitToHeight($fitToHeight, false); - } - - // offset: 16; size: 8; header margin (IEEE 754 floating-point value) - $marginHeader = self::extractNumber(substr($recordData, 16, 8)); - $this->phpSheet->getPageMargins()->setHeader($marginHeader); - - // offset: 24; size: 8; footer margin (IEEE 754 floating-point value) - $marginFooter = self::extractNumber(substr($recordData, 24, 8)); - $this->phpSheet->getPageMargins()->setFooter($marginFooter); - } - } - - /** - * PROTECT - Sheet protection (BIFF2 through BIFF8) - * if this record is omitted, then it also means no sheet protection. - */ - private function readProtect(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if ($this->readDataOnly) { - return; - } - - // offset: 0; size: 2; - - // bit 0, mask 0x01; 1 = sheet is protected - $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0; - $this->phpSheet->getProtection()->setSheet((bool) $bool); - } - - /** - * SCENPROTECT. - */ - private function readScenProtect(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if ($this->readDataOnly) { - return; - } - - // offset: 0; size: 2; - - // bit: 0, mask 0x01; 1 = scenarios are protected - $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0; - - $this->phpSheet->getProtection()->setScenarios((bool) $bool); - } - - /** - * OBJECTPROTECT. - */ - private function readObjectProtect(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if ($this->readDataOnly) { - return; - } - - // offset: 0; size: 2; - - // bit: 0, mask 0x01; 1 = objects are protected - $bool = (0x01 & self::getUInt2d($recordData, 0)) >> 0; - - $this->phpSheet->getProtection()->setObjects((bool) $bool); - } - - /** - * PASSWORD - Sheet protection (hashed) password (BIFF2 through BIFF8). - */ - private function readPassword(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 2; 16-bit hash value of password - $password = strtoupper(dechex(self::getUInt2d($recordData, 0))); // the hashed password - $this->phpSheet->getProtection()->setPassword($password, true); - } - } - - /** - * Read DEFCOLWIDTH record. - */ - private function readDefColWidth(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; default column width - $width = self::getUInt2d($recordData, 0); - if ($width != 8) { - $this->phpSheet->getDefaultColumnDimension()->setWidth($width); - } - } - - /** - * Read COLINFO record. - */ - private function readColInfo(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 2; index to first column in range - $firstColumnIndex = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; index to last column in range - $lastColumnIndex = self::getUInt2d($recordData, 2); - - // offset: 4; size: 2; width of the column in 1/256 of the width of the zero character - $width = self::getUInt2d($recordData, 4); - - // offset: 6; size: 2; index to XF record for default column formatting - $xfIndex = self::getUInt2d($recordData, 6); - - // offset: 8; size: 2; option flags - // bit: 0; mask: 0x0001; 1= columns are hidden - $isHidden = (0x0001 & self::getUInt2d($recordData, 8)) >> 0; - - // bit: 10-8; mask: 0x0700; outline level of the columns (0 = no outline) - $level = (0x0700 & self::getUInt2d($recordData, 8)) >> 8; - - // bit: 12; mask: 0x1000; 1 = collapsed - $isCollapsed = (0x1000 & self::getUInt2d($recordData, 8)) >> 12; - - // offset: 10; size: 2; not used - - for ($i = $firstColumnIndex + 1; $i <= $lastColumnIndex + 1; ++$i) { - if ($lastColumnIndex == 255 || $lastColumnIndex == 256) { - $this->phpSheet->getDefaultColumnDimension()->setWidth($width / 256); - - break; - } - $this->phpSheet->getColumnDimensionByColumn($i)->setWidth($width / 256); - $this->phpSheet->getColumnDimensionByColumn($i)->setVisible(!$isHidden); - $this->phpSheet->getColumnDimensionByColumn($i)->setOutlineLevel($level); - $this->phpSheet->getColumnDimensionByColumn($i)->setCollapsed($isCollapsed); - if (isset($this->mapCellXfIndex[$xfIndex])) { - $this->phpSheet->getColumnDimensionByColumn($i)->setXfIndex($this->mapCellXfIndex[$xfIndex]); - } - } - } - } - - /** - * ROW. - * - * This record contains the properties of a single row in a - * sheet. Rows and cells in a sheet are divided into blocks - * of 32 rows. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readRow(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 2; index of this row - $r = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; index to column of the first cell which is described by a cell record - - // offset: 4; size: 2; index to column of the last cell which is described by a cell record, increased by 1 - - // offset: 6; size: 2; - - // bit: 14-0; mask: 0x7FFF; height of the row, in twips = 1/20 of a point - $height = (0x7FFF & self::getUInt2d($recordData, 6)) >> 0; - - // bit: 15: mask: 0x8000; 0 = row has custom height; 1= row has default height - $useDefaultHeight = (0x8000 & self::getUInt2d($recordData, 6)) >> 15; - - if (!$useDefaultHeight) { - $this->phpSheet->getRowDimension($r + 1)->setRowHeight($height / 20); - } - - // offset: 8; size: 2; not used - - // offset: 10; size: 2; not used in BIFF5-BIFF8 - - // offset: 12; size: 4; option flags and default row formatting - - // bit: 2-0: mask: 0x00000007; outline level of the row - $level = (0x00000007 & self::getInt4d($recordData, 12)) >> 0; - $this->phpSheet->getRowDimension($r + 1)->setOutlineLevel($level); - - // bit: 4; mask: 0x00000010; 1 = outline group start or ends here... and is collapsed - $isCollapsed = (0x00000010 & self::getInt4d($recordData, 12)) >> 4; - $this->phpSheet->getRowDimension($r + 1)->setCollapsed($isCollapsed); - - // bit: 5; mask: 0x00000020; 1 = row is hidden - $isHidden = (0x00000020 & self::getInt4d($recordData, 12)) >> 5; - $this->phpSheet->getRowDimension($r + 1)->setVisible(!$isHidden); - - // bit: 7; mask: 0x00000080; 1 = row has explicit format - $hasExplicitFormat = (0x00000080 & self::getInt4d($recordData, 12)) >> 7; - - // bit: 27-16; mask: 0x0FFF0000; only applies when hasExplicitFormat = 1; index to XF record - $xfIndex = (0x0FFF0000 & self::getInt4d($recordData, 12)) >> 16; - - if ($hasExplicitFormat && isset($this->mapCellXfIndex[$xfIndex])) { - $this->phpSheet->getRowDimension($r + 1)->setXfIndex($this->mapCellXfIndex[$xfIndex]); - } - } - } - - /** - * Read RK record - * This record represents a cell that contains an RK value - * (encoded integer or floating-point value). If a - * floating-point value cannot be encoded to an RK value, - * a NUMBER record will be written. This record replaces the - * record INTEGER written in BIFF2. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readRk(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; index to row - $row = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; index to column - $column = self::getUInt2d($recordData, 2); - $columnString = Coordinate::stringFromColumnIndex($column + 1); - - // Read cell? - if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { - // offset: 4; size: 2; index to XF record - $xfIndex = self::getUInt2d($recordData, 4); - - // offset: 6; size: 4; RK value - $rknum = self::getInt4d($recordData, 6); - $numValue = self::getIEEE754($rknum); - - $cell = $this->phpSheet->getCell($columnString . ($row + 1)); - if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { - // add style information - $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); - } - - // add cell - $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC); - } - } - - /** - * Read LABELSST record - * This record represents a cell that contains a string. It - * replaces the LABEL record and RSTRING record used in - * BIFF2-BIFF5. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readLabelSst(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; index to row - $row = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; index to column - $column = self::getUInt2d($recordData, 2); - $columnString = Coordinate::stringFromColumnIndex($column + 1); - - $emptyCell = true; - // Read cell? - if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { - // offset: 4; size: 2; index to XF record - $xfIndex = self::getUInt2d($recordData, 4); - - // offset: 6; size: 4; index to SST record - $index = self::getInt4d($recordData, 6); - - // add cell - if (($fmtRuns = $this->sst[$index]['fmtRuns']) && !$this->readDataOnly) { - // then we should treat as rich text - $richText = new RichText(); - $charPos = 0; - $sstCount = count($this->sst[$index]['fmtRuns']); - for ($i = 0; $i <= $sstCount; ++$i) { - if (isset($fmtRuns[$i])) { - $text = StringHelper::substring($this->sst[$index]['value'], $charPos, $fmtRuns[$i]['charPos'] - $charPos); - $charPos = $fmtRuns[$i]['charPos']; - } else { - $text = StringHelper::substring($this->sst[$index]['value'], $charPos, StringHelper::countCharacters($this->sst[$index]['value'])); - } - - if (StringHelper::countCharacters($text) > 0) { - if ($i == 0) { // first text run, no style - $richText->createText($text); - } else { - $textRun = $richText->createTextRun($text); - if (isset($fmtRuns[$i - 1])) { - if ($fmtRuns[$i - 1]['fontIndex'] < 4) { - $fontIndex = $fmtRuns[$i - 1]['fontIndex']; - } else { - // this has to do with that index 4 is omitted in all BIFF versions for some strange reason - // check the OpenOffice documentation of the FONT record - $fontIndex = $fmtRuns[$i - 1]['fontIndex'] - 1; - } - $textRun->setFont(clone $this->objFonts[$fontIndex]); - } - } - } - } - if ($this->readEmptyCells || trim($richText->getPlainText()) !== '') { - $cell = $this->phpSheet->getCell($columnString . ($row + 1)); - $cell->setValueExplicit($richText, DataType::TYPE_STRING); - $emptyCell = false; - } - } else { - if ($this->readEmptyCells || trim($this->sst[$index]['value']) !== '') { - $cell = $this->phpSheet->getCell($columnString . ($row + 1)); - $cell->setValueExplicit($this->sst[$index]['value'], DataType::TYPE_STRING); - $emptyCell = false; - } - } - - if (!$this->readDataOnly && !$emptyCell && isset($this->mapCellXfIndex[$xfIndex])) { - // add style information - $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); - } - } - } - - /** - * Read MULRK record - * This record represents a cell range containing RK value - * cells. All cells are located in the same row. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readMulRk(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; index to row - $row = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; index to first column - $colFirst = self::getUInt2d($recordData, 2); - - // offset: var; size: 2; index to last column - $colLast = self::getUInt2d($recordData, $length - 2); - $columns = $colLast - $colFirst + 1; - - // offset within record data - $offset = 4; - - for ($i = 1; $i <= $columns; ++$i) { - $columnString = Coordinate::stringFromColumnIndex($colFirst + $i); - - // Read cell? - if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { - // offset: var; size: 2; index to XF record - $xfIndex = self::getUInt2d($recordData, $offset); - - // offset: var; size: 4; RK value - $numValue = self::getIEEE754(self::getInt4d($recordData, $offset + 2)); - $cell = $this->phpSheet->getCell($columnString . ($row + 1)); - if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { - // add style - $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); - } - - // add cell value - $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC); - } - - $offset += 6; - } - } - - /** - * Read NUMBER record - * This record represents a cell that contains a - * floating-point value. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readNumber(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; index to row - $row = self::getUInt2d($recordData, 0); - - // offset: 2; size 2; index to column - $column = self::getUInt2d($recordData, 2); - $columnString = Coordinate::stringFromColumnIndex($column + 1); - - // Read cell? - if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { - // offset 4; size: 2; index to XF record - $xfIndex = self::getUInt2d($recordData, 4); - - $numValue = self::extractNumber(substr($recordData, 6, 8)); - - $cell = $this->phpSheet->getCell($columnString . ($row + 1)); - if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { - // add cell style - $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); - } - - // add cell value - $cell->setValueExplicit($numValue, DataType::TYPE_NUMERIC); - } - } - - /** - * Read FORMULA record + perhaps a following STRING record if formula result is a string - * This record contains the token array and the result of a - * formula cell. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readFormula(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; row index - $row = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; col index - $column = self::getUInt2d($recordData, 2); - $columnString = Coordinate::stringFromColumnIndex($column + 1); - - // offset: 20: size: variable; formula structure - $formulaStructure = substr($recordData, 20); - - // offset: 14: size: 2; option flags, recalculate always, recalculate on open etc. - $options = self::getUInt2d($recordData, 14); - - // bit: 0; mask: 0x0001; 1 = recalculate always - // bit: 1; mask: 0x0002; 1 = calculate on open - // bit: 2; mask: 0x0008; 1 = part of a shared formula - $isPartOfSharedFormula = (bool) (0x0008 & $options); - - // WARNING: - // We can apparently not rely on $isPartOfSharedFormula. Even when $isPartOfSharedFormula = true - // the formula data may be ordinary formula data, therefore we need to check - // explicitly for the tExp token (0x01) - $isPartOfSharedFormula = $isPartOfSharedFormula && ord($formulaStructure[2]) == 0x01; - - if ($isPartOfSharedFormula) { - // part of shared formula which means there will be a formula with a tExp token and nothing else - // get the base cell, grab tExp token - $baseRow = self::getUInt2d($formulaStructure, 3); - $baseCol = self::getUInt2d($formulaStructure, 5); - $this->baseCell = Coordinate::stringFromColumnIndex($baseCol + 1) . ($baseRow + 1); - } - - // Read cell? - if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { - if ($isPartOfSharedFormula) { - // formula is added to this cell after the sheet has been read - $this->sharedFormulaParts[$columnString . ($row + 1)] = $this->baseCell; - } - - // offset: 16: size: 4; not used - - // offset: 4; size: 2; XF index - $xfIndex = self::getUInt2d($recordData, 4); - - // offset: 6; size: 8; result of the formula - if ((ord($recordData[6]) == 0) && (ord($recordData[12]) == 255) && (ord($recordData[13]) == 255)) { - // String formula. Result follows in appended STRING record - $dataType = DataType::TYPE_STRING; - - // read possible SHAREDFMLA record - $code = self::getUInt2d($this->data, $this->pos); - if ($code == self::XLS_TYPE_SHAREDFMLA) { - $this->readSharedFmla(); - } - - // read STRING record - $value = $this->readString(); - } elseif ( - (ord($recordData[6]) == 1) - && (ord($recordData[12]) == 255) - && (ord($recordData[13]) == 255) - ) { - // Boolean formula. Result is in +2; 0=false, 1=true - $dataType = DataType::TYPE_BOOL; - $value = (bool) ord($recordData[8]); - } elseif ( - (ord($recordData[6]) == 2) - && (ord($recordData[12]) == 255) - && (ord($recordData[13]) == 255) - ) { - // Error formula. Error code is in +2 - $dataType = DataType::TYPE_ERROR; - $value = Xls\ErrorCode::lookup(ord($recordData[8])); - } elseif ( - (ord($recordData[6]) == 3) - && (ord($recordData[12]) == 255) - && (ord($recordData[13]) == 255) - ) { - // Formula result is a null string - $dataType = DataType::TYPE_NULL; - $value = ''; - } else { - // forumla result is a number, first 14 bytes like _NUMBER record - $dataType = DataType::TYPE_NUMERIC; - $value = self::extractNumber(substr($recordData, 6, 8)); - } - - $cell = $this->phpSheet->getCell($columnString . ($row + 1)); - if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { - // add cell style - $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); - } - - // store the formula - if (!$isPartOfSharedFormula) { - // not part of shared formula - // add cell value. If we can read formula, populate with formula, otherwise just used cached value - try { - if ($this->version != self::XLS_BIFF8) { - throw new Exception('Not BIFF8. Can only read BIFF8 formulas'); - } - $formula = $this->getFormulaFromStructure($formulaStructure); // get formula in human language - $cell->setValueExplicit('=' . $formula, DataType::TYPE_FORMULA); - } catch (PhpSpreadsheetException $e) { - $cell->setValueExplicit($value, $dataType); - } - } else { - if ($this->version == self::XLS_BIFF8) { - // do nothing at this point, formula id added later in the code - } else { - $cell->setValueExplicit($value, $dataType); - } - } - - // store the cached calculated value - $cell->setCalculatedValue($value); - } - } - - /** - * Read a SHAREDFMLA record. This function just stores the binary shared formula in the reader, - * which usually contains relative references. - * These will be used to construct the formula in each shared formula part after the sheet is read. - */ - private function readSharedFmla(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0, size: 6; cell range address of the area used by the shared formula, not used for anything - $cellRange = substr($recordData, 0, 6); - $cellRange = $this->readBIFF5CellRangeAddressFixed($cellRange); // note: even BIFF8 uses BIFF5 syntax - - // offset: 6, size: 1; not used - - // offset: 7, size: 1; number of existing FORMULA records for this shared formula - $no = ord($recordData[7]); - - // offset: 8, size: var; Binary token array of the shared formula - $formula = substr($recordData, 8); - - // at this point we only store the shared formula for later use - $this->sharedFormulas[$this->baseCell] = $formula; - } - - /** - * Read a STRING record from current stream position and advance the stream pointer to next record - * This record is used for storing result from FORMULA record when it is a string, and - * it occurs directly after the FORMULA record. - * - * @return string The string contents as UTF-8 - */ - private function readString() - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if ($this->version == self::XLS_BIFF8) { - $string = self::readUnicodeStringLong($recordData); - $value = $string['value']; - } else { - $string = $this->readByteStringLong($recordData); - $value = $string['value']; - } - - return $value; - } - - /** - * Read BOOLERR record - * This record represents a Boolean value or error value - * cell. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readBoolErr(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; row index - $row = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; column index - $column = self::getUInt2d($recordData, 2); - $columnString = Coordinate::stringFromColumnIndex($column + 1); - - // Read cell? - if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { - // offset: 4; size: 2; index to XF record - $xfIndex = self::getUInt2d($recordData, 4); - - // offset: 6; size: 1; the boolean value or error value - $boolErr = ord($recordData[6]); - - // offset: 7; size: 1; 0=boolean; 1=error - $isError = ord($recordData[7]); - - $cell = $this->phpSheet->getCell($columnString . ($row + 1)); - switch ($isError) { - case 0: // boolean - $value = (bool) $boolErr; - - // add cell value - $cell->setValueExplicit($value, DataType::TYPE_BOOL); - - break; - case 1: // error type - $value = Xls\ErrorCode::lookup($boolErr); - - // add cell value - $cell->setValueExplicit($value, DataType::TYPE_ERROR); - - break; - } - - if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { - // add cell style - $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); - } - } - } - - /** - * Read MULBLANK record - * This record represents a cell range of empty cells. All - * cells are located in the same row. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readMulBlank(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; index to row - $row = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; index to first column - $fc = self::getUInt2d($recordData, 2); - - // offset: 4; size: 2 x nc; list of indexes to XF records - // add style information - if (!$this->readDataOnly && $this->readEmptyCells) { - for ($i = 0; $i < $length / 2 - 3; ++$i) { - $columnString = Coordinate::stringFromColumnIndex($fc + $i + 1); - - // Read cell? - if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { - $xfIndex = self::getUInt2d($recordData, 4 + 2 * $i); - if (isset($this->mapCellXfIndex[$xfIndex])) { - $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]); - } - } - } - } - - // offset: 6; size 2; index to last column (not needed) - } - - /** - * Read LABEL record - * This record represents a cell that contains a string. In - * BIFF8 it is usually replaced by the LABELSST record. - * Excel still uses this record, if it copies unformatted - * text cells to the clipboard. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readLabel(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; index to row - $row = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; index to column - $column = self::getUInt2d($recordData, 2); - $columnString = Coordinate::stringFromColumnIndex($column + 1); - - // Read cell? - if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { - // offset: 4; size: 2; XF index - $xfIndex = self::getUInt2d($recordData, 4); - - // add cell value - // todo: what if string is very long? continue record - if ($this->version == self::XLS_BIFF8) { - $string = self::readUnicodeStringLong(substr($recordData, 6)); - $value = $string['value']; - } else { - $string = $this->readByteStringLong(substr($recordData, 6)); - $value = $string['value']; - } - if ($this->readEmptyCells || trim($value) !== '') { - $cell = $this->phpSheet->getCell($columnString . ($row + 1)); - $cell->setValueExplicit($value, DataType::TYPE_STRING); - - if (!$this->readDataOnly && isset($this->mapCellXfIndex[$xfIndex])) { - // add cell style - $cell->setXfIndex($this->mapCellXfIndex[$xfIndex]); - } - } - } - } - - /** - * Read BLANK record. - */ - private function readBlank(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; row index - $row = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; col index - $col = self::getUInt2d($recordData, 2); - $columnString = Coordinate::stringFromColumnIndex($col + 1); - - // Read cell? - if (($this->getReadFilter() !== null) && $this->getReadFilter()->readCell($columnString, $row + 1, $this->phpSheet->getTitle())) { - // offset: 4; size: 2; XF index - $xfIndex = self::getUInt2d($recordData, 4); - - // add style information - if (!$this->readDataOnly && $this->readEmptyCells && isset($this->mapCellXfIndex[$xfIndex])) { - $this->phpSheet->getCell($columnString . ($row + 1))->setXfIndex($this->mapCellXfIndex[$xfIndex]); - } - } - } - - /** - * Read MSODRAWING record. - */ - private function readMsoDrawing(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - - // get spliced record data - $splicedRecordData = $this->getSplicedRecordData(); - $recordData = $splicedRecordData['recordData']; - - $this->drawingData .= $recordData; - } - - /** - * Read OBJ record. - */ - private function readObj(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if ($this->readDataOnly || $this->version != self::XLS_BIFF8) { - return; - } - - // recordData consists of an array of subrecords looking like this: - // ft: 2 bytes; ftCmo type (0x15) - // cb: 2 bytes; size in bytes of ftCmo data - // ot: 2 bytes; Object Type - // id: 2 bytes; Object id number - // grbit: 2 bytes; Option Flags - // data: var; subrecord data - - // for now, we are just interested in the second subrecord containing the object type - $ftCmoType = self::getUInt2d($recordData, 0); - $cbCmoSize = self::getUInt2d($recordData, 2); - $otObjType = self::getUInt2d($recordData, 4); - $idObjID = self::getUInt2d($recordData, 6); - $grbitOpts = self::getUInt2d($recordData, 6); - - $this->objs[] = [ - 'ftCmoType' => $ftCmoType, - 'cbCmoSize' => $cbCmoSize, - 'otObjType' => $otObjType, - 'idObjID' => $idObjID, - 'grbitOpts' => $grbitOpts, - ]; - $this->textObjRef = $idObjID; - } - - /** - * Read WINDOW2 record. - */ - private function readWindow2(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; option flags - $options = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; index to first visible row - $firstVisibleRow = self::getUInt2d($recordData, 2); - - // offset: 4; size: 2; index to first visible colum - $firstVisibleColumn = self::getUInt2d($recordData, 4); - if ($this->version === self::XLS_BIFF8) { - // offset: 8; size: 2; not used - // offset: 10; size: 2; cached magnification factor in page break preview (in percent); 0 = Default (60%) - // offset: 12; size: 2; cached magnification factor in normal view (in percent); 0 = Default (100%) - // offset: 14; size: 4; not used - if (!isset($recordData[10])) { - $zoomscaleInPageBreakPreview = 0; - } else { - $zoomscaleInPageBreakPreview = self::getUInt2d($recordData, 10); - } - - if ($zoomscaleInPageBreakPreview === 0) { - $zoomscaleInPageBreakPreview = 60; - } - - if (!isset($recordData[12])) { - $zoomscaleInNormalView = 0; - } else { - $zoomscaleInNormalView = self::getUInt2d($recordData, 12); - } - - if ($zoomscaleInNormalView === 0) { - $zoomscaleInNormalView = 100; - } - } - - // bit: 1; mask: 0x0002; 0 = do not show gridlines, 1 = show gridlines - $showGridlines = (bool) ((0x0002 & $options) >> 1); - $this->phpSheet->setShowGridlines($showGridlines); - - // bit: 2; mask: 0x0004; 0 = do not show headers, 1 = show headers - $showRowColHeaders = (bool) ((0x0004 & $options) >> 2); - $this->phpSheet->setShowRowColHeaders($showRowColHeaders); - - // bit: 3; mask: 0x0008; 0 = panes are not frozen, 1 = panes are frozen - $this->frozen = (bool) ((0x0008 & $options) >> 3); - - // bit: 6; mask: 0x0040; 0 = columns from left to right, 1 = columns from right to left - $this->phpSheet->setRightToLeft((bool) ((0x0040 & $options) >> 6)); - - // bit: 10; mask: 0x0400; 0 = sheet not active, 1 = sheet active - $isActive = (bool) ((0x0400 & $options) >> 10); - if ($isActive) { - $this->spreadsheet->setActiveSheetIndex($this->spreadsheet->getIndex($this->phpSheet)); - } - - // bit: 11; mask: 0x0800; 0 = normal view, 1 = page break view - $isPageBreakPreview = (bool) ((0x0800 & $options) >> 11); - - //FIXME: set $firstVisibleRow and $firstVisibleColumn - - if ($this->phpSheet->getSheetView()->getView() !== SheetView::SHEETVIEW_PAGE_LAYOUT) { - //NOTE: this setting is inferior to page layout view(Excel2007-) - $view = $isPageBreakPreview ? SheetView::SHEETVIEW_PAGE_BREAK_PREVIEW : SheetView::SHEETVIEW_NORMAL; - $this->phpSheet->getSheetView()->setView($view); - if ($this->version === self::XLS_BIFF8) { - $zoomScale = $isPageBreakPreview ? $zoomscaleInPageBreakPreview : $zoomscaleInNormalView; - $this->phpSheet->getSheetView()->setZoomScale($zoomScale); - $this->phpSheet->getSheetView()->setZoomScaleNormal($zoomscaleInNormalView); - } - } - } - - /** - * Read PLV Record(Created by Excel2007 or upper). - */ - private function readPageLayoutView(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; rt - //->ignore - $rt = self::getUInt2d($recordData, 0); - // offset: 2; size: 2; grbitfr - //->ignore - $grbitFrt = self::getUInt2d($recordData, 2); - // offset: 4; size: 8; reserved - //->ignore - - // offset: 12; size 2; zoom scale - $wScalePLV = self::getUInt2d($recordData, 12); - // offset: 14; size 2; grbit - $grbit = self::getUInt2d($recordData, 14); - - // decomprise grbit - $fPageLayoutView = $grbit & 0x01; - $fRulerVisible = ($grbit >> 1) & 0x01; //no support - $fWhitespaceHidden = ($grbit >> 3) & 0x01; //no support - - if ($fPageLayoutView === 1) { - $this->phpSheet->getSheetView()->setView(SheetView::SHEETVIEW_PAGE_LAYOUT); - $this->phpSheet->getSheetView()->setZoomScale($wScalePLV); //set by Excel2007 only if SHEETVIEW_PAGE_LAYOUT - } - //otherwise, we cannot know whether SHEETVIEW_PAGE_LAYOUT or SHEETVIEW_PAGE_BREAK_PREVIEW. - } - - /** - * Read SCL record. - */ - private function readScl(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // offset: 0; size: 2; numerator of the view magnification - $numerator = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; numerator of the view magnification - $denumerator = self::getUInt2d($recordData, 2); - - // set the zoom scale (in percent) - $this->phpSheet->getSheetView()->setZoomScale($numerator * 100 / $denumerator); - } - - /** - * Read PANE record. - */ - private function readPane(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 2; position of vertical split - $px = self::getUInt2d($recordData, 0); - - // offset: 2; size: 2; position of horizontal split - $py = self::getUInt2d($recordData, 2); - - // offset: 4; size: 2; top most visible row in the bottom pane - $rwTop = self::getUInt2d($recordData, 4); - - // offset: 6; size: 2; first visible left column in the right pane - $colLeft = self::getUInt2d($recordData, 6); - - if ($this->frozen) { - // frozen panes - $cell = Coordinate::stringFromColumnIndex($px + 1) . ($py + 1); - $topLeftCell = Coordinate::stringFromColumnIndex($colLeft + 1) . ($rwTop + 1); - $this->phpSheet->freezePane($cell, $topLeftCell); - } - // unfrozen panes; split windows; not supported by PhpSpreadsheet core - } - } - - /** - * Read SELECTION record. There is one such record for each pane in the sheet. - */ - private function readSelection(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 1; pane identifier - $paneId = ord($recordData[0]); - - // offset: 1; size: 2; index to row of the active cell - $r = self::getUInt2d($recordData, 1); - - // offset: 3; size: 2; index to column of the active cell - $c = self::getUInt2d($recordData, 3); - - // offset: 5; size: 2; index into the following cell range list to the - // entry that contains the active cell - $index = self::getUInt2d($recordData, 5); - - // offset: 7; size: var; cell range address list containing all selected cell ranges - $data = substr($recordData, 7); - $cellRangeAddressList = $this->readBIFF5CellRangeAddressList($data); // note: also BIFF8 uses BIFF5 syntax - - $selectedCells = $cellRangeAddressList['cellRangeAddresses'][0]; - - // first row '1' + last row '16384' indicates that full column is selected (apparently also in BIFF8!) - if (preg_match('/^([A-Z]+1\:[A-Z]+)16384$/', $selectedCells)) { - $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)16384$/', '${1}1048576', $selectedCells); - } - - // first row '1' + last row '65536' indicates that full column is selected - if (preg_match('/^([A-Z]+1\:[A-Z]+)65536$/', $selectedCells)) { - $selectedCells = preg_replace('/^([A-Z]+1\:[A-Z]+)65536$/', '${1}1048576', $selectedCells); - } - - // first column 'A' + last column 'IV' indicates that full row is selected - if (preg_match('/^(A\d+\:)IV(\d+)$/', $selectedCells)) { - $selectedCells = preg_replace('/^(A\d+\:)IV(\d+)$/', '${1}XFD${2}', $selectedCells); - } - - $this->phpSheet->setSelectedCells($selectedCells); - } - } - - private function includeCellRangeFiltered($cellRangeAddress) - { - $includeCellRange = true; - if ($this->getReadFilter() !== null) { - $includeCellRange = false; - $rangeBoundaries = Coordinate::getRangeBoundaries($cellRangeAddress); - ++$rangeBoundaries[1][0]; - for ($row = $rangeBoundaries[0][1]; $row <= $rangeBoundaries[1][1]; ++$row) { - for ($column = $rangeBoundaries[0][0]; $column != $rangeBoundaries[1][0]; ++$column) { - if ($this->getReadFilter()->readCell($column, $row, $this->phpSheet->getTitle())) { - $includeCellRange = true; - - break 2; - } - } - } - } - - return $includeCellRange; - } - - /** - * MERGEDCELLS. - * - * This record contains the addresses of merged cell ranges - * in the current sheet. - * - * -- "OpenOffice.org's Documentation of the Microsoft - * Excel File Format" - */ - private function readMergedCells(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if ($this->version == self::XLS_BIFF8 && !$this->readDataOnly) { - $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($recordData); - foreach ($cellRangeAddressList['cellRangeAddresses'] as $cellRangeAddress) { - if ( - (strpos($cellRangeAddress, ':') !== false) && - ($this->includeCellRangeFiltered($cellRangeAddress)) - ) { - $this->phpSheet->mergeCells($cellRangeAddress); - } - } - } - } - - /** - * Read HYPERLINK record. - */ - private function readHyperLink(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer forward to next record - $this->pos += 4 + $length; - - if (!$this->readDataOnly) { - // offset: 0; size: 8; cell range address of all cells containing this hyperlink - try { - $cellRange = $this->readBIFF8CellRangeAddressFixed($recordData); - } catch (PhpSpreadsheetException $e) { - return; - } - - // offset: 8, size: 16; GUID of StdLink - - // offset: 24, size: 4; unknown value - - // offset: 28, size: 4; option flags - // bit: 0; mask: 0x00000001; 0 = no link or extant, 1 = file link or URL - $isFileLinkOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 0; - - // bit: 1; mask: 0x00000002; 0 = relative path, 1 = absolute path or URL - $isAbsPathOrUrl = (0x00000001 & self::getUInt2d($recordData, 28)) >> 1; - - // bit: 2 (and 4); mask: 0x00000014; 0 = no description - $hasDesc = (0x00000014 & self::getUInt2d($recordData, 28)) >> 2; - - // bit: 3; mask: 0x00000008; 0 = no text, 1 = has text - $hasText = (0x00000008 & self::getUInt2d($recordData, 28)) >> 3; - - // bit: 7; mask: 0x00000080; 0 = no target frame, 1 = has target frame - $hasFrame = (0x00000080 & self::getUInt2d($recordData, 28)) >> 7; - - // bit: 8; mask: 0x00000100; 0 = file link or URL, 1 = UNC path (inc. server name) - $isUNC = (0x00000100 & self::getUInt2d($recordData, 28)) >> 8; - - // offset within record data - $offset = 32; - - if ($hasDesc) { - // offset: 32; size: var; character count of description text - $dl = self::getInt4d($recordData, 32); - // offset: 36; size: var; character array of description text, no Unicode string header, always 16-bit characters, zero terminated - $desc = self::encodeUTF16(substr($recordData, 36, 2 * ($dl - 1)), false); - $offset += 4 + 2 * $dl; - } - if ($hasFrame) { - $fl = self::getInt4d($recordData, $offset); - $offset += 4 + 2 * $fl; - } - - // detect type of hyperlink (there are 4 types) - $hyperlinkType = null; - - if ($isUNC) { - $hyperlinkType = 'UNC'; - } elseif (!$isFileLinkOrUrl) { - $hyperlinkType = 'workbook'; - } elseif (ord($recordData[$offset]) == 0x03) { - $hyperlinkType = 'local'; - } elseif (ord($recordData[$offset]) == 0xE0) { - $hyperlinkType = 'URL'; - } - - switch ($hyperlinkType) { - case 'URL': - // section 5.58.2: Hyperlink containing a URL - // e.g. http://example.org/index.php - - // offset: var; size: 16; GUID of URL Moniker - $offset += 16; - // offset: var; size: 4; size (in bytes) of character array of the URL including trailing zero word - $us = self::getInt4d($recordData, $offset); - $offset += 4; - // offset: var; size: $us; character array of the URL, no Unicode string header, always 16-bit characters, zero-terminated - $url = self::encodeUTF16(substr($recordData, $offset, $us - 2), false); - $nullOffset = strpos($url, chr(0x00)); - if ($nullOffset) { - $url = substr($url, 0, $nullOffset); - } - $url .= $hasText ? '#' : ''; - $offset += $us; - - break; - case 'local': - // section 5.58.3: Hyperlink to local file - // examples: - // mydoc.txt - // ../../somedoc.xls#Sheet!A1 - - // offset: var; size: 16; GUI of File Moniker - $offset += 16; - - // offset: var; size: 2; directory up-level count. - $upLevelCount = self::getUInt2d($recordData, $offset); - $offset += 2; - - // offset: var; size: 4; character count of the shortened file path and name, including trailing zero word - $sl = self::getInt4d($recordData, $offset); - $offset += 4; - - // offset: var; size: sl; character array of the shortened file path and name in 8.3-DOS-format (compressed Unicode string) - $shortenedFilePath = substr($recordData, $offset, $sl); - $shortenedFilePath = self::encodeUTF16($shortenedFilePath, true); - $shortenedFilePath = substr($shortenedFilePath, 0, -1); // remove trailing zero - - $offset += $sl; - - // offset: var; size: 24; unknown sequence - $offset += 24; - - // extended file path - // offset: var; size: 4; size of the following file link field including string lenth mark - $sz = self::getInt4d($recordData, $offset); - $offset += 4; - - // only present if $sz > 0 - if ($sz > 0) { - // offset: var; size: 4; size of the character array of the extended file path and name - $xl = self::getInt4d($recordData, $offset); - $offset += 4; - - // offset: var; size 2; unknown - $offset += 2; - - // offset: var; size $xl; character array of the extended file path and name. - $extendedFilePath = substr($recordData, $offset, $xl); - $extendedFilePath = self::encodeUTF16($extendedFilePath, false); - $offset += $xl; - } - - // construct the path - $url = str_repeat('..\\', $upLevelCount); - $url .= ($sz > 0) ? $extendedFilePath : $shortenedFilePath; // use extended path if available - $url .= $hasText ? '#' : ''; - - break; - case 'UNC': - // section 5.58.4: Hyperlink to a File with UNC (Universal Naming Convention) Path - // todo: implement - return; - case 'workbook': - // section 5.58.5: Hyperlink to the Current Workbook - // e.g. Sheet2!B1:C2, stored in text mark field - $url = 'sheet://'; - - break; - default: - return; - } - - if ($hasText) { - // offset: var; size: 4; character count of text mark including trailing zero word - $tl = self::getInt4d($recordData, $offset); - $offset += 4; - // offset: var; size: var; character array of the text mark without the # sign, no Unicode header, always 16-bit characters, zero-terminated - $text = self::encodeUTF16(substr($recordData, $offset, 2 * ($tl - 1)), false); - $url .= $text; - } - - // apply the hyperlink to all the relevant cells - foreach (Coordinate::extractAllCellReferencesInRange($cellRange) as $coordinate) { - $this->phpSheet->getCell($coordinate)->getHyperLink()->setUrl($url); - } - } - } - - /** - * Read DATAVALIDATIONS record. - */ - private function readDataValidations(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer forward to next record - $this->pos += 4 + $length; - } - - /** - * Read DATAVALIDATION record. - */ - private function readDataValidation(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer forward to next record - $this->pos += 4 + $length; - - if ($this->readDataOnly) { - return; - } - - // offset: 0; size: 4; Options - $options = self::getInt4d($recordData, 0); - - // bit: 0-3; mask: 0x0000000F; type - $type = (0x0000000F & $options) >> 0; - switch ($type) { - case 0x00: - $type = DataValidation::TYPE_NONE; - - break; - case 0x01: - $type = DataValidation::TYPE_WHOLE; - - break; - case 0x02: - $type = DataValidation::TYPE_DECIMAL; - - break; - case 0x03: - $type = DataValidation::TYPE_LIST; - - break; - case 0x04: - $type = DataValidation::TYPE_DATE; - - break; - case 0x05: - $type = DataValidation::TYPE_TIME; - - break; - case 0x06: - $type = DataValidation::TYPE_TEXTLENGTH; - - break; - case 0x07: - $type = DataValidation::TYPE_CUSTOM; - - break; - } - - // bit: 4-6; mask: 0x00000070; error type - $errorStyle = (0x00000070 & $options) >> 4; - switch ($errorStyle) { - case 0x00: - $errorStyle = DataValidation::STYLE_STOP; - - break; - case 0x01: - $errorStyle = DataValidation::STYLE_WARNING; - - break; - case 0x02: - $errorStyle = DataValidation::STYLE_INFORMATION; - - break; - } - - // bit: 7; mask: 0x00000080; 1= formula is explicit (only applies to list) - // I have only seen cases where this is 1 - $explicitFormula = (0x00000080 & $options) >> 7; - - // bit: 8; mask: 0x00000100; 1= empty cells allowed - $allowBlank = (0x00000100 & $options) >> 8; - - // bit: 9; mask: 0x00000200; 1= suppress drop down arrow in list type validity - $suppressDropDown = (0x00000200 & $options) >> 9; - - // bit: 18; mask: 0x00040000; 1= show prompt box if cell selected - $showInputMessage = (0x00040000 & $options) >> 18; - - // bit: 19; mask: 0x00080000; 1= show error box if invalid values entered - $showErrorMessage = (0x00080000 & $options) >> 19; - - // bit: 20-23; mask: 0x00F00000; condition operator - $operator = (0x00F00000 & $options) >> 20; - switch ($operator) { - case 0x00: - $operator = DataValidation::OPERATOR_BETWEEN; - - break; - case 0x01: - $operator = DataValidation::OPERATOR_NOTBETWEEN; - - break; - case 0x02: - $operator = DataValidation::OPERATOR_EQUAL; - - break; - case 0x03: - $operator = DataValidation::OPERATOR_NOTEQUAL; - - break; - case 0x04: - $operator = DataValidation::OPERATOR_GREATERTHAN; - - break; - case 0x05: - $operator = DataValidation::OPERATOR_LESSTHAN; - - break; - case 0x06: - $operator = DataValidation::OPERATOR_GREATERTHANOREQUAL; - - break; - case 0x07: - $operator = DataValidation::OPERATOR_LESSTHANOREQUAL; - - break; - } - - // offset: 4; size: var; title of the prompt box - $offset = 4; - $string = self::readUnicodeStringLong(substr($recordData, $offset)); - $promptTitle = $string['value'] !== chr(0) ? $string['value'] : ''; - $offset += $string['size']; - - // offset: var; size: var; title of the error box - $string = self::readUnicodeStringLong(substr($recordData, $offset)); - $errorTitle = $string['value'] !== chr(0) ? $string['value'] : ''; - $offset += $string['size']; - - // offset: var; size: var; text of the prompt box - $string = self::readUnicodeStringLong(substr($recordData, $offset)); - $prompt = $string['value'] !== chr(0) ? $string['value'] : ''; - $offset += $string['size']; - - // offset: var; size: var; text of the error box - $string = self::readUnicodeStringLong(substr($recordData, $offset)); - $error = $string['value'] !== chr(0) ? $string['value'] : ''; - $offset += $string['size']; - - // offset: var; size: 2; size of the formula data for the first condition - $sz1 = self::getUInt2d($recordData, $offset); - $offset += 2; - - // offset: var; size: 2; not used - $offset += 2; - - // offset: var; size: $sz1; formula data for first condition (without size field) - $formula1 = substr($recordData, $offset, $sz1); - $formula1 = pack('v', $sz1) . $formula1; // prepend the length - - try { - $formula1 = $this->getFormulaFromStructure($formula1); - - // in list type validity, null characters are used as item separators - if ($type == DataValidation::TYPE_LIST) { - $formula1 = str_replace(chr(0), ',', $formula1); - } - } catch (PhpSpreadsheetException $e) { - return; - } - $offset += $sz1; - - // offset: var; size: 2; size of the formula data for the first condition - $sz2 = self::getUInt2d($recordData, $offset); - $offset += 2; - - // offset: var; size: 2; not used - $offset += 2; - - // offset: var; size: $sz2; formula data for second condition (without size field) - $formula2 = substr($recordData, $offset, $sz2); - $formula2 = pack('v', $sz2) . $formula2; // prepend the length - - try { - $formula2 = $this->getFormulaFromStructure($formula2); - } catch (PhpSpreadsheetException $e) { - return; - } - $offset += $sz2; - - // offset: var; size: var; cell range address list with - $cellRangeAddressList = $this->readBIFF8CellRangeAddressList(substr($recordData, $offset)); - $cellRangeAddresses = $cellRangeAddressList['cellRangeAddresses']; - - foreach ($cellRangeAddresses as $cellRange) { - $stRange = $this->phpSheet->shrinkRangeToFit($cellRange); - foreach (Coordinate::extractAllCellReferencesInRange($stRange) as $coordinate) { - $objValidation = $this->phpSheet->getCell($coordinate)->getDataValidation(); - $objValidation->setType($type); - $objValidation->setErrorStyle($errorStyle); - $objValidation->setAllowBlank((bool) $allowBlank); - $objValidation->setShowInputMessage((bool) $showInputMessage); - $objValidation->setShowErrorMessage((bool) $showErrorMessage); - $objValidation->setShowDropDown(!$suppressDropDown); - $objValidation->setOperator($operator); - $objValidation->setErrorTitle($errorTitle); - $objValidation->setError($error); - $objValidation->setPromptTitle($promptTitle); - $objValidation->setPrompt($prompt); - $objValidation->setFormula1($formula1); - $objValidation->setFormula2($formula2); - } - } - } - - /** - * Read SHEETLAYOUT record. Stores sheet tab color information. - */ - private function readSheetLayout(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // local pointer in record data - $offset = 0; - - if (!$this->readDataOnly) { - // offset: 0; size: 2; repeated record identifier 0x0862 - - // offset: 2; size: 10; not used - - // offset: 12; size: 4; size of record data - // Excel 2003 uses size of 0x14 (documented), Excel 2007 uses size of 0x28 (not documented?) - $sz = self::getInt4d($recordData, 12); - - switch ($sz) { - case 0x14: - // offset: 16; size: 2; color index for sheet tab - $colorIndex = self::getUInt2d($recordData, 16); - $color = Xls\Color::map($colorIndex, $this->palette, $this->version); - $this->phpSheet->getTabColor()->setRGB($color['rgb']); - - break; - case 0x28: - // TODO: Investigate structure for .xls SHEETLAYOUT record as saved by MS Office Excel 2007 - return; - - break; - } - } - } - - /** - * Read SHEETPROTECTION record (FEATHEADR). - */ - private function readSheetProtection(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - if ($this->readDataOnly) { - return; - } - - // offset: 0; size: 2; repeated record header - - // offset: 2; size: 2; FRT cell reference flag (=0 currently) - - // offset: 4; size: 8; Currently not used and set to 0 - - // offset: 12; size: 2; Shared feature type index (2=Enhanced Protetion, 4=SmartTag) - $isf = self::getUInt2d($recordData, 12); - if ($isf != 2) { - return; - } - - // offset: 14; size: 1; =1 since this is a feat header - - // offset: 15; size: 4; size of rgbHdrSData - - // rgbHdrSData, assume "Enhanced Protection" - // offset: 19; size: 2; option flags - $options = self::getUInt2d($recordData, 19); - - // bit: 0; mask 0x0001; 1 = user may edit objects, 0 = users must not edit objects - $bool = (0x0001 & $options) >> 0; - $this->phpSheet->getProtection()->setObjects(!$bool); - - // bit: 1; mask 0x0002; edit scenarios - $bool = (0x0002 & $options) >> 1; - $this->phpSheet->getProtection()->setScenarios(!$bool); - - // bit: 2; mask 0x0004; format cells - $bool = (0x0004 & $options) >> 2; - $this->phpSheet->getProtection()->setFormatCells(!$bool); - - // bit: 3; mask 0x0008; format columns - $bool = (0x0008 & $options) >> 3; - $this->phpSheet->getProtection()->setFormatColumns(!$bool); - - // bit: 4; mask 0x0010; format rows - $bool = (0x0010 & $options) >> 4; - $this->phpSheet->getProtection()->setFormatRows(!$bool); - - // bit: 5; mask 0x0020; insert columns - $bool = (0x0020 & $options) >> 5; - $this->phpSheet->getProtection()->setInsertColumns(!$bool); - - // bit: 6; mask 0x0040; insert rows - $bool = (0x0040 & $options) >> 6; - $this->phpSheet->getProtection()->setInsertRows(!$bool); - - // bit: 7; mask 0x0080; insert hyperlinks - $bool = (0x0080 & $options) >> 7; - $this->phpSheet->getProtection()->setInsertHyperlinks(!$bool); - - // bit: 8; mask 0x0100; delete columns - $bool = (0x0100 & $options) >> 8; - $this->phpSheet->getProtection()->setDeleteColumns(!$bool); - - // bit: 9; mask 0x0200; delete rows - $bool = (0x0200 & $options) >> 9; - $this->phpSheet->getProtection()->setDeleteRows(!$bool); - - // bit: 10; mask 0x0400; select locked cells - $bool = (0x0400 & $options) >> 10; - $this->phpSheet->getProtection()->setSelectLockedCells(!$bool); - - // bit: 11; mask 0x0800; sort cell range - $bool = (0x0800 & $options) >> 11; - $this->phpSheet->getProtection()->setSort(!$bool); - - // bit: 12; mask 0x1000; auto filter - $bool = (0x1000 & $options) >> 12; - $this->phpSheet->getProtection()->setAutoFilter(!$bool); - - // bit: 13; mask 0x2000; pivot tables - $bool = (0x2000 & $options) >> 13; - $this->phpSheet->getProtection()->setPivotTables(!$bool); - - // bit: 14; mask 0x4000; select unlocked cells - $bool = (0x4000 & $options) >> 14; - $this->phpSheet->getProtection()->setSelectUnlockedCells(!$bool); - - // offset: 21; size: 2; not used - } - - /** - * Read RANGEPROTECTION record - * Reading of this record is based on Microsoft Office Excel 97-2000 Binary File Format Specification, - * where it is referred to as FEAT record. - */ - private function readRangeProtection(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // move stream pointer to next record - $this->pos += 4 + $length; - - // local pointer in record data - $offset = 0; - - if (!$this->readDataOnly) { - $offset += 12; - - // offset: 12; size: 2; shared feature type, 2 = enhanced protection, 4 = smart tag - $isf = self::getUInt2d($recordData, 12); - if ($isf != 2) { - // we only read FEAT records of type 2 - return; - } - $offset += 2; - - $offset += 5; - - // offset: 19; size: 2; count of ref ranges this feature is on - $cref = self::getUInt2d($recordData, 19); - $offset += 2; - - $offset += 6; - - // offset: 27; size: 8 * $cref; list of cell ranges (like in hyperlink record) - $cellRanges = []; - for ($i = 0; $i < $cref; ++$i) { - try { - $cellRange = $this->readBIFF8CellRangeAddressFixed(substr($recordData, 27 + 8 * $i, 8)); - } catch (PhpSpreadsheetException $e) { - return; - } - $cellRanges[] = $cellRange; - $offset += 8; - } - - // offset: var; size: var; variable length of feature specific data - $rgbFeat = substr($recordData, $offset); - $offset += 4; - - // offset: var; size: 4; the encrypted password (only 16-bit although field is 32-bit) - $wPassword = self::getInt4d($recordData, $offset); - $offset += 4; - - // Apply range protection to sheet - if ($cellRanges) { - $this->phpSheet->protectCells(implode(' ', $cellRanges), strtoupper(dechex($wPassword)), true); - } - } - } - - /** - * Read a free CONTINUE record. Free CONTINUE record may be a camouflaged MSODRAWING record - * When MSODRAWING data on a sheet exceeds 8224 bytes, CONTINUE records are used instead. Undocumented. - * In this case, we must treat the CONTINUE record as a MSODRAWING record. - */ - private function readContinue(): void - { - $length = self::getUInt2d($this->data, $this->pos + 2); - $recordData = $this->readRecordData($this->data, $this->pos + 4, $length); - - // check if we are reading drawing data - // this is in case a free CONTINUE record occurs in other circumstances we are unaware of - if ($this->drawingData == '') { - // move stream pointer to next record - $this->pos += 4 + $length; - - return; - } - - // check if record data is at least 4 bytes long, otherwise there is no chance this is MSODRAWING data - if ($length < 4) { - // move stream pointer to next record - $this->pos += 4 + $length; - - return; - } - - // dirty check to see if CONTINUE record could be a camouflaged MSODRAWING record - // look inside CONTINUE record to see if it looks like a part of an Escher stream - // we know that Escher stream may be split at least at - // 0xF003 MsofbtSpgrContainer - // 0xF004 MsofbtSpContainer - // 0xF00D MsofbtClientTextbox - $validSplitPoints = [0xF003, 0xF004, 0xF00D]; // add identifiers if we find more - - $splitPoint = self::getUInt2d($recordData, 2); - if (in_array($splitPoint, $validSplitPoints)) { - // get spliced record data (and move pointer to next record) - $splicedRecordData = $this->getSplicedRecordData(); - $this->drawingData .= $splicedRecordData['recordData']; - - return; - } - - // move stream pointer to next record - $this->pos += 4 + $length; - } - - /** - * Reads a record from current position in data stream and continues reading data as long as CONTINUE - * records are found. Splices the record data pieces and returns the combined string as if record data - * is in one piece. - * Moves to next current position in data stream to start of next record different from a CONtINUE record. - * - * @return array - */ - private function getSplicedRecordData() - { - $data = ''; - $spliceOffsets = []; - - $i = 0; - $spliceOffsets[0] = 0; - - do { - ++$i; - - // offset: 0; size: 2; identifier - $identifier = self::getUInt2d($this->data, $this->pos); - // offset: 2; size: 2; length - $length = self::getUInt2d($this->data, $this->pos + 2); - $data .= $this->readRecordData($this->data, $this->pos + 4, $length); - - $spliceOffsets[$i] = $spliceOffsets[$i - 1] + $length; - - $this->pos += 4 + $length; - $nextIdentifier = self::getUInt2d($this->data, $this->pos); - } while ($nextIdentifier == self::XLS_TYPE_CONTINUE); - - return [ - 'recordData' => $data, - 'spliceOffsets' => $spliceOffsets, - ]; - } - - /** - * Convert formula structure into human readable Excel formula like 'A3+A5*5'. - * - * @param string $formulaStructure The complete binary data for the formula - * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas - * - * @return string Human readable formula - */ - private function getFormulaFromStructure($formulaStructure, $baseCell = 'A1') - { - // offset: 0; size: 2; size of the following formula data - $sz = self::getUInt2d($formulaStructure, 0); - - // offset: 2; size: sz - $formulaData = substr($formulaStructure, 2, $sz); - - // offset: 2 + sz; size: variable (optional) - if (strlen($formulaStructure) > 2 + $sz) { - $additionalData = substr($formulaStructure, 2 + $sz); - } else { - $additionalData = ''; - } - - return $this->getFormulaFromData($formulaData, $additionalData, $baseCell); - } - - /** - * Take formula data and additional data for formula and return human readable formula. - * - * @param string $formulaData The binary data for the formula itself - * @param string $additionalData Additional binary data going with the formula - * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas - * - * @return string Human readable formula - */ - private function getFormulaFromData($formulaData, $additionalData = '', $baseCell = 'A1') - { - // start parsing the formula data - $tokens = []; - - while (strlen($formulaData) > 0 && $token = $this->getNextToken($formulaData, $baseCell)) { - $tokens[] = $token; - $formulaData = substr($formulaData, $token['size']); - } - - $formulaString = $this->createFormulaFromTokens($tokens, $additionalData); - - return $formulaString; - } - - /** - * Take array of tokens together with additional data for formula and return human readable formula. - * - * @param array $tokens - * @param string $additionalData Additional binary data going with the formula - * - * @return string Human readable formula - */ - private function createFormulaFromTokens($tokens, $additionalData) - { - // empty formula? - if (empty($tokens)) { - return ''; - } - - $formulaStrings = []; - foreach ($tokens as $token) { - // initialize spaces - $space0 = $space0 ?? ''; // spaces before next token, not tParen - $space1 = $space1 ?? ''; // carriage returns before next token, not tParen - $space2 = $space2 ?? ''; // spaces before opening parenthesis - $space3 = $space3 ?? ''; // carriage returns before opening parenthesis - $space4 = $space4 ?? ''; // spaces before closing parenthesis - $space5 = $space5 ?? ''; // carriage returns before closing parenthesis - - switch ($token['name']) { - case 'tAdd': // addition - case 'tConcat': // addition - case 'tDiv': // division - case 'tEQ': // equality - case 'tGE': // greater than or equal - case 'tGT': // greater than - case 'tIsect': // intersection - case 'tLE': // less than or equal - case 'tList': // less than or equal - case 'tLT': // less than - case 'tMul': // multiplication - case 'tNE': // multiplication - case 'tPower': // power - case 'tRange': // range - case 'tSub': // subtraction - $op2 = array_pop($formulaStrings); - $op1 = array_pop($formulaStrings); - $formulaStrings[] = "$op1$space1$space0{$token['data']}$op2"; - unset($space0, $space1); - - break; - case 'tUplus': // unary plus - case 'tUminus': // unary minus - $op = array_pop($formulaStrings); - $formulaStrings[] = "$space1$space0{$token['data']}$op"; - unset($space0, $space1); - - break; - case 'tPercent': // percent sign - $op = array_pop($formulaStrings); - $formulaStrings[] = "$op$space1$space0{$token['data']}"; - unset($space0, $space1); - - break; - case 'tAttrVolatile': // indicates volatile function - case 'tAttrIf': - case 'tAttrSkip': - case 'tAttrChoose': - // token is only important for Excel formula evaluator - // do nothing - break; - case 'tAttrSpace': // space / carriage return - // space will be used when next token arrives, do not alter formulaString stack - switch ($token['data']['spacetype']) { - case 'type0': - $space0 = str_repeat(' ', $token['data']['spacecount']); - - break; - case 'type1': - $space1 = str_repeat("\n", $token['data']['spacecount']); - - break; - case 'type2': - $space2 = str_repeat(' ', $token['data']['spacecount']); - - break; - case 'type3': - $space3 = str_repeat("\n", $token['data']['spacecount']); - - break; - case 'type4': - $space4 = str_repeat(' ', $token['data']['spacecount']); - - break; - case 'type5': - $space5 = str_repeat("\n", $token['data']['spacecount']); - - break; - } - - break; - case 'tAttrSum': // SUM function with one parameter - $op = array_pop($formulaStrings); - $formulaStrings[] = "{$space1}{$space0}SUM($op)"; - unset($space0, $space1); - - break; - case 'tFunc': // function with fixed number of arguments - case 'tFuncV': // function with variable number of arguments - if ($token['data']['function'] != '') { - // normal function - $ops = []; // array of operators - for ($i = 0; $i < $token['data']['args']; ++$i) { - $ops[] = array_pop($formulaStrings); - } - $ops = array_reverse($ops); - $formulaStrings[] = "$space1$space0{$token['data']['function']}(" . implode(',', $ops) . ')'; - unset($space0, $space1); - } else { - // add-in function - $ops = []; // array of operators - for ($i = 0; $i < $token['data']['args'] - 1; ++$i) { - $ops[] = array_pop($formulaStrings); - } - $ops = array_reverse($ops); - $function = array_pop($formulaStrings); - $formulaStrings[] = "$space1$space0$function(" . implode(',', $ops) . ')'; - unset($space0, $space1); - } - - break; - case 'tParen': // parenthesis - $expression = array_pop($formulaStrings); - $formulaStrings[] = "$space3$space2($expression$space5$space4)"; - unset($space2, $space3, $space4, $space5); - - break; - case 'tArray': // array constant - $constantArray = self::readBIFF8ConstantArray($additionalData); - $formulaStrings[] = $space1 . $space0 . $constantArray['value']; - $additionalData = substr($additionalData, $constantArray['size']); // bite of chunk of additional data - unset($space0, $space1); - - break; - case 'tMemArea': - // bite off chunk of additional data - $cellRangeAddressList = $this->readBIFF8CellRangeAddressList($additionalData); - $additionalData = substr($additionalData, $cellRangeAddressList['size']); - $formulaStrings[] = "$space1$space0{$token['data']}"; - unset($space0, $space1); - - break; - case 'tArea': // cell range address - case 'tBool': // boolean - case 'tErr': // error code - case 'tInt': // integer - case 'tMemErr': - case 'tMemFunc': - case 'tMissArg': - case 'tName': - case 'tNameX': - case 'tNum': // number - case 'tRef': // single cell reference - case 'tRef3d': // 3d cell reference - case 'tArea3d': // 3d cell range reference - case 'tRefN': - case 'tAreaN': - case 'tStr': // string - $formulaStrings[] = "$space1$space0{$token['data']}"; - unset($space0, $space1); - - break; - } - } - $formulaString = $formulaStrings[0]; - - return $formulaString; - } - - /** - * Fetch next token from binary formula data. - * - * @param string $formulaData Formula data - * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas - * - * @return array - */ - private function getNextToken($formulaData, $baseCell = 'A1') - { - // offset: 0; size: 1; token id - $id = ord($formulaData[0]); // token id - $name = false; // initialize token name - - switch ($id) { - case 0x03: - $name = 'tAdd'; - $size = 1; - $data = '+'; - - break; - case 0x04: - $name = 'tSub'; - $size = 1; - $data = '-'; - - break; - case 0x05: - $name = 'tMul'; - $size = 1; - $data = '*'; - - break; - case 0x06: - $name = 'tDiv'; - $size = 1; - $data = '/'; - - break; - case 0x07: - $name = 'tPower'; - $size = 1; - $data = '^'; - - break; - case 0x08: - $name = 'tConcat'; - $size = 1; - $data = '&'; - - break; - case 0x09: - $name = 'tLT'; - $size = 1; - $data = '<'; - - break; - case 0x0A: - $name = 'tLE'; - $size = 1; - $data = '<='; - - break; - case 0x0B: - $name = 'tEQ'; - $size = 1; - $data = '='; - - break; - case 0x0C: - $name = 'tGE'; - $size = 1; - $data = '>='; - - break; - case 0x0D: - $name = 'tGT'; - $size = 1; - $data = '>'; - - break; - case 0x0E: - $name = 'tNE'; - $size = 1; - $data = '<>'; - - break; - case 0x0F: - $name = 'tIsect'; - $size = 1; - $data = ' '; - - break; - case 0x10: - $name = 'tList'; - $size = 1; - $data = ','; - - break; - case 0x11: - $name = 'tRange'; - $size = 1; - $data = ':'; - - break; - case 0x12: - $name = 'tUplus'; - $size = 1; - $data = '+'; - - break; - case 0x13: - $name = 'tUminus'; - $size = 1; - $data = '-'; - - break; - case 0x14: - $name = 'tPercent'; - $size = 1; - $data = '%'; - - break; - case 0x15: // parenthesis - $name = 'tParen'; - $size = 1; - $data = null; - - break; - case 0x16: // missing argument - $name = 'tMissArg'; - $size = 1; - $data = ''; - - break; - case 0x17: // string - $name = 'tStr'; - // offset: 1; size: var; Unicode string, 8-bit string length - $string = self::readUnicodeStringShort(substr($formulaData, 1)); - $size = 1 + $string['size']; - $data = self::UTF8toExcelDoubleQuoted($string['value']); - - break; - case 0x19: // Special attribute - // offset: 1; size: 1; attribute type flags: - switch (ord($formulaData[1])) { - case 0x01: - $name = 'tAttrVolatile'; - $size = 4; - $data = null; - - break; - case 0x02: - $name = 'tAttrIf'; - $size = 4; - $data = null; - - break; - case 0x04: - $name = 'tAttrChoose'; - // offset: 2; size: 2; number of choices in the CHOOSE function ($nc, number of parameters decreased by 1) - $nc = self::getUInt2d($formulaData, 2); - // offset: 4; size: 2 * $nc - // offset: 4 + 2 * $nc; size: 2 - $size = 2 * $nc + 6; - $data = null; - - break; - case 0x08: - $name = 'tAttrSkip'; - $size = 4; - $data = null; - - break; - case 0x10: - $name = 'tAttrSum'; - $size = 4; - $data = null; - - break; - case 0x40: - case 0x41: - $name = 'tAttrSpace'; - $size = 4; - // offset: 2; size: 2; space type and position - switch (ord($formulaData[2])) { - case 0x00: - $spacetype = 'type0'; - - break; - case 0x01: - $spacetype = 'type1'; - - break; - case 0x02: - $spacetype = 'type2'; - - break; - case 0x03: - $spacetype = 'type3'; - - break; - case 0x04: - $spacetype = 'type4'; - - break; - case 0x05: - $spacetype = 'type5'; - - break; - default: - throw new Exception('Unrecognized space type in tAttrSpace token'); - - break; - } - // offset: 3; size: 1; number of inserted spaces/carriage returns - $spacecount = ord($formulaData[3]); - - $data = ['spacetype' => $spacetype, 'spacecount' => $spacecount]; - - break; - default: - throw new Exception('Unrecognized attribute flag in tAttr token'); - - break; - } - - break; - case 0x1C: // error code - // offset: 1; size: 1; error code - $name = 'tErr'; - $size = 2; - $data = Xls\ErrorCode::lookup(ord($formulaData[1])); - - break; - case 0x1D: // boolean - // offset: 1; size: 1; 0 = false, 1 = true; - $name = 'tBool'; - $size = 2; - $data = ord($formulaData[1]) ? 'TRUE' : 'FALSE'; - - break; - case 0x1E: // integer - // offset: 1; size: 2; unsigned 16-bit integer - $name = 'tInt'; - $size = 3; - $data = self::getUInt2d($formulaData, 1); - - break; - case 0x1F: // number - // offset: 1; size: 8; - $name = 'tNum'; - $size = 9; - $data = self::extractNumber(substr($formulaData, 1)); - $data = str_replace(',', '.', (string) $data); // in case non-English locale - - break; - case 0x20: // array constant - case 0x40: - case 0x60: - // offset: 1; size: 7; not used - $name = 'tArray'; - $size = 8; - $data = null; - - break; - case 0x21: // function with fixed number of arguments - case 0x41: - case 0x61: - $name = 'tFunc'; - $size = 3; - // offset: 1; size: 2; index to built-in sheet function - switch (self::getUInt2d($formulaData, 1)) { - case 2: - $function = 'ISNA'; - $args = 1; - - break; - case 3: - $function = 'ISERROR'; - $args = 1; - - break; - case 10: - $function = 'NA'; - $args = 0; - - break; - case 15: - $function = 'SIN'; - $args = 1; - - break; - case 16: - $function = 'COS'; - $args = 1; - - break; - case 17: - $function = 'TAN'; - $args = 1; - - break; - case 18: - $function = 'ATAN'; - $args = 1; - - break; - case 19: - $function = 'PI'; - $args = 0; - - break; - case 20: - $function = 'SQRT'; - $args = 1; - - break; - case 21: - $function = 'EXP'; - $args = 1; - - break; - case 22: - $function = 'LN'; - $args = 1; - - break; - case 23: - $function = 'LOG10'; - $args = 1; - - break; - case 24: - $function = 'ABS'; - $args = 1; - - break; - case 25: - $function = 'INT'; - $args = 1; - - break; - case 26: - $function = 'SIGN'; - $args = 1; - - break; - case 27: - $function = 'ROUND'; - $args = 2; - - break; - case 30: - $function = 'REPT'; - $args = 2; - - break; - case 31: - $function = 'MID'; - $args = 3; - - break; - case 32: - $function = 'LEN'; - $args = 1; - - break; - case 33: - $function = 'VALUE'; - $args = 1; - - break; - case 34: - $function = 'TRUE'; - $args = 0; - - break; - case 35: - $function = 'FALSE'; - $args = 0; - - break; - case 38: - $function = 'NOT'; - $args = 1; - - break; - case 39: - $function = 'MOD'; - $args = 2; - - break; - case 40: - $function = 'DCOUNT'; - $args = 3; - - break; - case 41: - $function = 'DSUM'; - $args = 3; - - break; - case 42: - $function = 'DAVERAGE'; - $args = 3; - - break; - case 43: - $function = 'DMIN'; - $args = 3; - - break; - case 44: - $function = 'DMAX'; - $args = 3; - - break; - case 45: - $function = 'DSTDEV'; - $args = 3; - - break; - case 48: - $function = 'TEXT'; - $args = 2; - - break; - case 61: - $function = 'MIRR'; - $args = 3; - - break; - case 63: - $function = 'RAND'; - $args = 0; - - break; - case 65: - $function = 'DATE'; - $args = 3; - - break; - case 66: - $function = 'TIME'; - $args = 3; - - break; - case 67: - $function = 'DAY'; - $args = 1; - - break; - case 68: - $function = 'MONTH'; - $args = 1; - - break; - case 69: - $function = 'YEAR'; - $args = 1; - - break; - case 71: - $function = 'HOUR'; - $args = 1; - - break; - case 72: - $function = 'MINUTE'; - $args = 1; - - break; - case 73: - $function = 'SECOND'; - $args = 1; - - break; - case 74: - $function = 'NOW'; - $args = 0; - - break; - case 75: - $function = 'AREAS'; - $args = 1; - - break; - case 76: - $function = 'ROWS'; - $args = 1; - - break; - case 77: - $function = 'COLUMNS'; - $args = 1; - - break; - case 83: - $function = 'TRANSPOSE'; - $args = 1; - - break; - case 86: - $function = 'TYPE'; - $args = 1; - - break; - case 97: - $function = 'ATAN2'; - $args = 2; - - break; - case 98: - $function = 'ASIN'; - $args = 1; - - break; - case 99: - $function = 'ACOS'; - $args = 1; - - break; - case 105: - $function = 'ISREF'; - $args = 1; - - break; - case 111: - $function = 'CHAR'; - $args = 1; - - break; - case 112: - $function = 'LOWER'; - $args = 1; - - break; - case 113: - $function = 'UPPER'; - $args = 1; - - break; - case 114: - $function = 'PROPER'; - $args = 1; - - break; - case 117: - $function = 'EXACT'; - $args = 2; - - break; - case 118: - $function = 'TRIM'; - $args = 1; - - break; - case 119: - $function = 'REPLACE'; - $args = 4; - - break; - case 121: - $function = 'CODE'; - $args = 1; - - break; - case 126: - $function = 'ISERR'; - $args = 1; - - break; - case 127: - $function = 'ISTEXT'; - $args = 1; - - break; - case 128: - $function = 'ISNUMBER'; - $args = 1; - - break; - case 129: - $function = 'ISBLANK'; - $args = 1; - - break; - case 130: - $function = 'T'; - $args = 1; - - break; - case 131: - $function = 'N'; - $args = 1; - - break; - case 140: - $function = 'DATEVALUE'; - $args = 1; - - break; - case 141: - $function = 'TIMEVALUE'; - $args = 1; - - break; - case 142: - $function = 'SLN'; - $args = 3; - - break; - case 143: - $function = 'SYD'; - $args = 4; - - break; - case 162: - $function = 'CLEAN'; - $args = 1; - - break; - case 163: - $function = 'MDETERM'; - $args = 1; - - break; - case 164: - $function = 'MINVERSE'; - $args = 1; - - break; - case 165: - $function = 'MMULT'; - $args = 2; - - break; - case 184: - $function = 'FACT'; - $args = 1; - - break; - case 189: - $function = 'DPRODUCT'; - $args = 3; - - break; - case 190: - $function = 'ISNONTEXT'; - $args = 1; - - break; - case 195: - $function = 'DSTDEVP'; - $args = 3; - - break; - case 196: - $function = 'DVARP'; - $args = 3; - - break; - case 198: - $function = 'ISLOGICAL'; - $args = 1; - - break; - case 199: - $function = 'DCOUNTA'; - $args = 3; - - break; - case 207: - $function = 'REPLACEB'; - $args = 4; - - break; - case 210: - $function = 'MIDB'; - $args = 3; - - break; - case 211: - $function = 'LENB'; - $args = 1; - - break; - case 212: - $function = 'ROUNDUP'; - $args = 2; - - break; - case 213: - $function = 'ROUNDDOWN'; - $args = 2; - - break; - case 214: - $function = 'ASC'; - $args = 1; - - break; - case 215: - $function = 'DBCS'; - $args = 1; - - break; - case 221: - $function = 'TODAY'; - $args = 0; - - break; - case 229: - $function = 'SINH'; - $args = 1; - - break; - case 230: - $function = 'COSH'; - $args = 1; - - break; - case 231: - $function = 'TANH'; - $args = 1; - - break; - case 232: - $function = 'ASINH'; - $args = 1; - - break; - case 233: - $function = 'ACOSH'; - $args = 1; - - break; - case 234: - $function = 'ATANH'; - $args = 1; - - break; - case 235: - $function = 'DGET'; - $args = 3; - - break; - case 244: - $function = 'INFO'; - $args = 1; - - break; - case 252: - $function = 'FREQUENCY'; - $args = 2; - - break; - case 261: - $function = 'ERROR.TYPE'; - $args = 1; - - break; - case 271: - $function = 'GAMMALN'; - $args = 1; - - break; - case 273: - $function = 'BINOMDIST'; - $args = 4; - - break; - case 274: - $function = 'CHIDIST'; - $args = 2; - - break; - case 275: - $function = 'CHIINV'; - $args = 2; - - break; - case 276: - $function = 'COMBIN'; - $args = 2; - - break; - case 277: - $function = 'CONFIDENCE'; - $args = 3; - - break; - case 278: - $function = 'CRITBINOM'; - $args = 3; - - break; - case 279: - $function = 'EVEN'; - $args = 1; - - break; - case 280: - $function = 'EXPONDIST'; - $args = 3; - - break; - case 281: - $function = 'FDIST'; - $args = 3; - - break; - case 282: - $function = 'FINV'; - $args = 3; - - break; - case 283: - $function = 'FISHER'; - $args = 1; - - break; - case 284: - $function = 'FISHERINV'; - $args = 1; - - break; - case 285: - $function = 'FLOOR'; - $args = 2; - - break; - case 286: - $function = 'GAMMADIST'; - $args = 4; - - break; - case 287: - $function = 'GAMMAINV'; - $args = 3; - - break; - case 288: - $function = 'CEILING'; - $args = 2; - - break; - case 289: - $function = 'HYPGEOMDIST'; - $args = 4; - - break; - case 290: - $function = 'LOGNORMDIST'; - $args = 3; - - break; - case 291: - $function = 'LOGINV'; - $args = 3; - - break; - case 292: - $function = 'NEGBINOMDIST'; - $args = 3; - - break; - case 293: - $function = 'NORMDIST'; - $args = 4; - - break; - case 294: - $function = 'NORMSDIST'; - $args = 1; - - break; - case 295: - $function = 'NORMINV'; - $args = 3; - - break; - case 296: - $function = 'NORMSINV'; - $args = 1; - - break; - case 297: - $function = 'STANDARDIZE'; - $args = 3; - - break; - case 298: - $function = 'ODD'; - $args = 1; - - break; - case 299: - $function = 'PERMUT'; - $args = 2; - - break; - case 300: - $function = 'POISSON'; - $args = 3; - - break; - case 301: - $function = 'TDIST'; - $args = 3; - - break; - case 302: - $function = 'WEIBULL'; - $args = 4; - - break; - case 303: - $function = 'SUMXMY2'; - $args = 2; - - break; - case 304: - $function = 'SUMX2MY2'; - $args = 2; - - break; - case 305: - $function = 'SUMX2PY2'; - $args = 2; - - break; - case 306: - $function = 'CHITEST'; - $args = 2; - - break; - case 307: - $function = 'CORREL'; - $args = 2; - - break; - case 308: - $function = 'COVAR'; - $args = 2; - - break; - case 309: - $function = 'FORECAST'; - $args = 3; - - break; - case 310: - $function = 'FTEST'; - $args = 2; - - break; - case 311: - $function = 'INTERCEPT'; - $args = 2; - - break; - case 312: - $function = 'PEARSON'; - $args = 2; - - break; - case 313: - $function = 'RSQ'; - $args = 2; - - break; - case 314: - $function = 'STEYX'; - $args = 2; - - break; - case 315: - $function = 'SLOPE'; - $args = 2; - - break; - case 316: - $function = 'TTEST'; - $args = 4; - - break; - case 325: - $function = 'LARGE'; - $args = 2; - - break; - case 326: - $function = 'SMALL'; - $args = 2; - - break; - case 327: - $function = 'QUARTILE'; - $args = 2; - - break; - case 328: - $function = 'PERCENTILE'; - $args = 2; - - break; - case 331: - $function = 'TRIMMEAN'; - $args = 2; - - break; - case 332: - $function = 'TINV'; - $args = 2; - - break; - case 337: - $function = 'POWER'; - $args = 2; - - break; - case 342: - $function = 'RADIANS'; - $args = 1; - - break; - case 343: - $function = 'DEGREES'; - $args = 1; - - break; - case 346: - $function = 'COUNTIF'; - $args = 2; - - break; - case 347: - $function = 'COUNTBLANK'; - $args = 1; - - break; - case 350: - $function = 'ISPMT'; - $args = 4; - - break; - case 351: - $function = 'DATEDIF'; - $args = 3; - - break; - case 352: - $function = 'DATESTRING'; - $args = 1; - - break; - case 353: - $function = 'NUMBERSTRING'; - $args = 2; - - break; - case 360: - $function = 'PHONETIC'; - $args = 1; - - break; - case 368: - $function = 'BAHTTEXT'; - $args = 1; - - break; - default: - throw new Exception('Unrecognized function in formula'); - - break; - } - $data = ['function' => $function, 'args' => $args]; - - break; - case 0x22: // function with variable number of arguments - case 0x42: - case 0x62: - $name = 'tFuncV'; - $size = 4; - // offset: 1; size: 1; number of arguments - $args = ord($formulaData[1]); - // offset: 2: size: 2; index to built-in sheet function - $index = self::getUInt2d($formulaData, 2); - switch ($index) { - case 0: - $function = 'COUNT'; - - break; - case 1: - $function = 'IF'; - - break; - case 4: - $function = 'SUM'; - - break; - case 5: - $function = 'AVERAGE'; - - break; - case 6: - $function = 'MIN'; - - break; - case 7: - $function = 'MAX'; - - break; - case 8: - $function = 'ROW'; - - break; - case 9: - $function = 'COLUMN'; - - break; - case 11: - $function = 'NPV'; - - break; - case 12: - $function = 'STDEV'; - - break; - case 13: - $function = 'DOLLAR'; - - break; - case 14: - $function = 'FIXED'; - - break; - case 28: - $function = 'LOOKUP'; - - break; - case 29: - $function = 'INDEX'; - - break; - case 36: - $function = 'AND'; - - break; - case 37: - $function = 'OR'; - - break; - case 46: - $function = 'VAR'; - - break; - case 49: - $function = 'LINEST'; - - break; - case 50: - $function = 'TREND'; - - break; - case 51: - $function = 'LOGEST'; - - break; - case 52: - $function = 'GROWTH'; - - break; - case 56: - $function = 'PV'; - - break; - case 57: - $function = 'FV'; - - break; - case 58: - $function = 'NPER'; - - break; - case 59: - $function = 'PMT'; - - break; - case 60: - $function = 'RATE'; - - break; - case 62: - $function = 'IRR'; - - break; - case 64: - $function = 'MATCH'; - - break; - case 70: - $function = 'WEEKDAY'; - - break; - case 78: - $function = 'OFFSET'; - - break; - case 82: - $function = 'SEARCH'; - - break; - case 100: - $function = 'CHOOSE'; - - break; - case 101: - $function = 'HLOOKUP'; - - break; - case 102: - $function = 'VLOOKUP'; - - break; - case 109: - $function = 'LOG'; - - break; - case 115: - $function = 'LEFT'; - - break; - case 116: - $function = 'RIGHT'; - - break; - case 120: - $function = 'SUBSTITUTE'; - - break; - case 124: - $function = 'FIND'; - - break; - case 125: - $function = 'CELL'; - - break; - case 144: - $function = 'DDB'; - - break; - case 148: - $function = 'INDIRECT'; - - break; - case 167: - $function = 'IPMT'; - - break; - case 168: - $function = 'PPMT'; - - break; - case 169: - $function = 'COUNTA'; - - break; - case 183: - $function = 'PRODUCT'; - - break; - case 193: - $function = 'STDEVP'; - - break; - case 194: - $function = 'VARP'; - - break; - case 197: - $function = 'TRUNC'; - - break; - case 204: - $function = 'USDOLLAR'; - - break; - case 205: - $function = 'FINDB'; - - break; - case 206: - $function = 'SEARCHB'; - - break; - case 208: - $function = 'LEFTB'; - - break; - case 209: - $function = 'RIGHTB'; - - break; - case 216: - $function = 'RANK'; - - break; - case 219: - $function = 'ADDRESS'; - - break; - case 220: - $function = 'DAYS360'; - - break; - case 222: - $function = 'VDB'; - - break; - case 227: - $function = 'MEDIAN'; - - break; - case 228: - $function = 'SUMPRODUCT'; - - break; - case 247: - $function = 'DB'; - - break; - case 255: - $function = ''; - - break; - case 269: - $function = 'AVEDEV'; - - break; - case 270: - $function = 'BETADIST'; - - break; - case 272: - $function = 'BETAINV'; - - break; - case 317: - $function = 'PROB'; - - break; - case 318: - $function = 'DEVSQ'; - - break; - case 319: - $function = 'GEOMEAN'; - - break; - case 320: - $function = 'HARMEAN'; - - break; - case 321: - $function = 'SUMSQ'; - - break; - case 322: - $function = 'KURT'; - - break; - case 323: - $function = 'SKEW'; - - break; - case 324: - $function = 'ZTEST'; - - break; - case 329: - $function = 'PERCENTRANK'; - - break; - case 330: - $function = 'MODE'; - - break; - case 336: - $function = 'CONCATENATE'; - - break; - case 344: - $function = 'SUBTOTAL'; - - break; - case 345: - $function = 'SUMIF'; - - break; - case 354: - $function = 'ROMAN'; - - break; - case 358: - $function = 'GETPIVOTDATA'; - - break; - case 359: - $function = 'HYPERLINK'; - - break; - case 361: - $function = 'AVERAGEA'; - - break; - case 362: - $function = 'MAXA'; - - break; - case 363: - $function = 'MINA'; - - break; - case 364: - $function = 'STDEVPA'; - - break; - case 365: - $function = 'VARPA'; - - break; - case 366: - $function = 'STDEVA'; - - break; - case 367: - $function = 'VARA'; - - break; - default: - throw new Exception('Unrecognized function in formula'); - - break; - } - $data = ['function' => $function, 'args' => $args]; - - break; - case 0x23: // index to defined name - case 0x43: - case 0x63: - $name = 'tName'; - $size = 5; - // offset: 1; size: 2; one-based index to definedname record - $definedNameIndex = self::getUInt2d($formulaData, 1) - 1; - // offset: 2; size: 2; not used - $data = $this->definedname[$definedNameIndex]['name']; - - break; - case 0x24: // single cell reference e.g. A5 - case 0x44: - case 0x64: - $name = 'tRef'; - $size = 5; - $data = $this->readBIFF8CellAddress(substr($formulaData, 1, 4)); - - break; - case 0x25: // cell range reference to cells in the same sheet (2d) - case 0x45: - case 0x65: - $name = 'tArea'; - $size = 9; - $data = $this->readBIFF8CellRangeAddress(substr($formulaData, 1, 8)); - - break; - case 0x26: // Constant reference sub-expression - case 0x46: - case 0x66: - $name = 'tMemArea'; - // offset: 1; size: 4; not used - // offset: 5; size: 2; size of the following subexpression - $subSize = self::getUInt2d($formulaData, 5); - $size = 7 + $subSize; - $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize)); - - break; - case 0x27: // Deleted constant reference sub-expression - case 0x47: - case 0x67: - $name = 'tMemErr'; - // offset: 1; size: 4; not used - // offset: 5; size: 2; size of the following subexpression - $subSize = self::getUInt2d($formulaData, 5); - $size = 7 + $subSize; - $data = $this->getFormulaFromData(substr($formulaData, 7, $subSize)); - - break; - case 0x29: // Variable reference sub-expression - case 0x49: - case 0x69: - $name = 'tMemFunc'; - // offset: 1; size: 2; size of the following sub-expression - $subSize = self::getUInt2d($formulaData, 1); - $size = 3 + $subSize; - $data = $this->getFormulaFromData(substr($formulaData, 3, $subSize)); - - break; - case 0x2C: // Relative 2d cell reference reference, used in shared formulas and some other places - case 0x4C: - case 0x6C: - $name = 'tRefN'; - $size = 5; - $data = $this->readBIFF8CellAddressB(substr($formulaData, 1, 4), $baseCell); - - break; - case 0x2D: // Relative 2d range reference - case 0x4D: - case 0x6D: - $name = 'tAreaN'; - $size = 9; - $data = $this->readBIFF8CellRangeAddressB(substr($formulaData, 1, 8), $baseCell); - - break; - case 0x39: // External name - case 0x59: - case 0x79: - $name = 'tNameX'; - $size = 7; - // offset: 1; size: 2; index to REF entry in EXTERNSHEET record - // offset: 3; size: 2; one-based index to DEFINEDNAME or EXTERNNAME record - $index = self::getUInt2d($formulaData, 3); - // assume index is to EXTERNNAME record - $data = $this->externalNames[$index - 1]['name']; - // offset: 5; size: 2; not used - break; - case 0x3A: // 3d reference to cell - case 0x5A: - case 0x7A: - $name = 'tRef3d'; - $size = 7; - - try { - // offset: 1; size: 2; index to REF entry - $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1)); - // offset: 3; size: 4; cell address - $cellAddress = $this->readBIFF8CellAddress(substr($formulaData, 3, 4)); - - $data = "$sheetRange!$cellAddress"; - } catch (PhpSpreadsheetException $e) { - // deleted sheet reference - $data = '#REF!'; - } - - break; - case 0x3B: // 3d reference to cell range - case 0x5B: - case 0x7B: - $name = 'tArea3d'; - $size = 11; - - try { - // offset: 1; size: 2; index to REF entry - $sheetRange = $this->readSheetRangeByRefIndex(self::getUInt2d($formulaData, 1)); - // offset: 3; size: 8; cell address - $cellRangeAddress = $this->readBIFF8CellRangeAddress(substr($formulaData, 3, 8)); - - $data = "$sheetRange!$cellRangeAddress"; - } catch (PhpSpreadsheetException $e) { - // deleted sheet reference - $data = '#REF!'; - } - - break; - // Unknown cases // don't know how to deal with - default: - throw new Exception('Unrecognized token ' . sprintf('%02X', $id) . ' in formula'); - - break; - } - - return [ - 'id' => $id, - 'name' => $name, - 'size' => $size, - 'data' => $data, - ]; - } - - /** - * Reads a cell address in BIFF8 e.g. 'A2' or '$A$2' - * section 3.3.4. - * - * @param string $cellAddressStructure - * - * @return string - */ - private function readBIFF8CellAddress($cellAddressStructure) - { - // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767)) - $row = self::getUInt2d($cellAddressStructure, 0) + 1; - - // offset: 2; size: 2; index to column or column offset + relative flags - // bit: 7-0; mask 0x00FF; column index - $column = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($cellAddressStructure, 2)) + 1); - - // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) - if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) { - $column = '$' . $column; - } - // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) - if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) { - $row = '$' . $row; - } - - return $column . $row; - } - - /** - * Reads a cell address in BIFF8 for shared formulas. Uses positive and negative values for row and column - * to indicate offsets from a base cell - * section 3.3.4. - * - * @param string $cellAddressStructure - * @param string $baseCell Base cell, only needed when formula contains tRefN tokens, e.g. with shared formulas - * - * @return string - */ - private function readBIFF8CellAddressB($cellAddressStructure, $baseCell = 'A1') - { - [$baseCol, $baseRow] = Coordinate::coordinateFromString($baseCell); - $baseCol = Coordinate::columnIndexFromString($baseCol) - 1; - - // offset: 0; size: 2; index to row (0... 65535) (or offset (-32768... 32767)) - $rowIndex = self::getUInt2d($cellAddressStructure, 0); - $row = self::getUInt2d($cellAddressStructure, 0) + 1; - - // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) - if (!(0x4000 & self::getUInt2d($cellAddressStructure, 2))) { - // offset: 2; size: 2; index to column or column offset + relative flags - // bit: 7-0; mask 0x00FF; column index - $colIndex = 0x00FF & self::getUInt2d($cellAddressStructure, 2); - - $column = Coordinate::stringFromColumnIndex($colIndex + 1); - $column = '$' . $column; - } else { - // offset: 2; size: 2; index to column or column offset + relative flags - // bit: 7-0; mask 0x00FF; column index - $relativeColIndex = 0x00FF & self::getInt2d($cellAddressStructure, 2); - $colIndex = $baseCol + $relativeColIndex; - $colIndex = ($colIndex < 256) ? $colIndex : $colIndex - 256; - $colIndex = ($colIndex >= 0) ? $colIndex : $colIndex + 256; - $column = Coordinate::stringFromColumnIndex($colIndex + 1); - } - - // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) - if (!(0x8000 & self::getUInt2d($cellAddressStructure, 2))) { - $row = '$' . $row; - } else { - $rowIndex = ($rowIndex <= 32767) ? $rowIndex : $rowIndex - 65536; - $row = $baseRow + $rowIndex; - } - - return $column . $row; - } - - /** - * Reads a cell range address in BIFF5 e.g. 'A2:B6' or 'A1' - * always fixed range - * section 2.5.14. - * - * @param string $subData - * - * @return string - */ - private function readBIFF5CellRangeAddressFixed($subData) - { - // offset: 0; size: 2; index to first row - $fr = self::getUInt2d($subData, 0) + 1; - - // offset: 2; size: 2; index to last row - $lr = self::getUInt2d($subData, 2) + 1; - - // offset: 4; size: 1; index to first column - $fc = ord($subData[4]); - - // offset: 5; size: 1; index to last column - $lc = ord($subData[5]); - - // check values - if ($fr > $lr || $fc > $lc) { - throw new Exception('Not a cell range address'); - } - - // column index to letter - $fc = Coordinate::stringFromColumnIndex($fc + 1); - $lc = Coordinate::stringFromColumnIndex($lc + 1); - - if ($fr == $lr && $fc == $lc) { - return "$fc$fr"; - } - - return "$fc$fr:$lc$lr"; - } - - /** - * Reads a cell range address in BIFF8 e.g. 'A2:B6' or 'A1' - * always fixed range - * section 2.5.14. - * - * @param string $subData - * - * @return string - */ - private function readBIFF8CellRangeAddressFixed($subData) - { - // offset: 0; size: 2; index to first row - $fr = self::getUInt2d($subData, 0) + 1; - - // offset: 2; size: 2; index to last row - $lr = self::getUInt2d($subData, 2) + 1; - - // offset: 4; size: 2; index to first column - $fc = self::getUInt2d($subData, 4); - - // offset: 6; size: 2; index to last column - $lc = self::getUInt2d($subData, 6); - - // check values - if ($fr > $lr || $fc > $lc) { - throw new Exception('Not a cell range address'); - } - - // column index to letter - $fc = Coordinate::stringFromColumnIndex($fc + 1); - $lc = Coordinate::stringFromColumnIndex($lc + 1); - - if ($fr == $lr && $fc == $lc) { - return "$fc$fr"; - } - - return "$fc$fr:$lc$lr"; - } - - /** - * Reads a cell range address in BIFF8 e.g. 'A2:B6' or '$A$2:$B$6' - * there are flags indicating whether column/row index is relative - * section 3.3.4. - * - * @param string $subData - * - * @return string - */ - private function readBIFF8CellRangeAddress($subData) - { - // todo: if cell range is just a single cell, should this funciton - // not just return e.g. 'A1' and not 'A1:A1' ? - - // offset: 0; size: 2; index to first row (0... 65535) (or offset (-32768... 32767)) - $fr = self::getUInt2d($subData, 0) + 1; - - // offset: 2; size: 2; index to last row (0... 65535) (or offset (-32768... 32767)) - $lr = self::getUInt2d($subData, 2) + 1; - - // offset: 4; size: 2; index to first column or column offset + relative flags - - // bit: 7-0; mask 0x00FF; column index - $fc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 4)) + 1); - - // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) - if (!(0x4000 & self::getUInt2d($subData, 4))) { - $fc = '$' . $fc; - } - - // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) - if (!(0x8000 & self::getUInt2d($subData, 4))) { - $fr = '$' . $fr; - } - - // offset: 6; size: 2; index to last column or column offset + relative flags - - // bit: 7-0; mask 0x00FF; column index - $lc = Coordinate::stringFromColumnIndex((0x00FF & self::getUInt2d($subData, 6)) + 1); - - // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) - if (!(0x4000 & self::getUInt2d($subData, 6))) { - $lc = '$' . $lc; - } - - // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) - if (!(0x8000 & self::getUInt2d($subData, 6))) { - $lr = '$' . $lr; - } - - return "$fc$fr:$lc$lr"; - } - - /** - * Reads a cell range address in BIFF8 for shared formulas. Uses positive and negative values for row and column - * to indicate offsets from a base cell - * section 3.3.4. - * - * @param string $subData - * @param string $baseCell Base cell - * - * @return string Cell range address - */ - private function readBIFF8CellRangeAddressB($subData, $baseCell = 'A1') - { - [$baseCol, $baseRow] = Coordinate::coordinateFromString($baseCell); - $baseCol = Coordinate::columnIndexFromString($baseCol) - 1; - - // TODO: if cell range is just a single cell, should this funciton - // not just return e.g. 'A1' and not 'A1:A1' ? - - // offset: 0; size: 2; first row - $frIndex = self::getUInt2d($subData, 0); // adjust below - - // offset: 2; size: 2; relative index to first row (0... 65535) should be treated as offset (-32768... 32767) - $lrIndex = self::getUInt2d($subData, 2); // adjust below - - // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) - if (!(0x4000 & self::getUInt2d($subData, 4))) { - // absolute column index - // offset: 4; size: 2; first column with relative/absolute flags - // bit: 7-0; mask 0x00FF; column index - $fcIndex = 0x00FF & self::getUInt2d($subData, 4); - $fc = Coordinate::stringFromColumnIndex($fcIndex + 1); - $fc = '$' . $fc; - } else { - // column offset - // offset: 4; size: 2; first column with relative/absolute flags - // bit: 7-0; mask 0x00FF; column index - $relativeFcIndex = 0x00FF & self::getInt2d($subData, 4); - $fcIndex = $baseCol + $relativeFcIndex; - $fcIndex = ($fcIndex < 256) ? $fcIndex : $fcIndex - 256; - $fcIndex = ($fcIndex >= 0) ? $fcIndex : $fcIndex + 256; - $fc = Coordinate::stringFromColumnIndex($fcIndex + 1); - } - - // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) - if (!(0x8000 & self::getUInt2d($subData, 4))) { - // absolute row index - $fr = $frIndex + 1; - $fr = '$' . $fr; - } else { - // row offset - $frIndex = ($frIndex <= 32767) ? $frIndex : $frIndex - 65536; - $fr = $baseRow + $frIndex; - } - - // bit: 14; mask 0x4000; (1 = relative column index, 0 = absolute column index) - if (!(0x4000 & self::getUInt2d($subData, 6))) { - // absolute column index - // offset: 6; size: 2; last column with relative/absolute flags - // bit: 7-0; mask 0x00FF; column index - $lcIndex = 0x00FF & self::getUInt2d($subData, 6); - $lc = Coordinate::stringFromColumnIndex($lcIndex + 1); - $lc = '$' . $lc; - } else { - // column offset - // offset: 4; size: 2; first column with relative/absolute flags - // bit: 7-0; mask 0x00FF; column index - $relativeLcIndex = 0x00FF & self::getInt2d($subData, 4); - $lcIndex = $baseCol + $relativeLcIndex; - $lcIndex = ($lcIndex < 256) ? $lcIndex : $lcIndex - 256; - $lcIndex = ($lcIndex >= 0) ? $lcIndex : $lcIndex + 256; - $lc = Coordinate::stringFromColumnIndex($lcIndex + 1); - } - - // bit: 15; mask 0x8000; (1 = relative row index, 0 = absolute row index) - if (!(0x8000 & self::getUInt2d($subData, 6))) { - // absolute row index - $lr = $lrIndex + 1; - $lr = '$' . $lr; - } else { - // row offset - $lrIndex = ($lrIndex <= 32767) ? $lrIndex : $lrIndex - 65536; - $lr = $baseRow + $lrIndex; - } - - return "$fc$fr:$lc$lr"; - } - - /** - * Read BIFF8 cell range address list - * section 2.5.15. - * - * @param string $subData - * - * @return array - */ - private function readBIFF8CellRangeAddressList($subData) - { - $cellRangeAddresses = []; - - // offset: 0; size: 2; number of the following cell range addresses - $nm = self::getUInt2d($subData, 0); - - $offset = 2; - // offset: 2; size: 8 * $nm; list of $nm (fixed) cell range addresses - for ($i = 0; $i < $nm; ++$i) { - $cellRangeAddresses[] = $this->readBIFF8CellRangeAddressFixed(substr($subData, $offset, 8)); - $offset += 8; - } - - return [ - 'size' => 2 + 8 * $nm, - 'cellRangeAddresses' => $cellRangeAddresses, - ]; - } - - /** - * Read BIFF5 cell range address list - * section 2.5.15. - * - * @param string $subData - * - * @return array - */ - private function readBIFF5CellRangeAddressList($subData) - { - $cellRangeAddresses = []; - - // offset: 0; size: 2; number of the following cell range addresses - $nm = self::getUInt2d($subData, 0); - - $offset = 2; - // offset: 2; size: 6 * $nm; list of $nm (fixed) cell range addresses - for ($i = 0; $i < $nm; ++$i) { - $cellRangeAddresses[] = $this->readBIFF5CellRangeAddressFixed(substr($subData, $offset, 6)); - $offset += 6; - } - - return [ - 'size' => 2 + 6 * $nm, - 'cellRangeAddresses' => $cellRangeAddresses, - ]; - } - - /** - * Get a sheet range like Sheet1:Sheet3 from REF index - * Note: If there is only one sheet in the range, one gets e.g Sheet1 - * It can also happen that the REF structure uses the -1 (FFFF) code to indicate deleted sheets, - * in which case an Exception is thrown. - * - * @param int $index - * - * @return false|string - */ - private function readSheetRangeByRefIndex($index) - { - if (isset($this->ref[$index])) { - $type = $this->externalBooks[$this->ref[$index]['externalBookIndex']]['type']; - - switch ($type) { - case 'internal': - // check if we have a deleted 3d reference - if ($this->ref[$index]['firstSheetIndex'] == 0xFFFF || $this->ref[$index]['lastSheetIndex'] == 0xFFFF) { - throw new Exception('Deleted sheet reference'); - } - - // we have normal sheet range (collapsed or uncollapsed) - $firstSheetName = $this->sheets[$this->ref[$index]['firstSheetIndex']]['name']; - $lastSheetName = $this->sheets[$this->ref[$index]['lastSheetIndex']]['name']; - - if ($firstSheetName == $lastSheetName) { - // collapsed sheet range - $sheetRange = $firstSheetName; - } else { - $sheetRange = "$firstSheetName:$lastSheetName"; - } - - // escape the single-quotes - $sheetRange = str_replace("'", "''", $sheetRange); - - // if there are special characters, we need to enclose the range in single-quotes - // todo: check if we have identified the whole set of special characters - // it seems that the following characters are not accepted for sheet names - // and we may assume that they are not present: []*/:\? - if (preg_match("/[ !\"@#£$%&{()}<>=+'|^,;-]/u", $sheetRange)) { - $sheetRange = "'$sheetRange'"; - } - - return $sheetRange; - - break; - default: - // TODO: external sheet support - throw new Exception('Xls reader only supports internal sheets in formulas'); - - break; - } - } - - return false; - } - - /** - * read BIFF8 constant value array from array data - * returns e.g. ['value' => '{1,2;3,4}', 'size' => 40] - * section 2.5.8. - * - * @param string $arrayData - * - * @return array - */ - private static function readBIFF8ConstantArray($arrayData) - { - // offset: 0; size: 1; number of columns decreased by 1 - $nc = ord($arrayData[0]); - - // offset: 1; size: 2; number of rows decreased by 1 - $nr = self::getUInt2d($arrayData, 1); - $size = 3; // initialize - $arrayData = substr($arrayData, 3); - - // offset: 3; size: var; list of ($nc + 1) * ($nr + 1) constant values - $matrixChunks = []; - for ($r = 1; $r <= $nr + 1; ++$r) { - $items = []; - for ($c = 1; $c <= $nc + 1; ++$c) { - $constant = self::readBIFF8Constant($arrayData); - $items[] = $constant['value']; - $arrayData = substr($arrayData, $constant['size']); - $size += $constant['size']; - } - $matrixChunks[] = implode(',', $items); // looks like e.g. '1,"hello"' - } - $matrix = '{' . implode(';', $matrixChunks) . '}'; - - return [ - 'value' => $matrix, - 'size' => $size, - ]; - } - - /** - * read BIFF8 constant value which may be 'Empty Value', 'Number', 'String Value', 'Boolean Value', 'Error Value' - * section 2.5.7 - * returns e.g. ['value' => '5', 'size' => 9]. - * - * @param string $valueData - * - * @return array - */ - private static function readBIFF8Constant($valueData) - { - // offset: 0; size: 1; identifier for type of constant - $identifier = ord($valueData[0]); - - switch ($identifier) { - case 0x00: // empty constant (what is this?) - $value = ''; - $size = 9; - - break; - case 0x01: // number - // offset: 1; size: 8; IEEE 754 floating-point value - $value = self::extractNumber(substr($valueData, 1, 8)); - $size = 9; - - break; - case 0x02: // string value - // offset: 1; size: var; Unicode string, 16-bit string length - $string = self::readUnicodeStringLong(substr($valueData, 1)); - $value = '"' . $string['value'] . '"'; - $size = 1 + $string['size']; - - break; - case 0x04: // boolean - // offset: 1; size: 1; 0 = FALSE, 1 = TRUE - if (ord($valueData[1])) { - $value = 'TRUE'; - } else { - $value = 'FALSE'; - } - $size = 9; - - break; - case 0x10: // error code - // offset: 1; size: 1; error code - $value = Xls\ErrorCode::lookup(ord($valueData[1])); - $size = 9; - - break; - } - - return [ - 'value' => $value, - 'size' => $size, - ]; - } - - /** - * Extract RGB color - * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.4. - * - * @param string $rgb Encoded RGB value (4 bytes) - * - * @return array - */ - private static function readRGB($rgb) - { - // offset: 0; size 1; Red component - $r = ord($rgb[0]); - - // offset: 1; size: 1; Green component - $g = ord($rgb[1]); - - // offset: 2; size: 1; Blue component - $b = ord($rgb[2]); - - // HEX notation, e.g. 'FF00FC' - $rgb = sprintf('%02X%02X%02X', $r, $g, $b); - - return ['rgb' => $rgb]; - } - - /** - * Read byte string (8-bit string length) - * OpenOffice documentation: 2.5.2. - * - * @param string $subData - * - * @return array - */ - private function readByteStringShort($subData) - { - // offset: 0; size: 1; length of the string (character count) - $ln = ord($subData[0]); - - // offset: 1: size: var; character array (8-bit characters) - $value = $this->decodeCodepage(substr($subData, 1, $ln)); - - return [ - 'value' => $value, - 'size' => 1 + $ln, // size in bytes of data structure - ]; - } - - /** - * Read byte string (16-bit string length) - * OpenOffice documentation: 2.5.2. - * - * @param string $subData - * - * @return array - */ - private function readByteStringLong($subData) - { - // offset: 0; size: 2; length of the string (character count) - $ln = self::getUInt2d($subData, 0); - - // offset: 2: size: var; character array (8-bit characters) - $value = $this->decodeCodepage(substr($subData, 2)); - - //return $string; - return [ - 'value' => $value, - 'size' => 2 + $ln, // size in bytes of data structure - ]; - } - - /** - * Extracts an Excel Unicode short string (8-bit string length) - * OpenOffice documentation: 2.5.3 - * function will automatically find out where the Unicode string ends. - * - * @param string $subData - * - * @return array - */ - private static function readUnicodeStringShort($subData) - { - $value = ''; - - // offset: 0: size: 1; length of the string (character count) - $characterCount = ord($subData[0]); - - $string = self::readUnicodeString(substr($subData, 1), $characterCount); - - // add 1 for the string length - ++$string['size']; - - return $string; - } - - /** - * Extracts an Excel Unicode long string (16-bit string length) - * OpenOffice documentation: 2.5.3 - * this function is under construction, needs to support rich text, and Asian phonetic settings. - * - * @param string $subData - * - * @return array - */ - private static function readUnicodeStringLong($subData) - { - $value = ''; - - // offset: 0: size: 2; length of the string (character count) - $characterCount = self::getUInt2d($subData, 0); - - $string = self::readUnicodeString(substr($subData, 2), $characterCount); - - // add 2 for the string length - $string['size'] += 2; - - return $string; - } - - /** - * Read Unicode string with no string length field, but with known character count - * this function is under construction, needs to support rich text, and Asian phonetic settings - * OpenOffice.org's Documentation of the Microsoft Excel File Format, section 2.5.3. - * - * @param string $subData - * @param int $characterCount - * - * @return array - */ - private static function readUnicodeString($subData, $characterCount) - { - $value = ''; - - // offset: 0: size: 1; option flags - // bit: 0; mask: 0x01; character compression (0 = compressed 8-bit, 1 = uncompressed 16-bit) - $isCompressed = !((0x01 & ord($subData[0])) >> 0); - - // bit: 2; mask: 0x04; Asian phonetic settings - $hasAsian = (0x04) & ord($subData[0]) >> 2; - - // bit: 3; mask: 0x08; Rich-Text settings - $hasRichText = (0x08) & ord($subData[0]) >> 3; - - // offset: 1: size: var; character array - // this offset assumes richtext and Asian phonetic settings are off which is generally wrong - // needs to be fixed - $value = self::encodeUTF16(substr($subData, 1, $isCompressed ? $characterCount : 2 * $characterCount), $isCompressed); - - return [ - 'value' => $value, - 'size' => $isCompressed ? 1 + $characterCount : 1 + 2 * $characterCount, // the size in bytes including the option flags - ]; - } - - /** - * Convert UTF-8 string to string surounded by double quotes. Used for explicit string tokens in formulas. - * Example: hello"world --> "hello""world". - * - * @param string $value UTF-8 encoded string - * - * @return string - */ - private static function UTF8toExcelDoubleQuoted($value) - { - return '"' . str_replace('"', '""', $value) . '"'; - } - - /** - * Reads first 8 bytes of a string and return IEEE 754 float. - * - * @param string $data Binary string that is at least 8 bytes long - * - * @return float - */ - private static function extractNumber($data) - { - $rknumhigh = self::getInt4d($data, 4); - $rknumlow = self::getInt4d($data, 0); - $sign = ($rknumhigh & 0x80000000) >> 31; - $exp = (($rknumhigh & 0x7ff00000) >> 20) - 1023; - $mantissa = (0x100000 | ($rknumhigh & 0x000fffff)); - $mantissalow1 = ($rknumlow & 0x80000000) >> 31; - $mantissalow2 = ($rknumlow & 0x7fffffff); - $value = $mantissa / 2 ** (20 - $exp); - - if ($mantissalow1 != 0) { - $value += 1 / 2 ** (21 - $exp); - } - - $value += $mantissalow2 / 2 ** (52 - $exp); - if ($sign) { - $value *= -1; - } - - return $value; - } - - /** - * @param int $rknum - * - * @return float - */ - private static function getIEEE754($rknum) - { - if (($rknum & 0x02) != 0) { - $value = $rknum >> 2; - } else { - // changes by mmp, info on IEEE754 encoding from - // research.microsoft.com/~hollasch/cgindex/coding/ieeefloat.html - // The RK format calls for using only the most significant 30 bits - // of the 64 bit floating point value. The other 34 bits are assumed - // to be 0 so we use the upper 30 bits of $rknum as follows... - $sign = ($rknum & 0x80000000) >> 31; - $exp = ($rknum & 0x7ff00000) >> 20; - $mantissa = (0x100000 | ($rknum & 0x000ffffc)); - $value = $mantissa / 2 ** (20 - ($exp - 1023)); - if ($sign) { - $value = -1 * $value; - } - //end of changes by mmp - } - if (($rknum & 0x01) != 0) { - $value /= 100; - } - - return $value; - } - - /** - * Get UTF-8 string from (compressed or uncompressed) UTF-16 string. - * - * @param string $string - * @param bool $compressed - * - * @return string - */ - private static function encodeUTF16($string, $compressed = false) - { - if ($compressed) { - $string = self::uncompressByteString($string); - } - - return StringHelper::convertEncoding($string, 'UTF-8', 'UTF-16LE'); - } - - /** - * Convert UTF-16 string in compressed notation to uncompressed form. Only used for BIFF8. - * - * @param string $string - * - * @return string - */ - private static function uncompressByteString($string) - { - $uncompressedString = ''; - $strLen = strlen($string); - for ($i = 0; $i < $strLen; ++$i) { - $uncompressedString .= $string[$i] . "\0"; - } - - return $uncompressedString; - } - - /** - * Convert string to UTF-8. Only used for BIFF5. - * - * @param string $string - * - * @return string - */ - private function decodeCodepage($string) - { - return StringHelper::convertEncoding($string, 'UTF-8', $this->codepage); - } - - /** - * Read 16-bit unsigned integer. - * - * @param string $data - * @param int $pos - * - * @return int - */ - public static function getUInt2d($data, $pos) - { - return ord($data[$pos]) | (ord($data[$pos + 1]) << 8); - } - - /** - * Read 16-bit signed integer. - * - * @param string $data - * @param int $pos - * - * @return int - */ - public static function getInt2d($data, $pos) - { - return unpack('s', $data[$pos] . $data[$pos + 1])[1]; - } - - /** - * Read 32-bit signed integer. - * - * @param string $data - * @param int $pos - * - * @return int - */ - public static function getInt4d($data, $pos) - { - // 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; - } - - private function parseRichText($is) - { - $value = new RichText(); - $value->createText($is); - - return $value; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color.php deleted file mode 100644 index c45f88c72b5..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color.php +++ /dev/null @@ -1,36 +0,0 @@ - 'FF0000'] - */ - public static function map($color, $palette, $version) - { - if ($color <= 0x07 || $color >= 0x40) { - // special built-in color - return Color\BuiltIn::lookup($color); - } elseif (isset($palette, $palette[$color - 8])) { - // palette color, color index 0x08 maps to pallete index 0 - return $palette[$color - 8]; - } - - // default color table - if ($version == Xls::XLS_BIFF8) { - return Color\BIFF8::lookup($color); - } - - // BIFF5 - return Color\BIFF5::lookup($color); - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php deleted file mode 100644 index 743d938773e..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF5.php +++ /dev/null @@ -1,81 +0,0 @@ - '000000', - 0x09 => 'FFFFFF', - 0x0A => 'FF0000', - 0x0B => '00FF00', - 0x0C => '0000FF', - 0x0D => 'FFFF00', - 0x0E => 'FF00FF', - 0x0F => '00FFFF', - 0x10 => '800000', - 0x11 => '008000', - 0x12 => '000080', - 0x13 => '808000', - 0x14 => '800080', - 0x15 => '008080', - 0x16 => 'C0C0C0', - 0x17 => '808080', - 0x18 => '8080FF', - 0x19 => '802060', - 0x1A => 'FFFFC0', - 0x1B => 'A0E0F0', - 0x1C => '600080', - 0x1D => 'FF8080', - 0x1E => '0080C0', - 0x1F => 'C0C0FF', - 0x20 => '000080', - 0x21 => 'FF00FF', - 0x22 => 'FFFF00', - 0x23 => '00FFFF', - 0x24 => '800080', - 0x25 => '800000', - 0x26 => '008080', - 0x27 => '0000FF', - 0x28 => '00CFFF', - 0x29 => '69FFFF', - 0x2A => 'E0FFE0', - 0x2B => 'FFFF80', - 0x2C => 'A6CAF0', - 0x2D => 'DD9CB3', - 0x2E => 'B38FEE', - 0x2F => 'E3E3E3', - 0x30 => '2A6FF9', - 0x31 => '3FB8CD', - 0x32 => '488436', - 0x33 => '958C41', - 0x34 => '8E5E42', - 0x35 => 'A0627A', - 0x36 => '624FAC', - 0x37 => '969696', - 0x38 => '1D2FBE', - 0x39 => '286676', - 0x3A => '004500', - 0x3B => '453E01', - 0x3C => '6A2813', - 0x3D => '85396A', - 0x3E => '4A3285', - 0x3F => '424242', - ]; - - /** - * Map color array from BIFF5 built-in color index. - * - * @param int $color - * - * @return array - */ - public static function lookup($color) - { - if (isset(self::$map[$color])) { - return ['rgb' => self::$map[$color]]; - } - - return ['rgb' => '000000']; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php deleted file mode 100644 index 5c109fb071f..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BIFF8.php +++ /dev/null @@ -1,81 +0,0 @@ - '000000', - 0x09 => 'FFFFFF', - 0x0A => 'FF0000', - 0x0B => '00FF00', - 0x0C => '0000FF', - 0x0D => 'FFFF00', - 0x0E => 'FF00FF', - 0x0F => '00FFFF', - 0x10 => '800000', - 0x11 => '008000', - 0x12 => '000080', - 0x13 => '808000', - 0x14 => '800080', - 0x15 => '008080', - 0x16 => 'C0C0C0', - 0x17 => '808080', - 0x18 => '9999FF', - 0x19 => '993366', - 0x1A => 'FFFFCC', - 0x1B => 'CCFFFF', - 0x1C => '660066', - 0x1D => 'FF8080', - 0x1E => '0066CC', - 0x1F => 'CCCCFF', - 0x20 => '000080', - 0x21 => 'FF00FF', - 0x22 => 'FFFF00', - 0x23 => '00FFFF', - 0x24 => '800080', - 0x25 => '800000', - 0x26 => '008080', - 0x27 => '0000FF', - 0x28 => '00CCFF', - 0x29 => 'CCFFFF', - 0x2A => 'CCFFCC', - 0x2B => 'FFFF99', - 0x2C => '99CCFF', - 0x2D => 'FF99CC', - 0x2E => 'CC99FF', - 0x2F => 'FFCC99', - 0x30 => '3366FF', - 0x31 => '33CCCC', - 0x32 => '99CC00', - 0x33 => 'FFCC00', - 0x34 => 'FF9900', - 0x35 => 'FF6600', - 0x36 => '666699', - 0x37 => '969696', - 0x38 => '003366', - 0x39 => '339966', - 0x3A => '003300', - 0x3B => '333300', - 0x3C => '993300', - 0x3D => '993366', - 0x3E => '333399', - 0x3F => '333333', - ]; - - /** - * Map color array from BIFF8 built-in color index. - * - * @param int $color - * - * @return array - */ - public static function lookup($color) - { - if (isset(self::$map[$color])) { - return ['rgb' => self::$map[$color]]; - } - - return ['rgb' => '000000']; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php deleted file mode 100644 index 90d50e336bb..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Color/BuiltIn.php +++ /dev/null @@ -1,35 +0,0 @@ - '000000', - 0x01 => 'FFFFFF', - 0x02 => 'FF0000', - 0x03 => '00FF00', - 0x04 => '0000FF', - 0x05 => 'FFFF00', - 0x06 => 'FF00FF', - 0x07 => '00FFFF', - 0x40 => '000000', // system window text color - 0x41 => 'FFFFFF', // system window background color - ]; - - /** - * Map built-in color to RGB value. - * - * @param int $color Indexed color - * - * @return array - */ - public static function lookup($color) - { - if (isset(self::$map[$color])) { - return ['rgb' => self::$map[$color]]; - } - - return ['rgb' => '000000']; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php deleted file mode 100644 index 7daf7230ff0..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/ErrorCode.php +++ /dev/null @@ -1,32 +0,0 @@ - '#NULL!', - 0x07 => '#DIV/0!', - 0x0F => '#VALUE!', - 0x17 => '#REF!', - 0x1D => '#NAME?', - 0x24 => '#NUM!', - 0x2A => '#N/A', - ]; - - /** - * Map error code, e.g. '#N/A'. - * - * @param int $code - * - * @return bool|string - */ - public static function lookup($code) - { - if (isset(self::$map[$code])) { - return self::$map[$code]; - } - - return false; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php deleted file mode 100644 index 306fc8f1f74..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Escher.php +++ /dev/null @@ -1,677 +0,0 @@ -object = $object; - } - - /** - * Load Escher stream data. May be a partial Escher stream. - * - * @param string $data - * - * @return BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer - */ - public function load($data) - { - $this->data = $data; - - // total byte size of Excel data (workbook global substream + sheet substreams) - $this->dataSize = strlen($this->data); - - $this->pos = 0; - - // Parse Escher stream - while ($this->pos < $this->dataSize) { - // offset: 2; size: 2: Record Type - $fbt = Xls::getUInt2d($this->data, $this->pos + 2); - - switch ($fbt) { - case self::DGGCONTAINER: - $this->readDggContainer(); - - break; - case self::DGG: - $this->readDgg(); - - break; - case self::BSTORECONTAINER: - $this->readBstoreContainer(); - - break; - case self::BSE: - $this->readBSE(); - - break; - case self::BLIPJPEG: - $this->readBlipJPEG(); - - break; - case self::BLIPPNG: - $this->readBlipPNG(); - - break; - case self::OPT: - $this->readOPT(); - - break; - case self::TERTIARYOPT: - $this->readTertiaryOPT(); - - break; - case self::SPLITMENUCOLORS: - $this->readSplitMenuColors(); - - break; - case self::DGCONTAINER: - $this->readDgContainer(); - - break; - case self::DG: - $this->readDg(); - - break; - case self::SPGRCONTAINER: - $this->readSpgrContainer(); - - break; - case self::SPCONTAINER: - $this->readSpContainer(); - - break; - case self::SPGR: - $this->readSpgr(); - - break; - case self::SP: - $this->readSp(); - - break; - case self::CLIENTTEXTBOX: - $this->readClientTextbox(); - - break; - case self::CLIENTANCHOR: - $this->readClientAnchor(); - - break; - case self::CLIENTDATA: - $this->readClientData(); - - break; - default: - $this->readDefault(); - - break; - } - } - - return $this->object; - } - - /** - * Read a generic record. - */ - private function readDefault(): void - { - // offset 0; size: 2; recVer and recInstance - $verInstance = Xls::getUInt2d($this->data, $this->pos); - - // offset: 2; size: 2: Record Type - $fbt = Xls::getUInt2d($this->data, $this->pos + 2); - - // bit: 0-3; mask: 0x000F; recVer - $recVer = (0x000F & $verInstance) >> 0; - - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - } - - /** - * Read DggContainer record (Drawing Group Container). - */ - private function readDggContainer(): void - { - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - - // record is a container, read contents - $dggContainer = new DggContainer(); - $this->object->setDggContainer($dggContainer); - $reader = new self($dggContainer); - $reader->load($recordData); - } - - /** - * Read Dgg record (Drawing Group). - */ - private function readDgg(): void - { - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - } - - /** - * Read BstoreContainer record (Blip Store Container). - */ - private function readBstoreContainer(): void - { - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - - // record is a container, read contents - $bstoreContainer = new BstoreContainer(); - $this->object->setBstoreContainer($bstoreContainer); - $reader = new self($bstoreContainer); - $reader->load($recordData); - } - - /** - * Read BSE record. - */ - private function readBSE(): void - { - // offset: 0; size: 2; recVer and recInstance - - // bit: 4-15; mask: 0xFFF0; recInstance - $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; - - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - - // add BSE to BstoreContainer - $BSE = new BSE(); - $this->object->addBSE($BSE); - - $BSE->setBLIPType($recInstance); - - // offset: 0; size: 1; btWin32 (MSOBLIPTYPE) - $btWin32 = ord($recordData[0]); - - // offset: 1; size: 1; btWin32 (MSOBLIPTYPE) - $btMacOS = ord($recordData[1]); - - // offset: 2; size: 16; MD4 digest - $rgbUid = substr($recordData, 2, 16); - - // offset: 18; size: 2; tag - $tag = Xls::getUInt2d($recordData, 18); - - // offset: 20; size: 4; size of BLIP in bytes - $size = Xls::getInt4d($recordData, 20); - - // offset: 24; size: 4; number of references to this BLIP - $cRef = Xls::getInt4d($recordData, 24); - - // offset: 28; size: 4; MSOFO file offset - $foDelay = Xls::getInt4d($recordData, 28); - - // offset: 32; size: 1; unused1 - $unused1 = ord($recordData[32]); - - // offset: 33; size: 1; size of nameData in bytes (including null terminator) - $cbName = ord($recordData[33]); - - // offset: 34; size: 1; unused2 - $unused2 = ord($recordData[34]); - - // offset: 35; size: 1; unused3 - $unused3 = ord($recordData[35]); - - // offset: 36; size: $cbName; nameData - $nameData = substr($recordData, 36, $cbName); - - // offset: 36 + $cbName, size: var; the BLIP data - $blipData = substr($recordData, 36 + $cbName); - - // record is a container, read contents - $reader = new self($BSE); - $reader->load($blipData); - } - - /** - * Read BlipJPEG record. Holds raw JPEG image data. - */ - private function readBlipJPEG(): void - { - // offset: 0; size: 2; recVer and recInstance - - // bit: 4-15; mask: 0xFFF0; recInstance - $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; - - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - - $pos = 0; - - // offset: 0; size: 16; rgbUid1 (MD4 digest of) - $rgbUid1 = substr($recordData, 0, 16); - $pos += 16; - - // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3 - if (in_array($recInstance, [0x046B, 0x06E3])) { - $rgbUid2 = substr($recordData, 16, 16); - $pos += 16; - } - - // offset: var; size: 1; tag - $tag = ord($recordData[$pos]); - ++$pos; - - // offset: var; size: var; the raw image data - $data = substr($recordData, $pos); - - $blip = new Blip(); - $blip->setData($data); - - $this->object->setBlip($blip); - } - - /** - * Read BlipPNG record. Holds raw PNG image data. - */ - private function readBlipPNG(): void - { - // offset: 0; size: 2; recVer and recInstance - - // bit: 4-15; mask: 0xFFF0; recInstance - $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; - - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - - $pos = 0; - - // offset: 0; size: 16; rgbUid1 (MD4 digest of) - $rgbUid1 = substr($recordData, 0, 16); - $pos += 16; - - // offset: 16; size: 16; rgbUid2 (MD4 digest), only if $recInstance = 0x46B or 0x6E3 - if ($recInstance == 0x06E1) { - $rgbUid2 = substr($recordData, 16, 16); - $pos += 16; - } - - // offset: var; size: 1; tag - $tag = ord($recordData[$pos]); - ++$pos; - - // offset: var; size: var; the raw image data - $data = substr($recordData, $pos); - - $blip = new Blip(); - $blip->setData($data); - - $this->object->setBlip($blip); - } - - /** - * Read OPT record. This record may occur within DggContainer record or SpContainer. - */ - private function readOPT(): void - { - // offset: 0; size: 2; recVer and recInstance - - // bit: 4-15; mask: 0xFFF0; recInstance - $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; - - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - - $this->readOfficeArtRGFOPTE($recordData, $recInstance); - } - - /** - * Read TertiaryOPT record. - */ - private function readTertiaryOPT(): void - { - // offset: 0; size: 2; recVer and recInstance - - // bit: 4-15; mask: 0xFFF0; recInstance - $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; - - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - } - - /** - * Read SplitMenuColors record. - */ - private function readSplitMenuColors(): void - { - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - } - - /** - * Read DgContainer record (Drawing Container). - */ - private function readDgContainer(): void - { - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - - // record is a container, read contents - $dgContainer = new DgContainer(); - $this->object->setDgContainer($dgContainer); - $reader = new self($dgContainer); - $escher = $reader->load($recordData); - } - - /** - * Read Dg record (Drawing). - */ - private function readDg(): void - { - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - } - - /** - * Read SpgrContainer record (Shape Group Container). - */ - private function readSpgrContainer(): void - { - // context is either context DgContainer or SpgrContainer - - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - - // record is a container, read contents - $spgrContainer = new SpgrContainer(); - - if ($this->object instanceof DgContainer) { - // DgContainer - $this->object->setSpgrContainer($spgrContainer); - } else { - // SpgrContainer - $this->object->addChild($spgrContainer); - } - - $reader = new self($spgrContainer); - $escher = $reader->load($recordData); - } - - /** - * Read SpContainer record (Shape Container). - */ - private function readSpContainer(): void - { - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // add spContainer to spgrContainer - $spContainer = new SpContainer(); - $this->object->addChild($spContainer); - - // move stream pointer to next record - $this->pos += 8 + $length; - - // record is a container, read contents - $reader = new self($spContainer); - $escher = $reader->load($recordData); - } - - /** - * Read Spgr record (Shape Group). - */ - private function readSpgr(): void - { - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - } - - /** - * Read Sp record (Shape). - */ - private function readSp(): void - { - // offset: 0; size: 2; recVer and recInstance - - // bit: 4-15; mask: 0xFFF0; recInstance - $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; - - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - } - - /** - * Read ClientTextbox record. - */ - private function readClientTextbox(): void - { - // offset: 0; size: 2; recVer and recInstance - - // bit: 4-15; mask: 0xFFF0; recInstance - $recInstance = (0xFFF0 & Xls::getUInt2d($this->data, $this->pos)) >> 4; - - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - } - - /** - * Read ClientAnchor record. This record holds information about where the shape is anchored in worksheet. - */ - private function readClientAnchor(): void - { - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - - // offset: 2; size: 2; upper-left corner column index (0-based) - $c1 = Xls::getUInt2d($recordData, 2); - - // offset: 4; size: 2; upper-left corner horizontal offset in 1/1024 of column width - $startOffsetX = Xls::getUInt2d($recordData, 4); - - // offset: 6; size: 2; upper-left corner row index (0-based) - $r1 = Xls::getUInt2d($recordData, 6); - - // offset: 8; size: 2; upper-left corner vertical offset in 1/256 of row height - $startOffsetY = Xls::getUInt2d($recordData, 8); - - // offset: 10; size: 2; bottom-right corner column index (0-based) - $c2 = Xls::getUInt2d($recordData, 10); - - // offset: 12; size: 2; bottom-right corner horizontal offset in 1/1024 of column width - $endOffsetX = Xls::getUInt2d($recordData, 12); - - // offset: 14; size: 2; bottom-right corner row index (0-based) - $r2 = Xls::getUInt2d($recordData, 14); - - // offset: 16; size: 2; bottom-right corner vertical offset in 1/256 of row height - $endOffsetY = Xls::getUInt2d($recordData, 16); - - // set the start coordinates - $this->object->setStartCoordinates(Coordinate::stringFromColumnIndex($c1 + 1) . ($r1 + 1)); - - // set the start offsetX - $this->object->setStartOffsetX($startOffsetX); - - // set the start offsetY - $this->object->setStartOffsetY($startOffsetY); - - // set the end coordinates - $this->object->setEndCoordinates(Coordinate::stringFromColumnIndex($c2 + 1) . ($r2 + 1)); - - // set the end offsetX - $this->object->setEndOffsetX($endOffsetX); - - // set the end offsetY - $this->object->setEndOffsetY($endOffsetY); - } - - /** - * Read ClientData record. - */ - private function readClientData(): void - { - $length = Xls::getInt4d($this->data, $this->pos + 4); - $recordData = substr($this->data, $this->pos + 8, $length); - - // move stream pointer to next record - $this->pos += 8 + $length; - } - - /** - * Read OfficeArtRGFOPTE table of property-value pairs. - * - * @param string $data Binary data - * @param int $n Number of properties - */ - private function readOfficeArtRGFOPTE($data, $n): void - { - $splicedComplexData = substr($data, 6 * $n); - - // loop through property-value pairs - for ($i = 0; $i < $n; ++$i) { - // read 6 bytes at a time - $fopte = substr($data, 6 * $i, 6); - - // offset: 0; size: 2; opid - $opid = Xls::getUInt2d($fopte, 0); - - // bit: 0-13; mask: 0x3FFF; opid.opid - $opidOpid = (0x3FFF & $opid) >> 0; - - // bit: 14; mask 0x4000; 1 = value in op field is BLIP identifier - $opidFBid = (0x4000 & $opid) >> 14; - - // bit: 15; mask 0x8000; 1 = this is a complex property, op field specifies size of complex data - $opidFComplex = (0x8000 & $opid) >> 15; - - // offset: 2; size: 4; the value for this property - $op = Xls::getInt4d($fopte, 2); - - if ($opidFComplex) { - $complexData = substr($splicedComplexData, 0, $op); - $splicedComplexData = substr($splicedComplexData, $op); - - // we store string value with complex data - $value = $complexData; - } else { - // we store integer value - $value = $op; - } - - $this->object->setOPT($opidOpid, $value); - } - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php deleted file mode 100644 index c0417ba6932..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/MD5.php +++ /dev/null @@ -1,184 +0,0 @@ -reset(); - } - - /** - * Reset the MD5 stream context. - */ - public function reset(): void - { - $this->a = 0x67452301; - $this->b = 0xEFCDAB89; - $this->c = 0x98BADCFE; - $this->d = 0x10325476; - } - - /** - * Get MD5 stream context. - * - * @return string - */ - public function getContext() - { - $s = ''; - foreach (['a', 'b', 'c', 'd'] as $i) { - $v = $this->{$i}; - $s .= chr($v & 0xff); - $s .= chr(($v >> 8) & 0xff); - $s .= chr(($v >> 16) & 0xff); - $s .= chr(($v >> 24) & 0xff); - } - - return $s; - } - - /** - * Add data to context. - * - * @param string $data Data to add - */ - public function add($data): void - { - $words = array_values(unpack('V16', $data)); - - $A = $this->a; - $B = $this->b; - $C = $this->c; - $D = $this->d; - - $F = ['self', 'f']; - $G = ['self', 'g']; - $H = ['self', 'h']; - $I = ['self', 'i']; - - // ROUND 1 - self::step($F, $A, $B, $C, $D, $words[0], 7, 0xd76aa478); - self::step($F, $D, $A, $B, $C, $words[1], 12, 0xe8c7b756); - self::step($F, $C, $D, $A, $B, $words[2], 17, 0x242070db); - self::step($F, $B, $C, $D, $A, $words[3], 22, 0xc1bdceee); - self::step($F, $A, $B, $C, $D, $words[4], 7, 0xf57c0faf); - self::step($F, $D, $A, $B, $C, $words[5], 12, 0x4787c62a); - self::step($F, $C, $D, $A, $B, $words[6], 17, 0xa8304613); - self::step($F, $B, $C, $D, $A, $words[7], 22, 0xfd469501); - self::step($F, $A, $B, $C, $D, $words[8], 7, 0x698098d8); - self::step($F, $D, $A, $B, $C, $words[9], 12, 0x8b44f7af); - self::step($F, $C, $D, $A, $B, $words[10], 17, 0xffff5bb1); - self::step($F, $B, $C, $D, $A, $words[11], 22, 0x895cd7be); - self::step($F, $A, $B, $C, $D, $words[12], 7, 0x6b901122); - self::step($F, $D, $A, $B, $C, $words[13], 12, 0xfd987193); - self::step($F, $C, $D, $A, $B, $words[14], 17, 0xa679438e); - self::step($F, $B, $C, $D, $A, $words[15], 22, 0x49b40821); - - // ROUND 2 - self::step($G, $A, $B, $C, $D, $words[1], 5, 0xf61e2562); - self::step($G, $D, $A, $B, $C, $words[6], 9, 0xc040b340); - self::step($G, $C, $D, $A, $B, $words[11], 14, 0x265e5a51); - self::step($G, $B, $C, $D, $A, $words[0], 20, 0xe9b6c7aa); - self::step($G, $A, $B, $C, $D, $words[5], 5, 0xd62f105d); - self::step($G, $D, $A, $B, $C, $words[10], 9, 0x02441453); - self::step($G, $C, $D, $A, $B, $words[15], 14, 0xd8a1e681); - self::step($G, $B, $C, $D, $A, $words[4], 20, 0xe7d3fbc8); - self::step($G, $A, $B, $C, $D, $words[9], 5, 0x21e1cde6); - self::step($G, $D, $A, $B, $C, $words[14], 9, 0xc33707d6); - self::step($G, $C, $D, $A, $B, $words[3], 14, 0xf4d50d87); - self::step($G, $B, $C, $D, $A, $words[8], 20, 0x455a14ed); - self::step($G, $A, $B, $C, $D, $words[13], 5, 0xa9e3e905); - self::step($G, $D, $A, $B, $C, $words[2], 9, 0xfcefa3f8); - self::step($G, $C, $D, $A, $B, $words[7], 14, 0x676f02d9); - self::step($G, $B, $C, $D, $A, $words[12], 20, 0x8d2a4c8a); - - // ROUND 3 - self::step($H, $A, $B, $C, $D, $words[5], 4, 0xfffa3942); - self::step($H, $D, $A, $B, $C, $words[8], 11, 0x8771f681); - self::step($H, $C, $D, $A, $B, $words[11], 16, 0x6d9d6122); - self::step($H, $B, $C, $D, $A, $words[14], 23, 0xfde5380c); - self::step($H, $A, $B, $C, $D, $words[1], 4, 0xa4beea44); - self::step($H, $D, $A, $B, $C, $words[4], 11, 0x4bdecfa9); - self::step($H, $C, $D, $A, $B, $words[7], 16, 0xf6bb4b60); - self::step($H, $B, $C, $D, $A, $words[10], 23, 0xbebfbc70); - self::step($H, $A, $B, $C, $D, $words[13], 4, 0x289b7ec6); - self::step($H, $D, $A, $B, $C, $words[0], 11, 0xeaa127fa); - self::step($H, $C, $D, $A, $B, $words[3], 16, 0xd4ef3085); - self::step($H, $B, $C, $D, $A, $words[6], 23, 0x04881d05); - self::step($H, $A, $B, $C, $D, $words[9], 4, 0xd9d4d039); - self::step($H, $D, $A, $B, $C, $words[12], 11, 0xe6db99e5); - self::step($H, $C, $D, $A, $B, $words[15], 16, 0x1fa27cf8); - self::step($H, $B, $C, $D, $A, $words[2], 23, 0xc4ac5665); - - // ROUND 4 - self::step($I, $A, $B, $C, $D, $words[0], 6, 0xf4292244); - self::step($I, $D, $A, $B, $C, $words[7], 10, 0x432aff97); - self::step($I, $C, $D, $A, $B, $words[14], 15, 0xab9423a7); - self::step($I, $B, $C, $D, $A, $words[5], 21, 0xfc93a039); - self::step($I, $A, $B, $C, $D, $words[12], 6, 0x655b59c3); - self::step($I, $D, $A, $B, $C, $words[3], 10, 0x8f0ccc92); - self::step($I, $C, $D, $A, $B, $words[10], 15, 0xffeff47d); - self::step($I, $B, $C, $D, $A, $words[1], 21, 0x85845dd1); - self::step($I, $A, $B, $C, $D, $words[8], 6, 0x6fa87e4f); - self::step($I, $D, $A, $B, $C, $words[15], 10, 0xfe2ce6e0); - self::step($I, $C, $D, $A, $B, $words[6], 15, 0xa3014314); - self::step($I, $B, $C, $D, $A, $words[13], 21, 0x4e0811a1); - self::step($I, $A, $B, $C, $D, $words[4], 6, 0xf7537e82); - self::step($I, $D, $A, $B, $C, $words[11], 10, 0xbd3af235); - self::step($I, $C, $D, $A, $B, $words[2], 15, 0x2ad7d2bb); - self::step($I, $B, $C, $D, $A, $words[9], 21, 0xeb86d391); - - $this->a = ($this->a + $A) & 0xffffffff; - $this->b = ($this->b + $B) & 0xffffffff; - $this->c = ($this->c + $C) & 0xffffffff; - $this->d = ($this->d + $D) & 0xffffffff; - } - - private static function f($X, $Y, $Z) - { - return ($X & $Y) | ((~$X) & $Z); // X AND Y OR NOT X AND Z - } - - private static function g($X, $Y, $Z) - { - return ($X & $Z) | ($Y & (~$Z)); // X AND Z OR Y AND NOT Z - } - - private static function h($X, $Y, $Z) - { - return $X ^ $Y ^ $Z; // X XOR Y XOR Z - } - - private static function i($X, $Y, $Z) - { - return $Y ^ ($X | (~$Z)); // Y XOR (X OR NOT Z) - } - - private static function step($func, &$A, $B, $C, $D, $M, $s, $t): void - { - $A = ($A + call_user_func($func, $B, $C, $D) + $M + $t) & 0xffffffff; - $A = self::rotate($A, $s); - $A = ($B + $A) & 0xffffffff; - } - - private static function rotate($decimal, $bits) - { - $binary = str_pad(decbin($decimal), 32, '0', STR_PAD_LEFT); - - return bindec(substr($binary, $bits) . substr($binary, 0, $bits)); - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/RC4.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/RC4.php deleted file mode 100644 index 691aca7c398..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/RC4.php +++ /dev/null @@ -1,61 +0,0 @@ -i = 0; $this->i < 256; ++$this->i) { - $this->s[$this->i] = $this->i; - } - - $this->j = 0; - for ($this->i = 0; $this->i < 256; ++$this->i) { - $this->j = ($this->j + $this->s[$this->i] + ord($key[$this->i % $len])) % 256; - $t = $this->s[$this->i]; - $this->s[$this->i] = $this->s[$this->j]; - $this->s[$this->j] = $t; - } - $this->i = $this->j = 0; - } - - /** - * Symmetric decryption/encryption function. - * - * @param string $data Data to encrypt/decrypt - * - * @return string - */ - public function RC4($data) - { - $len = strlen($data); - for ($c = 0; $c < $len; ++$c) { - $this->i = ($this->i + 1) % 256; - $this->j = ($this->j + $this->s[$this->i]) % 256; - $t = $this->s[$this->i]; - $this->s[$this->i] = $this->s[$this->j]; - $this->s[$this->j] = $t; - - $t = ($this->s[$this->i] + $this->s[$this->j]) % 256; - - $data[$c] = chr(ord($data[$c]) ^ $this->s[$t]); - } - - return $data; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/Border.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/Border.php deleted file mode 100644 index 91cbe36f136..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/Border.php +++ /dev/null @@ -1,42 +0,0 @@ - StyleBorder::BORDER_NONE, - 0x01 => StyleBorder::BORDER_THIN, - 0x02 => StyleBorder::BORDER_MEDIUM, - 0x03 => StyleBorder::BORDER_DASHED, - 0x04 => StyleBorder::BORDER_DOTTED, - 0x05 => StyleBorder::BORDER_THICK, - 0x06 => StyleBorder::BORDER_DOUBLE, - 0x07 => StyleBorder::BORDER_HAIR, - 0x08 => StyleBorder::BORDER_MEDIUMDASHED, - 0x09 => StyleBorder::BORDER_DASHDOT, - 0x0A => StyleBorder::BORDER_MEDIUMDASHDOT, - 0x0B => StyleBorder::BORDER_DASHDOTDOT, - 0x0C => StyleBorder::BORDER_MEDIUMDASHDOTDOT, - 0x0D => StyleBorder::BORDER_SLANTDASHDOT, - ]; - - /** - * Map border style - * OpenOffice documentation: 2.5.11. - * - * @param int $index - * - * @return string - */ - public static function lookup($index) - { - if (isset(self::$map[$index])) { - return self::$map[$index]; - } - - return StyleBorder::BORDER_NONE; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php deleted file mode 100644 index 7b85c088330..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Reader/Xls/Style/FillPattern.php +++ /dev/null @@ -1,47 +0,0 @@ - Fill::FILL_NONE, - 0x01 => Fill::FILL_SOLID, - 0x02 => Fill::FILL_PATTERN_MEDIUMGRAY, - 0x03 => Fill::FILL_PATTERN_DARKGRAY, - 0x04 => Fill::FILL_PATTERN_LIGHTGRAY, - 0x05 => Fill::FILL_PATTERN_DARKHORIZONTAL, - 0x06 => Fill::FILL_PATTERN_DARKVERTICAL, - 0x07 => Fill::FILL_PATTERN_DARKDOWN, - 0x08 => Fill::FILL_PATTERN_DARKUP, - 0x09 => Fill::FILL_PATTERN_DARKGRID, - 0x0A => Fill::FILL_PATTERN_DARKTRELLIS, - 0x0B => Fill::FILL_PATTERN_LIGHTHORIZONTAL, - 0x0C => Fill::FILL_PATTERN_LIGHTVERTICAL, - 0x0D => Fill::FILL_PATTERN_LIGHTDOWN, - 0x0E => Fill::FILL_PATTERN_LIGHTUP, - 0x0F => Fill::FILL_PATTERN_LIGHTGRID, - 0x10 => Fill::FILL_PATTERN_LIGHTTRELLIS, - 0x11 => Fill::FILL_PATTERN_GRAY125, - 0x12 => Fill::FILL_PATTERN_GRAY0625, - ]; - - /** - * Get fill pattern from index - * OpenOffice documentation: 2.5.12. - * - * @param int $index - * - * @return string - */ - public static function lookup($index) - { - if (isset(self::$map[$index])) { - return self::$map[$index]; - } - - return Fill::FILL_NONE; - } -} diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php index 7525df8a6b9..bec7132fc84 100644 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php +++ b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/File.php @@ -109,6 +109,12 @@ class File */ public static function sysGetTempDir() { + // Moodle hack! + if (function_exists('make_temp_directory')) { + $temp = make_temp_directory('phpspreadsheet'); + return realpath(dirname($temp)); + } + if (self::$useUploadTempDirectory) { // use upload-directory when defined to allow running on environments having very restricted // open_basedir configs diff --git a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php deleted file mode 100644 index d380995cd08..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE.php +++ /dev/null @@ -1,566 +0,0 @@ - | -// | 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/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php deleted file mode 100644 index cee5cd99d30..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/ChainedBlockStream.php +++ /dev/null @@ -1,196 +0,0 @@ -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/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php deleted file mode 100644 index cf764d0bfa3..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS.php +++ /dev/null @@ -1,237 +0,0 @@ - | -// | 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/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php deleted file mode 100644 index dd1cda2de03..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/File.php +++ /dev/null @@ -1,64 +0,0 @@ - | -// | 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/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php deleted file mode 100644 index 5466d2bc3c8..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLE/PPS/Root.php +++ /dev/null @@ -1,426 +0,0 @@ - | -// | 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/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php deleted file mode 100644 index 7112b090537..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/OLERead.php +++ /dev/null @@ -1,350 +0,0 @@ -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/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php deleted file mode 100644 index c9eaf378b63..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Shared/Xls.php +++ /dev/null @@ -1,279 +0,0 @@ -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/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php deleted file mode 100644 index c7c2e7d6058..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls.php +++ /dev/null @@ -1,901 +0,0 @@ -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/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php deleted file mode 100644 index 84e27d0d814..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/BIFFwriter.php +++ /dev/null @@ -1,224 +0,0 @@ - -// * -// * 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/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php deleted file mode 100644 index 1ee2e904714..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Escher.php +++ /dev/null @@ -1,510 +0,0 @@ -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/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php deleted file mode 100644 index 9cb31ead085..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Font.php +++ /dev/null @@ -1,147 +0,0 @@ -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/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php deleted file mode 100644 index f89957a442b..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Parser.php +++ /dev/null @@ -1,1483 +0,0 @@ -=,;#()"{} - 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/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php deleted file mode 100644 index f752bce62de..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Workbook.php +++ /dev/null @@ -1,1191 +0,0 @@ - -// * -// * 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/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php deleted file mode 100644 index a793128ae0b..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Worksheet.php +++ /dev/null @@ -1,4491 +0,0 @@ - -// * -// * 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) || $bitmap instanceof GdImage ? $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 GdImage|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/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php b/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php deleted file mode 100644 index 3e8169b3068..00000000000 --- a/lib/phpspreadsheet/vendor/phpoffice/phpspreadsheet/src/PhpSpreadsheet/Writer/Xls/Xf.php +++ /dev/null @@ -1,548 +0,0 @@ - -// * -// * 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/lib/thirdpartylibs.xml b/lib/thirdpartylibs.xml index 1f31b443dd9..8876c515642 100644 --- a/lib/thirdpartylibs.xml +++ b/lib/thirdpartylibs.xml @@ -32,7 +32,7 @@ phpspreadsheet PhpSpreadsheet MIT - 1.10.1 + 1.16.0