AbstractFtpAdapter::getPassword()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2
Metric Value
dl 0
loc 4
ccs 0
cts 4
cp 0
rs 10
cc 1
eloc 2
nc 1
nop 0
crap 2
1
<?php
2
3
namespace LWI\DeliveryTracking\Adapter;
4
5
abstract class AbstractFtpAdapter
6
{
7
    /**
8
     * @const  VISIBILITY_PUBLIC  public visibility
9
     */
10
    const VISIBILITY_PUBLIC = 'public';
11
    /**
12
     * @const  VISIBILITY_PRIVATE  private visibility
13
     */
14
    const VISIBILITY_PRIVATE = 'private';
15
16
    /**
17
     * @var array
18
     */
19
    protected $configurable = [
20
        'host',
21
        'port',
22
        'username',
23
        'password',
24
        'ssl',
25
        'timeout',
26
        'root',
27
        'permPrivate',
28
        'permPublic',
29
        'passive',
30
        'transferMode',
31
        'systemType',
32
    ];
33
34
    /**
35
     * @var mixed
36
     */
37
    protected $connection;
38
39
    /**
40
     * @var string
41
     */
42
    protected $host;
43
44
    /**
45
     * @var int
46
     */
47
    protected $port = 21;
48
49
    /**
50
     * @var string|null
51
     */
52
    protected $username;
53
54
    /**
55
     * @var string|null
56
     */
57
    protected $password;
58
59
    /**
60
     * @var bool
61
     */
62
    protected $ssl = false;
63
64
    /**
65
     * @var int
66
     */
67
    protected $timeout = 90;
68
69
    /**
70
     * @var bool
71
     */
72
    protected $passive = true;
73
74
    /**
75
     * @var int
76
     */
77
    protected $transferMode = FTP_BINARY;
78
79
    /**
80
     * @var string
81
     */
82
    protected $separator = '/';
83
84
    /**
85
     * @var string|null
86
     */
87
    protected $root;
88
89
    /**
90
     * @var int
91
     */
92
    protected $permPublic = 0744;
93
94
    /**
95
     * @var int
96
     */
97
    protected $permPrivate = 0700;
98
99
    /**
100
     * @var string
101
     */
102
    protected $systemType;
103
104
    /**
105
     * Constructor.
106
     *
107
     * @param array $config
108
     */
109
    public function __construct(array $config)
110
    {
111
        $this->setConfig($config);
112
    }
113
114
    /**
115
     * Disconnect on destruction.
116
     */
117
    public function __destruct()
118
    {
119
        $this->disconnect();
120
    }
121
122
    /**
123
     * Set the config.
124
     *
125
     * @param array $config
126
     *
127
     * @return $this
128
     */
129
    public function setConfig(array $config)
130
    {
131
        foreach ($this->configurable as $setting) {
132
            if (! isset($config[$setting])) {
133
                continue;
134
            }
135
136
            $method = 'set'.ucfirst($setting);
137
138
            if (method_exists($this, $method)) {
139
                $this->$method($config[$setting]);
140
            }
141
        }
142
        return $this;
143
    }
144
145
    /**
146
     * Connect to the FTP server.
147
     */
148
    public function connect()
149
    {
150
        if ($this->ssl) {
151
            $this->connection = ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout());
152
        } else {
153
            $this->connection = ftp_connect($this->getHost(), $this->getPort(), $this->getTimeout());
154
        }
155
        if (!$this->connection) {
156
            throw new \RuntimeException(
157
                'Could not connect to host: ' . $this->getHost() . ', port:' . $this->getPort()
158
            );
159
        }
160
        $this->login();
161
        $this->setConnectionPassiveMode();
162
        $this->setConnectionRoot();
163
    }
164
165
    /**
166
     * Set the connections to passive mode.
167
     *
168
     * @throws \RuntimeException
169
     */
170
    protected function setConnectionPassiveMode()
171
    {
172
        if (!ftp_pasv($this->connection, $this->passive)) {
173
            throw new \RuntimeException(
174
                'Could not set passive mode for connection: ' . $this->getHost() . '::' . $this->getPort()
175
            );
176
        }
177
    }
178
    /**
179
     * Set the connection root.
180
     */
181
    protected function setConnectionRoot()
182
    {
183
        $root = $this->getRoot();
184
        $connection = $this->connection;
185
        if (empty($root) === false && ! ftp_chdir($connection, $root)) {
186
            throw new \RuntimeException('Root is invalid or does not exist: ' . $this->getRoot());
187
        }
188
        // Store absolute path for further reference.
189
        // This is needed when creating directories and
190
        // initial root was a relative path, else the root
191
        // would be relative to the chdir'd path.
192
        $this->root = ftp_pwd($connection);
193
    }
