These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | |||
3 | namespace Robo\Task\Archive; |
||
4 | |||
5 | use Robo\Result; |
||
6 | use Robo\Task\BaseTask; |
||
7 | use Robo\Task\Filesystem\FilesystemStack; |
||
8 | use Robo\Task\Filesystem\DeleteDir; |
||
9 | use Robo\Contract\BuilderAwareInterface; |
||
10 | use Robo\Common\BuilderAwareTrait; |
||
11 | |||
12 | /** |
||
13 | * Extracts an archive. |
||
14 | * |
||
15 | * Note that often, distributions are packaged in tar or zip archives |
||
16 | * where the topmost folder may contain variable information, such as |
||
17 | * the release date, or the version of the package. This information |
||
18 | * is very useful when unpacking by hand, but arbitrarily-named directories |
||
19 | * are much less useful to scripts. Therefore, by default, Extract will |
||
20 | * remove the top-level directory, and instead store all extracted files |
||
21 | * into the directory specified by $archivePath. |
||
22 | * |
||
23 | * To keep the top-level directory when extracting, use |
||
24 | * `preserveTopDirectory(true)`. |
||
25 | * |
||
26 | * ``` php |
||
27 | * <?php |
||
28 | * $this->taskExtract($archivePath) |
||
29 | * ->to($destination) |
||
30 | * ->preserveTopDirectory(false) // the default |
||
31 | * ->run(); |
||
32 | * ?> |
||
33 | * ``` |
||
34 | */ |
||
35 | class Extract extends BaseTask implements BuilderAwareInterface |
||
36 | { |
||
37 | use BuilderAwareTrait; |
||
38 | |||
39 | /** |
||
40 | * @var string |
||
41 | */ |
||
42 | protected $filename; |
||
43 | |||
44 | /** |
||
45 | * @var string |
||
46 | */ |
||
47 | protected $to; |
||
48 | |||
49 | /** |
||
50 | * @var bool |
||
51 | */ |
||
52 | private $preserveTopDirectory = false; |
||
53 | |||
54 | /** |
||
55 | * @param string $filename |
||
56 | */ |
||
57 | public function __construct($filename) |
||
58 | { |
||
59 | $this->filename = $filename; |
||
60 | } |
||
61 | |||
62 | /** |
||
63 | * Location to store extracted files. |
||
64 | * |
||
65 | * @param string $to |
||
66 | * |
||
67 | * @return $this |
||
68 | */ |
||
69 | public function to($to) |
||
70 | { |
||
71 | $this->to = $to; |
||
72 | return $this; |
||
73 | } |
||
74 | |||
75 | /** |
||
76 | * @param bool $preserve |
||
77 | * |
||
78 | * @return $this |
||
79 | */ |
||
80 | public function preserveTopDirectory($preserve = true) |
||
81 | { |
||
82 | $this->preserveTopDirectory = $preserve; |
||
83 | return $this; |
||
84 | } |
||
85 | |||
86 | /** |
||
87 | * {@inheritdoc} |
||
88 | */ |
||
89 | public function run() |
||
90 | { |
||
91 | View Code Duplication | if (!file_exists($this->filename)) { |
|
92 | $this->printTaskError("File {filename} does not exist", ['filename' => $this->filename]); |
||
93 | |||
94 | return false; |
||
95 | } |
||
96 | if (!($mimetype = static::archiveType($this->filename))) { |
||
97 | $this->printTaskError("Could not determine type of archive for {filename}", ['filename' => $this->filename]); |
||
98 | |||
99 | return false; |
||
100 | } |
||
101 | |||
102 | // We will first extract to $extractLocation and then move to $this->to |
||
103 | $extractLocation = static::getTmpDir(); |
||
104 | @mkdir($extractLocation); |
||
105 | @mkdir(dirname($this->to)); |
||
106 | |||
107 | $this->startTimer(); |
||
108 | |||
109 | $this->printTaskInfo("Extracting {filename}", ['filename' => $this->filename]); |
||
110 | |||
111 | $result = $this->extractAppropriateType($mimetype, $extractLocation); |
||
112 | if ($result->wasSuccessful()) { |
||
113 | $this->printTaskInfo("{filename} extracted", ['filename' => $this->filename]); |
||
114 | // Now, we want to move the extracted files to $this->to. There |
||
115 | // are two possibilities that we must consider: |
||
116 | // |
||
117 | // (1) Archived files were encapsulated in a folder with an arbitrary name |
||
118 | // (2) There was no encapsulating folder, and all the files in the archive |
||
119 | // were extracted into $extractLocation |
||
120 | // |
||
121 | // In the case of (1), we want to move and rename the encapsulating folder |
||
122 | // to $this->to. |
||
123 | // |
||
124 | // In the case of (2), we will just move and rename $extractLocation. |
||
125 | $filesInExtractLocation = glob("$extractLocation/*"); |
||
126 | $hasEncapsulatingFolder = ((count($filesInExtractLocation) == 1) && is_dir($filesInExtractLocation[0])); |
||
127 | if ($hasEncapsulatingFolder && !$this->preserveTopDirectory) { |
||
128 | $result = (new FilesystemStack()) |
||
129 | ->inflect($this) |
||
130 | ->rename($filesInExtractLocation[0], $this->to) |
||
131 | ->run(); |
||
132 | (new DeleteDir($extractLocation)) |
||
133 | ->inflect($this) |
||
134 | ->run(); |
||
135 | } else { |
||
136 | $result = (new FilesystemStack()) |
||
137 | ->inflect($this) |
||
138 | ->rename($extractLocation, $this->to) |
||
139 | ->run(); |
||
140 | } |
||
141 | } |
||
142 | $this->stopTimer(); |
||
143 | $result['time'] = $this->getExecutionTime(); |
||
144 | |||
145 | return $result; |
||
146 | } |
||
147 | |||
148 | /** |
||
149 | * @param string $mimetype |
||
150 | * @param string $extractLocation |
||
151 | * |
||
152 | * @return \Robo\Result |
||
153 | */ |
||
154 | protected function extractAppropriateType($mimetype, $extractLocation) |
||
155 | { |
||
156 | // Perform the extraction of a zip file. |
||
157 | if (($mimetype == 'application/zip') || ($mimetype == 'application/x-zip')) { |
||
158 | return $this->extractZip($extractLocation); |
||
159 | } |
||
160 | return $this->extractTar($extractLocation); |
||
161 | } |
||
162 | |||
163 | /** |
||
164 | * @param string $extractLocation |
||
165 | * |
||
166 | * @return \Robo\Result |
||
167 | */ |
||
168 | protected function extractZip($extractLocation) |
||
169 | { |
||
170 | if (!extension_loaded('zlib')) { |
||
171 | return Result::errorMissingExtension($this, 'zlib', 'zip extracting'); |
||
172 | } |
||
173 | |||
174 | $zip = new \ZipArchive(); |
||
175 | if (($status = $zip->open($this->filename)) !== true) { |
||
176 | return Result::error($this, "Could not open zip archive {$this->filename}"); |
||
177 | } |
||
178 | if (!$zip->extractTo($extractLocation)) { |
||
179 | return Result::error($this, "Could not extract zip archive {$this->filename}"); |
||
180 | } |
||
181 | $zip->close(); |
||
182 | |||
183 | return Result::success($this); |
||
184 | } |
||
185 | |||
186 | /** |
||
187 | * @param string $extractLocation |
||
188 | * |
||
189 | * @return \Robo\Result |
||
190 | */ |
||
191 | protected function extractTar($extractLocation) |
||
192 | { |
||
193 | if (!class_exists('Archive_Tar')) { |
||
194 | return Result::errorMissingPackage($this, 'Archive_Tar', 'pear/archive_tar'); |
||
195 | } |
||
196 | $tar_object = new \Archive_Tar($this->filename); |
||
197 | if (!$tar_object->extract($extractLocation)) { |
||
0 ignored issues
–
show
|
|||
198 | return Result::error($this, "Could not extract tar archive {$this->filename}"); |
||
199 | } |
||
200 | |||
201 | return Result::success($this); |
||
202 | } |
||
203 | |||
204 | /** |
||
205 | * @param string $filename |
||
206 | * |
||
207 | * @return bool|string |
||
208 | */ |
||
209 | protected static function archiveType($filename) |
||
210 | { |
||
211 | $content_type = false; |
||
212 | if (class_exists('finfo')) { |
||
213 | $finfo = new \finfo(FILEINFO_MIME_TYPE); |
||
214 | $content_type = $finfo->file($filename); |
||
215 | // If finfo cannot determine the content type, then we will try other methods |
||
216 | if ($content_type == 'application/octet-stream') { |
||
217 | $content_type = false; |
||
218 | } |
||
219 | } |
||
220 | // Examing the file's magic header bytes. |
||
221 | if (!$content_type) { |
||
222 | if ($file = fopen($filename, 'rb')) { |
||
223 | $first = fread($file, 2); |
||
224 | fclose($file); |
||
225 | if ($first !== false) { |
||
226 | // Interpret the two bytes as a little endian 16-bit unsigned int. |
||
227 | $data = unpack('v', $first); |
||
228 | switch ($data[1]) { |
||
229 | case 0x8b1f: |
||
230 | // First two bytes of gzip files are 0x1f, 0x8b (little-endian). |
||
231 | // See http://www.gzip.org/zlib/rfc-gzip.html#header-trailer |
||
232 | $content_type = 'application/x-gzip'; |
||
233 | break; |
||
234 | |||
235 | case 0x4b50: |
||
236 | // First two bytes of zip files are 0x50, 0x4b ('PK') (little-endian). |
||
237 | // See http://en.wikipedia.org/wiki/Zip_(file_format)#File_headers |
||
238 | $content_type = 'application/zip'; |
||
239 | break; |
||
240 | |||
241 | case 0x5a42: |
||
242 | // First two bytes of bzip2 files are 0x5a, 0x42 ('BZ') (big-endian). |
||
243 | // See http://en.wikipedia.org/wiki/Bzip2#File_format |
||
244 | $content_type = 'application/x-bzip2'; |
||
245 | break; |
||
246 | } |
||
247 | } |
||
248 | } |
||
249 | } |
||
250 | // 3. Lastly if above methods didn't work, try to guess the mime type from |
||
251 | // the file extension. This is useful if the file has no identificable magic |
||
252 | // header bytes (for example tarballs). |
||
253 | if (!$content_type) { |
||
254 | // Remove querystring from the filename, if present. |
||
255 | $filename = basename(current(explode('?', $filename, 2))); |
||
256 | $extension_mimetype = array( |
||
257 | '.tar.gz' => 'application/x-gzip', |
||
258 | '.tgz' => 'application/x-gzip', |
||
259 | '.tar' => 'application/x-tar', |
||
260 | ); |
||
261 | foreach ($extension_mimetype as $extension => $ct) { |
||
262 | if (substr($filename, -strlen($extension)) === $extension) { |
||
263 | $content_type = $ct; |
||
264 | break; |
||
265 | } |
||
266 | } |
||
267 | } |
||
268 | |||
269 | return $content_type; |
||
270 | } |
||
271 | |||
272 | /** |
||
273 | * @return string |
||
274 | */ |
||
275 | protected static function getTmpDir() |
||
276 | { |
||
277 | return getcwd().'/tmp'.rand().time(); |
||
278 | } |
||
279 | } |
||
280 |
If an expression can have both
false
, andnull
as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.