run:R W Run
7.85 KB
2026-03-11 16:18:51
R W Run
3.54 KB
2026-03-11 16:18:51
R W Run
148.33 KB
2026-03-11 16:18:51
R W Run
11.45 KB
2026-03-11 16:18:51
R W Run
3.58 KB
2026-03-11 16:18:51
R W Run
2.53 KB
2026-03-11 16:18:51
R W Run
2.6 KB
2026-03-11 16:18:51
R W Run
6.59 KB
2026-03-11 16:18:51
R W Run
14.83 KB
2026-03-11 16:18:51
R W Run
21.18 KB
2026-03-11 16:18:51
R W Run
48.13 KB
2026-03-11 16:18:51
R W Run
4.07 KB
2026-03-11 16:18:51
R W Run
5.3 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
26.73 KB
2026-03-11 16:18:51
R W Run
2.8 KB
2026-03-11 16:18:51
R W Run
15.2 KB
2026-03-11 16:18:51
R W Run
192.08 KB
2026-03-11 16:18:51
R W Run
11.77 KB
2026-03-11 16:18:51
R W Run
3.2 KB
2026-03-11 16:18:51
R W Run
22.89 KB
2026-03-11 16:18:51
R W Run
12.77 KB
2026-03-11 16:18:51
R W Run
4.08 KB
2026-03-11 16:18:51
R W Run
26.27 KB
2026-03-11 16:18:51
R W Run
4.97 KB
2026-03-11 16:18:51
R W Run
5.57 KB
2026-03-11 16:18:51
R W Run
13.93 KB
2026-03-11 16:18:51
R W Run
4.09 KB
2026-03-11 16:18:51
R W Run
6.79 KB
2026-03-11 16:18:51
R W Run
60.45 KB
2026-03-11 16:18:51
R W Run
32.4 KB
2026-03-11 16:18:51
R W Run
18.24 KB
2026-03-11 16:18:51
R W Run
66.01 KB
2026-03-11 16:18:51
R W Run
23.84 KB
2026-03-11 16:18:51
R W Run
17.72 KB
2026-03-11 16:18:51
R W Run
22.71 KB
2026-03-11 16:18:51
R W Run
18.05 KB
2026-03-11 16:18:51
R W Run
22.76 KB
2026-03-11 16:18:51
R W Run
7.34 KB
2026-03-11 16:18:51
R W Run
4.51 KB
2026-03-11 16:18:51
R W Run
9.02 KB
2026-03-11 16:18:51
R W Run
1.46 KB
2026-03-11 16:18:51
R W Run
51.76 KB
2026-03-11 16:18:51
R W Run
25.29 KB
2026-03-11 16:18:51
R W Run
21.61 KB
2026-03-11 16:18:51
R W Run
27.77 KB
2026-03-11 16:18:51
R W Run
15.35 KB
2026-03-11 16:18:51
R W Run
24.54 KB
2026-03-11 16:18:51
R W Run
56.44 KB
2026-03-11 16:18:51
R W Run
1.42 KB
2026-03-11 16:18:51
R W Run
63.66 KB
2026-03-11 16:18:51
R W Run
31.9 KB
2026-03-11 16:18:51
R W Run
14.44 KB
2026-03-11 16:18:51
R W Run
36.47 KB
2026-03-11 16:18:51
R W Run
14 KB
2026-03-11 16:18:51
R W Run
121.89 KB
2026-03-11 16:18:51
R W Run
6.26 KB
2026-03-11 16:18:51
R W Run
20.73 KB
2026-03-11 16:18:51
R W Run
15.23 KB
2026-03-11 16:18:51
R W Run
10.14 KB
2026-03-11 16:18:51
R W Run
6.94 KB
2026-03-11 16:18:51
R W Run
1.44 KB
2026-03-11 16:18:51
R W Run
46.85 KB
2026-03-11 16:18:51
R W Run
18.61 KB
2026-03-11 16:18:51
R W Run
6.08 KB
2026-03-11 16:18:51
R W Run
20.06 KB
2026-03-11 16:18:51
R W Run
5.73 KB
2026-03-11 16:18:51
R W Run
68.18 KB
2026-03-11 16:18:51
R W Run
40.8 KB
2026-03-11 16:18:51
R W Run
1.44 KB
2026-03-11 16:18:51
R W Run
25.26 KB
2026-03-11 16:18:51
R W Run
95.94 KB
2026-03-11 16:18:51
R W Run
43.12 KB
2026-03-11 16:18:51
R W Run
41.73 KB
2026-03-11 16:18:51
R W Run
6.46 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
116.31 KB
2026-03-11 16:18:51
R W Run
9.39 KB
2026-03-11 16:18:51
R W Run
64.34 KB
2026-03-11 16:18:51
R W Run
44.73 KB
2026-03-11 16:18:51
R W Run
1.27 KB
2026-03-11 16:18:51
R W Run
3.68 KB
2026-03-11 16:18:51
R W Run
33.53 KB
2026-03-11 16:18:51
R W Run
48.84 KB
2026-03-11 16:18:51
R W Run
26.35 KB
2026-03-11 16:18:51
R W Run
1.12 KB
2026-03-11 16:18:51
R W Run
4.19 KB
2026-03-11 16:18:51
R W Run
38.19 KB
2026-03-11 16:18:51
R W Run
91.33 KB
2026-03-11 16:18:51
R W Run
80.39 KB
2026-03-11 16:18:51
R W Run
32.67 KB
2026-03-11 16:18:51
R W Run
16.18 KB
2026-03-11 16:18:51
R W Run
44.46 KB
2026-03-11 16:18:51
R W Run
6.23 KB
2026-03-11 16:18:51
R W Run
8.23 KB
2026-03-11 16:18:51
R W Run
96.96 KB
2026-03-11 16:18:51
R W Run
6.83 KB
2026-03-11 16:18:51
R W Run
46.62 KB
2026-03-11 16:18:51
R W Run
10.82 KB
2026-03-11 16:18:51
R W Run
68.86 KB
2026-03-11 16:18:51
R W Run
33.63 KB
2026-03-11 16:18:51
R W Run
113.3 KB
2026-03-11 16:18:51
R W Run
22.98 KB
2026-03-11 16:18:51
R W Run
10.66 KB
2026-03-11 16:18:51
R W Run
error_log
📄class-wp-filesystem-ftpext.php
1<?php
2/**
3 * WordPress FTP Filesystem.
4 *
5 * @package WordPress
6 * @subpackage Filesystem
7 */
8
9/**
10 * WordPress Filesystem Class for implementing FTP.
11 *
12 * @since 2.5.0
13 *
14 * @see WP_Filesystem_Base
15 */
16class WP_Filesystem_FTPext extends WP_Filesystem_Base {
17
18 /**
19 * @since 2.5.0
20 * @var FTP\Connection|resource|false
21 */
22 public $link;
23
24 /**
25 * Constructor.
26 *
27 * @since 2.5.0
28 *
29 * @param array $opt
30 */
31 public function __construct( $opt = '' ) {
32 $this->method = 'ftpext';
33 $this->errors = new WP_Error();
34
35 // Check if possible to use ftp functions.
36 if ( ! extension_loaded( 'ftp' ) ) {
37 $this->errors->add( 'no_ftp_ext', __( 'The ftp PHP extension is not available' ) );
38 return;
39 }
40
41 // This class uses the timeout on a per-connection basis, others use it on a per-action basis.
42 if ( ! defined( 'FS_TIMEOUT' ) ) {
43 define( 'FS_TIMEOUT', 4 * MINUTE_IN_SECONDS );
44 }
45
46 if ( empty( $opt['port'] ) ) {
47 $this->options['port'] = 21;
48 } else {
49 $this->options['port'] = $opt['port'];
50 }
51
52 if ( empty( $opt['hostname'] ) ) {
53 $this->errors->add( 'empty_hostname', __( 'FTP hostname is required' ) );
54 } else {
55 $this->options['hostname'] = $opt['hostname'];
56 }
57
58 // Check if the options provided are OK.
59 if ( empty( $opt['username'] ) ) {
60 $this->errors->add( 'empty_username', __( 'FTP username is required' ) );
61 } else {
62 $this->options['username'] = $opt['username'];
63 }
64
65 if ( empty( $opt['password'] ) ) {
66 $this->errors->add( 'empty_password', __( 'FTP password is required' ) );
67 } else {
68 $this->options['password'] = $opt['password'];
69 }
70
71 $this->options['ssl'] = false;
72
73 if ( isset( $opt['connection_type'] ) && 'ftps' === $opt['connection_type'] ) {
74 $this->options['ssl'] = true;
75 }
76 }
77
78 /**
79 * Connects filesystem.
80 *
81 * @since 2.5.0
82 *
83 * @return bool True on success, false on failure.
84 */
85 public function connect() {
86 if ( isset( $this->options['ssl'] ) && $this->options['ssl'] && function_exists( 'ftp_ssl_connect' ) ) {
87 $this->link = @ftp_ssl_connect( $this->options['hostname'], $this->options['port'], FS_CONNECT_TIMEOUT );
88 } else {
89 $this->link = @ftp_connect( $this->options['hostname'], $this->options['port'], FS_CONNECT_TIMEOUT );
90 }
91
92 if ( ! $this->link ) {
93 $this->errors->add(
94 'connect',
95 sprintf(
96 /* translators: %s: hostname:port */
97 __( 'Failed to connect to FTP Server %s' ),
98 $this->options['hostname'] . ':' . $this->options['port']
99 )
100 );
101
102 return false;
103 }
104
105 if ( ! @ftp_login( $this->link, $this->options['username'], $this->options['password'] ) ) {
106 $this->errors->add(
107 'auth',
108 sprintf(
109 /* translators: %s: Username. */
110 __( 'Username/Password incorrect for %s' ),
111 $this->options['username']
112 )
113 );
114
115 return false;
116 }
117
118 // Set the connection to use Passive FTP.
119 ftp_pasv( $this->link, true );
120
121 if ( @ftp_get_option( $this->link, FTP_TIMEOUT_SEC ) < FS_TIMEOUT ) {
122 @ftp_set_option( $this->link, FTP_TIMEOUT_SEC, FS_TIMEOUT );
123 }
124
125 return true;
126 }
127
128 /**
129 * Reads entire file into a string.
130 *
131 * @since 2.5.0
132 *
133 * @param string $file Name of the file to read.
134 * @return string|false Read data on success, false if no temporary file could be opened,
135 * or if the file couldn't be retrieved.
136 */
137 public function get_contents( $file ) {
138 $tempfile = wp_tempnam( $file );
139 $temphandle = fopen( $tempfile, 'w+' );
140
141 if ( ! $temphandle ) {
142 unlink( $tempfile );
143 return false;
144 }
145
146 if ( ! ftp_fget( $this->link, $temphandle, $file, FTP_BINARY ) ) {
147 fclose( $temphandle );
148 unlink( $tempfile );
149 return false;
150 }
151
152 fseek( $temphandle, 0 ); // Skip back to the start of the file being written to.
153 $contents = '';
154
155 while ( ! feof( $temphandle ) ) {
156 $contents .= fread( $temphandle, 8 * KB_IN_BYTES );
157 }
158
159 fclose( $temphandle );
160 unlink( $tempfile );
161
162 return $contents;
163 }
164
165 /**
166 * Reads entire file into an array.
167 *
168 * @since 2.5.0
169 *
170 * @param string $file Path to the file.
171 * @return array|false File contents in an array on success, false on failure.
172 */
173 public function get_contents_array( $file ) {
174 return explode( "\n", $this->get_contents( $file ) );
175 }
176
177 /**
178 * Writes a string to a file.
179 *
180 * @since 2.5.0
181 *
182 * @param string $file Remote path to the file where to write the data.
183 * @param string $contents The data to write.
184 * @param int|false $mode Optional. The file permissions as octal number, usually 0644.
185 * Default false.
186 * @return bool True on success, false on failure.
187 */
188 public function put_contents( $file, $contents, $mode = false ) {
189 $tempfile = wp_tempnam( $file );
190 $temphandle = fopen( $tempfile, 'wb+' );
191
192 if ( ! $temphandle ) {
193 unlink( $tempfile );
194 return false;
195 }
196
197 mbstring_binary_safe_encoding();
198
199 $data_length = strlen( $contents );
200 $bytes_written = fwrite( $temphandle, $contents );
201
202 reset_mbstring_encoding();
203
204 if ( $data_length !== $bytes_written ) {
205 fclose( $temphandle );
206 unlink( $tempfile );
207 return false;
208 }
209
210 fseek( $temphandle, 0 ); // Skip back to the start of the file being written to.
211
212 $ret = ftp_fput( $this->link, $file, $temphandle, FTP_BINARY );
213
214 fclose( $temphandle );
215 unlink( $tempfile );
216
217 $this->chmod( $file, $mode );
218
219 return $ret;
220 }
221
222 /**
223 * Gets the current working directory.
224 *
225 * @since 2.5.0
226 *
227 * @return string|false The current working directory on success, false on failure.
228 */
229 public function cwd() {
230 $cwd = ftp_pwd( $this->link );
231
232 if ( $cwd ) {
233 $cwd = trailingslashit( $cwd );
234 }
235
236 return $cwd;
237 }
238
239 /**
240 * Changes current directory.
241 *
242 * @since 2.5.0
243 *
244 * @param string $dir The new current directory.
245 * @return bool True on success, false on failure.
246 */
247 public function chdir( $dir ) {
248 return @ftp_chdir( $this->link, $dir );
249 }
250
251 /**
252 * Changes filesystem permissions.
253 *
254 * @since 2.5.0
255 *
256 * @param string $file Path to the file.
257 * @param int|false $mode Optional. The permissions as octal number, usually 0644 for files,
258 * 0755 for directories. Default false.
259 * @param bool $recursive Optional. If set to true, changes file permissions recursively.
260 * Default false.
261 * @return bool True on success, false on failure.
262 */
263 public function chmod( $file, $mode = false, $recursive = false ) {
264 if ( ! $mode ) {
265 if ( $this->is_file( $file ) ) {
266 $mode = FS_CHMOD_FILE;
267 } elseif ( $this->is_dir( $file ) ) {
268 $mode = FS_CHMOD_DIR;
269 } else {
270 return false;
271 }
272 }
273
274 // chmod any sub-objects if recursive.
275 if ( $recursive && $this->is_dir( $file ) ) {
276 $filelist = $this->dirlist( $file );
277
278 foreach ( (array) $filelist as $filename => $filemeta ) {
279 $this->chmod( $file . '/' . $filename, $mode, $recursive );
280 }
281 }
282
283 // chmod the file or directory.
284 if ( ! function_exists( 'ftp_chmod' ) ) {
285 return (bool) ftp_site( $this->link, sprintf( 'CHMOD %o %s', $mode, $file ) );
286 }
287
288 return (bool) ftp_chmod( $this->link, $mode, $file );
289 }
290
291 /**
292 * Gets the file owner.
293 *
294 * @since 2.5.0
295 *
296 * @param string $file Path to the file.
297 * @return string|false Username of the owner on success, false on failure.
298 */
299 public function owner( $file ) {
300 $dir = $this->dirlist( $file );
301
302 return $dir[ $file ]['owner'];
303 }
304
305 /**
306 * Gets the permissions of the specified file or filepath in their octal format.
307 *
308 * @since 2.5.0
309 *
310 * @param string $file Path to the file.
311 * @return string Mode of the file (the last 3 digits).
312 */
313 public function getchmod( $file ) {
314 $dir = $this->dirlist( $file );
315
316 return $dir[ $file ]['permsn'];
317 }
318
319 /**
320 * Gets the file's group.
321 *
322 * @since 2.5.0
323 *
324 * @param string $file Path to the file.
325 * @return string|false The group on success, false on failure.
326 */
327 public function group( $file ) {
328 $dir = $this->dirlist( $file );
329
330 return $dir[ $file ]['group'];
331 }
332
333 /**
334 * Copies a file.
335 *
336 * @since 2.5.0
337 *
338 * @param string $source Path to the source file.
339 * @param string $destination Path to the destination file.
340 * @param bool $overwrite Optional. Whether to overwrite the destination file if it exists.
341 * Default false.
342 * @param int|false $mode Optional. The permissions as octal number, usually 0644 for files,
343 * 0755 for dirs. Default false.
344 * @return bool True on success, false on failure.
345 */
346 public function copy( $source, $destination, $overwrite = false, $mode = false ) {
347 if ( ! $overwrite && $this->exists( $destination ) ) {
348 return false;
349 }
350
351 $content = $this->get_contents( $source );
352
353 if ( false === $content ) {
354 return false;
355 }
356
357 return $this->put_contents( $destination, $content, $mode );
358 }
359
360 /**
361 * Moves a file or directory.
362 *
363 * After moving files or directories, OPcache will need to be invalidated.
364 *
365 * If moving a directory fails, `copy_dir()` can be used for a recursive copy.
366 *
367 * Use `move_dir()` for moving directories with OPcache invalidation and a
368 * fallback to `copy_dir()`.
369 *
370 * @since 2.5.0
371 *
372 * @param string $source Path to the source file or directory.
373 * @param string $destination Path to the destination file or directory.
374 * @param bool $overwrite Optional. Whether to overwrite the destination if it exists.
375 * Default false.
376 * @return bool True on success, false on failure.
377 */
378 public function move( $source, $destination, $overwrite = false ) {
379 return ftp_rename( $this->link, $source, $destination );
380 }
381
382 /**
383 * Deletes a file or directory.
384 *
385 * @since 2.5.0
386 *
387 * @param string $file Path to the file or directory.
388 * @param bool $recursive Optional. If set to true, deletes files and folders recursively.
389 * Default false.
390 * @param string|false $type Type of resource. 'f' for file, 'd' for directory.
391 * Default false.
392 * @return bool True on success, false on failure.
393 */
394 public function delete( $file, $recursive = false, $type = false ) {
395 if ( empty( $file ) ) {
396 return false;
397 }
398
399 if ( 'f' === $type || $this->is_file( $file ) ) {
400 return ftp_delete( $this->link, $file );
401 }
402
403 if ( ! $recursive ) {
404 return ftp_rmdir( $this->link, $file );
405 }
406
407 $filelist = $this->dirlist( trailingslashit( $file ) );
408
409 if ( ! empty( $filelist ) ) {
410 foreach ( $filelist as $delete_file ) {
411 $this->delete( trailingslashit( $file ) . $delete_file['name'], $recursive, $delete_file['type'] );
412 }
413 }
414
415 return ftp_rmdir( $this->link, $file );
416 }
417
418 /**
419 * Checks if a file or directory exists.
420 *
421 * @since 2.5.0
422 * @since 6.3.0 Returns false for an empty path.
423 *
424 * @param string $path Path to file or directory.
425 * @return bool Whether $path exists or not.
426 */
427 public function exists( $path ) {
428 /*
429 * Check for empty path. If ftp_nlist() receives an empty path,
430 * it checks the current working directory and may return true.
431 *
432 * See https://core.trac.wordpress.org/ticket/33058.
433 */
434 if ( '' === $path ) {
435 return false;
436 }
437
438 $list = ftp_nlist( $this->link, $path );
439
440 if ( empty( $list ) && $this->is_dir( $path ) ) {
441 return true; // File is an empty directory.
442 }
443
444 return ! empty( $list ); // Empty list = no file, so invert.
445 }
446
447 /**
448 * Checks if resource is a file.
449 *
450 * @since 2.5.0
451 *
452 * @param string $file File path.
453 * @return bool Whether $file is a file.
454 */
455 public function is_file( $file ) {
456 return $this->exists( $file ) && ! $this->is_dir( $file );
457 }
458
459 /**
460 * Checks if resource is a directory.
461 *
462 * @since 2.5.0
463 *
464 * @param string $path Directory path.
465 * @return bool Whether $path is a directory.
466 */
467 public function is_dir( $path ) {
468 $cwd = $this->cwd();
469 $result = @ftp_chdir( $this->link, trailingslashit( $path ) );
470
471 if ( $result && $path === $this->cwd() || $this->cwd() !== $cwd ) {
472 @ftp_chdir( $this->link, $cwd );
473 return true;
474 }
475
476 return false;
477 }
478
479 /**
480 * Checks if a file is readable.
481 *
482 * @since 2.5.0
483 *
484 * @param string $file Path to file.
485 * @return bool Whether $file is readable.
486 */
487 public function is_readable( $file ) {
488 return true;
489 }
490
491 /**
492 * Checks if a file or directory is writable.
493 *
494 * @since 2.5.0
495 *
496 * @param string $path Path to file or directory.
497 * @return bool Whether $path is writable.
498 */
499 public function is_writable( $path ) {
500 return true;
501 }
502
503 /**
504 * Gets the file's last access time.
505 *
506 * @since 2.5.0
507 *
508 * @param string $file Path to file.
509 * @return int|false Unix timestamp representing last access time, false on failure.
510 */
511 public function atime( $file ) {
512 return false;
513 }
514
515 /**
516 * Gets the file modification time.
517 *
518 * @since 2.5.0
519 *
520 * @param string $file Path to file.
521 * @return int|false Unix timestamp representing modification time, false on failure.
522 */
523 public function mtime( $file ) {
524 return ftp_mdtm( $this->link, $file );
525 }
526
527 /**
528 * Gets the file size (in bytes).
529 *
530 * @since 2.5.0
531 *
532 * @param string $file Path to file.
533 * @return int|false Size of the file in bytes on success, false on failure.
534 */
535 public function size( $file ) {
536 $size = ftp_size( $this->link, $file );
537
538 return ( $size > -1 ) ? $size : false;
539 }
540
541 /**
542 * Sets the access and modification times of a file.
543 *
544 * Note: If $file doesn't exist, it will be created.
545 *
546 * @since 2.5.0
547 *
548 * @param string $file Path to file.
549 * @param int $time Optional. Modified time to set for file.
550 * Default 0.
551 * @param int $atime Optional. Access time to set for file.
552 * Default 0.
553 * @return bool True on success, false on failure.
554 */
555 public function touch( $file, $time = 0, $atime = 0 ) {
556 return false;
557 }
558
559 /**
560 * Creates a directory.
561 *
562 * @since 2.5.0
563 *
564 * @param string $path Path for new directory.
565 * @param int|false $chmod Optional. The permissions as octal number (or false to skip chmod).
566 * Default false.
567 * @param string|int|false $chown Optional. A user name or number (or false to skip chown).
568 * Default false.
569 * @param string|int|false $chgrp Optional. A group name or number (or false to skip chgrp).
570 * Default false.
571 * @return bool True on success, false on failure.
572 */
573 public function mkdir( $path, $chmod = false, $chown = false, $chgrp = false ) {
574 $path = untrailingslashit( $path );
575
576 if ( empty( $path ) ) {
577 return false;
578 }
579
580 if ( ! ftp_mkdir( $this->link, $path ) ) {
581 return false;
582 }
583
584 $this->chmod( $path, $chmod );
585
586 return true;
587 }
588
589 /**
590 * Deletes a directory.
591 *
592 * @since 2.5.0
593 *
594 * @param string $path Path to directory.
595 * @param bool $recursive Optional. Whether to recursively remove files/directories.
596 * Default false.
597 * @return bool True on success, false on failure.
598 */
599 public function rmdir( $path, $recursive = false ) {
600 return $this->delete( $path, $recursive );
601 }
602
603 /**
604 * Parses an individual entry from the FTP LIST command output.
605 *
606 * @param string $line A line from the directory listing.
607 * @return array|string {
608 * Array of file information. Empty string if the line could not be parsed.
609 *
610 * @type string $name Name of the file or directory.
611 * @type string $perms *nix representation of permissions.
612 * @type string $permsn Octal representation of permissions.
613 * @type string|false $number File number as a string, or false if not available.
614 * @type string|false $owner Owner name or ID, or false if not available.
615 * @type string|false $group File permissions group, or false if not available.
616 * @type string|false $size Size of file in bytes as a string, or false if not available.
617 * @type string|false $lastmodunix Last modified unix timestamp as a string, or false if not available.
618 * @type string|false $lastmod Last modified month (3 letters) and day (without leading 0), or
619 * false if not available.
620 * @type string|false $time Last modified time, or false if not available.
621 * @type string $type Type of resource. 'f' for file, 'd' for directory, 'l' for link.
622 * @type array|false $files If a directory and `$recursive` is true, contains another array of files.
623 * False if unable to list directory contents.
624 * }
625 */
626 public function parselisting( $line ) {
627 static $is_windows = null;
628
629 if ( is_null( $is_windows ) ) {
630 $is_windows = stripos( ftp_systype( $this->link ), 'win' ) !== false;
631 }
632
633 if ( $is_windows && preg_match( '/([0-9]{2})-([0-9]{2})-([0-9]{2}) +([0-9]{2}):([0-9]{2})(AM|PM) +([0-9]+|<DIR>) +(.+)/', $line, $lucifer ) ) {
634 $b = array();
635
636 if ( $lucifer[3] < 70 ) {
637 $lucifer[3] += 2000;
638 } else {
639 $lucifer[3] += 1900; // 4-digit year fix.
640 }
641
642 $b['isdir'] = ( '<DIR>' === $lucifer[7] );
643
644 if ( $b['isdir'] ) {
645 $b['type'] = 'd';
646 } else {
647 $b['type'] = 'f';
648 }
649
650 $b['size'] = $lucifer[7];
651 $b['month'] = $lucifer[1];
652 $b['day'] = $lucifer[2];
653 $b['year'] = $lucifer[3];
654 $b['hour'] = $lucifer[4];
655 $b['minute'] = $lucifer[5];
656 $b['time'] = mktime( $lucifer[4] + ( strcasecmp( $lucifer[6], 'PM' ) === 0 ? 12 : 0 ), $lucifer[5], 0, $lucifer[1], $lucifer[2], $lucifer[3] );
657 $b['am/pm'] = $lucifer[6];
658 $b['name'] = $lucifer[8];
659 } elseif ( ! $is_windows ) {
660 $lucifer = preg_split( '/[ ]/', $line, 9, PREG_SPLIT_NO_EMPTY );
661
662 if ( $lucifer ) {
663 $lcount = count( $lucifer );
664
665 if ( $lcount < 8 ) {
666 return '';
667 }
668
669 $b = array();
670 $b['isdir'] = 'd' === $lucifer[0][0];
671 $b['islink'] = 'l' === $lucifer[0][0];
672
673 if ( $b['isdir'] ) {
674 $b['type'] = 'd';
675 } elseif ( $b['islink'] ) {
676 $b['type'] = 'l';
677 } else {
678 $b['type'] = 'f';
679 }
680
681 $b['perms'] = $lucifer[0];
682 $b['permsn'] = $this->getnumchmodfromh( $b['perms'] );
683 $b['number'] = $lucifer[1];
684 $b['owner'] = $lucifer[2];
685 $b['group'] = $lucifer[3];
686 $b['size'] = $lucifer[4];
687
688 if ( 8 === $lcount ) {
689 sscanf( $lucifer[5], '%d-%d-%d', $b['year'], $b['month'], $b['day'] );
690 sscanf( $lucifer[6], '%d:%d', $b['hour'], $b['minute'] );
691
692 $b['time'] = mktime( $b['hour'], $b['minute'], 0, $b['month'], $b['day'], $b['year'] );
693 $b['name'] = $lucifer[7];
694 } else {
695 $b['month'] = $lucifer[5];
696 $b['day'] = $lucifer[6];
697
698 if ( preg_match( '/([0-9]{2}):([0-9]{2})/', $lucifer[7], $l2 ) ) {
699 $b['year'] = gmdate( 'Y' );
700 $b['hour'] = $l2[1];
701 $b['minute'] = $l2[2];
702 } else {
703 $b['year'] = $lucifer[7];
704 $b['hour'] = 0;
705 $b['minute'] = 0;
706 }
707
708 $b['time'] = strtotime( sprintf( '%d %s %d %02d:%02d', $b['day'], $b['month'], $b['year'], $b['hour'], $b['minute'] ) );
709 $b['name'] = $lucifer[8];
710 }
711 }
712 }
713
714 // Replace symlinks formatted as "source -> target" with just the source name.
715 if ( isset( $b['islink'] ) && $b['islink'] ) {
716 $b['name'] = preg_replace( '/(\s*->\s*.*)$/', '', $b['name'] );
717 }
718
719 return $b;
720 }
721
722 /**
723 * Gets details for files in a directory or a specific file.
724 *
725 * @since 2.5.0
726 *
727 * @param string $path Path to directory or file.
728 * @param bool $include_hidden Optional. Whether to include details of hidden ("." prefixed) files.
729 * Default true.
730 * @param bool $recursive Optional. Whether to recursively include file details in nested directories.
731 * Default false.
732 * @return array|false {
733 * Array of arrays containing file information. False if unable to list directory contents.
734 *
735 * @type array ...$0 {
736 * Array of file information. Note that some elements may not be available on all filesystems.
737 *
738 * @type string $name Name of the file or directory.
739 * @type string $perms *nix representation of permissions.
740 * @type string $permsn Octal representation of permissions.
741 * @type int|string|false $number File number. May be a numeric string. False if not available.
742 * @type string|false $owner Owner name or ID, or false if not available.
743 * @type string|false $group File permissions group, or false if not available.
744 * @type int|string|false $size Size of file in bytes. May be a numeric string.
745 * False if not available.
746 * @type int|string|false $lastmodunix Last modified unix timestamp. May be a numeric string.
747 * False if not available.
748 * @type string|false $lastmod Last modified month (3 letters) and day (without leading 0), or
749 * false if not available.
750 * @type string|false $time Last modified time, or false if not available.
751 * @type string $type Type of resource. 'f' for file, 'd' for directory, 'l' for link.
752 * @type array|false $files If a directory and `$recursive` is true, contains another array of
753 * files. False if unable to list directory contents.
754 * }
755 * }
756 */
757 public function dirlist( $path = '.', $include_hidden = true, $recursive = false ) {
758 if ( $this->is_file( $path ) ) {
759 $limit_file = basename( $path );
760 $path = dirname( $path ) . '/';
761 } else {
762 $limit_file = false;
763 }
764
765 $pwd = ftp_pwd( $this->link );
766
767 if ( ! @ftp_chdir( $this->link, $path ) ) { // Can't change to folder = folder doesn't exist.
768 return false;
769 }
770
771 $list = ftp_rawlist( $this->link, '-a', false );
772
773 @ftp_chdir( $this->link, $pwd );
774
775 if ( empty( $list ) ) { // Empty array = non-existent folder (real folder will show . at least).
776 return false;
777 }
778
779 $dirlist = array();
780
781 foreach ( $list as $k => $v ) {
782 $entry = $this->parselisting( $v );
783
784 if ( empty( $entry ) ) {
785 continue;
786 }
787
788 if ( '.' === $entry['name'] || '..' === $entry['name'] ) {
789 continue;
790 }
791
792 if ( ! $include_hidden && '.' === $entry['name'][0] ) {
793 continue;
794 }
795
796 if ( $limit_file && $entry['name'] !== $limit_file ) {
797 continue;
798 }
799
800 $dirlist[ $entry['name'] ] = $entry;
801 }
802
803 $path = trailingslashit( $path );
804 $ret = array();
805
806 foreach ( (array) $dirlist as $struc ) {
807 if ( 'd' === $struc['type'] ) {
808 if ( $recursive ) {
809 $struc['files'] = $this->dirlist( $path . $struc['name'], $include_hidden, $recursive );
810 } else {
811 $struc['files'] = array();
812 }
813 }
814
815 $ret[ $struc['name'] ] = $struc;
816 }
817
818 return $ret;
819 }
820
821 /**
822 * Destructor.
823 *
824 * @since 2.5.0
825 */
826 public function __destruct() {
827 if ( $this->link ) {
828 ftp_close( $this->link );
829 }
830 }
831}
832