MDL-70314 lib: Apply Moodle specific phpspreadsheet changes

This commit is contained in:
Mathew May 2021-01-08 14:52:48 +08:00
parent 1ee8dd7f6f
commit d41210cf0f
70 changed files with 17 additions and 24606 deletions

View File

@ -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/*

View File

@ -1 +0,0 @@
open_collective: zipstream

View File

@ -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.

View File

@ -1,6 +0,0 @@
clover.xml
composer.lock
coverage.clover
.idea
phpunit.xml
vendor

View File

@ -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

View File

@ -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

View File

@ -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:

View File

@ -1,24 +0,0 @@
MIT License
Copyright (C) 2007-2009 Paul Duncan <pabs@pablotron.org>
Copyright (C) 2014 Jonatan Männchen <jonatan@maennchen.ch>
Copyright (C) 2014 Jesse G. Donat <donatj@gmail.com>
Copyright (C) 2018 Nicolas CARPi <nicolas.carpi@curie.fr>
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.

View File

@ -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 <pabs@pablotron.org> - https://pablotron.org/
* Jonatan Männchen <jonatan@maennchen.ch> - https://maennchen.dev
* Jesse G. Donat <donatj@gmail.com> - https://donatstudios.com
* Nicolas CARPi <nico-git@deltablot.email> - https://www.deltablot.com
* Nik Barham <nik@brokencube.co.uk> - https://www.brokencube.co.uk
## Contributors
### Code Contributors
This project exists thanks to all the people who contribute. [[Contribute](CONTRIBUTING.md)].
<a href="https://github.com/maennchen/ZipStream-PHP/graphs/contributors"><img src="https://opencollective.com/zipstream/contributors.svg?width=890&button=false" /></a>
### Financial Contributors
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/zipstream/contribute)]
#### Individuals
<a href="https://opencollective.com/zipstream"><img src="https://opencollective.com/zipstream/individuals.svg?width=890"></a>
#### 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)]
<a href="https://opencollective.com/zipstream/organization/0/website"><img src="https://opencollective.com/zipstream/organization/0/avatar.svg"></a>
<a href="https://opencollective.com/zipstream/organization/1/website"><img src="https://opencollective.com/zipstream/organization/1/avatar.svg"></a>
<a href="https://opencollective.com/zipstream/organization/2/website"><img src="https://opencollective.com/zipstream/organization/2/avatar.svg"></a>
<a href="https://opencollective.com/zipstream/organization/3/website"><img src="https://opencollective.com/zipstream/organization/3/avatar.svg"></a>
<a href="https://opencollective.com/zipstream/organization/4/website"><img src="https://opencollective.com/zipstream/organization/4/avatar.svg"></a>
<a href="https://opencollective.com/zipstream/organization/5/website"><img src="https://opencollective.com/zipstream/organization/5/avatar.svg"></a>
<a href="https://opencollective.com/zipstream/organization/6/website"><img src="https://opencollective.com/zipstream/organization/6/avatar.svg"></a>
<a href="https://opencollective.com/zipstream/organization/7/website"><img src="https://opencollective.com/zipstream/organization/7/avatar.svg"></a>
<a href="https://opencollective.com/zipstream/organization/8/website"><img src="https://opencollective.com/zipstream/organization/8/avatar.svg"></a>
<a href="https://opencollective.com/zipstream/organization/9/website"><img src="https://opencollective.com/zipstream/organization/9/avatar.svg"></a>

View File

@ -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/"
}
}
}

View File

@ -1,17 +0,0 @@
<phpunit bootstrap="test/bootstrap.php">
<testsuites>
<testsuite name="Application">
<directory>test</directory>
</testsuite>
</testsuites>
<logging>
<log type="coverage-clover" target="clover.xml"/>
</logging>
<filter>
<whitelist processUncoveredFilesFromWhitelist="true">
<directory suffix=".php">src</directory>
</whitelist>
</filter>
</phpunit>

View File

@ -1,55 +0,0 @@
<?xml version="1.0"?>
<psalm
totallyTyped="false"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<LessSpecificReturnType errorLevel="info" />
<!-- level 3 issues - slightly lazy code writing, but provably low false-negatives -->
<DeprecatedMethod errorLevel="info" />
<DeprecatedProperty errorLevel="info" />
<DeprecatedClass errorLevel="info" />
<DeprecatedConstant errorLevel="info" />
<DeprecatedFunction errorLevel="info" />
<DeprecatedInterface errorLevel="info" />
<DeprecatedTrait errorLevel="info" />
<InternalMethod errorLevel="info" />
<InternalProperty errorLevel="info" />
<InternalClass errorLevel="info" />
<MissingClosureReturnType errorLevel="info" />
<MissingReturnType errorLevel="info" />
<MissingPropertyType errorLevel="info" />
<InvalidDocblock errorLevel="info" />
<MisplacedRequiredParam errorLevel="info" />
<PropertyNotSetInConstructor errorLevel="info" />
<MissingConstructor errorLevel="info" />
<MissingClosureParamType errorLevel="info" />
<MissingParamType errorLevel="info" />
<RedundantCondition errorLevel="info" />
<DocblockTypeContradiction errorLevel="info" />
<RedundantConditionGivenDocblockType errorLevel="info" />
<UnresolvableInclude errorLevel="info" />
<RawObjectIteration errorLevel="info" />
<InvalidStringClass errorLevel="info" />
</issueHandlers>
</psalm>

View File

@ -1,172 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream;
use OverflowException;
class Bigint
{
/**
* @var int[]
*/
private $bytes = [0, 0, 0, 0, 0, 0, 0, 0];
/**
* Initialize the bytes array
*
* @param int $value
*/
public function __construct(int $value = 0)
{
$this->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;
}
}

View File

@ -1,70 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream;
class DeflateStream extends Stream
{
protected $filter;
/**
* @var Option\File
*/
protected $options;
/**
* Rewind stream
*
* @return void
*/
public function rewind(): void
{
// deflate filter needs to be removed before rewind
if ($this->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
);
}
}

View File

@ -1,11 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream;
/**
* This class is only for inheriting
*/
abstract class Exception extends \Exception
{
}

View File

@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream\Exception;
use ZipStream\Exception;
/**
* This Exception gets invoked if file or comment encoding is incorrect
*/
class EncodingException extends Exception
{
}

View File

@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream\Exception;
use ZipStream\Exception;
/**
* This Exception gets invoked if a file wasn't found
*/
class FileNotFoundException extends Exception
{
/**
* Constructor of the Exception
*
* @param String $path - The path which wasn't found
*/
public function __construct(string $path)
{
parent::__construct("The file with the path $path wasn't found.");
}
}

View File

@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream\Exception;
use ZipStream\Exception;
/**
* This Exception gets invoked if a file wasn't found
*/
class FileNotReadableException extends Exception
{
/**
* Constructor of the Exception
*
* @param String $path - The path which wasn't found
*/
public function __construct(string $path)
{
parent::__construct("The file with the path $path isn't readable.");
}
}

View File

@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream\Exception;
use ZipStream\Exception;
/**
* This Exception gets invoked if options are incompatible
*/
class IncompatibleOptionsException extends Exception
{
}

View File

@ -1,17 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream\Exception;
use ZipStream\Exception;
/**
* This Exception gets invoked if a counter value exceeds storage size
*/
class OverflowException extends Exception
{
public function __construct()
{
parent::__construct('File size exceeds limit of 32 bit integer. Please enable "zip64" option.');
}
}

View File

@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream\Exception;
use ZipStream\Exception;
/**
* This Exception gets invoked if `fread` fails on a stream.
*/
class StreamNotReadableException extends Exception
{
/**
* Constructor of the Exception
*
* @param string $fileName - The name of the file which the stream belongs to.
*/
public function __construct(string $fileName)
{
parent::__construct("The stream for $fileName could not be read.");
}
}

View File

@ -1,477 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream;
use Psr\Http\Message\StreamInterface;
use ZipStream\Exception\EncodingException;
use ZipStream\Exception\FileNotFoundException;
use ZipStream\Exception\FileNotReadableException;
use ZipStream\Exception\OverflowException;
use ZipStream\Option\File as FileOptions;
use ZipStream\Option\Method;
use ZipStream\Option\Version;
class File
{
const HASH_ALGORITHM = 'crc32b';
const BIT_ZERO_HEADER = 0x0008;
const BIT_EFS_UTF8 = 0x0800;
const COMPUTE = 1;
const SEND = 2;
private const CHUNKED_READ_BLOCK_SIZE = 1048576;
/**
* @var string
*/
public $name;
/**
* @var FileOptions
*/
public $opt;
/**
* @var Bigint
*/
public $len;
/**
* @var Bigint
*/
public $zlen;
/** @var int */
public $crc;
/**
* @var Bigint
*/
public $hlen;
/**
* @var Bigint
*/
public $ofs;
/**
* @var int
*/
public $bits;
/**
* @var Version
*/
public $version;
/**
* @var ZipStream
*/
public $zip;
/**
* @var resource
*/
private $deflate;
/**
* @var resource
*/
private $hash;
/**
* @var Method
*/
private $method;
/**
* @var Bigint
*/
private $totalLength;
public function __construct(ZipStream $zip, string $name, ?FileOptions $opt = null)
{
$this->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;
}
}

View File

@ -1,261 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream\Option;
final class Archive
{
const DEFAULT_DEFLATE_LEVEL = 6;
/**
* @var string
*/
private $comment = '';
/**
* Size, in bytes, of the largest file to try
* and load into memory (used by
* addFileFromPath()). Large files may also
* be compressed differently; see the
* 'largeFileMethod' option. Default is ~20 Mb.
*
* @var int
*/
private $largeFileSize = 20 * 1024 * 1024;
/**
* How to handle large files. Legal values are
* Method::STORE() (the default), or
* Method::DEFLATE(). STORE sends the file
* raw and is significantly
* faster, while DEFLATE compresses the file
* and is much, much slower. Note that DEFLATE
* must compress the file twice and is extremely slow.
*
* @var Method
*/
private $largeFileMethod;
/**
* Boolean indicating whether or not to send
* the HTTP headers for this file.
*
* @var bool
*/
private $sendHttpHeaders = false;
/**
* The method called to send headers
*
* @var Callable
*/
private $httpHeaderCallback = 'header';
/**
* Enable Zip64 extension, supporting very large
* archives (any size > 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;
}
}

View File

@ -1,116 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream\Option;
use DateTime;
final class File
{
/**
* @var string
*/
private $comment = '';
/**
* @var Method
*/
private $method;
/**
* @var int
*/
private $deflateLevel;
/**
* @var DateTime
*/
private $time;
/**
* @var int
*/
private $size = 0;
public function defaultTo(Archive $archiveOptions): void
{
$this->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;
}
}

View File

@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream\Option;
use MyCLabs\Enum\Enum;
/**
* Methods enum
*
* @method static STORE(): Method
* @method static DEFLATE(): Method
* @psalm-immutable
*/
class Method extends Enum
{
const STORE = 0x00;
const DEFLATE = 0x08;
}

View File

@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream\Option;
use MyCLabs\Enum\Enum;
/**
* Class Version
* @package ZipStream\Option
*
* @method static STORE(): Version
* @method static DEFLATE(): Version
* @method static ZIP64(): Version
* @psalm-immutable
*/
class Version extends Enum
{
const STORE = 0x000A; // 1.00
const DEFLATE = 0x0014; // 2.00
const ZIP64 = 0x002D; // 4.50
}

View File

@ -1,253 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream;
use Psr\Http\Message\StreamInterface;
use RuntimeException;
/**
* Describes a data stream.
*
* Typically, an instance will wrap a PHP stream; this interface provides
* a wrapper around the most common operations, including serialization of
* the entire stream to a string.
*/
class Stream implements StreamInterface
{
protected $stream;
public function __construct($stream)
{
$this->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;
}
}

View File