194
    /**
195
     * Login.
196
     *
197
     * @throws \RuntimeException
198
     */
199
    protected function login()
200
    {
201
        set_error_handler(
202
            function () {
203
            }
204
        );
205
        $isLoggedIn = ftp_login($this->connection, $this->getUsername(), $this->getPassword());
206
        restore_error_handler();
207
        if (!$isLoggedIn) {
208
            $this->disconnect();
209
            throw new \RuntimeException(
210
                'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort(
211
                ) . ', username: ' . $this->getUsername()
212
            );
213
        }
214
    }
215
216
    /**
217
     * Disconnect from the FTP server.
218
     */
219
    public function disconnect()
220
    {
221
        if ($this->isConnected()) {
222
            ftp_close($this->connection);
223
        }
224
        $this->connection = null;
225
    }
226
227
    /**
228
     * @inheritdoc
229
     */
230
    public function read($path)
231
    {
232
        if (!$object = $this->readStream($path)) {
233
            return false;
234
        }
235
        $object['contents'] = stream_get_contents($object['stream']);
236
        fclose($object['stream']);
237
        unset($object['stream']);
238
        return $object;
239
    }
240
    /**
241
     * @inheritdoc
242
     */
243
    public function readStream($path)
244
    {
245
        $stream = fopen('php://temp', 'w+');
246
        $result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode);
247
        rewind($stream);
248
        if (!$result) {
249
            fclose($stream);
250
            return false;
251
        }
252
        return compact('stream');
253
    }
254
255
    /**
256
     * @inheritdoc
257
     *
258
     * @param string $directory
259
     */
260
    protected function listDirectoryContents($directory, $recursive = true)
261
    {
262
        $listing = ftp_rawlist($this->getConnection(), '-lna ' . $directory, $recursive);
263
        return $listing ? $this->normalizeListing($listing, $directory) : [];
264
    }
265
    /**
266
     * Check if the connection is open.
267
     *
268
     * @return bool
269
     */
270
    public function isConnected()
271
    {
272
        return ! is_null($this->connection) && ftp_systype($this->connection) !== false;
273
    }
274
275
    /**
276
     * @return mixed
277
     */
278
    public function getConnection()
279
    {
280
        if (!$this->isConnected()) {
281
            $this->disconnect();
282
            $this->connect();
283
        }
284
        return $this->connection;
285
    }
286
287
    /**
288
     * Set the host.
289
     *
290
     * @param string $host
291
     *
292
     * @return $this
293
     */
294
    public function setHost($host)
295
    {
296
        $this->host = $host;
297
        return $this;
298
    }
299
300
    /**
301
     * Returns the host.
302
     *
303
     * @return string
304
     */
305
    public function getHost()
306
    {
307
        return $this->host;
308
    }
309
310
    /**
311
     * Set the ftp port.
312
     *
313
     * @param int|string $port
314
     *
315
     * @return $this
316
     */
317
    public function setPort($port)
318
    {
319
        $this->port = (int) $port;
320
        return $this;
321
    }
322
323
    /**
324
     * Returns the ftp port.
325
     *
326
     * @return int
327
     */
328
    public function getPort()
329
    {
330
        return $this->port;
331
    }
332
333
    /**
334
     * Set ftp username.
335
     *
336
     * @param string $username
337
     *
338
     * @return $this
339
     */
340
    public function setUsername($username)
341
    {
342
        $this->username = $username;
343
        return $this;
344
    }
345
346
    /**
347
     * Returns the ftp username.
348
     *
349
     * @return string username
350
     */
351
    public function getUsername()
352
    {
353
        return empty($this->username) ? 'anonymous' : $this->username;
354
    }
355
356
    /**
357
     * Set the ftp password.
358
     *
359
     * @param string $password
360
     *
361
     * @return $this
362
     */
363
    public function setPassword($password)
364
    {
365
        $this->password = $password;
366
        return $this;
367
    }
368
369
    /**
370
     * Returns the password.
371
     *
372
     * @return string password
373
     */
374
    public function getPassword()
375
    {
376
        return $this->password;
377
    }
378
379
    /**
380
     * Set if Ssl is enabled.
381
     *
382
     * @param bool $ssl
383
     *
384
     * @return $this
385
     */
386
    public function setSsl($ssl)
387
    {
388
        $this->ssl = (bool) $ssl;
389
        return $this;
390
    }
391
392
    /**
393
     * Get if ssl is enabled
394
     *
395
     * @return boolean
396
     */
397
    public function isSsl()
398
    {
399
        return $this->ssl;
400
    }
401
402
    /**
403
     * Set the amount of seconds before the connection should timeout.
404
     *
405
     * @param int $timeout
406
     *
407
     * @return $this
408
     */
409
    public function setTimeout($timeout)
