FtpDeployTask::main()   F
last analyzed

Complexity

Conditions 27
Paths 3623

Size

Total Lines 139
Code Lines 83

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 756

Importance

Changes 0
Metric Value
eloc 83
dl 0
loc 139
ccs 0
cts 85
cp 0
rs 0
c 0
b 0
f 0
cc 27
nc 3623
nop 0
crap 756

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
5
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
6
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
7
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
8
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
9
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
10
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
11
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
12
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
13
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
14
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
15
 *
16
 * This software consists of voluntary contributions made by many individuals
17
 * and is licensed under the LGPL. For more information please see
18
 * <http://phing.info>.
19
 */
20
21
namespace Phing\Task\Ext\FtpDeploy;
22
23
use Phing\Exception\BuildException;
24
use Phing\ExceptionBuildException;
0 ignored issues
show
Bug introduced by
The type Phing\ExceptionBuildException was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
25
use Phing\Io\File;
26
use Phing\Io\FileSystem;
27
use Phing\Phing;
28
use Phing\Task;
29
use Phing\Task\System\Element\LogLevelAware;
30
use Phing\Type\Element\FileSetAware;
31
use Phing\Util\StringHelper;
32
33
/**
34
 * FtpDeployTask
35
 *
36
 * Deploys a set of files to a remote FTP server.
37
 *
38
 *
39
 * Example usage:
40
 * <ftpdeploy host="host" port="21" username="user" password="password" dir="public_html" mode="ascii" clearfirst="true" depends="false" filemode="" dirmode="">
41
 *   <fileset dir=".">
42
 *     <include name="**"/>
43
 *     <exclude name="phing"/>
44
 *     <exclude name="build.xml"/>
45
 *     <exclude name="images/**.png"/>
46
 *     <exclude name="images/**.gif"/>
47
 *     <exclude name="images/**.jpg"/>
48
 *   </fileset>
49
 * </ftpdeploy>
50
 *
51
 * @author      Jorrit Schippers <jorrit at ncode dot nl>
52
 * @contributor Steffen Sørensen <[email protected]>
53
 * @since       2.3.1
54
 * @package     phing.tasks.ext
55
 */
