: str_replace(): Passing null to parameter #2 ($replace) of type array|string is deprecated in
* Simple elFinder driver for SFTP using phpseclib 1
* @author Dmitry (dio) Levashov
* @author Cem (discofever), sitecode
* @reference http://phpseclib.sourceforge.net/sftp/2.0/examples.html
class elFinderVolumeSFTPphpseclib extends elFinderVolumeFTP {
* Extend options with required fields
* @author Dmitry (dio) Levashov
* @author Cem (DiscoFever)
public function __construct()
'phpseclibDir' => '../phpseclib/',
'connectCallback' => null, //provide your own already instantiated phpseclib $Sftp object returned by this callback
//'connectCallback'=> function($options) {
// //load and instantiate phpseclib $sftp
'rootCssClass' => 'elfinder-navbar-root-ftp',
$this->options = array_merge($this->options, $opts);
$this->options['mimeDetect'] = 'internal';
* Call from elFinder::netmout() before volume->mount()
* @return array volume root options
public function netmountPrepare($options)
$options['statOwner'] = true;
$options['allowChmodReadOnly'] = true;
$options['acceptedName'] = '#^[^/\\?*:|"<>]*[^./\\?*:|"<>]$#';
/*********************************************************************/
/*********************************************************************/
* Prepare SFTP connection
* Connect to remote server and check if credentials are correct, if so, store the connection
* @author Dmitry (dio) Levashov
* @author Cem (DiscoFever)
protected function init()
if (!$this->options['connectCallback']) {
if (!$this->options['host']
|| !$this->options['port']) {
return $this->setError('Required options undefined.');
if (!$this->options['path']) {
$this->options['path'] = '/';
$this->netMountKey = md5(join('-', array('sftpphpseclib', $this->options['host'], $this->options['port'], $this->options['path'], $this->options['user'])));
set_include_path(get_include_path() . PATH_SEPARATOR . getcwd().'/'.$this->options['phpseclibDir']);
include_once('Net/SFTP.php');
if (!class_exists('Net_SFTP')) {
return $this->setError('SFTP extension not loaded. Install phpseclib version 1: http://phpseclib.sourceforge.net/ Set option "phpseclibDir" accordingly.');
// remove protocol from host
$scheme = parse_url($this->options['host'], PHP_URL_SCHEME);
$this->options['host'] = substr($this->options['host'], strlen($scheme) + 3);
$this->netMountKey = md5(join('-', array('sftpphpseclib', $this->options['path'])));
$this->root = $this->options['path'] = $this->_normpath($this->options['path']);
if (empty($this->options['alias'])) {
$this->options['alias'] = $this->options['user'] . '@' . $this->options['host'];
if (!empty($this->options['netkey'])) {
elFinder::$instance->updateNetVolumeOption($this->options['netkey'], 'alias', $this->options['alias']);
$this->rootName = $this->options['alias'];
$this->options['separator'] = '/';
if (is_null($this->options['syncChkAsTs'])) {
$this->options['syncChkAsTs'] = true;
return $this->needOnline? $this->connect() : true;
* Configure after successfull mount.
* @throws elFinderAbortException
* @author Dmitry (dio) Levashov
protected function configure()
$this->disabled[] = 'mkfile';
$this->disabled[] = 'paste';
$this->disabled[] = 'upload';
$this->disabled[] = 'edit';
//$this->disabled[] = 'archive';
//$this->disabled[] = 'extract';
$this->disabled[] = 'archive';
$this->disabled[] = 'extract';
protected function connect()
if ($this->options['connectCallback']) {
$this->connect = $this->options['connectCallback']($this->options);
if (!$this->connect || !$this->connect->isConnected()) {
return $this->setError('Unable to connect successfully');
$host = $this->options['host'] . ($this->options['port'] != 22 ? ':' . $this->options['port'] : '');
$this->connect = new Net_SFTP($host);
//TODO check fingerprint before login, fail if no match to last time
if (!$this->connect->login($this->options['user'], $this->options['pass'])) {
return $this->setError('Unable to connect to SFTP server ' . $host);
return $this->setError('Error while connecting to SFTP server ' . $host . ': ' . $e->getMessage());
if (!$this->connect->chdir($this->root)
/*|| $this->root != $this->connect->pwd()*/) {
return $this->setError('Unable to open root folder.');
protected function ftpRawList($path)
return $this->connect->rawlist($path ?: '.') ?: [];
/*********************************************************************/
/*********************************************************************/
* Close opened connection
* @author Dmitry (dio) Levashov
$this->connect && $this->connect->disconnect();
* Parse line from rawlist() output and return file stat (array)
* @param array $info from rawlist() output
* @author Dmitry Levashov
protected function parseRaw($info, $base, $nameOnly = false)
if ($info['filename'] == '.' || $info['filename'] == '..') {
$name = $info['filename'];
if ($info['type'] === 3) {
// check recursive processing
if ($this->cacheDirTarget && $this->_joinPath($base, $name) !== $this->cacheDirTarget) {
$target = $this->connect->readlink($name);
if (substr($target, 0, 1) !== $this->separator) {
$target = $this->getFullPath($target, $base);
$target = $this->_normpath($target);
$stat['target'] = $target;
return array('name' => $name);
$stat['ts'] = $info['mtime'];
if ($this->options['statOwner']) {
$stat['owner'] = $info['uid'];
$stat['group'] = $info['gid'];
$stat['perm'] = $info['permissions'];
$stat['isowner'] = isset($stat['owner']) ? ($this->options['owner'] ? true : ($stat['owner'] == $this->options['user'])) : true;
$owner_computed = isset($stat['isowner']) ? $stat['isowner'] : $this->options['owner'];
$perm = $this->parsePermissions($info['permissions'], $owner_computed);
if ($info['type'] === NET_SFTP_TYPE_DIRECTORY) {
$stat['mime'] = 'directory';
} elseif ($info['type'] === NET_SFTP_TYPE_SYMLINK) {
$stat['mime'] = 'symlink';
$stat['mime'] = $this->mimetype($stat['name'], true);
$stat['size'] = $info['size'];
$stat['read'] = $perm['read'];
$stat['write'] = $perm['write'];
* Parse permissions string. Return array(read => true/false, write => true/false)
* The isowner parameter is computed by the caller.
* If the owner parameter in the options is true, the user is the actual owner of all objects even if the user used in the ftp Login
* is different from the file owner id.
* If the owner parameter is false to understand if the user is the file owner we compare the ftp user with the file owner id.
* @param Boolean $isowner . Tell if the current user is the owner of the object.
* @author Dmitry (dio) Levashov
protected function parsePermissions($permissions, $isowner = true)
$permissions = decoct($permissions);
$perm = $isowner ? decbin($permissions[-3]) : decbin($permissions[-1]);
* @param string $path dir path
* @author Dmitry Levashov, sitecode
protected function cacheDir($path)
$this->dirsCache[$path] = array();
$encPath = $this->convEncIn($path);
foreach ($this->ftpRawList($encPath) as $info) {
if (($stat = $this->parseRaw($info, $encPath))) {
$list = $this->convEncOut($list);
$prefix = ($path === $this->separator) ? $this->separator : $path . $this->separator;
foreach ($list as $stat) {
$p = $prefix . $stat['name'];
if (isset($stat['target'])) {
$targets[$stat['name']] = $stat['target'];
$stat = $this->updateCache($p, $stat);
if (empty($stat['hidden'])) {
if (!$hasDir && $stat['mime'] === 'directory') {
} elseif (!$hasDir && $stat['mime'] === 'symlink') {
$this->dirsCache[$path][] = $p;
foreach ($targets as $name => $target) {
$cacheDirTarget = $this->cacheDirTarget;
$this->cacheDirTarget = $this->convEncIn($target, true);
if ($tstat = $this->stat($target)) {
$stat['size'] = $tstat['size'];
$stat['alias'] = $target;
$stat['thash'] = $tstat['hash'];
$stat['mime'] = $tstat['mime'];
$stat['read'] = $tstat['read'];
$stat['write'] = $tstat['write'];
if (isset($tstat['ts'])) {
$stat['ts'] = $tstat['ts'];
if (isset($tstat['owner'])) {
$stat['owner'] = $tstat['owner'];
if (isset($tstat['group'])) {
$stat['group'] = $tstat['group'];
if (isset($tstat['perm'])) {
$stat['perm'] = $tstat['perm'];
if (isset($tstat['isowner'])) {
$stat['isowner'] = $tstat['isowner'];
$stat['mime'] = 'symlink-broken';
$this->cacheDirTarget = $cacheDirTarget;
$stat = $this->updateCache($p, $stat);
if (empty($stat['hidden'])) {
if (!$hasDir && $stat['mime'] === 'directory') {
$this->dirsCache[$path][] = $p;
if (isset($this->sessionCache['subdirs'])) {
$this->sessionCache['subdirs'][$path] = $hasDir;
/***************** file stat ********************/
* Return stat for given path.
* Stat contains following fields:
* - (int) size file size in b. required
* - (int) ts file modification time in unix time. required
* - (string) mime mimetype. required for folders, others - optionally
* - (bool) read read permissions. required
* - (bool) write write permissions. required
* - (bool) locked is object locked. optionally
* - (bool) hidden is object hidden. optionally
* - (string) alias for symlinks - link target path relative to root path. optionally
* - (string) target for symlinks - link target path. optionally
* If file does not exists - returns empty array or false.
* @param string $path file path
* @author Dmitry (dio) Levashov
protected function _stat($path)
$outPath = $this->convEncOut($path);
if (isset($this->cache[$outPath])) {
return $this->convEncIn($this->cache[$outPath]);
if ($path === $this->root) {
if ($this->needOnline && (($this->ARGS['cmd'] === 'open' && $this->ARGS['target'] === $this->encode($this->root)) || $this->isMyReload())) {
foreach ($this->ftpRawList($path) as $info) {
if ($info['filename'] === '.') {
$info['filename'] = 'root';
if ($stat = $this->parseRaw($info, $path)) {
$res = array_merge($res, $stat);
if ($check && ($stat = $this->parseRaw($info, $path))) {
if (isset($stat['ts']) && !empty($stat['ts'])) {
$ts = max($ts, $stat['ts']);
if (isset($stat['dirs']) && $stat['mime'] === 'directory') {
$this->cache[$outPath] = $res;
$pPath = $this->_dirname($path);
if ($this->_inPath($pPath, $this->root)) {
$outPPpath = $this->convEncOut($pPath);
if (!isset($this->dirsCache[$outPPpath])) {
if (isset($this->sessionCache['subdirs']) && isset($this->sessionCache['subdirs'][$outPPpath])) {
$parentSubdirs = $this->sessionCache['subdirs'][$outPPpath];
$this->cacheDir($outPPpath);
$this->sessionCache['subdirs'][$outPPpath] = $parentSubdirs;