@ -1,599 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStream;
use Psr\Http\Message\StreamInterface;
use ZipStream\Exception\OverflowException;
use ZipStream\Option\Archive as ArchiveOptions;
use ZipStream\Option\File as FileOptions;
use ZipStream\Option\Version;
/**
* ZipStream
*
* Streamed, dynamically generated zip archives.
*
* Usage:
*
* Streaming zip archives is a simple, three-step process:
*
* 1. Create the zip stream:
*
* $zip = new ZipStream('example.zip');
*
* 2. Add one or more files to the archive:
*
* * add first file
* $data = file_get_contents('some_file.gif');
* $zip->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();
}
}

View File

@ -1,65 +0,0 @@
<?php
declare(strict_types=1);
namespace BigintTest;
use OverflowException;
use PHPUnit\Framework\TestCase;
use ZipStream\Bigint;
class BigintTest extends TestCase
{
public function testConstruct(): void
{
$bigint = new Bigint(0x12345678);
$this->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));
}
}

View File

@ -1,586 +0,0 @@
<?php
declare(strict_types=1);
namespace ZipStreamTest;
use org\bovigo\vfs\vfsStream;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\TestCase;
use ZipStream\File;
use ZipStream\Option\Archive as ArchiveOptions;
use ZipStream\Option\File as FileOptions;
use ZipStream\Option\Method;
use ZipStream\ZipStream;
/**
* Test Class for the Main ZipStream CLass
*/
class ZipStreamTest extends TestCase
{
const OSX_ARCHIVE_UTILITY =
'/System/Library/CoreServices/Applications/Archive Utility.app/Contents/MacOS/Archive Utility';
public function testFileNotFoundException(): void
{
$this->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();
}
}

View File

@ -1,6 +0,0 @@
<?php
declare(strict_types=1);
date_default_timezone_set('UTC');
require __DIR__ . '/../vendor/autoload.php';

View File

@ -1,39 +0,0 @@
<?php
declare(strict_types=1);
namespace BugHonorFileTimeTest;
use DateTime;
use PHPUnit\Framework\TestCase;
use ZipStream\Option\{
Archive,
File
};
use ZipStream\ZipStream;
use function fopen;
/**
* Asserts that specified last-modified timestamps are not overwritten when a
* file is added
*/
class BugHonorFileTimeTest extends TestCase
{
public function testHonorsFileTime(): void
{
$archiveOpt = new Archive();
$fileOpt = new File();
$expectedTime = new DateTime('2019-04-21T19:25:00-0800');
$archiveOpt->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());
}
}

View File

@ -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
}
}

View File

@ -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

View File

@ -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.

View File

@ -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)

View File

@ -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.

View File

@ -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"
}
}

View File

@ -1,20 +0,0 @@
<?xml version="1.0"?>
<psalm
totallyTyped="true"
resolveFromConfigFile="true"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://getpsalm.org/schema/config"
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
>
<projectFiles>
<directory name="src" />
<ignoreFiles>
<directory name="vendor" />
<directory name="src/PHPUnit" />
</ignoreFiles>
</projectFiles>
<issueHandlers>
<MixedAssignment errorLevel="info" />
</issueHandlers>
</psalm>

View File

