'ftp', 'port' => 21, 'timeout' => 90, 'user' => 'anonymous', 'password' => '', 'key' => '', 'tmp' => 'tests/_data', 'passive' => false, 'cleanup' => true ]; /** * Required configuration fields * * @var array */ protected $requiredFields = ['host']; // ----------- SETUP METHODS BELOW HERE -------------------------// /** * Setup connection and login with config settings * * @param \Codeception\TestInterface $test */ public function _before(TestInterface $test) { // Login using config settings $this->loginAs($this->config['user'], $this->config['password']); } /** * Close the FTP connection & Clear up */ public function _after(TestInterface $test) { $this->_closeConnection(); // Clean up temp files if ($this->config['cleanup']) { if (file_exists($this->config['tmp'] . '/ftp_data_file.tmp')) { unlink($this->config['tmp'] . '/ftp_data_file.tmp'); } } } /** * Change the logged in user mid-way through your test, this closes the * current connection to the server and initialises and new connection. * * On initiation of this modules you are automatically logged into * the server using the specified config options or defaulted * to anonymous user if not provided. * * ``` php * loginAs('user','password'); * ?> * ``` * * @param String $user * @param String $password */ public function loginAs($user = 'anonymous', $password = '') { $this->_openConnection($user, $password); // Create new connection and login. } /** * Enters a directory on the ftp system - FTP root directory is used by default * * @param $path */ public function amInPath($path) { $this->_changeDirectory($this->path = $this->absolutizePath($path) . ($path == '/' ? '' : DIRECTORY_SEPARATOR)); $this->debug('Moved to ' . $this->path); } /** * Resolve path * * @param $path * @return string */ protected function absolutizePath($path) { if (strpos($path, '/') === 0) { return $path; } return $this->path . $path; } // ----------- SEARCH METHODS BELOW HERE ------------------------// /** * Checks if file exists in path on the remote FTP/SFTP system. * DOES NOT OPEN the file when it's exists * * ``` php * seeFileFound('UserModel.php','app/models'); * ?> * ``` * * @param $filename * @param string $path */ public function seeFileFound($filename, $path = '') { $files = $this->grabFileList($path); $this->debug("see file: {$filename}"); $this->assertContains($filename, $files, "file {$filename} not found in {$path}"); } /** * Checks if file exists in path on the remote FTP/SFTP system, using regular expression as filename. * DOES NOT OPEN the file when it's exists * * ``` php * seeFileFoundMatches('/^UserModel_([0-9]{6}).php$/','app/models'); * ?> * ``` * * @param $regex * @param string $path */ public function seeFileFoundMatches($regex, $path = '') { foreach ($this->grabFileList($path) as $filename) { preg_match($regex, $filename, $matches); if (!empty($matches)) { $this->debug("file '{$filename}' matches '{$regex}'"); return; } } $this->fail("no file matches found for '{$regex}'"); } /** * Checks if file does not exist in path on the remote FTP/SFTP system * * @param $filename * @param string $path */ public function dontSeeFileFound($filename, $path = '') { $files = $this->grabFileList($path); $this->debug("don't see file: {$filename}"); $this->assertNotContains($filename, $files); } /** * Checks if file does not exist in path on the remote FTP/SFTP system, using regular expression as filename. * DOES NOT OPEN the file when it's exists * * @param $regex * @param string $path */ public function dontSeeFileFoundMatches($regex, $path = '') { foreach ($this->grabFileList($path) as $filename) { preg_match($regex, $filename, $matches); if (!empty($matches)) { $this->fail("file matches found for {$regex}"); } } $this->assertTrue(true); $this->debug("no files match '{$regex}'"); } // ----------- UTILITY METHODS BELOW HERE -------------------------// /** * Opens a file (downloads from the remote FTP/SFTP system to a tmp directory for processing) * and stores it's content. * * Usage: * * ``` php * openFile('composer.json'); * $I->seeInThisFile('codeception/codeception'); * ?> * ``` * * @param $filename */ public function openFile($filename) { $this->_openFile($this->absolutizePath($filename)); } /** * Saves contents to tmp file and uploads the FTP/SFTP system. * Overwrites current file on server if exists. * * ``` php * writeToFile('composer.json', 'some data here'); * ?> * ``` * * @param $filename * @param $contents */ public function writeToFile($filename, $contents) { $this->_writeToFile($this->absolutizePath($filename), $contents); } /** * Create a directory on the server * * ``` php * makeDir('vendor'); * ?> * ``` * * @param $dirname */ public function makeDir($dirname) { $this->makeDirectory($this->absolutizePath($dirname)); } /** * Currently not supported in this module, overwrite inherited method * * @param $src * @param $dst */ public function copyDir($src, $dst) { $this->fail('copyDir() currently unsupported by FTP module'); } /** * Rename/Move file on the FTP/SFTP server * * ``` php * renameFile('composer.lock', 'composer_old.lock'); * ?> * ``` * * @param $filename * @param $rename */ public function renameFile($filename, $rename) { $this->renameDirectory($this->absolutizePath($filename), $this->absolutizePath($rename)); } /** * Rename/Move directory on the FTP/SFTP server * * ``` php * renameDir('vendor', 'vendor_old'); * ?> * ``` * * @param $dirname * @param $rename */ public function renameDir($dirname, $rename) { $this->renameDirectory($this->absolutizePath($dirname), $this->absolutizePath($rename)); } /** * Deletes a file on the remote FTP/SFTP system * * ``` php * deleteFile('composer.lock'); * ?> * ``` * * @param $filename */ public function deleteFile($filename) { $this->delete($this->absolutizePath($filename)); } /** * Deletes directory with all subdirectories on the remote FTP/SFTP server * * ``` php * deleteDir('vendor'); * ?> * ``` * * @param $dirname */ public function deleteDir($dirname) { $this->delete($this->absolutizePath($dirname)); } /** * Erases directory contents on the FTP/SFTP server * * ``` php * cleanDir('logs'); * ?> * ``` * * @param $dirname */ public function cleanDir($dirname) { $this->clearDirectory($this->absolutizePath($dirname)); } // ----------- GRABBER METHODS BELOW HERE -----------------------// /** * Grabber method for returning file/folders listing in an array * * ```php * grabFileList(); * $count = $I->grabFileList('TEST', false); // Include . .. .thumbs.db * ?> * ``` * * @param string $path * @param bool $ignore - suppress '.', '..' and '.thumbs.db' * @return array */ public function grabFileList($path = '', $ignore = true) { $absolutize_path = $this->absolutizePath($path) . ($path != '' && substr($path, -1) != '/' ? DIRECTORY_SEPARATOR : ''); $files = $this->_listFiles($absolutize_path); $display_files = []; if (is_array($files) && !empty($files)) { $this->debug('File List:'); foreach ($files as &$file) { if (strtolower($file) != '.' && strtolower($file) != '..' && strtolower($file) != 'thumbs.db' ) { // Ignore '.', '..' and 'thumbs.db' // Replace full path from file listings if returned in listing $file = str_replace( $absolutize_path, '', $file ); $display_files[] = $file; $this->debug(' - ' . $file); } } return $ignore ? $display_files : $files; } $this->debug("File List: "); return []; } /** * Grabber method for returning file/folders count in directory * * ```php * grabFileCount(); * $count = $I->grabFileCount('TEST', false); // Include . .. .thumbs.db * ?> * ``` * * @param string $path * @param bool $ignore - suppress '.', '..' and '.thumbs.db' * @return int */ public function grabFileCount($path = '', $ignore = true) { $count = count($this->grabFileList($path, $ignore)); $this->debug("File Count: {$count}"); return $count; } /** * Grabber method to return file size * * ```php * grabFileSize('test.txt'); * ?> * ``` * * @param $filename * @return bool */ public function grabFileSize($filename) { $fileSize = $this->size($filename); $this->debug("{$filename} has a file size of {$fileSize}"); return $fileSize; } /** * Grabber method to return last modified timestamp * * ```php * grabFileModified('test.txt'); * ?> * ``` * * @param $filename * @return bool */ public function grabFileModified($filename) { $time = $this->modified($filename); $this->debug("{$filename} was last modified at {$time}"); return $time; } /** * Grabber method to return current working directory * * ```php * grabDirectory(); * ?> * ``` * * @return string */ public function grabDirectory() { $pwd = $this->_directory(); $this->debug("PWD: {$pwd}"); return $pwd; } // ----------- SERVER CONNECTION METHODS BELOW HERE -------------// /** * Open a new FTP/SFTP connection and authenticate user. * * @param string $user * @param string $password */ private function _openConnection($user = 'anonymous', $password = '') { $this->_closeConnection(); // Close connection if already open if ($this->isSFTP()) { $this->sftpConnect($user, $password); } else { $this->ftpConnect($user, $password); } $pwd = $this->grabDirectory(); $this->path = $pwd . ($pwd == '/' ? '' : DIRECTORY_SEPARATOR); } /** * Close open FTP/SFTP connection */ private function _closeConnection() { if (!$this->ftp) { return; } if (!$this->isSFTP()) { ftp_close($this->ftp); $this->ftp = null; } } /** * Get the file listing for FTP/SFTP connection * * @param String $path * @return array */ private function _listFiles($path) { if ($this->isSFTP()) { $files = @$this->ftp->nlist($path); } else { $files = @ftp_nlist($this->ftp, $path); } if ($files === false) { $this->fail("couldn't list files"); } return $files; } /** * Get the current directory for the FTP/SFTP connection * * @return string */ private function _directory() { if ($this->isSFTP()) { // == DIRECTORY_SEPARATOR ? '' : $pwd; $pwd = @$this->ftp->pwd(); } else { $pwd = @ftp_pwd($this->ftp); } if (!$pwd) { $this->fail("couldn't get current directory"); } } /** * Change the working directory on the FTP/SFTP server * * @param $path */ private function _changeDirectory($path) { if ($this->isSFTP()) { $changed = @$this->ftp->chdir($path); } else { $changed = @ftp_chdir($this->ftp, $path); } if (!$changed) { $this->fail("couldn't change directory {$path}"); } } /** * Download remote file to local tmp directory and open contents. * * @param $filename */ private function _openFile($filename) { // Check local tmp directory if (!is_dir($this->config['tmp']) || !is_writeable($this->config['tmp'])) { $this->fail('tmp directory not found or is not writable'); } // Download file to local tmp directory $tmp_file = $this->config['tmp'] . "/ftp_data_file.tmp"; if ($this->isSFTP()) { $downloaded = @$this->ftp->get($filename, $tmp_file); } else { $downloaded = @ftp_get($this->ftp, $tmp_file, $filename, FTP_BINARY); } if (!$downloaded) { $this->fail('failed to download file to tmp directory'); } // Open file content to variable if ($this->file = file_get_contents($tmp_file)) { $this->filepath = $filename; } else { $this->fail('failed to open tmp file'); } } /** * Write data to local tmp file and upload to server * * @param $filename * @param $contents */ private function _writeToFile($filename, $contents) { // Check local tmp directory if (!is_dir($this->config['tmp']) || !is_writeable($this->config['tmp'])) { $this->fail('tmp directory not found or is not writable'); } // Build temp file $tmp_file = $this->config['tmp'] . "/ftp_data_file.tmp"; file_put_contents($tmp_file, $contents); // Update variables $this->filepath = $tmp_file; $this->file = $contents; // Upload the file to server if ($this->isSFTP()) { $uploaded = @$this->ftp->put($filename, $tmp_file, NET_SFTP_LOCAL_FILE); } else { $uploaded = ftp_put($this->ftp, $filename, $tmp_file, FTP_BINARY); } if (!$uploaded) { $this->fail('failed to upload file to server'); } } /** * Make new directory on server * * @param $path */ private function makeDirectory($path) { if ($this->isSFTP()) { $created = @$this->ftp->mkdir($path, true); } else { $created = @ftp_mkdir($this->ftp, $path); } if (!$created) { $this->fail("couldn't make directory {$path}"); } $this->debug("Make directory: {$path}"); } /** * Rename/Move directory/file on server * * @param $path * @param $rename */ private function renameDirectory($path, $rename) { if ($this->isSFTP()) { $renamed = @$this->ftp->rename($path, $rename); } else { $renamed = @ftp_rename($this->ftp, $path, $rename); } if (!$renamed) { $this->fail("couldn't rename directory {$path} to {$rename}"); } $this->debug("Renamed directory: {$path} to {$rename}"); } /** * Delete file on server * * @param $filename */ private function delete($filename, $isDir = false) { if ($this->isSFTP()) { $deleted = @$this->ftp->delete($filename, $isDir); } else { $deleted = @$this->ftpDelete($filename); } if (!$deleted) { $this->fail("couldn't delete {$filename}"); } $this->debug("Deleted: {$filename}"); } /** * Function to recursively delete folder, used for PHP FTP build in client. * * @param $directory * @return bool */ private function ftpDelete($directory) { // here we attempt to delete the file/directory if (!(@ftp_rmdir($this->ftp, $directory) || @ftp_delete($this->ftp, $directory))) { // if the attempt to delete fails, get the file listing $filelist = @ftp_nlist($this->ftp, $directory); // loop through the file list and recursively delete the FILE in the list foreach ($filelist as $file) { $this->ftpDelete($file); } // if the file list is empty, delete the DIRECTORY we passed $this->ftpDelete($directory); } return true; } /** * Clear directory on server of all content * * @param $path */ private function clearDirectory($path) { $this->debug("Clear directory: {$path}"); $this->delete($path); $this->makeDirectory($path); } /** * Return the size of a given file * * @param $filename * @return bool */ private function size($filename) { if ($this->isSFTP()) { $size = (int)@$this->ftp->size($filename); } else { $size = @ftp_size($this->ftp, $filename); } if ($size > 0) { return $size; } $this->fail("couldn't get the file size for {$filename}"); } /** * Return the last modified time of a given file * * @param $filename * @return bool */ private function modified($filename) { if ($this->isSFTP()) { $info = @$this->ftp->lstat($filename); if ($info) { return $info['mtime']; } } else { if ($time = @ftp_mdtm($this->ftp, $filename)) { return $time; } } $this->fail("couldn't get the file size for {$filename}"); } /** * @param $user * @param $password */ protected function sftpConnect($user, $password) { $this->ftp = new \Net_SFTP($this->config['host'], $this->config['port'], $this->config['timeout']); if ($this->ftp === false) { $this->ftp = null; $this->fail('failed to connect to ftp server'); } if (isset($this->config['key'])) { $keyFile = file_get_contents($this->config['key']); $password = new \Crypt_RSA(); $password->loadKey($keyFile); } if (!$this->ftp->login($user, $password)) { $this->fail('failed to authenticate user'); } } /** * @param $user * @param $password */ protected function ftpConnect($user, $password) { $this->ftp = ftp_connect($this->config['host'], $this->config['port'], $this->config['timeout']); if ($this->ftp === false) { $this->ftp = null; $this->fail('failed to connect to ftp server'); } // Login using given access details if (!@ftp_login($this->ftp, $user, $password)) { $this->fail('failed to authenticate user'); } // Set passive mode option (ftp only option) if (isset($this->config['passive'])) { ftp_pasv($this->ftp, $this->config['passive']); } } protected function isSFTP() { return strtolower($this->config['type']) == 'sftp'; } }