Version 1
[yaffs-website] / vendor / alchemy / zippy / src / Adapter / ZipExtensionAdapter.php
1 <?php
2
3 /*
4  * This file is part of Zippy.
5  *
6  * (c) Alchemy <info@alchemy.fr>
7  *
8  * For the full copyright and license information, please view the LICENSE
9  * file that was distributed with this source code.
10  */
11
12 namespace Alchemy\Zippy\Adapter;
13
14 use Alchemy\Zippy\Adapter\Resource\ResourceInterface;
15 use Alchemy\Zippy\Adapter\Resource\ZipArchiveResource;
16 use Alchemy\Zippy\Adapter\VersionProbe\ZipExtensionVersionProbe;
17 use Alchemy\Zippy\Archive\Archive;
18 use Alchemy\Zippy\Archive\Member;
19 use Alchemy\Zippy\Exception\NotSupportedException;
20 use Alchemy\Zippy\Exception\RuntimeException;
21 use Alchemy\Zippy\Exception\InvalidArgumentException;
22 use Alchemy\Zippy\Resource\Resource as ZippyResource;
23 use Alchemy\Zippy\Resource\ResourceManager;
24
25 /**
26  * ZipExtensionAdapter allows you to create and extract files from archives
27  * using PHP Zip extension
28  *
29  * @see http://www.php.net/manual/en/book.zip.php
30  */
31 class ZipExtensionAdapter extends AbstractAdapter
32 {
33     private $errorCodesMapping = array(
34         \ZipArchive::ER_EXISTS => "File already exists",
35         \ZipArchive::ER_INCONS => "Zip archive inconsistent",
36         \ZipArchive::ER_INVAL  => "Invalid argument",
37         \ZipArchive::ER_MEMORY => "Malloc failure",
38         \ZipArchive::ER_NOENT  => "No such file",
39         \ZipArchive::ER_NOZIP  => "Not a zip archive",
40         \ZipArchive::ER_OPEN   => "Can't open file",
41         \ZipArchive::ER_READ   => "Read error",
42         \ZipArchive::ER_SEEK   => "Seek error"
43     );
44
45     public function __construct(ResourceManager $manager)
46     {
47         parent::__construct($manager);
48         $this->probe = new ZipExtensionVersionProbe();
49     }
50
51     /**
52      * @inheritdoc
53      */
54     protected function doListMembers(ResourceInterface $resource)
55     {
56         $members = array();
57         for ($i = 0; $i < $resource->getResource()->numFiles; $i++) {
58             $stat = $resource->getResource()->statIndex($i);
59             $members[] = new Member(
60                 $resource,
61                 $this,
62                 $stat['name'],
63                 $stat['size'],
64                 new \DateTime('@' . $stat['mtime']),
65                 0 === strlen($resource->getResource()->getFromIndex($i, 1))
66             );
67         }
68
69         return $members;
70     }
71
72     /**
73      * @inheritdoc
74      */
75     public static function getName()
76     {
77         return 'zip-extension';
78     }
79
80     /**
81      * @inheritdoc
82      */
83     protected function doExtract(ResourceInterface $resource, $to)
84     {
85         return $this->extractMembers($resource, null, $to);
86     }
87
88     /**
89      * @inheritdoc
90      */
91     protected function doExtractMembers(ResourceInterface $resource, $members, $to, $overwrite = false)
92     {
93         if (null === $to) {
94             // if no destination is given, will extract to zip current folder
95             $to = dirname(realpath($resource->getResource()->filename));
96         }
97
98         if (!is_dir($to)) {
99             $resource->getResource()->close();
100             throw new InvalidArgumentException(sprintf("%s is not a directory", $to));
101         }
102
103         if (!is_writable($to)) {
104             $resource->getResource()->close();
105             throw new InvalidArgumentException(sprintf("%s is not writable", $to));
106         }
107
108         if (null !== $members) {
109             $membersTemp = (array) $members;
110             if (empty($membersTemp)) {
111                 $resource->getResource()->close();
112
113                 throw new InvalidArgumentException("no members provided");
114             }
115             $members = array();
116             // allows $members to be an array of strings or array of Members
117             foreach ($membersTemp as $member) {
118                 if ($member instanceof Member) {
119                     $member = $member->getLocation();
120                 }
121
122                 if ($resource->getResource()->locateName($member) === false) {
123                     $resource->getResource()->close();
124
125                     throw new InvalidArgumentException(sprintf('%s is not in the zip file', $member));
126                 }
127
128                 if ($overwrite == false) {
129                     if (file_exists($member)) {
130                         $resource->getResource()->close();
131
132                         throw new RuntimeException('Target file ' . $member . ' already exists.');
133                     }
134                 }
135
136                 $members[] = $member;
137             }
138         }
139
140         if (!$resource->getResource()->extractTo($to, $members)) {
141             $resource->getResource()->close();
142
143             throw new InvalidArgumentException(sprintf('Unable to extract archive : %s', $resource->getResource()->getStatusString()));
144         }
145
146         return new \SplFileInfo($to);
147     }
148
149     /**
150      * @inheritdoc
151      */
152     protected function doRemove(ResourceInterface $resource, $files)
153     {
154         $files = (array) $files;
155
156         if (empty($files)) {
157             throw new InvalidArgumentException("no files provided");
158         }
159
160         // either remove all files or none in case of error
161         foreach ($files as $file) {
162             if ($resource->getResource()->locateName($file) === false) {
163                 $resource->getResource()->unchangeAll();
164                 $resource->getResource()->close();
165
166                 throw new InvalidArgumentException(sprintf('%s is not in the zip file', $file));
167             }
168             if (!$resource->getResource()->deleteName($file)) {
169                 $resource->getResource()->unchangeAll();
170                 $resource->getResource()->close();
171
172                 throw new RuntimeException(sprintf('unable to remove %s', $file));
173             }
174         }
175         $this->flush($resource->getResource());
176
177         return $files;
178     }
179
180     /**
181      * @inheritdoc
182      */
183     protected function doAdd(ResourceInterface $resource, $files, $recursive)
184     {
185         $files = (array) $files;
186         if (empty($files)) {
187             $resource->getResource()->close();
188             throw new InvalidArgumentException("no files provided");
189         }
190         $this->addEntries($resource, $files, $recursive);
191
192         return $files;
193     }
194
195     /**
196      * @inheritdoc
197      */
198     protected function doCreate($path, $files, $recursive)
199     {
200         $files = (array) $files;
201
202         if (empty($files)) {
203             throw new NotSupportedException("Cannot create an empty zip");
204         }
205
206         $resource = $this->getResource($path, \ZipArchive::CREATE);
207         $this->addEntries($resource, $files, $recursive);
208
209         return new Archive($resource, $this, $this->manager);
210     }
211
212     /**
213      * Returns a new instance of the invoked adapter
214      *
215      * @return AbstractAdapter
216      *
217      * @throws RuntimeException In case object could not be instanciated
218      */
219     public static function newInstance()
220     {
221         return new ZipExtensionAdapter(ResourceManager::create());
222     }
223
224     protected function createResource($path)
225     {
226         return $this->getResource($path, \ZipArchive::CHECKCONS);
227     }
228
229     private function getResource($path, $mode)
230     {
231         $zip = new \ZipArchive();
232         $res = $zip->open($path, $mode);
233
234         if ($res !== true) {
235             throw new RuntimeException($this->errorCodesMapping[$res]);
236         }
237
238         return new ZipArchiveResource($zip);
239     }
240
241     private function addEntries(ResourceInterface $zipResource, array $files, $recursive)
242     {
243         $stack = new \SplStack();
244
245         $error = null;
246         $cwd = getcwd();
247         $collection = $this->manager->handle($cwd, $files);
248
249         $this->chdir($collection->getContext());
250
251         $adapter = $this;
252
253         try {
254             $collection->forAll(function($i, ZippyResource $resource) use ($zipResource, $stack, $recursive, $adapter) {
255                 $adapter->checkReadability($zipResource->getResource(), $resource->getTarget());
256                 if (is_dir($resource->getTarget())) {
257                     if ($recursive) {
258                         $stack->push($resource->getTarget() . ((substr($resource->getTarget(), -1) === DIRECTORY_SEPARATOR) ? '' : DIRECTORY_SEPARATOR));
259                     } else {
260                         $adapter->addEmptyDir($zipResource->getResource(), $resource->getTarget());
261                     }
262                 } else {
263                     $adapter->addFileToZip($zipResource->getResource(), $resource->getTarget());
264                 }
265
266                 return true;
267             });
268
269             // recursively add dirs
270             while (!$stack->isEmpty()) {
271                 $dir = $stack->pop();
272                 // removes . and ..
273                 $files = array_diff(scandir($dir), array(".", ".."));
274                 if (count($files) > 0) {
275                     foreach ($files as $file) {
276                         $file = $dir . $file;
277                         $this->checkReadability($zipResource->getResource(), $file);
278                         if (is_dir($file)) {
279                             $stack->push($file . DIRECTORY_SEPARATOR);
280                         } else {
281                             $this->addFileToZip($zipResource->getResource(), $file);
282                         }
283                     }
284                 } else {
285                     $this->addEmptyDir($zipResource->getResource(), $dir);
286                 }
287             }
288             $this->flush($zipResource->getResource());
289
290             $this->manager->cleanup($collection);
291         } catch (\Exception $e) {
292             $error = $e;
293         }
294
295         $this->chdir($cwd);
296
297         if ($error) {
298             throw $error;
299         }
300     }
301
302     /**
303      * @info is public for PHP 5.3 compatibility, should be private
304      *
305      * @param \ZipArchive $zip
306      * @param string      $file
307      */
308     public function checkReadability(\ZipArchive $zip, $file)
309     {
310         if (!is_readable($file)) {
311             $zip->unchangeAll();
312             $zip->close();
313
314             throw new InvalidArgumentException(sprintf('could not read %s', $file));
315         }
316     }
317
318     /**
319      * @info is public for PHP 5.3 compatibility, should be private
320      *
321      * @param \ZipArchive $zip
322      * @param string      $file
323      */
324     public function addFileToZip(\ZipArchive $zip, $file)
325     {
326         if (!$zip->addFile($file)) {
327             $zip->unchangeAll();
328             $zip->close();
329
330             throw new RuntimeException(sprintf('unable to add %s to the zip file', $file));
331         }
332     }
333
334     /**
335      * @info is public for PHP 5.3 compatibility, should be private
336      *
337      * @param \ZipArchive $zip
338      * @param string      $dir
339      */
340     public function addEmptyDir(\ZipArchive $zip, $dir)
341     {
342         if (!$zip->addEmptyDir($dir)) {
343             $zip->unchangeAll();
344             $zip->close();
345
346             throw new RuntimeException(sprintf('unable to add %s to the zip file', $dir));
347         }
348     }
349
350     /**
351      * Flushes changes to the archive
352      *
353      * @param \ZipArchive $zip
354      */
355     private function flush(\ZipArchive $zip) // flush changes by reopening the file
356     {
357         $path = $zip->filename;
358         $zip->close();
359         $zip->open($path, \ZipArchive::CHECKCONS);
360     }
361 }