@ -1,250 +0,0 @@
<?php
/**
* @link http://github.com/myclabs/php-enum
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
*/
namespace MyCLabs\Enum;
/**
* Base Enum class
*
* Create an enum by implementing this class and adding class constants.
*
* @author Matthieu Napoli <matthieu@mnapoli.fr>
* @author Daniel Costa <danielcosta@gmail.com>
* @author Mirosław Filip <mirfilip@gmail.com>
*
* @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<class-string, array<string, mixed>>
*/
protected static $cache = [];
/**
* Cache of instances of the Enum class
*
* @var array
* @psalm-var array<class-string, array<string, static>>
*/
protected static $instances = [];
/**
* Creates a new value of some type
*
* @psalm-pure
* @param mixed $value
*
* @psalm-param static<T>|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<string>
* @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<string, static>
* @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<string, mixed>
* @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();
}
}

View File

@ -1,54 +0,0 @@
<?php
namespace MyCLabs\Enum\PHPUnit;
use MyCLabs\Enum\Enum;
use SebastianBergmann\Comparator\ComparisonFailure;
/**
* Use this Comparator to get nice output when using PHPUnit assertEquals() with Enums.
*
* Add this to your PHPUnit bootstrap PHP file:
*
* \SebastianBergmann\Comparator\Factory::getInstance()->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()}()";
}
}

View File

@ -1,22 +0,0 @@
<?xml version="1.0"?>
<ruleset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" name="PHP_CodeSniffer"
xsi:noNamespaceSchemaLocation="vendor/squizlabs/php_codesniffer/phpcs.xsd">
<file>samples</file>
<file>src</file>
<file>tests</file>
<exclude-pattern>samples/Header.php</exclude-pattern>
<exclude-pattern>*/tests/Core/*/*Test\.(inc|css|js)$</exclude-pattern>
<arg name="report-width" value="200"/>
<arg name="parallel" value="80"/>
<arg name="cache" value="/tmp/.phpspreadsheet.phpcs-cache"/>
<arg name="colors"/>
<arg value="np"/>
<!-- Include the whole PSR12 standard -->
<rule ref="PSR12">
<exclude name="PSR2.Methods.MethodDeclaration.Underscore"/>
</rule>
</ruleset>

View File

@ -1,36 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Reader\Xls;
class Color
{
/**
* Read color.
*
* @param int $color Indexed color
* @param array $palette Color palette
* @param int $version
*
* @return array RGB color value, example: ['rgb' => '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);
}
}

View File

@ -1,81 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color;
class BIFF5
{
protected static $map = [
0x08 => '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'];
}
}

View File

@ -1,81 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color;
class BIFF8
{
protected static $map = [
0x08 => '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'];
}
}

View File

@ -1,35 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Color;
class BuiltIn
{
protected static $map = [
0x00 => '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'];
}
}

View File

@ -1,32 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
class ErrorCode
{
protected static $map = [
0x00 => '#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;
}
}

View File

@ -1,677 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Reader\Xls;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip;
class Escher
{
const DGGCONTAINER = 0xF000;
const BSTORECONTAINER = 0xF001;
const DGCONTAINER = 0xF002;
const SPGRCONTAINER = 0xF003;
const SPCONTAINER = 0xF004;
const DGG = 0xF006;
const BSE = 0xF007;
const DG = 0xF008;
const SPGR = 0xF009;
const SP = 0xF00A;
const OPT = 0xF00B;
const CLIENTTEXTBOX = 0xF00D;
const CLIENTANCHOR = 0xF010;
const CLIENTDATA = 0xF011;
const BLIPJPEG = 0xF01D;
const BLIPPNG = 0xF01E;
const SPLITMENUCOLORS = 0xF11E;
const TERTIARYOPT = 0xF122;
/**
* Escher stream data (binary).
*
* @var string
*/
private $data;
/**
* Size in bytes of the Escher stream data.
*
* @var int
*/
private $dataSize;
/**
* Current position of stream pointer in Escher stream data.
*
* @var int
*/
private $pos;
/**
* The object to be returned by the reader. Modified during load.
*
* @var BSE|BstoreContainer|DgContainer|DggContainer|\PhpOffice\PhpSpreadsheet\Shared\Escher|SpContainer|SpgrContainer
*/
private $object;
/**
* Create a new Escher instance.
*
* @param mixed $object
*/
public function __construct($object)
{
$this->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);
}
}
}

View File

@ -1,184 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
class MD5
{
// Context
private $a;
private $b;
private $c;
private $d;
/**
* MD5 stream constructor.
*/
public function __construct()
{
$this->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));
}
}

View File

