first commit

This commit is contained in:
2024-07-15 11:28:08 +02:00
commit f52d538ea5
21891 changed files with 6161164 additions and 0 deletions

View File

@@ -0,0 +1,237 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check;
use Awf\Container\Container;
use Solo\Alice\Exception\CannotOpenLogfile;
use Solo\Alice\Exception\StopScanningEarly;
use Exception;
use InvalidArgumentException;
/**
* Abstract class for ALICE checks
*
* @since 7.0.0
*/
abstract class Base
{
/**
* Application container
*
* @var Container
*/
protected $container;
/**
* Check priority
*
* @var int
* @since 7.0.0
*/
protected $priority = 0;
/**
* The full path to the log file we are analyzing
*
* @var string
* @since 7.0.0
*/
protected $logFile = null;
/**
* Language key with the description of the check implemented by this class.
*
* @var string
* @since 7.0.0
*/
protected $checkLanguageKey = '';
/**
* Language key and sprintf() parameters for the detected error.
*
* Position 0 of the array is the language string. Positions 1 onwards (optional) are the sprintf() parameters.
*
* @var array
* @since 7.0.0
*/
protected $errorLanguageKey = [];
/**
* Status of the current check.
*
* 1 = success; 0 = warning; -1 = failure
*
* @var int
* @since 7.0.0
*/
protected $result = 1;
/**
* Check constructor
*
* @param Container $container Application container
* @param string $logFile The log file we will be analyzing
*
* @return void
* @since 7.0.0
*/
public function __construct(Container $container, $logFile)
{
$this->container = $container;
$this->logFile = $logFile;
}
/**
* Run a check
*
* @return void
* @throws CannotOpenLogfile If the log file cannot be opened
* @throws Exception If an unhandled error occurs
* @since 7.0.0
*/
abstract public function check();
/**
* Returns the solution that should be applied to fix the issue
*
* @return string Steps required to fixing the issue
* @since 7.0.0
*/
abstract public function getSolution();
/**
* Returns the status of this check.
*
* @return int 1 = success; 0 = warning; -1 = failure
* @since 7.0.0
*/
public function getResult()
{
return $this->result;
}
/**
* Set the result for current check.
*
* @param int $result 1 = success; 0 = warning; -1 = failure
*
* @return void
* @since 7.0.0
*/
public function setResult($result)
{
// Allow only a set of results
if (!in_array($result, [1, 0, -1], true))
{
$result = -1;
}
$this->result = $result;
}
/**
* Gets the priority of this check
*
* @return int
* @since 7.0.0
*/
public function getPriority()
{
return $this->priority;
}
/**
* Returns the language key and any sprintf() parameters for the error detected by this check
*
* @return array Position 0 is the lang key, everything else is the sprintf parameters
* @since 7.0.0
*/
public function getErrorLanguageKey()
{
return $this->errorLanguageKey;
}
/**
* @param array $errorLanguageKey
*
* @since 7.0.0
*/
public function setErrorLanguageKey($errorLanguageKey)
{
if (!is_array($errorLanguageKey))
{
throw new InvalidArgumentException(sprintf(
"Method %s now only accepts an array as its parameter", __METHOD__
));
}
$this->errorLanguageKey = $errorLanguageKey;
}
/**
* Returns the language key with this check's description
*
* @return string
* @since 7.0.0
*/
public function getCheckLanguageKey()
{
return $this->checkLanguageKey;
}
/**
* Runs a scanner callback against all lines of the log file
*
* @param callable $callback The scanner callback to execute on each line of the log file.
*
* @throws Exception If the scanner callback detects an error
*/
protected function scanLines(callable $callback)
{
// Open the log file for reading
$handle = @fopen($this->logFile, 'r');
// Did we fail to open the log file?
if ($handle === false)
{
throw new CannotOpenLogfile($this->logFile);
}
$prev_data = '';
$buffer = 65536;
while (!feof($handle))
{
$line = fgets($handle);
// Apply the callback on the current line.
try
{
call_user_func($callback, $line);
}
catch (StopScanningEarly $e)
{
/**
* This exception is used to stop scanning the log file, e.g. if the checker has found the information
* it was looking for. We just need to terminate the loop WITHOUT rethrowing the exception.
*/
break;
}
catch (Exception $e)
{
// The check detected an error condition. Close the log file and rethrow the exception.
fclose($handle);
throw $e;
}
}
// All right. We finished processing the log file. Close the handle.
fclose($handle);
}
}

View File

@@ -0,0 +1,84 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Filesystem;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Awf\Text\Text;
/**
* Checks if the user is trying to backup directories with a lot of files
*/
class LargeDirectories extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 30;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_FILESYSTEM_LARGE_DIRECTORIES';
parent::__construct($container, $logFile);
}
public function check()
{
$prev_dir = '';
$large_dir = [];
$this->scanLines(function ($data) use (&$prev_dir, &$large_dir) {
// Let's get all the involved directories
preg_match_all('#Scanning files of <root>/(.*)#', $data, $matches);
if (!isset($matches[1]) || empty($matches[1]))
{
return;
}
$dirs = $matches[1];
if ($prev_dir)
{
array_unshift($dirs, $prev_dir);
}
foreach ($dirs as $dir)
{
preg_match_all('#Adding ' . $dir . '/([^\/]*) to#', $data, $tmp_matches);
if (count($tmp_matches[0]) > 250)
{
$large_dir[] = ['position' => $dir, 'elements' => count($tmp_matches[0])];
}
}
$prev_dir = array_pop($dirs);
});
if (empty($large_dir))
{
return;
}
$errorMsg = [];
// Let's log all the results
foreach ($large_dir as $dir)
{
$errorMsg[] = $dir['position'] . ', ' . $dir['elements'] . ' files';
}
$this->setResult(-1);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_FILESYSTEM_LARGE_DIRECTORIES_ERROR', implode("\n", $errorMsg),
]);
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_FILESYSTEM_LARGE_DIRECTORIES_SOLUTION');
}
}

View File

@@ -0,0 +1,106 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Filesystem;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Awf\Text\Text;
/**
* Checks if the user is trying to backup too big files
*/
class LargeFiles extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 20;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_FILESYSTEM_LARGE_FILES';
parent::__construct($container, $logFile);
}
public function check()
{
$bigfiles = [];
$this->scanLines(function ($data) use (&$bigfiles) {
preg_match_all('#(_before_|\*after\*) large file: (<root>.*?) \- size: (\d+)#i', $data, $tmp_matches);
// Record valid matches only (i.e. with a filesize)
if (!isset($tmp_matches[3]) || empty($tmp_matches[3]))
{
return;
}
for ($i = 0; $i < count($tmp_matches[2]); $i++)
{
// Get flagged files only once; I could have a breaking step after, before or BOTH a large file
$key = md5($tmp_matches[2][$i]);
if (!isset($bigfiles[$key]))
{
$bigfiles[$key] = [
'filename' => $tmp_matches[2][$i],
'size' => round($tmp_matches[3][$i] / 1024 / 1024, 2),
];
}
}
});
if (empty($bigfiles))
{
return;
}
/**
* Depending on the size of the detected files this could be a success, warning or error condition.
*
* Files over 10MB : error
* Files 2 to 10MB : warning
* Files < 2MB : success (user not warned)
*/
foreach ($bigfiles as $file)
{
// More than 10 Mb? Always set the result to error, no matter what
if ($file['size'] >= 10)
{
$this->setResult(-1);
break;
}
// Warning for "smaller" files, set the warn only if we don't already have a failure state
if ($file['size'] > 2)
{
$this->setResult(0);
}
}
// If all files were too small to report just go away.
if ($this->getResult() == 1)
{
return;
}
$errorMsg = [];
foreach ($bigfiles as $bad)
{
$errorMsg[] = 'File: ' . $bad['filename'] . ' ' . $bad['size'] . ' Mb';
}
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_FILESYSTEM_LARGE_FILES_ERROR', implode("\n", $errorMsg),
]);
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_FILESYSTEM_LARGE_FILES_SOLUTION');
}
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Filesystem;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Awf\Text\Text;
/**
* Checks if the user is trying to backup multiple Joomla! installations with a single backup
*/
class MultipleSites extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 10;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_FILESYSTEM_MULTIPLE_SITES';
parent::__construct($container, $logFile);
}
public function check()
{
$subfolders = [];
$this->scanLines(function ($data) use (&$subfolders) {
preg_match_all('#Adding\s(.*?)/administrator/index\.php to archive#i', $data, $matches);
if (!$matches[1])
{
return;
}
$subfolders = array_merge($subfolders, $matches[1]);
});
if (empty($subfolders))
{
return;
}
$this->setResult(0);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_FILESYSTEM_MULTIPLE_SITES_ERROR', implode("\n", $subfolders),
]);
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_FILESYSTEM_MULTIPLE_SITES_SOLUTION');
}
}

