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-ssh2.php
1<?php
2/**
3 * WordPress Filesystem Class for implementing SSH2
4 *
5 * To use this class you must follow these steps for PHP 5.2.6+
6 *
7 * {@link http://kevin.vanzonneveld.net/techblog/article/make_ssh_connections_with_php/ - Installation Notes}
8 *
9 * Compile libssh2 (Note: Only 0.14 is officially working with PHP 5.2.6+ right now, But many users have found the latest versions work)
10 *
11 * cd /usr/src
12 * wget https://www.libssh2.org/download/libssh2-0.14.tar.gz
13 * tar -zxvf libssh2-0.14.tar.gz
14 * cd libssh2-0.14/
15 * ./configure
16 * make all install
17 *
18 * Note: Do not leave the directory yet!
19 *
20 * Enter: pecl install -f ssh2
21 *
22 * Copy the ssh.so file it creates to your PHP Module Directory.
23 * Open up your PHP.INI file and look for where extensions are placed.
24 * Add in your PHP.ini file: extension=ssh2.so
25 *
26 * Restart Apache!
27 * Check phpinfo() streams to confirm that: ssh2.shell, ssh2.exec, ssh2.tunnel, ssh2.scp, ssh2.sftp exist.
28 *
29 * Note: As of WordPress 2.8, this utilizes the PHP5+ function `stream_get_contents()`.
30 *
31 * @since 2.7.0
32 *
33 * @package WordPress
34 * @subpackage Filesystem
35 */
36class WP_Filesystem_SSH2 extends WP_Filesystem_Base {
37
38 /**
39 * @since 2.7.0
40 * @var resource
41 */
42 public $link = false;
43
44 /**
45 * @since 2.7.0
46 * @var resource
47 */
48 public $sftp_link;
49
50 /**
51 * @since 2.7.0
52 * @var bool
53 */
54 public $keys = false;
55
56 /**
57 * Constructor.
58 *
59 * @since 2.7.0
60 *
61 * @param array $opt
62 */
63 public function __construct( $opt = '' ) {
64 $this->method = 'ssh2';
65 $this->errors = new WP_Error();
66
67 // Check if possible to use ssh2 functions.
68 if ( ! extension_loaded( 'ssh2' ) ) {
69 $this->errors->add( 'no_ssh2_ext', __( 'The ssh2 PHP extension is not available' ) );
70 return;
71 }
72
73 // Set defaults:
74 if ( empty( $opt['port'] ) ) {
75 $this->options['port'] = 22;
76 } else {
77 $this->options['port'] = $opt['port'];
78 }
79
80 if ( empty( $opt['hostname'] ) ) {
81 $this->errors->add( 'empty_hostname', __( 'SSH2 hostname is required' ) );
82 } else {
83 $this->options['hostname'] = $opt['hostname'];
84 }
85
86 // Check if the options provided are OK.
87 if ( ! empty( $opt['public_key'] ) && ! empty( $opt['private_key'] ) ) {
88 $this->options['public_key'] = $opt['public_key'];
89 $this->options['private_key'] = $opt['private_key'];
90
91 $this->options['hostkey'] = array( 'hostkey' => 'ssh-rsa,ssh-ed25519' );
92
93 $this->keys = true;
94 } elseif ( empty( $opt['username'] ) ) {
95 $this->errors->add( 'empty_username', __( 'SSH2 username is required' ) );
96 }
97
98 if ( ! empty( $opt['username'] ) ) {
99 $this->options['username'] = $opt['username'];
100 }
101
102 if ( empty( $opt['password'] ) ) {
103 // Password can be blank if we are using keys.
104 if ( ! $this->keys ) {
105 $this->errors->add( 'empty_password', __( 'SSH2 password is required' ) );
106 } else {
107 $this->options['password'] = null;
108 }
109 } else {
110 $this->options['password'] = $opt['password'];
111 }
112 }
113
114 /**
115 * Connects filesystem.
116 *
117 * @since 2.7.0
118 *
119 * @return bool True on success, false on failure.
120 */
121 public function connect() {
122 if ( ! $this->keys ) {
123 $this->link = @ssh2_connect( $this->options['hostname'], $this->options['port'] );
124 } else {
125 $this->link = @ssh2_connect( $this->options['hostname'], $this->options['port'], $this->options['hostkey'] );
126 }
127
128 if ( ! $this->link ) {
129 $this->errors->add(
130 'connect',
131 sprintf(
132 /* translators: %s: hostname:port */
133 __( 'Failed to connect to SSH2 Server %s' ),
134 $this->options['hostname'] . ':' . $this->options['port']
135 )
136 );
137
138 return false;
139 }
140
141 if ( ! $this->keys ) {
142 if ( ! @ssh2_auth_password( $this->link, $this->options['username'], $this->options['password'] ) ) {
143 $this->errors->add(
144 'auth',
145 sprintf(
146 /* translators: %s: Username. */
147 __( 'Username/Password incorrect for %s' ),
148 $this->options['username']
149 )
150 );
151
152 return false;
153 }
154 } else {
155 if ( ! @ssh2_auth_pubkey_file( $this->link, $this->options['username'], $this->options['public_key'], $this->options['private_key'], $this->options['password'] ) ) {
156 $this->errors->add(
157 'auth',
158 sprintf(
159 /* translators: %s: Username. */
160 __( 'Public and Private keys incorrect for %s' ),
161 $this->options['username']
162 )
163 );
164
165 return false;
166 }
167 }
168
169 $this->sftp_link = ssh2_sftp( $this->link );
170
171 if ( ! $this->sftp_link ) {
172 $this->errors->add(
173 'connect',
174 sprintf(
175 /* translators: %s: hostname:port */
176 __( 'Failed to initialize a SFTP subsystem session with the SSH2 Server %s' ),
177 $this->options['hostname'] . ':' . $this->options['port']
178 )
179 );
180
181 return false;
182 }
183
184 return true;
185 }
186
187 /**
188 * Gets the ssh2.sftp PHP stream wrapper path to open for the given file.
189 *
190 * This method also works around a PHP bug where the root directory (/) cannot
191 * be opened by PHP functions, causing a false failure. In order to work around
192 * this, the path is converted to /./ which is semantically the same as /
193 * See https://bugs.php.net/bug.php?id=64169 for more details.
194 *
195 * @since 4.4.0
196 *
197 * @param string $path The File/Directory path on the remote server to return
198 * @return string The ssh2.sftp:// wrapped path to use.
199 */
200 public function sftp_path( $path ) {
201 if ( '/' === $path ) {
202 $path = '/./';
203 }
204
205 return 'ssh2.sftp://' . $this->sftp_link . '/' . ltrim( $path, '/' );
206 }
207
208 /**
209 * @since 2.7.0
210 *
211 * @param string $command
212 * @param bool $returnbool
213 * @return bool|string True on success, false on failure. String if the command was executed, `$returnbool`
214 * is false (default), and data from the resulting stream was retrieved.
215 */
216 public function run_command( $command, $returnbool = false ) {
217 if ( ! $this->link ) {
218 return false;
219 }
220
221 $stream = ssh2_exec( $this->link, $command );
222
223 if ( ! $stream ) {
224 $this->errors->add(
225 'command',
226 sprintf(
227 /* translators: %s: Command. */
228 __( 'Unable to perform command: %s' ),
229 $command
230 )
231 );
232 } else {
233 stream_set_blocking( $stream, true );
234 stream_set_timeout( $stream, FS_TIMEOUT );
235 $data = stream_get_contents( $stream );
236 fclose( $stream );
237
238 if ( $returnbool ) {
239 return ( false === $data ) ? false : '' !== trim( $data );
240 } else {
241 return $data;
242 }
243 }
244
245 return false;
246 }
247
248 /**
249 * Reads entire file into a string.
250 *
251 * @since 2.7.0
252 *
253 * @param string $file Name of the file to read.
254 * @return string|false Read data on success, false if no temporary file could be opened,
255 * or if the file couldn't be retrieved.
256 */
257 public function get_contents( $file ) {
258 return file_get_contents( $this->sftp_path( $file ) );
259 }
260
261 /**
262 * Reads entire file into an array.
263 *
264 * @since 2.7.0
265 *
266 * @param string $file Path to the file.
267 * @return array|false File contents in an array on success, false on failure.
268 */
269 public function get_contents_array( $file ) {
270 return file( $this->sftp_path( $file ) );
271 }
272
273 /**
274 * Writes a string to a file.
275 *
276 * @since 2.7.0
277 *
278 * @param string $file Remote path to the file where to write the data.
279 * @param string $contents The data to write.
280 * @param int|false $mode Optional. The file permissions as octal number, usually 0644.
281 * Default false.
282 * @return bool True on success, false on failure.
283 */
284 public function put_contents( $file, $contents, $mode = false ) {
285 $ret = file_put_contents( $this->sftp_path( $file ), $contents );
286
287 if ( strlen( $contents ) !== $ret ) {
288 return false;
289 }
290
291 $this->chmod( $file, $mode );
292
293 return true;
294 }
295
296 /**
297 * Gets the current working directory.
298 *
299 * @since 2.7.0
300 *
301 * @return string|false The current working directory on success, false on failure.
302 */
303 public function cwd() {
304 $cwd = ssh2_sftp_realpath( $this->sftp_link, '.' );
305
306 if ( $cwd ) {
307 $cwd = trailingslashit( trim( $cwd ) );
308 }
309
310 return $cwd;
311 }
312
313 /**
314 * Changes current directory.
315 *
316 * @since 2.7.0
317 *
318 * @param string $dir The new current directory.
319 * @return bool True on success, false on failure.
320 */
321 public function chdir( $dir ) {
322 return $this->run_command( 'cd ' . $dir, true );
323 }
324
325 /**
326 * Changes the file group.
327 *
328 * @since 2.7.0
329 *
330 * @param string $file Path to the file.
331 * @param string|int $group A group name or number.
332 * @param bool $recursive Optional. If set to true, changes file group recursively.
333 * Default false.
334 * @return bool True on success, false on failure.
335 */
336 public function chgrp( $file, $group, $recursive = false ) {
337 if ( ! $this->exists( $file ) ) {
338 return false;
339 }
340
341 if ( ! $recursive || ! $this->is_dir( $file ) ) {
342 return $this->run_command( sprintf( 'chgrp %s %s', escapeshellarg( $group ), escapeshellarg( $file ) ), true );
343 }
344
345 return $this->run_command( sprintf( 'chgrp -R %s %s', escapeshellarg( $group ), escapeshellarg( $file ) ), true );
346 }
347
348 /**
349 * Changes filesystem permissions.
350 *
351 * @since 2.7.0
352 *
353 * @param string $file Path to the file.
354 * @param int|false $mode Optional. The permissions as octal number, usually 0644 for files,
355 * 0755 for directories. Default false.
356 * @param bool $recursive Optional. If set to true, changes file permissions recursively.
357 * Default false.
358 * @return bool True on success, false on failure.
359 */
360 public function chmod( $file, $mode = false, $recursive = false ) {
361 if ( ! $this->exists( $file ) ) {
362 return false;
363 }
364
365 if ( ! $mode ) {
366 if ( $this->is_file( $file ) ) {
367 $mode = FS_CHMOD_FILE;
368 } elseif ( $this->is_dir( $file ) ) {
369 $mode = FS_CHMOD_DIR;
370 } else {
371 return false;
372 }
373 }
374
375 if ( ! $recursive || ! $this->is_dir( $file ) ) {
376 return $this->run_command( sprintf( 'chmod %o %s', $mode, escapeshellarg( $file ) ), true );
377 }
378
379 return $this->run_command( sprintf( 'chmod -R %o %s', $mode, escapeshellarg( $file ) ), true );
380 }
381
382 /**
383 * Changes the owner of a file or directory.
384 *
385 * @since 2.7.0
386 *
387 * @param string $file Path to the file or directory.
388 * @param string|int $owner A user name or number.
389 * @param bool $recursive Optional. If set to true, changes file owner recursively.
390 * Default false.
391 * @return bool True on success, false on failure.
392 */
393 public function chown( $file, $owner, $recursive = false ) {
394 if ( ! $this->exists( $file ) ) {
395 return false;
396 }
397
398 if ( ! $recursive || ! $this->is_dir( $file ) ) {
399 return $this->run_command( sprintf( 'chown %s %s', escapeshellarg( $owner ), escapeshellarg( $file ) ), true );
400 }
401
402 return $this->run_command( sprintf( 'chown -R %s %s', escapeshellarg( $owner ), escapeshellarg( $file ) ), true );
403 }
404
405 /**
406 * Gets the file owner.
407 *
408 * @since 2.7.0
409 *
410 * @param string $file Path to the file.
411 * @return string|false Username of the owner on success, false on failure.
412 */
413 public function owner( $file ) {
414 $owneruid = @fileowner( $this->sftp_path( $file ) );
415
416 if ( ! $owneruid ) {
417 return false;
418 }
419
420 if ( ! function_exists( 'posix_getpwuid' ) ) {
421 return $owneruid;
422 }
423
424 $ownerarray = posix_getpwuid( $owneruid );
425
426 if ( ! $ownerarray ) {
427 return false;
428 }
429
430 return $ownerarray['name'];
431 }
432
433 /**
434 * Gets the permissions of the specified file or filepath in their octal format.
435 *
436 * @since 2.7.0
437 *
438 * @param string $file Path to the file.
439 * @return string Mode of the file (the last 3 digits).
440 */
441 public function getchmod( $file ) {
442 return substr( decoct( @fileperms( $this->sftp_path( $file ) ) ), -3 );
443 }
444
445 /**
446 * Gets the file's group.
447 *
448 * @since 2.7.0
449 *
450 * @param string $file Path to the file.
451 * @return string|false The group on success, false on failure.
452 */
453 public function group( $file ) {
454 $gid = @filegroup( $this->sftp_path( $file ) );
455
456 if ( ! $gid ) {
457 return false;
458 }
459
460 if ( ! function_exists( 'posix_getgrgid' ) ) {
461 return $gid;
462 }
463
464 $grouparray = posix_getgrgid( $gid );
465
466 if ( ! $grouparray ) {
467 return false;
468 }
469
470 return $grouparray['name'];
471 }
472
473 /**
474 * Copies a file.
475 *
476 * @since 2.7.0
477 *
478 * @param string $source Path to the source file.
479 * @param string $destination Path to the destination file.
480 * @param bool $overwrite Optional. Whether to overwrite the destination file if it exists.
481 * Default false.
482 * @param int|false $mode Optional. The permissions as octal number, usually 0644 for files,
483 * 0755 for dirs. Default false.
484 * @return bool True on success, false on failure.
485 */
486 public function copy( $source, $destination, $overwrite = false, $mode = false ) {
487 if ( ! $overwrite && $this->exists( $destination ) ) {
488 return false;
489 }
490
491 $content = $this->get_contents( $source );
492
493 if ( false === $content ) {
494 return false;
495 }
496
497 return $this->put_contents( $destination, $content, $mode );
498 }
499
500 /**
501 * Moves a file or directory.
502 *
503 * After moving files or directories, OPcache will need to be invalidated.
504 *
505 * If moving a directory fails, `copy_dir()` can be used for a recursive copy.
506 *
507 * Use `move_dir()` for moving directories with OPcache invalidation and a
508 * fallback to `copy_dir()`.
509 *
510 * @since 2.7.0
511 *
512 * @param string $source Path to the source file or directory.
513 * @param string $destination Path to the destination file or directory.
514 * @param bool $overwrite Optional. Whether to overwrite the destination if it exists.
515 * Default false.
516 * @return bool True on success, false on failure.
517 */
518 public function move( $source, $destination, $overwrite = false ) {
519 if ( $this->exists( $destination ) ) {
520 if ( $overwrite ) {
521 // We need to remove the destination before we can rename the source.
522 $this->delete( $destination, false, 'f' );
523 } else {
524 // If we're not overwriting, the rename will fail, so return early.
525 return false;
526 }
527 }
528
529 return ssh2_sftp_rename( $this->sftp_link, $source, $destination );
530 }
531
532 /**
533 * Deletes a file or directory.
534 *
535 * @since 2.7.0
536 *
537 * @param string $file Path to the file or directory.
538 * @param bool $recursive Optional. If set to true, deletes files and folders recursively.
539 * Default false.
540 * @param string|false $type Type of resource. 'f' for file, 'd' for directory.
541 * Default false.
542 * @return bool True on success, false on failure.
543 */
544 public function delete( $file, $recursive = false, $type = false ) {
545 if ( 'f' === $type || $this->is_file( $file ) ) {
546 return ssh2_sftp_unlink( $this->sftp_link, $file );
547 }
548
549 if ( ! $recursive ) {
550 return ssh2_sftp_rmdir( $this->sftp_link, $file );
551 }
552
553 $filelist = $this->dirlist( $file );
554
555 if ( is_array( $filelist ) ) {
556 foreach ( $filelist as $filename => $fileinfo ) {
557 $this->delete( $file . '/' . $filename, $recursive, $fileinfo['type'] );
558 }
559 }
560
561 return ssh2_sftp_rmdir( $this->sftp_link, $file );
562 }
563
564 /**
565 * Checks if a file or directory exists.
566 *
567 * @since 2.7.0
568 *
569 * @param string $path Path to file or directory.
570 * @return bool Whether $path exists or not.
571 */
572 public function exists( $path ) {
573 return file_exists( $this->sftp_path( $path ) );
574 }
575
576 /**
577 * Checks if resource is a file.
578 *
579 * @since 2.7.0
580 *
581 * @param string $file File path.
582 * @return bool Whether $file is a file.
583 */
584 public function is_file( $file ) {
585 return is_file( $this->sftp_path( $file ) );
586 }
587
588 /**
589 * Checks if resource is a directory.
590 *
591 * @since 2.7.0
592 *
593 * @param string $path Directory path.
594 * @return bool Whether $path is a directory.
595 */
596 public function is_dir( $path ) {
597 return is_dir( $this->sftp_path( $path ) );
598 }
599
600 /**
601 * Checks if a file is readable.
602 *
603 * @since 2.7.0
604 *
605 * @param string $file Path to file.
606 * @return bool Whether $file is readable.
607 */
608 public function is_readable( $file ) {
609 return is_readable( $this->sftp_path( $file ) );
610 }
611
612 /**
613 * Checks if a file or directory is writable.
614 *
615 * @since 2.7.0
616 *
617 * @param string $path Path to file or directory.
618 * @return bool Whether $path is writable.
619 */
620 public function is_writable( $path ) {
621 // PHP will base its writable checks on system_user === file_owner, not ssh_user === file_owner.
622 return true;
623 }
624
625 /**
626 * Gets the file's last access time.
627 *
628 * @since 2.7.0
629 *
630 * @param string $file Path to file.
631 * @return int|false Unix timestamp representing last access time, false on failure.
632 */
633 public function atime( $file ) {
634 return fileatime( $this->sftp_path( $file ) );
635 }
636
637 /**
638 * Gets the file modification time.
639 *
640 * @since 2.7.0
641 *
642 * @param string $file Path to file.
643 * @return int|false Unix timestamp representing modification time, false on failure.
644 */
645 public function mtime( $file ) {
646 return filemtime( $this->sftp_path( $file ) );
647 }
648
649 /**
650 * Gets the file size (in bytes).
651 *
652 * @since 2.7.0
653 *
654 * @param string $file Path to file.
655 * @return int|false Size of the file in bytes on success, false on failure.
656 */
657 public function size( $file ) {
658 return filesize( $this->sftp_path( $file ) );
659 }
660
661 /**
662 * Sets the access and modification times of a file.
663 *
664 * Note: Not implemented.
665 *
666 * @since 2.7.0
667 *
668 * @param string $file Path to file.
669 * @param int $time Optional. Modified time to set for file.
670 * Default 0.
671 * @param int $atime Optional. Access time to set for file.
672 * Default 0.
673 */
674 public function touch( $file, $time = 0, $atime = 0 ) {
675 // Not implemented.
676 }
677
678 /**
679 * Creates a directory.
680 *
681 * @since 2.7.0
682 *
683 * @param string $path Path for new directory.
684 * @param int|false $chmod Optional. The permissions as octal number (or false to skip chmod).
685 * Default false.
686 * @param string|int|false $chown Optional. A user name or number (or false to skip chown).
687 * Default false.
688 * @param string|int|false $chgrp Optional. A group name or number (or false to skip chgrp).
689 * Default false.
690 * @return bool True on success, false on failure.
691 */
692 public function mkdir( $path, $chmod = false, $chown = false, $chgrp = false ) {
693 $path = untrailingslashit( $path );
694
695 if ( empty( $path ) ) {
696 return false;
697 }
698
699 if ( ! $chmod ) {
700 $chmod = FS_CHMOD_DIR;
701 }
702
703 if ( ! ssh2_sftp_mkdir( $this->sftp_link, $path, $chmod, true ) ) {
704 return false;
705 }
706
707 // Set directory permissions.
708 ssh2_sftp_chmod( $this->sftp_link, $path, $chmod );
709
710 if ( $chown ) {
711 $this->chown( $path, $chown );
712 }
713
714 if ( $chgrp ) {
715 $this->chgrp( $path, $chgrp );
716 }
717
718 return true;
719 }
720
721 /**
722 * Deletes a directory.
723 *
724 * @since 2.7.0
725 *
726 * @param string $path Path to directory.
727 * @param bool $recursive Optional. Whether to recursively remove files/directories.
728 * Default false.
729 * @return bool True on success, false on failure.
730 */
731 public function rmdir( $path, $recursive = false ) {
732 return $this->delete( $path, $recursive );
733 }
734
735 /**
736 * Gets details for files in a directory or a specific file.
737 *
738 * @since 2.7.0
739 *
740 * @param string $path Path to directory or file.
741 * @param bool $include_hidden Optional. Whether to include details of hidden ("." prefixed) files.
742 * Default true.
743 * @param bool $recursive Optional. Whether to recursively include file details in nested directories.
744 * Default false.
745 * @return array|false {
746 * Array of arrays containing file information. False if unable to list directory contents.
747 *
748 * @type array ...$0 {
749 * Array of file information. Note that some elements may not be available on all filesystems.
750 *
751 * @type string $name Name of the file or directory.
752 * @type string $perms *nix representation of permissions.
753 * @type string $permsn Octal representation of permissions.
754 * @type false $number File number. Always false in this context.
755 * @type string|false $owner Owner name or ID, or false if not available.
756 * @type string|false $group File permissions group, or false if not available.
757 * @type int|string|false $size Size of file in bytes. May be a numeric string.
758 * False if not available.
759 * @type int|string|false $lastmodunix Last modified unix timestamp. May be a numeric string.
760 * False if not available.
761 * @type string|false $lastmod Last modified month (3 letters) and day (without leading 0), or
762 * false if not available.
763 * @type string|false $time Last modified time, or false if not available.
764 * @type string $type Type of resource. 'f' for file, 'd' for directory, 'l' for link.
765 * @type array|false $files If a directory and `$recursive` is true, contains another array of
766 * files. False if unable to list directory contents.
767 * }
768 * }
769 */
770 public function dirlist( $path, $include_hidden = true, $recursive = false ) {
771 if ( $this->is_file( $path ) ) {
772 $limit_file = basename( $path );
773 $path = dirname( $path );
774 } else {
775 $limit_file = false;
776 }
777
778 if ( ! $this->is_dir( $path ) || ! $this->is_readable( $path ) ) {
779 return false;
780 }
781
782 $ret = array();
783 $dir = dir( $this->sftp_path( $path ) );
784
785 if ( ! $dir ) {
786 return false;
787 }
788
789 $path = trailingslashit( $path );
790
791 while ( false !== ( $entry = $dir->read() ) ) {
792 $struc = array();
793 $struc['name'] = $entry;
794
795 if ( '.' === $struc['name'] || '..' === $struc['name'] ) {
796 continue; // Do not care about these folders.
797 }
798
799 if ( ! $include_hidden && '.' === $struc['name'][0] ) {
800 continue;
801 }
802
803 if ( $limit_file && $struc['name'] !== $limit_file ) {
804 continue;
805 }
806
807 $struc['perms'] = $this->gethchmod( $path . $entry );
808 $struc['permsn'] = $this->getnumchmodfromh( $struc['perms'] );
809 $struc['number'] = false;
810 $struc['owner'] = $this->owner( $path . $entry );
811 $struc['group'] = $this->group( $path . $entry );
812 $struc['size'] = $this->size( $path . $entry );
813 $struc['lastmodunix'] = $this->mtime( $path . $entry );
814 $struc['lastmod'] = gmdate( 'M j', $struc['lastmodunix'] );
815 $struc['time'] = gmdate( 'h:i:s', $struc['lastmodunix'] );
816 $struc['type'] = $this->is_dir( $path . $entry ) ? 'd' : 'f';
817
818 if ( 'd' === $struc['type'] ) {
819 if ( $recursive ) {
820 $struc['files'] = $this->dirlist( $path . $struc['name'], $include_hidden, $recursive );
821 } else {
822 $struc['files'] = array();
823 }
824 }
825
826 $ret[ $struc['name'] ] = $struc;
827 }
828
829 $dir->close();
830 unset( $dir );
831
832 return $ret;
833 }
834}
835