aboutsummaryrefslogtreecommitdiffstats
path: root/vendor/mikespub/php-epub-meta/src/Tools/ZipFile.php
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/mikespub/php-epub-meta/src/Tools/ZipFile.php')
-rw-r--r--vendor/mikespub/php-epub-meta/src/Tools/ZipFile.php343
1 files changed, 343 insertions, 0 deletions
diff --git a/vendor/mikespub/php-epub-meta/src/Tools/ZipFile.php b/vendor/mikespub/php-epub-meta/src/Tools/ZipFile.php
new file mode 100644
index 000000000..b083b0c4f
--- /dev/null
+++ b/vendor/mikespub/php-epub-meta/src/Tools/ZipFile.php
@@ -0,0 +1,343 @@
+<?php
+/**
+ * ZipFile class
+ *
+ * @license GPL 2 (http://www.gnu.org/licenses/gpl.html)
+ * @author Didier Corbière <contact@atoll-digital-library.org>
+ * @author mikespub
+ */
+
+namespace SebLucas\EPubMeta\Tools;
+
+use Exception;
+use ZipArchive;
+
+/**
+ * ZipFile class allows to open files inside a zip file with the standard php zip functions
+ *
+ * This class also supports adding/replacing/deleting files inside the zip file, but changes
+ * will *not* be reflected correctly until you close the zip file, and open it again if needed
+ *
+ * Note: this is not meant to handle a massive amount of files inside generic archive files.
+ * It is specifically meant for EPUB files which typically contain hundreds/thousands of pages,
+ * not millions or more. And any changes you make are kept in memory, so don't re-write every
+ * page of War and Peace either - better to unzip that locally and then re-zip it afterwards.
+ */
+class ZipFile
+{
+ public const DOWNLOAD = 1; // download (default)
+ public const NOHEADER = 4; // option to use with DOWNLOAD: no header is sent
+ public const FILE = 8; // output to file , or add from file
+ public const STRING = 32; // output to string, or add from string
+ public const MIME_TYPE = 'application/epub+zip';
+
+ /** @var ZipArchive|null */
+ protected $mZip;
+ /** @var array<string, mixed>|null */
+ protected $mEntries;
+ /** @var array<string, mixed> */
+ protected $mChanges = [];
+ /** @var string|null */
+ protected $mFileName;
+
+ public function __construct()
+ {
+ $this->mZip = null;
+ $this->mEntries = null;
+ $this->mFileName = null;
+ }
+
+ /**
+ * Destructor
+ */
+ public function __destruct()
+ {
+ $this->Close();
+ }
+
+ /**
+ * Open a zip file and read it's entries
+ *
+ * @param string $inFileName
+ * @param int|null $inFlags
+ * @return boolean True if zip file has been correctly opended, else false
+ */
+ public function Open($inFileName, $inFlags = 0) // ZipArchive::RDONLY)
+ {
+ $this->Close();
+ $inFileName = realpath($inFileName);
+
+ $this->mZip = new ZipArchive();
+ $result = $this->mZip->open($inFileName, $inFlags);
+ if ($result !== true) {
+ return false;
+ }
+
+ $this->mFileName = $inFileName;
+
+ $this->mEntries = [];
+ $this->mChanges = [];
+
+ for ($i = 0; $i < $this->mZip->numFiles; $i++) {
+ $entry = $this->mZip->statIndex($i);
+ $fileName = $entry['name'];
+ $this->mEntries[$fileName] = $entry;
+ $this->mChanges[$fileName] = ['status' => 'unchanged'];
+ }
+
+ return true;
+ }
+
+ /**
+ * Check if a file exist in the zip entries
+ *
+ * @param string $inFileName File to search
+ *
+ * @return boolean True if the file exist, else false
+ */
+ public function FileExists($inFileName)
+ {
+ if (!isset($this->mZip)) {
+ return false;
+ }
+
+ if (!isset($this->mEntries[$inFileName])) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Read the content of a file in the zip entries
+ *
+ * @param string $inFileName File to search
+ *
+ * @return mixed File content the file exist, else false
+ */
+ public function FileRead($inFileName)
+ {
+ if (!isset($this->mZip)) {
+ return false;
+ }
+
+ if (!isset($this->mEntries[$inFileName])) {
+ return false;
+ }
+
+ $data = false;
+
+ $changes = $this->mChanges[$inFileName] ?? ['status' => 'unchanged'];
+ switch ($changes['status']) {
+ case 'unchanged':
+ $data = $this->mZip->getFromName($inFileName);
+ break;
+ case 'added':
+ case 'modified':
+ if (isset($changes['data'])) {
+ $data = $changes['data'];
+ } elseif (isset($changes['path'])) {
+ $data = file_get_contents($changes['path']);
+ }
+ break;
+ case 'deleted':
+ default:
+ break;
+ }
+ return $data;
+ }
+
+ /**
+ * Get a file handler to a file in the zip entries (read-only)
+ *
+ * @param string $inFileName File to search
+ *
+ * @return resource|bool File handler if the file exist, else false
+ */
+ public function FileStream($inFileName)
+ {
+ if (!isset($this->mZip)) {
+ return false;
+ }
+
+ if (!isset($this->mEntries[$inFileName])) {
+ return false;
+ }
+
+ // @todo streaming of added/modified data?
+ return $this->mZip->getStream($inFileName);
+ }
+
+ /**
+ * Summary of FileAdd
+ * @param string $inFileName
+ * @param mixed $inData
+ * @return bool
+ */
+ public function FileAdd($inFileName, $inData)
+ {
+ if (!isset($this->mZip)) {
+ return false;
+ }
+
+ if (!$this->mZip->addFromString($inFileName, $inData)) {
+ return false;
+ }
+ $this->mEntries[$inFileName] = $this->mZip->statName($inFileName);
+ $this->mChanges[$inFileName] = ['status' => 'added', 'data' => $inData];
+ return true;
+ }
+
+ /**
+ * Summary of FileAddPath
+ * @param string $inFileName
+ * @param string $inFilePath
+ * @return mixed
+ */
+ public function FileAddPath($inFileName, $inFilePath)
+ {
+ if (!isset($this->mZip)) {
+ return false;
+ }
+
+ if (!$this->mZip->addFile($inFilePath, $inFileName)) {
+ return false;
+ }
+ $this->mEntries[$inFileName] = $this->mZip->statName($inFileName);
+ $this->mChanges[$inFileName] = ['status' => 'added', 'path' => $inFilePath];
+ return true;
+ }
+
+ /**
+ * Summary of FileDelete
+ * @param string $inFileName
+ * @return bool
+ */
+ public function FileDelete($inFileName)
+ {
+ if (!$this->FileExists($inFileName)) {
+ return false;
+ }
+
+ if (!$this->mZip->deleteName($inFileName)) {
+ return false;
+ }
+ unset($this->mEntries[$inFileName]);
+ $this->mChanges[$inFileName] = ['status' => 'deleted'];
+ return true;
+ }
+
+ /**
+ * Replace the content of a file in the zip entries
+ *
+ * @param string $inFileName File with content to replace
+ * @param string|bool $inData Data content to replace, or false to delete
+ * @return bool
+ */
+ public function FileReplace($inFileName, $inData)
+ {
+ if (!isset($this->mZip)) {
+ return false;
+ }
+
+ if ($inData === false) {
+ return $this->FileDelete($inFileName);
+ }
+
+ if (!$this->mZip->addFromString($inFileName, $inData)) {
+ return false;
+ }
+ $this->mEntries[$inFileName] = $this->mZip->statName($inFileName);
+ $this->mChanges[$inFileName] = ['status' => 'modified', 'data' => $inData];
+ return true;
+ }
+
+ /**
+ * Return the state of the file.
+ * @param mixed $inFileName
+ * @return string|bool 'u'=unchanged, 'm'=modified, 'd'=deleted, 'a'=added, false=unknown
+ */
+ public function FileGetState($inFileName)
+ {
+ $changes = $this->mChanges[$inFileName] ?? ['status' => false];
+ return $changes['status'];
+ }
+
+ /**
+ * Summary of FileCancelModif
+ * @param string $inFileName
+ * @param bool $ReplacedAndDeleted
+ * @return int
+ */
+ public function FileCancelModif($inFileName, $ReplacedAndDeleted = true)
+ {
+ // cancel added, modified or deleted modifications on a file in the archive
+ // return the number of cancels
+
+ $nbr = 0;
+
+ if (!$this->mZip->unchangeName($inFileName)) {
+ return $nbr;
+ }
+ $nbr += 1;
+ $this->mChanges[$inFileName] = ['status' => 'unchanged'];
+ return $nbr;
+ }
+
+ /**
+ * Close the zip file
+ *
+ * @return void
+ * @throws Exception
+ */
+ public function Close()
+ {
+ if (!isset($this->mZip)) {
+ return;
+ }
+
+ if (!$this->mZip->close()) {
+ $status = $this->mZip->getStatusString();
+ $this->mZip = null;
+ throw new Exception($status);
+ }
+ $this->mZip = null;
+ }
+
+ /**
+ * Summary of Flush
+ * @param mixed $render
+ * @param mixed $outFileName
+ * @param mixed $contentType
+ * @param bool $sendHeaders
+ * @return void
+ */
+ public function Flush($render = self::DOWNLOAD, $outFileName = '', $contentType = '', $sendHeaders = true)
+ {
+ // we need to close the zip file to save all changes here - probably not what you wanted :-()
+ $this->Close();
+
+ $outFileName = $outFileName ?: $this->mFileName;
+ $contentType = $contentType ?: static::MIME_TYPE;
+ if (!$sendHeaders) {
+ $render = $render | static::NOHEADER;
+ }
+ $inFilePath = realpath($this->mFileName);
+
+ if (($render & static::NOHEADER) !== static::NOHEADER) {
+ $expires = 60 * 60 * 24 * 14;
+ header('Pragma: public');
+ header('Cache-Control: max-age=' . $expires);
+ header('Expires: ' . gmdate('D, d M Y H:i:s', time() + $expires) . ' GMT');
+
+ header('Content-Type: ' . $contentType);
+ header('Content-Disposition: attachment; filename="' . basename($outFileName) . '"');
+
+ // see fetch.php for use of Config::get('x_accel_redirect')
+ header('Content-Length: ' . filesize($inFilePath));
+ //header(Config::get('x_accel_redirect') . ': ' . $inFilePath);
+ }
+
+ readfile($inFilePath);
+ }
+}