Completed
Push — master ( 76e253...8b6df5 )
by Sebastian
01:47
created

Client::lsLegacy()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 15
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4.016

Importance

Changes 0
Metric Value
cc 4
eloc 9
nc 5
nop 1
dl 0
loc 15
ccs 9
cts 10
cp 0.9
crap 4.016
rs 9.2
c 0
b 0
f 0
1
<?php
2
/**
3
 * This file is part of SebastianFeldmann\Ftp.
4
 *
5
 * (c) Sebastian Feldmann <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
namespace SebastianFeldmann\Ftp;
11
12
use RuntimeException;
13
14
/**
15
 * Class Client
16
 *
17
 * @package SebastianFeldmann\Ftp
18
 * @author  Sebastian Feldmann <[email protected]>
19
 * @link    https://github.com/sebastianfeldmann/ftp
20
 * @since   Class available since Release 0.9.0
21
 *
22
 * @method bool   cdUp()                                     - Changes to the parent directory
23
 * @method string chDir(string $directory)                   - Changes the current directory on a FTP server
24
 * @method string mdtm(string $file)                         - Returns last modification time from given file
25
 * @method bool   mkDir(string $path)                        - Create a directory on the FTP server
26
 * @method array  mlsd(string $path)                         - Return list of file info arrays (>= php 7.2.0)
27
 * @method array  nlist(string $path)                        - Returns list of files in given dir
28
 * @method bool   pasv(bool $passive)                        - Sets the ftp passive mode on or off
29
 * @method bool   put(string $name, string $file, int $mode) - Uploads a file to the FTP server
30
 * @method string pwd()                                      - Returns current working directory
31
 * @method int    size(string $file)                         - Returns given files sizes in bytes
32
 */