View File

@@ -0,0 +1,92 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Filesystem;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Awf\Text\Text;
/**
* Checks if the user is trying to backup old backups
*/
class OldBackups extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 40;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_FILESYSTEM_OLD_BACKUPS';
parent::__construct($container, $logFile);
}
public function check()
{
$bigfiles = [];
$this->scanLines(function ($data) use (&$bigfiles) {
// Only looking files with extensions like .jpa, .jps, .j01, .j02, ..., .j99, .j100, ..., .j99999, .z01, ...
preg_match_all('#-- Adding.*? <root>/(.*?)(\.(?:jpa|jps|j\d{2,5}|z\d{2,5}))#i', $data, $tmp_matches);
if (!isset($tmp_matches[1]) || !$tmp_matches[1])
{
return;
}
// Record valid matches only
for ($i = 0; $i < count($tmp_matches[1]); $i++)
{
// Get flagged files only once
$key = md5($tmp_matches[1][$i] . $tmp_matches[2][$i]);
if (isset($bigfiles[$key]))
{
continue;
}
$filename = $tmp_matches[1][$i] . $tmp_matches[2][$i];
$filePath = $this->container->basePath . '/' . $filename;
$fileSize = 0;
if (@file_exists($filePath) && @is_file($filePath))
{
$fileSize = @filesize($filePath);
}
if ($fileSize > 1048576)
{
$bigfiles[$key] = [
'filename' => $filename,
];
}
}
});
if (empty($bigfiles))
{
return;
}
$errorMsg = [];
$this->setResult(-1);
foreach ($bigfiles as $bad)
{
$errorMsg[] = 'File: ' . $bad['filename'];
}
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_FILESYSTEM_OLD_BACKUPS_ERROR', implode("\n", $errorMsg),
]);
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_FILESYSTEM_OLD_BACKUPS_SOLUTION');
}
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Requirements;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Akeeba\Engine\Factory;
use Exception;
use Awf\Text\Text;
/**
* Checks for database permissions (SHOW permissions)
*/
class DatabasePermissions extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 40;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_DBPERMISSIONS';
parent::__construct($container, $logFile);
}
public function check()
{
$db = Factory::getDatabase();
// Can I execute SHOW statements?
try
{
$result = $db->setQuery('SHOW TABLES')->query();
}
catch (Exception $e)
{
$result = false;
}
if (!$result)
{
$this->setResult(-1);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_DBPERMISSIONS_ERROR',
]);
return;
}
try
{
$result = $db->setQuery('SHOW CREATE TABLE ' . $db->nameQuote('#__ak_profiles'))->query();
}
catch (Exception $e)
{
$result = false;
}
if (!$result)
{
$this->setResult(-1);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_DBPERMISSIONS_ERROR',
]);
return;
}
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_DBPERMISSIONS_SOLUTION');
}
}

View File

@@ -0,0 +1,67 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Requirements;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Awf\Text\Text;
/**
* Checks for supported DB type and version
*/
class DatabaseVersion extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 20;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_DATABASE';
parent::__construct($container, $logFile);
}
public function check()
{
// Instead of reading the log, I can simply take the JDatabase object and test it
$db = $this->container->db;
$connector = strtolower($db->name);
$version = $db->getVersion();
switch ($connector)
{
case 'mysql':
case 'mysqli':
case 'pdomysql':
if (version_compare($version, '5.0.47', 'lt'))
{
$this->setResult(-1);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_DATABASE_VERSION_TOO_OLD', $version,
]);
}
break;
case 'pdo':
case 'sqlite':
$this->setResult(-1);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_DATABASE_UNSUPPORTED', $connector,
]);
break;
default:
$this->setResult(-1);
$this->setErrorLanguageKey(['COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_DATABASE_UNKNOWN', $connector]);
break;
}
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_DATABASE_SOLUTION');
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Requirements;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Solo\Alice\Exception\StopScanningEarly;
use Awf\Text\Text;
/**
* Checks if we have enough memory to perform backup; at least 16Mb
*/
class Memory extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 30;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_MEMORY';
parent::__construct($container, $logFile);
}
public function check()
{
$limit = null;
$usage = false;
$this->scanLines(function ($line) use (&$limit, &$usage) {
if (is_null($limit))
{
$pos = strpos($line, '|Memory limit');
if ($pos !== false)
{
$limit = trim(substr($line, strpos($line, ':', $pos) + 1));
$limit = str_ireplace('M', '', $limit);
// Convert to integer for better handling and checks
$limit = (int) $limit;
}
}
if (!$usage)
{
$pos = strpos($line, '|Current mem. usage');
if ($pos !== false)
{
$usage = trim(substr($line, strpos($line, ':', $pos) + 1));
// Converting to Mb for better handling
$usage = round($usage / 1024 / 1024, 2);
}
}
throw new StopScanningEarly();
});
if (empty($limit) || empty($usage))
{
// Inconclusive check. Cannot get the memory information.
return;
}
$available = $limit - $usage;
if ($limit < 0)
{
// Stupid host uses a negative memory limit. This is the same as setting no memory limit. Bleh.
return;
}
if ($available >= 16)
{
// We have enough memory.
return;
}
$this->setResult(-1);
$this->setErrorLanguageKey(['COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_MEMORY_TOO_FEW', $available]);
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_MEMORY_SOLUTION');
}
}

