| Server IP : 5.129.245.98 / Your IP : 216.73.216.246 Web Server : Apache/2.4.66 (Debian) System : Linux 1b8c17c336b9 6.8.0-111-generic #111-Ubuntu SMP PREEMPT_DYNAMIC Sat Apr 11 23:16:02 UTC 2026 x86_64 User : www-data ( 33) PHP Version : 8.3.30 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : OFF | Perl : ON | Python : OFF | Sudo : OFF | Pkexec : OFF Directory : /var/www/html/wp-content/plugins/wp-all-import-pro/classes/filesystem/ |
Upload File : |
<?php
defined( 'ABSPATH' ) || exit;
use Wpai\Http\Request;
use Wpai\Http\JsonResponse;
use League\Flysystem\Filesystem;
use League\Flysystem\Sftp\SftpAdapter;
use League\Flysystem\Adapter\Ftp as FtpAdapter;
class RemoteFilesystem {
private $options;
private $filesystem;
private $error = false;
private $rel_type;
private $default_port = false;
private $error_stack = [];
private $type = '';
private $contents;
private $orig_type = '';
private $allow_hidden_files_folders;
// Should match list in classes/upload.php#56 - xml|gzip|zip|csv|tsv|gz|json|txt|dat|psv|sql|xls|xlsx
private $allowed_file_extensions = ['xml','gzip','zip','csv','tsv','gz','json','txt','dat','psv','sql','xls','xlsx'];
private $debug;
private $attempted_roots = [];
public function __construct( $options ) {
// Enable debug filter
$this->debug = apply_filters('wpai_ftp_enable_debug', false);
// Show hidden files filter.
$this->allow_hidden_files_folders = apply_filters('wpai_ftp_allow_hidden_files_folders', false);
// Allowed file extensions filter.
$this->allowed_file_extensions = apply_filters('wpai_ftp_allowed_file_extensions', $this->allowed_file_extensions);
/*
* INI default_socket_timeout should be higher than the 'timeout' parameter below.
*/
// Default options
$default_options = [
'root' => '/',
'timeout' => apply_filters('wpai_ftp_timeout', 10),
// FTP only options
'passive' => apply_filters('wpai_ftp_passive_mode', true),
'ignorePassiveAddress' => apply_filters('wpai_ftp_ignore_passive_address', false),
'enableTimestampsOnUnixListings' => true,
'ssl' => false,
];
$this->options = $this->option_merge( $default_options, $options );
// Root override
$this->options['root'] = apply_filters('wpai_ftp_root', $this->options['root'], PMXI_Plugin::getCurrentImportId());
$this->attempted_roots[] = $this->options['root'];
// Ensure no trailing slash or text after the URL exists.
$this->options['host'] = preg_replace( '@(?<![/:])/.*@', '', $this->options['host'] );
// Connect to FTP server.
$this->connect();
// Process any relative file references.
$this->get_relative();
}
/**
* Determine protocol and attempt to connect.
*/
private function connect() {
if ( preg_match( '%^sftp://%i', trim( $this->options['host'] ) ) || !empty($this->options['privateKey']) ) {
$this->default_port = [ 22, 2222 ];
$this->options['host'] = preg_replace( '%^sftp://%i', '', trim($this->options['host'] ));
$this->buildFilesystem( 'sftp' );
} elseif ( preg_match( '%^ftp://%i', trim( $this->options['host'] ) ) ) {
$this->default_port = [ 21 ];
$this->options['host'] = preg_replace( '%^ftp://%i', '', trim( $this->options['host'] ));
$this->buildFilesystem( 'ftp' );
} elseif ( preg_match( '%^ftps://%i', trim( $this->options['host'] ) ) ) {
$this->default_port = [ 21 ];
$this->options['host'] = preg_replace( '%^ftps://%i', '', trim( $this->options['host'] ) );
$this->options['ssl'] = true;
$this->buildFilesystem( 'ftp' );
} elseif ( trim( $this->options['port'] ) == 21 ) {
$this->default_port = [ 21 ];
$this->buildFilesystem( 'ftp' );
} elseif ( trim( $this->options['port'] ) == 22 || trim( $this->options['port'] ) == 2222 ) {
$this->default_port = [ 22, 2222 ];
$this->buildFilesystem( 'sftp' );
} else {
// Try SFTP by default.
$this->default_port = [ 22, 2222 ];
$this->buildFilesystem( 'sftp' );
}
}
private function buildFilesystem( $type ) {
$this->type = $type;
if( $this->orig_type === '' )
$this->orig_type = $type;
try {
switch ( $type ) {
case 'ftp':
if(!defined('FTP_BINARY')){
throw new Exception('PHP FTP support is not enabled on your site. FTP connections will fail.');
}
$this->filesystem = new Filesystem( new FtpAdapter( $this->options ) );
break;
case 'sftp':
$this->filesystem = new Filesystem( new SftpAdapter( $this->options ) );
break;
}
} catch ( \Throwable $e ) {
$this->error = $e->getMessage();
}
}
public function listContents( $recursive = false ) {
try {
// Store the contents in a parameter since we are using a recursive method on error.
$this->contents = $this->filesystem->listContents( $this->options['dir'], $recursive );
// Filter the contents.
$this->filter_contents();
} catch ( \Throwable $e ) {
$this->error = $e->getMessage();
// Log all of the error messages if debug mode is enabled.
if( $this->debug ){
error_log("WPAI_FTP_DEBUG");
error_log("Error Message:");
error_log($e->getMessage());
error_log("Options Array:");
error_log(print_r($this->options, true));
error_log("END WPAI_FTP_DEBUG");
}
if ( str_replace( [
'Root is invalid or does not exist:',
], '', $e->getMessage() ) !== $e->getMessage() ) {
$new_root = '/';
if ( ! in_array( '/home/', $this->attempted_roots ) ) {
$new_root = '/home/';
} elseif ( preg_match( '@\..{0,4}@', $this->options['root'] ) === 1 && ! in_array( dirname( $this->options['root'] ), $this->attempted_roots ) ) {
$new_root = dirname( $this->options['root'] );
} elseif ( ! in_array( $this->options['dir'], $this->attempted_roots ) ) {
$new_root = $this->options['dir'];
}
if ( ! in_array( $new_root, $this->attempted_roots ) ) {
// Remove the root from the directory path to ensure it's not doubled.
$this->options['dir'] = str_replace( $new_root, '', $this->options['dir']);
$this->options['root'] = $new_root;
$this->attempted_roots[] = $this->options['root'];
$this->error_stack[] = $this->error;
$this->error = false;
$this->buildFilesystem( $this->type );
$this->listContents( $recursive );
} else {
return $this->contents;
}
}
// Check if it was a login failure or connection failure.
// Don't retry if login failure or if host couldn't be found.
elseif ( str_replace( [ 'Could not login',
'php_network_getaddresses: getaddrinfo failed:',
], '',$e->getMessage() ) !== $e->getMessage() ) {
return $this->contents;
} else {
errorProcessing:
// Retry with default port(s)
if ( is_array( $this->default_port ) && count( $this->default_port ) > 0) {
$port = array_pop( $this->default_port );
while ( $port === $this->options['port'] && count( $this->default_port ) > 0 ) {
$port = array_pop( $this->default_port );
}
if ( $port != $this->options['port'] ) {
$this->options['port'] = $port;
$this->error_stack[] = $this->error;
$this->error = false;
$this->buildFilesystem( $this->type );
$this->listContents( $recursive );
}else{
goto errorProcessing;
}
} elseif ( $this->orig_type === $this->type && empty( $this->options['privateKey'] ) ) {
$this->type = $this->type === 'sftp' ? 'ftp' : 'sftp';
$this->default_port = $this->type === 'sftp' ? [ 22, 2222 ] : [ 21 ];
$this->error_stack[] = $this->error;
$this->error = false;
$this->buildFilesystem( $this->type );
$this->listContents( $recursive );
} elseif ( count( $this->error_stack ) > 0 ) {
// Sort the errors so they are in order
$this->error = '<br/><br/><b>Connection Attempt:</b><br/>' . implode( '<br/><br/><b>Connection Attempt:</b><br/>', $this->error_stack ) . '<br/><br/><b>Connection Attempt:</b><br/>' . $this->error;
}
// Return if there was an error.
return $this->contents;
}
}
// If an additional error wasn't caught then clear the error stack and move on.
$this->error = false;
$this->error_stack = [];
return $this->contents;
}
public function copy() {
try {
$uploads = wp_upload_dir();
$destination = wp_all_import_secure_file( $uploads['basedir'] . DIRECTORY_SEPARATOR . PMXI_Plugin::UPLOADS_DIRECTORY, PMXI_Plugin::getCurrentImportId(), true );
// Write the file.
$result = file_put_contents( $destination . '/' . basename( $this->options['dir'] ), $this->filesystem->readStream( $this->options['dir'] ) );
if ( $result ) {
return [ $destination . '/' . basename( $this->options['dir'] ) ];
}
} catch ( \Throwable $e ) {
$this->error = $e->getMessage();
// If FTP try via cURL as a last resort
if ( $this->type == 'ftp' ) {
try {
$curl = curl_init();
$filename = $destination . '/' . basename( $this->options['dir'] );
$file = fopen( $destination . '/' . basename( $this->options['dir'] ), 'w' );
curl_setopt( $curl, CURLOPT_URL, 'ftp://' . str_replace(array('ftps://', 'ftp://'), '', $this->options['ftp_host']) . '/' . $this->options['dir'] ); #input
curl_setopt( $curl, CURLOPT_RETURNTRANSFER, 1 );
curl_setopt( $curl, CURLOPT_FILE, $file ); #output
curl_setopt( $curl, CURLOPT_USERPWD, $this->options['ftp_username'] . ':' . $this->options['ftp_password'] );
curl_setopt($curl, CURLOPT_FTP_SSL, CURLFTPSSL_TRY);
curl_setopt($curl, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_DEFAULT);
curl_setopt($curl, CURLOPT_PORT, $this->options['port']);
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 0);
curl_exec( $curl );
curl_close( $curl );
fclose( $file );
if ( ( is_file( $filename ) ) && ( 0 !== filesize( $filename ) ) ) {
return [ $filename ];
}
} catch ( \Throwable $e ) {
$this->error = $e->getMessage();
return false;
}
} else {
return false;
}
}
}
public function getError() {
return $this->error;
}
private function option_merge( $default, $opts ) {
$options = array_merge( $default, $opts );
if ( isset( $opts['ftp_host'] ) ) {
$options['host'] = $opts['ftp_host'];
}
if ( isset( $opts['ftp_port'] ) ) {
$options['port'] = $opts['ftp_port'];
}
if ( isset( $opts['ftp_path'] ) ) {
$options['dir'] = $opts['ftp_path'];
}
if ( isset( $opts['ftp_root'] ) ) {
$options['root'] = $opts['ftp_root'];
}
if ( isset( $opts['ftp_username'] ) ) {
$options['username'] = $opts['ftp_username'];
}
if ( isset( $opts['ftp_password'] ) ) {
$options['password'] = $opts['ftp_password'];
}
if ( isset( $opts['ftp_private_key'] ) ) {
$options['privateKey'] = $opts['ftp_private_key'];
}
return $options;
}
private function get_relative() {
try {
$matches = [];
// Check if a relative file reference was provided.
preg_match( '#{(.*)\.(.{0,4})}#', $this->options['dir'], $matches );
// Ensure all the expected pieces were found or do nothing.
if ( isset( $matches[0] ) && isset( $matches[1] ) && isset( $matches[2] ) ) {
$relative = $matches[1]; // Relative reference such as oldest.
$this->rel_type = $matches[2]; // The file extension to find.
// Remove relative file reference from dir before LIST.
$this->options['dir'] = trim( str_replace( $matches[0], '', $this->options['dir'] ), '/' );
$contents = $this->listContents();
if ( $this->error !== false ) {
throw new Exception( 'Failed to retrieve remote file listing.' );
}
switch ( $relative ) {
case ( 'any' ):
// Filter out any non files and filter by file type.
$contents = array_filter( $contents, function ( $var ) {
return ( isset( $var['type'] ) && $var['type'] == 'file' && isset( $var['extension'] ) && ( $var['extension'] == $this->rel_type || empty( $this->rel_type ) ) );
} );
$file = array_pop( $contents );
isset( $file['path'] ) && $this->options['dir'] = $file['path'];
break;
case ( 'oldest' ):
// Filter out any non files and filter by file type.
$contents = array_filter( $contents, function ( $var ) {
return ( isset( $var['type'] ) && $var['type'] == 'file' && isset( $var['extension'] ) && ( $var['extension'] == $this->rel_type || empty( $this->rel_type ) ) );
} );
// Sort by timestamp newest to oldest.
uasort( $contents, function ( $a, $b ) {
return $b['timestamp'] - $a['timestamp'];
} );
$file = array_pop( $contents );
isset( $file['path'] ) && $this->options['dir'] = $file['path'];
break;
case ( 'newest' ):
// Filter out any non files and filter by file type.
$contents = array_filter( $contents, function ( $var ) {
return ( isset( $var['type'] ) && $var['type'] == 'file' && isset( $var['extension'] ) && ( $var['extension'] == $this->rel_type || empty( $this->rel_type ) ) );
} );
// Sort by timestamp oldest to newest.
uasort( $contents, function ( $a, $b ) {
return $a['timestamp'] - $b['timestamp'];
} );
$file = array_pop( $contents );
isset( $file['path'] ) && $this->options['dir'] = $file['path'];
break;
case ( 'custom' ):
// Pass the current dir, found files, and target extension back to the user via filter.
// The full directory pointing to a single file must be returned.
$this->options['dir'] = apply_filters('wpai_ftp_custom_target_file_filter', $this->options['dir'], $contents, $this->rel_type);
break;
}
}
} catch ( \Throwable $e ) {
$this->error = $e->getMessage();
}
}
public function get_port() {
return $this->options['port'];
}
public function get_protocol() {
if( $this->type == 'ftp' && $this->options['ssl'] == true )
$this->type = 'ftps';
return $this->type;
}
public function get_root() {
return $this->options['root'];
}
private function filter_contents(){
// Build filter string.
$filters = [];
if( !$this->allow_hidden_files_folders ) {
$filters[] = '^\.'; // Match leading period.
}
if( count($filters) > 0) {
$filter = '@' . implode( '|', $filters ) . '@';
$this->contents = array_filter( $this->contents, function ( $var ) use ( $filter ) {
// Ensure that $var has an extension element in all cases.
if( !isset($var['extension']))
$var['extension'] = '';
return ( preg_match( $filter, $var['basename'] ) !== 1 && ( $var['type'] === 'dir' || in_array( strtolower($var['extension']), $this->allowed_file_extensions ) ) );
} );
}
}
}