@ -1,61 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls;
class RC4
{
// Context
protected $s = [];
protected $i = 0;
protected $j = 0;
/**
* RC4 stream decryption/encryption constrcutor.
*
* @param string $key Encryption key/passphrase
*/
public function __construct($key)
{
$len = strlen($key);
for ($this->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;
}
}

View File

@ -1,42 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Style;
use PhpOffice\PhpSpreadsheet\Style\Border as StyleBorder;
class Border
{
protected static $map = [
0x00 => 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;
}
}

View File

@ -1,47 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Reader\Xls\Style;
use PhpOffice\PhpSpreadsheet\Style\Fill;
class FillPattern
{
protected static $map = [
0x00 => Fill::FILL_NONE,
0x01 => Fill::FILL_SOLID,
0x02 => Fill::FILL_PATTERN_MEDIUMGRAY,
0x03 => Fill::FILL_PATTERN_DARKGRAY,
0x04 => Fill::FILL_PATTERN_LIGHTGRAY,
0x05 => Fill::FILL_PATTERN_DARKHORIZONTAL,
0x06 => Fill::FILL_PATTERN_DARKVERTICAL,
0x07 => Fill::FILL_PATTERN_DARKDOWN,
0x08 => Fill::FILL_PATTERN_DARKUP,
0x09 => Fill::FILL_PATTERN_DARKGRID,
0x0A => Fill::FILL_PATTERN_DARKTRELLIS,
0x0B => Fill::FILL_PATTERN_LIGHTHORIZONTAL,
0x0C => Fill::FILL_PATTERN_LIGHTVERTICAL,
0x0D => Fill::FILL_PATTERN_LIGHTDOWN,
0x0E => Fill::FILL_PATTERN_LIGHTUP,
0x0F => Fill::FILL_PATTERN_LIGHTGRID,
0x10 => Fill::FILL_PATTERN_LIGHTTRELLIS,
0x11 => Fill::FILL_PATTERN_GRAY125,
0x12 => Fill::FILL_PATTERN_GRAY0625,
];
/**
* Get fill pattern from index
* OpenOffice documentation: 2.5.12.
*
* @param int $index
*
* @return string
*/
public static function lookup($index)
{
if (isset(self::$map[$index])) {
return self::$map[$index];
}
return Fill::FILL_NONE;
}
}

View File

@ -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

View File

@ -1,566 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Shared;
// vim: set expandtab tabstop=4 shiftwidth=4:
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Xavier Noguer <xnoguer@php.net> |
// | 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 <xnoguer@php.net>
* @author Christian Schmidt <schmidt@php.net>
*/
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);
}
}

View File

@ -1,196 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Shared\OLE;
use PhpOffice\PhpSpreadsheet\Shared\OLE;
class ChainedBlockStream
{
/**
* The OLE container of the file that is being read.
*
* @var OLE
*/
public $ole;
/**
* Parameters specified by fopen().
*
* @var array
*/
public $params;
/**
* The binary data of the file.
*
* @var string
*/
public $data;
/**
* The file pointer.
*
* @var int byte offset
*/
public $pos;
/**
* Implements support for fopen().
* For creating streams using this wrapper, use OLE_PPS_File::getStream().
*
* @param string $path resource name including scheme, e.g.
* ole-chainedblockstream://oleInstanceId=1
* @param string $mode only "r" is supported
* @param int $options mask of STREAM_REPORT_ERRORS and STREAM_USE_PATH
* @param string &$openedPath absolute path of the opened stream (out parameter)
*
* @return bool true on success
*/
public function stream_open($path, $mode, $options, &$openedPath) // @codingStandardsIgnoreLine
{
if ($mode != 'r') {
if ($options & STREAM_REPORT_ERRORS) {
trigger_error('Only reading is supported', E_USER_WARNING);
}
return false;
}
// 25 is length of "ole-chainedblockstream://"
parse_str(substr($path, 25), $this->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 )
}

View File

@ -1,237 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Shared\OLE;
// vim: set expandtab tabstop=4 shiftwidth=4:
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Xavier Noguer <xnoguer@php.net> |
// | Based on OLE::Storage_Lite by Kawai, Takanori |
// +----------------------------------------------------------------------+
//
use PhpOffice\PhpSpreadsheet\Shared\OLE;
/**
* Class for creating PPS's for OLE containers.
*
* @author Xavier Noguer <xnoguer@php.net>
*/
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;
}
}

View File