View File

@@ -0,0 +1,57 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Requirements;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Solo\Alice\Exception\StopScanningEarly;
use Awf\Text\Text;
/**
* Checks if the user is using a too old or too new PHP version
*/
class PHPVersion extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 10;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_PHP_VERSION';
parent::__construct($container, $logFile);
}
public function check()
{
$this->scanLines(function ($line) {
$pos = strpos($line, '|PHP Version');
if ($pos === false)
{
return;
}
$version = trim(substr($line, strpos($line, ':', $pos) + 1));
// PHP too old (well, this should never happen)
if (version_compare($version, '5.6', 'lt'))
{
$this->setResult(-1);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_PHP_VERSION_ERR_TOO_OLD',
]);
}
throw new StopScanningEarly();
});
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_REQUIREMENTS_PHP_VERSION_SOLUTION');
}
}

View File

@@ -0,0 +1,125 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Runtimeerrors;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Solo\Alice\Exception\StopScanningEarly;
use Akeeba\Engine\Factory;
use Awf\Text\Text;
/**
* Check if the user added the site database as additional database. Some servers won't allow more than one connection
* to the same database, causing the backup process to fail
*/
class AddedCoreDatabaseAsExtra extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 100;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_DBADD_JSAME';
parent::__construct($container, $logFile);
}
public function check()
{
$profile = 0;
$this->scanLines(function ($line) use (&$profile) {
$pos = strpos($line, '|Loaded profile');
if ($pos === false)
{
return;
}
preg_match('/profile\s+#(\d+)/', $line, $matches);
if (isset($matches[1]))
{
$profile = (int) $matches[1];
}
throw new StopScanningEarly();
});
// Mhm... no profile ID? Something weird happened better stop here and mark the test as skipped
if ($profile <= 0)
{
return;
}
// Do I have to switch profile?
$container = $this->container;
$cur_profile = $container->segment->get('profile', null);
if ($cur_profile != $profile)
{
$container->segment->set('profile', $profile);
}
$error = false;
$config = $container->appConfig;
$filters = Factory::getFilters();
$multidb = $filters->getFilterData('multidb');
$jdb = [
'driver' => $config->get('dbtype'),
'host' => $config->get('host'),
'username' => $config->get('user'),
'password' => $config->get('password'),
'database' => $config->get('db'),
];
foreach ($multidb as $addDb)
{
$options = [
'driver' => $addDb['driver'],
'host' => $addDb['host'],
'username' => $addDb['username'],
'password' => $addDb['password'],
'database' => $addDb['database'],
];
// It's the same database used by Joomla, this could led to errors
if ($jdb == $options)
{
$error = true;
}
}
// If needed set the old profile again
if ($cur_profile != $profile)
{
$container->segment->set('profile', $cur_profile);
}
if (!$error)
{
return;
}
$this->setResult(-1);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_DBADD_JSAME_ERROR',
]);
}
public function getSolution()
{
// Test skipped? No need to provide a solution
if ($this->getResult() === 0)
{
return '';
}
return Text::_('COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_DBADD_JSAME_SOLUTION');
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Runtimeerrors;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Solo\Alice\Exception\StopScanningEarly;
use Awf\Text\Text;
/**
* Checks if the user is using a too old or too new PHP version
*/
class CorruptInstallation extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 60;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_CORRUPTED_INSTALL';
parent::__construct($container, $logFile);
}
public function check()
{
$error = false;
$foundLoadedProfile = false;
$this->scanLines(function ($line) use (&$foundLoadedProfile) {
// First we need to find the "Loaded profile" line
if (!$foundLoadedProfile)
{
$pos = strpos($line, '|Loaded profile');
if ($pos !== false)
{
// Mark the line as found. We are interested in the line AFTER this one.
$foundLoadedProfile = true;
}
// Since at this point we are not past the "Loaded profile" we need to keep parsing the log file.
return;
}
// Ok, we are just past the "Loaded profile" line. Let's see if it's a broken install.
$logline = trim(substr($line, 24));
// If it's not an empty line then it is definitely not a broken install
if ($logline != '|')
{
throw new StopScanningEarly();
}
// Empty line?? Most likely it's a broken install
$this->setResult(-1);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_CORRUPTED_INSTALL_ERROR',
]);
throw new StopScanningEarly();
});
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_CORRUPTED_INSTALL_SOLUTION');
}
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Runtimeerrors;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Awf\Text\Text;
/**
* Checks if error logs are included inside the backup. Since their size grows while we're trying to backup them,
* this could led to corrupted archives.
*/
class ErrorLogsInArchive extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 80;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_ERRORFILES';
parent::__construct($container, $logFile);
}
public function check()
{
$error_files = [];
$this->scanLines(function ($data) use (&$error_files) {
preg_match_all('#Adding(.*?(/php_error_cpanel\.|php_error_cpanel\.|/error_)log)#', $data, $tmp_matches);
if (isset($tmp_matches[1]))
{
$error_files = array_merge($error_files, $tmp_matches[1]);
}
});
if (empty($error_files))
{
return;
}
$this->setResult(-1);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_ERRORFILES_FOUND', implode("\n", $error_files),
]);
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_ERRORFILES_SOLUTION');
}
}

