first commit
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
|
||||
namespace Box\Spout\Writer\Common\Helper;
|
||||
|
||||
/**
|
||||
* Class AbstractStyleHelper
|
||||
* This class provides helper functions to manage styles
|
||||
*
|
||||
* @package Box\Spout\Writer\Common\Helper
|
||||
*/
|
||||
abstract class AbstractStyleHelper
|
||||
{
|
||||
/** @var array [SERIALIZED_STYLE] => [STYLE_ID] mapping table, keeping track of the registered styles */
|
||||
protected $serializedStyleToStyleIdMappingTable = [];
|
||||
|
||||
/** @var array [STYLE_ID] => [STYLE] mapping table, keeping track of the registered styles */
|
||||
protected $styleIdToStyleMappingTable = [];
|
||||
|
||||
/**
|
||||
* @param \Box\Spout\Writer\Style\Style $defaultStyle
|
||||
*/
|
||||
public function __construct($defaultStyle)
|
||||
{
|
||||
// This ensures that the default style is the first one to be registered
|
||||
$this->registerStyle($defaultStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers the given style as a used style.
|
||||
* Duplicate styles won't be registered more than once.
|
||||
*
|
||||
* @param \Box\Spout\Writer\Style\Style $style The style to be registered
|
||||
* @return \Box\Spout\Writer\Style\Style The registered style, updated with an internal ID.
|
||||
*/
|
||||
public function registerStyle($style)
|
||||
{
|
||||
$serializedStyle = $style->serialize();
|
||||
|
||||
if (!$this->hasStyleAlreadyBeenRegistered($style)) {
|
||||
$nextStyleId = count($this->serializedStyleToStyleIdMappingTable);
|
||||
$style->setId($nextStyleId);
|
||||
|
||||
$this->serializedStyleToStyleIdMappingTable[$serializedStyle] = $nextStyleId;
|
||||
$this->styleIdToStyleMappingTable[$nextStyleId] = $style;
|
||||
}
|
||||
|
||||
return $this->getStyleFromSerializedStyle($serializedStyle);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given style has already been registered.
|
||||
*
|
||||
* @param \Box\Spout\Writer\Style\Style $style
|
||||
* @return bool
|
||||
*/
|
||||
protected function hasStyleAlreadyBeenRegistered($style)
|
||||
{
|
||||
$serializedStyle = $style->serialize();
|
||||
|
||||
// Using isset here because it is way faster than array_key_exists...
|
||||
return isset($this->serializedStyleToStyleIdMappingTable[$serializedStyle]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the registered style associated to the given serialization.
|
||||
*
|
||||
* @param string $serializedStyle The serialized style from which the actual style should be fetched from
|
||||
* @return \Box\Spout\Writer\Style\Style
|
||||
*/
|
||||
protected function getStyleFromSerializedStyle($serializedStyle)
|
||||
{
|
||||
$styleId = $this->serializedStyleToStyleIdMappingTable[$serializedStyle];
|
||||
return $this->styleIdToStyleMappingTable[$styleId];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return \Box\Spout\Writer\Style\Style[] List of registered styles
|
||||
*/
|
||||
protected function getRegisteredStyles()
|
||||
{
|
||||
return array_values($this->styleIdToStyleMappingTable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default style
|
||||
*
|
||||
* @return \Box\Spout\Writer\Style\Style Default style
|
||||
*/
|
||||
protected function getDefaultStyle()
|
||||
{
|
||||
// By construction, the default style has ID 0
|
||||
return $this->styleIdToStyleMappingTable[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply additional styles if the given row needs it.
|
||||
* Typically, set "wrap text" if a cell contains a new line.
|
||||
*
|
||||
* @param \Box\Spout\Writer\Style\Style $style The original style
|
||||
* @param array $dataRow The row the style will be applied to
|
||||
* @return \Box\Spout\Writer\Style\Style The updated style
|
||||
*/
|
||||
public function applyExtraStylesIfNeeded($style, $dataRow)
|
||||
{
|
||||
$updatedStyle = $this->applyWrapTextIfCellContainsNewLine($style, $dataRow);
|
||||
return $updatedStyle;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the "wrap text" option if a cell of the given row contains a new line.
|
||||
*
|
||||
* @NOTE: There is a bug on the Mac version of Excel (2011 and below) where new lines
|
||||
* are ignored even when the "wrap text" option is set. This only occurs with
|
||||
* inline strings (shared strings do work fine).
|
||||
* A workaround would be to encode "\n" as "_x000D_" but it does not work
|
||||
* on the Windows version of Excel...
|
||||
*
|
||||
* @param \Box\Spout\Writer\Style\Style $style The original style
|
||||
* @param array $dataRow The row the style will be applied to
|
||||
* @return \Box\Spout\Writer\Style\Style The eventually updated style
|
||||
*/
|
||||
protected function applyWrapTextIfCellContainsNewLine($style, $dataRow)
|
||||
{
|
||||
// if the "wrap text" option is already set, no-op
|
||||
if ($style->hasSetWrapText()) {
|
||||
return $style;
|
||||
}
|
||||
|
||||
foreach ($dataRow as $cell) {
|
||||
if (is_string($cell) && strpos($cell, "\n") !== false) {
|
||||
$style->setShouldWrapText();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $style;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace Box\Spout\Writer\Common\Helper;
|
||||
|
||||
/**
|
||||
* Class CellHelper
|
||||
* This class provides helper functions when working with cells
|
||||
*
|
||||
* @package Box\Spout\Writer\Common\Helper
|
||||
*/
|
||||
class CellHelper
|
||||
{
|
||||
/** @var array Cache containing the mapping column index => cell index */
|
||||
private static $columnIndexToCellIndexCache = [];
|
||||
|
||||
/**
|
||||
* Returns the cell index (base 26) associated to the base 10 column index.
|
||||
* Excel uses A to Z letters for column indexing, where A is the 1st column,
|
||||
* Z is the 26th and AA is the 27th.
|
||||
* The mapping is zero based, so that 0 maps to A, B maps to 1, Z to 25 and AA to 26.
|
||||
*
|
||||
* @param int $columnIndex The Excel column index (0, 42, ...)
|
||||
* @return string The associated cell index ('A', 'BC', ...)
|
||||
*/
|
||||
public static function getCellIndexFromColumnIndex($columnIndex)
|
||||
{
|
||||
$originalColumnIndex = $columnIndex;
|
||||
|
||||
// Using isset here because it is way faster than array_key_exists...
|
||||
if (!isset(self::$columnIndexToCellIndexCache[$originalColumnIndex])) {
|
||||
$cellIndex = '';
|
||||
$capitalAAsciiValue = ord('A');
|
||||
|
||||
do {
|
||||
$modulus = $columnIndex % 26;
|
||||
$cellIndex = chr($capitalAAsciiValue + $modulus) . $cellIndex;
|
||||
|
||||
// substracting 1 because it's zero-based
|
||||
$columnIndex = intval($columnIndex / 26) - 1;
|
||||
|
||||
} while ($columnIndex >= 0);
|
||||
|
||||
self::$columnIndexToCellIndexCache[$originalColumnIndex] = $cellIndex;
|
||||
}
|
||||
|
||||
return self::$columnIndexToCellIndexCache[$originalColumnIndex];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @return bool Whether the given value is considered "empty"
|
||||
*/
|
||||
public static function isEmpty($value)
|
||||
{
|
||||
return ($value === null || $value === '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $value
|
||||
* @return bool Whether the given value is a non empty string
|
||||
*/
|
||||
public static function isNonEmptyString($value)
|
||||
{
|
||||
return (gettype($value) === 'string' && $value !== '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given value is numeric.
|
||||
* A numeric value is from type "integer" or "double" ("float" is not returned by gettype).
|
||||
*
|
||||
* @param $value
|
||||
* @return bool Whether the given value is numeric
|
||||
*/
|
||||
public static function isNumeric($value)
|
||||
{
|
||||
$valueType = gettype($value);
|
||||
return ($valueType === 'integer' || $valueType === 'double');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given value is boolean.
|
||||
* "true"/"false" and 0/1 are not booleans.
|
||||
*
|
||||
* @param $value
|
||||
* @return bool Whether the given value is boolean
|
||||
*/
|
||||
public static function isBoolean($value)
|
||||
{
|
||||
return gettype($value) === 'boolean';
|
||||
}
|
||||
}
|
||||
217
modules/x13import/tools/Spout/Writer/Common/Helper/ZipHelper.php
Normal file
217
modules/x13import/tools/Spout/Writer/Common/Helper/ZipHelper.php
Normal file
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
namespace Box\Spout\Writer\Common\Helper;
|
||||
|
||||
/**
|
||||
* Class ZipHelper
|
||||
* This class provides helper functions to create zip files
|
||||
*
|
||||
* @package Box\Spout\Writer\Common\Helper
|
||||
*/
|
||||
class ZipHelper
|
||||
{
|
||||
const ZIP_EXTENSION = '.zip';
|
||||
|
||||
/** Controls what to do when trying to add an existing file */
|
||||
const EXISTING_FILES_SKIP = 'skip';
|
||||
const EXISTING_FILES_OVERWRITE = 'overwrite';
|
||||
|
||||
/** @var string Path of the folder where the zip file will be created */
|
||||
protected $tmpFolderPath;
|
||||
|
||||
/** @var \ZipArchive The ZipArchive instance */
|
||||
protected $zip;
|
||||
|
||||
/**
|
||||
* @param string $tmpFolderPath Path of the temp folder where the zip file will be created
|
||||
*/
|
||||
public function __construct($tmpFolderPath)
|
||||
{
|
||||
$this->tmpFolderPath = $tmpFolderPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the already created ZipArchive instance or
|
||||
* creates one if none exists.
|
||||
*
|
||||
* @return \ZipArchive
|
||||
*/
|
||||
protected function createOrGetZip()
|
||||
{
|
||||
if (!isset($this->zip)) {
|
||||
$this->zip = new \ZipArchive();
|
||||
$zipFilePath = $this->getZipFilePath();
|
||||
|
||||
$this->zip->open($zipFilePath, \ZipArchive::CREATE|\ZipArchive::OVERWRITE);
|
||||
}
|
||||
|
||||
return $this->zip;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string Path where the zip file of the given folder will be created
|
||||
*/
|
||||
public function getZipFilePath()
|
||||
{
|
||||
return $this->tmpFolderPath . self::ZIP_EXTENSION;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given file, located under the given root folder to the archive.
|
||||
* The file will be compressed.
|
||||
*
|
||||
* Example of use:
|
||||
* addFileToArchive('/tmp/xlsx/foo', 'bar/baz.xml');
|
||||
* => will add the file located at '/tmp/xlsx/foo/bar/baz.xml' in the archive, but only as 'bar/baz.xml'
|
||||
*
|
||||
* @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
|
||||
* @param string $localFilePath Path of the file to be added, under the root folder
|
||||
* @param string|void $existingFileMode Controls what to do when trying to add an existing file
|
||||
* @return void
|
||||
*/
|
||||
public function addFileToArchive($rootFolderPath, $localFilePath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
|
||||
{
|
||||
$this->addFileToArchiveWithCompressionMethod(
|
||||
$rootFolderPath,
|
||||
$localFilePath,
|
||||
$existingFileMode,
|
||||
\ZipArchive::CM_DEFAULT
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given file, located under the given root folder to the archive.
|
||||
* The file will NOT be compressed.
|
||||
*
|
||||
* Example of use:
|
||||
* addUncompressedFileToArchive('/tmp/xlsx/foo', 'bar/baz.xml');
|
||||
* => will add the file located at '/tmp/xlsx/foo/bar/baz.xml' in the archive, but only as 'bar/baz.xml'
|
||||
*
|
||||
* @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
|
||||
* @param string $localFilePath Path of the file to be added, under the root folder
|
||||
* @param string|void $existingFileMode Controls what to do when trying to add an existing file
|
||||
* @return void
|
||||
*/
|
||||
public function addUncompressedFileToArchive($rootFolderPath, $localFilePath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
|
||||
{
|
||||
$this->addFileToArchiveWithCompressionMethod(
|
||||
$rootFolderPath,
|
||||
$localFilePath,
|
||||
$existingFileMode,
|
||||
\ZipArchive::CM_STORE
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds the given file, located under the given root folder to the archive.
|
||||
* The file will NOT be compressed.
|
||||
*
|
||||
* Example of use:
|
||||
* addUncompressedFileToArchive('/tmp/xlsx/foo', 'bar/baz.xml');
|
||||
* => will add the file located at '/tmp/xlsx/foo/bar/baz.xml' in the archive, but only as 'bar/baz.xml'
|
||||
*
|
||||
* @param string $rootFolderPath Path of the root folder that will be ignored in the archive tree.
|
||||
* @param string $localFilePath Path of the file to be added, under the root folder
|
||||
* @param string $existingFileMode Controls what to do when trying to add an existing file
|
||||
* @param int $compressionMethod The compression method
|
||||
* @return void
|
||||
*/
|
||||
protected function addFileToArchiveWithCompressionMethod($rootFolderPath, $localFilePath, $existingFileMode, $compressionMethod)
|
||||
{
|
||||
$zip = $this->createOrGetZip();
|
||||
|
||||
if (!$this->shouldSkipFile($zip, $localFilePath, $existingFileMode)) {
|
||||
$normalizedFullFilePath = $this->getNormalizedRealPath($rootFolderPath . '/' . $localFilePath);
|
||||
$zip->addFile($normalizedFullFilePath, $localFilePath);
|
||||
|
||||
if (self::canChooseCompressionMethod()) {
|
||||
$zip->setCompressionName($localFilePath, $compressionMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool Whether it is possible to choose the desired compression method to be used
|
||||
*/
|
||||
public static function canChooseCompressionMethod()
|
||||
{
|
||||
// setCompressionName() is a PHP7+ method...
|
||||
return (method_exists(new \ZipArchive(), 'setCompressionName'));
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $folderPath Path to the folder to be zipped
|
||||
* @param string|void $existingFileMode Controls what to do when trying to add an existing file
|
||||
* @return void
|
||||
*/
|
||||
public function addFolderToArchive($folderPath, $existingFileMode = self::EXISTING_FILES_OVERWRITE)
|
||||
{
|
||||
$zip = $this->createOrGetZip();
|
||||
|
||||
$folderRealPath = $this->getNormalizedRealPath($folderPath) . '/';
|
||||
$itemIterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($folderPath, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST);
|
||||
|
||||
foreach ($itemIterator as $itemInfo) {
|
||||
$itemRealPath = $this->getNormalizedRealPath($itemInfo->getPathname());
|
||||
$itemLocalPath = str_replace($folderRealPath, '', $itemRealPath);
|
||||
|
||||
if ($itemInfo->isFile() && !$this->shouldSkipFile($zip, $itemLocalPath, $existingFileMode)) {
|
||||
$zip->addFile($itemRealPath, $itemLocalPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \ZipArchive $zip
|
||||
* @param string $itemLocalPath
|
||||
* @param string $existingFileMode
|
||||
* @return bool Whether the file should be added to the archive or skipped
|
||||
*/
|
||||
protected function shouldSkipFile($zip, $itemLocalPath, $existingFileMode)
|
||||
{
|
||||
// Skip files if:
|
||||
// - EXISTING_FILES_SKIP mode chosen
|
||||
// - File already exists in the archive
|
||||
return ($existingFileMode === self::EXISTING_FILES_SKIP && $zip->locateName($itemLocalPath) !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns canonicalized absolute pathname, containing only forward slashes.
|
||||
*
|
||||
* @param string $path Path to normalize
|
||||
* @return string Normalized and canonicalized path
|
||||
*/
|
||||
protected function getNormalizedRealPath($path)
|
||||
{
|
||||
$realPath = realpath($path);
|
||||
return str_replace(DIRECTORY_SEPARATOR, '/', $realPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the archive and copies it into the given stream
|
||||
*
|
||||
* @param resource $streamPointer Pointer to the stream to copy the zip
|
||||
* @return void
|
||||
*/
|
||||
public function closeArchiveAndCopyToStream($streamPointer)
|
||||
{
|
||||
$zip = $this->createOrGetZip();
|
||||
$zip->close();
|
||||
unset($this->zip);
|
||||
|
||||
$this->copyZipToStream($streamPointer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Streams the contents of the zip file into the given stream
|
||||
*
|
||||
* @param resource $pointer Pointer to the stream to copy the zip
|
||||
* @return void
|
||||
*/
|
||||
protected function copyZipToStream($pointer)
|
||||
{
|
||||
$zipFilePointer = fopen($this->getZipFilePath(), 'r');
|
||||
stream_copy_to_stream($zipFilePointer, $pointer);
|
||||
fclose($zipFilePointer);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user