PHP Funktion zum Verzeichnisabgleich via FTP/SFTP/FTPS mit lftp
PHP function to synchronise via FTP/SFTP/FTPS using lftp
The source shown here is a wrapper for the shell program lftp
. It can be included
to use the function lftp_sync()
, or it can be executed directly from the command line.
Context: LFTP is a powerful FTP tool to use for automated synchronisation. The wrapper
script here makes it even a bit easier to use.
Dieser Quelltext stellt eine Wrapper-Funktion für das Shellproramm lftp
zur Verfügung.
In eine Skriptdatei gepackt kann er entweder in andere PHP-Skripte eingebunden werden,
oder die Skriptdatei selbst kann in der Shell ausgeführt werden. Dabei vereinfacht sie
die Verwendung von lftp
nochmals.
Function source code
Funktion
<?
/**
* Synchronises a directory structure via FTP/FTPS/SFTP using lftp.
*
* - $from and $to are the local/remote folders. Depending on the protocol
* specification the script detects if it is remote-to-local or vice versa.
* E.g. `lftp_sync("/home/me", "ftps://my.domain/remote/folder");`
*
* - $exclude and $include are arrays of regex patterns
* E.g. array('/\.git/', '/\.hg/', '/\.svn/', '\.DS_Store$', 'Desktop\.ini$', 'Thumbs\.db$');
*
* - $delete_orphanded_files=true: Delete files in the destination that do not
* exist in the source folder/location.
*
* - $verbose=true: verbose ouput (ftps tells what it is doing)
*
* - $dont_fetch_output=false: Output is not fetched in the return variabls,
* but passed through to stdout directly.
*
* @param string $from
* @param string $to
* @param array $exclude=array()
* @param array $include=array()
* @param bool $delete_orphanded_files=true
* @param bool $verbose=true
* @param bool $dont_fetch_output=false
* @return array
* @throws \Exception
* @package de.atwillys.php.fn.net
*/
function lftp_sync($from, $to, $exclude=array(), $include=array(), $delete_orphanded_files=true,
$verbose=true, $dont_fetch_output=false)
{
$fiddle = function($what) {
if(empty($what)) return array();
$return = array('proto' => '', 'user' => '', 'pass' => '', 'host' => '');
if(!preg_match('#^(ftp|ftps|sftp)://(.*)$#smix', $what)) {
return array('local-dir' => rtrim($what, '/') . '/');
}
list($return['proto'], $what) = explode('://', $what, 2);
if(!preg_match("#^(.*?)@([^@/\']+)[:]?[\']?(/.*)$#smix", $what, $m)) return array();
array_shift($m);
$return['remote-dir'] = rtrim(trim(array_pop($m), "'"), '/') . '/';
$return['host'] = trim(array_pop($m),"'");
$m = empty($m) ? '' : explode(':', array_pop($m), 2);
$return['user'] = trim(array_shift($m), "'");
$return['pass'] = empty($m) ? '' : trim(array_shift($m), "'");
return $return;
};
$from = $fiddle($from);
$to = $fiddle($to);
if(empty($from)) {
throw new \Exception('Sync "from" is not specified correctly.');
} else if(empty($to)) {
throw new \Exception('Sync "to" is not specified correctly.');
} else if(isset($from['host']) && isset($to['host'])) {
throw new \Exception('You cannot sync from remote to remote');
} else if(!isset($from['host']) && !isset($to['host'])) {
throw new \Exception('You cannot sync from local to local');
}
$args = array_merge($from, $to);
$sync_to_remote = isset($to['host']);
$host = escapeshellarg($args['proto'] . '://' . $args['host']);
$user = escapeshellarg($args['user']);
$pass = escapeshellarg($args['pass']);
$remote_folder = escapeshellarg($args['remote-dir']);
$lf = realpath(strpos($lf = $args['local-dir'], '~/') !== 0 || !isset($_SERVER['HOME']) ?
$lf : rtrim($_SERVER['HOME'], '/') . substr($lf, 1));
$local_folder = escapeshellarg(rtrim($lf, '/') . '/');
$from_folder = $sync_to_remote ? $local_folder : $remote_folder;
$to_folder = $sync_to_remote ? $remote_folder : $local_folder;
$incl_excl = '';
foreach($exclude as $e) $incl_excl .= ' --exclude ' . escapeshellarg($e);
foreach($include as $e) $incl_excl .= ' --include ' . escapeshellarg($e);
if(!is_dir($lf)) {
throw new \Exception("Local folder '{$lf}' does not exist.");
}
unset($fiddle, $args, $from, $to);
$mirror = 'mirror'
. ($sync_to_remote ? ' --reverse' : '')
. ($delete_orphanded_files ? ' --delete' : '')
. ($verbose ? ' --verbose' : '')
. "$incl_excl $from_folder $to_folder"
;
$cmd = ''
. "lftp <<EOF 2>&1\n"
. "set net:timeout 10\n"
. "set net:connection-limit 10\n"
. "set net:max-retries 2\n"
. "set bmk:save-passwords false\n"
. "set cmd:fail-exit true\n"
. "set cmd:interactive false\n"
. "set cache:enable false\n"
. "set dns:cache-enable false\n"
. "set ftp:passive-mode true\n"
. "set ftp:ssl-allow true\n"
. "set mirror:parallel-directories false\n"
. "set mirror:set-permissions true\n"
. "set mirror:parallel-transfer-count 2\n"
. "set mirror:dereference false\n"
. "set ssl:verify-certificate no\n"
. "open $host\n"
. "user $user $pass\n"
. "cd $remote_folder\n"
. "lcd $local_folder\n"
. "$mirror\n"
. "bye\n"
. "EOF\n"
;
if(!$dont_fetch_output) @ob_start();
$lastline = trim(array_pop(explode("\r", trim(@system($cmd, $exit), "\t "))), "\t ");
return array(
'command' => str_replace("user $user $pass\n", "user $user <PASSWORD>\n", $cmd), // user might log this
'exit-code' => $exit,
'last-line' => $lastline,
'stdout' => $dont_fetch_output ? '' : @ob_get_clean()
);
}
/**
* Direct command line usage
*/
if(php_sapi_name() == 'cli' && basename($_SERVER['PHP_SELF']) === basename(__FILE__)) {
try {
while(ob_get_level()) ob_get_clean();
$sopts = 'hvd';
$lopts = array('help', 'verbose', 'delete', 'from::', 'to::', 'exclude::', 'include::');
$args = getopt($sopts, $lopts);
$from = isset($args['from']) ? $args['from'] : '';
$to = isset($args['to']) ? $args['to'] : '';
$e = isset($args['exclude']) ? $args['exclude'] : array();
$i = isset($args['include']) ? $args['include'] : array();
$exclude = empty($e) ? array() : (is_array($e) ? $e : array($e));
$include = empty($i) ? array() : (is_array($i) ? $i : array($i));
$delete = isset($args['d']) || isset($args['delete']);
$verbose = isset($args['v']) || isset($args['verbose']);
if(isset($args['h']) || isset($args['help'])) {
$prg = trim(basename($argv[0]), ' /.');
print <<<HERE
Usage: {$prg} [-h|--help] [-v|--verbose] [-d|--delete] --form=<location> --to=<location>
[--exclude=<path> [--exclude=<path>] ..] [--include=<path> [--include=<path>] ..]
-h, --help : Show this usage.
-v, --verbose : High verbosity of lftp.
-f, --delete : Delete orphaned file at the location specified by --to.
--from : Local or remote location.
--from : Local or remote location.
--exclude : Exclude path from sync (see lftp pattnern convention).
Can be specified multiple times.
--include : Re-include excluded path. Can be specified multiple times.
Examples:
{$prg} --from /home/here/ --to ftps://myserver.org/home/there/
{$prg} --from ftps://myserver.org/home/there/ --to /home/here/
{$prg} -vh --from a --to ftp://srv/a --exclude a/b/** --exclude a/c/** \
--include a/c/d
Requirements:
lftp (apt-get install lftp / brew install lftp).
HERE;
exit(1);
}
unset($args);
$r = lftp_sync($from, $to, $exclude, $include, $delete, $verbose, true);
if($r['exit-code'] != 0) {
print("\033[0;31mError: " . $r['last-line'] . "\033[0m\n");
exit($r['exit-code']);
}
} catch(\Exception $e) {
print("\033[0;31mError: " . $e->getMessage() . "\033[0m\n");
exit(1);
}
}