log(LogLevel::DEBUG, __CLASS__ . " :: new instance"); $registry = Factory::getConfiguration(); $this->_host = $registry->get('engine.archiver.directftp.host', ''); $this->_port = $registry->get('engine.archiver.directftp.port', '21'); $this->_user = $registry->get('engine.archiver.directftp.user', ''); $this->_pass = $registry->get('engine.archiver.directftp.pass', ''); $this->_initdir = $registry->get('engine.archiver.directftp.initial_directory', ''); $this->_usessl = $registry->get('engine.archiver.directftp.ftps', false); $this->_passive = $registry->get('engine.archiver.directftp.passive_mode', true); if (isset($options['host'])) { $this->_host = $options['host']; } if (isset($options['port'])) { $this->_port = $options['port']; } if (isset($options['user'])) { $this->_user = $options['user']; } if (isset($options['pass'])) { $this->_pass = $options['pass']; } if (isset($options['initdir'])) { $this->_initdir = $options['initdir']; } if (isset($options['usessl'])) { $this->_usessl = $options['usessl']; } if (isset($options['passive'])) { $this->_passive = $options['passive']; } // You can't fix stupid, but at least you get to shout at them if (strtolower(substr($this->_host, 0, 6)) == 'ftp://') { Factory::getLog()->log(LogLevel::WARNING, 'YOU ARE *** N O T *** SUPPOSED TO ENTER THE ftp:// PROTOCOL PREFIX IN THE FTP HOSTNAME FIELD OF THE DirectFTP ARCHIVER ENGINE.'); Factory::getLog()->log(LogLevel::WARNING, 'I am trying to fix your bad configuration setting, but the backup might fail anyway. You MUST fix this in your configuration.'); $this->_host = substr($this->_host, 6); } $this->connect_ok = $this->_connectFTP(); Factory::getLog()->log(LogLevel::DEBUG, __CLASS__ . " :: FTP connection status: " . ($this->connect_ok ? 'success' : 'FAIL')); } /** * Returns a string with the extension (including the dot) of the files produced * by this class. * * @return string */ public function getExtension() { return ''; } /** * The most basic file transaction: add a single entry (file or directory) to * the archive. * * @param bool $isVirtual If true, the next parameter contains file data instead of a file name * @param string $sourceNameOrData Absolute file name to read data from or the file data itself is $isVirtual is * true * @param string $targetName The (relative) file name under which to store the file in the archive * * @return boolean True on success, false otherwise */ protected function _addFile($isVirtual, &$sourceNameOrData, $targetName) { // Are we connected to a server? if ( !is_resource($this->_ftphandle)) { if ( !$this->_connectFTP()) { return false; } } // See if it's a directory $isDir = $isVirtual ? false : is_dir($sourceNameOrData); if ($isDir) { // Just try to create the remote directory return $this->_makeDirectory($targetName); } else { // We have a file we need to upload if ($isVirtual) { // Create a temporary file, upload, rename it $tempFileName = Factory::getTempFiles()->createRegisterTempFile(); if (function_exists('file_put_contents')) { // Easy writing using file_put_contents if (@file_put_contents($tempFileName, $sourceNameOrData) === false) { $this->setError('Could not store virtual file ' . $targetName . ' to ' . $tempFileName . ' using file_put_contents() before uploading.'); return false; } } else { // The long way, using fopen() and fwrite() $fp = @fopen($tempFileName, 'wb'); if ($fp === false) { $this->setError('Could not store virtual file ' . $targetName . ' to ' . $tempFileName . ' using fopen() before uploading.'); return false; } else { $result = @fwrite($fp, $sourceNameOrData); if ($result === false) { $this->setError('Could not store virtual file ' . $targetName . ' to ' . $tempFileName . ' using fwrite() before uploading.'); return false; } @fclose($fp); } } // Upload the temporary file under the final name $res = $this->_upload($tempFileName, $targetName); // Remove the temporary file Factory::getTempFiles()->unregisterAndDeleteTempFile($tempFileName, true); return $res; } else { // Upload a file return $this->_upload($sourceNameOrData, $targetName); } } } /** * "Magic" function called just before serialization of the object. Disconnects * from the FTP server and allows PHP to serialize like normal. * * @return array The variables to serialize */ public function _onSerialize() { if (is_resource($this->_ftphandle)) { @ftp_close($this->_ftphandle); } $this->_ftphandle = null; $this->_currentdir = null; return array_keys(get_object_vars($this)); } /** * Tries to connect to the remote FTP server and change into the initial directory * * @return bool True is connection successful, false otherwise */ protected function _connectFTP() { Factory::getLog()->log(LogLevel::DEBUG, 'Connecting to remote FTP'); // Connect to the FTP server if ($this->_usessl) { if (function_exists('ftp_ssl_connect')) { $this->_ftphandle = @ftp_ssl_connect($this->_host, $this->_port); } else { $this->_ftphandle = false; } } else { $this->_ftphandle = @ftp_connect($this->_host, $this->_port); } if ($this->_ftphandle === false) { $this->setError("Wrong FTP hostname or port (host:port = {$this->_host}:{$this->_port})"); return false; } // Login if ( !@ftp_login($this->_ftphandle, $this->_user, $this->_pass)) { $this->setError('Invalid username/password for the remote FTP server'); return false; } // Change to initial directory if ( !@ftp_chdir($this->_ftphandle, $this->_initdir)) { $this->setError('Invalid initial directory for the remote FTP server'); return false; } $this->_currentdir = $this->_initdir; @ftp_pasv($this->_ftphandle, $this->_passive); $this->resetErrors(); return true; } /** * Changes to the requested directory in the remote server. You give only the * path relative to the initial directory and it does all the rest by itself, * including doing nothing if the remote directory is the one we want. If the * directory doesn't exist, it creates it. * * @param $dir string The (realtive) remote directory * * @return bool True if successful, false otherwise. */ protected function _ftp_chdir($dir) { // Calculate "real" (absolute) FTP path $realdir = substr($this->_initdir, -1) == '/' ? substr($this->_initdir, 0, strlen($this->_initdir) - 1) : $this->_initdir; $realdir .= '/' . $dir; $realdir = substr($realdir, 0, 1) == '/' ? $realdir : '/' . $realdir; if ($this->_currentdir == $realdir) { // Already there, do nothing return true; } $result = @ftp_chdir($this->_ftphandle, $realdir); if ($result === false) { // The directory doesn't exist, let's try to create it... if ( !$this->_makeDirectory($dir)) { return false; } // After creating it, change into it @ftp_chdir($this->_ftphandle, $realdir); } // Update the private "current remote directory" variable $this->_currentdir = $realdir; return true; } /** * Recursively create a directory in the FTP server * * @param string $dir The directory to create * * @return bool True on success */ protected function _makeDirectory($dir) { $alldirs = explode('/', $dir); $previousDir = substr($this->_initdir, -1) == '/' ? substr($this->_initdir, 0, strlen($this->_initdir) - 1) : $this->_initdir; $previousDir = substr($previousDir, 0, 1) == '/' ? $previousDir : '/' . $previousDir; foreach ($alldirs as $curdir) { $check = $previousDir . '/' . $curdir; if ( !@ftp_chdir($this->_ftphandle, $check)) { if (@ftp_mkdir($this->_ftphandle, $check) === false) { $this->setError('Could not create directory ' . $check); return false; } @ftp_chmod($this->_ftphandle, 0755, $check); } $previousDir = $check; } return true; } /** * Uploads a file to the remote server * * @param $sourceName string The absolute path to the source local file * @param $targetName string The relative path to the targer remote file * * @return bool True if successful */ protected function _upload($sourceName, $targetName) { // Try to change into the remote directory, possibly creating it if it doesn't exist $dir = dirname($targetName); if ( !$this->_ftp_chdir($dir)) { return false; } // Upload $realdir = substr($this->_initdir, -1) == '/' ? substr($this->_initdir, 0, strlen($this->_initdir) - 1) : $this->_initdir; $realdir .= '/' . $dir; $realdir = substr($realdir, 0, 1) == '/' ? $realdir : '/' . $realdir; $realname = $realdir . '/' . basename($targetName); $res = @ftp_put($this->_ftphandle, $realname, $sourceName, FTP_BINARY); if ( !$res) { // If the file was unreadable, just skip it... if (is_readable($sourceName)) { $this->setError('Uploading ' . $targetName . ' has failed.'); return false; } else { $this->setWarning('Uploading ' . $targetName . ' has failed because the file is unreadable.'); return true; } } else { @ftp_chmod($this->_ftphandle, 0755, $realname); return true; } } }