View File

@@ -0,0 +1,122 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Runtimeerrors;
use Awf\Container\Container;
use Awf\Database\Driver;
use Solo\Alice\Check\Base;
use Solo\Alice\Exception\StopScanningEarly;
use Akeeba\Engine\Factory;
use Exception;
use Awf\Text\Text;
/**
* Check if the user add one or more additional database, but the connection details are wrong
* In such cases Akeeba Backup will receive an error, halting the whole backup process
*/
class ExtraDatabaseCannotConnect extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 90;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_DBADD_WRONG';
parent::__construct($container, $logFile);
}
public function check()
{
$profile = 0;
$this->scanLines(function ($line) use (&$profile) {
$pos = strpos($line, '|Loaded profile');
if ($pos === false)
{
return;
}
preg_match('/profile\s+#(\d+)/', $line, $matches);
if (isset($matches[1]))
{
$profile = (int) $matches[1];
}
throw new StopScanningEarly();
});
// Mhm... no profile ID? Something weird happened better stop here and mark the test as skipped
if ($profile <= 0)
{
return;
}
// Do I have to switch profile?
$container = $this->container;
$cur_profile = $container->segment->get('profile', null);
if ($cur_profile != $profile)
{
$container->segment->set('profile', $profile);
}
$error = false;
$filters = Factory::getFilters();
$multidb = $filters->getFilterData('multidb');
foreach ($multidb as $addDb)
{
$options = [
'driver' => $addDb['driver'],
'host' => $addDb['host'],
'port' => $addDb['port'],
'user' => $addDb['username'],
'password' => $addDb['password'],
'database' => $addDb['database'],
'prefix' => $addDb['prefix'],
];
try
{
$db = Driver::getInstance($options);
$db->connect();
$db->disconnect();
}
catch (Exception $e)
{
$error = true;
}
}
// If needed set the old profile again
if ($cur_profile != $profile)
{
$container->segment->set('profile', $cur_profile);
}
if ($error)
{
$this->setResult(-1);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_DBADD_WRONG_ERROR',
]);
}
}
public function getSolution()
{
// Test skipped? No need to provide a solution
if ($this->getResult() === 0)
{
return '';
}
return Text::_('COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_DBADD_WRONG_SOLUTION');
}
}

View File

