wp/wp-admin/includes/class-wp-filesystem-ftpext.php
changeset 9 177826044cd9
parent 7 cf61fcea0001
child 16 a86126ab1dd4
equal deleted inserted replaced
8:c7c34916027a 9:177826044cd9
    12  * @since 2.5.0
    12  * @since 2.5.0
    13  *
    13  *
    14  * @see WP_Filesystem_Base
    14  * @see WP_Filesystem_Base
    15  */
    15  */
    16 class WP_Filesystem_FTPext extends WP_Filesystem_Base {
    16 class WP_Filesystem_FTPext extends WP_Filesystem_Base {
       
    17 
       
    18 	/**
       
    19 	 * @since 2.5.0
       
    20 	 * @var resource
       
    21 	 */
    17 	public $link;
    22 	public $link;
    18 
    23 
    19 	/**
    24 	/**
       
    25 	 * Constructor.
       
    26 	 *
       
    27 	 * @since 2.5.0
    20 	 *
    28 	 *
    21 	 * @param array $opt
    29 	 * @param array $opt
    22 	 */
    30 	 */
    23 	public function __construct( $opt = '' ) {
    31 	public function __construct( $opt = '' ) {
    24 		$this->method = 'ftpext';
    32 		$this->method = 'ftpext';
    25 		$this->errors = new WP_Error();
    33 		$this->errors = new WP_Error();
    26 
    34 
    27 		// Check if possible to use ftp functions.
    35 		// Check if possible to use ftp functions.
    28 		if ( ! extension_loaded('ftp') ) {
    36 		if ( ! extension_loaded( 'ftp' ) ) {
    29 			$this->errors->add('no_ftp_ext', __('The ftp PHP extension is not available'));
    37 			$this->errors->add( 'no_ftp_ext', __( 'The ftp PHP extension is not available' ) );
    30 			return;
    38 			return;
    31 		}
    39 		}
    32 
    40 
    33 		// This Class uses the timeout on a per-connection basis, Others use it on a per-action basis.
    41 		// This Class uses the timeout on a per-connection basis, Others use it on a per-action basis.
    34 
    42 
    35 		if ( ! defined('FS_TIMEOUT') )
    43 		if ( ! defined( 'FS_TIMEOUT' ) ) {
    36 			define('FS_TIMEOUT', 240);
    44 			define( 'FS_TIMEOUT', 240 );
    37 
    45 		}
    38 		if ( empty($opt['port']) )
    46 
       
    47 		if ( empty( $opt['port'] ) ) {
    39 			$this->options['port'] = 21;
    48 			$this->options['port'] = 21;
    40 		else
    49 		} else {
    41 			$this->options['port'] = $opt['port'];
    50 			$this->options['port'] = $opt['port'];
    42 
    51 		}
    43 		if ( empty($opt['hostname']) )
    52 
    44 			$this->errors->add('empty_hostname', __('FTP hostname is required'));
    53 		if ( empty( $opt['hostname'] ) ) {
    45 		else
    54 			$this->errors->add( 'empty_hostname', __( 'FTP hostname is required' ) );
       
    55 		} else {
    46 			$this->options['hostname'] = $opt['hostname'];
    56 			$this->options['hostname'] = $opt['hostname'];
       
    57 		}
    47 
    58 
    48 		// Check if the options provided are OK.
    59 		// Check if the options provided are OK.
    49 		if ( empty($opt['username']) )
    60 		if ( empty( $opt['username'] ) ) {
    50 			$this->errors->add('empty_username', __('FTP username is required'));
    61 			$this->errors->add( 'empty_username', __( 'FTP username is required' ) );
    51 		else
    62 		} else {
    52 			$this->options['username'] = $opt['username'];
    63 			$this->options['username'] = $opt['username'];
    53 
    64 		}
    54 		if ( empty($opt['password']) )
    65 
    55 			$this->errors->add('empty_password', __('FTP password is required'));
    66 		if ( empty( $opt['password'] ) ) {
    56 		else
    67 			$this->errors->add( 'empty_password', __( 'FTP password is required' ) );
       
    68 		} else {
    57 			$this->options['password'] = $opt['password'];
    69 			$this->options['password'] = $opt['password'];
       
    70 		}
    58 
    71 
    59 		$this->options['ssl'] = false;
    72 		$this->options['ssl'] = false;
    60 		if ( isset($opt['connection_type']) && 'ftps' == $opt['connection_type'] )
    73 		if ( isset( $opt['connection_type'] ) && 'ftps' == $opt['connection_type'] ) {
    61 			$this->options['ssl'] = true;
    74 			$this->options['ssl'] = true;
    62 	}
    75 		}
    63 
    76 	}
    64 	/**
    77 
    65 	 *
    78 	/**
    66 	 * @return bool
    79 	 * Connects filesystem.
       
    80 	 *
       
    81 	 * @since 2.5.0
       
    82 	 *
       
    83 	 * @return bool True on success, false on failure.
    67 	 */
    84 	 */
    68 	public function connect() {
    85 	public function connect() {
    69 		if ( isset($this->options['ssl']) && $this->options['ssl'] && function_exists('ftp_ssl_connect') )
    86 		if ( isset( $this->options['ssl'] ) && $this->options['ssl'] && function_exists( 'ftp_ssl_connect' ) ) {
    70 			$this->link = @ftp_ssl_connect($this->options['hostname'], $this->options['port'], FS_CONNECT_TIMEOUT);
    87 			$this->link = @ftp_ssl_connect( $this->options['hostname'], $this->options['port'], FS_CONNECT_TIMEOUT );
    71 		else
    88 		} else {
    72 			$this->link = @ftp_connect($this->options['hostname'], $this->options['port'], FS_CONNECT_TIMEOUT);
    89 			$this->link = @ftp_connect( $this->options['hostname'], $this->options['port'], FS_CONNECT_TIMEOUT );
       
    90 		}
    73 
    91 
    74 		if ( ! $this->link ) {
    92 		if ( ! $this->link ) {
    75 			$this->errors->add( 'connect',
    93 			$this->errors->add(
       
    94 				'connect',
    76 				/* translators: %s: hostname:port */
    95 				/* translators: %s: hostname:port */
    77 				sprintf( __( 'Failed to connect to FTP Server %s' ),
    96 				sprintf(
       
    97 					__( 'Failed to connect to FTP Server %s' ),
    78 					$this->options['hostname'] . ':' . $this->options['port']
    98 					$this->options['hostname'] . ':' . $this->options['port']
    79 				)
    99 				)
    80 			);
   100 			);
    81 			return false;
   101 			return false;
    82 		}
   102 		}
    83 
   103 
    84 		if ( ! @ftp_login( $this->link,$this->options['username'], $this->options['password'] ) ) {
   104 		if ( ! @ftp_login( $this->link, $this->options['username'], $this->options['password'] ) ) {
    85 			$this->errors->add( 'auth',
   105 			$this->errors->add(
       
   106 				'auth',
    86 				/* translators: %s: username */
   107 				/* translators: %s: username */
    87 				sprintf( __( 'Username/Password incorrect for %s' ),
   108 				sprintf(
       
   109 					__( 'Username/Password incorrect for %s' ),
    88 					$this->options['username']
   110 					$this->options['username']
    89 				)
   111 				)
    90 			);
   112 			);
    91 			return false;
   113 			return false;
    92 		}
   114 		}
    93 
   115 
    94 		// Set the Connection to use Passive FTP
   116 		// Set the Connection to use Passive FTP
    95 		@ftp_pasv( $this->link, true );
   117 		@ftp_pasv( $this->link, true );
    96 		if ( @ftp_get_option($this->link, FTP_TIMEOUT_SEC) < FS_TIMEOUT )
   118 		if ( @ftp_get_option( $this->link, FTP_TIMEOUT_SEC ) < FS_TIMEOUT ) {
    97 			@ftp_set_option($this->link, FTP_TIMEOUT_SEC, FS_TIMEOUT);
   119 			@ftp_set_option( $this->link, FTP_TIMEOUT_SEC, FS_TIMEOUT );
       
   120 		}
    98 
   121 
    99 		return true;
   122 		return true;
   100 	}
   123 	}
   101 
   124 
   102 	/**
   125 	/**
   103 	 * Retrieves the file contents.
   126 	 * Reads entire file into a string.
   104 	 *
   127 	 *
   105 	 * @since 2.5.0
   128 	 * @since 2.5.0
   106 	 *
   129 	 *
   107 	 * @param string $file Filename.
   130 	 * @param string $file Name of the file to read.
   108 	 * @return string|false File contents on success, false if no temp file could be opened,
   131 	 * @return string|false Read data on success, false if no temporary file could be opened,
   109 	 *                      or if the file couldn't be retrieved.
   132 	 *                      or if the file couldn't be retrieved.
   110 	 */
   133 	 */
   111 	public function get_contents( $file ) {
   134 	public function get_contents( $file ) {
   112 		$tempfile = wp_tempnam($file);
   135 		$tempfile = wp_tempnam( $file );
   113 		$temp = fopen($tempfile, 'w+');
   136 		$temp     = fopen( $tempfile, 'w+' );
   114 
   137 
   115 		if ( ! $temp ) {
   138 		if ( ! $temp ) {
   116 			unlink( $tempfile );
   139 			unlink( $tempfile );
   117 			return false;
   140 			return false;
   118 		}
   141 		}
   124 		}
   147 		}
   125 
   148 
   126 		fseek( $temp, 0 ); // Skip back to the start of the file being written to
   149 		fseek( $temp, 0 ); // Skip back to the start of the file being written to
   127 		$contents = '';
   150 		$contents = '';
   128 
   151 
   129 		while ( ! feof($temp) )
   152 		while ( ! feof( $temp ) ) {
   130 			$contents .= fread($temp, 8192);
   153 			$contents .= fread( $temp, 8 * KB_IN_BYTES );
   131 
   154 		}
   132 		fclose($temp);
   155 
   133 		unlink($tempfile);
   156 		fclose( $temp );
       
   157 		unlink( $tempfile );
   134 		return $contents;
   158 		return $contents;
   135 	}
   159 	}
   136 
   160 
   137 	/**
   161 	/**
   138 	 *
   162 	 * Reads entire file into an array.
   139 	 * @param string $file
   163 	 *
   140 	 * @return array
   164 	 * @since 2.5.0
   141 	 */
   165 	 *
   142 	public function get_contents_array($file) {
   166 	 * @param string $file Path to the file.
   143 		return explode("\n", $this->get_contents($file));
   167 	 * @return array|false File contents in an array on success, false on failure.
   144 	}
   168 	 */
   145 
   169 	public function get_contents_array( $file ) {
   146 	/**
   170 		return explode( "\n", $this->get_contents( $file ) );
   147 	 *
   171 	}
   148 	 * @param string $file
   172 
   149 	 * @param string $contents
   173 	/**
   150 	 * @param bool|int $mode
   174 	 * Writes a string to a file.
   151 	 * @return bool
   175 	 *
   152 	 */
   176 	 * @since 2.5.0
   153 	public function put_contents($file, $contents, $mode = false ) {
   177 	 *
   154 		$tempfile = wp_tempnam($file);
   178 	 * @param string    $file     Remote path to the file where to write the data.
   155 		$temp = fopen( $tempfile, 'wb+' );
   179 	 * @param string    $contents The data to write.
       
   180 	 * @param int|false $mode     Optional. The file permissions as octal number, usually 0644.
       
   181 	 *                            Default false.
       
   182 	 * @return bool True on success, false on failure.
       
   183 	 */
       
   184 	public function put_contents( $file, $contents, $mode = false ) {
       
   185 		$tempfile = wp_tempnam( $file );
       
   186 		$temp     = fopen( $tempfile, 'wb+' );
   156 
   187 
   157 		if ( ! $temp ) {
   188 		if ( ! $temp ) {
   158 			unlink( $tempfile );
   189 			unlink( $tempfile );
   159 			return false;
   190 			return false;
   160 		}
   191 		}
   161 
   192 
   162 		mbstring_binary_safe_encoding();
   193 		mbstring_binary_safe_encoding();
   163 
   194 
   164 		$data_length = strlen( $contents );
   195 		$data_length   = strlen( $contents );
   165 		$bytes_written = fwrite( $temp, $contents );
   196 		$bytes_written = fwrite( $temp, $contents );
   166 
   197 
   167 		reset_mbstring_encoding();
   198 		reset_mbstring_encoding();
   168 
   199 
   169 		if ( $data_length !== $bytes_written ) {
   200 		if ( $data_length !== $bytes_written ) {
   174 
   205 
   175 		fseek( $temp, 0 ); // Skip back to the start of the file being written to
   206 		fseek( $temp, 0 ); // Skip back to the start of the file being written to
   176 
   207 
   177 		$ret = @ftp_fput( $this->link, $file, $temp, FTP_BINARY );
   208 		$ret = @ftp_fput( $this->link, $file, $temp, FTP_BINARY );
   178 
   209 
   179 		fclose($temp);
   210 		fclose( $temp );
   180 		unlink($tempfile);
   211 		unlink( $tempfile );
   181 
   212 
   182 		$this->chmod($file, $mode);
   213 		$this->chmod( $file, $mode );
   183 
   214 
   184 		return $ret;
   215 		return $ret;
   185 	}
   216 	}
   186 
   217 
   187 	/**
   218 	/**
   188 	 *
   219 	 * Gets the current working directory.
   189 	 * @return string
   220 	 *
       
   221 	 * @since 2.5.0
       
   222 	 *
       
   223 	 * @return string|false The current working directory on success, false on failure.
   190 	 */
   224 	 */
   191 	public function cwd() {
   225 	public function cwd() {
   192 		$cwd = @ftp_pwd($this->link);
   226 		$cwd = @ftp_pwd( $this->link );
   193 		if ( $cwd )
   227 		if ( $cwd ) {
   194 			$cwd = trailingslashit($cwd);
   228 			$cwd = trailingslashit( $cwd );
       
   229 		}
   195 		return $cwd;
   230 		return $cwd;
   196 	}
   231 	}
   197 
   232 
   198 	/**
   233 	/**
   199 	 *
   234 	 * Changes current directory.
   200 	 * @param string $dir
   235 	 *
   201 	 * @return bool
   236 	 * @since 2.5.0
   202 	 */
   237 	 *
   203 	public function chdir($dir) {
   238 	 * @param string $dir The new current directory.
   204 		return @ftp_chdir($this->link, $dir);
   239 	 * @return bool True on success, false on failure.
   205 	}
   240 	 */
   206 
   241 	public function chdir( $dir ) {
   207 	/**
   242 		return @ftp_chdir( $this->link, $dir );
   208 	 *
   243 	}
   209 	 * @param string $file
   244 
   210 	 * @param int $mode
   245 	/**
   211 	 * @param bool $recursive
   246 	 * Changes filesystem permissions.
   212 	 * @return bool
   247 	 *
   213 	 */
   248 	 * @since 2.5.0
   214 	public function chmod($file, $mode = false, $recursive = false) {
   249 	 *
       
   250 	 * @param string    $file      Path to the file.
       
   251 	 * @param int|false $mode      Optional. The permissions as octal number, usually 0644 for files,
       
   252 	 *                             0755 for directories. Default false.
       
   253 	 * @param bool      $recursive Optional. If set to true, changes file group recursively.
       
   254 	 *                             Default false.
       
   255 	 * @return bool True on success, false on failure.
       
   256 	 */
       
   257 	public function chmod( $file, $mode = false, $recursive = false ) {
   215 		if ( ! $mode ) {
   258 		if ( ! $mode ) {
   216 			if ( $this->is_file($file) )
   259 			if ( $this->is_file( $file ) ) {
   217 				$mode = FS_CHMOD_FILE;
   260 				$mode = FS_CHMOD_FILE;
   218 			elseif ( $this->is_dir($file) )
   261 			} elseif ( $this->is_dir( $file ) ) {
   219 				$mode = FS_CHMOD_DIR;
   262 				$mode = FS_CHMOD_DIR;
   220 			else
   263 			} else {
   221 				return false;
   264 				return false;
       
   265 			}
   222 		}
   266 		}
   223 
   267 
   224 		// chmod any sub-objects if recursive.
   268 		// chmod any sub-objects if recursive.
   225 		if ( $recursive && $this->is_dir($file) ) {
   269 		if ( $recursive && $this->is_dir( $file ) ) {
   226 			$filelist = $this->dirlist($file);
   270 			$filelist = $this->dirlist( $file );
   227 			foreach ( (array)$filelist as $filename => $filemeta )
   271 			foreach ( (array) $filelist as $filename => $filemeta ) {
   228 				$this->chmod($file . '/' . $filename, $mode, $recursive);
   272 				$this->chmod( $file . '/' . $filename, $mode, $recursive );
       
   273 			}
   229 		}
   274 		}
   230 
   275 
   231 		// chmod the file or directory
   276 		// chmod the file or directory
   232 		if ( ! function_exists('ftp_chmod') )
   277 		if ( ! function_exists( 'ftp_chmod' ) ) {
   233 			return (bool)@ftp_site($this->link, sprintf('CHMOD %o %s', $mode, $file));
   278 			return (bool) @ftp_site( $this->link, sprintf( 'CHMOD %o %s', $mode, $file ) );
   234 		return (bool)@ftp_chmod($this->link, $mode, $file);
   279 		}
   235 	}
   280 		return (bool) @ftp_chmod( $this->link, $mode, $file );
   236 
   281 	}
   237 	/**
   282 
   238 	 *
   283 	/**
   239 	 * @param string $file
   284 	 * Gets the file owner.
   240 	 * @return string
   285 	 *
   241 	 */
   286 	 * @since 2.5.0
   242 	public function owner($file) {
   287 	 *
   243 		$dir = $this->dirlist($file);
   288 	 * @param string $file Path to the file.
   244 		return $dir[$file]['owner'];
   289 	 * @return string|false Username of the owner on success, false on failure.
   245 	}
   290 	 */
   246 	/**
   291 	public function owner( $file ) {
   247 	 *
   292 		$dir = $this->dirlist( $file );
   248 	 * @param string $file
   293 		return $dir[ $file ]['owner'];
   249 	 * @return string
   294 	}
   250 	 */
   295 
   251 	public function getchmod($file) {
   296 	/**
   252 		$dir = $this->dirlist($file);
   297 	 * Gets the permissions of the specified file or filepath in their octal format.
   253 		return $dir[$file]['permsn'];
   298 	 *
   254 	}
   299 	 * @since 2.5.0
   255 
   300 	 *
   256 	/**
   301 	 * @param string $file Path to the file.
   257 	 *
   302 	 * @return string Mode of the file (the last 3 digits).
   258 	 * @param string $file
   303 	 */
   259 	 * @return string
   304 	public function getchmod( $file ) {
   260 	 */
   305 		$dir = $this->dirlist( $file );
   261 	public function group($file) {
   306 		return $dir[ $file ]['permsn'];
   262 		$dir = $this->dirlist($file);
   307 	}
   263 		return $dir[$file]['group'];
   308 
   264 	}
   309 	/**
   265 
   310 	 * Gets the file's group.
   266 	/**
   311 	 *
   267 	 *
   312 	 * @since 2.5.0
   268 	 * @param string $source
   313 	 *
   269 	 * @param string $destination
   314 	 * @param string $file Path to the file.
   270 	 * @param bool   $overwrite
   315 	 * @return string|false The group on success, false on failure.
   271 	 * @param string|bool $mode
   316 	 */
   272 	 * @return bool
   317 	public function group( $file ) {
   273 	 */
   318 		$dir = $this->dirlist( $file );
   274 	public function copy($source, $destination, $overwrite = false, $mode = false) {
   319 		return $dir[ $file ]['group'];
   275 		if ( ! $overwrite && $this->exists($destination) )
   320 	}
   276 			return false;
   321 
   277 		$content = $this->get_contents($source);
   322 	/**
   278 		if ( false === $content )
   323 	 * Copies a file.
   279 			return false;
   324 	 *
   280 		return $this->put_contents($destination, $content, $mode);
   325 	 * @since 2.5.0
   281 	}
   326 	 *
   282 
   327 	 * @param string    $source      Path to the source file.
   283 	/**
   328 	 * @param string    $destination Path to the destination file.
   284 	 *
   329 	 * @param bool      $overwrite   Optional. Whether to overwrite the destination file if it exists.
   285 	 * @param string $source
   330 	 *                               Default false.
   286 	 * @param string $destination
   331 	 * @param int|false $mode        Optional. The permissions as octal number, usually 0644 for files,
   287 	 * @param bool $overwrite
   332 	 *                               0755 for dirs. Default false.
   288 	 * @return bool
   333 	 * @return bool True on success, false on failure.
   289 	 */
   334 	 */
   290 	public function move($source, $destination, $overwrite = false) {
   335 	public function copy( $source, $destination, $overwrite = false, $mode = false ) {
   291 		return ftp_rename($this->link, $source, $destination);
   336 		if ( ! $overwrite && $this->exists( $destination ) ) {
   292 	}
   337 			return false;
   293 
   338 		}
   294 	/**
   339 		$content = $this->get_contents( $source );
   295 	 *
   340 		if ( false === $content ) {
   296 	 * @param string $file
   341 			return false;
   297 	 * @param bool $recursive
   342 		}
   298 	 * @param string $type
   343 		return $this->put_contents( $destination, $content, $mode );
   299 	 * @return bool
   344 	}
   300 	 */
   345 
   301 	public function delete($file, $recursive = false, $type = false) {
   346 	/**
   302 		if ( empty($file) )
   347 	 * Moves a file.
   303 			return false;
   348 	 *
   304 		if ( 'f' == $type || $this->is_file($file) )
   349 	 * @since 2.5.0
   305 			return @ftp_delete($this->link, $file);
   350 	 *
   306 		if ( !$recursive )
   351 	 * @param string $source      Path to the source file.
   307 			return @ftp_rmdir($this->link, $file);
   352 	 * @param string $destination Path to the destination file.
   308 
   353 	 * @param bool   $overwrite   Optional. Whether to overwrite the destination file if it exists.
   309 		$filelist = $this->dirlist( trailingslashit($file) );
   354 	 *                            Default false.
   310 		if ( !empty($filelist) )
   355 	 * @return bool True on success, false on failure.
   311 			foreach ( $filelist as $delete_file )
   356 	 */
   312 				$this->delete( trailingslashit($file) . $delete_file['name'], $recursive, $delete_file['type'] );
   357 	public function move( $source, $destination, $overwrite = false ) {
   313 		return @ftp_rmdir($this->link, $file);
   358 		return ftp_rename( $this->link, $source, $destination );
   314 	}
   359 	}
   315 
   360 
   316 	/**
   361 	/**
   317 	 *
   362 	 * Deletes a file or directory.
   318 	 * @param string $file
   363 	 *
   319 	 * @return bool
   364 	 * @since 2.5.0
   320 	 */
   365 	 *
   321 	public function exists($file) {
   366 	 * @param string       $file      Path to the file or directory.
   322 		$list = @ftp_nlist($this->link, $file);
   367 	 * @param bool         $recursive Optional. If set to true, changes file group recursively.
       
   368 	 *                                Default false.
       
   369 	 * @param string|false $type      Type of resource. 'f' for file, 'd' for directory.
       
   370 	 *                                Default false.
       
   371 	 * @return bool True on success, false on failure.
       
   372 	 */
       
   373 	public function delete( $file, $recursive = false, $type = false ) {
       
   374 		if ( empty( $file ) ) {
       
   375 			return false;
       
   376 		}
       
   377 		if ( 'f' == $type || $this->is_file( $file ) ) {
       
   378 			return @ftp_delete( $this->link, $file );
       
   379 		}
       
   380 		if ( ! $recursive ) {
       
   381 			return @ftp_rmdir( $this->link, $file );
       
   382 		}
       
   383 
       
   384 		$filelist = $this->dirlist( trailingslashit( $file ) );
       
   385 		if ( ! empty( $filelist ) ) {
       
   386 			foreach ( $filelist as $delete_file ) {
       
   387 				$this->delete( trailingslashit( $file ) . $delete_file['name'], $recursive, $delete_file['type'] );
       
   388 			}
       
   389 		}
       
   390 		return @ftp_rmdir( $this->link, $file );
       
   391 	}
       
   392 
       
   393 	/**
       
   394 	 * Checks if a file or directory exists.
       
   395 	 *
       
   396 	 * @since 2.5.0
       
   397 	 *
       
   398 	 * @param string $file Path to file or directory.
       
   399 	 * @return bool Whether $file exists or not.
       
   400 	 */
       
   401 	public function exists( $file ) {
       
   402 		$list = @ftp_nlist( $this->link, $file );
   323 
   403 
   324 		if ( empty( $list ) && $this->is_dir( $file ) ) {
   404 		if ( empty( $list ) && $this->is_dir( $file ) ) {
   325 			return true; // File is an empty directory.
   405 			return true; // File is an empty directory.
   326 		}
   406 		}
   327 
   407 
   328 		return !empty($list); //empty list = no file, so invert.
   408 		return ! empty( $list ); //empty list = no file, so invert.
   329 	}
   409 	}
   330 
   410 
   331 	/**
   411 	/**
   332 	 *
   412 	 * Checks if resource is a file.
   333 	 * @param string $file
   413 	 *
   334 	 * @return bool
   414 	 * @since 2.5.0
   335 	 */
   415 	 *
   336 	public function is_file($file) {
   416 	 * @param string $file File path.
   337 		return $this->exists($file) && !$this->is_dir($file);
   417 	 * @return bool Whether $file is a file.
   338 	}
   418 	 */
   339 
   419 	public function is_file( $file ) {
   340 	/**
   420 		return $this->exists( $file ) && ! $this->is_dir( $file );
   341 	 *
   421 	}
   342 	 * @param string $path
   422 
   343 	 * @return bool
   423 	/**
   344 	 */
   424 	 * Checks if resource is a directory.
   345 	public function is_dir($path) {
   425 	 *
   346 		$cwd = $this->cwd();
   426 	 * @since 2.5.0
   347 		$result = @ftp_chdir($this->link, trailingslashit($path) );
   427 	 *
       
   428 	 * @param string $path Directory path.
       
   429 	 * @return bool Whether $path is a directory.
       
   430 	 */
       
   431 	public function is_dir( $path ) {
       
   432 		$cwd    = $this->cwd();
       
   433 		$result = @ftp_chdir( $this->link, trailingslashit( $path ) );
   348 		if ( $result && $path == $this->cwd() || $this->cwd() != $cwd ) {
   434 		if ( $result && $path == $this->cwd() || $this->cwd() != $cwd ) {
   349 			@ftp_chdir($this->link, $cwd);
   435 			@ftp_chdir( $this->link, $cwd );
   350 			return true;
   436 			return true;
   351 		}
   437 		}
   352 		return false;
   438 		return false;
   353 	}
   439 	}
   354 
   440 
   355 	/**
   441 	/**
   356 	 *
   442 	 * Checks if a file is readable.
   357 	 * @param string $file
   443 	 *
   358 	 * @return bool
   444 	 * @since 2.5.0
   359 	 */
   445 	 *
   360 	public function is_readable($file) {
   446 	 * @param string $file Path to file.
       
   447 	 * @return bool Whether $file is readable.
       
   448 	 */
       
   449 	public function is_readable( $file ) {
   361 		return true;
   450 		return true;
   362 	}
   451 	}
   363 
   452 
   364 	/**
   453 	/**
   365 	 *
   454 	 * Checks if a file or directory is writable.
   366 	 * @param string $file
   455 	 *
   367 	 * @return bool
   456 	 * @since 2.5.0
   368 	 */
   457 	 *
   369 	public function is_writable($file) {
   458 	 * @param string $file Path to file or directory.
       
   459 	 * @return bool Whether $file is writable.
       
   460 	 */
       
   461 	public function is_writable( $file ) {
   370 		return true;
   462 		return true;
   371 	}
   463 	}
   372 
   464 
   373 	/**
   465 	/**
   374 	 *
   466 	 * Gets the file's last access time.
   375 	 * @param string $file
   467 	 *
   376 	 * @return bool
   468 	 * @since 2.5.0
   377 	 */
   469 	 *
   378 	public function atime($file) {
   470 	 * @param string $file Path to file.
       
   471 	 * @return int|false Unix timestamp representing last access time, false on failure.
       
   472 	 */
       
   473 	public function atime( $file ) {
   379 		return false;
   474 		return false;
   380 	}
   475 	}
   381 
   476 
   382 	/**
   477 	/**
   383 	 *
   478 	 * Gets the file modification time.
   384 	 * @param string $file
   479 	 *
   385 	 * @return int
   480 	 * @since 2.5.0
   386 	 */
   481 	 *
   387 	public function mtime($file) {
   482 	 * @param string $file Path to file.
   388 		return ftp_mdtm($this->link, $file);
   483 	 * @return int|false Unix timestamp representing modification time, false on failure.
   389 	}
   484 	 */
   390 
   485 	public function mtime( $file ) {
   391 	/**
   486 		return ftp_mdtm( $this->link, $file );
   392 	 *
   487 	}
   393 	 * @param string $file
   488 
   394 	 * @return int
   489 	/**
   395 	 */
   490 	 * Gets the file size (in bytes).
   396 	public function size($file) {
   491 	 *
   397 		return ftp_size($this->link, $file);
   492 	 * @since 2.5.0
   398 	}
   493 	 *
   399 
   494 	 * @param string $file Path to file.
   400 	/**
   495 	 * @return int|false Size of the file in bytes on success, false on failure.
   401 	 *
   496 	 */
   402 	 * @param string $file
   497 	public function size( $file ) {
   403 	 * @return bool
   498 		return ftp_size( $this->link, $file );
   404 	 */
   499 	}
   405 	public function touch($file, $time = 0, $atime = 0) {
   500 
       
   501 	/**
       
   502 	 * Sets the access and modification times of a file.
       
   503 	 *
       
   504 	 * Note: If $file doesn't exist, it will be created.
       
   505 	 *
       
   506 	 * @since 2.5.0
       
   507 	 *
       
   508 	 * @param string $file  Path to file.
       
   509 	 * @param int    $time  Optional. Modified time to set for file.
       
   510 	 *                      Default 0.
       
   511 	 * @param int    $atime Optional. Access time to set for file.
       
   512 	 *                      Default 0.
       
   513 	 * @return bool True on success, false on failure.
       
   514 	 */
       
   515 	public function touch( $file, $time = 0, $atime = 0 ) {
   406 		return false;
   516 		return false;
   407 	}
   517 	}
   408 
   518 
   409 	/**
   519 	/**
   410 	 *
   520 	 * Creates a directory.
   411 	 * @param string $path
   521 	 *
   412 	 * @param mixed $chmod
   522 	 * @since 2.5.0
   413 	 * @param mixed $chown
   523 	 *
   414 	 * @param mixed $chgrp
   524 	 * @param string     $path  Path for new directory.
   415 	 * @return bool
   525 	 * @param int|false  $chmod Optional. The permissions as octal number (or false to skip chmod).
   416 	 */
   526 	 *                          Default false.
   417 	public function mkdir($path, $chmod = false, $chown = false, $chgrp = false) {
   527 	 * @param string|int $chown Optional. A user name or number (or false to skip chown).
   418 		$path = untrailingslashit($path);
   528 	 *                          Default false.
   419 		if ( empty($path) )
   529 	 * @param string|int $chgrp Optional. A group name or number (or false to skip chgrp).
   420 			return false;
   530 	 *                          Default false.
   421 
   531 	 * @return bool True on success, false on failure.
   422 		if ( !@ftp_mkdir($this->link, $path) )
   532 	 */
   423 			return false;
   533 	public function mkdir( $path, $chmod = false, $chown = false, $chgrp = false ) {
   424 		$this->chmod($path, $chmod);
   534 		$path = untrailingslashit( $path );
       
   535 		if ( empty( $path ) ) {
       
   536 			return false;
       
   537 		}
       
   538 
       
   539 		if ( ! @ftp_mkdir( $this->link, $path ) ) {
       
   540 			return false;
       
   541 		}
       
   542 		$this->chmod( $path, $chmod );
   425 		return true;
   543 		return true;
   426 	}
   544 	}
   427 
   545 
   428 	/**
   546 	/**
   429 	 *
   547 	 * Deletes a directory.
   430 	 * @param string $path
   548 	 *
   431 	 * @param bool $recursive
   549 	 * @since 2.5.0
   432 	 * @return bool
   550 	 *
   433 	 */
   551 	 * @param string $path      Path to directory.
   434 	public function rmdir($path, $recursive = false) {
   552 	 * @param bool   $recursive Optional. Whether to recursively remove files/directories.
   435 		return $this->delete($path, $recursive);
   553 	 *                          Default false.
   436 	}
   554 	 * @return bool True on success, false on failure.
   437 
   555 	 */
   438 	/**
   556 	public function rmdir( $path, $recursive = false ) {
   439 	 *
   557 		return $this->delete( $path, $recursive );
       
   558 	}
       
   559 
       
   560 	/**
   440 	 * @staticvar bool $is_windows
   561 	 * @staticvar bool $is_windows
   441 	 * @param string $line
   562 	 * @param string $line
   442 	 * @return array
   563 	 * @return array
   443 	 */
   564 	 */
   444 	public function parselisting($line) {
   565 	public function parselisting( $line ) {
   445 		static $is_windows = null;
   566 		static $is_windows = null;
   446 		if ( is_null($is_windows) )
   567 		if ( is_null( $is_windows ) ) {
   447 			$is_windows = stripos( ftp_systype($this->link), 'win') !== false;
   568 			$is_windows = stripos( ftp_systype( $this->link ), 'win' ) !== false;
   448 
   569 		}
   449 		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) ) {
   570 
       
   571 		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 ) ) {
   450 			$b = array();
   572 			$b = array();
   451 			if ( $lucifer[3] < 70 )
   573 			if ( $lucifer[3] < 70 ) {
   452 				$lucifer[3] +=2000;
   574 				$lucifer[3] += 2000;
   453 			else
   575 			} else {
   454 				$lucifer[3] += 1900; // 4digit year fix
   576 				$lucifer[3] += 1900; // 4digit year fix
   455 			$b['isdir'] = ( $lucifer[7] == '<DIR>');
   577 			}
   456 			if ( $b['isdir'] )
   578 			$b['isdir'] = ( $lucifer[7] == '<DIR>' );
       
   579 			if ( $b['isdir'] ) {
   457 				$b['type'] = 'd';
   580 				$b['type'] = 'd';
   458 			else
   581 			} else {
   459 				$b['type'] = 'f';
   582 				$b['type'] = 'f';
   460 			$b['size'] = $lucifer[7];
   583 			}
   461 			$b['month'] = $lucifer[1];
   584 			$b['size']   = $lucifer[7];
   462 			$b['day'] = $lucifer[2];
   585 			$b['month']  = $lucifer[1];
   463 			$b['year'] = $lucifer[3];
   586 			$b['day']    = $lucifer[2];
   464 			$b['hour'] = $lucifer[4];
   587 			$b['year']   = $lucifer[3];
       
   588 			$b['hour']   = $lucifer[4];
   465 			$b['minute'] = $lucifer[5];
   589 			$b['minute'] = $lucifer[5];
   466 			$b['time'] = @mktime($lucifer[4] + (strcasecmp($lucifer[6], "PM") == 0 ? 12 : 0), $lucifer[5], 0, $lucifer[1], $lucifer[2], $lucifer[3]);
   590 			$b['time']   = @mktime( $lucifer[4] + ( strcasecmp( $lucifer[6], 'PM' ) == 0 ? 12 : 0 ), $lucifer[5], 0, $lucifer[1], $lucifer[2], $lucifer[3] );
   467 			$b['am/pm'] = $lucifer[6];
   591 			$b['am/pm']  = $lucifer[6];
   468 			$b['name'] = $lucifer[8];
   592 			$b['name']   = $lucifer[8];
   469 		} elseif ( !$is_windows && $lucifer = preg_split('/[ ]/', $line, 9, PREG_SPLIT_NO_EMPTY)) {
   593 		} elseif ( ! $is_windows && $lucifer = preg_split( '/[ ]/', $line, 9, PREG_SPLIT_NO_EMPTY ) ) {
   470 			//echo $line."\n";
   594 			//echo $line."\n";
   471 			$lcount = count($lucifer);
   595 			$lcount = count( $lucifer );
   472 			if ( $lcount < 8 )
   596 			if ( $lcount < 8 ) {
   473 				return '';
   597 				return '';
   474 			$b = array();
   598 			}
   475 			$b['isdir'] = $lucifer[0]{0} === 'd';
   599 			$b           = array();
       
   600 			$b['isdir']  = $lucifer[0]{0} === 'd';
   476 			$b['islink'] = $lucifer[0]{0} === 'l';
   601 			$b['islink'] = $lucifer[0]{0} === 'l';
   477 			if ( $b['isdir'] )
   602 			if ( $b['isdir'] ) {
   478 				$b['type'] = 'd';
   603 				$b['type'] = 'd';
   479 			elseif ( $b['islink'] )
   604 			} elseif ( $b['islink'] ) {
   480 				$b['type'] = 'l';
   605 				$b['type'] = 'l';
   481 			else
   606 			} else {
   482 				$b['type'] = 'f';
   607 				$b['type'] = 'f';
   483 			$b['perms'] = $lucifer[0];
   608 			}
       
   609 			$b['perms']  = $lucifer[0];
   484 			$b['permsn'] = $this->getnumchmodfromh( $b['perms'] );
   610 			$b['permsn'] = $this->getnumchmodfromh( $b['perms'] );
   485 			$b['number'] = $lucifer[1];
   611 			$b['number'] = $lucifer[1];
   486 			$b['owner'] = $lucifer[2];
   612 			$b['owner']  = $lucifer[2];
   487 			$b['group'] = $lucifer[3];
   613 			$b['group']  = $lucifer[3];
   488 			$b['size'] = $lucifer[4];
   614 			$b['size']   = $lucifer[4];
   489 			if ( $lcount == 8 ) {
   615 			if ( $lcount == 8 ) {
   490 				sscanf($lucifer[5], '%d-%d-%d', $b['year'], $b['month'], $b['day']);
   616 				sscanf( $lucifer[5], '%d-%d-%d', $b['year'], $b['month'], $b['day'] );
   491 				sscanf($lucifer[6], '%d:%d', $b['hour'], $b['minute']);
   617 				sscanf( $lucifer[6], '%d:%d', $b['hour'], $b['minute'] );
   492 				$b['time'] = @mktime($b['hour'], $b['minute'], 0, $b['month'], $b['day'], $b['year']);
   618 				$b['time'] = @mktime( $b['hour'], $b['minute'], 0, $b['month'], $b['day'], $b['year'] );
   493 				$b['name'] = $lucifer[7];
   619 				$b['name'] = $lucifer[7];
   494 			} else {
   620 			} else {
   495 				$b['month'] = $lucifer[5];
   621 				$b['month'] = $lucifer[5];
   496 				$b['day'] = $lucifer[6];
   622 				$b['day']   = $lucifer[6];
   497 				if ( preg_match('/([0-9]{2}):([0-9]{2})/', $lucifer[7], $l2) ) {
   623 				if ( preg_match( '/([0-9]{2}):([0-9]{2})/', $lucifer[7], $l2 ) ) {
   498 					$b['year'] = date("Y");
   624 					$b['year']   = date( 'Y' );
   499 					$b['hour'] = $l2[1];
   625 					$b['hour']   = $l2[1];
   500 					$b['minute'] = $l2[2];
   626 					$b['minute'] = $l2[2];
   501 				} else {
   627 				} else {
   502 					$b['year'] = $lucifer[7];
   628 					$b['year']   = $lucifer[7];
   503 					$b['hour'] = 0;
   629 					$b['hour']   = 0;
   504 					$b['minute'] = 0;
   630 					$b['minute'] = 0;
   505 				}
   631 				}
   506 				$b['time'] = strtotime( sprintf('%d %s %d %02d:%02d', $b['day'], $b['month'], $b['year'], $b['hour'], $b['minute']) );
   632 				$b['time'] = strtotime( sprintf( '%d %s %d %02d:%02d', $b['day'], $b['month'], $b['year'], $b['hour'], $b['minute'] ) );
   507 				$b['name'] = $lucifer[8];
   633 				$b['name'] = $lucifer[8];
   508 			}
   634 			}
   509 		}
   635 		}
   510 
   636 
   511 		// Replace symlinks formatted as "source -> target" with just the source name
   637 		// Replace symlinks formatted as "source -> target" with just the source name
   515 
   641 
   516 		return $b;
   642 		return $b;
   517 	}
   643 	}
   518 
   644 
   519 	/**
   645 	/**
   520 	 *
   646 	 * Gets details for files in a directory or a specific file.
   521 	 * @param string $path
   647 	 *
   522 	 * @param bool $include_hidden
   648 	 * @since 2.5.0
   523 	 * @param bool $recursive
   649 	 *
   524 	 * @return bool|array
   650 	 * @param string $path           Path to directory or file.
   525 	 */
   651 	 * @param bool   $include_hidden Optional. Whether to include details of hidden ("." prefixed) files.
   526 	public function dirlist($path = '.', $include_hidden = true, $recursive = false) {
   652 	 *                               Default true.
   527 		if ( $this->is_file($path) ) {
   653 	 * @param bool   $recursive      Optional. Whether to recursively include file details in nested directories.
   528 			$limit_file = basename($path);
   654 	 *                               Default false.
   529 			$path = dirname($path) . '/';
   655 	 * @return array|false {
       
   656 	 *     Array of files. False if unable to list directory contents.
       
   657 	 *
       
   658 	 *     @type string $name        Name of the file or directory.
       
   659 	 *     @type string $perms       *nix representation of permissions.
       
   660 	 *     @type int    $permsn      Octal representation of permissions.
       
   661 	 *     @type string $owner       Owner name or ID.
       
   662 	 *     @type int    $size        Size of file in bytes.
       
   663 	 *     @type int    $lastmodunix Last modified unix timestamp.
       
   664 	 *     @type mixed  $lastmod     Last modified month (3 letter) and day (without leading 0).
       
   665 	 *     @type int    $time        Last modified time.
       
   666 	 *     @type string $type        Type of resource. 'f' for file, 'd' for directory.
       
   667 	 *     @type mixed  $files       If a directory and $recursive is true, contains another array of files.
       
   668 	 * }
       
   669 	 */
       
   670 	public function dirlist( $path = '.', $include_hidden = true, $recursive = false ) {
       
   671 		if ( $this->is_file( $path ) ) {
       
   672 			$limit_file = basename( $path );
       
   673 			$path       = dirname( $path ) . '/';
   530 		} else {
   674 		} else {
   531 			$limit_file = false;
   675 			$limit_file = false;
   532 		}
   676 		}
   533 
   677 
   534 		$pwd = @ftp_pwd($this->link);
   678 		$pwd = @ftp_pwd( $this->link );
   535 		if ( ! @ftp_chdir($this->link, $path) ) // Cant change to folder = folder doesn't exist
   679 		if ( ! @ftp_chdir( $this->link, $path ) ) { // Can't change to folder = folder doesn't exist.
   536 			return false;
   680 			return false;
   537 		$list = @ftp_rawlist($this->link, '-a', false);
   681 		}
   538 		@ftp_chdir($this->link, $pwd);
   682 		$list = @ftp_rawlist( $this->link, '-a', false );
   539 
   683 		@ftp_chdir( $this->link, $pwd );
   540 		if ( empty($list) ) // Empty array = non-existent folder (real folder will show . at least)
   684 
   541 			return false;
   685 		if ( empty( $list ) ) { // Empty array = non-existent folder (real folder will show . at least).
       
   686 			return false;
       
   687 		}
   542 
   688 
   543 		$dirlist = array();
   689 		$dirlist = array();
   544 		foreach ( $list as $k => $v ) {
   690 		foreach ( $list as $k => $v ) {
   545 			$entry = $this->parselisting($v);
   691 			$entry = $this->parselisting( $v );
   546 			if ( empty($entry) )
   692 			if ( empty( $entry ) ) {
   547 				continue;
   693 				continue;
   548 
   694 			}
   549 			if ( '.' == $entry['name'] || '..' == $entry['name'] )
   695 
       
   696 			if ( '.' == $entry['name'] || '..' == $entry['name'] ) {
   550 				continue;
   697 				continue;
   551 
   698 			}
   552 			if ( ! $include_hidden && '.' == $entry['name'][0] )
   699 
       
   700 			if ( ! $include_hidden && '.' == $entry['name'][0] ) {
   553 				continue;
   701 				continue;
   554 
   702 			}
   555 			if ( $limit_file && $entry['name'] != $limit_file)
   703 
       
   704 			if ( $limit_file && $entry['name'] != $limit_file ) {
   556 				continue;
   705 				continue;
       
   706 			}
   557 
   707 
   558 			$dirlist[ $entry['name'] ] = $entry;
   708 			$dirlist[ $entry['name'] ] = $entry;
   559 		}
   709 		}
   560 
   710 
   561 		$ret = array();
   711 		$ret = array();
   562 		foreach ( (array)$dirlist as $struc ) {
   712 		foreach ( (array) $dirlist as $struc ) {
   563 			if ( 'd' == $struc['type'] ) {
   713 			if ( 'd' == $struc['type'] ) {
   564 				if ( $recursive )
   714 				if ( $recursive ) {
   565 					$struc['files'] = $this->dirlist($path . '/' . $struc['name'], $include_hidden, $recursive);
   715 					$struc['files'] = $this->dirlist( $path . '/' . $struc['name'], $include_hidden, $recursive );
   566 				else
   716 				} else {
   567 					$struc['files'] = array();
   717 					$struc['files'] = array();
       
   718 				}
   568 			}
   719 			}
   569 
   720 
   570 			$ret[ $struc['name'] ] = $struc;
   721 			$ret[ $struc['name'] ] = $struc;
   571 		}
   722 		}
   572 		return $ret;
   723 		return $ret;
   573 	}
   724 	}
   574 
   725 
   575 	/**
   726 	/**
       
   727 	 * Destructor.
       
   728 	 *
       
   729 	 * @since 2.5.0
   576 	 */
   730 	 */
   577 	public function __destruct() {
   731 	public function __destruct() {
   578 		if ( $this->link )
   732 		if ( $this->link ) {
   579 			ftp_close($this->link);
   733 			ftp_close( $this->link );
       
   734 		}
   580 	}
   735 	}
   581 }
   736 }