Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / vendor / consolidation / robo / src / Task / Archive / Extract.php
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         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)) {
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 }