@@ -0,0 +1,56 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Runtimeerrors;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Solo\Alice\Exception\StopScanningEarly;
use Awf\Text\Text;
/**
* Checks if a fatal error occurred during the backup process
*/
class FatalError extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 110;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_FATALERROR';
parent::__construct($container, $logFile);
}
public function check()
{
$this->scanLines(function ($data) {
preg_match('#ERROR \|.*?\|(.*)#', $data, $tmp_matches);
if (!isset($tmp_matches[1]))
{
return;
}
$error = $tmp_matches[1];
if (empty($error))
{
return;
}
$this->setResult(-1);
$this->setErrorLanguageKey(['COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_FATALERROR_ERROR', $error]);
throw new StopScanningEarly();
});
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_FATALERROR_SOLUTION');
}
}

View File

@@ -0,0 +1,88 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Runtimeerrors;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Awf\Text\Text;
/**
* Checks that the Kettenrad instance is not dead; the number of "Starting step" and "Saving Kettenrad" instance
* must be the same, plus none of the steps could be repeated (except the first one).
*/
class Kettenrad extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 10;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_KETTENRAD';
parent::__construct($container, $logFile);
}
public function check()
{
$starting = [];
$saving = [];
$this->scanLines(function ($data) use (&$starting, &$saving) {
preg_match_all('#Starting Step number (\d+)#i', $data, $tmp_matches);
if (isset($tmp_matches[1]))
{
$starting = array_merge($starting, $tmp_matches[1]);
}
preg_match_all('#Finished Step number (\d+)#i', $data, $tmp_matches);
if (isset($tmp_matches[1]))
{
$saving = array_merge($saving, $tmp_matches[1]);
}
});
/**
* Check that none of "Starting step" number is repeated, EXCEPT for the first one (it's ok).
* That could happen when some poorly configured server processes the same request twice
*/
foreach ($starting as $stepNumber)
{
if ($stepNumber == 1)
{
continue;
}
/**
* Did a step run more than once?
*
* It is OK if it started multiple times but was only logged as finished once. This means it failed and the
* user took advantage of our retry-on-error feature for backend backups.
*
* However, if we see that it was logged as *finished* multiple times then it means that the same step ran
* multiple times in parallel. This is where the real problem is.
*/
if (count(array_keys($starting, $stepNumber)) > 1)
{
if (count(array_keys($saving, $stepNumber)) > 1)
{
$this->setResult(-1);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_KETTENRAD_STARTING_MORE_ONCE', $stepNumber,
]);
return;
}
}
}
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_KETTENRAD_SOLUTION');
}
}

View File

@@ -0,0 +1,77 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Runtimeerrors;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Solo\Alice\Exception\StopScanningEarly;
use Awf\Text\Text;
/**
* Checks if the user is post processing the archive but didn't set any part size.
* Most likely this could lead to timeouts while uploading
*/
class PartSize extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 70;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_PART_SIZE';
parent::__construct($container, $logFile);
}
public function check()
{
$partsize = 0;
$postproc = '';
$this->scanLines(function ($data) use (&$partsize, &$postproc) {
if (empty($partsize))
{
preg_match('#\|Part size.*:(\d+)#i', $data, $match);
if (isset($match[1]))
{
$partsize = $match[1];
}
}
if (empty($postproc))
{
preg_match('#Loading.*post-processing.*?\((.*?)\)#i', $data, $match);
if (isset($match[1]))
{
$postproc = trim($match[1]);
}
}
// Wait until I have both pieces of data
if (empty($partsize) || empty($postproc))
{
return;
}
if (($partsize > 2000000000) && ($postproc != 'none'))
{
$this->setResult(0);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_PART_SIZE_ERROR',
]);
}
throw new StopScanningEarly();
});
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_PART_SIZE_SOLUTION');
}
}

View File

