From 42b9a6a9018655b4072928970666da9ab5512248 Mon Sep 17 00:00:00 2001 From: Leon Date: Fri, 10 Mar 2017 15:25:19 +1300 Subject: [PATCH] fix FTP submissions --- library/FTP/FtpClient.php | 877 +++++++++++++++++++++++++++++++++++ library/FTP/FtpException.php | 20 + library/FTP/FtpWrapper.php | 115 +++++ library/FTP/ftp.php | 466 ------------------- library/StaticHtmlOutput.php | 45 +- views/options-page.phtml | 4 +- 6 files changed, 1032 insertions(+), 495 deletions(-) create mode 100644 library/FTP/FtpClient.php create mode 100644 library/FTP/FtpException.php create mode 100644 library/FTP/FtpWrapper.php delete mode 100644 library/FTP/ftp.php diff --git a/library/FTP/FtpClient.php b/library/FTP/FtpClient.php new file mode 100644 index 00000000..231592a4 --- /dev/null +++ b/library/FTP/FtpClient.php @@ -0,0 +1,877 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @copyright Nicolas Tallefourtane http://nicolab.net + */ +namespace FtpClient; + +use \Countable; + +/** + * The FTP and SSL-FTP client for PHP. + * + * @method bool alloc() alloc(int $filesize, string &$result = null) Allocates space for a file to be uploaded + * @method bool cdup() cdup() Changes to the parent directory + * @method bool chdir() chdir(string $directory) Changes the current directory on a FTP server + * @method int chmod() chmod(int $mode, string $filename) Set permissions on a file via FTP + * @method bool delete() delete(string $path) Deletes a file on the FTP server + * @method bool exec() exec(string $command) Requests execution of a command on the FTP server + * @method bool fget() fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server and saves to an open file + * @method bool fput() fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Uploads from an open file to the FTP server + * @method mixed get_option() get_option(int $option) Retrieves various runtime behaviours of the current FTP stream + * @method bool get() get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server + * @method int mdtm() mdtm(string $remote_file) Returns the last modified time of the given file + * @method int nb_continue() nb_continue() Continues retrieving/sending a file (non-blocking) + * @method int nb_fget() nb_fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to an open file (non-blocking) + * @method int nb_fput() nb_fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Stores a file from an open file to the FTP server (non-blocking) + * @method int nb_get() nb_get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to a local file (non-blocking) + * @method int nb_put() nb_put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Stores a file on the FTP server (non-blocking) + * @method bool pasv() pasv(bool $pasv) Turns passive mode on or off + * @method bool put() put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Uploads a file to the FTP server + * @method string pwd() pwd() Returns the current directory name + * @method bool quit() quit() Closes an FTP connection + * @method array raw() raw(string $command) Sends an arbitrary command to an FTP server + * @method bool rename() rename(string $oldname, string $newname) Renames a file or a directory on the FTP server + * @method bool set_option() set_option(int $option, mixed $value) Set miscellaneous runtime FTP options + * @method bool site() site(string $command) Sends a SITE command to the server + * @method int size() size(string $remote_file) Returns the size of the given file + * @method string systype() systype() Returns the system type identifier of the remote FTP server + * + * @author Nicolas Tallefourtane + */ +class FtpClient implements Countable +{ + /** + * The connection with the server. + * + * @var resource + */ + protected $conn; + + /** + * PHP FTP functions wrapper. + * + * @var FtpWrapper + */ + private $ftp; + + /** + * Constructor. + * + * @param resource|null $connection + * @throws FtpException If FTP extension is not loaded. + */ + public function __construct($connection = null) + { + if (!extension_loaded('ftp')) { + throw new FtpException('FTP extension is not loaded!'); + } + + if ($connection) { + $this->conn = $connection; + } + + $this->setWrapper(new FtpWrapper($this->conn)); + } + + /** + * Close the connection when the object is destroyed. + */ + public function __destruct() + { + if ($this->conn) { + $this->ftp->close(); + } + } + + /** + * Call an internal method or a FTP method handled by the wrapper. + * + * Wrap the FTP PHP functions to call as method of FtpClient object. + * The connection is automaticaly passed to the FTP PHP functions. + * + * @param string $method + * @param array $arguments + * @return mixed + * @throws FtpException When the function is not valid + */ + public function __call($method, array $arguments) + { + return $this->ftp->__call($method, $arguments); + } + + /** + * Overwrites the PHP limit + * + * @param string|null $memory The memory limit, if null is not modified + * @param int $time_limit The max execution time, unlimited by default + * @param bool $ignore_user_abort Ignore user abort, true by default + * @return FtpClient + */ + public function setPhpLimit($memory = null, $time_limit = 0, $ignore_user_abort = true) + { + if (null !== $memory) { + ini_set('memory_limit', $memory); + } + + ignore_user_abort(true); + set_time_limit($time_limit); + + return $this; + } + + /** + * Get the help information of the remote FTP server. + * + * @return array + */ + public function help() + { + return $this->ftp->raw('help'); + } + + /** + * Open a FTP connection. + * + * @param string $host + * @param bool $ssl + * @param int $port + * @param int $timeout + * + * @return FTPClient + * @throws FtpException If unable to connect + */ + public function connect($host, $ssl = false, $port = 21, $timeout = 90) + { + if ($ssl) { + $this->conn = @$this->ftp->ssl_connect($host, $port, $timeout); + } else { + $this->conn = @$this->ftp->connect($host, $port, $timeout); + } + + if (!$this->conn) { + throw new FtpException('Unable to connect'); + } + + return $this; + } + + /** + * Closes the current FTP connection. + * + * @return bool + */ + public function close() + { + if ($this->conn) { + $this->ftp->close(); + $this->conn = null; + } + } + + /** + * Get the connection with the server. + * + * @return resource + */ + public function getConnection() + { + return $this->conn; + } + + /** + * Get the wrapper. + * + * @return FtpWrapper + */ + public function getWrapper() + { + return $this->ftp; + } + + /** + * Logs in to an FTP connection. + * + * @param string $username + * @param string $password + * + * @return FtpClient + * @throws FtpException If the login is incorrect + */ + public function login($username = 'anonymous', $password = '') + { + $result = $this->ftp->login($username, $password); + + if ($result === false) { + throw new FtpException('Login incorrect'); + } + + return $this; + } + + /** + * Returns the last modified time of the given file. + * Return -1 on error + * + * @param string $remoteFile + * @param string|null $format + * + * @return int + */ + public function modifiedTime($remoteFile, $format = null) + { + $time = $this->ftp->mdtm($remoteFile); + + if ($time !== -1 && $format !== null) { + return date($format, $time); + } + + return $time; + } + + /** + * Changes to the parent directory. + * + * @throws FtpException + * @return FtpClient + */ + public function up() + { + $result = @$this->ftp->cdup(); + + if ($result === false) { + throw new FtpException('Unable to get parent folder'); + } + + return $this; + } + + /** + * Returns a list of files in the given directory. + * + * @param string $directory The directory, by default is "." the current directory + * @param bool $recursive + * @param callable $filter A callable to filter the result, by default is asort() PHP function. + * The result is passed in array argument, + * must take the argument by reference ! + * The callable should proceed with the reference array + * because is the behavior of several PHP sorting + * functions (by reference ensure directly the compatibility + * with all PHP sorting functions). + * + * @return array + * @throws FtpException If unable to list the directory + */ + public function nlist($directory = '.', $recursive = false, $filter = 'sort') + { + if (!$this->isDir($directory)) { + throw new FtpException('"'.$directory.'" is not a directory'); + } + + $files = $this->ftp->nlist($directory); + + if ($files === false) { + throw new FtpException('Unable to list directory'); + } + + $result = array(); + $dir_len = strlen($directory); + + // if it's the current + if (false !== ($kdot = array_search('.', $files))) { + unset($files[$kdot]); + } + + // if it's the parent + if(false !== ($kdot = array_search('..', $files))) { + unset($files[$kdot]); + } + + if (!$recursive) { + foreach ($files as $file) { + $result[] = $directory.'/'.$file; + } + + // working with the reference (behavior of several PHP sorting functions) + $filter($result); + + return $result; + } + + // utils for recursion + $flatten = function (array $arr) use (&$flatten) { + + $flat = []; + + foreach ($arr as $k => $v) { + if (is_array($v)) { + $flat = array_merge($flat, $flatten($v)); + } else { + $flat[] = $v; + } + } + + return $flat; + }; + + foreach ($files as $file) { + $file = $directory.'/'.$file; + + // if contains the root path (behavior of the recursivity) + if (0 === strpos($file, $directory, $dir_len)) { + $file = substr($file, $dir_len); + } + + if ($this->isDir($file)) { + $result[] = $file; + $items = $flatten($this->nlist($file, true, $filter)); + + foreach ($items as $item) { + $result[] = $item; + } + + } else { + $result[] = $file; + } + } + + $result = array_unique($result); + + $filter($result); + + return $result; + } + + /** + * Creates a directory. + * + * @see FtpClient::rmdir() + * @see FtpClient::remove() + * @see FtpClient::put() + * @see FtpClient::putAll() + * + * @param string $directory The directory + * @param bool $recursive + * @return array + */ + public function mkdir($directory, $recursive = false) + { + if (!$recursive or $this->isDir($directory)) { + return $this->ftp->mkdir($directory); + } + + $result = false; + $pwd = $this->ftp->pwd(); + $parts = explode('/', $directory); + + foreach ($parts as $part) { + if ($part == '') { + continue; + } + + if (!@$this->ftp->chdir($part)) { + $result = $this->ftp->mkdir($part); + $this->ftp->chdir($part); + } + } + + $this->ftp->chdir($pwd); + + return $result; + } + + /** + * Remove a directory. + * + * @see FtpClient::mkdir() + * @see FtpClient::cleanDir() + * @see FtpClient::remove() + * @see FtpClient::delete() + * @param string $directory + * @param bool $recursive Forces deletion if the directory is not empty + * @return bool + * @throws FtpException If unable to list the directory to remove + */ + public function rmdir($directory, $recursive = true) + { + if ($recursive) { + $files = $this->nlist($directory, false, 'rsort'); + + // remove children + foreach ($files as $file) { + $this->remove($file, true); + } + } + + // remove the directory + return $this->ftp->rmdir($directory); + } + + /** + * Empty directory. + * + * @see FtpClient::remove() + * @see FtpClient::delete() + * @see FtpClient::rmdir() + * + * @param string $directory + * @return bool + */ + public function cleanDir($directory) + { + if(!$files = $this->nlist($directory)) { + return $this->isEmpty($directory); + } + + // remove children + foreach ($files as $file) { + $this->remove($file, true); + } + + return $this->isEmpty($directory); + } + + /** + * Remove a file or a directory. + * + * @see FtpClient::rmdir() + * @see FtpClient::cleanDir() + * @see FtpClient::delete() + * @param string $path The path of the file or directory to remove + * @param bool $recursive Is effective only if $path is a directory, {@see FtpClient::rmdir()} + * @return bool + */ + public function remove($path, $recursive = false) + { + try { + if (@$this->ftp->delete($path) + or ($this->isDir($path) and @$this->rmdir($path, $recursive))) { + return true; + } + + return false; + } catch (\Exception $e) { + return false; + } + } + + /** + * Check if a directory exist. + * + * @param string $directory + * @return bool + * @throws FtpException + */ + public function isDir($directory) + { + $pwd = $this->ftp->pwd(); + + if ($pwd === false) { + throw new FtpException('Unable to resolve the current directory'); + } + + if (@$this->ftp->chdir($directory)) { + $this->ftp->chdir($pwd); + return true; + } + + $this->ftp->chdir($pwd); + + return false; + } + + /** + * Check if a directory is empty. + * + * @param string $directory + * @return bool + */ + public function isEmpty($directory) + { + return $this->count($directory, null, false) === 0 ? true : false; + } + + /** + * Scan a directory and returns the details of each item. + * + * @see FtpClient::nlist() + * @see FtpClient::rawlist() + * @see FtpClient::parseRawList() + * @see FtpClient::dirSize() + * @param string $directory + * @param bool $recursive + * @return array + */ + public function scanDir($directory = '.', $recursive = false) + { + return $this->parseRawList($this->rawlist($directory, $recursive)); + } + + /** + * Returns the total size of the given directory in bytes. + * + * @param string $directory The directory, by default is the current directory. + * @param bool $recursive true by default + * @return int The size in bytes. + */ + public function dirSize($directory = '.', $recursive = true) + { + $items = $this->scanDir($directory, $recursive); + $size = 0; + + foreach ($items as $item) { + $size += (int) $item['size']; + } + + return $size; + } + + /** + * Count the items (file, directory, link, unknown). + * + * @param string $directory The directory, by default is the current directory. + * @param string|null $type The type of item to count (file, directory, link, unknown) + * @param bool $recursive true by default + * @return int + */ + public function count($directory = '.', $type = null, $recursive = true) + { + $items = (null === $type ? $this->nlist($directory, $recursive) + : $this->scanDir($directory, $recursive)); + + $count = 0; + foreach ($items as $item) { + if (null === $type or $item['type'] == $type) { + $count++; + } + } + + return $count; + } + + /** + * Uploads a file to the server from a string. + * + * @param string $remote_file + * @param string $content + * @return FtpClient + * @throws FtpException When the transfer fails + */ + public function putFromString($remote_file, $content) + { + $handle = fopen('php://temp', 'w'); + + fwrite($handle, $content); + rewind($handle); + + if ($this->ftp->fput($remote_file, $handle, FTP_BINARY)) { + return $this; + } + + throw new FtpException('Unable to put the file "'.$remote_file.'"'); + } + + /** + * Uploads a file to the server. + * + * @param string $local_file + * @return FtpClient + * @throws FtpException When the transfer fails + */ + public function putFromPath($local_file) + { + $remote_file = basename($local_file); + $handle = fopen($local_file, 'r'); + + if ($this->ftp->fput($remote_file, $handle, FTP_BINARY)) { + rewind($handle); + return $this; + } + + throw new FtpException( + 'Unable to put the remote file from the local file "'.$local_file.'"' + ); + } + + /** + * Upload files. + * + * @param string $source_directory + * @param string $target_directory + * @param int $mode + * @return FtpClient + */ + public function putAll($source_directory, $target_directory, $mode = FTP_BINARY) + { + $d = dir($source_directory); + + // do this for each file in the directory + while ($file = $d->read()) { + + // to prevent an infinite loop + if ($file != "." && $file != "..") { + + // do the following if it is a directory + if (is_dir($source_directory.'/'.$file)) { + + if (!$this->isDir($target_directory.'/'.$file)) { + + // create directories that do not yet exist + $this->ftp->mkdir($target_directory.'/'.$file); + } + + // recursive part + $this->putAll( + $source_directory.'/'.$file, $target_directory.'/'.$file, + $mode + ); + } else { + + // put the files + $this->ftp->put( + $target_directory.'/'.$file, $source_directory.'/'.$file, + $mode + ); + } + } + } + + return $this; + } + + /** + * Returns a detailed list of files in the given directory. + * + * @see FtpClient::nlist() + * @see FtpClient::scanDir() + * @see FtpClient::dirSize() + * @param string $directory The directory, by default is the current directory + * @param bool $recursive + * @return array + * @throws FtpException + */ + public function rawlist($directory = '.', $recursive = false) + { + if (!$this->isDir($directory)) { + throw new FtpException('"'.$directory.'" is not a directory.'); + } + + $list = $this->ftp->rawlist($directory); + $items = array(); + + if (!$list) { + return $items; + } + + if (false == $recursive) { + + foreach ($list as $path => $item) { + $chunks = preg_split("/\s+/", $item); + + // if not "name" + if (empty($chunks[8]) || $chunks[8] == '.' || $chunks[8] == '..') { + continue; + } + + $path = $directory.'/'.$chunks[8]; + + if (isset($chunks[9])) { + $nbChunks = count($chunks); + + for ($i = 9; $i < $nbChunks; $i++) { + $path .= ' '.$chunks[$i]; + } + } + + + if (substr($path, 0, 2) == './') { + $path = substr($path, 2); + } + + $items[ $this->rawToType($item).'#'.$path ] = $item; + } + + return $items; + } + + $path = ''; + + foreach ($list as $item) { + $len = strlen($item); + + if (!$len + + // "." + || ($item[$len-1] == '.' && $item[$len-2] == ' ' + + // ".." + or $item[$len-1] == '.' && $item[$len-2] == '.' && $item[$len-3] == ' ') + ){ + + continue; + } + + $chunks = preg_split("/\s+/", $item); + + // if not "name" + if (empty($chunks[8]) || $chunks[8] == '.' || $chunks[8] == '..') { + continue; + } + + $path = $directory.'/'.$chunks[8]; + + if (isset($chunks[9])) { + $nbChunks = count($chunks); + + for ($i = 9; $i < $nbChunks; $i++) { + $path .= ' '.$chunks[$i]; + } + } + + if (substr($path, 0, 2) == './') { + $path = substr($path, 2); + } + + $items[$this->rawToType($item).'#'.$path] = $item; + + if ($item[0] == 'd') { + $sublist = $this->rawlist($path, true); + + foreach ($sublist as $subpath => $subitem) { + $items[$subpath] = $subitem; + } + } + } + + return $items; + } + + /** + * Parse raw list. + * + * @see FtpClient::rawlist() + * @see FtpClient::scanDir() + * @see FtpClient::dirSize() + * @param array $rawlist + * @return array + */ + public function parseRawList(array $rawlist) + { + $items = array(); + $path = ''; + + foreach ($rawlist as $key => $child) { + $chunks = preg_split("/\s+/", $child); + + if (isset($chunks[8]) && ($chunks[8] == '.' or $chunks[8] == '..')) { + continue; + } + + if (count($chunks) === 1) { + $len = strlen($chunks[0]); + + if ($len && $chunks[0][$len-1] == ':') { + $path = substr($chunks[0], 0, -1); + } + + continue; + } + + $item = [ + 'permissions' => $chunks[0], + 'number' => $chunks[1], + 'owner' => $chunks[2], + 'group' => $chunks[3], + 'size' => $chunks[4], + 'month' => $chunks[5], + 'day' => $chunks[6], + 'time' => $chunks[7], + 'name' => $chunks[8], + 'type' => $this->rawToType($chunks[0]), + ]; + + if ($item['type'] == 'link') { + $item['target'] = $chunks[10]; // 9 is "->" + } + + // if the key is not the path, behavior of ftp_rawlist() PHP function + if (is_int($key) || false === strpos($key, $item['name'])) { + array_splice($chunks, 0, 8); + + $key = $item['type'].'#' + .($path ? $path.'/' : '') + .implode(" ", $chunks); + + if ($item['type'] == 'link') { + + // get the first part of 'link#the-link.ext -> /path/of/the/source.ext' + $exp = explode(' ->', $key); + $key = rtrim($exp[0]); + } + + $items[$key] = $item; + + } else { + + // the key is the path, behavior of FtpClient::rawlist() method() + $items[$key] = $item; + } + } + + return $items; + } + + /** + * Convert raw info (drwx---r-x ...) to type (file, directory, link, unknown). + * Only the first char is used for resolving. + * + * @param string $permission Example : drwx---r-x + * + * @return string The file type (file, directory, link, unknown) + * @throws FtpException + */ + public function rawToType($permission) + { + if (!is_string($permission)) { + throw new FtpException('The "$permission" argument must be a string, "' + .gettype($permission).'" given.'); + } + + if (empty($permission[0])) { + return 'unknown'; + } + + switch ($permission[0]) { + case '-': + return 'file'; + + case 'd': + return 'directory'; + + case 'l': + return 'link'; + + default: + return 'unknown'; + } + } + + /** + * Set the wrapper which forward the PHP FTP functions to use in FtpClient instance. + * + * @param FtpWrapper $wrapper + * @return FtpClient + */ + protected function setWrapper(FtpWrapper $wrapper) + { + $this->ftp = $wrapper; + + return $this; + } +} diff --git a/library/FTP/FtpException.php b/library/FTP/FtpException.php new file mode 100644 index 00000000..f17ed7f8 --- /dev/null +++ b/library/FTP/FtpException.php @@ -0,0 +1,20 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @copyright Nicolas Tallefourtane http://nicolab.net + */ +namespace FtpClient; + +/** + * The FtpException class. + * Exception thrown if an error on runtime of the FTP client occurs. + * @inheritDoc + * @author Nicolas Tallefourtane + */ +class FtpException extends \Exception {} diff --git a/library/FTP/FtpWrapper.php b/library/FTP/FtpWrapper.php new file mode 100644 index 00000000..cd12de03 --- /dev/null +++ b/library/FTP/FtpWrapper.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + * + * @copyright Nicolas Tallefourtane http://nicolab.net + */ +namespace FtpClient; + +/** + * Wrap the PHP FTP functions + * + * @method bool alloc() alloc(int $filesize, string &$result = null) Allocates space for a file to be uploaded + * @method bool cdup() cdup() Changes to the parent directory + * @method bool chdir() chdir(string $directory) Changes the current directory on a FTP server + * @method int chmod() chmod(int $mode, string $filename) Set permissions on a file via FTP + * @method bool close() close() Closes an FTP connection + * @method bool delete() delete(string $path) Deletes a file on the FTP server + * @method bool exec() exec(string $command) Requests execution of a command on the FTP server + * @method bool fget() fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server and saves to an open file + * @method bool fput() fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Uploads from an open file to the FTP server + * @method mixed get_option() get_option(int $option) Retrieves various runtime behaviours of the current FTP stream + * @method bool get() get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Downloads a file from the FTP server + * @method bool login() login(string $username, string $password) Logs in to an FTP connection + * @method int mdtm() mdtm(string $remote_file) Returns the last modified time of the given file + * @method string mkdir() mkdir(string $directory) Creates a directory + * @method int nb_continue() nb_continue() Continues retrieving/sending a file (non-blocking) + * @method int nb_fget() nb_fget(resource $handle, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to an open file (non-blocking) + * @method int nb_fput() nb_fput(string $remote_file, resource $handle, int $mode, int $startpos = 0) Stores a file from an open file to the FTP server (non-blocking) + * @method int nb_get() nb_get(string $local_file, string $remote_file, int $mode, int $resumepos = 0) Retrieves a file from the FTP server and writes it to a local file (non-blocking) + * @method int nb_put() nb_put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Stores a file on the FTP server (non-blocking) + * @method array nlist() nlist(string $directory) Returns a list of files in the given directory + * @method bool pasv() pasv(bool $pasv) Turns passive mode on or off + * @method bool put() put(string $remote_file, string $local_file, int $mode, int $startpos = 0) Uploads a file to the FTP server + * @method string pwd() pwd() Returns the current directory name + * @method bool quit() quit() Closes an FTP connection + * @method array raw() raw(string $command) Sends an arbitrary command to an FTP server + * @method array rawlist() rawlist(string $directory, bool $recursive = false) Returns a detailed list of files in the given directory + * @method bool rename() rename(string $oldname, string $newname) Renames a file or a directory on the FTP server + * @method bool rmdir() rmdir(string $directory) Removes a directory + * @method bool set_option() set_option(int $option, mixed $value) Set miscellaneous runtime FTP options + * @method bool site() site(string $command) Sends a SITE command to the server + * @method int size() size(string $remote_file) Returns the size of the given file + * @method string systype() systype() Returns the system type identifier of the remote FTP server + * + * @author Nicolas Tallefourtane + */ +class FtpWrapper +{ + /** + * The connection with the server + * + * @var resource + */ + protected $conn; + + /** + * Constructor. + * + * @param resource &$connection The FTP (or SSL-FTP) connection (takes by reference). + */ + public function __construct(&$connection) + { + $this->conn = &$connection; + } + + /** + * Forward the method call to FTP functions + * + * @param string $function + * @param array $arguments + * @return mixed + * @throws FtpException When the function is not valid + */ + public function __call($function, array $arguments) + { + $function = 'ftp_' . $function; + + if (function_exists($function)) { + array_unshift($arguments, $this->conn); + return call_user_func_array($function, $arguments); + } + + throw new FtpException("{$function} is not a valid FTP function"); + } + + /** + * Opens a FTP connection + * + * @param string $host + * @param int $port + * @param int $timeout + * @return resource + */ + public function connect($host, $port = 21, $timeout = 90) + { + return ftp_connect($host, $port, $timeout); + } + + /** + * Opens a Secure SSL-FTP connection + * @param string $host + * @param int $port + * @param int $timeout + * @return resource + */ + public function ssl_connect($host, $port = 21, $timeout = 90) + { + return ftp_ssl_connect($host, $port, $timeout); + } +} diff --git a/library/FTP/ftp.php b/library/FTP/ftp.php deleted file mode 100644 index a3afd580..00000000 --- a/library/FTP/ftp.php +++ /dev/null @@ -1,466 +0,0 @@ - - * - * Everyone is permitted to copy and distribute verbatim or modified - * copies of this license document, and changing it is allowed as long - * as the name is changed. - * - * DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE - * TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - * - * 0. You just DO WHAT THE FUCK YOU WANT TO. - */ - -// ------------------------------------------------------------------------ - -/** - * Ftp class for recursive upload and download files via ftp protocol - * - * @author Kristian Feldsam - iKFSystems, info@ikfsystems.sk - * @web http://www.ikfsystems.sk - */ - -class ftp { - private $conn, $login_result, $logData, $ftpUser, $ftpPass, $ftpHost, $retry, $ftpPasv, $ftpMode, $verbose, $logPath, $createMask; - - // -------------------------------------------------------------------- - - /** - * Construct method - * - * @param array keys[passive_mode(true|false)|transfer_mode(FTP_ASCII|FTP_BINARY)|reattempts(int)|log_path|verbose(true|false)|create_mask(default:0777)] - * @return void - */ - function __construct($o) - { - $this->retry = (isset($o['reattempts'])) ? $o['reattempts'] : 3; - $this->ftpPasv = (isset($o['passive_mode'])) ? $o['passive_mode'] : true; - $this->ftpMode = (isset($o['transfer_mode'])) ? $o['transfer_mode'] : FTP_BINARY; - $this->verbose = (isset($o['verbose'])) ? $o['verbose'] : false; - $this->logPath = (isset($o['log_path'])) ? $o['log_path'] : false; - $this->createMask = (isset($o['create_mask'])) ? $o['create_mask'] : 0777; - } - - // -------------------------------------------------------------------- - - /** - * Connection method - * - * @param string hostname - * @param string username - * @param string password - * @return void - */ - public function conn($hostname, $username, $password) - { - $this->ftpUser = $username; - $this->ftpPass = $password; - $this->ftpHost = $hostname; - - $this->initConn(); - } - - // -------------------------------------------------------------------- - - /** - * Init connection method - connect to ftp server and set passive mode - * - * @return bool - */ - function initConn() - { - $this->conn = ftp_connect($this->ftpHost); - $this->login_result = ftp_login($this->conn, $this->ftpUser, $this->ftpPass); - if($this->conn && $this->login_result) - { - ftp_pasv($this->conn, $this->ftpPasv); - return true; - } - return false; - } - - // -------------------------------------------------------------------- - - /** - * Put method - upload files(folders) to ftp server - * - * @param string path to destionation file/folder on ftp - * @param string path to source file/folder on local disk - * @param int only for identify reattempt, dont use this param - * @return bool - */ - public function put($destinationFile, $sourceFile, $retry = 0) - { - if(file_exists($sourceFile)) - { - if(!$this->isDir($sourceFile, true)) - { - $this->createSubDirs($destinationFile); - if(!ftp_put($this->conn, $destinationFile, $sourceFile, $this->ftpMode)) - { - $retry++; - if($retry > $this->retry) - { - $this->logData('Error when uploading file: '.$sourceFile.' => '.$destinationFile, 'error'); - return false; - } - if($this->verbose) echo 'Retry: '.$retry."\n"; - $this->reconnect(); - $this->put($destinationFile, $sourceFile, $retry); - } - else - { - $this->logData($sourceFile.' => '.$destinationFile, 'ok'); - return true; - } - } - else - { - $this->recursive($destinationFile, $sourceFile, 'put'); - } - } - } - - // -------------------------------------------------------------------- - - /** - * Get method - download files(folders) from ftp server - * - * @param string path to destionation file/folder on local disk - * @param string path to source file/folder on ftp server - * @param int only for identify reattempt, dont use this param - * @return bool - */ - public function get($destinationFile, $sourceFile, $retry = 0) - { - if(!$this->isDir($sourceFile, false)) - { - if($this->verbose)echo $sourceFile.' => '.$destinationFile."\n"; - $this->createSubDirs($destinationFile, false, true); - if(!ftp_get($this->conn, $destinationFile, $sourceFile, $this->ftpMode)) - { - $retry++; - if($retry > $this->retry) - { - $this->logData('Error when downloading file: '.$sourceFile.' => '.$destinationFile, 'error'); - return false; - } - if($this->verbose) echo 'Retry: '.$retry."\n"; - $this->reconnect(); - $this->get($destinationFile, $sourceFile, $retry); - } - else - { - $this->logData($sourceFile.' => '.$destinationFile, 'ok'); - return true; - } - } - else - { - $this->recursive($destinationFile, $sourceFile, 'get'); - } - } - - // -------------------------------------------------------------------- - - /** - * Make dir method - make folder on ftp server or local disk - * - * @param string path to destionation folder on ftp or local disk - * @param bool true for local, false for ftp - * @return bool - */ - public function makeDir($dir, $local = false) - { - if($local) - { - if(!file_exists($dir) && !is_dir($dir))return mkdir($dir, $this->createMask); else return true; - } - else - { - ftp_mkdir($this->conn,$dir); - return ftp_chmod($this->conn, $this->createMask, $dir); - } - } - - // -------------------------------------------------------------------- - - /** - * Cd up method - change working dir up - * - * @param bool true for local, false for ftp - * @return bool - */ - public function cdUp($local) - { - return $local ? chdir('..') : ftp_cdup($this->conn); - } - - // -------------------------------------------------------------------- - - /** - * List contents of dir method - list all files in specified directory - * - * @param string path to destionation folder on ftp or local disk - * @param bool true for local, false for ftp - * @return bool - */ - public function listFiles($file, $local = false) - { - if(!$this->isDir($file, $local))return false; - if($local) - { - return scandir($file); - } - else - { - if(!preg_match('/\//', $file)) - { - return ftp_nlist($this->conn, $file); - }else - { - $dirs = explode('/', $file); - foreach($dirs as $dir) - { - $this->changeDir($dir, $local); - } - $last = count($dirs)-1; - $this->cdUp($local); - $list = ftp_nlist($this->conn, $dirs[$last]); - $i = 0; - foreach($dirs as $dir) - { - if($i < $last) $this->cdUp($local); - $i++; - } - return $list; - } - } - } - - // -------------------------------------------------------------------- - - /** - * Returns current working directory - * - * @param bool true for local, false for ftp - * @return bool - */ - public function pwd($local = false) - { - return $local ? getcwd() : ftp_pwd($this->conn); - } - - // -------------------------------------------------------------------- - - /** - * Change current working directory - * - * @param string dir name - * @param bool true for local, false for ftp - * @return bool - */ - public function changeDir($dir, $local = false) - { - return $local ? chdir($dir) : @ftp_chdir($this->conn, $dir); - } - - // -------------------------------------------------------------------- - - /** - * Create subdirectories - * - * @param string path - * @param bool - * @param bool true for local, false for ftp - * @param bool change current working directory back - * @return void - */ - function createSubDirs($file, $last = false, $local = false, $chDirBack = true) - { - if(preg_match('/\//',$file)) - { - $origin = $this->pwd($local); - if(!$last) $file = substr($file, 0, strrpos($file,'/')); - $dirs = explode('/',$file); - foreach($dirs as $dir) - { - if(!$this->isDir($dir, $local)) - { - $this->makeDir($dir, $local); - $this->changeDir($dir, $local); - } - else - { - $this->changeDir($dir, $local); - } - } - if($chDirBack) $this->changeDir($origin, $local); - } - } - - // -------------------------------------------------------------------- - - /** - * Recursion - * - * @param string destionation file/folder - * @param string source file/folder - * @param string put or get - * @return void - */ - function recursive($destinationFile, $sourceFile, $mode) - { - $local = ($mode == 'put') ? true : false; - $list = $this->listFiles($sourceFile, $local); - if($this->verbose) echo "\n".'Folder: '.$sourceFile."\n"; - if($this->verbose) print_r($list); - $x=0; - $z=0; - foreach($list as $file) - { - if($file == '.' || $file == '..')continue; - $destFile = $destinationFile.'/'.$file; - $srcFile = $sourceFile.'/'.$file; - if($this->isDir($srcFile,$local)) - { - $this->recursive($destFile, $srcFile, $mode); - } - else - { - if($local) - { - $this->put($destFile, $srcFile); - } - else - { - $this->get($destFile, $srcFile); - } - } - } - } - - // -------------------------------------------------------------------- - - /** - * Check if is dir - * - * @param string path to folder - * @return bool - */ - public function isDir($dir, $local) - { - if($local) return is_dir($dir); - if($this->changeDir($dir))return $this->cdUp(0); - return false; - } - - // -------------------------------------------------------------------- - - /** - * Save log data to array - * - * @param string data - * @param string type(error|ok) - * @return void - */ - function logData($data, $type) - { - $this->logData[$type][] = $data; - } - - // -------------------------------------------------------------------- - - /** - * Get log data array - * - * @return array - */ - public function getLogData() - { - return $this->logData; - } - - // -------------------------------------------------------------------- - - /** - * Save log data to file - * - * @return void - */ - public function logDataToFiles() - { - if(!$this->logPath) return false; - $ftp->makeDir($this->logPath, true); - $log = $ftp->getLogData(); - $sep = "\n".date('y-m-d H-i-s').' '; - $logc = date('y-m-d H-i-s').' '.join($sep,$log['error'])."\n"; - $this->addToFile($this->logPath.'/'.$config->name.'-error.log',$logc); - $logc = date('y-m-d H-i-s').' '.join($sep,$log['ok'])."\n"; - $this->addToFile($this->logPath.'/'.$config->name.'-ok.log',$logc); - } - - // -------------------------------------------------------------------- - - /** - * Reconnect method - * - * @return void - */ - public function reconnect() - { - $this->closeConn(); - $this->initConn(); - } - - // -------------------------------------------------------------------- - - /** - * Close connection method - * - * @return void - */ - public function closeConn() - { - return ftp_close($this->conn); - } - - // -------------------------------------------------------------------- - - /** - * Write to file - * - * @param string path to file - * @param string text - * @param string fopen mode - * @return void - */ - function addToFile($file, $ins, $mode = 'a') - { - $fp = fopen($file, $mode); - fwrite($fp,$ins); - fclose($fp); - } - - // -------------------------------------------------------------------- - - /** - * Destruct method - close connection and save log data to file - * - * @return void - */ - function __destruct() - { - $this->closeConn(); - $this->logDataToFiles(); - } -} - -// END ftp class - -/* End of file ftp.php */ -/* Location: ftp.php */ \ No newline at end of file diff --git a/library/StaticHtmlOutput.php b/library/StaticHtmlOutput.php index aa392831..5a53b4db 100644 --- a/library/StaticHtmlOutput.php +++ b/library/StaticHtmlOutput.php @@ -191,10 +191,6 @@ class StaticHtmlOutput ->assign('exportLog', $this->_exportLog) ->assign('staticExportSettings', $this->_options->getOption('static-export-settings')) /* - ->assign('baseUrl', $this->_options->getOption('baseUrl')) - ->assign('additionalUrls', $this->_options->getOption('additionalUrls')) - ->assign('cleanMeta', $this->_options->getOption('cleanMeta')) - ->assign('retainStaticFiles', $this->_options->getOption('retainStaticFiles')) ->assign('sendViaFTP', $this->_options->getOption('sendViaFTP')) ->assign('ftpServer', $this->_options->getOption('ftpServer')) ->assign('ftpUsername', $this->_options->getOption('ftpUsername')) @@ -232,10 +228,6 @@ class StaticHtmlOutput $this->_options ->setOption('static-export-settings', filter_input(INPUT_POST, 'staticExportSettings', FILTER_SANITIZE_URL)) /* - ->setOption('baseUrl', filter_input(INPUT_POST, 'baseUrl', FILTER_SANITIZE_URL)) - ->setOption('additionalUrls', filter_input(INPUT_POST, 'additionalUrls')) - ->setOption('cleanMeta', filter_input(INPUT_POST, 'cleanMeta')) - ->setOption('retainStaticFiles', filter_input(INPUT_POST, 'retainStaticFiles')) ->setOption('sendViaFTP', filter_input(INPUT_POST, 'sendViaFTP')) ->setOption('ftpServer', filter_input(INPUT_POST, 'ftpServer')) ->setOption('ftpUsername', filter_input(INPUT_POST, 'ftpUsername')) @@ -297,19 +289,13 @@ class StaticHtmlOutput { wp_mkdir_p($archiveDir); } - - // override options with posted vars -/* ->setOption('retainStaticFiles', filter_input(INPUT_POST, 'retainStaticFiles')) -*/ // Prepare queue $baseUrl = untrailingslashit(home_url()); - // TODO: overridden $newBaseUrl = untrailingslashit($this->_options->getOption('baseUrl')); $newBaseUrl = untrailingslashit(filter_input(INPUT_POST, 'baseUrl', FILTER_SANITIZE_URL)); $urlsQueue = array_unique(array_merge( array(trailingslashit($baseUrl)), $this->_getListOfLocalFilesByUrl(array(get_template_directory_uri())), - // TODO: overridden $this->_getListOfLocalFilesByUrl(explode("\n", $this->_options->getOption('additionalUrls'))) $this->_getListOfLocalFilesByUrl(explode("\n", filter_input(INPUT_POST, 'additionalUrls'))) )); @@ -321,7 +307,6 @@ class StaticHtmlOutput //echo "Processing ". $currentUrl."
"; - // TODO: overridden $urlResponse = new StaticHtmlOutput_UrlRequest($currentUrl, $this->_options->getOption('cleanMeta')); $urlResponse = new StaticHtmlOutput_UrlRequest($currentUrl, filter_input(INPUT_POST, 'cleanMeta')); $urlResponse->cleanup(); @@ -371,27 +356,33 @@ class StaticHtmlOutput $zipArchive->close(); rename($tempZip, $archiveName . '.zip'); - - // TODO: overridden if($this->_options->getOption('sendViaFTP') == 1) + if(filter_input(INPUT_POST, 'sendViaFTP') == 1) { //crude FTP addition - require_once(__DIR__.'/FTP/ftp.php'); - $config = array();//keys[passive_mode(true|false)|transfer_mode(FTP_ASCII|FTP_BINARY)|reattempts(int)|log_path|verbose(true|false)|create_mask(default:0777)] - $ftp = new ftp($config); - // TODO: overridden $ftp->conn($this->_options->getOption('ftpServer'), $this->_options->getOption('ftpUsername'), filter_input(INPUT_POST, 'ftpPassword')); - $ftp->conn(filter_input(INPUT_POST, 'ftpServer'), filter_input(INPUT_POST, 'ftpUsername'), filter_input(INPUT_POST, 'ftpRemotePath')); - - //Crude FTP - $ftp->put($this->_options->getOption('ftpRemotePath'), $archiveName . '/'); - + //require_once(__DIR__.'/FTP/ftp.php'); + require_once(__DIR__.'/FTP/FtpClient.php'); + require_once(__DIR__.'/FTP/FtpException.php'); + require_once(__DIR__.'/FTP/FtpWrapper.php'); + + $ftp = new \FtpClient\FtpClient(); + $ftp->connect(filter_input(INPUT_POST, 'ftpServer')); + $ftp->login(filter_input(INPUT_POST, 'ftpUsername'), filter_input(INPUT_POST, 'ftpPassword')); + + // create remote directory before putting + $ftp->mkdir(filter_input(INPUT_POST, 'ftpRemotePath')); + + $ftp->putAll($archiveName . '/', filter_input(INPUT_POST, 'ftpRemotePath')); + + // TODO: check for active FTP connection before trying to send... + error_log('has tried to connect to FTP'); + unset($ftp); } // TODO: keep copy of last export folder for incremental addition // Remove temporary files unless user requested to keep or needed for FTP transfer - // TODO: overridden if ($this->_options->getOption('retainStaticFiles') != 1) if ($this->_options->getOption('retainStaticFiles') != 1) { $iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($archiveDir), RecursiveIteratorIterator::CHILD_FIRST); diff --git a/views/options-page.phtml b/views/options-page.phtml index d7b813d9..711b88aa 100644 --- a/views/options-page.phtml +++ b/views/options-page.phtml @@ -189,8 +189,8 @@ jQuery(document).ready(function($){ $(targetExportSettingsBlock).find('#retainStaticFiles')[0].checked = settingsBlock.retainStaticFiles; $(targetExportSettingsBlock).find('#sendViaFTP')[0].checked = settingsBlock.sendViaFTP; $(targetExportSettingsBlock).find('#ftpServer').first().val(decodeURIComponent(settingsBlock.ftpServer)); - $(targetExportSettingsBlock).find('#ftpUsername').first().val(settingsBlock.ftpUsername); - $(targetExportSettingsBlock).find('#ftpPassword').first().val(settingsBlock.ftpPassword); + $(targetExportSettingsBlock).find('#ftpUsername').first().val(decodeURIComponent(settingsBlock.ftpUsername)); + $(targetExportSettingsBlock).find('#ftpPassword').first().val(decodeURIComponent(settingsBlock.ftpPassword)); $(targetExportSettingsBlock).find('#ftpRemotePath').first().val(decodeURIComponent(settingsBlock.ftpRemotePath)); console.log('total settingsBlocks to render: ' + archives.length);