410
    {
411
        $this->timeout = (int) $timeout;
412
        return $this;
413
    }
414
415
    /**
416
     * Returns the amount of seconds before the connection will timeout.
417
     *
418
     * @return int
419
     */
420
    public function getTimeout()
421
    {
422
        return $this->timeout;
423
    }
424
425
    /**
426
     * Set if passive is enabled
427
     *
428
     * @param boolean $passive
429
     */
430
    public function setPassive($passive)
431
    {
432
        $this->passive = $passive;
433
    }
434
435
    /**
436
     * Get if passive is enabled
437
     *
438
     * @return boolean
439
     */
440
    public function isPassive()
441
    {
442
        return $this->passive;
443
    }
444
445
    /**
446
     * Set the transfer mode
447
     *
448
     * @param int $transferMode
449
     */
450
    public function setTransferMode($transferMode)
451
    {
452
        $this->transferMode = $transferMode;
453
    }
454
455
    /**
456
     * Get the transfer mode
457
     *
458
     * @return int
459
     */
460
    public function getTransferMode()
461
    {
462
        return $this->transferMode;
463
    }
464
465
    /**
466
     * Set the separator used
467
     *
468
     * @param string $separator
469
     */
470
    public function setSeparator($separator)
471
    {
472
        $this->separator = $separator;
473
    }
474
475
    /**
476
     * Returns the separator used
477
     *
478
     * @return string
479
     */
480
    public function getSeparator()
481
    {
482
        return $this->separator;
483
    }
484
485
    /**
486
     * Set the root folder to work from.
487
     *
488
     * @param string $root
489
     *
490
     * @return $this
491
     */
492
    public function setRoot($root)
493
    {
494
        $this->root = rtrim($root, '\\/').$this->separator;
495
        return $this;
496
    }
497
498
    /**
499
     * Returns the root folder to work from.
500
     *
501
     * @return string
502
     */
503
    public function getRoot()
504
    {
505
        return $this->root;
506
    }
507
508
    /**
509
     * Set the public permission value.
510
     *
511
     * @param int $permPublic
512
     *
513
     * @return $this
514
     */
515
    public function setPermPublic($permPublic)
516
    {
517
        $this->permPublic = $permPublic;
518
        return $this;
519
    }
520
521
    /**
522
     * Get the public permission value.
523
     *
524
     * @return int
525
     */
526
    public function getPermPublic()
527
    {
528
        return $this->permPublic;
529
    }
530
531
    /**
532
     * Set the private permission value.
533
     *
534
     * @param int $permPrivate
535
     *
536
     * @return $this
537
     */
538
    public function setPermPrivate($permPrivate)
539
    {
540
        $this->permPrivate = $permPrivate;
541
        return $this;
542
    }
543
544
    /**
545
     * Get the private permission value.
546
     *
547
     * @return int
548
     */
549
    public function getPermPrivate()
550
    {
551
        return $this->permPrivate;
552
    }
553
554
    /**
555
     * Set the FTP system type (windows or unix).
556
     *
557
     * @param string $systemType
558
     *
559
     * @return $this
560
     */
561
    public function setSystemType($systemType)
562
    {
563
        $this->systemType = strtolower($systemType);
564
        return $this;
565
    }
566
567
    /**
568
     * Return the FTP system type.
569
     *
570
     * @return string
571
     */
572
    public function getSystemType()
573
    {
574
        return $this->systemType;
575
    }
576
577
    /**
578
     * Normalize a directory listing.
579
     *
580
     * @param array  $listing
581
     * @param string $prefix
582
     *
583
     * @return array directory listing
584
     */
585
    protected function normalizeListing(array $listing, $prefix = '')
586
    {
587
        $base = $prefix;
588
        $result = [];
589
        $listing = $this->removeDotDirectories($listing);
590
        while ($item = array_shift($listing)) {
591
            if (preg_match('#^.*:$#', $item)) {
592
                $base = trim($item, ':');
593
                continue;
594
            }
595
            $result[] = $this->normalizeObject($item, $base);
596
        }
597
        return $this->sortListing($result);
598
    }
599
600
    /**
601
     * Sort a directory listing.
602
     *
603
     * @param array $result
604
     *
605
     * @return array sorted listing
606
     */
607
    protected function sortListing(array $result)
608
    {
609
        $compare = function ($one, $two) {
610
            return strnatcmp($one['path'], $two['path']);
611
        };
612
        usort($result, $compare);
613
        return $result;
614
    }
615
616
    /**
617
     * Normalize a file entry.
618
     *
619
     * @param string $item
620
     * @param string $base
621
     * @return array normalized file array
622
     *
623
     * @throws \Exception
624
     */
625
    protected function normalizeObject($item, $base)
