EVOLUTION-MANAGER
Edit File: class.ftp.curl.php
<?php defined("ABSPATH") or die(""); class DUP_PRO_FTPcURL { /** @var string FTP host */ private $server; /** @var string FTP port */ private $port = 21; /** @var string FTP username */ private $username; /** @var string FTP password */ private $password; /** @var string remote FTP directory */ private $directory = '/'; /** @var integer timeout in seconds */ private $timeout_in_sec = 90; /** @var boolean whether FTP is ssl ftp or not */ private $ssl = false; /** @var boolean whether FTP is passive mode or not */ private $passive_mode = false; /** * class constructor * * @param string $server FTP host * @param integer $port FTP port * @param string $username FTP username * @param string $password FTP password * @param string $directory remote FTP directory * @param integer $timeout_in_sec timeout in seconds * @param boolean $ssl whether it is ssl ftp or not * @param boolean $passive_mode whether it is passive mode or not * * @return void */ public function __construct($server, $port = 21, $username = 'anonymous', $password = 'anonymous@gmail.com', $directory = '/', $timeout_in_sec = 15, $ssl = false, $passive_mode = false) { if (empty($server)) { throw new InvalidArgumentException(DUP_PRO_U::esc_html__('Invalid $server argument with the empty in DUP_PRO_FTPcURL class constructor.')); } if (!is_string($server)) { throw new InvalidArgumentException(sprintf(DUP_PRO_U::esc_html__('Invalid $server argument with the value %s in DUP_PRO_FTPcURL class constructor.')), $server); } if (empty($port)) { throw new InvalidArgumentException(DUP_PRO_U::esc_html__('Invalid $port argument with the empty in DUP_PRO_FTPcURL class constructor.')); } $port = (int) $port; if ($port <= 0) { throw new InvalidArgumentException(sprintf(DUP_PRO_U::esc_html__('Invalid $port argument with the value %s in DUP_PRO_FTPcURL class constructor.')), $port); } if (empty($username)) { throw new InvalidArgumentException(DUP_PRO_U::esc_html__('Invalid $username argument with the empty in DUP_PRO_FTPcURL class constructor.')); } if (!is_string($username)) { throw new InvalidArgumentException(sprintf(DUP_PRO_U::esc_html__('Invalid $username argument with the value %s in DUP_PRO_FTPcURL class constructor.')), $username); } if (empty($password)) { throw new InvalidArgumentException(DUP_PRO_U::esc_html__('Invalid $password argument with the empty in DUP_PRO_FTPcURL class constructor.')); } if (!is_string($password)) { throw new InvalidArgumentException(sprintf(DUP_PRO_U::esc_html__('Invalid $password argument with the value %s in DUP_PRO_FTPcURL class constructor.')), $password); } if (empty($directory)) { throw new InvalidArgumentException(DUP_PRO_U::esc_html__('Invalid $directory argument with the empty in DUP_PRO_FTPcURL class constructor.')); } if (!is_string($directory)) { throw new InvalidArgumentException(sprintf(DUP_PRO_U::esc_html__('Invalid $directory argument with the value %s in DUP_PRO_FTPcURL class constructor.')), $directory); } if (empty($timeout_in_sec)) { throw new InvalidArgumentException(DUP_PRO_U::esc_html__('Invalid $timeout_in_sec argument with the empty in DUP_PRO_FTPcURL class constructor.')); } $timeout_in_sec = (int) $timeout_in_sec; if ($timeout_in_sec <= 0) { throw new InvalidArgumentException(sprintf(DUP_PRO_U::esc_html__('Invalid $timeout_in_sec argument with the value %s in DUP_PRO_FTPcURL class constructor.')), $timeout_in_sec); } if (!is_bool($ssl)) { throw new InvalidArgumentException(sprintf(DUP_PRO_U::esc_html__('Invalid $ssl argument with the value %s in DUP_PRO_FTPcURL class constructor.')), $ssl); } if (!is_bool($passive_mode)) { throw new InvalidArgumentException(sprintf(DUP_PRO_U::esc_html__('Invalid $passive_mode argument with the value %s in DUP_PRO_FTPcURL class constructor.')), $passive_mode); } $this->server = $server; $this->port = $port; $this->username = $username; $this->password = $password; $this->directory = $directory; $this->timeout_in_sec = $timeout_in_sec; $this->ssl = $ssl; $this->passive_mode = $passive_mode; } /** * curl call * * @param string $path where the curl call occur * @param array $options configuration options * * @throws Exception upon error * @return void */ private function curl_call($path, $options = array()) { $ch = curl_init(); $url = sprintf('ftp://%s:%d/%s', $this->server, $this->port, $this->untrailing_left_slash_path($path)); curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_USERPWD, sprintf('%s:%s', $this->username, $this->password)); curl_setopt($ch, CURLOPT_HEADER, false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_BINARYTRANSFER, true); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $this->timeout_in_sec); curl_setopt($ch, CURLOPT_TIMEOUT, 0); if ($this->ssl) { curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); curl_setopt($ch, CURLOPT_FTP_SSL, CURLFTPSSL_TRY); curl_setopt($ch, CURLOPT_FTPSSLAUTH, CURLFTPAUTH_TLS); } else { // To do based on settings verify peer $global = DUP_PRO_Global_Entity::get_instance(); $setopts[CURLOPT_SSL_VERIFYPEER] = $global->ssl_disableverify ? false : true; $setopts[CURLOPT_SSL_VERIFYHOST] = $global->ssl_disableverify ? 0 : 2; if (!$global->ssl_useservercerts) { $setopts[CURLOPT_CAINFO] = DUPLICATOR_PRO_CERT_PATH; $setopts[CURLOPT_CAPATH] = DUPLICATOR_PRO_CERT_PATH; } } if ($this->passive_mode) { curl_setopt($ch, CURLOPT_FTP_USE_EPSV, true); } else { curl_setopt($ch, CURLOPT_FTP_USE_EPRT, true); curl_setopt($ch, CURLOPT_FTPPORT, 0); } foreach ($options as $name => $value) { curl_setopt($ch, $name, $value); } if (($response = curl_exec($ch)) === false) { if (($errno = curl_errno($ch))) { switch ($errno) { case 6: case 7: throw new Exception(DUP_PRO_U::esc_html__('Unable to connect to FTP server. Please check your FTP hostname, port, and active mode settings.')); case 9: throw new Exception(DUP_PRO_U::esc_html__('Unable to change FTP directory. Please ensure that you have permission on the server.')); case 23: throw new Exception(DUP_PRO_U::esc_html__('Unable to download file from FTP server. Please ensure that you have enough disk space.')); case 28: throw new Exception(DUP_PRO_U::esc_html__('Connecting to FTP server timed out. Please check FTP hostname, port, username, password, and active mode settings.')); case 67: throw new Exception(DUP_PRO_U::esc_html__('Unable to login to FTP server. Please check your username and password.')); default: throw new Exception(sprintf(DUP_PRO_U::esc_html__('Unable to connect to FTP. Error code: %s.'), $errno)); } } } $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); if ($http_code >= 400) { if (isset($this->messages[$http_code])) { throw new Exception(sprintf(DUP_PRO_U::esc_html__('Error code: %s.'), $this->messages[ $http_code ])); } else { throw new Exception(sprintf(DUP_PRO_U::esc_html__('Error code: %s.'), $http_code)); } } return $response; } /** * test ftp connection * * @throws Exception upon error * @return boolean */ public function test_conn() { try { $this->curl_call( '/', array( CURLOPT_TIMEOUT => $this->timeout_in_sec, ) ); } catch (Exception $e) { $message = sprintf(DUP_PRO_U::esc_html__('Error connecting to FTP server %1$s:%2$d. Exception Error: %3$s.'), $this->server, $this->port, $e->getMessage()); DUP_PRO_LOG::trace($message); throw $e; } return true; } /** * create a dir * * @param string $folder_path a dir path which need to create * * @throws Exception upon error * @return boolean */ public function create_directory($folder_path) { try { $this->curl_call( $folder_path, array( CURLOPT_FTP_CREATE_MISSING_DIRS => true, ) ); } catch(Exception $e) { DUP_PRO_LOG::trace("Error creating folder when using FTP cURL. FTP Info: " . $this->get_info().' Exception Error: ' . $e->getMessage()); return false; } DUP_PRO_LOG::trace("Successfully created folder by FTP cURL."); return true; } /** * upload file * * @param string $local_file_path local file path * @param string $remote_file_path remote file path * @param string $dest_filename destination file name * * @throws Exception upon error * @return void */ public function upload_file($local_file_path, $remote_file_path, $dest_filename = '') { $remote_file_path = $this->untrailing_left_slash_path($remote_file_path); $file_size = filesize($local_file_path); // Upload file from file stream if (($file_stream = fopen($local_file_path, 'rb'))) { $options = array( CURLOPT_UPLOAD => true, // by chance If FTP curl storage set and In btw a user has deleted storage dir from a FTP program CURLOPT_FTP_CREATE_MISSING_DIRS => true, CURLOPT_INFILE => $file_stream, CURLOPT_INFILESIZE => $file_size, CURLOPT_NOPROGRESS => true, ); $this->curl_call(sprintf('/%s/%s', $this->directory, $remote_file_path), $options); fclose( $file_stream ); } return true; } /** * download file * * @param string $remote_file_path * @param string $local either local file path or local dir, depends on the $is_local_directory * @param boolean $is_local_directory whether $local is local directory or full path to download * * @throws Exception upon error * @return boolean */ public function download_file($remote_file_path, $local, $is_local_directory = true) { $remote_file_path = $this->untrailing_left_slash_path($remote_file_path); if ($is_local_directory) { $filename = basename($remote_file_path); $local_file_path = "$local/$filename"; } else { $local_file_path = $local; } if (($file_stream = fopen($local_file_path, 'wb'))) { try { $options = array( CURLOPT_FILE => $file_stream, CURLOPT_NOPROGRESS => true, ); $this->curl_call(sprintf('/%s/%s', $this->directory, $remote_file_path), $options); } catch (Exception $e) { DUP_PRO_LOG::trace("Error downloading " . $remote_source_filepath . " into " . $local_dest_filepath . ". FTP Info: " . $this->get_info().' Exception Error: ' . $e->getMessage()); throw $e; } fclose($file_stream); } return true; } /** * delete remote file * * @param string $file_path remote file path * * @throws Exception upon error * @return boolean */ public function delete($file_path) { $file_path = $this->untrailing_left_slash_path($file_path); try { $this->curl_call( sprintf('/%s/', $this->directory), array( CURLOPT_QUOTE => array(sprintf('DELE /%s', $this->untrailing_left_slash_path(sprintf('/%s/%s', $this->directory, $file_path)))), ) ); } catch (Exception $e) { DUP_PRO_LOG::trace("Failed to delete ".$file_path." from " . $this->server . ". Error code: " . $e->getCode() . ". Error Message:" . $e->getMessage()); return false; } DUP_PRO_LOG::trace("Successfully deleted ".$file_path." from " . $this->server); return true; } /** * upload file by chunking mode * * @param string $source_filepath local file path * @param string $storage_folder remote folder * @param integer $max_upload_time_in_sec max upload time in sec in a chunk * @param integer $offset file offset * @param integer $server_load_delay * @param string $dest_filename remote file name * @return object */ public function upload_chunk($source_filepath, $storage_folder, $max_upload_time_in_sec = 15, $offset = 0, $server_load_delay = 0, $dest_filename = '') { DUP_PRO_LOG::trace("----------------------------"); DUP_PRO_LOG::trace("FTP CURL CHUNK UPLOAD START"); DUP_PRO_LOG::trace("FTP CHUNK OFFSET IN=$offset"); /* @var $ftp_upload_info DUP_PRO_FTP_UploadInfo */ $ftp_upload_info = new DUP_PRO_FTP_UploadInfo(); $start_time = time(); $remote_file_path = !empty($dest_filename) ? $dest_filename : basename($source_filepath); try { DUP_PRO_LOG::trace("call upload chunk for FTP cURL"); $fp = fopen($source_filepath, 'rb'); if ($fp) { // Read file chunk data while (fseek($fp, $offset) !== -1 && $file_chunk_data = fread($fp, DUPLICATOR_PRO_FTP_CURL_CHUNK_SIZE)) { $ret = $this->upload_file_chunk($file_chunk_data, $remote_file_path, $offset); if ($ret) { $offset = ftell($fp); } $time_passed = time() - $start_time; if ($time_passed < $max_upload_time_in_sec) { $offset = ftell($fp); DUP_PRO_LOG::trace("Timed out $max_upload_time_in_sec so saving off offset $offset"); break; } if($server_load_delay !== 0) { usleep($server_load_delay); } } $ftp_upload_info->next_offset = $offset; } else { $message = sprintf(DUP_PRO_U::esc_html__('Error opening %1$ for cURL FTP'), $source_filepath); DUP_PRO_LOG::trace($message); $ftp_upload_info->error_details = $message; $ftp_upload_info->next_offset = $offset; } DUP_PRO_LOG::trace("closing local file handle"); fclose($fp); $source_filesize = filesize($source_filepath); if ($source_filesize <= $ftp_upload_info->next_offset) { $ftp_size = $this->get_file_size($remote_file_path); if (!$ftp_size && $ftp_size != $source_filesize) { $error_message = sprintf(DUP_PRO_U::esc_html__('cURL FTP size mismatch for %1$s. Local file=%2$d bytes while server\'s file is %3$d bytes.'), $source_filepath, $source_filesize, $ftp_size); DUP_PRO_LOG::trace($error_message); $ftp_upload_info->error_details = $error_message; $ftp_upload_info->fatal_error = true; $this->delete($remote_file_path); } else { DUP_PRO_LOG::trace("FTP cURL upload file finish"); $ftp_upload_info->success = true; } } else { DUP_PRO_LOG::trace("FTP cURL upload file is not finished yet.."); } } catch (Exception $e) { $message = "Tried to upload file when connection wasn't opened. Info:" . $this->get_info() .' Exception Error: '. $e->getMessage(); $ftp_upload_info->error_details = $message; DUP_PRO_LOG::trace($message); $ftp_upload_info->next_offset = $offset; } DUP_PRO_LOG::trace("FTP CURL CHUNK UPLOAD END"); DUP_PRO_LOG::trace("----------------------------"); return $ftp_upload_info; } /** * upload file chunk with given data * * @param string $file_chunk_data * @param string $remote_file_path * @param integer $file_range_start * * @throws Exception upon error * @return boolean */ private function upload_file_chunk($file_chunk_data, $remote_file_path, $file_range_start = 0) { $remote_file_path = $this->untrailing_left_slash_path($remote_file_path); if (($file_chunk_stream = fopen('php://temp', 'wb+')) === false) { return true; } if (($file_chunk_size = fwrite($file_chunk_stream, $file_chunk_data)) === false) { return true; } rewind($file_chunk_stream); $this->curl_call( sprintf('/%s/%s', $this->directory, $remote_file_path), array( CURLOPT_UPLOAD => true, CURLOPT_FTPAPPEND => true, CURLOPT_INFILE => $file_chunk_stream, CURLOPT_INFILESIZE => $file_chunk_size, ) ); fclose($file_chunk_stream); return true; } /** * get remote file size * * @param string $remote_file_name * @return boolean|integer gives size of remote file or false on failure */ public function get_file_size($remote_file_name) { $items = $this->raw_list_folder($this->directory); foreach ($items as $item) { if ($meta = preg_split('/\s+/', $item)) { if (!empty($meta[8]) && !empty($meta[4]) && $meta[8] == $remote_file_name) { return $meta[4]; } } } return false; } /** * get information of the FTP * * @return string */ private function get_info() { $ssl_string = DUP_PRO_STR::boolToString($this->ssl); $passive_string = DUP_PRO_STR::boolToString($this->passive_mode); return sprintf(DUP_PRO_U::esc_html__('Server:%1$s Port:%2$d User:%3$s Directory:%4$s SSL:%5$s Passive:%6$s.'), $this->server, $this->port, $this->username, $this->directory, $ssl_string, $passive_string); } /** * get all files in remote folder * * @param string $folder_path remote folder path * * @throws Exception upon error * @return array */ public function get_filelist($folder_path) { $items = $this->raw_list_folder($folder_path); $files = array(); foreach ($items as $item) { if ($meta = preg_split('/\s+/', $item)) { if (!empty($meta[8])) { $files[] = trim($meta[8]); } } } DUP_PRO_LOG::trace("Get file list from cURL FTP: " . print_r($files, true)); return $files; } /** * get all files meta information * * @param string $folder_path remote dir path * * @throws Exception upon error * @return array */ private function raw_list_folder($folder_path) { $folder_path = $this->untrailing_left_slash_path($folder_path); $items = $this->curl_call( sprintf('/%s/', $folder_path), array( CURLOPT_CUSTOMREQUEST => 'LIST -tlA', ) ); return explode("\n", $items); } /** * clean path with removing left slash * * @param string $path * @return string */ private function untrailing_left_slash_path($path) { return ltrim(preg_replace('/[\\\\\/]+/', '/', $path), '/'); } }