Passed
Pull Request — master (#210)
by Michele
11:12
created

Binary::setInput()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 1
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
nc 1
nop 1
1
<?php
2
3
/**
4
 * Pickle
5
 *
6
 *
7
 * @license
8
 *
9
 * New BSD License
10
 *
11
 * Copyright © 2015-2015, Pickle community. All rights reserved.
12
 *
13
 * Redistribution and use in source and binary forms, with or without
14
 * modification, are permitted provided that the following conditions are met:
15
 *     * Redistributions of source code must retain the above copyright
16
 *       notice, this list of conditions and the following disclaimer.
17
 *     * Redistributions in binary form must reproduce the above copyright
18
 *       notice, this list of conditions and the following disclaimer in the
19
 *       documentation and/or other materials provided with the distribution.
20
 *     * Neither the name of the Hoa nor the names of its contributors may be
21
 *       used to endorse or promote products derived from this software without
22
 *       specific prior written permission.
23
 *
24
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
25
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
28
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
29
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
30
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
31
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
32
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
33
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
 * POSSIBILITY OF SUCH DAMAGE.
35
 */
36
37
namespace Pickle\Package\PHP\Command\Install\Windows;
38
39
use Symfony\Component\Console\Input\InputInterface;
40
use Symfony\Component\Console\Output\OutputInterface;
41
use Pickle\Base\Util\FileOps;
42
use Pickle\Engine;
43
use Pickle\Base\Archive;
44
use Pickle\Base\Util;
45
46
class Binary
47
{
48
    use FileOps;
49
50
    private $php;
51
    private $extName;
52
    private $extVersion;
53
    private $progress = null;
54
    private $input = null;
55
    private $output = null;
56
    private $extDll = null;
57
58
    /**
59
     * @param string $ext
60
     */
61
    public function __construct($ext)
62
    {
63
        // used only if only the extension name is given
64
        if (strpos('//', $ext) !== false) {
65
            $this->extensionPeclExists();
66
        }
67
68
        $this->extName = $ext;
69
        $this->php = Engine::factory();
70
    }
71
72
    public function setProgress($progress)
73
    {
74
        $this->progress = $progress;
75
    }
76
77
    public function setInput(InputInterface $input)
78
    {
79
        $this->input = $input;
80
    }
81
82
    public function setOutput(OutputInterface $output)
83
    {
84
        $this->output = $output;
85
    }
86
87
    private function extensionPeclExists()
88
    {
89
        $url = 'https://pecl.php.net/get/'.$this->extName;
90
        $headers = get_headers($url, 1);
91
        $status = $headers[0];
92
        if (strpos($status, '404')) {
93
            throw new \Exception("Extension <$this->extName> cannot be found");
94
        }
95
    }
96
97
    /**
98
     * @param string $url
99
     */
100
    private function findInLinks($url, $toFind)
101
    {
102
        $page = @file_get_contents($url);
0 ignored issues
show
Unused Code introduced by
The assignment to $page is dead and can be removed.
Loading history...
103
        $opts = [
104
            'http' => [
105
                'header' => 'User-Agent: pickle'
106
            ],
107
        ];
108
        $context = stream_context_create($opts);
109
        $page = @file_get_contents($url, false, $context);
110
        if (!$page) {
111
            return false;
112
        }
113
        $dom = new \DOMDocument();
114
        $dom->loadHTML($page);
115
        $links = $dom->getElementsByTagName('a');
116
        if (!$links) {
0 ignored issues
show
introduced by
$links is of type DOMNodeList, thus it always evaluated to true.
Loading history...
117
            return false;
118
        }
119
120
        foreach ($links as $link) {
121
            if ($link->nodeValue[0] == '[') {
122
                continue;
123
            }
124
            $value = trim($link->nodeValue);
125
            if ($toFind == $value) {
126
                return $value;
127
            }
128
        }
129
130
        return false;
131
    }
132
133
    /**
134
     * @return string
135
     *
136
     * @throws \Exception
137
     */
138
    private function fetchZipName()
139
    {
140
        $phpVc = $this->php->getCompiler();
141
        $phpArch = $this->php->getArchitecture();
142
        $phpZts = $this->php->getZts() ? '-ts' : '-nts';
143
        $phpVersion = $this->php->getMajorVersion().'.'.$this->php->getMinorVersion();
144
        $pkgVersion = $this->extVersion;
145
        $extName = strtolower($this->extName);
146
        $baseUrl = 'https://windows.php.net/downloads/pecl/releases/';
147
148
        if (false === $this->findInLinks($baseUrl.$extName, $pkgVersion)) {
149
            throw new \Exception('Binary for <'.$extName.'-'.$pkgVersion.'> cannot be found');
150
        }
151
152
        $fileToFind = 'php_'.$extName.'-'.$pkgVersion.'-'.$phpVersion.$phpZts.'-'.$phpVc.'-'.$phpArch.'.zip';
153
        $fileUrl = $this->findInLinks($baseUrl.$extName.'/'.$pkgVersion, $fileToFind);
154
155
        if (!$fileUrl) {
156
            throw new \Exception('Binary for <'.$fileToFind.'> cannot be found');
157
        }
158
        $url = $baseUrl.$extName.'/'.$pkgVersion.'/'.$fileToFind;
159
160
        return $url;
161
    }
162
163
    /**
164
     * @param string $zipFile
165
     *
166
     * @throws \Exception
167
     */
168
    private function uncompress($zipFile)
169
    {
170
        $this->createTempDir($this->extName);
171
        $this->cleanup();
172
        $zipClass = Archive\Factory::getUnzipperClassName();
173
        $zipArchive = $zipClass($zipFile);
174
        /** @var \Pickle\Base\Interfaces\Archive\Unzipper $zipArchive */
175
        $this->output->writeln('Extracting archives...');
176
        $zipArchive->extractTo($this->tempDir);
177
    }
178
179
    /**
180
     * @param string $url
181
     *
182
     * @return string
183
     *
184
     * @throws \Exception
185
     */
186
    private function download($url)
187
    {
188
        $progress = $this->progress;
189
        $progress->setOverwrite(true);
190
        $ctx = stream_context_create(
191
            array(
192
                'http' => [
193
                    'header' => 'User-Agent: pickle'
194
                ]
195
            ),
196
            array(
197
                'notification' => function ($notificationCode, $severity, $message, $messageCode, $bytesTransferred, $bytesMax) use ($progress) {
198
                    switch ($notificationCode) {
199
                        case STREAM_NOTIFY_RESOLVE:
200
                        case STREAM_NOTIFY_AUTH_REQUIRED:
201
                        case STREAM_NOTIFY_COMPLETED:
202
                        case STREAM_NOTIFY_FAILURE:
203
                        case STREAM_NOTIFY_AUTH_RESULT:
204
                            break;
205
                
206
                        case STREAM_NOTIFY_REDIRECTED:
207
                            break;
208
                
209
                        case STREAM_NOTIFY_CONNECT:
210
                            break;
211
                
212
                        case STREAM_NOTIFY_FILE_SIZE_IS:
213
                            $progress->start($bytesMax);
214
                            break;
215
                
216
                        case STREAM_NOTIFY_MIME_TYPE_IS:
217
                            break;
218
                
219
                        case STREAM_NOTIFY_PROGRESS:
220
                            $progress->setProgress($bytesTransferred);
221
                            break;
222
                    };
223
                },
224
            )
225
        );
226
        $output->writeln("downloading $url ");
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $output seems to be never defined.
Loading history...
227
        $fileContents = file_get_contents($url, false, $ctx);
228
        $progress->finish();
229
        if (!$fileContents) {
230
            throw new \Exception('Cannot fetch <'.$url.'>');
231
        }
232
        $tmpdir = Util\TmpDir::get();
233
        $path = $tmpdir.'/'.$this->extName.'.zip';
234
        if (!file_put_contents($path, $fileContents)) {
235
            throw new \Exception('Cannot save temporary file <'.$path.'>');
236
        }
237
238
        return $path;
239
    }
240
241
    /**
242
     * @throws \Exception
243
     */
244
    private function copyFiles()
245
    {
246
        $DLLs = glob($this->tempDir.'/*.dll');
247
        $this->extDll = [];
248
        foreach ($DLLs as $dll) {
249
            $dll = realpath($dll);
250
            $basename = basename($dll);
251
            $dest = $this->php->getExtensionDir().DIRECTORY_SEPARATOR.$basename;
252
            if (substr($basename, 0, 4) == 'php_') {
253
                $this->extDll[] = $basename;
254
                $this->output->writeln("copying $dll to ".$dest."\n");
255
                $success = copy($dll, $this->php->getExtensionDir().'/'.$basename);
256
                if (!$success) {
257
                    throw new \Exception('Cannot copy DLL <'.$dll.'> to <'.$dest.'>');
258
                }
259
            } else {
260
                $success = copy($dll, dirname($this->php->getPath()).'/'.$basename);
261
                if (!$success) {
262
                    throw new \Exception('Cannot copy DLL <'.$dll.'> to <'.$dest.'>');
263
                }
264
            }
265
        }
266
    }
267
268
    /**
269
     * @throws \Exception
270
     */
271
    private function updateIni()
272
    {
273
        $ini = \Pickle\Engine\Ini::factory($this->php);
274
        $ini->updatePickleSection($this->extDll);
275
    }
276
277
    /**
278
     * @return array
279
     *
280
     * @throws \Exception
281
     */
282
    private function getInfoFromPecl()
283
    {
284
        $url = 'https://pecl.php.net/get/'.$this->extName;
285
        $headers = get_headers($url);
286
287
        if (strpos($headers[0], '404') !== false) {
288
            throw new \Exception('Cannot find extension <'.$this->extName.'>');
289
        }
290
        $headerPkg = null;
291
        foreach ($headers as $header) {
292
            if (strpos($header, 'tgz') !== false) {
293
                $headerPkg = $header;
294
                break;
295
            }
296
        }
297
        if ($headerPkg === null) {
298
            throw new \Exception('Cannot find extension <'.$this->extName.'>');
299
        }
300
301
        if (!preg_match("|=(.*)\.[a-z0-9]{2,3}$|", $headerPkg, $m)) {
302
            throw new \Exception('Invalid response from pecl.php.net');
303
        }
304
        $packageFullname = $m[1];
305
306
        list($name, $version) = explode('-', $packageFullname);
307
        if ($name == '' || $version == '') {
308
            throw new \Exception('Invalid response from pecl.php.net');
309
        }
310
311
        return [$name, $version];
312
    }
313
314
    /**
315
     *  1. check if ext exists
316
     *  2. check if given version requested
317
     *  2.1 yes? check if builds available
318
     *  2.2 no? get latest version+build.
319
     *
320
     * @throws \Exception
321
     */
322
    public function install()
323
    {
324
        list($this->extName, $this->extVersion) = $this->getInfoFromPecl();
325
        $url = $this->fetchZipName();
326
        $pathArchive = $this->download($url);
327
        $this->uncompress($pathArchive);
328
        $this->copyFiles();
329
        $this->cleanup();
330
        $this->updateIni();
331
    }
332
333
    public function getExtDllPaths()
334
    {
335
        $ret = array();
336
337
        foreach ($this->extDll as $dll) {
338
            $ret[] = $this->php->getExtensionDir().DIRECTORY_SEPARATOR.$dll;
339
        }
340
341
        return $ret;
342
    }
343
}
344
345
/* vim: set tabstop=4 shiftwidth=4 expandtab: fdm=marker */
346