src/Access/Local.php
<?php
// Copyright (c) 2016, Avan.Tech, et. al.
// Copyright (c) 2008, Luis Argerich, Garland Foster, Eduardo Polidor, et. al.
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
namespace TikiManager\Access;
use Symfony\Component\Filesystem\Exception\IOException;
use Symfony\Component\Filesystem\Filesystem;
use TikiManager\Application\Tiki\Versions\TikiRequirements;
use TikiManager\Command\Helper\CommandHelper;
use TikiManager\Config\Environment as Env;
use TikiManager\Libs\Host\Local as LocalHost;
use TikiManager\Libs\Host\Command;
use TikiManager\Application\Instance;
use TikiManager\Libs\Helpers\ApplicationHelper;
class Local extends Access implements ShellPrompt
{
private $location;
private $env = [];
private $hostlib = null;
private $changeLocation = null;
public function __construct(Instance $instance)
{
parent::__construct($instance, 'local');
$this->setenv('HTTP_ACCEPT_ENCODING', '');
}
private function isLocalPath($pathname)
{
if ($pathname[0] == '/') {
return true;
}
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN' && preg_match("|^[a-zA-Z]:[/\\\\]|", $pathname)) {
return true;
}
return false;
}
public function getHost()
{
if (!(is_object($this->hostlib) && $this->hostlib instanceof LocalHost)) {
$this->hostlib = new LocalHost();
}
$host = $this->hostlib;
$cwd = $this->instance->webroot;
// change cwd before executing commands, for instance in CoreOS it may influence what
// php interpreter version is used to execute commands, if the dir is not available
// try the parent directory
if ($this->changeLocation === null && !empty($cwd)) {
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
if (chdir($cwd)) {
$this->changeLocation = $cwd;
} elseif (chdir(dirname($cwd))) {
$this->changeLocation = dirname($cwd);
}
} else {
if ($this->fileExists($cwd)) {
$this->changeLocation = $cwd;
} elseif ($this->fileExists(dirname($cwd))) {
$this->changeLocation = dirname($cwd);
}
if ($this->changeLocation === null) {
$this->changeLocation = false;
}
}
}
if ($this->changeLocation) {
$host->chdir($this->changeLocation);
}
return $host;
}
public function firstConnect()
{
return true;
}
/**
* @param TikiRequirements|null $requirements
* @return false|mixed
* @throws \Exception When no PHP interpreter was found in the system
*/
public function getInterpreterPath(TikiRequirements $requirements = null)
{
$instance = $this->instance;
$detectedBinaries = $instance->phpexec ? [$instance->phpexec] : $instance->getDiscovery()->detectPHP();
$valid = [];
foreach ($detectedBinaries as $binary) {
try {
$version = $this->getInterpreterVersion($binary);
} catch (\Exception $e) {
continue;
}
$formattedVersion = CommandHelper::formatPhpVersion($version);
if (($version >= 50300 && !$requirements) || ($requirements && $requirements->getPhpVersion()->isValidVersion($formattedVersion))) {
$valid[$formattedVersion] = $binary;
}
}
if (count($valid) == 1) {
return reset($valid);
}
// Instance current PHPExec no longer valid, re-detect again!
if ($instance->phpexec) {
$instance->phpexec = null;
return $this->getInterpreterPath($requirements);
}
if (empty($valid)) {
throw new \Exception("No suitable php interpreter was found on {$instance->name} instance");
}
// Assume that the first in the list should be the default one;
$defaultVersion = key($valid);
// List available options for user
krsort($valid);
$question = 'Multiple PHP interpreters available on host, which version do you want to use?';
$options = array_keys($valid);
$pickedVersion = $this->io->choice($question, $options, $defaultVersion);
return $valid[$pickedVersion];
}
public function getSVNPath()
{
$host = $this->getHost();
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$svn_name='svn.exe';
$sets = [
['where svn'],
];
} else {
$svn_name='svn';
$sets = [
['which svn'],
];
}
foreach ($sets as $attempt) {
// Get possible paths
$svns = $host->runCommands($attempt);
$svns = explode("\n", $svns);
// Check different versions
$valid = [];
foreach ($svns as $interpreter) {
if (! in_array(basename($interpreter), [$svn_name])) {
continue;
}
if (strpos($interpreter, ' ') !== false) {
$interpreter = '"' . trim($interpreter) . '"'; // wrap command if contains spaces
}
$versionInfo = $host->runCommands("$interpreter --version");
if (preg_match("/svn, version (\d+\.\d+\.\d+)/", $versionInfo, $matches)) {
$valid[$matches[1]] = $interpreter;
}
}
// Handle easy cases
if (count($valid) == 0) {
continue;
}
if (count($valid) == 1) {
return reset($valid);
}
// List available options for user
$this->io->writeln("Multiple SVN'es available on host :");
$counter = 0;
krsort($valid);
$versions = array_keys($valid);
foreach ($valid as $version => $path) {
$this->io->writeln("[$counter] $path ($version)");
$counter++;
}
// Ask user
$counter--;
$selection = -1;
while (! array_key_exists($selection, $versions)) {
$selection = $this->io->ask("Which version do you want to use? (0-$counter) : ");
}
$version = $versions[$selection];
return $valid[$version];
}
}
public function getInterpreterVersion($interpreter)
{
return $this->instance->getDiscovery()->detectPHPVersion($interpreter);
}
public function getDistributionName($interpreter)
{
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
return "Windows";
}
$host = $this->getHost();
$command = file_get_contents(
sprintf('%s/../getlinuxdistro.php', dirname(__FILE__))
);
$linuxName = $host->runCommands("$interpreter -r '$command'");
return $linuxName;
}
public function createDirectory($path)
{
$fs = new Filesystem();
try {
$fs->mkdir($path);
} catch (IOException $e) {
return false;
}
return true;
}
public function fileExists($filename)
{
if (!$this->isLocalPath($filename)) {
$filename = $this->instance->getWebPath($filename);
}
return file_exists($filename);
}
public function fileGetContents($filename)
{
if (!$this->isLocalPath($filename)) {
$filename = $this->instance->getWebPath($filename);
}
return @file_get_contents($filename);
}
public function fileModificationDate($filename)
{
if (!$this->isLocalPath($filename)) {
$filename = $this->instance->getWebPath($filename);
}
return date("Y-m-d", filemtime($filename));
}
public function runPHP($localFile, $args = [])
{
$host = $this->getHost();
$remoteName = md5($localFile);
$remoteFile = $this->instance->getWorkPath($remoteName);
$host->sendFile($localFile, $remoteFile);
$arg = implode(' ', array_map('escapeshellarg', $args));
$output = $host->runCommands("{$this->instance->phpexec} -d memory_limit=256M {$remoteFile} {$arg}");
if (file_exists($remoteFile)) {
unlink($remoteFile);
}
return $output;
}
/**
* @param $filename
* @param string $target
* @return string
*/
public function downloadFile($filename, $target = ''): string
{
if (!$this->isLocalPath($filename)) {
$filename = $this->instance->getWebPath($filename);
}
$dot = strrpos($filename, '.');
$ext = substr($filename, $dot);
$tempFolder = Env::get('TEMP_FOLDER');
$local = $target ?: tempnam($tempFolder, 'trim');
$this->getHost()->receiveFile($filename, $local);
if (!$target) {
$target = $local . $ext;
rename($local, $target);
chmod($target, 0644);
}
return $target;
}
public function uploadFile($filename, $remoteLocation)
{
$host = $this->getHost();
if ($this->isLocalPath($remoteLocation)) {
$host->sendFile($filename, $remoteLocation);
} else {
$host->sendFile($filename, $this->instance->getWebPath($remoteLocation));
}
}
public function deleteFile($filename)
{
preg_match('/^([a-zA-Z]\:[\/,\\\\]).{1,}/', $filename, $matches);
if ($filename[0] != '/' && empty($matches)) {
$filename = $this->instance->getWebPath($filename);
}
if (file_exists($filename)) {
@unlink($filename);
}
}
public function moveFile($remoteSource, $remoteTarget)
{
if (!$this->isLocalPath($remoteSource)) {
$remoteSource = $this->instance->getWebPath($remoteSource);
}
if (!$this->isLocalPath($remoteTarget)) {
$remoteTarget = $this->instance->getWebPath($remoteTarget);
}
rename($remoteSource, $remoteTarget);
}
public function copyFile($remoteSource, $remoteTarget)
{
if (!$this->isLocalPath($remoteSource)) {
$remoteSource = $this->instance->getWebPath($remoteSource);
}
if (!$this->isLocalPath($remoteTarget)) {
$remoteTarget = $this->instance->getWebPath($remoteTarget);
}
copy($remoteSource, $remoteTarget);
}
public function chdir($location)
{
$this->location = $location;
}
public function setenv($var, $value)
{
$this->env[$var] = $value;
}
public function shellExec($commands, $output = false)
{
if (! is_array($commands)) {
$commands = func_get_args();
}
$host = $this->getHost();
if ($this->location) {
$host->chdir($this->location);
}
foreach ($this->env as $key => $value) {
$host->setenv($key, $value);
}
return $host->runCommands($commands, $output);
}
public function createCommand($bin, $args = [], $stdin = '')
{
$options = [];
if ($this->location) {
$options['cwd'] = $this->location;
}
if ($this->env) {
$options['env'] = $this->env;
}
$command = new Command($bin, $args, $stdin);
$command->setOptions($options);
$command->setHost($this->getHost());
return $command;
}
public function runCommand($command, $options = [])
{
$host = $this->getHost();
if ($this->location) {
$options['cwd'] = $this->location;
}
if ($this->env) {
$options['env'] = $this->env;
}
return $command->run($host, $options);
}
public function openShell($workingDir = '')
{
$host = $this->getHost();
return $host->openShell($workingDir);
}
public function hasExecutable($command)
{
$command = escapeshellcmd($command);
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
$exists = $this->shellExec("where $command");
} else {
$exists = $this->shellExec("which $command");
}
return ! empty($exists);
}
public function localizeFolder($remoteLocation, $localMirror)
{
$host = $this->getHost();
if (ApplicationHelper::isWindows()) {
$localMirror .= DIRECTORY_SEPARATOR . basename($remoteLocation);
$result = $host->windowsSync(
$remoteLocation,
$localMirror,
null,
['.svn/tmp']
);
return $result > 8 ? $result : 0;
} else {
if (! file_exists($remoteLocation)) {
// certain storage folders might not exist, so we have nothing to sync
return 0;
} else {
return $host->rsync([
'src' => $remoteLocation,
'dest' => $localMirror,
'link-dest' => dirname($remoteLocation)
]);
}
}
}
public function isEmptyDir($path): bool
{
$dirContents = scandir($path);
$dirContents = array_diff($dirContents, ['.','..']);
return empty($dirContents);
}
}