56
class FtpDeployTask extends Task
57
{
58
    use FileSetAware;
59
    use LogLevelAware;
60
61
    private $host = null;
62
    private $port = 21;
63
    private $ssl = false;
64
    private $username;
65
    private $password;
66
    private $dir;
67
    private $mode = FTP_BINARY;
68
    private $clearFirst = false;
69
    private $passive = false;
70
    private $depends = false;
71
    private $dirmode = false;
72
    private $filemode = false;
73
    private $rawDataFallback = false;
74
    private $skipOnSameSize = false;
75
76
    public function __construct()
77
    {
78
        parent::__construct();
79
        $this->filesets = [];
80
    }
81
82
    /**
83
     * @param string $host
84
     */
85
    public function setHost(string $host): void
86
    {
87
        $this->host = $host;
88
    }
89
90
    /**
91
     * @param int $port
92
     */
93
    public function setPort(int $port): void
94
    {
95
        $this->port = $port;
96
    }
97
98
    /**
99
     * @param bool $ssl
100
     */
101
    public function setSsl(bool $ssl): void
102
    {
103
        $this->ssl = $ssl;
104
    }
105
106
    /**
107
     * @param string $username
108
     */
109
    public function setUsername($username): void
110
    {
111
        $this->username = $username;
112
    }
113
114
    /**
115
     * @param string $password
116
     */
117
    public function setPassword(string $password)
118
    {
119
        $this->password = $password;
120
    }
121
122
    /**
123
     * @param $dir
124
     */
125
    public function setDir($dir)
126
    {
127
        $this->dir = $dir;
128
    }
129
130
    /**
131
     * @param $mode
132
     */
133
    public function setMode($mode)
134
    {
135
        switch (strtolower($mode)) {
136
            case 'ascii':
137
                $this->mode = FTP_ASCII;
138
                break;
139
            case 'binary':
140
            case 'bin':
141
                $this->mode = FTP_BINARY;
142
                break;
143
        }
144
    }
145
146
    /**
147
     * @param bool $passive
148
     */
149
    public function setPassive(bool $passive): void
150
    {
151
        $this->passive = $passive;
152
    }
153
154
    /**
155
     * @param bool $clearFirst
156
     */
157
    public function setClearFirst(bool $clearFirst): void
158
    {
159
        $this->clearFirst = $clearFirst;
160
    }
161
162
    /**
163
     * @param bool $depends
164
     */
165
    public function setDepends(bool $depends): void
166
    {
167
        $this->depends = $depends;
168
    }
169
170
    /**
171
     * @param string $filemode
172
     */
173
    public function setFilemode($filemode): void
174
    {
175
        $this->filemode = octdec(str_pad($filemode, 4, '0', STR_PAD_LEFT));
176
    }
177
178
    /**
179
     * @param $dirmode
180
     */
181
    public function setDirmode($dirmode): void
182
    {
183
        $this->dirmode = octdec(str_pad($dirmode, 4, '0', STR_PAD_LEFT));
184
    }
185
186
    /**
187
     * @param $fallback
188
     */
189
    public function setRawdatafallback(bool $fallback): void
190
    {
191
        $this->rawDataFallback = $fallback;
192
    }
193
194
    /**
195
     * @param bool|string|int $skipOnSameSize
196
     */
197
    public function setSkipOnSameSize($skipOnSameSize): void
198
    {
199
        $this->skipOnSameSize = StringHelper::booleanValue($skipOnSameSize);
200
    }
201
202
    /**
203
     * The init method: check if Net_FTP is available
204
     */
205
    public function init()
206
    {
207
        $paths = Phing::explodeIncludePath();
208
        foreach ($paths as $path) {
209
            if (file_exists($path . DIRECTORY_SEPARATOR . 'Net' . DIRECTORY_SEPARATOR . 'FTP.php')) {
210
                return true;
211
            }
212
        }
213
        throw new BuildException('The FTP Deploy task requires the Net_FTP PEAR package.');
214
    }
215
216
    /**
217
     * The main entry point method.
218
     */
219
    public function main()
220
    {
221
        $project = $this->getProject();
222
223
        $ftp = new \Net_FTP($this->host, $this->port);
224
        if ($this->ssl) {
225
            $ret = $ftp->setSsl();
226
            if (@\PEAR::isError($ret)) {
227
                throw new BuildException(
228
                    'SSL connection not supported by php: ' . $ret->getMessage()
229
                );
230
            }
231
232
            $this->log('Use SSL connection', $this->logLevel);
233
        }
234
        $ret = $ftp->connect();
235
        if (@\PEAR::isError($ret)) {
236
            throw new BuildException(
237
                'Could not connect to FTP server ' . $this->host . ' on port ' . $this->port . ': ' . $ret->getMessage()
238
            );
239
        }
240
241
        $this->log('Connected to FTP server ' . $this->host . ' on port ' . $this->port, $this->logLevel);
242
243
        $ret = $ftp->login($this->username, $this->password);
244
        if (@\PEAR::isError($ret)) {
245
            throw new BuildException(
246
                'Could not login to FTP server ' . $this->host . ' on port ' . $this->port . ' with username ' . $this->username . ': ' . $ret->getMessage()
247
            );
248
        }
249
250
        $this->log('Logged in to FTP server with username ' . $this->username, $this->logLevel);
251
252
        if ($this->passive) {
253
            $this->log('Setting passive mode', $this->logLevel);
254
            $ret = $ftp->setPassive();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $ret is correct as $ftp->setPassive() targeting Net_FTP::setPassive() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
255
            if (@\PEAR::isError($ret)) {
256
                $ftp->disconnect();
257
                throw new BuildException('Could not set PASSIVE mode: ' . $ret->getMessage());
258
            }
259
        }
260
261
        // append '/' to the end if necessary
262
        $dir = substr($this->dir, -1) === '/' ? $this->dir : $this->dir . '/';
263
264
        if ($this->clearFirst) {
265
            // TODO change to a loop through all files and directories within current directory
266
            $this->log('Clearing directory ' . $dir, $this->logLevel);
267
            $ftp->rm($dir, true);
268
        }
269
270
        // Create directory just in case
271
        $ret = $ftp->mkdir($dir, true);
272
        if (@\PEAR::isError($ret)) {
273
            $ftp->disconnect();
274
            throw new BuildException('Could not create directory ' . $dir . ': ' . $ret->getMessage());
275
        }
276
277
        $ret = $ftp->cd($dir);
278
        if (@\PEAR::isError($ret)) {
279
            $ftp->disconnect();
280
            throw new BuildException('Could not change to directory ' . $dir . ': ' . $ret->getMessage());
281
        }
282
283
        $this->log('Changed directory ' . $dir, $this->logLevel);
284
285
        $fs = FileSystem::getFileSystem();
286
        $convert = $fs->getSeparator() === '\\';
287
288
        foreach ($this->filesets as $fs) {
289
            // Array for holding directory content informations
290
            $remoteFileInformations = [];
291
292
            $ds = $fs->getDirectoryScanner($project);
293
            $fromDir = $fs->getDir($project);
294
            $srcFiles = $ds->getIncludedFiles();
295
            $srcDirs = $ds->getIncludedDirectories();
296
297
            foreach ($srcDirs as $dirname) {
298
                if ($convert) {
299
                    $dirname = str_replace('\\', '/', $dirname);
300
                }
301
302
                // Read directory informations, if file exists, else create the directory
303
                if (!$this->directoryInformations($ftp, $remoteFileInformations, $dirname)) {
304
                    $this->log('Will create directory ' . $dirname, $this->logLevel);
305
                    $ret = $ftp->mkdir($dirname, true);
306
                    if (@\PEAR::isError($ret)) {
307
                        $ftp->disconnect();
308
                        throw new BuildException('Could not create directory ' . $dirname . ': ' . $ret->getMessage());
309
                    }
310
                }
311
                if ($this->dirmode) {
312
                    if ($this->dirmode === 'inherit') {
313
                        $mode = fileperms($dirname);
314
                    } else {
315
                        $mode = $this->dirmode;
316
                    }
317
                    // Because Net_FTP does not support a chmod call we call ftp_chmod directly
318
                    ftp_chmod($ftp->_handle, $mode, $dirname);
319
                }
320
            }
321
322
            foreach ($srcFiles as $filename) {
323
                $file = new File($fromDir->getAbsolutePath(), $filename);
324
                if ($convert) {
325
                    $filename = str_replace('\\', '/', $filename);
326
                }
327
328
                $local_filemtime = filemtime($file->getCanonicalPath());
329
                $remoteFileModificationTime = $remoteFileInformations[$filename]['stamp'] ?? 0;
330
331
                if (!$this->depends || ($local_filemtime > $remoteFileModificationTime)) {
332
                    if ($this->skipOnSameSize === true && $file->length() === $ftp->size($filename)) {
333
                        $this->log('Skipped ' . $file->getCanonicalPath(), $this->logLevel);
334
                        continue;
335
                    }
336
337
                    $this->log('Will copy ' . $file->getCanonicalPath() . ' to ' . $filename, $this->logLevel);
338
                    $ret = $ftp->put($file->getCanonicalPath(), $filename, true, $this->mode);
339
                    if (@\PEAR::isError($ret)) {
340
                        $ftp->disconnect();
341
                        throw new BuildException('Could not deploy file ' . $filename . ': ' . $ret->getMessage());
342
                    }
343
                }
344
                if ($this->filemode) {
345
                    if ($this->filemode === 'inherit') {
346
                        $mode = fileperms($filename);
347
                    } else {
348
                        $mode = $this->filemode;
349
                    }
350
                    // Because Net_FTP does not support a chmod call we call ftp_chmod directly
351
                    ftp_chmod($ftp->_handle, $mode, $filename);
0 ignored issues
show
Bug introduced by
It seems like $mode can also be of type true; however, parameter $permissions of ftp_chmod() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

351
                    ftp_chmod($ftp->_handle, /** @scrutinizer ignore-type */ $mode, $filename);
Loading history...
352
                }
353
            }
354
        }
355
356
        $ftp->disconnect();
357
        $this->log('Disconnected from FTP server', $this->logLevel);
358
    }
359
360
    /**
361
     * @param \Net_FTP $ftp
362
     * @param $remoteFileInformations
363
     * @param $directory
364
     * @return bool
365
     */
366
    private function directoryInformations(\Net_FTP $ftp, &$remoteFileInformations, $directory)
367
    {
368
        $content = $ftp->ls($directory);
369
        if (@\PEAR::isError($content)) {
370
            if ($this->rawDataFallback) {
371
                $content = $ftp->ls($directory, NET_FTP_RAWLIST);
372
            }
373
            if (@\PEAR::isError($content)) {
374
                return false;
375
            }
376
            $content = $this->parseRawFtpContent($content, $directory);
377
        }
378
379
        if (count($content) == 0) {
380
            return false;
381
        }
382
383
        if (!empty($directory)) {
384
            $directory .= '/';
385
        }
386
        foreach ($content as $val) {
387
            if ($val['name'] !== '.' && $val['name'] !== '..') {
388
                $remoteFileInformations[$directory . $val['name']] = $val;
389
            }
390
        }
391
392
        return true;
393
    }
394
395
    /**
396
     * @param $content
397
     * @param null $directory
0 ignored issues
show
Documentation Bug introduced by
Are you sure the doc-type for parameter $directory is correct as it would always require null to be passed?
Loading history...
398
     * @return array
399
     */
400
    private function parseRawFtpContent($content, $directory = null): array
401
    {
402
        if (!is_array($content)) {
403
            return [];
404
        }
405
406
        $this->log('Start parsing FTP_RAW_DATA in ' . $directory);
407
        $retval = [];
408
        foreach ($content as $rawInfo) {
409
            $rawInfo = explode(' ', $rawInfo);
410
            $rawInfo2 = [];
411
            foreach ($rawInfo as $part) {
412
                $part = trim($part);
413
                if (!empty($part)) {
414
                    $rawInfo2[] = $part;
415
                }
416
            }
417
418
            $date = date_parse($rawInfo2[5] . ' ' . $rawInfo2[6] . ' ' . $rawInfo2[7]);
419
            if ($date['year'] === false) {
420
                $date['year'] = date('Y');
421
            }
422
            $date = mktime(
423
                $date['hour'],
424
                $date['minute'],
425
                $date['second'],
426
                $date['month'],
427
                $date['day'],
428
                $date['year']
429
            );
430
431
            $retval[] = [
432
                'name' => $rawInfo2[8],
433
                'rights' => substr($rawInfo2[0], 1),
434
                'user' => $rawInfo2[2],
435
                'group' => $rawInfo2[3],
436
                'date' => $date,
437
                'stamp' => $date,
438
                'is_dir' => strpos($rawInfo2[0], 'd') === 0,
439
                'files_inside' => (int) $rawInfo2[1],
440
                'size' => (int) $rawInfo2[4],
441
            ];
442
        }
443
444
        $this->log('Finished parsing FTP_RAW_DATA in ' . $directory);
445
446
        return $retval;
447
    }
448
}
449