This commit is contained in:
wangjinlei
2022-11-23 14:48:15 +08:00
parent 0a92e69b83
commit ec59e99a8b
636 changed files with 59164 additions and 46329 deletions

View File

@@ -0,0 +1,79 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Shared;
use InvalidArgumentException;
use ReflectionClass;
abstract class AbstractEnum
{
private static $constCacheArray;
private static function getConstants()
{
if (self::$constCacheArray == null) {
self::$constCacheArray = [];
}
$calledClass = static::class;
if (!array_key_exists($calledClass, self::$constCacheArray)) {
$reflect = new ReflectionClass($calledClass);
self::$constCacheArray[$calledClass] = $reflect->getConstants();
}
return self::$constCacheArray[$calledClass];
}
/**
* Returns all values for this enum.
*
* @return array
*/
public static function values()
{
return array_values(self::getConstants());
}
/**
* Returns true the value is valid for this enum.
*
* @param string $value
*
* @return bool true if value is valid
*/
public static function isValid($value)
{
$values = array_values(self::getConstants());
return in_array($value, $values, true);
}
/**
* Validates that the value passed is a valid value.
*
* @param string $value
*/
public static function validate($value): void
{
if (!self::isValid($value)) {
$calledClass = static::class;
$values = array_values(self::getConstants());
throw new InvalidArgumentException("$value is not a valid value for $calledClass, possible values are " . implode(', ', $values));
}
}
}

View File

@@ -0,0 +1,455 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Shared;
/**
* Common converter functions.
*/
class Converter
{
const INCH_TO_CM = 2.54;
const INCH_TO_TWIP = 1440;
const INCH_TO_PIXEL = 96;
const INCH_TO_POINT = 72;
const INCH_TO_PICA = 6;
const PIXEL_TO_EMU = 9525;
const DEGREE_TO_ANGLE = 60000;
/**
* Convert centimeter to twip.
*
* @param float $centimeter
*
* @return float
*/
public static function cmToTwip($centimeter = 1)
{
return $centimeter / self::INCH_TO_CM * self::INCH_TO_TWIP;
}
/**
* Convert centimeter to inch.
*
* @param float $centimeter
*
* @return float
*/
public static function cmToInch($centimeter = 1)
{
return $centimeter / self::INCH_TO_CM;
}
/**
* Convert centimeter to pixel.
*
* @param float $centimeter
*
* @return float
*/
public static function cmToPixel($centimeter = 1)
{
return $centimeter / self::INCH_TO_CM * self::INCH_TO_PIXEL;
}
/**
* Convert centimeter to point.
*
* @param float $centimeter
*
* @return float
*/
public static function cmToPoint($centimeter = 1)
{
return $centimeter / self::INCH_TO_CM * self::INCH_TO_POINT;
}
/**
* Convert centimeter to EMU.
*
* @param float $centimeter
*
* @return float
*/
public static function cmToEmu($centimeter = 1)
{
return round($centimeter / self::INCH_TO_CM * self::INCH_TO_PIXEL * self::PIXEL_TO_EMU);
}
/**
* Convert inch to twip.
*
* @param float $inch
*
* @return float
*/
public static function inchToTwip($inch = 1)
{
return $inch * self::INCH_TO_TWIP;
}
/**
* Convert inch to centimeter.
*
* @param float $inch
*
* @return float
*/
public static function inchToCm($inch = 1)
{
return $inch * self::INCH_TO_CM;
}
/**
* Convert inch to pixel.
*
* @param float $inch
*
* @return float
*/
public static function inchToPixel($inch = 1)
{
return $inch * self::INCH_TO_PIXEL;
}
/**
* Convert inch to point.
*
* @param float $inch
*
* @return float
*/
public static function inchToPoint($inch = 1)
{
return $inch * self::INCH_TO_POINT;
}
/**
* Convert inch to EMU.
*
* @param float $inch
*
* @return int
*/
public static function inchToEmu($inch = 1)
{
return round($inch * self::INCH_TO_PIXEL * self::PIXEL_TO_EMU);
}
/**
* Convert pixel to twip.
*
* @param float $pixel
*
* @return float
*/
public static function pixelToTwip($pixel = 1)
{
return $pixel / self::INCH_TO_PIXEL * self::INCH_TO_TWIP;
}
/**
* Convert pixel to centimeter.
*
* @param float $pixel
*
* @return float
*/
public static function pixelToCm($pixel = 1)
{
return $pixel / self::INCH_TO_PIXEL * self::INCH_TO_CM;
}
/**
* Convert pixel to point.
*
* @param float $pixel
*
* @return float
*/
public static function pixelToPoint($pixel = 1)
{
return $pixel / self::INCH_TO_PIXEL * self::INCH_TO_POINT;
}
/**
* Convert pixel to EMU.
*
* @param float $pixel
*
* @return int
*/
public static function pixelToEmu($pixel = 1)
{
return round($pixel * self::PIXEL_TO_EMU);
}
/**
* Convert point to twip unit.
*
* @param float $point
*
* @return float
*/
public static function pointToTwip($point = 1)
{
return $point / self::INCH_TO_POINT * self::INCH_TO_TWIP;
}
/**
* Convert point to pixel.
*
* @param float $point
*
* @return float
*/
public static function pointToPixel($point = 1)
{
return $point / self::INCH_TO_POINT * self::INCH_TO_PIXEL;
}
/**
* Convert point to EMU.
*
* @param float $point
*
* @return float
*/
public static function pointToEmu($point = 1)
{
return round($point / self::INCH_TO_POINT * self::INCH_TO_PIXEL * self::PIXEL_TO_EMU);
}
/**
* Convert point to cm.
*
* @param float $point
*
* @return float
*/
public static function pointToCm($point = 1)
{
return $point / self::INCH_TO_POINT * self::INCH_TO_CM;
}
/**
* Convert EMU to pixel.
*
* @param float $emu
*
* @return float
*/
public static function emuToPixel($emu = 1)
{
return round($emu / self::PIXEL_TO_EMU);
}
/**
* Convert pica to point.
*
* @param float $pica
*
* @return float
*/
public static function picaToPoint($pica = 1)
{
return $pica / self::INCH_TO_PICA * self::INCH_TO_POINT;
}
/**
* Convert degree to angle.
*
* @param float $degree
*
* @return int
*/
public static function degreeToAngle($degree = 1)
{
return (int) round($degree * self::DEGREE_TO_ANGLE);
}
/**
* Convert angle to degrees.
*
* @param float $angle
*
* @return int
*/
public static function angleToDegree($angle = 1)
{
return round($angle / self::DEGREE_TO_ANGLE);
}
/**
* Convert colorname as string to RGB.
*
* @param string $value color name
*
* @return string color as hex RGB string, or original value if unknown
*/
public static function stringToRgb($value)
{
switch ($value) {
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_YELLOW:
return 'FFFF00';
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_LIGHTGREEN:
return '90EE90';
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_CYAN:
return '00FFFF';
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_MAGENTA:
return 'FF00FF';
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_BLUE:
return '0000FF';
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_RED:
return 'FF0000';
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKBLUE:
return '00008B';
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKCYAN:
return '008B8B';
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKGREEN:
return '006400';
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKMAGENTA:
return '8B008B';
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKRED:
return '8B0000';
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKYELLOW:
return '8B8B00';
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_DARKGRAY:
return 'A9A9A9';
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_LIGHTGRAY:
return 'D3D3D3';
case \PhpOffice\PhpWord\Style\Font::FGCOLOR_BLACK:
return '000000';
}
return $value;
}
/**
* Convert HTML hexadecimal to RGB.
*
* @param string $value HTML Color in hexadecimal
*
* @return array Value in RGB
*/
public static function htmlToRgb($value)
{
if ($value[0] == '#') {
$value = substr($value, 1);
} else {
$value = self::stringToRgb($value);
}
if (strlen($value) == 6) {
[$red, $green, $blue] = [$value[0] . $value[1], $value[2] . $value[3], $value[4] . $value[5]];
} elseif (strlen($value) == 3) {
[$red, $green, $blue] = [$value[0] . $value[0], $value[1] . $value[1], $value[2] . $value[2]];
} else {
return false;
}
$red = ctype_xdigit($red) ? hexdec($red) : 0;
$green = ctype_xdigit($green) ? hexdec($green) : 0;
$blue = ctype_xdigit($blue) ? hexdec($blue) : 0;
return [$red, $green, $blue];
}
/**
* Transforms a size in CSS format (eg. 10px, 10px, ...) to points.
*
* @param string $value
*
* @return float
*/
public static function cssToPoint($value)
{
if ($value == '0') {
return 0;
}
$matches = [];
if (preg_match('/^[+-]?([0-9]+\.?[0-9]*)?(px|em|ex|%|in|cm|mm|pt|pc)$/i', $value, $matches)) {
$size = $matches[1];
$unit = $matches[2];
switch ($unit) {
case 'pt':
return $size;
case 'px':
return self::pixelToPoint($size);
case 'cm':
return self::cmToPoint($size);
case 'mm':
return self::cmToPoint($size / 10);
case 'in':
return self::inchToPoint($size);
case 'pc':
return self::picaToPoint($size);
}
}
return null;
}
/**
* Transforms a size in CSS format (eg. 10px, 10px, ...) to twips.
*
* @param string $value
*
* @return float
*/
public static function cssToTwip($value)
{
return self::pointToTwip(self::cssToPoint($value));
}
/**
* Transforms a size in CSS format (eg. 10px, 10px, ...) to pixel.
*
* @param string $value
*
* @return float
*/
public static function cssToPixel($value)
{
return self::pointToPixel(self::cssToPoint($value));
}
/**
* Transforms a size in CSS format (eg. 10px, 10px, ...) to cm.
*
* @param string $value
*
* @return float
*/
public static function cssToCm($value)
{
return self::pointToCm(self::cssToPoint($value));
}
/**
* Transforms a size in CSS format (eg. 10px, 10px, ...) to emu.
*
* @param string $value
*
* @return float
*/
public static function cssToEmu($value)
{
return self::pointToEmu(self::cssToPoint($value));
}
}