@ -1,64 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
// vim: set expandtab tabstop=4 shiftwidth=4:
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Xavier Noguer <xnoguer@php.net> |
// | 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 <xnoguer@php.net>
*/
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;
}
}

View File

@ -1,426 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Shared\OLE\PPS;
// vim: set expandtab tabstop=4 shiftwidth=4:
// +----------------------------------------------------------------------+
// | PHP Version 4 |
// +----------------------------------------------------------------------+
// | Copyright (c) 1997-2002 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 2.02 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available at through the world-wide-web at |
// | http://www.php.net/license/2_02.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
// | Author: Xavier Noguer <xnoguer@php.net> |
// | 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 <xnoguer@php.net>
*/
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));
}
}
}

View File

@ -1,350 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Reader\Exception as ReaderException;
class OLERead
{
private $data = '';
// Size of a sector = 512 bytes
const BIG_BLOCK_SIZE = 0x200;
// Size of a short sector = 64 bytes
const SMALL_BLOCK_SIZE = 0x40;
// Size of a directory entry always = 128 bytes
const PROPERTY_STORAGE_BLOCK_SIZE = 0x80;
// Minimum size of a standard stream = 4096 bytes, streams smaller than this are stored as short streams
const SMALL_BLOCK_THRESHOLD = 0x1000;
// header offsets
const NUM_BIG_BLOCK_DEPOT_BLOCKS_POS = 0x2c;
const ROOT_START_BLOCK_POS = 0x30;
const SMALL_BLOCK_DEPOT_BLOCK_POS = 0x3c;
const EXTENSION_BLOCK_POS = 0x44;
const NUM_EXTENSION_BLOCK_POS = 0x48;
const BIG_BLOCK_DEPOT_BLOCKS_POS = 0x4c;
// property storage offsets (directory offsets)
const SIZE_OF_NAME_POS = 0x40;
const TYPE_POS = 0x42;
const START_BLOCK_POS = 0x74;
const SIZE_POS = 0x78;
public $wrkbook;
public $summaryInformation;
public $documentSummaryInformation;
/**
* @var int
*/
private $numBigBlockDepotBlocks;
/**
* @var int
*/
private $rootStartBlock;
/**
* @var int
*/
private $sbdStartBlock;
/**
* @var int
*/
private $extensionBlock;
/**
* @var int
*/
private $numExtensionBlocks;
/**
* @var string
*/
private $bigBlockChain;
/**
* @var string
*/
private $smallBlockChain;
/**
* @var string
*/
private $entry;
/**
* @var int
*/
private $rootentry;
/**
* @var array
*/
private $props = [];
/**
* Read the file.
*
* @param $pFilename string Filename
*/
public function read($pFilename): void
{
File::assertFile($pFilename);
// Get the file identifier
// Don't bother reading the whole file until we know it's a valid OLE file
$this->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;
}
}

View File

@ -1,279 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Shared;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;
class Xls
{
/**
* Get the width of a column in pixels. We use the relationship y = ceil(7x) where
* x is the width in intrinsic Excel units (measuring width in number of normal characters)
* This holds for Arial 10.
*
* @param Worksheet $sheet The sheet
* @param string $col The column
*
* @return int The width in pixels
*/
public static function sizeCol($sheet, $col = 'A')
{
// default font of the workbook
$font = $sheet->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,
];
}
}

View File

