Make decryption actually work incl. decompression
[epubpypt.git] / src / ZipWrapper.php
1 <?php
2 namespace Epubpypt;
3
4 /**
5  * .zip file wrapper that works with directories (unzipped files)
6  */
7 class ZipWrapper
8 {
9     /**
10      * Path to .zip file (null when directory)
11      */
12     protected $zipPath;
13
14     /**
15      * Directory path with trailing slash (null when zip)
16      */
17     protected $dirPath;
18
19     /**
20      * @var ZipArchive
21      */
22     protected $zip;
23
24     public function __construct($path, $readOnly = true)
25     {
26         if (is_dir($path)) {
27             $this->dirPath = rtrim($path, '/') . '/';
28         } else {
29             $this->zipPath = $path;
30             $this->zip = new \ZipArchive();
31             $res = $this->zip->open(
32                 $this->zipPath,
33                 $readOnly ? \ZipArchive::RDONLY : 0
34             );
35             if ($res !== true) {
36                 $errorMap = [
37                     \ZipArchive::ER_EXISTS => 'File already exists',
38                     \ZipArchive::ER_INCONS => 'Zip archive inconsistent',
39                     \ZipArchive::ER_INVAL  => 'Invalid argument',
40                     \ZipArchive::ER_MEMORY => 'Malloc failure',
41                     \ZipArchive::ER_NOENT  => 'No such file',
42                     \ZipArchive::ER_NOZIP  => 'Not a zip archive',
43                     \ZipArchive::ER_OPEN   => 'Can\'t open file',
44                     \ZipArchive::ER_READ   => 'Read error',
45                     \ZipArchive::ER_SEEK   => 'Seek error',
46                 ];
47                 throw new \Exception(
48                     'Error opening .epub file: ' . $errorMap[$res]
49                 );
50             }
51         }
52     }
53
54     /**
55      * (Over)write content of single file inside the zip archive
56      */
57     public function addFromString($filePath, $content): bool
58     {
59         if ($this->zip) {
60             return $this->zip->addFromString($filePath, $content);
61         } else {
62             return file_put_contents($this->dirPath . $filePath, $content) !== false;
63         }
64     }
65
66     /**
67      * Write ZIP archive contents
68      */
69     public function close(): bool
70     {
71         if ($this->zip) {
72             return $this->zip->close();
73         } else {
74             return true;
75         }
76     }
77
78     /**
79      * Delete a file
80      */
81     public function deleteName($filePath): bool
82     {
83         if ($this->zip) {
84             return $this->zip->deleteName($filePath);
85         } else {
86             return unlink($this->dirPath . $filePath);
87         }
88     }
89
90     /**
91      * Get content of given file path
92      *
93      * @return string|false false on error
94      */
95     public function getFromName($filePath)
96     {
97         if ($this->zip) {
98             return $this->zip->getFromName($filePath);
99         } else {
100             return file_get_contents($this->dirPath . $filePath);
101         }
102     }
103
104     /**
105      * foreach() over all files
106      *
107      * @return array|Iterator
108      */
109     public function filesIterator()
110     {
111         if ($this->zip) {
112             for ($n = 0; $n < $this->zip->numFiles; $n++) {
113                 $info = $this->zip->statIndex($n);
114                 yield $info['name'];
115             }
116         } else {
117             $it = new \RecursiveIteratorIterator(
118                 new \RecursiveDirectoryIterator($this->dirPath)
119             );
120             $it->rewind();
121             while($it->valid()) {
122                 if ($it->isFile()) {
123                     yield substr($it->getPathname(), strlen($this->dirPath));
124                 }
125                 $it->next();
126             }
127         }
128     }
129 }