Completed
Push — master ( 212b1e...c0d8c8 )
by Sebastian
01:56
created

Client::lsDirs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 1
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
ccs 0
cts 5
cp 0
crap 2
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 1.0.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  nlist(string $path)      - Returns list of files in given dir
27
 * @method bool   pasv(bool $passive)      - Sets the ftp passive mode on or off
28
 * @method bool   put()                    - Uploads a file to the FTP server
29
 * @method string pwd()                    - Returns current working directory
30
 * @method int    size(string $file)       - Returns given files sizes in bytes
31
 */
32
class Client
33
{
34
    /**
35
     * PHP FTP connection resource.
36
     *
37
     * @var resource
38
     */
39
    private $connection;
40
41
    /**
42
     * Host to connect to.
43
     *
44
     * @var string
45
     */
46
    private $host;
47
48
    /**
49
     * Port to connect to.
50
     *
51
     * @var int
52
     */
53
    private $port;
54
55
    /**
56
     * User to login.
57
     *
58
     * @var string
59
     */
60
    private $user;
61
62
    /**
63
     * Password to login.
64
     *
65
     * @var string
66
     */
67
    private $password;
68
69
    /**
70
     * Use passive ftp mode
71
     *
72
     * @var bool
73
     */
74
    private $passive;
75
76
    /**
77
     * Client constructor.
78
     *
79
     * @param string $url
80
     * @param bool   $passive
81
     */
82 1
    public function __construct(string $url, bool $passive = false)
83
    {
84 1
        if (!extension_loaded('ftp')) {
85
            throw new RuntimeException('FTP extension is not loaded.');
86
        }
87 1
        $this->passive = $passive;
88 1
        $this->setup($url);
89 1
        $this->login();
90 1
    }
91
92
    /**
93
     * Determine if file is a directory.
94
     *
95
     * @param  string $name
96
     * @return bool
97
     */
98 1
    public function isDir(string $name)
99
    {
100 1
        $current = $this->pwd();
101
        try {
102 1
            $this->chDir($name);
103 1
            $this->chDir($current);
104 1
            return true;
105 1
        } catch (\Exception $e) {
106
            // do nothing
107
        }
108 1
        return false;
109
    }
110
111
    /**
112
     * Returns to the home directory.
113
     *
114
     * @return void
115
     */
116
    public function chHome()
117
    {
118
        $this->chDir('');
119
    }
120
121
    /**
122
     * Return list of all files in directory.
123
     *
124
     * @param  string $path
125
     * @return \SebastianFeldmann\Ftp\File[]
126
     * @throws \Exception
127
     */
128 1
    public function ls(string $path = '') : array
129
    {
130 1
        $list = [];
131 1
        foreach ($this->nlist($path) as $name) {
132 1
            $type   = $this->isDir($name) ? 'dir' : 'file';
133 1
            $mtime  = $this->mdtm($name);
134 1
            $size   = $this->size($name);
135 1
            $list[] = new File(['name' => $name, 'modify' => $mtime, 'type' => $type, 'size' => $size]);
136
        }
137 1
        return $list;
138
    }
139
140
    /**
141
     * Return list of directories in given path.
142
     *
143
     * @param  string $path
144
     * @return array
145
     * @throws \Exception
146
     */
147
    public function lsDirs(string $path = '') : array
148
    {
149
        return array_filter(
150
            $this->ls($path),
151
            function(File $file) {
152
                return $file->isDir();
153
            }
154
        );
155
    }
156
157
    /**
158
     * Return list of files in given path.
159
     *
160
     * @param  string $path
161
     * @return array
162
     * @throws \Exception
163
     */
164
    public function lsFiles(string $path = '') : array
165
    {
166
        return array_filter(
167
            $this->ls($path),
168
            function(File $file) {
169
                return $file->isFile();
170
            }
171
        );
172
    }
173
174
    /**
175
     * Upload local file to ftp server.
176
     *
177
     * @param  string $file Path to local file that should be uploaded.
178
     * @param  string $path Path to store the file under.
179
     * @param  string $name Filename on the ftp server.
180
     * @return void
181
     */
182
    public function uploadFile(string $file, string $path, string $name)
183
    {
184
        // to store the file we have to make sure the directory exists
185
        foreach ($this->extractDirectories($path) as $dir) {
186
            // if change to directory fails
187
            // create the dir and change into it afterwards
188
            if (!$this->chDir($dir)) {
189
                $this->mkDir($dir);
190
                $this->chDir($dir);
191
            }
192
        }
193
        if (!$this->put($name, $file, FTP_BINARY)) {
0 ignored issues
show
Unused Code introduced by
The call to SebastianFeldmann\Ftp\Client::put() has too many arguments starting with $name. ( Ignorable by Annotation )

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

193
        if (!$this->/** @scrutinizer ignore-call */ put($name, $file, FTP_BINARY)) {

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
194
            $error   = error_get_last();
195
            $message = $error['message'];
196
            throw new RuntimeException(sprintf('error uploading file: %s - %s', $file, $message));
197
        }
198
    }
199
200
    /**
201
     * Setup local member variables by parsing the ftp url.
202
     *
203
     * @param string $url
204
     */
205 1
    private function setup(string $url)
206
    {
207 1
        $parts          = \parse_url($url);
208 1
        $this->host     = $parts['host'] ?? '';
209 1
        $this->port     = $parts['port'] ?? 21;
210 1
        $this->user     = $parts['user'] ?? '';
211 1
        $this->password = $parts['pass'] ?? '';
212 1
    }
213
214
    /**
215
     * Setup ftp connection
216
     *
217
     * @throws \RuntimeException
218
     */
219 1
    private function login()
220
    {
221 1
        if (empty($this->host)) {
222
            throw new RuntimeException('no host to connect to');
223
        }
224
225 1
        $old = error_reporting(0);
226 1
        if (!$this->connection = ftp_connect($this->host, $this->port)) {
0 ignored issues
show
Documentation Bug introduced by
It seems like ftp_connect($this->host, $this->port) of type array is incompatible with the declared type resource of property $connection.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
227
            error_reporting($old);
228
            throw new RuntimeException(sprintf('unable to connect to ftp server %s', $this->host));
229
        }
230
231 1
        if (!ftp_login($this->connection, $this->user, $this->password)) {
232
            error_reporting($old);
233
            throw new RuntimeException(
234
                sprintf('authentication failed for %s@%s', $this->user, $this->host)
235
            );
236
        }
237
        // set passive mode if needed
238 1
        $this->pasv($this->passive);
239 1
        error_reporting($old);
240 1
    }
241
242
    /**
243
     * Return list of remote directories to travers.
244
     *
245
     * @param  string $path
246
     * @return array
247
     */
248
    private function extractDirectories(string $path) : array
249
    {
250
        $remoteDirs = [];
251
        if (!empty($path)) {
252
            $remoteDirs = explode('/', $path);
253
            // fix empty first array element for absolute path
254
            if (substr($path, 0, 1) === '/') {
255
                $remoteDirs[0] = '/';
256
            }
257
            $remoteDirs = array_filter($remoteDirs);
258
        }
259
        return $remoteDirs;
260
    }
261
262
    /**
263
     * Handle all ftp_* functions.
264
     *
265
     * @param  string $name
266
     * @param  array  $args
267
     * @return mixed
268
     */
269 1
    public function __call($name, $args)
270
    {
271 1
        $function = 'ftp_' . strtolower($name);
272 1
        if (!function_exists($function)) {
273
            throw new RuntimeException(sprintf('invalid method call: %s', $function));
274
        }
275 1
        $old = error_reporting(0);
276 1
        array_unshift($args, $this->connection);
277 1
        if (!$result = call_user_func_array($function, $args)) {
278 1
            $error = error_get_last();
279 1
            error_reporting($old);
280 1
            throw new RuntimeException($error['message']);
281
        }
282 1
        error_reporting($old);
283 1
        return $result;
284
    }
285
}
286