@ -1,901 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Writer;
use PhpOffice\PhpSpreadsheet\Calculation\Calculation;
use PhpOffice\PhpSpreadsheet\Calculation\Functions;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\RichText\RichText;
use PhpOffice\PhpSpreadsheet\RichText\Run;
use PhpOffice\PhpSpreadsheet\Shared\Drawing as SharedDrawing;
use PhpOffice\PhpSpreadsheet\Shared\Escher;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip;
use PhpOffice\PhpSpreadsheet\Shared\OLE;
use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\File;
use PhpOffice\PhpSpreadsheet\Shared\OLE\PPS\Root;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
use PhpOffice\PhpSpreadsheet\Worksheet\Drawing;
use PhpOffice\PhpSpreadsheet\Worksheet\MemoryDrawing;
class Xls extends BaseWriter
{
/**
* PhpSpreadsheet object.
*
* @var Spreadsheet
*/
private $spreadsheet;
/**
* Total number of shared strings in workbook.
*
* @var int
*/
private $strTotal = 0;
/**
* Number of unique shared strings in workbook.
*
* @var int
*/
private $strUnique = 0;
/**
* Array of unique shared strings in workbook.
*
* @var array
*/
private $strTable = [];
/**
* Color cache. Mapping between RGB value and color index.
*
* @var array
*/
private $colors;
/**
* Formula parser.
*
* @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Parser
*/
private $parser;
/**
* Identifier clusters for drawings. Used in MSODRAWINGGROUP record.
*
* @var array
*/
private $IDCLs;
/**
* Basic OLE object summary information.
*
* @var array
*/
private $summaryInformation;
/**
* Extended OLE object document summary information.
*
* @var array
*/
private $documentSummaryInformation;
/**
* @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Workbook
*/
private $writerWorkbook;
/**
* @var \PhpOffice\PhpSpreadsheet\Writer\Xls\Worksheet[]
*/
private $writerWorksheets;
/**
* Create a new Xls Writer.
*
* @param Spreadsheet $spreadsheet PhpSpreadsheet object
*/
public function __construct(Spreadsheet $spreadsheet)
{
$this->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;
}
}

View File

@ -1,224 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
use PhpOffice\PhpSpreadsheet\Writer\Exception as WriterException;
// Original file header of PEAR::Spreadsheet_Excel_Writer_BIFFwriter (used as the base for this class):
// -----------------------------------------------------------------------------------------
// * Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
// *
// * 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
// * <jmcnamara@cpan.org>
// *
// * 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;
}
}

View File

@ -1,510 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
use PhpOffice\PhpSpreadsheet\Cell\Coordinate;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DgContainer\SpgrContainer\SpContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE;
use PhpOffice\PhpSpreadsheet\Shared\Escher\DggContainer\BstoreContainer\BSE\Blip;
class Escher
{
/**
* The object we are writing.
*/
private $object;
/**
* The written binary data.
*/
private $data;
/**
* Shape offsets. Positions in binary stream where a new shape record begins.
*
* @var array
*/
private $spOffsets;
/**
* Shape types.
*
* @var array
*/
private $spTypes;
/**
* Constructor.
*
* @param mixed $object
*/
public function __construct($object)
{
$this->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;
}
}

View File

@ -1,147 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
use PhpOffice\PhpSpreadsheet\Shared\StringHelper;
class Font
{
/**
* Color index.
*
* @var int
*/
private $colorIndex;
/**
* Font.
*
* @var \PhpOffice\PhpSpreadsheet\Style\Font
*/
private $font;
/**
* Constructor.
*/
public function __construct(\PhpOffice\PhpSpreadsheet\Style\Font $font)
{
$this->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;
}
}

View File

@ -1,548 +0,0 @@
<?php
namespace PhpOffice\PhpSpreadsheet\Writer\Xls;
use PhpOffice\PhpSpreadsheet\Style\Alignment;
use PhpOffice\PhpSpreadsheet\Style\Border;
use PhpOffice\PhpSpreadsheet\Style\Borders;
use PhpOffice\PhpSpreadsheet\Style\Fill;
use PhpOffice\PhpSpreadsheet\Style\Protection;
use PhpOffice\PhpSpreadsheet\Style\Style;
// Original file header of PEAR::Spreadsheet_Excel_Writer_Format (used as the base for this class):
// -----------------------------------------------------------------------------------------
// /*
// * Module written/ported by Xavier Noguer <xnoguer@rezebra.com>
// *
// * 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
// * <jmcnamara@cpan.org>
// *
// * 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;
}
}
}

View File

@ -32,7 +32,7 @@
<location>phpspreadsheet</location>
<name>PhpSpreadsheet</name>
<license>MIT</license>
<version>1.10.1</version>
<version>1.16.0</version>
<licenseversion></licenseversion>
</library>
<library>