first commit

This commit is contained in:
2024-11-05 12:22:50 +01:00
commit e5682a3912
19641 changed files with 2948548 additions and 0 deletions

View File

@@ -0,0 +1,175 @@
<?php
namespace Box\Spout\Common\Helper;
use Box\Spout\Common\Exception\EncodingConversionException;
/**
* Class EncodingHelper
* This class provides helper functions to work with encodings.
*
* @package Box\Spout\Common\Helper
*/
class EncodingHelper
{
/** Definition of the encodings that can have a BOM */
const ENCODING_UTF8 = 'UTF-8';
const ENCODING_UTF16_LE = 'UTF-16LE';
const ENCODING_UTF16_BE = 'UTF-16BE';
const ENCODING_UTF32_LE = 'UTF-32LE';
const ENCODING_UTF32_BE = 'UTF-32BE';
/** Definition of the BOMs for the different encodings */
const BOM_UTF8 = "\xEF\xBB\xBF";
const BOM_UTF16_LE = "\xFF\xFE";
const BOM_UTF16_BE = "\xFE\xFF";
const BOM_UTF32_LE = "\xFF\xFE\x00\x00";
const BOM_UTF32_BE = "\x00\x00\xFE\xFF";
/** @var \Box\Spout\Common\Helper\GlobalFunctionsHelper Helper to work with global functions */
protected $globalFunctionsHelper;
/** @var array Map representing the encodings supporting BOMs (key) and their associated BOM (value) */
protected $supportedEncodingsWithBom;
/**
* @param \Box\Spout\Common\Helper\GlobalFunctionsHelper $globalFunctionsHelper
*/
public function __construct($globalFunctionsHelper)
{
$this->globalFunctionsHelper = $globalFunctionsHelper;
$this->supportedEncodingsWithBom = [
self::ENCODING_UTF8 => self::BOM_UTF8,
self::ENCODING_UTF16_LE => self::BOM_UTF16_LE,
self::ENCODING_UTF16_BE => self::BOM_UTF16_BE,
self::ENCODING_UTF32_LE => self::BOM_UTF32_LE,
self::ENCODING_UTF32_BE => self::BOM_UTF32_BE,
];
}
/**
* Returns the number of bytes to use as offset in order to skip the BOM.
*
* @param resource $filePointer Pointer to the file to check
* @param string $encoding Encoding of the file to check
* @return int Bytes offset to apply to skip the BOM (0 means no BOM)
*/
public function getBytesOffsetToSkipBOM($filePointer, $encoding)
{
$byteOffsetToSkipBom = 0;
if ($this->hasBOM($filePointer, $encoding)) {
$bomUsed = $this->supportedEncodingsWithBom[$encoding];
// we skip the N first bytes
$byteOffsetToSkipBom = strlen($bomUsed);
}
return $byteOffsetToSkipBom;
}
/**
* Returns whether the file identified by the given pointer has a BOM.
*
* @param resource $filePointer Pointer to the file to check
* @param string $encoding Encoding of the file to check
* @return bool TRUE if the file has a BOM, FALSE otherwise
*/
protected function hasBOM($filePointer, $encoding)
{
$hasBOM = false;
$this->globalFunctionsHelper->rewind($filePointer);
if (array_key_exists($encoding, $this->supportedEncodingsWithBom)) {
$potentialBom = $this->supportedEncodingsWithBom[$encoding];
$numBytesInBom = strlen($potentialBom);
$hasBOM = ($this->globalFunctionsHelper->fgets($filePointer, $numBytesInBom + 1) === $potentialBom);
}
return $hasBOM;
}
/**
* Attempts to convert a non UTF-8 string into UTF-8.
*
* @param string $string Non UTF-8 string to be converted
* @param string $sourceEncoding The encoding used to encode the source string
* @return string The converted, UTF-8 string
* @throws \Box\Spout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed
*/
public function attemptConversionToUTF8($string, $sourceEncoding)
{
return $this->attemptConversion($string, $sourceEncoding, self::ENCODING_UTF8);
}
/**
* Attempts to convert a UTF-8 string into the given encoding.
*
* @param string $string UTF-8 string to be converted
* @param string $targetEncoding The encoding the string should be re-encoded into
* @return string The converted string, encoded with the given encoding
* @throws \Box\Spout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed
*/
public function attemptConversionFromUTF8($string, $targetEncoding)
{
return $this->attemptConversion($string, self::ENCODING_UTF8, $targetEncoding);
}
/**
* Attempts to convert the given string to the given encoding.
* Depending on what is installed on the server, we will try to iconv or mbstring.
*
* @param string $string string to be converted
* @param string $sourceEncoding The encoding used to encode the source string
* @param string $targetEncoding The encoding the string should be re-encoded into
* @return string The converted string, encoded with the given encoding
* @throws \Box\Spout\Common\Exception\EncodingConversionException If conversion is not supported or if the conversion failed
*/
protected function attemptConversion($string, $sourceEncoding, $targetEncoding)
{
// if source and target encodings are the same, it's a no-op
if ($sourceEncoding === $targetEncoding) {
return $string;
}
$convertedString = null;
if ($this->canUseIconv()) {
$convertedString = $this->globalFunctionsHelper->iconv($string, $sourceEncoding, $targetEncoding);
} else if ($this->canUseMbString()) {
$convertedString = $this->globalFunctionsHelper->mb_convert_encoding($string, $sourceEncoding, $targetEncoding);
} else {
throw new EncodingConversionException("The conversion from $sourceEncoding to $targetEncoding is not supported. Please install \"iconv\" or \"PHP Intl\".");
}
if ($convertedString === false) {
throw new EncodingConversionException("The conversion from $sourceEncoding to $targetEncoding failed.");
}
return $convertedString;
}
/**
* Returns whether "iconv" can be used.
*
* @return bool TRUE if "iconv" is available and can be used, FALSE otherwise
*/
protected function canUseIconv()
{
return $this->globalFunctionsHelper->function_exists('iconv');
}
/**
* Returns whether "mb_string" functions can be used.
* These functions come with the PHP Intl package.
*
* @return bool TRUE if "mb_string" functions are available and can be used, FALSE otherwise
*/
protected function canUseMbString()
{
return $this->globalFunctionsHelper->function_exists('mb_convert_encoding');
}
}