33
class Client
34
{
35
    /**
36
     * PHP FTP connection resource.
37
     *
38
     * @var resource
39
     */
40
    private $connection;
41
42
    /**
43
     * Host to connect to.
44
     *
45
     * @var string
46
     */
47
    private $host;
48
49
    /**
50
     * Port to connect to.
51
     *
52
     * @var int
53
     */
54
    private $port;
55
56
    /**
57
     * User to login.
58
     *
59
     * @var string
60
     */
61
    private $user;
62
63
    /**
64
     * Password to login.
65
     *
66
     * @var string
67
     */
68
    private $password;
69
70
    /**
71
     * Use passive ftp mode
72
     *
73
     * @var bool
74
     */
75
    private $passive;
76
77
    /**
78
     * Client constructor.
79
     *
80
     * @param string $url
81
     * @param bool   $passive
82
     */
83 1
    public function __construct(string $url, bool $passive = false)
84
    {
85 1
        if (!extension_loaded('ftp')) {
86
            throw new RuntimeException('FTP extension is not loaded.');
87
        }
88 1
        $this->passive = $passive;
89 1
        $this->setup($url);
90 1
        $this->login();
91 1
    }
92
93
    /**
94
     * Determine if file is a directory.
95
     *
96
     * @param  string $name
97
     * @return bool
98
     */
99 1
    public function isDir(string $name)
100
    {
101 1
        $current = $this->pwd();
102
        try {
103 1
            $this->chDir($name);
104 1
            $this->chDir($current);
105 1
            return true;
106 1
        } catch (\Exception $e) {
107
            // do nothing
108
        }
109 1
        return false;
110
    }
111
112
    /**
113
     * Returns to the home directory.
114
     *
115
     * @return void
116
     */
117
    public function chHome()
118
    {
119
        $this->chDir('');
120
    }
121
122
    /**
123
     * Return list of all files in directory.
124
     *
125
     * @param  string $path
126
     * @return \SebastianFeldmann\Ftp\File[]
127
     * @throws \Exception
128
     */
129 1
    public function ls(string $path = '') : array
130
    {
131 1
        return version_compare(PHP_VERSION, '7.2.0', '>=')
132
            ? $this->ls72($path)
133 1
            : $this->lsLegacy($path);
134
135
    }
136
137
    /**
138
     * Return list of all files in directory for php 7.2.0 and higher.
139
     *
140
     * @param  string $path
141
     * @return \SebastianFeldmann\Ftp\File[]
142
     * @throws \Exception
143
     */
144
    private function ls72(string $path) : array
145
    {
146
        $list = [];
147
        foreach ($this->mlsd($path) as $fileInfo) {
148
            $list[] = new File($fileInfo);
149
        }
150
        return $list;
151
    }
152
153
    /**
154
     * Return list of all files in directory for php versione below 7.2.0.
155
     *
156
     * @param  string $path
157
     * @return \SebastianFeldmann\Ftp\File[]
158
     * @throws \Exception
159
     */
160 1
    private function lsLegacy(string $path) : array
161
    {
162 1
        $list = [];
163 1
        foreach ($this->nlist($path) as $name) {
164 1
            $type   = $this->isDir($name) ? 'dir' : 'file';
165 1
            $size   = $this->size($name);
166 1
            $mtime  = $this->mdtm($name);
167
168 1
            if ($mtime == -1) {
169
                throw new RuntimeException('FTP server doesnt support \'ftp_mdtm\'');
170
            }
171
172 1
            $list[] = new File(['name' => $name, 'modify' => $mtime, 'type' => $type, 'size' => $size]);
173
        }
174 1
        return $list;
175
    }
176
177
    /**
178
     * Return list of directories in given path.
179
     *
180
     * @param  string $path
181
     * @return array
182
     * @throws \Exception
183
     */
184
    public function lsDirs(string $path = '') : array
185
    {
186
        return array_filter(
187
            $this->ls($path),
188
            function(File $file) {
189
                return $file->isDir();
190
            }
191
        );
192
    }
193
194
    /**
195
     * Return list of files in given path.
196
     *
197
     * @param  string $path
198
     * @return array
199
     * @throws \Exception
200
     */
201
    public function lsFiles(string $path = '') : array
202
    {
203
        return array_filter(
204
            $this->ls($path),
205
            function(File $file) {
206
                return $file->isFile();
207
            }
208
        );
209
    }
210
211
    /**
212
     * Upload local file to ftp server.
213
     *
214
     * @param  string $file Path to local file that should be uploaded.
215
     * @param  string $path Path to store the file under.
216
     * @param  string $name Filename on the ftp server.
217
     * @return void
218
     */
219
    public function uploadFile(string $file, string $path, string $name)
220
    {
221
        // to store the file we have to make sure the directory exists
222
        foreach ($this->extractDirectories($path) as $dir) {
223
            // if change to directory fails
224
            // create the dir and change into it afterwards
225
            try {
226
                $this->chDir($dir);
227
            } catch (\Exception $e) {
228
                $this->mkDir($dir);
229
                $this->chDir($dir);
230
            }
231
        }
232
        if (!$this->put($name, $file, FTP_BINARY)) {
233
            $error   = error_get_last();
234
            $message = $error['message'];
235
            throw new RuntimeException(sprintf('error uploading file: %s - %s', $file, $message));
236
        }
237
    }
238
239
    /**
240
     * Setup local member variables by parsing the ftp url.
241
     *
242
     * @param string $url
243
     */
244 1
    private function setup(string $url)
245
    {
246 1
        $parts          = \parse_url($url);
247 1
        $this->host     = $parts['host'] ?? '';
248 1
        $this->port     = $parts['port'] ?? 21;
249 1
        $this->user     = $parts['user'] ?? '';
250 1
        $this->password = $parts['pass'] ?? '';
251 1
    }
252
253
    /**
254
     * Setup ftp connection
255
     *
256
     * @throws \RuntimeException
257
     */
258 1
    private function login()
259
    {
260 1
        if (empty($this->host)) {
261
            throw new RuntimeException('no host to connect to');
262
        }
263
264 1
        $old = error_reporting(0);
265 1
        if (!$this->connection = ftp_connect($this->host, $this->port)) {
266
            error_reporting($old);
267
            throw new RuntimeException(sprintf('unable to connect to ftp server %s', $this->host));
268
        }
269
270 1
        if (!ftp_login($this->connection, $this->user, $this->password)) {
271
            error_reporting($old);
272
            throw new RuntimeException(
273
                sprintf('authentication failed for %s@%s', $this->user, $this->host)
274
            );
275
        }
276
        // set passive mode if needed
277 1
        $this->pasv($this->passive);
278 1
        error_reporting($old);
279 1
    }
280
281
    /**
282
     * Return list of remote directories to travers.
283
     *
284
     * @param  string $path
285
     * @return array
286
     */
287
    private function extractDirectories(string $path) : array
288
    {
289
        $remoteDirs = [];
290
        if (!empty($path)) {
291
            $remoteDirs = explode('/', $path);
292
            // fix empty first array element for absolute path
293
            if (substr($path, 0, 1) === '/') {
294
                $remoteDirs[0] = '/';
295
            }
296
            $remoteDirs = array_filter($remoteDirs);
297
        }
298
        return $remoteDirs;
299
    }
300
301
    /**
302
     * Handle all ftp_* functions.
303
     *
304
     * @param  string $name
305
     * @param  array  $args
306
     * @return mixed
307
     */
308 1
    public function __call($name, $args)
309
    {
310 1
        $function = 'ftp_' . strtolower($name);
311 1
        if (!function_exists($function)) {
312
            throw new RuntimeException(sprintf('invalid method call: %s', $function));
313
        }
314 1
        $old = error_reporting(0);
315 1
        array_unshift($args, $this->connection);
316 1
        if (!$result = call_user_func_array($function, $args)) {
317 1
            $error = error_get_last();
318 1
            error_reporting($old);
319 1
            throw new RuntimeException($error['message']);
320
        }
321 1
        error_reporting($old);
322 1
        return $result;
323
    }
324
}
325