@@ -0,0 +1,160 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Runtimeerrors;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Exception;
use Awf\Text\Text;
/**
* Checks that every page load is not hitting the timeout limit.
* Time diff is performed against the "Start step" and "Saving Kettenrad" timestamps.
*
* TODO This needs to be rewritten. It makes no sense. A backup CAN NOT POSSIBLY take longer than PHP's time limit!
*/
class Timeout extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 20;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_TIMEOUT';
parent::__construct($container, $logFile);
}
public function check()
{
$starting = [];
$saving = [];
$isCli = false;
$this->scanLines(function ($data) use (&$starting, &$saving, &$isCli) {
if (preg_match('/PHP SAPI\s{1,}:\s*cli/', $data) == 1)
{
// This is CLI backup.
$isCli = true;
}
preg_match_all('#(\d{6}\s\d{2}:\d{2}:\d{2})\|.*?Starting Step number#i', $data, $tmp_matches);
if (isset($tmp_matches[1]))
{
$starting = array_merge($starting, $tmp_matches[1]);
}
preg_match_all('#(\d{6}\s\d{2}:\d{2}:\d{2})\|.*?Finished Step number#i', $data, $tmp_matches);
if (isset($tmp_matches[1]))
{
$saving = array_merge($saving, $tmp_matches[1]);
}
});
// If there is an issue with starting and saving instances, I can't go on, first of all fix that
if (count($saving) != count($starting))
{
$this->setResult(-1);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_TIMEOUT_KETTENRAD_BROKEN',
]);
return;
}
$temp = [];
// Let's expand the date part so I can safely work with that strings
foreach ($starting as $item)
{
$temp[] = '20' . substr($item, 0, 2) . '-' . substr($item, 2, 2) . '-' . substr($item, 4, 2) . substr($item, 6);
}
$starting = $temp;
$temp = [];
// Let's expand the date part so I can safely work with that strings
foreach ($saving as $item)
{
$temp[] = '20' . substr($item, 0, 2) . '-' . substr($item, 2, 2) . '-' . substr($item, 4, 2) . substr($item, 6);
}
$saving = $temp;
$maxExecution = $this->detectMaxExec($isCli);
/**
* If I detected a CLI backup without a max execution time limit (THIS IS THE ONLY WAY, PER PHP'S DOCUMENTATION)
* I immediately quit since we can't possibly time out.
*/
if ($maxExecution == -1)
{
return;
}
// Ok, did I have any timeout between the start and saving step (ie page loads)?
for ($i = 0; $i < count($starting); $i++)
{
$duration = strtotime($saving[$i]) - strtotime($starting[$i]);
if ($duration > $maxExecution)
{
$this->setResult(-1);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_TIMEOUT_MAX_EXECUTION', $duration,
]);
return;
}
}
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_TIMEOUT_SOLUTION');
}
/**
* Detects max execution time, reading backup log. If the maximum execution time is set to 0 or it's bigger
* than 100, it gets the default value of 100.
*
* @return int
* @throws Exception
*/
private function detectMaxExec($isCli = false)
{
$time = 0;
$this->scanLines(function ($line) use (&$time) {
$pos = stripos($line, '|Max. exec. time');
if ($pos === false)
{
return;
}
$time = (int) trim(substr($line, strpos($line, ':', $pos) + 1));
});
/**
* CLI backups.
* Negative, zero or no detected time: we return -1 (no limit).
*/
$time = ($time <= 0) ? -1 : $time;
/**
* Over a web server backups.
* Negative, zero or no detected time: we consider it to be 100 seconds.
* Values over 100 seconds: we cap the to 100 seconds.
*
* The time limit cap has to do with Apache's internal timeout.
*/
$time = ($time <= 0) ? 100 : min($time, 100);
return $time;
}
}

View File

@@ -0,0 +1,80 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Runtimeerrors;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Awf\Text\Text;
/**
* Checks if the user is trying to backup tables with too many rows, causing the system to fail
*/
class TooManyRows extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 50;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_TOOMANYROWS';
parent::__construct($container, $logFile);
}
public function check()
{
$tables = [];
$row_limit = 1000000;
$this->scanLines(function ($data) use (&$tables, &$row_limit) {
// Let's save every scanned table
preg_match_all('#Continuing dump of (.*?) from record \#(\d+)#i', $data, $matches);
if (!isset($matches[1]) || empty($matches[1]))
{
return;
}
for ($i = 0; $i < count($matches[1]); $i++)
{
if ($matches[2][$i] >= $row_limit)
{
$table = trim($matches[1][$i]);
$tables[$table] = $matches[2][$i];
}
}
});
if (!count($tables))
{
return;
}
$errorMsg = [];
foreach ($tables as $table => $rows)
{
$errorMsg[] = sprintf(
"%s %d %s %s",
Text::_('COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_TOOMANYROWS_TABLE'),
$table,
number_format((float) $rows),
Text::_('COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_TOOMANYROWS_ROWS'
));
}
// Let's raise only a warning, maybe the server is powerful enough to dump huge tables and the problem is somewhere else
$this->setResult(0);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_TOOMANYROWS_ERROR', implode("\n", $errorMsg),
]);
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_TOOMANYROWS_SOLUTION');
}
}