View File

@@ -0,0 +1,133 @@
<?php
namespace Box\Spout\Common\Helper;
use Box\Spout\Common\Exception\IOException;
/**
* Class FileSystemHelper
* This class provides helper functions to help with the file system operations
* like files/folders creation & deletion
*
* @package Box\Spout\Common\Helper
*/
class FileSystemHelper
{
/** @var string Real path of the base folder where all the I/O can occur */
protected $baseFolderRealPath;
/**
* @param string $baseFolderPath The path of the base folder where all the I/O can occur
*/
public function __construct($baseFolderPath)
{
$this->baseFolderRealPath = realpath($baseFolderPath);
}
/**
* Creates an empty folder with the given name under the given parent folder.
*
* @param string $parentFolderPath The parent folder path under which the folder is going to be created
* @param string $folderName The name of the folder to create
* @return string Path of the created folder
* @throws \Box\Spout\Common\Exception\IOException If unable to create the folder or if the folder path is not inside of the base folder
*/
public function createFolder($parentFolderPath, $folderName)
{
$this->throwIfOperationNotInBaseFolder($parentFolderPath);
$folderPath = $parentFolderPath . '/' . $folderName;
$wasCreationSuccessful = mkdir($folderPath, 0777, true);
if (!$wasCreationSuccessful) {
throw new IOException("Unable to create folder: $folderPath");
}
return $folderPath;
}
/**
* Creates a file with the given name and content in the given folder.
* The parent folder must exist.
*
* @param string $parentFolderPath The parent folder path where the file is going to be created
* @param string $fileName The name of the file to create
* @param string $fileContents The contents of the file to create
* @return string Path of the created file
* @throws \Box\Spout\Common\Exception\IOException If unable to create the file or if the file path is not inside of the base folder
*/
public function createFileWithContents($parentFolderPath, $fileName, $fileContents)
{
$this->throwIfOperationNotInBaseFolder($parentFolderPath);
$filePath = $parentFolderPath . '/' . $fileName;
$wasCreationSuccessful = file_put_contents($filePath, $fileContents);
if ($wasCreationSuccessful === false) {
throw new IOException("Unable to create file: $filePath");
}
return $filePath;
}
/**
* Delete the file at the given path
*
* @param string $filePath Path of the file to delete
* @return void
* @throws \Box\Spout\Common\Exception\IOException If the file path is not inside of the base folder
*/
public function deleteFile($filePath)
{
$this->throwIfOperationNotInBaseFolder($filePath);
if (file_exists($filePath) && is_file($filePath)) {
unlink($filePath);
}
}
/**
* Delete the folder at the given path as well as all its contents
*
* @param string $folderPath Path of the folder to delete
* @return void
* @throws \Box\Spout\Common\Exception\IOException If the folder path is not inside of the base folder
*/
public function deleteFolderRecursively($folderPath)
{
$this->throwIfOperationNotInBaseFolder($folderPath);
$itemIterator = new \RecursiveIteratorIterator(
new \RecursiveDirectoryIterator($folderPath, \RecursiveDirectoryIterator::SKIP_DOTS),
\RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($itemIterator as $item) {
if ($item->isDir()) {
rmdir($item->getPathname());
} else {
unlink($item->getPathname());
}
}
rmdir($folderPath);
}
/**
* All I/O operations must occur inside the base folder, for security reasons.
* This function will throw an exception if the folder where the I/O operation
* should occur is not inside the base folder.
*
* @param string $operationFolderPath The path of the folder where the I/O operation should occur
* @return void
* @throws \Box\Spout\Common\Exception\IOException If the folder where the I/O operation should occur is not inside the base folder
*/
protected function throwIfOperationNotInBaseFolder($operationFolderPath)
{
$operationFolderRealPath = realpath($operationFolderPath);
$isInBaseFolder = (strpos($operationFolderRealPath, $this->baseFolderRealPath) === 0);
if (!$isInBaseFolder) {
throw new IOException("Cannot perform I/O operation outside of the base folder: {$this->baseFolderRealPath}");
}
}
}

View File

@@ -0,0 +1,318 @@
<?php
namespace Box\Spout\Common\Helper;
/**
* Class GlobalFunctionsHelper
* This class wraps global functions to facilitate testing
*
* @codeCoverageIgnore
*
* @package Box\Spout\Common\Helper
*/
class GlobalFunctionsHelper
{
/**
* Wrapper around global function fopen()
* @see fopen()
*
* @param string $fileName
* @param string $mode
* @return resource|bool
*/
public function fopen($fileName, $mode)
{
return fopen($fileName, $mode);
}
/**
* Wrapper around global function fgets()
* @see fgets()
*
* @param resource $handle
* @param int|void $length
* @return string
*/
public function fgets($handle, $length = null)
{
return fgets($handle, $length);
}
/**
* Wrapper around global function fputs()
* @see fputs()
*
* @param resource $handle
* @param string $string
* @return int
*/
public function fputs($handle, $string)
{
return fputs($handle, $string);
}
/**
* Wrapper around global function fflush()
* @see fflush()
*
* @param resource $handle
* @return bool
*/
public function fflush($handle)
{
return fflush($handle);
}
/**
* Wrapper around global function fseek()
* @see fseek()
*
* @param resource $handle
* @param int $offset
* @return int
*/
public function fseek($handle, $offset)
{
return fseek($handle, $offset);
}
/**
* Wrapper around global function fgetcsv()
* @see fgetcsv()
*
* @param resource $handle
* @param int|void $length
* @param string|void $delimiter
* @param string|void $enclosure
* @return array
*/
public function fgetcsv($handle, $length = null, $delimiter = null, $enclosure = null)
{
return fgetcsv($handle, $length, $delimiter, $enclosure);
}
/**
* Wrapper around global function fputcsv()
* @see fputcsv()
*
* @param resource $handle
* @param array $fields
* @param string|void $delimiter
* @param string|void $enclosure
* @return int
*/
public function fputcsv($handle, array $fields, $delimiter = null, $enclosure = null)
{
return fputcsv($handle, $fields, $delimiter, $enclosure);
}
/**
* Wrapper around global function fwrite()
* @see fwrite()
*
* @param resource $handle
* @param string $string
* @return int
*/
public function fwrite($handle, $string)
{
return fwrite($handle, $string);
}
/**
* Wrapper around global function fclose()
* @see fclose()
*
* @param resource $handle
* @return bool
*/
public function fclose($handle)
{
return fclose($handle);
}
/**
* Wrapper around global function rewind()
* @see rewind()
*
* @param resource $handle
* @return bool
*/
public function rewind($handle)
{
return rewind($handle);
}
/**
* Wrapper around global function file_exists()
* @see file_exists()
*
* @param string $fileName
* @return bool
*/
public function file_exists($fileName)
{
return file_exists($fileName);
}
/**
* Wrapper around global function file_get_contents()
* @see file_get_contents()
*
* @param string $filePath
* @return string
*/
public function file_get_contents($filePath)
{
$realFilePath = $this->convertToUseRealPath($filePath);
return file_get_contents($realFilePath);
}
/**
* Updates the given file path to use a real path.
* This is to avoid issues on some Windows setup.
*
* @param string $filePath File path
* @return string The file path using a real path
*/
protected function convertToUseRealPath($filePath)
{
$realFilePath = $filePath;
if ($this->isZipStream($filePath)) {
if (preg_match('/zip:\/\/(.*)#(.*)/', $filePath, $matches)) {
$documentPath = $matches[1];
$documentInsideZipPath = $matches[2];
$realFilePath = 'zip://' . realpath($documentPath) . '#' . $documentInsideZipPath;
}
} else {
$realFilePath = realpath($filePath);
}
return $realFilePath;
}
/**
* Returns whether the given path is a zip stream.
*
* @param string $path Path pointing to a document
* @return bool TRUE if path is a zip stream, FALSE otherwise
*/
protected function isZipStream($path)
{
return (strpos($path, 'zip://') === 0);
}
/**
* Wrapper around global function feof()
* @see feof()
*
* @param resource
* @return bool
*/
public function feof($handle)
{
return feof($handle);
}
/**
* Wrapper around global function is_readable()
* @see is_readable()
*
* @param string $fileName
* @return bool
*/
public function is_readable($fileName)
{
return is_readable($fileName);
}
/**
* Wrapper around global function basename()
* @see basename()
*
* @param string $path
* @param string|void $suffix
* @return string
*/
public function basename($path, $suffix = null)
{
return basename($path, $suffix);
}
/**
* Wrapper around global function header()
* @see header()
*
* @param string $string
* @return void
*/
public function header($string)
{
header($string);
}
/**
* Wrapper around global function ob_end_clean()
* @see ob_end_clean()
*
* @return void
*/
public function ob_end_clean()
{
if (ob_get_length() > 0) {
ob_end_clean();
}
}
/**
* Wrapper around global function iconv()
* @see iconv()
*
* @param string $string The string to be converted
* @param string $sourceEncoding The encoding of the source string
* @param string $targetEncoding The encoding the source string should be converted to
* @return string|bool the converted string or FALSE on failure.
*/
public function iconv($string, $sourceEncoding, $targetEncoding)
{
return iconv($sourceEncoding, $targetEncoding, $string);
}
/**
* Wrapper around global function mb_convert_encoding()
* @see mb_convert_encoding()
*
* @param string $string The string to be converted
* @param string $sourceEncoding The encoding of the source string
* @param string $targetEncoding The encoding the source string should be converted to
* @return string|bool the converted string or FALSE on failure.
*/
public function mb_convert_encoding($string, $sourceEncoding, $targetEncoding)
{
return mb_convert_encoding($string, $targetEncoding, $sourceEncoding);
}
/**
* Wrapper around global function stream_get_wrappers()
* @see stream_get_wrappers()
*
* @return array
*/
public function stream_get_wrappers()
{
return stream_get_wrappers();
}
/**
* Wrapper around global function function_exists()
* @see function_exists()
*
* @param string $functionName
* @return bool
*/
public function function_exists($functionName)
{
return function_exists($functionName);
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Box\Spout\Common\Helper;
/**
* Class StringHelper
* This class provides helper functions to work with strings and multibyte strings.
*
* @codeCoverageIgnore
*
* @package Box\Spout\Common\Helper
*/
class StringHelper
{
/** @var bool Whether the mbstring extension is loaded */
protected $hasMbstringSupport;
/**
*
*/
public function __construct()
{
$this->hasMbstringSupport = extension_loaded('mbstring');
}
/**
* Returns the length of the given string.
* It uses the multi-bytes function is available.
* @see strlen
* @see mb_strlen
*
* @param string $string
* @return int
*/
public function getStringLength($string)
{
return $this->hasMbstringSupport ? mb_strlen($string) : strlen($string);
}
/**
* Returns the position of the first occurrence of the given character/substring within the given string.
* It uses the multi-bytes function is available.
* @see strpos
* @see mb_strpos
*
* @param string $char Needle
* @param string $string Haystack
* @return int Char/substring's first occurrence position within the string if found (starts at 0) or -1 if not found
*/
public function getCharFirstOccurrencePosition($char, $string)
{
$position = $this->hasMbstringSupport ? mb_strpos($string, $char) : strpos($string, $char);
return ($position !== false) ? $position : -1;
}
/**
* Returns the position of the last occurrence of the given character/substring within the given string.
* It uses the multi-bytes function is available.
* @see strrpos
* @see mb_strrpos
*
* @param string $char Needle
* @param string $string Haystack
* @return int Char/substring's last occurrence position within the string if found (starts at 0) or -1 if not found
*/
public function getCharLastOccurrencePosition($char, $string)
{
$position = $this->hasMbstringSupport ? mb_strrpos($string, $char) : strrpos($string, $char);
return ($position !== false) ? $position : -1;
}
}