View File

@@ -0,0 +1,263 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Shared;
/**
* Drawing.
*/
class Drawing
{
const DPI_96 = 96;
/**
* Convert pixels to EMU.
*
* @param int $pValue Value in pixels
*
* @return int
*/
public static function pixelsToEmu($pValue = 0)
{
return round($pValue * 9525);
}
/**
* Convert EMU to pixels.
*
* @param int $pValue Value in EMU
*
* @return int
*/
public static function emuToPixels($pValue = 0)
{
if ($pValue == 0) {
return 0;
}
return round($pValue / 9525);
}
/**
* Convert pixels to points.
*
* @param int $pValue Value in pixels
*
* @return float
*/
public static function pixelsToPoints($pValue = 0)
{
return $pValue * 0.67777777;
}
/**
* Convert points width to centimeters.
*
* @param int $pValue Value in points
*
* @return float
*/
public static function pointsToCentimeters($pValue = 0)
{
if ($pValue == 0) {
return 0;
}
return (($pValue * 1.333333333) / self::DPI_96) * 2.54;
}
/**
* Convert points width to pixels.
*
* @param int $pValue Value in points
*
* @return float
*/
public static function pointsToPixels($pValue = 0)
{
if ($pValue == 0) {
return 0;
}
return $pValue * 1.333333333;
}
/**
* Convert pixels to centimeters.
*
* @param int $pValue Value in pixels
*
* @return float
*/
public static function pixelsToCentimeters($pValue = 0)
{
//return $pValue * 0.028;
return ($pValue / self::DPI_96) * 2.54;
}
/**
* Convert centimeters width to pixels.
*
* @param int $pValue Value in centimeters
*
* @return float
*/
public static function centimetersToPixels($pValue = 0)
{
if ($pValue == 0) {
return 0;
}
return ($pValue / 2.54) * self::DPI_96;
}
/**
* Convert degrees to angle.
*
* @param int $pValue Degrees
*
* @return int
*/
public static function degreesToAngle($pValue = 0)
{
return (int) round($pValue * 60000);
}
/**
* Convert angle to degrees.
*
* @param int $pValue Angle
*
* @return int
*/
public static function angleToDegrees($pValue = 0)
{
if ($pValue == 0) {
return 0;
}
return round($pValue / 60000);
}
/**
* Convert centimeters width to twips.
*
* @param int $pValue
*
* @return float
*/
public static function centimetersToTwips($pValue = 0)
{
if ($pValue == 0) {
return 0;
}
return $pValue * 566.928;
}
/**
* Convert twips width to centimeters.
*
* @param int $pValue
*
* @return float
*/
public static function twipsToCentimeters($pValue = 0)
{
if ($pValue == 0) {
return 0;
}
return $pValue / 566.928;
}
/**
* Convert inches width to twips.
*
* @param int $pValue
*
* @return float
*/
public static function inchesToTwips($pValue = 0)
{
if ($pValue == 0) {
return 0;
}
return $pValue * 1440;
}
/**
* Convert twips width to inches.
*
* @param int $pValue
*
* @return float
*/
public static function twipsToInches($pValue = 0)
{
if ($pValue == 0) {
return 0;
}
return $pValue / 1440;
}
/**
* Convert twips width to pixels.
*
* @param int $pValue
*
* @return float
*/
public static function twipsToPixels($pValue = 0)
{
if ($pValue == 0) {
return 0;
}
return round($pValue / 15.873984);
}
/**
* Convert HTML hexadecimal to RGB.
*
* @param string $pValue HTML Color in hexadecimal
*
* @return array|false Value in RGB
*/
public static function htmlToRGB($pValue)
{
if ($pValue[0] == '#') {
$pValue = substr($pValue, 1);
}
if (strlen($pValue) == 6) {
[$colorR, $colorG, $colorB] = [$pValue[0] . $pValue[1], $pValue[2] . $pValue[3], $pValue[4] . $pValue[5]];
} elseif (strlen($pValue) == 3) {
[$colorR, $colorG, $colorB] = [$pValue[0] . $pValue[0], $pValue[1] . $pValue[1], $pValue[2] . $pValue[2]];
} else {
return false;
}
$colorR = hexdec($colorR);
$colorG = hexdec($colorG);
$colorB = hexdec($colorB);
return [$colorR, $colorG, $colorB];
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,243 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Shared\Microsoft;
/**
* Password encoder for microsoft office applications.
*/
class PasswordEncoder
{
const ALGORITHM_MD2 = 'MD2';
const ALGORITHM_MD4 = 'MD4';
const ALGORITHM_MD5 = 'MD5';
const ALGORITHM_SHA_1 = 'SHA-1';
const ALGORITHM_SHA_256 = 'SHA-256';
const ALGORITHM_SHA_384 = 'SHA-384';
const ALGORITHM_SHA_512 = 'SHA-512';
const ALGORITHM_RIPEMD = 'RIPEMD';
const ALGORITHM_RIPEMD_160 = 'RIPEMD-160';
const ALGORITHM_MAC = 'MAC';
const ALGORITHM_HMAC = 'HMAC';
/**
* Mapping between algorithm name and algorithm ID.
*
* @var array
*
* @see https://msdn.microsoft.com/en-us/library/documentformat.openxml.wordprocessing.writeprotection.cryptographicalgorithmsid(v=office.14).aspx
*/
private static $algorithmMapping = [
self::ALGORITHM_MD2 => [1, 'md2'],
self::ALGORITHM_MD4 => [2, 'md4'],
self::ALGORITHM_MD5 => [3, 'md5'],
self::ALGORITHM_SHA_1 => [4, 'sha1'],
self::ALGORITHM_MAC => [5, ''], // 'mac' -> not possible with hash()
self::ALGORITHM_RIPEMD => [6, 'ripemd'],
self::ALGORITHM_RIPEMD_160 => [7, 'ripemd160'],
self::ALGORITHM_HMAC => [9, ''], //'hmac' -> not possible with hash()
self::ALGORITHM_SHA_256 => [12, 'sha256'],
self::ALGORITHM_SHA_384 => [13, 'sha384'],
self::ALGORITHM_SHA_512 => [14, 'sha512'],
];
private static $initialCodeArray = [
0xE1F0,
0x1D0F,
0xCC9C,
0x84C0,
0x110C,
0x0E10,
0xF1CE,
0x313E,
0x1872,
0xE139,
0xD40F,
0x84F9,
0x280C,
0xA96A,
0x4EC3,
];
private static $encryptionMatrix = [
[0xAEFC, 0x4DD9, 0x9BB2, 0x2745, 0x4E8A, 0x9D14, 0x2A09],
[0x7B61, 0xF6C2, 0xFDA5, 0xEB6B, 0xC6F7, 0x9DCF, 0x2BBF],
[0x4563, 0x8AC6, 0x05AD, 0x0B5A, 0x16B4, 0x2D68, 0x5AD0],
[0x0375, 0x06EA, 0x0DD4, 0x1BA8, 0x3750, 0x6EA0, 0xDD40],
[0xD849, 0xA0B3, 0x5147, 0xA28E, 0x553D, 0xAA7A, 0x44D5],
[0x6F45, 0xDE8A, 0xAD35, 0x4A4B, 0x9496, 0x390D, 0x721A],
[0xEB23, 0xC667, 0x9CEF, 0x29FF, 0x53FE, 0xA7FC, 0x5FD9],
[0x47D3, 0x8FA6, 0x0F6D, 0x1EDA, 0x3DB4, 0x7B68, 0xF6D0],
[0xB861, 0x60E3, 0xC1C6, 0x93AD, 0x377B, 0x6EF6, 0xDDEC],
[0x45A0, 0x8B40, 0x06A1, 0x0D42, 0x1A84, 0x3508, 0x6A10],
[0xAA51, 0x4483, 0x8906, 0x022D, 0x045A, 0x08B4, 0x1168],
[0x76B4, 0xED68, 0xCAF1, 0x85C3, 0x1BA7, 0x374E, 0x6E9C],
[0x3730, 0x6E60, 0xDCC0, 0xA9A1, 0x4363, 0x86C6, 0x1DAD],
[0x3331, 0x6662, 0xCCC4, 0x89A9, 0x0373, 0x06E6, 0x0DCC],
[0x1021, 0x2042, 0x4084, 0x8108, 0x1231, 0x2462, 0x48C4],
];
private static $passwordMaxLength = 15;
/**
* Create a hashed password that MS Word will be able to work with.
*
* @see https://blogs.msdn.microsoft.com/vsod/2010/04/05/how-to-set-the-editing-restrictions-in-word-using-open-xml-sdk-2-0/
*
* @param string $password
* @param string $algorithmName
* @param string $salt
* @param int $spinCount
*
* @return string
*/
public static function hashPassword($password, $algorithmName = self::ALGORITHM_SHA_1, $salt = null, $spinCount = 10000)
{
$origEncoding = mb_internal_encoding();
mb_internal_encoding('UTF-8');
$password = mb_substr($password, 0, min(self::$passwordMaxLength, mb_strlen($password)));
// Get the single-byte values by iterating through the Unicode characters of the truncated password.
// For each character, if the low byte is not equal to 0, take it. Otherwise, take the high byte.
$passUtf8 = mb_convert_encoding($password, 'UCS-2LE', 'UTF-8');
$byteChars = [];
for ($i = 0; $i < mb_strlen($password); ++$i) {
$byteChars[$i] = ord(substr($passUtf8, $i * 2, 1));
if ($byteChars[$i] == 0) {
$byteChars[$i] = ord(substr($passUtf8, $i * 2 + 1, 1));
}
}
// build low-order word and hig-order word and combine them
$combinedKey = self::buildCombinedKey($byteChars);
// build reversed hexadecimal string
$hex = str_pad(strtoupper(dechex($combinedKey & 0xFFFFFFFF)), 8, '0', \STR_PAD_LEFT);
$reversedHex = $hex[6] . $hex[7] . $hex[4] . $hex[5] . $hex[2] . $hex[3] . $hex[0] . $hex[1];
$generatedKey = mb_convert_encoding($reversedHex, 'UCS-2LE', 'UTF-8');
// Implementation Notes List:
// Word requires that the initial hash of the password with the salt not be considered in the count.
// The initial hash of salt + key is not included in the iteration count.
$algorithm = self::getAlgorithm($algorithmName);
$generatedKey = hash($algorithm, $salt . $generatedKey, true);
for ($i = 0; $i < $spinCount; ++$i) {
$generatedKey = hash($algorithm, $generatedKey . pack('CCCC', $i, $i >> 8, $i >> 16, $i >> 24), true);
}
$generatedKey = base64_encode($generatedKey);
mb_internal_encoding($origEncoding);
return $generatedKey;
}
/**
* Get algorithm from self::$algorithmMapping.
*
* @param string $algorithmName
*
* @return string
*/
private static function getAlgorithm($algorithmName)
{
$algorithm = self::$algorithmMapping[$algorithmName][1];
if ($algorithm == '') {
$algorithm = 'sha1';
}
return $algorithm;
}
/**
* Returns the algorithm ID.
*
* @param string $algorithmName
*
* @return int
*/
public static function getAlgorithmId($algorithmName)
{
return self::$algorithmMapping[$algorithmName][0];
}
/**
* Build combined key from low-order word and high-order word.
*
* @param array $byteChars byte array representation of password
*
* @return int
*/
private static function buildCombinedKey($byteChars)
{
$byteCharsLength = count($byteChars);
// Compute the high-order word
// Initialize from the initial code array (see above), depending on the passwords length.
$highOrderWord = self::$initialCodeArray[$byteCharsLength - 1];
// For each character in the password:
// For every bit in the character, starting with the least significant and progressing to (but excluding)
// the most significant, if the bit is set, XOR the keys high-order word with the corresponding word from
// the Encryption Matrix
for ($i = 0; $i < $byteCharsLength; ++$i) {
$tmp = self::$passwordMaxLength - $byteCharsLength + $i;
$matrixRow = self::$encryptionMatrix[$tmp];
for ($intBit = 0; $intBit < 7; ++$intBit) {
if (($byteChars[$i] & (0x0001 << $intBit)) != 0) {
$highOrderWord = ($highOrderWord ^ $matrixRow[$intBit]);
}
}
}
// Compute low-order word
// Initialize with 0
$lowOrderWord = 0;
// For each character in the password, going backwards
for ($i = $byteCharsLength - 1; $i >= 0; --$i) {
// low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR character
$lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteChars[$i]);
}
// Lastly, low-order word = (((low-order word SHR 14) AND 0x0001) OR (low-order word SHL 1) AND 0x7FFF)) XOR strPassword length XOR 0xCE4B.
$lowOrderWord = (((($lowOrderWord >> 14) & 0x0001) | (($lowOrderWord << 1) & 0x7FFF)) ^ $byteCharsLength ^ 0xCE4B);
// Combine the Low and High Order Word
return self::int32(($highOrderWord << 16) + $lowOrderWord);
}
/**
* Simulate behaviour of (signed) int32.
*
* @codeCoverageIgnore
*
* @param int $value
*
* @return int
*/
private static function int32($value)
{
$value = ($value & 0xFFFFFFFF);
if ($value & 0x80000000) {
$value = -((~$value & 0xFFFFFFFF) + 1);
}
return $value;
}
}

View File

@@ -0,0 +1,323 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
* @copyright 2010-2018 PHPWord contributors
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Shared;
use PhpOffice\PhpWord\Exception\Exception;
defined('IDENTIFIER_OLE') ||
define('IDENTIFIER_OLE', pack('CCCCCCCC', 0xd0, 0xcf, 0x11, 0xe0, 0xa1, 0xb1, 0x1a, 0xe1));
class OLERead
{
private $data = '';
// OLE identifier
const IDENTIFIER_OLE = IDENTIFIER_OLE;
// 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 $wrkdocument = null;
public $wrk1Table = null;
public $wrkData = null;
public $wrkObjectPool = null;
public $summaryInformation = null;
public $docSummaryInfos = null;
/**
* Read the file
*
* @param $sFileName string Filename
*
* @throws Exception
*/
public function read($sFileName)
{
// Check if file exists and is readable
if (!is_readable($sFileName)) {
throw new Exception('Could not open ' . $sFileName . ' for reading! File does not exist, or it is not readable.');
}
// 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($sFileName, false, null, 0, 8);
// Check OLE identifier
if ($this->data != self::IDENTIFIER_OLE) {
throw new Exception('The filename ' . $sFileName . ' is not recognised as an OLE file');
}
// Get the file data
$this->data = file_get_contents($sFileName);
// 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 = array();
$pos = self::BIG_BLOCK_DEPOT_BLOCKS_POS;
$bbdBlocks = $this->numBigBlockDepotBlocks;
// @codeCoverageIgnoreStart
if ($this->numExtensionBlocks != 0) {
$bbdBlocks = (self::BIG_BLOCK_SIZE - self::BIG_BLOCK_DEPOT_BLOCKS_POS) / 4;
}
// @codeCoverageIgnoreEnd
for ($i = 0; $i < $bbdBlocks; ++$i) {
$bigBlockDepotBlocks[$i] = self::getInt4d($this->data, $pos);
$pos += 4;
}
// @codeCoverageIgnoreStart
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);
}
}
// @codeCoverageIgnoreEnd
$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 mixed $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 ''; // @codeCoverageIgnore
}
$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 $blSectorId Sector ID where the stream starts
* @return string Data for standard stream
*/
private function readData($blSectorId)
{
$block = $blSectorId;
$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()
{
$offset = 0;
// loop through entires, each entry is 128 bytes
$entryLen = strlen($this->entry);
while ($offset < $entryLen) {
// entry data (128 bytes)
$data = substr($this->entry, $offset, self::PROPERTY_STORAGE_BLOCK_SIZE);
// size in bytes of name
$nameSize = ord($data[self::SIZE_OF_NAME_POS]) | (ord($data[self::SIZE_OF_NAME_POS + 1]) << 8);
// type of entry
$type = ord($data[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($data, self::START_BLOCK_POS);
$size = self::getInt4d($data, self::SIZE_POS);
$name = str_replace("\x00", '', substr($data, 0, $nameSize));
$this->props[] = array(
'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)
// print_r($upName.PHP_EOL);
if (($upName === 'WORDDOCUMENT')) {
$this->wrkdocument = count($this->props) - 1;
} elseif ($upName === '1TABLE') {
$this->wrk1Table = count($this->props) - 1;
} elseif ($upName === 'DATA') {
$this->wrkData = count($this->props) - 1;
} elseif ($upName === 'OBJECTPOOL') {
$this->wrkObjectPoolelseif = count($this->props) - 1;
} elseif ($upName === 'ROOT ENTRY' || $upName === 'R') {
$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->docSummaryInfos = 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)
{
// FIX: represent numbers correctly on 64-bit system
// http://sourceforge.net/tracker/index.php?func=detail&aid=1487372&group_id=99160&atid=623334
// Hacked by Andreas Rehm 2006 to ensure correct result of the <<24 block on 32 and 64bit systems
$or24 = ord($data[$pos + 3]);
if ($or24 >= 128) {
// negative number
$ord24 = -abs((256 - $or24) << 24);
} else {
$ord24 = ($or24 & 127) << 24;
}
return ord($data[$pos]) | (ord($data[$pos + 1]) << 8) | (ord($data[$pos + 2]) << 16) | $ord24;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,252 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Shared;
/**
* Text.
*/
class Text
{
/**
* Control characters array.
*
* @var string[]
*/
private static $controlCharacters = [];
/**
* Build control characters array.
*/
private static function buildControlCharacters(): void
{
for ($i = 0; $i <= 19; ++$i) {
if ($i != 9 && $i != 10 && $i != 13) {
$find = '_x' . sprintf('%04s', strtoupper(dechex($i))) . '_';
$replace = chr($i);
self::$controlCharacters[$find] = $replace;
}
}
}
/**
* Convert from PHP control character to OpenXML escaped control character.
*
* Excel 2007 team:
* ----------------
* That's correct, control characters are stored directly in the shared-strings table.
* We do encode characters that cannot be represented in XML using the following escape sequence:
* _xHHHH_ where H represents a hexadecimal character in the character's value...
* So you could end up with something like _x0008_ in a string (either in a cell value (<v>)
* element or in the shared string <t> element.
*
* @param string $value Value to escape
*
* @return string
*/
public static function controlCharacterPHP2OOXML($value = '')
{
if (empty(self::$controlCharacters)) {
self::buildControlCharacters();
}
return str_replace(array_values(self::$controlCharacters), array_keys(self::$controlCharacters), $value);
}
/**
* Return a number formatted for being integrated in xml files.
*
* @param float $number
* @param int $decimals
*
* @return string
*/
public static function numberFormat($number, $decimals)
{
return number_format($number, $decimals, '.', '');
}
/**
* @param int $dec
*
* @see http://stackoverflow.com/a/7153133/2235790
*
* @author velcrow
*
* @return string
*/
public static function chr($dec)
{
if ($dec <= 0x7F) {
return chr($dec);
}
if ($dec <= 0x7FF) {
return chr(($dec >> 6) + 192) . chr(($dec & 63) + 128);
}
if ($dec <= 0xFFFF) {
return chr(($dec >> 12) + 224) . chr((($dec >> 6) & 63) + 128) . chr(($dec & 63) + 128);
}
if ($dec <= 0x1FFFFF) {
return chr(($dec >> 18) + 240) . chr((($dec >> 12) & 63) + 128) . chr((($dec >> 6) & 63) + 128) . chr(($dec & 63) + 128);
}
return '';
}
/**
* Convert from OpenXML escaped control character to PHP control character.
*
* @param string $value Value to unescape
*
* @return string
*/
public static function controlCharacterOOXML2PHP($value = '')
{
if (empty(self::$controlCharacters)) {
self::buildControlCharacters();
}
return str_replace(array_keys(self::$controlCharacters), array_values(self::$controlCharacters), $value);
}
/**
* Check if a string contains UTF-8 data.
*
* @param string $value
*
* @return bool
*/
public static function isUTF8($value = '')
{
return is_string($value) && ($value === '' || preg_match('/^./su', $value) == 1);
}
/**
* Return UTF8 encoded value.
*
* @param string $value
*
* @return string
*/
public static function toUTF8($value = '')
{
if (null !== $value && !self::isUTF8($value)) {
$value = utf8_encode($value);
}
return $value;
}
/**
* Returns unicode from UTF8 text.
*
* The function is splitted to reduce cyclomatic complexity
*
* @param string $text UTF8 text
*
* @return string Unicode text
*
* @since 0.11.0
*/
public static function toUnicode($text)
{
return self::unicodeToEntities(self::utf8ToUnicode($text));
}
/**
* Returns unicode array from UTF8 text.
*
* @param string $text UTF8 text
*
* @return array
*
* @since 0.11.0
* @see http://www.randomchaos.com/documents/?source=php_and_unicode
*/
public static function utf8ToUnicode($text)
{
$unicode = [];
$values = [];
$lookingFor = 1;
// Gets unicode for each character
for ($i = 0; $i < strlen($text); ++$i) {
$thisValue = ord($text[$i]);
if ($thisValue < 128) {
$unicode[] = $thisValue;
} else {
if (count($values) == 0) {
$lookingFor = $thisValue < 224 ? 2 : 3;
}
$values[] = $thisValue;
if (count($values) == $lookingFor) {
if ($lookingFor == 3) {
$number = (($values[0] % 16) * 4096) + (($values[1] % 64) * 64) + ($values[2] % 64);
} else {
$number = (($values[0] % 32) * 64) + ($values[1] % 64);
}
$unicode[] = $number;
$values = [];
$lookingFor = 1;
}
}
}
return $unicode;
}
/**
* Returns entites from unicode array.
*
* @param array $unicode
*
* @return string
*
* @since 0.11.0
* @see http://www.randomchaos.com/documents/?source=php_and_unicode
*/
private static function unicodeToEntities($unicode)
{
$entities = '';
foreach ($unicode as $value) {
if ($value != 65279) {
$entities .= $value > 127 ? '\uc0{\u' . $value . '}' : chr($value);
}
}
return $entities;
}
/**
* Return name without underscore for < 0.10.0 variable name compatibility.
*
* @param string $value
*
* @return string
*/
public static function removeUnderscorePrefix($value)
{
if (null !== $value) {
if (substr($value, 0, 1) == '_') {
$value = substr($value, 1);
}
}
return $value;
}
}

View File

@@ -0,0 +1,231 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Shared;
use DOMDocument;
use DOMElement;
use DOMNodeList;
use DOMXpath;
use Exception;
use InvalidArgumentException;
use ZipArchive;
/**
* XML Reader wrapper.
*
* @since 0.2.1
*/
class XMLReader
{
/**
* DOMDocument object.
*
* @var DOMDocument
*/
private $dom;
/**
* DOMXpath object.
*
* @var DOMXpath
*/
private $xpath;
/**
* Get DOMDocument from ZipArchive.
*
* @param string $zipFile
* @param string $xmlFile
*
* @return DOMDocument|false
*/
public function getDomFromZip($zipFile, $xmlFile)
{
if (file_exists($zipFile) === false) {
throw new Exception('Cannot find archive file.');
}
$zip = new ZipArchive();
$zip->open($zipFile);
$content = $zip->getFromName($xmlFile);
$zip->close();
if ($content === false) {
return false;
}
return $this->getDomFromString($content);
}
/**
* Get DOMDocument from content string.
*
* @param string $content
*
* @return DOMDocument
*/
public function getDomFromString($content)
{
if (\PHP_VERSION_ID < 80000) {
$originalLibXMLEntityValue = libxml_disable_entity_loader(true);
}
$this->dom = new DOMDocument();
$this->dom->loadXML($content);
if (\PHP_VERSION_ID < 80000) {
libxml_disable_entity_loader($originalLibXMLEntityValue);
}
return $this->dom;
}
/**
* Get elements.
*
* @param string $path
* @param DOMElement $contextNode
*
* @return DOMNodeList
*/
public function getElements($path, ?DOMElement $contextNode = null)
{
if ($this->dom === null) {
return [];
}
if ($this->xpath === null) {
$this->xpath = new DOMXpath($this->dom);
}
if (null === $contextNode) {
return $this->xpath->query($path);
}
return $this->xpath->query($path, $contextNode);
}
/**
* Registers the namespace with the DOMXPath object.
*
* @param string $prefix The prefix
* @param string $namespaceURI The URI of the namespace
*
* @return bool true on success or false on failure
*/
public function registerNamespace($prefix, $namespaceURI)
{
if ($this->dom === null) {
throw new InvalidArgumentException('Dom needs to be loaded before registering a namespace');
}
if ($this->xpath === null) {
$this->xpath = new DOMXpath($this->dom);
}
return $this->xpath->registerNamespace($prefix, $namespaceURI);
}
/**
* Get element.
*
* @param string $path
* @param DOMElement $contextNode
*
* @return null|DOMElement
*/
public function getElement($path, ?DOMElement $contextNode = null)
{
$elements = $this->getElements($path, $contextNode);
if ($elements->length > 0) {
return $elements->item(0);
}
return null;
}
/**
* Get element attribute.
*
* @param string $attribute
* @param DOMElement $contextNode
* @param string $path
*
* @return null|string
*/
public function getAttribute($attribute, ?DOMElement $contextNode = null, $path = null)
{
$return = null;
if ($path !== null) {
$elements = $this->getElements($path, $contextNode);
if ($elements->length > 0) {
/** @var DOMElement $node Type hint */
$node = $elements->item(0);
$return = $node->getAttribute($attribute);
}
} else {
if ($contextNode !== null) {
$return = $contextNode->getAttribute($attribute);
}
}
return ($return == '') ? null : $return;
}
/**
* Get element value.
*
* @param string $path
* @param DOMElement $contextNode
*
* @return null|string
*/
public function getValue($path, ?DOMElement $contextNode = null)
{
$elements = $this->getElements($path, $contextNode);
if ($elements->length > 0) {
return $elements->item(0)->nodeValue;
}
return null;
}
/**
* Count elements.
*
* @param string $path
* @param DOMElement $contextNode
*
* @return int
*/
public function countElements($path, ?DOMElement $contextNode = null)
{
$elements = $this->getElements($path, $contextNode);
return $elements->length;
}
/**
* Element exists.
*
* @param string $path
* @param DOMElement $contextNode
*
* @return bool
*/
public function elementExists($path, ?DOMElement $contextNode = null)
{
return $this->getElements($path, $contextNode)->length > 0;
}
}

View File

@@ -0,0 +1,187 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Shared;
use Exception;
use ReturnTypeWillChange;
/**
* XMLWriter.
*
* @method bool endElement()
* @method mixed flush(bool $empty = null)
* @method bool openMemory()
* @method string outputMemory(bool $flush = null)
* @method bool setIndent(bool $indent)
* @method bool startDocument(string $version = 1.0, string $encoding = null, string $standalone = null)
* @method bool startElement(string $name)
* @method bool text(string $content)
* @method bool writeCData(string $content)
* @method bool writeComment(string $content)
* @method bool writeElement(string $name, string $content = null)
* @method bool writeRaw(string $content)
*/
class XMLWriter extends \XMLWriter
{
/** Temporary storage method */
const STORAGE_MEMORY = 1;
const STORAGE_DISK = 2;
/**
* Temporary filename.
*
* @var string
*/
private $tempFileName = '';
/**
* Create a new \PhpOffice\PhpWord\Shared\XMLWriter instance.
*
* @param int $pTemporaryStorage Temporary storage location
* @param string $pTemporaryStorageDir Temporary storage folder
* @param bool $compatibility
*/
public function __construct($pTemporaryStorage = self::STORAGE_MEMORY, $pTemporaryStorageDir = null, $compatibility = false)
{
// Open temporary storage
if ($pTemporaryStorage == self::STORAGE_MEMORY) {
$this->openMemory();
} else {
if (!$pTemporaryStorageDir || !is_dir($pTemporaryStorageDir)) {
$pTemporaryStorageDir = sys_get_temp_dir();
}
// Create temporary filename
$this->tempFileName = @tempnam($pTemporaryStorageDir, 'xml');
// Open storage
$this->openUri($this->tempFileName);
}
if ($compatibility) {
$this->setIndent(false);
$this->setIndentString('');
} else {
$this->setIndent(true);
$this->setIndentString(' ');
}
}
/**
* Destructor.
*/
public function __destruct()
{
// Unlink temporary files
if (empty($this->tempFileName)) {
return;
}
if (PHP_OS != 'WINNT' && @unlink($this->tempFileName) === false) {
throw new Exception('The file ' . $this->tempFileName . ' could not be deleted.');
}
}
/**
* Get written data.
*
* @return string
*/
public function getData()
{
if ($this->tempFileName == '') {
return $this->outputMemory(true);
}
$this->flush();
return file_get_contents($this->tempFileName);
}
/**
* Write simple element and attribute(s) block.
*
* There are two options:
* 1. If the `$attributes` is an array, then it's an associative array of attributes
* 2. If not, then it's a simple attribute-value pair
*
* @param string $element
* @param array|string $attributes
* @param string $value
*/
public function writeElementBlock($element, $attributes, $value = null): void
{
$this->startElement($element);
if (!is_array($attributes)) {
$attributes = [$attributes => $value];
}
foreach ($attributes as $attribute => $value) {
$this->writeAttribute($attribute, $value);
}
$this->endElement();
}
/**
* Write element if ...
*
* @param bool $condition
* @param string $element
* @param string $attribute
* @param mixed $value
*/
public function writeElementIf($condition, $element, $attribute = null, $value = null): void
{
if ($condition == true) {
if (null === $attribute) {
$this->writeElement($element, $value);
} else {
$this->startElement($element);
$this->writeAttribute($attribute, $value);
$this->endElement();
}
}
}
/**
* Write attribute if ...
*
* @param bool $condition
* @param string $attribute
* @param mixed $value
*/
public function writeAttributeIf($condition, $attribute, $value): void
{
if ($condition == true) {
$this->writeAttribute($attribute, $value);
}
}
/**
* @param string $name
* @param mixed $value
*
* @return bool
*/
#[ReturnTypeWillChange]
public function writeAttribute($name, $value)
{
if (is_float($value)) {
$value = json_encode($value);
}
return parent::writeAttribute($name, $value ?? '');
}
}

View File

@@ -0,0 +1,419 @@
<?php
/**
* This file is part of PHPWord - A pure PHP library for reading and writing
* word processing documents.
*
* PHPWord is free software distributed under the terms of the GNU Lesser
* General Public License version 3 as published by the Free Software Foundation.
*
* For the full copyright and license information, please read the LICENSE
* file that was distributed with this source code. For the full list of
* contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
*
* @see https://github.com/PHPOffice/PHPWord
*
* @license http://www.gnu.org/licenses/lgpl.txt LGPL version 3
*/
namespace PhpOffice\PhpWord\Shared;
use PclZip;
use PhpOffice\PhpWord\Exception\Exception;
use PhpOffice\PhpWord\Settings;
/**
* ZipArchive wrapper.
*
* Wraps zip archive functionality of PHP ZipArchive and PCLZip. PHP ZipArchive
* properties and methods are bypassed and used as the model for the PCLZip
* emulation. Only needed PHP ZipArchive features are implemented.
*
* @method bool addFile(string $filename, string $localname = null)
* @method bool addFromString(string $localname, string $contents)
* @method string getNameIndex(int $index)
* @method int locateName(string $name)
*
* @since 0.10.0
*/
class ZipArchive
{
/** @const int Flags for open method */
const CREATE = 1; // Emulate \ZipArchive::CREATE
const OVERWRITE = 8; // Emulate \ZipArchive::OVERWRITE
/**
* Number of files (emulate ZipArchive::$numFiles).
*
* @var int
*/
public $numFiles = 0;
/**
* Archive filename (emulate ZipArchive::$filename).
*
* @var string
*/
public $filename;
/**
* Temporary storage directory.
*
* @var string
*/
private $tempDir;
/**
* Internal zip archive object.
*
* @var PclZip|\ZipArchive
*/
private $zip;
/**
* Use PCLZip (default behaviour).
*
* @var bool
*/
private $usePclzip = true;
/**
* Create new instance.
*/
public function __construct()
{
$this->usePclzip = (Settings::getZipClass() != 'ZipArchive');
if ($this->usePclzip) {
if (!defined('PCLZIP_TEMPORARY_DIR')) {
define('PCLZIP_TEMPORARY_DIR', Settings::getTempDir() . '/');
}
require_once 'PCLZip/pclzip.lib.php';
}
}
/**
* Catch function calls: pass to ZipArchive or PCLZip.
*
* `call_user_func_array` can only used for public function, hence the `public` in all `pcl...` methods
*
* @param mixed $function
* @param mixed $args
*
* @return mixed
*/
public function __call($function, $args)
{
// Set object and function
$zipFunction = $function;
if (!$this->usePclzip) {
$zipObject = $this->zip;
} else {
$zipObject = $this;
$zipFunction = "pclzip{$zipFunction}";
}
// Run function
$result = false;
if (method_exists($zipObject, $zipFunction)) {
$result = @call_user_func_array([$zipObject, $zipFunction], $args);
}
return $result;
}
/**
* Open a new zip archive.
*
* @param string $filename The file name of the ZIP archive to open
* @param int $flags The mode to use to open the archive
*
* @return bool
*/
public function open($filename, $flags = null)
{
$result = true;
$this->filename = $filename;
$this->tempDir = Settings::getTempDir();
if (!$this->usePclzip) {
$zip = new \ZipArchive();
// PHP 8.1 compat - passing null as second arg to \ZipArchive::open() is deprecated
// passing 0 achieves the same behaviour
if ($flags === null) {
$flags = 0;
}
$result = $zip->open($this->filename, $flags);
// Scrutizer will report the property numFiles does not exist
// See https://github.com/scrutinizer-ci/php-analyzer/issues/190
$this->numFiles = $zip->numFiles;
} else {
$zip = new PclZip($this->filename);
$zipContent = $zip->listContent();
$this->numFiles = is_array($zipContent) ? count($zipContent) : 0;
}
$this->zip = $zip;
return $result;
}
/**
* Close the active archive.
*
* @return bool
*
* @codeCoverageIgnore Can't find any test case. Uncomment when found.
*/
public function close()
{
if (!$this->usePclzip) {
if ($this->zip->close() === false) {
throw new Exception("Could not close zip file {$this->filename}: ");
}
}
return true;
}
/**
* Extract the archive contents (emulate \ZipArchive).
*
* @param string $destination
* @param array|string $entries
*
* @return bool
*
* @since 0.10.0
*/
public function extractTo($destination, $entries = null)
{
if (!is_dir($destination)) {
return false;
}
if (!$this->usePclzip) {
return $this->zip->extractTo($destination, $entries);
}
return $this->pclzipExtractTo($destination, $entries);
}
/**
* Extract file from archive by given file name (emulate \ZipArchive).
*
* @param string $filename Filename for the file in zip archive
*
* @return string $contents File string contents
*/
public function getFromName($filename)
{
if (!$this->usePclzip) {
$contents = $this->zip->getFromName($filename);
if ($contents === false) {
$filename = substr($filename, 1);
$contents = $this->zip->getFromName($filename);
}
} else {
$contents = $this->pclzipGetFromName($filename);
}
return $contents;
}
/**
* Add a new file to the zip archive (emulate \ZipArchive).
*
* @param string $filename Directory/Name of the file to add to the zip archive
* @param string $localname Directory/Name of the file added to the zip
*
* @return bool
*/
public function pclzipAddFile($filename, $localname = null)
{
/** @var PclZip $zip Type hint */
$zip = $this->zip;
// Bugfix GH-261 https://github.com/PHPOffice/PHPWord/pull/261
$realpathFilename = realpath($filename);
if ($realpathFilename !== false) {
$filename = $realpathFilename;
}
$filenameParts = pathinfo($filename);
$localnameParts = pathinfo($localname);
// To Rename the file while adding it to the zip we
// need to create a temp file with the correct name
$tempFile = false;
if ($filenameParts['basename'] != $localnameParts['basename']) {
$tempFile = true; // temp file created
$temppath = $this->tempDir . DIRECTORY_SEPARATOR . $localnameParts['basename'];
copy($filename, $temppath);
$filename = $temppath;
$filenameParts = pathinfo($temppath);
}
$pathRemoved = $filenameParts['dirname'];
$pathAdded = $localnameParts['dirname'];
if (!$this->usePclzip) {
$pathAdded = $pathAdded . '/' . ltrim(str_replace('\\', '/', substr($filename, strlen($pathRemoved))), '/');
//$res = $zip->addFile($filename, $pathAdded);
$res = $zip->addFromString($pathAdded, file_get_contents($filename)); // addFile can't use subfolders in some cases
} else {
$res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded);
}
if ($tempFile) {
// Remove temp file, if created
unlink($this->tempDir . DIRECTORY_SEPARATOR . $localnameParts['basename']);
}
return $res != 0;
}
/**
* Add a new file to the zip archive from a string of raw data (emulate \ZipArchive).
*
* @param string $localname Directory/Name of the file to add to the zip archive
* @param string $contents String of data to add to the zip archive
*
* @return bool
*/
public function pclzipAddFromString($localname, $contents)
{
/** @var PclZip $zip Type hint */
$zip = $this->zip;
$filenameParts = pathinfo($localname);
// Write $contents to a temp file
$handle = fopen($this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename'], 'wb');
fwrite($handle, $contents);
fclose($handle);
// Add temp file to zip
$filename = $this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename'];
$pathRemoved = $this->tempDir;
$pathAdded = $filenameParts['dirname'];
$res = $zip->add($filename, PCLZIP_OPT_REMOVE_PATH, $pathRemoved, PCLZIP_OPT_ADD_PATH, $pathAdded);
// Remove temp file
@unlink($this->tempDir . DIRECTORY_SEPARATOR . $filenameParts['basename']);
return $res != 0;
}
/**
* Extract the archive contents (emulate \ZipArchive).
*
* @param string $destination
* @param array|string $entries
*
* @return bool
*
* @since 0.10.0
*/
public function pclzipExtractTo($destination, $entries = null)
{
/** @var PclZip $zip Type hint */
$zip = $this->zip;
// Extract all files
if (null === $entries) {
$result = $zip->extract(PCLZIP_OPT_PATH, $destination);
return $result > 0;
}
// Extract by entries
if (!is_array($entries)) {
$entries = [$entries];
}
foreach ($entries as $entry) {
$entryIndex = $this->locateName($entry);
$result = $zip->extractByIndex($entryIndex, PCLZIP_OPT_PATH, $destination);
if ($result <= 0) {
return false;
}
}
return true;
}
/**
* Extract file from archive by given file name (emulate \ZipArchive).
*
* @param string $filename Filename for the file in zip archive
*
* @return string $contents File string contents
*/
public function pclzipGetFromName($filename)
{
/** @var PclZip $zip Type hint */
$zip = $this->zip;
$listIndex = $this->pclzipLocateName($filename);
$contents = false;
if ($listIndex !== false) {
$extracted = $zip->extractByIndex($listIndex, PCLZIP_OPT_EXTRACT_AS_STRING);
} else {
$filename = substr($filename, 1);
$listIndex = $this->pclzipLocateName($filename);
$extracted = $zip->extractByIndex($listIndex, PCLZIP_OPT_EXTRACT_AS_STRING);
}
if ((is_array($extracted)) && ($extracted != 0)) {
$contents = $extracted[0]['content'];
}
return $contents;
}
/**
* Returns the name of an entry using its index (emulate \ZipArchive).
*
* @param int $index
*
* @return bool|string
*
* @since 0.10.0
*/
public function pclzipGetNameIndex($index)
{
/** @var PclZip $zip Type hint */
$zip = $this->zip;
$list = $zip->listContent();
if (isset($list[$index])) {
return $list[$index]['filename'];
}
return false;
}
/**
* Returns the index of the entry in the archive (emulate \ZipArchive).
*
* @param string $filename Filename for the file in zip archive
*
* @return int
*/
public function pclzipLocateName($filename)
{
/** @var PclZip $zip Type hint */
$zip = $this->zip;
$list = $zip->listContent();
$listCount = count($list);
$listIndex = -1;
for ($i = 0; $i < $listCount; ++$i) {
if (strtolower($list[$i]['filename']) == strtolower($filename) ||
strtolower($list[$i]['stored_filename']) == strtolower($filename)) {
$listIndex = $i;
break;
}
}
return ($listIndex > -1) ? $listIndex : false;
}
}