View File

@@ -0,0 +1,81 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Runtimeerrors;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Awf\Text\Text;
/**
* Checks if the user is trying to backup too many databases, causing the system to fail
*/
class TooManyTables extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 40;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_TOOMANYDBS';
parent::__construct($container, $logFile);
}
public function check()
{
$tables = [];
$ex_tables = [];
$this->scanLines(function ($data) use (&$tables, &$ex_tables) {
// Let's save every scanned table
preg_match_all('#Native\\[a-zA-Z]* :: Adding.*?\(internal name (.*?)\)#i', $data, $matches);
if (!isset($matches[1]) || empty($matches[1]))
{
return;
}
$tables = array_merge($tables, $matches[1]);
});
if (empty($tables))
{
return;
}
// Let's loop on saved tables and look at their prefixes
foreach ($tables as $table)
{
preg_match('/^(.*?_)/', $table, $matches);
if ($matches[1] !== '#_' && !in_array($matches[1], $ex_tables))
{
$ex_tables[] = $matches[1];
}
}
if (!count($ex_tables))
{
return;
}
$this->setResult(-1);
if (count($ex_tables) > 0 && count($ex_tables) <= 3)
{
$this->setResult(0);
}
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_TOOMANYDBS_ERROR',
]);
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_TOOMANYDBS_SOLUTION');
}
}

View File

@@ -0,0 +1,76 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Check\Runtimeerrors;
use Awf\Container\Container;
use Solo\Alice\Check\Base;
use Solo\Alice\Exception\StopScanningEarly;
use Awf\Text\Text;
/**
* Checks if Akeeba Backup failed to write data inside the archive (WIN hosts only)
*/
class WindowsCannotAppend extends Base
{
public function __construct(Container $container, $logFile = null)
{
$this->priority = 30;
$this->checkLanguageKey = 'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_WINCANTAPPEND';
parent::__construct($container, $logFile);
}
public function check()
{
// Customer is not on windows, this problem happened on Windows only
if (!$this->isWin())
{
return;
}
$this->scanLines(function ($data) {
if (preg_match('#Could not open archive file.*? for append#i', $data))
{
$this->setResult(-1);
$this->setErrorLanguageKey([
'COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_WINCANTAPPEND_ERROR',
]);
throw new StopScanningEarly();
}
});
}
public function getSolution()
{
return Text::_('COM_AKEEBA_ALICE_ANALYZE_RUNTIME_ERRORS_WINCANTAPPEND_SOLUTION');
}
private function isWin()
{
$OS = '';
$this->scanLines(function ($line) use (&$OS) {
$pos = stripos($line, '|OS Version');
if ($pos !== false)
{
$OS = trim(substr($line, strpos($line, ':', $pos) + 1));
throw new StopScanningEarly();
}
});
if (stripos($OS, 'windows') !== false)
{
return true;
}
return false;
}
}

View File

@@ -0,0 +1,25 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Exception;
use Awf\Text\Text;
use RuntimeException;
use Throwable;
/**
* ALICE Exception: cannot open log file
*/
class CannotOpenLogfile extends RuntimeException
{
public function __construct($logFile, Throwable $previous = null)
{
$message = Text::sprintf('COM_AKEEBA_ALICE_ERR_CANNOT_OPEN_LOGFILE', $logFile);
parent::__construct($message, 500, $previous);
}
}

View File

@@ -0,0 +1,18 @@
<?php
/**
* @package solo
* @copyright Copyright (c)2014-2022 Nicholas K. Dionysopoulos / Akeeba Ltd
* @license GNU General Public License version 3, or later
*/
namespace Solo\Alice\Exception;
use RuntimeException;
/**
* This exception tells ALICE to stop reading lines from the log file. It is not rethrown. It's only meant to stop the
* scanning early.
*/
class StopScanningEarly extends RuntimeException
{
}

View File

@@ -0,0 +1,16 @@
<?xml version="1.0"?>
<!--
This only works on IIS 7 or later. See https://www.iis.net/configreference/system.webserver/security/requestfiltering/fileextensions
-->
<configuration>
<system.webServer>
<security>
<requestFiltering>
<fileExtensions allowUnlisted="false" >
<clear />
<add fileExtension=".html" allowed="true"/>
</fileExtensions>
</requestFiltering>
</security>
</system.webServer>
</configuration>