Created
January 20, 2017 02:25
-
-
Save sifex/e9e1812e6c9d5fe8c1722e483cb66b2d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace League\Flysystem\Adapter; | |
| use ErrorException; | |
| use League\Flysystem\Adapter\Polyfill\StreamedCopyTrait; | |
| use League\Flysystem\AdapterInterface; | |
| use League\Flysystem\Config; | |
| use League\Flysystem\Util; | |
| use League\Flysystem\Util\MimeType; | |
| use RuntimeException; | |
| class Ftp extends AbstractFtpAdapter | |
| { | |
| use StreamedCopyTrait; | |
| /** | |
| * @var int | |
| */ | |
| protected $transferMode = FTP_BINARY; | |
| /** | |
| * @var null|bool | |
| */ | |
| protected $ignorePassiveAddress = null; | |
| /** | |
| * @var bool | |
| */ | |
| protected $recurseManually = false; | |
| /** | |
| * @var array | |
| */ | |
| protected $configurable = [ | |
| 'host', | |
| 'port', | |
| 'username', | |
| 'password', | |
| 'ssl', | |
| 'timeout', | |
| 'root', | |
| 'permPrivate', | |
| 'permPublic', | |
| 'passive', | |
| 'transferMode', | |
| 'systemType', | |
| 'ignorePassiveAddress', | |
| 'recurseManually', | |
| ]; | |
| /** | |
| * @var null|string | |
| */ | |
| protected $ftpServerName = null; | |
| /** | |
| * Set the transfer mode. | |
| * | |
| * @param int $mode | |
| * | |
| * @return $this | |
| */ | |
| public function setTransferMode($mode) | |
| { | |
| $this->transferMode = $mode; | |
| return $this; | |
| } | |
| /** | |
| * Set if Ssl is enabled. | |
| * | |
| * @param bool $ssl | |
| * | |
| * @return $this | |
| */ | |
| public function setSsl($ssl) | |
| { | |
| $this->ssl = (bool) $ssl; | |
| return $this; | |
| } | |
| /** | |
| * Set if passive mode should be used. | |
| * | |
| * @param bool $passive | |
| */ | |
| public function setPassive($passive = true) | |
| { | |
| $this->passive = $passive; | |
| } | |
| /** | |
| * @param bool $ignorePassiveAddress | |
| */ | |
| public function setIgnorePassiveAddress($ignorePassiveAddress) | |
| { | |
| $this->ignorePassiveAddress = $ignorePassiveAddress; | |
| } | |
| /** | |
| * @param bool $recurseManually | |
| */ | |
| public function setRecurseManually($recurseManually) | |
| { | |
| $this->recurseManually = $recurseManually; | |
| } | |
| /** | |
| * Connect to the FTP server. | |
| */ | |
| public function connect() | |
| { | |
| if ($this->ssl) { | |
| $this->connection = ftp_ssl_connect($this->getHost(), $this->getPort(), $this->getTimeout()); | |
| } else { | |
| $this->connection = ftp_connect($this->getHost(), $this->getPort(), $this->getTimeout()); | |
| } | |
| if ( ! $this->connection) { | |
| throw new RuntimeException('Could not connect to host: ' . $this->getHost() . ', port:' . $this->getPort()); | |
| } | |
| $this->login(); | |
| $this->setConnectionPassiveMode(); | |
| $this->setConnectionRoot(); | |
| $this->ftpServerName = $this->detectFtpServerName(); | |
| } | |
| /** | |
| * Set the connections to passive mode. | |
| * | |
| * @throws RuntimeException | |
| */ | |
| protected function setConnectionPassiveMode() | |
| { | |
| if (is_bool($this->ignorePassiveAddress) && defined('FTP_USEPASVADDRESS')) { | |
| ftp_set_option($this->connection, FTP_USEPASVADDRESS, ! $this->ignorePassiveAddress); | |
| } | |
| if ( ! ftp_pasv($this->connection, $this->passive)) { | |
| throw new RuntimeException( | |
| 'Could not set passive mode for connection: ' . $this->getHost() . '::' . $this->getPort() | |
| ); | |
| } | |
| } | |
| /** | |
| * Set the connection root. | |
| */ | |
| protected function setConnectionRoot() | |
| { | |
| $root = $this->getRoot(); | |
| $connection = $this->connection; | |
| if (empty($root) === false && ! ftp_chdir($connection, $root)) { | |
| throw new RuntimeException('Root is invalid or does not exist: ' . $this->getRoot()); | |
| } | |
| // Store absolute path for further reference. | |
| // This is needed when creating directories and | |
| // initial root was a relative path, else the root | |
| // would be relative to the chdir'd path. | |
| $this->root = ftp_pwd($connection); | |
| } | |
| /** | |
| * Login. | |
| * | |
| * @throws RuntimeException | |
| */ | |
| protected function login() | |
| { | |
| set_error_handler( | |
| function () { | |
| } | |
| ); | |
| $isLoggedIn = ftp_login($this->connection, $this->getUsername(), $this->getPassword()); | |
| restore_error_handler(); | |
| if ( ! $isLoggedIn) { | |
| $this->disconnect(); | |
| throw new RuntimeException( | |
| 'Could not login with connection: ' . $this->getHost() . '::' . $this->getPort( | |
| ) . ', username: ' . $this->getUsername() | |
| ); | |
| } | |
| } | |
| /** | |
| * Disconnect from the FTP server. | |
| */ | |
| public function disconnect() | |
| { | |
| if ($this->isConnected()) { | |
| ftp_close($this->connection); | |
| } | |
| $this->connection = null; | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function write($path, $contents, Config $config) | |
| { | |
| $stream = fopen('php://temp', 'w+b'); | |
| fwrite($stream, $contents); | |
| rewind($stream); | |
| $result = $this->writeStream($path, $stream, $config); | |
| fclose($stream); | |
| if ($result === false) { | |
| return false; | |
| } | |
| $result['contents'] = $contents; | |
| $result['mimetype'] = Util::guessMimeType($path, $contents); | |
| return $result; | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function writeStream($path, $resource, Config $config) | |
| { | |
| $this->ensureDirectory(Util::dirname($path)); | |
| if ( ! ftp_fput($this->getConnection(), $path, $resource, $this->transferMode)) { | |
| return false; | |
| } | |
| if ($visibility = $config->get('visibility')) { | |
| $this->setVisibility($path, $visibility); | |
| } | |
| return compact('path', 'visibility'); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function update($path, $contents, Config $config) | |
| { | |
| return $this->write($path, $contents, $config); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function updateStream($path, $resource, Config $config) | |
| { | |
| return $this->writeStream($path, $resource, $config); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function rename($path, $newpath) | |
| { | |
| return ftp_rename($this->getConnection(), $path, $newpath); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function delete($path) | |
| { | |
| return ftp_delete($this->getConnection(), $path); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function deleteDir($dirname) | |
| { | |
| $connection = $this->getConnection(); | |
| $contents = array_reverse($this->listDirectoryContents($dirname)); | |
| foreach ($contents as $object) { | |
| if ($object['type'] === 'file') { | |
| if ( ! ftp_delete($connection, $object['path'])) { | |
| return false; | |
| } | |
| } elseif ( ! ftp_rmdir($connection, $object['path'])) { | |
| return false; | |
| } | |
| } | |
| return ftp_rmdir($connection, $dirname); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function createDir($dirname, Config $config) | |
| { | |
| $connection = $this->getConnection(); | |
| $directories = explode('/', $dirname); | |
| foreach ($directories as $directory) { | |
| if (false === $this->createActualDirectory($directory, $connection)) { | |
| $this->setConnectionRoot(); | |
| return false; | |
| } | |
| ftp_chdir($connection, $directory); | |
| } | |
| $this->setConnectionRoot(); | |
| return ['path' => $dirname]; | |
| } | |
| /** | |
| * Create a directory. | |
| * | |
| * @param string $directory | |
| * @param resource $connection | |
| * | |
| * @return bool | |
| */ | |
| protected function createActualDirectory($directory, $connection) | |
| { | |
| // List the current directory | |
| $listing = ftp_nlist($connection, '.') ?: []; | |
| foreach ($listing as $key => $item) { | |
| if (preg_match('~^\./.*~', $item)) { | |
| $listing[$key] = substr($item, 2); | |
| } | |
| } | |
| if (in_array($directory, $listing)) { | |
| return true; | |
| } | |
| return (boolean) ftp_mkdir($connection, $directory); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function getMetadata($path) | |
| { | |
| $connection = $this->getConnection(); | |
| if ($path === '') { | |
| return ['type' => 'dir', 'path' => '']; | |
| } | |
| if (@ftp_chdir($connection, $path) === true) { | |
| $this->setConnectionRoot(); | |
| return ['type' => 'dir', 'path' => $path]; | |
| } | |
| $listing = $this->ftp_rawlist('-A', str_replace('*', '\\*', $path)); | |
| if (empty($listing)) { | |
| return false; | |
| } | |
| if (preg_match('/.* not found/', $listing[0])) { | |
| return false; | |
| } | |
| if (preg_match('/^total [0-9]*$/', $listing[0])) { | |
| array_shift($listing); | |
| } | |
| return $this->normalizeObject($listing[0], ''); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function getMimetype($path) | |
| { | |
| if ( ! $metadata = $this->getMetadata($path)) { | |
| return false; | |
| } | |
| $metadata['mimetype'] = MimeType::detectByFilename($path); | |
| return $metadata; | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function getTimestamp($path) | |
| { | |
| $timestamp = ftp_mdtm($this->getConnection(), $path); | |
| return ($timestamp !== -1) ? ['timestamp' => $timestamp] : false; | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function read($path) | |
| { | |
| if ( ! $object = $this->readStream($path)) { | |
| return false; | |
| } | |
| $object['contents'] = stream_get_contents($object['stream']); | |
| fclose($object['stream']); | |
| unset($object['stream']); | |
| return $object; | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function readStream($path) | |
| { | |
| $stream = fopen('php://temp', 'w+b'); | |
| $result = ftp_fget($this->getConnection(), $stream, $path, $this->transferMode); | |
| rewind($stream); | |
| if ( ! $result) { | |
| fclose($stream); | |
| return false; | |
| } | |
| return compact('stream'); | |
| } | |
| /** | |
| * @inheritdoc | |
| */ | |
| public function setVisibility($path, $visibility) | |
| { | |
| $mode = $visibility === AdapterInterface::VISIBILITY_PUBLIC ? $this->getPermPublic() : $this->getPermPrivate(); | |
| if ( ! ftp_chmod($this->getConnection(), $mode, $path)) { | |
| return false; | |
| } | |
| return compact('visibility'); | |
| } | |
| /** | |
| * @inheritdoc | |
| * | |
| * @param string $directory | |
| */ | |
| protected function listDirectoryContents($directory, $recursive = true) | |
| { | |
| $directory = str_replace('*', '\\*', $directory); | |
| if ($recursive && $this->recurseManually) { | |
| return $this->listDirectoryContentsRecursive($directory); | |
| } | |
| $options = $recursive ? '-alnR' : '-aln'; | |
| $listing = $this->ftp_rawlist($options, $directory); | |
| return $listing ? $this->normalizeListing($listing, $directory) : []; | |
| } | |
| /** | |
| * @inheritdoc | |
| * | |
| * @param string $directory | |
| */ | |
| protected function listDirectoryContentsRecursive($directory) | |
| { | |
| $listing = $this->normalizeListing($this->ftp_rawlist('-aln', $directory) ?: []); | |
| $output = []; | |
| foreach ($listing as $directory) { | |
| $output[] = $directory; | |
| if ($directory['type'] !== 'dir') continue; | |
| $output = array_merge($output, $this->listDirectoryContentsRecursive($directory['path'])); | |
| } | |
| return $output; | |
| } | |
| /** | |
| * Check if the connection is open. | |
| * | |
| * @return bool | |
| * @throws ErrorException | |
| */ | |
| public function isConnected() | |
| { | |
| try { | |
| return is_resource($this->connection) && ftp_rawlist($this->connection, '/') !== false; | |
| } catch (ErrorException $e) { | |
| fclose($this->connection); | |
| $this->connection = null; | |
| if (strpos($e->getMessage(), 'ftp_rawlist') === false) { | |
| throw $e; | |
| } | |
| return false; | |
| } | |
| } | |
| protected function detectFtpServerName() | |
| { | |
| $response = ftp_raw($this->getConnection(), 'HELP'); | |
| if (!$response) { | |
| return null; | |
| } | |
| $response = implode("\n", $response); | |
| if (stripos($response, 'Pure-FTPd')) { | |
| return 'Pure-FTPd'; | |
| } | |
| return null; | |
| } | |
| protected function ftp_rawlist($options, $path) | |
| { | |
| if ($this->ftpServerName == 'Pure-FTPd') { | |
| $path = str_replace(' ', '\ ', $path); | |
| } | |
| return ftp_rawlist($this->getConnection(), $options . ' ' . $path); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment