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)) { |
|
0 ignored issues
–
show
|
|||
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)) { |
||
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 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.