626
    {
627
        $systemType = $this->systemType ?: $this->detectSystemType($item);
628
        if ($systemType === 'unix') {
629
            return $this->normalizeUnixObject($item, $base);
630
        } elseif ($systemType === 'windows') {
631
            return $this->normalizeWindowsObject($item, $base);
632
        }
633
        throw new \Exception('Unsupported system type.');
634
    }
635
636
    /**
637
     * Normalize a Unix file entry.
638
     *
639
     * @param string $item
640
     * @param string $base
641
     *
642
     * @return array normalized file array
643
     */
644
    protected function normalizeUnixObject($item, $base)
645
    {
646
        $item = preg_replace('#\s+#', ' ', trim($item), 7);
647
        list(
648
            $permissions,
649
            /* $number */,
650
            /* $owner */,
651
            /* $group */,
652
            $size,
653
            $month,
654
            $day,
655
            $time,
656
            $name
657
        ) = explode(' ', $item, 9);
658
659
        $type = $this->detectType($permissions);
660
        $path = empty($base) ? $name : $base.$this->separator.$name;
661
        if ($type === 'dir') {
662
            return compact('type', 'path');
663
        }
664
        $permissions = $this->normalizePermissions($permissions);
665
        $visibility = $permissions & 0044 ? self::VISIBILITY_PUBLIC : self::VISIBILITY_PRIVATE;
666
        $size = (int) $size;
667
668
        $date = \DateTime::createFromFormat(
669
            'Y-M-d H:i',
670
            '2015-'.$month.'-'.$day.' '.$time,
671
            new \DateTimeZone('Europe/Paris')
672
        );
673
674
        return compact('type', 'path', 'visibility', 'size', 'date');
675
    }
676
677
    /**
678
     * Normalize a Windows/DOS file entry.
679
     *
680
     * @param string $item
681
     * @param string $base
682
     *
683
     * @return array normalized file array
684
     */
685
    protected function normalizeWindowsObject($item, $base)
686
    {
687
        $item = preg_replace('#\s+#', ' ', trim($item), 3);
688
        list($date, $time, $size, $name) = explode(' ', $item, 4);
689
        $path = empty($base) ? $name : $base.$this->separator.$name;
690
        // Check for the correct date/time format
691
        $format = strlen($date) === 8 ? 'm-d-yH:iA' : 'Y-m-dH:i';
692
        $dateTime = \DateTime::createFromFormat($format, $date.$time)->getTimestamp();
693
        if ($size === '<DIR>') {
694
            $type = 'dir';
695
            return compact('type', 'path', 'dateTime');
696
        }
697
        $type = 'file';
698
        $visibility = self::VISIBILITY_PUBLIC;
699
        $size = (int) $size;
700
        return compact('type', 'path', 'visibility', 'size', 'dateTime');
701
    }
702
703
    /**
704
     * Get the system type from a listing item.
705
     *
706
     * @param string $item
707
     *
708
     * @return string the system type
709
     */
710
    protected function detectSystemType($item)
711
    {
712
        if (preg_match('/^[0-9]{2,4}-[0-9]{2}-[0-9]{2}/', $item)) {
713
            return $this->systemType = 'windows';
714
        }
715
        return $this->systemType = 'unix';
716
    }
717
718
    /**
719
     * Get the file type from the permissions.
720
     *
721
     * @param string $permissions
722
     *
723
     * @return string file type
724
     */
725
    protected function detectType($permissions)
726
    {
727
        return substr($permissions, 0, 1) === 'd' ? 'dir' : 'file';
728
    }
729
730
    /**
731
     * Normalize a permissions string.
732
     *
733
     * @param string $permissions
734
     *
735
     * @return int
736
     */
737
    protected function normalizePermissions($permissions)
738
    {
739
        // remove the type identifier
740
        $permissions = substr($permissions, 1);
741
        // map the string rights to the numeric counterparts
742
        $map = ['-' => '0', 'r' => '4', 'w' => '2', 'x' => '1'];
743
        $permissions = strtr($permissions, $map);
744
        // split up the permission groups
745
        $parts = str_split($permissions, 3);
746
        // convert the groups
747
        $mapper = function ($part) {
748
            return array_sum(str_split($part));
749
        };
750
        // get the sum of the groups
751
        return array_sum(array_map($mapper, $parts));
752
    }
753
754
    /**
755
     * Filter out dot-directories.
756
     *
757
     * @param array $list
758
     *
759
     * @return array
760
     */
761
    public function removeDotDirectories(array $list)
762
    {
763
        $filter = function ($line) {
764
            if (! empty($line) && !preg_match('#.* \.(\.)?$|^total#', $line)) {
765
                return true;
766
            }
767
            return false;
768
        };
769
        return array_filter($list, $filter);
770
    }
771
}
772